cssupgrade 0.0.0-stage → 2026.0.0
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/.claude-plugin/plugin.json +22 -0
- package/.editorconfig +27 -0
- package/.gitattributes +1 -0
- package/.github/agents/superjoe.agent.md +65 -0
- package/.github/copilot-instructions.md +32 -0
- package/.github/dependabot.yml +10 -0
- package/.github/workflows/ci.yml +35 -0
- package/.github/workflows/pr.yml +27 -0
- package/.github/workflows/release.yml +22 -0
- package/.idea/cssupgrade.iml +14 -0
- package/.idea/inspectionProfiles/Project_Default.xml +28 -0
- package/.idea/inspectionProfiles/profiles_settings.xml +6 -0
- package/.idea/misc.xml +4 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.oxfmtrc.json +3 -0
- package/.pre-commit-config.yaml +52 -0
- package/.pre-commit-hooks.yaml +8 -0
- package/AGENTS.md +54 -0
- package/CONTRIBUTING.md +40 -0
- package/LICENSE +24 -0
- package/README.md +173 -2
- package/bin/cssupgrade.js +320 -0
- package/images/logo-dark.svg +10 -0
- package/images/logo-light.svg +10 -0
- package/package.json +34 -4
- package/skills/cssupgrade/SKILL.md +16 -0
- package/src/index.js +53 -0
- package/src/newlyAvailable.js +1 -0
- package/src/parser.js +43 -0
- package/src/pool.js +165 -0
- package/src/prefilters.js +29 -0
- package/src/widelyAvailable/cssNesting.js +918 -0
- package/src/widelyAvailable.js +1 -0
- package/src/worker.js +31 -0
- package/tests/baseline.test.js +51 -0
- package/tests/cli.test.js +229 -0
- package/tests/css-nesting.test.js +838 -0
- package/tests/fixtures/silent-worker.js +5 -0
- package/tests/general.test.js +42 -0
- package/tests/parser.test.js +43 -0
- package/tests/pool.test.js +242 -0
- package/tests/prefilters.test.js +24 -0
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
import assert from "node:assert/strict"
|
|
2
|
+
import { describe, test } from "node:test"
|
|
3
|
+
import { transform } from "../src/index.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Assert that a transformation produces the expected source text.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} input - CSS source text.
|
|
9
|
+
* @param {string} expected - Expected transformed source.
|
|
10
|
+
* @returns {void}
|
|
11
|
+
*/
|
|
12
|
+
function assertTransform(input, expected) {
|
|
13
|
+
const result = transform(input)
|
|
14
|
+
|
|
15
|
+
assert.equal(result.modified, true)
|
|
16
|
+
assert.equal(result.code, expected)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("cssNesting", () => {
|
|
20
|
+
test("nest a descendant rule", () => {
|
|
21
|
+
assertTransform(
|
|
22
|
+
`.card {
|
|
23
|
+
padding: 1rem;
|
|
24
|
+
}
|
|
25
|
+
.card .title {
|
|
26
|
+
font-weight: bold;
|
|
27
|
+
}`,
|
|
28
|
+
`.card {
|
|
29
|
+
padding: 1rem;
|
|
30
|
+
.title {
|
|
31
|
+
font-weight: bold;
|
|
32
|
+
}
|
|
33
|
+
}`,
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("nest compound selectors", () => {
|
|
38
|
+
assertTransform(
|
|
39
|
+
`.a {
|
|
40
|
+
color: red;
|
|
41
|
+
}
|
|
42
|
+
.a:hover {
|
|
43
|
+
color: blue;
|
|
44
|
+
}
|
|
45
|
+
.a.primary {
|
|
46
|
+
color: green;
|
|
47
|
+
}`,
|
|
48
|
+
`.a {
|
|
49
|
+
color: red;
|
|
50
|
+
&:hover {
|
|
51
|
+
color: blue;
|
|
52
|
+
}
|
|
53
|
+
&.primary {
|
|
54
|
+
color: green;
|
|
55
|
+
}
|
|
56
|
+
}`,
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test("nest relative combinators", () => {
|
|
61
|
+
assertTransform(
|
|
62
|
+
`.a {
|
|
63
|
+
color: red;
|
|
64
|
+
}
|
|
65
|
+
.a > .b {
|
|
66
|
+
color: blue;
|
|
67
|
+
}
|
|
68
|
+
.a + .c {
|
|
69
|
+
color: green;
|
|
70
|
+
}
|
|
71
|
+
.a ~ .d {
|
|
72
|
+
color: black;
|
|
73
|
+
}`,
|
|
74
|
+
`.a {
|
|
75
|
+
color: red;
|
|
76
|
+
& > .b {
|
|
77
|
+
color: blue;
|
|
78
|
+
}
|
|
79
|
+
& + .c {
|
|
80
|
+
color: green;
|
|
81
|
+
}
|
|
82
|
+
& ~ .d {
|
|
83
|
+
color: black;
|
|
84
|
+
}
|
|
85
|
+
}`,
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test("nest every selector combination", () => {
|
|
90
|
+
assertTransform(
|
|
91
|
+
`.a, .b {
|
|
92
|
+
color: red;
|
|
93
|
+
}
|
|
94
|
+
.a .c, .b .c {
|
|
95
|
+
color: blue;
|
|
96
|
+
}`,
|
|
97
|
+
`.a, .b {
|
|
98
|
+
color: red;
|
|
99
|
+
.c {
|
|
100
|
+
color: blue;
|
|
101
|
+
}
|
|
102
|
+
}`,
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test("nest a chain of rules", () => {
|
|
107
|
+
assertTransform(
|
|
108
|
+
`.a {
|
|
109
|
+
color: red;
|
|
110
|
+
}
|
|
111
|
+
.a .b {
|
|
112
|
+
color: blue;
|
|
113
|
+
}
|
|
114
|
+
.a .b .c {
|
|
115
|
+
color: green;
|
|
116
|
+
}`,
|
|
117
|
+
`.a {
|
|
118
|
+
color: red;
|
|
119
|
+
.b {
|
|
120
|
+
color: blue;
|
|
121
|
+
.c {
|
|
122
|
+
color: green;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}`,
|
|
126
|
+
)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test("nest siblings under the ancestor", () => {
|
|
130
|
+
assertTransform(
|
|
131
|
+
`.a {
|
|
132
|
+
color: red;
|
|
133
|
+
}
|
|
134
|
+
.a .b {
|
|
135
|
+
color: blue;
|
|
136
|
+
}
|
|
137
|
+
.a .c {
|
|
138
|
+
color: green;
|
|
139
|
+
}`,
|
|
140
|
+
`.a {
|
|
141
|
+
color: red;
|
|
142
|
+
.b {
|
|
143
|
+
color: blue;
|
|
144
|
+
}
|
|
145
|
+
.c {
|
|
146
|
+
color: green;
|
|
147
|
+
}
|
|
148
|
+
}`,
|
|
149
|
+
)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test("nest under a parent without declarations", () => {
|
|
153
|
+
assertTransform(
|
|
154
|
+
`.a {
|
|
155
|
+
}
|
|
156
|
+
.a .b {
|
|
157
|
+
color: red;
|
|
158
|
+
}`,
|
|
159
|
+
`.a {
|
|
160
|
+
.b {
|
|
161
|
+
color: red;
|
|
162
|
+
}
|
|
163
|
+
}`,
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test("leave partial selector coverage unchanged", () => {
|
|
168
|
+
const input = `.a, .b { color: red; }
|
|
169
|
+
.a .c { color: blue; }`
|
|
170
|
+
|
|
171
|
+
assert.equal(transform(input).modified, false)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test("leave parents with different specificity unchanged", () => {
|
|
175
|
+
const input = `.a, #b { color: red; }
|
|
176
|
+
.a .c, #b .c { color: blue; }`
|
|
177
|
+
|
|
178
|
+
assert.equal(transform(input).modified, false)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test("leave a :where() parent with a lower specificity unchanged", () => {
|
|
182
|
+
const input = `.a, :where(#x) { color: red; }
|
|
183
|
+
.a .c, :where(#x) .c { color: blue; }`
|
|
184
|
+
|
|
185
|
+
assert.equal(transform(input).modified, false)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test("nest parents with equal functional pseudo-class specificity", () => {
|
|
189
|
+
assertTransform(
|
|
190
|
+
`.a:not(#b), .c:not(#d) { color: red; }
|
|
191
|
+
.a:not(#b) .e, .c:not(#d) .e { color: blue; }`,
|
|
192
|
+
`.a:not(#b), .c:not(#d) {
|
|
193
|
+
color: red;
|
|
194
|
+
.e {
|
|
195
|
+
color: blue;
|
|
196
|
+
}
|
|
197
|
+
}`,
|
|
198
|
+
)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test("nest parents with equal nth-child specificity", () => {
|
|
202
|
+
assertTransform(
|
|
203
|
+
`.a:nth-child(2n of #b), .c:nth-child(2n of #d) { color: red; }
|
|
204
|
+
.a:nth-child(2n of #b) .e, .c:nth-child(2n of #d) .e { color: blue; }`,
|
|
205
|
+
`.a:nth-child(2n of #b), .c:nth-child(2n of #d) {
|
|
206
|
+
color: red;
|
|
207
|
+
.e {
|
|
208
|
+
color: blue;
|
|
209
|
+
}
|
|
210
|
+
}`,
|
|
211
|
+
)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test("nest parents with functional pseudo-classes that are not selectors", () => {
|
|
215
|
+
assertTransform(
|
|
216
|
+
`.a:lang(en), .b:dir(ltr) { color: red; }
|
|
217
|
+
.a:lang(en) .c, .b:dir(ltr) .c { color: blue; }`,
|
|
218
|
+
`.a:lang(en), .b:dir(ltr) {
|
|
219
|
+
color: red;
|
|
220
|
+
.c {
|
|
221
|
+
color: blue;
|
|
222
|
+
}
|
|
223
|
+
}`,
|
|
224
|
+
)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
test("nest under a universal selector", () => {
|
|
228
|
+
assertTransform(
|
|
229
|
+
`* {
|
|
230
|
+
box-sizing: border-box;
|
|
231
|
+
}
|
|
232
|
+
* .a {
|
|
233
|
+
color: red;
|
|
234
|
+
}`,
|
|
235
|
+
`* {
|
|
236
|
+
box-sizing: border-box;
|
|
237
|
+
.a {
|
|
238
|
+
color: red;
|
|
239
|
+
}
|
|
240
|
+
}`,
|
|
241
|
+
)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test("nest under an attribute selector", () => {
|
|
245
|
+
assertTransform(
|
|
246
|
+
`[data-x] {
|
|
247
|
+
color: red;
|
|
248
|
+
}
|
|
249
|
+
[data-x] .a {
|
|
250
|
+
color: blue;
|
|
251
|
+
}`,
|
|
252
|
+
`[data-x] {
|
|
253
|
+
color: red;
|
|
254
|
+
.a {
|
|
255
|
+
color: blue;
|
|
256
|
+
}
|
|
257
|
+
}`,
|
|
258
|
+
)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test("nest rules in an at-rule block", () => {
|
|
262
|
+
assertTransform(
|
|
263
|
+
`@media (min-width: 600px) {
|
|
264
|
+
.a {
|
|
265
|
+
color: red;
|
|
266
|
+
}
|
|
267
|
+
.a .b {
|
|
268
|
+
color: blue;
|
|
269
|
+
}
|
|
270
|
+
}`,
|
|
271
|
+
`@media (min-width: 600px) {
|
|
272
|
+
.a {
|
|
273
|
+
color: red;
|
|
274
|
+
.b {
|
|
275
|
+
color: blue;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}`,
|
|
279
|
+
)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
test("nest rules in nested at-rule blocks", () => {
|
|
283
|
+
assertTransform(
|
|
284
|
+
`@supports (display: grid) {
|
|
285
|
+
@media (min-width: 600px) {
|
|
286
|
+
.a {
|
|
287
|
+
color: red;
|
|
288
|
+
}
|
|
289
|
+
.a .b {
|
|
290
|
+
color: blue;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}`,
|
|
294
|
+
`@supports (display: grid) {
|
|
295
|
+
@media (min-width: 600px) {
|
|
296
|
+
.a {
|
|
297
|
+
color: red;
|
|
298
|
+
.b {
|
|
299
|
+
color: blue;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}`,
|
|
304
|
+
)
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
test("nest rules in a layer block", () => {
|
|
308
|
+
assertTransform(
|
|
309
|
+
`@layer components {
|
|
310
|
+
.a {
|
|
311
|
+
color: red;
|
|
312
|
+
}
|
|
313
|
+
.a .b {
|
|
314
|
+
color: blue;
|
|
315
|
+
}
|
|
316
|
+
}`,
|
|
317
|
+
`@layer components {
|
|
318
|
+
.a {
|
|
319
|
+
color: red;
|
|
320
|
+
.b {
|
|
321
|
+
color: blue;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}`,
|
|
325
|
+
)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
test("preserve comments inside rules", () => {
|
|
329
|
+
assertTransform(
|
|
330
|
+
`.a {
|
|
331
|
+
/* before */
|
|
332
|
+
color: red;
|
|
333
|
+
}
|
|
334
|
+
/* between */
|
|
335
|
+
.a .b {
|
|
336
|
+
/* inside */
|
|
337
|
+
color: blue;
|
|
338
|
+
}`,
|
|
339
|
+
`.a {
|
|
340
|
+
/* before */
|
|
341
|
+
color: red;
|
|
342
|
+
/* between */
|
|
343
|
+
.b {
|
|
344
|
+
/* inside */
|
|
345
|
+
color: blue;
|
|
346
|
+
}
|
|
347
|
+
}`,
|
|
348
|
+
)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
test("preserve a comment inside a declaration value", () => {
|
|
352
|
+
assertTransform(
|
|
353
|
+
`.a {
|
|
354
|
+
color: /* note */ red;
|
|
355
|
+
}
|
|
356
|
+
.a .b {
|
|
357
|
+
color: blue;
|
|
358
|
+
}`,
|
|
359
|
+
`.a {
|
|
360
|
+
color: /* note */ red;
|
|
361
|
+
.b {
|
|
362
|
+
color: blue;
|
|
363
|
+
}
|
|
364
|
+
}`,
|
|
365
|
+
)
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
test("add missing semicolons", () => {
|
|
369
|
+
assertTransform(
|
|
370
|
+
`.a { color: red }
|
|
371
|
+
.a .b { color: blue }`,
|
|
372
|
+
`.a {
|
|
373
|
+
color: red;
|
|
374
|
+
.b {
|
|
375
|
+
color: blue;
|
|
376
|
+
}
|
|
377
|
+
}`,
|
|
378
|
+
)
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
test("preserve custom properties with block values", () => {
|
|
382
|
+
assertTransform(
|
|
383
|
+
`.a {
|
|
384
|
+
--theme: { color: red; };
|
|
385
|
+
}
|
|
386
|
+
.a .b {
|
|
387
|
+
color: blue;
|
|
388
|
+
}`,
|
|
389
|
+
`.a {
|
|
390
|
+
--theme: { color: red; };
|
|
391
|
+
.b {
|
|
392
|
+
color: blue;
|
|
393
|
+
}
|
|
394
|
+
}`,
|
|
395
|
+
)
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
test("preserve multiline declarations", () => {
|
|
399
|
+
assertTransform(
|
|
400
|
+
`.a {
|
|
401
|
+
background:
|
|
402
|
+
linear-gradient(red, blue);
|
|
403
|
+
}
|
|
404
|
+
.a .b {
|
|
405
|
+
color: blue;
|
|
406
|
+
}`,
|
|
407
|
+
`.a {
|
|
408
|
+
background:
|
|
409
|
+
linear-gradient(red, blue);
|
|
410
|
+
.b {
|
|
411
|
+
color: blue;
|
|
412
|
+
}
|
|
413
|
+
}`,
|
|
414
|
+
)
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
test("preserve the indentation of the source text", () => {
|
|
418
|
+
assertTransform(
|
|
419
|
+
` .a {
|
|
420
|
+
color: red;
|
|
421
|
+
}
|
|
422
|
+
.a .b {
|
|
423
|
+
color: blue;
|
|
424
|
+
}`,
|
|
425
|
+
` .a {
|
|
426
|
+
color: red;
|
|
427
|
+
.b {
|
|
428
|
+
color: blue;
|
|
429
|
+
}
|
|
430
|
+
}`,
|
|
431
|
+
)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
test("use two spaces when the source has no indentation", () => {
|
|
435
|
+
assertTransform(
|
|
436
|
+
".a{color:red}.a .c{color:blue}",
|
|
437
|
+
`.a {
|
|
438
|
+
color:red;
|
|
439
|
+
.c {
|
|
440
|
+
color:blue;
|
|
441
|
+
}
|
|
442
|
+
}`,
|
|
443
|
+
)
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
test("leave non-adjacent rules unchanged", () => {
|
|
447
|
+
const input = `.a { color: red; }
|
|
448
|
+
.x { color: green; }
|
|
449
|
+
.a .b { color: blue; }`
|
|
450
|
+
|
|
451
|
+
assert.equal(transform(input).modified, false)
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
test("leave existing nesting unchanged", () => {
|
|
455
|
+
const input = `.a {
|
|
456
|
+
color: red;
|
|
457
|
+
.b { color: blue; }
|
|
458
|
+
}`
|
|
459
|
+
|
|
460
|
+
assert.equal(transform(input).modified, false)
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
test("leave a parent with a comment in its prelude unchanged", () => {
|
|
464
|
+
const input = `.a /* keep */ { color: red; }
|
|
465
|
+
.a .b { color: blue; }`
|
|
466
|
+
|
|
467
|
+
assert.equal(transform(input).modified, false)
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
test("leave an unknown functional pseudo-class unchanged", () => {
|
|
471
|
+
const input = `.a:foo(.b) { color: red; }
|
|
472
|
+
.a:foo(.b) .c { color: blue; }`
|
|
473
|
+
|
|
474
|
+
assert.equal(transform(input).modified, false)
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
test("leave a pseudo-element parent unchanged", () => {
|
|
478
|
+
const input = `.a::before { color: red; }
|
|
479
|
+
.a::before .b { color: blue; }`
|
|
480
|
+
|
|
481
|
+
assert.equal(transform(input).modified, false)
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
test("leave a nesting selector parent unchanged", () => {
|
|
485
|
+
const input = `& .a { color: red; }
|
|
486
|
+
& .a .b { color: blue; }`
|
|
487
|
+
|
|
488
|
+
assert.equal(transform(input).modified, false)
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
test("leave a rule with a raw declaration unchanged", () => {
|
|
492
|
+
const input = `.a { ; }
|
|
493
|
+
.a .b { color: blue; }`
|
|
494
|
+
|
|
495
|
+
assert.equal(transform(input).modified, false)
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
test("leave keyframes unchanged", () => {
|
|
499
|
+
const input = `@keyframes fade {
|
|
500
|
+
from { opacity: 0 }
|
|
501
|
+
to { opacity: 1 }
|
|
502
|
+
}`
|
|
503
|
+
|
|
504
|
+
assert.equal(transform(input).modified, false)
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
test("leave page and font-face rules unchanged", () => {
|
|
508
|
+
const input = `@page { margin: 1cm; @top-left { content: "x" } }
|
|
509
|
+
@font-face { font-family: "x"; src: url("x.woff2"); }`
|
|
510
|
+
|
|
511
|
+
assert.equal(transform(input).modified, false)
|
|
512
|
+
})
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
describe("cssNesting edge cases", () => {
|
|
516
|
+
test("leave a rule with an empty selector list argument unchanged", () => {
|
|
517
|
+
const input = `.a:is() { color: red; }
|
|
518
|
+
.a:is() .b { color: blue; }`
|
|
519
|
+
|
|
520
|
+
assert.equal(transform(input).modified, false)
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
test("leave a rule with an unknown selector in a selector list unchanged", () => {
|
|
524
|
+
const input = `.a:is(.b, :foo(.c)) { color: red; }
|
|
525
|
+
.a:is(.b, :foo(.c)) .d { color: blue; }`
|
|
526
|
+
|
|
527
|
+
assert.equal(transform(input).modified, false)
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
test("nest parents with an nth-child argument without a selector", () => {
|
|
531
|
+
assertTransform(
|
|
532
|
+
`.a:nth-child(2n), .b:nth-child(2n) { color: red; }
|
|
533
|
+
.a:nth-child(2n) .c, .b:nth-child(2n) .c { color: blue; }`,
|
|
534
|
+
`.a:nth-child(2n), .b:nth-child(2n) {
|
|
535
|
+
color: red;
|
|
536
|
+
.c {
|
|
537
|
+
color: blue;
|
|
538
|
+
}
|
|
539
|
+
}`,
|
|
540
|
+
)
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
test("leave a rule with an unknown nth-child selector unchanged", () => {
|
|
544
|
+
const input = `.a:nth-child(2n of :foo(.b)) { color: red; }
|
|
545
|
+
.a:nth-child(2n of :foo(.b)) .c { color: blue; }`
|
|
546
|
+
|
|
547
|
+
assert.equal(transform(input).modified, false)
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
test("nest parents whose selector list argument contains a pseudo-element", () => {
|
|
551
|
+
assertTransform(
|
|
552
|
+
`.a:is(::before), .b:is(::after) { color: red; }
|
|
553
|
+
.a:is(::before) .c, .b:is(::after) .c { color: blue; }`,
|
|
554
|
+
`.a:is(::before), .b:is(::after) {
|
|
555
|
+
color: red;
|
|
556
|
+
.c {
|
|
557
|
+
color: blue;
|
|
558
|
+
}
|
|
559
|
+
}`,
|
|
560
|
+
)
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
test("pick the larger specificity of an id in a selector list", () => {
|
|
564
|
+
assertTransform(
|
|
565
|
+
`.a:is(.b, #c, .d), .e:is(.f, #g, .h) { color: red; }
|
|
566
|
+
.a:is(.b, #c, .d) .i, .e:is(.f, #g, .h) .i { color: blue; }`,
|
|
567
|
+
`.a:is(.b, #c, .d), .e:is(.f, #g, .h) {
|
|
568
|
+
color: red;
|
|
569
|
+
.i {
|
|
570
|
+
color: blue;
|
|
571
|
+
}
|
|
572
|
+
}`,
|
|
573
|
+
)
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
test("pick the larger specificity of a class in a selector list", () => {
|
|
577
|
+
assertTransform(
|
|
578
|
+
`.a:is(.b.c, .d), .e:is(.f.g, .h) { color: red; }
|
|
579
|
+
.a:is(.b.c, .d) .i, .e:is(.f.g, .h) .i { color: blue; }`,
|
|
580
|
+
`.a:is(.b.c, .d), .e:is(.f.g, .h) {
|
|
581
|
+
color: red;
|
|
582
|
+
.i {
|
|
583
|
+
color: blue;
|
|
584
|
+
}
|
|
585
|
+
}`,
|
|
586
|
+
)
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
test("pick the larger specificity of a later class in a selector list", () => {
|
|
590
|
+
assertTransform(
|
|
591
|
+
`.a:is(.b, .c.d), .e:is(.f, .g.h) { color: red; }
|
|
592
|
+
.a:is(.b, .c.d) .i, .e:is(.f, .g.h) .i { color: blue; }`,
|
|
593
|
+
`.a:is(.b, .c.d), .e:is(.f, .g.h) {
|
|
594
|
+
color: red;
|
|
595
|
+
.i {
|
|
596
|
+
color: blue;
|
|
597
|
+
}
|
|
598
|
+
}`,
|
|
599
|
+
)
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
test("pick the specificity of a type selector in a selector list", () => {
|
|
603
|
+
assertTransform(
|
|
604
|
+
`.a:is(.b.c, .d.e), .f:is(.g.h, .i.j) { color: red; }
|
|
605
|
+
.a:is(.b.c, .d.e) .k, .f:is(.g.h, .i.j) .k { color: blue; }`,
|
|
606
|
+
`.a:is(.b.c, .d.e), .f:is(.g.h, .i.j) {
|
|
607
|
+
color: red;
|
|
608
|
+
.k {
|
|
609
|
+
color: blue;
|
|
610
|
+
}
|
|
611
|
+
}`,
|
|
612
|
+
)
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
test("pick the larger specificity of a pseudo-element in a selector list", () => {
|
|
616
|
+
assertTransform(
|
|
617
|
+
`.a:is(.b.c, .d.e::before), .f:is(.g.h, .i.j::after) { color: red; }
|
|
618
|
+
.a:is(.b.c, .d.e::before) .k, .f:is(.g.h, .i.j::after) .k { color: blue; }`,
|
|
619
|
+
`.a:is(.b.c, .d.e::before), .f:is(.g.h, .i.j::after) {
|
|
620
|
+
color: red;
|
|
621
|
+
.k {
|
|
622
|
+
color: blue;
|
|
623
|
+
}
|
|
624
|
+
}`,
|
|
625
|
+
)
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
test("leave rules separated by stray text unchanged", () => {
|
|
629
|
+
const input = `.a { color: red; } ; .a .b { color: blue; }`
|
|
630
|
+
|
|
631
|
+
assert.equal(transform(input).modified, false)
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
test("leave duplicate parent selectors unchanged", () => {
|
|
635
|
+
const input = `.a, .a { color: red; }
|
|
636
|
+
.a .b { color: blue; }`
|
|
637
|
+
|
|
638
|
+
assert.equal(transform(input).modified, false)
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
test("leave duplicate child selectors unchanged", () => {
|
|
642
|
+
const input = `.a { color: red; }
|
|
643
|
+
.a .b, .a .b { color: blue; }`
|
|
644
|
+
|
|
645
|
+
assert.equal(transform(input).modified, false)
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
test("leave a repeated parent selector unchanged", () => {
|
|
649
|
+
const input = `.a { color: red; }
|
|
650
|
+
.a { color: blue; }`
|
|
651
|
+
|
|
652
|
+
assert.equal(transform(input).modified, false)
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
test("leave a selector that only shares a prefix unchanged", () => {
|
|
656
|
+
const input = `.a { color: red; }
|
|
657
|
+
.ab { color: blue; }`
|
|
658
|
+
|
|
659
|
+
assert.equal(transform(input).modified, false)
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
test("leave a child with a comment in its prelude unchanged", () => {
|
|
663
|
+
const input = `.a { color: red; }
|
|
664
|
+
.a .b /* keep */ { color: blue; }`
|
|
665
|
+
|
|
666
|
+
assert.equal(transform(input).modified, false)
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
test("leave a rule with nested rules following a plain rule unchanged", () => {
|
|
670
|
+
const input = `.a { color: red; }
|
|
671
|
+
.x { .y { color: blue; } }`
|
|
672
|
+
|
|
673
|
+
assert.equal(transform(input).modified, false)
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
test("preserve at-rules without a block", () => {
|
|
677
|
+
assertTransform(
|
|
678
|
+
`@import "x";
|
|
679
|
+
.a {
|
|
680
|
+
color: red;
|
|
681
|
+
}
|
|
682
|
+
.a .b {
|
|
683
|
+
color: blue;
|
|
684
|
+
}`,
|
|
685
|
+
`@import "x";
|
|
686
|
+
.a {
|
|
687
|
+
color: red;
|
|
688
|
+
.b {
|
|
689
|
+
color: blue;
|
|
690
|
+
}
|
|
691
|
+
}`,
|
|
692
|
+
)
|
|
693
|
+
})
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
describe("cssNesting line indentation", () => {
|
|
697
|
+
test("nest a rule that shares its line with another rule", () => {
|
|
698
|
+
assertTransform(
|
|
699
|
+
".x { color: red; } .a { color: blue; } .a .b { color: green; }",
|
|
700
|
+
`.x { color: red; } .a {
|
|
701
|
+
color: blue;
|
|
702
|
+
.b {
|
|
703
|
+
color: green;
|
|
704
|
+
}
|
|
705
|
+
}`,
|
|
706
|
+
)
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
test("nest parents with an empty nth-child argument", () => {
|
|
710
|
+
assertTransform(
|
|
711
|
+
`.a:nth-child(), .b:nth-child() { color: red; }
|
|
712
|
+
.a:nth-child() .c, .b:nth-child() .c { color: blue; }`,
|
|
713
|
+
`.a:nth-child(), .b:nth-child() {
|
|
714
|
+
color: red;
|
|
715
|
+
.c {
|
|
716
|
+
color: blue;
|
|
717
|
+
}
|
|
718
|
+
}`,
|
|
719
|
+
)
|
|
720
|
+
})
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
describe("cssNesting unterminated at-rules", () => {
|
|
724
|
+
test("nest rules in an unterminated at-rule block", () => {
|
|
725
|
+
const result = transform(
|
|
726
|
+
`@media all {
|
|
727
|
+
.a { color: red }
|
|
728
|
+
.a .b { color: blue }`,
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
assert.equal(result.modified, true)
|
|
732
|
+
assert.equal(
|
|
733
|
+
result.code,
|
|
734
|
+
`@media all {
|
|
735
|
+
.a {
|
|
736
|
+
color: red;
|
|
737
|
+
.b {
|
|
738
|
+
color: blue;
|
|
739
|
+
}
|
|
740
|
+
}`,
|
|
741
|
+
)
|
|
742
|
+
})
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
describe("cssNesting block braces", () => {
|
|
746
|
+
test("preserve an empty at-rule block", () => {
|
|
747
|
+
assertTransform(
|
|
748
|
+
"@media all { } .a { } .a .b { color: red }",
|
|
749
|
+
`@media all { } .a {
|
|
750
|
+
.b {
|
|
751
|
+
color: red;
|
|
752
|
+
}
|
|
753
|
+
}`,
|
|
754
|
+
)
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
test("preserve the trailing whitespace of an unterminated at-rule", () => {
|
|
758
|
+
assertTransform(
|
|
759
|
+
`@media all {
|
|
760
|
+
.a { color: red }
|
|
761
|
+
.a .b { color: blue }
|
|
762
|
+
`,
|
|
763
|
+
`@media all {
|
|
764
|
+
.a {
|
|
765
|
+
color: red;
|
|
766
|
+
.b {
|
|
767
|
+
color: blue;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
`,
|
|
771
|
+
)
|
|
772
|
+
})
|
|
773
|
+
})
|
|
774
|
+
|
|
775
|
+
describe("cssNesting host selectors", () => {
|
|
776
|
+
test("nest under :host without arguments", () => {
|
|
777
|
+
assertTransform(
|
|
778
|
+
`:host {
|
|
779
|
+
color: red;
|
|
780
|
+
}
|
|
781
|
+
:host .a {
|
|
782
|
+
color: blue;
|
|
783
|
+
}`,
|
|
784
|
+
`:host {
|
|
785
|
+
color: red;
|
|
786
|
+
.a {
|
|
787
|
+
color: blue;
|
|
788
|
+
}
|
|
789
|
+
}`,
|
|
790
|
+
)
|
|
791
|
+
})
|
|
792
|
+
|
|
793
|
+
test("nest under :host-context without arguments", () => {
|
|
794
|
+
assertTransform(
|
|
795
|
+
`:host-context {
|
|
796
|
+
color: red;
|
|
797
|
+
}
|
|
798
|
+
:host-context .a {
|
|
799
|
+
color: blue;
|
|
800
|
+
}`,
|
|
801
|
+
`:host-context {
|
|
802
|
+
color: red;
|
|
803
|
+
.a {
|
|
804
|
+
color: blue;
|
|
805
|
+
}
|
|
806
|
+
}`,
|
|
807
|
+
)
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
test("nest under :host with arguments", () => {
|
|
811
|
+
assertTransform(
|
|
812
|
+
`:host(.a), :host(.b) { color: red; }
|
|
813
|
+
:host(.a) .c, :host(.b) .c { color: blue; }`,
|
|
814
|
+
`:host(.a), :host(.b) {
|
|
815
|
+
color: red;
|
|
816
|
+
.c {
|
|
817
|
+
color: blue;
|
|
818
|
+
}
|
|
819
|
+
}`,
|
|
820
|
+
)
|
|
821
|
+
})
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
describe("cssNesting line endings", () => {
|
|
825
|
+
test("preserve CRLF line endings", () => {
|
|
826
|
+
assertTransform(
|
|
827
|
+
".a {\r\n color: red;\r\n}\r\n.a .b {\r\n color: blue;\r\n}\r\n",
|
|
828
|
+
".a {\r\n color: red;\r\n .b {\r\n color: blue;\r\n }\r\n}\r\n",
|
|
829
|
+
)
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
test("preserve tab indentation", () => {
|
|
833
|
+
assertTransform(
|
|
834
|
+
".a {\n\tcolor: red;\n}\n.a .b {\n\tcolor: blue;\n}",
|
|
835
|
+
".a {\n\tcolor: red;\n\t.b {\n\t\tcolor: blue;\n\t}\n}",
|
|
836
|
+
)
|
|
837
|
+
})
|
|
838
|
+
})
|