Skip to content

Commit 8842891

Browse files
committed
chore: tweaks
- copy all tests from https://github.com/MananTank/validate-html-nesting - add test for #13318
1 parent c489a3e commit 8842891

File tree

3 files changed

+187
-1
lines changed

3 files changed

+187
-1
lines changed

packages/compiler-dom/__tests__/transforms/validateHtmlNesting.spec.ts

+182
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type CompilerError, compile } from '../../src'
2+
import { isValidHTMLNesting } from '../../src/htmlNesting'
23

34
describe('validate html nesting', () => {
45
it('should warn with p > div', () => {
@@ -17,4 +18,185 @@ describe('validate html nesting', () => {
1718
})
1819
expect(err).toBeUndefined()
1920
})
21+
22+
// #13318
23+
it('should not warn when parent tag is template', () => {
24+
let err: CompilerError | undefined
25+
compile(`<template><tr/></template>`, {
26+
onWarn: e => (err = e),
27+
})
28+
expect(err).toBeUndefined()
29+
})
30+
})
31+
32+
/**
33+
* Copied from https://github.com/MananTank/validate-html-nesting
34+
* with ISC license
35+
*/
36+
describe('isValidHTMLNesting', () => {
37+
test('form', () => {
38+
// invalid
39+
expect(isValidHTMLNesting('form', 'form')).toBe(false)
40+
41+
// valid
42+
expect(isValidHTMLNesting('form', 'div')).toBe(true)
43+
expect(isValidHTMLNesting('form', 'input')).toBe(true)
44+
expect(isValidHTMLNesting('form', 'select')).toBe(true)
45+
expect(isValidHTMLNesting('form', 'button')).toBe(true)
46+
expect(isValidHTMLNesting('form', 'label')).toBe(true)
47+
expect(isValidHTMLNesting('form', 'h1')).toBe(true)
48+
})
49+
50+
test('p', () => {
51+
// invalid
52+
expect(isValidHTMLNesting('p', 'p')).toBe(false)
53+
expect(isValidHTMLNesting('p', 'div')).toBe(false)
54+
expect(isValidHTMLNesting('p', 'hr')).toBe(false)
55+
expect(isValidHTMLNesting('p', 'blockquote')).toBe(false)
56+
expect(isValidHTMLNesting('p', 'pre')).toBe(false)
57+
58+
// valid
59+
expect(isValidHTMLNesting('p', 'a')).toBe(true)
60+
expect(isValidHTMLNesting('p', 'span')).toBe(true)
61+
expect(isValidHTMLNesting('p', 'abbr')).toBe(true)
62+
expect(isValidHTMLNesting('p', 'button')).toBe(true)
63+
expect(isValidHTMLNesting('p', 'b')).toBe(true)
64+
expect(isValidHTMLNesting('p', 'i')).toBe(true)
65+
expect(isValidHTMLNesting('p', 'input')).toBe(true)
66+
expect(isValidHTMLNesting('p', 'label')).toBe(true)
67+
})
68+
69+
test('a', () => {
70+
// invalid
71+
expect(isValidHTMLNesting('a', 'a')).toBe(false)
72+
73+
// valid
74+
expect(isValidHTMLNesting('a', 'div')).toBe(true)
75+
expect(isValidHTMLNesting('a', 'span')).toBe(true)
76+
})
77+
78+
test('button', () => {
79+
// invalid
80+
expect(isValidHTMLNesting('button', 'button')).toBe(false)
81+
82+
// valid
83+
expect(isValidHTMLNesting('button', 'div')).toBe(true)
84+
expect(isValidHTMLNesting('button', 'span')).toBe(true)
85+
})
86+
87+
test('table', () => {
88+
// invalid
89+
expect(isValidHTMLNesting('table', 'tr')).toBe(false)
90+
expect(isValidHTMLNesting('table', 'table')).toBe(false)
91+
expect(isValidHTMLNesting('table', 'td')).toBe(false)
92+
93+
// valid
94+
expect(isValidHTMLNesting('table', 'thead')).toBe(true)
95+
expect(isValidHTMLNesting('table', 'tbody')).toBe(true)
96+
expect(isValidHTMLNesting('table', 'tfoot')).toBe(true)
97+
expect(isValidHTMLNesting('table', 'caption')).toBe(true)
98+
expect(isValidHTMLNesting('table', 'colgroup')).toBe(true)
99+
})
100+
101+
test('td', () => {
102+
// valid
103+
expect(isValidHTMLNesting('td', 'span')).toBe(true)
104+
expect(isValidHTMLNesting('tr', 'td')).toBe(true)
105+
106+
// invalid
107+
expect(isValidHTMLNesting('td', 'td')).toBe(false)
108+
expect(isValidHTMLNesting('div', 'td')).toBe(false)
109+
})
110+
111+
test('tbody', () => {
112+
// invalid
113+
expect(isValidHTMLNesting('tbody', 'td')).toBe(false)
114+
115+
// valid
116+
expect(isValidHTMLNesting('tbody', 'tr')).toBe(true)
117+
})
118+
119+
test('tr', () => {
120+
// invalid
121+
expect(isValidHTMLNesting('tr', 'tr')).toBe(false)
122+
expect(isValidHTMLNesting('table', 'tr')).toBe(false)
123+
124+
// valid
125+
expect(isValidHTMLNesting('tbody', 'tr')).toBe(true)
126+
expect(isValidHTMLNesting('thead', 'tr')).toBe(true)
127+
expect(isValidHTMLNesting('tfoot', 'tr')).toBe(true)
128+
expect(isValidHTMLNesting('tr', 'td')).toBe(true)
129+
expect(isValidHTMLNesting('tr', 'th')).toBe(true)
130+
})
131+
132+
test('li', () => {
133+
// invalid
134+
expect(isValidHTMLNesting('li', 'li')).toBe(false)
135+
// valid
136+
expect(isValidHTMLNesting('li', 'div')).toBe(true)
137+
expect(isValidHTMLNesting('li', 'ul')).toBe(true)
138+
})
139+
140+
test('headings', () => {
141+
// invalid
142+
expect(isValidHTMLNesting('h1', 'h1')).toBe(false)
143+
expect(isValidHTMLNesting('h2', 'h1')).toBe(false)
144+
expect(isValidHTMLNesting('h3', 'h1')).toBe(false)
145+
expect(isValidHTMLNesting('h1', 'h6')).toBe(false)
146+
147+
// valid
148+
expect(isValidHTMLNesting('h1', 'div')).toBe(true)
149+
})
150+
151+
describe('SVG', () => {
152+
test('svg', () => {
153+
// invalid non-svg tags as children
154+
expect(isValidHTMLNesting('svg', 'div')).toBe(false)
155+
expect(isValidHTMLNesting('svg', 'img')).toBe(false)
156+
expect(isValidHTMLNesting('svg', 'p')).toBe(false)
157+
expect(isValidHTMLNesting('svg', 'h2')).toBe(false)
158+
expect(isValidHTMLNesting('svg', 'span')).toBe(false)
159+
160+
// valid non-svg tags as children
161+
expect(isValidHTMLNesting('svg', 'a')).toBe(true)
162+
expect(isValidHTMLNesting('svg', 'textarea')).toBe(true)
163+
expect(isValidHTMLNesting('svg', 'input')).toBe(true)
164+
expect(isValidHTMLNesting('svg', 'select')).toBe(true)
165+
166+
// valid svg tags as children
167+
expect(isValidHTMLNesting('svg', 'g')).toBe(true)
168+
expect(isValidHTMLNesting('svg', 'ellipse')).toBe(true)
169+
expect(isValidHTMLNesting('svg', 'feOffset')).toBe(true)
170+
})
171+
172+
test('foreignObject', () => {
173+
// valid
174+
expect(isValidHTMLNesting('foreignObject', 'g')).toBe(true)
175+
expect(isValidHTMLNesting('foreignObject', 'div')).toBe(true)
176+
expect(isValidHTMLNesting('foreignObject', 'a')).toBe(true)
177+
expect(isValidHTMLNesting('foreignObject', 'textarea')).toBe(true)
178+
})
179+
180+
test('g', () => {
181+
// valid
182+
expect(isValidHTMLNesting('g', 'div')).toBe(true)
183+
expect(isValidHTMLNesting('g', 'p')).toBe(true)
184+
expect(isValidHTMLNesting('g', 'a')).toBe(true)
185+
expect(isValidHTMLNesting('g', 'textarea')).toBe(true)
186+
expect(isValidHTMLNesting('g', 'g')).toBe(true)
187+
})
188+
189+
test('dl', () => {
190+
// valid
191+
expect(isValidHTMLNesting('dl', 'dt')).toBe(true)
192+
expect(isValidHTMLNesting('dl', 'dd')).toBe(true)
193+
expect(isValidHTMLNesting('dl', 'div')).toBe(true)
194+
expect(isValidHTMLNesting('div', 'dt')).toBe(true)
195+
expect(isValidHTMLNesting('div', 'dd')).toBe(true)
196+
197+
// invalid
198+
expect(isValidHTMLNesting('span', 'dt')).toBe(false)
199+
expect(isValidHTMLNesting('span', 'dd')).toBe(false)
200+
})
201+
})
20202
})

packages/compiler-dom/src/htmlNesting.ts

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
* returns true if given parent-child nesting is valid HTML
1212
*/
1313
export function isValidHTMLNesting(parent: string, child: string): boolean {
14+
// if the parent is a template, it can have any child
15+
if (parent === 'template') {
16+
return true
17+
}
18+
1419
// if we know the list of children that are the only valid children for the given parent
1520
if (parent in onlyValidChildren) {
1621
return onlyValidChildren[parent].has(child)

packages/compiler-dom/src/transforms/validateHtmlNesting.ts

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export const validateHtmlNesting: NodeTransform = (node, context) => {
1313
context.parent &&
1414
context.parent.type === NodeTypes.ELEMENT &&
1515
context.parent.tagType === ElementTypes.ELEMENT &&
16-
context.parent.tag !== 'template' &&
1716
!isValidHTMLNesting(context.parent.tag, node.tag)
1817
) {
1918
const error = new SyntaxError(

0 commit comments

Comments
 (0)