@teleporthq/teleport-uidl-resolver 0.43.53 → 0.43.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/html-nesting/diagnostics.ts +95 -0
- package/__tests__/html-nesting/mocks.ts +66 -0
- package/__tests__/html-nesting/paragraph-repair.ts +251 -0
- package/dist/cjs/resolver.d.ts.map +1 -1
- package/dist/cjs/resolver.js +9 -0
- package/dist/cjs/resolver.js.map +1 -1
- package/dist/cjs/resolvers/html-nesting/diagnostics.d.ts +23 -0
- package/dist/cjs/resolvers/html-nesting/diagnostics.d.ts.map +1 -0
- package/dist/cjs/resolvers/html-nesting/diagnostics.js +77 -0
- package/dist/cjs/resolvers/html-nesting/diagnostics.js.map +1 -0
- package/dist/cjs/resolvers/html-nesting/index.d.ts +33 -0
- package/dist/cjs/resolvers/html-nesting/index.d.ts.map +1 -0
- package/dist/cjs/resolvers/html-nesting/index.js +185 -0
- package/dist/cjs/resolvers/html-nesting/index.js.map +1 -0
- package/dist/cjs/resolvers/html-nesting/paragraph-repair.d.ts +15 -0
- package/dist/cjs/resolvers/html-nesting/paragraph-repair.d.ts.map +1 -0
- package/dist/cjs/resolvers/html-nesting/paragraph-repair.js +129 -0
- package/dist/cjs/resolvers/html-nesting/paragraph-repair.js.map +1 -0
- package/dist/cjs/resolvers/html-nesting/types.d.ts +37 -0
- package/dist/cjs/resolvers/html-nesting/types.d.ts.map +1 -0
- package/dist/cjs/resolvers/html-nesting/types.js +3 -0
- package/dist/cjs/resolvers/html-nesting/types.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/resolver.d.ts.map +1 -1
- package/dist/esm/resolver.js +9 -0
- package/dist/esm/resolver.js.map +1 -1
- package/dist/esm/resolvers/html-nesting/diagnostics.d.ts +23 -0
- package/dist/esm/resolvers/html-nesting/diagnostics.d.ts.map +1 -0
- package/dist/esm/resolvers/html-nesting/diagnostics.js +72 -0
- package/dist/esm/resolvers/html-nesting/diagnostics.js.map +1 -0
- package/dist/esm/resolvers/html-nesting/index.d.ts +33 -0
- package/dist/esm/resolvers/html-nesting/index.d.ts.map +1 -0
- package/dist/esm/resolvers/html-nesting/index.js +181 -0
- package/dist/esm/resolvers/html-nesting/index.js.map +1 -0
- package/dist/esm/resolvers/html-nesting/paragraph-repair.d.ts +15 -0
- package/dist/esm/resolvers/html-nesting/paragraph-repair.d.ts.map +1 -0
- package/dist/esm/resolvers/html-nesting/paragraph-repair.js +123 -0
- package/dist/esm/resolvers/html-nesting/paragraph-repair.js.map +1 -0
- package/dist/esm/resolvers/html-nesting/types.d.ts +37 -0
- package/dist/esm/resolvers/html-nesting/types.d.ts.map +1 -0
- package/dist/esm/resolvers/html-nesting/types.js +2 -0
- package/dist/esm/resolvers/html-nesting/types.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/resolver.ts +10 -0
- package/src/resolvers/html-nesting/diagnostics.ts +88 -0
- package/src/resolvers/html-nesting/index.ts +253 -0
- package/src/resolvers/html-nesting/paragraph-repair.ts +152 -0
- package/src/resolvers/html-nesting/types.ts +39 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { GeneratorOptions } from '@teleporthq/teleport-types'
|
|
2
|
+
import { resolveHtmlNesting } from '../../src/resolvers/html-nesting'
|
|
3
|
+
import { component, element, text } from './mocks'
|
|
4
|
+
|
|
5
|
+
const NO_OPTIONS: GeneratorOptions = {}
|
|
6
|
+
|
|
7
|
+
let warnSpy: jest.SpyInstance
|
|
8
|
+
|
|
9
|
+
const warnings = (): string[] => warnSpy.mock.calls.map((call) => String(call[0]))
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
warnSpy.mockRestore()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('resolveHtmlNesting — nesting that has no lossless repair', () => {
|
|
20
|
+
it('reports a nested anchor and leaves both in place', () => {
|
|
21
|
+
const inner = element('a', [text('Details')])
|
|
22
|
+
const outer = element('a', [element('div', [inner])])
|
|
23
|
+
|
|
24
|
+
resolveHtmlNesting(component(outer, 'Product-Card'), NO_OPTIONS)
|
|
25
|
+
|
|
26
|
+
expect(inner.content.elementType).toBe('a')
|
|
27
|
+
expect(warnings()).toEqual([expect.stringContaining('<a> nested inside another <a>')])
|
|
28
|
+
expect(warnings()[0]).toContain('Product-Card')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('forgets an anchor once a scope boundary hides it from the parser', () => {
|
|
32
|
+
const inner = element('a', [text('Details')])
|
|
33
|
+
const outer = element('a', [element('table', [element('td', [inner])])])
|
|
34
|
+
|
|
35
|
+
resolveHtmlNesting(component(outer), NO_OPTIONS)
|
|
36
|
+
|
|
37
|
+
expect(warnings().filter((message) => message.includes('nested inside another <a>'))).toEqual(
|
|
38
|
+
[]
|
|
39
|
+
)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('reports a nested form', () => {
|
|
43
|
+
const outer = element('form', [element('div', [element('form', [])])])
|
|
44
|
+
|
|
45
|
+
resolveHtmlNesting(component(outer), NO_OPTIONS)
|
|
46
|
+
|
|
47
|
+
expect(warnings()).toEqual([expect.stringContaining('<form> nested inside another <form>')])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('reports content the parser would hoist out of a table', () => {
|
|
51
|
+
const table = element('table', [element('div', [])])
|
|
52
|
+
|
|
53
|
+
resolveHtmlNesting(component(table), NO_OPTIONS)
|
|
54
|
+
|
|
55
|
+
expect(warnings()).toEqual([expect.stringContaining('<div> is not valid content for <table>')])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('sees through a component wrapper when checking table content', () => {
|
|
59
|
+
const table = element('table', [element('Repeater', [element('tr', [])])])
|
|
60
|
+
|
|
61
|
+
resolveHtmlNesting(component(table), NO_OPTIONS)
|
|
62
|
+
|
|
63
|
+
expect(warnings()).toEqual([expect.stringContaining('<tr> is not valid content for <table>')])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('accepts a well-formed table', () => {
|
|
67
|
+
const table = element('table', [
|
|
68
|
+
element('tbody', [element('tr', [element('td', [text('7')])])]),
|
|
69
|
+
])
|
|
70
|
+
|
|
71
|
+
resolveHtmlNesting(component(table), NO_OPTIONS)
|
|
72
|
+
|
|
73
|
+
expect(warnings()).toEqual([])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('reports a heading inside a heading', () => {
|
|
77
|
+
const heading = element('h2', [element('h3', [text('Sub')])])
|
|
78
|
+
|
|
79
|
+
resolveHtmlNesting(component(heading), NO_OPTIONS)
|
|
80
|
+
|
|
81
|
+
expect(warnings()).toEqual([expect.stringContaining('<h3> nested inside <h2>')])
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('says nothing about markup that parses as written', () => {
|
|
85
|
+
const section = element('section', [
|
|
86
|
+
element('h2', [text('Title')]),
|
|
87
|
+
element('p', [element('a', [element('span', [text('link')])])]),
|
|
88
|
+
element('button', [element('span', [text('Buy')])]),
|
|
89
|
+
])
|
|
90
|
+
|
|
91
|
+
resolveHtmlNesting(component(section), NO_OPTIONS)
|
|
92
|
+
|
|
93
|
+
expect(warnings()).toEqual([])
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComponentUIDL,
|
|
3
|
+
GeneratorOptions,
|
|
4
|
+
UIDLElementNode,
|
|
5
|
+
UIDLNode,
|
|
6
|
+
UIDLStyleSetDefinition,
|
|
7
|
+
UIDLStyleSheetContent,
|
|
8
|
+
} from '@teleporthq/teleport-types'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The nesting resolver runs on an ALREADY MAPPED tree, so these mocks are
|
|
12
|
+
* written with the final tag in `elementType` — exactly the shape
|
|
13
|
+
* `utils.resolveNode` leaves behind.
|
|
14
|
+
*/
|
|
15
|
+
export const element = (
|
|
16
|
+
elementType: string,
|
|
17
|
+
children: UIDLNode[] = [],
|
|
18
|
+
content: Partial<UIDLElementNode['content']> = {}
|
|
19
|
+
): UIDLElementNode => ({
|
|
20
|
+
type: 'element',
|
|
21
|
+
content: {
|
|
22
|
+
elementType,
|
|
23
|
+
children,
|
|
24
|
+
...content,
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const text = (content: string): UIDLNode => ({ type: 'static', content })
|
|
29
|
+
|
|
30
|
+
export const component = (node: UIDLElementNode, name = 'Homepage'): ComponentUIDL =>
|
|
31
|
+
({ name, node } as ComponentUIDL)
|
|
32
|
+
|
|
33
|
+
/** A node-level `referencedStyles` entry pointing at a project style set. */
|
|
34
|
+
export const referencingProjectStyle = (
|
|
35
|
+
referenceId: string
|
|
36
|
+
): Partial<UIDLElementNode['content']> => ({
|
|
37
|
+
referencedStyles: {
|
|
38
|
+
[referenceId]: {
|
|
39
|
+
type: 'style-map',
|
|
40
|
+
content: { mapType: 'project-referenced', referenceId },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export const optionsWithProjectStyles = (
|
|
46
|
+
styleSetDefinitions: Record<string, UIDLStyleSetDefinition>
|
|
47
|
+
): GeneratorOptions => ({
|
|
48
|
+
projectStyleSet: { styleSetDefinitions, fileName: 'style', path: '.' },
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export const staticStyleSet = (declarations: Record<string, string>): UIDLStyleSetDefinition => {
|
|
52
|
+
const content: Record<string, UIDLStyleSheetContent> = {}
|
|
53
|
+
Object.keys(declarations).forEach((property) => {
|
|
54
|
+
content[property] = { type: 'static', content: declarations[property] }
|
|
55
|
+
})
|
|
56
|
+
return { type: 'reusable-project-style-map', content }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The `elementType` of every element in the tree, in depth-first order. */
|
|
60
|
+
export const tagsOf = (node: UIDLNode, tags: string[] = []): string[] => {
|
|
61
|
+
if (node.type === 'element') {
|
|
62
|
+
tags.push(node.content.elementType)
|
|
63
|
+
;(node.content.children || []).forEach((child) => tagsOf(child, tags))
|
|
64
|
+
}
|
|
65
|
+
return tags
|
|
66
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { GeneratorOptions, UIDLElementNode } from '@teleporthq/teleport-types'
|
|
2
|
+
import { resolveHtmlNesting } from '../../src/resolvers/html-nesting'
|
|
3
|
+
import {
|
|
4
|
+
component,
|
|
5
|
+
element,
|
|
6
|
+
optionsWithProjectStyles,
|
|
7
|
+
referencingProjectStyle,
|
|
8
|
+
staticStyleSet,
|
|
9
|
+
tagsOf,
|
|
10
|
+
text,
|
|
11
|
+
} from './mocks'
|
|
12
|
+
|
|
13
|
+
const NO_OPTIONS: GeneratorOptions = {}
|
|
14
|
+
|
|
15
|
+
// `console.warn` is the resolver's diagnostic channel; silence it so the suite
|
|
16
|
+
// output stays readable while still exercising the code path.
|
|
17
|
+
let warnSpy: jest.SpyInstance
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
warnSpy.mockRestore()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('resolveHtmlNesting — paragraph content', () => {
|
|
28
|
+
it('retags a wrapper <div> reached through a framework component', () => {
|
|
29
|
+
// The shape that shipped: <p>… <Link><a><div><span>Privacy Policy</span>.
|
|
30
|
+
const wrapper = element('div', [element('span', [text('Privacy Policy')])])
|
|
31
|
+
const paragraph = element('p', [
|
|
32
|
+
element('span', [text('We promise only joy, no junk. See our ')]),
|
|
33
|
+
element('Link', [element('a', [wrapper])]),
|
|
34
|
+
element('span', [text('.')]),
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
38
|
+
|
|
39
|
+
expect(paragraph.content.elementType).toBe('p')
|
|
40
|
+
expect(wrapper.content.elementType).toBe('span')
|
|
41
|
+
expect(tagsOf(paragraph)).toEqual(['p', 'span', 'Link', 'a', 'span', 'span', 'span'])
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('restates the block display the retagged wrapper loses with its tag', () => {
|
|
45
|
+
const wrapper = element('div', [element('span', [text('Read more')])])
|
|
46
|
+
const paragraph = element('p', [wrapper])
|
|
47
|
+
|
|
48
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
49
|
+
|
|
50
|
+
expect(wrapper.content.style).toEqual({ display: { type: 'static', content: 'block' } })
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('leaves the display alone when the node already declares one', () => {
|
|
54
|
+
const wrapper = element('div', [], {
|
|
55
|
+
style: { display: { type: 'static', content: 'flex' } },
|
|
56
|
+
})
|
|
57
|
+
const paragraph = element('p', [wrapper])
|
|
58
|
+
|
|
59
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
60
|
+
|
|
61
|
+
expect(wrapper.content.elementType).toBe('span')
|
|
62
|
+
expect(wrapper.content.style.display).toEqual({ type: 'static', content: 'flex' })
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('leaves the display alone when a project class declares one', () => {
|
|
66
|
+
const wrapper = element('div', [], referencingProjectStyle('card-row'))
|
|
67
|
+
const paragraph = element('p', [wrapper])
|
|
68
|
+
|
|
69
|
+
resolveHtmlNesting(
|
|
70
|
+
component(paragraph),
|
|
71
|
+
optionsWithProjectStyles({ 'card-row': staticStyleSet({ display: 'flex' }) })
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
expect(wrapper.content.elementType).toBe('span')
|
|
75
|
+
expect(wrapper.content.style).toBeUndefined()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('adds the display when the project class it references declares none', () => {
|
|
79
|
+
const wrapper = element('div', [], referencingProjectStyle('card-row'))
|
|
80
|
+
const paragraph = element('p', [wrapper])
|
|
81
|
+
|
|
82
|
+
resolveHtmlNesting(
|
|
83
|
+
component(paragraph),
|
|
84
|
+
optionsWithProjectStyles({ 'card-row': staticStyleSet({ color: 'red' }) })
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
expect(wrapper.content.style).toEqual({ display: { type: 'static', content: 'block' } })
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('does not guess a display when the referenced style set cannot be read', () => {
|
|
91
|
+
const wrapper = element('div', [], referencingProjectStyle('card-row'))
|
|
92
|
+
const paragraph = element('p', [wrapper])
|
|
93
|
+
|
|
94
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
95
|
+
|
|
96
|
+
expect(wrapper.content.elementType).toBe('span')
|
|
97
|
+
expect(wrapper.content.style).toBeUndefined()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('retags every offending wrapper, however deep', () => {
|
|
101
|
+
const outer = element('div', [])
|
|
102
|
+
const inner = element('div', [])
|
|
103
|
+
outer.content.children = [element('span', [inner])]
|
|
104
|
+
const paragraph = element('p', [outer])
|
|
105
|
+
|
|
106
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
107
|
+
|
|
108
|
+
expect(outer.content.elementType).toBe('span')
|
|
109
|
+
expect(inner.content.elementType).toBe('span')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('demotes the paragraph when an offender carries meaning of its own', () => {
|
|
113
|
+
const list = element('ul', [element('li', [text('One')])])
|
|
114
|
+
const paragraph = element('p', [element('span', [text('Sizes:')]), list], {
|
|
115
|
+
semanticType: 'p',
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
119
|
+
|
|
120
|
+
expect(paragraph.content.elementType).toBe('div')
|
|
121
|
+
expect(paragraph.content.semanticType).toBe('div')
|
|
122
|
+
expect(list.content.elementType).toBe('ul')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('restores the paragraph block margins the demotion drops', () => {
|
|
126
|
+
const paragraph = element('p', [element('h3', [text('Heading')])])
|
|
127
|
+
|
|
128
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
129
|
+
|
|
130
|
+
expect(paragraph.content.style).toEqual({
|
|
131
|
+
marginTop: { type: 'static', content: '1em' },
|
|
132
|
+
marginBottom: { type: 'static', content: '1em' },
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('keeps the authored spacing instead of restoring the browser default', () => {
|
|
137
|
+
const paragraph = element('p', [element('h3', [text('Heading')])], {
|
|
138
|
+
...referencingProjectStyle('newsletter-privacy'),
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
resolveHtmlNesting(
|
|
142
|
+
component(paragraph),
|
|
143
|
+
optionsWithProjectStyles({ 'newsletter-privacy': staticStyleSet({ margin: '0' }) })
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
expect(paragraph.content.elementType).toBe('div')
|
|
147
|
+
expect(paragraph.content.style).toBeUndefined()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('demotes rather than retags when the paragraph holds both kinds of offender', () => {
|
|
151
|
+
const wrapper = element('div', [])
|
|
152
|
+
const paragraph = element('p', [wrapper, element('table', [])])
|
|
153
|
+
|
|
154
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
155
|
+
|
|
156
|
+
expect(paragraph.content.elementType).toBe('div')
|
|
157
|
+
expect(wrapper.content.elementType).toBe('div')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('repairs a nested paragraph on its own terms', () => {
|
|
161
|
+
const wrapper = element('div', [])
|
|
162
|
+
const inner = element('p', [wrapper])
|
|
163
|
+
const outer = element('p', [inner])
|
|
164
|
+
|
|
165
|
+
resolveHtmlNesting(component(outer), NO_OPTIONS)
|
|
166
|
+
|
|
167
|
+
expect(outer.content.elementType).toBe('div')
|
|
168
|
+
expect(inner.content.elementType).toBe('p')
|
|
169
|
+
expect(wrapper.content.elementType).toBe('span')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('leaves a paragraph whose content is already valid untouched', () => {
|
|
173
|
+
const paragraph = element('p', [
|
|
174
|
+
element('span', [text('See our ')]),
|
|
175
|
+
element('a', [element('span', [text('terms')])]),
|
|
176
|
+
])
|
|
177
|
+
|
|
178
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
179
|
+
|
|
180
|
+
expect(tagsOf(paragraph)).toEqual(['p', 'span', 'a', 'span'])
|
|
181
|
+
expect(warnSpy).not.toHaveBeenCalled()
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('ignores a wrapper that a <button> puts out of paragraph scope', () => {
|
|
185
|
+
// `<p><button><div/></button></p>` parses as written — the parser only
|
|
186
|
+
// closes a paragraph that is in BUTTON scope.
|
|
187
|
+
const wrapper = element('div', [])
|
|
188
|
+
const paragraph = element('p', [element('button', [wrapper])])
|
|
189
|
+
|
|
190
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
191
|
+
|
|
192
|
+
expect(paragraph.content.elementType).toBe('p')
|
|
193
|
+
expect(wrapper.content.elementType).toBe('div')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('does not descend into <svg>, where the HTML content model does not apply', () => {
|
|
197
|
+
const svgChild = element('title', [text('Arrow')])
|
|
198
|
+
const paragraph = element('p', [element('svg', [svgChild])])
|
|
199
|
+
|
|
200
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
201
|
+
|
|
202
|
+
expect(paragraph.content.elementType).toBe('p')
|
|
203
|
+
expect(svgChild.content.elementType).toBe('title')
|
|
204
|
+
expect(warnSpy).not.toHaveBeenCalled()
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('repairs render branches of a CMS repeater against the enclosing paragraph', () => {
|
|
208
|
+
const listWrapper = element('div', [])
|
|
209
|
+
const emptyWrapper = element('div', [])
|
|
210
|
+
const paragraph: UIDLElementNode = element('p', [
|
|
211
|
+
{
|
|
212
|
+
type: 'cms-list-repeater',
|
|
213
|
+
content: {
|
|
214
|
+
nodes: { list: listWrapper, empty: emptyWrapper },
|
|
215
|
+
},
|
|
216
|
+
} as never,
|
|
217
|
+
])
|
|
218
|
+
|
|
219
|
+
resolveHtmlNesting(component(paragraph), NO_OPTIONS)
|
|
220
|
+
|
|
221
|
+
expect(listWrapper.content.elementType).toBe('span')
|
|
222
|
+
expect(emptyWrapper.content.elementType).toBe('span')
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('inspects an element passed as an attribute on its own', () => {
|
|
226
|
+
const wrapper = element('div', [])
|
|
227
|
+
const slotParagraph = element('p', [wrapper])
|
|
228
|
+
const host = element('p', [], { attrs: { renderItem: slotParagraph } })
|
|
229
|
+
|
|
230
|
+
resolveHtmlNesting(component(host), NO_OPTIONS)
|
|
231
|
+
|
|
232
|
+
// The attribute tree is repaired against ITS OWN paragraph, and the host
|
|
233
|
+
// paragraph is not blamed for content it never renders inline.
|
|
234
|
+
expect(wrapper.content.elementType).toBe('span')
|
|
235
|
+
expect(slotParagraph.content.elementType).toBe('p')
|
|
236
|
+
expect(host.content.elementType).toBe('p')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('walks element trees held in prop definitions', () => {
|
|
240
|
+
const wrapper = element('div', [])
|
|
241
|
+
const paragraph = element('p', [wrapper])
|
|
242
|
+
const uidl = component(element('div', []))
|
|
243
|
+
uidl.propDefinitions = {
|
|
244
|
+
header: { type: 'element', defaultValue: paragraph },
|
|
245
|
+
} as never
|
|
246
|
+
|
|
247
|
+
resolveHtmlNesting(uidl, NO_OPTIONS)
|
|
248
|
+
|
|
249
|
+
expect(wrapper.content.elementType).toBe('span')
|
|
250
|
+
})
|
|
251
|
+
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,EACP,gBAAgB,EAEjB,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,EACP,gBAAgB,EAEjB,MAAM,4BAA4B,CAAA;AAQnC;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC3B,OAAO,CAAC,OAAO,CAMd;gBAEW,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE;IAQlC,UAAU,CAAC,OAAO,EAAE,OAAO;IAI3B,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,gBAA6C;IAkDxF,cAAc,CACnB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,gBAA6C;CAYzD"}
|
package/dist/cjs/resolver.js
CHANGED
|
@@ -41,6 +41,7 @@ var style_set_definitions_1 = require("./resolvers/style-set-definitions");
|
|
|
41
41
|
var referenced_styles_1 = require("./resolvers/referenced-styles");
|
|
42
42
|
var embed_node_1 = require("./resolvers/embed-node");
|
|
43
43
|
var unbound_expressions_1 = require("./resolvers/unbound-expressions");
|
|
44
|
+
var html_nesting_1 = require("./resolvers/html-nesting");
|
|
44
45
|
/**
|
|
45
46
|
* The resolver takes the input UIDL and converts all the abstract node types into
|
|
46
47
|
* concrete node types, based on the mappings you provide
|
|
@@ -81,6 +82,14 @@ var Resolver = /** @class */ (function () {
|
|
|
81
82
|
(0, embed_node_1.resolveHtmlNode)(uidl, newOptions);
|
|
82
83
|
utils.resolveNode(uidl.node, newOptions);
|
|
83
84
|
utils.resolveNodeInPropDefinitions(uidl, newOptions);
|
|
85
|
+
// Element types are concrete HTML tags from here on, which is the earliest
|
|
86
|
+
// point at which the markup can be checked against the HTML content model.
|
|
87
|
+
// Nesting the parser refuses to keep (a `<div>` inside a `<p>`, a `<form>`
|
|
88
|
+
// inside a `<form>`) makes the served HTML parse into a different tree than
|
|
89
|
+
// the one that was rendered, and React then fails to hydrate the page.
|
|
90
|
+
if (!newOptions.skipHtmlNestingResolver) {
|
|
91
|
+
(0, html_nesting_1.resolveHtmlNesting)(uidl, newOptions);
|
|
92
|
+
}
|
|
84
93
|
// Guard against `expr` nodes that reference an out-of-scope identifier
|
|
85
94
|
// (e.g. an orphaned `<option>{{ cat.name }}</option>` whose repeater was
|
|
86
95
|
// never generated). Left untouched they compile to `ReferenceError`s that
|
package/dist/cjs/resolver.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAAgC;AAChC,+DAAuD;AAQvD,mDAAwD;AACxD,2EAA8E;AAC9E,mEAAsE;AACtE,qDAAwD;AACxD,uEAA2E;
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { UIDLElementNode } from '@teleporthq/teleport-types';
|
|
2
|
+
import { NestingContext, NestingResolverContext } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* The DOM-restructuring nestings that have no lossless repair.
|
|
5
|
+
*
|
|
6
|
+
* Every one of them either removes an element the page needs (`<form>` inside
|
|
7
|
+
* `<form>` — the inner one is never created) or moves it somewhere else
|
|
8
|
+
* (`<div>` inside `<tbody>` is foster-parented out of the table). Renaming a
|
|
9
|
+
* tag to make the markup parse would silently drop the behaviour the element
|
|
10
|
+
* was added for, so these are reported rather than rewritten: the fix belongs
|
|
11
|
+
* in whatever produced the tree.
|
|
12
|
+
*
|
|
13
|
+
* Paragraph content is deliberately absent — it IS repairable losslessly, and
|
|
14
|
+
* `paragraph-repair` handles it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const reportUnrepairableNesting: (node: UIDLElementNode, tag: string, nesting: NestingContext, context: NestingResolverContext) => void;
|
|
17
|
+
/**
|
|
18
|
+
* `ancestorInfo` bookkeeping for the tags whose duplicates the parser rejects.
|
|
19
|
+
* `<a>`, `<button>` and `<nobr>` stop counting past a scope boundary (a table
|
|
20
|
+
* cell, an `<object>`, …); the form pointer has no such boundary.
|
|
21
|
+
*/
|
|
22
|
+
export declare const nextOpenSelfNestingTags: (tag: string, current: Record<string, boolean>) => Record<string, boolean>;
|
|
23
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../../src/resolvers/html-nesting/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAE5D,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAmBhE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,SAC9B,eAAe,OAChB,MAAM,WACF,cAAc,WACd,sBAAsB,KAC9B,IA0BF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAC7B,MAAM,WACF,OAAO,MAAM,EAAE,OAAO,CAAC,KAC/B,OAAO,MAAM,EAAE,OAAO,CAYxB,CAAA"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.nextOpenSelfNestingTags = exports.reportUnrepairableNesting = void 0;
|
|
15
|
+
var teleport_shared_1 = require("@teleporthq/teleport-shared");
|
|
16
|
+
var SELF_NESTING_TAGS = teleport_shared_1.HtmlContentModel.SELF_NESTING_TAGS, acceptsChildTag = teleport_shared_1.HtmlContentModel.acceptsChildTag, isHeadingTag = teleport_shared_1.HtmlContentModel.isHeadingTag, endsInlineScope = teleport_shared_1.HtmlContentModel.endsInlineScope;
|
|
17
|
+
var HYDRATION_DOC = 'nextjs.org/docs/messages/react-hydration-error';
|
|
18
|
+
var warn = function (context, message) {
|
|
19
|
+
console.warn("[html-nesting] ".concat(context.componentName, ": ").concat(message, " (see ").concat(HYDRATION_DOC, ")"));
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* `<div>` alone rarely tells anyone which node to open. The UIDL `name` is the
|
|
23
|
+
* element's identity in the editor, so it is quoted whenever it adds anything.
|
|
24
|
+
*/
|
|
25
|
+
var describeElement = function (node, tag) {
|
|
26
|
+
var name = node.content.name;
|
|
27
|
+
return name && name !== tag ? "<".concat(tag, "> \"").concat(name, "\"") : "<".concat(tag, ">");
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The DOM-restructuring nestings that have no lossless repair.
|
|
31
|
+
*
|
|
32
|
+
* Every one of them either removes an element the page needs (`<form>` inside
|
|
33
|
+
* `<form>` — the inner one is never created) or moves it somewhere else
|
|
34
|
+
* (`<div>` inside `<tbody>` is foster-parented out of the table). Renaming a
|
|
35
|
+
* tag to make the markup parse would silently drop the behaviour the element
|
|
36
|
+
* was added for, so these are reported rather than rewritten: the fix belongs
|
|
37
|
+
* in whatever produced the tree.
|
|
38
|
+
*
|
|
39
|
+
* Paragraph content is deliberately absent — it IS repairable losslessly, and
|
|
40
|
+
* `paragraph-repair` handles it.
|
|
41
|
+
*/
|
|
42
|
+
var reportUnrepairableNesting = function (node, tag, nesting, context) {
|
|
43
|
+
var element = describeElement(node, tag);
|
|
44
|
+
if (SELF_NESTING_TAGS.has(tag) && nesting.openSelfNestingTags[tag]) {
|
|
45
|
+
warn(context, "".concat(element, " nested inside another <").concat(tag, ">. The parser refuses to nest them and ") +
|
|
46
|
+
"closes or drops one of the two");
|
|
47
|
+
}
|
|
48
|
+
var parentTag = nesting.parentTag;
|
|
49
|
+
if (!parentTag) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!acceptsChildTag(parentTag, tag)) {
|
|
53
|
+
warn(context, "".concat(element, " is not valid content for <").concat(parentTag, ">. The parser moves or drops it"));
|
|
54
|
+
}
|
|
55
|
+
if (isHeadingTag(tag) && isHeadingTag(parentTag)) {
|
|
56
|
+
warn(context, "".concat(element, " nested inside <").concat(parentTag, ">. The parser closes the outer heading"));
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
exports.reportUnrepairableNesting = reportUnrepairableNesting;
|
|
60
|
+
/**
|
|
61
|
+
* `ancestorInfo` bookkeeping for the tags whose duplicates the parser rejects.
|
|
62
|
+
* `<a>`, `<button>` and `<nobr>` stop counting past a scope boundary (a table
|
|
63
|
+
* cell, an `<object>`, …); the form pointer has no such boundary.
|
|
64
|
+
*/
|
|
65
|
+
var nextOpenSelfNestingTags = function (tag, current) {
|
|
66
|
+
var _a;
|
|
67
|
+
var next = current;
|
|
68
|
+
if (endsInlineScope(tag)) {
|
|
69
|
+
next = __assign(__assign({}, next), { a: false, button: false, nobr: false });
|
|
70
|
+
}
|
|
71
|
+
if (SELF_NESTING_TAGS.has(tag)) {
|
|
72
|
+
next = __assign(__assign({}, next), (_a = {}, _a[tag] = true, _a));
|
|
73
|
+
}
|
|
74
|
+
return next;
|
|
75
|
+
};
|
|
76
|
+
exports.nextOpenSelfNestingTags = nextOpenSelfNestingTags;
|
|
77
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../../../src/resolvers/html-nesting/diagnostics.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AACA,+DAA8D;AAGtD,IAAA,iBAAiB,GAAqD,kCAAgB,kBAArE,EAAE,eAAe,GAAoC,kCAAgB,gBAApD,EAAE,YAAY,GAAsB,kCAAgB,aAAtC,EAAE,eAAe,GAAK,kCAAgB,gBAArB,CAAqB;AAE9F,IAAM,aAAa,GAAG,gDAAgD,CAAA;AAEtE,IAAM,IAAI,GAAG,UAAC,OAA+B,EAAE,OAAe;IAC5D,OAAO,CAAC,IAAI,CAAC,yBAAkB,OAAO,CAAC,aAAa,eAAK,OAAO,mBAAS,aAAa,MAAG,CAAC,CAAA;AAC5F,CAAC,CAAA;AAED;;;GAGG;AACH,IAAM,eAAe,GAAG,UAAC,IAAqB,EAAE,GAAW;IACjD,IAAA,IAAI,GAAK,IAAI,CAAC,OAAO,KAAjB,CAAiB;IAC7B,OAAO,IAAI,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,WAAI,GAAG,iBAAM,IAAI,OAAG,CAAC,CAAC,CAAC,WAAI,GAAG,MAAG,CAAA;AACjE,CAAC,CAAA;AAED;;;;;;;;;;;;GAYG;AACI,IAAM,yBAAyB,GAAG,UACvC,IAAqB,EACrB,GAAW,EACX,OAAuB,EACvB,OAA+B;IAE/B,IAAM,OAAO,GAAG,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IAE1C,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE;QAClE,IAAI,CACF,OAAO,EACP,UAAG,OAAO,qCAA2B,GAAG,4CAAyC;YAC/E,gCAAgC,CACnC,CAAA;KACF;IAEO,IAAA,SAAS,GAAK,OAAO,UAAZ,CAAY;IAC7B,IAAI,CAAC,SAAS,EAAE;QACd,OAAM;KACP;IAED,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE;QACpC,IAAI,CACF,OAAO,EACP,UAAG,OAAO,wCAA8B,SAAS,oCAAiC,CACnF,CAAA;KACF;IAED,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,SAAS,CAAC,EAAE;QAChD,IAAI,CAAC,OAAO,EAAE,UAAG,OAAO,6BAAmB,SAAS,2CAAwC,CAAC,CAAA;KAC9F;AACH,CAAC,CAAA;AA/BY,QAAA,yBAAyB,6BA+BrC;AAED;;;;GAIG;AACI,IAAM,uBAAuB,GAAG,UACrC,GAAW,EACX,OAAgC;;IAEhC,IAAI,IAAI,GAAG,OAAO,CAAA;IAElB,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE;QACxB,IAAI,yBAAQ,IAAI,KAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,GAAE,CAAA;KACzD;IAED,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAC9B,IAAI,yBAAQ,IAAI,gBAAG,GAAG,IAAG,IAAI,MAAE,CAAA;KAChC;IAED,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAfY,QAAA,uBAAuB,2BAenC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ComponentUIDL, GeneratorOptions } from '@teleporthq/teleport-types';
|
|
2
|
+
/**
|
|
3
|
+
* Makes the generated markup parse into the tree it was rendered as.
|
|
4
|
+
*
|
|
5
|
+
* Server-rendered HTML is re-parsed by the browser before React hydrates it,
|
|
6
|
+
* and a handful of tag combinations make the parser rewrite the tree — closing
|
|
7
|
+
* a `<p>` early, hoisting a `<div>` out of a `<table>`, dropping a nested
|
|
8
|
+
* `<form>`. Whatever React rendered on the server no longer matches the DOM it
|
|
9
|
+
* finds on the client, hydration fails, and the page is silently re-rendered
|
|
10
|
+
* from scratch: styles flash, server-computed content is thrown away, and the
|
|
11
|
+
* console fills with "Hydration failed because the initial UI does not match
|
|
12
|
+
* what was rendered on the server".
|
|
13
|
+
*
|
|
14
|
+
* The pass runs after the element mapping, so every `elementType` is already
|
|
15
|
+
* the tag that will be emitted (`text`+`p` has become `p`, a navlink has become
|
|
16
|
+
* `Link` wrapping an `a`). It walks the resolved tree with the same
|
|
17
|
+
* `ancestorInfo` bookkeeping react-dom's `validateDOMNesting` uses, so it flags
|
|
18
|
+
* exactly what React would have flagged at runtime — only early enough to do
|
|
19
|
+
* something about it.
|
|
20
|
+
*
|
|
21
|
+
* Paragraph content is repaired in place, because a rename is enough and costs
|
|
22
|
+
* nothing (see `paragraph-repair`). The rest is reported: renaming a `<form>`
|
|
23
|
+
* or a `<td>` to make it parse would throw away the behaviour it was added for.
|
|
24
|
+
*
|
|
25
|
+
* Element types that are not HTML tags — `Link`, `Repeater`, `Fragment`,
|
|
26
|
+
* `TqMotion`, custom elements — have no content model of their own. They are
|
|
27
|
+
* walked THROUGH rather than treated as a boundary: `<p><Link><a><div>` is the
|
|
28
|
+
* shape that shipped, and only a walk that ignores the component in the middle
|
|
29
|
+
* can see it. Component INSTANCES are a real boundary and are not walked at
|
|
30
|
+
* all; each one is resolved as its own UIDL.
|
|
31
|
+
*/
|
|
32
|
+
export declare const resolveHtmlNesting: (uidl: ComponentUIDL, options: GeneratorOptions) => void;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/resolvers/html-nesting/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,gBAAgB,EAGjB,MAAM,4BAA4B,CAAA;AAkBnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,kBAAkB,SAAU,aAAa,WAAW,gBAAgB,KAAG,IAiBnF,CAAA"}
|