mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Run test suite against both engines (#10373)
* Run test suite against both engines * make eslint happy * only run `stable` tests on Node 12 * use normal expectation instead of snapshot file When we run the tests only against `stable` (for node 12), then the snapshots exists for the `Oxide` build. They are marked as `obsolete` and will cause the `npm run test` script to fail. Sadly. Inlined them for now, but ideally we make those tests more blackbox-y so that we test that we get source maps and that we can map the sourcemap back to the input files (without looking at the actual annotations). * properly indent inline css Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com> Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
7d1491449e
commit
42136e94ce
@ -1,378 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`apply generates source maps 1`] = `
|
||||
Array [
|
||||
"2:4 -> 2:4",
|
||||
"3:6-27 -> 3:6-27",
|
||||
"4:6-33 -> 4:6-18",
|
||||
"4:6-33 -> 5:6-17",
|
||||
"4:6-33 -> 6:6-24",
|
||||
"4:6-33 -> 7:6-61",
|
||||
"5:4 -> 8:4",
|
||||
"7:4 -> 10:4",
|
||||
"8:6-39 -> 11:6-39",
|
||||
"9:6-31 -> 12:6-18",
|
||||
"9:6-31 -> 13:6-17",
|
||||
"9:6-31 -> 14:6-24",
|
||||
"9:6-31 -> 15:6-61",
|
||||
"10:4 -> 16:4",
|
||||
"13:6 -> 18:4",
|
||||
"13:6-29 -> 19:6-18",
|
||||
"13:6-29 -> 20:6-17",
|
||||
"13:6-29 -> 21:6-24",
|
||||
"13:6 -> 22:6",
|
||||
"13:29 -> 23:0",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`components have source maps 1`] = `
|
||||
Array [
|
||||
"2:4 -> 1:0",
|
||||
"2:4 -> 2:4",
|
||||
"2:24 -> 3:0",
|
||||
"2:4 -> 4:0",
|
||||
"2:4 -> 5:4",
|
||||
"2:4 -> 6:8",
|
||||
"2:24 -> 7:4",
|
||||
"2:24 -> 8:0",
|
||||
"2:4 -> 9:0",
|
||||
"2:4 -> 10:4",
|
||||
"2:4 -> 11:8",
|
||||
"2:24 -> 12:4",
|
||||
"2:24 -> 13:0",
|
||||
"2:4 -> 14:0",
|
||||
"2:4 -> 15:4",
|
||||
"2:4 -> 16:8",
|
||||
"2:24 -> 17:4",
|
||||
"2:24 -> 18:0",
|
||||
"2:4 -> 19:0",
|
||||
"2:4 -> 20:4",
|
||||
"2:4 -> 21:8",
|
||||
"2:24 -> 22:4",
|
||||
"2:24 -> 23:0",
|
||||
"2:4 -> 24:0",
|
||||
"2:4 -> 25:4",
|
||||
"2:4 -> 26:8",
|
||||
"2:24 -> 27:4",
|
||||
"2:24 -> 28:0",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`preflight + base have source maps 1`] = `
|
||||
Array [
|
||||
"2:4 -> 1:0",
|
||||
"2:18-4 -> 3:1-2",
|
||||
"2:18 -> 6:1",
|
||||
"2:4 -> 8:0",
|
||||
"2:4-18 -> 11:2-32",
|
||||
"2:4-18 -> 12:2-25",
|
||||
"2:4-18 -> 13:2-29",
|
||||
"2:4-18 -> 14:2-31",
|
||||
"2:18 -> 15:0",
|
||||
"2:4 -> 17:0",
|
||||
"2:4-18 -> 19:2-18",
|
||||
"2:18 -> 20:0",
|
||||
"2:4 -> 22:0",
|
||||
"2:18 -> 28:1",
|
||||
"2:4 -> 30:0",
|
||||
"2:4-18 -> 31:2-26",
|
||||
"2:4-18 -> 32:2-40",
|
||||
"2:4-18 -> 33:2-26",
|
||||
"2:4-18 -> 34:2-21",
|
||||
"2:4-18 -> 35:2-230",
|
||||
"2:4-18 -> 36:2-39",
|
||||
"2:18 -> 37:0",
|
||||
"2:4 -> 39:0",
|
||||
"2:18 -> 42:1",
|
||||
"2:4 -> 44:0",
|
||||
"2:4-18 -> 45:2-19",
|
||||
"2:4-18 -> 46:2-30",
|
||||
"2:18 -> 47:0",
|
||||
"2:4 -> 49:0",
|
||||
"2:18 -> 53:1",
|
||||
"2:4 -> 55:0",
|
||||
"2:4-18 -> 56:2-19",
|
||||
"2:4-18 -> 57:2-24",
|
||||
"2:4-18 -> 58:2-31",
|
||||
"2:18 -> 59:0",
|
||||
"2:4 -> 61:0",
|
||||
"2:18 -> 63:1",
|
||||
"2:4 -> 65:0",
|
||||
"2:4-18 -> 66:2-35",
|
||||
"2:18 -> 67:0",
|
||||
"2:4 -> 69:0",
|
||||
"2:18 -> 71:1",
|
||||
"2:4 -> 73:0",
|
||||
"2:4-18 -> 79:2-20",
|
||||
"2:4-18 -> 80:2-22",
|
||||
"2:18 -> 81:0",
|
||||
"2:4 -> 83:0",
|
||||
"2:18 -> 85:1",
|
||||
"2:4 -> 87:0",
|
||||
"2:4-18 -> 88:2-16",
|
||||
"2:4-18 -> 89:2-26",
|
||||
"2:18 -> 90:0",
|
||||
"2:4 -> 92:0",
|
||||
"2:18 -> 94:1",
|
||||
"2:4 -> 96:0",
|
||||
"2:4-18 -> 98:2-21",
|
||||
"2:18 -> 99:0",
|
||||
"2:4 -> 101:0",
|
||||
"2:18 -> 104:1",
|
||||
"2:4 -> 106:0",
|
||||
"2:4-18 -> 110:2-121",
|
||||
"2:4-18 -> 111:2-24",
|
||||
"2:18 -> 112:0",
|
||||
"2:4 -> 114:0",
|
||||
"2:18 -> 116:1",
|
||||
"2:4 -> 118:0",
|
||||
"2:4-18 -> 119:2-16",
|
||||
"2:18 -> 120:0",
|
||||
"2:4 -> 122:0",
|
||||
"2:18 -> 124:1",
|
||||
"2:4 -> 126:0",
|
||||
"2:4-18 -> 128:2-16",
|
||||
"2:4-18 -> 129:2-16",
|
||||
"2:4-18 -> 130:2-20",
|
||||
"2:4-18 -> 131:2-26",
|
||||
"2:18 -> 132:0",
|
||||
"2:4 -> 134:0",
|
||||
"2:4-18 -> 135:2-17",
|
||||
"2:18 -> 136:0",
|
||||
"2:4 -> 138:0",
|
||||
"2:4-18 -> 139:2-13",
|
||||
"2:18 -> 140:0",
|
||||
"2:4 -> 142:0",
|
||||
"2:18 -> 146:1",
|
||||
"2:4 -> 148:0",
|
||||
"2:4-18 -> 149:2-24",
|
||||
"2:4-18 -> 150:2-31",
|
||||
"2:4-18 -> 151:2-35",
|
||||
"2:18 -> 152:0",
|
||||
"2:4 -> 154:0",
|
||||
"2:18 -> 158:1",
|
||||
"2:4 -> 160:0",
|
||||
"2:4-18 -> 165:2-30",
|
||||
"2:4-18 -> 166:2-25",
|
||||
"2:4-18 -> 167:2-30",
|
||||
"2:4-18 -> 168:2-30",
|
||||
"2:4-18 -> 169:2-24",
|
||||
"2:4-18 -> 170:2-19",
|
||||
"2:4-18 -> 171:2-20",
|
||||
"2:18 -> 172:0",
|
||||
"2:4 -> 174:0",
|
||||
"2:18 -> 176:1",
|
||||
"2:4 -> 178:0",
|
||||
"2:4-18 -> 180:2-22",
|
||||
"2:18 -> 181:0",
|
||||
"2:4 -> 183:0",
|
||||
"2:18 -> 186:1",
|
||||
"2:4 -> 188:0",
|
||||
"2:4-18 -> 192:2-36",
|
||||
"2:4-18 -> 193:2-39",
|
||||
"2:4-18 -> 194:2-32",
|
||||
"2:18 -> 195:0",
|
||||
"2:4 -> 197:0",
|
||||
"2:18 -> 199:1",
|
||||
"2:4 -> 201:0",
|
||||
"2:4-18 -> 202:2-15",
|
||||
"2:18 -> 203:0",
|
||||
"2:4 -> 205:0",
|
||||
"2:18 -> 207:1",
|
||||
"2:4 -> 209:0",
|
||||
"2:4-18 -> 210:2-18",
|
||||
"2:18 -> 211:0",
|
||||
"2:4 -> 213:0",
|
||||
"2:18 -> 215:1",
|
||||
"2:4 -> 217:0",
|
||||
"2:4-18 -> 218:2-26",
|
||||
"2:18 -> 219:0",
|
||||
"2:4 -> 221:0",
|
||||
"2:18 -> 223:1",
|
||||
"2:4 -> 225:0",
|
||||
"2:4-18 -> 227:2-14",
|
||||
"2:18 -> 228:0",
|
||||
"2:4 -> 230:0",
|
||||
"2:18 -> 233:1",
|
||||
"2:4 -> 235:0",
|
||||
"2:4-18 -> 236:2-39",
|
||||
"2:4-18 -> 237:2-30",
|
||||
"2:18 -> 238:0",
|
||||
"2:4 -> 240:0",
|
||||
"2:18 -> 242:1",
|
||||
"2:4 -> 244:0",
|
||||
"2:4-18 -> 245:2-26",
|
||||
"2:18 -> 246:0",
|
||||
"2:4 -> 248:0",
|
||||
"2:18 -> 251:1",
|
||||
"2:4 -> 253:0",
|
||||
"2:4-18 -> 254:2-36",
|
||||
"2:4-18 -> 255:2-23",
|
||||
"2:18 -> 256:0",
|
||||
"2:4 -> 258:0",
|
||||
"2:18 -> 260:1",
|
||||
"2:4 -> 262:0",
|
||||
"2:4-18 -> 263:2-20",
|
||||
"2:18 -> 264:0",
|
||||
"2:4 -> 266:0",
|
||||
"2:18 -> 268:1",
|
||||
"2:4 -> 270:0",
|
||||
"2:4-18 -> 283:2-11",
|
||||
"2:18 -> 284:0",
|
||||
"2:4 -> 286:0",
|
||||
"2:4-18 -> 287:2-11",
|
||||
"2:4-18 -> 288:2-12",
|
||||
"2:18 -> 289:0",
|
||||
"2:4 -> 291:0",
|
||||
"2:4-18 -> 292:2-12",
|
||||
"2:18 -> 293:0",
|
||||
"2:4 -> 295:0",
|
||||
"2:4-18 -> 298:2-18",
|
||||
"2:4-18 -> 299:2-11",
|
||||
"2:4-18 -> 300:2-12",
|
||||
"2:18 -> 301:0",
|
||||
"2:4 -> 303:0",
|
||||
"2:18 -> 305:1",
|
||||
"2:4 -> 307:0",
|
||||
"2:4-18 -> 308:2-18",
|
||||
"2:18 -> 309:0",
|
||||
"2:4 -> 311:0",
|
||||
"2:18 -> 314:1",
|
||||
"2:4 -> 316:0",
|
||||
"2:4-18 -> 318:2-20",
|
||||
"2:4-18 -> 319:2-24",
|
||||
"2:18 -> 320:0",
|
||||
"2:4 -> 322:0",
|
||||
"2:18 -> 324:1",
|
||||
"2:4 -> 326:0",
|
||||
"2:4-18 -> 328:2-17",
|
||||
"2:18 -> 329:0",
|
||||
"2:4 -> 331:0",
|
||||
"2:18 -> 333:1",
|
||||
"2:4 -> 334:0",
|
||||
"2:4-18 -> 335:2-17",
|
||||
"2:18 -> 336:0",
|
||||
"2:4 -> 338:0",
|
||||
"2:18 -> 342:1",
|
||||
"2:4 -> 344:0",
|
||||
"2:4-18 -> 352:2-24",
|
||||
"2:4-18 -> 353:2-32",
|
||||
"2:18 -> 354:0",
|
||||
"2:4 -> 356:0",
|
||||
"2:18 -> 358:1",
|
||||
"2:4 -> 360:0",
|
||||
"2:4-18 -> 362:2-17",
|
||||
"2:4-18 -> 363:2-14",
|
||||
"2:18 -> 364:0",
|
||||
"2:4-18 -> 366:0-72",
|
||||
"2:4 -> 367:0",
|
||||
"2:4-18 -> 368:2-15",
|
||||
"2:18 -> 369:0",
|
||||
"2:4 -> 371:0",
|
||||
"2:4-18 -> 372:2-26",
|
||||
"2:4-18 -> 373:2-26",
|
||||
"2:4-18 -> 374:2-21",
|
||||
"2:4-18 -> 375:2-21",
|
||||
"2:4-18 -> 376:2-16",
|
||||
"2:4-18 -> 377:2-16",
|
||||
"2:4-18 -> 378:2-16",
|
||||
"2:4-18 -> 379:2-17",
|
||||
"2:4-18 -> 380:2-17",
|
||||
"2:4-18 -> 381:2-15",
|
||||
"2:4-18 -> 382:2-15",
|
||||
"2:4-18 -> 383:2-20",
|
||||
"2:4-18 -> 384:2-40",
|
||||
"2:4-18 -> 385:2-17",
|
||||
"2:4-18 -> 386:2-22",
|
||||
"2:4-18 -> 387:2-24",
|
||||
"2:4-18 -> 388:2-25",
|
||||
"2:4-18 -> 389:2-26",
|
||||
"2:4-18 -> 390:2-20",
|
||||
"2:4-18 -> 391:2-29",
|
||||
"2:4-18 -> 392:2-30",
|
||||
"2:4-18 -> 393:2-40",
|
||||
"2:4-18 -> 394:2-36",
|
||||
"2:4-18 -> 395:2-29",
|
||||
"2:4-18 -> 396:2-24",
|
||||
"2:4-18 -> 397:2-32",
|
||||
"2:4-18 -> 398:2-14",
|
||||
"2:4-18 -> 399:2-20",
|
||||
"2:4-18 -> 400:2-18",
|
||||
"2:4-18 -> 401:2-19",
|
||||
"2:4-18 -> 402:2-20",
|
||||
"2:4-18 -> 403:2-16",
|
||||
"2:4-18 -> 404:2-18",
|
||||
"2:4-18 -> 405:2-15",
|
||||
"2:4-18 -> 406:2-21",
|
||||
"2:4-18 -> 407:2-23",
|
||||
"2:4-18 -> 408:2-29",
|
||||
"2:4-18 -> 409:2-27",
|
||||
"2:4-18 -> 410:2-28",
|
||||
"2:4-18 -> 411:2-29",
|
||||
"2:4-18 -> 412:2-25",
|
||||
"2:4-18 -> 413:2-26",
|
||||
"2:4-18 -> 414:2-27",
|
||||
"2:4 -> 415:2",
|
||||
"2:18 -> 416:0",
|
||||
"2:4 -> 418:0",
|
||||
"2:4-18 -> 419:2-26",
|
||||
"2:4-18 -> 420:2-26",
|
||||
"2:4-18 -> 421:2-21",
|
||||
"2:4-18 -> 422:2-21",
|
||||
"2:4-18 -> 423:2-16",
|
||||
"2:4-18 -> 424:2-16",
|
||||
"2:4-18 -> 425:2-16",
|
||||
"2:4-18 -> 426:2-17",
|
||||
"2:4-18 -> 427:2-17",
|
||||
"2:4-18 -> 428:2-15",
|
||||
"2:4-18 -> 429:2-15",
|
||||
"2:4-18 -> 430:2-20",
|
||||
"2:4-18 -> 431:2-40",
|
||||
"2:4-18 -> 432:2-17",
|
||||
"2:4-18 -> 433:2-22",
|
||||
"2:4-18 -> 434:2-24",
|
||||
"2:4-18 -> 435:2-25",
|
||||
"2:4-18 -> 436:2-26",
|
||||
"2:4-18 -> 437:2-20",
|
||||
"2:4-18 -> 438:2-29",
|
||||
"2:4-18 -> 439:2-30",
|
||||
"2:4-18 -> 440:2-40",
|
||||
"2:4-18 -> 441:2-36",
|
||||
"2:4-18 -> 442:2-29",
|
||||
"2:4-18 -> 443:2-24",
|
||||
"2:4-18 -> 444:2-32",
|
||||
"2:4-18 -> 445:2-14",
|
||||
"2:4-18 -> 446:2-20",
|
||||
"2:4-18 -> 447:2-18",
|
||||
"2:4-18 -> 448:2-19",
|
||||
"2:4-18 -> 449:2-20",
|
||||
"2:4-18 -> 450:2-16",
|
||||
"2:4-18 -> 451:2-18",
|
||||
"2:4-18 -> 452:2-15",
|
||||
"2:4-18 -> 453:2-21",
|
||||
"2:4-18 -> 454:2-23",
|
||||
"2:4-18 -> 455:2-29",
|
||||
"2:4-18 -> 456:2-27",
|
||||
"2:4-18 -> 457:2-28",
|
||||
"2:4-18 -> 458:2-29",
|
||||
"2:4-18 -> 459:2-25",
|
||||
"2:4-18 -> 460:2-26",
|
||||
"2:4-18 -> 461:2-27",
|
||||
"2:4 -> 462:2",
|
||||
"2:18 -> 463:0",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`source maps for layer rules are not rewritten to point to @tailwind directives 1`] = `
|
||||
Array [
|
||||
"2:6 -> 1:0",
|
||||
"2:6 -> 2:10",
|
||||
"2:25 -> 3:0",
|
||||
"3:8 -> 4:8",
|
||||
"4:10-31 -> 5:10-31",
|
||||
"5:8 -> 6:8",
|
||||
"3:8 -> 7:8",
|
||||
"4:10-31 -> 8:10-31",
|
||||
"5:8 -> 9:8",
|
||||
]
|
||||
`;
|
||||
@ -1,276 +1,278 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('basic', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="animate-spin"></div>
|
||||
<div class="hover:animate-ping"></div>
|
||||
<div class="group-hover:animate-bounce"></div>
|
||||
`,
|
||||
crosscheck(() => {
|
||||
test('basic', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="animate-spin"></div>
|
||||
<div class="hover:animate-ping"></div>
|
||||
<div class="group-hover:animate-bounce"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes ping {
|
||||
75%,
|
||||
100% {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.hover\:animate-ping:hover {
|
||||
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
}
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(-25%);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
}
|
||||
50% {
|
||||
transform: none;
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
.group:hover .group-hover\:animate-bounce {
|
||||
animation: bounce 1s infinite;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('custom', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="animate-one"></div>` }],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
one: { to: { transform: 'rotate(360deg)' } },
|
||||
},
|
||||
animation: {
|
||||
one: 'one 2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes one {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes ping {
|
||||
75%,
|
||||
100% {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
.animate-one {
|
||||
animation: one 2s;
|
||||
}
|
||||
}
|
||||
.hover\:animate-ping:hover {
|
||||
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
}
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(-25%);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('custom prefixed', () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: `<div class="tw-animate-one"></div>` }],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
one: { to: { transform: 'rotate(360deg)' } },
|
||||
},
|
||||
animation: {
|
||||
one: 'one 2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes tw-one {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
50% {
|
||||
transform: none;
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
.tw-animate-one {
|
||||
animation: tw-one 2s;
|
||||
}
|
||||
}
|
||||
.group:hover .group-hover\:animate-bounce {
|
||||
animation: bounce 1s infinite;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('custom', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="animate-one"></div>` }],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
one: { to: { transform: 'rotate(360deg)' } },
|
||||
},
|
||||
animation: {
|
||||
one: 'one 2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes one {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.animate-one {
|
||||
animation: one 2s;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('custom prefixed', () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: `<div class="tw-animate-one"></div>` }],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
one: { to: { transform: 'rotate(360deg)' } },
|
||||
},
|
||||
animation: {
|
||||
one: 'one 2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes tw-one {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.tw-animate-one {
|
||||
animation: tw-one 2s;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('multiple', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="animate-multiple"></div>` }],
|
||||
theme: {
|
||||
extend: {
|
||||
animation: {
|
||||
multiple: 'bounce 2s linear, pulse 3s ease-in',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(-25%);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
}
|
||||
50% {
|
||||
transform: none;
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.animate-multiple {
|
||||
animation: bounce 2s linear, pulse 3s ease-in;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('multiple custom', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="animate-multiple"></div>` }],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
one: { to: { transform: 'rotate(360deg)' } },
|
||||
two: { to: { transform: 'scale(1.23)' } },
|
||||
},
|
||||
animation: {
|
||||
multiple: 'one 2s, two 3s',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes one {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes two {
|
||||
to {
|
||||
transform: scale(1.23);
|
||||
}
|
||||
}
|
||||
.animate-multiple {
|
||||
animation: one 2s, two 3s;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('with dots in the name', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="animate-zoom-.5"></div>
|
||||
<div class="animate-zoom-1.5"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
'zoom-.5': { to: { transform: 'scale(0.5)' } },
|
||||
'zoom-1.5': { to: { transform: 'scale(1.5)' } },
|
||||
},
|
||||
animation: {
|
||||
'zoom-.5': 'zoom-.5 2s',
|
||||
'zoom-1.5': 'zoom-1.5 2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes zoom-\.5 {
|
||||
to {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
.animate-zoom-\.5 {
|
||||
animation: zoom-\.5 2s;
|
||||
}
|
||||
@keyframes zoom-1\.5 {
|
||||
to {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
.animate-zoom-1\.5 {
|
||||
animation: zoom-1\.5 2s;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('with dots in the name and prefix', () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="tw-animate-zoom-.5"></div>
|
||||
<div class="tw-animate-zoom-1.5"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
'zoom-.5': { to: { transform: 'scale(0.5)' } },
|
||||
'zoom-1.5': { to: { transform: 'scale(1.5)' } },
|
||||
},
|
||||
animation: {
|
||||
'zoom-.5': 'zoom-.5 2s',
|
||||
'zoom-1.5': 'zoom-1.5 2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes tw-zoom-\.5 {
|
||||
to {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
.tw-animate-zoom-\.5 {
|
||||
animation: tw-zoom-\.5 2s;
|
||||
}
|
||||
@keyframes tw-zoom-1\.5 {
|
||||
to {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
.tw-animate-zoom-1\.5 {
|
||||
animation: tw-zoom-1\.5 2s;
|
||||
}
|
||||
`)
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('multiple', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="animate-multiple"></div>` }],
|
||||
theme: {
|
||||
extend: {
|
||||
animation: {
|
||||
multiple: 'bounce 2s linear, pulse 3s ease-in',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(-25%);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
}
|
||||
50% {
|
||||
transform: none;
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.animate-multiple {
|
||||
animation: bounce 2s linear, pulse 3s ease-in;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('multiple custom', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="animate-multiple"></div>` }],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
one: { to: { transform: 'rotate(360deg)' } },
|
||||
two: { to: { transform: 'scale(1.23)' } },
|
||||
},
|
||||
animation: {
|
||||
multiple: 'one 2s, two 3s',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes one {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes two {
|
||||
to {
|
||||
transform: scale(1.23);
|
||||
}
|
||||
}
|
||||
.animate-multiple {
|
||||
animation: one 2s, two 3s;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('with dots in the name', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="animate-zoom-.5"></div>
|
||||
<div class="animate-zoom-1.5"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
'zoom-.5': { to: { transform: 'scale(0.5)' } },
|
||||
'zoom-1.5': { to: { transform: 'scale(1.5)' } },
|
||||
},
|
||||
animation: {
|
||||
'zoom-.5': 'zoom-.5 2s',
|
||||
'zoom-1.5': 'zoom-1.5 2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes zoom-\.5 {
|
||||
to {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
.animate-zoom-\.5 {
|
||||
animation: zoom-\.5 2s;
|
||||
}
|
||||
@keyframes zoom-1\.5 {
|
||||
to {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
.animate-zoom-1\.5 {
|
||||
animation: zoom-1\.5 2s;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('with dots in the name and prefix', () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="tw-animate-zoom-.5"></div>
|
||||
<div class="tw-animate-zoom-1.5"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
'zoom-.5': { to: { transform: 'scale(0.5)' } },
|
||||
'zoom-1.5': { to: { transform: 'scale(1.5)' } },
|
||||
},
|
||||
animation: {
|
||||
'zoom-.5': 'zoom-.5 2s',
|
||||
'zoom-1.5': 'zoom-1.5 2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@keyframes tw-zoom-\.5 {
|
||||
to {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
.tw-animate-zoom-\.5 {
|
||||
animation: tw-zoom-\.5 2s;
|
||||
}
|
||||
@keyframes tw-zoom-1\.5 {
|
||||
to {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
.tw-animate-zoom-1\.5 {
|
||||
animation: tw-zoom-1\.5 2s;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
3536
tests/apply.test.js
3536
tests/apply.test.js
File diff suppressed because it is too large
Load Diff
@ -1,470 +1,474 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
test('basic arbitrary properties', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[paint-order:markers]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('basic arbitrary properties', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[paint-order:markers]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[paint-order\:markers\] {
|
||||
paint-order: markers;
|
||||
}
|
||||
`)
|
||||
.\[paint-order\:markers\] {
|
||||
paint-order: markers;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('different arbitrary properties are picked up separately', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[foo:bar] [bar:baz]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
test('different arbitrary properties are picked up separately', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[foo:bar] [bar:baz]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[bar\:baz\] {
|
||||
bar: baz;
|
||||
}
|
||||
.\[bar\:baz\] {
|
||||
bar: baz;
|
||||
}
|
||||
|
||||
.\[foo\:bar\] {
|
||||
foo: bar;
|
||||
}
|
||||
`)
|
||||
.\[foo\:bar\] {
|
||||
foo: bar;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary properties with modifiers', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="dark:lg:hover:[paint-order:markers]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
test('arbitrary properties with modifiers', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="dark:lg:hover:[paint-order:markers]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@media (min-width: 1024px) {
|
||||
.dark\:lg\:hover\:\[paint-order\:markers\]:hover {
|
||||
paint-order: markers;
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@media (min-width: 1024px) {
|
||||
.dark\:lg\:hover\:\[paint-order\:markers\]:hover {
|
||||
paint-order: markers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary properties are sorted after utilities', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="content-none [paint-order:markers] hover:pointer-events-none"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.content-none {
|
||||
--tw-content: none;
|
||||
content: var(--tw-content);
|
||||
}
|
||||
.\[paint-order\:markers\] {
|
||||
paint-order: markers;
|
||||
}
|
||||
.hover\:pointer-events-none:hover {
|
||||
pointer-events: none;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using CSS variables', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[--my-var:auto]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[--my-var\:auto\] {
|
||||
--my-var: auto;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using underscores as spaces', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[--my-var:2px_4px]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[--my-var\:2px_4px\] {
|
||||
--my-var: 2px 4px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using the important modifier', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="![--my-var:2px_4px]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\!\[--my-var\:2px_4px\] {
|
||||
--my-var: 2px 4px !important;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('colons are allowed in quotes', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[content:'foo:bar']"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[content\:\'foo\:bar\'\] {
|
||||
content: 'foo:bar';
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('colons are allowed in braces', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[background-image:url(http://example.com/picture.jpg)]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[background-image\:url\(http\:\/\/example\.com\/picture\.jpg\)\] {
|
||||
background-image: url(http://example.com/picture.jpg);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('invalid class', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[a:b:c:d]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('invalid arbitrary property', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[autoplay:\${autoplay}]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('invalid arbitrary property 2', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`[0:02]`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using fractional spacing values inside theme() function', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="[border:_calc(5vw_-_theme(spacing[2.5]))_double_theme('colors.fuchsia.700')]"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[border\:_calc\(5vw_-_theme\(spacing\[2\.5\]\)\)_double_theme\(\'colors\.fuchsia\.700\'\)\] {
|
||||
border: calc(5vw - 0.625rem) double #a21caf;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using multiple arbitrary props having fractional spacing values', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="[height:_calc(100vh_-_theme(spacing[2.5]))] [box-shadow:_0_calc(theme(spacing[0.5])_*_-1)_theme(colors.red.400)_inset]"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[box-shadow\:_0_calc\(theme\(spacing\[0\.5\]\)_\*_-1\)_theme\(colors\.red\.400\)_inset\] {
|
||||
box-shadow: 0 calc(0.125rem * -1) #f87171 inset;
|
||||
}
|
||||
.\[height\:_calc\(100vh_-_theme\(spacing\[2\.5\]\)\)\] {
|
||||
height: calc(100vh - 0.625rem);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to read theme values in arbitrary properties (without quotes)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="[--a:theme(colors.blue.500)] [color:var(--a)]"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[--a\:theme\(colors\.blue\.500\)\] {
|
||||
--a: #3b82f6;
|
||||
}
|
||||
.\[color\:var\(--a\)\] {
|
||||
color: var(--a);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to read theme values in arbitrary properties (with quotes)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="[color:var(--a)] [--a:theme('colors.blue.500')]"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[--a\:theme\(\'colors\.blue\.500\'\)\] {
|
||||
--a: #3b82f6;
|
||||
}
|
||||
.\[color\:var\(--a\)\] {
|
||||
color: var(--a);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not generate invalid CSS', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="[https://en.wikipedia.org/wiki]"></div>
|
||||
<div class="[http://example.org]"></div>
|
||||
<div class="[http://example]"></div>
|
||||
<div class="[ftp://example]"></div>
|
||||
<div class="[stillworks:/example]"></div>
|
||||
`,
|
||||
|
||||
// NOTE: In this case `stillworks:/example` being generated is not ideal
|
||||
// but it at least doesn't produce invalid CSS when run through prettier
|
||||
// So we can let it through since it is technically valid
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.\[stillworks\:\/example\] {
|
||||
stillworks: /example;
|
||||
}
|
||||
`)
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary properties are sorted after utilities', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="content-none [paint-order:markers] hover:pointer-events-none"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.content-none {
|
||||
--tw-content: none;
|
||||
content: var(--tw-content);
|
||||
}
|
||||
.\[paint-order\:markers\] {
|
||||
paint-order: markers;
|
||||
}
|
||||
.hover\:pointer-events-none:hover {
|
||||
pointer-events: none;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using CSS variables', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[--my-var:auto]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[--my-var\:auto\] {
|
||||
--my-var: auto;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using underscores as spaces', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[--my-var:2px_4px]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[--my-var\:2px_4px\] {
|
||||
--my-var: 2px 4px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using the important modifier', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="![--my-var:2px_4px]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\!\[--my-var\:2px_4px\] {
|
||||
--my-var: 2px 4px !important;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('colons are allowed in quotes', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[content:'foo:bar']"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[content\:\'foo\:bar\'\] {
|
||||
content: 'foo:bar';
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('colons are allowed in braces', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[background-image:url(http://example.com/picture.jpg)]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[background-image\:url\(http\:\/\/example\.com\/picture\.jpg\)\] {
|
||||
background-image: url(http://example.com/picture.jpg);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('invalid class', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[a:b:c:d]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('invalid arbitrary property', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="[autoplay:\${autoplay}]"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('invalid arbitrary property 2', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`[0:02]`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using fractional spacing values inside theme() function', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="[border:_calc(5vw_-_theme(spacing[2.5]))_double_theme('colors.fuchsia.700')]"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[border\:_calc\(5vw_-_theme\(spacing\[2\.5\]\)\)_double_theme\(\'colors\.fuchsia\.700\'\)\] {
|
||||
border: calc(5vw - 0.625rem) double #a21caf;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('using multiple arbitrary props having fractional spacing values', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="[height:_calc(100vh_-_theme(spacing[2.5]))] [box-shadow:_0_calc(theme(spacing[0.5])_*_-1)_theme(colors.red.400)_inset]"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[box-shadow\:_0_calc\(theme\(spacing\[0\.5\]\)_\*_-1\)_theme\(colors\.red\.400\)_inset\] {
|
||||
box-shadow: 0 calc(0.125rem * -1) #f87171 inset;
|
||||
}
|
||||
.\[height\:_calc\(100vh_-_theme\(spacing\[2\.5\]\)\)\] {
|
||||
height: calc(100vh - 0.625rem);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to read theme values in arbitrary properties (without quotes)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="[--a:theme(colors.blue.500)] [color:var(--a)]"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[--a\:theme\(colors\.blue\.500\)\] {
|
||||
--a: #3b82f6;
|
||||
}
|
||||
.\[color\:var\(--a\)\] {
|
||||
color: var(--a);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to read theme values in arbitrary properties (with quotes)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="[color:var(--a)] [--a:theme('colors.blue.500')]"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.\[--a\:theme\(\'colors\.blue\.500\'\)\] {
|
||||
--a: #3b82f6;
|
||||
}
|
||||
.\[color\:var\(--a\)\] {
|
||||
color: var(--a);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not generate invalid CSS', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="[https://en.wikipedia.org/wiki]"></div>
|
||||
<div class="[http://example.org]"></div>
|
||||
<div class="[http://example]"></div>
|
||||
<div class="[ftp://example]"></div>
|
||||
<div class="[stillworks:/example]"></div>
|
||||
`,
|
||||
|
||||
// NOTE: In this case `stillworks:/example` being generated is not ideal
|
||||
// but it at least doesn't produce invalid CSS when run through prettier
|
||||
// So we can let it through since it is technically valid
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.\[stillworks\:\/example\] {
|
||||
stillworks: /example;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,117 +1,119 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import log from '../src/util/log'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
let warn
|
||||
crosscheck(() => {
|
||||
let warn
|
||||
|
||||
beforeEach(() => {
|
||||
warn = jest.spyOn(log, 'warn')
|
||||
})
|
||||
beforeEach(() => {
|
||||
warn = jest.spyOn(log, 'warn')
|
||||
})
|
||||
|
||||
afterEach(() => warn.mockClear())
|
||||
afterEach(() => warn.mockClear())
|
||||
|
||||
it('can block classes matched literally', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="font-bold uppercase sm:hover:text-sm hover:text-sm bg-red-500/50 my-custom-class"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50', 'my-custom-class'],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
.my-custom-class {
|
||||
color: red;
|
||||
it('can block classes matched literally', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="font-bold uppercase sm:hover:text-sm hover:text-sm bg-red-500/50 my-custom-class"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50', 'my-custom-class'],
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
.my-custom-class {
|
||||
color: red;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.sm\:hover\:text-sm:hover {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.my-custom-class {
|
||||
color: red;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.sm\:hover\:text-sm:hover {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('can block classes inside @layer', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="font-bold my-custom-class"></div>`,
|
||||
},
|
||||
],
|
||||
blocklist: ['my-custom-class'],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
.my-custom-class {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`)
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('can block classes inside @layer', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="font-bold my-custom-class"></div>`,
|
||||
},
|
||||
],
|
||||
blocklist: ['my-custom-class'],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
.my-custom-class {
|
||||
color: red;
|
||||
}
|
||||
it('blocklists do NOT support regexes', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-bold bg-[#f00d1e]"></div>` }],
|
||||
blocklist: [/^bg-\[[^]+\]$/],
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
let result = await run('@tailwind utilities', config)
|
||||
|
||||
it('blocklists do NOT support regexes', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-bold bg-[#f00d1e]"></div>` }],
|
||||
blocklist: [/^bg-\[[^]+\]$/],
|
||||
}
|
||||
|
||||
let result = await run('@tailwind utilities', config)
|
||||
|
||||
expect(result.css).toMatchCss(css`
|
||||
.bg-\[\#f00d1e\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(240 13 30 / var(--tw-bg-opacity));
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn.mock.calls.map((x) => x[0])).toEqual(['blocklist-invalid'])
|
||||
})
|
||||
|
||||
it('can block classes generated by the safelist', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-bold"></div>` }],
|
||||
safelist: [{ pattern: /^bg-red-(400|500)$/ }],
|
||||
blocklist: ['bg-red-500'],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.bg-red-400 {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.bg-\[\#f00d1e\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
|
||||
background-color: rgb(240 13 30 / var(--tw-bg-opacity));
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn.mock.calls.map((x) => x[0])).toEqual(['blocklist-invalid'])
|
||||
})
|
||||
|
||||
it('can block classes generated by the safelist', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-bold"></div>` }],
|
||||
safelist: [{ pattern: /^bg-red-(400|500)$/ }],
|
||||
blocklist: ['bg-red-500'],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.bg-red-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,85 +1,37 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
test('collapse adjacent rules', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="custom-component"></div>
|
||||
<div class="sm:custom-component"></div>
|
||||
<div class="md:custom-component"></div>
|
||||
<div class="lg:custom-component"></div>
|
||||
<div class="font-bold"></div>
|
||||
<div class="sm:font-bold"></div>
|
||||
<div class="sm:text-center"></div>
|
||||
<div class="md:font-bold"></div>
|
||||
<div class="md:text-center"></div>
|
||||
<div class="lg:font-bold"></div>
|
||||
<div class="lg:text-center"></div>
|
||||
<div class="some-apply-thing"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [
|
||||
function ({ addVariant }) {
|
||||
addVariant('foo-bar', '@supports (foo: bar)')
|
||||
},
|
||||
],
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('collapse adjacent rules', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="custom-component"></div>
|
||||
<div class="sm:custom-component"></div>
|
||||
<div class="md:custom-component"></div>
|
||||
<div class="lg:custom-component"></div>
|
||||
<div class="font-bold"></div>
|
||||
<div class="sm:font-bold"></div>
|
||||
<div class="sm:text-center"></div>
|
||||
<div class="md:font-bold"></div>
|
||||
<div class="md:text-center"></div>
|
||||
<div class="lg:font-bold"></div>
|
||||
<div class="lg:text-center"></div>
|
||||
<div class="some-apply-thing"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [
|
||||
function ({ addVariant }) {
|
||||
addVariant('foo-bar', '@supports (foo: bar)')
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Gilroy';
|
||||
src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
|
||||
}
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@layer base {
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
src: url('/fonts/Poppins.woff2') format('woff2'), url('/fonts/Poppins.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Proxima Nova';
|
||||
src: url('/fonts/ProximaNova.woff2') format('woff2'),
|
||||
url('/fonts/ProximaNova.woff') format('woff');
|
||||
}
|
||||
}
|
||||
.foo,
|
||||
.bar {
|
||||
color: black;
|
||||
}
|
||||
.foo,
|
||||
.bar {
|
||||
font-weight: 700;
|
||||
}
|
||||
.some-apply-thing {
|
||||
@apply foo-bar:md:text-black foo-bar:md:font-bold foo-bar:text-black foo-bar:font-bold md:font-bold md:text-black;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
src: url('/fonts/Poppins.woff2') format('woff2'), url('/fonts/Poppins.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Proxima Nova';
|
||||
src: url('/fonts/ProximaNova.woff2') format('woff2'),
|
||||
url('/fonts/ProximaNova.woff') format('woff');
|
||||
}
|
||||
${defaults}
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
|
||||
@ -91,29 +43,72 @@ test('collapse adjacent rules', () => {
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@layer base {
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
src: url('/fonts/Poppins.woff2') format('woff2'),
|
||||
url('/fonts/Poppins.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Proxima Nova';
|
||||
src: url('/fonts/ProximaNova.woff2') format('woff2'),
|
||||
url('/fonts/ProximaNova.woff') format('woff');
|
||||
}
|
||||
}
|
||||
.foo,
|
||||
.bar {
|
||||
color: black;
|
||||
}
|
||||
.foo,
|
||||
.bar {
|
||||
font-weight: 700;
|
||||
}
|
||||
@supports (foo: bar) {
|
||||
.some-apply-thing {
|
||||
font-weight: 700;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
||||
}
|
||||
.some-apply-thing {
|
||||
@apply foo-bar:md:text-black foo-bar:md:font-bold foo-bar:text-black foo-bar:font-bold md:font-bold md:text-black;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.some-apply-thing {
|
||||
font-weight: 700;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
src: url('/fonts/Poppins.woff2') format('woff2'),
|
||||
url('/fonts/Poppins.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Proxima Nova';
|
||||
src: url('/fonts/ProximaNova.woff2') format('woff2'),
|
||||
url('/fonts/ProximaNova.woff') format('woff');
|
||||
}
|
||||
${defaults}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Gilroy';
|
||||
src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
|
||||
}
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.foo,
|
||||
.bar {
|
||||
color: black;
|
||||
font-weight: 700;
|
||||
}
|
||||
@supports (foo: bar) {
|
||||
.some-apply-thing {
|
||||
font-weight: 700;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@supports (foo: bar) {
|
||||
@media (min-width: 768px) {
|
||||
.some-apply-thing {
|
||||
font-weight: 700;
|
||||
@ -121,49 +116,58 @@ test('collapse adjacent rules', () => {
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.sm\:text-center {
|
||||
text-align: center;
|
||||
@supports (foo: bar) {
|
||||
@media (min-width: 768px) {
|
||||
.some-apply-thing {
|
||||
font-weight: 700;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
.sm\:font-bold {
|
||||
font-weight: 700;
|
||||
@media (min-width: 640px) {
|
||||
.sm\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.sm\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:text-center {
|
||||
text-align: center;
|
||||
@media (min-width: 768px) {
|
||||
.md\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.md\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
.md\:font-bold {
|
||||
font-weight: 700;
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.lg\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.lg\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('duplicate url imports does not break rule collapsing', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
test('duplicate url imports does not break rule collapsing', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@import url('https://example.com');
|
||||
@import url('https://example.com');
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
let input = css`
|
||||
@import url('https://example.com');
|
||||
`)
|
||||
@import url('https://example.com');
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@import url('https://example.com');
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,175 +1,177 @@
|
||||
import { run, css, html } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
it('should collapse duplicate declarations with the same units (px)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="example"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.example {
|
||||
height: 100px;
|
||||
height: 200px;
|
||||
}
|
||||
crosscheck(() => {
|
||||
it('should collapse duplicate declarations with the same units (px)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="example"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example {
|
||||
height: 200px;
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.example {
|
||||
height: 100px;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should collapse duplicate declarations with the same units (no unit)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="example"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.example {
|
||||
line-height: 3;
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example {
|
||||
line-height: 2;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not collapse duplicate declarations with the different units', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="example"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.example {
|
||||
height: 100px;
|
||||
height: 50%;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example {
|
||||
height: 100px;
|
||||
height: 50%;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should collapse the duplicate declarations with the same unit, but leave the ones with different units', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="example"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.example {
|
||||
height: 100px;
|
||||
height: 50%;
|
||||
height: 20vh;
|
||||
height: 200px;
|
||||
height: 100%;
|
||||
height: 30vh;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example {
|
||||
height: 200px;
|
||||
height: 100%;
|
||||
height: 30vh;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should collapse the duplicate declarations with the exact same value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="example"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.example {
|
||||
height: var(--value);
|
||||
color: blue;
|
||||
height: var(--value);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example {
|
||||
color: blue;
|
||||
height: var(--value);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should work on a real world example', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="h-available"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.h-available {
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.h-available {
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
}
|
||||
`)
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example {
|
||||
height: 200px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should collapse duplicate declarations with the same units (no unit)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="example"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.example {
|
||||
line-height: 3;
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example {
|
||||
line-height: 2;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not collapse duplicate declarations with the different units', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="example"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.example {
|
||||
height: 100px;
|
||||
height: 50%;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example {
|
||||
height: 100px;
|
||||
height: 50%;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should collapse the duplicate declarations with the same unit, but leave the ones with different units', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="example"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.example {
|
||||
height: 100px;
|
||||
height: 50%;
|
||||
height: 20vh;
|
||||
height: 200px;
|
||||
height: 100%;
|
||||
height: 30vh;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example {
|
||||
height: 200px;
|
||||
height: 100%;
|
||||
height: 30vh;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should collapse the duplicate declarations with the exact same value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="example"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.example {
|
||||
height: var(--value);
|
||||
color: blue;
|
||||
height: var(--value);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.example {
|
||||
color: blue;
|
||||
height: var(--value);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should work on a real world example', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="h-available"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.h-available {
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.h-available {
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,234 +1,236 @@
|
||||
import { run, css, html } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('basic color opacity modifier', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red-500/50"></div>` }],
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('basic color opacity modifier', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red-500/50"></div>` }],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-red-500\/50 {
|
||||
background-color: rgb(239 68 68 / 0.5);
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-red-500\/50 {
|
||||
background-color: rgb(239 68 68 / 0.5);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('colors with slashes are matched first', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red-500/50"></div>` }],
|
||||
theme: {
|
||||
extend: {
|
||||
test('colors with slashes are matched first', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red-500/50"></div>` }],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'red-500/50': '#ff0000',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-red-500\/50 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary color opacity modifier', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red-500/[var(--opacity)]"></div>` }],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-red-500\/\[var\(--opacity\)\] {
|
||||
background-color: rgb(239 68 68 / var(--opacity));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('missing alpha generates nothing', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red-500/"></div>` }],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(``)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary color with opacity from scale', async () => {
|
||||
let config = {
|
||||
content: [{ raw: 'bg-[wheat]/50' }],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-\[wheat\]\/50 {
|
||||
background-color: rgb(245 222 179 / 0.5);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary color with arbitrary opacity', async () => {
|
||||
let config = {
|
||||
content: [{ raw: 'bg-[#bada55]/[0.2]' }],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-\[\#bada55\]\/\[0\.2\] {
|
||||
background-color: rgb(186 218 85 / 0.2);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('undefined theme color with opacity from scale', async () => {
|
||||
let config = {
|
||||
content: [{ raw: 'bg-garbage/50' }],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(``)
|
||||
})
|
||||
})
|
||||
|
||||
test('values not in the opacity config are ignored', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red-500/29"></div>` }],
|
||||
theme: {
|
||||
opacity: {
|
||||
0: '0',
|
||||
25: '0.25',
|
||||
5: '0.5',
|
||||
75: '0.75',
|
||||
100: '1',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(``)
|
||||
})
|
||||
})
|
||||
|
||||
test('function colors are supported', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-blue/50"></div>` }],
|
||||
theme: {
|
||||
colors: {
|
||||
'red-500/50': '#ff0000',
|
||||
blue: ({ opacityValue }) => {
|
||||
return `rgba(var(--colors-blue), ${opacityValue})`
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-red-500\/50 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-blue\/50 {
|
||||
background-color: rgba(var(--colors-blue), 0.5);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary color opacity modifier', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red-500/[var(--opacity)]"></div>` }],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-red-500\/\[var\(--opacity\)\] {
|
||||
background-color: rgb(239 68 68 / var(--opacity));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('missing alpha generates nothing', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red-500/"></div>` }],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(``)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary color with opacity from scale', async () => {
|
||||
let config = {
|
||||
content: [{ raw: 'bg-[wheat]/50' }],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-\[wheat\]\/50 {
|
||||
background-color: rgb(245 222 179 / 0.5);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary color with arbitrary opacity', async () => {
|
||||
let config = {
|
||||
content: [{ raw: 'bg-[#bada55]/[0.2]' }],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-\[\#bada55\]\/\[0\.2\] {
|
||||
background-color: rgb(186 218 85 / 0.2);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('undefined theme color with opacity from scale', async () => {
|
||||
let config = {
|
||||
content: [{ raw: 'bg-garbage/50' }],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(``)
|
||||
})
|
||||
})
|
||||
|
||||
test('values not in the opacity config are ignored', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red-500/29"></div>` }],
|
||||
theme: {
|
||||
opacity: {
|
||||
0: '0',
|
||||
25: '0.25',
|
||||
5: '0.5',
|
||||
75: '0.75',
|
||||
100: '1',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(``)
|
||||
})
|
||||
})
|
||||
|
||||
test('function colors are supported', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-blue/50"></div>` }],
|
||||
theme: {
|
||||
colors: {
|
||||
blue: ({ opacityValue }) => {
|
||||
return `rgba(var(--colors-blue), ${opacityValue})`
|
||||
test('utilities that support any type are supported', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="from-red-500/50"></div>
|
||||
<div class="fill-red-500/25"></div>
|
||||
<div class="placeholder-red-500/75"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fill: (theme) => theme('colors'),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-blue\/50 {
|
||||
background-color: rgba(var(--colors-blue), 0.5);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('utilities that support any type are supported', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="from-red-500/50"></div>
|
||||
<div class="fill-red-500/25"></div>
|
||||
<div class="placeholder-red-500/75"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fill: (theme) => theme('colors'),
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.from-red-500\/50 {
|
||||
--tw-gradient-from: rgb(239 68 68 / 0.5);
|
||||
--tw-gradient-to: rgb(239 68 68 / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.fill-red-500\/25 {
|
||||
fill: rgb(239 68 68 / 0.25);
|
||||
}
|
||||
.placeholder-red-500\/75::placeholder {
|
||||
color: rgb(239 68 68 / 0.75);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('opacity modifier in combination with partial custom properties', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="bg-[hsl(var(--foo),50%,50%)]"></div>
|
||||
<div class="bg-[hsl(123,var(--foo),50%)]"></div>
|
||||
<div class="bg-[hsl(123,50%,var(--foo))]"></div>
|
||||
<div class="bg-[hsl(var(--foo),50%,50%)]/50"></div>
|
||||
<div class="bg-[hsl(123,var(--foo),50%)]/50"></div>
|
||||
<div class="bg-[hsl(123,50%,var(--foo))]/50"></div>
|
||||
<div class="bg-[hsl(var(--foo),var(--bar),var(--baz))]/50"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-\[hsl\(123\2c 50\%\2c var\(--foo\)\)\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(123 50% var(--foo) / var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-\[hsl\(123\2c 50\%\2c var\(--foo\)\)\]\/50 {
|
||||
background-color: hsl(123 50% var(--foo) / 0.5);
|
||||
}
|
||||
.bg-\[hsl\(123\2c var\(--foo\)\2c 50\%\)\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(123 var(--foo) 50% / var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-\[hsl\(123\2c var\(--foo\)\2c 50\%\)\]\/50 {
|
||||
background-color: hsl(123 var(--foo) 50% / 0.5);
|
||||
}
|
||||
.bg-\[hsl\(var\(--foo\)\2c 50\%\2c 50\%\)\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(var(--foo) 50% 50% / var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-\[hsl\(var\(--foo\)\2c 50\%\2c 50\%\)\]\/50 {
|
||||
background-color: hsl(var(--foo) 50% 50% / 0.5);
|
||||
}
|
||||
.bg-\[hsl\(var\(--foo\)\2c var\(--bar\)\2c var\(--baz\)\)\]\/50 {
|
||||
background-color: hsl(var(--foo) var(--bar) var(--baz) / 0.5);
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.from-red-500\/50 {
|
||||
--tw-gradient-from: rgb(239 68 68 / 0.5);
|
||||
--tw-gradient-to: rgb(239 68 68 / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.fill-red-500\/25 {
|
||||
fill: rgb(239 68 68 / 0.25);
|
||||
}
|
||||
.placeholder-red-500\/75::placeholder {
|
||||
color: rgb(239 68 68 / 0.75);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('opacity modifier in combination with partial custom properties', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="bg-[hsl(var(--foo),50%,50%)]"></div>
|
||||
<div class="bg-[hsl(123,var(--foo),50%)]"></div>
|
||||
<div class="bg-[hsl(123,50%,var(--foo))]"></div>
|
||||
<div class="bg-[hsl(var(--foo),50%,50%)]/50"></div>
|
||||
<div class="bg-[hsl(123,var(--foo),50%)]/50"></div>
|
||||
<div class="bg-[hsl(123,50%,var(--foo))]/50"></div>
|
||||
<div class="bg-[hsl(var(--foo),var(--bar),var(--baz))]/50"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-\[hsl\(123\2c 50\%\2c var\(--foo\)\)\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(123 50% var(--foo) / var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-\[hsl\(123\2c 50\%\2c var\(--foo\)\)\]\/50 {
|
||||
background-color: hsl(123 50% var(--foo) / 0.5);
|
||||
}
|
||||
.bg-\[hsl\(123\2c var\(--foo\)\2c 50\%\)\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(123 var(--foo) 50% / var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-\[hsl\(123\2c var\(--foo\)\2c 50\%\)\]\/50 {
|
||||
background-color: hsl(123 var(--foo) 50% / 0.5);
|
||||
}
|
||||
.bg-\[hsl\(var\(--foo\)\2c 50\%\2c 50\%\)\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(var(--foo) 50% 50% / var(--tw-bg-opacity));
|
||||
}
|
||||
.bg-\[hsl\(var\(--foo\)\2c 50\%\2c 50\%\)\]\/50 {
|
||||
background-color: hsl(var(--foo) 50% 50% / 0.5);
|
||||
}
|
||||
.bg-\[hsl\(var\(--foo\)\2c var\(--bar\)\2c var\(--baz\)\)\]\/50 {
|
||||
background-color: hsl(var(--foo) var(--bar) var(--baz) / 0.5);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,89 +1,92 @@
|
||||
import { parseColor, formatColor } from '../src/util/color'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
describe('parseColor', () => {
|
||||
it.each`
|
||||
color | output
|
||||
${'black'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }}
|
||||
${'#0088cc'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
|
||||
${'#08c'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
|
||||
${'#0088cc99'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
|
||||
${'#08c9'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
|
||||
${'rgb(0, 30, 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
|
||||
${'rgba(0, 30, 60, 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: '0.5' }}
|
||||
${'rgb(0 30 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
|
||||
${'rgb(0 30 60 / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: '0.5' }}
|
||||
${'rgb(var(--foo), 30, 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
|
||||
${'rgb(0, var(--foo), 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
|
||||
${'rgb(0, 30, var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
|
||||
${'rgb(0, 30, var(--foo), 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
|
||||
${'rgb(var(--foo), 30, var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
|
||||
${'rgb(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
|
||||
${'rgb(var(--foo) 30 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
|
||||
${'rgb(0 var(--foo) 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
|
||||
${'rgb(0 30 var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
|
||||
${'rgb(0 30 var(--foo) / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
|
||||
${'rgb(var(--foo) 30 var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
|
||||
${'rgb(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
|
||||
${'hsl(0, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0deg, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0rad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0grad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0turn, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
|
||||
${'hsla(0, 30%, 60%, 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsla(0deg, 30%, 60%, 0.5)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsla(0rad, 30%, 60%, 0.5)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsla(0grad, 30%, 60%, 0.5)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsla(0turn, 30%, 60%, 0.5)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(0 30% 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0deg 30% 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0rad 30% 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0grad 30% 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0turn 30% 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(0deg 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(0rad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(0grad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(0turn 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(var(--foo), 30%, 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0, var(--foo), 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
|
||||
${'hsl(0, 30%, var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
|
||||
${'hsl(0, 30%, var(--foo), 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
|
||||
${'hsl(var(--foo), 30%, var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
|
||||
${'hsl(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
|
||||
${'hsl(var(--foo) 30% 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0 var(--foo) 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
|
||||
${'hsl(0 30% var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
|
||||
${'hsl(0 30% var(--foo) / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
|
||||
${'hsl(var(--foo) 30% var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
|
||||
${'hsl(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
|
||||
${'transparent'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: '0' }}
|
||||
`('should parse "$color" to the correct value', ({ color, output }) => {
|
||||
expect(parseColor(color)).toEqual(output)
|
||||
crosscheck(() => {
|
||||
describe('parseColor', () => {
|
||||
it.each`
|
||||
color | output
|
||||
${'black'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }}
|
||||
${'#0088cc'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
|
||||
${'#08c'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
|
||||
${'#0088cc99'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
|
||||
${'#08c9'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
|
||||
${'rgb(0, 30, 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
|
||||
${'rgba(0, 30, 60, 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: '0.5' }}
|
||||
${'rgb(0 30 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
|
||||
${'rgb(0 30 60 / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: '0.5' }}
|
||||
${'rgb(var(--foo), 30, 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
|
||||
${'rgb(0, var(--foo), 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
|
||||
${'rgb(0, 30, var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
|
||||
${'rgb(0, 30, var(--foo), 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
|
||||
${'rgb(var(--foo), 30, var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
|
||||
${'rgb(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
|
||||
${'rgb(var(--foo) 30 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
|
||||
${'rgb(0 var(--foo) 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
|
||||
${'rgb(0 30 var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
|
||||
${'rgb(0 30 var(--foo) / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
|
||||
${'rgb(var(--foo) 30 var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
|
||||
${'rgb(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
|
||||
${'hsl(0, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0deg, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0rad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0grad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0turn, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
|
||||
${'hsla(0, 30%, 60%, 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsla(0deg, 30%, 60%, 0.5)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsla(0rad, 30%, 60%, 0.5)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsla(0grad, 30%, 60%, 0.5)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsla(0turn, 30%, 60%, 0.5)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(0 30% 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0deg 30% 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0rad 30% 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0grad 30% 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0turn 30% 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(0deg 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(0rad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(0grad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(0turn 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
|
||||
${'hsl(var(--foo), 30%, 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0, var(--foo), 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
|
||||
${'hsl(0, 30%, var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
|
||||
${'hsl(0, 30%, var(--foo), 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
|
||||
${'hsl(var(--foo), 30%, var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
|
||||
${'hsl(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
|
||||
${'hsl(var(--foo) 30% 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
|
||||
${'hsl(0 var(--foo) 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
|
||||
${'hsl(0 30% var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
|
||||
${'hsl(0 30% var(--foo) / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
|
||||
${'hsl(var(--foo) 30% var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
|
||||
${'hsl(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
|
||||
${'transparent'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: '0' }}
|
||||
`('should parse "$color" to the correct value', ({ color, output }) => {
|
||||
expect(parseColor(color)).toEqual(output)
|
||||
})
|
||||
|
||||
it.each`
|
||||
color
|
||||
${'var(--my-color)'}
|
||||
${'currentColor'}
|
||||
${'inherit'}
|
||||
${'initial'}
|
||||
${'revert'}
|
||||
${'unset'}
|
||||
`('should return `null` for unparseable color "$color"', ({ color }) => {
|
||||
expect(parseColor(color)).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
it.each`
|
||||
color
|
||||
${'var(--my-color)'}
|
||||
${'currentColor'}
|
||||
${'inherit'}
|
||||
${'initial'}
|
||||
${'revert'}
|
||||
${'unset'}
|
||||
`('should return `null` for unparseable color "$color"', ({ color }) => {
|
||||
expect(parseColor(color)).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatColor', () => {
|
||||
it.each`
|
||||
color | output
|
||||
${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }} | ${'rgb(0 0 0)'}
|
||||
${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }} | ${'rgb(0 136 204)'}
|
||||
${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }} | ${'rgb(0 136 204 / 0.6)'}
|
||||
${{ mode: 'hsl', color: ['0', '0%', '0%'], alpha: undefined }} | ${'hsl(0 0% 0%)'}
|
||||
${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: undefined }} | ${'hsl(0 136% 204%)'}
|
||||
${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: '0.6' }} | ${'hsl(0 136% 204% / 0.6)'}
|
||||
`('should format the color pieces into a proper "$output"', ({ color, output }) => {
|
||||
expect(formatColor(color)).toEqual(output)
|
||||
describe('formatColor', () => {
|
||||
it.each`
|
||||
color | output
|
||||
${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }} | ${'rgb(0 0 0)'}
|
||||
${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }} | ${'rgb(0 136 204)'}
|
||||
${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }} | ${'rgb(0 136 204 / 0.6)'}
|
||||
${{ mode: 'hsl', color: ['0', '0%', '0%'], alpha: undefined }} | ${'hsl(0 0% 0%)'}
|
||||
${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: undefined }} | ${'hsl(0 136% 204%)'}
|
||||
${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: '0.6' }} | ${'hsl(0 136% 204% / 0.6)'}
|
||||
`('should format the color pieces into a proper "$output"', ({ color, output }) => {
|
||||
expect(formatColor(color)).toEqual(output)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,81 +1,83 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
it('should generate the partial selector, if only a partial is used (base layer)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
:root,
|
||||
.a {
|
||||
color: black;
|
||||
}
|
||||
crosscheck(() => {
|
||||
it('should generate the partial selector, if only a partial is used (base layer)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
:root {
|
||||
font-weight: bold;
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
:root,
|
||||
.a {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
/* --- */
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
:root {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
:root,
|
||||
.a {
|
||||
color: black;
|
||||
/* --- */
|
||||
|
||||
:root,
|
||||
.a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
${defaults}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should generate the partial selector, if only a partial is used (utilities layer)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="a"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
:root {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
:root,
|
||||
.a {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
${defaults}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should generate the partial selector, if only a partial is used (utilities layer)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="a"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
:root {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
:root,
|
||||
.a {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
:root {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
:root,
|
||||
.a {
|
||||
color: black;
|
||||
}
|
||||
`)
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
:root {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
:root,
|
||||
.a {
|
||||
color: black;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,25 +1,28 @@
|
||||
import configurePlugins from '../src/util/configurePlugins'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
test('setting a plugin to false removes it', () => {
|
||||
const plugins = ['fontSize', 'display', 'backgroundPosition']
|
||||
crosscheck(() => {
|
||||
test('setting a plugin to false removes it', () => {
|
||||
const plugins = ['fontSize', 'display', 'backgroundPosition']
|
||||
|
||||
const configuredPlugins = configurePlugins({ display: false }, plugins)
|
||||
const configuredPlugins = configurePlugins({ display: false }, plugins)
|
||||
|
||||
expect(configuredPlugins).toEqual(['fontSize', 'backgroundPosition'])
|
||||
})
|
||||
|
||||
test('passing only false removes all plugins', () => {
|
||||
const plugins = ['fontSize', 'display', 'backgroundPosition']
|
||||
|
||||
const configuredPlugins = configurePlugins(false, plugins)
|
||||
|
||||
expect(configuredPlugins).toEqual([])
|
||||
})
|
||||
|
||||
test('passing an array safelists plugins', () => {
|
||||
const plugins = ['fontSize', 'display', 'backgroundPosition']
|
||||
|
||||
const configuredPlugins = configurePlugins(['display'], plugins)
|
||||
|
||||
expect(configuredPlugins).toEqual(['display'])
|
||||
expect(configuredPlugins).toEqual(['fontSize', 'backgroundPosition'])
|
||||
})
|
||||
|
||||
test('passing only false removes all plugins', () => {
|
||||
const plugins = ['fontSize', 'display', 'backgroundPosition']
|
||||
|
||||
const configuredPlugins = configurePlugins(false, plugins)
|
||||
|
||||
expect(configuredPlugins).toEqual([])
|
||||
})
|
||||
|
||||
test('passing an array safelists plugins', () => {
|
||||
const plugins = ['fontSize', 'display', 'backgroundPosition']
|
||||
|
||||
const configuredPlugins = configurePlugins(['display'], plugins)
|
||||
|
||||
expect(configuredPlugins).toEqual(['display'])
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,345 +1,347 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('options are not required', () => {
|
||||
let config = { content: [{ raw: html`<div class="container"></div>` }] }
|
||||
crosscheck(({}) => {
|
||||
test('options are not required', () => {
|
||||
let config = { content: [{ raw: html`<div class="container"></div>` }] }
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
max-width: 640px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('screens can be passed explicitly', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['400px', '500px'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.container {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container {
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('screens are ordered ascending by min-width', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['500px', '400px'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.container {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container {
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('screens are deduplicated by min-width', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
screens: {
|
||||
sm: '576px',
|
||||
md: '768px',
|
||||
'sm-only': { min: '576px', max: '767px' },
|
||||
test('screens can be passed explicitly', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['400px', '500px'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
max-width: 576px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('the container can be centered by default', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('horizontal padding can be included by default', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
padding: '2rem',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('responsive horizontal padding can be included by default', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
screens: {
|
||||
sm: '576px',
|
||||
md: { min: '768px' },
|
||||
lg: { 'min-width': '992px' },
|
||||
xl: { min: '1200px', max: '1600px' },
|
||||
},
|
||||
container: {
|
||||
padding: {
|
||||
DEFAULT: '1rem',
|
||||
sm: '2rem',
|
||||
lg: '4rem',
|
||||
xl: '5rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container {
|
||||
max-width: 576px;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container {
|
||||
max-width: 992px;
|
||||
padding-right: 4rem;
|
||||
padding-left: 4rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
padding-right: 5rem;
|
||||
padding-left: 5rem;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('setting all options at once', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['400px', '500px'],
|
||||
center: true,
|
||||
padding: '2rem',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.container {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container {
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('container can use variants', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="lg:hover:container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['400px', '500px'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:hover\:container:hover {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.lg\:hover\:container:hover {
|
||||
.container {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.lg\:hover\:container:hover {
|
||||
.container {
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('screens are ordered ascending by min-width', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['500px', '400px'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.container {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container {
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('screens are deduplicated by min-width', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
screens: {
|
||||
sm: '576px',
|
||||
md: '768px',
|
||||
'sm-only': { min: '576px', max: '767px' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container {
|
||||
max-width: 576px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('the container can be centered by default', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('horizontal padding can be included by default', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
padding: '2rem',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('responsive horizontal padding can be included by default', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
screens: {
|
||||
sm: '576px',
|
||||
md: { min: '768px' },
|
||||
lg: { 'min-width': '992px' },
|
||||
xl: { min: '1200px', max: '1600px' },
|
||||
},
|
||||
container: {
|
||||
padding: {
|
||||
DEFAULT: '1rem',
|
||||
sm: '2rem',
|
||||
lg: '4rem',
|
||||
xl: '5rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.container {
|
||||
max-width: 576px;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container {
|
||||
max-width: 992px;
|
||||
padding-right: 4rem;
|
||||
padding-left: 4rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
padding-right: 5rem;
|
||||
padding-left: 5rem;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('setting all options at once', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['400px', '500px'],
|
||||
center: true,
|
||||
padding: '2rem',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.container {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.container {
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('container can use variants', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="lg:hover:container"></div>` }],
|
||||
theme: {
|
||||
container: {
|
||||
screens: ['400px', '500px'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind components', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:hover\:container:hover {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
.lg\:hover\:container:hover {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 500px) {
|
||||
.lg\:hover\:container:hover {
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,127 +1,132 @@
|
||||
import { crosscheck, css } from './util/run'
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const postcss = require('postcss')
|
||||
const tailwind = require('../src/index.js')
|
||||
const sharedState = require('../src/lib/sharedState.js')
|
||||
const configPath = path.resolve(__dirname, './context-reuse.tailwind.config.js')
|
||||
const { css } = require('./util/run.js')
|
||||
|
||||
function run(input, config = {}, from = null) {
|
||||
let { currentTestName } = expect.getState()
|
||||
crosscheck(({ stable, oxide }) => {
|
||||
function run(input, config = {}, from = null) {
|
||||
let { currentTestName } = expect.getState()
|
||||
|
||||
from = `${path.resolve(__filename)}?test=${currentTestName}&${from}`
|
||||
from = `${path.resolve(__filename)}?test=${currentTestName}&${from}`
|
||||
|
||||
return postcss(tailwind(config)).process(input, { from })
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
let config = {
|
||||
content: [path.resolve(__dirname, './context-reuse.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
return postcss(tailwind(config)).process(input, { from })
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`)
|
||||
})
|
||||
beforeEach(async () => {
|
||||
let config = {
|
||||
content: [path.resolve(__dirname, './context-reuse.test.html')],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await fs.promises.unlink(configPath)
|
||||
})
|
||||
|
||||
it('re-uses the context across multiple files with the same config', async () => {
|
||||
let results = [
|
||||
await run(`@tailwind utilities;`, configPath, `id=1`),
|
||||
|
||||
// Using @apply directives should still re-use the context
|
||||
// They depend on the config but do not the other way around
|
||||
await run(`body { @apply bg-blue-400; }`, configPath, `id=2`),
|
||||
await run(`body { @apply text-red-400; }`, configPath, `id=3`),
|
||||
await run(`body { @apply mb-4; }`, configPath, `id=4`),
|
||||
]
|
||||
|
||||
let dependencies = results.map((result) => {
|
||||
return result.messages
|
||||
.filter((message) => message.type === 'dependency')
|
||||
.map((message) => message.file)
|
||||
await fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`)
|
||||
})
|
||||
|
||||
// The content files don't have any utilities in them so this should be empty
|
||||
expect(results[0].css).toMatchFormattedCss(css``)
|
||||
afterEach(async () => {
|
||||
await fs.promises.unlink(configPath)
|
||||
})
|
||||
|
||||
// However, @apply is being used so we want to verify that they're being inlined into the CSS rules
|
||||
expect(results[1].css).toMatchFormattedCss(css`
|
||||
body {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
|
||||
}
|
||||
`)
|
||||
oxide.test.todo('re-uses the context across multiple files with the same config')
|
||||
stable.test('re-uses the context across multiple files with the same config', async () => {
|
||||
let results = [
|
||||
await run(`@tailwind utilities;`, configPath, `id=1`),
|
||||
|
||||
expect(results[2].css).toMatchFormattedCss(css`
|
||||
body {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(248 113 113 / var(--tw-text-opacity));
|
||||
}
|
||||
`)
|
||||
// Using @apply directives should still re-use the context
|
||||
// They depend on the config but do not the other way around
|
||||
await run(`body { @apply bg-blue-400; }`, configPath, `id=2`),
|
||||
await run(`body { @apply text-red-400; }`, configPath, `id=3`),
|
||||
await run(`body { @apply mb-4; }`, configPath, `id=4`),
|
||||
]
|
||||
|
||||
expect(results[3].css).toMatchFormattedCss(css`
|
||||
body {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
`)
|
||||
let dependencies = results.map((result) => {
|
||||
return result.messages
|
||||
.filter((message) => message.type === 'dependency')
|
||||
.map((message) => message.file)
|
||||
})
|
||||
|
||||
// Files with @tailwind directives depends on the PostCSS tree, config, AND any content files
|
||||
expect(dependencies[0]).toEqual([
|
||||
path.resolve(__dirname, 'context-reuse.test.html'),
|
||||
path.resolve(__dirname, 'context-reuse.tailwind.config.js'),
|
||||
])
|
||||
// The content files don't have any utilities in them so this should be empty
|
||||
expect(results[0].css).toMatchFormattedCss(css``)
|
||||
|
||||
// @apply depends only on the containing PostCSS tree *and* the config file but no content files
|
||||
// as they cannot affect the outcome of the @apply directives
|
||||
expect(dependencies[1]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
|
||||
// However, @apply is being used so we want to verify that they're being inlined into the CSS rules
|
||||
expect(results[1].css).toMatchFormattedCss(css`
|
||||
body {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
|
||||
}
|
||||
`)
|
||||
|
||||
expect(dependencies[2]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
|
||||
expect(results[2].css).toMatchFormattedCss(css`
|
||||
body {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(248 113 113 / var(--tw-text-opacity));
|
||||
}
|
||||
`)
|
||||
|
||||
expect(dependencies[3]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
|
||||
expect(results[3].css).toMatchFormattedCss(css`
|
||||
body {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
`)
|
||||
|
||||
// And none of this should have resulted in multiple contexts being created
|
||||
expect(sharedState.contextSourcesMap.size).toBe(1)
|
||||
})
|
||||
// Files with @tailwind directives depends on the PostCSS tree, config, AND any content files
|
||||
expect(dependencies[0]).toEqual([
|
||||
path.resolve(__dirname, 'context-reuse.test.html'),
|
||||
path.resolve(__dirname, 'context-reuse.tailwind.config.js'),
|
||||
])
|
||||
|
||||
it('updates layers when any CSS containing @tailwind directives changes', async () => {
|
||||
let result
|
||||
// @apply depends only on the containing PostCSS tree *and* the config file but no content files
|
||||
// as they cannot affect the outcome of the @apply directives
|
||||
expect(dependencies[1]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
|
||||
|
||||
// Compile the initial version once
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
.custom-utility {
|
||||
expect(dependencies[2]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
|
||||
|
||||
expect(dependencies[3]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
|
||||
|
||||
// And none of this should have resulted in multiple contexts being created
|
||||
expect(sharedState.contextSourcesMap.size).toBe(1)
|
||||
})
|
||||
|
||||
oxide.test.todo('updates layers when any CSS containing @tailwind directives changes')
|
||||
stable.test('updates layers when any CSS containing @tailwind directives changes', async () => {
|
||||
let result
|
||||
|
||||
// Compile the initial version once
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
.custom-utility {
|
||||
color: orange;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
result = await run(input, configPath, `id=1`)
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.only\:custom-utility:only-child {
|
||||
color: orange;
|
||||
}
|
||||
}
|
||||
`
|
||||
`)
|
||||
|
||||
result = await run(input, configPath, `id=1`)
|
||||
// Save the file with a change
|
||||
input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
.custom-utility {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.only\:custom-utility:only-child {
|
||||
color: orange;
|
||||
}
|
||||
`)
|
||||
result = await run(input, configPath, `id=1`)
|
||||
|
||||
// Save the file with a change
|
||||
input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
.custom-utility {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.only\:custom-utility:only-child {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
result = await run(input, configPath, `id=1`)
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.only\:custom-utility:only-child {
|
||||
color: blue;
|
||||
}
|
||||
`)
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { env } from '../src/lib/sharedState'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
function customExtractor(content) {
|
||||
let matches = content.match(/class="([^"]+)"/)
|
||||
@ -22,111 +21,117 @@ let expected = css`
|
||||
}
|
||||
`
|
||||
|
||||
let group = env.OXIDE ? describe.skip : describe
|
||||
|
||||
group('modern', () => {
|
||||
test('extract.DEFAULT', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: sharedHtml }],
|
||||
extract: {
|
||||
DEFAULT: customExtractor,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('extract.{extension}', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: sharedHtml }],
|
||||
extract: {
|
||||
html: customExtractor,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('extract function', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: sharedHtml }],
|
||||
extract: customExtractor,
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('raw content with extension', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [
|
||||
{
|
||||
raw: sharedHtml,
|
||||
extension: 'html',
|
||||
crosscheck(({ stable, oxide }) => {
|
||||
describe('modern', () => {
|
||||
oxide.test.todo('extract.DEFAULT')
|
||||
stable.test('extract.DEFAULT', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: sharedHtml }],
|
||||
extract: {
|
||||
DEFAULT: customExtractor,
|
||||
},
|
||||
],
|
||||
extract: {
|
||||
html: () => ['invisible'],
|
||||
},
|
||||
},
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
group('legacy', () => {
|
||||
test('defaultExtractor', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: sharedHtml }],
|
||||
options: {
|
||||
defaultExtractor: customExtractor,
|
||||
oxide.test.todo('extract.{extension}')
|
||||
stable.test('extract.{extension}', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: sharedHtml }],
|
||||
extract: {
|
||||
html: customExtractor,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('extractors array', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: sharedHtml }],
|
||||
options: {
|
||||
extractors: [
|
||||
oxide.test.todo('extract function')
|
||||
stable.test('extract function', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: sharedHtml }],
|
||||
extract: customExtractor,
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
oxide.test.todo('raw content with extension')
|
||||
stable.test('raw content with extension', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [
|
||||
{
|
||||
extractor: customExtractor,
|
||||
extensions: ['html'],
|
||||
raw: sharedHtml,
|
||||
extension: 'html',
|
||||
},
|
||||
],
|
||||
extract: {
|
||||
html: () => ['invisible'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('legacy', () => {
|
||||
oxide.test.todo('defaultExtractor')
|
||||
stable.test('defaultExtractor', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: sharedHtml }],
|
||||
options: {
|
||||
defaultExtractor: customExtractor,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
oxide.test.todo('extractors array')
|
||||
stable.test('extractors array', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: sharedHtml }],
|
||||
options: {
|
||||
extractors: [
|
||||
{
|
||||
extractor: customExtractor,
|
||||
extensions: ['html'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,55 +1,57 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('custom separator', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="md_hover_text-right"></div>
|
||||
<div class="motion-safe_hover_text-center"></div>
|
||||
<div class="dark_focus_text-left"></div>
|
||||
<div class="group-hover_focus-within_text-left"></div>
|
||||
<div class="rtl_active_text-center"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
separator: '_',
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('custom separator', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="md_hover_text-right"></div>
|
||||
<div class="motion-safe_hover_text-center"></div>
|
||||
<div class="dark_focus_text-left"></div>
|
||||
<div class="group-hover_focus-within_text-left"></div>
|
||||
<div class="rtl_active_text-center"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
separator: '_',
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.group:hover .group-hover_focus-within_text-left:focus-within {
|
||||
text-align: left;
|
||||
}
|
||||
[dir='rtl'] .rtl_active_text-center:active {
|
||||
text-align: center;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe_hover_text-center:hover {
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.group:hover .group-hover_focus-within_text-left:focus-within {
|
||||
text-align: left;
|
||||
}
|
||||
[dir='rtl'] .rtl_active_text-center:active {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.dark .dark_focus_text-left:focus {
|
||||
text-align: left;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md_hover_text-right:hover {
|
||||
text-align: right;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe_hover_text-center:hover {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
.dark .dark_focus_text-left:focus {
|
||||
text-align: left;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md_hover_text-right:hover {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('dash is not supported', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
content: [{ raw: 'lg-hover-font-bold' }],
|
||||
separator: '-',
|
||||
}
|
||||
|
||||
return expect(run('@tailwind utilities', config)).rejects.toThrowError(
|
||||
"The '-' character cannot be used as a custom separator in JIT mode due to parsing ambiguity. Please use another character like '_' instead."
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test('dash is not supported', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
content: [{ raw: 'lg-hover-font-bold' }],
|
||||
separator: '-',
|
||||
}
|
||||
|
||||
return expect(run('@tailwind utilities', config)).rejects.toThrowError(
|
||||
"The '-' character cannot be used as a custom separator in JIT mode due to parsing ambiguity. Please use another character like '_' instead."
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,70 +1,72 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { env } from '../src/lib/sharedState'
|
||||
|
||||
let t = env.OXIDE ? test.skip : test
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
function customTransformer(content) {
|
||||
return content.replace(/uppercase/g, 'lowercase')
|
||||
}
|
||||
|
||||
t('transform function', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: html`<div class="uppercase"></div>` }],
|
||||
transform: customTransformer,
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.lowercase {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
t('transform.DEFAULT', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: html`<div class="uppercase"></div>` }],
|
||||
transform: {
|
||||
DEFAULT: customTransformer,
|
||||
crosscheck(({ stable, oxide }) => {
|
||||
oxide.test.todo('transform function')
|
||||
stable.test('transform function', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: html`<div class="uppercase"></div>` }],
|
||||
transform: customTransformer,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.lowercase {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.lowercase {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t('transform.{extension}', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [
|
||||
{ raw: 'blah blah blah', extension: 'html' },
|
||||
{ raw: 'blah blah blah', extension: 'php' },
|
||||
],
|
||||
transform: {
|
||||
html: () => 'uppercase',
|
||||
php: () => 'lowercase',
|
||||
oxide.test.todo('transform.DEFAULT')
|
||||
stable.test('transform.DEFAULT', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [{ raw: html`<div class="uppercase"></div>` }],
|
||||
transform: {
|
||||
DEFAULT: customTransformer,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.lowercase {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.lowercase {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
oxide.test.todo('transform.{extension}')
|
||||
stable.test('transform.{extension}', () => {
|
||||
let config = {
|
||||
content: {
|
||||
files: [
|
||||
{ raw: 'blah blah blah', extension: 'html' },
|
||||
{ raw: 'blah blah blah', extension: 'php' },
|
||||
],
|
||||
transform: {
|
||||
html: () => 'uppercase',
|
||||
php: () => 'lowercase',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.lowercase {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -3,97 +3,34 @@ import path from 'path'
|
||||
import { cjsConfigFile, defaultConfigFile } from '../src/constants'
|
||||
import inTempDirectory from '../jest/runInTempDirectory'
|
||||
|
||||
import { run, html, css, javascript } from './util/run'
|
||||
import { crosscheck, run, html, css, javascript } from './util/run'
|
||||
|
||||
test('it uses the values from the custom config file', () => {
|
||||
let config = require(path.resolve(`${__dirname}/fixtures/custom-config.js`))
|
||||
crosscheck(() => {
|
||||
test('it uses the values from the custom config file', () => {
|
||||
let config = require(path.resolve(`${__dirname}/fixtures/custom-config.js`))
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('custom config can be passed as an object', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mobile:font-bold"></div>` }],
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('custom config path can be passed using `config` property in an object', () => {
|
||||
let config = {
|
||||
config: path.resolve(`${__dirname}/fixtures/custom-config.js`),
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('custom config can be passed under the `config` property', () => {
|
||||
let config = {
|
||||
config: {
|
||||
test('custom config can be passed as an object', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mobile:font-bold"></div>` }],
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('tailwind.config.cjs is picked up by default', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(cjsConfigFile),
|
||||
javascript`module.exports = {
|
||||
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
|
||||
return run('@tailwind utilities').then((result) => {
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
@ -103,23 +40,13 @@ test('tailwind.config.cjs is picked up by default', () => {
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('tailwind.config.js is picked up by default', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(defaultConfigFile),
|
||||
javascript`module.exports = {
|
||||
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
test('custom config path can be passed using `config` property in an object', () => {
|
||||
let config = {
|
||||
config: path.resolve(`${__dirname}/fixtures/custom-config.js`),
|
||||
}
|
||||
|
||||
return run('@tailwind utilities').then((result) => {
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
@ -129,258 +56,333 @@ test('tailwind.config.js is picked up by default', () => {
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('tailwind.config.cjs is picked up by default when passing an empty object', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(cjsConfigFile),
|
||||
javascript`module.exports = {
|
||||
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
|
||||
test('custom config can be passed under the `config` property', () => {
|
||||
let config = {
|
||||
config: {
|
||||
content: [{ raw: html`<div class="mobile:font-bold"></div>` }],
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
|
||||
return run('@tailwind utilities', {}).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('tailwind.config.js is picked up by default when passing an empty object', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(defaultConfigFile),
|
||||
javascript`module.exports = {
|
||||
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
|
||||
return run('@tailwind utilities', {}).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('the default config can be overridden using the presets key', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
|
||||
presets: [
|
||||
{
|
||||
theme: {
|
||||
extend: { minHeight: { secondary: '24px' } },
|
||||
},
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { minHeight: { primary: '48px' } },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.min-h-0 {
|
||||
min-height: 0px;
|
||||
}
|
||||
.min-h-primary {
|
||||
min-height: 48px;
|
||||
}
|
||||
.min-h-secondary {
|
||||
min-height: 24px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('presets can be functions', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
|
||||
presets: [
|
||||
() => ({
|
||||
test('tailwind.config.cjs is picked up by default', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(cjsConfigFile),
|
||||
javascript`module.exports = {
|
||||
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
|
||||
theme: {
|
||||
extend: { minHeight: { secondary: '24px' } },
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
theme: {
|
||||
extend: { minHeight: { primary: '48px' } },
|
||||
},
|
||||
}
|
||||
}`
|
||||
)
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.min-h-0 {
|
||||
min-height: 0px;
|
||||
}
|
||||
.min-h-primary {
|
||||
min-height: 48px;
|
||||
}
|
||||
.min-h-secondary {
|
||||
min-height: 24px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities').then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('the default config can be removed by using an empty presets key in a preset', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
|
||||
presets: [
|
||||
{
|
||||
presets: [],
|
||||
test('tailwind.config.js is picked up by default', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(defaultConfigFile),
|
||||
javascript`module.exports = {
|
||||
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
|
||||
theme: {
|
||||
extend: { minHeight: { secondary: '24px' } },
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
|
||||
return run('@tailwind utilities').then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('tailwind.config.cjs is picked up by default when passing an empty object', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(cjsConfigFile),
|
||||
javascript`module.exports = {
|
||||
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
|
||||
return run('@tailwind utilities', {}).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('tailwind.config.js is picked up by default when passing an empty object', () => {
|
||||
return inTempDirectory(() => {
|
||||
fs.writeFileSync(
|
||||
path.resolve(defaultConfigFile),
|
||||
javascript`module.exports = {
|
||||
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
|
||||
theme: {
|
||||
screens: {
|
||||
mobile: '400px',
|
||||
},
|
||||
},
|
||||
}`
|
||||
)
|
||||
|
||||
return run('@tailwind utilities', {}).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@media (min-width: 400px) {
|
||||
.mobile\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('the default config can be overridden using the presets key', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
|
||||
presets: [
|
||||
{
|
||||
theme: {
|
||||
extend: { minHeight: { secondary: '24px' } },
|
||||
},
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { minHeight: { primary: '48px' } },
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { minHeight: { primary: '48px' } },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.min-h-primary {
|
||||
min-height: 48px;
|
||||
}
|
||||
.min-h-secondary {
|
||||
min-height: 24px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.min-h-0 {
|
||||
min-height: 0px;
|
||||
}
|
||||
.min-h-primary {
|
||||
min-height: 48px;
|
||||
}
|
||||
.min-h-secondary {
|
||||
min-height: 24px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('presets can have their own presets', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red bg-transparent bg-black bg-white"></div>` }],
|
||||
presets: [
|
||||
{
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: { red: '#dd0000' },
|
||||
},
|
||||
test('presets can be functions', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
|
||||
presets: [
|
||||
() => ({
|
||||
theme: {
|
||||
extend: { minHeight: { secondary: '24px' } },
|
||||
},
|
||||
}),
|
||||
],
|
||||
theme: {
|
||||
extend: { minHeight: { primary: '48px' } },
|
||||
},
|
||||
{
|
||||
presets: [
|
||||
{
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
red: '#ff0000',
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.min-h-0 {
|
||||
min-height: 0px;
|
||||
}
|
||||
.min-h-primary {
|
||||
min-height: 48px;
|
||||
}
|
||||
.min-h-secondary {
|
||||
min-height: 24px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('the default config can be removed by using an empty presets key in a preset', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
|
||||
presets: [
|
||||
{
|
||||
presets: [],
|
||||
theme: {
|
||||
extend: { minHeight: { secondary: '24px' } },
|
||||
},
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { minHeight: { primary: '48px' } },
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.min-h-primary {
|
||||
min-height: 48px;
|
||||
}
|
||||
.min-h-secondary {
|
||||
min-height: 24px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('presets can have their own presets', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red bg-transparent bg-black bg-white"></div>` }],
|
||||
presets: [
|
||||
{
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: { red: '#dd0000' },
|
||||
},
|
||||
},
|
||||
{
|
||||
presets: [
|
||||
{
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
red: '#ff0000',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
black: 'black',
|
||||
red: '#ee0000',
|
||||
},
|
||||
backgroundColor: (theme) => theme('colors'),
|
||||
},
|
||||
},
|
||||
corePlugins: ['backgroundColor'],
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { colors: { white: 'white' } },
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-black {
|
||||
background-color: black;
|
||||
}
|
||||
.bg-red {
|
||||
background-color: #ee0000;
|
||||
}
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('function presets can be mixed with object presets', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red bg-transparent bg-black bg-white"></div>` }],
|
||||
presets: [
|
||||
() => ({
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: { red: '#dd0000' },
|
||||
},
|
||||
}),
|
||||
{
|
||||
presets: [
|
||||
() => ({
|
||||
presets: [],
|
||||
theme: {
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
red: '#ff0000',
|
||||
black: 'black',
|
||||
red: '#ee0000',
|
||||
},
|
||||
backgroundColor: (theme) => theme('colors'),
|
||||
},
|
||||
}),
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
black: 'black',
|
||||
red: '#ee0000',
|
||||
},
|
||||
backgroundColor: (theme) => theme('colors'),
|
||||
},
|
||||
corePlugins: ['backgroundColor'],
|
||||
},
|
||||
corePlugins: ['backgroundColor'],
|
||||
],
|
||||
theme: {
|
||||
extend: { colors: { white: 'white' } },
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { colors: { white: 'white' } },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-black {
|
||||
background-color: black;
|
||||
}
|
||||
.bg-red {
|
||||
background-color: #ee0000;
|
||||
}
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-black {
|
||||
background-color: black;
|
||||
}
|
||||
.bg-red {
|
||||
background-color: #ee0000;
|
||||
}
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('function presets can be mixed with object presets', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-red bg-transparent bg-black bg-white"></div>` }],
|
||||
presets: [
|
||||
() => ({
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: { red: '#dd0000' },
|
||||
},
|
||||
}),
|
||||
{
|
||||
presets: [
|
||||
() => ({
|
||||
presets: [],
|
||||
theme: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
red: '#ff0000',
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
black: 'black',
|
||||
red: '#ee0000',
|
||||
},
|
||||
backgroundColor: (theme) => theme('colors'),
|
||||
},
|
||||
},
|
||||
corePlugins: ['backgroundColor'],
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: { colors: { white: 'white' } },
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-black {
|
||||
background-color: black;
|
||||
}
|
||||
.bg-red {
|
||||
background-color: #ee0000;
|
||||
}
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,123 +1,125 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
it('should be possible to use the darkMode "class" mode', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
crosscheck(() => {
|
||||
it('should be possible to use the darkMode "class" mode', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.dark .dark\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to change the class name', () => {
|
||||
let config = {
|
||||
darkMode: ['class', '.test-dark'],
|
||||
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.test-dark .dark\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to use the darkMode "media" mode', () => {
|
||||
let config = {
|
||||
darkMode: 'media',
|
||||
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:font-bold {
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.dark .dark\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should default to the `media` mode when no mode is provided', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
it('should be possible to change the class name', () => {
|
||||
let config = {
|
||||
darkMode: ['class', '.test-dark'],
|
||||
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:font-bold {
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.test-dark .dark\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should default to the `media` mode when mode is set to `false`', () => {
|
||||
let config = {
|
||||
darkMode: false,
|
||||
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
it('should be possible to use the darkMode "media" mode', () => {
|
||||
let config = {
|
||||
darkMode: 'media',
|
||||
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:font-bold {
|
||||
font-weight: 700;
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should default to the `media` mode when no mode is provided', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should default to the `media` mode when mode is set to `false`', () => {
|
||||
let config = {
|
||||
darkMode: false,
|
||||
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import config from '../src/public/default-config'
|
||||
import configStub from '../stubs/defaultConfig.stub.js'
|
||||
|
||||
test.todo('remove mutation from these tests so we can run against both engines')
|
||||
|
||||
test('the default config matches the stub', () => {
|
||||
expect(config).toEqual(configStub)
|
||||
})
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import theme from '../src/public/default-theme'
|
||||
import configStub from '../stubs/defaultConfig.stub.js'
|
||||
|
||||
test.todo('remove mutation from these tests so we can run against both engines')
|
||||
|
||||
test('the default theme matches the stub', () => {
|
||||
expect(theme).toEqual(configStub.theme)
|
||||
})
|
||||
|
||||
@ -1,128 +1,130 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
it('should warn when we detect nested css', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="nested"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
.nested {
|
||||
.example {
|
||||
}
|
||||
crosscheck(() => {
|
||||
it('should warn when we detect nested css', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="nested"></div>` }],
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.messages).toHaveLength(1)
|
||||
expect(result.messages).toMatchObject([
|
||||
{
|
||||
type: 'warning',
|
||||
text: [
|
||||
'Nested CSS was detected, but CSS nesting has not been configured correctly.',
|
||||
'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
|
||||
'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
|
||||
].join('\n'),
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('should not warn when we detect nested css inside css @layer rules', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="underline"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@layer tw-base, tw-components, tw-utilities;
|
||||
@layer tw-utilities {
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@layer tw-base, tw-components, tw-utilities;
|
||||
@layer tw-utilities {
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
.nested {
|
||||
.example {
|
||||
}
|
||||
}
|
||||
`)
|
||||
expect(result.messages).toHaveLength(0)
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.messages).toHaveLength(1)
|
||||
expect(result.messages).toMatchObject([
|
||||
{
|
||||
type: 'warning',
|
||||
text: [
|
||||
'Nested CSS was detected, but CSS nesting has not been configured correctly.',
|
||||
'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
|
||||
'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
|
||||
].join('\n'),
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should warn when we detect namespaced @tailwind at rules', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="text-center"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
.namespace {
|
||||
@tailwind utilities;
|
||||
it('should not warn when we detect nested css inside css @layer rules', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="underline"></div>` }],
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.messages).toHaveLength(1)
|
||||
expect(result.messages).toMatchObject([
|
||||
{
|
||||
type: 'warning',
|
||||
text: [
|
||||
'Nested @tailwind rules were detected, but are not supported.',
|
||||
"Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
|
||||
'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
|
||||
].join('\n'),
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('should not warn when nesting a single rule inside a media query', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="nested"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.nested {
|
||||
let input = css`
|
||||
@layer tw-base, tw-components, tw-utilities;
|
||||
@layer tw-utilities {
|
||||
@tailwind utilities;
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.messages).toHaveLength(0)
|
||||
expect(result.messages).toEqual([])
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@layer tw-base, tw-components, tw-utilities;
|
||||
@layer tw-utilities {
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
}
|
||||
`)
|
||||
expect(result.messages).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should only warn for the first detected nesting ', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="nested other"></div>` }],
|
||||
}
|
||||
it('should warn when we detect namespaced @tailwind at rules', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="text-center"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
let input = css`
|
||||
.namespace {
|
||||
@tailwind utilities;
|
||||
}
|
||||
`
|
||||
|
||||
.nested {
|
||||
.example {
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.messages).toHaveLength(1)
|
||||
expect(result.messages).toMatchObject([
|
||||
{
|
||||
type: 'warning',
|
||||
text: [
|
||||
'Nested @tailwind rules were detected, but are not supported.',
|
||||
"Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
|
||||
'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
|
||||
].join('\n'),
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('should not warn when nesting a single rule inside a media query', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="nested"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.nested {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.messages).toHaveLength(0)
|
||||
expect(result.messages).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
it('should only warn for the first detected nesting ', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="nested other"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
.nested {
|
||||
.example {
|
||||
}
|
||||
|
||||
.other {
|
||||
}
|
||||
}
|
||||
|
||||
.other {
|
||||
.example {
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
.other {
|
||||
.example {
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.messages).toHaveLength(1)
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.messages).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import escapeClassName from '../src/util/escapeClassName'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
test('invalid characters are escaped', () => {
|
||||
expect(escapeClassName('w:_$-1/2')).toEqual('w\\:_\\$-1\\/2')
|
||||
crosscheck(() => {
|
||||
test('invalid characters are escaped', () => {
|
||||
expect(escapeClassName('w:_$-1/2')).toEqual('w\\:_\\$-1\\/2')
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,206 +1,208 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('experimental universal selector improvements (box-shadow)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
content: [{ raw: html`<div class="resize shadow"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('experimental universal selector improvements (box-shadow)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
content: [{ raw: html`<div class="resize shadow"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.shadow {
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
}
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.shadow {
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
}
|
||||
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
|
||||
0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('experimental universal selector improvements (pseudo hover)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
content: [{ raw: html`<div class="resize hover:shadow"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.hover\:shadow {
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
}
|
||||
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.hover\:shadow:hover {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
|
||||
0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('experimental universal selector improvements (multiple classes: group)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
content: [{ raw: html`<div class="resize group-hover:shadow"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.group-hover\:shadow {
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
}
|
||||
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.group:hover .group-hover\:shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
|
||||
0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('experimental universal selector improvements (child selectors: divide-y)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
content: [{ raw: html`<div class="resize divide-y"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('experimental universal selector improvements (hover:divide-y)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
content: [{ raw: html`<div class="resize hover:divide-y"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.hover\:divide-y:hover > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('experimental universal selector improvements (#app important)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
important: '#app',
|
||||
content: [{ raw: html`<div class="resize divide-y shadow"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.shadow {
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
}
|
||||
|
||||
#app .resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
#app .divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
|
||||
#app .shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
|
||||
0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
`)
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
|
||||
0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('experimental universal selector improvements (pseudo hover)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
content: [{ raw: html`<div class="resize hover:shadow"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.hover\:shadow {
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
}
|
||||
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.hover\:shadow:hover {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
|
||||
0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('experimental universal selector improvements (multiple classes: group)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
content: [{ raw: html`<div class="resize group-hover:shadow"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.group-hover\:shadow {
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
}
|
||||
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.group:hover .group-hover\:shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
|
||||
0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('experimental universal selector improvements (child selectors: divide-y)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
content: [{ raw: html`<div class="resize divide-y"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('experimental universal selector improvements (hover:divide-y)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
content: [{ raw: html`<div class="resize hover:divide-y"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.hover\:divide-y:hover > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('experimental universal selector improvements (#app important)', () => {
|
||||
let config = {
|
||||
experimental: 'all',
|
||||
important: '#app',
|
||||
content: [{ raw: html`<div class="resize divide-y shadow"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.shadow {
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
}
|
||||
|
||||
#app .resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
#app .divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
|
||||
#app .shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
|
||||
0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,30 +1,34 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('PHP arrays', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{ raw: html`<h1 class="<?php echo wrap(['class' => "max-w-[16rem]"]); ?>">Hello world</h1>` },
|
||||
],
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('PHP arrays', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<h1 class="<?php echo wrap(['class' => "max-w-[16rem]"]); ?>">Hello world</h1>`,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.max-w-\[16rem\] {
|
||||
max-width: 16rem;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary values with quotes', async () => {
|
||||
let config = { content: [{ raw: html`<div class="content-['hello]']"></div>` }] }
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.content-\[\'hello\]\'\] {
|
||||
--tw-content: 'hello]';
|
||||
content: var(--tw-content);
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.max-w-\[16rem\] {
|
||||
max-width: 16rem;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary values with quotes', async () => {
|
||||
let config = { content: [{ raw: html`<div class="content-['hello]']"></div>` }] }
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.content-\[\'hello\]\'\] {
|
||||
--tw-content: 'hello]';
|
||||
content: var(--tw-content);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,81 +1,84 @@
|
||||
import { crosscheck } from './util/run'
|
||||
import flattenColorPalette from '../src/util/flattenColorPalette'
|
||||
|
||||
test('it flattens nested color objects', () => {
|
||||
expect(
|
||||
flattenColorPalette({
|
||||
crosscheck(() => {
|
||||
test('it flattens nested color objects', () => {
|
||||
expect(
|
||||
flattenColorPalette({
|
||||
purple: 'purple',
|
||||
white: {
|
||||
25: 'rgba(255,255,255,.25)',
|
||||
50: 'rgba(255,255,255,.5)',
|
||||
75: 'rgba(255,255,255,.75)',
|
||||
DEFAULT: '#fff',
|
||||
},
|
||||
red: {
|
||||
1: 'rgb(33,0,0)',
|
||||
2: 'rgb(67,0,0)',
|
||||
3: 'rgb(100,0,0)',
|
||||
},
|
||||
green: {
|
||||
1: 'rgb(0,33,0)',
|
||||
2: 'rgb(0,67,0)',
|
||||
3: 'rgb(0,100,0)',
|
||||
},
|
||||
blue: {
|
||||
1: 'rgb(0,0,33)',
|
||||
2: 'rgb(0,0,67)',
|
||||
3: 'rgb(0,0,100)',
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
purple: 'purple',
|
||||
white: {
|
||||
25: 'rgba(255,255,255,.25)',
|
||||
50: 'rgba(255,255,255,.5)',
|
||||
75: 'rgba(255,255,255,.75)',
|
||||
DEFAULT: '#fff',
|
||||
},
|
||||
red: {
|
||||
1: 'rgb(33,0,0)',
|
||||
2: 'rgb(67,0,0)',
|
||||
3: 'rgb(100,0,0)',
|
||||
},
|
||||
green: {
|
||||
1: 'rgb(0,33,0)',
|
||||
2: 'rgb(0,67,0)',
|
||||
3: 'rgb(0,100,0)',
|
||||
},
|
||||
blue: {
|
||||
1: 'rgb(0,0,33)',
|
||||
2: 'rgb(0,0,67)',
|
||||
3: 'rgb(0,0,100)',
|
||||
},
|
||||
'white-25': 'rgba(255,255,255,.25)',
|
||||
'white-50': 'rgba(255,255,255,.5)',
|
||||
'white-75': 'rgba(255,255,255,.75)',
|
||||
white: '#fff',
|
||||
'red-1': 'rgb(33,0,0)',
|
||||
'red-2': 'rgb(67,0,0)',
|
||||
'red-3': 'rgb(100,0,0)',
|
||||
'green-1': 'rgb(0,33,0)',
|
||||
'green-2': 'rgb(0,67,0)',
|
||||
'green-3': 'rgb(0,100,0)',
|
||||
'blue-1': 'rgb(0,0,33)',
|
||||
'blue-2': 'rgb(0,0,67)',
|
||||
'blue-3': 'rgb(0,0,100)',
|
||||
})
|
||||
).toEqual({
|
||||
purple: 'purple',
|
||||
'white-25': 'rgba(255,255,255,.25)',
|
||||
'white-50': 'rgba(255,255,255,.5)',
|
||||
'white-75': 'rgba(255,255,255,.75)',
|
||||
white: '#fff',
|
||||
'red-1': 'rgb(33,0,0)',
|
||||
'red-2': 'rgb(67,0,0)',
|
||||
'red-3': 'rgb(100,0,0)',
|
||||
'green-1': 'rgb(0,33,0)',
|
||||
'green-2': 'rgb(0,67,0)',
|
||||
'green-3': 'rgb(0,100,0)',
|
||||
'blue-1': 'rgb(0,0,33)',
|
||||
'blue-2': 'rgb(0,0,67)',
|
||||
'blue-3': 'rgb(0,0,100)',
|
||||
})
|
||||
})
|
||||
|
||||
test('it flattens deeply nested color objects', () => {
|
||||
expect(
|
||||
flattenColorPalette({
|
||||
primary: 'purple',
|
||||
secondary: {
|
||||
DEFAULT: 'blue',
|
||||
hover: 'cyan',
|
||||
focus: 'red',
|
||||
},
|
||||
button: {
|
||||
primary: {
|
||||
DEFAULT: 'magenta',
|
||||
hover: 'green',
|
||||
focus: {
|
||||
DEFAULT: 'yellow',
|
||||
variant: 'orange',
|
||||
test('it flattens deeply nested color objects', () => {
|
||||
expect(
|
||||
flattenColorPalette({
|
||||
primary: 'purple',
|
||||
secondary: {
|
||||
DEFAULT: 'blue',
|
||||
hover: 'cyan',
|
||||
focus: 'red',
|
||||
},
|
||||
button: {
|
||||
primary: {
|
||||
DEFAULT: 'magenta',
|
||||
hover: 'green',
|
||||
focus: {
|
||||
DEFAULT: 'yellow',
|
||||
variant: 'orange',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
primary: 'purple',
|
||||
secondary: 'blue',
|
||||
'secondary-hover': 'cyan',
|
||||
'secondary-focus': 'red',
|
||||
'button-primary': 'magenta',
|
||||
'button-primary-hover': 'green',
|
||||
'button-primary-focus': 'yellow',
|
||||
'button-primary-focus-variant': 'orange',
|
||||
})
|
||||
).toEqual({
|
||||
primary: 'purple',
|
||||
secondary: 'blue',
|
||||
'secondary-hover': 'cyan',
|
||||
'secondary-focus': 'red',
|
||||
'button-primary': 'magenta',
|
||||
'button-primary-hover': 'green',
|
||||
'button-primary-focus': 'yellow',
|
||||
'button-primary-focus-variant': 'orange',
|
||||
})
|
||||
|
||||
test('it handles empty objects', () => {
|
||||
expect(flattenColorPalette({})).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
test('it handles empty objects', () => {
|
||||
expect(flattenColorPalette({})).toEqual({})
|
||||
})
|
||||
|
||||
@ -1,356 +1,359 @@
|
||||
import { finalizeSelector } from '../src/util/formatVariantSelector'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
it('should be possible to add a simple variant to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'hover:text-center'
|
||||
crosscheck(() => {
|
||||
it('should be possible to add a simple variant to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'hover:text-center'
|
||||
|
||||
let formats = [{ format: '&:hover', isArbitraryVariant: false }]
|
||||
let formats = [{ format: '&:hover', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual('.hover\\:text-center:hover')
|
||||
})
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual('.hover\\:text-center:hover')
|
||||
})
|
||||
|
||||
it('should be possible to add a multiple simple variants to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'focus:hover:text-center'
|
||||
it('should be possible to add a multiple simple variants to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'focus:hover:text-center'
|
||||
|
||||
let formats = [
|
||||
{ format: '&:hover', isArbitraryVariant: false },
|
||||
{ format: '&:focus', isArbitraryVariant: false },
|
||||
]
|
||||
let formats = [
|
||||
{ format: '&:hover', isArbitraryVariant: false },
|
||||
{ format: '&:focus', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.focus\\:hover\\:text-center:hover:focus'
|
||||
)
|
||||
})
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.focus\\:hover\\:text-center:hover:focus'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add a simple variant to a selector containing escaped parts', () => {
|
||||
let selector = '.bg-\\[rgba\\(0\\,0\\,0\\)\\]'
|
||||
let candidate = 'hover:bg-[rgba(0,0,0)]'
|
||||
|
||||
let formats = [{ format: '&:hover', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add a simple variant to a selector containing escaped parts (escape is slightly different)', () => {
|
||||
let selector = '.bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]'
|
||||
let candidate = 'hover:bg-[rgba(0,0,0)]'
|
||||
|
||||
let formats = [{ format: '&:hover', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add a simple variant to a more complex selector', () => {
|
||||
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'hover:space-x-4'
|
||||
|
||||
let formats = [{ format: '&:hover', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add multiple simple variants to a more complex selector', () => {
|
||||
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'disabled:focus:hover:space-x-4'
|
||||
|
||||
let formats = [
|
||||
{ format: '&:hover', isArbitraryVariant: false },
|
||||
{ format: '&:focus', isArbitraryVariant: false },
|
||||
{ format: '&:disabled', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.disabled\\:focus\\:hover\\:space-x-4:hover:focus:disabled > :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add a single merge variant to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'group-hover:text-center'
|
||||
|
||||
let formats = [{ format: ':merge(.group):hover &', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover .group-hover\\:text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add multiple merge variants to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'group-focus:group-hover:text-center'
|
||||
|
||||
let formats = [
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.group):focus &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:focus:hover .group-focus\\:group-hover\\:text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add a single merge variant to a more complex selector', () => {
|
||||
let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'group-hover:space-x-4'
|
||||
|
||||
let formats = [{ format: ':merge(.group):hover &', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover .group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add multiple merge variants to a more complex selector', () => {
|
||||
let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'group-focus:group-hover:space-x-4'
|
||||
|
||||
let formats = [
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.group):focus &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:focus:hover .group-focus\\:group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add multiple unique merge variants to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'peer-focus:group-hover:text-center'
|
||||
|
||||
let formats = [
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.peer):focus ~ &' },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.peer:focus ~ .group:hover .peer-focus\\:group-hover\\:text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add multiple unique merge variants to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'group-hover:peer-focus:text-center'
|
||||
|
||||
let formats = [
|
||||
{ format: ':merge(.peer):focus ~ &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover .peer:focus ~ .group-hover\\:peer-focus\\:text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to use multiple :merge() calls with different "arguments"', () => {
|
||||
let selector = '.foo'
|
||||
let candidate = 'peer-focus:group-focus:peer-hover:group-hover:foo'
|
||||
|
||||
let formats = [
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.peer):hover ~ &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.group):focus &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.peer):focus ~ &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.peer:focus:hover ~ .group:focus:hover .peer-focus\\:group-focus\\:peer-hover\\:group-hover\\:foo'
|
||||
)
|
||||
})
|
||||
|
||||
it('group hover and prose headings combination', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'group-hover:prose-headings:text-center'
|
||||
let formats = [
|
||||
{ format: ':where(&) :is(h1, h2, h3, h4)', isArbitraryVariant: false }, // Prose Headings
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group Hover
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover :where(.group-hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4)'
|
||||
)
|
||||
})
|
||||
|
||||
it('group hover and prose headings combination flipped', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'prose-headings:group-hover:text-center'
|
||||
let formats = [
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group Hover
|
||||
{ format: ':where(&) :is(h1, h2, h3, h4)', isArbitraryVariant: false }, // Prose Headings
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
':where(.group:hover .prose-headings\\:group-hover\\:text-center) :is(h1, h2, h3, h4)'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to handle a complex utility', () => {
|
||||
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'peer-disabled:peer-first-child:group-hover:group-focus:focus:hover:space-x-4'
|
||||
let formats = [
|
||||
{ format: '&:hover', isArbitraryVariant: false }, // Hover
|
||||
{ format: '&:focus', isArbitraryVariant: false }, // Focus
|
||||
{ format: ':merge(.group):focus &', isArbitraryVariant: false }, // Group focus
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group hover
|
||||
{ format: ':merge(.peer):first-child ~ &', isArbitraryVariant: false }, // Peer first-child
|
||||
{ format: ':merge(.peer):disabled ~ &', isArbitraryVariant: false }, // Peer disabled
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.peer:disabled:first-child ~ .group:hover:focus .peer-disabled\\:peer-first-child\\:group-hover\\:group-focus\\:focus\\:hover\\:space-x-4:hover:focus > :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
|
||||
it('should match base utilities that are prefixed', () => {
|
||||
let context = { tailwindConfig: { prefix: 'tw-' } }
|
||||
let selector = '.tw-text-center'
|
||||
let candidate = 'tw-text-center'
|
||||
let formats = []
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual('.tw-text-center')
|
||||
})
|
||||
|
||||
it('should prefix classes from variants', () => {
|
||||
let context = { tailwindConfig: { prefix: 'tw-' } }
|
||||
let selector = '.tw-text-center'
|
||||
let candidate = 'foo:tw-text-center'
|
||||
let formats = [{ format: '.foo &', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
|
||||
'.tw-foo .foo\\:tw-text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('should not prefix classes from arbitrary variants', () => {
|
||||
let context = { tailwindConfig: { prefix: 'tw-' } }
|
||||
let selector = '.tw-text-center'
|
||||
let candidate = '[.foo_&]:tw-text-center'
|
||||
let formats = [{ format: '.foo &', isArbitraryVariant: true }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
|
||||
'.foo .\\[\\.foo_\\&\\]\\:tw-text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('Merged selectors with mixed combinators uses the first one', () => {
|
||||
// This isn't explicitly specced behavior but it is how it works today
|
||||
|
||||
let selector = '.text-center'
|
||||
let candidate = 'text-center'
|
||||
let formats = [
|
||||
{ format: ':merge(.group):focus > &', isArbitraryVariant: true },
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: true },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover:focus > .text-center'
|
||||
)
|
||||
})
|
||||
|
||||
describe('real examples', () => {
|
||||
it('example a', () => {
|
||||
let selector = '.placeholder-red-500::placeholder'
|
||||
let candidate = 'hover:placeholder-red-500'
|
||||
it('should be possible to add a simple variant to a selector containing escaped parts', () => {
|
||||
let selector = '.bg-\\[rgba\\(0\\,0\\,0\\)\\]'
|
||||
let candidate = 'hover:bg-[rgba(0,0,0)]'
|
||||
|
||||
let formats = [{ format: '&:hover', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.hover\\:placeholder-red-500:hover::placeholder'
|
||||
'.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
|
||||
)
|
||||
})
|
||||
|
||||
it('example b', () => {
|
||||
it('should be possible to add a simple variant to a selector containing escaped parts (escape is slightly different)', () => {
|
||||
let selector = '.bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]'
|
||||
let candidate = 'hover:bg-[rgba(0,0,0)]'
|
||||
|
||||
let formats = [{ format: '&:hover', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add a simple variant to a more complex selector', () => {
|
||||
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'group-hover:hover:space-x-4'
|
||||
let candidate = 'hover:space-x-4'
|
||||
|
||||
let formats = [{ format: '&:hover', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add multiple simple variants to a more complex selector', () => {
|
||||
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'disabled:focus:hover:space-x-4'
|
||||
|
||||
let formats = [
|
||||
{ format: '&:hover', isArbitraryVariant: false },
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
{ format: '&:focus', isArbitraryVariant: false },
|
||||
{ format: '&:disabled', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover .group-hover\\:hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
|
||||
'.disabled\\:focus\\:hover\\:space-x-4:hover:focus:disabled > :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
|
||||
it('should work for group-hover and class dark mode combinations', () => {
|
||||
it('should be possible to add a single merge variant to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'dark:group-hover:text-center'
|
||||
let candidate = 'group-hover:text-center'
|
||||
|
||||
let formats = [{ format: ':merge(.group):hover &', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover .group-hover\\:text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add multiple merge variants to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'group-focus:group-hover:text-center'
|
||||
|
||||
let formats = [
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
{ format: '.dark &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.group):focus &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.dark .group:hover .dark\\:group-hover\\:text-center'
|
||||
'.group:focus:hover .group-focus\\:group-hover\\:text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('should work for group-hover and class dark mode combinations (reversed)', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'group-hover:dark:text-center'
|
||||
it('should be possible to add a single merge variant to a more complex selector', () => {
|
||||
let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'group-hover:space-x-4'
|
||||
|
||||
let formats = [{ format: ':merge(.group):hover &', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover .group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add multiple merge variants to a more complex selector', () => {
|
||||
let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'group-focus:group-hover:space-x-4'
|
||||
|
||||
let formats = [
|
||||
{ format: '.dark &' },
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.group):focus &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:focus:hover .group-focus\\:group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add multiple unique merge variants to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'peer-focus:group-hover:text-center'
|
||||
|
||||
let formats = [
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.peer):focus ~ &' },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.peer:focus ~ .group:hover .peer-focus\\:group-hover\\:text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to add multiple unique merge variants to a simple selector', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'group-hover:peer-focus:text-center'
|
||||
|
||||
let formats = [
|
||||
{ format: ':merge(.peer):focus ~ &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover .dark .group-hover\\:dark\\:text-center'
|
||||
'.group:hover .peer:focus ~ .group-hover\\:peer-focus\\:text-center'
|
||||
)
|
||||
})
|
||||
|
||||
describe('prose-headings', () => {
|
||||
it('should be possible to use hover:prose-headings:text-center', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'hover:prose-headings:text-center'
|
||||
it('should be possible to use multiple :merge() calls with different "arguments"', () => {
|
||||
let selector = '.foo'
|
||||
let candidate = 'peer-focus:group-focus:peer-hover:group-hover:foo'
|
||||
|
||||
let formats = [{ format: ':where(&) :is(h1, h2, h3, h4)' }, { format: '&:hover' }]
|
||||
let formats = [
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.peer):hover ~ &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.group):focus &', isArbitraryVariant: false },
|
||||
{ format: ':merge(.peer):focus ~ &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.peer:focus:hover ~ .group:focus:hover .peer-focus\\:group-focus\\:peer-hover\\:group-hover\\:foo'
|
||||
)
|
||||
})
|
||||
|
||||
it('group hover and prose headings combination', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'group-hover:prose-headings:text-center'
|
||||
let formats = [
|
||||
{ format: ':where(&) :is(h1, h2, h3, h4)', isArbitraryVariant: false }, // Prose Headings
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group Hover
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover :where(.group-hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4)'
|
||||
)
|
||||
})
|
||||
|
||||
it('group hover and prose headings combination flipped', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'prose-headings:group-hover:text-center'
|
||||
let formats = [
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group Hover
|
||||
{ format: ':where(&) :is(h1, h2, h3, h4)', isArbitraryVariant: false }, // Prose Headings
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
':where(.group:hover .prose-headings\\:group-hover\\:text-center) :is(h1, h2, h3, h4)'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to handle a complex utility', () => {
|
||||
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'peer-disabled:peer-first-child:group-hover:group-focus:focus:hover:space-x-4'
|
||||
let formats = [
|
||||
{ format: '&:hover', isArbitraryVariant: false }, // Hover
|
||||
{ format: '&:focus', isArbitraryVariant: false }, // Focus
|
||||
{ format: ':merge(.group):focus &', isArbitraryVariant: false }, // Group focus
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group hover
|
||||
{ format: ':merge(.peer):first-child ~ &', isArbitraryVariant: false }, // Peer first-child
|
||||
{ format: ':merge(.peer):disabled ~ &', isArbitraryVariant: false }, // Peer disabled
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.peer:disabled:first-child ~ .group:hover:focus .peer-disabled\\:peer-first-child\\:group-hover\\:group-focus\\:focus\\:hover\\:space-x-4:hover:focus > :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
|
||||
it('should match base utilities that are prefixed', () => {
|
||||
let context = { tailwindConfig: { prefix: 'tw-' } }
|
||||
let selector = '.tw-text-center'
|
||||
let candidate = 'tw-text-center'
|
||||
let formats = []
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual('.tw-text-center')
|
||||
})
|
||||
|
||||
it('should prefix classes from variants', () => {
|
||||
let context = { tailwindConfig: { prefix: 'tw-' } }
|
||||
let selector = '.tw-text-center'
|
||||
let candidate = 'foo:tw-text-center'
|
||||
let formats = [{ format: '.foo &', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
|
||||
'.tw-foo .foo\\:tw-text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('should not prefix classes from arbitrary variants', () => {
|
||||
let context = { tailwindConfig: { prefix: 'tw-' } }
|
||||
let selector = '.tw-text-center'
|
||||
let candidate = '[.foo_&]:tw-text-center'
|
||||
let formats = [{ format: '.foo &', isArbitraryVariant: true }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
|
||||
'.foo .\\[\\.foo_\\&\\]\\:tw-text-center'
|
||||
)
|
||||
})
|
||||
|
||||
it('Merged selectors with mixed combinators uses the first one', () => {
|
||||
// This isn't explicitly specced behavior but it is how it works today
|
||||
|
||||
let selector = '.text-center'
|
||||
let candidate = 'text-center'
|
||||
let formats = [
|
||||
{ format: ':merge(.group):focus > &', isArbitraryVariant: true },
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: true },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover:focus > .text-center'
|
||||
)
|
||||
})
|
||||
|
||||
describe('real examples', () => {
|
||||
it('example a', () => {
|
||||
let selector = '.placeholder-red-500::placeholder'
|
||||
let candidate = 'hover:placeholder-red-500'
|
||||
|
||||
let formats = [{ format: '&:hover', isArbitraryVariant: false }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
':where(.hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4):hover'
|
||||
'.hover\\:placeholder-red-500:hover::placeholder'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to use prose-headings:hover:text-center', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'prose-headings:hover:text-center'
|
||||
it('example b', () => {
|
||||
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
|
||||
let candidate = 'group-hover:hover:space-x-4'
|
||||
|
||||
let formats = [{ format: '&:hover' }, { format: ':where(&) :is(h1, h2, h3, h4)' }]
|
||||
let formats = [
|
||||
{ format: '&:hover', isArbitraryVariant: false },
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
':where(.prose-headings\\:hover\\:text-center:hover) :is(h1, h2, h3, h4)'
|
||||
'.group:hover .group-hover\\:hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('pseudo elements', () => {
|
||||
it.each`
|
||||
before | after
|
||||
${'&::before'} | ${'&::before'}
|
||||
${'&::before:hover'} | ${'&:hover::before'}
|
||||
${'&:before:hover'} | ${'&:hover:before'}
|
||||
${'&::file-selector-button:hover'} | ${'&::file-selector-button:hover'}
|
||||
${'&:hover::file-selector-button'} | ${'&:hover::file-selector-button'}
|
||||
${'.parent:hover &'} | ${'.parent:hover &'}
|
||||
${'.parent::before &'} | ${'.parent &::before'}
|
||||
${'.parent::before &:hover'} | ${'.parent &:hover::before'}
|
||||
${':where(&::before) :is(h1, h2, h3, h4)'} | ${':where(&) :is(h1, h2, h3, h4)::before'}
|
||||
${':where(&::file-selector-button) :is(h1, h2, h3, h4)'} | ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'}
|
||||
`('should translate "$before" into "$after"', ({ before, after }) => {
|
||||
let result = finalizeSelector('.a', [{ format: before, isArbitraryVariant: false }], {
|
||||
candidate: 'a',
|
||||
it('should work for group-hover and class dark mode combinations', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'dark:group-hover:text-center'
|
||||
|
||||
let formats = [
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
{ format: '.dark &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.dark .group:hover .dark\\:group-hover\\:text-center'
|
||||
)
|
||||
})
|
||||
|
||||
expect(result).toEqual(after.replace('&', '.a'))
|
||||
it('should work for group-hover and class dark mode combinations (reversed)', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'group-hover:dark:text-center'
|
||||
|
||||
let formats = [
|
||||
{ format: '.dark &' },
|
||||
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
|
||||
]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
'.group:hover .dark .group-hover\\:dark\\:text-center'
|
||||
)
|
||||
})
|
||||
|
||||
describe('prose-headings', () => {
|
||||
it('should be possible to use hover:prose-headings:text-center', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'hover:prose-headings:text-center'
|
||||
|
||||
let formats = [{ format: ':where(&) :is(h1, h2, h3, h4)' }, { format: '&:hover' }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
':where(.hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4):hover'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to use prose-headings:hover:text-center', () => {
|
||||
let selector = '.text-center'
|
||||
let candidate = 'prose-headings:hover:text-center'
|
||||
|
||||
let formats = [{ format: '&:hover' }, { format: ':where(&) :is(h1, h2, h3, h4)' }]
|
||||
|
||||
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
|
||||
':where(.prose-headings\\:hover\\:text-center:hover) :is(h1, h2, h3, h4)'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('pseudo elements', () => {
|
||||
it.each`
|
||||
before | after
|
||||
${'&::before'} | ${'&::before'}
|
||||
${'&::before:hover'} | ${'&:hover::before'}
|
||||
${'&:before:hover'} | ${'&:hover:before'}
|
||||
${'&::file-selector-button:hover'} | ${'&::file-selector-button:hover'}
|
||||
${'&:hover::file-selector-button'} | ${'&:hover::file-selector-button'}
|
||||
${'.parent:hover &'} | ${'.parent:hover &'}
|
||||
${'.parent::before &'} | ${'.parent &::before'}
|
||||
${'.parent::before &:hover'} | ${'.parent &:hover::before'}
|
||||
${':where(&::before) :is(h1, h2, h3, h4)'} | ${':where(&) :is(h1, h2, h3, h4)::before'}
|
||||
${':where(&::file-selector-button) :is(h1, h2, h3, h4)'} | ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'}
|
||||
`('should translate "$before" into "$after"', ({ before, after }) => {
|
||||
let result = finalizeSelector('.a', [{ format: before, isArbitraryVariant: false }], {
|
||||
candidate: 'a',
|
||||
})
|
||||
|
||||
expect(result).toEqual(after.replace('&', '.a'))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,47 +1,48 @@
|
||||
import { css } from './util/run'
|
||||
|
||||
import { generateRules } from '../src/lib/generateRules'
|
||||
import resolveConfig from '../src/public/resolve-config'
|
||||
import { createContext } from '../src/lib/setupContextUtils'
|
||||
import { crosscheck, css } from './util/run'
|
||||
|
||||
it('should not generate rules that are incorrect', () => {
|
||||
let config = {
|
||||
plugins: [
|
||||
({ matchVariant }) => {
|
||||
matchVariant('@', (value) => `@container (min-width: ${value})`)
|
||||
},
|
||||
],
|
||||
}
|
||||
let context = createContext(resolveConfig(config))
|
||||
let rules = generateRules(
|
||||
new Set([
|
||||
// Invalid, missing `-`
|
||||
'group[:hover]:underline',
|
||||
|
||||
// Invalid, `-` should not be there
|
||||
'@-[200px]:underline',
|
||||
|
||||
// Valid
|
||||
'group-[:hover]:underline',
|
||||
'@[200px]:underline',
|
||||
]),
|
||||
context
|
||||
)
|
||||
|
||||
// Ensure we only have 2 valid rules
|
||||
expect(rules).toHaveLength(2)
|
||||
|
||||
// Ensure we have the correct values
|
||||
expect(rules[0][1].toString()).toMatchFormattedCss(css`
|
||||
.group:hover .group-\[\:hover\]\:underline {
|
||||
text-decoration-line: underline;
|
||||
crosscheck(() => {
|
||||
it('should not generate rules that are incorrect', () => {
|
||||
let config = {
|
||||
plugins: [
|
||||
({ matchVariant }) => {
|
||||
matchVariant('@', (value) => `@container (min-width: ${value})`)
|
||||
},
|
||||
],
|
||||
}
|
||||
`)
|
||||
expect(rules[1][1].toString()).toMatchFormattedCss(css`
|
||||
@container (min-width: 200px) {
|
||||
.\@\[200px\]\:underline {
|
||||
let context = createContext(resolveConfig(config))
|
||||
let rules = generateRules(
|
||||
new Set([
|
||||
// Invalid, missing `-`
|
||||
'group[:hover]:underline',
|
||||
|
||||
// Invalid, `-` should not be there
|
||||
'@-[200px]:underline',
|
||||
|
||||
// Valid
|
||||
'group-[:hover]:underline',
|
||||
'@[200px]:underline',
|
||||
]),
|
||||
context
|
||||
)
|
||||
|
||||
// Ensure we only have 2 valid rules
|
||||
expect(rules).toHaveLength(2)
|
||||
|
||||
// Ensure we have the correct values
|
||||
expect(rules[0][1].toString()).toMatchFormattedCss(css`
|
||||
.group:hover .group-\[\:hover\]\:underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
}
|
||||
`)
|
||||
`)
|
||||
expect(rules[1][1].toString()).toMatchFormattedCss(css`
|
||||
@container (min-width: 200px) {
|
||||
.\@\[200px\]\:underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,119 +1,122 @@
|
||||
import resolveConfig from '../src/public/resolve-config'
|
||||
import { createContext } from '../src/lib/setupContextUtils'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
it('should generate every possible class, without variants', () => {
|
||||
let config = {}
|
||||
crosscheck(() => {
|
||||
it('should generate every possible class, without variants', () => {
|
||||
let config = {}
|
||||
|
||||
let context = createContext(resolveConfig(config))
|
||||
let classes = context.getClassList()
|
||||
expect(classes).toBeInstanceOf(Array)
|
||||
let context = createContext(resolveConfig(config))
|
||||
let classes = context.getClassList()
|
||||
expect(classes).toBeInstanceOf(Array)
|
||||
|
||||
// Verify we have a `container` for the 'components' section.
|
||||
expect(classes).toContain('container')
|
||||
// Verify we have a `container` for the 'components' section.
|
||||
expect(classes).toContain('container')
|
||||
|
||||
// Verify we handle the DEFAULT case correctly
|
||||
expect(classes).toContain('border')
|
||||
// Verify we handle the DEFAULT case correctly
|
||||
expect(classes).toContain('border')
|
||||
|
||||
// Verify we handle negative values correctly
|
||||
expect(classes).toContain('-inset-1/4')
|
||||
expect(classes).toContain('-m-0')
|
||||
expect(classes).not.toContain('-uppercase')
|
||||
expect(classes).not.toContain('-opacity-50')
|
||||
// Verify we handle negative values correctly
|
||||
expect(classes).toContain('-inset-1/4')
|
||||
expect(classes).toContain('-m-0')
|
||||
expect(classes).not.toContain('-uppercase')
|
||||
expect(classes).not.toContain('-opacity-50')
|
||||
|
||||
config = { theme: { extend: { margin: { DEFAULT: '5px' } } } }
|
||||
context = createContext(resolveConfig(config))
|
||||
classes = context.getClassList()
|
||||
config = { theme: { extend: { margin: { DEFAULT: '5px' } } } }
|
||||
context = createContext(resolveConfig(config))
|
||||
classes = context.getClassList()
|
||||
|
||||
expect(classes).not.toContain('-m-DEFAULT')
|
||||
})
|
||||
expect(classes).not.toContain('-m-DEFAULT')
|
||||
})
|
||||
|
||||
it('should generate every possible class while handling negatives and prefixes', () => {
|
||||
let config = { prefix: 'tw-' }
|
||||
let context = createContext(resolveConfig(config))
|
||||
let classes = context.getClassList()
|
||||
expect(classes).toBeInstanceOf(Array)
|
||||
it('should generate every possible class while handling negatives and prefixes', () => {
|
||||
let config = { prefix: 'tw-' }
|
||||
let context = createContext(resolveConfig(config))
|
||||
let classes = context.getClassList()
|
||||
expect(classes).toBeInstanceOf(Array)
|
||||
|
||||
// Verify we have a `container` for the 'components' section.
|
||||
expect(classes).toContain('tw-container')
|
||||
// Verify we have a `container` for the 'components' section.
|
||||
expect(classes).toContain('tw-container')
|
||||
|
||||
// Verify we handle the DEFAULT case correctly
|
||||
expect(classes).toContain('tw-border')
|
||||
// Verify we handle the DEFAULT case correctly
|
||||
expect(classes).toContain('tw-border')
|
||||
|
||||
// Verify we handle negative values correctly
|
||||
expect(classes).toContain('-tw-inset-1/4')
|
||||
expect(classes).toContain('-tw-m-0')
|
||||
expect(classes).not.toContain('-tw-uppercase')
|
||||
expect(classes).not.toContain('-tw-opacity-50')
|
||||
// Verify we handle negative values correctly
|
||||
expect(classes).toContain('-tw-inset-1/4')
|
||||
expect(classes).toContain('-tw-m-0')
|
||||
expect(classes).not.toContain('-tw-uppercase')
|
||||
expect(classes).not.toContain('-tw-opacity-50')
|
||||
|
||||
// These utilities do work but there's no reason to generate
|
||||
// them alongside the `-{prefix}-{utility}` versions
|
||||
expect(classes).not.toContain('tw--inset-1/4')
|
||||
expect(classes).not.toContain('tw--m-0')
|
||||
// These utilities do work but there's no reason to generate
|
||||
// them alongside the `-{prefix}-{utility}` versions
|
||||
expect(classes).not.toContain('tw--inset-1/4')
|
||||
expect(classes).not.toContain('tw--m-0')
|
||||
|
||||
config = {
|
||||
prefix: 'tw-',
|
||||
theme: { extend: { margin: { DEFAULT: '5px' } } },
|
||||
}
|
||||
context = createContext(resolveConfig(config))
|
||||
classes = context.getClassList()
|
||||
config = {
|
||||
prefix: 'tw-',
|
||||
theme: { extend: { margin: { DEFAULT: '5px' } } },
|
||||
}
|
||||
context = createContext(resolveConfig(config))
|
||||
classes = context.getClassList()
|
||||
|
||||
expect(classes).not.toContain('-tw-m-DEFAULT')
|
||||
})
|
||||
expect(classes).not.toContain('-tw-m-DEFAULT')
|
||||
})
|
||||
|
||||
it('should not generate utilities with opacity by default', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
let classes = context.getClassList()
|
||||
it('should not generate utilities with opacity by default', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
let classes = context.getClassList()
|
||||
|
||||
expect(classes).not.toContain('bg-red-500/50')
|
||||
})
|
||||
expect(classes).not.toContain('bg-red-500/50')
|
||||
})
|
||||
|
||||
it('should not generate utilities with opacity even if safe-listed', () => {
|
||||
let config = {
|
||||
safelist: [
|
||||
{
|
||||
pattern: /^bg-red-(400|500)(\/(40|50))?$/,
|
||||
},
|
||||
],
|
||||
}
|
||||
it('should not generate utilities with opacity even if safe-listed', () => {
|
||||
let config = {
|
||||
safelist: [
|
||||
{
|
||||
pattern: /^bg-red-(400|500)(\/(40|50))?$/,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let context = createContext(resolveConfig(config))
|
||||
let classes = context.getClassList()
|
||||
let context = createContext(resolveConfig(config))
|
||||
let classes = context.getClassList()
|
||||
|
||||
expect(classes).not.toContain('bg-red-500/50')
|
||||
})
|
||||
expect(classes).not.toContain('bg-red-500/50')
|
||||
})
|
||||
|
||||
it('should not generate utilities that are set to undefined or null to so that they are removed', () => {
|
||||
let config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
red: null,
|
||||
green: undefined,
|
||||
blue: {
|
||||
100: null,
|
||||
200: undefined,
|
||||
it('should not generate utilities that are set to undefined or null to so that they are removed', () => {
|
||||
let config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
red: null,
|
||||
green: undefined,
|
||||
blue: {
|
||||
100: null,
|
||||
200: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
safelist: [
|
||||
{
|
||||
pattern: /^bg-(red|green|blue)-.*$/,
|
||||
},
|
||||
],
|
||||
}
|
||||
safelist: [
|
||||
{
|
||||
pattern: /^bg-(red|green|blue)-.*$/,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let context = createContext(resolveConfig(config))
|
||||
let classes = context.getClassList()
|
||||
let context = createContext(resolveConfig(config))
|
||||
let classes = context.getClassList()
|
||||
|
||||
expect(classes).not.toContain('bg-red-100') // Red is `null`
|
||||
expect(classes).not.toContain('bg-red-100') // Red is `null`
|
||||
|
||||
expect(classes).not.toContain('bg-green-100') // Green is `undefined`
|
||||
expect(classes).not.toContain('bg-green-100') // Green is `undefined`
|
||||
|
||||
expect(classes).not.toContain('bg-blue-100') // Blue.100 is `null`
|
||||
expect(classes).not.toContain('bg-blue-200') // Blue.200 is `undefined`
|
||||
expect(classes).not.toContain('bg-blue-100') // Blue.100 is `null`
|
||||
expect(classes).not.toContain('bg-blue-200') // Blue.200 is `undefined`
|
||||
|
||||
expect(classes).toContain('bg-blue-50')
|
||||
expect(classes).toContain('bg-blue-300')
|
||||
expect(classes).toContain('bg-blue-50')
|
||||
expect(classes).toContain('bg-blue-300')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import resolveConfig from '../src/public/resolve-config'
|
||||
import { createContext } from '../src/lib/setupContextUtils'
|
||||
import bigSign from '../src/util/bigSign'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
/**
|
||||
* This is a function that the prettier-plugin-tailwindcss would use. It would
|
||||
@ -24,92 +25,94 @@ function defaultSort(arrayOfTuples) {
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
it('should return a list of tuples with the sort order', () => {
|
||||
let input = 'font-bold underline hover:font-medium unknown'
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
expect(context.getClassOrder(input.split(' '))).toEqual([
|
||||
['font-bold', expect.any(BigInt)],
|
||||
['underline', expect.any(BigInt)],
|
||||
['hover:font-medium', expect.any(BigInt)],
|
||||
crosscheck(() => {
|
||||
it('should return a list of tuples with the sort order', () => {
|
||||
let input = 'font-bold underline hover:font-medium unknown'
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
expect(context.getClassOrder(input.split(' '))).toEqual([
|
||||
['font-bold', expect.any(BigInt)],
|
||||
['underline', expect.any(BigInt)],
|
||||
['hover:font-medium', expect.any(BigInt)],
|
||||
|
||||
// Unknown values receive `null`
|
||||
['unknown', null],
|
||||
])
|
||||
})
|
||||
// Unknown values receive `null`
|
||||
['unknown', null],
|
||||
])
|
||||
})
|
||||
|
||||
it.each([
|
||||
// Utitlies
|
||||
['px-3 p-1 py-3', 'p-1 px-3 py-3'],
|
||||
it.each([
|
||||
// Utitlies
|
||||
['px-3 p-1 py-3', 'p-1 px-3 py-3'],
|
||||
|
||||
// Utitlies and components
|
||||
['px-4 container', 'container px-4'],
|
||||
// Utitlies and components
|
||||
['px-4 container', 'container px-4'],
|
||||
|
||||
// Utilities with variants
|
||||
['px-3 focus:hover:p-3 hover:p-1 py-3', 'px-3 py-3 hover:p-1 focus:hover:p-3'],
|
||||
// Utilities with variants
|
||||
['px-3 focus:hover:p-3 hover:p-1 py-3', 'px-3 py-3 hover:p-1 focus:hover:p-3'],
|
||||
|
||||
// Utitlies with important
|
||||
['px-3 !py-4', 'px-3 !py-4'],
|
||||
['!py-4 px-3', '!py-4 px-3'],
|
||||
// Utitlies with important
|
||||
['px-3 !py-4', 'px-3 !py-4'],
|
||||
['!py-4 px-3', '!py-4 px-3'],
|
||||
|
||||
// Components with variants
|
||||
['hover:container container', 'container hover:container'],
|
||||
// Components with variants
|
||||
['hover:container container', 'container hover:container'],
|
||||
|
||||
// Components and utilities with variants
|
||||
[
|
||||
'focus:hover:container hover:underline hover:container p-1',
|
||||
'p-1 hover:container hover:underline focus:hover:container',
|
||||
],
|
||||
// Components and utilities with variants
|
||||
[
|
||||
'focus:hover:container hover:underline hover:container p-1',
|
||||
'p-1 hover:container hover:underline focus:hover:container',
|
||||
],
|
||||
|
||||
// Leave user css order alone, and move to the front
|
||||
['b p-1 a', 'b a p-1'],
|
||||
['hover:b focus:p-1 a', 'hover:b a focus:p-1'],
|
||||
// Leave user css order alone, and move to the front
|
||||
['b p-1 a', 'b a p-1'],
|
||||
['hover:b focus:p-1 a', 'hover:b a focus:p-1'],
|
||||
|
||||
// Add special treatment for `group` and `peer`
|
||||
['a peer container underline', 'a peer container underline'],
|
||||
])('should sort "%s" based on the order we generate them in to "%s"', (input, output) => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
|
||||
})
|
||||
|
||||
it.each([
|
||||
// Utitlies
|
||||
['tw-px-3 tw-p-1 tw-py-3', 'tw-p-1 tw-px-3 tw-py-3'],
|
||||
|
||||
// Utitlies and components
|
||||
['tw-px-4 tw-container', 'tw-container tw-px-4'],
|
||||
|
||||
// Utilities with variants
|
||||
[
|
||||
'tw-px-3 focus:hover:tw-p-3 hover:tw-p-1 tw-py-3',
|
||||
'tw-px-3 tw-py-3 hover:tw-p-1 focus:hover:tw-p-3',
|
||||
],
|
||||
|
||||
// Utitlies with important
|
||||
['tw-px-3 !tw-py-4', 'tw-px-3 !tw-py-4'],
|
||||
['!tw-py-4 tw-px-3', '!tw-py-4 tw-px-3'],
|
||||
|
||||
// Components with variants
|
||||
['hover:tw-container tw-container', 'tw-container hover:tw-container'],
|
||||
|
||||
// Components and utilities with variants
|
||||
[
|
||||
'focus:hover:tw-container hover:tw-underline hover:tw-container tw-p-1',
|
||||
'tw-p-1 hover:tw-container hover:tw-underline focus:hover:tw-container',
|
||||
],
|
||||
|
||||
// Leave user css order alone, and move to the front
|
||||
['b tw-p-1 a', 'b a tw-p-1'],
|
||||
['hover:b focus:tw-p-1 a', 'hover:b a focus:tw-p-1'],
|
||||
|
||||
// Add special treatment for `group` and `peer`
|
||||
['a tw-peer tw-container tw-underline', 'a tw-peer tw-container tw-underline'],
|
||||
])(
|
||||
'should sort "%s" with prefixex based on the order we generate them in to "%s"',
|
||||
(input, output) => {
|
||||
let config = { prefix: 'tw-' }
|
||||
// Add special treatment for `group` and `peer`
|
||||
['a peer container underline', 'a peer container underline'],
|
||||
])('should sort "%s" based on the order we generate them in to "%s"', (input, output) => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it.each([
|
||||
// Utitlies
|
||||
['tw-px-3 tw-p-1 tw-py-3', 'tw-p-1 tw-px-3 tw-py-3'],
|
||||
|
||||
// Utitlies and components
|
||||
['tw-px-4 tw-container', 'tw-container tw-px-4'],
|
||||
|
||||
// Utilities with variants
|
||||
[
|
||||
'tw-px-3 focus:hover:tw-p-3 hover:tw-p-1 tw-py-3',
|
||||
'tw-px-3 tw-py-3 hover:tw-p-1 focus:hover:tw-p-3',
|
||||
],
|
||||
|
||||
// Utitlies with important
|
||||
['tw-px-3 !tw-py-4', 'tw-px-3 !tw-py-4'],
|
||||
['!tw-py-4 tw-px-3', '!tw-py-4 tw-px-3'],
|
||||
|
||||
// Components with variants
|
||||
['hover:tw-container tw-container', 'tw-container hover:tw-container'],
|
||||
|
||||
// Components and utilities with variants
|
||||
[
|
||||
'focus:hover:tw-container hover:tw-underline hover:tw-container tw-p-1',
|
||||
'tw-p-1 hover:tw-container hover:tw-underline focus:hover:tw-container',
|
||||
],
|
||||
|
||||
// Leave user css order alone, and move to the front
|
||||
['b tw-p-1 a', 'b a tw-p-1'],
|
||||
['hover:b focus:tw-p-1 a', 'hover:b a focus:tw-p-1'],
|
||||
|
||||
// Add special treatment for `group` and `peer`
|
||||
['a tw-peer tw-container tw-underline', 'a tw-peer tw-container tw-underline'],
|
||||
])(
|
||||
'should sort "%s" with prefixex based on the order we generate them in to "%s"',
|
||||
(input, output) => {
|
||||
let config = { prefix: 'tw-' }
|
||||
let context = createContext(resolveConfig(config))
|
||||
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -2,185 +2,188 @@ import postcss from 'postcss'
|
||||
import selectorParser from 'postcss-selector-parser'
|
||||
import resolveConfig from '../src/public/resolve-config'
|
||||
import { createContext } from '../src/lib/setupContextUtils'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
it('should return a list of variants with meta information about the variant', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
crosscheck(() => {
|
||||
it('should return a list of variants with meta information about the variant', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
let variants = context.getVariants()
|
||||
let variants = context.getVariants()
|
||||
|
||||
expect(variants).toContainEqual({
|
||||
name: 'hover',
|
||||
isArbitrary: false,
|
||||
hasDash: true,
|
||||
values: [],
|
||||
selectors: expect.any(Function),
|
||||
expect(variants).toContainEqual({
|
||||
name: 'hover',
|
||||
isArbitrary: false,
|
||||
hasDash: true,
|
||||
values: [],
|
||||
selectors: expect.any(Function),
|
||||
})
|
||||
|
||||
expect(variants).toContainEqual({
|
||||
name: 'group',
|
||||
isArbitrary: true,
|
||||
hasDash: true,
|
||||
values: expect.any(Array),
|
||||
selectors: expect.any(Function),
|
||||
})
|
||||
|
||||
// `group-hover` now belongs to the `group` variant. The information exposed for the `group`
|
||||
// variant is all you need.
|
||||
expect(variants.find((v) => v.name === 'group-hover')).toBeUndefined()
|
||||
})
|
||||
|
||||
expect(variants).toContainEqual({
|
||||
name: 'group',
|
||||
isArbitrary: true,
|
||||
hasDash: true,
|
||||
values: expect.any(Array),
|
||||
selectors: expect.any(Function),
|
||||
it('should provide selectors for simple variants', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variant = variants.find((v) => v.name === 'hover')
|
||||
expect(variant.selectors()).toEqual(['&:hover'])
|
||||
})
|
||||
|
||||
// `group-hover` now belongs to the `group` variant. The information exposed for the `group`
|
||||
// variant is all you need.
|
||||
expect(variants.find((v) => v.name === 'group-hover')).toBeUndefined()
|
||||
})
|
||||
it('should provide selectors for parallel variants', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
it('should provide selectors for simple variants', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variants = context.getVariants()
|
||||
let variant = variants.find((v) => v.name === 'marker')
|
||||
expect(variant.selectors()).toEqual(['& *::marker', '&::marker'])
|
||||
})
|
||||
|
||||
let variant = variants.find((v) => v.name === 'hover')
|
||||
expect(variant.selectors()).toEqual(['&:hover'])
|
||||
})
|
||||
it('should provide selectors for complex matchVariant variants like `group`', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
it('should provide selectors for parallel variants', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variants = context.getVariants()
|
||||
let variant = variants.find((v) => v.name === 'group')
|
||||
expect(variant.selectors()).toEqual(['.group &'])
|
||||
expect(variant.selectors({})).toEqual(['.group &'])
|
||||
expect(variant.selectors({ value: 'hover' })).toEqual(['.group:hover &'])
|
||||
expect(variant.selectors({ value: '.foo_&' })).toEqual(['.foo .group &'])
|
||||
expect(variant.selectors({ modifier: 'foo', value: 'hover' })).toEqual(['.group\\/foo:hover &'])
|
||||
expect(variant.selectors({ modifier: 'foo', value: '.foo_&' })).toEqual(['.foo .group\\/foo &'])
|
||||
})
|
||||
|
||||
let variant = variants.find((v) => v.name === 'marker')
|
||||
expect(variant.selectors()).toEqual(['& *::marker', '&::marker'])
|
||||
})
|
||||
it('should provide selectors for variants with atrules', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
it('should provide selectors for complex matchVariant variants like `group`', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variants = context.getVariants()
|
||||
let variant = variants.find((v) => v.name === 'supports')
|
||||
expect(variant.selectors({ value: 'display:grid' })).toEqual(['@supports (display:grid)'])
|
||||
expect(variant.selectors({ value: 'aspect-ratio' })).toEqual([
|
||||
'@supports (aspect-ratio: var(--tw))',
|
||||
])
|
||||
})
|
||||
|
||||
let variant = variants.find((v) => v.name === 'group')
|
||||
expect(variant.selectors()).toEqual(['.group &'])
|
||||
expect(variant.selectors({})).toEqual(['.group &'])
|
||||
expect(variant.selectors({ value: 'hover' })).toEqual(['.group:hover &'])
|
||||
expect(variant.selectors({ value: '.foo_&' })).toEqual(['.foo .group &'])
|
||||
expect(variant.selectors({ modifier: 'foo', value: 'hover' })).toEqual(['.group\\/foo:hover &'])
|
||||
expect(variant.selectors({ modifier: 'foo', value: '.foo_&' })).toEqual(['.foo .group\\/foo &'])
|
||||
})
|
||||
|
||||
it('should provide selectors for variants with atrules', () => {
|
||||
let config = {}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variant = variants.find((v) => v.name === 'supports')
|
||||
expect(variant.selectors({ value: 'display:grid' })).toEqual(['@supports (display:grid)'])
|
||||
expect(variant.selectors({ value: 'aspect-ratio' })).toEqual([
|
||||
'@supports (aspect-ratio: var(--tw))',
|
||||
])
|
||||
})
|
||||
|
||||
it('should provide selectors for custom plugins that do a combination of parallel variants with modifiers with arbitrary values and with atrules', () => {
|
||||
let config = {
|
||||
plugins: [
|
||||
function ({ matchVariant }) {
|
||||
matchVariant('foo', (value, { modifier }) => {
|
||||
return [
|
||||
`
|
||||
it('should provide selectors for custom plugins that do a combination of parallel variants with modifiers with arbitrary values and with atrules', () => {
|
||||
let config = {
|
||||
plugins: [
|
||||
function ({ matchVariant }) {
|
||||
matchVariant('foo', (value, { modifier }) => {
|
||||
return [
|
||||
`
|
||||
@supports (foo: ${modifier}) {
|
||||
@media (width <= 400px) {
|
||||
&:hover
|
||||
}
|
||||
}
|
||||
`,
|
||||
`.${modifier}\\/${value} &:focus`,
|
||||
]
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variant = variants.find((v) => v.name === 'foo')
|
||||
expect(variant.selectors({ modifier: 'bar', value: 'baz' })).toEqual([
|
||||
'@supports (foo: bar) { @media (width <= 400px) { &:hover } }',
|
||||
'.bar\\/baz &:focus',
|
||||
])
|
||||
})
|
||||
|
||||
it('should work for plugins that still use the modifySelectors API', () => {
|
||||
let config = {
|
||||
plugins: [
|
||||
function ({ addVariant }) {
|
||||
addVariant('foo', ({ modifySelectors, container }) => {
|
||||
// Manually mutating the selector
|
||||
modifySelectors(({ selector }) => {
|
||||
return selectorParser((selectors) => {
|
||||
selectors.walkClasses((classNode) => {
|
||||
classNode.value = `foo:${classNode.value}`
|
||||
classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
|
||||
})
|
||||
}).processSync(selector)
|
||||
`.${modifier}\\/${value} &:focus`,
|
||||
]
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
// Manually wrap in supports query
|
||||
let wrapper = postcss.atRule({ name: 'supports', params: 'display: grid' })
|
||||
let nodes = container.nodes
|
||||
container.removeAll()
|
||||
wrapper.append(nodes)
|
||||
container.append(wrapper)
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
let context = createContext(resolveConfig(config))
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variant = variants.find((v) => v.name === 'foo')
|
||||
expect(variant.selectors({})).toEqual(['@supports (display: grid) { .foo .foo\\:& }'])
|
||||
})
|
||||
|
||||
it('should special case the `@`', () => {
|
||||
let config = {
|
||||
plugins: [
|
||||
({ matchVariant }) => {
|
||||
matchVariant(
|
||||
'@',
|
||||
(value, { modifier }) => `@container ${modifier ?? ''} (min-width: ${value})`,
|
||||
{
|
||||
modifiers: 'any',
|
||||
values: {
|
||||
xs: '20rem',
|
||||
sm: '24rem',
|
||||
md: '28rem',
|
||||
lg: '32rem',
|
||||
xl: '36rem',
|
||||
'2xl': '42rem',
|
||||
'3xl': '48rem',
|
||||
'4xl': '56rem',
|
||||
'5xl': '64rem',
|
||||
'6xl': '72rem',
|
||||
'7xl': '80rem',
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variant = variants.find((v) => v.name === '@')
|
||||
expect(variant).toEqual({
|
||||
name: '@',
|
||||
isArbitrary: true,
|
||||
hasDash: false,
|
||||
values: expect.any(Array),
|
||||
selectors: expect.any(Function),
|
||||
let variant = variants.find((v) => v.name === 'foo')
|
||||
expect(variant.selectors({ modifier: 'bar', value: 'baz' })).toEqual([
|
||||
'@supports (foo: bar) { @media (width <= 400px) { &:hover } }',
|
||||
'.bar\\/baz &:focus',
|
||||
])
|
||||
})
|
||||
|
||||
it('should work for plugins that still use the modifySelectors API', () => {
|
||||
let config = {
|
||||
plugins: [
|
||||
function ({ addVariant }) {
|
||||
addVariant('foo', ({ modifySelectors, container }) => {
|
||||
// Manually mutating the selector
|
||||
modifySelectors(({ selector }) => {
|
||||
return selectorParser((selectors) => {
|
||||
selectors.walkClasses((classNode) => {
|
||||
classNode.value = `foo:${classNode.value}`
|
||||
classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
|
||||
})
|
||||
}).processSync(selector)
|
||||
})
|
||||
|
||||
// Manually wrap in supports query
|
||||
let wrapper = postcss.atRule({ name: 'supports', params: 'display: grid' })
|
||||
let nodes = container.nodes
|
||||
container.removeAll()
|
||||
wrapper.append(nodes)
|
||||
container.append(wrapper)
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variant = variants.find((v) => v.name === 'foo')
|
||||
expect(variant.selectors({})).toEqual(['@supports (display: grid) { .foo .foo\\:& }'])
|
||||
})
|
||||
|
||||
it('should special case the `@`', () => {
|
||||
let config = {
|
||||
plugins: [
|
||||
({ matchVariant }) => {
|
||||
matchVariant(
|
||||
'@',
|
||||
(value, { modifier }) => `@container ${modifier ?? ''} (min-width: ${value})`,
|
||||
{
|
||||
modifiers: 'any',
|
||||
values: {
|
||||
xs: '20rem',
|
||||
sm: '24rem',
|
||||
md: '28rem',
|
||||
lg: '32rem',
|
||||
xl: '36rem',
|
||||
'2xl': '42rem',
|
||||
'3xl': '48rem',
|
||||
'4xl': '56rem',
|
||||
'5xl': '64rem',
|
||||
'6xl': '72rem',
|
||||
'7xl': '80rem',
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
let context = createContext(resolveConfig(config))
|
||||
|
||||
let variants = context.getVariants()
|
||||
|
||||
let variant = variants.find((v) => v.name === '@')
|
||||
expect(variant).toEqual({
|
||||
name: '@',
|
||||
isArbitrary: true,
|
||||
hasDash: false,
|
||||
values: expect.any(Array),
|
||||
selectors: expect.any(Function),
|
||||
})
|
||||
expect(variant.selectors({ value: 'xs', modifier: 'foo' })).toEqual([
|
||||
'@container foo (min-width: 20rem)',
|
||||
])
|
||||
})
|
||||
expect(variant.selectors({ value: 'xs', modifier: 'foo' })).toEqual([
|
||||
'@container foo (min-width: 20rem)',
|
||||
])
|
||||
})
|
||||
|
||||
@ -1,82 +1,84 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
test('using @import instead of @tailwind', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<h1>Hello world!</h1>
|
||||
<div class="container"></div>
|
||||
<div class="mt-6"></div>
|
||||
<div class="bg-black"></div>
|
||||
<div class="md:hover:text-center"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
function ({ addBase }) {
|
||||
addBase({
|
||||
h1: {
|
||||
fontSize: '32px',
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('using @import instead of @tailwind', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<h1>Hello world!</h1>
|
||||
<div class="container"></div>
|
||||
<div class="mt-6"></div>
|
||||
<div class="bg-black"></div>
|
||||
<div class="md:hover:text-center"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
function ({ addBase }) {
|
||||
addBase({
|
||||
h1: {
|
||||
fontSize: '32px',
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
`
|
||||
let input = css`
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
}
|
||||
${defaults}
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
}
|
||||
${defaults}
|
||||
.container {
|
||||
max-width: 640px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.mt-6 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:hover\:text-center:hover {
|
||||
text-align: center;
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
.mt-6 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:hover\:text-center:hover {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,228 +2,230 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
import * as sharedState from '../src/lib/sharedState'
|
||||
|
||||
import { run, css, html, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
test('important boolean', () => {
|
||||
let config = {
|
||||
important: true,
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="container"></div>
|
||||
<div class="btn"></div>
|
||||
<div class="animate-spin"></div>
|
||||
<div class="custom-util"></div>
|
||||
<div class="custom-component"></div>
|
||||
<div class="custom-important-component"></div>
|
||||
<div class="font-bold"></div>
|
||||
<div class="md:hover:text-right"></div>
|
||||
<div class="motion-safe:hover:text-center"></div>
|
||||
<div class="dark:focus:text-left"></div>
|
||||
<div class="group-hover:focus-within:text-left"></div>
|
||||
<div class="rtl:active:text-center"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
function ({ addComponents, addUtilities }) {
|
||||
addComponents(
|
||||
{
|
||||
'.btn': {
|
||||
button: 'yes',
|
||||
},
|
||||
},
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addComponents(
|
||||
{
|
||||
'@font-face': {
|
||||
'font-family': 'Inter',
|
||||
},
|
||||
'@page': {
|
||||
margin: '1cm',
|
||||
},
|
||||
},
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addUtilities(
|
||||
{
|
||||
'.custom-util': {
|
||||
button: 'no',
|
||||
},
|
||||
},
|
||||
{ respectImportant: false }
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
.custom-component {
|
||||
@apply font-bold;
|
||||
}
|
||||
.custom-important-component {
|
||||
@apply text-center !important;
|
||||
}
|
||||
}
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
button: yes !important;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Inter;
|
||||
}
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
.custom-component {
|
||||
font-weight: 700;
|
||||
}
|
||||
.custom-important-component {
|
||||
text-align: center !important;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite !important;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.custom-util {
|
||||
button: no;
|
||||
}
|
||||
.group:hover .group-hover\:focus-within\:text-left:focus-within {
|
||||
text-align: left !important;
|
||||
}
|
||||
[dir='rtl'] .rtl\:active\:text-center:active {
|
||||
text-align: center !important;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe\:hover\:text-center:hover {
|
||||
text-align: center !important;
|
||||
}
|
||||
}
|
||||
.dark .dark\:focus\:text-left:focus {
|
||||
text-align: left !important;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:hover\:text-right:hover {
|
||||
text-align: right !important;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
// This is in a describe block so we can use `afterEach` :)
|
||||
describe('duplicate elision', () => {
|
||||
let filePath = path.resolve(__dirname, './important-boolean-duplicates.test.html')
|
||||
|
||||
afterEach(async () => await fs.promises.unlink(filePath))
|
||||
|
||||
test('important rules are not duplicated when rebuilding', async () => {
|
||||
crosscheck(() => {
|
||||
test('important boolean', () => {
|
||||
let config = {
|
||||
important: true,
|
||||
content: [filePath],
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="container"></div>
|
||||
<div class="btn"></div>
|
||||
<div class="animate-spin"></div>
|
||||
<div class="custom-util"></div>
|
||||
<div class="custom-component"></div>
|
||||
<div class="custom-important-component"></div>
|
||||
<div class="font-bold"></div>
|
||||
<div class="md:hover:text-right"></div>
|
||||
<div class="motion-safe:hover:text-center"></div>
|
||||
<div class="dark:focus:text-left"></div>
|
||||
<div class="group-hover:focus-within:text-left"></div>
|
||||
<div class="rtl:active:text-center"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
function ({ addComponents, addUtilities }) {
|
||||
addComponents(
|
||||
{
|
||||
'.btn': {
|
||||
button: 'yes',
|
||||
},
|
||||
},
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addComponents(
|
||||
{
|
||||
'@font-face': {
|
||||
'font-family': 'Inter',
|
||||
},
|
||||
'@page': {
|
||||
margin: '1cm',
|
||||
},
|
||||
},
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addUtilities(
|
||||
{
|
||||
'.custom-util': {
|
||||
button: 'no',
|
||||
},
|
||||
},
|
||||
{ respectImportant: false }
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(
|
||||
config.content[0],
|
||||
html`
|
||||
<div class="ml-2"></div>
|
||||
<div class="ml-4"></div>
|
||||
`
|
||||
)
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
.custom-component {
|
||||
@apply font-bold;
|
||||
}
|
||||
.custom-important-component {
|
||||
@apply text-center !important;
|
||||
}
|
||||
}
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
let allContexts = Array.from(sharedState.contextMap.values())
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
button: yes !important;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Inter;
|
||||
}
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
.custom-component {
|
||||
font-weight: 700;
|
||||
}
|
||||
.custom-important-component {
|
||||
text-align: center !important;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite !important;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.custom-util {
|
||||
button: no;
|
||||
}
|
||||
.group:hover .group-hover\:focus-within\:text-left:focus-within {
|
||||
text-align: left !important;
|
||||
}
|
||||
[dir='rtl'] .rtl\:active\:text-center:active {
|
||||
text-align: center !important;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.motion-safe\:hover\:text-center:hover {
|
||||
text-align: center !important;
|
||||
}
|
||||
}
|
||||
.dark .dark\:focus\:text-left:focus {
|
||||
text-align: left !important;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:hover\:text-right:hover {
|
||||
text-align: right !important;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
let context = allContexts[allContexts.length - 1]
|
||||
// This is in a describe block so we can use `afterEach` :)
|
||||
describe('duplicate elision', () => {
|
||||
let filePath = path.resolve(__dirname, './important-boolean-duplicates.test.html')
|
||||
|
||||
let ruleCacheSize1 = context.ruleCache.size
|
||||
afterEach(async () => await fs.promises.unlink(filePath))
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem !important;
|
||||
test('important rules are not duplicated when rebuilding', async () => {
|
||||
let config = {
|
||||
important: true,
|
||||
content: [filePath],
|
||||
}
|
||||
.ml-4 {
|
||||
margin-left: 1rem !important;
|
||||
}
|
||||
`)
|
||||
|
||||
await fs.promises.writeFile(
|
||||
config.content[0],
|
||||
html`
|
||||
<div class="ml-2"></div>
|
||||
<div class="ml-6"></div>
|
||||
await fs.promises.writeFile(
|
||||
config.content[0],
|
||||
html`
|
||||
<div class="ml-2"></div>
|
||||
<div class="ml-4"></div>
|
||||
`
|
||||
)
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
)
|
||||
|
||||
result = await run(input, config)
|
||||
let result = await run(input, config)
|
||||
let allContexts = Array.from(sharedState.contextMap.values())
|
||||
|
||||
let ruleCacheSize2 = context.ruleCache.size
|
||||
let context = allContexts[allContexts.length - 1]
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
.ml-4 {
|
||||
margin-left: 1rem !important;
|
||||
}
|
||||
.ml-6 {
|
||||
margin-left: 1.5rem !important;
|
||||
}
|
||||
`)
|
||||
let ruleCacheSize1 = context.ruleCache.size
|
||||
|
||||
// The rule cache was effectively doubling in size previously
|
||||
// because the rule cache was never de-duped
|
||||
// This ensures this behavior doesn't return
|
||||
expect(ruleCacheSize2 - ruleCacheSize1).toBeLessThan(10)
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
.ml-4 {
|
||||
margin-left: 1rem !important;
|
||||
}
|
||||
`)
|
||||
|
||||
await fs.promises.writeFile(
|
||||
config.content[0],
|
||||
html`
|
||||
<div class="ml-2"></div>
|
||||
<div class="ml-6"></div>
|
||||
`
|
||||
)
|
||||
|
||||
result = await run(input, config)
|
||||
|
||||
let ruleCacheSize2 = context.ruleCache.size
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
.ml-4 {
|
||||
margin-left: 1rem !important;
|
||||
}
|
||||
.ml-6 {
|
||||
margin-left: 1.5rem !important;
|
||||
}
|
||||
`)
|
||||
|
||||
// The rule cache was effectively doubling in size previously
|
||||
// because the rule cache was never de-duped
|
||||
// This ensures this behavior doesn't return
|
||||
expect(ruleCacheSize2 - ruleCacheSize1).toBeLessThan(10)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,77 +1,79 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
test('important modifier with prefix', () => {
|
||||
let config = {
|
||||
important: false,
|
||||
prefix: 'tw-',
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`<!-- The string "!*" can cause problems if we don't handle it, let's include it -->
|
||||
<div class="!*"></div>
|
||||
<div class="!tw-container"></div>
|
||||
<div class="!tw-font-bold"></div>
|
||||
<div class="hover:!tw-text-center"></div>
|
||||
<div class="lg:!tw-opacity-50"></div>
|
||||
<div class="xl:focus:disabled:!tw-float-right"></div> `,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('important modifier with prefix', () => {
|
||||
let config = {
|
||||
important: false,
|
||||
prefix: 'tw-',
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`<!-- The string "!*" can cause problems if we don't handle it, let's include it -->
|
||||
<div class="!*"></div>
|
||||
<div class="!tw-container"></div>
|
||||
<div class="!tw-font-bold"></div>
|
||||
<div class="hover:!tw-text-center"></div>
|
||||
<div class="lg:!tw-opacity-50"></div>
|
||||
<div class="xl:focus:disabled:!tw-float-right"></div> `,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.\!tw-container {
|
||||
width: 100% !important;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.\!tw-container {
|
||||
max-width: 640px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.\!tw-container {
|
||||
max-width: 768px !important;
|
||||
@media (min-width: 640px) {
|
||||
.\!tw-container {
|
||||
max-width: 640px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.\!tw-container {
|
||||
max-width: 1024px !important;
|
||||
@media (min-width: 768px) {
|
||||
.\!tw-container {
|
||||
max-width: 768px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.\!tw-container {
|
||||
max-width: 1280px !important;
|
||||
@media (min-width: 1024px) {
|
||||
.\!tw-container {
|
||||
max-width: 1024px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.\!tw-container {
|
||||
max-width: 1536px !important;
|
||||
@media (min-width: 1280px) {
|
||||
.\!tw-container {
|
||||
max-width: 1280px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.\!tw-font-bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.hover\:\!tw-text-center:hover {
|
||||
text-align: center !important;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:\!tw-opacity-50 {
|
||||
opacity: 0.5 !important;
|
||||
@media (min-width: 1536px) {
|
||||
.\!tw-container {
|
||||
max-width: 1536px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.xl\:focus\:disabled\:\!tw-float-right:disabled:focus {
|
||||
float: right !important;
|
||||
.\!tw-font-bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
}
|
||||
`)
|
||||
.hover\:\!tw-text-center:hover {
|
||||
text-align: center !important;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:\!tw-opacity-50 {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.xl\:focus\:disabled\:\!tw-float-right:disabled:focus {
|
||||
float: right !important;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,109 +1,111 @@
|
||||
import { run, css, html } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('important modifier', () => {
|
||||
let config = {
|
||||
important: false,
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="!container"></div>
|
||||
<div class="!font-bold"></div>
|
||||
<div class="hover:!text-center"></div>
|
||||
<div class="lg:!opacity-50"></div>
|
||||
<div class="xl:focus:disabled:!float-right"></div>
|
||||
<div class="!custom-parent-5"></div>
|
||||
<div class="btn !disabled"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
function ({ theme, matchUtilities, addComponents }) {
|
||||
matchUtilities(
|
||||
{
|
||||
'custom-parent': (value) => {
|
||||
return {
|
||||
'.custom-child': {
|
||||
margin: value,
|
||||
},
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('important modifier', () => {
|
||||
let config = {
|
||||
important: false,
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="!container"></div>
|
||||
<div class="!font-bold"></div>
|
||||
<div class="hover:!text-center"></div>
|
||||
<div class="lg:!opacity-50"></div>
|
||||
<div class="xl:focus:disabled:!float-right"></div>
|
||||
<div class="!custom-parent-5"></div>
|
||||
<div class="btn !disabled"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
function ({ theme, matchUtilities, addComponents }) {
|
||||
matchUtilities(
|
||||
{
|
||||
'custom-parent': (value) => {
|
||||
return {
|
||||
'.custom-child': {
|
||||
margin: value,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{ values: theme('spacing') }
|
||||
)
|
||||
addComponents({
|
||||
'.btn': {
|
||||
'&.disabled, &:disabled': {
|
||||
color: 'gray',
|
||||
{ values: theme('spacing') }
|
||||
)
|
||||
addComponents({
|
||||
'.btn': {
|
||||
'&.disabled, &:disabled': {
|
||||
color: 'gray',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.\!container {
|
||||
width: 100% !important;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.\!container {
|
||||
max-width: 640px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.\!container {
|
||||
max-width: 768px !important;
|
||||
@media (min-width: 640px) {
|
||||
.\!container {
|
||||
max-width: 640px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.\!container {
|
||||
max-width: 1024px !important;
|
||||
@media (min-width: 768px) {
|
||||
.\!container {
|
||||
max-width: 768px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.\!container {
|
||||
max-width: 1280px !important;
|
||||
@media (min-width: 1024px) {
|
||||
.\!container {
|
||||
max-width: 1024px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.\!container {
|
||||
max-width: 1536px !important;
|
||||
@media (min-width: 1280px) {
|
||||
.\!container {
|
||||
max-width: 1280px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn.disabled,
|
||||
.btn:disabled {
|
||||
color: gray;
|
||||
}
|
||||
.btn.\!disabled {
|
||||
color: gray !important;
|
||||
}
|
||||
.\!font-bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.\!custom-parent-5 .custom-child {
|
||||
margin: 1.25rem !important;
|
||||
}
|
||||
.hover\:\!text-center:hover {
|
||||
text-align: center !important;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:\!opacity-50 {
|
||||
opacity: 0.5 !important;
|
||||
@media (min-width: 1536px) {
|
||||
.\!container {
|
||||
max-width: 1536px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.xl\:focus\:disabled\:\!float-right:disabled:focus {
|
||||
float: right !important;
|
||||
.btn.disabled,
|
||||
.btn:disabled {
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
`)
|
||||
.btn.\!disabled {
|
||||
color: gray !important;
|
||||
}
|
||||
.\!font-bold {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.\!custom-parent-5 .custom-child {
|
||||
margin: 1.25rem !important;
|
||||
}
|
||||
.hover\:\!text-center:hover {
|
||||
text-align: center !important;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:\!opacity-50 {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.xl\:focus\:disabled\:\!float-right:disabled:focus {
|
||||
float: right !important;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,154 +1,156 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
test('important selector', () => {
|
||||
let config = {
|
||||
important: '#app',
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="container"></div>
|
||||
<div class="btn"></div>
|
||||
<div class="animate-spin"></div>
|
||||
<div class="custom-util"></div>
|
||||
<div class="custom-component"></div>
|
||||
<div class="custom-important-component"></div>
|
||||
<div class="font-bold"></div>
|
||||
<div class="md:hover:text-right"></div>
|
||||
<div class="motion-safe:hover:text-center"></div>
|
||||
<div class="dark:focus:text-left"></div>
|
||||
<div class="group-hover:focus-within:text-left"></div>
|
||||
<div class="rtl:active:text-center"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
function ({ addComponents, addUtilities }) {
|
||||
addComponents(
|
||||
{
|
||||
'.btn': {
|
||||
button: 'yes',
|
||||
crosscheck(() => {
|
||||
test('important selector', () => {
|
||||
let config = {
|
||||
important: '#app',
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="container"></div>
|
||||
<div class="btn"></div>
|
||||
<div class="animate-spin"></div>
|
||||
<div class="custom-util"></div>
|
||||
<div class="custom-component"></div>
|
||||
<div class="custom-important-component"></div>
|
||||
<div class="font-bold"></div>
|
||||
<div class="md:hover:text-right"></div>
|
||||
<div class="motion-safe:hover:text-center"></div>
|
||||
<div class="dark:focus:text-left"></div>
|
||||
<div class="group-hover:focus-within:text-left"></div>
|
||||
<div class="rtl:active:text-center"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
plugins: [
|
||||
function ({ addComponents, addUtilities }) {
|
||||
addComponents(
|
||||
{
|
||||
'.btn': {
|
||||
button: 'yes',
|
||||
},
|
||||
},
|
||||
},
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addComponents(
|
||||
{
|
||||
'@font-face': {
|
||||
'font-family': 'Inter',
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addComponents(
|
||||
{
|
||||
'@font-face': {
|
||||
'font-family': 'Inter',
|
||||
},
|
||||
'@page': {
|
||||
margin: '1cm',
|
||||
},
|
||||
},
|
||||
'@page': {
|
||||
margin: '1cm',
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addUtilities(
|
||||
{
|
||||
'.custom-util': {
|
||||
button: 'no',
|
||||
},
|
||||
},
|
||||
},
|
||||
{ respectImportant: true }
|
||||
)
|
||||
addUtilities(
|
||||
{
|
||||
'.custom-util': {
|
||||
button: 'no',
|
||||
},
|
||||
},
|
||||
{ respectImportant: false }
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
.custom-component {
|
||||
@apply font-bold;
|
||||
}
|
||||
.custom-important-component {
|
||||
@apply text-center !important;
|
||||
}
|
||||
{ respectImportant: false }
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.container {
|
||||
width: 100%;
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
.custom-component {
|
||||
@apply font-bold;
|
||||
}
|
||||
.custom-important-component {
|
||||
@apply text-center !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.container {
|
||||
max-width: 640px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
}
|
||||
#app .btn {
|
||||
button: yes;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Inter;
|
||||
}
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
.custom-component {
|
||||
font-weight: 700;
|
||||
}
|
||||
.custom-important-component {
|
||||
text-align: center !important;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
}
|
||||
#app .animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
#app .font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.custom-util {
|
||||
button: no;
|
||||
}
|
||||
#app .group:hover .group-hover\:focus-within\:text-left:focus-within {
|
||||
text-align: left;
|
||||
}
|
||||
#app [dir='rtl'] .rtl\:active\:text-center:active {
|
||||
text-align: center;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
#app .motion-safe\:hover\:text-center:hover {
|
||||
#app .btn {
|
||||
button: yes;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Inter;
|
||||
}
|
||||
@page {
|
||||
margin: 1cm;
|
||||
}
|
||||
.custom-component {
|
||||
font-weight: 700;
|
||||
}
|
||||
.custom-important-component {
|
||||
text-align: center !important;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
#app .animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
#app .font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.custom-util {
|
||||
button: no;
|
||||
}
|
||||
#app .group:hover .group-hover\:focus-within\:text-left:focus-within {
|
||||
text-align: left;
|
||||
}
|
||||
#app [dir='rtl'] .rtl\:active\:text-center:active {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
#app .dark .dark\:focus\:text-left:focus {
|
||||
text-align: left;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
#app .md\:hover\:text-right:hover {
|
||||
text-align: right;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
#app .motion-safe\:hover\:text-center:hover {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
#app .dark .dark\:focus\:text-left:focus {
|
||||
text-align: left;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
#app .md\:hover\:text-right:hover {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,302 +1,249 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
test('custom user-land utilities', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="focus:hover:align-chocolate align-banana hover:align-banana uppercase"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@layer utilities {
|
||||
.align-banana {
|
||||
text-align: banana;
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('custom user-land utilities', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="focus:hover:align-chocolate align-banana hover:align-banana uppercase"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
let input = css`
|
||||
@layer utilities {
|
||||
.align-banana {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.align-chocolate {
|
||||
text-align: chocolate;
|
||||
}
|
||||
}
|
||||
`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
@layer utilities {
|
||||
.align-chocolate {
|
||||
text-align: chocolate;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.align-banana {
|
||||
text-align: banana;
|
||||
}
|
||||
.hover\:align-banana:hover {
|
||||
text-align: banana;
|
||||
}
|
||||
.focus\:hover\:align-chocolate:hover:focus {
|
||||
text-align: chocolate;
|
||||
}
|
||||
`)
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.align-banana {
|
||||
text-align: banana;
|
||||
}
|
||||
.hover\:align-banana:hover {
|
||||
text-align: banana;
|
||||
}
|
||||
.focus\:hover\:align-chocolate:hover:focus {
|
||||
text-align: chocolate;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('comments can be used inside layers without crashing', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="important-utility important-component"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
/* Important base */
|
||||
div {
|
||||
background-color: #bada55;
|
||||
}
|
||||
test('comments can be used inside layers without crashing', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="important-utility important-component"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Important utility */
|
||||
.important-utility {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
/* Important component */
|
||||
@layer base {
|
||||
/* Important base */
|
||||
div {
|
||||
background-color: #bada55;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Important utility */
|
||||
.important-utility {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Important component */
|
||||
.important-component {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
/* Important base */
|
||||
div {
|
||||
background-color: #bada55;
|
||||
}
|
||||
|
||||
${defaults}
|
||||
|
||||
/* Important component */
|
||||
.important-component {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
`
|
||||
text-align: banana;
|
||||
}
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
/* Important base */
|
||||
div {
|
||||
background-color: #bada55;
|
||||
}
|
||||
|
||||
${defaults}
|
||||
|
||||
/* Important component */
|
||||
.important-component {
|
||||
text-align: banana;
|
||||
}
|
||||
|
||||
/* Important utility */
|
||||
.important-utility {
|
||||
text-align: banana;
|
||||
}
|
||||
`)
|
||||
/* Important utility */
|
||||
.important-utility {
|
||||
text-align: banana;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('comments can be used inside layers (with important) without crashing', () => {
|
||||
let config = {
|
||||
important: true,
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="important-utility important-component"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
/* Important base */
|
||||
div {
|
||||
background-color: #bada55;
|
||||
}
|
||||
test('comments can be used inside layers (with important) without crashing', () => {
|
||||
let config = {
|
||||
important: true,
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="important-utility important-component"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Important utility */
|
||||
.important-utility {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
/* Important component */
|
||||
@layer base {
|
||||
/* Important base */
|
||||
div {
|
||||
background-color: #bada55;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Important utility */
|
||||
.important-utility {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Important component */
|
||||
.important-component {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
/* Important base */
|
||||
div {
|
||||
background-color: #bada55;
|
||||
}
|
||||
|
||||
${defaults}
|
||||
|
||||
/* Important component */
|
||||
.important-component {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
`
|
||||
text-align: banana;
|
||||
}
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
/* Important base */
|
||||
div {
|
||||
background-color: #bada55;
|
||||
}
|
||||
|
||||
${defaults}
|
||||
|
||||
/* Important component */
|
||||
.important-component {
|
||||
text-align: banana;
|
||||
}
|
||||
|
||||
/* Important utility */
|
||||
.important-utility {
|
||||
text-align: banana !important;
|
||||
}
|
||||
`)
|
||||
/* Important utility */
|
||||
.important-utility {
|
||||
text-align: banana !important;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('layers are grouped and inserted at the matching @tailwind rule', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{ raw: html`<div class="input btn card float-squirrel align-banana align-sandwich"></div>` },
|
||||
],
|
||||
plugins: [
|
||||
function ({ addBase, addComponents, addUtilities }) {
|
||||
addBase({ body: { margin: 0 } })
|
||||
test('layers are grouped and inserted at the matching @tailwind rule', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="input btn card float-squirrel align-banana align-sandwich"></div>`,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
function ({ addBase, addComponents, addUtilities }) {
|
||||
addBase({ body: { margin: 0 } })
|
||||
|
||||
addComponents({
|
||||
'.input': { background: 'white' },
|
||||
})
|
||||
addComponents({
|
||||
'.input': { background: 'white' },
|
||||
})
|
||||
|
||||
addUtilities({
|
||||
'.float-squirrel': { float: 'squirrel' },
|
||||
})
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@layer vanilla {
|
||||
strong {
|
||||
font-weight: medium;
|
||||
}
|
||||
addUtilities({
|
||||
'.float-squirrel': { float: 'squirrel' },
|
||||
})
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.btn {
|
||||
background: blue;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.align-banana {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
h1 {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.card {
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
p {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.align-sandwich {
|
||||
text-align: sandwich;
|
||||
}
|
||||
}
|
||||
|
||||
@layer chocolate {
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
expect.assertions(2)
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.warnings().length).toBe(0)
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
let input = css`
|
||||
@layer vanilla {
|
||||
strong {
|
||||
font-weight: medium;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.btn {
|
||||
background: blue;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: bold;
|
||||
@layer utilities {
|
||||
.align-banana {
|
||||
text-align: banana;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-weight: normal;
|
||||
@layer base {
|
||||
h1 {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
${defaults}
|
||||
|
||||
.input {
|
||||
background: white;
|
||||
@layer components {
|
||||
.card {
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: blue;
|
||||
@layer base {
|
||||
p {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.float-squirrel {
|
||||
float: squirrel;
|
||||
}
|
||||
|
||||
.align-banana {
|
||||
text-align: banana;
|
||||
}
|
||||
|
||||
.align-sandwich {
|
||||
text-align: sandwich;
|
||||
@layer utilities {
|
||||
.align-sandwich {
|
||||
text-align: sandwich;
|
||||
}
|
||||
}
|
||||
|
||||
@layer chocolate {
|
||||
@ -304,43 +251,100 @@ test('layers are grouped and inserted at the matching @tailwind rule', () => {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
`
|
||||
|
||||
it('should keep `@supports` rules inside `@layer`s', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="test"></div>` }],
|
||||
plugins: [],
|
||||
}
|
||||
expect.assertions(2)
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.test {
|
||||
--tw-test: 1;
|
||||
}
|
||||
|
||||
@supports (backdrop-filter: blur(1px)) {
|
||||
.test {
|
||||
--tw-test: 0.9;
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.warnings().length).toBe(0)
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@layer vanilla {
|
||||
strong {
|
||||
font-weight: medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
${defaults}
|
||||
|
||||
.input {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: blue;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.float-squirrel {
|
||||
float: squirrel;
|
||||
}
|
||||
|
||||
.align-banana {
|
||||
text-align: banana;
|
||||
}
|
||||
|
||||
.align-sandwich {
|
||||
text-align: sandwich;
|
||||
}
|
||||
|
||||
@layer chocolate {
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should keep `@supports` rules inside `@layer`s', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="test"></div>` }],
|
||||
plugins: [],
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.test {
|
||||
--tw-test: 1;
|
||||
}
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@supports (backdrop-filter: blur(1px)) {
|
||||
@layer utilities {
|
||||
.test {
|
||||
--tw-test: 0.9;
|
||||
--tw-test: 1;
|
||||
}
|
||||
|
||||
@supports (backdrop-filter: blur(1px)) {
|
||||
.test {
|
||||
--tw-test: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.test {
|
||||
--tw-test: 1;
|
||||
}
|
||||
|
||||
@supports (backdrop-filter: blur(1px)) {
|
||||
.test {
|
||||
--tw-test: 0.9;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,79 +1,81 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('using @layer without @tailwind', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="foo"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@layer components {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('using @layer without @tailwind', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="foo"></div>` }],
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(input, config)).rejects.toThrowError(
|
||||
'`@layer components` is used but no matching `@tailwind components` directive is present.'
|
||||
)
|
||||
})
|
||||
|
||||
test('using @responsive without @tailwind', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="foo"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@responsive {
|
||||
.foo {
|
||||
color: black;
|
||||
let input = css`
|
||||
@layer components {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(input, config)).rejects.toThrowError(
|
||||
'`@layer components` is used but no matching `@tailwind components` directive is present.'
|
||||
)
|
||||
})
|
||||
|
||||
test('using @responsive without @tailwind', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="foo"></div>` }],
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(input, config)).rejects.toThrowError(
|
||||
'`@responsive` is used but `@tailwind utilities` is missing.'
|
||||
)
|
||||
})
|
||||
|
||||
test('using @variants without @tailwind', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="foo"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@variants hover {
|
||||
.foo {
|
||||
color: black;
|
||||
let input = css`
|
||||
@responsive {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(input, config)).rejects.toThrowError(
|
||||
'`@responsive` is used but `@tailwind utilities` is missing.'
|
||||
)
|
||||
})
|
||||
|
||||
test('using @variants without @tailwind', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="foo"></div>` }],
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(input, config)).rejects.toThrowError(
|
||||
'`@variants` is used but `@tailwind utilities` is missing.'
|
||||
)
|
||||
})
|
||||
|
||||
test('non-Tailwind @layer rules are okay', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="foo"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@layer custom {
|
||||
.foo {
|
||||
color: black;
|
||||
let input = css`
|
||||
@variants hover {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
await expect(run(input, config)).rejects.toThrowError(
|
||||
'`@variants` is used but `@tailwind utilities` is missing.'
|
||||
)
|
||||
})
|
||||
|
||||
test('non-Tailwind @layer rules are okay', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="foo"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@layer custom {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`)
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
@layer custom {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,91 +1,95 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
it('should be possible to matchComponents', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="card-[#0088cc] hover:card-[#f0f]">
|
||||
<div class="card-header font-bold"></div>
|
||||
<div class="shadow"></div>
|
||||
<div class="card-footer text-center"></div>
|
||||
</div>`,
|
||||
crosscheck(() => {
|
||||
it('should be possible to matchComponents', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="card-[#0088cc] hover:card-[#f0f]">
|
||||
<div class="card-header font-bold"></div>
|
||||
<div class="shadow"></div>
|
||||
<div class="card-footer text-center"></div>
|
||||
</div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
plugins: [
|
||||
function ({ matchComponents }) {
|
||||
matchComponents({
|
||||
card: (value) => {
|
||||
return [
|
||||
{ color: value },
|
||||
{
|
||||
'.card-header': {
|
||||
borderTopWidth: 3,
|
||||
borderTopColor: value,
|
||||
plugins: [
|
||||
function ({ matchComponents }) {
|
||||
matchComponents({
|
||||
card: (value) => {
|
||||
return [
|
||||
{ color: value },
|
||||
{
|
||||
'.card-header': {
|
||||
borderTopWidth: 3,
|
||||
borderTopColor: value,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'.card-footer': {
|
||||
borderBottomWidth: 3,
|
||||
borderBottomColor: value,
|
||||
{
|
||||
'.card-footer': {
|
||||
borderBottomWidth: 3,
|
||||
borderBottomColor: value,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return run('@tailwind base; @tailwind components; @tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
return run('@tailwind base; @tailwind components; @tailwind utilities', config).then(
|
||||
(result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.card-\[\#0088cc\] {
|
||||
color: #0088cc;
|
||||
.card-\[\#0088cc\] {
|
||||
color: #0088cc;
|
||||
}
|
||||
|
||||
.card-\[\#0088cc\] .card-header {
|
||||
border-top-width: 3px;
|
||||
border-top-color: #0088cc;
|
||||
}
|
||||
|
||||
.card-\[\#0088cc\] .card-footer {
|
||||
border-bottom-width: 3px;
|
||||
border-bottom-color: #0088cc;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
|
||||
0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
|
||||
.hover\:card-\[\#f0f\]:hover {
|
||||
color: #f0f;
|
||||
}
|
||||
|
||||
.hover\:card-\[\#f0f\]:hover .card-header {
|
||||
border-top-width: 3px;
|
||||
border-top-color: #f0f;
|
||||
}
|
||||
|
||||
.hover\:card-\[\#f0f\]:hover .card-footer {
|
||||
border-bottom-width: 3px;
|
||||
border-bottom-color: #f0f;
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
.card-\[\#0088cc\] .card-header {
|
||||
border-top-width: 3px;
|
||||
border-top-color: #0088cc;
|
||||
}
|
||||
|
||||
.card-\[\#0088cc\] .card-footer {
|
||||
border-bottom-width: 3px;
|
||||
border-bottom-color: #0088cc;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
|
||||
0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
|
||||
.hover\:card-\[\#f0f\]:hover {
|
||||
color: #f0f;
|
||||
}
|
||||
|
||||
.hover\:card-\[\#f0f\]:hover .card-header {
|
||||
border-top-width: 3px;
|
||||
border-top-color: #f0f;
|
||||
}
|
||||
|
||||
.hover\:card-\[\#f0f\]:hover .card-footer {
|
||||
border-bottom-width: 3px;
|
||||
border-bottom-color: #f0f;
|
||||
}
|
||||
`)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,29 +1,32 @@
|
||||
import { elementSelectorParser } from '../src/lib/resolveDefaultsAtRules'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
it.each`
|
||||
before | after
|
||||
${'*'} | ${'*'}
|
||||
${'*:hover'} | ${'*'}
|
||||
${'* > *'} | ${'* > *'}
|
||||
${'.foo'} | ${'.foo'}
|
||||
${'.foo:hover'} | ${'.foo'}
|
||||
${'.foo:focus:hover'} | ${'.foo'}
|
||||
${'li:first-child'} | ${'li'}
|
||||
${'li:before'} | ${'li:before'}
|
||||
${'li::before'} | ${'li::before'}
|
||||
${'#app .foo'} | ${'.foo'}
|
||||
${'#app'} | ${'[id=app]'}
|
||||
${'#app.other'} | ${'.other'}
|
||||
${'input[type="text"]'} | ${'[type="text"]'}
|
||||
${'input[type="text"].foo'} | ${'.foo'}
|
||||
${'.group .group\\:foo'} | ${'.group\\:foo'}
|
||||
${'.group:hover .group-hover\\:foo'} | ${'.group-hover\\:foo'}
|
||||
${'.owl > * + *'} | ${'.owl > *'}
|
||||
${'.owl > :not([hidden]) + :not([hidden])'} | ${'.owl > *'}
|
||||
${'.group:hover .group-hover\\:owl > :not([hidden]) + :not([hidden])'} | ${'.group-hover\\:owl > *'}
|
||||
${'.peer:first-child ~ .peer-first\\:shadow-md'} | ${'.peer-first\\:shadow-md'}
|
||||
${'.whats ~ .next > span:hover'} | ${'span'}
|
||||
${'.foo .bar ~ .baz > .next > span > article:hover'} | ${'article'}
|
||||
`('should generate "$after" from "$before"', ({ before, after }) => {
|
||||
expect(elementSelectorParser.transformSync(before).join(', ')).toEqual(after)
|
||||
crosscheck(() => {
|
||||
it.each`
|
||||
before | after
|
||||
${'*'} | ${'*'}
|
||||
${'*:hover'} | ${'*'}
|
||||
${'* > *'} | ${'* > *'}
|
||||
${'.foo'} | ${'.foo'}
|
||||
${'.foo:hover'} | ${'.foo'}
|
||||
${'.foo:focus:hover'} | ${'.foo'}
|
||||
${'li:first-child'} | ${'li'}
|
||||
${'li:before'} | ${'li:before'}
|
||||
${'li::before'} | ${'li::before'}
|
||||
${'#app .foo'} | ${'.foo'}
|
||||
${'#app'} | ${'[id=app]'}
|
||||
${'#app.other'} | ${'.other'}
|
||||
${'input[type="text"]'} | ${'[type="text"]'}
|
||||
${'input[type="text"].foo'} | ${'.foo'}
|
||||
${'.group .group\\:foo'} | ${'.group\\:foo'}
|
||||
${'.group:hover .group-hover\\:foo'} | ${'.group-hover\\:foo'}
|
||||
${'.owl > * + *'} | ${'.owl > *'}
|
||||
${'.owl > :not([hidden]) + :not([hidden])'} | ${'.owl > *'}
|
||||
${'.group:hover .group-hover\\:owl > :not([hidden]) + :not([hidden])'} | ${'.group-hover\\:owl > *'}
|
||||
${'.peer:first-child ~ .peer-first\\:shadow-md'} | ${'.peer-first\\:shadow-md'}
|
||||
${'.whats ~ .next > span:hover'} | ${'span'}
|
||||
${'.foo .bar ~ .baz > .next > span > article:hover'} | ${'article'}
|
||||
`('should generate "$after" from "$before"', ({ before, after }) => {
|
||||
expect(elementSelectorParser.transformSync(before).join(', ')).toEqual(after)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,97 +1,99 @@
|
||||
import selectorParser from 'postcss-selector-parser'
|
||||
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('modify selectors', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="font-bold"></div>
|
||||
<div class="foo:font-bold"></div>
|
||||
<div class="foo:hover:font-bold"></div>
|
||||
<div class="sm:foo:font-bold"></div>
|
||||
<div class="md:foo:focus:font-bold"></div>
|
||||
<div class="markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
<div class="foo:markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
<div class="foo:visited:markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
<div class="lg:foo:disabled:markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [
|
||||
function ({ addVariant }) {
|
||||
addVariant('foo', ({ modifySelectors, separator }) => {
|
||||
modifySelectors(({ selector }) => {
|
||||
return selectorParser((selectors) => {
|
||||
selectors.walkClasses((classNode) => {
|
||||
classNode.value = `foo${separator}${classNode.value}`
|
||||
classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
|
||||
})
|
||||
}).processSync(selector)
|
||||
crosscheck(() => {
|
||||
test('modify selectors', () => {
|
||||
let config = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="font-bold"></div>
|
||||
<div class="foo:font-bold"></div>
|
||||
<div class="foo:hover:font-bold"></div>
|
||||
<div class="sm:foo:font-bold"></div>
|
||||
<div class="md:foo:focus:font-bold"></div>
|
||||
<div class="markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
<div class="foo:markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
<div class="foo:visited:markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
<div class="lg:foo:disabled:markdown">
|
||||
<p>Lorem ipsum dolor sit amet...</p>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [
|
||||
function ({ addVariant }) {
|
||||
addVariant('foo', ({ modifySelectors, separator }) => {
|
||||
modifySelectors(({ selector }) => {
|
||||
return selectorParser((selectors) => {
|
||||
selectors.walkClasses((classNode) => {
|
||||
classNode.value = `foo${separator}${classNode.value}`
|
||||
classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
|
||||
})
|
||||
}).processSync(selector)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.markdown > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.markdown > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.foo .foo\:markdown > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.foo .foo\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.foo .foo\:visited\:markdown:visited > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.foo .foo\:hover\:font-bold:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.foo .sm\:foo\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.foo .md\:foo\:focus\:font-bold:focus {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.foo .lg\:foo\:disabled\:markdown:disabled > p {
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.markdown > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.markdown > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.foo .foo\:markdown > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.foo .foo\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.foo .foo\:visited\:markdown:visited > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.foo .foo\:hover\:font-bold:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.foo .sm\:foo\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.foo .md\:foo\:focus\:font-bold:focus {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.foo .lg\:foo\:disabled\:markdown:disabled > p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,52 +1,54 @@
|
||||
import path from 'path'
|
||||
import postcss from 'postcss'
|
||||
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
function pluginThatMutatesRules() {
|
||||
return (root) => {
|
||||
root.walkRules((rule) => {
|
||||
rule.nodes
|
||||
.filter((node) => node.prop === 'background-image')
|
||||
.forEach((node) => {
|
||||
node.value = 'url("./bar.png")'
|
||||
})
|
||||
crosscheck(() => {
|
||||
function pluginThatMutatesRules() {
|
||||
return (root) => {
|
||||
root.walkRules((rule) => {
|
||||
rule.nodes
|
||||
.filter((node) => node.prop === 'background-image')
|
||||
.forEach((node) => {
|
||||
node.value = 'url("./bar.png")'
|
||||
})
|
||||
|
||||
return rule
|
||||
})
|
||||
return rule
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('plugins mutating rules after tailwind doesnt break it', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-foo"></div>` }],
|
||||
theme: {
|
||||
backgroundImage: {
|
||||
foo: 'url("./foo.png")',
|
||||
test('plugins mutating rules after tailwind doesnt break it', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="bg-foo"></div>` }],
|
||||
theme: {
|
||||
backgroundImage: {
|
||||
foo: 'url("./foo.png")',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
function checkResult(result) {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-foo {
|
||||
background-image: url('./foo.png');
|
||||
}
|
||||
`)
|
||||
}
|
||||
function checkResult(result) {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.bg-foo {
|
||||
background-image: url('./foo.png');
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
// Verify the first run produces the expected result
|
||||
let firstRun = await run('@tailwind utilities', config)
|
||||
checkResult(firstRun)
|
||||
// Verify the first run produces the expected result
|
||||
let firstRun = await run('@tailwind utilities', config)
|
||||
checkResult(firstRun)
|
||||
|
||||
// Outside of the context of tailwind jit more postcss plugins may operate on the AST:
|
||||
// In this case we have a plugin that mutates rules directly
|
||||
await postcss([pluginThatMutatesRules()]).process(firstRun, {
|
||||
from: path.resolve(__filename),
|
||||
// Outside of the context of tailwind jit more postcss plugins may operate on the AST:
|
||||
// In this case we have a plugin that mutates rules directly
|
||||
await postcss([pluginThatMutatesRules()]).process(firstRun, {
|
||||
from: path.resolve(__filename),
|
||||
})
|
||||
|
||||
// Verify subsequent runs don't produce mutated rules
|
||||
let secondRun = await run('@tailwind utilities', config)
|
||||
checkResult(secondRun)
|
||||
})
|
||||
|
||||
// Verify subsequent runs don't produce mutated rules
|
||||
let secondRun = await run('@tailwind utilities', config)
|
||||
checkResult(secondRun)
|
||||
})
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import negateValue from '../src/util/negateValue'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
test('it negates numeric CSS values', () => {
|
||||
expect(negateValue('5')).toEqual('-5')
|
||||
expect(negateValue('10px')).toEqual('-10px')
|
||||
expect(negateValue('18rem')).toEqual('-18rem')
|
||||
expect(negateValue('-10')).toEqual('10')
|
||||
expect(negateValue('-7ch')).toEqual('7ch')
|
||||
})
|
||||
crosscheck(() => {
|
||||
test('it negates numeric CSS values', () => {
|
||||
expect(negateValue('5')).toEqual('-5')
|
||||
expect(negateValue('10px')).toEqual('-10px')
|
||||
expect(negateValue('18rem')).toEqual('-18rem')
|
||||
expect(negateValue('-10')).toEqual('10')
|
||||
expect(negateValue('-7ch')).toEqual('7ch')
|
||||
})
|
||||
|
||||
test('values that cannot be negated become undefined', () => {
|
||||
expect(negateValue('auto')).toBeUndefined()
|
||||
expect(negateValue('cover')).toBeUndefined()
|
||||
test('values that cannot be negated become undefined', () => {
|
||||
expect(negateValue('auto')).toBeUndefined()
|
||||
expect(negateValue('cover')).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,27 +1,29 @@
|
||||
import * as path from 'path'
|
||||
import { run, css, defaults } from './util/run'
|
||||
import { crosscheck, run, css, defaults } from './util/run'
|
||||
|
||||
it('should be possible to use negated content patterns', () => {
|
||||
let config = {
|
||||
content: [
|
||||
path.resolve(__dirname, './negated-content-*.test.html'),
|
||||
'!' + path.resolve(__dirname, './negated-content-ignore.test.html'),
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
crosscheck(() => {
|
||||
it('should be possible to use negated content patterns', () => {
|
||||
let config = {
|
||||
content: [
|
||||
path.resolve(__dirname, './negated-content-*.test.html'),
|
||||
'!' + path.resolve(__dirname, './negated-content-ignore.test.html'),
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
`)
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,344 +1,348 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('using a negative prefix with a negative scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-2 -mt-2"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
2: '8px',
|
||||
'-2': '-4px',
|
||||
crosscheck(() => {
|
||||
test('using a negative prefix with a negative scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-2 -mt-2"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
2: '8px',
|
||||
'-2': '-4px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-2 {
|
||||
margin-top: -4px;
|
||||
}
|
||||
.mt-2 {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-2 {
|
||||
margin-top: -4px;
|
||||
}
|
||||
.mt-2 {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('using a negative scale value with a plugin that does not support dynamic negative values', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-opacity-50"></div>` }],
|
||||
theme: {
|
||||
opacity: {
|
||||
'-50': '0.5',
|
||||
test('using a negative scale value with a plugin that does not support dynamic negative values', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-opacity-50"></div>` }],
|
||||
theme: {
|
||||
opacity: {
|
||||
'-50': '0.5',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-opacity-50 {
|
||||
opacity: 0.5;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-opacity-50 {
|
||||
opacity: 0.5;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('using a negative prefix without a negative scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
5: '20px',
|
||||
test('using a negative prefix without a negative scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
5: '20px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-5 {
|
||||
margin-top: -20px;
|
||||
}
|
||||
.mt-5 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-5 {
|
||||
margin-top: -20px;
|
||||
}
|
||||
.mt-5 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('being an asshole', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
'-[10px]': '55px',
|
||||
test('being an asshole', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
'-[10px]': '55px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-\[10px\] {
|
||||
margin-top: 55px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-\[10px\] {
|
||||
margin-top: 55px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('being a real asshole', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
'[10px]': '55px',
|
||||
test('being a real asshole', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
'[10px]': '55px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-\[10px\] {
|
||||
margin-top: -55px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-\[10px\] {
|
||||
margin-top: -55px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('a value that includes a variable', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
5: 'var(--sizing-5)',
|
||||
test('a value that includes a variable', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
5: 'var(--sizing-5)',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-5 {
|
||||
margin-top: calc(var(--sizing-5) * -1);
|
||||
}
|
||||
.mt-5 {
|
||||
margin-top: var(--sizing-5);
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-5 {
|
||||
margin-top: calc(var(--sizing-5) * -1);
|
||||
}
|
||||
.mt-5 {
|
||||
margin-top: var(--sizing-5);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('a value that includes a calc', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
5: 'calc(52px * -3)',
|
||||
test('a value that includes a calc', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
5: 'calc(52px * -3)',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-5 {
|
||||
margin-top: calc(calc(52px * -3) * -1);
|
||||
}
|
||||
.mt-5 {
|
||||
margin-top: calc(52px * -3);
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-5 {
|
||||
margin-top: calc(calc(52px * -3) * -1);
|
||||
}
|
||||
.mt-5 {
|
||||
margin-top: calc(52px * -3);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('a value that includes min/max/clamp functions', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-min -mt-min mt-max -mt-max mt-clamp -mt-clamp"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
min: 'min(100vmin, 3rem)',
|
||||
max: 'max(100vmax, 3rem)',
|
||||
clamp: 'clamp(1rem, 100vh, 3rem)',
|
||||
test('a value that includes min/max/clamp functions', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{ raw: html`<div class="mt-min -mt-min mt-max -mt-max mt-clamp -mt-clamp"></div>` },
|
||||
],
|
||||
theme: {
|
||||
margin: {
|
||||
min: 'min(100vmin, 3rem)',
|
||||
max: 'max(100vmax, 3rem)',
|
||||
clamp: 'clamp(1rem, 100vh, 3rem)',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-clamp {
|
||||
margin-top: calc(clamp(1rem, 100vh, 3rem) * -1);
|
||||
}
|
||||
.-mt-max {
|
||||
margin-top: calc(max(100vmax, 3rem) * -1);
|
||||
}
|
||||
.-mt-min {
|
||||
margin-top: calc(min(100vmin, 3rem) * -1);
|
||||
}
|
||||
.mt-clamp {
|
||||
margin-top: clamp(1rem, 100vh, 3rem);
|
||||
}
|
||||
.mt-max {
|
||||
margin-top: max(100vmax, 3rem);
|
||||
}
|
||||
.mt-min {
|
||||
margin-top: min(100vmin, 3rem);
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-clamp {
|
||||
margin-top: calc(clamp(1rem, 100vh, 3rem) * -1);
|
||||
}
|
||||
.-mt-max {
|
||||
margin-top: calc(max(100vmax, 3rem) * -1);
|
||||
}
|
||||
.-mt-min {
|
||||
margin-top: calc(min(100vmin, 3rem) * -1);
|
||||
}
|
||||
.mt-clamp {
|
||||
margin-top: clamp(1rem, 100vh, 3rem);
|
||||
}
|
||||
.mt-max {
|
||||
margin-top: max(100vmax, 3rem);
|
||||
}
|
||||
.mt-min {
|
||||
margin-top: min(100vmin, 3rem);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('a keyword value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-auto mt-auto"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
auto: 'auto',
|
||||
test('a keyword value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-auto mt-auto"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
auto: 'auto',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.mt-auto {
|
||||
margin-top: auto;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.mt-auto {
|
||||
margin-top: auto;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('a zero value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-0 -mt-0"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
0: '0',
|
||||
test('a zero value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt-0 -mt-0"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
0: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.mt-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.mt-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('a color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-bg-red"></div>` }],
|
||||
theme: {
|
||||
colors: {
|
||||
red: 'red',
|
||||
test('a color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-bg-red"></div>` }],
|
||||
theme: {
|
||||
colors: {
|
||||
red: 'red',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css``)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css``)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary values', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
|
||||
}
|
||||
test('arbitrary values', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-\[10px\] {
|
||||
margin-top: -10px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-\[10px\] {
|
||||
margin-top: -10px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('negating a negative scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-weird"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
weird: '-15px',
|
||||
test('negating a negative scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-weird"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
weird: '-15px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-weird {
|
||||
margin-top: 15px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt-weird {
|
||||
margin-top: 15px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('negating a default value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
DEFAULT: '15px',
|
||||
test('negating a default value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
DEFAULT: '15px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt {
|
||||
margin-top: -15px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt {
|
||||
margin-top: -15px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('using a negative prefix with a negative default scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt -mt"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
DEFAULT: '8px',
|
||||
'-DEFAULT': '-4px',
|
||||
test('using a negative prefix with a negative default scale value', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="mt -mt"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
DEFAULT: '8px',
|
||||
'-DEFAULT': '-4px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt {
|
||||
margin-top: -4px;
|
||||
}
|
||||
.mt {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.-mt {
|
||||
margin-top: -4px;
|
||||
}
|
||||
.mt {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('negating a default value with a configured prefix', () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: html`<div class="tw--mt"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
DEFAULT: '15px',
|
||||
test('negating a default value with a configured prefix', () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: html`<div class="tw--mt"></div>` }],
|
||||
theme: {
|
||||
margin: {
|
||||
DEFAULT: '15px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.tw--mt {
|
||||
margin-top: -15px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary value keywords should be ignored', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[auto]"></div>` }],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css``)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css`
|
||||
.tw--mt {
|
||||
margin-top: -15px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary value keywords should be ignored', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="-mt-[auto]"></div>` }],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchCss(css``)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,42 +1,41 @@
|
||||
import { normalizeConfig } from '../src/util/normalizeConfig'
|
||||
import { run, css } from './util/run'
|
||||
import resolveConfig from '../src/public/resolve-config'
|
||||
import { env } from '../src/lib/sharedState'
|
||||
import log from '../src/util/log'
|
||||
import { crosscheck, run, css } from './util/run'
|
||||
|
||||
let t = env.OXIDE ? test.skip : test
|
||||
|
||||
it.each`
|
||||
config
|
||||
${{ purge: [{ raw: 'text-center' }] }}
|
||||
${{ purge: { content: [{ raw: 'text-center' }] } }}
|
||||
${{ content: { content: [{ raw: 'text-center' }] } }}
|
||||
`('should normalize content $config', ({ config }) => {
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
`)
|
||||
crosscheck(({ stable, oxide }) => {
|
||||
it.each`
|
||||
config
|
||||
${{ purge: [{ raw: 'text-center' }] }}
|
||||
${{ purge: { content: [{ raw: 'text-center' }] } }}
|
||||
${{ content: { content: [{ raw: 'text-center' }] } }}
|
||||
`('should normalize content $config', ({ config }) => {
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it.each`
|
||||
config
|
||||
${{ purge: { safelist: ['text-center'] } }}
|
||||
${{ purge: { options: { safelist: ['text-center'] } } }}
|
||||
${{ content: { safelist: ['text-center'] } }}
|
||||
`('should normalize safelist $config', ({ config }) => {
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
`)
|
||||
it.each`
|
||||
config
|
||||
${{ purge: { safelist: ['text-center'] } }}
|
||||
${{ purge: { options: { safelist: ['text-center'] } } }}
|
||||
${{ content: { safelist: ['text-center'] } }}
|
||||
`('should normalize safelist $config', ({ config }) => {
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.each`
|
||||
oxide.test.todo('should normalize extractors')
|
||||
stable.test.each`
|
||||
config
|
||||
${{ content: [{ raw: 'text-center' }], purge: { extract: () => ['font-bold'] } }}
|
||||
${{ content: [{ raw: 'text-center' }], purge: { extract: { DEFAULT: () => ['font-bold'] } } }}
|
||||
@ -50,103 +49,104 @@ t.each`
|
||||
}}
|
||||
${{ content: [{ raw: 'text-center' }], purge: { extract: { html: () => ['font-bold'] } } }}
|
||||
`('should normalize extractors $config', ({ config }) => {
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t('should still be possible to use the "old" v2 config', () => {
|
||||
let config = {
|
||||
purge: {
|
||||
oxide.test.todo('should still be possible to use the "old" v2 config')
|
||||
stable.test('should still be possible to use the "old" v2 config', () => {
|
||||
let config = {
|
||||
purge: {
|
||||
content: [
|
||||
{ raw: 'text-svelte', extension: 'svelte' },
|
||||
{ raw: '# My Big Heading', extension: 'md' },
|
||||
],
|
||||
options: {
|
||||
defaultExtractor(content) {
|
||||
return content.split(' ').concat(['font-bold'])
|
||||
},
|
||||
},
|
||||
extract: {
|
||||
svelte(content) {
|
||||
return content.replace('svelte', 'center').split(' ')
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
md() {
|
||||
return 'text-4xl'
|
||||
},
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
extends: {},
|
||||
},
|
||||
variants: {
|
||||
extends: {},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-4xl {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should keep content files with globs', () => {
|
||||
let config = {
|
||||
content: ['./example-folder/**/*.{html,js}'],
|
||||
}
|
||||
|
||||
expect(normalizeConfig(resolveConfig(config)).content).toEqual({
|
||||
files: ['./example-folder/**/*.{html,js}'],
|
||||
relative: false,
|
||||
extract: {},
|
||||
transform: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should warn when we detect invalid globs with incorrect brace expansion', () => {
|
||||
let spy = jest.spyOn(log, 'warn')
|
||||
|
||||
let config = {
|
||||
content: [
|
||||
{ raw: 'text-svelte', extension: 'svelte' },
|
||||
{ raw: '# My Big Heading', extension: 'md' },
|
||||
'./{example-folder}/**/*.{html,js}',
|
||||
'./{example-folder}/**/*.{html}',
|
||||
'./example-folder/**/*.{html}',
|
||||
],
|
||||
options: {
|
||||
defaultExtractor(content) {
|
||||
return content.split(' ').concat(['font-bold'])
|
||||
},
|
||||
},
|
||||
extract: {
|
||||
svelte(content) {
|
||||
return content.replace('svelte', 'center').split(' ')
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
md() {
|
||||
return 'text-4xl'
|
||||
},
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
extends: {},
|
||||
},
|
||||
variants: {
|
||||
extends: {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
// No rewrite happens
|
||||
expect(normalizeConfig(resolveConfig(config)).content).toEqual({
|
||||
files: [
|
||||
'./{example-folder}/**/*.{html,js}',
|
||||
'./{example-folder}/**/*.{html}',
|
||||
'./example-folder/**/*.{html}',
|
||||
],
|
||||
relative: false,
|
||||
extract: {},
|
||||
transform: {},
|
||||
})
|
||||
|
||||
.text-4xl {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
// But a warning should happen
|
||||
expect(spy).toHaveBeenCalledTimes(2)
|
||||
expect(spy.mock.calls.map((x) => x[0])).toEqual(['invalid-glob-braces', 'invalid-glob-braces'])
|
||||
spy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
it('should keep content files with globs', () => {
|
||||
let config = {
|
||||
content: ['./example-folder/**/*.{html,js}'],
|
||||
}
|
||||
|
||||
expect(normalizeConfig(resolveConfig(config)).content).toEqual({
|
||||
files: ['./example-folder/**/*.{html,js}'],
|
||||
relative: false,
|
||||
extract: {},
|
||||
transform: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should warn when we detect invalid globs with incorrect brace expansion', () => {
|
||||
let spy = jest.spyOn(log, 'warn')
|
||||
|
||||
let config = {
|
||||
content: [
|
||||
'./{example-folder}/**/*.{html,js}',
|
||||
'./{example-folder}/**/*.{html}',
|
||||
'./example-folder/**/*.{html}',
|
||||
],
|
||||
}
|
||||
|
||||
// No rewrite happens
|
||||
expect(normalizeConfig(resolveConfig(config)).content).toEqual({
|
||||
files: [
|
||||
'./{example-folder}/**/*.{html,js}',
|
||||
'./{example-folder}/**/*.{html}',
|
||||
'./example-folder/**/*.{html}',
|
||||
],
|
||||
relative: false,
|
||||
extract: {},
|
||||
transform: {},
|
||||
})
|
||||
|
||||
// But a warning should happen
|
||||
expect(spy).toHaveBeenCalledTimes(2)
|
||||
expect(spy.mock.calls.map((x) => x[0])).toEqual(['invalid-glob-braces', 'invalid-glob-braces'])
|
||||
|
||||
spy.mockClear()
|
||||
})
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { normalize } from '../src/util/dataTypes'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
let table = [
|
||||
['foo', 'foo'],
|
||||
@ -46,6 +47,8 @@ let table = [
|
||||
['color(0_0_0_/_1.0)', 'color(0 0 0 / 1.0)'],
|
||||
]
|
||||
|
||||
it.each(table)('normalize data: %s', (input, output) => {
|
||||
expect(normalize(input)).toBe(output)
|
||||
crosscheck(() => {
|
||||
it.each(table)('normalize data: %s', (input, output) => {
|
||||
expect(normalize(input)).toBe(output)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,63 +1,66 @@
|
||||
import { normalizeScreens } from '../src/util/normalizeScreens'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
it('should normalize an array of string values', () => {
|
||||
let screens = ['768px', '1200px']
|
||||
crosscheck(() => {
|
||||
it('should normalize an array of string values', () => {
|
||||
let screens = ['768px', '1200px']
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: '768px', not: false, values: [{ min: '768px', max: undefined }] },
|
||||
{ name: '1200px', not: false, values: [{ min: '1200px', max: undefined }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with string values', () => {
|
||||
let screens = {
|
||||
a: '768px',
|
||||
b: '1200px',
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', not: false, values: [{ min: '1200px', max: undefined }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with object values', () => {
|
||||
let screens = {
|
||||
a: { min: '768px' },
|
||||
b: { max: '1200px' },
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with multiple object values', () => {
|
||||
let screens = {
|
||||
a: [{ min: '768px' }, { max: '1200px' }],
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{
|
||||
name: 'a',
|
||||
not: false,
|
||||
values: [
|
||||
{ max: undefined, min: '768px', raw: undefined },
|
||||
{ max: '1200px', min: undefined, raw: undefined },
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with object values (min-width normalized to width)', () => {
|
||||
let screens = {
|
||||
a: { 'min-width': '768px' },
|
||||
b: { max: '1200px' },
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
|
||||
])
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: '768px', not: false, values: [{ min: '768px', max: undefined }] },
|
||||
{ name: '1200px', not: false, values: [{ min: '1200px', max: undefined }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with string values', () => {
|
||||
let screens = {
|
||||
a: '768px',
|
||||
b: '1200px',
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', not: false, values: [{ min: '1200px', max: undefined }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with object values', () => {
|
||||
let screens = {
|
||||
a: { min: '768px' },
|
||||
b: { max: '1200px' },
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with multiple object values', () => {
|
||||
let screens = {
|
||||
a: [{ min: '768px' }, { max: '1200px' }],
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{
|
||||
name: 'a',
|
||||
not: false,
|
||||
values: [
|
||||
{ max: undefined, min: '768px', raw: undefined },
|
||||
{ max: '1200px', min: undefined, raw: undefined },
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should normalize an object with object values (min-width normalized to width)', () => {
|
||||
let screens = {
|
||||
a: { 'min-width': '768px' },
|
||||
b: { max: '1200px' },
|
||||
}
|
||||
|
||||
expect(normalizeScreens(screens)).toEqual([
|
||||
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
|
||||
{ name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,31 +1,23 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { env } from '../src/lib/sharedState'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
let t = env.ENGINE === 'oxide' ? test : test.skip
|
||||
crosscheck(({ stable, oxide }) => {
|
||||
stable.test.todo('space-x uses physical properties')
|
||||
oxide.test('space-x uses logical properties', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="space-x-4"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
env.OXIDE = true
|
||||
})
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
afterEach(() => {
|
||||
env.OXIDE = false
|
||||
})
|
||||
|
||||
t('space-x uses logical properties', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="space-x-4"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-inline-end: calc(1rem * var(--tw-space-x-reverse));
|
||||
margin-inline-start: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
`)
|
||||
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-inline-end: calc(1rem * var(--tw-space-x-reverse));
|
||||
margin-inline-start: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,136 +1,138 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('basic parallel variants', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="hover:test:font-black test:font-bold test:font-medium font-normal"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
function test({ addVariant }) {
|
||||
addVariant('test', ['& *::test', '&::test'])
|
||||
},
|
||||
],
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('basic parallel variants', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="hover:test:font-black test:font-bold test:font-medium font-normal"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
function test({ addVariant }) {
|
||||
addVariant('test', ['& *::test', '&::test'])
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
.test\:font-bold *::test {
|
||||
font-weight: 700;
|
||||
}
|
||||
.test\:font-medium *::test {
|
||||
font-weight: 500;
|
||||
}
|
||||
.hover\:test\:font-black *:hover::test {
|
||||
font-weight: 900;
|
||||
}
|
||||
.test\:font-bold::test {
|
||||
font-weight: 700;
|
||||
}
|
||||
.test\:font-medium::test {
|
||||
font-weight: 500;
|
||||
}
|
||||
.hover\:test\:font-black:hover::test {
|
||||
font-weight: 900;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
.test\:font-bold *::test {
|
||||
font-weight: 700;
|
||||
}
|
||||
.test\:font-medium *::test {
|
||||
font-weight: 500;
|
||||
}
|
||||
.hover\:test\:font-black *:hover::test {
|
||||
font-weight: 900;
|
||||
}
|
||||
.test\:font-bold::test {
|
||||
font-weight: 700;
|
||||
}
|
||||
.test\:font-medium::test {
|
||||
font-weight: 500;
|
||||
}
|
||||
.hover\:test\:font-black:hover::test {
|
||||
font-weight: 900;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('parallel variants can be generated using a function that returns parallel variants', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="hover:test:font-black test:font-bold test:font-medium font-normal"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
function test({ addVariant }) {
|
||||
addVariant('test', () => ['& *::test', '&::test'])
|
||||
},
|
||||
],
|
||||
}
|
||||
test('parallel variants can be generated using a function that returns parallel variants', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="hover:test:font-black test:font-bold test:font-medium font-normal"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
function test({ addVariant }) {
|
||||
addVariant('test', () => ['& *::test', '&::test'])
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
.test\:font-bold *::test {
|
||||
font-weight: 700;
|
||||
}
|
||||
.test\:font-medium *::test {
|
||||
font-weight: 500;
|
||||
}
|
||||
.test\:font-bold::test {
|
||||
font-weight: 700;
|
||||
}
|
||||
.test\:font-medium::test {
|
||||
font-weight: 500;
|
||||
}
|
||||
.hover\:test\:font-black *:hover::test {
|
||||
font-weight: 900;
|
||||
}
|
||||
.hover\:test\:font-black:hover::test {
|
||||
font-weight: 900;
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
.test\:font-bold *::test {
|
||||
font-weight: 700;
|
||||
}
|
||||
.test\:font-medium *::test {
|
||||
font-weight: 500;
|
||||
}
|
||||
.test\:font-bold::test {
|
||||
font-weight: 700;
|
||||
}
|
||||
.test\:font-medium::test {
|
||||
font-weight: 500;
|
||||
}
|
||||
.hover\:test\:font-black *:hover::test {
|
||||
font-weight: 900;
|
||||
}
|
||||
.hover\:test\:font-black:hover::test {
|
||||
font-weight: 900;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('a function that returns parallel variants can modify the container', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="hover:test:font-black test:font-bold test:font-medium font-normal"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
function test({ addVariant }) {
|
||||
addVariant('test', ({ container }) => {
|
||||
container.walkDecls((decl) => {
|
||||
decl.value = `calc(0 + ${decl.value})`
|
||||
test('a function that returns parallel variants can modify the container', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="hover:test:font-black test:font-bold test:font-medium font-normal"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
function test({ addVariant }) {
|
||||
addVariant('test', ({ container }) => {
|
||||
container.walkDecls((decl) => {
|
||||
decl.value = `calc(0 + ${decl.value})`
|
||||
})
|
||||
|
||||
return ['& *::test', '&::test']
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return ['& *::test', '&::test']
|
||||
})
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
.test\:font-bold *::test {
|
||||
font-weight: calc(0 + 700);
|
||||
}
|
||||
.test\:font-medium *::test {
|
||||
font-weight: calc(0 + 500);
|
||||
}
|
||||
.test\:font-bold::test {
|
||||
font-weight: calc(0 + 700);
|
||||
}
|
||||
.test\:font-medium::test {
|
||||
font-weight: calc(0 + 500);
|
||||
}
|
||||
.hover\:test\:font-black *:hover::test {
|
||||
font-weight: calc(0 + 900);
|
||||
}
|
||||
.hover\:test\:font-black:hover::test {
|
||||
font-weight: calc(0 + 900);
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
.test\:font-bold *::test {
|
||||
font-weight: calc(0 + 700);
|
||||
}
|
||||
.test\:font-medium *::test {
|
||||
font-weight: calc(0 + 500);
|
||||
}
|
||||
.test\:font-bold::test {
|
||||
font-weight: calc(0 + 700);
|
||||
}
|
||||
.test\:font-medium::test {
|
||||
font-weight: calc(0 + 500);
|
||||
}
|
||||
.hover\:test\:font-black *:hover::test {
|
||||
font-weight: calc(0 + 900);
|
||||
}
|
||||
.hover\:test\:font-black:hover::test {
|
||||
font-weight: calc(0 + 900);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,200 +1,203 @@
|
||||
import parseAnimationValue from '../src/util/parseAnimationValue'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
describe('Tailwind Defaults', () => {
|
||||
it.each([
|
||||
[
|
||||
'spin 1s linear infinite',
|
||||
{
|
||||
value: 'spin 1s linear infinite',
|
||||
name: 'spin',
|
||||
duration: '1s',
|
||||
timingFunction: 'linear',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
],
|
||||
[
|
||||
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
{
|
||||
value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
name: 'ping',
|
||||
duration: '1s',
|
||||
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
],
|
||||
[
|
||||
'bounce 1s infinite',
|
||||
{
|
||||
value: 'bounce 1s infinite',
|
||||
name: 'bounce',
|
||||
duration: '1s',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
],
|
||||
])('should be possible to parse: "%s"', (input, expected) => {
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(1)
|
||||
expect(parsed[0]).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('css variables', () => {
|
||||
it('should be possible to use css variables', () => {
|
||||
let parsed = parseAnimationValue('jump var(--animation-duration, 10s) linear infinite')
|
||||
expect(parsed[0]).toEqual({
|
||||
value: 'jump var(--animation-duration, 10s) linear infinite',
|
||||
name: 'jump',
|
||||
timingFunction: 'linear',
|
||||
iterationCount: 'infinite',
|
||||
unknown: ['var(--animation-duration, 10s)'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('MDN Examples', () => {
|
||||
it.each([
|
||||
[
|
||||
'3s ease-in 1s 2 reverse both paused slidein',
|
||||
{
|
||||
value: '3s ease-in 1s 2 reverse both paused slidein',
|
||||
delay: '1s',
|
||||
direction: 'reverse',
|
||||
duration: '3s',
|
||||
fillMode: 'both',
|
||||
iterationCount: '2',
|
||||
name: 'slidein',
|
||||
playState: 'paused',
|
||||
timingFunction: 'ease-in',
|
||||
},
|
||||
],
|
||||
[
|
||||
'slidein 3s linear 1s',
|
||||
{
|
||||
value: 'slidein 3s linear 1s',
|
||||
delay: '1s',
|
||||
duration: '3s',
|
||||
name: 'slidein',
|
||||
timingFunction: 'linear',
|
||||
},
|
||||
],
|
||||
['slidein 3s', { value: 'slidein 3s', duration: '3s', name: 'slidein' }],
|
||||
])('should be possible to parse: "%s"', (input, expected) => {
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(1)
|
||||
expect(parsed[0]).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('duration & delay', () => {
|
||||
it.each([
|
||||
// Positive seconds (integer)
|
||||
['spin 2s 1s linear', { duration: '2s', delay: '1s' }],
|
||||
|
||||
// Negative seconds (integer)
|
||||
['spin -2s -1s linear', { duration: '-2s', delay: '-1s' }],
|
||||
|
||||
// Positive seconds (float)
|
||||
['spin 2.321s 1.321s linear', { duration: '2.321s', delay: '1.321s' }],
|
||||
|
||||
// Negative seconds (float)
|
||||
['spin -2.321s -1.321s linear', { duration: '-2.321s', delay: '-1.321s' }],
|
||||
|
||||
// Positive milliseconds (integer)
|
||||
['spin 200ms 100ms linear', { duration: '200ms', delay: '100ms' }],
|
||||
|
||||
// Negative milliseconds (integer)
|
||||
['spin -200ms -100ms linear', { duration: '-200ms', delay: '-100ms' }],
|
||||
|
||||
// Positive milliseconds (float)
|
||||
['spin 200.321ms 100.321ms linear', { duration: '200.321ms', delay: '100.321ms' }],
|
||||
|
||||
// Negative milliseconds (float)
|
||||
['spin -200.321ms -100.321ms linear', { duration: '-200.321ms', delay: '-100.321ms' }],
|
||||
])('should be possible to parse "%s" into %o', (input, { duration, delay }) => {
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(1)
|
||||
expect(parsed[0].duration).toEqual(duration)
|
||||
expect(parsed[0].delay).toEqual(delay)
|
||||
})
|
||||
})
|
||||
|
||||
describe('iteration count', () => {
|
||||
it.each([
|
||||
// Number
|
||||
['1 spin 200s 100s linear', '1'],
|
||||
['spin 2 200s 100s linear', '2'],
|
||||
['spin 200s 3 100s linear', '3'],
|
||||
['spin 200s 100s 4 linear', '4'],
|
||||
['spin 200s 100s linear 5', '5'],
|
||||
|
||||
// Infinite
|
||||
['infinite spin 200s 100s linear', 'infinite'],
|
||||
['spin infinite 200s 100s linear', 'infinite'],
|
||||
['spin 200s infinite 100s linear', 'infinite'],
|
||||
['spin 200s 100s infinite linear', 'infinite'],
|
||||
['spin 200s 100s linear infinite', 'infinite'],
|
||||
])(
|
||||
'should be possible to parse "%s" with an iteraction count of "%s"',
|
||||
(input, iterationCount) => {
|
||||
crosscheck(() => {
|
||||
describe('Tailwind Defaults', () => {
|
||||
it.each([
|
||||
[
|
||||
'spin 1s linear infinite',
|
||||
{
|
||||
value: 'spin 1s linear infinite',
|
||||
name: 'spin',
|
||||
duration: '1s',
|
||||
timingFunction: 'linear',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
],
|
||||
[
|
||||
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
{
|
||||
value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
name: 'ping',
|
||||
duration: '1s',
|
||||
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
],
|
||||
[
|
||||
'bounce 1s infinite',
|
||||
{
|
||||
value: 'bounce 1s infinite',
|
||||
name: 'bounce',
|
||||
duration: '1s',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
],
|
||||
])('should be possible to parse: "%s"', (input, expected) => {
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(1)
|
||||
expect(parsed[0].iterationCount).toEqual(iterationCount)
|
||||
}
|
||||
)
|
||||
})
|
||||
expect(parsed[0]).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiple animations', () => {
|
||||
it('should be possible to parse multiple applications at once', () => {
|
||||
const input = [
|
||||
'spin 1s linear infinite',
|
||||
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
|
||||
].join(',')
|
||||
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(3)
|
||||
expect(parsed).toEqual([
|
||||
{
|
||||
value: 'spin 1s linear infinite',
|
||||
name: 'spin',
|
||||
duration: '1s',
|
||||
describe('css variables', () => {
|
||||
it('should be possible to use css variables', () => {
|
||||
let parsed = parseAnimationValue('jump var(--animation-duration, 10s) linear infinite')
|
||||
expect(parsed[0]).toEqual({
|
||||
value: 'jump var(--animation-duration, 10s) linear infinite',
|
||||
name: 'jump',
|
||||
timingFunction: 'linear',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
{
|
||||
value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
name: 'ping',
|
||||
duration: '1s',
|
||||
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
{
|
||||
value: 'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
|
||||
name: 'pulse',
|
||||
duration: '2s',
|
||||
timingFunction: 'cubic-bezier(0.4, 0, 0.6)',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
])
|
||||
unknown: ['var(--animation-duration, 10s)'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('MDN Examples', () => {
|
||||
it.each([
|
||||
[
|
||||
'3s ease-in 1s 2 reverse both paused slidein',
|
||||
{
|
||||
value: '3s ease-in 1s 2 reverse both paused slidein',
|
||||
delay: '1s',
|
||||
direction: 'reverse',
|
||||
duration: '3s',
|
||||
fillMode: 'both',
|
||||
iterationCount: '2',
|
||||
name: 'slidein',
|
||||
playState: 'paused',
|
||||
timingFunction: 'ease-in',
|
||||
},
|
||||
],
|
||||
[
|
||||
'slidein 3s linear 1s',
|
||||
{
|
||||
value: 'slidein 3s linear 1s',
|
||||
delay: '1s',
|
||||
duration: '3s',
|
||||
name: 'slidein',
|
||||
timingFunction: 'linear',
|
||||
},
|
||||
],
|
||||
['slidein 3s', { value: 'slidein 3s', duration: '3s', name: 'slidein' }],
|
||||
])('should be possible to parse: "%s"', (input, expected) => {
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(1)
|
||||
expect(parsed[0]).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('duration & delay', () => {
|
||||
it.each([
|
||||
// Positive seconds (integer)
|
||||
['spin 2s 1s linear', { duration: '2s', delay: '1s' }],
|
||||
|
||||
// Negative seconds (integer)
|
||||
['spin -2s -1s linear', { duration: '-2s', delay: '-1s' }],
|
||||
|
||||
// Positive seconds (float)
|
||||
['spin 2.321s 1.321s linear', { duration: '2.321s', delay: '1.321s' }],
|
||||
|
||||
// Negative seconds (float)
|
||||
['spin -2.321s -1.321s linear', { duration: '-2.321s', delay: '-1.321s' }],
|
||||
|
||||
// Positive milliseconds (integer)
|
||||
['spin 200ms 100ms linear', { duration: '200ms', delay: '100ms' }],
|
||||
|
||||
// Negative milliseconds (integer)
|
||||
['spin -200ms -100ms linear', { duration: '-200ms', delay: '-100ms' }],
|
||||
|
||||
// Positive milliseconds (float)
|
||||
['spin 200.321ms 100.321ms linear', { duration: '200.321ms', delay: '100.321ms' }],
|
||||
|
||||
// Negative milliseconds (float)
|
||||
['spin -200.321ms -100.321ms linear', { duration: '-200.321ms', delay: '-100.321ms' }],
|
||||
])('should be possible to parse "%s" into %o', (input, { duration, delay }) => {
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(1)
|
||||
expect(parsed[0].duration).toEqual(duration)
|
||||
expect(parsed[0].delay).toEqual(delay)
|
||||
})
|
||||
})
|
||||
|
||||
describe('iteration count', () => {
|
||||
it.each([
|
||||
// Number
|
||||
['1 spin 200s 100s linear', '1'],
|
||||
['spin 2 200s 100s linear', '2'],
|
||||
['spin 200s 3 100s linear', '3'],
|
||||
['spin 200s 100s 4 linear', '4'],
|
||||
['spin 200s 100s linear 5', '5'],
|
||||
|
||||
// Infinite
|
||||
['infinite spin 200s 100s linear', 'infinite'],
|
||||
['spin infinite 200s 100s linear', 'infinite'],
|
||||
['spin 200s infinite 100s linear', 'infinite'],
|
||||
['spin 200s 100s infinite linear', 'infinite'],
|
||||
['spin 200s 100s linear infinite', 'infinite'],
|
||||
])(
|
||||
'should be possible to parse "%s" with an iteraction count of "%s"',
|
||||
(input, iterationCount) => {
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(1)
|
||||
expect(parsed[0].iterationCount).toEqual(iterationCount)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('multiple animations', () => {
|
||||
it('should be possible to parse multiple applications at once', () => {
|
||||
const input = [
|
||||
'spin 1s linear infinite',
|
||||
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
|
||||
].join(',')
|
||||
|
||||
const parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(3)
|
||||
expect(parsed).toEqual([
|
||||
{
|
||||
value: 'spin 1s linear infinite',
|
||||
name: 'spin',
|
||||
duration: '1s',
|
||||
timingFunction: 'linear',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
{
|
||||
value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
|
||||
name: 'ping',
|
||||
duration: '1s',
|
||||
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
{
|
||||
value: 'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
|
||||
name: 'pulse',
|
||||
duration: '2s',
|
||||
timingFunction: 'cubic-bezier(0.4, 0, 0.6)',
|
||||
iterationCount: 'infinite',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it.each`
|
||||
input | value | direction | playState | fillMode | iterationCount | timingFunction | duration | delay | name
|
||||
${'1s spin 1s infinite'} | ${'1s spin 1s infinite'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'spin'}
|
||||
${'infinite infinite 1s 1s'} | ${'infinite infinite 1s 1s'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'infinite'}
|
||||
${'ease 1s ease 1s'} | ${'ease 1s ease 1s'} | ${undefined} | ${undefined} | ${undefined} | ${undefined} | ${'ease'} | ${'1s'} | ${'1s'} | ${'ease'}
|
||||
${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'paused backwards infinite ease-in 1s 2s name normal'} | ${'paused backwards infinite ease-in 1s 2s name normal'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'backwards infinite ease-in 1s 2s name normal paused'} | ${'backwards infinite ease-in 1s 2s name normal paused'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'infinite ease-in 1s 2s name normal paused backwards'} | ${'infinite ease-in 1s 2s name normal paused backwards'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'ease-in 1s 2s name normal paused backwards infinite'} | ${'ease-in 1s 2s name normal paused backwards infinite'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'1s 2s name normal paused backwards infinite ease-in'} | ${'1s 2s name normal paused backwards infinite ease-in'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'2s name normal paused backwards infinite ease-in 1s'} | ${'2s name normal paused backwards infinite ease-in 1s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'2s'} | ${'1s'} | ${'name'}
|
||||
${'name normal paused backwards infinite ease-in 1s 2s'} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${' name normal paused backwards infinite ease-in 1s 2s '} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
`('should parse "$input" correctly', ({ input, ...expected }) => {
|
||||
let parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(1)
|
||||
expect(parsed[0]).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
it.each`
|
||||
input | value | direction | playState | fillMode | iterationCount | timingFunction | duration | delay | name
|
||||
${'1s spin 1s infinite'} | ${'1s spin 1s infinite'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'spin'}
|
||||
${'infinite infinite 1s 1s'} | ${'infinite infinite 1s 1s'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'infinite'}
|
||||
${'ease 1s ease 1s'} | ${'ease 1s ease 1s'} | ${undefined} | ${undefined} | ${undefined} | ${undefined} | ${'ease'} | ${'1s'} | ${'1s'} | ${'ease'}
|
||||
${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'paused backwards infinite ease-in 1s 2s name normal'} | ${'paused backwards infinite ease-in 1s 2s name normal'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'backwards infinite ease-in 1s 2s name normal paused'} | ${'backwards infinite ease-in 1s 2s name normal paused'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'infinite ease-in 1s 2s name normal paused backwards'} | ${'infinite ease-in 1s 2s name normal paused backwards'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'ease-in 1s 2s name normal paused backwards infinite'} | ${'ease-in 1s 2s name normal paused backwards infinite'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'1s 2s name normal paused backwards infinite ease-in'} | ${'1s 2s name normal paused backwards infinite ease-in'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${'2s name normal paused backwards infinite ease-in 1s'} | ${'2s name normal paused backwards infinite ease-in 1s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'2s'} | ${'1s'} | ${'name'}
|
||||
${'name normal paused backwards infinite ease-in 1s 2s'} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
${' name normal paused backwards infinite ease-in 1s 2s '} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
|
||||
`('should parse "$input" correctly', ({ input, ...expected }) => {
|
||||
let parsed = parseAnimationValue(input)
|
||||
expect(parsed).toHaveLength(1)
|
||||
expect(parsed[0]).toEqual(expected)
|
||||
})
|
||||
|
||||
@ -1,316 +1,319 @@
|
||||
import parseObjectStyles from '../src/util/parseObjectStyles'
|
||||
import postcss from 'postcss'
|
||||
import { crosscheck, css } from './util/run'
|
||||
|
||||
function css(nodes) {
|
||||
function toCss(nodes) {
|
||||
return postcss.root({ nodes }).toString()
|
||||
}
|
||||
|
||||
test('it parses simple single class definitions', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foobar': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foobar {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses multiple class definitions', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
},
|
||||
'.bar': {
|
||||
width: '200px',
|
||||
height: '100px',
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem
|
||||
}
|
||||
.bar {
|
||||
width: 200px;
|
||||
height: 100px
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses nested pseudo-selectors', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
':hover': {
|
||||
backgroundColor: 'orange',
|
||||
crosscheck(() => {
|
||||
test('it parses simple single class definitions', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foobar': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
},
|
||||
':focus': {
|
||||
backgroundColor: 'blue',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foobar {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
.foo:hover {
|
||||
background-color: orange;
|
||||
}
|
||||
.foo:focus {
|
||||
background-color: blue;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses top-level media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'@media (min-width: 200px)': {
|
||||
test('it parses multiple class definitions', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'orange',
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
},
|
||||
},
|
||||
'.bar': {
|
||||
width: '200px',
|
||||
height: '100px',
|
||||
},
|
||||
})
|
||||
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
.bar {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
@media (min-width: 200px) {
|
||||
.foo {
|
||||
background-color: orange
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
test('it parses nested pseudo-selectors', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'&:hover': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
'&:focus': {
|
||||
backgroundColor: 'blue',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
test('it parses nested media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
.foo:hover {
|
||||
background-color: orange;
|
||||
}
|
||||
.foo:focus {
|
||||
background-color: blue;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses top-level media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'@media (min-width: 200px)': {
|
||||
backgroundColor: 'orange',
|
||||
'.foo': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
@media (min-width: 200px) {
|
||||
.foo {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@media (min-width: 200px) {
|
||||
.foo {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it bubbles nested screen rules', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'@screen sm': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@screen sm {
|
||||
.foo {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses pseudo-selectors in nested media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
':hover': {
|
||||
test('it parses nested media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'@media (min-width: 200px)': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@media (min-width: 200px) {
|
||||
.foo {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@media (min-width: 200px) {
|
||||
.foo:hover {
|
||||
test('it bubbles nested screen rules', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'@screen sm': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@screen sm {
|
||||
.foo {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses pseudo-selectors in nested media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'&:hover': {
|
||||
'@media (min-width: 200px)': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@media (min-width: 200px) {
|
||||
.foo:hover {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses descendant selectors', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
.foo .bar {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses descendant selectors', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
`)
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
.foo .bar {
|
||||
background-color: orange;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses nested multi-class selectors', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'&.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
.foo.bar {
|
||||
background-color: orange;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it parses nested multi-class selectors in media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'@media (min-width: 200px)': {
|
||||
test('it parses nested multi-class selectors', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'&.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@media (min-width: 200px) {
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
.foo.bar {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it strips empty selectors when nesting', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
'.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
`)
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo .bar {
|
||||
background-color: orange
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it can parse an array of styles', () => {
|
||||
const result = parseObjectStyles([
|
||||
{
|
||||
test('it parses nested multi-class selectors in media queries', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
{
|
||||
'.bar': {
|
||||
backgroundColor: 'red',
|
||||
color: 'white',
|
||||
padding: '1rem',
|
||||
'@media (min-width: 200px)': {
|
||||
'&.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'.foo': {
|
||||
backgroundColor: 'blue',
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
.foo {
|
||||
background-color: orange
|
||||
}
|
||||
.bar {
|
||||
background-color: red
|
||||
}
|
||||
.foo {
|
||||
background-color: blue
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('custom properties preserve their case', () => {
|
||||
const result = parseObjectStyles({
|
||||
':root': {
|
||||
'--colors-aColor-500': '0',
|
||||
},
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foo {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
}
|
||||
@media (min-width: 200px) {
|
||||
.foo.bar {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
expect(css(result)).toMatchCss(`
|
||||
:root {
|
||||
--colors-aColor-500: 0;
|
||||
}
|
||||
`)
|
||||
test('it strips empty selectors when nesting', () => {
|
||||
const result = parseObjectStyles({
|
||||
'.foo': {
|
||||
'.bar': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foo .bar {
|
||||
background-color: orange;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('it can parse an array of styles', () => {
|
||||
const result = parseObjectStyles([
|
||||
{
|
||||
'.foo': {
|
||||
backgroundColor: 'orange',
|
||||
},
|
||||
},
|
||||
{
|
||||
'.bar': {
|
||||
backgroundColor: 'red',
|
||||
},
|
||||
},
|
||||
{
|
||||
'.foo': {
|
||||
backgroundColor: 'blue',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
.foo {
|
||||
background-color: orange;
|
||||
}
|
||||
.bar {
|
||||
background-color: red;
|
||||
}
|
||||
.foo {
|
||||
background-color: blue;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('custom properties preserve their case', () => {
|
||||
const result = parseObjectStyles({
|
||||
':root': {
|
||||
'--colors-aColor-500': '0',
|
||||
},
|
||||
})
|
||||
|
||||
expect(toCss(result)).toMatchFormattedCss(css`
|
||||
:root {
|
||||
--colors-aColor-500: 0;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,42 +1,33 @@
|
||||
import { run, html, css, defaults } from '../util/run'
|
||||
import { env } from '../../src/lib/sharedState'
|
||||
import { crosscheck, run, html, css, defaults } from '../util/run'
|
||||
|
||||
it('should add the divide styles for divide-y and a default border color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="divide-y"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
crosscheck(({ stable, oxide }) => {
|
||||
it('should add the divide styles for divide-y and a default border color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="divide-y"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should add the divide styles for divide-x and a default border color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="divide-x"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let expected = env.OXIDE
|
||||
? css`
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.divide-x > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-x-reverse: 0;
|
||||
border-inline-end-width: calc(1px * var(--tw-divide-x-reverse));
|
||||
border-inline-start-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
|
||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
`
|
||||
: css`
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should add the divide styles for divide-x and a default border color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="divide-x"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
stable.expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.divide-x > :not([hidden]) ~ :not([hidden]) {
|
||||
@ -44,66 +35,74 @@ it('should add the divide styles for divide-x and a default border color', () =>
|
||||
border-right-width: calc(1px * var(--tw-divide-x-reverse));
|
||||
border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
|
||||
}
|
||||
`
|
||||
`)
|
||||
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
it('should add the divide styles for divide-y-reverse and a default border color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="divide-y-reverse"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.divide-y-reverse > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 1;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should add the divide styles for divide-x-reverse and a default border color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="divide-x-reverse"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.divide-x-reverse > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-x-reverse: 1;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should only inject the base styles once if we use divide and border at the same time', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="divide-y border-r"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
|
||||
.border-r {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
`)
|
||||
oxide.expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.divide-x > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-x-reverse: 0;
|
||||
border-inline-end-width: calc(1px * var(--tw-divide-x-reverse));
|
||||
border-inline-start-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should add the divide styles for divide-y-reverse and a default border color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="divide-y-reverse"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.divide-y-reverse > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 1;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should add the divide styles for divide-x-reverse and a default border color', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="divide-x-reverse"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.divide-x-reverse > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-x-reverse: 1;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should only inject the base styles once if we use divide and border at the same time', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="divide-y border-r"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
${defaults}
|
||||
|
||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
|
||||
.border-r {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,98 +1,100 @@
|
||||
import { run, html, css } from '../util/run'
|
||||
import { crosscheck, run, html, css } from '../util/run'
|
||||
|
||||
test('font-family utilities can be defined as a string', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-sans"></div>` }],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: 'Helvetica, Arial, sans-serif',
|
||||
crosscheck(() => {
|
||||
test('font-family utilities can be defined as a string', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-sans"></div>` }],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: 'Helvetica, Arial, sans-serif',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.font-sans {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-family utilities can be defined as an array', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-sans"></div>` }],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ['Helvetica', 'Arial', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.font-sans {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-family values are not automatically escaped', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-sans"></div>` }],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ["'Exo 2'", 'sans-serif'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.font-sans {
|
||||
font-family: 'Exo 2', sans-serif;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-feature-settings can be provided when families are defined as a string', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-sans"></div>` }],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ['Helvetica, Arial, sans-serif', { fontFeatureSettings: '"cv11", "ss01"' }],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(`
|
||||
.font-sans {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-feature-settings: "cv11", "ss01";
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-feature-settings can be provided when families are defined as an array', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-sans"></div>` }],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: [['Helvetica', 'Arial', 'sans-serif'], { fontFeatureSettings: '"cv11", "ss01"' }],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(`
|
||||
.font-sans {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-feature-settings: "cv11", "ss01";
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-sans {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-family utilities can be defined as an array', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-sans"></div>` }],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ['Helvetica', 'Arial', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-sans {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-family values are not automatically escaped', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-sans"></div>` }],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ["'Exo 2'", 'sans-serif'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-sans {
|
||||
font-family: 'Exo 2', sans-serif;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-feature-settings can be provided when families are defined as a string', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-sans"></div>` }],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ['Helvetica, Arial, sans-serif', { fontFeatureSettings: '"cv11", "ss01"' }],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-sans {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-feature-settings: 'cv11', 'ss01';
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-feature-settings can be provided when families are defined as an array', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-sans"></div>` }],
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: [['Helvetica', 'Arial', 'sans-serif'], { fontFeatureSettings: '"cv11", "ss01"' }],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-sans {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-feature-settings: 'cv11', 'ss01';
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,195 +1,197 @@
|
||||
import { run, html, css } from '../util/run'
|
||||
import { crosscheck, run, html, css } from '../util/run'
|
||||
|
||||
test('font-size utilities can include a default line-height', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', '24px'],
|
||||
lg: ['20px', '28px'],
|
||||
crosscheck(() => {
|
||||
test('font-size utilities can include a default line-height', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', '24px'],
|
||||
lg: ['20px', '28px'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.text-lg {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
.text-md {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-size utilities can include a default letter-spacing', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', { letterSpacing: '-0.01em' }],
|
||||
lg: ['20px', { letterSpacing: '-0.02em' }],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.text-lg {
|
||||
font-size: 20px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.text-md {
|
||||
font-size: 16px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-size utilities can include a default line-height and letter-spacing', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', { lineHeight: '24px', letterSpacing: '-0.01em' }],
|
||||
lg: ['20px', { lineHeight: '28px', letterSpacing: '-0.02em' }],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.text-lg {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.text-md {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-size utilities can include a font-weight', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', { lineHeight: '24px', fontWeight: 500 }],
|
||||
lg: ['20px', { lineHeight: '28px', fontWeight: 'bold' }],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.text-lg {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.text-md {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-size utilities can include a line-height modifier', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="text-sm md:text-base">
|
||||
<div class="text-sm/6 md:text-base/7"></div>
|
||||
<div class="text-sm/[21px] md:text-base/[33px]"></div>
|
||||
<div class="text-[13px]/6 md:text-[19px]/8"></div>
|
||||
<div class="text-[17px]/[23px] md:text-[21px]/[29px]"></div>
|
||||
<div class="text-sm/999 md:text-base/000"></div>
|
||||
</div>`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: ['12px', '20px'],
|
||||
base: ['16px', '24px'],
|
||||
},
|
||||
lineHeight: {
|
||||
6: '24px',
|
||||
7: '28px',
|
||||
8: '32px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.text-\[13px\]\/6 {
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.text-\[17px\]\/\[23px\] {
|
||||
font-size: 17px;
|
||||
line-height: 23px;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.text-sm\/6 {
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.text-sm\/\[21px\] {
|
||||
font-size: 12px;
|
||||
line-height: 21px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:text-\[19px\]\/8 {
|
||||
font-size: 19px;
|
||||
line-height: 32px;
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.text-lg {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
.md\:text-\[21px\]\/\[29px\] {
|
||||
font-size: 21px;
|
||||
line-height: 29px;
|
||||
}
|
||||
.md\:text-base {
|
||||
.text-md {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.md\:text-base\/7 {
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-size utilities can include a default letter-spacing', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', { letterSpacing: '-0.01em' }],
|
||||
lg: ['20px', { letterSpacing: '-0.02em' }],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.text-lg {
|
||||
font-size: 20px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.text-md {
|
||||
font-size: 16px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-size utilities can include a default line-height and letter-spacing', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', { lineHeight: '24px', letterSpacing: '-0.01em' }],
|
||||
lg: ['20px', { lineHeight: '28px', letterSpacing: '-0.02em' }],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.text-lg {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.md\:text-base\/\[33px\] {
|
||||
.text-md {
|
||||
font-size: 16px;
|
||||
line-height: 33px;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
}
|
||||
`)
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-size utilities can include a font-weight', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: '12px',
|
||||
md: ['16px', { lineHeight: '24px', fontWeight: 500 }],
|
||||
lg: ['20px', { lineHeight: '28px', fontWeight: 'bold' }],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.text-lg {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.text-md {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('font-size utilities can include a line-height modifier', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="text-sm md:text-base">
|
||||
<div class="text-sm/6 md:text-base/7"></div>
|
||||
<div class="text-sm/[21px] md:text-base/[33px]"></div>
|
||||
<div class="text-[13px]/6 md:text-[19px]/8"></div>
|
||||
<div class="text-[17px]/[23px] md:text-[21px]/[29px]"></div>
|
||||
<div class="text-sm/999 md:text-base/000"></div>
|
||||
</div>`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
fontSize: {
|
||||
sm: ['12px', '20px'],
|
||||
base: ['16px', '24px'],
|
||||
},
|
||||
lineHeight: {
|
||||
6: '24px',
|
||||
7: '28px',
|
||||
8: '32px',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.text-\[13px\]\/6 {
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.text-\[17px\]\/\[23px\] {
|
||||
font-size: 17px;
|
||||
line-height: 23px;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.text-sm\/6 {
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.text-sm\/\[21px\] {
|
||||
font-size: 12px;
|
||||
line-height: 21px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:text-\[19px\]\/8 {
|
||||
font-size: 19px;
|
||||
line-height: 32px;
|
||||
}
|
||||
.md\:text-\[21px\]\/\[29px\] {
|
||||
font-size: 21px;
|
||||
line-height: 29px;
|
||||
}
|
||||
.md\:text-base {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.md\:text-base\/7 {
|
||||
font-size: 16px;
|
||||
line-height: 28px;
|
||||
}
|
||||
.md\:text-base\/\[33px\] {
|
||||
font-size: 16px;
|
||||
line-height: 33px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,72 +1,74 @@
|
||||
import { run, html, css } from '../util/run'
|
||||
import { crosscheck, run, html, css } from '../util/run'
|
||||
|
||||
test('opacity variables are given to colors defined as closures', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="text-primary text-secondary from-primary from-secondary via-primary via-secondary to-primary to-secondary text-opacity-50"
|
||||
></div>`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
colors: {
|
||||
primary: ({ opacityVariable, opacityValue }) => {
|
||||
if (opacityValue !== undefined) {
|
||||
return `rgba(31,31,31,${opacityValue})`
|
||||
}
|
||||
|
||||
if (opacityVariable !== undefined) {
|
||||
return `rgba(31,31,31,var(${opacityVariable},1))`
|
||||
}
|
||||
|
||||
return `rgb(31,31,31)`
|
||||
crosscheck(() => {
|
||||
test('opacity variables are given to colors defined as closures', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div
|
||||
class="text-primary text-secondary from-primary from-secondary via-primary via-secondary to-primary to-secondary text-opacity-50"
|
||||
></div>`,
|
||||
},
|
||||
secondary: 'hsl(10, 50%, 50%)',
|
||||
},
|
||||
opacity: {
|
||||
50: '0.5',
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
theme: {
|
||||
colors: {
|
||||
primary: ({ opacityVariable, opacityValue }) => {
|
||||
if (opacityValue !== undefined) {
|
||||
return `rgba(31,31,31,${opacityValue})`
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.from-primary {
|
||||
--tw-gradient-from: rgb(31, 31, 31);
|
||||
--tw-gradient-to: rgba(31, 31, 31, 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.from-secondary {
|
||||
--tw-gradient-from: hsl(10, 50%, 50%);
|
||||
--tw-gradient-to: hsl(10 50% 50% / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.via-primary {
|
||||
--tw-gradient-to: rgba(31, 31, 31, 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), rgb(31, 31, 31), var(--tw-gradient-to);
|
||||
}
|
||||
.via-secondary {
|
||||
--tw-gradient-to: hsl(10 50% 50% / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), hsl(10, 50%, 50%), var(--tw-gradient-to);
|
||||
}
|
||||
.to-primary {
|
||||
--tw-gradient-to: rgb(31, 31, 31);
|
||||
}
|
||||
.to-secondary {
|
||||
--tw-gradient-to: hsl(10, 50%, 50%);
|
||||
}
|
||||
.text-primary {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(31, 31, 31, var(--tw-text-opacity));
|
||||
}
|
||||
.text-secondary {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(10 50% 50% / var(--tw-text-opacity));
|
||||
}
|
||||
.text-opacity-50 {
|
||||
--tw-text-opacity: 0.5;
|
||||
}
|
||||
`)
|
||||
if (opacityVariable !== undefined) {
|
||||
return `rgba(31,31,31,var(${opacityVariable},1))`
|
||||
}
|
||||
|
||||
return `rgb(31,31,31)`
|
||||
},
|
||||
secondary: 'hsl(10, 50%, 50%)',
|
||||
},
|
||||
opacity: {
|
||||
50: '0.5',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.from-primary {
|
||||
--tw-gradient-from: rgb(31, 31, 31);
|
||||
--tw-gradient-to: rgba(31, 31, 31, 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.from-secondary {
|
||||
--tw-gradient-from: hsl(10, 50%, 50%);
|
||||
--tw-gradient-to: hsl(10 50% 50% / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.via-primary {
|
||||
--tw-gradient-to: rgba(31, 31, 31, 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), rgb(31, 31, 31), var(--tw-gradient-to);
|
||||
}
|
||||
.via-secondary {
|
||||
--tw-gradient-to: hsl(10 50% 50% / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), hsl(10, 50%, 50%), var(--tw-gradient-to);
|
||||
}
|
||||
.to-primary {
|
||||
--tw-gradient-to: rgb(31, 31, 31);
|
||||
}
|
||||
.to-secondary {
|
||||
--tw-gradient-to: hsl(10, 50%, 50%);
|
||||
}
|
||||
.text-primary {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(31, 31, 31, var(--tw-text-opacity));
|
||||
}
|
||||
.text-secondary {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(10 50% 50% / var(--tw-text-opacity));
|
||||
}
|
||||
.text-opacity-50 {
|
||||
--tw-text-opacity: 0.5;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,74 +1,78 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
it('should be possible to use contrast-more and contrast-less variants', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{ raw: html`<div class="bg-white contrast-more:bg-pink-500 contrast-less:bg-black"></div>` },
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
crosscheck(() => {
|
||||
it('should be possible to use contrast-more and contrast-less variants', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`<div class="bg-white contrast-more:bg-pink-500 contrast-less:bg-black"></div>`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
.bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
@media (prefers-contrast: more) {
|
||||
.contrast-more\:bg-pink-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(236 72 153 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-contrast: less) {
|
||||
.contrast-less\:bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('dark mode should appear after the contrast variants', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="contrast-more:bg-black dark:bg-white"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
@media (prefers-contrast: more) {
|
||||
.contrast-more\:bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:bg-white {
|
||||
.bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@media (prefers-contrast: more) {
|
||||
.contrast-more\:bg-pink-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(236 72 153 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-contrast: less) {
|
||||
.contrast-less\:bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('dark mode should appear after the contrast variants', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="contrast-more:bg-black dark:bg-white"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
${defaults}
|
||||
|
||||
@media (prefers-contrast: more) {
|
||||
.contrast-more\:bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { run, html, css, defaults } from './util/run'
|
||||
import { env } from '../src/lib/sharedState'
|
||||
import { crosscheck, run, html, css, defaults } from './util/run'
|
||||
|
||||
let group = env.OXIDE ? describe.skip : describe
|
||||
|
||||
group('prefix', () => {
|
||||
test('prefix', () => {
|
||||
crosscheck(({ stable, oxide }) => {
|
||||
oxide.test.todo('prefix')
|
||||
stable.test('prefix', () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
darkMode: 'class',
|
||||
@ -191,7 +189,8 @@ group('prefix', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('negative values: marker before prefix', async () => {
|
||||
oxide.test.todo('negative values: marker before prefix')
|
||||
stable.test('negative values: marker before prefix', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: html`<div class="-tw-top-1"></div>` }],
|
||||
@ -213,7 +212,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('negative values: marker after prefix', async () => {
|
||||
oxide.test.todo('negative values: marker after prefix')
|
||||
stable.test('negative values: marker after prefix', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: html`<div class="tw--top-1"></div>` }],
|
||||
@ -235,7 +235,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('negative values: marker before prefix and arbitrary value', async () => {
|
||||
oxide.test.todo('negative values: marker before prefix and arbitrary value')
|
||||
stable.test('negative values: marker before prefix and arbitrary value', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: html`<div class="-tw-top-[1px]"></div>` }],
|
||||
@ -257,7 +258,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('negative values: marker after prefix and arbitrary value', async () => {
|
||||
oxide.test.todo('negative values: marker after prefix and arbitrary value')
|
||||
stable.test('negative values: marker after prefix and arbitrary value', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: html`<div class="tw--top-[1px]"></div>` }],
|
||||
@ -279,7 +281,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('negative values: no marker and arbitrary value', async () => {
|
||||
oxide.test.todo('negative values: no marker and arbitrary value')
|
||||
stable.test('negative values: no marker and arbitrary value', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: html`<div class="tw-top-[-1px]"></div>` }],
|
||||
@ -301,7 +304,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('negative values: variant versions', async () => {
|
||||
oxide.test.todo('negative values: variant versions')
|
||||
stable.test('negative values: variant versions', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [
|
||||
@ -346,7 +350,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('negative values: prefix and apply', async () => {
|
||||
oxide.test.todo('negative values: prefix and apply')
|
||||
stable.test('negative values: prefix and apply', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: html`` }],
|
||||
@ -396,7 +401,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('negative values: prefix in the safelist', async () => {
|
||||
oxide.test.todo('negative values: prefix in the safelist')
|
||||
stable.test('negative values: prefix in the safelist', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
safelist: [{ pattern: /-tw-top-1/g }, { pattern: /tw--top-1/g }],
|
||||
@ -427,7 +433,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('prefix with negative values and variants in the safelist', async () => {
|
||||
oxide.test.todo('prefix with negative values and variants in the safelist')
|
||||
stable.test('prefix with negative values and variants in the safelist', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
safelist: [
|
||||
@ -476,7 +483,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('prefix does not detect and generate unnecessary classes', async () => {
|
||||
oxide.test.todo('prefix does not detect and generate unnecessary classes')
|
||||
stable.test('prefix does not detect and generate unnecessary classes', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-_',
|
||||
content: [{ raw: html`-aaa-filter aaaa-table aaaa-hidden` }],
|
||||
@ -492,7 +500,8 @@ group('prefix', () => {
|
||||
expect(result.css).toMatchFormattedCss(css``)
|
||||
})
|
||||
|
||||
it('supports prefixed utilities using arbitrary values', async () => {
|
||||
oxide.test.todo('supports prefixed utilities using arbitrary values')
|
||||
stable.test('supports prefixed utilities using arbitrary values', async () => {
|
||||
let config = {
|
||||
prefix: 'tw-',
|
||||
content: [{ raw: html`foo` }],
|
||||
@ -518,7 +527,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('supports non-word prefixes (1)', async () => {
|
||||
oxide.test.todo('supports non-word prefixes (1)')
|
||||
stable.test('supports non-word prefixes (1)', async () => {
|
||||
let config = {
|
||||
prefix: '@',
|
||||
content: [
|
||||
@ -577,7 +587,8 @@ group('prefix', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('supports non-word prefixes (2)', async () => {
|
||||
oxide.test.todo('supports non-word prefixes (2)')
|
||||
stable.test('supports non-word prefixes (2)', async () => {
|
||||
let config = {
|
||||
prefix: '@]$',
|
||||
content: [
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
import prefix from '../src/util/prefixSelector'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
test('it prefixes classes with the provided prefix', () => {
|
||||
expect(prefix('tw-', '.foo')).toEqual('.tw-foo')
|
||||
})
|
||||
crosscheck(() => {
|
||||
test('it prefixes classes with the provided prefix', () => {
|
||||
expect(prefix('tw-', '.foo')).toEqual('.tw-foo')
|
||||
})
|
||||
|
||||
test('it properly prefixes selectors with non-standard characters', () => {
|
||||
expect(prefix('tw-', '.hello\\:world')).toEqual('.tw-hello\\:world')
|
||||
expect(prefix('tw-', '.foo\\/bar')).toEqual('.tw-foo\\/bar')
|
||||
expect(prefix('tw-', '.wew\\.lad')).toEqual('.tw-wew\\.lad')
|
||||
})
|
||||
test('it properly prefixes selectors with non-standard characters', () => {
|
||||
expect(prefix('tw-', '.hello\\:world')).toEqual('.tw-hello\\:world')
|
||||
expect(prefix('tw-', '.foo\\/bar')).toEqual('.tw-foo\\/bar')
|
||||
expect(prefix('tw-', '.wew\\.lad')).toEqual('.tw-wew\\.lad')
|
||||
})
|
||||
|
||||
test('it prefixes all classes in a selector', () => {
|
||||
expect(prefix('tw-', '.btn-blue .w-1\\/4 > h1.text-xl + a .bar')).toEqual(
|
||||
'.tw-btn-blue .tw-w-1\\/4 > h1.tw-text-xl + a .tw-bar'
|
||||
)
|
||||
test('it prefixes all classes in a selector', () => {
|
||||
expect(prefix('tw-', '.btn-blue .w-1\\/4 > h1.text-xl + a .bar')).toEqual(
|
||||
'.tw-btn-blue .tw-w-1\\/4 > h1.tw-text-xl + a .tw-bar'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,21 +1,23 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
it('preflight has a correct border color fallback', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="border-black"></div>` }],
|
||||
theme: {
|
||||
borderColor: ({ theme }) => theme('colors'),
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: { preflight: true },
|
||||
}
|
||||
crosscheck(() => {
|
||||
it('preflight has a correct border color fallback', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="border-black"></div>` }],
|
||||
theme: {
|
||||
borderColor: ({ theme }) => theme('colors'),
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: { preflight: true },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toContain(`border-color: currentColor;`)
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toContain(`border-color: currentColor;`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,167 +1,171 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
import { run, html, css } from './util/run'
|
||||
import { env } from '../src/lib/sharedState'
|
||||
crosscheck(({ stable, oxide }) => {
|
||||
it('raw content', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="sr-only"></div>
|
||||
<div class="content-center"></div>
|
||||
<div class="items-start"></div>
|
||||
<div class="self-end"></div>
|
||||
<div class="animate-none"></div>
|
||||
<div class="animate-spin"></div>
|
||||
<div class="appearance-none"></div>
|
||||
<div class="bg-local"></div>
|
||||
<div class="bg-clip-border"></div>
|
||||
<div class="bg-green-500"></div>
|
||||
<div class="bg-gradient-to-r"></div>
|
||||
<div class="bg-opacity-20"></div>
|
||||
<div class="bg-top"></div>
|
||||
<div class="bg-no-repeat"></div>
|
||||
<div class="bg-cover"></div>
|
||||
<div class="bg-origin-border bg-origin-padding bg-origin-content"></div>
|
||||
<div class="border-collapse"></div>
|
||||
<div class="border-black"></div>
|
||||
<div class="border-opacity-10"></div>
|
||||
<div class="rounded-md"></div>
|
||||
<div class="border-solid"></div>
|
||||
<div class="border"></div>
|
||||
<div class="border-2"></div>
|
||||
<div class="shadow"></div>
|
||||
<div class="shadow-md"></div>
|
||||
<div class="shadow-lg"></div>
|
||||
<div class="decoration-clone decoration-slice"></div>
|
||||
<div class="box-border"></div>
|
||||
<div class="clear-left"></div>
|
||||
<div class="container"></div>
|
||||
<div class="cursor-pointer"></div>
|
||||
<div class="hidden inline-grid"></div>
|
||||
<div class="divide-gray-200"></div>
|
||||
<div class="divide-opacity-50"></div>
|
||||
<div class="divide-dotted"></div>
|
||||
<div class="divide-x-2 divide-y-4 divide-x-0 divide-y-0"></div>
|
||||
<div class="fill-current"></div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="flex-row-reverse"></div>
|
||||
<div class="flex-grow"></div>
|
||||
<div class="flex-grow-0"></div>
|
||||
<div class="flex-shrink"></div>
|
||||
<div class="flex-shrink-0"></div>
|
||||
<div class="flex-wrap"></div>
|
||||
<div class="float-right"></div>
|
||||
<div class="font-sans"></div>
|
||||
<div class="text-2xl"></div>
|
||||
<div class="antialiased"></div>
|
||||
<div class="not-italic"></div>
|
||||
<div class="tabular-nums ordinal diagonal-fractions"></div>
|
||||
<div class="font-medium"></div>
|
||||
<div class="gap-x-2 gap-y-3 gap-4"></div>
|
||||
<div class="from-red-300 via-purple-200 to-blue-400"></div>
|
||||
<div class="auto-cols-min"></div>
|
||||
<div class="grid-flow-row"></div>
|
||||
<div class="auto-rows-max"></div>
|
||||
<div class="col-span-3"></div>
|
||||
<div class="col-start-1"></div>
|
||||
<div class="col-end-4"></div>
|
||||
<div class="row-span-2"></div>
|
||||
<div class="row-start-3"></div>
|
||||
<div class="row-end-5"></div>
|
||||
<div class="grid-cols-4"></div>
|
||||
<div class="grid-rows-3"></div>
|
||||
<div class="h-16"></div>
|
||||
<div class="inset-0 inset-y-4 inset-x-2 top-6 right-8 bottom-12 left-16"></div>
|
||||
<div class="isolate isolation-auto"></div>
|
||||
<div class="justify-center"></div>
|
||||
<div class="justify-items-end"></div>
|
||||
<div class="justify-self-start"></div>
|
||||
<div class="tracking-tight"></div>
|
||||
<div class="leading-relaxed leading-5"></div>
|
||||
<div class="list-inside"></div>
|
||||
<div class="list-disc"></div>
|
||||
<div class="m-4 my-2 mx-auto mt-0 mr-1 mb-3 ml-4"></div>
|
||||
<div class="max-h-screen"></div>
|
||||
<div class="max-w-full"></div>
|
||||
<div class="min-h-0"></div>
|
||||
<div class="min-w-min"></div>
|
||||
<div class="object-cover"></div>
|
||||
<div class="object-bottom"></div>
|
||||
<div class="opacity-90"></div>
|
||||
<div class="bg-blend-darken bg-blend-difference"></div>
|
||||
<div class="mix-blend-multiply mix-blend-saturation"></div>
|
||||
<div class="order-last order-2"></div>
|
||||
<div class="overflow-hidden"></div>
|
||||
<div class="overscroll-contain"></div>
|
||||
<div class="scroll-smooth"></div>
|
||||
<div class="p-4 py-2 px-3 pt-1 pr-2 pb-3 pl-4"></div>
|
||||
<div class="place-content-start"></div>
|
||||
<div class="placeholder-green-300"></div>
|
||||
<div class="placeholder-opacity-60"></div>
|
||||
<div class="place-items-end"></div>
|
||||
<div class="place-self-center"></div>
|
||||
<div class="pointer-events-none"></div>
|
||||
<div class="absolute"></div>
|
||||
<div class="resize-none"></div>
|
||||
<div class="ring-white"></div>
|
||||
<div class="ring-offset-blue-300"></div>
|
||||
<div class="ring-offset-2"></div>
|
||||
<div class="ring-opacity-40"></div>
|
||||
<div class="ring ring-4"></div>
|
||||
<div
|
||||
class="filter filter-none blur-md brightness-150 contrast-50 drop-shadow-md grayscale hue-rotate-60 invert saturate-200 sepia"
|
||||
></div>
|
||||
<div
|
||||
class="backdrop-filter backdrop-filter-none backdrop-blur-lg backdrop-brightness-50 backdrop-contrast-0 backdrop-grayscale backdrop-hue-rotate-90 backdrop-invert backdrop-opacity-75 backdrop-saturate-150 backdrop-sepia"
|
||||
></div>
|
||||
<div class="rotate-3"></div>
|
||||
<div class="scale-95"></div>
|
||||
<div class="skew-y-12 skew-x-12"></div>
|
||||
<div class="space-x-4 space-y-3 space-x-reverse space-y-reverse"></div>
|
||||
<div class="stroke-current"></div>
|
||||
<div class="stroke-2"></div>
|
||||
<div class="table-fixed"></div>
|
||||
<div class="text-center"></div>
|
||||
<div class="text-indigo-500"></div>
|
||||
<div class="underline"></div>
|
||||
<div class="text-opacity-10"></div>
|
||||
<div class="overflow-ellipsis truncate"></div>
|
||||
<div class="uppercase"></div>
|
||||
<div class="transform transform-gpu"></div>
|
||||
<div class="origin-top-right"></div>
|
||||
<div class="delay-300"></div>
|
||||
<div class="duration-200"></div>
|
||||
<div class="transition transition-all"></div>
|
||||
<div class="ease-in-out"></div>
|
||||
<div class="translate-x-5 -translate-x-4 translate-y-6 -translate-x-3"></div>
|
||||
<div class="select-none"></div>
|
||||
<div class="align-middle"></div>
|
||||
<div class="invisible"></div>
|
||||
<div class="collapse"></div>
|
||||
<div class="whitespace-nowrap"></div>
|
||||
<div class="w-12"></div>
|
||||
<div class="break-words"></div>
|
||||
<div class="z-30"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
it('raw content', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="sr-only"></div>
|
||||
<div class="content-center"></div>
|
||||
<div class="items-start"></div>
|
||||
<div class="self-end"></div>
|
||||
<div class="animate-none"></div>
|
||||
<div class="animate-spin"></div>
|
||||
<div class="appearance-none"></div>
|
||||
<div class="bg-local"></div>
|
||||
<div class="bg-clip-border"></div>
|
||||
<div class="bg-green-500"></div>
|
||||
<div class="bg-gradient-to-r"></div>
|
||||
<div class="bg-opacity-20"></div>
|
||||
<div class="bg-top"></div>
|
||||
<div class="bg-no-repeat"></div>
|
||||
<div class="bg-cover"></div>
|
||||
<div class="bg-origin-border bg-origin-padding bg-origin-content"></div>
|
||||
<div class="border-collapse"></div>
|
||||
<div class="border-black"></div>
|
||||
<div class="border-opacity-10"></div>
|
||||
<div class="rounded-md"></div>
|
||||
<div class="border-solid"></div>
|
||||
<div class="border"></div>
|
||||
<div class="border-2"></div>
|
||||
<div class="shadow"></div>
|
||||
<div class="shadow-md"></div>
|
||||
<div class="shadow-lg"></div>
|
||||
<div class="decoration-clone decoration-slice"></div>
|
||||
<div class="box-border"></div>
|
||||
<div class="clear-left"></div>
|
||||
<div class="container"></div>
|
||||
<div class="cursor-pointer"></div>
|
||||
<div class="hidden inline-grid"></div>
|
||||
<div class="divide-gray-200"></div>
|
||||
<div class="divide-opacity-50"></div>
|
||||
<div class="divide-dotted"></div>
|
||||
<div class="divide-x-2 divide-y-4 divide-x-0 divide-y-0"></div>
|
||||
<div class="fill-current"></div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="flex-row-reverse"></div>
|
||||
<div class="flex-grow"></div>
|
||||
<div class="flex-grow-0"></div>
|
||||
<div class="flex-shrink"></div>
|
||||
<div class="flex-shrink-0"></div>
|
||||
<div class="flex-wrap"></div>
|
||||
<div class="float-right"></div>
|
||||
<div class="font-sans"></div>
|
||||
<div class="text-2xl"></div>
|
||||
<div class="antialiased"></div>
|
||||
<div class="not-italic"></div>
|
||||
<div class="tabular-nums ordinal diagonal-fractions"></div>
|
||||
<div class="font-medium"></div>
|
||||
<div class="gap-x-2 gap-y-3 gap-4"></div>
|
||||
<div class="from-red-300 via-purple-200 to-blue-400"></div>
|
||||
<div class="auto-cols-min"></div>
|
||||
<div class="grid-flow-row"></div>
|
||||
<div class="auto-rows-max"></div>
|
||||
<div class="col-span-3"></div>
|
||||
<div class="col-start-1"></div>
|
||||
<div class="col-end-4"></div>
|
||||
<div class="row-span-2"></div>
|
||||
<div class="row-start-3"></div>
|
||||
<div class="row-end-5"></div>
|
||||
<div class="grid-cols-4"></div>
|
||||
<div class="grid-rows-3"></div>
|
||||
<div class="h-16"></div>
|
||||
<div class="inset-0 inset-y-4 inset-x-2 top-6 right-8 bottom-12 left-16"></div>
|
||||
<div class="isolate isolation-auto"></div>
|
||||
<div class="justify-center"></div>
|
||||
<div class="justify-items-end"></div>
|
||||
<div class="justify-self-start"></div>
|
||||
<div class="tracking-tight"></div>
|
||||
<div class="leading-relaxed leading-5"></div>
|
||||
<div class="list-inside"></div>
|
||||
<div class="list-disc"></div>
|
||||
<div class="m-4 my-2 mx-auto mt-0 mr-1 mb-3 ml-4"></div>
|
||||
<div class="max-h-screen"></div>
|
||||
<div class="max-w-full"></div>
|
||||
<div class="min-h-0"></div>
|
||||
<div class="min-w-min"></div>
|
||||
<div class="object-cover"></div>
|
||||
<div class="object-bottom"></div>
|
||||
<div class="opacity-90"></div>
|
||||
<div class="bg-blend-darken bg-blend-difference"></div>
|
||||
<div class="mix-blend-multiply mix-blend-saturation"></div>
|
||||
<div class="order-last order-2"></div>
|
||||
<div class="overflow-hidden"></div>
|
||||
<div class="overscroll-contain"></div>
|
||||
<div class="scroll-smooth"></div>
|
||||
<div class="p-4 py-2 px-3 pt-1 pr-2 pb-3 pl-4"></div>
|
||||
<div class="place-content-start"></div>
|
||||
<div class="placeholder-green-300"></div>
|
||||
<div class="placeholder-opacity-60"></div>
|
||||
<div class="place-items-end"></div>
|
||||
<div class="place-self-center"></div>
|
||||
<div class="pointer-events-none"></div>
|
||||
<div class="absolute"></div>
|
||||
<div class="resize-none"></div>
|
||||
<div class="ring-white"></div>
|
||||
<div class="ring-offset-blue-300"></div>
|
||||
<div class="ring-offset-2"></div>
|
||||
<div class="ring-opacity-40"></div>
|
||||
<div class="ring ring-4"></div>
|
||||
<div
|
||||
class="filter filter-none blur-md brightness-150 contrast-50 drop-shadow-md grayscale hue-rotate-60 invert saturate-200 sepia"
|
||||
></div>
|
||||
<div
|
||||
class="backdrop-filter backdrop-filter-none backdrop-blur-lg backdrop-brightness-50 backdrop-contrast-0 backdrop-grayscale backdrop-hue-rotate-90 backdrop-invert backdrop-opacity-75 backdrop-saturate-150 backdrop-sepia"
|
||||
></div>
|
||||
<div class="rotate-3"></div>
|
||||
<div class="scale-95"></div>
|
||||
<div class="skew-y-12 skew-x-12"></div>
|
||||
<div class="space-x-4 space-y-3 space-x-reverse space-y-reverse"></div>
|
||||
<div class="stroke-current"></div>
|
||||
<div class="stroke-2"></div>
|
||||
<div class="table-fixed"></div>
|
||||
<div class="text-center"></div>
|
||||
<div class="text-indigo-500"></div>
|
||||
<div class="underline"></div>
|
||||
<div class="text-opacity-10"></div>
|
||||
<div class="overflow-ellipsis truncate"></div>
|
||||
<div class="uppercase"></div>
|
||||
<div class="transform transform-gpu"></div>
|
||||
<div class="origin-top-right"></div>
|
||||
<div class="delay-300"></div>
|
||||
<div class="duration-200"></div>
|
||||
<div class="transition transition-all"></div>
|
||||
<div class="ease-in-out"></div>
|
||||
<div class="translate-x-5 -translate-x-4 translate-y-6 -translate-x-3"></div>
|
||||
<div class="select-none"></div>
|
||||
<div class="align-middle"></div>
|
||||
<div class="invisible"></div>
|
||||
<div class="collapse"></div>
|
||||
<div class="whitespace-nowrap"></div>
|
||||
<div class="w-12"></div>
|
||||
<div class="break-words"></div>
|
||||
<div class="z-30"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
let expectedPath = env.OXIDE
|
||||
? path.resolve(__dirname, './raw-content.oxide.test.css')
|
||||
: path.resolve(__dirname, './raw-content.test.css')
|
||||
let expected = fs.readFileSync(expectedPath, 'utf8')
|
||||
|
||||
expect(result.css).toMatchFormattedCss(expected)
|
||||
return run(input, config).then((result) => {
|
||||
stable
|
||||
.expect(result.css)
|
||||
.toMatchFormattedCss(
|
||||
fs.readFileSync(path.resolve(__dirname, './raw-content.test.css'), 'utf8')
|
||||
)
|
||||
oxide
|
||||
.expect(result.css)
|
||||
.toMatchFormattedCss(
|
||||
fs.readFileSync(path.resolve(__dirname, './raw-content.oxide.test.css'), 'utf8')
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,22 +1,24 @@
|
||||
import { run, css } from './util/run'
|
||||
import { crosscheck, run, css } from './util/run'
|
||||
|
||||
test('relative purge paths', () => {
|
||||
let config = {
|
||||
content: ['./tests/relative-purge-paths.test.html'],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('relative purge paths', () => {
|
||||
let config = {
|
||||
content: ['./tests/relative-purge-paths.test.html'],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toIncludeCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toIncludeCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,155 +1,157 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('responsive and variants atrules', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="responsive-in-utilities"></div>
|
||||
<div class="variants-in-utilities"></div>
|
||||
<div class="both-in-utilities"></div>
|
||||
<div class="responsive-at-root"></div>
|
||||
<div class="variants-at-root"></div>
|
||||
<div class="both-at-root"></div>
|
||||
<div class="responsive-in-components"></div>
|
||||
<div class="variants-in-components"></div>
|
||||
<div class="both-in-components"></div>
|
||||
<div class="md:focus:responsive-in-utilities"></div>
|
||||
<div class="md:focus:variants-in-utilities"></div>
|
||||
<div class="md:focus:both-in-utilities"></div>
|
||||
<div class="md:focus:responsive-at-root"></div>
|
||||
<div class="md:focus:variants-at-root"></div>
|
||||
<div class="md:focus:both-at-root"></div>
|
||||
<div class="md:focus:responsive-in-components"></div>
|
||||
<div class="md:focus:variants-in-components"></div>
|
||||
<div class="md:focus:both-in-components"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
crosscheck(() => {
|
||||
test('responsive and variants atrules', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="responsive-in-utilities"></div>
|
||||
<div class="variants-in-utilities"></div>
|
||||
<div class="both-in-utilities"></div>
|
||||
<div class="responsive-at-root"></div>
|
||||
<div class="variants-at-root"></div>
|
||||
<div class="both-at-root"></div>
|
||||
<div class="responsive-in-components"></div>
|
||||
<div class="variants-in-components"></div>
|
||||
<div class="both-in-components"></div>
|
||||
<div class="md:focus:responsive-in-utilities"></div>
|
||||
<div class="md:focus:variants-in-utilities"></div>
|
||||
<div class="md:focus:both-in-utilities"></div>
|
||||
<div class="md:focus:responsive-at-root"></div>
|
||||
<div class="md:focus:variants-at-root"></div>
|
||||
<div class="md:focus:both-at-root"></div>
|
||||
<div class="md:focus:responsive-in-components"></div>
|
||||
<div class="md:focus:variants-in-components"></div>
|
||||
<div class="md:focus:both-in-components"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
@responsive {
|
||||
.responsive-in-utilities {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
@variants {
|
||||
.variants-in-utilities {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
@responsive {
|
||||
@variants {
|
||||
.both-in-utilities {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@responsive {
|
||||
.responsive-in-utilities {
|
||||
color: blue;
|
||||
.responsive-at-root {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
@variants {
|
||||
.variants-in-utilities {
|
||||
color: red;
|
||||
.variants-at-root {
|
||||
color: orange;
|
||||
}
|
||||
}
|
||||
@responsive {
|
||||
@variants {
|
||||
.both-in-utilities {
|
||||
color: green;
|
||||
.both-at-root {
|
||||
color: pink;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@responsive {
|
||||
.responsive-at-root {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
@variants {
|
||||
.variants-at-root {
|
||||
color: orange;
|
||||
}
|
||||
}
|
||||
@responsive {
|
||||
@variants {
|
||||
.both-at-root {
|
||||
color: pink;
|
||||
@layer components {
|
||||
@responsive {
|
||||
.responsive-in-components {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
@variants {
|
||||
.variants-in-components {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
@responsive {
|
||||
@variants {
|
||||
.both-in-components {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@layer components {
|
||||
@responsive {
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.responsive-in-components {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
@variants {
|
||||
.variants-in-components {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
@responsive {
|
||||
@variants {
|
||||
.both-in-components {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.responsive-in-components {
|
||||
color: blue;
|
||||
}
|
||||
.variants-in-components {
|
||||
color: red;
|
||||
}
|
||||
.both-in-components {
|
||||
color: green;
|
||||
}
|
||||
.responsive-in-utilities {
|
||||
color: blue;
|
||||
}
|
||||
.variants-in-utilities {
|
||||
color: red;
|
||||
}
|
||||
.both-in-utilities {
|
||||
color: green;
|
||||
}
|
||||
.responsive-at-root {
|
||||
color: white;
|
||||
}
|
||||
.variants-at-root {
|
||||
color: orange;
|
||||
}
|
||||
.both-at-root {
|
||||
color: pink;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:focus\:responsive-in-components:focus {
|
||||
color: blue;
|
||||
}
|
||||
.md\:focus\:variants-in-components:focus {
|
||||
color: red;
|
||||
}
|
||||
.md\:focus\:both-in-components:focus {
|
||||
.both-in-components {
|
||||
color: green;
|
||||
}
|
||||
.md\:focus\:responsive-in-utilities:focus {
|
||||
.responsive-in-utilities {
|
||||
color: blue;
|
||||
}
|
||||
.md\:focus\:variants-in-utilities:focus {
|
||||
.variants-in-utilities {
|
||||
color: red;
|
||||
}
|
||||
.md\:focus\:both-in-utilities:focus {
|
||||
.both-in-utilities {
|
||||
color: green;
|
||||
}
|
||||
.md\:focus\:responsive-at-root:focus {
|
||||
.responsive-at-root {
|
||||
color: white;
|
||||
}
|
||||
.md\:focus\:variants-at-root:focus {
|
||||
.variants-at-root {
|
||||
color: orange;
|
||||
}
|
||||
.md\:focus\:both-at-root:focus {
|
||||
.both-at-root {
|
||||
color: pink;
|
||||
}
|
||||
}
|
||||
`)
|
||||
@media (min-width: 768px) {
|
||||
.md\:focus\:responsive-in-components:focus {
|
||||
color: blue;
|
||||
}
|
||||
.md\:focus\:variants-in-components:focus {
|
||||
color: red;
|
||||
}
|
||||
.md\:focus\:both-in-components:focus {
|
||||
color: green;
|
||||
}
|
||||
.md\:focus\:responsive-in-utilities:focus {
|
||||
color: blue;
|
||||
}
|
||||
.md\:focus\:variants-in-utilities:focus {
|
||||
color: red;
|
||||
}
|
||||
.md\:focus\:both-in-utilities:focus {
|
||||
color: green;
|
||||
}
|
||||
.md\:focus\:responsive-at-root:focus {
|
||||
color: white;
|
||||
}
|
||||
.md\:focus\:variants-at-root:focus {
|
||||
color: orange;
|
||||
}
|
||||
.md\:focus\:both-at-root:focus {
|
||||
color: pink;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,15 @@
|
||||
import postcss from 'postcss'
|
||||
import plugin from '../src/lib/substituteScreenAtRules'
|
||||
import config from '../stubs/defaultConfig.stub.js'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
function run(input, opts = config) {
|
||||
return postcss([plugin({ tailwindConfig: opts })]).process(input, { from: undefined })
|
||||
}
|
||||
|
||||
test('it can generate media queries from configured screen sizes', () => {
|
||||
const input = `
|
||||
crosscheck(() => {
|
||||
test('it can generate media queries from configured screen sizes', () => {
|
||||
const input = `
|
||||
@screen sm {
|
||||
.banana { color: yellow; }
|
||||
}
|
||||
@ -19,7 +21,7 @@ test('it can generate media queries from configured screen sizes', () => {
|
||||
}
|
||||
`
|
||||
|
||||
const output = `
|
||||
const output = `
|
||||
@media (min-width: 500px) {
|
||||
.banana { color: yellow; }
|
||||
}
|
||||
@ -31,17 +33,18 @@ test('it can generate media queries from configured screen sizes', () => {
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, {
|
||||
theme: {
|
||||
screens: {
|
||||
sm: '500px',
|
||||
md: '750px',
|
||||
lg: '1000px',
|
||||
return run(input, {
|
||||
theme: {
|
||||
screens: {
|
||||
sm: '500px',
|
||||
md: '750px',
|
||||
lg: '1000px',
|
||||
},
|
||||
},
|
||||
},
|
||||
separator: ':',
|
||||
}).then((result) => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
separator: ':',
|
||||
}).then((result) => {
|
||||
expect(result.css).toMatchCss(output)
|
||||
expect(result.warnings().length).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,20 +1,23 @@
|
||||
import { resolveDebug } from '../src/lib/sharedState'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
it.each`
|
||||
value | expected
|
||||
${'true'} | ${true}
|
||||
${'1'} | ${true}
|
||||
${'false'} | ${false}
|
||||
${'0'} | ${false}
|
||||
${'*'} | ${true}
|
||||
${'tailwindcss'} | ${true}
|
||||
${'tailwindcss:*'} | ${true}
|
||||
${'other,tailwindcss'} | ${true}
|
||||
${'other,tailwindcss:*'} | ${true}
|
||||
${'other,-tailwindcss'} | ${false}
|
||||
${'other,-tailwindcss:*'} | ${false}
|
||||
${'-tailwindcss'} | ${false}
|
||||
${'-tailwindcss:*'} | ${false}
|
||||
`('should resolve the debug ($value) flag correctly ($expected)', ({ value, expected }) => {
|
||||
expect(resolveDebug(value)).toBe(expected)
|
||||
crosscheck(() => {
|
||||
it.each`
|
||||
value | expected
|
||||
${'true'} | ${true}
|
||||
${'1'} | ${true}
|
||||
${'false'} | ${false}
|
||||
${'0'} | ${false}
|
||||
${'*'} | ${true}
|
||||
${'tailwindcss'} | ${true}
|
||||
${'tailwindcss:*'} | ${true}
|
||||
${'other,tailwindcss'} | ${true}
|
||||
${'other,tailwindcss:*'} | ${true}
|
||||
${'other,-tailwindcss'} | ${false}
|
||||
${'other,-tailwindcss:*'} | ${false}
|
||||
${'-tailwindcss'} | ${false}
|
||||
${'-tailwindcss:*'} | ${false}
|
||||
`('should resolve the debug ($value) flag correctly ($expected)', ({ value, expected }) => {
|
||||
expect(resolveDebug(value)).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,149 +1,517 @@
|
||||
import postcss from 'postcss'
|
||||
import { runWithSourceMaps as run, html, css, map } from './util/run'
|
||||
import { parseSourceMaps } from './util/source-maps'
|
||||
import { crosscheck, runWithSourceMaps as run, html, css, map } from './util/run'
|
||||
|
||||
it('apply generates source maps', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="with-declaration"></div>
|
||||
<div class="with-comment"></div>
|
||||
<div class="just-apply"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
.with-declaration {
|
||||
background-color: red;
|
||||
@apply h-4 w-4 bg-green-500;
|
||||
crosscheck(() => {
|
||||
it('apply generates source maps', async () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="with-declaration"></div>
|
||||
<div class="with-comment"></div>
|
||||
<div class="just-apply"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
.with-comment {
|
||||
/* sourcemap will work here too */
|
||||
@apply h-4 w-4 bg-red-500;
|
||||
}
|
||||
|
||||
.just-apply {
|
||||
@apply h-4 w-4 bg-black;
|
||||
}
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
let { sources, annotations } = parseSourceMaps(result)
|
||||
|
||||
// All CSS generated by Tailwind CSS should be annotated with source maps
|
||||
// And always be able to point to the original source file
|
||||
expect(sources).not.toContain('<no source>')
|
||||
expect(sources.length).toBe(1)
|
||||
|
||||
expect(annotations).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('preflight + base have source maps', async () => {
|
||||
let config = {
|
||||
content: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
let { sources, annotations } = parseSourceMaps(result)
|
||||
|
||||
// All CSS generated by Tailwind CSS should be annotated with source maps
|
||||
// And always be able to point to the original source file
|
||||
expect(sources).not.toContain('<no source>')
|
||||
expect(sources.length).toBe(1)
|
||||
|
||||
expect(annotations).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('utilities have source maps', async () => {
|
||||
let config = {
|
||||
content: [{ raw: `text-red-500` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
let { sources, annotations } = parseSourceMaps(result)
|
||||
|
||||
// All CSS generated by Tailwind CSS should be annotated with source maps
|
||||
// And always be able to point to the original source file
|
||||
expect(sources).not.toContain('<no source>')
|
||||
expect(sources.length).toBe(1)
|
||||
|
||||
expect(annotations).toStrictEqual(['2:4 -> 1:0', '2:4-23 -> 2:4-24', '2:4 -> 3:4', '2:23 -> 4:0'])
|
||||
})
|
||||
|
||||
it('components have source maps', async () => {
|
||||
let config = {
|
||||
content: [{ raw: `container` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
let { sources, annotations } = parseSourceMaps(result)
|
||||
|
||||
// All CSS generated by Tailwind CSS should be annotated with source maps
|
||||
// And always be able to point to the original source file
|
||||
expect(sources).not.toContain('<no source>')
|
||||
expect(sources.length).toBe(1)
|
||||
|
||||
expect(annotations).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('source maps for layer rules are not rewritten to point to @tailwind directives', async () => {
|
||||
let config = {
|
||||
content: [{ raw: `font-normal foo hover:foo` }],
|
||||
}
|
||||
|
||||
let utilitiesFile = postcss.parse(
|
||||
css`
|
||||
@tailwind utilities;
|
||||
`,
|
||||
{ from: 'components.css', map: { prev: map } }
|
||||
)
|
||||
|
||||
let mainCssFile = postcss.parse(
|
||||
css`
|
||||
@layer utilities {
|
||||
.foo {
|
||||
background-color: red;
|
||||
}
|
||||
let input = css`
|
||||
.with-declaration {
|
||||
background-color: red;
|
||||
@apply h-4 w-4 bg-green-500;
|
||||
}
|
||||
`,
|
||||
{ from: 'input.css', map: { prev: map } }
|
||||
)
|
||||
|
||||
// Just pretend that there's an @import in `mainCssFile` that imports the nodes from `utilitiesFile`
|
||||
let input = postcss.root({
|
||||
nodes: [...utilitiesFile.nodes, ...mainCssFile.nodes],
|
||||
source: mainCssFile.source,
|
||||
.with-comment {
|
||||
/* sourcemap will work here too */
|
||||
@apply h-4 w-4 bg-red-500;
|
||||
}
|
||||
|
||||
.just-apply {
|
||||
@apply h-4 w-4 bg-black;
|
||||
}
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
let { sources, annotations } = parseSourceMaps(result)
|
||||
|
||||
// All CSS generated by Tailwind CSS should be annotated with source maps
|
||||
// And always be able to point to the original source file
|
||||
expect(sources).not.toContain('<no source>')
|
||||
expect(sources.length).toBe(1)
|
||||
|
||||
expect(annotations).toEqual([
|
||||
'2:6 -> 2:6',
|
||||
'3:8-29 -> 3:8-29',
|
||||
'4:8-35 -> 4:8-20',
|
||||
'4:8-35 -> 5:8-19',
|
||||
'4:8-35 -> 6:8-26',
|
||||
'4:8-35 -> 7:8-63',
|
||||
'5:6 -> 8:6',
|
||||
'7:6 -> 10:6',
|
||||
'8:8-41 -> 11:8-41',
|
||||
'9:8-33 -> 12:8-20',
|
||||
'9:8-33 -> 13:8-19',
|
||||
'9:8-33 -> 14:8-26',
|
||||
'9:8-33 -> 15:8-63',
|
||||
'10:6 -> 16:6',
|
||||
'13:8 -> 18:6',
|
||||
'13:8-31 -> 19:8-20',
|
||||
'13:8-31 -> 20:8-19',
|
||||
'13:8-31 -> 21:8-26',
|
||||
'13:8 -> 22:8',
|
||||
'13:31 -> 23:0',
|
||||
])
|
||||
})
|
||||
|
||||
let result = await run(input, config)
|
||||
it('preflight + base have source maps', async () => {
|
||||
let config = {
|
||||
content: [],
|
||||
}
|
||||
|
||||
let { sources, annotations } = parseSourceMaps(result)
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
`
|
||||
|
||||
// All CSS generated by Tailwind CSS should be annotated with source maps
|
||||
// And always be able to point to the original source file
|
||||
expect(sources).not.toContain('<no source>')
|
||||
let result = await run(input, config)
|
||||
let { sources, annotations } = parseSourceMaps(result)
|
||||
|
||||
// And we should see that the source map for the layer rule is not rewritten
|
||||
// to point to the @tailwind directive but instead points to the original
|
||||
expect(sources.length).toBe(2)
|
||||
expect(sources).toEqual(['components.css', 'input.css'])
|
||||
// All CSS generated by Tailwind CSS should be annotated with source maps
|
||||
// And always be able to point to the original source file
|
||||
expect(sources).not.toContain('<no source>')
|
||||
expect(sources.length).toBe(1)
|
||||
|
||||
expect(annotations).toMatchSnapshot()
|
||||
expect(annotations).toEqual([
|
||||
'2:6 -> 1:0',
|
||||
'2:20-6 -> 3:1-2',
|
||||
'2:20 -> 6:1',
|
||||
'2:6 -> 8:0',
|
||||
'2:6-20 -> 11:2-32',
|
||||
'2:6-20 -> 12:2-25',
|
||||
'2:6-20 -> 13:2-29',
|
||||
'2:6-20 -> 14:2-31',
|
||||
'2:20 -> 15:0',
|
||||
'2:6 -> 17:0',
|
||||
'2:6-20 -> 19:2-18',
|
||||
'2:20 -> 20:0',
|
||||
'2:6 -> 22:0',
|
||||
'2:20 -> 28:1',
|
||||
'2:6 -> 30:0',
|
||||
'2:6-20 -> 31:2-26',
|
||||
'2:6-20 -> 32:2-40',
|
||||
'2:6-20 -> 33:2-26',
|
||||
'2:6-20 -> 34:2-21',
|
||||
'2:6-20 -> 35:2-230',
|
||||
'2:6-20 -> 36:2-39',
|
||||
'2:20 -> 37:0',
|
||||
'2:6 -> 39:0',
|
||||
'2:20 -> 42:1',
|
||||
'2:6 -> 44:0',
|
||||
'2:6-20 -> 45:2-19',
|
||||
'2:6-20 -> 46:2-30',
|
||||
'2:20 -> 47:0',
|
||||
'2:6 -> 49:0',
|
||||
'2:20 -> 53:1',
|
||||
'2:6 -> 55:0',
|
||||
'2:6-20 -> 56:2-19',
|
||||
'2:6-20 -> 57:2-24',
|
||||
'2:6-20 -> 58:2-31',
|
||||
'2:20 -> 59:0',
|
||||
'2:6 -> 61:0',
|
||||
'2:20 -> 63:1',
|
||||
'2:6 -> 65:0',
|
||||
'2:6-20 -> 66:2-35',
|
||||
'2:20 -> 67:0',
|
||||
'2:6 -> 69:0',
|
||||
'2:20 -> 71:1',
|
||||
'2:6 -> 73:0',
|
||||
'2:6-20 -> 79:2-20',
|
||||
'2:6-20 -> 80:2-22',
|
||||
'2:20 -> 81:0',
|
||||
'2:6 -> 83:0',
|
||||
'2:20 -> 85:1',
|
||||
'2:6 -> 87:0',
|
||||
'2:6-20 -> 88:2-16',
|
||||
'2:6-20 -> 89:2-26',
|
||||
'2:20 -> 90:0',
|
||||
'2:6 -> 92:0',
|
||||
'2:20 -> 94:1',
|
||||
'2:6 -> 96:0',
|
||||
'2:6-20 -> 98:2-21',
|
||||
'2:20 -> 99:0',
|
||||
'2:6 -> 101:0',
|
||||
'2:20 -> 104:1',
|
||||
'2:6 -> 106:0',
|
||||
'2:6-20 -> 110:2-121',
|
||||
'2:6-20 -> 111:2-24',
|
||||
'2:20 -> 112:0',
|
||||
'2:6 -> 114:0',
|
||||
'2:20 -> 116:1',
|
||||
'2:6 -> 118:0',
|
||||
'2:6-20 -> 119:2-16',
|
||||
'2:20 -> 120:0',
|
||||
'2:6 -> 122:0',
|
||||
'2:20 -> 124:1',
|
||||
'2:6 -> 126:0',
|
||||
'2:6-20 -> 128:2-16',
|
||||
'2:6-20 -> 129:2-16',
|
||||
'2:6-20 -> 130:2-20',
|
||||
'2:6-20 -> 131:2-26',
|
||||
'2:20 -> 132:0',
|
||||
'2:6 -> 134:0',
|
||||
'2:6-20 -> 135:2-17',
|
||||
'2:20 -> 136:0',
|
||||
'2:6 -> 138:0',
|
||||
'2:6-20 -> 139:2-13',
|
||||
'2:20 -> 140:0',
|
||||
'2:6 -> 142:0',
|
||||
'2:20 -> 146:1',
|
||||
'2:6 -> 148:0',
|
||||
'2:6-20 -> 149:2-24',
|
||||
'2:6-20 -> 150:2-31',
|
||||
'2:6-20 -> 151:2-35',
|
||||
'2:20 -> 152:0',
|
||||
'2:6 -> 154:0',
|
||||
'2:20 -> 158:1',
|
||||
'2:6 -> 160:0',
|
||||
'2:6-20 -> 165:2-30',
|
||||
'2:6-20 -> 166:2-25',
|
||||
'2:6-20 -> 167:2-30',
|
||||
'2:6-20 -> 168:2-30',
|
||||
'2:6-20 -> 169:2-24',
|
||||
'2:6-20 -> 170:2-19',
|
||||
'2:6-20 -> 171:2-20',
|
||||
'2:20 -> 172:0',
|
||||
'2:6 -> 174:0',
|
||||
'2:20 -> 176:1',
|
||||
'2:6 -> 178:0',
|
||||
'2:6-20 -> 180:2-22',
|
||||
'2:20 -> 181:0',
|
||||
'2:6 -> 183:0',
|
||||
'2:20 -> 186:1',
|
||||
'2:6 -> 188:0',
|
||||
'2:6-20 -> 192:2-36',
|
||||
'2:6-20 -> 193:2-39',
|
||||
'2:6-20 -> 194:2-32',
|
||||
'2:20 -> 195:0',
|
||||
'2:6 -> 197:0',
|
||||
'2:20 -> 199:1',
|
||||
'2:6 -> 201:0',
|
||||
'2:6-20 -> 202:2-15',
|
||||
'2:20 -> 203:0',
|
||||
'2:6 -> 205:0',
|
||||
'2:20 -> 207:1',
|
||||
'2:6 -> 209:0',
|
||||
'2:6-20 -> 210:2-18',
|
||||
'2:20 -> 211:0',
|
||||
'2:6 -> 213:0',
|
||||
'2:20 -> 215:1',
|
||||
'2:6 -> 217:0',
|
||||
'2:6-20 -> 218:2-26',
|
||||
'2:20 -> 219:0',
|
||||
'2:6 -> 221:0',
|
||||
'2:20 -> 223:1',
|
||||
'2:6 -> 225:0',
|
||||
'2:6-20 -> 227:2-14',
|
||||
'2:20 -> 228:0',
|
||||
'2:6 -> 230:0',
|
||||
'2:20 -> 233:1',
|
||||
'2:6 -> 235:0',
|
||||
'2:6-20 -> 236:2-39',
|
||||
'2:6-20 -> 237:2-30',
|
||||
'2:20 -> 238:0',
|
||||
'2:6 -> 240:0',
|
||||
'2:20 -> 242:1',
|
||||
'2:6 -> 244:0',
|
||||
'2:6-20 -> 245:2-26',
|
||||
'2:20 -> 246:0',
|
||||
'2:6 -> 248:0',
|
||||
'2:20 -> 251:1',
|
||||
'2:6 -> 253:0',
|
||||
'2:6-20 -> 254:2-36',
|
||||
'2:6-20 -> 255:2-23',
|
||||
'2:20 -> 256:0',
|
||||
'2:6 -> 258:0',
|
||||
'2:20 -> 260:1',
|
||||
'2:6 -> 262:0',
|
||||
'2:6-20 -> 263:2-20',
|
||||
'2:20 -> 264:0',
|
||||
'2:6 -> 266:0',
|
||||
'2:20 -> 268:1',
|
||||
'2:6 -> 270:0',
|
||||
'2:6-20 -> 283:2-11',
|
||||
'2:20 -> 284:0',
|
||||
'2:6 -> 286:0',
|
||||
'2:6-20 -> 287:2-11',
|
||||
'2:6-20 -> 288:2-12',
|
||||
'2:20 -> 289:0',
|
||||
'2:6 -> 291:0',
|
||||
'2:6-20 -> 292:2-12',
|
||||
'2:20 -> 293:0',
|
||||
'2:6 -> 295:0',
|
||||
'2:6-20 -> 298:2-18',
|
||||
'2:6-20 -> 299:2-11',
|
||||
'2:6-20 -> 300:2-12',
|
||||
'2:20 -> 301:0',
|
||||
'2:6 -> 303:0',
|
||||
'2:20 -> 305:1',
|
||||
'2:6 -> 307:0',
|
||||
'2:6-20 -> 308:2-18',
|
||||
'2:20 -> 309:0',
|
||||
'2:6 -> 311:0',
|
||||
'2:20 -> 314:1',
|
||||
'2:6 -> 316:0',
|
||||
'2:6-20 -> 318:2-20',
|
||||
'2:6-20 -> 319:2-24',
|
||||
'2:20 -> 320:0',
|
||||
'2:6 -> 322:0',
|
||||
'2:20 -> 324:1',
|
||||
'2:6 -> 326:0',
|
||||
'2:6-20 -> 328:2-17',
|
||||
'2:20 -> 329:0',
|
||||
'2:6 -> 331:0',
|
||||
'2:20 -> 333:1',
|
||||
'2:6 -> 334:0',
|
||||
'2:6-20 -> 335:2-17',
|
||||
'2:20 -> 336:0',
|
||||
'2:6 -> 338:0',
|
||||
'2:20 -> 342:1',
|
||||
'2:6 -> 344:0',
|
||||
'2:6-20 -> 352:2-24',
|
||||
'2:6-20 -> 353:2-32',
|
||||
'2:20 -> 354:0',
|
||||
'2:6 -> 356:0',
|
||||
'2:20 -> 358:1',
|
||||
'2:6 -> 360:0',
|
||||
'2:6-20 -> 362:2-17',
|
||||
'2:6-20 -> 363:2-14',
|
||||
'2:20 -> 364:0',
|
||||
'2:6-20 -> 366:0-72',
|
||||
'2:6 -> 367:0',
|
||||
'2:6-20 -> 368:2-15',
|
||||
'2:20 -> 369:0',
|
||||
'2:6 -> 371:0',
|
||||
'2:6-20 -> 372:2-26',
|
||||
'2:6-20 -> 373:2-26',
|
||||
'2:6-20 -> 374:2-21',
|
||||
'2:6-20 -> 375:2-21',
|
||||
'2:6-20 -> 376:2-16',
|
||||
'2:6-20 -> 377:2-16',
|
||||
'2:6-20 -> 378:2-16',
|
||||
'2:6-20 -> 379:2-17',
|
||||
'2:6-20 -> 380:2-17',
|
||||
'2:6-20 -> 381:2-15',
|
||||
'2:6-20 -> 382:2-15',
|
||||
'2:6-20 -> 383:2-20',
|
||||
'2:6-20 -> 384:2-40',
|
||||
'2:6-20 -> 385:2-17',
|
||||
'2:6-20 -> 386:2-22',
|
||||
'2:6-20 -> 387:2-24',
|
||||
'2:6-20 -> 388:2-25',
|
||||
'2:6-20 -> 389:2-26',
|
||||
'2:6-20 -> 390:2-20',
|
||||
'2:6-20 -> 391:2-29',
|
||||
'2:6-20 -> 392:2-30',
|
||||
'2:6-20 -> 393:2-40',
|
||||
'2:6-20 -> 394:2-36',
|
||||
'2:6-20 -> 395:2-29',
|
||||
'2:6-20 -> 396:2-24',
|
||||
'2:6-20 -> 397:2-32',
|
||||
'2:6-20 -> 398:2-14',
|
||||
'2:6-20 -> 399:2-20',
|
||||
'2:6-20 -> 400:2-18',
|
||||
'2:6-20 -> 401:2-19',
|
||||
'2:6-20 -> 402:2-20',
|
||||
'2:6-20 -> 403:2-16',
|
||||
'2:6-20 -> 404:2-18',
|
||||
'2:6-20 -> 405:2-15',
|
||||
'2:6-20 -> 406:2-21',
|
||||
'2:6-20 -> 407:2-23',
|
||||
'2:6-20 -> 408:2-29',
|
||||
'2:6-20 -> 409:2-27',
|
||||
'2:6-20 -> 410:2-28',
|
||||
'2:6-20 -> 411:2-29',
|
||||
'2:6-20 -> 412:2-25',
|
||||
'2:6-20 -> 413:2-26',
|
||||
'2:6-20 -> 414:2-27',
|
||||
'2:6 -> 415:2',
|
||||
'2:20 -> 416:0',
|
||||
'2:6 -> 418:0',
|
||||
'2:6-20 -> 419:2-26',
|
||||
'2:6-20 -> 420:2-26',
|
||||
'2:6-20 -> 421:2-21',
|
||||
'2:6-20 -> 422:2-21',
|
||||
'2:6-20 -> 423:2-16',
|
||||
'2:6-20 -> 424:2-16',
|
||||
'2:6-20 -> 425:2-16',
|
||||
'2:6-20 -> 426:2-17',
|
||||
'2:6-20 -> 427:2-17',
|
||||
'2:6-20 -> 428:2-15',
|
||||
'2:6-20 -> 429:2-15',
|
||||
'2:6-20 -> 430:2-20',
|
||||
'2:6-20 -> 431:2-40',
|
||||
'2:6-20 -> 432:2-17',
|
||||
'2:6-20 -> 433:2-22',
|
||||
'2:6-20 -> 434:2-24',
|
||||
'2:6-20 -> 435:2-25',
|
||||
'2:6-20 -> 436:2-26',
|
||||
'2:6-20 -> 437:2-20',
|
||||
'2:6-20 -> 438:2-29',
|
||||
'2:6-20 -> 439:2-30',
|
||||
'2:6-20 -> 440:2-40',
|
||||
'2:6-20 -> 441:2-36',
|
||||
'2:6-20 -> 442:2-29',
|
||||
'2:6-20 -> 443:2-24',
|
||||
'2:6-20 -> 444:2-32',
|
||||
'2:6-20 -> 445:2-14',
|
||||
'2:6-20 -> 446:2-20',
|
||||
'2:6-20 -> 447:2-18',
|
||||
'2:6-20 -> 448:2-19',
|
||||
'2:6-20 -> 449:2-20',
|
||||
'2:6-20 -> 450:2-16',
|
||||
'2:6-20 -> 451:2-18',
|
||||
'2:6-20 -> 452:2-15',
|
||||
'2:6-20 -> 453:2-21',
|
||||
'2:6-20 -> 454:2-23',
|
||||
'2:6-20 -> 455:2-29',
|
||||
'2:6-20 -> 456:2-27',
|
||||
'2:6-20 -> 457:2-28',
|
||||
'2:6-20 -> 458:2-29',
|
||||
'2:6-20 -> 459:2-25',
|
||||
'2:6-20 -> 460:2-26',
|
||||
'2:6-20 -> 461:2-27',
|
||||
'2:6 -> 462:2',
|
||||
'2:20 -> 463:0',
|
||||
])
|
||||
})
|
||||
|
||||
test('utilities have source maps', async () => {
|
||||
let config = {
|
||||
content: [{ raw: `text-red-500` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
let { sources, annotations } = parseSourceMaps(result)
|
||||
|
||||
// All CSS generated by Tailwind CSS should be annotated with source maps
|
||||
// And always be able to point to the original source file
|
||||
expect(sources).not.toContain('<no source>')
|
||||
expect(sources.length).toBe(1)
|
||||
|
||||
expect(annotations).toStrictEqual([
|
||||
'2:6 -> 1:0',
|
||||
'2:6-25 -> 2:4-24',
|
||||
'2:6 -> 3:4',
|
||||
'2:25 -> 4:0',
|
||||
])
|
||||
})
|
||||
|
||||
it('components have source maps', async () => {
|
||||
let config = {
|
||||
content: [{ raw: `container` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
let { sources, annotations } = parseSourceMaps(result)
|
||||
|
||||
// All CSS generated by Tailwind CSS should be annotated with source maps
|
||||
// And always be able to point to the original source file
|
||||
expect(sources).not.toContain('<no source>')
|
||||
expect(sources.length).toBe(1)
|
||||
|
||||
expect(annotations).toEqual([
|
||||
'2:6 -> 1:0',
|
||||
'2:6 -> 2:4',
|
||||
'2:26 -> 3:0',
|
||||
'2:6 -> 4:0',
|
||||
'2:6 -> 5:4',
|
||||
'2:6 -> 6:8',
|
||||
'2:26 -> 7:4',
|
||||
'2:26 -> 8:0',
|
||||
'2:6 -> 9:0',
|
||||
'2:6 -> 10:4',
|
||||
'2:6 -> 11:8',
|
||||
'2:26 -> 12:4',
|
||||
'2:26 -> 13:0',
|
||||
'2:6 -> 14:0',
|
||||
'2:6 -> 15:4',
|
||||
'2:6 -> 16:8',
|
||||
'2:26 -> 17:4',
|
||||
'2:26 -> 18:0',
|
||||
'2:6 -> 19:0',
|
||||
'2:6 -> 20:4',
|
||||
'2:6 -> 21:8',
|
||||
'2:26 -> 22:4',
|
||||
'2:26 -> 23:0',
|
||||
'2:6 -> 24:0',
|
||||
'2:6 -> 25:4',
|
||||
'2:6 -> 26:8',
|
||||
'2:26 -> 27:4',
|
||||
'2:26 -> 28:0',
|
||||
])
|
||||
})
|
||||
|
||||
it('source maps for layer rules are not rewritten to point to @tailwind directives', async () => {
|
||||
let config = {
|
||||
content: [{ raw: `font-normal foo hover:foo` }],
|
||||
}
|
||||
|
||||
let utilitiesFile = postcss.parse(
|
||||
css`
|
||||
@tailwind utilities;
|
||||
`,
|
||||
{ from: 'components.css', map: { prev: map } }
|
||||
)
|
||||
|
||||
let mainCssFile = postcss.parse(
|
||||
css`
|
||||
@layer utilities {
|
||||
.foo {
|
||||
background-color: red;
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ from: 'input.css', map: { prev: map } }
|
||||
)
|
||||
|
||||
// Just pretend that there's an @import in `mainCssFile` that imports the nodes from `utilitiesFile`
|
||||
let input = postcss.root({
|
||||
nodes: [...utilitiesFile.nodes, ...mainCssFile.nodes],
|
||||
source: mainCssFile.source,
|
||||
})
|
||||
|
||||
let result = await run(input, config)
|
||||
|
||||
let { sources, annotations } = parseSourceMaps(result)
|
||||
|
||||
// All CSS generated by Tailwind CSS should be annotated with source maps
|
||||
// And always be able to point to the original source file
|
||||
expect(sources).not.toContain('<no source>')
|
||||
|
||||
// And we should see that the source map for the layer rule is not rewritten
|
||||
// to point to the @tailwind directive but instead points to the original
|
||||
expect(sources.length).toBe(2)
|
||||
expect(sources).toEqual(['components.css', 'input.css'])
|
||||
|
||||
expect(annotations).toEqual([
|
||||
'2:8 -> 1:0',
|
||||
'2:8 -> 2:12',
|
||||
'2:27 -> 3:0',
|
||||
'3:10 -> 4:10',
|
||||
'4:12-33 -> 5:12-33',
|
||||
'5:10 -> 6:10',
|
||||
'3:10 -> 7:10',
|
||||
'4:12-33 -> 8:12-33',
|
||||
'5:10 -> 9:10',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,47 +1,47 @@
|
||||
import { run } from './util/run'
|
||||
import { crosscheck, run, css } from './util/run'
|
||||
|
||||
let css = String.raw
|
||||
crosscheck(() => {
|
||||
test('it detects classes in lit-html templates', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: `html\`<button class="bg-blue-400 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">\${data.title}</button>\`;`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
test('it detects classes in lit-html templates', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: `html\`<button class="bg-blue-400 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">\${data.title}</button>\`;`,
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.bg-blue-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
|
||||
}
|
||||
.px-4 {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
.hover\:bg-blue-600:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
|
||||
}
|
||||
`)
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.bg-blue-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
|
||||
}
|
||||
.px-4 {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
.hover\:bg-blue-600:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,46 +1,43 @@
|
||||
import path from 'path'
|
||||
import { crosscheck, run, css } from './util/run'
|
||||
|
||||
import { run } from './util/run'
|
||||
import { env } from '../src/lib/sharedState'
|
||||
crosscheck(({ stable, oxide }) => {
|
||||
oxide.test.todo('it detects svelte based on the file extension')
|
||||
stable.test('it detects svelte based on the file extension', () => {
|
||||
let config = {
|
||||
content: [path.resolve(__dirname, './syntax-svelte.test.svelte')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = String.raw
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let t = env.OXIDE ? test.skip : test
|
||||
|
||||
t('it detects svelte based on the file extension', () => {
|
||||
let config = {
|
||||
content: [path.resolve(__dirname, './syntax-svelte.test.svelte')],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.bg-red-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:hover\:bg-blue-500:hover {
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.bg-red-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
|
||||
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
`)
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:hover\:bg-blue-500:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t('using raw with svelte extension', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: `
|
||||
oxide.test.todo('using raw with svelte extension')
|
||||
stable.test('using raw with svelte extension', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: `
|
||||
<script>
|
||||
let current = 'foo'
|
||||
</script>
|
||||
@ -53,31 +50,32 @@ t('using raw with svelte extension', () => {
|
||||
Click me
|
||||
</button>
|
||||
`,
|
||||
extension: 'svelte',
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
extension: 'svelte',
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.bg-red-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:hover\:bg-blue-500:hover {
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchCss(css`
|
||||
.bg-red-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
|
||||
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
`)
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:hover\:bg-blue-500:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,67 +1,69 @@
|
||||
import { run, html, css } from './util/run'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
test('class variants are inserted at `@tailwind variants`', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-bold hover:font-bold md:font-bold"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@tailwind variants;
|
||||
.foo {
|
||||
color: black;
|
||||
crosscheck(() => {
|
||||
test('class variants are inserted at `@tailwind variants`', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-bold hover:font-bold md:font-bold"></div>` }],
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.hover\:font-bold:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@tailwind variants;
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
`
|
||||
|
||||
test('`@tailwind screens` works as an alias for `@tailwind variants`', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-bold hover:font-bold md:font-bold"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@tailwind screens;
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.hover\:font-bold:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:font-bold {
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
.hover\:font-bold:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('`@tailwind screens` works as an alias for `@tailwind variants`', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-bold hover:font-bold md:font-bold"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@tailwind screens;
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
`)
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.hover\:font-bold:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.md\:font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,18 +1,21 @@
|
||||
import { toPath } from '../src/util/toPath'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
it('should keep an array as an array', () => {
|
||||
let input = ['a', 'b', '0', 'c']
|
||||
crosscheck(() => {
|
||||
it('should keep an array as an array', () => {
|
||||
let input = ['a', 'b', '0', 'c']
|
||||
|
||||
expect(toPath(input)).toBe(input)
|
||||
})
|
||||
|
||||
it.each`
|
||||
input | output
|
||||
${'a.b.c'} | ${['a', 'b', 'c']}
|
||||
${'a[0].b.c'} | ${['a', '0', 'b', 'c']}
|
||||
${'.a'} | ${['a']}
|
||||
${'[].a'} | ${['a']}
|
||||
${'a[1.5][b][c]'} | ${['a', '1.5', 'b', 'c']}
|
||||
`('should convert "$input" to "$output"', ({ input, output }) => {
|
||||
expect(toPath(input)).toEqual(output)
|
||||
expect(toPath(input)).toBe(input)
|
||||
})
|
||||
|
||||
it.each`
|
||||
input | output
|
||||
${'a.b.c'} | ${['a', 'b', 'c']}
|
||||
${'a[0].b.c'} | ${['a', '0', 'b', 'c']}
|
||||
${'.a'} | ${['a']}
|
||||
${'[].a'} | ${['a']}
|
||||
${'a[1.5][b][c]'} | ${['a', '1.5', 'b', 'c']}
|
||||
`('should convert "$input" to "$output"', ({ input, output }) => {
|
||||
expect(toPath(input)).toEqual(output)
|
||||
})
|
||||
})
|
||||
|
||||
18
tests/util/crosscheck.test.js
Normal file
18
tests/util/crosscheck.test.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { crosscheck } from './run'
|
||||
|
||||
crosscheck(({ stable, oxide, engine }) => {
|
||||
stable.test('should run on stable', () => {
|
||||
expect(engine.stable).toBe(true)
|
||||
expect(engine.oxide).toBe(false)
|
||||
})
|
||||
oxide.test('should run on oxide', () => {
|
||||
expect(engine.stable).toBe(false)
|
||||
expect(engine.oxide).toBe(true)
|
||||
})
|
||||
test('should run on both', () => {
|
||||
oxide.expect(engine.oxide).toBe(true)
|
||||
oxide.expect(engine.stable).toBe(false)
|
||||
stable.expect(engine.oxide).toBe(false)
|
||||
stable.expect(engine.stable).toBe(true)
|
||||
})
|
||||
})
|
||||
@ -1,6 +1,7 @@
|
||||
import path from 'path'
|
||||
import postcss from 'postcss'
|
||||
import tailwind from '../../src'
|
||||
import { env } from '../../src/lib/sharedState'
|
||||
|
||||
export * from './strings'
|
||||
export * from './defaults'
|
||||
@ -31,3 +32,49 @@ export function runWithSourceMaps(input, config, plugin = tailwind) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
let nullTest = Object.assign(function () {}, {
|
||||
skip: () => {},
|
||||
only: () => {},
|
||||
each: () => () => {},
|
||||
todo: () => {},
|
||||
})
|
||||
let nullProxy = new Proxy(
|
||||
{
|
||||
test: nullTest,
|
||||
it: nullTest,
|
||||
xit: nullTest.skip,
|
||||
fit: nullTest.only,
|
||||
xdescribe: nullTest.skip,
|
||||
fdescribe: nullTest.only,
|
||||
},
|
||||
{
|
||||
get(target, prop, _receiver) {
|
||||
if (prop in target) {
|
||||
return target[prop]
|
||||
}
|
||||
return Object.assign(() => {
|
||||
return nullProxy
|
||||
}, nullProxy)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export function crosscheck(fn) {
|
||||
let engines =
|
||||
env.ENGINE === 'oxide' ? [{ engine: 'Stable' }, { engine: 'Oxide' }] : [{ engine: 'Stable' }]
|
||||
|
||||
describe.each(engines)('$engine', ({ engine }) => {
|
||||
let engines = {
|
||||
oxide: engine === 'Oxide' ? globalThis : nullProxy,
|
||||
stable: engine === 'Stable' ? globalThis : nullProxy,
|
||||
engine: { oxide: engine === 'Oxide', stable: engine === 'Stable' },
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
env.OXIDE = engines.engine.oxide
|
||||
})
|
||||
|
||||
fn(engines)
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,74 +1,76 @@
|
||||
import { html, run, css } from './util/run'
|
||||
import log from '../src/util/log'
|
||||
import { crosscheck, run, html, css } from './util/run'
|
||||
|
||||
let warn
|
||||
crosscheck(() => {
|
||||
let warn
|
||||
|
||||
beforeEach(() => {
|
||||
warn = jest.spyOn(log, 'warn')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
warn.mockClear()
|
||||
})
|
||||
|
||||
test('it warns when there is no content key', async () => {
|
||||
let config = {
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
`
|
||||
|
||||
await run(input, config)
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn.mock.calls.map((x) => x[0])).toEqual(['content-problems'])
|
||||
})
|
||||
|
||||
test('it warns when there is an empty content key', async () => {
|
||||
let config = {
|
||||
content: [],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
`
|
||||
|
||||
await run(input, config)
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn.mock.calls.map((x) => x[0])).toEqual(['content-problems'])
|
||||
})
|
||||
|
||||
test('it warns when there are no utilities generated', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`nothing here matching a utility` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
await run(input, config)
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn.mock.calls.map((x) => x[0])).toEqual(['content-problems'])
|
||||
})
|
||||
|
||||
it('warnings are not thrown when only variant utilities are generated', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="sm:underline"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
await run(input, config)
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(0)
|
||||
beforeEach(() => {
|
||||
warn = jest.spyOn(log, 'warn')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
warn.mockClear()
|
||||
})
|
||||
|
||||
test('it warns when there is no content key', async () => {
|
||||
let config = {
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
`
|
||||
|
||||
await run(input, config)
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn.mock.calls.map((x) => x[0])).toEqual(['content-problems'])
|
||||
})
|
||||
|
||||
test('it warns when there is an empty content key', async () => {
|
||||
let config = {
|
||||
content: [],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind base;
|
||||
`
|
||||
|
||||
await run(input, config)
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn.mock.calls.map((x) => x[0])).toEqual(['content-problems'])
|
||||
})
|
||||
|
||||
test('it warns when there are no utilities generated', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`nothing here matching a utility` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
await run(input, config)
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1)
|
||||
expect(warn.mock.calls.map((x) => x[0])).toEqual(['content-problems'])
|
||||
})
|
||||
|
||||
it('warnings are not thrown when only variant utilities are generated', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="sm:underline"></div>` }],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
await run(input, config)
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,240 +1,243 @@
|
||||
import withAlphaVariable from '../src/util/withAlphaVariable'
|
||||
import { crosscheck } from './util/run'
|
||||
|
||||
test('it adds the right custom property', () => {
|
||||
expect(
|
||||
withAlphaVariable({ color: '#ff0000', property: 'color', variable: '--tw-text-opacity' })
|
||||
).toEqual({
|
||||
'--tw-text-opacity': '1',
|
||||
color: 'rgb(255 0 0 / var(--tw-text-opacity))',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsl(240 100% 50%)',
|
||||
property: 'color',
|
||||
variable: '--tw-text-opacity',
|
||||
crosscheck(() => {
|
||||
test('it adds the right custom property', () => {
|
||||
expect(
|
||||
withAlphaVariable({ color: '#ff0000', property: 'color', variable: '--tw-text-opacity' })
|
||||
).toEqual({
|
||||
'--tw-text-opacity': '1',
|
||||
color: 'rgb(255 0 0 / var(--tw-text-opacity))',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsl(240 100% 50%)',
|
||||
property: 'color',
|
||||
variable: '--tw-text-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-text-opacity': '1',
|
||||
color: 'hsl(240 100% 50% / var(--tw-text-opacity))',
|
||||
})
|
||||
})
|
||||
|
||||
test('it ignores colors that cannot be parsed', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'currentColor',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'currentColor',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(255, 0)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgb(255, 0)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(255)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgb(255)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(255, 0, 0, 255)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgb(255, 0, 0, 255)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(var(--color))',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgb(var(--color))',
|
||||
})
|
||||
})
|
||||
|
||||
test('it ignores colors that already have an alpha channel', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#ff0000ff',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#ff0000ff',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#ff000080',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#ff000080',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#f00a',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#f00a',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#f00f',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#f00f',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgba(255, 255, 255, 1)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgba(255, 255, 255, 1)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgba(255, 255, 255, 0.5)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgba(255, 255, 255, 0.5)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgba(255 255 255 / 0.5)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgba(255 255 255 / 0.5)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsla(240, 100%, 50%, 1)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'hsla(240, 100%, 50%, 1)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsla(240, 100%, 50%, 0.5)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'hsla(240, 100%, 50%, 0.5)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsl(240 100% 50% / 0.5)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'hsl(240 100% 50% / 0.5)',
|
||||
})
|
||||
})
|
||||
|
||||
test('it allows a closure to be passed', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: ({ opacityVariable }) => `rgba(0, 0, 0, var(${opacityVariable}))`,
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: ({ opacityValue }) => `rgba(0, 0, 0, ${opacityValue})`,
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))',
|
||||
})
|
||||
})
|
||||
|
||||
test('it transforms rgb and hsl to space-separated rgb and hsl', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(50, 50, 50)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(50 50 50)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsl(50, 50%, 50%)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsl(50 50% 50%)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))',
|
||||
})
|
||||
})
|
||||
|
||||
test('it transforms named colors to rgb', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'red',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'rgb(255 0 0 / var(--tw-bg-opacity))',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-text-opacity': '1',
|
||||
color: 'hsl(240 100% 50% / var(--tw-text-opacity))',
|
||||
})
|
||||
})
|
||||
|
||||
test('it ignores colors that cannot be parsed', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'currentColor',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'currentColor',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(255, 0)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgb(255, 0)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(255)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgb(255)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(255, 0, 0, 255)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgb(255, 0, 0, 255)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(var(--color))',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgb(var(--color))',
|
||||
})
|
||||
})
|
||||
|
||||
test('it ignores colors that already have an alpha channel', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#ff0000ff',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#ff0000ff',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#ff000080',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#ff000080',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#f00a',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#f00a',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: '#f00f',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': '#f00f',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgba(255, 255, 255, 1)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgba(255, 255, 255, 1)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgba(255, 255, 255, 0.5)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgba(255, 255, 255, 0.5)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgba(255 255 255 / 0.5)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'rgba(255 255 255 / 0.5)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsla(240, 100%, 50%, 1)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'hsla(240, 100%, 50%, 1)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsla(240, 100%, 50%, 0.5)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'hsla(240, 100%, 50%, 0.5)',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsl(240 100% 50% / 0.5)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'background-color': 'hsl(240 100% 50% / 0.5)',
|
||||
})
|
||||
})
|
||||
|
||||
test('it allows a closure to be passed', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: ({ opacityVariable }) => `rgba(0, 0, 0, var(${opacityVariable}))`,
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: ({ opacityValue }) => `rgba(0, 0, 0, ${opacityValue})`,
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))',
|
||||
})
|
||||
})
|
||||
|
||||
test('it transforms rgb and hsl to space-separated rgb and hsl', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(50, 50, 50)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'rgb(50 50 50)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsl(50, 50%, 50%)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))',
|
||||
})
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'hsl(50 50% 50%)',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))',
|
||||
})
|
||||
})
|
||||
|
||||
test('it transforms named colors to rgb', () => {
|
||||
expect(
|
||||
withAlphaVariable({
|
||||
color: 'red',
|
||||
property: 'background-color',
|
||||
variable: '--tw-bg-opacity',
|
||||
})
|
||||
).toEqual({
|
||||
'--tw-bg-opacity': '1',
|
||||
'background-color': 'rgb(255 0 0 / var(--tw-bg-opacity))',
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user