ripple 0.2.166 → 0.2.168
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/package.json +2 -2
- package/src/compiler/phases/1-parse/index.js +30 -1
- package/src/compiler/phases/1-parse/style.js +36 -1
- package/src/compiler/phases/2-analyze/css-analyze.js +145 -0
- package/src/compiler/phases/2-analyze/index.js +7 -0
- package/src/compiler/phases/2-analyze/prune.js +165 -11
- package/src/compiler/phases/2-analyze/validation.js +156 -0
- package/src/compiler/phases/3-transform/stylesheet.js +102 -3
- package/src/runtime/internal/client/blocks.js +7 -5
- package/src/runtime/internal/client/index.js +2 -1
- package/src/runtime/internal/client/runtime.js +9 -5
- package/tests/client/css/global-additional-cases.test.ripple +702 -0
- package/tests/client/css/global-advanced-selectors.test.ripple +229 -0
- package/tests/client/css/global-at-rules.test.ripple +126 -0
- package/tests/client/css/global-basic.test.ripple +165 -0
- package/tests/client/css/global-classes-ids.test.ripple +179 -0
- package/tests/client/css/global-combinators.test.ripple +124 -0
- package/tests/client/css/global-complex-nesting.test.ripple +221 -0
- package/tests/client/css/global-edge-cases.test.ripple +200 -0
- package/tests/client/css/global-keyframes.test.ripple +101 -0
- package/tests/client/css/global-nested.test.ripple +150 -0
- package/tests/client/css/global-pseudo.test.ripple +155 -0
- package/tests/client/css/global-scoping.test.ripple +229 -0
- package/tests/client/dynamic-elements.test.ripple +0 -1
- package/tests/server/streaming-ssr.test.ripple +9 -6
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import { compile } from 'ripple/compiler';
|
|
2
|
+
|
|
3
|
+
describe('CSS :global additional use cases', () => {
|
|
4
|
+
it('handles :global as modifier with dot notation', () => {
|
|
5
|
+
const source = `
|
|
6
|
+
export component Test() {
|
|
7
|
+
<div class="x">{'content'}</div>
|
|
8
|
+
|
|
9
|
+
<style>
|
|
10
|
+
div :global.x {
|
|
11
|
+
color: red;
|
|
12
|
+
}
|
|
13
|
+
</style>
|
|
14
|
+
}`;
|
|
15
|
+
const { css } = compile(source, 'test.ripple');
|
|
16
|
+
|
|
17
|
+
expect(css).toMatch(/div\.ripple-[a-z0-9]+\.x {/);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('handles :global modifier in nested syntax', () => {
|
|
21
|
+
const source = `
|
|
22
|
+
export component Test() {
|
|
23
|
+
<div class="x">{'content'}</div>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
div {
|
|
27
|
+
:global.x {
|
|
28
|
+
color: green;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
</style>
|
|
32
|
+
}`;
|
|
33
|
+
const { css } = compile(source, 'test.ripple');
|
|
34
|
+
|
|
35
|
+
expect(css).toContain('&.x {');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('handles :global with & nesting selector inside block', () => {
|
|
39
|
+
const source = `
|
|
40
|
+
export component Test() {
|
|
41
|
+
<div class="x">{'content'}</div>
|
|
42
|
+
|
|
43
|
+
<style>
|
|
44
|
+
div {
|
|
45
|
+
& :global.x {
|
|
46
|
+
color: green;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
</style>
|
|
50
|
+
}`;
|
|
51
|
+
const { css } = compile(source, 'test.ripple');
|
|
52
|
+
|
|
53
|
+
expect(css).toContain('&.x {');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('handles :global with :is pseudo-class modifier', () => {
|
|
57
|
+
const source = `
|
|
58
|
+
export component Test() {
|
|
59
|
+
<div>{'content'}</div>
|
|
60
|
+
|
|
61
|
+
<style>
|
|
62
|
+
div :global:is(html.dark-mode *) {
|
|
63
|
+
color: green;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
66
|
+
}`;
|
|
67
|
+
const { css } = compile(source, 'test.ripple');
|
|
68
|
+
|
|
69
|
+
expect(css).toMatch(/div\.ripple-[a-z0-9]+:is\(html\.dark-mode \*\) {/);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('handles :global with & inside :global block', () => {
|
|
73
|
+
const source = `
|
|
74
|
+
export component Test() {
|
|
75
|
+
<div class="class">{'content'}</div>
|
|
76
|
+
|
|
77
|
+
<style>
|
|
78
|
+
div {
|
|
79
|
+
&:global(.class) {
|
|
80
|
+
color: green;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
84
|
+
}`;
|
|
85
|
+
const { css } = compile(source, 'test.ripple');
|
|
86
|
+
|
|
87
|
+
expect(css).toMatch(/div\.ripple-[a-z0-9]+ {/);
|
|
88
|
+
expect(css).toContain('&.class {');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('handles :global(*) with & and descendant', () => {
|
|
92
|
+
const source = `
|
|
93
|
+
export component Test() {
|
|
94
|
+
<div class="class">{'content'}</div>
|
|
95
|
+
|
|
96
|
+
<style>
|
|
97
|
+
:global(*) {
|
|
98
|
+
&:hover .class {
|
|
99
|
+
color: green;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
</style>
|
|
103
|
+
}`;
|
|
104
|
+
const { css } = compile(source, 'test.ripple');
|
|
105
|
+
|
|
106
|
+
expect(css).toContain('* {');
|
|
107
|
+
expect(css).toMatch(/&:hover \.class\.ripple-[a-z0-9]+ {/);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('handles multiple :global selectors in list', () => {
|
|
111
|
+
const source = `
|
|
112
|
+
export component Test() {
|
|
113
|
+
<x>{'content'}</x>
|
|
114
|
+
<y>{'content'}</y>
|
|
115
|
+
|
|
116
|
+
<style>
|
|
117
|
+
:global x, :global y {
|
|
118
|
+
color: green;
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
121
|
+
}`;
|
|
122
|
+
const { css } = compile(source, 'test.ripple');
|
|
123
|
+
|
|
124
|
+
expect(css).toContain(' x, y {');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('handles :global block with nested selector list', () => {
|
|
128
|
+
const source = `
|
|
129
|
+
export component Test() {
|
|
130
|
+
<div>
|
|
131
|
+
<y>{'content'}</y>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<style>
|
|
135
|
+
div :global, div :global y, unused :global {
|
|
136
|
+
z {
|
|
137
|
+
color: green;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
141
|
+
}`;
|
|
142
|
+
const { css } = compile(source, 'test.ripple');
|
|
143
|
+
|
|
144
|
+
expect(css).toMatch(/div\.ripple-[a-z0-9]+/);
|
|
145
|
+
expect(css).toContain('z {');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('handles keyframes inside :global block', () => {
|
|
149
|
+
const source = `
|
|
150
|
+
export component Test() {
|
|
151
|
+
<div class="x">{'animated'}</div>
|
|
152
|
+
|
|
153
|
+
<style>
|
|
154
|
+
:global {
|
|
155
|
+
.x {
|
|
156
|
+
animation: test 1s;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.y {
|
|
160
|
+
animation: test-in 1s;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@keyframes test-in {
|
|
164
|
+
to {
|
|
165
|
+
opacity: 1;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@keyframes test {
|
|
171
|
+
to {
|
|
172
|
+
opacity: 1;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
</style>
|
|
176
|
+
}`;
|
|
177
|
+
const { css } = compile(source, 'test.ripple');
|
|
178
|
+
|
|
179
|
+
expect(css).toContain('@keyframes test-in {');
|
|
180
|
+
expect(css).toContain('.x {');
|
|
181
|
+
expect(css).toContain('.y {');
|
|
182
|
+
expect(css).toMatch(/@keyframes ripple-[a-z0-9]+-test/);
|
|
183
|
+
expect(css).not.toMatch(/\.x\.ripple-[a-z0-9]+/);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('handles global keyframes with no component elements', () => {
|
|
187
|
+
const source = `
|
|
188
|
+
export component Test() {
|
|
189
|
+
<div>{'content'}</div>
|
|
190
|
+
|
|
191
|
+
<style>
|
|
192
|
+
@keyframes -global-orphan {
|
|
193
|
+
0% { color: red; }
|
|
194
|
+
100% { color: blue; }
|
|
195
|
+
}
|
|
196
|
+
</style>
|
|
197
|
+
}`;
|
|
198
|
+
const { css } = compile(source, 'test.ripple');
|
|
199
|
+
|
|
200
|
+
expect(css).toContain('@keyframes orphan');
|
|
201
|
+
expect(css).not.toContain('-global-orphan');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('handles :global with class modifier syntax', () => {
|
|
205
|
+
const source = `
|
|
206
|
+
export component Test() {
|
|
207
|
+
<div class="blue">{'might be programmatically added'}</div>
|
|
208
|
+
<span class="x blue">{'span content'}</span>
|
|
209
|
+
|
|
210
|
+
<style>
|
|
211
|
+
div:global(.blue) {
|
|
212
|
+
color: blue;
|
|
213
|
+
}
|
|
214
|
+
span:global(.blue).x {
|
|
215
|
+
color: blue;
|
|
216
|
+
}
|
|
217
|
+
span.x:global(.bg) {
|
|
218
|
+
background: red;
|
|
219
|
+
}
|
|
220
|
+
</style>
|
|
221
|
+
}`;
|
|
222
|
+
const { css } = compile(source, 'test.ripple');
|
|
223
|
+
|
|
224
|
+
expect(css).toMatch(/div\.ripple-[a-z0-9]+\.blue {/);
|
|
225
|
+
expect(css).toMatch(/span\.blue\.x\.ripple-[a-z0-9]+ {/);
|
|
226
|
+
expect(css).toMatch(/span\.x\.ripple-[a-z0-9]+\.bg {/);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('handles multiple :global() in descendant sequence', () => {
|
|
230
|
+
const source = `
|
|
231
|
+
export component Test() {
|
|
232
|
+
<p>{'this may or may not be styled'}</p>
|
|
233
|
+
|
|
234
|
+
<style>
|
|
235
|
+
:global(div) > :global(section) > p {
|
|
236
|
+
color: red;
|
|
237
|
+
}
|
|
238
|
+
</style>
|
|
239
|
+
}`;
|
|
240
|
+
const { css } = compile(source, 'test.ripple');
|
|
241
|
+
|
|
242
|
+
expect(css).toContain('div > section > p');
|
|
243
|
+
expect(css).toMatch(/p\.ripple-[a-z0-9]+ {/);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('handles :is with :global and html context', () => {
|
|
247
|
+
const source = `
|
|
248
|
+
export component Test() {
|
|
249
|
+
<x>
|
|
250
|
+
<y>
|
|
251
|
+
<z>{'content'}</z>
|
|
252
|
+
</y>
|
|
253
|
+
</x>
|
|
254
|
+
|
|
255
|
+
<style>
|
|
256
|
+
x :is(:global(html *)) {
|
|
257
|
+
color: green;
|
|
258
|
+
}
|
|
259
|
+
</style>
|
|
260
|
+
}`;
|
|
261
|
+
const { css } = compile(source, 'test.ripple');
|
|
262
|
+
|
|
263
|
+
expect(css).toMatch(/x\.ripple-[a-z0-9]+ :is\(html \*\) {/);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('handles :global block with :has inside', () => {
|
|
267
|
+
const source = `
|
|
268
|
+
export component Test() {
|
|
269
|
+
<div>
|
|
270
|
+
<x>{'content'}</x>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<style>
|
|
274
|
+
:global(.foo) {
|
|
275
|
+
:has(x) {
|
|
276
|
+
color: green;
|
|
277
|
+
}
|
|
278
|
+
&:has(x) {
|
|
279
|
+
color: green;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
:global(.foo):has(x) {
|
|
284
|
+
color: green;
|
|
285
|
+
}
|
|
286
|
+
</style>
|
|
287
|
+
}`;
|
|
288
|
+
const { css } = compile(source, 'test.ripple');
|
|
289
|
+
|
|
290
|
+
expect(css).toContain('.foo {');
|
|
291
|
+
expect(css).toMatch(/\.ripple-[a-z0-9]+:has\(x:where\(\.ripple-[a-z0-9]+\)\) {/);
|
|
292
|
+
expect(css).toMatch(/&:has\(x\.ripple-[a-z0-9]+\) {/);
|
|
293
|
+
expect(css).toMatch(/.foo:has\(x\.ripple-[a-z0-9]+\) {/);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('handles :not with :global in complex nesting', () => {
|
|
297
|
+
const source = `
|
|
298
|
+
export component Test() {
|
|
299
|
+
<p class="foo">{'foo'}</p>
|
|
300
|
+
<p class="bar">
|
|
301
|
+
{'bar'}
|
|
302
|
+
<span>{'baz'}</span>
|
|
303
|
+
</p>
|
|
304
|
+
<span>{'buzz'}</span>
|
|
305
|
+
|
|
306
|
+
<style>
|
|
307
|
+
:not(:global(.foo)) {
|
|
308
|
+
color: green;
|
|
309
|
+
}
|
|
310
|
+
:not(.foo):not(:global(.unused)) {
|
|
311
|
+
color: green;
|
|
312
|
+
}
|
|
313
|
+
:global(.x):not(.foo) {
|
|
314
|
+
color: green;
|
|
315
|
+
}
|
|
316
|
+
:global(.x) :not(p) {
|
|
317
|
+
color: green;
|
|
318
|
+
}
|
|
319
|
+
:global(.x):not(p) {
|
|
320
|
+
color: green;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
:global(span):not(p span) {
|
|
324
|
+
color: green;
|
|
325
|
+
}
|
|
326
|
+
span:not(:global(p span)) {
|
|
327
|
+
color: green;
|
|
328
|
+
}
|
|
329
|
+
:global(span:not(p span)) {
|
|
330
|
+
color: green;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
:global(.x) {
|
|
334
|
+
:not(.foo) {
|
|
335
|
+
color: green;
|
|
336
|
+
}
|
|
337
|
+
&:not(.foo) {
|
|
338
|
+
color: green;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
</style>
|
|
342
|
+
}`;
|
|
343
|
+
const { css } = compile(source, 'test.ripple');
|
|
344
|
+
|
|
345
|
+
expect(css).toContain(':not(.foo)');
|
|
346
|
+
expect(css).toContain('span');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('handles sibling combinators with children and :global before scoped elements', () => {
|
|
350
|
+
const source = `
|
|
351
|
+
export component Test({ children }) {
|
|
352
|
+
<div>
|
|
353
|
+
<p class="before">{'before'}</p>
|
|
354
|
+
|
|
355
|
+
<children />
|
|
356
|
+
|
|
357
|
+
<p class="foo">
|
|
358
|
+
<span>{'foo'}</span>
|
|
359
|
+
</p>
|
|
360
|
+
<p class="bar">{'bar'}</p>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<style>
|
|
364
|
+
.before + .foo { color: green; }
|
|
365
|
+
.before ~ .foo { color: green; }
|
|
366
|
+
.before ~ .bar { color: green; }
|
|
367
|
+
|
|
368
|
+
:global(.x) + .foo { color: green; }
|
|
369
|
+
:global(.x) + .foo span { color: green; }
|
|
370
|
+
:global(.x) ~ .foo { color: green; }
|
|
371
|
+
:global(.x) ~ .foo span { color: green; }
|
|
372
|
+
:global(.x) ~ .bar { color: green; }
|
|
373
|
+
|
|
374
|
+
/* should be unused as this is not a possibility */
|
|
375
|
+
:global(.x) + .bar { color: green; }
|
|
376
|
+
</style>
|
|
377
|
+
}`;
|
|
378
|
+
const { css } = compile(source, 'test.ripple');
|
|
379
|
+
|
|
380
|
+
expect(css).toMatch(/\.before\.ripple-[a-z0-9]+ \+ \.foo:where\(\.ripple-[a-z0-9]+\) {/);
|
|
381
|
+
expect((css.match(/\.x\ /g) || []).length).toBe(5);
|
|
382
|
+
expect((css.match(/\(unused\) :global\(\.x\) /g) || []).length).toBe(1);
|
|
383
|
+
expect(css).toContain('(unused) :global(.x) + .bar {');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('handles sibling combinators with component and :global before scoped elements', () => {
|
|
387
|
+
const source = `
|
|
388
|
+
export component Test({ children }) {
|
|
389
|
+
<div>
|
|
390
|
+
<p class="before">{'before'}</p>
|
|
391
|
+
|
|
392
|
+
<Child1 />
|
|
393
|
+
|
|
394
|
+
<p class="foo">
|
|
395
|
+
<span>{'foo'}</span>
|
|
396
|
+
</p>
|
|
397
|
+
<p class="bar">{'bar'}</p>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<style>
|
|
401
|
+
.before + .foo { color: green; }
|
|
402
|
+
.before ~ .foo { color: green; }
|
|
403
|
+
.before ~ .bar { color: green; }
|
|
404
|
+
|
|
405
|
+
:global(.x) + .foo { color: green; }
|
|
406
|
+
:global(.x) + .foo span { color: green; }
|
|
407
|
+
:global(.x) ~ .foo { color: green; }
|
|
408
|
+
:global(.x) ~ .foo span { color: green; }
|
|
409
|
+
:global(.x) ~ .bar { color: green; }
|
|
410
|
+
|
|
411
|
+
/* should be unused as this is not a possibility */
|
|
412
|
+
:global(.x) + .bar { color: green; }
|
|
413
|
+
</style>
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
component Child1() {
|
|
417
|
+
<div>{'child1'}</div>
|
|
418
|
+
}`;
|
|
419
|
+
const { css } = compile(source, 'test.ripple');
|
|
420
|
+
|
|
421
|
+
expect(css).toMatch(/\.before\.ripple-[a-z0-9]+ \+ \.foo:where\(\.ripple-[a-z0-9]+\) {/);
|
|
422
|
+
expect((css.match(/\.x\ /g) || []).length).toBe(5);
|
|
423
|
+
expect((css.match(/\(unused\) :global\(\.x\) /g) || []).length).toBe(1);
|
|
424
|
+
expect(css).toContain('(unused) :global(.x) + .bar {');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it(
|
|
428
|
+
'handles sibling combinators with dynamic component and :global before scoped elements',
|
|
429
|
+
() => {
|
|
430
|
+
const source = `
|
|
431
|
+
import { track } from 'ripple';
|
|
432
|
+
export component Test({ children }) {
|
|
433
|
+
const DynamicComponent = track(() => Child1);
|
|
434
|
+
<div>
|
|
435
|
+
<p class="before">{'before'}</p>
|
|
436
|
+
|
|
437
|
+
<@DynamicComponent />
|
|
438
|
+
|
|
439
|
+
<p class="foo">
|
|
440
|
+
<span>{'foo'}</span>
|
|
441
|
+
</p>
|
|
442
|
+
<p class="bar">{'bar'}</p>
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
<style>
|
|
446
|
+
.before + .foo { color: green; }
|
|
447
|
+
.before ~ .foo { color: green; }
|
|
448
|
+
.before ~ .bar { color: green; }
|
|
449
|
+
|
|
450
|
+
:global(.x) + .foo { color: green; }
|
|
451
|
+
:global(.x) + .foo span { color: green; }
|
|
452
|
+
:global(.x) ~ .foo { color: green; }
|
|
453
|
+
:global(.x) ~ .foo span { color: green; }
|
|
454
|
+
:global(.x) ~ .bar { color: green; }
|
|
455
|
+
|
|
456
|
+
/* should be unused as this is not a possibility */
|
|
457
|
+
:global(.x) + .bar { color: green; }
|
|
458
|
+
</style>
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
component Child1() {
|
|
462
|
+
<div>{'child1'}</div>
|
|
463
|
+
}`;
|
|
464
|
+
const { css } = compile(source, 'test.ripple');
|
|
465
|
+
|
|
466
|
+
expect(css).toMatch(/\.before\.ripple-[a-z0-9]+ \+ \.foo:where\(\.ripple-[a-z0-9]+\) {/);
|
|
467
|
+
expect((css.match(/\.x\ /g) || []).length).toBe(5);
|
|
468
|
+
expect((css.match(/\(unused\) :global\(\.x\) /g) || []).length).toBe(1);
|
|
469
|
+
expect(css).toContain('(unused) :global(.x) + .bar {');
|
|
470
|
+
},
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
it(
|
|
474
|
+
'handles sibling combinators with dynamic element or regular element and :global before scoped elements',
|
|
475
|
+
() => {
|
|
476
|
+
const source = `
|
|
477
|
+
import { track } from 'ripple';
|
|
478
|
+
export component Test({ children, classes }) {
|
|
479
|
+
const dynamicElement = track('div');
|
|
480
|
+
<div>
|
|
481
|
+
<p class="before">{'before'}</p>
|
|
482
|
+
// Use Dynamic Element but it's the same with a regular one
|
|
483
|
+
<@dynamicElement class={classes} />
|
|
484
|
+
|
|
485
|
+
<p class="foo">
|
|
486
|
+
<span>{'foo'}</span>
|
|
487
|
+
</p>
|
|
488
|
+
<p class="bar">{'bar'}</p>
|
|
489
|
+
</div>
|
|
490
|
+
|
|
491
|
+
<style>
|
|
492
|
+
.before + .foo { color: green; }
|
|
493
|
+
.before ~ .foo { color: green; }
|
|
494
|
+
.before ~ .bar { color: green; }
|
|
495
|
+
|
|
496
|
+
:global(.x) + .foo { color: green; }
|
|
497
|
+
:global(.x) + .foo span { color: green; }
|
|
498
|
+
:global(.x) ~ .foo { color: green; }
|
|
499
|
+
:global(.x) ~ .foo span { color: green; }
|
|
500
|
+
:global(.x) ~ .bar { color: green; }
|
|
501
|
+
|
|
502
|
+
/* should be unused as this is not a possibility */
|
|
503
|
+
:global(.x) + .bar { color: green; }
|
|
504
|
+
</style>
|
|
505
|
+
}`;
|
|
506
|
+
|
|
507
|
+
const { css } = compile(source, 'test.ripple');
|
|
508
|
+
|
|
509
|
+
expect(css).toMatch(/\.before\.ripple-[a-z0-9]+ \+ \.foo:where\(\.ripple-[a-z0-9]+\) {/);
|
|
510
|
+
expect((css.match(/\.x\ /g) || []).length).toBe(5);
|
|
511
|
+
expect((css.match(/\(unused\) :global\(\.x\) /g) || []).length).toBe(1);
|
|
512
|
+
expect(css).toContain('(unused) :global(.x) + .bar {');
|
|
513
|
+
},
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
it('handles :global with multiple global descendants', () => {
|
|
517
|
+
const source = `
|
|
518
|
+
export component Test() {
|
|
519
|
+
<div class="root">
|
|
520
|
+
<section class="whatever">
|
|
521
|
+
<p>{'hello'}</p>
|
|
522
|
+
</section>
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
<style>
|
|
526
|
+
:global(html) :global(body) .root p {
|
|
527
|
+
color: red;
|
|
528
|
+
}
|
|
529
|
+
</style>
|
|
530
|
+
}`;
|
|
531
|
+
const { css } = compile(source, 'test.ripple');
|
|
532
|
+
|
|
533
|
+
expect(css).toMatch(/html body \.root\.ripple-[a-z0-9]+ p:where\(\.ripple-[a-z0-9]+\) {/);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('handles nested @media with :global blocks', () => {
|
|
537
|
+
const source = `
|
|
538
|
+
export component Test() {
|
|
539
|
+
<div>{'content'}</div>
|
|
540
|
+
|
|
541
|
+
<style>
|
|
542
|
+
div {
|
|
543
|
+
color: black;
|
|
544
|
+
|
|
545
|
+
@media (min-width: 768px) {
|
|
546
|
+
:global {
|
|
547
|
+
.foo {
|
|
548
|
+
color: red;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
color: blue;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
</style>
|
|
556
|
+
}`;
|
|
557
|
+
const { css } = compile(source, 'test.ripple');
|
|
558
|
+
|
|
559
|
+
expect(css).toContain('@media (min-width: 768px) {');
|
|
560
|
+
expect(css).toContain('.foo {');
|
|
561
|
+
expect(css).not.toMatch(/\.foo\.ripple-[a-z0-9]+/);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('handles :has with complex combinators', () => {
|
|
565
|
+
const source = `
|
|
566
|
+
export component Test() {
|
|
567
|
+
<g>
|
|
568
|
+
<h>
|
|
569
|
+
<i>{'content'}</i>
|
|
570
|
+
</h>
|
|
571
|
+
<j>
|
|
572
|
+
<k>{'content'}</k>
|
|
573
|
+
</j>
|
|
574
|
+
</g>
|
|
575
|
+
|
|
576
|
+
<style>
|
|
577
|
+
g:has(> h > i) {
|
|
578
|
+
color: green;
|
|
579
|
+
}
|
|
580
|
+
h:has(> h > i) {
|
|
581
|
+
color: red;
|
|
582
|
+
}
|
|
583
|
+
g:has(+ j > k) {
|
|
584
|
+
color: green;
|
|
585
|
+
}
|
|
586
|
+
</style>
|
|
587
|
+
}`;
|
|
588
|
+
const { css } = compile(source, 'test.ripple');
|
|
589
|
+
|
|
590
|
+
expect(css).toMatch(
|
|
591
|
+
/g\.ripple-[a-z0-9]+:has\(> h:where\(\.ripple-[a-z0-9]+\) > i:where\(\.ripple-[a-z0-9]+\)\)/,
|
|
592
|
+
);
|
|
593
|
+
expect(css).toContain('(unused) h:has(> h > i)');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('handles :global with attribute selectors containing special characters', () => {
|
|
597
|
+
const source = `
|
|
598
|
+
export component Test() {
|
|
599
|
+
<div>
|
|
600
|
+
<h1 data-title="Hello, world!">{'hello world'}</h1>
|
|
601
|
+
</div>
|
|
602
|
+
|
|
603
|
+
<style>
|
|
604
|
+
div :global(h1[data-title="Hello, world!"]) {
|
|
605
|
+
color: red;
|
|
606
|
+
}
|
|
607
|
+
div :global(h1[attribute], video[autoplay]) {
|
|
608
|
+
color: red;
|
|
609
|
+
}
|
|
610
|
+
</style>
|
|
611
|
+
}`;
|
|
612
|
+
const { css } = compile(source, 'test.ripple');
|
|
613
|
+
|
|
614
|
+
expect(css).toMatch(/div\.ripple-[a-z0-9]+ h1\[data-title="Hello, world!"\]/);
|
|
615
|
+
expect(css).toContain('h1[attribute], video[autoplay]');
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('handles escaped commas in :global class names', () => {
|
|
619
|
+
const source = `
|
|
620
|
+
export component Test() {
|
|
621
|
+
<div>
|
|
622
|
+
<h1 class="h1,h2,h3">{'hello world'}</h1>
|
|
623
|
+
</div>
|
|
624
|
+
|
|
625
|
+
<style>
|
|
626
|
+
div :global(.h1\\,h2\\,h3) {
|
|
627
|
+
color: red;
|
|
628
|
+
}
|
|
629
|
+
</style>
|
|
630
|
+
}`;
|
|
631
|
+
const { css } = compile(source, 'test.ripple');
|
|
632
|
+
|
|
633
|
+
expect(css).toMatch(/div\.ripple-[a-z0-9]+ \.h1\\,h2\\,h3 {/);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* :global WITH :is/:where CONTAINING MULTIPLE SELECTORS
|
|
638
|
+
*/
|
|
639
|
+
it('handles :global with :is containing multiple selectors', () => {
|
|
640
|
+
const source = `
|
|
641
|
+
export component Test() {
|
|
642
|
+
<div>
|
|
643
|
+
<h1>{'hello world'}</h1>
|
|
644
|
+
<h2>{'subtitle'}</h2>
|
|
645
|
+
</div>
|
|
646
|
+
|
|
647
|
+
<style>
|
|
648
|
+
div :global(:is(h1, h2)) {
|
|
649
|
+
color: red;
|
|
650
|
+
}
|
|
651
|
+
div :global(:where(h1, h2)) {
|
|
652
|
+
color: red;
|
|
653
|
+
}
|
|
654
|
+
</style>
|
|
655
|
+
}`;
|
|
656
|
+
const { css } = compile(source, 'test.ripple');
|
|
657
|
+
|
|
658
|
+
expect(css).toMatch(/div\.ripple-[a-z0-9]+ :is\(h1, h2\)/);
|
|
659
|
+
expect(css).toMatch(/div\.ripple-[a-z0-9]+ :where\(h1, h2\)/);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('handles :global with :is containing compound selectors', () => {
|
|
663
|
+
const source = `
|
|
664
|
+
export component Test() {
|
|
665
|
+
<div>
|
|
666
|
+
<h1>{'hello world'}</h1>
|
|
667
|
+
<h2>{'subtitle'}</h2>
|
|
668
|
+
<h3>{'sub-subtitle'}</h3>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
<style>
|
|
672
|
+
div :global(h1 ~ :is(h2, h3)) {
|
|
673
|
+
color: red;
|
|
674
|
+
}
|
|
675
|
+
</style>
|
|
676
|
+
}`;
|
|
677
|
+
const { css } = compile(source, 'test.ripple');
|
|
678
|
+
|
|
679
|
+
expect(css).toMatch(/div\.ripple-[a-z0-9]+ h1 ~ :is\(h2, h3\)/);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it('handles :global with pseudo-elements', () => {
|
|
683
|
+
const source = `
|
|
684
|
+
export component Test() {
|
|
685
|
+
<div>
|
|
686
|
+
<h1 class="foo">{'hello world'}</h1>
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
<style>
|
|
690
|
+
.foo :global(.bar)::after {
|
|
691
|
+
color: red;
|
|
692
|
+
}
|
|
693
|
+
.foo :global(.bar)::after .baz {
|
|
694
|
+
color: red;
|
|
695
|
+
}
|
|
696
|
+
</style>
|
|
697
|
+
}`;
|
|
698
|
+
expect(() => compile(source, 'test.ripple')).toThrow(
|
|
699
|
+
':global(...) can be at the start or end of a selector sequence, but not in the middle',
|
|
700
|
+
);
|
|
701
|
+
});
|
|
702
|
+
});
|