diff --git a/tests/__snapshots__/source-maps.test.js.snap b/tests/__snapshots__/source-maps.test.js.snap
deleted file mode 100644
index b1016e638..000000000
--- a/tests/__snapshots__/source-maps.test.js.snap
+++ /dev/null
@@ -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",
-]
-`;
diff --git a/tests/animations.test.js b/tests/animations.test.js
index a1c580a79..53bb2a8ab 100644
--- a/tests/animations.test.js
+++ b/tests/animations.test.js
@@ -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`
-
-
-
-
+ it('should be possible to use modifiers and arbitrary groups', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .group\/foo:hover .group-hover\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .group:hover .group-hover\:underline {
+ text-decoration-line: underline;
+ }
+ .group\/foo:focus .group-\[\&\:focus\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .group:focus .group-\[\&\:focus\]\:underline {
+ text-decoration-line: underline;
+ }
+ .group\/foo[data-open] .group-\[\&\[data-open\]\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .group[data-open] .group-\[\&\[data-open\]\]\:underline {
+ text-decoration-line: underline;
+ }
+ .group\/foo.in-foo .group-\[\.in-foo\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .group.in-foo .group-\[\.in-foo\]\:underline {
+ text-decoration-line: underline;
+ }
+ .in-foo .group\/foo .group-\[\.in-foo_\&\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .in-foo .group .group-\[\.in-foo_\&\]\:underline {
+ text-decoration-line: underline;
+ }
+ .group\/foo:hover .group-\[\:hover\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .group:hover .group-\[\:hover\]\:underline {
+ text-decoration-line: underline;
+ }
+ .group\/foo[data-open] .group-\[\[data-open\]\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .group[data-open] .group-\[\[data-open\]\]\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+ })
+
+ it('should be possible to use modifiers and arbitrary peers', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
-
-
-
+ let input = css`
+ @tailwind utilities;
+ `
-
-
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .peer\/foo:hover ~ .peer-hover\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .peer:hover ~ .peer-hover\:underline {
+ text-decoration-line: underline;
+ }
+ .peer\/foo:focus ~ .peer-\[\&\:focus\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .peer:focus ~ .peer-\[\&\:focus\]\:underline {
+ text-decoration-line: underline;
+ }
+ .peer\/foo[data-open] ~ .peer-\[\&\[data-open\]\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .peer[data-open] ~ .peer-\[\&\[data-open\]\]\:underline {
+ text-decoration-line: underline;
+ }
+ .peer\/foo.in-foo ~ .peer-\[\.in-foo\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .peer.in-foo ~ .peer-\[\.in-foo\]\:underline {
+ text-decoration-line: underline;
+ }
+ .in-foo .peer\/foo ~ .peer-\[\.in-foo_\&\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .in-foo .peer ~ .peer-\[\.in-foo_\&\]\:underline {
+ text-decoration-line: underline;
+ }
+ .peer\/foo:hover ~ .peer-\[\:hover\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .peer:hover ~ .peer-\[\:hover\]\:underline {
+ text-decoration-line: underline;
+ }
+ .peer\/foo[data-open] ~ .peer-\[\[data-open\]\]\/foo\:underline {
+ text-decoration-line: underline;
+ }
+ .peer[data-open] ~ .peer-\[\[data-open\]\]\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+ })
-
-
-
-
-
+ it('Arbitrary variants are ordered alphabetically', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .group\/foo:hover .group-hover\/foo\:underline {
- text-decoration-line: underline;
- }
- .group:hover .group-hover\:underline {
- text-decoration-line: underline;
- }
- .group\/foo:focus .group-\[\&\:focus\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .group:focus .group-\[\&\:focus\]\:underline {
- text-decoration-line: underline;
- }
- .group\/foo[data-open] .group-\[\&\[data-open\]\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .group[data-open] .group-\[\&\[data-open\]\]\:underline {
- text-decoration-line: underline;
- }
- .group\/foo.in-foo .group-\[\.in-foo\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .group.in-foo .group-\[\.in-foo\]\:underline {
- text-decoration-line: underline;
- }
- .in-foo .group\/foo .group-\[\.in-foo_\&\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .in-foo .group .group-\[\.in-foo_\&\]\:underline {
- text-decoration-line: underline;
- }
- .group\/foo:hover .group-\[\:hover\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .group:hover .group-\[\:hover\]\:underline {
- text-decoration-line: underline;
- }
- .group\/foo[data-open] .group-\[\[data-open\]\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .group[data-open] .group-\[\[data-open\]\]\:underline {
- text-decoration-line: underline;
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .\[\&\:\:a\]\:underline::a {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:b\]\:underline::b {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:c\]\:underline::c {
+ text-decoration-line: underline;
+ }
+ `)
+ })
})
-})
-it('should be possible to use modifiers and arbitrary peers', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
+ it('Arbitrary variants support multiple attribute selectors', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
-
-
+ let input = css`
+ @tailwind utilities;
+ `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .peer\/foo:hover ~ .peer-hover\/foo\:underline {
- text-decoration-line: underline;
- }
- .peer:hover ~ .peer-hover\:underline {
- text-decoration-line: underline;
- }
- .peer\/foo:focus ~ .peer-\[\&\:focus\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .peer:focus ~ .peer-\[\&\:focus\]\:underline {
- text-decoration-line: underline;
- }
- .peer\/foo[data-open] ~ .peer-\[\&\[data-open\]\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .peer[data-open] ~ .peer-\[\&\[data-open\]\]\:underline {
- text-decoration-line: underline;
- }
- .peer\/foo.in-foo ~ .peer-\[\.in-foo\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .peer.in-foo ~ .peer-\[\.in-foo\]\:underline {
- text-decoration-line: underline;
- }
- .in-foo .peer\/foo ~ .peer-\[\.in-foo_\&\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .in-foo .peer ~ .peer-\[\.in-foo_\&\]\:underline {
- text-decoration-line: underline;
- }
- .peer\/foo:hover ~ .peer-\[\:hover\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .peer:hover ~ .peer-\[\:hover\]\:underline {
- text-decoration-line: underline;
- }
- .peer\/foo[data-open] ~ .peer-\[\[data-open\]\]\/foo\:underline {
- text-decoration-line: underline;
- }
- .peer[data-open] ~ .peer-\[\[data-open\]\]\:underline {
- text-decoration-line: underline;
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ [data-foo='bar'][data-baz] .\[\[data-foo\=\'bar\'\]\[data-baz\]_\&\]\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
})
-})
-it('Arbitrary variants are ordered alphabetically', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .\[\&\:\:a\]\:underline::a {
- text-decoration-line: underline;
- }
- .\[\&\:\:b\]\:underline::b {
- text-decoration-line: underline;
- }
- .\[\&\:\:c\]\:underline::c {
- text-decoration-line: underline;
- }
- `)
- })
-})
-
-it('Arbitrary variants support multiple attribute selectors', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- [data-foo='bar'][data-baz] .\[\[data-foo\=\'bar\'\]\[data-baz\]_\&\]\:underline {
- text-decoration-line: underline;
- }
- `)
- })
-})
-
-it('Invalid arbitrary variants selectors should produce nothing instead of failing', () => {
- let config = {
- content: [
- {
- raw: html`
+ it('Invalid arbitrary variants selectors should produce nothing instead of failing', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- }
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css``)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css``)
+ })
})
-})
-it('should output responsive variants + stacked variants in the right order', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+ it('should output responsive variants + stacked variants in the right order', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 1280px) {
- .xl\:p-1 {
- padding: 0.25rem;
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 1280px) {
+ .xl\:p-1 {
+ padding: 0.25rem;
+ }
}
- }
- .\[\&_ul\]\:flex ul {
- display: flex;
- }
- .\[\&_ul\]\:flex-col ul {
- flex-direction: column;
- }
- @media (min-width: 768px) {
- .md\:\[\&_ul\]\:flex-row ul {
- flex-direction: row;
+ .\[\&_ul\]\:flex ul {
+ display: flex;
}
- }
- `)
+ .\[\&_ul\]\:flex-col ul {
+ flex-direction: column;
+ }
+ @media (min-width: 768px) {
+ .md\:\[\&_ul\]\:flex-row ul {
+ flex-direction: row;
+ }
+ }
+ `)
+ })
})
-})
-it('should sort multiple variant fns with normal variants between them', () => {
- /** @type {string[]} */
- let lines = []
+ it('should sort multiple variant fns with normal variants between them', () => {
+ /** @type {string[]} */
+ let lines = []
- for (let a of [1, 2]) {
- for (let b of [2, 1]) {
- for (let c of [1, 2]) {
- for (let d of [2, 1]) {
- for (let e of [1, 2]) {
- lines.push(`
`)
+ for (let a of [1, 2]) {
+ for (let b of [2, 1]) {
+ for (let c of [1, 2]) {
+ for (let d of [2, 1]) {
+ for (let e of [1, 2]) {
+ lines.push(`
`)
+ }
}
}
}
}
- }
- // Fisher-Yates shuffle
- for (let i = lines.length - 1; i > 0; i--) {
- let j = Math.floor(Math.random() * i)
- ;[lines[i], lines[j]] = [lines[j], lines[i]]
- }
+ // Fisher-Yates shuffle
+ for (let i = lines.length - 1; i > 0; i--) {
+ let j = Math.floor(Math.random() * i)
+ ;[lines[i], lines[j]] = [lines[j], lines[i]]
+ }
- let config = {
- content: [
- {
- raw: lines.join('\n'),
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- function ({ addVariant, matchVariant }) {
- addVariant('foo1', '&[data-foo=1]')
- addVariant('foo2', '&[data-foo=2]')
+ let config = {
+ content: [
+ {
+ raw: lines.join('\n'),
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ addVariant, matchVariant }) {
+ addVariant('foo1', '&[data-foo=1]')
+ addVariant('foo2', '&[data-foo=2]')
- matchVariant('bar', (value) => `&[data-bar=${value}]`, {
- sort: (a, b) => b.value - a.value,
- })
+ matchVariant('bar', (value) => `&[data-bar=${value}]`, {
+ sort: (a, b) => b.value - a.value,
+ })
- addVariant('baz1', '&[data-baz=1]')
- addVariant('baz2', '&[data-baz=2]')
+ addVariant('baz1', '&[data-baz=1]')
+ addVariant('baz2', '&[data-baz=2]')
- matchVariant('qux', (value) => `&[data-qux=${value}]`, {
- sort: (a, b) => b.value - a.value,
- })
+ matchVariant('qux', (value) => `&[data-qux=${value}]`, {
+ sort: (a, b) => b.value - a.value,
+ })
- addVariant('fred1', '&[data-fred=1]')
- addVariant('fred2', '&[data-fred=2]')
- },
- ],
- }
+ addVariant('fred1', '&[data-fred=1]')
+ addVariant('fred2', '&[data-fred=2]')
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .fred1\:qux-\[2\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[2\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[2\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[2\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[2\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[2\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[2\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[2\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[1\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[1\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[1\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[1\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[1\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[1\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[1\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred1\:qux-\[1\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='1'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[2\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[2\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[2\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[2\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[2\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[2\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[2\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[2\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[1\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[1\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[1\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[1\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[1\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[1\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[1\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='2'] {
- padding: 0.25rem;
- }
- .fred2\:qux-\[1\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='2'] {
- padding: 0.25rem;
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .fred1\:qux-\[2\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[2\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[2\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[2\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[2\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[2\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[2\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[2\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[1\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[1\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[1\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[1\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[1\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[1\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[1\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred1\:qux-\[1\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='1'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[2\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[2\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[2\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[2\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[2\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[2\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[2\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[2\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[1\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[1\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[1\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[1\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[1\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[1\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[1\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ .fred2\:qux-\[1\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='2'] {
+ padding: 0.25rem;
+ }
+ `)
+ })
})
})
diff --git a/tests/basic-usage.test.js b/tests/basic-usage.test.js
index 7a6aba6a1..f71dc9106 100644
--- a/tests/basic-usage.test.js
+++ b/tests/basic-usage.test.js
@@ -1,135 +1,132 @@
import fs from 'fs'
import path from 'path'
+import { crosscheck, run, html, css, defaults } from './util/run'
-import { env } from '../src/lib/sharedState'
-import { html, run, css, defaults } from './util/run'
-
-let oxideSkip = env.OXIDE ? test.skip : test
-
-test('basic usage', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
{
backdrop-saturate-150
backdrop-sepia
"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ 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) => {
- let expectedPath = env.OXIDE
- ? path.resolve(__dirname, './basic-usage.oxide.test.css')
- : path.resolve(__dirname, './basic-usage.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, './basic-usage.test.css'), 'utf8')
+ )
+ oxide
+ .expect(result.css)
+ .toMatchFormattedCss(
+ fs.readFileSync(path.resolve(__dirname, './basic-usage.oxide.test.css'), 'utf8')
+ )
+ })
})
-})
-test('all plugins are executed that match a candidate', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- colors: {
- green: {
- light: 'green',
+ test('all plugins are executed that match a candidate', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ colors: {
+ green: {
+ light: 'green',
+ },
},
},
- },
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
-
- .bg-green {
- /* Empty on purpose */
+ corePlugins: { preflight: false },
}
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-green-light {
- --tw-bg-opacity: 1;
- background-color: rgb(0 128 0 / var(--tw-bg-opacity));
- }
+ let input = css`
+ @tailwind utilities;
.bg-green {
/* Empty on purpose */
}
- `)
- })
-})
+ `
-test('per-plugin colors with the same key can differ when using a custom colors object', () => {
- let config = {
- content: [
- {
- raw: html`
-
This should be green text on red background.
- `,
- },
- ],
- theme: {
- // colors & theme MUST be plain objects
- // If they're functions here the test passes regardless
- colors: {
- theme: {
- bg: 'red',
- text: 'green',
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-green-light {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 128 0 / var(--tw-bg-opacity));
+ }
+
+ .bg-green {
+ /* Empty on purpose */
+ }
+ `)
+ })
+ })
+
+ test('per-plugin colors with the same key can differ when using a custom colors object', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
This should be green text on red background.
+ `,
},
- },
- extend: {
- textColor: {
+ ],
+ theme: {
+ // colors & theme MUST be plain objects
+ // If they're functions here the test passes regardless
+ colors: {
theme: {
- DEFAULT: 'green',
+ bg: 'red',
+ text: 'green',
},
},
- backgroundColor: {
- theme: {
- DEFAULT: 'red',
+ extend: {
+ textColor: {
+ theme: {
+ DEFAULT: 'green',
+ },
+ },
+ backgroundColor: {
+ theme: {
+ DEFAULT: 'red',
+ },
},
},
},
- },
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-theme {
- --tw-bg-opacity: 1;
- background-color: rgb(255 0 0 / var(--tw-bg-opacity));
- }
- .text-theme {
- --tw-text-opacity: 1;
- color: rgb(0 128 0 / var(--tw-text-opacity));
- }
- `)
- })
-})
-
-test('default ring color can be a function', () => {
- function color(variable) {
- return function ({ opacityVariable, opacityValue }) {
- if (opacityValue !== undefined) {
- return `rgba(${variable}, ${opacityValue})`
- }
- if (opacityVariable !== undefined) {
- return `rgba(${variable}, var(${opacityVariable}, 1))`
- }
- return `rgb(${variable})`
+ corePlugins: { preflight: false },
}
- }
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
+ let input = css`
+ @tailwind utilities;
+ `
- theme: {
- extend: {
- ringColor: {
- DEFAULT: color('var(--red)'),
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-theme {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 0 0 / var(--tw-bg-opacity));
+ }
+ .text-theme {
+ --tw-text-opacity: 1;
+ color: rgb(0 128 0 / var(--tw-text-opacity));
+ }
+ `)
+ })
+ })
+
+ test('default ring color can be a function', () => {
+ function color(variable) {
+ return function ({ opacityVariable, opacityValue }) {
+ if (opacityValue !== undefined) {
+ return `rgba(${variable}, ${opacityValue})`
+ }
+ if (opacityVariable !== undefined) {
+ return `rgba(${variable}, var(${opacityVariable}, 1))`
+ }
+ return `rgb(${variable})`
+ }
+ }
+
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+
+ theme: {
+ extend: {
+ ringColor: {
+ DEFAULT: color('var(--red)'),
+ },
},
},
- },
- plugins: [],
- corePlugins: { preflight: false },
- }
+ plugins: [],
+ 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({ defaultRingColor: 'rgba(var(--red), 0.5)' })}
- .ring {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: 'rgba(var(--red), 0.5)' })}
+ .ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ `)
+ })
})
-})
-it('falsy config values still work', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- inset: {
- 0: 0,
+ it('falsy config values still work', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ inset: {
+ 0: 0,
+ },
},
- },
- plugins: [],
- corePlugins: { preflight: false },
- }
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .inset-0 {
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .inset-0 {
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ }
+ `)
+ })
})
-})
-it('shadows support values without a leading zero', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- boxShadow: {
- one: '0.5rem 0.5rem 0.5rem #0005',
- two: '.5rem .5rem .5rem #0005',
+ it('shadows support values without a leading zero', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ boxShadow: {
+ one: '0.5rem 0.5rem 0.5rem #0005',
+ two: '.5rem .5rem .5rem #0005',
+ },
},
- },
- plugins: [],
- corePlugins: { preflight: false },
- }
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .shadow-one {
- --tw-shadow: 0.5rem 0.5rem 0.5rem #0005;
- --tw-shadow-colored: 0.5rem 0.5rem 0.5rem 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-two {
- --tw-shadow: 0.5rem 0.5rem 0.5rem #0005;
- --tw-shadow-colored: 0.5rem 0.5rem 0.5rem var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .shadow-one {
+ --tw-shadow: 0.5rem 0.5rem 0.5rem #0005;
+ --tw-shadow-colored: 0.5rem 0.5rem 0.5rem 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-two {
+ --tw-shadow: 0.5rem 0.5rem 0.5rem #0005;
+ --tw-shadow-colored: 0.5rem 0.5rem 0.5rem var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ `)
+ })
})
-})
-it('can scan extremely long classes without crashing', () => {
- let val = 'cols-' + '-a'.repeat(65536)
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
+ it('can scan extremely long classes without crashing', () => {
+ let val = 'cols-' + '-a'.repeat(65536)
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css``)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css``)
+ })
})
-})
-it('does not produce duplicate output when seeing variants preceding a wildcard (*)', () => {
- let config = {
- content: [{ raw: html`underline focus:*` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
-
- * {
- color: red;
+ it('does not produce duplicate output when seeing variants preceding a wildcard (*)', () => {
+ let config = {
+ content: [{ raw: html`underline focus:*` }],
+ corePlugins: { preflight: false },
}
- .combined,
- * {
- text-align: center;
- }
-
- @layer base {
- * {
- color: blue;
- }
-
- .combined,
- * {
- color: red;
- }
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- * {
- color: blue;
- }
-
- .combined,
- * {
- color: red;
- }
-
- ${defaults}
-
- .underline {
- text-decoration-line: underline;
- }
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
* {
color: red;
@@ -494,419 +458,468 @@ it('does not produce duplicate output when seeing variants preceding a wildcard
* {
text-align: center;
}
- `)
- })
-})
-it('can parse box shadows with variables', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- boxShadow: {
- lg: 'var(-a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0)',
+ @layer base {
+ * {
+ color: blue;
+ }
+
+ .combined,
+ * {
+ color: red;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ * {
+ color: blue;
+ }
+
+ .combined,
+ * {
+ color: red;
+ }
+
+ ${defaults}
+
+ .underline {
+ text-decoration-line: underline;
+ }
+
+ * {
+ color: red;
+ }
+
+ .combined,
+ * {
+ text-align: center;
+ }
+ `)
+ })
+ })
+
+ it('can parse box shadows with variables', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ boxShadow: {
+ lg: 'var(-a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0)',
+ },
},
- },
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .shadow-lg {
- --tw-shadow: var(-a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0);
- --tw-shadow-colored: 0 35px 60px -15px var(--tw-shadow-color),
- 0 0 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);
- }
- `)
- })
-})
-
-it('should generate styles using :not(.unknown-class) even if `.unknown-class` does not exist', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind components;
-
- @layer components {
- div:not(.unknown-class) {
- color: red;
- }
+ corePlugins: { preflight: false },
}
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- div:not(.unknown-class) {
- color: red;
- }
- `)
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .shadow-lg {
+ --tw-shadow: var(-a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0);
+ --tw-shadow-colored: 0 35px 60px -15px var(--tw-shadow-color),
+ 0 0 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);
+ }
+ `)
+ })
})
-})
-it('supports multiple backgrounds as arbitrary values even if only some are quoted', () => {
- let config = {
- content: [
- {
- raw: html`
`,
+ it('should generate styles using :not(.unknown-class) even if `.unknown-class` does not exist', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind components;
+
+ @layer components {
+ div:not(.unknown-class) {
+ color: red;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ div:not(.unknown-class) {
+ color: red;
+ }
+ `)
+ })
+ })
+
+ it('supports multiple backgrounds as arbitrary values even if only some are quoted', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-\[url\(\'\/images\/one-two-three\.png\'\)\2c
+ linear-gradient\(to_right\2c
+ _\#eeeeee\2c
+ _\#000000\)\] {
+ background-image: url('/images/one-two-three.png'),
+ linear-gradient(to right, #eeeeee, #000000);
+ }
+ `)
+ })
+ })
+
+ it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (1)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: 'rgb(59 130 246 / 0.5)' })}
+ .ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ `)
+ })
+ })
+
+ it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (2)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringOpacity: {
+ DEFAULT: 0.75,
+ },
},
- ],
- corePlugins: { preflight: false },
- }
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-\[url\(\'\/images\/one-two-three\.png\'\)\2c
- linear-gradient\(to_right\2c
- _\#eeeeee\2c
- _\#000000\)\] {
- background-image: url('/images/one-two-three.png'),
- linear-gradient(to right, #eeeeee, #000000);
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: 'rgb(59 130 246 / 0.75)' })}
+ .ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ `)
+ })
})
-})
-it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (1)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: 'rgb(59 130 246 / 0.5)' })}
- .ring {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- `)
- })
-})
-
-it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (2)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringOpacity: {
- DEFAULT: 0.75,
+ it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (1)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringColor: {
+ DEFAULT: '#ff7f7f',
+ },
},
- },
- }
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: 'rgb(59 130 246 / 0.75)' })}
- .ring {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: 'rgb(255 127 127 / 0.5)' })}
+ .ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ `)
+ })
})
-})
-it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (1)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringColor: {
- DEFAULT: '#ff7f7f',
+ it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (2)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringColor: {
+ DEFAULT: '#ff7f7f00',
+ },
},
- },
- }
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: 'rgb(255 127 127 / 0.5)' })}
- .ring {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: 'rgb(255 127 127 / 0.5)' })}
+ .ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ `)
+ })
})
-})
-it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (2)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringColor: {
- DEFAULT: '#ff7f7f00',
+ it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (1)', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#3b82f67f' })}
+ .ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ `)
+ })
+ })
+
+ it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (2)', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringOpacity: {
+ DEFAULT: 0.75,
+ },
},
- },
- }
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: 'rgb(255 127 127 / 0.5)' })}
- .ring {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#3b82f67f' })}
+ .ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ `)
+ })
})
-})
-it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (1)', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#3b82f67f' })}
- .ring {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- `)
- })
-})
-
-it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (2)', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringOpacity: {
- DEFAULT: 0.75,
+ it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (1)', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringColor: {
+ DEFAULT: '#ff7f7f',
+ },
},
- },
- }
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#3b82f67f' })}
- .ring {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#ff7f7f' })}
+ .ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ `)
+ })
})
-})
-it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (1)', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringColor: {
- DEFAULT: '#ff7f7f',
+ it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (2)', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringColor: {
+ DEFAULT: '#ff7f7f00',
+ },
},
- },
- }
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#ff7f7f' })}
- .ring {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#ff7f7f00' })}
+ .ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ `)
+ })
})
-})
-it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (2)', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringColor: {
- DEFAULT: '#ff7f7f00',
+ it('A bare ring-opacity utility is not supported when not using respectDefaultRingColorOpacity', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringOpacity: {
+ DEFAULT: '0.33',
+ },
},
- },
- }
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#ff7f7f00' })}
- .ring {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css``)
+ })
})
-})
-it('A bare ring-opacity utility is not supported when not using respectDefaultRingColorOpacity', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringOpacity: {
- DEFAULT: '0.33',
+ it('A bare ring-opacity utility is supported when using respectDefaultRingColorOpacity', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringOpacity: {
+ DEFAULT: '0.33',
+ },
},
- },
- }
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css``)
- })
-})
-
-it('A bare ring-opacity utility is supported when using respectDefaultRingColorOpacity', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringOpacity: {
- DEFAULT: '0.33',
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .ring-opacity {
- --tw-ring-opacity: 0.33;
- }
- `)
- })
-})
-
-it('Ring color utilities are generated when using respectDefaultRingColorOpacity', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .ring {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- .ring-blue-500 {
- --tw-ring-opacity: 1;
- --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
- }
- `)
- })
-})
-
-oxideSkip('should not crash when group names contain special characters', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [
- {
- raw: '
',
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .group\/\$\{id\}:hover .group-hover\/\$\{id\}\:visible {
- visibility: visible;
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .ring-opacity {
+ --tw-ring-opacity: 0.33;
+ }
+ `)
+ })
+ })
+
+ it('Ring color utilities are generated when using respectDefaultRingColorOpacity', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ .ring-blue-500 {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
+ }
+ `)
+ })
+ })
+
+ oxide.test.todo('should not crash when group names contain special characters')
+ stable.test('should not crash when group names contain special characters', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [
+ {
+ raw: '
',
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .group\/\$\{id\}:hover .group-hover\/\$\{id\}\:visible {
+ visibility: visible;
+ }
+ `)
+ })
})
})
diff --git a/tests/blocklist.test.js b/tests/blocklist.test.js
index acd4be359..b9667101c 100644
--- a/tests/blocklist.test.js
+++ b/tests/blocklist.test.js
@@ -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`
`,
- },
- ],
- 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`
`,
+ },
+ ],
+ 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`
`,
+ },
+ ],
+ 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`
`,
- },
- ],
- 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`
` }],
+ 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`
` }],
- 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`
` }],
- 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`
` }],
+ 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;
+ }
+ `)
+ })
})
})
diff --git a/tests/collapse-adjacent-rules.test.js b/tests/collapse-adjacent-rules.test.js
index c5f98aad5..6cc817de8 100644
--- a/tests/collapse-adjacent-rules.test.js
+++ b/tests/collapse-adjacent-rules.test.js
@@ -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`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {},
- plugins: [
- function ({ addVariant }) {
- addVariant('foo-bar', '@supports (foo: bar)')
- },
- ],
- }
+crosscheck(() => {
+ test('collapse adjacent rules', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ 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');
+ `)
+ })
})
})
diff --git a/tests/collapse-duplicate-declarations.test.js b/tests/collapse-duplicate-declarations.test.js
index db1651864..629f35639 100644
--- a/tests/collapse-duplicate-declarations.test.js
+++ b/tests/collapse-duplicate-declarations.test.js
@@ -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`
` }],
- 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`
` }],
+ 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`
` }],
- 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`
` }],
- 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`
` }],
- 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`
` }],
- 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`
` }],
- 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`
` }],
+ 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`
` }],
+ 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`
` }],
+ 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`
` }],
+ 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`
` }],
+ 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;
+ }
+ `)
+ })
})
})
diff --git a/tests/color-opacity-modifiers.test.js b/tests/color-opacity-modifiers.test.js
index eb708308f..127e5c7b3 100644
--- a/tests/color-opacity-modifiers.test.js
+++ b/tests/color-opacity-modifiers.test.js
@@ -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`
` }],
- }
+crosscheck(() => {
+ test('basic color opacity modifier', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
- 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`
` }],
- theme: {
- extend: {
+ test('colors with slashes are matched first', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ 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`
` }],
+ }
+
+ 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`
` }],
+ }
+
+ 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`
` }],
+ 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`
` }],
+ 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`
` }],
- }
-
- 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`
` }],
- }
-
- 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`
` }],
- 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`
` }],
- theme: {
- colors: {
- blue: ({ opacityValue }) => {
- return `rgba(var(--colors-blue), ${opacityValue})`
+ test('utilities that support any type are supported', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+ `,
+ },
+ ],
+ 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`
-
-
-
- `,
- },
- ],
- 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`
-
-
-
-
-
-
-
- `,
- },
- ],
- 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`
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ 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);
+ }
+ `)
+ })
})
})
diff --git a/tests/color.test.js b/tests/color.test.js
index 8d4f611c0..0c2c7b409 100644
--- a/tests/color.test.js
+++ b/tests/color.test.js
@@ -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)
+ })
})
})
diff --git a/tests/combined-selectors.test.js b/tests/combined-selectors.test.js
index 585bea11e..263be2c72 100644
--- a/tests/combined-selectors.test.js
+++ b/tests/combined-selectors.test.js
@@ -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`
` }],
- 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`
` }],
+ 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`
` }],
+ 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`
` }],
- 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;
+ }
+ `)
+ })
})
})
diff --git a/tests/configurePlugins.test.js b/tests/configurePlugins.test.js
index 490cddc03..367ab223f 100644
--- a/tests/configurePlugins.test.js
+++ b/tests/configurePlugins.test.js
@@ -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'])
+ })
})
diff --git a/tests/containerPlugin.test.js b/tests/containerPlugin.test.js
index 51b7093b9..d92fb5384 100644
--- a/tests/containerPlugin.test.js
+++ b/tests/containerPlugin.test.js
@@ -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`
` }] }
+crosscheck(({}) => {
+ test('options are not required', () => {
+ let config = { content: [{ raw: html`
` }] }
- 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`
` }],
- 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`
` }],
- 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`
` }],
- theme: {
- container: {
- screens: {
- sm: '576px',
- md: '768px',
- 'sm-only': { min: '576px', max: '767px' },
+ test('screens can be passed explicitly', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ 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`
` }],
- 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`
` }],
- 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`
` }],
- 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`
` }],
- 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`
` }],
- 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`
` }],
+ 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`
` }],
+ 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`
` }],
+ 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`
` }],
+ 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`
` }],
+ 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`
` }],
+ 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`
` }],
+ 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;
+ }
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/context-reuse.test.js b/tests/context-reuse.test.js
index 0846975ef..942138205 100644
--- a/tests/context-reuse.test.js
+++ b/tests/context-reuse.test.js
@@ -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;
- }
- `)
+ `)
+ })
})
diff --git a/tests/custom-extractors.test.js b/tests/custom-extractors.test.js
index 54e7aa61a..089084e77 100644
--- a/tests/custom-extractors.test.js
+++ b/tests/custom-extractors.test.js
@@ -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)
+ })
})
})
})
diff --git a/tests/custom-plugins.test.js b/tests/custom-plugins.test.js
index c3e7a3916..29ff0a340 100644
--- a/tests/custom-plugins.test.js
+++ b/tests/custom-plugins.test.js
@@ -1,56 +1,19 @@
import createPlugin from '../src/public/create-plugin'
-import { run, html, css, defaults } from './util/run'
+import { crosscheck, run, html, css, defaults } from './util/run'
-test('plugins can create utilities with object syntax', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addUtilities }) {
- addUtilities({
- '.custom-object-fill': {
- 'object-fit': 'fill',
- },
- '.custom-object-contain': {
- 'object-fit': 'contain',
- },
- '.custom-object-cover': {
- 'object-fit': 'cover',
- },
- })
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-object-fill {
- object-fit: fill;
- }
- .custom-object-contain {
- object-fit: contain;
- }
- .custom-object-cover {
- object-fit: cover;
- }
- `)
- })
-})
-
-test('plugins can create utilities with arrays of objects', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addUtilities }) {
- addUtilities([
- {
+crosscheck(() => {
+ test('plugins can create utilities with object syntax', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
'.custom-object-fill': {
'object-fit': 'fill',
},
@@ -60,414 +23,401 @@ test('plugins can create utilities with arrays of objects', () => {
'.custom-object-cover': {
'object-fit': 'cover',
},
- },
- ])
- },
- ],
- }
+ })
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-object-fill {
- object-fit: fill;
- }
- .custom-object-contain {
- object-fit: contain;
- }
- .custom-object-cover {
- object-fit: cover;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-object-fill {
+ object-fit: fill;
+ }
+ .custom-object-contain {
+ object-fit: contain;
+ }
+ .custom-object-cover {
+ object-fit: cover;
+ }
+ `)
+ })
})
-})
-test('plugins can create utilities with raw PostCSS nodes', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addUtilities, postcss }) {
- addUtilities([
- postcss.rule({ selector: '.custom-object-fill' }).append([
- postcss.decl({
- prop: 'object-fit',
- value: 'fill',
- }),
- ]),
- postcss.rule({ selector: '.custom-object-contain' }).append([
- postcss.decl({
- prop: 'object-fit',
- value: 'contain',
- }),
- ]),
- postcss.rule({ selector: '.custom-object-cover' }).append([
- postcss.decl({
- prop: 'object-fit',
- value: 'cover',
- }),
- ]),
- ])
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-object-fill {
- object-fit: fill;
- }
- .custom-object-contain {
- object-fit: contain;
- }
- .custom-object-cover {
- object-fit: cover;
- }
- `)
- })
-})
-
-test('plugins can create utilities with mixed object styles and PostCSS nodes', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addUtilities, postcss }) {
- addUtilities([
- {
- '.custom-object-fill': {
- objectFit: 'fill',
+ test('plugins can create utilities with arrays of objects', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities([
+ {
+ '.custom-object-fill': {
+ 'object-fit': 'fill',
+ },
+ '.custom-object-contain': {
+ 'object-fit': 'contain',
+ },
+ '.custom-object-cover': {
+ 'object-fit': 'cover',
+ },
},
- },
- postcss.rule({ selector: '.custom-object-contain' }).append([
- postcss.decl({
- prop: 'object-fit',
- value: 'contain',
- }),
- ]),
- postcss.rule({ selector: '.custom-object-cover' }).append([
- postcss.decl({
- prop: 'object-fit',
- value: 'cover',
- }),
- ]),
- ])
- },
- ],
- }
+ ])
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-object-fill {
- object-fit: fill;
- }
- .custom-object-contain {
- object-fit: contain;
- }
- .custom-object-cover {
- object-fit: cover;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-object-fill {
+ object-fit: fill;
+ }
+ .custom-object-contain {
+ object-fit: contain;
+ }
+ .custom-object-cover {
+ object-fit: cover;
+ }
+ `)
+ })
})
-})
-test('plugins can create components with object syntax', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.btn-blue': {
- backgroundColor: 'blue',
- color: 'white',
- padding: '.5rem 1rem',
- borderRadius: '.25rem',
- },
- '.btn-blue:hover': {
- backgroundColor: 'darkblue',
- },
- })
- },
- ],
- }
+ test('plugins can create utilities with raw PostCSS nodes', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities, postcss }) {
+ addUtilities([
+ postcss.rule({ selector: '.custom-object-fill' }).append([
+ postcss.decl({
+ prop: 'object-fit',
+ value: 'fill',
+ }),
+ ]),
+ postcss.rule({ selector: '.custom-object-contain' }).append([
+ postcss.decl({
+ prop: 'object-fit',
+ value: 'contain',
+ }),
+ ]),
+ postcss.rule({ selector: '.custom-object-cover' }).append([
+ postcss.decl({
+ prop: 'object-fit',
+ value: 'cover',
+ }),
+ ]),
+ ])
+ },
+ ],
+ }
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- background-color: blue;
- color: white;
- padding: 0.5rem 1rem;
- border-radius: 0.25rem;
- }
- .btn-blue:hover {
- background-color: darkblue;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-object-fill {
+ object-fit: fill;
+ }
+ .custom-object-contain {
+ object-fit: contain;
+ }
+ .custom-object-cover {
+ object-fit: cover;
+ }
+ `)
+ })
})
-})
-test('plugins can add base styles with object syntax', () => {
- let config = {
- content: [
- {
- raw: html`
![]()
`,
- },
- ],
- plugins: [
- function ({ addBase }) {
- addBase({
- img: {
- maxWidth: '100%',
- },
- button: {
- fontFamily: 'inherit',
- },
- })
- },
- ],
- corePlugins: { preflight: false },
- }
+ test('plugins can create utilities with mixed object styles and PostCSS nodes', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities, postcss }) {
+ addUtilities([
+ {
+ '.custom-object-fill': {
+ objectFit: 'fill',
+ },
+ },
+ postcss.rule({ selector: '.custom-object-contain' }).append([
+ postcss.decl({
+ prop: 'object-fit',
+ value: 'contain',
+ }),
+ ]),
+ postcss.rule({ selector: '.custom-object-cover' }).append([
+ postcss.decl({
+ prop: 'object-fit',
+ value: 'cover',
+ }),
+ ]),
+ ])
+ },
+ ],
+ }
- return run('@tailwind base', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- img {
- max-width: 100%;
- }
- button {
- font-family: inherit;
- }
-
- ${defaults}
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-object-fill {
+ object-fit: fill;
+ }
+ .custom-object-contain {
+ object-fit: contain;
+ }
+ .custom-object-cover {
+ object-fit: cover;
+ }
+ `)
+ })
})
-})
-test('plugins can add base styles with raw PostCSS nodes', () => {
- let config = {
- content: [
- {
- raw: html`
![]()
`,
- },
- ],
- plugins: [
- function ({ addBase, postcss }) {
- addBase([
- postcss.rule({ selector: 'img' }).append([
- postcss.decl({
- prop: 'max-width',
- value: '100%',
- }),
- ]),
- postcss.rule({ selector: 'button' }).append([
- postcss.decl({
- prop: 'font-family',
- value: 'inherit',
- }),
- ]),
- ])
- },
- ],
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind base', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- img {
- max-width: 100%;
- }
- button {
- font-family: inherit;
- }
- ${defaults}
- `)
- })
-})
-
-test('plugins can create components with raw PostCSS nodes', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addComponents, postcss }) {
- addComponents([
- postcss.rule({ selector: '.btn-blue' }).append([
- postcss.decl({
- prop: 'background-color',
- value: 'blue',
- }),
- postcss.decl({
- prop: 'color',
- value: 'white',
- }),
- postcss.decl({
- prop: 'padding',
- value: '.5rem 1rem',
- }),
- postcss.decl({
- prop: 'border-radius',
- value: '.25rem',
- }),
- ]),
- postcss.rule({ selector: '.btn-blue:hover' }).append([
- postcss.decl({
- prop: 'background-color',
- value: 'darkblue',
- }),
- ]),
- ])
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- background-color: blue;
- color: white;
- padding: 0.5rem 1rem;
- border-radius: 0.25rem;
- }
- .btn-blue:hover {
- background-color: darkblue;
- }
- `)
- })
-})
-
-test('plugins can create components with mixed object styles and raw PostCSS nodes', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addComponents, postcss }) {
- addComponents([
- postcss.rule({ selector: '.btn-blue' }).append([
- postcss.decl({
- prop: 'background-color',
- value: 'blue',
- }),
- postcss.decl({
- prop: 'color',
- value: 'white',
- }),
- postcss.decl({
- prop: 'padding',
- value: '.5rem 1rem',
- }),
- postcss.decl({
- prop: 'border-radius',
- value: '.25rem',
- }),
- ]),
- {
+ test('plugins can create components with object syntax', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents({
+ '.btn-blue': {
+ backgroundColor: 'blue',
+ color: 'white',
+ padding: '.5rem 1rem',
+ borderRadius: '.25rem',
+ },
'.btn-blue:hover': {
backgroundColor: 'darkblue',
},
- },
- ])
- },
- ],
- }
+ })
+ },
+ ],
+ }
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- background-color: blue;
- color: white;
- padding: 0.5rem 1rem;
- border-radius: 0.25rem;
- }
- .btn-blue:hover {
- background-color: darkblue;
- }
- `)
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ background-color: blue;
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ }
+ .btn-blue:hover {
+ background-color: darkblue;
+ }
+ `)
+ })
})
-})
-test('plugins can create components with media queries with object syntax', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.custom-container': {
- width: '100%',
- },
- '@media (min-width: 100px)': {
- '.custom-container': {
- maxWidth: '100px',
+ test('plugins can add base styles with object syntax', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
![]()
`,
+ },
+ ],
+ plugins: [
+ function ({ addBase }) {
+ addBase({
+ img: {
+ maxWidth: '100%',
},
- },
- '@media (min-width: 200px)': {
- '.custom-container': {
- maxWidth: '200px',
+ button: {
+ fontFamily: 'inherit',
},
- },
- '@media (min-width: 300px)': {
- '.custom-container': {
- maxWidth: '300px',
- },
- },
- })
- },
- ],
- }
+ })
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-container {
- width: 100%;
- }
- @media (min-width: 100px) {
- .custom-container {
- max-width: 100px;
+ return run('@tailwind base', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ img {
+ max-width: 100%;
}
- }
- @media (min-width: 200px) {
- .custom-container {
- max-width: 200px;
+ button {
+ font-family: inherit;
}
- }
- @media (min-width: 300px) {
- .custom-container {
- max-width: 300px;
- }
- }
- `)
+
+ ${defaults}
+ `)
+ })
})
-})
-test('media queries can be defined multiple times using objects-in-array syntax', () => {
- let config = {
- content: [
- {
- raw: html`
-
`,
- },
- ],
- plugins: [
- function ({ addComponents }) {
- addComponents([
- {
+ test('plugins can add base styles with raw PostCSS nodes', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
![]()
`,
+ },
+ ],
+ plugins: [
+ function ({ addBase, postcss }) {
+ addBase([
+ postcss.rule({ selector: 'img' }).append([
+ postcss.decl({
+ prop: 'max-width',
+ value: '100%',
+ }),
+ ]),
+ postcss.rule({ selector: 'button' }).append([
+ postcss.decl({
+ prop: 'font-family',
+ value: 'inherit',
+ }),
+ ]),
+ ])
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ img {
+ max-width: 100%;
+ }
+ button {
+ font-family: inherit;
+ }
+ ${defaults}
+ `)
+ })
+ })
+
+ test('plugins can create components with raw PostCSS nodes', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addComponents, postcss }) {
+ addComponents([
+ postcss.rule({ selector: '.btn-blue' }).append([
+ postcss.decl({
+ prop: 'background-color',
+ value: 'blue',
+ }),
+ postcss.decl({
+ prop: 'color',
+ value: 'white',
+ }),
+ postcss.decl({
+ prop: 'padding',
+ value: '.5rem 1rem',
+ }),
+ postcss.decl({
+ prop: 'border-radius',
+ value: '.25rem',
+ }),
+ ]),
+ postcss.rule({ selector: '.btn-blue:hover' }).append([
+ postcss.decl({
+ prop: 'background-color',
+ value: 'darkblue',
+ }),
+ ]),
+ ])
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ background-color: blue;
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ }
+ .btn-blue:hover {
+ background-color: darkblue;
+ }
+ `)
+ })
+ })
+
+ test('plugins can create components with mixed object styles and raw PostCSS nodes', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addComponents, postcss }) {
+ addComponents([
+ postcss.rule({ selector: '.btn-blue' }).append([
+ postcss.decl({
+ prop: 'background-color',
+ value: 'blue',
+ }),
+ postcss.decl({
+ prop: 'color',
+ value: 'white',
+ }),
+ postcss.decl({
+ prop: 'padding',
+ value: '.5rem 1rem',
+ }),
+ postcss.decl({
+ prop: 'border-radius',
+ value: '.25rem',
+ }),
+ ]),
+ {
+ '.btn-blue:hover': {
+ backgroundColor: 'darkblue',
+ },
+ },
+ ])
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ background-color: blue;
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ }
+ .btn-blue:hover {
+ background-color: darkblue;
+ }
+ `)
+ })
+ })
+
+ test('plugins can create components with media queries with object syntax', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents({
'.custom-container': {
width: '100%',
},
@@ -476,759 +426,873 @@ test('media queries can be defined multiple times using objects-in-array syntax'
maxWidth: '100px',
},
},
- },
- {
- '.btn': {
- padding: '1rem .5rem',
- display: 'block',
- },
- '@media (min-width: 100px)': {
- '.btn': {
- display: 'inline-block',
+ '@media (min-width: 200px)': {
+ '.custom-container': {
+ maxWidth: '200px',
},
},
- },
- ])
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-container {
- width: 100%;
- }
- @media (min-width: 100px) {
- .custom-container {
- max-width: 100px;
- }
- }
- .btn {
- padding: 1rem 0.5rem;
- display: block;
- }
- @media (min-width: 100px) {
- .btn {
- display: inline-block;
- }
- }
- `)
- })
-})
-
-test('plugins can create nested rules', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.btn-blue': {
- backgroundColor: 'blue',
- color: 'white',
- padding: '.5rem 1rem',
- borderRadius: '.25rem',
- '&:hover': {
- backgroundColor: 'darkblue',
- },
- '@media (min-width: 500px)': {
- '&:hover': {
- backgroundColor: 'orange',
+ '@media (min-width: 300px)': {
+ '.custom-container': {
+ maxWidth: '300px',
},
},
- '> a': {
- color: 'red',
- },
- 'h1 &': {
- color: 'purple',
- },
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- background-color: blue;
- color: white;
- padding: 0.5rem 1rem;
- border-radius: 0.25rem;
- }
- .btn-blue:hover {
- background-color: darkblue;
- }
- @media (min-width: 500px) {
- .btn-blue:hover {
- background-color: orange;
- }
- }
- .btn-blue > a {
- color: red;
- }
- h1 .btn-blue {
- color: purple;
- }
- `)
- })
-})
-
-test('plugins can create rules with escaped selectors', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ e, addUtilities }) {
- addUtilities({
- [`.${e('custom-top-1/4')}`]: {
- top: '25%',
- },
- })
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-top-1\/4 {
- top: 25%;
- }
- `)
- })
-})
-
-test('plugins can access the current config', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents, config }) {
- let containerClasses = [
- {
- '.custom-container': {
- width: '100%',
- },
- },
- ]
-
- for (let maxWidth of Object.values(config('theme.customScreens'))) {
- containerClasses.push({
- [`@media (min-width: ${maxWidth})`]: {
- '.custom-container': { maxWidth },
- },
})
- }
+ },
+ ],
+ }
- addComponents(containerClasses)
- },
- ],
- theme: {
- customScreens: {
- sm: '576px',
- md: '768px',
- lg: '992px',
- xl: '1200px',
- },
- },
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-container {
- width: 100%;
- }
- @media (min-width: 576px) {
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
.custom-container {
- max-width: 576px;
+ width: 100%;
}
- }
- @media (min-width: 768px) {
+ @media (min-width: 100px) {
+ .custom-container {
+ max-width: 100px;
+ }
+ }
+ @media (min-width: 200px) {
+ .custom-container {
+ max-width: 200px;
+ }
+ }
+ @media (min-width: 300px) {
+ .custom-container {
+ max-width: 300px;
+ }
+ }
+ `)
+ })
+ })
+
+ test('media queries can be defined multiple times using objects-in-array syntax', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
`,
+ },
+ ],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents([
+ {
+ '.custom-container': {
+ width: '100%',
+ },
+ '@media (min-width: 100px)': {
+ '.custom-container': {
+ maxWidth: '100px',
+ },
+ },
+ },
+ {
+ '.btn': {
+ padding: '1rem .5rem',
+ display: 'block',
+ },
+ '@media (min-width: 100px)': {
+ '.btn': {
+ display: 'inline-block',
+ },
+ },
+ },
+ ])
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
.custom-container {
- max-width: 768px;
+ width: 100%;
}
- }
- @media (min-width: 992px) {
+ @media (min-width: 100px) {
+ .custom-container {
+ max-width: 100px;
+ }
+ }
+ .btn {
+ padding: 1rem 0.5rem;
+ display: block;
+ }
+ @media (min-width: 100px) {
+ .btn {
+ display: inline-block;
+ }
+ }
+ `)
+ })
+ })
+
+ test('plugins can create nested rules', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents({
+ '.btn-blue': {
+ backgroundColor: 'blue',
+ color: 'white',
+ padding: '.5rem 1rem',
+ borderRadius: '.25rem',
+ '&:hover': {
+ backgroundColor: 'darkblue',
+ },
+ '@media (min-width: 500px)': {
+ '&:hover': {
+ backgroundColor: 'orange',
+ },
+ },
+ '> a': {
+ color: 'red',
+ },
+ 'h1 &': {
+ color: 'purple',
+ },
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ background-color: blue;
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ }
+ .btn-blue:hover {
+ background-color: darkblue;
+ }
+ @media (min-width: 500px) {
+ .btn-blue:hover {
+ background-color: orange;
+ }
+ }
+ .btn-blue > a {
+ color: red;
+ }
+ h1 .btn-blue {
+ color: purple;
+ }
+ `)
+ })
+ })
+
+ test('plugins can create rules with escaped selectors', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ e, addUtilities }) {
+ addUtilities({
+ [`.${e('custom-top-1/4')}`]: {
+ top: '25%',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-top-1\/4 {
+ top: 25%;
+ }
+ `)
+ })
+ })
+
+ test('plugins can access the current config', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents, config }) {
+ let containerClasses = [
+ {
+ '.custom-container': {
+ width: '100%',
+ },
+ },
+ ]
+
+ for (let maxWidth of Object.values(config('theme.customScreens'))) {
+ containerClasses.push({
+ [`@media (min-width: ${maxWidth})`]: {
+ '.custom-container': { maxWidth },
+ },
+ })
+ }
+
+ addComponents(containerClasses)
+ },
+ ],
+ theme: {
+ customScreens: {
+ sm: '576px',
+ md: '768px',
+ lg: '992px',
+ xl: '1200px',
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
.custom-container {
- max-width: 992px;
+ width: 100%;
}
- }
- @media (min-width: 1200px) {
- .custom-container {
- max-width: 1200px;
+ @media (min-width: 576px) {
+ .custom-container {
+ max-width: 576px;
+ }
}
- }
- `)
+ @media (min-width: 768px) {
+ .custom-container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 992px) {
+ .custom-container {
+ max-width: 992px;
+ }
+ }
+ @media (min-width: 1200px) {
+ .custom-container {
+ max-width: 1200px;
+ }
+ }
+ `)
+ })
})
-})
-test('plugins can check if corePlugins are enabled', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities, corePlugins }) {
- addUtilities({
- '.test': {
- 'text-color': corePlugins('textColor') ? 'true' : 'false',
- opacity: corePlugins('opacity') ? 'true' : 'false',
- },
- })
- },
- ],
- corePlugins: { textColor: false },
- }
+ test('plugins can check if corePlugins are enabled', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities, corePlugins }) {
+ addUtilities({
+ '.test': {
+ 'text-color': corePlugins('textColor') ? 'true' : 'false',
+ opacity: corePlugins('opacity') ? 'true' : 'false',
+ },
+ })
+ },
+ ],
+ corePlugins: { textColor: false },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .test {
- text-color: false;
- opacity: true;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test {
+ text-color: false;
+ opacity: true;
+ }
+ `)
+ })
})
-})
-test('plugins can check if corePlugins are enabled when using array white-listing', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities, corePlugins }) {
- addUtilities({
- '.test': {
- 'text-color': corePlugins('textColor') ? 'true' : 'false',
- opacity: corePlugins('opacity') ? 'true' : 'false',
- },
- })
- },
- ],
- corePlugins: ['textColor'],
- }
+ test('plugins can check if corePlugins are enabled when using array white-listing', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities, corePlugins }) {
+ addUtilities({
+ '.test': {
+ 'text-color': corePlugins('textColor') ? 'true' : 'false',
+ opacity: corePlugins('opacity') ? 'true' : 'false',
+ },
+ })
+ },
+ ],
+ corePlugins: ['textColor'],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .test {
- text-color: true;
- opacity: false;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test {
+ text-color: true;
+ opacity: false;
+ }
+ `)
+ })
})
-})
-test('plugins can provide fallbacks to keys missing from the config', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents, config }) {
- addComponents({
- '.btn': {
- borderRadius: config('borderRadius.default', '.25rem'),
- },
- })
+ test('plugins can provide fallbacks to keys missing from the config', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents, config }) {
+ addComponents({
+ '.btn': {
+ borderRadius: config('borderRadius.default', '.25rem'),
+ },
+ })
+ },
+ ],
+ theme: {
+ borderRadius: {
+ 1: '1px',
+ 2: '2px',
+ 4: '4px',
+ 8: '8px',
+ },
},
- ],
- theme: {
- borderRadius: {
- 1: '1px',
- 2: '2px',
- 4: '4px',
- 8: '8px',
- },
- },
- }
+ }
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn {
- border-radius: 0.25rem;
- }
- `)
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn {
+ border-radius: 0.25rem;
+ }
+ `)
+ })
})
-})
-test('plugins can add multiple sets of utilities and components', () => {
- let config = {
- content: [
- {
- raw: html`
-
`,
- },
- ],
- plugins: [
- function ({ addUtilities, addComponents }) {
- addComponents({
- '.card': {
- padding: '1rem',
- borderRadius: '.25rem',
- },
- })
+ test('plugins can add multiple sets of utilities and components', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities, addComponents }) {
+ addComponents({
+ '.card': {
+ padding: '1rem',
+ borderRadius: '.25rem',
+ },
+ })
- addUtilities({
- '.custom-skew-12deg': {
- transform: 'skewY(-12deg)',
- },
- })
+ addUtilities({
+ '.custom-skew-12deg': {
+ transform: 'skewY(-12deg)',
+ },
+ })
- addComponents({
- '.btn': {
- padding: '1rem .5rem',
- display: 'inline-block',
- },
- })
+ addComponents({
+ '.btn': {
+ padding: '1rem .5rem',
+ display: 'inline-block',
+ },
+ })
- addUtilities({
- '.custom-border-collapse': {
- borderCollapse: 'collapse',
- },
- })
- },
- ],
- }
+ addUtilities({
+ '.custom-border-collapse': {
+ borderCollapse: 'collapse',
+ },
+ })
+ },
+ ],
+ }
- return run('@tailwind components; @tailwind utilities;', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .card {
- padding: 1rem;
- border-radius: 0.25rem;
- }
- .btn {
- padding: 1rem 0.5rem;
- display: inline-block;
- }
- .custom-skew-12deg {
- transform: skewY(-12deg);
- }
- .custom-border-collapse {
- border-collapse: collapse;
- }
- `)
+ return run('@tailwind components; @tailwind utilities;', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .card {
+ padding: 1rem;
+ border-radius: 0.25rem;
+ }
+ .btn {
+ padding: 1rem 0.5rem;
+ display: inline-block;
+ }
+ .custom-skew-12deg {
+ transform: skewY(-12deg);
+ }
+ .custom-border-collapse {
+ border-collapse: collapse;
+ }
+ `)
+ })
})
-})
-test('plugins respect prefix and important options by default when adding utilities', () => {
- let config = {
- prefix: 'tw-',
- important: true,
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities({
- '.custom-rotate-90': {
- transform: 'rotate(90deg)',
- },
- })
- },
- ],
- }
+ test('plugins respect prefix and important options by default when adding utilities', () => {
+ let config = {
+ prefix: 'tw-',
+ important: true,
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.custom-rotate-90': {
+ transform: 'rotate(90deg)',
+ },
+ })
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-custom-rotate-90 {
- transform: rotate(90deg) !important;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-custom-rotate-90 {
+ transform: rotate(90deg) !important;
+ }
+ `)
+ })
})
-})
-test('when important is a selector it is used to scope utilities instead of adding !important', () => {
- let config = {
- prefix: 'tw-',
- important: '#app',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities({
- '.custom-rotate-90': {
- transform: 'rotate(90deg)',
- },
- })
- },
- ],
- }
+ test('when important is a selector it is used to scope utilities instead of adding !important', () => {
+ let config = {
+ prefix: 'tw-',
+ important: '#app',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.custom-rotate-90': {
+ transform: 'rotate(90deg)',
+ },
+ })
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- #app .tw-custom-rotate-90 {
- transform: rotate(90deg);
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ #app .tw-custom-rotate-90 {
+ transform: rotate(90deg);
+ }
+ `)
+ })
})
-})
-test('when important is a selector it scopes all selectors in a rule, even though defining utilities like this is stupid', () => {
- let config = {
- important: '#app',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities({
- '.custom-rotate-90, .custom-rotate-1\\/4': {
- transform: 'rotate(90deg)',
- },
- })
- },
- ],
- }
+ test('when important is a selector it scopes all selectors in a rule, even though defining utilities like this is stupid', () => {
+ let config = {
+ important: '#app',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.custom-rotate-90, .custom-rotate-1\\/4': {
+ transform: 'rotate(90deg)',
+ },
+ })
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- #app .custom-rotate-90,
- #app .custom-rotate-1\/4 {
- transform: rotate(90deg);
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ #app .custom-rotate-90,
+ #app .custom-rotate-1\/4 {
+ transform: rotate(90deg);
+ }
+ `)
+ })
})
-})
-test('important utilities are not made double important when important option is used', () => {
- let config = {
- important: true,
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities({
- '.custom-rotate-90': {
- transform: 'rotate(90deg) !important',
- },
- })
- },
- ],
- }
+ test('important utilities are not made double important when important option is used', () => {
+ let config = {
+ important: true,
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.custom-rotate-90': {
+ transform: 'rotate(90deg) !important',
+ },
+ })
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-rotate-90 {
- transform: rotate(90deg) !important;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-rotate-90 {
+ transform: rotate(90deg) !important;
+ }
+ `)
+ })
})
-})
-test("component declarations respect the 'prefix' option by default", () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.btn-blue': {
- backgroundColor: 'blue',
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-btn-blue {
- background-color: blue;
- }
- `)
- })
-})
-
-test('all selectors in a rule are prefixed', () => {
- let config = {
- prefix: 'tw-',
- content: [
- {
- raw: html`
-
`,
- },
- ],
- plugins: [
- function ({ addUtilities, addComponents }) {
- addUtilities({
- '.custom-rotate-90, .custom-rotate-1\\/4': {
- transform: 'rotate(90deg)',
- },
- })
- addComponents({
- '.btn-blue, .btn-red': {
- padding: '10px',
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-btn-blue,
- .tw-btn-red {
- padding: 10px;
- }
- `)
- })
-})
-
-test("component declarations can optionally ignore 'prefix' option", () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents }) {
- addComponents(
- {
+ test("component declarations respect the 'prefix' option by default", () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents({
'.btn-blue': {
backgroundColor: 'blue',
},
- },
- { respectPrefix: false }
- )
- },
- ],
- }
+ })
+ },
+ ],
+ }
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- background-color: blue;
- }
- `)
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-btn-blue {
+ background-color: blue;
+ }
+ `)
+ })
})
-})
-test("component declarations are not affected by the 'important' option", () => {
- let config = {
- important: true,
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.btn-blue': {
- backgroundColor: 'blue',
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- background-color: blue;
- }
- `)
- })
-})
-
-test("plugins can apply the user's chosen prefix to components manually", () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents, prefix }) {
- addComponents(
- {
- [prefix('.btn-blue')]: {
- backgroundColor: 'blue',
- },
- },
- { respectPrefix: false }
- )
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-btn-blue {
- background-color: blue;
- }
- `)
- })
-})
-
-test('prefix can optionally be ignored for utilities', () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities(
- {
- '.custom-rotate-90': {
- transform: 'rotate(90deg)',
- },
- },
- { respectPrefix: false }
- )
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-rotate-90 {
- transform: rotate(90deg);
- }
- `)
- })
-})
-
-test('important can optionally be ignored for utilities', () => {
- let config = {
- important: true,
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities(
- {
- '.custom-rotate-90': {
- transform: 'rotate(90deg)',
- },
- },
- { respectImportant: false }
- )
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-rotate-90 {
- transform: rotate(90deg);
- }
- `)
- })
-})
-
-test('prefix will prefix all classes in a selector', () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents, prefix }) {
- addComponents(
- {
- [prefix('.btn-blue .w-1\\/4 > h1.text-xl + a .bar')]: {
- backgroundColor: 'blue',
- },
- },
- { respectPrefix: false }
- )
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-btn-blue .tw-w-1\/4 > h1.tw-text-xl + a .tw-bar {
- background-color: blue;
- }
- `)
- })
-})
-
-test('plugins can be provided as an object with a handler function', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- {
- handler({ addUtilities }) {
+ test('all selectors in a rule are prefixed', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [
+ {
+ raw: html`
+
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities, addComponents }) {
addUtilities({
- '.custom-object-fill': {
- 'object-fit': 'fill',
+ '.custom-rotate-90, .custom-rotate-1\\/4': {
+ transform: 'rotate(90deg)',
},
- '.custom-object-contain': {
- 'object-fit': 'contain',
- },
- '.custom-object-cover': {
- 'object-fit': 'cover',
+ })
+ addComponents({
+ '.btn-blue, .btn-red': {
+ padding: '10px',
},
})
},
- },
- ],
- }
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-object-fill {
- object-fit: fill;
- }
- .custom-object-contain {
- object-fit: contain;
- }
- .custom-object-cover {
- object-fit: cover;
- }
- `)
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-btn-blue,
+ .tw-btn-red {
+ padding: 10px;
+ }
+ `)
+ })
})
-})
-test('plugins can provide a config but no handler', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- {
- config: {
- prefix: 'tw-',
+ test("component declarations can optionally ignore 'prefix' option", () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents(
+ {
+ '.btn-blue': {
+ backgroundColor: 'blue',
+ },
+ },
+ { respectPrefix: false }
+ )
},
- },
- {
- handler({ addUtilities }) {
- addUtilities({
- '.custom-object-fill': {
- 'object-fit': 'fill',
- },
- '.custom-object-contain': {
- 'object-fit': 'contain',
- },
- '.custom-object-cover': {
- 'object-fit': 'cover',
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ background-color: blue;
+ }
+ `)
+ })
+ })
+
+ test("component declarations are not affected by the 'important' option", () => {
+ let config = {
+ important: true,
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents({
+ '.btn-blue': {
+ backgroundColor: 'blue',
},
})
},
- },
- ],
- }
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-custom-object-fill {
- object-fit: fill;
- }
- .tw-custom-object-contain {
- object-fit: contain;
- }
- .tw-custom-object-cover {
- object-fit: cover;
- }
- `)
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ background-color: blue;
+ }
+ `)
+ })
})
-})
-test('plugins can be created using the `createPlugin` function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- screens: {
- sm: '400px',
+ test("plugins can apply the user's chosen prefix to components manually", () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents, prefix }) {
+ addComponents(
+ {
+ [prefix('.btn-blue')]: {
+ backgroundColor: 'blue',
+ },
+ },
+ { respectPrefix: false }
+ )
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-btn-blue {
+ background-color: blue;
+ }
+ `)
+ })
+ })
+
+ test('prefix can optionally be ignored for utilities', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities(
+ {
+ '.custom-rotate-90': {
+ transform: 'rotate(90deg)',
+ },
+ },
+ { respectPrefix: false }
+ )
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-rotate-90 {
+ transform: rotate(90deg);
+ }
+ `)
+ })
+ })
+
+ test('important can optionally be ignored for utilities', () => {
+ let config = {
+ important: true,
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities(
+ {
+ '.custom-rotate-90': {
+ transform: 'rotate(90deg)',
+ },
+ },
+ { respectImportant: false }
+ )
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-rotate-90 {
+ transform: rotate(90deg);
+ }
+ `)
+ })
+ })
+
+ test('prefix will prefix all classes in a selector', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents, prefix }) {
+ addComponents(
+ {
+ [prefix('.btn-blue .w-1\\/4 > h1.text-xl + a .bar')]: {
+ backgroundColor: 'blue',
+ },
+ },
+ { respectPrefix: false }
+ )
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-btn-blue .tw-w-1\/4 > h1.tw-text-xl + a .tw-bar {
+ background-color: blue;
+ }
+ `)
+ })
+ })
+
+ test('plugins can be provided as an object with a handler function', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ {
+ handler({ addUtilities }) {
+ addUtilities({
+ '.custom-object-fill': {
+ 'object-fit': 'fill',
+ },
+ '.custom-object-contain': {
+ 'object-fit': 'contain',
+ },
+ '.custom-object-cover': {
+ 'object-fit': 'cover',
+ },
+ })
+ },
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-object-fill {
+ object-fit: fill;
+ }
+ .custom-object-contain {
+ object-fit: contain;
+ }
+ .custom-object-cover {
+ object-fit: cover;
+ }
+ `)
+ })
+ })
+
+ test('plugins can provide a config but no handler', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ {
+ config: {
+ prefix: 'tw-',
+ },
+ },
+ {
+ handler({ addUtilities }) {
+ addUtilities({
+ '.custom-object-fill': {
+ 'object-fit': 'fill',
+ },
+ '.custom-object-contain': {
+ 'object-fit': 'contain',
+ },
+ '.custom-object-cover': {
+ 'object-fit': 'cover',
+ },
+ })
+ },
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-custom-object-fill {
+ object-fit: fill;
+ }
+ .tw-custom-object-contain {
+ object-fit: contain;
+ }
+ .tw-custom-object-cover {
+ object-fit: cover;
+ }
+ `)
+ })
+ })
+
+ test('plugins can be created using the `createPlugin` function', () => {
+ let config = {
+ content: [
+ { raw: html`
` },
+ ],
+ corePlugins: [],
+ theme: {
+ screens: {
+ sm: '400px',
+ },
},
- },
- plugins: [
- createPlugin(
- function ({ addUtilities, theme }) {
+ plugins: [
+ createPlugin(
+ function ({ addUtilities, theme }) {
+ addUtilities(
+ Object.fromEntries(
+ Object.entries(theme('testPlugin')).map(([k, v]) => [
+ `.test-${k}`,
+ { testProperty: v },
+ ])
+ )
+ )
+ },
+ {
+ theme: {
+ testPlugin: {
+ sm: '1rem',
+ md: '2rem',
+ lg: '3rem',
+ },
+ },
+ }
+ ),
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-sm {
+ test-property: 1rem;
+ }
+ .test-md {
+ test-property: 2rem;
+ }
+ .test-lg {
+ test-property: 3rem;
+ }
+ .hover\:test-sm:hover {
+ test-property: 1rem;
+ }
+ @media (min-width: 400px) {
+ .sm\:test-sm {
+ test-property: 1rem;
+ }
+ }
+ `)
+ })
+ })
+
+ test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => {
+ let plugin = createPlugin.withOptions(
+ function ({ className }) {
+ return function ({ addUtilities, theme }) {
addUtilities(
Object.fromEntries(
Object.entries(theme('testPlugin')).map(([k, v]) => [
- `.test-${k}`,
+ `.${className}-${k}`,
{ testProperty: v },
])
)
)
- },
- {
+ }
+ },
+ function () {
+ return {
theme: {
testPlugin: {
sm: '1rem',
@@ -1237,153 +1301,171 @@ test('plugins can be created using the `createPlugin` function', () => {
},
},
}
- ),
- ],
- }
+ }
+ )
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .test-sm {
- test-property: 1rem;
- }
- .test-md {
- test-property: 2rem;
- }
- .test-lg {
- test-property: 3rem;
- }
- .hover\:test-sm:hover {
- test-property: 1rem;
- }
- @media (min-width: 400px) {
- .sm\:test-sm {
- test-property: 1rem;
- }
- }
- `)
- })
-})
-
-test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => {
- let plugin = createPlugin.withOptions(
- function ({ className }) {
- return function ({ addUtilities, theme }) {
- addUtilities(
- Object.fromEntries(
- Object.entries(theme('testPlugin')).map(([k, v]) => [
- `.${className}-${k}`,
- { testProperty: v },
- ])
- )
- )
- }
- },
- function () {
- return {
- theme: {
- testPlugin: {
- sm: '1rem',
- md: '2rem',
- lg: '3rem',
- },
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: [],
+ theme: {
+ screens: {
+ sm: '400px',
},
- }
- }
- )
-
- let config = {
- content: [
- { raw: html`
` },
- ],
- corePlugins: [],
- theme: {
- screens: {
- sm: '400px',
},
- },
- plugins: [plugin({ className: 'banana' })],
- }
+ plugins: [plugin({ className: 'banana' })],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .banana-sm {
- test-property: 1rem;
- }
- .banana-md {
- test-property: 2rem;
- }
- .banana-lg {
- test-property: 3rem;
- }
- .hover\:banana-sm:hover {
- test-property: 1rem;
- }
- @media (min-width: 400px) {
- .sm\:banana-sm {
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .banana-sm {
test-property: 1rem;
}
- }
- `)
- })
-})
-
-test('plugins should cache correctly', () => {
- let plugin = createPlugin.withOptions(({ className = 'banana' } = {}) => ({ addComponents }) => {
- addComponents({ [`.${className}`]: { position: 'absolute' } })
- })
-
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- screens: {
- sm: '400px',
- },
- },
- }
-
- function internalRun(options = {}) {
- return run('@tailwind components', {
- ...config,
- plugins: [plugin(options)],
+ .banana-md {
+ test-property: 2rem;
+ }
+ .banana-lg {
+ test-property: 3rem;
+ }
+ .hover\:banana-sm:hover {
+ test-property: 1rem;
+ }
+ @media (min-width: 400px) {
+ .sm\:banana-sm {
+ test-property: 1rem;
+ }
+ }
+ `)
})
- }
+ })
- return Promise.all([internalRun(), internalRun({ className: 'apple' })]).then(
- ([result1, result2]) => {
- let expected1 = css`
- .banana {
- position: absolute;
+ test('plugins should cache correctly', () => {
+ let plugin = createPlugin.withOptions(
+ ({ className = 'banana' } = {}) =>
+ ({ addComponents }) => {
+ addComponents({ [`.${className}`]: { position: 'absolute' } })
}
+ )
- @media (min-width: 400px) {
- .sm\:banana {
- position: absolute;
- }
- }
- `
-
- let expected2 = css`
- .apple {
- position: absolute;
- }
-
- @media (min-width: 400px) {
- .sm\:apple {
- position: absolute;
- }
- }
- `
-
- expect(result1.css).toMatchCss(expected1)
- expect(result2.css).toMatchCss(expected2)
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ screens: {
+ sm: '400px',
+ },
+ },
}
- )
-})
-test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => {
- let plugin = createPlugin.withOptions(
- function ({ className } = { className: 'banana' }) {
+ function internalRun(options = {}) {
+ return run('@tailwind components', {
+ ...config,
+ plugins: [plugin(options)],
+ })
+ }
+
+ return Promise.all([internalRun(), internalRun({ className: 'apple' })]).then(
+ ([result1, result2]) => {
+ let expected1 = css`
+ .banana {
+ position: absolute;
+ }
+
+ @media (min-width: 400px) {
+ .sm\:banana {
+ position: absolute;
+ }
+ }
+ `
+
+ let expected2 = css`
+ .apple {
+ position: absolute;
+ }
+
+ @media (min-width: 400px) {
+ .sm\:apple {
+ position: absolute;
+ }
+ }
+ `
+
+ expect(result1.css).toMatchCss(expected1)
+ expect(result2.css).toMatchCss(expected2)
+ }
+ )
+ })
+
+ test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => {
+ let plugin = createPlugin.withOptions(
+ function ({ className } = { className: 'banana' }) {
+ return function ({ addUtilities, theme }) {
+ addUtilities(
+ Object.fromEntries(
+ Object.entries(theme('testPlugin')).map(([k, v]) => [
+ `.${className}-${k}`,
+ { testProperty: v },
+ ])
+ )
+ )
+ }
+ },
+ function () {
+ return {
+ theme: {
+ testPlugin: {
+ sm: '1rem',
+ md: '2rem',
+ lg: '3rem',
+ },
+ },
+ }
+ }
+ )
+
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: [],
+ theme: {
+ screens: {
+ sm: '400px',
+ },
+ },
+ plugins: [plugin],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .banana-sm {
+ test-property: 1rem;
+ }
+ .banana-md {
+ test-property: 2rem;
+ }
+ .banana-lg {
+ test-property: 3rem;
+ }
+ .hover\:banana-sm:hover {
+ test-property: 1rem;
+ }
+ @media (min-width: 400px) {
+ .sm\:banana-sm {
+ test-property: 1rem;
+ }
+ }
+ `)
+ })
+ })
+
+ test('the configFunction parameter is optional when using the `createPlugin.withOptions` function', () => {
+ let plugin = createPlugin.withOptions(function ({ className }) {
return function ({ addUtilities, theme }) {
addUtilities(
Object.fromEntries(
@@ -1394,523 +1476,464 @@ test('plugins created using `createPlugin.withOptions` do not need to be invoked
)
)
}
- },
- function () {
- return {
- theme: {
- testPlugin: {
- sm: '1rem',
- md: '2rem',
- lg: '3rem',
- },
+ })
+
+ let config = {
+ content: [
+ {
+ raw: html`
`,
},
- }
+ ],
+ corePlugins: [],
+ theme: {
+ screens: {
+ sm: '400px',
+ },
+ testPlugin: {
+ sm: '1px',
+ md: '2px',
+ lg: '3px',
+ },
+ },
+ plugins: [plugin({ className: 'banana' })],
}
- )
- let config = {
- content: [
- { raw: html`
` },
- ],
- corePlugins: [],
- theme: {
- screens: {
- sm: '400px',
- },
- },
- plugins: [plugin],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .banana-sm {
- test-property: 1rem;
- }
- .banana-md {
- test-property: 2rem;
- }
- .banana-lg {
- test-property: 3rem;
- }
- .hover\:banana-sm:hover {
- test-property: 1rem;
- }
- @media (min-width: 400px) {
- .sm\:banana-sm {
- test-property: 1rem;
- }
- }
- `)
- })
-})
-
-test('the configFunction parameter is optional when using the `createPlugin.withOptions` function', () => {
- let plugin = createPlugin.withOptions(function ({ className }) {
- return function ({ addUtilities, theme }) {
- addUtilities(
- Object.fromEntries(
- Object.entries(theme('testPlugin')).map(([k, v]) => [
- `.${className}-${k}`,
- { testProperty: v },
- ])
- )
- )
- }
- })
-
- let config = {
- content: [
- { raw: html`
` },
- ],
- corePlugins: [],
- theme: {
- screens: {
- sm: '400px',
- },
- testPlugin: {
- sm: '1px',
- md: '2px',
- lg: '3px',
- },
- },
- plugins: [plugin({ className: 'banana' })],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .banana-sm {
- test-property: 1px;
- }
- .banana-md {
- test-property: 2px;
- }
- .banana-lg {
- test-property: 3px;
- }
- .hover\:banana-sm:hover {
- test-property: 1px;
- }
- @media (min-width: 400px) {
- .sm\:banana-sm {
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .banana-sm {
test-property: 1px;
}
- }
- `)
- })
-})
-
-test('keyframes are not escaped', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities({
- foo: (value) => {
- return {
- [`@keyframes ${value}`]: {
- '25.001%': {
- color: 'black',
- },
- },
- animation: `${value} 1s infinite`,
- }
- },
- })
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @keyframes abc {
- 25.001% {
- color: black;
+ .banana-md {
+ test-property: 2px;
}
- }
+ .banana-lg {
+ test-property: 3px;
+ }
+ .hover\:banana-sm:hover {
+ test-property: 1px;
+ }
+ @media (min-width: 400px) {
+ .sm\:banana-sm {
+ test-property: 1px;
+ }
+ }
+ `)
+ })
+ })
- .foo-\[abc\] {
- animation: abc 1s infinite;
- }
+ test('keyframes are not escaped', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities({
+ foo: (value) => {
+ return {
+ [`@keyframes ${value}`]: {
+ '25.001%': {
+ color: 'black',
+ },
+ },
+ animation: `${value} 1s infinite`,
+ }
+ },
+ })
+ },
+ ],
+ }
- @media (min-width: 768px) {
- @keyframes def {
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @keyframes abc {
25.001% {
color: black;
}
}
- .md\:foo-\[def\] {
- animation: def 1s infinite;
+ .foo-\[abc\] {
+ animation: abc 1s infinite;
}
- }
- `)
- })
-})
-
-test('font sizes are retrieved without default line-heights or letter-spacing using theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- fontSize: {
- sm: ['14px', '20px'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- fontSize: theme('fontSize.sm'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- font-size: 14px;
- }
- `)
- })
-})
-
-test('outlines are retrieved without outline-offset using theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- outline: {
- black: ['2px dotted black', '4px'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- outline: theme('outline.black'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- outline: 2px dotted black;
- }
- `)
- })
-})
-
-test('box-shadow values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- boxShadow: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- boxShadow: theme('boxShadow.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- box-shadow: width, height;
- }
- `)
- })
-})
-
-test('transition-property values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- transitionProperty: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- transitionProperty: theme('transitionProperty.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- transition-property: width, height;
- }
- `)
- })
-})
-
-test('transition-duration values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- transitionDuration: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- transitionDuration: theme('transitionDuration.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- transition-duration: width, height;
- }
- `)
- })
-})
-
-test('transition-delay values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- transitionDuration: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- transitionDuration: theme('transitionDuration.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- transition-duration: width, height;
- }
- `)
- })
-})
-
-test('transition-timing-function values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- transitionTimingFunction: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- transitionTimingFunction: theme('transitionTimingFunction.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- transition-timing-function: width, height;
- }
- `)
- })
-})
-
-test('background-image values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- backgroundImage: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- backgroundImage: theme('backgroundImage.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- background-image: width, height;
- }
- `)
- })
-})
-
-test('background-size values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- backgroundSize: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- backgroundSize: theme('backgroundSize.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- background-size: width, height;
- }
- `)
- })
-})
-
-test('background-color values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- backgroundColor: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- backgroundColor: theme('backgroundColor.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- background-color: width, height;
- }
- `)
- })
-})
-
-test('cursor values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- cursor: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- cursor: theme('cursor.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- cursor: width, height;
- }
- `)
- })
-})
-
-test('animation values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- animation: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- animation: theme('animation.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- animation: width, height;
- }
- `)
- })
-})
-
-test('custom properties are not converted to kebab-case when added to base layer', () => {
- let config = {
- content: [],
- plugins: [
- function ({ addBase }) {
- addBase({
- ':root': {
- '--colors-primaryThing-500': '0, 0, 255',
- },
- })
- },
- ],
- }
-
- return run('@tailwind base', config).then((result) => {
- expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`)
+
+ @media (min-width: 768px) {
+ @keyframes def {
+ 25.001% {
+ color: black;
+ }
+ }
+
+ .md\:foo-\[def\] {
+ animation: def 1s infinite;
+ }
+ }
+ `)
+ })
+ })
+
+ test('font sizes are retrieved without default line-heights or letter-spacing using theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ fontSize: {
+ sm: ['14px', '20px'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ fontSize: theme('fontSize.sm'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ font-size: 14px;
+ }
+ `)
+ })
+ })
+
+ test('outlines are retrieved without outline-offset using theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ outline: {
+ black: ['2px dotted black', '4px'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ outline: theme('outline.black'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ outline: 2px dotted black;
+ }
+ `)
+ })
+ })
+
+ test('box-shadow values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ boxShadow: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ boxShadow: theme('boxShadow.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ box-shadow: width, height;
+ }
+ `)
+ })
+ })
+
+ test('transition-property values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ transitionProperty: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ transitionProperty: theme('transitionProperty.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ transition-property: width, height;
+ }
+ `)
+ })
+ })
+
+ test('transition-duration values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ transitionDuration: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ transitionDuration: theme('transitionDuration.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ transition-duration: width, height;
+ }
+ `)
+ })
+ })
+
+ test('transition-delay values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ transitionDuration: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ transitionDuration: theme('transitionDuration.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ transition-duration: width, height;
+ }
+ `)
+ })
+ })
+
+ test('transition-timing-function values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ transitionTimingFunction: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ transitionTimingFunction: theme('transitionTimingFunction.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ transition-timing-function: width, height;
+ }
+ `)
+ })
+ })
+
+ test('background-image values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ backgroundImage: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ backgroundImage: theme('backgroundImage.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ background-image: width, height;
+ }
+ `)
+ })
+ })
+
+ test('background-size values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ backgroundSize: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ backgroundSize: theme('backgroundSize.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ background-size: width, height;
+ }
+ `)
+ })
+ })
+
+ test('background-color values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ backgroundColor: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ backgroundColor: theme('backgroundColor.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ background-color: width, height;
+ }
+ `)
+ })
+ })
+
+ test('cursor values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ cursor: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ cursor: theme('cursor.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ cursor: width, height;
+ }
+ `)
+ })
+ })
+
+ test('animation values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ animation: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ animation: theme('animation.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ animation: width, height;
+ }
+ `)
+ })
+ })
+
+ test('custom properties are not converted to kebab-case when added to base layer', () => {
+ let config = {
+ content: [],
+ plugins: [
+ function ({ addBase }) {
+ addBase({
+ ':root': {
+ '--colors-primaryThing-500': '0, 0, 255',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind base', config).then((result) => {
+ expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`)
+ })
})
})
diff --git a/tests/custom-separator.test.js b/tests/custom-separator.test.js
index 24fcfe2c9..8e788c551 100644
--- a/tests/custom-separator.test.js
+++ b/tests/custom-separator.test.js
@@ -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`
-
-
-
-
-
- `,
- },
- ],
- separator: '_',
- }
+crosscheck(() => {
+ test('custom separator', () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+ `,
+ },
+ ],
+ 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."
- )
-})
diff --git a/tests/custom-transformers.test.js b/tests/custom-transformers.test.js
index 6e8d5f424..14e3abac0 100644
--- a/tests/custom-transformers.test.js
+++ b/tests/custom-transformers.test.js
@@ -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`
` }],
- 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`
` }],
- transform: {
- DEFAULT: customTransformer,
+crosscheck(({ stable, oxide }) => {
+ oxide.test.todo('transform function')
+ stable.test('transform function', () => {
+ let config = {
+ content: {
+ files: [{ raw: html`
` }],
+ 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`
` }],
+ 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;
+ }
+ `)
+ })
})
})
diff --git a/tests/customConfig.test.js b/tests/customConfig.test.js
index 2237235b8..324f6b9ae 100644
--- a/tests/customConfig.test.js
+++ b/tests/customConfig.test.js
@@ -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`
` }],
- 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`
` }],
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: '
' }],
- 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: '
' }],
- 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: '
' }],
+ test('custom config can be passed under the `config` property', () => {
+ let config = {
+ config: {
+ content: [{ raw: html`
` }],
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: '
' }],
- 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`
` }],
- 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`
` }],
- presets: [
- () => ({
+ test('tailwind.config.cjs is picked up by default', () => {
+ return inTempDirectory(() => {
+ fs.writeFileSync(
+ path.resolve(cjsConfigFile),
+ javascript`module.exports = {
+ content: [{ raw: '
' }],
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`
` }],
- presets: [
- {
- presets: [],
+ test('tailwind.config.js is picked up by default', () => {
+ return inTempDirectory(() => {
+ fs.writeFileSync(
+ path.resolve(defaultConfigFile),
+ javascript`module.exports = {
+ content: [{ raw: '
' }],
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: '
' }],
+ 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: '
' }],
+ 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`
` }],
+ 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`
` }],
- presets: [
- {
- presets: [],
- theme: {
- colors: { red: '#dd0000' },
- },
+ test('presets can be functions', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ 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`
` }],
+ 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`
` }],
+ 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`
` }],
- 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`
` }],
+ 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;
+ }
+ `)
+ })
})
})
diff --git a/tests/dark-mode.test.js b/tests/dark-mode.test.js
index 592a1d8f0..dc9086b32 100644
--- a/tests/dark-mode.test.js
+++ b/tests/dark-mode.test.js
@@ -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`
` }],
- corePlugins: { preflight: false },
- }
+crosscheck(() => {
+ it('should be possible to use the darkMode "class" mode', () => {
+ let config = {
+ darkMode: 'class',
+ content: [{ raw: 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}
- .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`
` }],
- 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`
` }],
- 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`
` }],
- corePlugins: { preflight: false },
- }
+ it('should be possible to change the class name', () => {
+ let config = {
+ darkMode: ['class', '.test-dark'],
+ content: [{ raw: 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}
-
- @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`
` }],
- corePlugins: { preflight: false },
- }
+ it('should be possible to use the darkMode "media" mode', () => {
+ let config = {
+ darkMode: 'media',
+ content: [{ raw: 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}
-
- @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`
` }],
+ 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`
` }],
+ 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;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/defaultConfig.test.js b/tests/defaultConfig.test.js
index c52c51d3f..b98251d85 100644
--- a/tests/defaultConfig.test.js
+++ b/tests/defaultConfig.test.js
@@ -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)
})
diff --git a/tests/defaultTheme.test.js b/tests/defaultTheme.test.js
index 05efddaa7..d618bcfaf 100644
--- a/tests/defaultTheme.test.js
+++ b/tests/defaultTheme.test.js
@@ -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)
})
diff --git a/tests/detect-nesting.test.js b/tests/detect-nesting.test.js
index 08eeacf98..39af6d422 100644
--- a/tests/detect-nesting.test.js
+++ b/tests/detect-nesting.test.js
@@ -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`
` }],
- }
-
- let input = css`
- @tailwind utilities;
-
- .nested {
- .example {
- }
+crosscheck(() => {
+ it('should warn when we detect nested css', () => {
+ let config = {
+ content: [{ raw: html`
` }],
}
- `
- 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`
` }],
- }
-
- 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`
` }],
- }
-
- let input = css`
- .namespace {
- @tailwind utilities;
+ it('should not warn when we detect nested css inside css @layer rules', () => {
+ let config = {
+ content: [{ raw: html`
` }],
}
- `
- 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`
` }],
- }
-
- 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`
` }],
- }
+ it('should warn when we detect namespaced @tailwind at rules', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
- 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`
` }],
+ }
+
+ 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`
` }],
+ }
+
+ 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)
+ })
})
})
diff --git a/tests/escapeClassName.test.js b/tests/escapeClassName.test.js
index 7a8cd7b7b..cdd834a79 100644
--- a/tests/escapeClassName.test.js
+++ b/tests/escapeClassName.test.js
@@ -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')
+ })
})
diff --git a/tests/evaluateTailwindFunctions.test.js b/tests/evaluateTailwindFunctions.test.js
index 0a6a4bf39..8586921b8 100644
--- a/tests/evaluateTailwindFunctions.test.js
+++ b/tests/evaluateTailwindFunctions.test.js
@@ -2,8 +2,8 @@ import fs from 'fs'
import path from 'path'
import postcss from 'postcss'
import plugin from '../src/lib/evaluateTailwindFunctions'
-import { run as runFull, css, html } from './util/run'
import log from '../src/util/log'
+import { crosscheck, run as runFull, html, css } from './util/run'
function run(input, opts = {}) {
return postcss([
@@ -16,1306 +16,1311 @@ function run(input, opts = {}) {
]).process(input, { from: undefined })
}
-test('it looks up values in the theme using dot notation', () => {
- let input = css`
- .banana {
- color: theme('colors.yellow');
- }
- `
+crosscheck(({ stable, oxide }) => {
+ test('it looks up values in the theme using dot notation', () => {
+ let input = css`
+ .banana {
+ color: theme('colors.yellow');
+ }
+ `
- let output = css`
- .banana {
- color: #f7cc50;
- }
- `
+ let output = css`
+ .banana {
+ color: #f7cc50;
+ }
+ `
- return run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('it looks up values in the theme using bracket notation', () => {
- let input = css`
- .banana {
- color: theme('colors[yellow]');
- }
- `
-
- let output = css`
- .banana {
- color: #f7cc50;
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('it looks up values in the theme using consecutive bracket notation', () => {
- let input = css`
- .banana {
- color: theme('colors[yellow][100]');
- }
- `
-
- let output = css`
- .banana {
- color: #f7cc50;
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- yellow: {
- 100: '#f7cc50',
- },
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('it looks up values in the theme using bracket notation that have dots in them', () => {
- let input = css`
- .banana {
- padding-top: theme('spacing[1.5]');
- }
- `
-
- let output = css`
- .banana {
- padding-top: 0.375rem;
- }
- `
-
- return run(input, {
- theme: {
- spacing: {
- '1.5': '0.375rem',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('theme with mismatched brackets throws an error ', async () => {
- let config = {
- theme: {
- spacing: {
- '1.5': '0.375rem',
- },
- },
- }
-
- let input = (path) => css`
- .banana {
- padding-top: theme('${path}');
- }
- `
-
- await expect(run(input('spacing[1.5]]'), config)).rejects.toThrowError(
- `Path is invalid. Has unbalanced brackets: spacing[1.5]]`
- )
-
- await expect(run(input('spacing[[1.5]'), config)).rejects.toThrowError(
- `Path is invalid. Has unbalanced brackets: spacing[[1.5]`
- )
-
- await expect(run(input('spacing[a['), config)).rejects.toThrowError(
- `Path is invalid. Has unbalanced brackets: spacing[a[`
- )
-})
-
-test('color can be a function', () => {
- let input = css`
- .backgroundColor {
- color: theme('backgroundColor.fn');
- }
- .borderColor {
- color: theme('borderColor.fn');
- }
- .caretColor {
- color: theme('caretColor.fn');
- }
- .colors {
- color: theme('colors.fn');
- }
- .divideColor {
- color: theme('divideColor.fn');
- }
- .fill {
- color: theme('fill.fn');
- }
- .gradientColorStops {
- color: theme('gradientColorStops.fn');
- }
- .placeholderColor {
- color: theme('placeholderColor.fn');
- }
- .ringColor {
- color: theme('ringColor.fn');
- }
- .ringOffsetColor {
- color: theme('ringOffsetColor.fn');
- }
- .stroke {
- color: theme('stroke.fn');
- }
- .textColor {
- color: theme('textColor.fn');
- }
- `
-
- let output = css`
- .backgroundColor {
- color: #f00;
- }
- .borderColor {
- color: #f00;
- }
- .caretColor {
- color: #f00;
- }
- .colors {
- color: #f00;
- }
- .divideColor {
- color: #f00;
- }
- .fill {
- color: #f00;
- }
- .gradientColorStops {
- color: #f00;
- }
- .placeholderColor {
- color: #f00;
- }
- .ringColor {
- color: #f00;
- }
- .ringOffsetColor {
- color: #f00;
- }
- .stroke {
- color: #f00;
- }
- .textColor {
- color: #f00;
- }
- `
-
- let fn = () => `#f00`
-
- return run(input, {
- theme: {
- backgroundColor: { fn },
- borderColor: { fn },
- caretColor: { fn },
- colors: { fn },
- divideColor: { fn },
- fill: { fn },
- gradientColorStops: { fn },
- placeholderColor: { fn },
- ringColor: { fn },
- ringOffsetColor: { fn },
- stroke: { fn },
- textColor: { fn },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('quotes are optional around the lookup path', () => {
- let input = css`
- .banana {
- color: theme(colors.yellow);
- }
- `
-
- let output = css`
- .banana {
- color: #f7cc50;
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('a default value can be provided', () => {
- let input = css`
- .cookieMonster {
- color: theme('colors.blue', #0000ff);
- }
- `
-
- let output = css`
- .cookieMonster {
- color: #0000ff;
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('the default value can use the theme function', () => {
- let input = css`
- .cookieMonster {
- color: theme('colors.blue', theme('colors.yellow'));
- }
- `
-
- let output = css`
- .cookieMonster {
- color: #f7cc50;
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('quotes are preserved around default values', () => {
- let input = css`
- .heading {
- font-family: theme('fontFamily.sans', 'Helvetica Neue');
- }
- `
-
- let output = css`
- .heading {
- font-family: 'Helvetica Neue';
- }
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- serif: 'Constantia',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('an unquoted list is valid as a default value', () => {
- let input = css`
- .heading {
- font-family: theme('fontFamily.sans', Helvetica, Arial, sans-serif);
- }
- `
-
- let output = css`
- .heading {
- font-family: Helvetica, Arial, sans-serif;
- }
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- serif: 'Constantia',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('a missing root theme value throws', () => {
- let input = css`
- .heading {
- color: theme('colours.gray.100');
- }
- `
-
- return expect(
- run(input, {
+ return run(input, {
theme: {
colors: {
yellow: '#f7cc50',
},
},
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
})
- ).rejects.toThrowError(
- `'colours.gray.100' does not exist in your theme config. Your theme has the following top-level keys: 'colors'`
- )
-})
+ })
-test('a missing nested theme property throws', () => {
- let input = css`
- .heading {
- color: theme('colors.red');
- }
- `
+ test('it looks up values in the theme using bracket notation', () => {
+ let input = css`
+ .banana {
+ color: theme('colors[yellow]');
+ }
+ `
- return expect(
- run(input, {
- theme: {
- colors: {
- blue: 'blue',
- yellow: '#f7cc50',
- },
- },
- })
- ).rejects.toThrowError(
- `'colors.red' does not exist in your theme config. 'colors' has the following valid keys: 'blue', 'yellow'`
- )
-})
+ let output = css`
+ .banana {
+ color: #f7cc50;
+ }
+ `
-test('a missing nested theme property with a close alternative throws with a suggestion', () => {
- let input = css`
- .heading {
- color: theme('colors.yellw');
- }
- `
-
- return expect(
- run(input, {
+ return run(input, {
theme: {
colors: {
yellow: '#f7cc50',
},
},
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
})
- ).rejects.toThrowError(
- `'colors.yellw' does not exist in your theme config. Did you mean 'colors.yellow'?`
- )
-})
+ })
-test('a path through a non-object throws', () => {
- let input = css`
- .heading {
- color: theme('colors.yellow.100');
- }
- `
+ test('it looks up values in the theme using consecutive bracket notation', () => {
+ let input = css`
+ .banana {
+ color: theme('colors[yellow][100]');
+ }
+ `
- return expect(
- run(input, {
+ let output = css`
+ .banana {
+ color: #f7cc50;
+ }
+ `
+
+ return run(input, {
theme: {
colors: {
- yellow: '#f7cc50',
+ yellow: {
+ 100: '#f7cc50',
+ },
},
},
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
})
- ).rejects.toThrowError(
- `'colors.yellow.100' does not exist in your theme config. 'colors.yellow' is not an object.`
- )
-})
+ })
-test('a path which exists but is not a string throws', () => {
- let input = css`
- .heading {
- color: theme('colors.yellow');
- }
- `
+ test('it looks up values in the theme using bracket notation that have dots in them', () => {
+ let input = css`
+ .banana {
+ padding-top: theme('spacing[1.5]');
+ }
+ `
- return expect(
- run(input, {
+ let output = css`
+ .banana {
+ padding-top: 0.375rem;
+ }
+ `
+
+ return run(input, {
theme: {
- colors: {
- yellow: Symbol(),
+ spacing: {
+ '1.5': '0.375rem',
},
},
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
})
- ).rejects.toThrowError(`'colors.yellow' was found but does not resolve to a string.`)
-})
+ })
-test('a path which exists but is invalid throws', () => {
- let input = css`
- .heading {
- color: theme('colors');
- }
- `
-
- return expect(
- run(input, {
+ test('theme with mismatched brackets throws an error ', async () => {
+ let config = {
theme: {
- colors: {},
- },
- })
- ).rejects.toThrowError(`'colors' was found but does not resolve to a string.`)
-})
-
-test('a path which is an object throws with a suggested key', () => {
- let input = css`
- .heading {
- color: theme('colors');
- }
- `
-
- return expect(
- run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
+ spacing: {
+ '1.5': '0.375rem',
},
},
- })
- ).rejects.toThrowError(
- `'colors' was found but does not resolve to a string. Did you mean something like 'colors.yellow'?`
- )
-})
-
-test('array values are joined by default', () => {
- let input = css`
- .heading {
- font-family: theme('fontFamily.sans');
}
- `
- let output = css`
- .heading {
- font-family: Inter, Helvetica, sans-serif;
- }
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- sans: ['Inter', 'Helvetica', 'sans-serif'],
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('font sizes are retrieved without default line-heights or letter-spacing', () => {
- let input = css`
- .heading-1 {
- font-size: theme('fontSize.lg');
- }
- .heading-2 {
- font-size: theme('fontSize.xl');
- }
- `
-
- let output = css`
- .heading-1 {
- font-size: 20px;
- }
- .heading-2 {
- font-size: 24px;
- }
- `
-
- return run(input, {
- theme: {
- fontSize: {
- lg: ['20px', '28px'],
- xl: ['24px', { lineHeight: '32px', letterSpacing: '-0.01em' }],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('outlines are retrieved without default outline-offset', () => {
- let input = css`
- .element {
- outline: theme('outline.black');
- }
- `
-
- let output = css`
- .element {
- outline: 2px dotted black;
- }
- `
-
- return run(input, {
- theme: {
- outline: {
- black: ['2px dotted black', '4px'],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('font-family values are joined when an array', () => {
- let input = css`
- .element {
- font-family: theme('fontFamily.sans');
- }
- `
-
- let output = css`
- .element {
- font-family: Helvetica, Arial, sans-serif;
- }
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- sans: ['Helvetica', 'Arial', 'sans-serif'],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('font-family values are retrieved without font-feature-settings', () => {
- let input = css`
- .heading-1 {
- font-family: theme('fontFamily.sans');
- }
- .heading-2 {
- font-family: theme('fontFamily.serif');
- }
- .heading-3 {
- font-family: theme('fontFamily.mono');
- }
- `
-
- let output = css`
- .heading-1 {
- font-family: Inter;
- }
- .heading-2 {
- font-family: Times, serif;
- }
- .heading-3 {
- font-family: Menlo, monospace;
- }
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- sans: ['Inter', { fontFeatureSettings: '"cv11"' }],
- serif: [['Times', 'serif'], { fontFeatureSettings: '"cv11"' }],
- mono: ['Menlo, monospace', { fontFeatureSettings: '"cv11"' }],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('font-feature-settings values can be retrieved', () => {
- let input = css`
- .heading {
- font-family: theme('fontFamily.sans');
- font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings');
- }
- `
-
- let output = css`
- .heading {
- font-family: Inter;
- font-feature-settings: 'cv11';
- }
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- sans: ['Inter', { fontFeatureSettings: "'cv11'" }],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('box-shadow values are joined when an array', () => {
- let input = css`
- .element {
- box-shadow: theme('boxShadow.wtf');
- }
- `
-
- let output = css`
- .element {
- box-shadow: 0 0 2px black, 1px 2px 3px white;
- }
- `
-
- return run(input, {
- theme: {
- boxShadow: {
- wtf: ['0 0 2px black', '1px 2px 3px white'],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('transition-property values are joined when an array', () => {
- let input = css`
- .element {
- transition-property: theme('transitionProperty.colors');
- }
- `
-
- let output = css`
- .element {
- transition-property: color, fill;
- }
- `
-
- return run(input, {
- theme: {
- transitionProperty: {
- colors: ['color', 'fill'],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('transition-duration values are joined when an array', () => {
- let input = css`
- .element {
- transition-duration: theme('transitionDuration.lol');
- }
- `
-
- let output = css`
- .element {
- transition-duration: 1s, 2s;
- }
- `
-
- return run(input, {
- theme: {
- transitionDuration: {
- lol: ['1s', '2s'],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('basic screen function calls are expanded', () => {
- let input = css`
- @media screen(sm) {
- .foo {
+ let input = (path) => css`
+ .banana {
+ padding-top: theme('${path}');
}
- }
- `
+ `
- let output = css`
- @media (min-width: 600px) {
- .foo {
- }
- }
- `
-
- return run(input, {
- theme: { screens: { sm: '600px' } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('screen function supports max-width screens', () => {
- let input = css`
- @media screen(sm) {
- .foo {
- }
- }
- `
-
- let output = css`
- @media (max-width: 600px) {
- .foo {
- }
- }
- `
-
- return run(input, {
- theme: { screens: { sm: { max: '600px' } } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('screen function supports min-width screens', () => {
- let input = css`
- @media screen(sm) {
- .foo {
- }
- }
- `
-
- let output = css`
- @media (min-width: 600px) {
- .foo {
- }
- }
- `
-
- return run(input, {
- theme: { screens: { sm: { min: '600px' } } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('screen function supports min-width and max-width screens', () => {
- let input = css`
- @media screen(sm) {
- .foo {
- }
- }
- `
-
- let output = css`
- @media (min-width: 600px) and (max-width: 700px) {
- .foo {
- }
- }
- `
-
- return run(input, {
- theme: { screens: { sm: { min: '600px', max: '700px' } } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('screen function supports raw screens', () => {
- let input = css`
- @media screen(mono) {
- .foo {
- }
- }
- `
-
- let output = css`
- @media monochrome {
- .foo {
- }
- }
- `
-
- return run(input, {
- theme: { screens: { mono: { raw: 'monochrome' } } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('screen arguments can be quoted', () => {
- let input = css`
- @media screen('sm') {
- .foo {
- }
- }
- `
-
- let output = css`
- @media (min-width: 600px) {
- .foo {
- }
- }
- `
-
- return run(input, {
- theme: { screens: { sm: '600px' } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function can extract alpha values for colors (1)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / 50%);
- }
- `
-
- let output = css`
- .foo {
- color: rgb(59 130 246 / 50%);
- }
- `
-
- return run(input, {
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function can extract alpha values for colors (2)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / 0.5);
- }
- `
-
- let output = css`
- .foo {
- color: rgb(59 130 246 / 0.5);
- }
- `
-
- return run(input, {
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function can extract alpha values for colors (3)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / var(--my-alpha));
- }
- `
-
- let output = css`
- .foo {
- color: rgb(59 130 246 / var(--my-alpha));
- }
- `
-
- return run(input, {
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function can extract alpha values for colors (4)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / 50%);
- }
- `
-
- let output = css`
- .foo {
- color: hsl(217 91% 60% / 50%);
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- blue: { 500: 'hsl(217, 91%, 60%)' },
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function can extract alpha values for colors (5)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / 0.5);
- }
- `
-
- let output = css`
- .foo {
- color: hsl(217 91% 60% / 0.5);
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- blue: { 500: 'hsl(217, 91%, 60%)' },
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function can extract alpha values for colors (6)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / var(--my-alpha));
- }
- `
-
- let output = css`
- .foo {
- color: hsl(217 91% 60% / var(--my-alpha));
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- blue: { 500: 'hsl(217, 91%, 60%)' },
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function can extract alpha values for colors (7)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / var(--my-alpha));
- }
- `
-
- let output = css`
- .foo {
- color: rgb(var(--foo) / var(--my-alpha));
- }
- `
-
- return runFull(input, {
- theme: {
- colors: {
- blue: {
- 500: 'rgb(var(--foo) /
)',
- },
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function can extract alpha values for colors (8)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / theme(opacity.myalpha));
- }
- `
-
- let output = css`
- .foo {
- color: rgb(var(--foo) / 50%);
- }
- `
-
- return runFull(input, {
- theme: {
- colors: {
- blue: {
- 500: 'rgb(var(--foo) / )',
- },
- },
-
- opacity: {
- myalpha: '50%',
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme functions replace the alpha value placeholder even with no alpha provided', () => {
- let input = css`
- .foo {
- background: theme(colors.blue.400);
- color: theme(colors.blue.500);
- }
- `
-
- let output = css`
- .foo {
- background: rgb(0 0 255 / 1);
- color: rgb(var(--foo) / 1);
- }
- `
-
- return runFull(input, {
- theme: {
- colors: {
- blue: {
- 400: 'rgb(0 0 255 / )',
- 500: 'rgb(var(--foo) / )',
- },
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme functions can reference values with slashes in brackets', () => {
- let input = css`
- .foo1 {
- color: theme(colors[a/b]);
- }
- .foo2 {
- color: theme(colors[a/b]/50%);
- }
- `
-
- let output = css`
- .foo1 {
- color: #000000;
- }
- .foo2 {
- color: rgb(0 0 0 / 50%);
- }
- `
-
- return runFull(input, {
- theme: {
- colors: {
- 'a/b': '#000000',
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme functions with alpha value inside quotes', () => {
- let input = css`
- .foo {
- color: theme('colors.yellow / 50%');
- }
- `
-
- let output = css`
- .foo {
- color: rgb(247 204 80 / 50%);
- }
- `
-
- return runFull(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme functions with alpha with quotes value around color only', () => {
- let input = css`
- .foo {
- color: theme('colors.yellow' / 50%);
- }
- `
-
- let output = css`
- .foo {
- color: rgb(247 204 80 / 50%);
- }
- `
-
- return runFull(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-it('can find values with slashes in the theme key while still allowing for alpha values ', () => {
- let input = css`
- .foo00 {
- color: theme(colors.foo-5);
- }
- .foo01 {
- color: theme(colors.foo-5/10);
- }
- .foo02 {
- color: theme(colors.foo-5/10/25);
- }
- .foo03 {
- color: theme(colors.foo-5 / 10);
- }
- .foo04 {
- color: theme(colors.foo-5/10 / 25);
- }
- `
-
- return runFull(input, {
- theme: {
- colors: {
- 'foo-5': '#050000',
- 'foo-5/10': '#051000',
- 'foo-5/10/25': '#051025',
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(css`
- .foo00 {
- color: #050000;
- }
- .foo01 {
- color: #051000;
- }
- .foo02 {
- color: #051025;
- }
- .foo03 {
- color: rgb(5 0 0 / 10);
- }
- .foo04 {
- color: rgb(5 16 0 / 25);
- }
- `)
- })
-})
-
-describe('context dependent', () => {
- let configPath = path.resolve(__dirname, './evaluate-tailwind-functions.tailwind.config.js')
- let filePath = path.resolve(__dirname, './evaluate-tailwind-functions.test.html')
- let config = {
- content: [filePath],
- corePlugins: { preflight: false },
- }
-
- // Rebuild the config file for each test
- beforeEach(() => fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`))
- afterEach(() => fs.promises.unlink(configPath))
-
- let warn
-
- beforeEach(() => {
- warn = jest.spyOn(log, 'warn')
- })
-
- afterEach(() => warn.mockClear())
-
- it('should not generate when theme fn doesnt resolve', async () => {
- await fs.promises.writeFile(
- filePath,
- html`
-
-
- `
+ await expect(run(input('spacing[1.5]]'), config)).rejects.toThrowError(
+ `Path is invalid. Has unbalanced brackets: spacing[1.5]]`
)
- // TODO: We need a way to reuse the context in our test suite without requiring writing to files
- // It should be an explicit thing tho — like we create a context and pass it in or something
- let result = await runFull('@tailwind utilities', configPath)
+ await expect(run(input('spacing[[1.5]'), config)).rejects.toThrowError(
+ `Path is invalid. Has unbalanced brackets: spacing[[1.5]`
+ )
- // 1. On first run it should work because it's been removed from the class cache
- expect(result.css).toMatchCss(css`
- .underline {
- text-decoration-line: underline;
+ await expect(run(input('spacing[a['), config)).rejects.toThrowError(
+ `Path is invalid. Has unbalanced brackets: spacing[a[`
+ )
+ })
+
+ test('color can be a function', () => {
+ let input = css`
+ .backgroundColor {
+ color: theme('backgroundColor.fn');
}
- `)
-
- // 2. But we get a warning in the console
- expect(warn).toHaveBeenCalledTimes(2)
- expect(warn.mock.calls.map((x) => x[0])).toEqual([
- 'invalid-theme-key-in-class',
- 'invalid-theme-key-in-class',
- ])
-
- // 3. The second run should work fine because it's been removed from the class cache
- result = await runFull('@tailwind utilities', configPath)
-
- expect(result.css).toMatchCss(css`
- .underline {
- text-decoration-line: underline;
+ .borderColor {
+ color: theme('borderColor.fn');
}
- `)
+ .caretColor {
+ color: theme('caretColor.fn');
+ }
+ .colors {
+ color: theme('colors.fn');
+ }
+ .divideColor {
+ color: theme('divideColor.fn');
+ }
+ .fill {
+ color: theme('fill.fn');
+ }
+ .gradientColorStops {
+ color: theme('gradientColorStops.fn');
+ }
+ .placeholderColor {
+ color: theme('placeholderColor.fn');
+ }
+ .ringColor {
+ color: theme('ringColor.fn');
+ }
+ .ringOffsetColor {
+ color: theme('ringOffsetColor.fn');
+ }
+ .stroke {
+ color: theme('stroke.fn');
+ }
+ .textColor {
+ color: theme('textColor.fn');
+ }
+ `
- // 4. But we've not received any further logs about it
- expect(warn).toHaveBeenCalledTimes(2)
- expect(warn.mock.calls.map((x) => x[0])).toEqual([
- 'invalid-theme-key-in-class',
- 'invalid-theme-key-in-class',
- ])
+ let output = css`
+ .backgroundColor {
+ color: #f00;
+ }
+ .borderColor {
+ color: #f00;
+ }
+ .caretColor {
+ color: #f00;
+ }
+ .colors {
+ color: #f00;
+ }
+ .divideColor {
+ color: #f00;
+ }
+ .fill {
+ color: #f00;
+ }
+ .gradientColorStops {
+ color: #f00;
+ }
+ .placeholderColor {
+ color: #f00;
+ }
+ .ringColor {
+ color: #f00;
+ }
+ .ringOffsetColor {
+ color: #f00;
+ }
+ .stroke {
+ color: #f00;
+ }
+ .textColor {
+ color: #f00;
+ }
+ `
+
+ let fn = () => `#f00`
+
+ return run(input, {
+ theme: {
+ backgroundColor: { fn },
+ borderColor: { fn },
+ caretColor: { fn },
+ colors: { fn },
+ divideColor: { fn },
+ fill: { fn },
+ gradientColorStops: { fn },
+ placeholderColor: { fn },
+ ringColor: { fn },
+ ringOffsetColor: { fn },
+ stroke: { fn },
+ textColor: { fn },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('quotes are optional around the lookup path', () => {
+ let input = css`
+ .banana {
+ color: theme(colors.yellow);
+ }
+ `
+
+ let output = css`
+ .banana {
+ color: #f7cc50;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('a default value can be provided', () => {
+ let input = css`
+ .cookieMonster {
+ color: theme('colors.blue', #0000ff);
+ }
+ `
+
+ let output = css`
+ .cookieMonster {
+ color: #0000ff;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('the default value can use the theme function', () => {
+ let input = css`
+ .cookieMonster {
+ color: theme('colors.blue', theme('colors.yellow'));
+ }
+ `
+
+ let output = css`
+ .cookieMonster {
+ color: #f7cc50;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('quotes are preserved around default values', () => {
+ let input = css`
+ .heading {
+ font-family: theme('fontFamily.sans', 'Helvetica Neue');
+ }
+ `
+
+ let output = css`
+ .heading {
+ font-family: 'Helvetica Neue';
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ serif: 'Constantia',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('an unquoted list is valid as a default value', () => {
+ let input = css`
+ .heading {
+ font-family: theme('fontFamily.sans', Helvetica, Arial, sans-serif);
+ }
+ `
+
+ let output = css`
+ .heading {
+ font-family: Helvetica, Arial, sans-serif;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ serif: 'Constantia',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('a missing root theme value throws', () => {
+ let input = css`
+ .heading {
+ color: theme('colours.gray.100');
+ }
+ `
+
+ return expect(
+ run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ })
+ ).rejects.toThrowError(
+ `'colours.gray.100' does not exist in your theme config. Your theme has the following top-level keys: 'colors'`
+ )
+ })
+
+ test('a missing nested theme property throws', () => {
+ let input = css`
+ .heading {
+ color: theme('colors.red');
+ }
+ `
+
+ return expect(
+ run(input, {
+ theme: {
+ colors: {
+ blue: 'blue',
+ yellow: '#f7cc50',
+ },
+ },
+ })
+ ).rejects.toThrowError(
+ `'colors.red' does not exist in your theme config. 'colors' has the following valid keys: 'blue', 'yellow'`
+ )
+ })
+
+ test('a missing nested theme property with a close alternative throws with a suggestion', () => {
+ let input = css`
+ .heading {
+ color: theme('colors.yellw');
+ }
+ `
+
+ return expect(
+ run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ })
+ ).rejects.toThrowError(
+ `'colors.yellw' does not exist in your theme config. Did you mean 'colors.yellow'?`
+ )
+ })
+
+ test('a path through a non-object throws', () => {
+ let input = css`
+ .heading {
+ color: theme('colors.yellow.100');
+ }
+ `
+
+ return expect(
+ run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ })
+ ).rejects.toThrowError(
+ `'colors.yellow.100' does not exist in your theme config. 'colors.yellow' is not an object.`
+ )
+ })
+
+ test('a path which exists but is not a string throws', () => {
+ let input = css`
+ .heading {
+ color: theme('colors.yellow');
+ }
+ `
+
+ return expect(
+ run(input, {
+ theme: {
+ colors: {
+ yellow: Symbol(),
+ },
+ },
+ })
+ ).rejects.toThrowError(`'colors.yellow' was found but does not resolve to a string.`)
+ })
+
+ test('a path which exists but is invalid throws', () => {
+ let input = css`
+ .heading {
+ color: theme('colors');
+ }
+ `
+
+ return expect(
+ run(input, {
+ theme: {
+ colors: {},
+ },
+ })
+ ).rejects.toThrowError(`'colors' was found but does not resolve to a string.`)
+ })
+
+ test('a path which is an object throws with a suggested key', () => {
+ let input = css`
+ .heading {
+ color: theme('colors');
+ }
+ `
+
+ return expect(
+ run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ })
+ ).rejects.toThrowError(
+ `'colors' was found but does not resolve to a string. Did you mean something like 'colors.yellow'?`
+ )
+ })
+
+ test('array values are joined by default', () => {
+ let input = css`
+ .heading {
+ font-family: theme('fontFamily.sans');
+ }
+ `
+
+ let output = css`
+ .heading {
+ font-family: Inter, Helvetica, sans-serif;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ sans: ['Inter', 'Helvetica', 'sans-serif'],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('font sizes are retrieved without default line-heights or letter-spacing', () => {
+ let input = css`
+ .heading-1 {
+ font-size: theme('fontSize.lg');
+ }
+ .heading-2 {
+ font-size: theme('fontSize.xl');
+ }
+ `
+
+ let output = css`
+ .heading-1 {
+ font-size: 20px;
+ }
+ .heading-2 {
+ font-size: 24px;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontSize: {
+ lg: ['20px', '28px'],
+ xl: ['24px', { lineHeight: '32px', letterSpacing: '-0.01em' }],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('outlines are retrieved without default outline-offset', () => {
+ let input = css`
+ .element {
+ outline: theme('outline.black');
+ }
+ `
+
+ let output = css`
+ .element {
+ outline: 2px dotted black;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ outline: {
+ black: ['2px dotted black', '4px'],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('font-family values are joined when an array', () => {
+ let input = css`
+ .element {
+ font-family: theme('fontFamily.sans');
+ }
+ `
+
+ let output = css`
+ .element {
+ font-family: Helvetica, Arial, sans-serif;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ sans: ['Helvetica', 'Arial', 'sans-serif'],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('font-family values are retrieved without font-feature-settings', () => {
+ let input = css`
+ .heading-1 {
+ font-family: theme('fontFamily.sans');
+ }
+ .heading-2 {
+ font-family: theme('fontFamily.serif');
+ }
+ .heading-3 {
+ font-family: theme('fontFamily.mono');
+ }
+ `
+
+ let output = css`
+ .heading-1 {
+ font-family: Inter;
+ }
+ .heading-2 {
+ font-family: Times, serif;
+ }
+ .heading-3 {
+ font-family: Menlo, monospace;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ sans: ['Inter', { fontFeatureSettings: '"cv11"' }],
+ serif: [['Times', 'serif'], { fontFeatureSettings: '"cv11"' }],
+ mono: ['Menlo, monospace', { fontFeatureSettings: '"cv11"' }],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('font-feature-settings values can be retrieved', () => {
+ let input = css`
+ .heading {
+ font-family: theme('fontFamily.sans');
+ font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings');
+ }
+ `
+
+ let output = css`
+ .heading {
+ font-family: Inter;
+ font-feature-settings: 'cv11';
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ sans: ['Inter', { fontFeatureSettings: "'cv11'" }],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('box-shadow values are joined when an array', () => {
+ let input = css`
+ .element {
+ box-shadow: theme('boxShadow.wtf');
+ }
+ `
+
+ let output = css`
+ .element {
+ box-shadow: 0 0 2px black, 1px 2px 3px white;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ boxShadow: {
+ wtf: ['0 0 2px black', '1px 2px 3px white'],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('transition-property values are joined when an array', () => {
+ let input = css`
+ .element {
+ transition-property: theme('transitionProperty.colors');
+ }
+ `
+
+ let output = css`
+ .element {
+ transition-property: color, fill;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ transitionProperty: {
+ colors: ['color', 'fill'],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('transition-duration values are joined when an array', () => {
+ let input = css`
+ .element {
+ transition-duration: theme('transitionDuration.lol');
+ }
+ `
+
+ let output = css`
+ .element {
+ transition-duration: 1s, 2s;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ transitionDuration: {
+ lol: ['1s', '2s'],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('basic screen function calls are expanded', () => {
+ let input = css`
+ @media screen(sm) {
+ .foo {
+ }
+ }
+ `
+
+ let output = css`
+ @media (min-width: 600px) {
+ .foo {
+ }
+ }
+ `
+
+ return run(input, {
+ theme: { screens: { sm: '600px' } },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('screen function supports max-width screens', () => {
+ let input = css`
+ @media screen(sm) {
+ .foo {
+ }
+ }
+ `
+
+ let output = css`
+ @media (max-width: 600px) {
+ .foo {
+ }
+ }
+ `
+
+ return run(input, {
+ theme: { screens: { sm: { max: '600px' } } },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('screen function supports min-width screens', () => {
+ let input = css`
+ @media screen(sm) {
+ .foo {
+ }
+ }
+ `
+
+ let output = css`
+ @media (min-width: 600px) {
+ .foo {
+ }
+ }
+ `
+
+ return run(input, {
+ theme: { screens: { sm: { min: '600px' } } },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('screen function supports min-width and max-width screens', () => {
+ let input = css`
+ @media screen(sm) {
+ .foo {
+ }
+ }
+ `
+
+ let output = css`
+ @media (min-width: 600px) and (max-width: 700px) {
+ .foo {
+ }
+ }
+ `
+
+ return run(input, {
+ theme: { screens: { sm: { min: '600px', max: '700px' } } },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('screen function supports raw screens', () => {
+ let input = css`
+ @media screen(mono) {
+ .foo {
+ }
+ }
+ `
+
+ let output = css`
+ @media monochrome {
+ .foo {
+ }
+ }
+ `
+
+ return run(input, {
+ theme: { screens: { mono: { raw: 'monochrome' } } },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('screen arguments can be quoted', () => {
+ let input = css`
+ @media screen('sm') {
+ .foo {
+ }
+ }
+ `
+
+ let output = css`
+ @media (min-width: 600px) {
+ .foo {
+ }
+ }
+ `
+
+ return run(input, {
+ theme: { screens: { sm: '600px' } },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function can extract alpha values for colors (1)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / 50%);
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: rgb(59 130 246 / 50%);
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function can extract alpha values for colors (2)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / 0.5);
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: rgb(59 130 246 / 0.5);
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function can extract alpha values for colors (3)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / var(--my-alpha));
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: rgb(59 130 246 / var(--my-alpha));
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function can extract alpha values for colors (4)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / 50%);
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: hsl(217 91% 60% / 50%);
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ blue: { 500: 'hsl(217, 91%, 60%)' },
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function can extract alpha values for colors (5)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / 0.5);
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: hsl(217 91% 60% / 0.5);
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ blue: { 500: 'hsl(217, 91%, 60%)' },
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function can extract alpha values for colors (6)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / var(--my-alpha));
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: hsl(217 91% 60% / var(--my-alpha));
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ blue: { 500: 'hsl(217, 91%, 60%)' },
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function can extract alpha values for colors (7)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / var(--my-alpha));
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: rgb(var(--foo) / var(--my-alpha));
+ }
+ `
+
+ return runFull(input, {
+ theme: {
+ colors: {
+ blue: {
+ 500: 'rgb(var(--foo) / )',
+ },
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function can extract alpha values for colors (8)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / theme(opacity.myalpha));
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: rgb(var(--foo) / 50%);
+ }
+ `
+
+ return runFull(input, {
+ theme: {
+ colors: {
+ blue: {
+ 500: 'rgb(var(--foo) / )',
+ },
+ },
+
+ opacity: {
+ myalpha: '50%',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme functions replace the alpha value placeholder even with no alpha provided', () => {
+ let input = css`
+ .foo {
+ background: theme(colors.blue.400);
+ color: theme(colors.blue.500);
+ }
+ `
+
+ let output = css`
+ .foo {
+ background: rgb(0 0 255 / 1);
+ color: rgb(var(--foo) / 1);
+ }
+ `
+
+ return runFull(input, {
+ theme: {
+ colors: {
+ blue: {
+ 400: 'rgb(0 0 255 / )',
+ 500: 'rgb(var(--foo) / )',
+ },
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme functions can reference values with slashes in brackets', () => {
+ let input = css`
+ .foo1 {
+ color: theme(colors[a/b]);
+ }
+ .foo2 {
+ color: theme(colors[a/b]/50%);
+ }
+ `
+
+ let output = css`
+ .foo1 {
+ color: #000000;
+ }
+ .foo2 {
+ color: rgb(0 0 0 / 50%);
+ }
+ `
+
+ return runFull(input, {
+ theme: {
+ colors: {
+ 'a/b': '#000000',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme functions with alpha value inside quotes', () => {
+ let input = css`
+ .foo {
+ color: theme('colors.yellow / 50%');
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: rgb(247 204 80 / 50%);
+ }
+ `
+
+ return runFull(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme functions with alpha with quotes value around color only', () => {
+ let input = css`
+ .foo {
+ color: theme('colors.yellow' / 50%);
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: rgb(247 204 80 / 50%);
+ }
+ `
+
+ return runFull(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ it('can find values with slashes in the theme key while still allowing for alpha values ', () => {
+ let input = css`
+ .foo00 {
+ color: theme(colors.foo-5);
+ }
+ .foo01 {
+ color: theme(colors.foo-5/10);
+ }
+ .foo02 {
+ color: theme(colors.foo-5/10/25);
+ }
+ .foo03 {
+ color: theme(colors.foo-5 / 10);
+ }
+ .foo04 {
+ color: theme(colors.foo-5/10 / 25);
+ }
+ `
+
+ return runFull(input, {
+ theme: {
+ colors: {
+ 'foo-5': '#050000',
+ 'foo-5/10': '#051000',
+ 'foo-5/10/25': '#051025',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .foo00 {
+ color: #050000;
+ }
+ .foo01 {
+ color: #051000;
+ }
+ .foo02 {
+ color: #051025;
+ }
+ .foo03 {
+ color: rgb(5 0 0 / 10);
+ }
+ .foo04 {
+ color: rgb(5 16 0 / 25);
+ }
+ `)
+ })
+ })
+
+ describe('context dependent', () => {
+ let configPath = path.resolve(__dirname, './evaluate-tailwind-functions.tailwind.config.js')
+ let filePath = path.resolve(__dirname, './evaluate-tailwind-functions.test.html')
+ let config = {
+ content: [filePath],
+ corePlugins: { preflight: false },
+ }
+
+ // Rebuild the config file for each test
+ beforeEach(() =>
+ fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`)
+ )
+ afterEach(() => fs.promises.unlink(configPath))
+
+ let warn
+
+ beforeEach(() => {
+ warn = jest.spyOn(log, 'warn')
+ })
+
+ afterEach(() => warn.mockClear())
+
+ oxide.test.todo('should not generate when theme fn doesnt resolve')
+ stable.test('should not generate when theme fn doesnt resolve', async () => {
+ await fs.promises.writeFile(
+ filePath,
+ html`
+
+
+ `
+ )
+
+ // TODO: We need a way to reuse the context in our test suite without requiring writing to files
+ // It should be an explicit thing tho — like we create a context and pass it in or something
+ let result = await runFull('@tailwind utilities', configPath)
+
+ // 1. On first run it should work because it's been removed from the class cache
+ expect(result.css).toMatchCss(css`
+ .underline {
+ text-decoration-line: underline;
+ }
+ `)
+
+ // 2. But we get a warning in the console
+ expect(warn).toHaveBeenCalledTimes(2)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual([
+ 'invalid-theme-key-in-class',
+ 'invalid-theme-key-in-class',
+ ])
+
+ // 3. The second run should work fine because it's been removed from the class cache
+ result = await runFull('@tailwind utilities', configPath)
+
+ expect(result.css).toMatchCss(css`
+ .underline {
+ text-decoration-line: underline;
+ }
+ `)
+
+ // 4. But we've not received any further logs about it
+ expect(warn).toHaveBeenCalledTimes(2)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual([
+ 'invalid-theme-key-in-class',
+ 'invalid-theme-key-in-class',
+ ])
+ })
})
})
diff --git a/tests/experimental.test.js b/tests/experimental.test.js
index 277136882..824c718d1 100644
--- a/tests/experimental.test.js
+++ b/tests/experimental.test.js
@@ -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`` }],
- corePlugins: { preflight: false },
- }
+crosscheck(() => {
+ test('experimental universal selector improvements (box-shadow)', () => {
+ let config = {
+ experimental: 'all',
+ content: [{ raw: html`` }],
+ 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`` }],
- 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`` }],
- 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`` }],
- 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`` }],
- 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`` }],
- 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`` }],
+ 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`` }],
+ 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`` }],
+ 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`` }],
+ 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`` }],
+ 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);
+ }
+ `)
+ })
})
})
diff --git a/tests/extractor-edge-cases.test.js b/tests/extractor-edge-cases.test.js
index 95025fa73..412702b35 100644
--- a/tests/extractor-edge-cases.test.js
+++ b/tests/extractor-edge-cases.test.js
@@ -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`">Hello world
` },
- ],
- }
+crosscheck(() => {
+ test('PHP arrays', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`">Hello world
`,
+ },
+ ],
+ }
- 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`` }] }
-
- 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`` }] }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .content-\[\'hello\]\'\] {
+ --tw-content: 'hello]';
+ content: var(--tw-content);
+ }
+ `)
+ })
})
})
diff --git a/tests/flattenColorPalette.test.js b/tests/flattenColorPalette.test.js
index e8edcd9aa..dae66e806 100644
--- a/tests/flattenColorPalette.test.js
+++ b/tests/flattenColorPalette.test.js
@@ -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({})
-})
diff --git a/tests/format-variant-selector.test.js b/tests/format-variant-selector.test.js
index 91bff419d..b752a1cab 100644
--- a/tests/format-variant-selector.test.js
+++ b/tests/format-variant-selector.test.js
@@ -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'))
+ })
})
})
diff --git a/tests/generate-rules.test.js b/tests/generate-rules.test.js
index a997a46cd..3aa8592bc 100644
--- a/tests/generate-rules.test.js
+++ b/tests/generate-rules.test.js
@@ -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;
+ }
+ }
+ `)
+ })
})
diff --git a/tests/getClassList.test.js b/tests/getClassList.test.js
index 8fd2df6f1..adeb4f659 100644
--- a/tests/getClassList.test.js
+++ b/tests/getClassList.test.js
@@ -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')
+ })
})
diff --git a/tests/getSortOrder.test.js b/tests/getSortOrder.test.js
index b036273e1..c65df3b6b 100644
--- a/tests/getSortOrder.test.js
+++ b/tests/getSortOrder.test.js
@@ -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)
+ }
+ )
+})
diff --git a/tests/getVariants.test.js b/tests/getVariants.test.js
index af34fd0e3..8d79f8243 100644
--- a/tests/getVariants.test.js
+++ b/tests/getVariants.test.js
@@ -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)',
- ])
})
diff --git a/tests/import-syntax.test.js b/tests/import-syntax.test.js
index 99f5163f7..c10d8b142 100644
--- a/tests/import-syntax.test.js
+++ b/tests/import-syntax.test.js
@@ -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`
- Hello world!
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- function ({ addBase }) {
- addBase({
- h1: {
- fontSize: '32px',
- },
- })
- },
- ],
- }
+crosscheck(() => {
+ test('using @import instead of @tailwind', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+ Hello world!
+
+
+
+
+ `,
+ },
+ ],
+ 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;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/important-boolean.test.js b/tests/important-boolean.test.js
index 209707f42..48fa4e894 100644
--- a/tests/important-boolean.test.js
+++ b/tests/important-boolean.test.js
@@ -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`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- 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`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ 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`
-
-
- `
- )
-
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`
-
-
+ await fs.promises.writeFile(
+ config.content[0],
+ html`
+
+
+ `
+ )
+
+ 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`
+
+
+ `
+ )
+
+ 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)
+ })
})
})
diff --git a/tests/important-modifier-prefix.test.js b/tests/important-modifier-prefix.test.js
index 2c6a8bb62..782ec809e 100644
--- a/tests/important-modifier-prefix.test.js
+++ b/tests/important-modifier-prefix.test.js
@@ -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`
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+crosscheck(() => {
+ test('important modifier with prefix', () => {
+ let config = {
+ important: false,
+ prefix: 'tw-',
+ darkMode: 'class',
+ content: [
+ {
+ raw: 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}
- .\!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;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/important-modifier.test.js b/tests/important-modifier.test.js
index f6fd5f822..bde7eee2e 100644
--- a/tests/important-modifier.test.js
+++ b/tests/important-modifier.test.js
@@ -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`
-
-
-
-
-
-
-
- `,
- },
- ],
- 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`
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ 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;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/important-selector.test.js b/tests/important-selector.test.js
index de7f8d271..bd0b1b11b 100644
--- a/tests/important-selector.test.js
+++ b/tests/important-selector.test.js
@@ -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`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- function ({ addComponents, addUtilities }) {
- addComponents(
- {
- '.btn': {
- button: 'yes',
+crosscheck(() => {
+ test('important selector', () => {
+ let config = {
+ important: '#app',
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ 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;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/kitchen-sink.test.js b/tests/kitchen-sink.test.js
index 7c340da58..9fe307d74 100644
--- a/tests/kitchen-sink.test.js
+++ b/tests/kitchen-sink.test.js
@@ -1,901 +1,896 @@
-import { env } from '../src/lib/sharedState'
-import { run, html, css } from './util/run'
+import { crosscheck, run, html, css } from './util/run'
-afterEach(() => {
- env.OXIDE = false
-})
+crosscheck(() => {
+ test('it works', () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-let cases = env.ENGINE === 'oxide' ? [[true], [false]] : [[false]]
-test.each(cases)('it works (using Rust: %p)', (useOxide) => {
- env.OXIDE = useOxide
-
- let config = {
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- extend: {
- screens: {
- range: { min: '1280px', max: '1535px' },
- multi: [{ min: '640px', max: '767px' }, { max: '868px' }],
- },
- gradientColorStops: {
- foo: '#bada55',
- },
- backgroundImage: {
- 'hero--home-1': "url('/images/homepage-1.jpg')",
- },
- },
- },
- plugins: [
- function ({ addVariant }) {
- addVariant(
- 'foo',
- ({ container }) => {
- container.walkRules((rule) => {
- rule.selector = `.foo\\:${rule.selector.slice(1)}`
- rule.walkDecls((decl) => {
- decl.important = true
})
- })
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ extend: {
+ screens: {
+ range: { min: '1280px', max: '1535px' },
+ multi: [{ min: '640px', max: '767px' }, { max: '868px' }],
},
- { before: 'sm' }
- )
+ gradientColorStops: {
+ foo: '#bada55',
+ },
+ backgroundImage: {
+ 'hero--home-1': "url('/images/homepage-1.jpg')",
+ },
+ },
},
- function ({ addUtilities, addBase, theme }) {
- addBase({
- h1: {
- fontSize: theme('fontSize.2xl'),
- fontWeight: theme('fontWeight.bold'),
- '&:first-child': {
- marginTop: '0px',
+ plugins: [
+ function ({ addVariant }) {
+ addVariant(
+ 'foo',
+ ({ container }) => {
+ container.walkRules((rule) => {
+ rule.selector = `.foo\\:${rule.selector.slice(1)}`
+ rule.walkDecls((decl) => {
+ decl.important = true
+ })
+ })
},
- },
- })
- addUtilities(
- {
- '.magic-none': {
- magic: 'none',
+ { before: 'sm' }
+ )
+ },
+ function ({ addUtilities, addBase, theme }) {
+ addBase({
+ h1: {
+ fontSize: theme('fontSize.2xl'),
+ fontWeight: theme('fontWeight.bold'),
+ '&:first-child': {
+ marginTop: '0px',
+ },
},
- '.magic-tons': {
- magic: 'tons',
+ })
+ addUtilities(
+ {
+ '.magic-none': {
+ magic: 'none',
+ },
+ '.magic-tons': {
+ magic: 'tons',
+ },
},
- },
- ['responsive', 'hover']
- )
- },
- ],
- }
+ ['responsive', 'hover']
+ )
+ },
+ ],
+ }
- let input = css`
- @layer utilities {
- .custom-util {
- background: #abcdef;
+ let input = css`
+ @layer utilities {
+ .custom-util {
+ background: #abcdef;
+ }
+ *,
+ ::before,
+ ::after,
+ ::backdrop {
+ margin: 10px;
+ }
}
- *,
- ::before,
- ::after,
- ::backdrop {
- margin: 10px;
+ @layer components {
+ .test-apply-font-variant {
+ @apply ordinal tabular-nums;
+ }
+ .custom-component {
+ background: #123456;
+ }
+ *,
+ ::before,
+ ::after,
+ ::backdrop {
+ padding: 5px;
+ }
+ .foo .bg-black {
+ appearance: none;
+ }
}
- }
- @layer components {
- .test-apply-font-variant {
- @apply ordinal tabular-nums;
+ @layer base {
+ div {
+ background: #654321;
+ }
}
- .custom-component {
- background: #123456;
- }
- *,
- ::before,
- ::after,
- ::backdrop {
- padding: 5px;
- }
- .foo .bg-black {
- appearance: none;
- }
- }
- @layer base {
- div {
- background: #654321;
- }
- }
- .theme-test {
- font-family: theme('fontFamily.sans');
- color: theme('colors.blue.500');
- }
- @screen lg {
- .screen-test {
- color: purple;
- }
- }
- .apply-1 {
- @apply mt-6;
- }
- .apply-2 {
- @apply mt-6;
- }
- .apply-test {
- @apply mt-6 bg-pink-500 hover:font-bold focus:hover:font-bold sm:bg-green-500 sm:focus:even:bg-pink-200;
- }
- .apply-components {
- @apply container mx-auto;
- }
- .drop-empty-rules {
- @apply hover:font-bold;
- }
- .apply-group {
- @apply group-hover:font-bold;
- }
- .apply-dark-mode {
- @apply dark:font-bold;
- }
- .apply-with-existing:hover {
- @apply font-normal sm:bg-green-500;
- }
- .multiple,
- .selectors {
- @apply font-bold group-hover:font-normal;
- }
- .list {
- @apply space-y-4;
- }
- .nested {
- .example {
- @apply font-bold hover:font-normal;
- }
- }
- .apply-order-a {
- @apply m-5 mt-6;
- }
- .apply-order-b {
- @apply m-5 mt-6;
- }
- .apply-dark-group-example-a {
- @apply dark:group-hover:bg-green-500;
- }
- .crazy-example {
- @apply sm:motion-safe:group-active:focus:opacity-10;
- }
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
.theme-test {
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
- 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
- 'Segoe UI Symbol', 'Noto Color Emoji';
- color: #3b82f6;
+ font-family: theme('fontFamily.sans');
+ color: theme('colors.blue.500');
}
- @media (min-width: 1024px) {
+ @screen lg {
.screen-test {
color: purple;
}
}
.apply-1 {
- margin-top: 1.5rem;
+ @apply mt-6;
}
.apply-2 {
- margin-top: 1.5rem;
+ @apply mt-6;
}
.apply-test {
- margin-top: 1.5rem;
- --tw-bg-opacity: 1;
- background-color: rgb(236 72 153 / var(--tw-bg-opacity));
- }
- .apply-test:hover {
- font-weight: 700;
- }
- .apply-test:hover:focus {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .apply-test {
- --tw-bg-opacity: 1;
- background-color: rgb(34 197 94 / var(--tw-bg-opacity));
- }
- .apply-test:nth-child(even):focus {
- --tw-bg-opacity: 1;
- background-color: rgb(251 207 232 / var(--tw-bg-opacity));
- }
+ @apply mt-6 bg-pink-500 hover:font-bold focus:hover:font-bold sm:bg-green-500 sm:focus:even:bg-pink-200;
}
.apply-components {
- width: 100%;
+ @apply container mx-auto;
}
- @media (min-width: 640px) {
- .apply-components {
- max-width: 640px;
- }
+ .drop-empty-rules {
+ @apply hover:font-bold;
}
- @media (min-width: 768px) {
- .apply-components {
- max-width: 768px;
- }
+ .apply-group {
+ @apply group-hover:font-bold;
}
- @media (min-width: 1024px) {
- .apply-components {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .apply-components {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .apply-components {
- max-width: 1536px;
- }
- }
- .apply-components {
- margin-left: auto;
- margin-right: auto;
- }
- .drop-empty-rules:hover {
- font-weight: 700;
- }
- .group:hover .apply-group {
- font-weight: 700;
- }
- .dark .apply-dark-mode {
- font-weight: 700;
+ .apply-dark-mode {
+ @apply dark:font-bold;
}
.apply-with-existing:hover {
- font-weight: 400;
- }
- @media (min-width: 640px) {
- .apply-with-existing:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(34 197 94 / var(--tw-bg-opacity));
- }
+ @apply font-normal sm:bg-green-500;
}
.multiple,
.selectors {
- font-weight: 700;
+ @apply font-bold group-hover:font-normal;
}
- .group:hover .multiple,
- .group:hover .selectors {
- font-weight: 400;
- }
- .list > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 0;
- margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
- margin-bottom: calc(1rem * var(--tw-space-y-reverse));
+ .list {
+ @apply space-y-4;
}
.nested {
.example {
- font-weight: 700;
- }
- .example:hover {
- font-weight: 400;
+ @apply font-bold hover:font-normal;
}
}
.apply-order-a {
- margin: 1.25rem;
- margin-top: 1.5rem;
+ @apply m-5 mt-6;
}
.apply-order-b {
- margin: 1.25rem;
- margin-top: 1.5rem;
+ @apply m-5 mt-6;
}
- .dark .group:hover .apply-dark-group-example-a {
- --tw-bg-opacity: 1;
- background-color: rgb(34 197 94 / var(--tw-bg-opacity));
+ .apply-dark-group-example-a {
+ @apply dark:group-hover:bg-green-500;
}
- @media (min-width: 640px) {
- @media (prefers-reduced-motion: no-preference) {
- .group:active .crazy-example:focus {
- opacity: 0.1;
+ .crazy-example {
+ @apply sm:motion-safe:group-active:focus:opacity-10;
+ }
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .theme-test {
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
+ Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
+ 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+ color: #3b82f6;
+ }
+ @media (min-width: 1024px) {
+ .screen-test {
+ color: purple;
}
}
- }
- h1 {
- font-size: 1.5rem;
- font-weight: 700;
- }
- h1:first-child {
- margin-top: 0px;
- }
- div {
- background: #654321;
- }
- *,
- ::before,
- ::after {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- }
- ::backdrop {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- }
- .container {
- width: 100%;
- }
- @media (min-width: 640px) {
- .container {
- max-width: 640px;
+ .apply-1 {
+ margin-top: 1.5rem;
}
- }
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
+ .apply-2 {
+ margin-top: 1.5rem;
}
- }
- @media (min-width: 1024px) {
- .container {
- max-width: 1024px;
+ .apply-test {
+ margin-top: 1.5rem;
+ --tw-bg-opacity: 1;
+ background-color: rgb(236 72 153 / var(--tw-bg-opacity));
}
- }
- @media (min-width: 1280px) {
- .container {
- max-width: 1280px;
+ .apply-test:hover {
+ font-weight: 700;
}
- }
- @media (min-width: 1536px) {
- .container {
- max-width: 1536px;
+ .apply-test:hover:focus {
+ font-weight: 700;
}
- }
- .test-apply-font-variant {
- --tw-ordinal: ordinal;
- --tw-numeric-spacing: tabular-nums;
- font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
- var(--tw-numeric-spacing) var(--tw-numeric-fraction);
- }
- .custom-component {
- background: #123456;
- }
- *,
- ::before,
- ::after,
- ::backdrop {
- padding: 5px;
- }
- .foo .bg-black {
- appearance: none;
- }
- .inset-6 {
- top: 1.5rem;
- right: 1.5rem;
- bottom: 1.5rem;
- left: 1.5rem;
- }
- .inset-x-1 {
- left: 0.25rem;
- right: 0.25rem;
- }
- .end-8 {
- inset-inline-end: 2rem;
- }
- .start-4 {
- inset-inline-start: 1rem;
- }
- .mx-1 {
- margin-left: 0.25rem;
- margin-right: 0.25rem;
- }
- .me-8 {
- margin-inline-end: 2rem;
- }
- .ms-4 {
- margin-inline-start: 1rem;
- }
- .mt-6 {
- margin-top: 1.5rem;
- }
- .scale-50 {
- --tw-scale-x: 0.5;
- --tw-scale-y: 0.5;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .transform {
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
- grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
- }
- .rounded-e {
- border-start-end-radius: 0.25rem;
- border-end-end-radius: 0.25rem;
- }
- .rounded-s {
- border-start-start-radius: 0.25rem;
- border-end-start-radius: 0.25rem;
- }
- .rounded-es {
- border-end-start-radius: 0.25rem;
- }
- .rounded-ss {
- border-start-start-radius: 0.25rem;
- }
- .border-2 {
- border-width: 2px;
- }
- .border-e-4 {
- border-inline-end-width: 4px;
- }
- .border-s-0 {
- border-inline-start-width: 0px;
- }
- .border-black {
- --tw-border-opacity: 1;
- border-color: rgb(0 0 0 / var(--tw-border-opacity));
- }
- .border-e-red-400 {
- --tw-border-opacity: 1;
- border-inline-end-color: rgb(248 113 113 / var(--tw-border-opacity));
- }
- .border-s-green-500 {
- --tw-border-opacity: 1;
- border-inline-start-color: rgb(34 197 94 / var(--tw-border-opacity));
- }
- .bg-black {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
- }
- .bg-green-500 {
- --tw-bg-opacity: 1;
- background-color: rgb(34 197 94 / var(--tw-bg-opacity));
- }
- .bg-opacity-50 {
- --tw-bg-opacity: 0.5;
- }
- .bg-gradient-to-r {
- background-image: linear-gradient(to right, var(--tw-gradient-stops));
- }
- .bg-hero--home-1 {
- background-image: url('/images/homepage-1.jpg');
- }
- .from-foo {
- --tw-gradient-from: #bada55;
- --tw-gradient-to: rgb(186 218 85 / 0);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
- }
- .px-1 {
- padding-left: 0.25rem;
- padding-right: 0.25rem;
- }
- .pe-8 {
- padding-inline-end: 2rem;
- }
- .ps-4 {
- padding-inline-start: 1rem;
- }
- .pt-6 {
- padding-top: 1.5rem;
- }
- .text-center {
- text-align: center;
- }
- .font-medium {
- font-weight: 500;
- }
- .shadow-md {
- --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px 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-sm {
- --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
- --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- .magic-none {
- magic: none;
- }
- .magic-tons {
- magic: tons;
- }
- .custom-util {
- background: #abcdef;
- }
- *,
- ::before,
- ::after,
- ::backdrop {
- margin: 10px;
- }
- .first\:pt-0:first-child {
- padding-top: 0px;
- }
- .hover\:container:hover {
- width: 100%;
- }
- @media (min-width: 640px) {
- .hover\:container:hover {
- max-width: 640px;
+ @media (min-width: 640px) {
+ .apply-test {
+ --tw-bg-opacity: 1;
+ background-color: rgb(34 197 94 / var(--tw-bg-opacity));
+ }
+ .apply-test:nth-child(even):focus {
+ --tw-bg-opacity: 1;
+ background-color: rgb(251 207 232 / var(--tw-bg-opacity));
+ }
}
- }
- @media (min-width: 768px) {
- .hover\:container:hover {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .hover\:container:hover {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .hover\:container:hover {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .hover\:container:hover {
- max-width: 1536px;
- }
- }
- .hover\:scale-75:hover {
- --tw-scale-x: 0.75;
- --tw-scale-y: 0.75;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .hover\:font-bold:hover {
- font-weight: 700;
- }
- .hover\:shadow-lg:hover {
- --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
- 0 4px 6px -4px 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\:custom-util:hover {
- background: #abcdef;
- }
- .focus\:font-normal:focus {
- font-weight: 400;
- }
- .focus\:ring-2:focus {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
- }
- .focus\:ring-blue-500:focus {
- --tw-ring-opacity: 1;
- --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
- }
- .focus\:hover\:font-light:hover:focus {
- font-weight: 300;
- }
- .disabled\:font-bold:disabled {
- font-weight: 700;
- }
- .group:hover .group-hover\:opacity-100 {
- opacity: 1;
- }
- .group:hover .group-hover\:custom-util {
- background: #abcdef;
- }
- .group:active .group-active\:opacity-10 {
- opacity: 0.1;
- }
- .foo\:custom-util {
- background: #abcdef !important;
- }
- .foo\:hover\:custom-util:hover {
- background: #abcdef !important;
- }
- @media (prefers-reduced-motion: no-preference) {
- .motion-safe\:transition {
- transition-property: color, background-color, border-color, text-decoration-color, fill,
- stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
- }
- .motion-safe\:custom-util {
- background: #abcdef;
- }
- }
- @media (prefers-reduced-motion: reduce) {
- .motion-reduce\:transition {
- transition-property: color, background-color, border-color, text-decoration-color, fill,
- stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
- }
- }
- .dark .dark\:custom-util {
- background: #abcdef;
- }
- @media (min-width: 640px) {
- .sm\:container {
+ .apply-components {
width: 100%;
}
@media (min-width: 640px) {
- .sm\:container {
+ .apply-components {
max-width: 640px;
}
}
@media (min-width: 768px) {
- .sm\:container {
+ .apply-components {
max-width: 768px;
}
}
@media (min-width: 1024px) {
- .sm\:container {
+ .apply-components {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
- .sm\:container {
+ .apply-components {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
- .sm\:container {
+ .apply-components {
max-width: 1536px;
}
}
- .sm\:text-center {
- text-align: center;
+ .apply-components {
+ margin-left: auto;
+ margin-right: auto;
}
- .sm\:tabular-nums {
+ .drop-empty-rules:hover {
+ font-weight: 700;
+ }
+ .group:hover .apply-group {
+ font-weight: 700;
+ }
+ .dark .apply-dark-mode {
+ font-weight: 700;
+ }
+ .apply-with-existing:hover {
+ font-weight: 400;
+ }
+ @media (min-width: 640px) {
+ .apply-with-existing:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(34 197 94 / var(--tw-bg-opacity));
+ }
+ }
+ .multiple,
+ .selectors {
+ font-weight: 700;
+ }
+ .group:hover .multiple,
+ .group:hover .selectors {
+ font-weight: 400;
+ }
+ .list > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(1rem * var(--tw-space-y-reverse));
+ }
+ .nested {
+ .example {
+ font-weight: 700;
+ }
+ .example:hover {
+ font-weight: 400;
+ }
+ }
+ .apply-order-a {
+ margin: 1.25rem;
+ margin-top: 1.5rem;
+ }
+ .apply-order-b {
+ margin: 1.25rem;
+ margin-top: 1.5rem;
+ }
+ .dark .group:hover .apply-dark-group-example-a {
+ --tw-bg-opacity: 1;
+ background-color: rgb(34 197 94 / var(--tw-bg-opacity));
+ }
+ @media (min-width: 640px) {
+ @media (prefers-reduced-motion: no-preference) {
+ .group:active .crazy-example:focus {
+ opacity: 0.1;
+ }
+ }
+ }
+ h1 {
+ font-size: 1.5rem;
+ font-weight: 700;
+ }
+ h1:first-child {
+ margin-top: 0px;
+ }
+ div {
+ background: #654321;
+ }
+ *,
+ ::before,
+ ::after {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ }
+ ::backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ }
+ .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;
+ }
+ }
+ .test-apply-font-variant {
+ --tw-ordinal: ordinal;
--tw-numeric-spacing: tabular-nums;
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
}
- .sm\:custom-util {
- background: #abcdef;
+ .custom-component {
+ background: #123456;
}
- @media (prefers-reduced-motion: no-preference) {
- .group:active .sm\:motion-safe\:group-active\:focus\:opacity-10:focus {
- opacity: 0.1;
- }
+ *,
+ ::before,
+ ::after,
+ ::backdrop {
+ padding: 5px;
}
- }
- @media (min-width: 768px) {
- .md\:container {
- width: 100%;
+ .foo .bg-black {
+ appearance: none;
}
- @media (min-width: 640px) {
- .md\:container {
- max-width: 640px;
- }
+ .inset-6 {
+ top: 1.5rem;
+ right: 1.5rem;
+ bottom: 1.5rem;
+ left: 1.5rem;
}
- @media (min-width: 768px) {
- .md\:container {
- max-width: 768px;
- }
+ .inset-x-1 {
+ left: 0.25rem;
+ right: 0.25rem;
}
- @media (min-width: 1024px) {
- .md\:container {
- max-width: 1024px;
- }
+ .end-8 {
+ inset-inline-end: 2rem;
}
- @media (min-width: 1280px) {
- .md\:container {
- max-width: 1280px;
- }
+ .start-4 {
+ inset-inline-start: 1rem;
}
- @media (min-width: 1536px) {
- .md\:container {
- max-width: 1536px;
- }
+ .mx-1 {
+ margin-left: 0.25rem;
+ margin-right: 0.25rem;
}
- .md\:text-center {
+ .me-8 {
+ margin-inline-end: 2rem;
+ }
+ .ms-4 {
+ margin-inline-start: 1rem;
+ }
+ .mt-6 {
+ margin-top: 1.5rem;
+ }
+ .scale-50 {
+ --tw-scale-x: 0.5;
+ --tw-scale-y: 0.5;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .transform {
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
+ grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
+ }
+ .rounded-e {
+ border-start-end-radius: 0.25rem;
+ border-end-end-radius: 0.25rem;
+ }
+ .rounded-s {
+ border-start-start-radius: 0.25rem;
+ border-end-start-radius: 0.25rem;
+ }
+ .rounded-es {
+ border-end-start-radius: 0.25rem;
+ }
+ .rounded-ss {
+ border-start-start-radius: 0.25rem;
+ }
+ .border-2 {
+ border-width: 2px;
+ }
+ .border-e-4 {
+ border-inline-end-width: 4px;
+ }
+ .border-s-0 {
+ border-inline-start-width: 0px;
+ }
+ .border-black {
+ --tw-border-opacity: 1;
+ border-color: rgb(0 0 0 / var(--tw-border-opacity));
+ }
+ .border-e-red-400 {
+ --tw-border-opacity: 1;
+ border-inline-end-color: rgb(248 113 113 / var(--tw-border-opacity));
+ }
+ .border-s-green-500 {
+ --tw-border-opacity: 1;
+ border-inline-start-color: rgb(34 197 94 / var(--tw-border-opacity));
+ }
+ .bg-black {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ }
+ .bg-green-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(34 197 94 / var(--tw-bg-opacity));
+ }
+ .bg-opacity-50 {
+ --tw-bg-opacity: 0.5;
+ }
+ .bg-gradient-to-r {
+ background-image: linear-gradient(to right, var(--tw-gradient-stops));
+ }
+ .bg-hero--home-1 {
+ background-image: url('/images/homepage-1.jpg');
+ }
+ .from-foo {
+ --tw-gradient-from: #bada55;
+ --tw-gradient-to: rgb(186 218 85 / 0);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ }
+ .px-1 {
+ padding-left: 0.25rem;
+ padding-right: 0.25rem;
+ }
+ .pe-8 {
+ padding-inline-end: 2rem;
+ }
+ .ps-4 {
+ padding-inline-start: 1rem;
+ }
+ .pt-6 {
+ padding-top: 1.5rem;
+ }
+ .text-center {
text-align: center;
}
- .md\:opacity-50 {
- opacity: 0.5;
+ .font-medium {
+ font-weight: 500;
}
- .md\:shadow-sm {
+ .shadow-md {
+ --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
+ 0 2px 4px -2px 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-sm {
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
- .md\:hover\:border-r-blue-500\/30:hover {
- border-right-color: rgb(59 130 246 / 0.3);
+ .magic-none {
+ magic: none;
}
- .md\:hover\:opacity-20:hover {
- opacity: 0.2;
+ .magic-tons {
+ magic: tons;
+ }
+ .custom-util {
+ background: #abcdef;
+ }
+ *,
+ ::before,
+ ::after,
+ ::backdrop {
+ margin: 10px;
+ }
+ .first\:pt-0:first-child {
+ padding-top: 0px;
+ }
+ .hover\:container:hover {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
+ .hover\:container:hover {
+ max-width: 640px;
+ }
+ }
+ @media (min-width: 768px) {
+ .hover\:container:hover {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .hover\:container:hover {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .hover\:container:hover {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .hover\:container:hover {
+ max-width: 1536px;
+ }
+ }
+ .hover\:scale-75:hover {
+ --tw-scale-x: 0.75;
+ --tw-scale-y: 0.75;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .hover\:font-bold:hover {
+ font-weight: 700;
+ }
+ .hover\:shadow-lg:hover {
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
+ 0 4px 6px -4px 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\:custom-util:hover {
+ background: #abcdef;
+ }
+ .focus\:font-normal:focus {
+ font-weight: 400;
+ }
+ .focus\:ring-2:focus {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ .focus\:ring-blue-500:focus {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
+ }
+ .focus\:hover\:font-light:hover:focus {
+ font-weight: 300;
+ }
+ .disabled\:font-bold:disabled {
+ font-weight: 700;
+ }
+ .group:hover .group-hover\:opacity-100 {
+ opacity: 1;
+ }
+ .group:hover .group-hover\:custom-util {
+ background: #abcdef;
+ }
+ .group:active .group-active\:opacity-10 {
+ opacity: 0.1;
+ }
+ .foo\:custom-util {
+ background: #abcdef !important;
+ }
+ .foo\:hover\:custom-util:hover {
+ background: #abcdef !important;
}
@media (prefers-reduced-motion: no-preference) {
- .md\:motion-safe\:hover\:transition:hover {
+ .motion-safe\:transition {
transition-property: color, background-color, border-color, text-decoration-color, fill,
stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
- .dark .md\:dark\:motion-safe\:foo\:active\:custom-util:active {
- background: #abcdef !important;
+ .motion-safe\:custom-util {
+ background: #abcdef;
}
}
+ @media (prefers-reduced-motion: reduce) {
+ .motion-reduce\:transition {
+ transition-property: color, background-color, border-color, text-decoration-color, fill,
+ stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+ }
+ }
+ .dark .dark\:custom-util {
+ background: #abcdef;
+ }
@media (min-width: 640px) {
- .md\:sm\:text-center {
+ .sm\:container {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
+ .sm\:container {
+ max-width: 640px;
+ }
+ }
+ @media (min-width: 768px) {
+ .sm\:container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .sm\:container {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .sm\:container {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .sm\:container {
+ max-width: 1536px;
+ }
+ }
+ .sm\:text-center {
text-align: center;
}
+ .sm\:tabular-nums {
+ --tw-numeric-spacing: tabular-nums;
+ font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
+ var(--tw-numeric-spacing) var(--tw-numeric-fraction);
+ }
+ .sm\:custom-util {
+ background: #abcdef;
+ }
+ @media (prefers-reduced-motion: no-preference) {
+ .group:active .sm\:motion-safe\:group-active\:focus\:opacity-10:focus {
+ opacity: 0.1;
+ }
+ }
}
- }
- @media (min-width: 1280px) and (max-width: 1535px) {
- .range\:text-right {
- text-align: right;
+ @media (min-width: 768px) {
+ .md\:container {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
+ .md\:container {
+ max-width: 640px;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .md\:container {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .md\:container {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .md\:container {
+ max-width: 1536px;
+ }
+ }
+ .md\:text-center {
+ text-align: center;
+ }
+ .md\:opacity-50 {
+ opacity: 0.5;
+ }
+ .md\:shadow-sm {
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ .md\:hover\:border-r-blue-500\/30:hover {
+ border-right-color: rgb(59 130 246 / 0.3);
+ }
+ .md\:hover\:opacity-20:hover {
+ opacity: 0.2;
+ }
+ @media (prefers-reduced-motion: no-preference) {
+ .md\:motion-safe\:hover\:transition:hover {
+ transition-property: color, background-color, border-color, text-decoration-color,
+ fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+ }
+ .dark .md\:dark\:motion-safe\:foo\:active\:custom-util:active {
+ background: #abcdef !important;
+ }
+ }
+ @media (min-width: 640px) {
+ .md\:sm\:text-center {
+ text-align: center;
+ }
+ }
}
- }
- @media (min-width: 640px) and (max-width: 767px), (max-width: 868px) {
- .multi\:text-left {
- text-align: left;
+ @media (min-width: 1280px) and (max-width: 1535px) {
+ .range\:text-right {
+ text-align: right;
+ }
}
- }
- `)
+ @media (min-width: 640px) and (max-width: 767px), (max-width: 868px) {
+ .multi\:text-left {
+ text-align: left;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/layer-at-rules.test.js b/tests/layer-at-rules.test.js
index a0404cbf6..a894d22e9 100644
--- a/tests/layer-at-rules.test.js
+++ b/tests/layer-at-rules.test.js
@@ -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``,
- },
- ],
- 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``,
+ },
+ ],
+ 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``,
- },
- ],
- 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``,
+ },
+ ],
+ 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``,
- },
- ],
- 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``,
+ },
+ ],
+ 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`` },
- ],
- 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``,
+ },
+ ],
+ 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`` }],
- 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`` }],
+ 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;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/layer-without-tailwind.test.js b/tests/layer-without-tailwind.test.js
index 231f7a150..2e1e4dd82 100644
--- a/tests/layer-without-tailwind.test.js
+++ b/tests/layer-without-tailwind.test.js
@@ -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`` }],
- }
-
- let input = css`
- @layer components {
- .foo {
- color: black;
- }
+crosscheck(() => {
+ test('using @layer without @tailwind', async () => {
+ let config = {
+ content: [{ raw: html`` }],
}
- `
- 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`` }],
- }
-
- 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`` }],
}
- `
- 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`` }],
- }
-
- 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`` }],
}
- `
- 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`` }],
- }
-
- 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`` }],
+ }
+
+ let input = css`
@layer custom {
.foo {
color: black;
}
}
- `)
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @layer custom {
+ .foo {
+ color: black;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/match-components.test.js b/tests/match-components.test.js
index 04ec0ee63..f1801857d 100644
--- a/tests/match-components.test.js
+++ b/tests/match-components.test.js
@@ -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``,
+crosscheck(() => {
+ it('should be possible to matchComponents', () => {
+ let config = {
+ content: [
+ {
+ raw: html``,
+ },
+ ],
+ 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;
- }
- `)
+ )
})
})
diff --git a/tests/match-utilities.test.js b/tests/match-utilities.test.js
index 41ef29a29..9b3cd4c56 100644
--- a/tests/match-utilities.test.js
+++ b/tests/match-utilities.test.js
@@ -1,573 +1,575 @@
-import { run, html, css } from './util/run'
+import { crosscheck, run, html, css } from './util/run'
-test('match utilities with modifiers', async () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
+crosscheck(() => {
+ test('match utilities with modifiers', async () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ corePlugins: { preflight: false },
- plugins: [
- ({ matchUtilities }) => {
- matchUtilities(
- {
- test: (value, { modifier }) => ({
- color: `${value}_${modifier}`,
- }),
- },
- {
- values: {
- DEFAULT: 'default',
- bar: 'bar',
- '1': 'one',
- '2': 'two',
- '1/foo': 'onefoo',
- '[8]/[9]': 'eightnine',
+ plugins: [
+ ({ matchUtilities }) => {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({
+ color: `${value}_${modifier}`,
+ }),
},
- modifiers: 'any',
- }
- )
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .test {
- color: default_null;
+ {
+ values: {
+ DEFAULT: 'default',
+ bar: 'bar',
+ '1': 'one',
+ '2': 'two',
+ '1/foo': 'onefoo',
+ '[8]/[9]': 'eightnine',
+ },
+ modifiers: 'any',
+ }
+ )
+ },
+ ],
}
- .test-1\/\[foo\] {
- color: one_[foo];
- }
- .test-1\/foo {
- color: onefoo_null;
- }
- .test-2\/foo {
- color: two_foo;
- }
- .test-\[8\]\/\[9\] {
- color: eightnine_null;
- }
- .test\/\[foo\] {
- color: default_[foo];
- }
- .test\/foo {
- color: default_foo;
- }
- `)
-})
-test('match utilities with modifiers in the config', async () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
+ let input = css`
+ @tailwind utilities;
+ `
- plugins: [
- ({ matchUtilities }) => {
- matchUtilities(
- {
- test: (value, { modifier }) => ({
- color: `${value}_${modifier}`,
- }),
- },
- {
- values: {
- DEFAULT: 'default',
- bar: 'bar',
- '1': 'one',
- },
- modifiers: {
- foo: 'mewtwo',
- },
- }
- )
- },
- ],
- }
+ let result = await run(input, config)
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .test {
- color: default_null;
- }
- .test-1\/\[bar\] {
- color: one_bar;
- }
- .test-1\/foo {
- color: one_mewtwo;
- }
- .test\/\[bar\] {
- color: default_bar;
- }
- .test\/foo {
- color: default_mewtwo;
- }
- `)
-})
-
-test('match utilities can omit utilities by returning null', async () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
-
- plugins: [
- ({ matchUtilities }) => {
- matchUtilities(
- {
- test: (value, { modifier }) =>
- modifier === 'bad'
- ? null
- : {
- color: `${value}_${modifier}`,
- },
- },
- {
- values: {
- DEFAULT: 'default',
- bar: 'bar',
- '1': 'one',
- },
- modifiers: 'any',
- }
- )
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .test {
- color: default_null;
- }
- .test\/good {
- color: default_good;
- }
- `)
-})
-
-test('matching utilities with a basic configured value', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value) => ({ value }),
- },
- {
- values: {
- foo: 'value_foo',
- },
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo {
- value: value_foo;
+ expect(result.css).toMatchFormattedCss(css`
+ .test {
+ color: default_null;
+ }
+ .test-1\/\[foo\] {
+ color: one_[foo];
+ }
+ .test-1\/foo {
+ color: onefoo_null;
+ }
+ .test-2\/foo {
+ color: two_foo;
+ }
+ .test-\[8\]\/\[9\] {
+ color: eightnine_null;
+ }
+ .test\/\[foo\] {
+ color: default_[foo];
+ }
+ .test\/foo {
+ color: default_foo;
}
`)
})
-})
-test('matching utilities with an arbitrary value and configured modifier', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- modifiers: {
- bar: 'configured_bar',
+ test('match utilities with modifiers in the config', async () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ corePlugins: { preflight: false },
+
+ plugins: [
+ ({ matchUtilities }) => {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({
+ color: `${value}_${modifier}`,
+ }),
},
- }
- )
- },
- ],
- corePlugins: [],
- }
+ {
+ values: {
+ DEFAULT: 'default',
+ bar: 'bar',
+ '1': 'one',
+ },
+ modifiers: {
+ foo: 'mewtwo',
+ },
+ }
+ )
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/bar {
- value: foo;
- modifier: configured_bar;
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .test {
+ color: default_null;
+ }
+ .test-1\/\[bar\] {
+ color: one_bar;
+ }
+ .test-1\/foo {
+ color: one_mewtwo;
+ }
+ .test\/\[bar\] {
+ color: default_bar;
+ }
+ .test\/foo {
+ color: default_mewtwo;
}
`)
})
-})
-test('matching utilities with an configured value and an arbitrary modifier (raw)', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- foo: 'configured_foo',
+ test('match utilities can omit utilities by returning null', async () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ corePlugins: { preflight: false },
+
+ plugins: [
+ ({ matchUtilities }) => {
+ matchUtilities(
+ {
+ test: (value, { modifier }) =>
+ modifier === 'bad'
+ ? null
+ : {
+ color: `${value}_${modifier}`,
+ },
},
- modifiers: 'any', // Raw `[value]`
- }
- )
- },
- ],
- corePlugins: [],
- }
+ {
+ values: {
+ DEFAULT: 'default',
+ bar: 'bar',
+ '1': 'one',
+ },
+ modifiers: 'any',
+ }
+ )
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo\/\[bar\] {
- value: configured_foo;
- modifier: [bar];
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .test {
+ color: default_null;
+ }
+ .test\/good {
+ color: default_good;
}
`)
})
-})
-test('matching utilities with an configured value and an arbitrary modifier (non-raw)', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- foo: 'configured_foo',
+ test('matching utilities with a basic configured value', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value) => ({ value }),
},
- modifiers: {},
- }
- )
- },
- ],
- corePlugins: [],
- }
+ {
+ values: {
+ foo: 'value_foo',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo\/\[bar\] {
- value: configured_foo;
- modifier: bar;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-foo {
+ value: value_foo;
+ }
+ `)
+ })
})
-})
-test('matching utilities with an configured value and a configured modifier', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- foo: 'configured_foo',
+ test('matching utilities with an arbitrary value and configured modifier', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
},
- modifiers: {
- bar: 'configured_bar',
+ {
+ modifiers: {
+ bar: 'configured_bar',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-\[foo\]\/bar {
+ value: foo;
+ modifier: configured_bar;
+ }
+ `)
+ })
+ })
+
+ test('matching utilities with an configured value and an arbitrary modifier (raw)', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
},
- }
- )
- },
- ],
- corePlugins: [],
- }
+ {
+ values: {
+ foo: 'configured_foo',
+ },
+ modifiers: 'any', // Raw `[value]`
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo\/bar {
- value: configured_foo;
- modifier: configured_bar;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-foo\/\[bar\] {
+ value: configured_foo;
+ modifier: [bar];
+ }
+ `)
+ })
})
-})
-test('matching utilities with an arbitrary value and an arbitrary modifier (raw)', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- modifiers: 'any',
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/\[bar\] {
- value: foo;
- modifier: [bar];
- }
- `)
- })
-})
-
-test('matching utilities with an arbitrary value and an arbitrary modifier (non-raw)', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- modifiers: {},
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/\[bar\] {
- value: foo;
- modifier: bar;
- }
- `)
- })
-})
-
-test('matching utilities with a lookup value that looks like an arbitrary value and modifier', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- '[foo]/[bar]': 'hello',
+ test('matching utilities with an configured value and an arbitrary modifier (non-raw)', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
},
- }
- )
- },
- ],
- corePlugins: [],
- }
+ {
+ values: {
+ foo: 'configured_foo',
+ },
+ modifiers: {},
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/\[bar\] {
- value: hello;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-foo\/\[bar\] {
+ value: configured_foo;
+ modifier: bar;
+ }
+ `)
+ })
})
-})
-test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = any)', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- '[foo]/[bar]': 'hello',
+ test('matching utilities with an configured value and a configured modifier', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
},
- modifiers: 'any',
- }
- )
- },
- ],
- corePlugins: [],
- }
+ {
+ values: {
+ foo: 'configured_foo',
+ },
+ modifiers: {
+ bar: 'configured_bar',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/\[bar\] {
- value: hello;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-foo\/bar {
+ value: configured_foo;
+ modifier: configured_bar;
+ }
+ `)
+ })
})
-})
-test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = {})', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- '[foo]/[bar]': 'hello',
+ test('matching utilities with an arbitrary value and an arbitrary modifier (raw)', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
},
- modifiers: {},
- }
- )
- },
- ],
- corePlugins: [],
- }
+ {
+ modifiers: 'any',
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/\[bar\] {
- value: hello;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-\[foo\]\/\[bar\] {
+ value: foo;
+ modifier: [bar];
+ }
+ `)
+ })
})
-})
-test('matching utilities with a lookup value that looks like an arbitrary value and a configured modifier', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- '[foo]/bar': 'hello',
+ test('matching utilities with an arbitrary value and an arbitrary modifier (non-raw)', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
},
- }
- )
- },
- ],
- corePlugins: [],
- }
+ {
+ modifiers: {},
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/bar {
- value: hello;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-\[foo\]\/\[bar\] {
+ value: foo;
+ modifier: bar;
+ }
+ `)
+ })
})
-})
-test('matching utilities with a lookup value that looks like a configured value and an arbitrary modifier', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- 'foo/[bar]': 'hello',
+ test('matching utilities with a lookup value that looks like an arbitrary value and modifier', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
},
- }
- )
- },
- ],
- corePlugins: [],
- }
+ {
+ values: {
+ '[foo]/[bar]': 'hello',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo\/\[bar\] {
- value: hello;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-\[foo\]\/\[bar\] {
+ value: hello;
+ }
+ `)
+ })
})
-})
-test('matching utilities with a lookup value that does not match the configured type', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- foo: 'not-a-percentage',
+ test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = any)', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
},
- type: ['percentage'],
- }
- )
- },
- ],
- corePlugins: [],
- }
+ {
+ values: {
+ '[foo]/[bar]': 'hello',
+ },
+ modifiers: 'any',
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo {
- value: not-a-percentage;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-\[foo\]\/\[bar\] {
+ value: hello;
+ }
+ `)
+ })
+ })
+
+ test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = {})', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ '[foo]/[bar]': 'hello',
+ },
+ modifiers: {},
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-\[foo\]\/\[bar\] {
+ value: hello;
+ }
+ `)
+ })
+ })
+
+ test('matching utilities with a lookup value that looks like an arbitrary value and a configured modifier', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ '[foo]/bar': 'hello',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-\[foo\]\/bar {
+ value: hello;
+ }
+ `)
+ })
+ })
+
+ test('matching utilities with a lookup value that looks like a configured value and an arbitrary modifier', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ 'foo/[bar]': 'hello',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-foo\/\[bar\] {
+ value: hello;
+ }
+ `)
+ })
+ })
+
+ test('matching utilities with a lookup value that does not match the configured type', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ foo: 'not-a-percentage',
+ },
+ type: ['percentage'],
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .test-foo {
+ value: not-a-percentage;
+ }
+ `)
+ })
})
})
diff --git a/tests/match-variants.test.js b/tests/match-variants.test.js
index ad08042af..41e7ca509 100644
--- a/tests/match-variants.test.js
+++ b/tests/match-variants.test.js
@@ -1,846 +1,850 @@
import resolveConfig from '../src/public/resolve-config'
import { createContext } from '../src/lib/setupContextUtils'
-import { run, html, css } from './util/run'
+import { crosscheck, run, html, css } from './util/run'
-test('partial arbitrary variants', () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('potato', (flavor) => `.potato-${flavor} &`)
- },
- ],
- }
+crosscheck(() => {
+ test('partial arbitrary variants', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('potato', (flavor) => `.potato-${flavor} &`)
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .potato-baked .potato-\[baked\]\:w-3 {
- width: 0.75rem;
- }
-
- .potato-yellow .potato-\[yellow\]\:bg-yellow-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 240 138 / var(--tw-bg-opacity));
- }
- `)
- })
-})
-
-test('partial arbitrary variants with at-rules', () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('potato', (flavor) => `@media (potato: ${flavor})`)
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (potato: baked) {
- .potato-\[baked\]\:w-3 {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .potato-baked .potato-\[baked\]\:w-3 {
width: 0.75rem;
}
- }
- @media (potato: yellow) {
- .potato-\[yellow\]\:bg-yellow-200 {
+
+ .potato-yellow .potato-\[yellow\]\:bg-yellow-200 {
--tw-bg-opacity: 1;
background-color: rgb(254 240 138 / var(--tw-bg-opacity));
}
- }
- `)
+ `)
+ })
})
-})
-test('partial arbitrary variants with at-rules and placeholder', () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('potato', (flavor) => `@media (potato: ${flavor}) { &:potato }`)
- },
- ],
- }
+ test('partial arbitrary variants with at-rules', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('potato', (flavor) => `@media (potato: ${flavor})`)
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (potato: baked) {
- .potato-\[baked\]\:w-3:potato {
- width: 0.75rem;
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (potato: baked) {
+ .potato-\[baked\]\:w-3 {
+ width: 0.75rem;
+ }
}
- }
- @media (potato: yellow) {
- .potato-\[yellow\]\:bg-yellow-200:potato {
- --tw-bg-opacity: 1;
- background-color: rgb(254 240 138 / var(--tw-bg-opacity));
+ @media (potato: yellow) {
+ .potato-\[yellow\]\:bg-yellow-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 240 138 / var(--tw-bg-opacity));
+ }
}
- }
- `)
+ `)
+ })
})
-})
-test('partial arbitrary variants with default values', () => {
- let config = {
- content: [
- {
- raw: html``,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('tooltip', (side) => `&${side}`, {
- values: {
- bottom: '[data-location="bottom"]',
- top: '[data-location="top"]',
- },
- })
- },
- ],
- }
+ test('partial arbitrary variants with at-rules and placeholder', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('potato', (flavor) => `@media (potato: ${flavor}) { &:potato }`)
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tooltip-bottom\:mt-2[data-location='bottom'] {
- margin-top: 0.5rem;
- }
-
- .tooltip-top\:mb-2[data-location='top'] {
- margin-bottom: 0.5rem;
- }
- `)
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (potato: baked) {
+ .potato-\[baked\]\:w-3:potato {
+ width: 0.75rem;
+ }
+ }
+ @media (potato: yellow) {
+ .potato-\[yellow\]\:bg-yellow-200:potato {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 240 138 / var(--tw-bg-opacity));
+ }
+ }
+ `)
+ })
})
-})
-test('matched variant values maintain the sort order they are registered in', () => {
- let config = {
- content: [
- {
- raw: html``,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('alphabet', (side) => `&${side}`, {
- values: {
- a: '[data-value="a"]',
- b: '[data-value="b"]',
- c: '[data-value="c"]',
- d: '[data-value="d"]',
- },
- })
- },
- ],
- }
+ test('partial arbitrary variants with default values', () => {
+ let config = {
+ content: [
+ {
+ raw: html``,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('tooltip', (side) => `&${side}`, {
+ values: {
+ bottom: '[data-location="bottom"]',
+ top: '[data-location="top"]',
+ },
+ })
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .alphabet-a\:underline[data-value='a'] {
- text-decoration-line: underline;
- }
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tooltip-bottom\:mt-2[data-location='bottom'] {
+ margin-top: 0.5rem;
+ }
- .alphabet-b\:underline[data-value='b'] {
- text-decoration-line: underline;
- }
-
- .alphabet-c\:underline[data-value='c'] {
- text-decoration-line: underline;
- }
-
- .alphabet-d\:underline[data-value='d'] {
- text-decoration-line: underline;
- }
- `)
+ .tooltip-top\:mb-2[data-location='top'] {
+ margin-bottom: 0.5rem;
+ }
+ `)
+ })
})
-})
-test('matchVariant can return an array of format strings from the function', () => {
- let config = {
- content: [
- {
- raw: html``,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('test', (selector) =>
- selector.split(',').map((selector) => `&.${selector} > *`)
- )
- },
- ],
- }
+ test('matched variant values maintain the sort order they are registered in', () => {
+ let config = {
+ content: [
+ {
+ raw: html``,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('alphabet', (side) => `&${side}`, {
+ values: {
+ a: '[data-value="a"]',
+ b: '[data-value="b"]',
+ c: '[data-value="c"]',
+ d: '[data-value="d"]',
+ },
+ })
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .test-\[a\2c b\2c c\]\:underline.a > * {
- text-decoration-line: underline;
- }
-
- .test-\[a\2c b\2c c\]\:underline.b > * {
- text-decoration-line: underline;
- }
-
- .test-\[a\2c b\2c c\]\:underline.c > * {
- text-decoration-line: underline;
- }
- `)
- })
-})
-
-it('should be possible to sort variants', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 500px) {
- .testmin-\[500px\]\:underline {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .alphabet-a\:underline[data-value='a'] {
text-decoration-line: underline;
}
- }
- @media (min-width: 700px) {
- .testmin-\[700px\]\:italic {
- font-style: italic;
- }
- }
- `)
- })
-})
-
-it('should be possible to compare arbitrary variants and hardcoded variants', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- values: {
- example: '600px',
- },
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 500px) {
- .testmin-\[500px\]\:italic {
- font-style: italic;
- }
- }
-
- @media (min-width: 600px) {
- .testmin-example\:italic {
- font-style: italic;
- }
- }
-
- @media (min-width: 700px) {
- .testmin-\[700px\]\:italic {
- font-style: italic;
- }
- }
- `)
- })
-})
-
-it('should be possible to sort stacked arbitrary variants correctly', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
-
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 100px) {
- @media (max-width: 400px) {
- .testmin-\[100px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
- }
- @media (max-width: 350px) {
- .testmin-\[100px\]\:testmax-\[350px\]\:underline {
- text-decoration-line: underline;
- }
- }
- @media (max-width: 300px) {
- .testmin-\[100px\]\:testmax-\[300px\]\:underline {
- text-decoration-line: underline;
- }
- }
- }
-
- @media (min-width: 150px) {
- @media (max-width: 400px) {
- .testmin-\[150px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
- }
- }
- `)
- })
-})
-
-it('should maintain sort from other variants, if sort functions of arbitrary variants return 0', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
-
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 100px) {
- @media (max-width: 200px) {
- .testmin-\[100px\]\:testmax-\[200px\]\:hover\:underline:hover {
- text-decoration-line: underline;
- }
- .testmin-\[100px\]\:testmax-\[200px\]\:focus\:underline:focus {
- text-decoration-line: underline;
- }
- }
- }
- `)
- })
-})
-
-it('should sort arbitrary variants left to right (1)', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 100px) {
- @media (max-width: 400px) {
- .testmin-\[100px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
+ .alphabet-b\:underline[data-value='b'] {
+ text-decoration-line: underline;
}
- @media (max-width: 300px) {
- .testmin-\[100px\]\:testmax-\[300px\]\:underline {
- text-decoration-line: underline;
- }
- }
- }
-
- @media (min-width: 200px) {
- @media (max-width: 400px) {
- .testmin-\[200px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
+ .alphabet-c\:underline[data-value='c'] {
+ text-decoration-line: underline;
}
- @media (max-width: 300px) {
- .testmin-\[200px\]\:testmax-\[300px\]\:underline {
- text-decoration-line: underline;
- }
+ .alphabet-d\:underline[data-value='d'] {
+ text-decoration-line: underline;
}
- }
- `)
- })
-})
-
-it('should sort arbitrary variants left to right (2)', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (max-width: 400px) {
- @media (min-width: 100px) {
- .testmax-\[400px\]\:testmin-\[100px\]\:underline {
- text-decoration-line: underline;
- }
- }
- @media (min-width: 200px) {
- .testmax-\[400px\]\:testmin-\[200px\]\:underline {
- text-decoration-line: underline;
- }
- }
- }
-
- @media (max-width: 300px) {
- @media (min-width: 100px) {
- .testmax-\[300px\]\:testmin-\[100px\]\:underline {
- text-decoration-line: underline;
- }
- }
- @media (min-width: 200px) {
- .testmax-\[300px\]\:testmin-\[200px\]\:underline {
- text-decoration-line: underline;
- }
- }
- }
- `)
- })
-})
-
-it('should guarantee that we are not passing values from other variants to the wrong function', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- let lookup = ['100px', '200px']
- if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
- throw new Error('We are seeing values that should not be there!')
- }
- return lookup.indexOf(a.value) - lookup.indexOf(z.value)
- },
- })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- let lookup = ['300px', '400px']
- if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
- throw new Error('We are seeing values that should not be there!')
- }
- return lookup.indexOf(z.value) - lookup.indexOf(a.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 100px) {
- @media (max-width: 400px) {
- .testmin-\[100px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
- }
-
- @media (max-width: 300px) {
- .testmin-\[100px\]\:testmax-\[300px\]\:underline {
- text-decoration-line: underline;
- }
- }
- }
-
- @media (min-width: 200px) {
- @media (max-width: 400px) {
- .testmin-\[200px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
- }
-
- @media (max-width: 300px) {
- .testmin-\[200px\]\:testmax-\[300px\]\:underline {
- text-decoration-line: underline;
- }
- }
- }
- `)
- })
-})
-
-it('should default to the DEFAULT value for variants', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('foo', (value) => `.foo${value} &`, {
- values: {
- DEFAULT: '.bar',
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo.bar .foo\:underline {
- text-decoration-line: underline;
- }
- `)
- })
-})
-
-it('should not generate anything if the matchVariant does not have a DEFAULT value configured', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('foo', (value) => `.foo${value} &`)
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css``)
- })
-})
-
-it('should be possible to use `null` as a DEFAULT value', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, {
- values: { DEFAULT: null },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo-good .foo\:underline {
- text-decoration-line: underline;
- }
- `)
- })
-})
-
-it('should be possible to use `undefined` as a DEFAULT value', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
- values: { DEFAULT: undefined },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo-good .foo\:underline {
- text-decoration-line: underline;
- }
- `)
- })
-})
-
-it('should be possible to use `undefined` as a DEFAULT value', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
- values: { DEFAULT: undefined },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo-good .foo\:underline {
- text-decoration-line: underline;
- }
- `)
- })
-})
-
-it('should not break things', () => {
- let config = {}
-
- let context = createContext(resolveConfig(config))
- let [[, fn]] = context.variantMap.get('group')
-
- let format
-
- expect(
- fn({
- format(input) {
- format = input
- },
+ `)
})
- ).toBe(undefined)
+ })
- expect(format).toBe(':merge(.group) &')
+ test('matchVariant can return an array of format strings from the function', () => {
+ let config = {
+ content: [
+ {
+ raw: html``,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('test', (selector) =>
+ selector.split(',').map((selector) => `&.${selector} > *`)
+ )
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-\[a\2c b\2c c\]\:underline.a > * {
+ text-decoration-line: underline;
+ }
+
+ .test-\[a\2c b\2c c\]\:underline.b > * {
+ text-decoration-line: underline;
+ }
+
+ .test-\[a\2c b\2c c\]\:underline.c > * {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+ })
+
+ it('should be possible to sort variants', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 500px) {
+ .testmin-\[500px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+
+ @media (min-width: 700px) {
+ .testmin-\[700px\]\:italic {
+ font-style: italic;
+ }
+ }
+ `)
+ })
+ })
+
+ it('should be possible to compare arbitrary variants and hardcoded variants', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ values: {
+ example: '600px',
+ },
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 500px) {
+ .testmin-\[500px\]\:italic {
+ font-style: italic;
+ }
+ }
+
+ @media (min-width: 600px) {
+ .testmin-example\:italic {
+ font-style: italic;
+ }
+ }
+
+ @media (min-width: 700px) {
+ .testmin-\[700px\]\:italic {
+ font-style: italic;
+ }
+ }
+ `)
+ })
+ })
+
+ it('should be possible to sort stacked arbitrary variants correctly', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 100px) {
+ @media (max-width: 400px) {
+ .testmin-\[100px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ @media (max-width: 350px) {
+ .testmin-\[100px\]\:testmax-\[350px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ @media (max-width: 300px) {
+ .testmin-\[100px\]\:testmax-\[300px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+
+ @media (min-width: 150px) {
+ @media (max-width: 400px) {
+ .testmin-\[150px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ `)
+ })
+ })
+
+ it('should maintain sort from other variants, if sort functions of arbitrary variants return 0', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 100px) {
+ @media (max-width: 200px) {
+ .testmin-\[100px\]\:testmax-\[200px\]\:hover\:underline:hover {
+ text-decoration-line: underline;
+ }
+ .testmin-\[100px\]\:testmax-\[200px\]\:focus\:underline:focus {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ `)
+ })
+ })
+
+ it('should sort arbitrary variants left to right (1)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 100px) {
+ @media (max-width: 400px) {
+ .testmin-\[100px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+
+ @media (max-width: 300px) {
+ .testmin-\[100px\]\:testmax-\[300px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+
+ @media (min-width: 200px) {
+ @media (max-width: 400px) {
+ .testmin-\[200px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+
+ @media (max-width: 300px) {
+ .testmin-\[200px\]\:testmax-\[300px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ `)
+ })
+ })
+
+ it('should sort arbitrary variants left to right (2)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (max-width: 400px) {
+ @media (min-width: 100px) {
+ .testmax-\[400px\]\:testmin-\[100px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ @media (min-width: 200px) {
+ .testmax-\[400px\]\:testmin-\[200px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+
+ @media (max-width: 300px) {
+ @media (min-width: 100px) {
+ .testmax-\[300px\]\:testmin-\[100px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ @media (min-width: 200px) {
+ .testmax-\[300px\]\:testmin-\[200px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ `)
+ })
+ })
+
+ it('should guarantee that we are not passing values from other variants to the wrong function', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ let lookup = ['100px', '200px']
+ if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
+ throw new Error('We are seeing values that should not be there!')
+ }
+ return lookup.indexOf(a.value) - lookup.indexOf(z.value)
+ },
+ })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ let lookup = ['300px', '400px']
+ if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
+ throw new Error('We are seeing values that should not be there!')
+ }
+ return lookup.indexOf(z.value) - lookup.indexOf(a.value)
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 100px) {
+ @media (max-width: 400px) {
+ .testmin-\[100px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+
+ @media (max-width: 300px) {
+ .testmin-\[100px\]\:testmax-\[300px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+
+ @media (min-width: 200px) {
+ @media (max-width: 400px) {
+ .testmin-\[200px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+
+ @media (max-width: 300px) {
+ .testmin-\[200px\]\:testmax-\[300px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ `)
+ })
+ })
+
+ it('should default to the DEFAULT value for variants', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('foo', (value) => `.foo${value} &`, {
+ values: {
+ DEFAULT: '.bar',
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo.bar .foo\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+ })
+
+ it('should not generate anything if the matchVariant does not have a DEFAULT value configured', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('foo', (value) => `.foo${value} &`)
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css``)
+ })
+ })
+
+ it('should be possible to use `null` as a DEFAULT value', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, {
+ values: { DEFAULT: null },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo-good .foo\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+ })
+
+ it('should be possible to use `undefined` as a DEFAULT value', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
+ values: { DEFAULT: undefined },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo-good .foo\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+ })
+
+ it('should be possible to use `undefined` as a DEFAULT value', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
+ values: { DEFAULT: undefined },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo-good .foo\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+ })
+
+ it('should not break things', () => {
+ let config = {}
+
+ let context = createContext(resolveConfig(config))
+ let [[, fn]] = context.variantMap.get('group')
+
+ let format
+
+ expect(
+ fn({
+ format(input) {
+ format = input
+ },
+ })
+ ).toBe(undefined)
+
+ expect(format).toBe(':merge(.group) &')
+ })
})
diff --git a/tests/min-max-screen-variants.test.js b/tests/min-max-screen-variants.test.js
index 0a6a5fc7f..f55789aa7 100644
--- a/tests/min-max-screen-variants.test.js
+++ b/tests/min-max-screen-variants.test.js
@@ -1,212 +1,311 @@
-import { run, css, html } 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()
+ })
-let defaultScreens = {
- sm: '640px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
-}
-
-it('sorts min and max correctly relative to screens and each other', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: defaultScreens,
- },
+ let defaultScreens = {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
}
- let input = css`
- @tailwind utilities;
- `
+ it('sorts min and max correctly relative to screens and each other', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: defaultScreens,
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (max-width: 800px) {
+ .max-\[800px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (max-width: 700px) {
+ .max-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 700px) {
+ .min-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 800px) {
+ .min-\[800px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+ })
+
+ it('works when using min variants screens config is empty and variants all use the same unit', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {},
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
- return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
- @media (max-width: 800px) {
- .max-\[800px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (max-width: 700px) {
- .max-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
@media (min-width: 700px) {
.min-\[700px\]\:font-bold {
font-weight: 700;
}
}
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
@media (min-width: 800px) {
.min-\[800px\]\:font-bold {
font-weight: 700;
}
}
`)
+
+ expect(warn).toHaveBeenCalledTimes(0)
})
-})
-it('works when using min variants screens config is empty and variants all use the same unit', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
+ it('works when using max variants screens config is empty and variants all use the same unit', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {},
},
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {},
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
}
- @media (min-width: 700px) {
- .min-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 800px) {
- .min-\[800px\]\:font-bold {
- font-weight: 700;
- }
- }
- `)
- expect(warn).toHaveBeenCalledTimes(0)
-})
+ let input = css`
+ @tailwind utilities;
+ `
-it('works when using max variants screens config is empty and variants all use the same unit', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (max-width: 800px) {
+ .max-\[800px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (max-width: 700px) {
+ .max-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+ })
+
+ it('converts simple min-width screens for max variant', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: defaultScreens,
},
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {},
- },
- }
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media not all and (min-width: 1024px) {
+ .max-lg\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (max-width: 700px) {
+ .max-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media not all and (min-width: 640px) {
+ .max-sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (max-width: 300px) {
+ .max-\[300px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+ })
+
+ it('does not have keyed screens for min variant', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: defaultScreens,
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 300px) {
+ .min-\[300px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 700px) {
+ .min-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+ })
+
+ it('warns when using min variants with complex screen configs', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+
+ // Any presence of an object makes it complex
+ yodawg: { min: '700px' },
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
- return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
- @media (max-width: 800px) {
- .max-\[800px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (max-width: 700px) {
- .max-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
-})
-
-it('converts simple min-width screens for max variant', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: defaultScreens,
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media not all and (min-width: 1024px) {
- .max-lg\:font-bold {
- font-weight: 700;
- }
- }
- @media (max-width: 700px) {
- .max-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media not all and (min-width: 640px) {
- .max-sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (max-width: 300px) {
- .max-\[300px\]\:font-bold {
- font-weight: 700;
- }
- }
@media (min-width: 640px) {
.sm\:font-bold {
font-weight: 700;
@@ -218,445 +317,360 @@ it('converts simple min-width screens for max variant', () => {
}
}
`)
+
+ expect(warn).toHaveBeenCalledTimes(1)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual(['complex-screen-config'])
})
-})
-it('does not have keyed screens for min variant', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
+ it('warns when using min variants with simple configs containing mixed units', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '48rem',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+ },
},
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: defaultScreens,
- },
- }
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
- return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
- @media (min-width: 300px) {
- .min-\[300px\]\:font-bold {
- font-weight: 700;
- }
- }
@media (min-width: 640px) {
.sm\:font-bold {
font-weight: 700;
}
}
+ @media (min-width: 48rem) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect(warn).toHaveBeenCalledTimes(1)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual(['mixed-screen-units'])
+ })
+
+ it('warns when using min variants with mixed units (with screens config)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect(warn).toHaveBeenCalledTimes(1)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual(['minmax-have-mixed-units'])
+ })
+
+ it('warns when using min variants with mixed units (with no screens config)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {},
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
@media (min-width: 700px) {
.min-\[700px\]\:font-bold {
font-weight: 700;
}
}
+ `)
+
+ expect(warn).toHaveBeenCalledTimes(1)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual(['minmax-have-mixed-units'])
+ })
+
+ it('warns when using max variants with complex screen configs', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+
+ // Any presence of an object makes it complex
+ yodawg: { min: '700px' },
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
@media (min-width: 768px) {
.md\:font-bold {
font-weight: 700;
}
}
`)
+
+ expect(warn).toHaveBeenCalledTimes(1)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual(['complex-screen-config'])
+ })
+
+ it('warns when using max variants with simple configs containing mixed units', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '48rem',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 48rem) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect(warn).toHaveBeenCalledTimes(1)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual(['mixed-screen-units'])
+ })
+
+ it('warns when using max variants with mixed units (with screens config)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect(warn).toHaveBeenCalledTimes(1)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual(['minmax-have-mixed-units'])
+ })
+
+ it('warns when using max variants with mixed units (with no screens config)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {},
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (max-width: 700px) {
+ .max-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect(warn).toHaveBeenCalledTimes(1)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual(['minmax-have-mixed-units'])
+ })
+
+ it('warns when using min and max variants with mixed units (with no screens config)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {},
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (max-width: 700rem) {
+ .max-\[700rem\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect(warn).toHaveBeenCalledTimes(1)
+ expect(warn.mock.calls.map((x) => x[0])).toEqual(['minmax-have-mixed-units'])
})
})
-
-it('warns when using min variants with complex screen configs', async () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
-
- // Any presence of an object makes it complex
- yodawg: { min: '700px' },
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect(warn).toHaveBeenCalledTimes(1)
- expect(warn.mock.calls.map((x) => x[0])).toEqual(['complex-screen-config'])
-})
-
-it('warns when using min variants with simple configs containing mixed units', async () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '48rem',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 48rem) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect(warn).toHaveBeenCalledTimes(1)
- expect(warn.mock.calls.map((x) => x[0])).toEqual(['mixed-screen-units'])
-})
-
-it('warns when using min variants with mixed units (with screens config)', async () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect(warn).toHaveBeenCalledTimes(1)
- expect(warn.mock.calls.map((x) => x[0])).toEqual(['minmax-have-mixed-units'])
-})
-
-it('warns when using min variants with mixed units (with no screens config)', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {},
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 700px) {
- .min-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect(warn).toHaveBeenCalledTimes(1)
- expect(warn.mock.calls.map((x) => x[0])).toEqual(['minmax-have-mixed-units'])
-})
-
-it('warns when using max variants with complex screen configs', async () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
-
- // Any presence of an object makes it complex
- yodawg: { min: '700px' },
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect(warn).toHaveBeenCalledTimes(1)
- expect(warn.mock.calls.map((x) => x[0])).toEqual(['complex-screen-config'])
-})
-
-it('warns when using max variants with simple configs containing mixed units', async () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '48rem',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 48rem) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect(warn).toHaveBeenCalledTimes(1)
- expect(warn.mock.calls.map((x) => x[0])).toEqual(['mixed-screen-units'])
-})
-
-it('warns when using max variants with mixed units (with screens config)', async () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect(warn).toHaveBeenCalledTimes(1)
- expect(warn.mock.calls.map((x) => x[0])).toEqual(['minmax-have-mixed-units'])
-})
-
-it('warns when using max variants with mixed units (with no screens config)', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {},
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (max-width: 700px) {
- .max-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect(warn).toHaveBeenCalledTimes(1)
- expect(warn.mock.calls.map((x) => x[0])).toEqual(['minmax-have-mixed-units'])
-})
-
-it('warns when using min and max variants with mixed units (with no screens config)', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {},
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (max-width: 700rem) {
- .max-\[700rem\]\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect(warn).toHaveBeenCalledTimes(1)
- expect(warn.mock.calls.map((x) => x[0])).toEqual(['minmax-have-mixed-units'])
-})
diff --git a/tests/minimum-impact-selector.test.js b/tests/minimum-impact-selector.test.js
index d8292e467..ed2fad914 100644
--- a/tests/minimum-impact-selector.test.js
+++ b/tests/minimum-impact-selector.test.js
@@ -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)
+ })
})
diff --git a/tests/modify-selectors.test.js b/tests/modify-selectors.test.js
index c440fab3f..a5e3ffaae 100644
--- a/tests/modify-selectors.test.js
+++ b/tests/modify-selectors.test.js
@@ -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`
-
-
-
-
-
-
-
Lorem ipsum dolor sit amet...
-
-
-
Lorem ipsum dolor sit amet...
-
-
-
Lorem ipsum dolor sit amet...
-
-
-
Lorem ipsum dolor sit amet...
-
- `,
- },
- ],
- 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`
+
+
+
+
+
+
+
Lorem ipsum dolor sit amet...
+
+
+
Lorem ipsum dolor sit amet...
+
+
+
Lorem ipsum dolor sit amet...
+
+
+
Lorem ipsum dolor sit amet...
+
+ `,
+ },
+ ],
+ 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;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/mutable.test.js b/tests/mutable.test.js
index 5c3f9a487..45419380f 100644
--- a/tests/mutable.test.js
+++ b/tests/mutable.test.js
@@ -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`` }],
- theme: {
- backgroundImage: {
- foo: 'url("./foo.png")',
+ test('plugins mutating rules after tailwind doesnt break it', async () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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)
})
diff --git a/tests/negateValue.test.js b/tests/negateValue.test.js
index c31741d48..052132f91 100644
--- a/tests/negateValue.test.js
+++ b/tests/negateValue.test.js
@@ -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()
+ })
})
diff --git a/tests/negated-content.test.js b/tests/negated-content.test.js
index 6d46e3a8d..66e231946 100644
--- a/tests/negated-content.test.js
+++ b/tests/negated-content.test.js
@@ -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;
+ }
+ `)
+ })
})
})
diff --git a/tests/negative-prefix.test.js b/tests/negative-prefix.test.js
index d28a6496e..af4196397 100644
--- a/tests/negative-prefix.test.js
+++ b/tests/negative-prefix.test.js
@@ -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`` }],
- theme: {
- margin: {
- 2: '8px',
- '-2': '-4px',
+crosscheck(() => {
+ test('using a negative prefix with a negative scale value', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- 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`` }],
+ 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`` }],
- theme: {
- margin: {
- 5: '20px',
+ test('using a negative prefix without a negative scale value', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- theme: {
- margin: {
- '-[10px]': '55px',
+ test('being an asshole', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- theme: {
- margin: {
- '[10px]': '55px',
+ test('being a real asshole', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- theme: {
- margin: {
- 5: 'var(--sizing-5)',
+ test('a value that includes a variable', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- theme: {
- margin: {
- 5: 'calc(52px * -3)',
+ test('a value that includes a calc', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- 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`` },
+ ],
+ 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`` }],
- theme: {
- margin: {
- auto: 'auto',
+ test('a keyword value', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- theme: {
- margin: {
- 0: '0',
+ test('a zero value', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- theme: {
- colors: {
- red: 'red',
+ test('a color', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- }
+ test('arbitrary values', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ }
- 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`` }],
- theme: {
- margin: {
- weird: '-15px',
+ test('negating a negative scale value', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- theme: {
- margin: {
- DEFAULT: '15px',
+ test('negating a default value', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- theme: {
- margin: {
- DEFAULT: '8px',
- '-DEFAULT': '-4px',
+ test('using a negative prefix with a negative default scale value', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- theme: {
- margin: {
- DEFAULT: '15px',
+ test('negating a default value with a configured prefix', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`` }],
+ 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`` }],
- }
-
- 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`` }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css``)
+ })
})
})
diff --git a/tests/normalize-config.test.js b/tests/normalize-config.test.js
index 8b21b2e64..6edb6f626 100644
--- a/tests/normalize-config.test.js
+++ b/tests/normalize-config.test.js
@@ -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()
-})
diff --git a/tests/normalize-data-types.test.js b/tests/normalize-data-types.test.js
index d99ce1c18..eff2fa677 100644
--- a/tests/normalize-data-types.test.js
+++ b/tests/normalize-data-types.test.js
@@ -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)
+ })
})
diff --git a/tests/normalize-screens.test.js b/tests/normalize-screens.test.js
index da31c50d3..464623f9f 100644
--- a/tests/normalize-screens.test.js
+++ b/tests/normalize-screens.test.js
@@ -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' }] },
+ ])
+ })
})
diff --git a/tests/opacity.test.js b/tests/opacity.test.js
index c456a4087..f31981a43 100644
--- a/tests/opacity.test.js
+++ b/tests/opacity.test.js
@@ -1,1032 +1,1034 @@
-import { run, html, css } from './util/run'
+import { crosscheck, run, html, css } from './util/run'
-test('opacity', () => {
- let config = {
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
- `,
+crosscheck(() => {
+ test('opacity', () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: {
+ backgroundOpacity: false,
+ borderOpacity: false,
+ divideOpacity: false,
+ placeholderOpacity: false,
+ textOpacity: false,
},
- ],
- corePlugins: {
- backgroundOpacity: false,
- borderOpacity: false,
- divideOpacity: false,
- placeholderOpacity: false,
- textOpacity: false,
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .divide-black > :not([hidden]) ~ :not([hidden]) {
- border-color: #000;
- }
- .border-black {
- border-color: #000;
- }
- .bg-black {
- background-color: #000;
- }
- .text-black {
- color: #000;
- }
- .placeholder-black::placeholder {
- color: #000;
- }
- `)
- })
-})
-
-test('colors defined as functions work when opacity plugins are disabled', () => {
- let config = {
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
- `,
- },
- ],
- theme: {
- colors: {
- primary: ({ opacityValue }) =>
- opacityValue === undefined
- ? 'rgb(var(--color-primary))'
- : `rgb(var(--color-primary) / ${opacityValue})`,
- },
- },
- corePlugins: {
- backgroundOpacity: false,
- borderOpacity: false,
- divideOpacity: false,
- placeholderOpacity: false,
- textOpacity: false,
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(var(--color-primary));
- }
- .border-primary {
- border-color: rgb(var(--color-primary));
- }
- .bg-primary {
- background-color: rgb(var(--color-primary));
- }
- .text-primary {
- color: rgb(var(--color-primary));
- }
- .placeholder-primary::placeholder {
- color: rgb(var(--color-primary));
- }
- `)
- })
-})
-
-it('can use defining custom properties for colors (opacity plugins enabled)', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 1;
- border-color: rgb(var(--color-primary) / var(--tw-divide-opacity));
- }
- .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 0.5;
- }
- .border-primary {
- --tw-border-opacity: 1;
- border-color: rgb(var(--color-primary) / var(--tw-border-opacity));
- }
- .border-opacity-50 {
- --tw-border-opacity: 0.5;
- }
- .bg-primary {
- --tw-bg-opacity: 1;
- background-color: rgb(var(--color-primary) / var(--tw-bg-opacity));
- }
- .bg-opacity-50 {
- --tw-bg-opacity: 0.5;
- }
- .text-primary {
- --tw-text-opacity: 1;
- color: rgb(var(--color-primary) / var(--tw-text-opacity));
- }
- .text-opacity-50 {
- --tw-text-opacity: 0.5;
- }
- .placeholder-primary::placeholder {
- --tw-placeholder-opacity: 1;
- color: rgb(var(--color-primary) / var(--tw-placeholder-opacity));
- }
- .placeholder-opacity-50::placeholder {
- --tw-placeholder-opacity: 0.5;
- }
- .ring-primary {
- --tw-ring-opacity: 1;
- --tw-ring-color: rgb(var(--color-primary) / var(--tw-ring-opacity));
- }
- .ring-opacity-50 {
- --tw-ring-opacity: 0.5;
- }
- `)
- })
-})
-
-it('can use rgb helper when defining custom properties for colors (opacity plugins disabled)', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- corePlugins: {
- backgroundOpacity: false,
- borderOpacity: false,
- divideOpacity: false,
- placeholderOpacity: false,
- textOpacity: false,
- ringOpacity: false,
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(var(--color-primary) / 1);
- }
- .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(var(--color-primary) / 0.5);
- }
- .border-primary {
- border-color: rgb(var(--color-primary) / 1);
- }
- .border-primary\/50 {
- border-color: rgb(var(--color-primary) / 0.5);
- }
- .bg-primary {
- background-color: rgb(var(--color-primary) / 1);
- }
- .bg-primary\/50 {
- background-color: rgb(var(--color-primary) / 0.5);
- }
- .text-primary {
- color: rgb(var(--color-primary) / 1);
- }
- .text-primary\/50 {
- color: rgb(var(--color-primary) / 0.5);
- }
- .placeholder-primary::placeholder {
- color: rgb(var(--color-primary) / 1);
- }
- .placeholder-primary\/50::placeholder {
- color: rgb(var(--color-primary) / 0.5);
- }
- .ring-primary {
- --tw-ring-color: rgb(var(--color-primary) / 1);
- }
- .ring-primary\/50 {
- --tw-ring-color: rgb(var(--color-primary) / 0.5);
- }
- `)
- })
-})
-
-it('can use hsl helper when defining custom properties for colors (opacity plugins enabled)', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- theme: {
- colors: {
- primary: 'hsl(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 1;
- border-color: hsl(var(--color-primary) / var(--tw-divide-opacity));
- }
- .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 0.5;
- }
- .border-primary {
- --tw-border-opacity: 1;
- border-color: hsl(var(--color-primary) / var(--tw-border-opacity));
- }
- .border-opacity-50 {
- --tw-border-opacity: 0.5;
- }
- .bg-primary {
- --tw-bg-opacity: 1;
- background-color: hsl(var(--color-primary) / var(--tw-bg-opacity));
- }
- .bg-opacity-50 {
- --tw-bg-opacity: 0.5;
- }
- .text-primary {
- --tw-text-opacity: 1;
- color: hsl(var(--color-primary) / var(--tw-text-opacity));
- }
- .text-opacity-50 {
- --tw-text-opacity: 0.5;
- }
- .placeholder-primary::placeholder {
- --tw-placeholder-opacity: 1;
- color: hsl(var(--color-primary) / var(--tw-placeholder-opacity));
- }
- .placeholder-opacity-50::placeholder {
- --tw-placeholder-opacity: 0.5;
- }
- .ring-primary {
- --tw-ring-opacity: 1;
- --tw-ring-color: hsl(var(--color-primary) / var(--tw-ring-opacity));
- }
- .ring-opacity-50 {
- --tw-ring-opacity: 0.5;
- }
- `)
- })
-})
-
-it('can use hsl helper when defining custom properties for colors (opacity plugins disabled)', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- theme: {
- colors: {
- primary: 'hsl(var(--color-primary) / )',
- },
- },
- corePlugins: {
- backgroundOpacity: false,
- borderOpacity: false,
- divideOpacity: false,
- placeholderOpacity: false,
- textOpacity: false,
- ringOpacity: false,
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]) {
- border-color: hsl(var(--color-primary) / 1);
- }
- .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) {
- border-color: hsl(var(--color-primary) / 0.5);
- }
- .border-primary {
- border-color: hsl(var(--color-primary) / 1);
- }
- .border-primary\/50 {
- border-color: hsl(var(--color-primary) / 0.5);
- }
- .bg-primary {
- background-color: hsl(var(--color-primary) / 1);
- }
- .bg-primary\/50 {
- background-color: hsl(var(--color-primary) / 0.5);
- }
- .text-primary {
- color: hsl(var(--color-primary) / 1);
- }
- .text-primary\/50 {
- color: hsl(var(--color-primary) / 0.5);
- }
- .placeholder-primary::placeholder {
- color: hsl(var(--color-primary) / 1);
- }
- .placeholder-primary\/50::placeholder {
- color: hsl(var(--color-primary) / 0.5);
- }
- .ring-primary {
- --tw-ring-color: hsl(var(--color-primary) / 1);
- }
- .ring-primary\/50 {
- --tw-ring-color: hsl(var(--color-primary) / 0.5);
- }
- `)
- })
-})
-
-test('Theme function in JS can apply alpha values to colors (1)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: rgb(59 130 246 / 50%);
}
- `
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / 50%'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .divide-black > :not([hidden]) ~ :not([hidden]) {
+ border-color: #000;
+ }
+ .border-black {
+ border-color: #000;
+ }
+ .bg-black {
+ background-color: #000;
+ }
+ .text-black {
+ color: #000;
+ }
+ .placeholder-black::placeholder {
+ color: #000;
+ }
+ `)
+ })
})
-})
-test('Theme function in JS can apply alpha values to colors (2)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: rgb(59 130 246 / 0.5);
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / 0.5'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function in JS can apply alpha values to colors (3)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: rgb(59 130 246 / var(--my-alpha));
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / var(--my-alpha)'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function in JS can apply alpha values to colors (4)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: hsl(217 91% 60% / 50%);
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / 50%'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function in JS can apply alpha values to colors (5)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: hsl(217 91% 60% / 0.5);
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / 0.5'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function in JS can apply alpha values to colors (6)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: hsl(217 91% 60% / var(--my-alpha));
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / var(--my-alpha)'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function in JS can apply alpha values to colors (7)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: rgb(var(--foo) / var(--my-alpha));
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: {
- blue: {
- 500: 'rgb(var(--foo) / )',
+ test('colors defined as functions work when opacity plugins are disabled', () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+ `,
+ },
+ ],
+ theme: {
+ colors: {
+ primary: ({ opacityValue }) =>
+ opacityValue === undefined
+ ? 'rgb(var(--color-primary))'
+ : `rgb(var(--color-primary) / ${opacityValue})`,
},
},
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / var(--my-alpha)'),
- }),
+ corePlugins: {
+ backgroundOpacity: false,
+ borderOpacity: false,
+ divideOpacity: false,
+ placeholderOpacity: false,
+ textOpacity: false,
},
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
-})
-
-test('Theme function prefers existing values in config', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: purple;
}
- `
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: {
- blue: {
- '500 / 50%': 'purple',
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .divide-primary > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(var(--color-primary));
+ }
+ .border-primary {
+ border-color: rgb(var(--color-primary));
+ }
+ .bg-primary {
+ background-color: rgb(var(--color-primary));
+ }
+ .text-primary {
+ color: rgb(var(--color-primary));
+ }
+ .placeholder-primary::placeholder {
+ color: rgb(var(--color-primary));
+ }
+ `)
+ })
+ })
+
+ it('can use defining custom properties for colors (opacity plugins enabled)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
},
},
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / 50%'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .divide-primary > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 1;
+ border-color: rgb(var(--color-primary) / var(--tw-divide-opacity));
+ }
+ .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 0.5;
+ }
+ .border-primary {
+ --tw-border-opacity: 1;
+ border-color: rgb(var(--color-primary) / var(--tw-border-opacity));
+ }
+ .border-opacity-50 {
+ --tw-border-opacity: 0.5;
+ }
+ .bg-primary {
+ --tw-bg-opacity: 1;
+ background-color: rgb(var(--color-primary) / var(--tw-bg-opacity));
+ }
+ .bg-opacity-50 {
+ --tw-bg-opacity: 0.5;
+ }
+ .text-primary {
+ --tw-text-opacity: 1;
+ color: rgb(var(--color-primary) / var(--tw-text-opacity));
+ }
+ .text-opacity-50 {
+ --tw-text-opacity: 0.5;
+ }
+ .placeholder-primary::placeholder {
+ --tw-placeholder-opacity: 1;
+ color: rgb(var(--color-primary) / var(--tw-placeholder-opacity));
+ }
+ .placeholder-opacity-50::placeholder {
+ --tw-placeholder-opacity: 0.5;
+ }
+ .ring-primary {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(var(--color-primary) / var(--tw-ring-opacity));
+ }
+ .ring-opacity-50 {
+ --tw-ring-opacity: 0.5;
+ }
+ `)
+ })
})
-})
-it('should be possible to use an as part of the color definition', () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: ['backgroundColor', 'backgroundOpacity'],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .bg-primary {
- --tw-bg-opacity: 1;
- background-color: rgb(var(--color-primary) / var(--tw-bg-opacity));
- }
- `)
- })
-})
-
-it('should be possible to use an as part of the color definition with an opacity modifiers', () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: ['backgroundColor', 'backgroundOpacity'],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .bg-primary\/50 {
- background-color: rgb(var(--color-primary) / 0.5);
- }
- `)
- })
-})
-
-it('should be possible to use an as part of the color definition with an opacity modifiers', () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: ['backgroundColor'],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .bg-primary {
- background-color: rgb(var(--color-primary) / 1);
- }
- `)
- })
-})
-
-it('should be possible to use inside arbitrary values', () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- corePlugins: ['backgroundColor', 'backgroundOpacity'],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .bg-\[rgb\(var\(--color-primary\)\/\\)\]\/50 {
- background-color: rgb(var(--color-primary) / 0.5);
- }
- `)
- })
-})
-
-it('Theme functions can reference values with slashes in brackets', () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- theme: {
- colors: {
- 'a/b': '#000000',
- },
- extend: {
- backgroundColor: ({ theme }) => ({
- foo1: theme('colors[a/b]'),
- foo2: theme('colors[a/b]/50%'),
- }),
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .bg-foo1 {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
- }
- .bg-foo2 {
- background-color: rgb(0 0 0 / 50%);
- }
- `)
- })
-})
-
-it('works with opacity values defined as a placeholder or a function in when colors is a function', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- theme: {
- colors: () => ({
- foobar1: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`,
- foobar2: `rgb(255 100 0 / )`,
- foobar3: {
- 100: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`,
- 200: `rgb(255 100 0 / )`,
+ it('can use rgb helper when defining custom properties for colors (opacity plugins disabled)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
},
- }),
- extend: {
- backgroundColor: ({ theme }) => ({
- foo10: theme('colors.foobar1'),
- foo20: theme('colors.foobar2'),
- foo30: theme('colors.foobar3.100'),
- foo40: theme('colors.foobar3.200'),
- foo11: theme('colors.foobar1 / 50%'),
- foo21: theme('colors.foobar2 / 50%'),
- foo31: theme('colors.foobar3.100 / 50%'),
- foo41: theme('colors.foobar3.200 / 50%'),
+ ],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ corePlugins: {
+ backgroundOpacity: false,
+ borderOpacity: false,
+ divideOpacity: false,
+ placeholderOpacity: false,
+ textOpacity: false,
+ ringOpacity: false,
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .divide-primary > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(var(--color-primary) / 1);
+ }
+ .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(var(--color-primary) / 0.5);
+ }
+ .border-primary {
+ border-color: rgb(var(--color-primary) / 1);
+ }
+ .border-primary\/50 {
+ border-color: rgb(var(--color-primary) / 0.5);
+ }
+ .bg-primary {
+ background-color: rgb(var(--color-primary) / 1);
+ }
+ .bg-primary\/50 {
+ background-color: rgb(var(--color-primary) / 0.5);
+ }
+ .text-primary {
+ color: rgb(var(--color-primary) / 1);
+ }
+ .text-primary\/50 {
+ color: rgb(var(--color-primary) / 0.5);
+ }
+ .placeholder-primary::placeholder {
+ color: rgb(var(--color-primary) / 1);
+ }
+ .placeholder-primary\/50::placeholder {
+ color: rgb(var(--color-primary) / 0.5);
+ }
+ .ring-primary {
+ --tw-ring-color: rgb(var(--color-primary) / 1);
+ }
+ .ring-primary\/50 {
+ --tw-ring-color: rgb(var(--color-primary) / 0.5);
+ }
+ `)
+ })
+ })
+
+ it('can use hsl helper when defining custom properties for colors (opacity plugins enabled)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ theme: {
+ colors: {
+ primary: 'hsl(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .divide-primary > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 1;
+ border-color: hsl(var(--color-primary) / var(--tw-divide-opacity));
+ }
+ .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 0.5;
+ }
+ .border-primary {
+ --tw-border-opacity: 1;
+ border-color: hsl(var(--color-primary) / var(--tw-border-opacity));
+ }
+ .border-opacity-50 {
+ --tw-border-opacity: 0.5;
+ }
+ .bg-primary {
+ --tw-bg-opacity: 1;
+ background-color: hsl(var(--color-primary) / var(--tw-bg-opacity));
+ }
+ .bg-opacity-50 {
+ --tw-bg-opacity: 0.5;
+ }
+ .text-primary {
+ --tw-text-opacity: 1;
+ color: hsl(var(--color-primary) / var(--tw-text-opacity));
+ }
+ .text-opacity-50 {
+ --tw-text-opacity: 0.5;
+ }
+ .placeholder-primary::placeholder {
+ --tw-placeholder-opacity: 1;
+ color: hsl(var(--color-primary) / var(--tw-placeholder-opacity));
+ }
+ .placeholder-opacity-50::placeholder {
+ --tw-placeholder-opacity: 0.5;
+ }
+ .ring-primary {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: hsl(var(--color-primary) / var(--tw-ring-opacity));
+ }
+ .ring-opacity-50 {
+ --tw-ring-opacity: 0.5;
+ }
+ `)
+ })
+ })
+
+ it('can use hsl helper when defining custom properties for colors (opacity plugins disabled)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ theme: {
+ colors: {
+ primary: 'hsl(var(--color-primary) / )',
+ },
+ },
+ corePlugins: {
+ backgroundOpacity: false,
+ borderOpacity: false,
+ divideOpacity: false,
+ placeholderOpacity: false,
+ textOpacity: false,
+ ringOpacity: false,
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .divide-primary > :not([hidden]) ~ :not([hidden]) {
+ border-color: hsl(var(--color-primary) / 1);
+ }
+ .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) {
+ border-color: hsl(var(--color-primary) / 0.5);
+ }
+ .border-primary {
+ border-color: hsl(var(--color-primary) / 1);
+ }
+ .border-primary\/50 {
+ border-color: hsl(var(--color-primary) / 0.5);
+ }
+ .bg-primary {
+ background-color: hsl(var(--color-primary) / 1);
+ }
+ .bg-primary\/50 {
+ background-color: hsl(var(--color-primary) / 0.5);
+ }
+ .text-primary {
+ color: hsl(var(--color-primary) / 1);
+ }
+ .text-primary\/50 {
+ color: hsl(var(--color-primary) / 0.5);
+ }
+ .placeholder-primary::placeholder {
+ color: hsl(var(--color-primary) / 1);
+ }
+ .placeholder-primary\/50::placeholder {
+ color: hsl(var(--color-primary) / 0.5);
+ }
+ .ring-primary {
+ --tw-ring-color: hsl(var(--color-primary) / 1);
+ }
+ .ring-primary\/50 {
+ --tw-ring-color: hsl(var(--color-primary) / 0.5);
+ }
+ `)
+ })
+ })
+
+ test('Theme function in JS can apply alpha values to colors (1)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: rgb(59 130 246 / 50%);
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / 50%'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function in JS can apply alpha values to colors (2)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: rgb(59 130 246 / 0.5);
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / 0.5'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function in JS can apply alpha values to colors (3)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: rgb(59 130 246 / var(--my-alpha));
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / var(--my-alpha)'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function in JS can apply alpha values to colors (4)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: hsl(217 91% 60% / 50%);
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / 50%'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function in JS can apply alpha values to colors (5)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: hsl(217 91% 60% / 0.5);
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / 0.5'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function in JS can apply alpha values to colors (6)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: hsl(217 91% 60% / var(--my-alpha));
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / var(--my-alpha)'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function in JS can apply alpha values to colors (7)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: rgb(var(--foo) / var(--my-alpha));
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: {
+ blue: {
+ 500: 'rgb(var(--foo) / )',
+ },
+ },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / var(--my-alpha)'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ test('Theme function prefers existing values in config', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: purple;
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: {
+ blue: {
+ '500 / 50%': 'purple',
+ },
+ },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / 50%'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+ })
+
+ it('should be possible to use an as part of the color definition', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ corePlugins: ['backgroundColor', 'backgroundOpacity'],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .bg-primary {
+ --tw-bg-opacity: 1;
+ background-color: rgb(var(--color-primary) / var(--tw-bg-opacity));
+ }
+ `)
+ })
+ })
+
+ it('should be possible to use an as part of the color definition with an opacity modifiers', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ corePlugins: ['backgroundColor', 'backgroundOpacity'],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .bg-primary\/50 {
+ background-color: rgb(var(--color-primary) / 0.5);
+ }
+ `)
+ })
+ })
+
+ it('should be possible to use an as part of the color definition with an opacity modifiers', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ corePlugins: ['backgroundColor'],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .bg-primary {
+ background-color: rgb(var(--color-primary) / 1);
+ }
+ `)
+ })
+ })
+
+ it('should be possible to use inside arbitrary values', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ corePlugins: ['backgroundColor', 'backgroundOpacity'],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .bg-\[rgb\(var\(--color-primary\)\/\\)\]\/50 {
+ background-color: rgb(var(--color-primary) / 0.5);
+ }
+ `)
+ })
+ })
+
+ it('Theme functions can reference values with slashes in brackets', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ theme: {
+ colors: {
+ 'a/b': '#000000',
+ },
+ extend: {
+ backgroundColor: ({ theme }) => ({
+ foo1: theme('colors[a/b]'),
+ foo2: theme('colors[a/b]/50%'),
+ }),
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .bg-foo1 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ }
+ .bg-foo2 {
+ background-color: rgb(0 0 0 / 50%);
+ }
+ `)
+ })
+ })
+
+ it('works with opacity values defined as a placeholder or a function in when colors is a function', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ theme: {
+ colors: () => ({
+ foobar1: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`,
+ foobar2: `rgb(255 100 0 / )`,
+ foobar3: {
+ 100: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`,
+ 200: `rgb(255 100 0 / )`,
+ },
}),
+ extend: {
+ backgroundColor: ({ theme }) => ({
+ foo10: theme('colors.foobar1'),
+ foo20: theme('colors.foobar2'),
+ foo30: theme('colors.foobar3.100'),
+ foo40: theme('colors.foobar3.200'),
+ foo11: theme('colors.foobar1 / 50%'),
+ foo21: theme('colors.foobar2 / 50%'),
+ foo31: theme('colors.foobar3.100 / 50%'),
+ foo41: theme('colors.foobar3.200 / 50%'),
+ }),
+ },
},
- },
- }
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .bg-foo10 {
- background-color: rgb(255 100 0 / 100%);
- }
- .bg-foo11 {
- background-color: rgb(255 100 0 / 50%);
- }
- .bg-foo20 {
- --tw-bg-opacity: 1;
- background-color: rgb(255 100 0 / var(--tw-bg-opacity));
- }
- .bg-foo21 {
- background-color: rgb(255 100 0 / 50%);
- }
- .bg-foo30 {
- background-color: rgb(255 100 0 / 100%);
- }
- .bg-foo31 {
- background-color: rgb(255 100 0 / 50%);
- }
- .bg-foo40 {
- --tw-bg-opacity: 1;
- background-color: rgb(255 100 0 / var(--tw-bg-opacity));
- }
- .bg-foo41 {
- background-color: rgb(255 100 0 / 50%);
- }
- `)
- })
-})
-
-it('The disableColorOpacityUtilitiesByDefault flag disables the color opacity plugins and removes their variables', () => {
- let config = {
- future: {
- disableColorOpacityUtilitiesByDefault: true,
- },
- content: [
- {
- raw: html`
-
-
-
-
- `,
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .divide-blue-300 > :not([hidden]) ~ :not([hidden]) {
- border-color: #93c5fd;
- }
- .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(147 197 253 / 0.5);
- }
- .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(147 197 253 / var(--my-opacity));
- }
- .border-blue-300 {
- border-color: #93c5fd;
- }
- .border-blue-300\/50 {
- border-color: rgb(147 197 253 / 0.5);
- }
- .border-blue-300\/\[var\(--my-opacity\)\] {
- border-color: rgb(147 197 253 / var(--my-opacity));
- }
- .bg-blue-300 {
- background-color: #93c5fd;
- }
- .bg-blue-300\/50 {
- background-color: rgb(147 197 253 / 0.5);
- }
- .bg-blue-300\/\[var\(--my-opacity\)\] {
- background-color: rgb(147 197 253 / var(--my-opacity));
- }
- .text-blue-300 {
- color: #93c5fd;
- }
- .text-blue-300\/50 {
- color: rgb(147 197 253 / 0.5);
- }
- .text-blue-300\/\[var\(--my-opacity\)\] {
- color: rgb(147 197 253 / var(--my-opacity));
- }
- .placeholder-blue-300::placeholder {
- color: #93c5fd;
- }
- .placeholder-blue-300\/50::placeholder {
- color: rgb(147 197 253 / 0.5);
- }
- .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder {
- color: rgb(147 197 253 / var(--my-opacity));
- }
- .ring-blue-300 {
- --tw-ring-color: #93c5fd;
- }
- .ring-blue-300\/50 {
- --tw-ring-color: rgb(147 197 253 / 0.5);
- }
- .ring-blue-300\/\[var\(--my-opacity\)\] {
- --tw-ring-color: rgb(147 197 253 / var(--my-opacity));
- }
- `)
- })
-})
-
-it('You can re-enable any opacity plugin even when disableColorOpacityUtilitiesByDefault is enabled', () => {
- let config = {
- future: {
- disableColorOpacityUtilitiesByDefault: true,
- },
- corePlugins: {
- backgroundOpacity: true,
- borderOpacity: true,
- divideOpacity: true,
- placeholderOpacity: true,
- ringOpacity: true,
- textOpacity: true,
- },
- content: [
- {
- raw: html`
-
-
-
-
- `,
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .divide-blue-300 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 1;
- border-color: rgb(147 197 253 / var(--tw-divide-opacity));
- }
- .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(147 197 253 / 0.5);
- }
- .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(147 197 253 / var(--my-opacity));
- }
- .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 0.5;
- }
- .border-blue-300 {
- --tw-border-opacity: 1;
- border-color: rgb(147 197 253 / var(--tw-border-opacity));
- }
- .border-blue-300\/50 {
- border-color: rgb(147 197 253 / 0.5);
- }
- .border-blue-300\/\[var\(--my-opacity\)\] {
- border-color: rgb(147 197 253 / var(--my-opacity));
- }
- .border-opacity-50 {
- --tw-border-opacity: 0.5;
- }
- .bg-blue-300 {
- --tw-bg-opacity: 1;
- background-color: rgb(147 197 253 / var(--tw-bg-opacity));
- }
- .bg-blue-300\/50 {
- background-color: rgb(147 197 253 / 0.5);
- }
- .bg-blue-300\/\[var\(--my-opacity\)\] {
- background-color: rgb(147 197 253 / var(--my-opacity));
- }
- .bg-opacity-50 {
- --tw-bg-opacity: 0.5;
- }
- .text-blue-300 {
- --tw-text-opacity: 1;
- color: rgb(147 197 253 / var(--tw-text-opacity));
- }
- .text-blue-300\/50 {
- color: rgb(147 197 253 / 0.5);
- }
- .text-blue-300\/\[var\(--my-opacity\)\] {
- color: rgb(147 197 253 / var(--my-opacity));
- }
- .text-opacity-50 {
- --tw-text-opacity: 0.5;
- }
- .placeholder-blue-300::placeholder {
- --tw-placeholder-opacity: 1;
- color: rgb(147 197 253 / var(--tw-placeholder-opacity));
- }
- .placeholder-blue-300\/50::placeholder {
- color: rgb(147 197 253 / 0.5);
- }
- .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder {
- color: rgb(147 197 253 / var(--my-opacity));
- }
- .placeholder-opacity-50::placeholder {
- --tw-placeholder-opacity: 0.5;
- }
- .ring-blue-300 {
- --tw-ring-opacity: 1;
- --tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity));
- }
- .ring-blue-300\/50 {
- --tw-ring-color: rgb(147 197 253 / 0.5);
- }
- .ring-blue-300\/\[var\(--my-opacity\)\] {
- --tw-ring-color: rgb(147 197 253 / var(--my-opacity));
- }
- .ring-opacity-50 {
- --tw-ring-opacity: 0.5;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .bg-foo10 {
+ background-color: rgb(255 100 0 / 100%);
+ }
+ .bg-foo11 {
+ background-color: rgb(255 100 0 / 50%);
+ }
+ .bg-foo20 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 100 0 / var(--tw-bg-opacity));
+ }
+ .bg-foo21 {
+ background-color: rgb(255 100 0 / 50%);
+ }
+ .bg-foo30 {
+ background-color: rgb(255 100 0 / 100%);
+ }
+ .bg-foo31 {
+ background-color: rgb(255 100 0 / 50%);
+ }
+ .bg-foo40 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 100 0 / var(--tw-bg-opacity));
+ }
+ .bg-foo41 {
+ background-color: rgb(255 100 0 / 50%);
+ }
+ `)
+ })
+ })
+
+ it('The disableColorOpacityUtilitiesByDefault flag disables the color opacity plugins and removes their variables', () => {
+ let config = {
+ future: {
+ disableColorOpacityUtilitiesByDefault: true,
+ },
+ content: [
+ {
+ raw: html`
+
+
+
+
+ `,
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .divide-blue-300 > :not([hidden]) ~ :not([hidden]) {
+ border-color: #93c5fd;
+ }
+ .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(147 197 253 / 0.5);
+ }
+ .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .border-blue-300 {
+ border-color: #93c5fd;
+ }
+ .border-blue-300\/50 {
+ border-color: rgb(147 197 253 / 0.5);
+ }
+ .border-blue-300\/\[var\(--my-opacity\)\] {
+ border-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .bg-blue-300 {
+ background-color: #93c5fd;
+ }
+ .bg-blue-300\/50 {
+ background-color: rgb(147 197 253 / 0.5);
+ }
+ .bg-blue-300\/\[var\(--my-opacity\)\] {
+ background-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .text-blue-300 {
+ color: #93c5fd;
+ }
+ .text-blue-300\/50 {
+ color: rgb(147 197 253 / 0.5);
+ }
+ .text-blue-300\/\[var\(--my-opacity\)\] {
+ color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .placeholder-blue-300::placeholder {
+ color: #93c5fd;
+ }
+ .placeholder-blue-300\/50::placeholder {
+ color: rgb(147 197 253 / 0.5);
+ }
+ .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder {
+ color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .ring-blue-300 {
+ --tw-ring-color: #93c5fd;
+ }
+ .ring-blue-300\/50 {
+ --tw-ring-color: rgb(147 197 253 / 0.5);
+ }
+ .ring-blue-300\/\[var\(--my-opacity\)\] {
+ --tw-ring-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ `)
+ })
+ })
+
+ it('You can re-enable any opacity plugin even when disableColorOpacityUtilitiesByDefault is enabled', () => {
+ let config = {
+ future: {
+ disableColorOpacityUtilitiesByDefault: true,
+ },
+ corePlugins: {
+ backgroundOpacity: true,
+ borderOpacity: true,
+ divideOpacity: true,
+ placeholderOpacity: true,
+ ringOpacity: true,
+ textOpacity: true,
+ },
+ content: [
+ {
+ raw: html`
+
+
+
+
+ `,
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchCss(css`
+ .divide-blue-300 > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 1;
+ border-color: rgb(147 197 253 / var(--tw-divide-opacity));
+ }
+ .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(147 197 253 / 0.5);
+ }
+ .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 0.5;
+ }
+ .border-blue-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(147 197 253 / var(--tw-border-opacity));
+ }
+ .border-blue-300\/50 {
+ border-color: rgb(147 197 253 / 0.5);
+ }
+ .border-blue-300\/\[var\(--my-opacity\)\] {
+ border-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .border-opacity-50 {
+ --tw-border-opacity: 0.5;
+ }
+ .bg-blue-300 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(147 197 253 / var(--tw-bg-opacity));
+ }
+ .bg-blue-300\/50 {
+ background-color: rgb(147 197 253 / 0.5);
+ }
+ .bg-blue-300\/\[var\(--my-opacity\)\] {
+ background-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .bg-opacity-50 {
+ --tw-bg-opacity: 0.5;
+ }
+ .text-blue-300 {
+ --tw-text-opacity: 1;
+ color: rgb(147 197 253 / var(--tw-text-opacity));
+ }
+ .text-blue-300\/50 {
+ color: rgb(147 197 253 / 0.5);
+ }
+ .text-blue-300\/\[var\(--my-opacity\)\] {
+ color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .text-opacity-50 {
+ --tw-text-opacity: 0.5;
+ }
+ .placeholder-blue-300::placeholder {
+ --tw-placeholder-opacity: 1;
+ color: rgb(147 197 253 / var(--tw-placeholder-opacity));
+ }
+ .placeholder-blue-300\/50::placeholder {
+ color: rgb(147 197 253 / 0.5);
+ }
+ .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder {
+ color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .placeholder-opacity-50::placeholder {
+ --tw-placeholder-opacity: 0.5;
+ }
+ .ring-blue-300 {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity));
+ }
+ .ring-blue-300\/50 {
+ --tw-ring-color: rgb(147 197 253 / 0.5);
+ }
+ .ring-blue-300\/\[var\(--my-opacity\)\] {
+ --tw-ring-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .ring-opacity-50 {
+ --tw-ring-opacity: 0.5;
+ }
+ `)
+ })
})
})
diff --git a/tests/oxide.test.js b/tests/oxide.test.js
index 9ec777a5e..c676a9f72 100644
--- a/tests/oxide.test.js
+++ b/tests/oxide.test.js
@@ -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`` }],
+ 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`` }],
- 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)));
+ }
+ `)
+ })
})
})
diff --git a/tests/parallel-variants.test.js b/tests/parallel-variants.test.js
index 3ad7a084b..3d439f746 100644
--- a/tests/parallel-variants.test.js
+++ b/tests/parallel-variants.test.js
@@ -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``,
- },
- ],
- plugins: [
- function test({ addVariant }) {
- addVariant('test', ['& *::test', '&::test'])
- },
- ],
- }
+crosscheck(() => {
+ test('basic parallel variants', async () => {
+ let config = {
+ content: [
+ {
+ raw: html``,
+ },
+ ],
+ 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``,
- },
- ],
- 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``,
+ },
+ ],
+ 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``,
- },
- ],
- 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``,
+ },
+ ],
+ 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);
+ }
+ `)
+ })
})
})
diff --git a/tests/parseAnimationValue.test.js b/tests/parseAnimationValue.test.js
index 293d6fbec..098178079 100644
--- a/tests/parseAnimationValue.test.js
+++ b/tests/parseAnimationValue.test.js
@@ -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)
-})
diff --git a/tests/parseObjectStyles.test.js b/tests/parseObjectStyles.test.js
index 06aa06c86..24590b21d 100644
--- a/tests/parseObjectStyles.test.js
+++ b/tests/parseObjectStyles.test.js
@@ -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;
+ }
+ `)
+ })
})
diff --git a/tests/plugins/divide.test.js b/tests/plugins/divide.test.js
index 0fce96e9b..72729935f 100644
--- a/tests/plugins/divide.test.js
+++ b/tests/plugins/divide.test.js
@@ -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`` }],
- 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`` }],
+ 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`` }],
- 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`` }],
+ 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`` }],
- 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`` }],
- 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`` }],
- 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`` }],
+ 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`` }],
+ 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`` }],
+ 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;
+ }
+ `)
+ })
})
})
diff --git a/tests/plugins/fontFamily.test.js b/tests/plugins/fontFamily.test.js
index 961689cf1..69c79b267 100644
--- a/tests/plugins/fontFamily.test.js
+++ b/tests/plugins/fontFamily.test.js
@@ -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`` }],
- theme: {
- fontFamily: {
- sans: 'Helvetica, Arial, sans-serif',
+crosscheck(() => {
+ test('font-family utilities can be defined as a string', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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`` }],
- 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`` }],
- 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`` }],
- 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`` }],
- 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`` }],
+ 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`` }],
+ 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`` }],
+ 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`` }],
+ 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';
+ }
+ `)
+ })
})
})
diff --git a/tests/plugins/fontSize.test.js b/tests/plugins/fontSize.test.js
index 525a65c04..745115ec6 100644
--- a/tests/plugins/fontSize.test.js
+++ b/tests/plugins/fontSize.test.js
@@ -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`` }],
- 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`` }],
+ 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`` }],
- 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`` }],
- 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`` }],
- 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``,
- },
- ],
- 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`` }],
+ 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`` }],
+ 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`` }],
+ 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``,
+ },
+ ],
+ 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;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/plugins/gradientColorStops.test.js b/tests/plugins/gradientColorStops.test.js
index a650a6955..c44678007 100644
--- a/tests/plugins/gradientColorStops.test.js
+++ b/tests/plugins/gradientColorStops.test.js
@@ -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``,
- },
- ],
- 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``,
},
- 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;
+ }
+ `)
+ })
})
})
diff --git a/tests/prefers-contrast.test.js b/tests/prefers-contrast.test.js
index 362096078..8e1ecd25c 100644
--- a/tests/prefers-contrast.test.js
+++ b/tests/prefers-contrast.test.js
@@ -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`` },
- ],
- corePlugins: { preflight: false },
- }
+crosscheck(() => {
+ it('should be possible to use contrast-more and contrast-less variants', () => {
+ let config = {
+ content: [
+ {
+ raw: 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}
+ 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`` }],
- 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`` }],
+ 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));
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/prefix.test.js b/tests/prefix.test.js
index 53b1a4448..bfb4bbf2c 100644
--- a/tests/prefix.test.js
+++ b/tests/prefix.test.js
@@ -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`` }],
@@ -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`` }],
@@ -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`` }],
@@ -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`` }],
@@ -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`` }],
@@ -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: [
diff --git a/tests/prefixSelector.test.js b/tests/prefixSelector.test.js
index 92fd6194f..c7db08d03 100644
--- a/tests/prefixSelector.test.js
+++ b/tests/prefixSelector.test.js
@@ -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'
+ )
+ })
})
diff --git a/tests/preflight.test.js b/tests/preflight.test.js
index 7ba5d3f9f..76b5ab93a 100644
--- a/tests/preflight.test.js
+++ b/tests/preflight.test.js
@@ -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`` }],
- theme: {
- borderColor: ({ theme }) => theme('colors'),
- },
- plugins: [],
- corePlugins: { preflight: true },
- }
+crosscheck(() => {
+ it('preflight has a correct border color fallback', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ 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;`)
+ })
})
})
diff --git a/tests/raw-content.test.js b/tests/raw-content.test.js
index 76fc1e89a..127a5d48a 100644
--- a/tests/raw-content.test.js
+++ b/tests/raw-content.test.js
@@ -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`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
-it('raw content', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- 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')
+ )
+ })
})
})
diff --git a/tests/relative-purge-paths.test.js b/tests/relative-purge-paths.test.js
index 0e7e5feed..95fbe7bad 100644
--- a/tests/relative-purge-paths.test.js
+++ b/tests/relative-purge-paths.test.js
@@ -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;
+ }
+ `)
+ })
})
})
diff --git a/tests/resolve-defaults-at-rules.test.js b/tests/resolve-defaults-at-rules.test.js
index 45f9872a0..56a423587 100644
--- a/tests/resolve-defaults-at-rules.test.js
+++ b/tests/resolve-defaults-at-rules.test.js
@@ -1,874 +1,877 @@
-import { run, html, css } from './util/run'
+import { crosscheck, run, html, css } from './util/run'
-test('basic utilities', async () => {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- ::before,
- ::after {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
- ::backdrop {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
+crosscheck(() => {
+ test('basic utilities', async () => {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
+ }
+ let input = css`
+ @tailwind base;
/* --- */
- .rotate-3 {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .skew-y-6 {
- --tw-skew-y: 6deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .scale-x-110 {
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- `)
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ *,
+ ::before,
+ ::after {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+ ::backdrop {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+
+ /* --- */
+ .rotate-3 {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .skew-y-6 {
+ --tw-skew-y: 6deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .scale-x-110 {
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
})
-})
-test('with pseudo-class variants', async () => {
- let config = {
- content: [
- { raw: html`` },
- ],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- ::before,
- ::after {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
- ::backdrop {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
+ test('with pseudo-class variants', async () => {
+ let config = {
+ content: [
+ { raw: html`` },
+ ],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
+ }
+ let input = css`
+ @tailwind base;
/* --- */
- .hover\:scale-x-110:hover {
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .focus\:rotate-3:focus {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .hover\:focus\:skew-y-6:focus:hover {
- --tw-skew-y: 6deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- `)
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ *,
+ ::before,
+ ::after {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+ ::backdrop {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+
+ /* --- */
+ .hover\:scale-x-110:hover {
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .focus\:rotate-3:focus {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .hover\:focus\:skew-y-6:focus:hover {
+ --tw-skew-y: 6deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
})
-})
-test('with pseudo-element variants', async () => {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- ::before,
- ::after {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
- ::backdrop {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
+ test('with pseudo-element variants', async () => {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
+ }
+ let input = css`
+ @tailwind base;
/* --- */
- .before\:scale-x-110::before {
- content: var(--tw-content);
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .after\:rotate-3::after {
- content: var(--tw-content);
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- `)
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ *,
+ ::before,
+ ::after {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+ ::backdrop {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+
+ /* --- */
+ .before\:scale-x-110::before {
+ content: var(--tw-content);
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .after\:rotate-3::after {
+ content: var(--tw-content);
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
})
-})
-test('with multi-class variants', async () => {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- ::before,
- ::after {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
- ::backdrop {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
+ test('with multi-class variants', async () => {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
+ }
+ let input = css`
+ @tailwind base;
/* --- */
- .group:hover .group-hover\:scale-x-110 {
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .peer:focus ~ .peer-focus\:rotate-3 {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- `)
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ *,
+ ::before,
+ ::after {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+ ::backdrop {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+
+ /* --- */
+ .group:hover .group-hover\:scale-x-110 {
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .peer:focus ~ .peer-focus\:rotate-3 {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
})
-})
-test('with multi-class pseudo-element variants', async () => {
- let config = {
- content: [
- { raw: html`` },
- ],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- ::before,
- ::after {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
- ::backdrop {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
+ test('with multi-class pseudo-element variants', async () => {
+ let config = {
+ content: [
+ { raw: html`` },
+ ],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
+ }
+ let input = css`
+ @tailwind base;
/* --- */
- .group:hover .group-hover\:before\:scale-x-110::before {
- content: var(--tw-content);
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .peer:focus ~ .peer-focus\:after\:rotate-3::after {
- content: var(--tw-content);
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- `)
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ *,
+ ::before,
+ ::after {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+ ::backdrop {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+
+ /* --- */
+ .group:hover .group-hover\:before\:scale-x-110::before {
+ content: var(--tw-content);
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .peer:focus ~ .peer-focus\:after\:rotate-3::after {
+ content: var(--tw-content);
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
})
-})
-test('with multi-class pseudo-element and pseudo-class variants', async () => {
- let config = {
- content: [
- {
- raw: html``,
- },
- ],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- ::before,
- ::after {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
- ::backdrop {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
+ test('with multi-class pseudo-element and pseudo-class variants', async () => {
+ let config = {
+ content: [
+ {
+ raw: html``,
+ },
+ ],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
+ }
+ let input = css`
+ @tailwind base;
/* --- */
- .group:hover .group-hover\:hover\:before\:scale-x-110:hover::before {
- content: var(--tw-content);
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .peer:focus ~ .peer-focus\:focus\:after\:rotate-3:focus::after {
- content: var(--tw-content);
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- `)
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ *,
+ ::before,
+ ::after {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+ ::backdrop {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+
+ /* --- */
+ .group:hover .group-hover\:hover\:before\:scale-x-110:hover::before {
+ content: var(--tw-content);
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .peer:focus ~ .peer-focus\:focus\:after\:rotate-3:focus::after {
+ content: var(--tw-content);
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
})
-})
-test('with apply', async () => {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
-
- @layer utilities {
- .foo {
- @apply rotate-3;
- }
+ test('with apply', async () => {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
}
- .bar {
- @apply before:scale-110;
- }
-
- .baz::before {
- @apply rotate-45;
- }
-
- .whats ~ .next > span:hover {
- @apply skew-x-6;
- }
-
- .media-queries {
- @apply md:rotate-45;
- }
-
- .a,
- .b,
- .c {
- @apply skew-y-3;
- }
-
- .a,
- .b {
- @apply rotate-45;
- }
-
- .a::before,
- .b::after {
- @apply rotate-90;
- }
-
- .recursive {
- @apply foo;
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- ::before,
- ::after {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
- ::backdrop {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
-
+ let input = css`
+ @tailwind base;
/* --- */
- .foo {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
+ @tailwind utilities;
+
+ @layer utilities {
+ .foo {
+ @apply rotate-3;
+ }
}
- .bar::before {
- content: var(--tw-content);
- --tw-scale-x: 1.1;
- --tw-scale-y: 1.1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
+
+ .bar {
+ @apply before:scale-110;
}
+
.baz::before {
- --tw-rotate: 45deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
+ @apply rotate-45;
}
+
.whats ~ .next > span:hover {
- --tw-skew-x: 6deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
+ @apply skew-x-6;
}
- @media (min-width: 768px) {
- .media-queries {
+
+ .media-queries {
+ @apply md:rotate-45;
+ }
+
+ .a,
+ .b,
+ .c {
+ @apply skew-y-3;
+ }
+
+ .a,
+ .b {
+ @apply rotate-45;
+ }
+
+ .a::before,
+ .b::after {
+ @apply rotate-90;
+ }
+
+ .recursive {
+ @apply foo;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ *,
+ ::before,
+ ::after {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+ ::backdrop {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+
+ /* --- */
+ .foo {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .bar::before {
+ content: var(--tw-content);
+ --tw-scale-x: 1.1;
+ --tw-scale-y: 1.1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .baz::before {
--tw-rotate: 45deg;
transform: translate(var(--tw-translate-x), var(--tw-translate-y))
rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
- }
- .a,
- .b,
- .c {
- --tw-skew-y: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .a,
- .b {
- --tw-rotate: 45deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .a::before,
- .b::after {
- --tw-rotate: 90deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .recursive {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- `)
- })
-})
-
-test('legacy pseudo-element syntax is supported', async () => {
- let config = {
- experimental: { optimizeUniversalDefaults: true },
- content: [{ raw: html`` }],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
-
- .a:before {
- @apply rotate-45;
- }
-
- .b:after {
- @apply rotate-3;
- }
-
- .c:first-line {
- @apply rotate-1;
- }
-
- .d:first-letter {
- @apply rotate-6;
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .a:before,
- .b:after,
- .c:first-line,
- .d:first-letter {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
- /* --- */
- .a:before {
- --tw-rotate: 45deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .b:after {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .c:first-line {
- --tw-rotate: 1deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- .d:first-letter {
- --tw-rotate: 6deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- `)
- })
-})
-
-test('with borders', async () => {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: ['borderWidth', 'borderColor', 'borderOpacity'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- /* --- */
- .border {
- border-width: 1px;
- }
- .border-red-500 {
- --tw-border-opacity: 1;
- border-color: rgb(239 68 68 / var(--tw-border-opacity));
- }
- @media (min-width: 768px) {
- .md\:border-2 {
- border-width: 2px;
+ .whats ~ .next > span:hover {
+ --tw-skew-x: 6deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
- }
- `)
+ @media (min-width: 768px) {
+ .media-queries {
+ --tw-rotate: 45deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ }
+ .a,
+ .b,
+ .c {
+ --tw-skew-y: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .a,
+ .b {
+ --tw-rotate: 45deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .a::before,
+ .b::after {
+ --tw-rotate: 90deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .recursive {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
})
-})
-test('with shadows', async () => {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: ['boxShadow', 'ringColor', 'ringWidth'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- ::before,
- ::after {
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- }
- ::backdrop {
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- }
+ test('legacy pseudo-element syntax is supported', async () => {
+ let config = {
+ experimental: { optimizeUniversalDefaults: true },
+ content: [{ raw: html`` }],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
+ }
+ let input = css`
+ @tailwind base;
/* --- */
- .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);
+ @tailwind utilities;
+
+ .a:before {
+ @apply rotate-45;
}
- .ring-1 {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
- var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width))
- var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+
+ .b:after {
+ @apply rotate-3;
}
- .ring-black\/25 {
- --tw-ring-color: rgb(0 0 0 / 0.25);
+
+ .c:first-line {
+ @apply rotate-1;
}
- @media (min-width: 768px) {
- .md\:shadow-xl {
- --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color),
- 0 8px 10px -6px var(--tw-shadow-color);
+
+ .d:first-letter {
+ @apply rotate-6;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .a:before,
+ .b:after,
+ .c:first-line,
+ .d:first-letter {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+ /* --- */
+ .a:before {
+ --tw-rotate: 45deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .b:after {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .c:first-line {
+ --tw-rotate: 1deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ .d:first-letter {
+ --tw-rotate: 6deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
+ })
+
+ test('with borders', async () => {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: ['borderWidth', 'borderColor', 'borderOpacity'],
+ }
+
+ let input = css`
+ @tailwind base;
+ /* --- */
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ /* --- */
+ .border {
+ border-width: 1px;
+ }
+ .border-red-500 {
+ --tw-border-opacity: 1;
+ border-color: rgb(239 68 68 / var(--tw-border-opacity));
+ }
+ @media (min-width: 768px) {
+ .md\:border-2 {
+ border-width: 2px;
+ }
+ }
+ `)
+ })
+ })
+
+ test('with shadows', async () => {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: ['boxShadow', 'ringColor', 'ringWidth'],
+ }
+
+ let input = css`
+ @tailwind base;
+ /* --- */
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ *,
+ ::before,
+ ::after {
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ }
+ ::backdrop {
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ }
+
+ /* --- */
+ .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);
}
- }
- `)
+ .ring-1 {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+ var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width))
+ var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
+ var(--tw-shadow, 0 0 #0000);
+ }
+ .ring-black\/25 {
+ --tw-ring-color: rgb(0 0 0 / 0.25);
+ }
+ @media (min-width: 768px) {
+ .md\:shadow-xl {
+ --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color),
+ 0 8px 10px -6px 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('when no utilities that need the defaults are used', async () => {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- ::before,
- ::after {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
- ::backdrop {
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- }
+ test('when no utilities that need the defaults are used', async () => {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
+ }
+ let input = css`
+ @tailwind base;
/* --- */
- `)
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ *,
+ ::before,
+ ::after {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+ ::backdrop {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ }
+
+ /* --- */
+ `)
+ })
})
-})
-test('when a utility uses defaults but they do not exist', async () => {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: ['rotate'],
- }
+ test('when a utility uses defaults but they do not exist', async () => {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: ['rotate'],
+ }
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
+ let input = css`
+ @tailwind base;
/* --- */
- .rotate-3 {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- `)
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ /* --- */
+ .rotate-3 {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
})
-})
-test('selectors are reduced to the lowest possible specificity', async () => {
- let config = {
- experimental: { optimizeUniversalDefaults: true },
- content: [{ raw: html`` }],
- corePlugins: [],
- }
-
- let input = css`
- @defaults test {
- --color: black;
+ test('selectors are reduced to the lowest possible specificity', async () => {
+ let config = {
+ experimental: { optimizeUniversalDefaults: true },
+ content: [{ raw: html`` }],
+ corePlugins: [],
}
- /* --- */
-
- .foo {
- @defaults test;
- background-color: var(--color);
- }
-
- #app {
- @defaults test;
- border-color: var(--color);
- }
-
- span#page {
- @defaults test;
- color: var(--color);
- }
-
- div[data-foo='bar']#other {
- @defaults test;
- fill: var(--color);
- }
-
- div[data-bar='baz'] {
- @defaults test;
- stroke: var(--color);
- }
-
- article {
- @defaults test;
- --article: var(--color);
- }
-
- div[data-foo='bar']#another::before {
- @defaults test;
- fill: var(--color);
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo,
- [id='app'],
- [id='page'],
- [id='other'],
- [data-bar='baz'],
- article,
- [id='another']::before {
+ let input = css`
+ @defaults test {
--color: black;
}
/* --- */
.foo {
+ @defaults test;
background-color: var(--color);
}
#app {
+ @defaults test;
border-color: var(--color);
}
span#page {
+ @defaults test;
color: var(--color);
}
div[data-foo='bar']#other {
+ @defaults test;
fill: var(--color);
}
div[data-bar='baz'] {
+ @defaults test;
stroke: var(--color);
}
article {
+ @defaults test;
--article: var(--color);
}
div[data-foo='bar']#another::before {
+ @defaults test;
fill: var(--color);
}
- `)
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo,
+ [id='app'],
+ [id='page'],
+ [id='other'],
+ [data-bar='baz'],
+ article,
+ [id='another']::before {
+ --color: black;
+ }
+
+ /* --- */
+
+ .foo {
+ background-color: var(--color);
+ }
+
+ #app {
+ border-color: var(--color);
+ }
+
+ span#page {
+ color: var(--color);
+ }
+
+ div[data-foo='bar']#other {
+ fill: var(--color);
+ }
+
+ div[data-bar='baz'] {
+ stroke: var(--color);
+ }
+
+ article {
+ --article: var(--color);
+ }
+
+ div[data-foo='bar']#another::before {
+ fill: var(--color);
+ }
+ `)
+ })
})
-})
-test('No defaults without @tailwind base', () => {
- let config = {
- experimental: { optimizeUniversalDefaults: true },
- content: [{ raw: html`` }],
- corePlugins: { preflight: false },
- }
-
- // Optimize universal defaults doesn't work well with isolated modules
- // We require you to use @tailwind base to inject the defaults
- let input = css`
- @tailwind components;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .scale-150 {
- --tw-scale-x: 1.5;
- --tw-scale-y: 1.5;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
- }
- `)
- })
-})
-
-test('no defaults and apply without @tailwind base', () => {
- let config = {
- experimental: { optimizeUniversalDefaults: true },
- content: [{ raw: html`` }],
- corePlugins: { preflight: false },
- }
-
- // Optimize universal defaults doesn't work well with isolated modules
- // We require you to use @tailwind base to inject the defaults
- let input = css`
- @tailwind components;
- @tailwind utilities;
-
- .my-card {
- @apply scale-150;
+ test('No defaults without @tailwind base', () => {
+ let config = {
+ experimental: { optimizeUniversalDefaults: true },
+ content: [{ raw: html`` }],
+ corePlugins: { preflight: false },
}
- `
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
+ // Optimize universal defaults doesn't work well with isolated modules
+ // We require you to use @tailwind base to inject the defaults
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .scale-150 {
+ --tw-scale-x: 1.5;
+ --tw-scale-y: 1.5;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
+ })
+
+ test('no defaults and apply without @tailwind base', () => {
+ let config = {
+ experimental: { optimizeUniversalDefaults: true },
+ content: [{ raw: html`` }],
+ corePlugins: { preflight: false },
+ }
+
+ // Optimize universal defaults doesn't work well with isolated modules
+ // We require you to use @tailwind base to inject the defaults
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+
.my-card {
- --tw-scale-x: 1.5;
- --tw-scale-y: 1.5;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
+ @apply scale-150;
}
- `)
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .my-card {
+ --tw-scale-x: 1.5;
+ --tw-scale-y: 1.5;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y))
+ rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
+ scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+ }
+ `)
+ })
})
})
diff --git a/tests/resolveConfig.test.js b/tests/resolveConfig.test.js
index 954c40659..c99a986de 100644
--- a/tests/resolveConfig.test.js
+++ b/tests/resolveConfig.test.js
@@ -1,1794 +1,1797 @@
import resolveConfig from '../src/util/resolveConfig'
import corePluginList from '../src/corePluginList'
+import { crosscheck } from './util/run'
-test('prefix key overrides default prefix', () => {
- const userConfig = {
- prefix: 'tw-',
- }
+crosscheck(() => {
+ test('prefix key overrides default prefix', () => {
+ const userConfig = {
+ prefix: 'tw-',
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
- }
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: 'tw-',
- important: false,
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
+ expect(result).toMatchObject({
+ prefix: 'tw-',
+ important: false,
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
+ })
})
-})
-test('important key overrides default important', () => {
- const userConfig = {
- important: true,
- }
+ test('important key overrides default important', () => {
+ const userConfig = {
+ important: true,
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
- }
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '',
- important: true,
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
+ expect(result).toMatchObject({
+ prefix: '',
+ important: true,
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
+ })
})
-})
-test('important (selector) key overrides default important', () => {
- const userConfig = {
- important: '#app',
- }
+ test('important (selector) key overrides default important', () => {
+ const userConfig = {
+ important: '#app',
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
- }
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '',
- important: '#app',
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
+ expect(result).toMatchObject({
+ prefix: '',
+ important: '#app',
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
+ })
})
-})
-test('separator key overrides default separator', () => {
- const userConfig = {
- separator: '__',
- }
+ test('separator key overrides default separator', () => {
+ const userConfig = {
+ separator: '__',
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
- }
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: '__',
- theme: {
- screens: {
- mobile: '400px',
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: '__',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
+ })
})
-})
-test('theme key is merged instead of replaced', () => {
- const userConfig = {
- theme: {
- screens: {
- mobile: '400px',
+ test('theme key is merged instead of replaced', () => {
+ const userConfig = {
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
- }
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- 'grey-darker': '#606f7b',
- 'grey-dark': '#8795a1',
- grey: '#b8c2cc',
- 'grey-light': '#dae1e7',
- 'grey-lighter': '#f1f5f8',
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ 'grey-darker': '#606f7b',
+ 'grey-dark': '#8795a1',
+ grey: '#b8c2cc',
+ 'grey-light': '#dae1e7',
+ 'grey-lighter': '#f1f5f8',
+ },
+ fonts: {
+ sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'],
+ serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'],
+ },
+ screens: {
+ sm: '500px',
+ md: '750px',
+ lg: '1000px',
+ },
},
- fonts: {
- sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'],
- serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'],
- },
- screens: {
- sm: '500px',
- md: '750px',
- lg: '1000px',
- },
- },
- }
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- 'grey-darker': '#606f7b',
- 'grey-dark': '#8795a1',
- grey: '#b8c2cc',
- 'grey-light': '#dae1e7',
- 'grey-lighter': '#f1f5f8',
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ 'grey-darker': '#606f7b',
+ 'grey-dark': '#8795a1',
+ grey: '#b8c2cc',
+ 'grey-light': '#dae1e7',
+ 'grey-lighter': '#f1f5f8',
+ },
+ fonts: {
+ sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'],
+ serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'],
+ },
+ screens: {
+ mobile: '400px',
+ },
},
- fonts: {
- sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'],
- serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'],
- },
- screens: {
- mobile: '400px',
- },
- },
+ })
})
-})
-test('theme key is deeply merged instead of replaced', () => {
- const userConfig = {
- theme: {
- extend: {
+ test('theme key is deeply merged instead of replaced', () => {
+ const userConfig = {
+ theme: {
+ extend: {
+ colors: {
+ grey: {
+ darker: '#606f7b',
+ dark: '#8795a1',
+ },
+ },
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ grey: {
+ grey: '#b8c2cc',
+ light: '#dae1e7',
+ lighter: '#f1f5f8',
+ },
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
colors: {
grey: {
darker: '#606f7b',
dark: '#8795a1',
+ grey: '#b8c2cc',
+ light: '#dae1e7',
+ lighter: '#f1f5f8',
},
},
},
- },
- }
+ })
+ })
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- grey: {
- grey: '#b8c2cc',
- light: '#dae1e7',
- lighter: '#f1f5f8',
+ test('missing top level keys are pulled from the default config', () => {
+ const userConfig = {}
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: { green: '#00ff00' },
+ screens: {
+ mobile: '400px',
},
},
- },
- }
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- grey: {
- darker: '#606f7b',
- dark: '#8795a1',
- grey: '#b8c2cc',
- light: '#dae1e7',
- lighter: '#f1f5f8',
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: { green: '#00ff00' },
+ screens: {
+ mobile: '400px',
},
},
- },
+ })
})
-})
-test('missing top level keys are pulled from the default config', () => {
- const userConfig = {}
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: { green: '#00ff00' },
- screens: {
- mobile: '400px',
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: { green: '#00ff00' },
- screens: {
- mobile: '400px',
- },
- },
- })
-})
-
-test('functions in the default theme section are lazily evaluated', () => {
- const userConfig = {
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- backgroundColors: (theme) => theme('colors'),
- textColors: (theme) => theme('colors'),
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- backgroundColors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- textColors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- },
- })
-})
-
-test('functions in the user theme section are lazily evaluated', () => {
- const userConfig = {
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- backgroundColors: (theme) => ({
- ...theme('colors'),
- customBackground: '#bada55',
- }),
- textColors: (theme) => ({
- ...theme('colors'),
- customText: '#facade',
- }),
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- backgroundColors: ({ colors }) => colors,
- textColors: ({ colors }) => colors,
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- backgroundColors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- customBackground: '#bada55',
- },
- textColors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- customText: '#facade',
- },
- },
- })
-})
-
-test('theme values in the extend section extend the existing theme', () => {
- const userConfig = {
- theme: {
- extend: {
- opacity: {
- 25: '25',
- 75: '.75',
- },
- backgroundColors: {
- customBackground: '#bada55',
+ test('functions in the default theme section are lazily evaluated', () => {
+ const userConfig = {
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
},
},
- },
- }
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- opacity: {
- 0: '0',
- 50: '.5',
- 100: '1',
- },
- backgroundColors: (theme) => theme('colors'),
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- opacity: {
- 0: '0',
- 50: '.5',
- 100: '1',
- 25: '25',
- 75: '.75',
- },
- backgroundColors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- customBackground: '#bada55',
- },
- },
- })
-})
-
-test('theme values in the extend section extend the user theme', () => {
- const userConfig = {
- theme: {
- opacity: {
- 0: '0',
- 20: '.2',
- 40: '.4',
- },
- height: (theme) => theme('width'),
- extend: {
- opacity: {
- 60: '.6',
- 80: '.8',
- 100: '1',
- },
- height: {
- customHeight: '500vh',
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
},
+ backgroundColors: (theme) => theme('colors'),
+ textColors: (theme) => theme('colors'),
},
- },
- }
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- opacity: {
- 0: '0',
- 50: '.5',
- 100: '1',
- },
- height: {
- 0: 0,
- full: '100%',
- },
- width: {
- 0: 0,
- 1: '.25rem',
- 2: '.5rem',
- 3: '.75rem',
- 4: '1rem',
- },
- },
- }
+ const result = resolveConfig([userConfig, defaultConfig])
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- opacity: {
- 0: '0',
- 20: '.2',
- 40: '.4',
- 60: '.6',
- 80: '.8',
- 100: '1',
- },
- height: {
- 0: 0,
- 1: '.25rem',
- 2: '.5rem',
- 3: '.75rem',
- 4: '1rem',
- customHeight: '500vh',
- },
- width: {
- 0: 0,
- 1: '.25rem',
- 2: '.5rem',
- 3: '.75rem',
- 4: '1rem',
- },
- },
- })
-})
-
-test('theme values in the extend section can extend values that are depended on lazily', () => {
- const userConfig = {
- theme: {
- extend: {
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
colors: {
red: 'red',
green: 'green',
blue: 'blue',
},
backgroundColors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ textColors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ },
+ })
+ })
+
+ test('functions in the user theme section are lazily evaluated', () => {
+ const userConfig = {
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ backgroundColors: (theme) => ({
+ ...theme('colors'),
+ customBackground: '#bada55',
+ }),
+ textColors: (theme) => ({
+ ...theme('colors'),
+ customText: '#facade',
+ }),
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
+ backgroundColors: ({ colors }) => colors,
+ textColors: ({ colors }) => colors,
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ backgroundColors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ customBackground: '#bada55',
+ },
+ textColors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ customText: '#facade',
+ },
+ },
+ })
+ })
+
+ test('theme values in the extend section extend the existing theme', () => {
+ const userConfig = {
+ theme: {
+ extend: {
+ opacity: {
+ 25: '25',
+ 75: '.75',
+ },
+ backgroundColors: {
+ customBackground: '#bada55',
+ },
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
+ opacity: {
+ 0: '0',
+ 50: '.5',
+ 100: '1',
+ },
+ backgroundColors: (theme) => theme('colors'),
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
+ opacity: {
+ 0: '0',
+ 50: '.5',
+ 100: '1',
+ 25: '25',
+ 75: '.75',
+ },
+ backgroundColors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
customBackground: '#bada55',
},
},
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- backgroundColors: (theme) => theme('colors'),
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- backgroundColors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- red: 'red',
- green: 'green',
- blue: 'blue',
- customBackground: '#bada55',
- },
- },
+ })
})
-})
-test('theme values in the extend section are not deeply merged when they are simple arrays', () => {
- const userConfig = {
- theme: {
- extend: {
+ test('theme values in the extend section extend the user theme', () => {
+ const userConfig = {
+ theme: {
+ opacity: {
+ 0: '0',
+ 20: '.2',
+ 40: '.4',
+ },
+ height: (theme) => theme('width'),
+ extend: {
+ opacity: {
+ 60: '.6',
+ 80: '.8',
+ 100: '1',
+ },
+ height: {
+ customHeight: '500vh',
+ },
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ opacity: {
+ 0: '0',
+ 50: '.5',
+ 100: '1',
+ },
+ height: {
+ 0: 0,
+ full: '100%',
+ },
+ width: {
+ 0: 0,
+ 1: '.25rem',
+ 2: '.5rem',
+ 3: '.75rem',
+ 4: '1rem',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ opacity: {
+ 0: '0',
+ 20: '.2',
+ 40: '.4',
+ 60: '.6',
+ 80: '.8',
+ 100: '1',
+ },
+ height: {
+ 0: 0,
+ 1: '.25rem',
+ 2: '.5rem',
+ 3: '.75rem',
+ 4: '1rem',
+ customHeight: '500vh',
+ },
+ width: {
+ 0: 0,
+ 1: '.25rem',
+ 2: '.5rem',
+ 3: '.75rem',
+ 4: '1rem',
+ },
+ },
+ })
+ })
+
+ test('theme values in the extend section can extend values that are depended on lazily', () => {
+ const userConfig = {
+ theme: {
+ extend: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ backgroundColors: {
+ customBackground: '#bada55',
+ },
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
+ backgroundColors: (theme) => theme('colors'),
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ backgroundColors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ customBackground: '#bada55',
+ },
+ },
+ })
+ })
+
+ test('theme values in the extend section are not deeply merged when they are simple arrays', () => {
+ const userConfig = {
+ theme: {
+ extend: {
+ fonts: {
+ sans: ['Comic Sans'],
+ serif: ['Papyrus', { fontFeatureSettings: '"cv11"' }],
+ mono: [['Lobster', 'Papyrus'], { fontFeatureSettings: '"cv11"' }],
+ },
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ fonts: {
+ sans: ['system-ui', 'Helvetica Neue', 'sans-serif'],
+ serif: ['Constantia', 'Georgia', 'serif'],
+ mono: ['Menlo', 'Courier New', 'monospace'],
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
fonts: {
sans: ['Comic Sans'],
serif: ['Papyrus', { fontFeatureSettings: '"cv11"' }],
mono: [['Lobster', 'Papyrus'], { fontFeatureSettings: '"cv11"' }],
},
},
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- fonts: {
- sans: ['system-ui', 'Helvetica Neue', 'sans-serif'],
- serif: ['Constantia', 'Georgia', 'serif'],
- mono: ['Menlo', 'Courier New', 'monospace'],
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- fonts: {
- sans: ['Comic Sans'],
- serif: ['Papyrus', { fontFeatureSettings: '"cv11"' }],
- mono: [['Lobster', 'Papyrus'], { fontFeatureSettings: '"cv11"' }],
- },
- },
+ })
})
-})
-test('theme values in the extend section are deeply merged, when they are arrays of objects', () => {
- const userConfig = {
- theme: {
- extend: {
+ test('theme values in the extend section are deeply merged, when they are arrays of objects', () => {
+ const userConfig = {
+ theme: {
+ extend: {
+ typography: {
+ ArrayArray: {
+ css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }],
+ },
+ ObjectArray: {
+ css: { a: { backgroundColor: 'red' } },
+ },
+ ArrayObject: {
+ css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }],
+ },
+ },
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
typography: {
ArrayArray: {
- css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }],
+ css: [{ a: { underline: 'none' } }],
},
ObjectArray: {
- css: { a: { backgroundColor: 'red' } },
+ css: [{ a: { underline: 'none' } }],
},
ArrayObject: {
- css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }],
+ css: { a: { underline: 'none' } },
},
},
},
- },
- }
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- typography: {
- ArrayArray: {
- css: [{ a: { underline: 'none' } }],
- },
- ObjectArray: {
- css: [{ a: { underline: 'none' } }],
- },
- ArrayObject: {
- css: { a: { underline: 'none' } },
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ typography: {
+ ArrayArray: {
+ css: [
+ { a: { underline: 'none' } },
+ { a: { backgroundColor: 'red' } },
+ { a: { color: 'green' } },
+ ],
+ },
+ ObjectArray: {
+ css: [{ a: { underline: 'none' } }, { a: { backgroundColor: 'red' } }],
+ },
+ ArrayObject: {
+ css: [
+ { a: { underline: 'none' } },
+ { a: { backgroundColor: 'red' } },
+ { a: { color: 'green' } },
+ ],
+ },
},
},
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- typography: {
- ArrayArray: {
- css: [
- { a: { underline: 'none' } },
- { a: { backgroundColor: 'red' } },
- { a: { color: 'green' } },
- ],
- },
- ObjectArray: {
- css: [{ a: { underline: 'none' } }, { a: { backgroundColor: 'red' } }],
- },
- ArrayObject: {
- css: [
- { a: { underline: 'none' } },
- { a: { backgroundColor: 'red' } },
- { a: { color: 'green' } },
- ],
- },
- },
- },
+ })
})
-})
-test('the theme function can use a default value if the key is missing', () => {
- const userConfig = {
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- borderColor: (theme) => ({
- default: theme('colors.gray', 'currentColor'),
- ...theme('colors'),
- }),
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- borderColor: {
- default: 'currentColor',
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- },
- })
-})
-
-test('the theme function can resolve function values', () => {
- const userConfig = {
- theme: {
- textColor: (theme) => ({
- lime: 'lime',
- ...theme('colors'),
- }),
- backgroundColor: (theme) => ({
- orange: 'orange',
- ...theme('textColor'),
- }),
- borderColor: (theme) => theme('backgroundColor'),
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- textColor: {
- lime: 'lime',
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- backgroundColor: {
- lime: 'lime',
- orange: 'orange',
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- borderColor: {
- lime: 'lime',
- orange: 'orange',
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- },
- })
-})
-
-test('the theme function can resolve deep function values', () => {
- const userConfig = {
- theme: {
- minWidth: (theme) => ({
- '1/3': theme('width.1/3'),
- }),
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- spacing: {
- 0: '0',
- },
- width: (theme) => ({
- ...theme('spacing'),
- '1/3': '33.33333%',
- }),
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- spacing: {
- 0: '0',
- },
- width: {
- 0: '0',
- '1/3': '33.33333%',
- },
- minWidth: {
- '1/3': '33.33333%',
- },
- },
- })
-})
-
-test('theme values in the extend section are lazily evaluated', () => {
- const userConfig = {
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- extend: {
+ test('the theme function can use a default value if the key is missing', () => {
+ const userConfig = {
+ theme: {
colors: {
- orange: 'orange',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
},
borderColor: (theme) => ({
- foo: theme('colors.orange'),
- bar: theme('colors.red'),
+ default: theme('colors.gray', 'currentColor'),
+ ...theme('colors'),
}),
},
- },
- }
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- borderColor: (theme) => ({
- default: theme('colors.yellow', 'currentColor'),
- ...theme('colors'),
- }),
- },
- }
+ const result = resolveConfig([userConfig, defaultConfig])
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- orange: 'orange',
- red: 'red',
- green: 'green',
- blue: 'blue',
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ borderColor: {
+ default: 'currentColor',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
},
- borderColor: {
- default: 'currentColor',
- foo: 'orange',
- bar: 'red',
- orange: 'orange',
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- },
+ })
})
-})
-test('lazily evaluated values have access to the config utils', () => {
- const userConfig = {
- theme: {
- inset: (theme) => theme('margin'),
- shift: (theme, { negative }) => ({
- ...theme('spacing'),
- ...negative(theme('spacing')),
- }),
- extend: {
- nudge: (theme, { negative }) => ({
+ test('the theme function can resolve function values', () => {
+ const userConfig = {
+ theme: {
+ textColor: (theme) => ({
+ lime: 'lime',
+ ...theme('colors'),
+ }),
+ backgroundColor: (theme) => ({
+ orange: 'orange',
+ ...theme('textColor'),
+ }),
+ borderColor: (theme) => theme('backgroundColor'),
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ textColor: {
+ lime: 'lime',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ backgroundColor: {
+ lime: 'lime',
+ orange: 'orange',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ borderColor: {
+ lime: 'lime',
+ orange: 'orange',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ },
+ })
+ })
+
+ test('the theme function can resolve deep function values', () => {
+ const userConfig = {
+ theme: {
+ minWidth: (theme) => ({
+ '1/3': theme('width.1/3'),
+ }),
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ spacing: {
+ 0: '0',
+ },
+ width: (theme) => ({
+ ...theme('spacing'),
+ '1/3': '33.33333%',
+ }),
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ spacing: {
+ 0: '0',
+ },
+ width: {
+ 0: '0',
+ '1/3': '33.33333%',
+ },
+ minWidth: {
+ '1/3': '33.33333%',
+ },
+ },
+ })
+ })
+
+ test('theme values in the extend section are lazily evaluated', () => {
+ const userConfig = {
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ extend: {
+ colors: {
+ orange: 'orange',
+ },
+ borderColor: (theme) => ({
+ foo: theme('colors.orange'),
+ bar: theme('colors.red'),
+ }),
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
+ borderColor: (theme) => ({
+ default: theme('colors.yellow', 'currentColor'),
+ ...theme('colors'),
+ }),
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ orange: 'orange',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ borderColor: {
+ default: 'currentColor',
+ foo: 'orange',
+ bar: 'red',
+ orange: 'orange',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ },
+ })
+ })
+
+ test('lazily evaluated values have access to the config utils', () => {
+ const userConfig = {
+ theme: {
+ inset: (theme) => theme('margin'),
+ shift: (theme, { negative }) => ({
+ ...theme('spacing'),
+ ...negative(theme('spacing')),
+ }),
+ extend: {
+ nudge: (theme, { negative }) => ({
+ ...theme('spacing'),
+ ...negative(theme('spacing')),
+ }),
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ spacing: {
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ margin: (theme, { negative }) => ({
...theme('spacing'),
...negative(theme('spacing')),
}),
},
- },
- }
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- spacing: {
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- margin: (theme, { negative }) => ({
- ...theme('spacing'),
- ...negative(theme('spacing')),
- }),
- },
- }
+ const result = resolveConfig([userConfig, defaultConfig])
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- spacing: {
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- inset: {
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- margin: {
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- shift: {
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- nudge: {
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- },
- })
-})
-
-test('the original theme is not mutated', () => {
- const userConfig = {
- theme: {
- extend: {
- colors: {
- orange: 'orange',
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ spacing: {
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ inset: {
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ margin: {
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ shift: {
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ nudge: {
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
},
},
- },
- }
+ })
+ })
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- },
- }
-
- resolveConfig([userConfig, defaultConfig])
-
- expect(userConfig).toEqual({
- theme: {
- extend: {
- colors: {
- orange: 'orange',
+ test('the original theme is not mutated', () => {
+ const userConfig = {
+ theme: {
+ extend: {
+ colors: {
+ orange: 'orange',
+ },
},
},
- },
- })
-})
+ }
-test('custom properties are multiplied by -1 for negative values', () => {
- const userConfig = {
- theme: {
- spacing: {
- 0: 0,
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- auto: 'auto',
- foo: 'var(--foo)',
- bar: 'var(--bar, 500px)',
- baz: 'calc(50% - 10px)',
- qux: '10poops',
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
},
- margin: (theme, { negative }) => ({
- ...theme('spacing'),
- ...negative(theme('spacing')),
- }),
- },
- }
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {},
- }
+ resolveConfig([userConfig, defaultConfig])
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result.theme.spacing).toEqual({
- 0: 0,
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- auto: 'auto',
- foo: 'var(--foo)',
- bar: 'var(--bar, 500px)',
- baz: 'calc(50% - 10px)',
- qux: '10poops',
+ expect(userConfig).toEqual({
+ theme: {
+ extend: {
+ colors: {
+ orange: 'orange',
+ },
+ },
+ },
+ })
})
- expect(result.theme.margin).toEqual({
- 0: 0,
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- auto: 'auto',
- foo: 'var(--foo)',
- bar: 'var(--bar, 500px)',
- baz: 'calc(50% - 10px)',
- qux: '10poops',
- '-0': '0',
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- '-foo': 'calc(var(--foo) * -1)',
- '-bar': 'calc(var(--bar, 500px) * -1)',
- '-baz': 'calc(calc(50% - 10px) * -1)',
- '-qux': '-10poops',
- })
-})
-test('more than two config objects can be resolved', () => {
- const firstConfig = {
- theme: {
- extend: {
- fontFamily: () => ({
- code: ['Menlo', 'monospace'],
+ test('custom properties are multiplied by -1 for negative values', () => {
+ const userConfig = {
+ theme: {
+ spacing: {
+ 0: 0,
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ auto: 'auto',
+ foo: 'var(--foo)',
+ bar: 'var(--bar, 500px)',
+ baz: 'calc(50% - 10px)',
+ qux: '10poops',
+ },
+ margin: (theme, { negative }) => ({
+ ...theme('spacing'),
+ ...negative(theme('spacing')),
}),
- colors: {
- red: 'red',
- },
- backgroundColor: {
- customBackgroundOne: '#bada55',
- },
- textDecorationColor: {
- orange: 'orange',
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {},
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result.theme.spacing).toEqual({
+ 0: 0,
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ auto: 'auto',
+ foo: 'var(--foo)',
+ bar: 'var(--bar, 500px)',
+ baz: 'calc(50% - 10px)',
+ qux: '10poops',
+ })
+ expect(result.theme.margin).toEqual({
+ 0: 0,
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ auto: 'auto',
+ foo: 'var(--foo)',
+ bar: 'var(--bar, 500px)',
+ baz: 'calc(50% - 10px)',
+ qux: '10poops',
+ '-0': '0',
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ '-foo': 'calc(var(--foo) * -1)',
+ '-bar': 'calc(var(--bar, 500px) * -1)',
+ '-baz': 'calc(calc(50% - 10px) * -1)',
+ '-qux': '-10poops',
+ })
+ })
+
+ test('more than two config objects can be resolved', () => {
+ const firstConfig = {
+ theme: {
+ extend: {
+ fontFamily: () => ({
+ code: ['Menlo', 'monospace'],
+ }),
+ colors: {
+ red: 'red',
+ },
+ backgroundColor: {
+ customBackgroundOne: '#bada55',
+ },
+ textDecorationColor: {
+ orange: 'orange',
+ },
},
},
- },
- }
+ }
- const secondConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- extend: {
+ const secondConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ extend: {
+ fontFamily: {
+ quote: ['Helvetica', 'serif'],
+ },
+ colors: {
+ green: 'green',
+ },
+ backgroundColor: {
+ customBackgroundTwo: '#facade',
+ },
+ textDecorationColor: (theme) => theme('colors'),
+ },
+ },
+ }
+
+ const thirdConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ extend: {
+ fontFamily: {
+ hero: ['Futura', 'sans-serif'],
+ },
+ colors: {
+ pink: 'pink',
+ },
+ backgroundColor: () => ({
+ customBackgroundThree: '#c0ffee',
+ }),
+ textDecorationColor: {
+ lime: 'lime',
+ },
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
fontFamily: {
+ body: ['Arial', 'sans-serif'],
+ display: ['Georgia', 'serif'],
+ },
+ colors: {
+ blue: 'blue',
+ },
+ backgroundColor: (theme) => theme('colors'),
+ },
+ }
+
+ const result = resolveConfig([firstConfig, secondConfig, thirdConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ fontFamily: {
+ body: ['Arial', 'sans-serif'],
+ display: ['Georgia', 'serif'],
+ code: ['Menlo', 'monospace'],
quote: ['Helvetica', 'serif'],
- },
- colors: {
- green: 'green',
- },
- backgroundColor: {
- customBackgroundTwo: '#facade',
- },
- textDecorationColor: (theme) => theme('colors'),
- },
- },
- }
-
- const thirdConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- extend: {
- fontFamily: {
hero: ['Futura', 'sans-serif'],
},
colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
pink: 'pink',
},
- backgroundColor: () => ({
+ backgroundColor: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ pink: 'pink',
+ customBackgroundOne: '#bada55',
+ customBackgroundTwo: '#facade',
customBackgroundThree: '#c0ffee',
- }),
+ },
textDecorationColor: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ pink: 'pink',
+ orange: 'orange',
lime: 'lime',
},
},
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- fontFamily: {
- body: ['Arial', 'sans-serif'],
- display: ['Georgia', 'serif'],
- },
- colors: {
- blue: 'blue',
- },
- backgroundColor: (theme) => theme('colors'),
- },
- }
-
- const result = resolveConfig([firstConfig, secondConfig, thirdConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- fontFamily: {
- body: ['Arial', 'sans-serif'],
- display: ['Georgia', 'serif'],
- code: ['Menlo', 'monospace'],
- quote: ['Helvetica', 'serif'],
- hero: ['Futura', 'sans-serif'],
- },
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- pink: 'pink',
- },
- backgroundColor: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- pink: 'pink',
- customBackgroundOne: '#bada55',
- customBackgroundTwo: '#facade',
- customBackgroundThree: '#c0ffee',
- },
- textDecorationColor: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- pink: 'pink',
- orange: 'orange',
- lime: 'lime',
- },
- },
+ })
})
-})
-test('plugin config modifications are applied', () => {
- const userConfig = {
- plugins: [
- {
- config: {
- prefix: 'tw-',
+ test('plugin config modifications are applied', () => {
+ const userConfig = {
+ plugins: [
+ {
+ config: {
+ prefix: 'tw-',
+ },
+ },
+ ],
+ }
+
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
},
},
- ],
- }
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
- },
- },
- }
+ const result = resolveConfig([userConfig, defaultConfig])
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: 'tw-',
- important: false,
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
- },
- },
- plugins: userConfig.plugins,
- })
-})
-
-test('user config takes precedence over plugin config modifications', () => {
- const userConfig = {
- prefix: 'user-',
- plugins: [
- {
- config: {
- prefix: 'tw-',
+ expect(result).toMatchObject({
+ prefix: 'tw-',
+ important: false,
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
},
},
- ],
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: 'user-',
- important: false,
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
- },
- },
- plugins: userConfig.plugins,
+ plugins: userConfig.plugins,
+ })
})
-})
-test('plugin config can register plugins that also have config', () => {
- const userConfig = {
- plugins: [
- {
- config: {
- prefix: 'tw-',
- plugins: [
- {
- config: {
- important: true,
+ test('user config takes precedence over plugin config modifications', () => {
+ const userConfig = {
+ prefix: 'user-',
+ plugins: [
+ {
+ config: {
+ prefix: 'tw-',
+ },
+ },
+ ],
+ }
+
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: 'user-',
+ important: false,
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ plugins: userConfig.plugins,
+ })
+ })
+
+ test('plugin config can register plugins that also have config', () => {
+ const userConfig = {
+ plugins: [
+ {
+ config: {
+ prefix: 'tw-',
+ plugins: [
+ {
+ config: {
+ important: true,
+ },
},
- },
- {
- config: {
- separator: '__',
+ {
+ config: {
+ separator: '__',
+ },
},
- },
- ],
+ ],
+ },
+ handler() {},
},
- handler() {},
- },
- ],
- }
+ ],
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
- }
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: 'tw-',
- important: true,
- separator: '__',
- theme: {
- screens: {
- mobile: '400px',
+ expect(result).toMatchObject({
+ prefix: 'tw-',
+ important: true,
+ separator: '__',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
- plugins: userConfig.plugins,
+ plugins: userConfig.plugins,
+ })
})
-})
-test('plugin configs take precedence over plugin configs registered by that plugin', () => {
- const userConfig = {
- plugins: [
- {
- config: {
- prefix: 'outer-',
- plugins: [
- {
- config: {
- prefix: 'inner-',
+ test('plugin configs take precedence over plugin configs registered by that plugin', () => {
+ const userConfig = {
+ plugins: [
+ {
+ config: {
+ prefix: 'outer-',
+ plugins: [
+ {
+ config: {
+ prefix: 'inner-',
+ },
},
- },
- ],
+ ],
+ },
+ handler() {},
},
- handler() {},
- },
- ],
- }
+ ],
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
- }
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: 'outer-',
- important: false,
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
+ expect(result).toMatchObject({
+ prefix: 'outer-',
+ important: false,
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
},
- },
- plugins: userConfig.plugins,
+ plugins: userConfig.plugins,
+ })
})
-})
-test('plugin theme extensions are added even if user overrides top-level theme config', () => {
- const userConfig = {
- theme: {
- width: {
- '1px': '1px',
+ test('plugin theme extensions are added even if user overrides top-level theme config', () => {
+ const userConfig = {
+ theme: {
+ width: {
+ '1px': '1px',
+ },
},
- },
- plugins: [
- {
- config: {
- theme: {
- extend: {
- width: {
- '2px': '2px',
- '3px': '3px',
+ plugins: [
+ {
+ config: {
+ theme: {
+ extend: {
+ width: {
+ '2px': '2px',
+ '3px': '3px',
+ },
},
},
},
+ handler() {},
},
- handler() {},
- },
- ],
- }
+ ],
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- width: {
- sm: '1rem',
- md: '2rem',
- lg: '3rem',
- },
- screens: {
- mobile: '400px',
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: ':',
- theme: {
- width: {
- '1px': '1px',
- '2px': '2px',
- '3px': '3px',
- },
- screens: {
- mobile: '400px',
- },
- },
- plugins: userConfig.plugins,
- })
-})
-
-test('user theme extensions take precedence over plugin theme extensions with the same key', () => {
- const userConfig = {
- theme: {
- extend: {
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
width: {
+ sm: '1rem',
+ md: '2rem',
+ lg: '3rem',
+ },
+ screens: {
+ mobile: '400px',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: ':',
+ theme: {
+ width: {
+ '1px': '1px',
+ '2px': '2px',
+ '3px': '3px',
+ },
+ screens: {
+ mobile: '400px',
+ },
+ },
+ plugins: userConfig.plugins,
+ })
+ })
+
+ test('user theme extensions take precedence over plugin theme extensions with the same key', () => {
+ const userConfig = {
+ theme: {
+ extend: {
+ width: {
+ xl: '6rem',
+ },
+ },
+ },
+ plugins: [
+ {
+ config: {
+ theme: {
+ extend: {
+ width: {
+ xl: '4rem',
+ },
+ },
+ },
+ },
+ handler() {},
+ },
+ ],
+ }
+
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ width: {
+ sm: '1rem',
+ md: '2rem',
+ lg: '3rem',
+ },
+ screens: {
+ mobile: '400px',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: ':',
+ theme: {
+ width: {
+ sm: '1rem',
+ md: '2rem',
+ lg: '3rem',
xl: '6rem',
},
+ screens: {
+ mobile: '400px',
+ },
},
- },
- plugins: [
- {
- config: {
- theme: {
- extend: {
- width: {
- xl: '4rem',
- },
+ plugins: userConfig.plugins,
+ })
+ })
+
+ test('extensions are applied in the right order', () => {
+ const userConfig = {
+ theme: {
+ extend: {
+ colors: {
+ grey: {
+ light: '#eee',
},
},
},
- handler() {},
},
- ],
- }
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- width: {
- sm: '1rem',
- md: '2rem',
- lg: '3rem',
+ const otherConfig = {
+ theme: {
+ extend: {
+ colors: {
+ grey: {
+ light: '#ddd',
+ darker: '#111',
+ },
+ },
+ },
},
- screens: {
- mobile: '400px',
- },
- },
- }
+ }
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: ':',
- theme: {
- width: {
- sm: '1rem',
- md: '2rem',
- lg: '3rem',
- xl: '6rem',
+ const anotherConfig = {
+ theme: {
+ extend: {
+ colors: {
+ grey: {
+ darker: '#222',
+ },
+ },
+ },
},
- screens: {
- mobile: '400px',
- },
- },
- plugins: userConfig.plugins,
- })
-})
+ }
-test('extensions are applied in the right order', () => {
- const userConfig = {
- theme: {
- extend: {
+ const defaultConfig = {
+ content: [],
+ theme: {
+ colors: {
+ grey: {
+ light: '#ccc',
+ dark: '#333',
+ },
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, otherConfig, anotherConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ theme: {
colors: {
grey: {
light: '#eee',
- },
- },
- },
- },
- }
-
- const otherConfig = {
- theme: {
- extend: {
- colors: {
- grey: {
- light: '#ddd',
+ dark: '#333',
darker: '#111',
},
},
},
- },
- }
+ })
+ })
- const anotherConfig = {
- theme: {
- extend: {
- colors: {
- grey: {
- darker: '#222',
+ test('core plugin configuration builds on the default list when starting with an empty object', () => {
+ const userConfig = {
+ corePlugins: { display: false },
+ }
+
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {},
+ corePlugins: {},
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: ':',
+ theme: {},
+ corePlugins: corePluginList.filter((c) => c !== 'display'),
+ })
+ })
+
+ test('core plugins that are disabled by default can be enabled', () => {
+ const userConfig = {
+ corePlugins: { display: true },
+ }
+
+ const defaultConfig = {
+ presets: [],
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {},
+ corePlugins: { display: false },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+ expect(result.corePlugins).toContain('display')
+ })
+
+ test('core plugin configurations stack', () => {
+ const userConfig = {
+ corePlugins: { display: false },
+ }
+
+ const otherConfig = {
+ corePlugins: ({ corePlugins }) => {
+ return [...corePlugins, 'margin']
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {},
+ corePlugins: ['float', 'display', 'padding'],
+ }
+
+ const result = resolveConfig([userConfig, otherConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: ':',
+ theme: {},
+ corePlugins: ['float', 'padding', 'margin'],
+ })
+ })
+
+ test('plugins are merged', () => {
+ const userConfig = {
+ plugins: ['3'],
+ }
+
+ const otherConfig = {
+ plugins: ['2'],
+ }
+
+ const defaultConfig = {
+ plugins: ['1'],
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {},
+ }
+
+ const result = resolveConfig([userConfig, otherConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: ':',
+ theme: {},
+ plugins: ['1', '2', '3'],
+ })
+ })
+
+ test('all helpers can be destructured from the first function argument', () => {
+ const userConfig = {
+ theme: {
+ example: ({ theme, colors, negative, breakpoints }) => ({
+ weight: theme('fontWeight.bold'),
+ black: colors.black,
+ white: colors.white,
+ ...negative(theme('spacing')),
+ ...breakpoints(theme('screens')),
+ }),
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ },
+ fontWeight: {
+ bold: 700,
+ },
+ spacing: {
+ 0: '0px',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ example: {
+ weight: 700,
+ black: '#000',
+ white: '#fff',
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ 'screen-sm': '640px',
+ 'screen-md': '768px',
+ },
+ },
+ })
+ })
+
+ test('does not duplicate extended configs every time resolveConfig is called', () => {
+ let shared = {
+ foo: { bar: { baz: [{ color: 'red' }] } },
+ }
+
+ const createConfig = (color) =>
+ resolveConfig([
+ {
+ theme: {
+ foo: shared.foo,
+ extend: {
+ foo: { bar: { baz: { color } } },
+ },
},
},
- },
- },
- }
+ ])
- const defaultConfig = {
- content: [],
- theme: {
- colors: {
- grey: {
- light: '#ccc',
- dark: '#333',
- },
- },
- },
- }
+ createConfig('orange')
+ createConfig('yellow')
+ createConfig('green')
- const result = resolveConfig([userConfig, otherConfig, anotherConfig, defaultConfig])
+ const result = createConfig('blue')
- expect(result).toMatchObject({
- theme: {
- colors: {
- grey: {
- light: '#eee',
- dark: '#333',
- darker: '#111',
- },
- },
- },
+ expect(shared.foo.bar.baz).toMatchObject([{ color: 'red' }])
+ expect(result.theme.foo.bar.baz).toMatchObject([{ color: 'red' }, { color: 'blue' }])
})
})
-
-test('core plugin configuration builds on the default list when starting with an empty object', () => {
- const userConfig = {
- corePlugins: { display: false },
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {},
- corePlugins: {},
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: ':',
- theme: {},
- corePlugins: corePluginList.filter((c) => c !== 'display'),
- })
-})
-
-test('core plugins that are disabled by default can be enabled', () => {
- const userConfig = {
- corePlugins: { display: true },
- }
-
- const defaultConfig = {
- presets: [],
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {},
- corePlugins: { display: false },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
- expect(result.corePlugins).toContain('display')
-})
-
-test('core plugin configurations stack', () => {
- const userConfig = {
- corePlugins: { display: false },
- }
-
- const otherConfig = {
- corePlugins: ({ corePlugins }) => {
- return [...corePlugins, 'margin']
- },
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {},
- corePlugins: ['float', 'display', 'padding'],
- }
-
- const result = resolveConfig([userConfig, otherConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: ':',
- theme: {},
- corePlugins: ['float', 'padding', 'margin'],
- })
-})
-
-test('plugins are merged', () => {
- const userConfig = {
- plugins: ['3'],
- }
-
- const otherConfig = {
- plugins: ['2'],
- }
-
- const defaultConfig = {
- plugins: ['1'],
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {},
- }
-
- const result = resolveConfig([userConfig, otherConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: ':',
- theme: {},
- plugins: ['1', '2', '3'],
- })
-})
-
-test('all helpers can be destructured from the first function argument', () => {
- const userConfig = {
- theme: {
- example: ({ theme, colors, negative, breakpoints }) => ({
- weight: theme('fontWeight.bold'),
- black: colors.black,
- white: colors.white,
- ...negative(theme('spacing')),
- ...breakpoints(theme('screens')),
- }),
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- sm: '640px',
- md: '768px',
- },
- fontWeight: {
- bold: 700,
- },
- spacing: {
- 0: '0px',
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- example: {
- weight: 700,
- black: '#000',
- white: '#fff',
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- 'screen-sm': '640px',
- 'screen-md': '768px',
- },
- },
- })
-})
-
-test('does not duplicate extended configs every time resolveConfig is called', () => {
- let shared = {
- foo: { bar: { baz: [{ color: 'red' }] } },
- }
-
- const createConfig = (color) =>
- resolveConfig([
- {
- theme: {
- foo: shared.foo,
- extend: {
- foo: { bar: { baz: { color } } },
- },
- },
- },
- ])
-
- createConfig('orange')
- createConfig('yellow')
- createConfig('green')
-
- const result = createConfig('blue')
-
- expect(shared.foo.bar.baz).toMatchObject([{ color: 'red' }])
- expect(result.theme.foo.bar.baz).toMatchObject([{ color: 'red' }, { color: 'blue' }])
-})
diff --git a/tests/responsive-and-variants-atrules.test.js b/tests/responsive-and-variants-atrules.test.js
index 5abc5b167..a825dc333 100644
--- a/tests/responsive-and-variants-atrules.test.js
+++ b/tests/responsive-and-variants-atrules.test.js
@@ -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`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+crosscheck(() => {
+ test('responsive and variants atrules', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ 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;
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/safelist.test.js b/tests/safelist.test.js
index 3aab69d13..a8404e634 100644
--- a/tests/safelist.test.js
+++ b/tests/safelist.test.js
@@ -1,534 +1,536 @@
-import { run, html, css } from './util/run'
+import { crosscheck, run, html, css } from './util/run'
-it('should not safelist anything', () => {
- let config = {
- content: [{ raw: html`` }],
- }
+crosscheck(() => {
+ it('should not safelist anything', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ }
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
-})
-
-it('should safelist strings', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: ['mt-[20px]', 'font-bold', 'text-gray-200', 'hover:underline'],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .mt-\[20px\] {
- margin-top: 20px;
- }
-
- .font-bold {
- font-weight: 700;
- }
-
- .uppercase {
- text-transform: uppercase;
- }
-
- .text-gray-200 {
- --tw-text-opacity: 1;
- color: rgb(229 231 235 / var(--tw-text-opacity));
- }
-
- .hover\:underline:hover {
- text-decoration-line: underline;
- }
- `)
- })
-})
-
-it('should safelist based on a pattern regex', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: [
- {
- pattern: /^bg-(red)-(100|200)$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .bg-red-100 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
-
- .bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
-
- .uppercase {
- text-transform: uppercase;
- }
-
- .hover\:bg-red-100:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
-
- .hover\:bg-red-200:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
- `)
- })
-})
-
-it('should not generate duplicates', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: [
- 'uppercase',
- {
- pattern: /^bg-(red)-(100|200)$/,
- variants: ['hover'],
- },
- {
- pattern: /^bg-(red)-(100|200)$/,
- variants: ['hover'],
- },
- {
- pattern: /^bg-(red)-(100|200)$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .bg-red-100 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
-
- .bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
-
- .uppercase {
- text-transform: uppercase;
- }
-
- .hover\:bg-red-100:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
-
- .hover\:bg-red-200:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
- `)
- })
-})
-
-it('should safelist when using a custom prefix', () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`` }],
- safelist: [
- {
- pattern: /^tw-bg-red-(100|200)$/g,
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .tw-bg-red-100 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
-
- .tw-bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
-
- .tw-uppercase {
- text-transform: uppercase;
- }
- `)
- })
-})
-
-it('should not safelist when an empty list is provided', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
-})
-
-it('should not safelist when an sparse/holey list is provided', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: [, , ,],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
-})
-
-it('should not safelist any invalid variants if provided', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: [
- {
- pattern: /^bg-(red)-(100|200)$/,
- variants: ['foo', 'bar'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .bg-red-100 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
- .bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
-
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
-})
-
-it('should safelist negatives based on a pattern regex', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: [
- {
- pattern: /^-top-1$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-top-1 {
- top: -0.25rem;
- }
-
- .uppercase {
- text-transform: uppercase;
- }
-
- .hover\:-top-1:hover {
- top: -0.25rem;
- }
- `)
- })
-})
-
-it('should safelist negatives based on a pattern regex', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: [
- {
- pattern: /^bg-red-(400|500)(\/(40|50))?$/,
- variants: ['hover'],
- },
- {
- pattern: /^(fill|ring|text)-red-200\/50$/,
- variants: ['hover'],
- },
- ],
- }
-
- 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));
- }
- .bg-red-400\/40 {
- background-color: rgb(248 113 113 / 0.4);
- }
- .bg-red-400\/50 {
- background-color: rgb(248 113 113 / 0.5);
- }
- .bg-red-500 {
- --tw-bg-opacity: 1;
- background-color: rgb(239 68 68 / var(--tw-bg-opacity));
- }
- .bg-red-500\/40 {
- background-color: rgb(239 68 68 / 0.4);
- }
- .bg-red-500\/50 {
- background-color: rgb(239 68 68 / 0.5);
- }
- .fill-red-200\/50 {
- fill: rgb(254 202 202 / 0.5);
- }
- .uppercase {
- text-transform: uppercase;
- }
- .text-red-200\/50 {
- color: rgb(254 202 202 / 0.5);
- }
- .ring-red-200\/50 {
- --tw-ring-color: rgb(254 202 202 / 0.5);
- }
- .hover\:bg-red-400:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(248 113 113 / var(--tw-bg-opacity));
- }
- .hover\:bg-red-400\/40:hover {
- background-color: rgb(248 113 113 / 0.4);
- }
- .hover\:bg-red-400\/50:hover {
- background-color: rgb(248 113 113 / 0.5);
- }
- .hover\:bg-red-500:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(239 68 68 / var(--tw-bg-opacity));
- }
- .hover\:bg-red-500\/40:hover {
- background-color: rgb(239 68 68 / 0.4);
- }
- .hover\:bg-red-500\/50:hover {
- background-color: rgb(239 68 68 / 0.5);
- }
- .hover\:fill-red-200\/50:hover {
- fill: rgb(254 202 202 / 0.5);
- }
- .hover\:text-red-200\/50:hover {
- color: rgb(254 202 202 / 0.5);
- }
- .hover\:ring-red-200\/50:hover {
- --tw-ring-color: rgb(254 202 202 / 0.5);
- }
- `)
- })
-})
-
-it('should safelist pattern regex with !important selector', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: [{ pattern: /^!grid-cols-(4|5|6)$/ }],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .\!grid-cols-4 {
- grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
- }
-
- .\!grid-cols-5 {
- grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
- }
-
- .\!grid-cols-6 {
- grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
- }
-
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
-})
-
-it('should safelist pattern regex with custom prefix along with !important selector', () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`` }],
- safelist: [{ pattern: /^!tw-grid-cols-(4|5|6)$/ }],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .\!tw-grid-cols-4 {
- grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
- }
-
- .\!tw-grid-cols-5 {
- grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
- }
-
- .\!tw-grid-cols-6 {
- grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
- }
-
- .tw-uppercase {
- text-transform: uppercase;
- }
- `)
- })
-})
-
-it('should safelist pattern regex having !important selector with variants', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: [
- {
- pattern: /^!bg-gray-(500|600|700|800)$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .\!bg-gray-500 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important;
- }
-
- .\!bg-gray-600 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important;
- }
-
- .\!bg-gray-700 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important;
- }
-
- .\!bg-gray-800 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important;
- }
-
- .uppercase {
- text-transform: uppercase;
- }
-
- .hover\:\!bg-gray-500:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important;
- }
-
- .hover\:\!bg-gray-600:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important;
- }
-
- .hover\:\!bg-gray-700:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important;
- }
-
- .hover\:\!bg-gray-800:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important;
- }
- `)
- })
-})
-
-it('should safelist multiple patterns with !important selector', () => {
- let config = {
- content: [{ raw: html`` }],
- safelist: [
- {
- pattern: /^!text-gray-(700|800|900)$/,
- variants: ['hover'],
- },
- {
- pattern: /^!bg-gray-(200|300|400)$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .\!bg-gray-200 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important;
- }
-
- .\!bg-gray-300 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important;
- }
-
- .\!bg-gray-400 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important;
- }
-
- .uppercase {
- text-transform: uppercase;
- }
-
- .\!text-gray-700 {
- --tw-text-opacity: 1 !important;
- color: rgb(55 65 81 / var(--tw-text-opacity)) !important;
- }
-
- .\!text-gray-800 {
- --tw-text-opacity: 1 !important;
- color: rgb(31 41 55 / var(--tw-text-opacity)) !important;
- }
-
- .\!text-gray-900 {
- --tw-text-opacity: 1 !important;
- color: rgb(17 24 39 / var(--tw-text-opacity)) !important;
- }
-
- .hover\:\!bg-gray-200:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important;
- }
-
- .hover\:\!bg-gray-300:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important;
- }
-
- .hover\:\!bg-gray-400:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important;
- }
-
- .hover\:\!text-gray-700:hover {
- --tw-text-opacity: 1 !important;
- color: rgb(55 65 81 / var(--tw-text-opacity)) !important;
- }
-
- .hover\:\!text-gray-800:hover {
- --tw-text-opacity: 1 !important;
- color: rgb(31 41 55 / var(--tw-text-opacity)) !important;
- }
-
- .hover\:\!text-gray-900:hover {
- --tw-text-opacity: 1 !important;
- color: rgb(17 24 39 / var(--tw-text-opacity)) !important;
- }
- `)
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+ })
+
+ it('should safelist strings', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: ['mt-[20px]', 'font-bold', 'text-gray-200', 'hover:underline'],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .mt-\[20px\] {
+ margin-top: 20px;
+ }
+
+ .font-bold {
+ font-weight: 700;
+ }
+
+ .uppercase {
+ text-transform: uppercase;
+ }
+
+ .text-gray-200 {
+ --tw-text-opacity: 1;
+ color: rgb(229 231 235 / var(--tw-text-opacity));
+ }
+
+ .hover\:underline:hover {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+ })
+
+ it('should safelist based on a pattern regex', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: [
+ {
+ pattern: /^bg-(red)-(100|200)$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .bg-red-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+
+ .bg-red-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+
+ .uppercase {
+ text-transform: uppercase;
+ }
+
+ .hover\:bg-red-100:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+
+ .hover\:bg-red-200:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+ `)
+ })
+ })
+
+ it('should not generate duplicates', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: [
+ 'uppercase',
+ {
+ pattern: /^bg-(red)-(100|200)$/,
+ variants: ['hover'],
+ },
+ {
+ pattern: /^bg-(red)-(100|200)$/,
+ variants: ['hover'],
+ },
+ {
+ pattern: /^bg-(red)-(100|200)$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .bg-red-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+
+ .bg-red-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+
+ .uppercase {
+ text-transform: uppercase;
+ }
+
+ .hover\:bg-red-100:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+
+ .hover\:bg-red-200:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+ `)
+ })
+ })
+
+ it('should safelist when using a custom prefix', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`` }],
+ safelist: [
+ {
+ pattern: /^tw-bg-red-(100|200)$/g,
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .tw-bg-red-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+
+ .tw-bg-red-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+
+ .tw-uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+ })
+
+ it('should not safelist when an empty list is provided', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+ })
+
+ it('should not safelist when an sparse/holey list is provided', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: [, , ,],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+ })
+
+ it('should not safelist any invalid variants if provided', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: [
+ {
+ pattern: /^bg-(red)-(100|200)$/,
+ variants: ['foo', 'bar'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .bg-red-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+ .bg-red-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+ })
+
+ it('should safelist negatives based on a pattern regex', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: [
+ {
+ pattern: /^-top-1$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .-top-1 {
+ top: -0.25rem;
+ }
+
+ .uppercase {
+ text-transform: uppercase;
+ }
+
+ .hover\:-top-1:hover {
+ top: -0.25rem;
+ }
+ `)
+ })
+ })
+
+ it('should safelist negatives based on a pattern regex', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: [
+ {
+ pattern: /^bg-red-(400|500)(\/(40|50))?$/,
+ variants: ['hover'],
+ },
+ {
+ pattern: /^(fill|ring|text)-red-200\/50$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ 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));
+ }
+ .bg-red-400\/40 {
+ background-color: rgb(248 113 113 / 0.4);
+ }
+ .bg-red-400\/50 {
+ background-color: rgb(248 113 113 / 0.5);
+ }
+ .bg-red-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+ }
+ .bg-red-500\/40 {
+ background-color: rgb(239 68 68 / 0.4);
+ }
+ .bg-red-500\/50 {
+ background-color: rgb(239 68 68 / 0.5);
+ }
+ .fill-red-200\/50 {
+ fill: rgb(254 202 202 / 0.5);
+ }
+ .uppercase {
+ text-transform: uppercase;
+ }
+ .text-red-200\/50 {
+ color: rgb(254 202 202 / 0.5);
+ }
+ .ring-red-200\/50 {
+ --tw-ring-color: rgb(254 202 202 / 0.5);
+ }
+ .hover\:bg-red-400:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(248 113 113 / var(--tw-bg-opacity));
+ }
+ .hover\:bg-red-400\/40:hover {
+ background-color: rgb(248 113 113 / 0.4);
+ }
+ .hover\:bg-red-400\/50:hover {
+ background-color: rgb(248 113 113 / 0.5);
+ }
+ .hover\:bg-red-500:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+ }
+ .hover\:bg-red-500\/40:hover {
+ background-color: rgb(239 68 68 / 0.4);
+ }
+ .hover\:bg-red-500\/50:hover {
+ background-color: rgb(239 68 68 / 0.5);
+ }
+ .hover\:fill-red-200\/50:hover {
+ fill: rgb(254 202 202 / 0.5);
+ }
+ .hover\:text-red-200\/50:hover {
+ color: rgb(254 202 202 / 0.5);
+ }
+ .hover\:ring-red-200\/50:hover {
+ --tw-ring-color: rgb(254 202 202 / 0.5);
+ }
+ `)
+ })
+ })
+
+ it('should safelist pattern regex with !important selector', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: [{ pattern: /^!grid-cols-(4|5|6)$/ }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .\!grid-cols-4 {
+ grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
+ }
+
+ .\!grid-cols-5 {
+ grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
+ }
+
+ .\!grid-cols-6 {
+ grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
+ }
+
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+ })
+
+ it('should safelist pattern regex with custom prefix along with !important selector', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`` }],
+ safelist: [{ pattern: /^!tw-grid-cols-(4|5|6)$/ }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .\!tw-grid-cols-4 {
+ grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
+ }
+
+ .\!tw-grid-cols-5 {
+ grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
+ }
+
+ .\!tw-grid-cols-6 {
+ grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
+ }
+
+ .tw-uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+ })
+
+ it('should safelist pattern regex having !important selector with variants', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: [
+ {
+ pattern: /^!bg-gray-(500|600|700|800)$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .\!bg-gray-500 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important;
+ }
+
+ .\!bg-gray-600 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important;
+ }
+
+ .\!bg-gray-700 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important;
+ }
+
+ .\!bg-gray-800 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important;
+ }
+
+ .uppercase {
+ text-transform: uppercase;
+ }
+
+ .hover\:\!bg-gray-500:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important;
+ }
+
+ .hover\:\!bg-gray-600:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important;
+ }
+
+ .hover\:\!bg-gray-700:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important;
+ }
+
+ .hover\:\!bg-gray-800:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important;
+ }
+ `)
+ })
+ })
+
+ it('should safelist multiple patterns with !important selector', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ safelist: [
+ {
+ pattern: /^!text-gray-(700|800|900)$/,
+ variants: ['hover'],
+ },
+ {
+ pattern: /^!bg-gray-(200|300|400)$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchCss(css`
+ .\!bg-gray-200 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important;
+ }
+
+ .\!bg-gray-300 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important;
+ }
+
+ .\!bg-gray-400 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important;
+ }
+
+ .uppercase {
+ text-transform: uppercase;
+ }
+
+ .\!text-gray-700 {
+ --tw-text-opacity: 1 !important;
+ color: rgb(55 65 81 / var(--tw-text-opacity)) !important;
+ }
+
+ .\!text-gray-800 {
+ --tw-text-opacity: 1 !important;
+ color: rgb(31 41 55 / var(--tw-text-opacity)) !important;
+ }
+
+ .\!text-gray-900 {
+ --tw-text-opacity: 1 !important;
+ color: rgb(17 24 39 / var(--tw-text-opacity)) !important;
+ }
+
+ .hover\:\!bg-gray-200:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important;
+ }
+
+ .hover\:\!bg-gray-300:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important;
+ }
+
+ .hover\:\!bg-gray-400:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important;
+ }
+
+ .hover\:\!text-gray-700:hover {
+ --tw-text-opacity: 1 !important;
+ color: rgb(55 65 81 / var(--tw-text-opacity)) !important;
+ }
+
+ .hover\:\!text-gray-800:hover {
+ --tw-text-opacity: 1 !important;
+ color: rgb(31 41 55 / var(--tw-text-opacity)) !important;
+ }
+
+ .hover\:\!text-gray-900:hover {
+ --tw-text-opacity: 1 !important;
+ color: rgb(17 24 39 / var(--tw-text-opacity)) !important;
+ }
+ `)
+ })
})
})
diff --git a/tests/screenAtRule.test.js b/tests/screenAtRule.test.js
index 01071c526..8bda65c94 100644
--- a/tests/screenAtRule.test.js
+++ b/tests/screenAtRule.test.js
@@ -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)
+ })
})
})
diff --git a/tests/shared-state.test.js b/tests/shared-state.test.js
index a3a9f2870..4791d07dc 100644
--- a/tests/shared-state.test.js
+++ b/tests/shared-state.test.js
@@ -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)
+ })
})
diff --git a/tests/source-maps.test.js b/tests/source-maps.test.js
index 04892c5ce..39ce7555c 100644
--- a/tests/source-maps.test.js
+++ b/tests/source-maps.test.js
@@ -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`
-
-
-
- `,
- },
- ],
- 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`
+
+
+
+ `,
+ },
+ ],
+ 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('')
- 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('')
- 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('')
- 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('')
- 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('')
+ 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('')
+ 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('')
+ 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('')
+ 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('')
+ 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('')
+
+ // 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',
+ ])
+ })
})
diff --git a/tests/syntax-lit-html.test.js b/tests/syntax-lit-html.test.js
index 1a780d136..3e00ebf54 100644
--- a/tests/syntax-lit-html.test.js
+++ b/tests/syntax-lit-html.test.js
@@ -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\`\`;`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {},
+ plugins: [],
+ }
-test('it detects classes in lit-html templates', () => {
- let config = {
- content: [
- {
- raw: `html\`\`;`,
- },
- ],
- 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));
+ }
+ `)
+ })
})
})
diff --git a/tests/syntax-svelte.test.js b/tests/syntax-svelte.test.js
index 4f2b5b5fe..be2c33e8b 100644
--- a/tests/syntax-svelte.test.js
+++ b/tests/syntax-svelte.test.js
@@ -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: `
@@ -53,31 +50,32 @@ t('using raw with svelte extension', () => {
Click me
`,
- 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));
+ }
+ }
+ `)
+ })
})
})
diff --git a/tests/tailwind-screens.test.js b/tests/tailwind-screens.test.js
index 784bf40c7..eddeb69eb 100644
--- a/tests/tailwind-screens.test.js
+++ b/tests/tailwind-screens.test.js
@@ -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`` }],
- }
-
- 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`` }],
}
- `
- 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`` }],
- }
-
- 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`` }],
+ }
+
+ 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;
+ }
+ `)
+ })
})
})
diff --git a/tests/to-path.test.js b/tests/to-path.test.js
index 8737194ed..1e6021ba9 100644
--- a/tests/to-path.test.js
+++ b/tests/to-path.test.js
@@ -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)
+ })
})
diff --git a/tests/util/crosscheck.test.js b/tests/util/crosscheck.test.js
new file mode 100644
index 000000000..032473386
--- /dev/null
+++ b/tests/util/crosscheck.test.js
@@ -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)
+ })
+})
diff --git a/tests/util/run.js b/tests/util/run.js
index 2d458942e..9a5bfc309 100644
--- a/tests/util/run.js
+++ b/tests/util/run.js
@@ -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)
+ })
+}
diff --git a/tests/variants.test.js b/tests/variants.test.js
index 8b864a61e..87d0ecbd9 100644
--- a/tests/variants.test.js
+++ b/tests/variants.test.js
@@ -1,186 +1,284 @@
import fs from 'fs'
import path from 'path'
import postcss from 'postcss'
+import { crosscheck, run, html, css, defaults } from './util/run'
-import { run, css, html, defaults } from './util/run'
-import { env } from '../src/lib/sharedState'
-
-test('variants', () => {
- let config = {
- darkMode: 'class',
- content: [path.resolve(__dirname, './variants.test.html')],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- let expectedPath = env.OXIDE
- ? path.resolve(__dirname, './variants.oxide.test.css')
- : path.resolve(__dirname, './variants.test.css')
- let expected = fs.readFileSync(expectedPath, 'utf8')
-
- expect(result.css).toMatchFormattedCss(expected)
- })
-})
-
-test('order matters and produces different behaviour', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
- `,
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .file\:hover\:bg-pink-600:hover::file-selector-button {
- --tw-bg-opacity: 1;
- background-color: rgb(219 39 119 / var(--tw-bg-opacity));
- }
- .hover\:file\:bg-pink-600::file-selector-button:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(219 39 119 / var(--tw-bg-opacity));
- }
- `)
- })
-})
-
-describe('custom advanced variants', () => {
- test('prose-headings usage on its own', () => {
+crosscheck(({ stable, oxide }) => {
+ test('variants', () => {
let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
- },
- ],
+ darkMode: 'class',
+ content: [path.resolve(__dirname, './variants.test.html')],
+ corePlugins: { preflight: false },
}
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- :where(.prose-headings\:text-center) :is(h1, h2, h3, h4) {
- text-align: center;
- }
- `)
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ stable
+ .expect(result.css)
+ .toMatchFormattedCss(
+ fs.readFileSync(path.resolve(__dirname, './variants.test.css'), 'utf8')
+ )
+ oxide
+ .expect(result.css)
+ .toMatchFormattedCss(
+ fs.readFileSync(path.resolve(__dirname, './variants.oxide.test.css'), 'utf8')
+ )
})
})
- test('prose-headings with another "simple" variant', () => {
+ test('order matters and produces different behaviour', () => {
let config = {
content: [
{
raw: html`
-
-
+
+
`,
},
],
- plugins: [
- function ({ addVariant }) {
- addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
- },
- ],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- :where(.hover\:prose-headings\:text-center) :is(h1, h2, h3, h4):hover {
- text-align: center;
- }
-
- :where(.prose-headings\:hover\:text-center:hover) :is(h1, h2, h3, h4) {
- text-align: center;
- }
- `)
- })
- })
-
- test('prose-headings with another "complex" variant', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
- `,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
- },
- ],
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
- .group:hover :where(.group-hover\:prose-headings\:text-center) :is(h1, h2, h3, h4) {
- text-align: center;
+ .file\:hover\:bg-pink-600:hover::file-selector-button {
+ --tw-bg-opacity: 1;
+ background-color: rgb(219 39 119 / var(--tw-bg-opacity));
}
-
- :where(.group:hover .prose-headings\:group-hover\:text-center) :is(h1, h2, h3, h4) {
- text-align: center;
+ .hover\:file\:bg-pink-600::file-selector-button:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(219 39 119 / var(--tw-bg-opacity));
}
`)
})
})
- test('using variants with multi-class selectors', () => {
+ describe('custom advanced variants', () => {
+ test('prose-headings usage on its own', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ :where(.prose-headings\:text-center) :is(h1, h2, h3, h4) {
+ text-align: center;
+ }
+ `)
+ })
+ })
+
+ test('prose-headings with another "simple" variant', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+ `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ :where(.hover\:prose-headings\:text-center) :is(h1, h2, h3, h4):hover {
+ text-align: center;
+ }
+
+ :where(.prose-headings\:hover\:text-center:hover) :is(h1, h2, h3, h4) {
+ text-align: center;
+ }
+ `)
+ })
+ })
+
+ test('prose-headings with another "complex" variant', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+ `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .group:hover :where(.group-hover\:prose-headings\:text-center) :is(h1, h2, h3, h4) {
+ text-align: center;
+ }
+
+ :where(.group:hover .prose-headings\:group-hover\:text-center) :is(h1, h2, h3, h4) {
+ text-align: center;
+ }
+ `)
+ })
+ })
+
+ test('using variants with multi-class selectors', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant, addComponents }) {
+ addComponents({
+ '.parent .child': {
+ foo: 'bar',
+ },
+ })
+ addVariant('screen', '@media screen')
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media screen {
+ .screen\:parent .child {
+ foo: bar;
+ }
+ .parent .screen\:child {
+ foo: bar;
+ }
+ }
+ `)
+ })
+ })
+
+ test('using multiple classNames in your custom variant', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('my-variant', '&:where(.one, .two, .three)')
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+
+ @layer components {
+ .test {
+ @apply my-variant:italic;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .test:where(.one, .two, .three) {
+ font-style: italic;
+ }
+
+ .my-variant\:underline:where(.one, .two, .three) {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+ })
+
+ test('variant format string must include at-rule or & (1)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('wtf-bbq', 'lol')
+ },
+ ],
+ }
+
+ await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError(
+ "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder."
+ )
+ })
+
+ test('variant format string must include at-rule or & (2)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('wtf-bbq', () => 'lol')
+ },
+ ],
+ }
+
+ await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError(
+ "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder."
+ )
+ })
+ })
+
+ test('stacked peer variants', async () => {
let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- plugins: [
- function ({ addVariant, addComponents }) {
- addComponents({
- '.parent .child': {
- foo: 'bar',
- },
- })
- addVariant('screen', '@media screen')
- },
- ],
+ content: [{ raw: 'peer-disabled:peer-focus:peer-hover:border-blue-500' }],
+ corePlugins: { preflight: false },
}
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media screen {
- .screen\:parent .child {
- foo: bar;
- }
- .parent .screen\:child {
- foo: bar;
- }
- }
- `)
- })
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
+
+ let expected = css`
+ .peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:border-blue-500 {
+ --tw-border-opacity: 1;
+ border-color: rgb(59 130 246 / var(--tw-border-opacity));
+ }
+ `
+
+ let result = await run(input, config)
+ expect(result.css).toIncludeCss(expected)
})
- test('using multiple classNames in your custom variant', () => {
+ it('should properly handle keyframes with multiple variants', async () => {
let config = {
content: [
{
- raw: html` `,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('my-variant', '&:where(.one, .two, .three)')
+ raw: 'animate-spin hover:animate-spin focus:animate-spin hover:animate-bounce focus:animate-bounce',
},
],
}
@@ -188,1023 +286,931 @@ describe('custom advanced variants', () => {
let input = css`
@tailwind components;
@tailwind utilities;
+ `
- @layer components {
- .test {
- @apply my-variant:italic;
+ let result = await run(input, config)
+ expect(result.css).toMatchFormattedCss(css`
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+ .animate-spin {
+ animation: spin 1s linear 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);
+ }
+ }
+ .hover\:animate-bounce:hover {
+ animation: bounce 1s infinite;
+ }
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+ .hover\:animate-spin:hover {
+ animation: spin 1s linear 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);
+ }
+ }
+ .focus\:animate-bounce:focus {
+ animation: bounce 1s infinite;
+ }
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+ .focus\:animate-spin:focus {
+ animation: spin 1s linear infinite;
+ }
+ `)
+ })
+
+ test('custom addVariant with more complex media query params', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('magic', '@media screen and (max-width: 600px)')
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media screen and (max-width: 600px) {
+ .magic\:text-center {
+ text-align: center;
+ }
+ }
+ `)
+ })
+ })
+
+ test('custom addVariant with nested media & format shorthand', () => {
+ let config = {
+ content: [
+ {
+ raw: html` `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('magic', '@supports (hover: hover) { @media print { &:disabled } }')
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @supports (hover: hover) {
+ @media print {
+ .magic\:text-center:disabled {
+ text-align: center;
+ }
+ }
+ }
+ `)
+ })
+ })
+
+ test('before and after variants are a bit special, and forced to the end', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+ `,
+ },
+ ],
+ plugins: [],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .before\:hover\:text-center:hover::before {
+ content: var(--tw-content);
+ text-align: center;
+ }
+
+ .hover\:before\:text-center:hover::before {
+ content: var(--tw-content);
+ text-align: center;
+ }
+ `)
+ })
+ })
+
+ test('before and after variants are a bit special, and forced to the end (2)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+ `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ :where(.before\:prose-headings\:text-center) :is(h1, h2, h3, h4)::before {
+ content: var(--tw-content);
+ text-align: center;
+ }
+
+ :where(.prose-headings\:before\:text-center) :is(h1, h2, h3, h4)::before {
+ content: var(--tw-content);
+ text-align: center;
+ }
+ `)
+ })
+ })
+
+ test('returning non-strings and non-selectors in addVariant', () => {
+ /** @type {import('../types/config').Config} */
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+ `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant, e }) {
+ addVariant('peer-aria-expanded', ({ modifySelectors, separator }) =>
+ // Returning anything other string | string[] | undefined here is not supported
+ // But we're trying to be lenient here and just throw it out
+ modifySelectors(
+ ({ className }) =>
+ `.peer[aria-expanded="true"] ~ .${e(`peer-aria-expanded${separator}${className}`)}`
+ )
+ )
+
+ addVariant('peer-aria-expanded-2', ({ modifySelectors, separator }) => {
+ let nodes = modifySelectors(
+ ({ className }) => `.${e(`peer-aria-expanded-2${separator}${className}`)}`
+ )
+
+ return [
+ // Returning anything other than strings here is not supported
+ // But we're trying to be lenient here and just throw it out
+ nodes,
+ '.peer[aria-expanded="false"] ~ &',
+ ]
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .peer[aria-expanded='true'] ~ .peer-aria-expanded\:text-center {
+ text-align: center;
+ }
+ .peer[aria-expanded='false'] ~ .peer-aria-expanded-2\:text-center {
+ text-align: center;
+ }
+ `)
+ })
+ })
+
+ it('should not generate variants of user css if it is not inside a layer', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+
+ .foo {
+ color: red;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ color: red;
+ }
+ `)
+ })
+ })
+
+ it('should be possible to use responsive modifiers that are defined with special characters', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ theme: {
+ screens: {
+ ' {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (max-width: 399px) {
+ .\ {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(
+ css`
+ ${defaults}
+ `
+ )
+ })
+ })
+
+ it('variants for components should not be produced in a file without a components layer', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 640px) {
+ .sm\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ `)
+ })
+ })
+
+ it('variants for utilities should not be produced in a file without a utilities layer', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 640px) {
+ .sm\:container {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
+ .sm\:container {
+ max-width: 640px;
+ }
+ }
+ @media (min-width: 768px) {
+ .sm\:container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .sm\:container {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .sm\:container {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .sm\:container {
+ max-width: 1536px;
+ }
+ }
+ }
+ `)
+ })
+ })
+
+ test('The visited variant removes opacity support', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+ Look, it's a link!
+ `,
+ },
+ ],
+ plugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .visited\:border-red-500:visited {
+ border-color: rgb(239 68 68);
+ }
+ .visited\:bg-red-500:visited {
+ background-color: rgb(239 68 68);
+ }
+ .visited\:text-red-500:visited {
+ color: rgb(239 68 68);
+ }
+ `)
+ })
+ })
+
+ it('appends variants to the correct place when using postcss documents', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
+
+ const doc = postcss.document()
+ doc.append(postcss.parse(`a {}`))
+ doc.append(postcss.parse(`@tailwind base`))
+ doc.append(postcss.parse(`@tailwind utilities`))
+ doc.append(postcss.parse(`b {}`))
+
+ const result = doc.toResult()
+
+ return run(result, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ a {
+ }
+ ${defaults}
+ .underline {
+ text-decoration-line: underline;
+ }
+ @media (min-width: 640px) {
+ .sm\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ b {
+ }
+ `)
+ })
+ })
+
+ it('variants support multiple, grouped selectors (html)', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .base1 .foo,
+ .base1 .bar {
+ color: red;
+ }
+
+ .base2 .bar .base2-foo {
+ color: red;
}
}
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
- .test:where(.one, .two, .three) {
- font-style: italic;
- }
+ @media (min-width: 640px) {
+ .sm\:base1 .foo,
+ .sm\:base1 .bar {
+ color: red;
+ }
- .my-variant\:underline:where(.one, .two, .three) {
- text-decoration-line: underline;
+ .sm\:base2 .bar .base2-foo {
+ color: red;
+ }
}
`)
})
})
- test('variant format string must include at-rule or & (1)', async () => {
+ it('variants support multiple, grouped selectors (apply)', () => {
let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('wtf-bbq', 'lol')
- },
- ],
+ content: [{ raw: html`` }],
+ plugins: [],
+ corePlugins: { preflight: false },
}
- await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError(
- "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder."
- )
- })
-
- test('variant format string must include at-rule or & (2)', async () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('wtf-bbq', () => 'lol')
- },
- ],
- }
-
- await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError(
- "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder."
- )
- })
-})
-
-test('stacked peer variants', async () => {
- let config = {
- content: [{ raw: 'peer-disabled:peer-focus:peer-hover:border-blue-500' }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
-
- let expected = css`
- .peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:border-blue-500 {
- --tw-border-opacity: 1;
- border-color: rgb(59 130 246 / var(--tw-border-opacity));
- }
- `
-
- let result = await run(input, config)
- expect(result.css).toIncludeCss(expected)
-})
-
-it('should properly handle keyframes with multiple variants', async () => {
- let config = {
- content: [
- {
- raw: 'animate-spin hover:animate-spin focus:animate-spin hover:animate-bounce focus:animate-bounce',
- },
- ],
- }
-
- let input = css`
- @tailwind components;
- @tailwind utilities;
- `
-
- let result = await run(input, config)
- expect(result.css).toMatchFormattedCss(css`
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
- }
- .animate-spin {
- animation: spin 1s linear 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);
- }
- }
- .hover\:animate-bounce:hover {
- animation: bounce 1s infinite;
- }
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
- }
- .hover\:animate-spin:hover {
- animation: spin 1s linear 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);
- }
- }
- .focus\:animate-bounce:focus {
- animation: bounce 1s infinite;
- }
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
- }
- .focus\:animate-spin:focus {
- animation: spin 1s linear infinite;
- }
- `)
-})
-
-test('custom addVariant with more complex media query params', () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('magic', '@media screen and (max-width: 600px)')
- },
- ],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media screen and (max-width: 600px) {
- .magic\:text-center {
- text-align: center;
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .base .foo,
+ .base .bar {
+ color: red;
}
}
- `)
- })
-})
-
-test('custom addVariant with nested media & format shorthand', () => {
- let config = {
- content: [
- {
- raw: html` `,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('magic', '@supports (hover: hover) { @media print { &:disabled } }')
- },
- ],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @supports (hover: hover) {
- @media print {
- .magic\:text-center:disabled {
- text-align: center;
- }
- }
+ .baz {
+ @apply sm:base;
}
- `)
- })
-})
+ `
-test('before and after variants are a bit special, and forced to the end', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
- `,
- },
- ],
- plugins: [],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .before\:hover\:text-center:hover::before {
- content: var(--tw-content);
- text-align: center;
- }
-
- .hover\:before\:text-center:hover::before {
- content: var(--tw-content);
- text-align: center;
- }
- `)
- })
-})
-
-test('before and after variants are a bit special, and forced to the end (2)', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
- `,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
- },
- ],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- :where(.before\:prose-headings\:text-center) :is(h1, h2, h3, h4)::before {
- content: var(--tw-content);
- text-align: center;
- }
-
- :where(.prose-headings\:before\:text-center) :is(h1, h2, h3, h4)::before {
- content: var(--tw-content);
- text-align: center;
- }
- `)
- })
-})
-
-test('returning non-strings and non-selectors in addVariant', () => {
- /** @type {import('../types/config').Config} */
- let config = {
- content: [
- {
- raw: html`
-
-
- `,
- },
- ],
- plugins: [
- function ({ addVariant, e }) {
- addVariant('peer-aria-expanded', ({ modifySelectors, separator }) =>
- // Returning anything other string | string[] | undefined here is not supported
- // But we're trying to be lenient here and just throw it out
- modifySelectors(
- ({ className }) =>
- `.peer[aria-expanded="true"] ~ .${e(`peer-aria-expanded${separator}${className}`)}`
- )
- )
-
- addVariant('peer-aria-expanded-2', ({ modifySelectors, separator }) => {
- let nodes = modifySelectors(
- ({ className }) => `.${e(`peer-aria-expanded-2${separator}${className}`)}`
- )
-
- return [
- // Returning anything other than strings here is not supported
- // But we're trying to be lenient here and just throw it out
- nodes,
- '.peer[aria-expanded="false"] ~ &',
- ]
- })
- },
- ],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .peer[aria-expanded='true'] ~ .peer-aria-expanded\:text-center {
- text-align: center;
- }
- .peer[aria-expanded='false'] ~ .peer-aria-expanded-2\:text-center {
- text-align: center;
- }
- `)
- })
-})
-
-it('should not generate variants of user css if it is not inside a layer', () => {
- let config = {
- content: [{ raw: html`` }],
- plugins: [],
- }
-
- let input = css`
- @tailwind components;
- @tailwind utilities;
-
- .foo {
- color: red;
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .foo {
- color: red;
- }
- `)
- })
-})
-
-it('should be possible to use responsive modifiers that are defined with special characters', () => {
- let config = {
- content: [{ raw: html`` }],
- theme: {
- screens: {
- ' {
- return expect(result.css).toMatchFormattedCss(css`
- @media (max-width: 399px) {
- .\ {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind base', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(
- css`
- ${defaults}
- `
- )
- })
-})
-
-it('variants for components should not be produced in a file without a components layer', () => {
- let config = {
- content: [{ raw: html`` }],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .sm\:underline {
- text-decoration-line: underline;
- }
- }
- `)
- })
-})
-
-it('variants for utilities should not be produced in a file without a utilities layer', () => {
- let config = {
- content: [{ raw: html`` }],
- }
-
- return run('@tailwind components', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .sm\:container {
- width: 100%;
- }
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
@media (min-width: 640px) {
- .sm\:container {
- max-width: 640px;
+ .baz .foo,
+ .baz .bar {
+ color: red;
}
}
- @media (min-width: 768px) {
- .sm\:container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .sm\:container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .sm\:container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .sm\:container {
- max-width: 1536px;
- }
+ `)
+ })
+ })
+
+ it('variants only picks the used selectors in a group (html)', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .a,
+ .b {
+ color: red;
}
}
- `)
- })
-})
+ `
-test('The visited variant removes opacity support', () => {
- let config = {
- content: [
- {
- raw: html`
- Look, it's a link!
- `,
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 640px) {
+ .sm\:b {
+ color: red;
+ }
+ }
+ `)
+ })
+ })
+
+ it('variants only picks the used selectors in a group (apply)', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .a,
+ .b {
+ color: red;
+ }
+ }
+ .baz {
+ @apply sm:b;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 640px) {
+ .baz {
+ color: red;
+ }
+ }
+ `)
+ })
+ })
+
+ test('hoverOnlyWhenSupported adds hover and pointer media features by default', () => {
+ let config = {
+ future: {
+ hoverOnlyWhenSupported: true,
},
- ],
- plugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .visited\:border-red-500:visited {
- border-color: rgb(239 68 68);
- }
- .visited\:bg-red-500:visited {
- background-color: rgb(239 68 68);
- }
- .visited\:text-red-500:visited {
- color: rgb(239 68 68);
- }
- `)
- })
-})
-
-it('appends variants to the correct place when using postcss documents', () => {
- let config = {
- content: [{ raw: html`` }],
- plugins: [],
- corePlugins: { preflight: false },
- }
-
- const doc = postcss.document()
- doc.append(postcss.parse(`a {}`))
- doc.append(postcss.parse(`@tailwind base`))
- doc.append(postcss.parse(`@tailwind utilities`))
- doc.append(postcss.parse(`b {}`))
-
- const result = doc.toResult()
-
- return run(result, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- a {
- }
- ${defaults}
- .underline {
- text-decoration-line: underline;
- }
- @media (min-width: 640px) {
- .sm\:underline {
- text-decoration-line: underline;
- }
- }
- b {
- }
- `)
- })
-})
-
-it('variants support multiple, grouped selectors (html)', () => {
- let config = {
- content: [{ raw: html`` }],
- plugins: [],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .base1 .foo,
- .base1 .bar {
- color: red;
- }
-
- .base2 .bar .base2-foo {
- color: red;
- }
+ content: [
+ {
+ raw: html``,
+ },
+ ],
+ corePlugins: { preflight: false },
}
- `
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .sm\:base1 .foo,
- .sm\:base1 .bar {
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+
+ @media (hover: hover) and (pointer: fine) {
+ .hover\:underline:hover {
+ text-decoration-line: underline;
+ }
+ .group:hover .group-hover\:underline {
+ text-decoration-line: underline;
+ }
+ .peer:hover ~ .peer-hover\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ `)
+ })
+ })
+
+ test('multi-class utilities handle selector-mutating variants correctly', () => {
+ let config = {
+ content: [
+ {
+ raw: html``,
+ },
+ {
+ raw: html``,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .foo.bar.baz {
color: red;
}
-
- .sm\:base2 .bar .base2-foo {
+ .foo1 .bar1 .baz1 {
color: red;
}
}
- `)
- })
-})
+ `
-it('variants support multiple, grouped selectors (apply)', () => {
- let config = {
- content: [{ raw: html`` }],
- plugins: [],
- corePlugins: { preflight: false },
- }
+ // The second set of ::after cases (w/ descendant selectors)
+ // are clearly "wrong" BUT you can't have a descendant of a
+ // pseudo - element so the utilities `after:foo1` and
+ // `after:bar1` are non-sensical so this is still
+ // perfectly fine behavior
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .base .foo,
- .base .bar {
- color: red;
- }
- }
- .baz {
- @apply sm:base;
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .baz .foo,
- .baz .bar {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .after\:foo.bar.baz::after {
+ content: var(--tw-content);
color: red;
}
- }
- `)
- })
-})
-
-it('variants only picks the used selectors in a group (html)', () => {
- let config = {
- content: [{ raw: html`` }],
- plugins: [],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .a,
- .b {
- color: red;
- }
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .sm\:b {
+ .after\:bar.foo.baz::after {
+ content: var(--tw-content);
color: red;
}
- }
- `)
- })
-})
-
-it('variants only picks the used selectors in a group (apply)', () => {
- let config = {
- content: [{ raw: html`` }],
- plugins: [],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .a,
- .b {
- color: red;
- }
- }
- .baz {
- @apply sm:b;
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .baz {
+ .after\:baz.foo.bar::after {
+ content: var(--tw-content);
color: red;
}
- }
- `)
- })
-})
-
-test('hoverOnlyWhenSupported adds hover and pointer media features by default', () => {
- let config = {
- future: {
- hoverOnlyWhenSupported: true,
- },
- content: [
- { raw: html`` },
- ],
- 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 (hover: hover) and (pointer: fine) {
- .hover\:underline:hover {
- text-decoration-line: underline;
- }
- .group:hover .group-hover\:underline {
- text-decoration-line: underline;
- }
- .peer:hover ~ .peer-hover\:underline {
- text-decoration-line: underline;
- }
- }
- `)
- })
-})
-
-test('multi-class utilities handle selector-mutating variants correctly', () => {
- let config = {
- content: [
- {
- raw: html``,
- },
- {
- raw: html``,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .foo.bar.baz {
- color: red;
- }
- .foo1 .bar1 .baz1 {
- color: red;
- }
- }
- `
-
- // The second set of ::after cases (w/ descendant selectors)
- // are clearly "wrong" BUT you can't have a descendant of a
- // pseudo - element so the utilities `after:foo1` and
- // `after:bar1` are non-sensical so this is still
- // perfectly fine behavior
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .after\:foo.bar.baz::after {
- content: var(--tw-content);
- color: red;
- }
- .after\:bar.foo.baz::after {
- content: var(--tw-content);
- color: red;
- }
- .after\:baz.foo.bar::after {
- content: var(--tw-content);
- color: red;
- }
- .after\:foo1 .bar1 .baz1::after {
- content: var(--tw-content);
- color: red;
- }
- .foo1 .after\:bar1 .baz1::after {
- content: var(--tw-content);
- color: red;
- }
- .foo1 .bar1 .after\:baz1::after {
- content: var(--tw-content);
- color: red;
- }
- .hover\:foo:hover.bar.baz {
- color: red;
- }
- .hover\:bar:hover.foo.baz {
- color: red;
- }
- .hover\:baz:hover.foo.bar {
- color: red;
- }
- .hover\:foo1:hover .bar1 .baz1 {
- color: red;
- }
- .foo1 .hover\:bar1:hover .baz1 {
- color: red;
- }
- .foo1 .bar1 .hover\:baz1:hover {
- color: red;
- }
- .group:hover .group-hover\:foo.bar.baz {
- color: red;
- }
- .group:hover .group-hover\:bar.foo.baz {
- color: red;
- }
- .group:hover .group-hover\:baz.foo.bar {
- color: red;
- }
- .group:hover .group-hover\:foo1 .bar1 .baz1 {
- color: red;
- }
- .foo1 .group:hover .group-hover\:bar1 .baz1 {
- color: red;
- }
- .foo1 .bar1 .group:hover .group-hover\:baz1 {
- color: red;
- }
- .peer:checked ~ .peer-checked\:foo.bar.baz {
- color: red;
- }
- .peer:checked ~ .peer-checked\:bar.foo.baz {
- color: red;
- }
- .peer:checked ~ .peer-checked\:baz.foo.bar {
- color: red;
- }
- .peer:checked ~ .peer-checked\:foo1 .bar1 .baz1 {
- color: red;
- }
- .foo1 .peer:checked ~ .peer-checked\:bar1 .baz1 {
- color: red;
- }
- .foo1 .bar1 .peer:checked ~ .peer-checked\:baz1 {
- color: red;
- }
- `)
- })
-})
-
-test('class inside pseudo-class function :has', () => {
- let config = {
- content: [
- { raw: html`` },
- { raw: html`` },
- { raw: html`` },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- :where(.foo) {
- color: red;
- }
- :matches(.foo, .bar, .baz) {
- color: orange;
- }
- :is(.foo) {
- color: yellow;
- }
- html:has(.foo) {
- color: green;
- }
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- :where(.foo) {
- color: red;
- }
- :matches(.foo, .bar, .baz) {
- color: orange;
- }
- :is(.foo) {
- color: yellow;
- }
- html:has(.foo) {
- color: green;
- }
-
- :where(.hover\:foo:hover) {
- color: red;
- }
- :matches(.hover\:foo:hover, .bar, .baz) {
- color: orange;
- }
- :matches(.foo, .hover\:bar:hover, .baz) {
- color: orange;
- }
- :matches(.foo, .bar, .hover\:baz:hover) {
- color: orange;
- }
- :is(.hover\:foo:hover) {
- color: yellow;
- }
- html:has(.hover\:foo:hover) {
- color: green;
- }
- @media (min-width: 640px) {
- :where(.sm\:foo) {
+ .after\:foo1 .bar1 .baz1::after {
+ content: var(--tw-content);
color: red;
}
- :matches(.sm\:foo, .bar, .baz) {
+ .foo1 .after\:bar1 .baz1::after {
+ content: var(--tw-content);
+ color: red;
+ }
+ .foo1 .bar1 .after\:baz1::after {
+ content: var(--tw-content);
+ color: red;
+ }
+ .hover\:foo:hover.bar.baz {
+ color: red;
+ }
+ .hover\:bar:hover.foo.baz {
+ color: red;
+ }
+ .hover\:baz:hover.foo.bar {
+ color: red;
+ }
+ .hover\:foo1:hover .bar1 .baz1 {
+ color: red;
+ }
+ .foo1 .hover\:bar1:hover .baz1 {
+ color: red;
+ }
+ .foo1 .bar1 .hover\:baz1:hover {
+ color: red;
+ }
+ .group:hover .group-hover\:foo.bar.baz {
+ color: red;
+ }
+ .group:hover .group-hover\:bar.foo.baz {
+ color: red;
+ }
+ .group:hover .group-hover\:baz.foo.bar {
+ color: red;
+ }
+ .group:hover .group-hover\:foo1 .bar1 .baz1 {
+ color: red;
+ }
+ .foo1 .group:hover .group-hover\:bar1 .baz1 {
+ color: red;
+ }
+ .foo1 .bar1 .group:hover .group-hover\:baz1 {
+ color: red;
+ }
+ .peer:checked ~ .peer-checked\:foo.bar.baz {
+ color: red;
+ }
+ .peer:checked ~ .peer-checked\:bar.foo.baz {
+ color: red;
+ }
+ .peer:checked ~ .peer-checked\:baz.foo.bar {
+ color: red;
+ }
+ .peer:checked ~ .peer-checked\:foo1 .bar1 .baz1 {
+ color: red;
+ }
+ .foo1 .peer:checked ~ .peer-checked\:bar1 .baz1 {
+ color: red;
+ }
+ .foo1 .bar1 .peer:checked ~ .peer-checked\:baz1 {
+ color: red;
+ }
+ `)
+ })
+ })
+
+ test('class inside pseudo-class function :has', () => {
+ let config = {
+ content: [
+ { raw: html`` },
+ { raw: html`` },
+ { raw: html`` },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ :where(.foo) {
+ color: red;
+ }
+ :matches(.foo, .bar, .baz) {
color: orange;
}
- :matches(.foo, .sm\:bar, .baz) {
- color: orange;
- }
- :matches(.foo, .bar, .sm\:baz) {
- color: orange;
- }
- :is(.sm\:foo) {
+ :is(.foo) {
color: yellow;
}
- html:has(.sm\:foo) {
+ html:has(.foo) {
color: green;
}
}
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ :where(.foo) {
+ color: red;
+ }
+ :matches(.foo, .bar, .baz) {
+ color: orange;
+ }
+ :is(.foo) {
+ color: yellow;
+ }
+ html:has(.foo) {
+ color: green;
+ }
+
+ :where(.hover\:foo:hover) {
+ color: red;
+ }
+ :matches(.hover\:foo:hover, .bar, .baz) {
+ color: orange;
+ }
+ :matches(.foo, .hover\:bar:hover, .baz) {
+ color: orange;
+ }
+ :matches(.foo, .bar, .hover\:baz:hover) {
+ color: orange;
+ }
+ :is(.hover\:foo:hover) {
+ color: yellow;
+ }
+ html:has(.hover\:foo:hover) {
+ color: green;
+ }
+ @media (min-width: 640px) {
+ :where(.sm\:foo) {
+ color: red;
+ }
+ :matches(.sm\:foo, .bar, .baz) {
+ color: orange;
+ }
+ :matches(.foo, .sm\:bar, .baz) {
+ color: orange;
+ }
+ :matches(.foo, .bar, .sm\:baz) {
+ color: orange;
+ }
+ :is(.sm\:foo) {
+ color: yellow;
+ }
+ html:has(.sm\:foo) {
+ color: green;
+ }
+ }
+ `)
+ })
+ })
+
+ test('variant functions returning arrays should output correct results when nesting', async () => {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ addUtilities, addVariant }) {
+ addVariant('test', () => ['@media (test)'])
+ addUtilities({
+ '.foo': {
+ display: 'grid',
+ '> *': {
+ 'grid-column': 'span 2',
+ },
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ @media (test) {
+ .test\:foo {
+ display: grid;
+ }
+ .test\:foo > * {
+ grid-column: span 2;
+ }
+ }
`)
})
-})
-test('variant functions returning arrays should output correct results when nesting', async () => {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: { preflight: false },
- plugins: [
- function ({ addUtilities, addVariant }) {
- addVariant('test', () => ['@media (test)'])
- addUtilities({
- '.foo': {
- display: 'grid',
- '> *': {
- 'grid-column': 'span 2',
- },
+ test('variants with slashes in them work', () => {
+ let config = {
+ content: [
+ {
+ raw: html` ar-1/10
`,
+ },
+ ],
+ theme: {
+ extend: {
+ screens: {
+ 'ar-1/10': { raw: '(min-aspect-ratio: 1/10)' },
},
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- @media (test) {
- .test\:foo {
- display: grid;
- }
- .test\:foo > * {
- grid-column: span 2;
- }
- }
- `)
-})
-
-test('variants with slashes in them work', () => {
- let config = {
- content: [
- {
- raw: html` ar-1/10
`,
- },
- ],
- theme: {
- extend: {
- screens: {
- 'ar-1/10': { raw: '(min-aspect-ratio: 1/10)' },
},
},
- },
- corePlugins: { preflight: false },
- }
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-aspect-ratio: 1/10) {
- .ar-1\/10\:text-red-500 {
- --tw-text-opacity: 1;
- color: rgb(239 68 68 / var(--tw-text-opacity));
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-aspect-ratio: 1/10) {
+ .ar-1\/10\:text-red-500 {
+ --tw-text-opacity: 1;
+ color: rgb(239 68 68 / var(--tw-text-opacity));
+ }
}
+ `)
+ })
+ })
+
+ test('variants with slashes suspport modifiers', () => {
+ let config = {
+ content: [
+ {
+ raw: html` ar-1/10
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ matchVariant }) {
+ matchVariant(
+ 'ar',
+ (value, { modifier }) => {
+ return [`@media (min-aspect-ratio: ${value}) and (foo: ${modifier})`]
+ },
+ { values: { '1/10': '1/10' } }
+ )
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-aspect-ratio: 1/10) and (foo: 20) {
+ .ar-1\/10\/20\:text-red-500 {
+ --tw-text-opacity: 1;
+ color: rgb(239 68 68 / var(--tw-text-opacity));
+ }
+ }
+ `)
+ })
+ })
+
+ test('arbitrary variant selectors should not re-order scrollbar pseudo classes', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .\[\&\:\:-webkit-resizer\:hover\]\:underline::-webkit-resizer:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar-button\:hover\]\:underline::-webkit-scrollbar-button:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar-corner\:hover\]\:underline::-webkit-scrollbar-corner:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar-thumb\:hover\]\:underline::-webkit-scrollbar-thumb:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar-track-piece\:hover\]\:underline::-webkit-scrollbar-track-piece:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar-track\:hover\]\:underline::-webkit-scrollbar-track:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar\:hover\]\:underline::-webkit-scrollbar:hover {
+ text-decoration-line: underline;
}
`)
})
})
-
-test('variants with slashes suspport modifiers', () => {
- let config = {
- content: [
- {
- raw: html` ar-1/10
`,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- function ({ matchVariant }) {
- matchVariant(
- 'ar',
- (value, { modifier }) => {
- return [`@media (min-aspect-ratio: ${value}) and (foo: ${modifier})`]
- },
- { values: { '1/10': '1/10' } }
- )
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-aspect-ratio: 1/10) and (foo: 20) {
- .ar-1\/10\/20\:text-red-500 {
- --tw-text-opacity: 1;
- color: rgb(239 68 68 / var(--tw-text-opacity));
- }
- }
- `)
- })
-})
-
-test('arbitrary variant selectors should not re-order scrollbar pseudo classes', async () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .\[\&\:\:-webkit-resizer\:hover\]\:underline::-webkit-resizer:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar-button\:hover\]\:underline::-webkit-scrollbar-button:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar-corner\:hover\]\:underline::-webkit-scrollbar-corner:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar-thumb\:hover\]\:underline::-webkit-scrollbar-thumb:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar-track-piece\:hover\]\:underline::-webkit-scrollbar-track-piece:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar-track\:hover\]\:underline::-webkit-scrollbar-track:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar\:hover\]\:underline::-webkit-scrollbar:hover {
- text-decoration-line: underline;
- }
- `)
-})
diff --git a/tests/warnings.test.js b/tests/warnings.test.js
index 5e8a106a0..d288a282a 100644
--- a/tests/warnings.test.js
+++ b/tests/warnings.test.js
@@ -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`` }],
- 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`` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ expect(warn).toHaveBeenCalledTimes(0)
+ })
})
diff --git a/tests/withAlphaVariable.test.js b/tests/withAlphaVariable.test.js
index d543dfc6e..29d39c7aa 100644
--- a/tests/withAlphaVariable.test.js
+++ b/tests/withAlphaVariable.test.js
@@ -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))',
})
})