1
+ import { browser } from '@wdio/globals' ;
2
+
1
3
import { renderToString } from '../hydrate/index.mjs' ;
4
+ import { setupIFrameTest } from '../util.js' ;
2
5
3
- describe ( 'ssr-shadow-cmp' , ( ) => {
4
- function getNodeNames ( chidNodes : NodeListOf < ChildNode > ) {
5
- return Array . from ( chidNodes )
6
- . flatMap ( ( node ) => {
7
- if ( node . nodeType === 3 ) {
8
- if ( node . textContent ?. trim ( ) ) {
9
- return 'text' ;
6
+ describe ( 'Sanity check SSR > Client hydration' , ( ) => {
7
+ const testSuites = async (
8
+ root : Document ,
9
+ method : 'scoped' | 'declarative-shadow-dom' ,
10
+ renderType : 'dist' | 'custom-elements' ,
11
+ ) => {
12
+ function getNodeNames ( chidNodes : NodeListOf < ChildNode > ) {
13
+ return Array . from ( chidNodes )
14
+ . flatMap ( ( node ) => {
15
+ if ( node . nodeType === 3 ) {
16
+ if ( node . textContent ?. trim ( ) ) {
17
+ return 'text' ;
18
+ } else {
19
+ return [ ] ;
20
+ }
21
+ } else if ( node . nodeType === 8 ) {
22
+ return 'comment' ;
10
23
} else {
11
- return [ ] ;
24
+ return node . nodeName . toLowerCase ( ) ;
12
25
}
13
- } else if ( node . nodeType === 8 ) {
14
- return 'comment' ;
15
- } else {
16
- return node . nodeName . toLowerCase ( ) ;
17
- }
18
- } )
19
- . join ( ' ' ) ;
20
- }
21
-
22
- it ( 'verifies all nodes are preserved during hydration' , async ( ) => {
23
- if ( ! document . querySelector ( '#stage' ) ) {
24
- const { html } = await renderToString (
25
- `
26
- <ssr-shadow-cmp>
27
- A text node
28
- <!-- a comment -->
29
- <div>An element</div>
30
- <!-- another comment -->
31
- Another text node
32
- </ssr-shadow-cmp>
33
- ` ,
34
- {
35
- fullDocument : true ,
36
- serializeShadowRoot : true ,
37
- constrainTimeouts : false ,
38
- } ,
39
- ) ;
40
- const stage = document . createElement ( 'div' ) ;
41
- stage . setAttribute ( 'id' , 'stage' ) ;
42
- stage . setHTMLUnsafe ( html ) ;
43
- document . body . appendChild ( stage ) ;
26
+ } )
27
+ . join ( ' ' ) ;
44
28
}
45
29
46
- // @ts -expect-error resolved through WDIO
47
- const { defineCustomElements } = await import ( '/dist/loader/index.js' ) ;
48
- defineCustomElements ( ) . catch ( console . error ) ;
30
+ return {
31
+ sanityCheck : async ( ) => {
32
+ if ( root . querySelector ( '#stage' ) ) {
33
+ root . querySelector ( '#stage' ) ?. remove ( ) ;
34
+ await browser . waitUntil ( async ( ) => ! root . querySelector ( '#stage' ) ) ;
35
+ }
36
+ const { html } = await renderToString (
37
+ `
38
+ <ssr-shadow-cmp>
39
+ A text node
40
+ <!-- a comment -->
41
+ <div>An element</div>
42
+ <!-- another comment -->
43
+ Another text node
44
+ </ssr-shadow-cmp>
45
+ ` ,
46
+ {
47
+ fullDocument : true ,
48
+ serializeShadowRoot : method ,
49
+ constrainTimeouts : false ,
50
+ prettyHTML : false ,
51
+ } ,
52
+ ) ;
53
+ const stage = root . createElement ( 'div' ) ;
54
+ stage . setAttribute ( 'id' , 'stage' ) ;
55
+ stage . setHTMLUnsafe ( html ) ;
56
+ root . body . appendChild ( stage ) ;
57
+
58
+ if ( renderType === 'dist' ) {
59
+ // @ts -expect-error resolved through WDIO
60
+ const { defineCustomElements } = await import ( '/dist/loader/index.js' ) ;
61
+ defineCustomElements ( ) . catch ( console . error ) ;
49
62
50
- // wait for Stencil to take over and reconcile
51
- await browser . waitUntil ( async ( ) => customElements . get ( 'ssr-shadow-cmp' ) ) ;
52
- expect ( typeof customElements . get ( 'ssr-shadow-cmp' ) ) . toBe ( 'function' ) ;
63
+ // wait for Stencil to take over and reconcile
64
+ await browser . waitUntil ( async ( ) => customElements . get ( 'ssr-shadow-cmp' ) ) ;
65
+ expect ( typeof customElements . get ( 'ssr-shadow-cmp' ) ) . toBe ( 'function' ) ;
66
+ }
53
67
54
- await expect ( getNodeNames ( document . querySelector ( 'ssr-shadow-cmp' ) . childNodes ) ) . toBe (
55
- `text comment div comment text` ,
56
- ) ;
68
+ const ele = root . querySelector ( 'ssr-shadow-cmp' ) ;
69
+ await browser . waitUntil ( async ( ) => ! ! ele . childNodes ) ;
70
+ await browser . pause ( 100 ) ;
71
+
72
+ // Checking slotted content
73
+ await expect ( getNodeNames ( ele . childNodes ) ) . toBe ( `text comment div comment text` ) ;
74
+
75
+ // Checking shadow content
76
+ const eles = method === 'scoped' ? 'div' : 'style div' ;
77
+ await expect ( getNodeNames ( ele . shadowRoot . childNodes ) ) . toBe ( eles ) ;
78
+
79
+ // Checking styling
80
+ await expect ( getComputedStyle ( ele ) . color ) . toBe ( 'rgb(255, 0, 0)' ) ;
81
+ await expect ( getComputedStyle ( ele ) . backgroundColor ) . toBe ( 'rgb(255, 255, 0)' ) ;
82
+ } ,
83
+
84
+ slots : async ( ) => {
85
+ if ( root . querySelector ( '#stage' ) ) {
86
+ root . querySelector ( '#stage' ) ?. remove ( ) ;
87
+ await browser . waitUntil ( async ( ) => ! root . querySelector ( '#stage' ) ) ;
88
+ }
89
+ const { html } = await renderToString (
90
+ `
91
+ <ssr-shadow-cmp>
92
+ <p>Default slot content</p>
93
+ <p slot="client-only">Client-only slot content</p>
94
+ </ssr-shadow-cmp>
95
+ ` ,
96
+ {
97
+ fullDocument : true ,
98
+ serializeShadowRoot : method ,
99
+ constrainTimeouts : false ,
100
+ prettyHTML : false ,
101
+ } ,
102
+ ) ;
103
+ const stage = root . createElement ( 'div' ) ;
104
+ stage . setAttribute ( 'id' , 'stage' ) ;
105
+ stage . setHTMLUnsafe ( html ) ;
106
+ root . body . appendChild ( stage ) ;
107
+
108
+ if ( renderType === 'dist' ) {
109
+ // @ts -expect-error resolved through WDIO
110
+ const { defineCustomElements } = await import ( '/dist/loader/index.js' ) ;
111
+ defineCustomElements ( ) . catch ( console . error ) ;
112
+
113
+ // wait for Stencil to take over and reconcile
114
+ await browser . waitUntil ( async ( ) => customElements . get ( 'ssr-shadow-cmp' ) ) ;
115
+ expect ( typeof customElements . get ( 'ssr-shadow-cmp' ) ) . toBe ( 'function' ) ;
116
+ }
117
+
118
+ await browser . waitUntil ( async ( ) => root . querySelector ( 'ssr-shadow-cmp [slot="client-only"]' ) ) ;
119
+ await expect ( root . querySelector ( 'ssr-shadow-cmp' ) . textContent ) . toBe (
120
+ 'Default slot contentClient-only slot content' ,
121
+ ) ;
122
+ } ,
123
+ } ;
124
+ } ;
125
+
126
+ describe ( 'dist / declarative-shadow-dom' , ( ) => {
127
+ let testSuite ;
128
+ beforeEach ( async ( ) => {
129
+ testSuite = await testSuites ( document , 'declarative-shadow-dom' , 'dist' ) ;
130
+ } ) ;
131
+
132
+ it ( 'verifies all nodes & styles are preserved during hydration' , async ( ) => {
133
+ await testSuite . sanityCheck ( ) ;
134
+ } ) ;
135
+
136
+ it ( 'resolves slots correctly during client-side hydration' , async ( ) => {
137
+ await testSuite . slots ( ) ;
138
+ } ) ;
139
+ } ) ;
140
+
141
+ describe ( 'dist / scoped' , ( ) => {
142
+ let testSuite ;
143
+ beforeEach ( async ( ) => {
144
+ testSuite = await testSuites ( document , 'scoped' , 'dist' ) ;
145
+ } ) ;
146
+
147
+ it ( 'verifies all nodes & styles are preserved during hydration' , async ( ) => {
148
+ await testSuite . sanityCheck ( ) ;
149
+ } ) ;
150
+
151
+ it ( 'resolves slots correctly during client-side hydration' , async ( ) => {
152
+ await testSuite . slots ( ) ;
153
+ } ) ;
154
+ } ) ;
155
+
156
+ describe ( 'custom-elements / declarative-shadow-dom' , ( ) => {
157
+ let doc : Document ;
158
+ let testSuite ;
159
+
160
+ beforeEach ( async ( ) => {
161
+ await setupIFrameTest ( '/ssr-hydration/custom-element.html' , 'dsd-custom-elements' ) ;
162
+ const frameEle : HTMLIFrameElement = document . querySelector ( 'iframe#dsd-custom-elements' ) ;
163
+ doc = frameEle . contentDocument ;
164
+ testSuite = await testSuites ( doc , 'declarative-shadow-dom' , 'custom-elements' ) ;
165
+ } ) ;
166
+
167
+ it ( 'verifies all nodes & styles are preserved during hydration' , async ( ) => {
168
+ await testSuite . sanityCheck ( ) ;
169
+ } ) ;
170
+
171
+ it ( 'resolves slots correctly during client-side hydration' , async ( ) => {
172
+ await testSuite . slots ( ) ;
173
+ } ) ;
174
+ } ) ;
57
175
58
- document . querySelector ( '#stage' ) ?. remove ( ) ;
59
- await browser . waitUntil ( async ( ) => ! document . querySelector ( '#stage' ) ) ;
176
+ describe ( 'custom-elements / scoped' , ( ) => {
177
+ let doc : Document ;
178
+ let testSuite ;
179
+
180
+ beforeEach ( async ( ) => {
181
+ await setupIFrameTest ( '/ssr-hydration/custom-element.html' , 'scoped-custom-elements' ) ;
182
+ const frameEle : HTMLIFrameElement = document . querySelector ( 'iframe#scoped-custom-elements' ) ;
183
+ doc = frameEle . contentDocument ;
184
+ testSuite = await testSuites ( doc , 'scoped' , 'custom-elements' ) ;
185
+ } ) ;
186
+
187
+ it ( 'verifies all nodes & styles are preserved during hydration' , async ( ) => {
188
+ await testSuite . sanityCheck ( ) ;
189
+ } ) ;
190
+
191
+ it ( 'resolves slots correctly during client-side hydration' , async ( ) => {
192
+ await testSuite . slots ( ) ;
193
+ } ) ;
60
194
} ) ;
61
195
62
196
it ( 'checks perf when loading lots of the same component' , async ( ) => {
63
- performance . mark ( 'start' ) ;
197
+ performance . mark ( 'start-dsd ' ) ;
64
198
65
199
await renderToString (
66
200
Array ( 50 )
@@ -69,49 +203,29 @@ describe('ssr-shadow-cmp', () => {
69
203
. join ( '' ) ,
70
204
{
71
205
fullDocument : true ,
72
- serializeShadowRoot : true ,
206
+ serializeShadowRoot : 'declarative-shadow-dom' ,
73
207
constrainTimeouts : false ,
74
208
} ,
75
209
) ;
76
- performance . mark ( 'end' ) ;
77
- const renderTime = performance . measure ( 'render' , 'start' , 'end' ) . duration ;
210
+ performance . mark ( 'end-dsd ' ) ;
211
+ let renderTime = performance . measure ( 'render' , 'start-dsd ' , 'end-dsd ' ) . duration ;
78
212
await expect ( renderTime ) . toBeLessThan ( 50 ) ;
79
- } ) ;
80
-
81
- it ( 'resolves slots correctly during client-side hydration' , async ( ) => {
82
- if ( ! document . querySelector ( '#stage' ) ) {
83
- const { html } = await renderToString (
84
- `
85
- <ssr-shadow-cmp>
86
- <p>Default slot content</p>
87
- <p slot="client-only">Client-only slot content</p>
88
- </ssr-shadow-cmp>
89
- ` ,
90
- {
91
- fullDocument : true ,
92
- serializeShadowRoot : true ,
93
- constrainTimeouts : false ,
94
- } ,
95
- ) ;
96
- const stage = document . createElement ( 'div' ) ;
97
- stage . setAttribute ( 'id' , 'stage' ) ;
98
- stage . setHTMLUnsafe ( html ) ;
99
- document . body . appendChild ( stage ) ;
100
- }
101
213
102
- // @ts -expect-error resolved through WDIO
103
- const { defineCustomElements } = await import ( '/dist/loader/index.js' ) ;
104
- defineCustomElements ( ) . catch ( console . error ) ;
214
+ performance . mark ( 'start-scoped' ) ;
105
215
106
- // wait for Stencil to take over and reconcile
107
- await browser . waitUntil ( async ( ) => customElements . get ( 'ssr-shadow-cmp' ) ) ;
108
- expect ( typeof customElements . get ( 'ssr-shadow-cmp' ) ) . toBe ( 'function' ) ;
109
-
110
- await browser . waitUntil ( async ( ) => document . querySelector ( 'ssr-shadow-cmp [slot="client-only"]' ) ) ;
111
- await expect ( document . querySelector ( 'ssr-shadow-cmp' ) . textContent ) . toBe (
112
- ' Default slot content Client-only slot content ' ,
216
+ await renderToString (
217
+ Array ( 50 )
218
+ . fill ( 0 )
219
+ . map ( ( _ , i ) => `<ssr-shadow-cmp>Value ${ i } </ssr-shadow-cmp>` )
220
+ . join ( '' ) ,
221
+ {
222
+ fullDocument : true ,
223
+ serializeShadowRoot : 'scoped' ,
224
+ constrainTimeouts : false ,
225
+ } ,
113
226
) ;
114
-
115
- document . querySelector ( '#stage' ) ?. remove ( ) ;
227
+ performance . mark ( 'end-scoped' ) ;
228
+ renderTime = performance . measure ( 'render' , 'start-scoped' , 'end-scoped' ) . duration ;
229
+ await expect ( renderTime ) . toBeLessThan ( 50 ) ;
116
230
} ) ;
117
231
} ) ;
0 commit comments