Improve snapshot tests (#20013)

This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:

First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`

Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
  "@layer utilities {
    .foo {
      color: #000;
    }

    .bar {
      color: red;
    }
  }"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.

Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
  "
  @layer utilities {
    .foo {
      color: #000;
    }

    .bar {
      color: red;
    }
  }
  "
`)
```

In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
  @layer utilities {
    .foo {
      color: #000;
    }

    .bar {
      color: red;
    }
  }
`)
```

That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.

Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.

The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.

When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
  .\[color:red\] {
    color: red;
  }
`
```

But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
  .\\[color:red\\] {
    color: red;
  }
`
```

**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>

Long story short, simple `\n` and `\n` boundaries it is!

## Test plan

- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
This commit is contained in:
Robin Malfait
2026-05-05 22:24:25 +02:00
committed by GitHub
parent 58dad4138b
commit 43e4825464
19 changed files with 3873 additions and 2164 deletions
@@ -1,7 +1,8 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-font-weight: initial;
@@ -304,5 +305,6 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
@property --tw-font-weight {
syntax: "*";
inherits: false
}"
}
"
`;
+62 -37
View File
@@ -4,6 +4,7 @@ import { tmpdir } from 'node:os'
import path from 'path'
import postcss from 'postcss'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { pretty } from '../../tailwindcss/src/test-utils/run'
import tailwindcss from './index'
// We give this file path to PostCSS for processing.
@@ -26,7 +27,7 @@ test("`@import 'tailwindcss'` is replaced with the generated CSS", async () => {
let result = await processor.process(`@import 'tailwindcss'`, { from: inputCssFilePath() })
expect(result.css.trim()).toMatchSnapshot()
expect(pretty(result.css)).toMatchSnapshot()
// Check for dependency messages
expect(result.messages).toContainEqual({
@@ -72,8 +73,9 @@ test('output is optimized by Lightning CSS', async () => {
{ from: inputCssFilePath() },
)
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
@@ -81,7 +83,8 @@ test('output is optimized by Lightning CSS', async () => {
.bar {
color: red;
}
}"
}
"
`)
})
@@ -100,10 +103,12 @@ test('@apply can be used without emitting the theme in the CSS file', async () =
{ from: inputCssFilePath() },
)
expect(result.css.trim()).toMatchInlineSnapshot(`
".foo {
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
.foo {
color: var(--color-red-500, oklch(63.7% .237 25.331));
}"
}
"
`)
})
@@ -164,8 +169,9 @@ describe('plugins', () => {
{ from: inputCssFilePath() },
)
expect(result.css.trim()).toMatchInlineSnapshot(`
".underline {
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
.underline {
text-decoration-line: underline;
}
@@ -177,7 +183,8 @@ describe('plugins', () => {
.hocus\\:underline:focus, .hocus\\:underline:hover {
text-decoration-line: underline;
}"
}
"
`)
})
@@ -194,8 +201,9 @@ describe('plugins', () => {
{ from: `${__dirname}/fixtures/another-project/input.css` },
)
expect(result.css.trim()).toMatchInlineSnapshot(`
".underline {
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
.underline {
text-decoration-line: underline;
}
@@ -207,7 +215,8 @@ describe('plugins', () => {
.hocus\\:underline:focus, .hocus\\:underline:hover {
text-decoration-line: underline;
}"
}
"
`)
})
@@ -224,8 +233,9 @@ describe('plugins', () => {
{ from: inputCssFilePath() },
)
expect(result.css.trim()).toMatchInlineSnapshot(`
".underline {
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
.underline {
text-decoration-line: underline;
}
@@ -237,7 +247,8 @@ describe('plugins', () => {
.hocus\\:underline:focus, .hocus\\:underline:hover {
text-decoration-line: underline;
}"
}
"
`)
})
})
@@ -260,10 +271,12 @@ test('bail early when Tailwind is not used', async () => {
// didn't use `@tailwind utilities` we didn't scan for utilities.
expect(result.css).not.toContain('.underline {')
expect(result.css.trim()).toMatchInlineSnapshot(`
".custom-css {
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
.custom-css {
color: red;
}"
}
"
`)
})
@@ -285,12 +298,14 @@ test('handle CSS when only using a `@reference` (we should not bail early)', asy
{ from: inputCssFilePath() },
)
expect(result.css.trim()).toMatchInlineSnapshot(`
"@media (min-width: 48rem) {
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@media (min-width: 48rem) {
.foo {
bar: baz;
}
}"
}
"
`)
})
@@ -310,10 +325,12 @@ test('handle CSS when using a `@variant` using variants that do not rely on the
{ from: inputCssFilePath() },
)
expect(result.css.trim()).toMatchInlineSnapshot(`
".foo[data-is-hoverable] {
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
.foo[data-is-hoverable] {
bar: baz;
}"
}
"
`)
})
@@ -348,23 +365,29 @@ test('runs `Once` plugins in the right order', async () => {
{ from: inputCssFilePath() },
)
expect(result.css.trim()).toMatchInlineSnapshot(`
".custom-css {
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
.custom-css {
color: red;
}"
}
"
`)
expect(before).toMatchInlineSnapshot(`
"@theme {
expect(pretty(before)).toMatchInlineSnapshot(`
"
@theme {
--color-red-500: red;
}
.custom-css {
color: theme(--color-red-500);
}"
}
"
`)
expect(after).toMatchInlineSnapshot(`
".custom-css {
expect(pretty(after)).toMatchInlineSnapshot(`
"
.custom-css {
color: red;
}"
}
"
`)
})
@@ -438,10 +461,12 @@ test('does not register the input file as a dependency, even if it is passed in
let result = await processor.process(`@tailwind utilities`, { from: './input.css' })
expect(result.css.trim()).toMatchInlineSnapshot(`
".underline {
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
.underline {
text-decoration-line: underline;
}"
}
"
`)
// Check for dependency messages
@@ -4,6 +4,7 @@ import postcss from 'postcss'
import atImport from 'postcss-import'
import { describe, expect, test } from 'vitest'
import fixRelativePathsPlugin from '.'
import { pretty } from '../../../tailwindcss/src/test-utils/run'
describe('fixRelativePathsPlugin', () => {
test('rewrites @source and @plugin to be relative to the initial css file', async () => {
@@ -14,11 +15,13 @@ describe('fixRelativePathsPlugin', () => {
let result = await processor.process(css, { from: cssPath })
expect(result.css.trim()).toMatchInlineSnapshot(`
"@source "../../example-project/src/**/*.ts";
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@source "../../example-project/src/**/*.ts";
@source "!../../example-project/src/**/*.ts";
@plugin "../../example-project/src/plugin.js";
@plugin "../../example-project/src/what\\"s-this.js";"
@plugin "../../example-project/src/what\\"s-this.js";
"
`)
})
@@ -30,11 +33,13 @@ describe('fixRelativePathsPlugin', () => {
let result = await processor.process(css, { from: cssPath })
expect(result.css.trim()).toMatchInlineSnapshot(`
"@plugin "/absolute/paths";
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@plugin "/absolute/paths";
@plugin "C:\\Program Files\\HAL 9000";
@plugin "\\\\Media\\Pictures\\Worth\\1000 words";
@plugin "some-node-dep";"
@plugin "some-node-dep";
"
`)
})
@@ -46,13 +51,15 @@ describe('fixRelativePathsPlugin', () => {
let result = await processor.process(css, { from: cssPath })
expect(result.css.trim()).toMatchInlineSnapshot(`
"@plugin './plugin-in-sibling.ts';
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@plugin './plugin-in-sibling.ts';
@plugin '../plugin-in-sibling.ts';
@plugin 'plugin-in-sibling';
@plugin './plugin-in-root.ts';
@plugin '../plugin-in-root.ts';
@plugin 'plugin-in-root';"
@plugin 'plugin-in-root';
"
`)
})
})
@@ -1,7 +1,8 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using the default theme 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-shadow: 0 0 #0000;
@@ -123,11 +124,13 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}"
}
"
`;
exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = `
"@layer theme {
"
@layer theme {
:root, :host {
--tw-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
@@ -385,5 +388,6 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = `
}
}
@layer components, utilities;"
@layer components, utilities;
"
`;
@@ -1,7 +1,8 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`border-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -182,11 +183,13 @@ exports[`border-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
exports[`border-b-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -367,11 +370,13 @@ exports[`border-b-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
exports[`border-be-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -552,11 +557,13 @@ exports[`border-be-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
exports[`border-bs-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -737,11 +744,13 @@ exports[`border-bs-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
exports[`border-e-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -922,11 +931,13 @@ exports[`border-e-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
exports[`border-l-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -1107,11 +1118,13 @@ exports[`border-l-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
exports[`border-r-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -1292,11 +1305,13 @@ exports[`border-r-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
exports[`border-s-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -1477,11 +1492,13 @@ exports[`border-s-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
exports[`border-t-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -1662,11 +1679,13 @@ exports[`border-t-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
exports[`border-x-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -1847,11 +1866,13 @@ exports[`border-x-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
exports[`border-y-* 1`] = `
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-border-style: solid;
@@ -2032,5 +2053,6 @@ exports[`border-y-* 1`] = `
syntax: "*";
inherits: false;
initial-value: solid;
}"
}
"
`;
+78 -37
View File
@@ -3,7 +3,7 @@ import { expect, test, vi } from 'vitest'
import type { Plugin } from './compat/plugin-api'
import { compile, type Config } from './index'
import plugin from './plugin'
import { optimizeCss } from './test-utils/run'
import { optimizeCss, pretty } from './test-utils/run'
const css = dedent
@@ -30,7 +30,7 @@ async function run(
) {
let compiler = await compile(css, { base: '/root', loadStylesheet, loadModule })
let result = compiler.build(candidates)
return optimize ? optimizeCss(result) : result
return optimize ? optimizeCss(result) : pretty(result)
}
test('can resolve relative @imports', async () => {
@@ -56,7 +56,8 @@ test('can resolve relative @imports', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
".foo {
"
.foo {
color: red;
}
"
@@ -96,7 +97,8 @@ test('can recursively resolve relative @imports', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
".baz {
"
.baz {
color: #00f;
}
"
@@ -126,7 +128,8 @@ test('extracts path from @import nodes', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"a {
"
a {
color: red;
}
"
@@ -140,7 +143,8 @@ test('extracts path from @import nodes', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"a {
"
a {
color: red;
}
"
@@ -154,7 +158,8 @@ test('extracts path from @import nodes', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"a {
"
a {
color: red;
}
"
@@ -169,7 +174,11 @@ test('url() imports are passed-through', async () => {
`,
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
),
).resolves.toMatchInlineSnapshot(`"@import url('example.css');"`)
).resolves.toMatchInlineSnapshot(`
"
@import url('example.css');
"
`)
await expect(
run(
@@ -178,7 +187,11 @@ test('url() imports are passed-through', async () => {
`,
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
),
).resolves.toMatchInlineSnapshot(`"@import url('./example.css');"`)
).resolves.toMatchInlineSnapshot(`
"
@import url('./example.css');
"
`)
await expect(
run(
@@ -187,7 +200,11 @@ test('url() imports are passed-through', async () => {
`,
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
),
).resolves.toMatchInlineSnapshot(`"@import url('/example.css');"`)
).resolves.toMatchInlineSnapshot(`
"
@import url('/example.css');
"
`)
await expect(
run(
@@ -196,7 +213,11 @@ test('url() imports are passed-through', async () => {
`,
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
),
).resolves.toMatchInlineSnapshot(`"@import url(example.css);"`)
).resolves.toMatchInlineSnapshot(`
"
@import url(example.css);
"
`)
await expect(
run(
@@ -205,7 +226,11 @@ test('url() imports are passed-through', async () => {
`,
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
),
).resolves.toMatchInlineSnapshot(`"@import url(./example.css);"`)
).resolves.toMatchInlineSnapshot(`
"
@import url(./example.css);
"
`)
await expect(
run(
@@ -214,7 +239,11 @@ test('url() imports are passed-through', async () => {
`,
{ loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false },
),
).resolves.toMatchInlineSnapshot(`"@import url(/example.css);"`)
).resolves.toMatchInlineSnapshot(`
"
@import url(/example.css);
"
`)
})
test('handles case-insensitive @import directive', async () => {
@@ -226,7 +255,8 @@ test('handles case-insensitive @import directive', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"a {
"
a {
color: red;
}
"
@@ -242,7 +272,8 @@ test('@media', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"@media print {
"
@media print {
a {
color: red;
}
@@ -258,7 +289,8 @@ test('@media', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"@media print, screen {
"
@media print, screen {
a {
color: red;
}
@@ -274,7 +306,8 @@ test('@media', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"@media screen and (orientation: landscape) {
"
@media screen and (orientation: landscape) {
a {
color: red;
}
@@ -290,7 +323,8 @@ test('@media', async () => {
{ loadStylesheet, optimize: false },
),
).resolves.toMatchInlineSnapshot(`
"@media foo(bar) {
"
@media foo(bar) {
a {
color: red;
}
@@ -308,7 +342,8 @@ test('@supports', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"@supports (display: grid) {
"
@supports (display: grid) {
a {
color: red;
}
@@ -324,7 +359,8 @@ test('@supports', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"@supports (display: grid) {
"
@supports (display: grid) {
@media screen and (max-width: 400px) {
a {
color: red;
@@ -343,7 +379,8 @@ test('@supports', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"@supports (not (display: grid)) and (display: flex) {
"
@supports (not (display: grid)) and (display: flex) {
@media screen and (max-width: 400px) {
a {
color: red;
@@ -363,7 +400,8 @@ test('@supports', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"@supports selector(h2 > p) and font-tech(color-COLRv1) {
"
@supports selector(h2 > p) and font-tech(color-COLRv1) {
a {
color: red;
}
@@ -381,7 +419,8 @@ test('@layer', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"@layer utilities {
"
@layer utilities {
a {
color: red;
}
@@ -397,7 +436,8 @@ test('@layer', async () => {
{ loadStylesheet },
),
).resolves.toMatchInlineSnapshot(`
"@layer {
"
@layer {
a {
color: red;
}
@@ -428,7 +468,8 @@ test('supports theme(reference) imports', async () => {
},
),
).resolves.toMatchInlineSnapshot(`
".text-red-500 {
"
.text-red-500 {
color: var(--color-red-500, red);
}
"
@@ -448,16 +489,14 @@ test('updates the base when loading modules inside nested files', async () => {
using loadModule = vi.fn().mockResolvedValue({ base: '', path: '', module: () => {} })
expect(
(
await run(
css`
@import './foo/bar.css';
@config './root-config.js';
@plugin './root-plugin.js';
`,
{ loadStylesheet, loadModule },
)
).trim(),
await run(
css`
@import './foo/bar.css';
@config './root-config.js';
@plugin './root-plugin.js';
`,
{ loadStylesheet, loadModule },
),
).toBe('')
expect(loadModule).toHaveBeenNthCalledWith(1, './nested-config.js', '/root/foo', 'config')
@@ -595,7 +634,8 @@ test('resolves @reference as `@import "…" reference`', async () => {
{ loadStylesheet, candidates: ['text-red-500'] },
),
).resolves.toMatchInlineSnapshot(`
".text-red-500 {
"
.text-red-500 {
color: var(--color-red-500, red);
}
"
@@ -628,7 +668,8 @@ test('resolves `@variant` used as `@custom-variant` inside `@reference`', async
{ loadStylesheet, candidates: ['dark:flex'] },
),
).resolves.toMatchInlineSnapshot(`
".dark\\:flex:where([data-theme="dark"] *) {
"
.dark\\:flex:where([data-theme="dark"] *) {
display: flex;
}
"
+170 -127
View File
@@ -1,6 +1,7 @@
import { describe, expect, test, vi } from 'vitest'
import { compile, type Config } from '..'
import { default as plugin } from '../plugin'
import { pretty } from '../test-utils/run'
import flattenColorPalette from './flatten-color-palette'
const css = String.raw
@@ -28,8 +29,9 @@ test('Config files can change dark mode (media)', async () => {
loadModule: async () => ({ module: { darkMode: 'media' }, base: '/root', path: '' }),
})
expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(`
".dark\\:underline {
expect(pretty(compiler.build(['dark:underline']))).toMatchInlineSnapshot(`
"
.dark\\:underline {
@media (prefers-color-scheme: dark) {
text-decoration-line: underline;
}
@@ -48,8 +50,9 @@ test('Config files can change dark mode (selector)', async () => {
loadModule: async () => ({ module: { darkMode: 'selector' }, base: '/root', path: '' }),
})
expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(`
".dark\\:underline {
expect(pretty(compiler.build(['dark:underline']))).toMatchInlineSnapshot(`
"
.dark\\:underline {
&:where(.dark, .dark *) {
text-decoration-line: underline;
}
@@ -72,8 +75,9 @@ test('Config files can change dark mode (variant)', async () => {
}),
})
expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(`
".dark\\:underline {
expect(pretty(compiler.build(['dark:underline']))).toMatchInlineSnapshot(`
"
.dark\\:underline {
&:where(:not(.light)) {
text-decoration-line: underline;
}
@@ -106,8 +110,9 @@ test('Config files can add plugins', async () => {
}),
})
expect(compiler.build(['no-scrollbar'])).toMatchInlineSnapshot(`
".no-scrollbar {
expect(pretty(compiler.build(['no-scrollbar']))).toMatchInlineSnapshot(`
"
.no-scrollbar {
scrollbar-width: none;
}
"
@@ -134,8 +139,9 @@ test('Plugins loaded from config files can contribute to the config', async () =
}),
})
expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(`
".dark\\:underline {
expect(pretty(compiler.build(['dark:underline']))).toMatchInlineSnapshot(`
"
.dark\\:underline {
&:where(:not(.light)) {
text-decoration-line: underline;
}
@@ -164,8 +170,9 @@ test('Config file presets can contribute to the config', async () => {
}),
})
expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(`
".dark\\:underline {
expect(pretty(compiler.build(['dark:underline']))).toMatchInlineSnapshot(`
"
.dark\\:underline {
&:where(:not(.light)) {
text-decoration-line: underline;
}
@@ -206,8 +213,9 @@ test('Config files can affect the theme', async () => {
}),
})
expect(compiler.build(['bg-primary', 'scrollbar-primary'])).toMatchInlineSnapshot(`
".bg-primary {
expect(pretty(compiler.build(['bg-primary', 'scrollbar-primary']))).toMatchInlineSnapshot(`
"
.bg-primary {
background-color: #c0ffee;
}
.scrollbar-primary {
@@ -250,8 +258,9 @@ test('Accessing a default color if a sub-color exists via CSS should work as exp
}),
})
expect(compiler.build([])).toMatchInlineSnapshot(`
".example {
expect(pretty(compiler.build([]))).toMatchInlineSnapshot(`
"
.example {
color: var(--foo-foo-bar);
border-color: var(--foo-foo);
}
@@ -282,8 +291,9 @@ test('Variants in CSS overwrite variants from plugins', async () => {
}),
})
expect(compiler.build(['dark:underline', 'light:underline'])).toMatchInlineSnapshot(`
".dark\\:underline {
expect(pretty(compiler.build(['dark:underline', 'light:underline']))).toMatchInlineSnapshot(`
"
.dark\\:underline {
&:is(.my-dark) {
text-decoration-line: underline;
}
@@ -369,9 +379,10 @@ describe('theme callbacks', () => {
}),
})
expect(compiler.build(['leading-base', 'leading-md', 'leading-xl', 'prose']))
expect(pretty(compiler.build(['leading-base', 'leading-md', 'leading-xl', 'prose'])))
.toMatchInlineSnapshot(`
"@layer properties;
"
@layer properties;
.prose {
[class~=lead-base] {
font-size: 100rem;
@@ -444,8 +455,9 @@ describe('theme overrides order', () => {
}),
})
expect(compiler.build(['bg-red', 'bg-blue'])).toMatchInlineSnapshot(`
":root, :host {
expect(pretty(compiler.build(['bg-red', 'bg-blue']))).toMatchInlineSnapshot(`
"
:root, :host {
--color-blue: blue;
}
.bg-blue {
@@ -518,22 +530,25 @@ describe('theme overrides order', () => {
})
expect(
compiler.build([
'bg-slate-100',
'bg-slate-200',
'bg-slate-300',
'bg-slate-400',
'bg-slate-500',
'bg-slate-600',
'hover-bg-slate-100',
'hover-bg-slate-200',
'hover-bg-slate-300',
'hover-bg-slate-400',
'hover-bg-slate-500',
'hover-bg-slate-600',
]),
pretty(
compiler.build([
'bg-slate-100',
'bg-slate-200',
'bg-slate-300',
'bg-slate-400',
'bg-slate-500',
'bg-slate-600',
'hover-bg-slate-100',
'hover-bg-slate-200',
'hover-bg-slate-300',
'hover-bg-slate-400',
'hover-bg-slate-500',
'hover-bg-slate-600',
]),
),
).toMatchInlineSnapshot(`
":root, :host {
"
:root, :host {
--color-slate-100: #000100;
--color-slate-300: #000300;
--color-slate-400: #100400;
@@ -618,8 +633,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
".font-sans {
expect(pretty(compiler.build(['font-sans']))).toMatchInlineSnapshot(`
"
.font-sans {
font-family: Potato Sans;
}
"
@@ -653,8 +669,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
".font-sans {
expect(pretty(compiler.build(['font-sans']))).toMatchInlineSnapshot(`
"
.font-sans {
font-family: Potato Sans;
font-feature-settings: "cv06";
}
@@ -689,8 +706,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
".font-sans {
expect(pretty(compiler.build(['font-sans']))).toMatchInlineSnapshot(`
"
.font-sans {
font-family: Potato Sans;
font-variation-settings: "XHGT" 0.7;
}
@@ -728,8 +746,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
".font-sans {
expect(pretty(compiler.build(['font-sans']))).toMatchInlineSnapshot(`
"
.font-sans {
font-family: Potato Sans;
font-feature-settings: "cv06";
font-variation-settings: "XHGT" 0.7;
@@ -768,8 +787,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
":root, :host {
expect(pretty(compiler.build(['font-sans']))).toMatchInlineSnapshot(`
"
:root, :host {
--font-sans: Sandwich Sans;
}
.font-sans {
@@ -806,8 +826,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`
".font-sans {
expect(pretty(compiler.build(['font-sans']))).toMatchInlineSnapshot(`
"
.font-sans {
font-family: Inter, system-ui, sans-serif;
}
"
@@ -841,7 +862,7 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`""`)
expect(pretty(compiler.build(['font-sans']))).toEqual('')
})
test('overriding `fontFamily.mono` sets `--default-mono-font-family`', async () => {
@@ -869,8 +890,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
".font-mono {
expect(pretty(compiler.build(['font-mono']))).toMatchInlineSnapshot(`
"
.font-mono {
font-family: Potato Mono;
}
"
@@ -904,8 +926,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
".font-mono {
expect(pretty(compiler.build(['font-mono']))).toMatchInlineSnapshot(`
"
.font-mono {
font-family: Potato Mono;
font-feature-settings: "cv06";
}
@@ -940,8 +963,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
".font-mono {
expect(pretty(compiler.build(['font-mono']))).toMatchInlineSnapshot(`
"
.font-mono {
font-family: Potato Mono;
font-variation-settings: "XHGT" 0.7;
}
@@ -979,8 +1003,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
".font-mono {
expect(pretty(compiler.build(['font-mono']))).toMatchInlineSnapshot(`
"
.font-mono {
font-family: Potato Mono;
font-feature-settings: "cv06";
font-variation-settings: "XHGT" 0.7;
@@ -1019,8 +1044,9 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`
":root, :host {
expect(pretty(compiler.build(['font-mono']))).toMatchInlineSnapshot(`
"
:root, :host {
--font-mono: Sandwich Mono;
}
.font-mono {
@@ -1057,7 +1083,7 @@ describe('default font family compatibility', () => {
}),
})
expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`""`)
expect(pretty(compiler.build(['font-mono']))).toEqual('')
})
})
@@ -1091,23 +1117,26 @@ test('creates variants for `data`, `supports`, and `aria` theme options at the s
})
expect(
compiler.build([
'aria-polite:underline',
'supports-child-combinator:underline',
'supports-foo:underline',
'data-checked:underline',
pretty(
compiler.build([
'aria-polite:underline',
'supports-child-combinator:underline',
'supports-foo:underline',
'data-checked:underline',
// Ensure core variants still work
'aria-hidden:flex',
'supports-grid:flex',
'data-foo:flex',
// Ensure core variants still work
'aria-hidden:flex',
'supports-grid:flex',
'data-foo:flex',
// The `print` variant should still be sorted last, even after registering
// the other custom variants.
'print:flex',
]),
// The `print` variant should still be sorted last, even after registering
// the other custom variants.
'print:flex',
]),
),
).toMatchInlineSnapshot(`
".aria-hidden\\:flex {
"
.aria-hidden\\:flex {
&[aria-hidden="true"] {
display: flex;
}
@@ -1183,9 +1212,10 @@ test('merges css breakpoints with js config screens', async () => {
}),
})
expect(compiler.build(['sm:flex', 'md:flex', 'lg:flex', 'min-sm:max-md:underline']))
expect(pretty(compiler.build(['sm:flex', 'md:flex', 'lg:flex', 'min-sm:max-md:underline'])))
.toMatchInlineSnapshot(`
".sm\\:flex {
"
.sm\\:flex {
@media (width >= 44rem) {
display: flex;
}
@@ -1230,9 +1260,10 @@ test('utilities must be prefixed', async () => {
})
// Prefixed utilities are generated
expect(compiler.build(['tw:underline', 'tw:hover:line-through', 'tw:custom']))
expect(pretty(compiler.build(['tw:underline', 'tw:hover:line-through', 'tw:custom'])))
.toMatchInlineSnapshot(`
".tw\\:custom {
"
.tw\\:custom {
color: red;
}
.tw\\:underline {
@@ -1279,8 +1310,9 @@ test('utilities used in @apply must be prefixed', async () => {
)
// Prefixed utilities are generated
expect(compiler.build([])).toMatchInlineSnapshot(`
".my-underline {
expect(pretty(compiler.build([]))).toMatchInlineSnapshot(`
"
.my-underline {
text-decoration-line: underline;
}
"
@@ -1338,8 +1370,9 @@ test('Prefixes configured in CSS take precedence over those defined in JS config
},
)
expect(compiler.build(['wat:custom'])).toMatchInlineSnapshot(`
".wat\\:custom {
expect(pretty(compiler.build(['wat:custom']))).toMatchInlineSnapshot(`
"
.wat\\:custom {
color: red;
}
"
@@ -1385,24 +1418,26 @@ test('important: `#app`', async () => {
}),
})
expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(`
"#app {
.custom {
color: red;
}
.underline {
text-decoration-line: underline;
}
.hover\\:line-through {
&:hover {
@media (hover: hover) {
text-decoration-line: line-through;
expect(pretty(compiler.build(['underline', 'hover:line-through', 'custom'])))
.toMatchInlineSnapshot(`
"
#app {
.custom {
color: red;
}
.underline {
text-decoration-line: underline;
}
.hover\\:line-through {
&:hover {
@media (hover: hover) {
text-decoration-line: line-through;
}
}
}
}
}
"
`)
"
`)
})
test('important: true', async () => {
@@ -1423,22 +1458,24 @@ test('important: true', async () => {
}),
})
expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(`
".custom {
color: red !important;
}
.underline {
text-decoration-line: underline !important;
}
.hover\\:line-through {
&:hover {
@media (hover: hover) {
text-decoration-line: line-through !important;
expect(pretty(compiler.build(['underline', 'hover:line-through', 'custom'])))
.toMatchInlineSnapshot(`
"
.custom {
color: red !important;
}
.underline {
text-decoration-line: underline !important;
}
.hover\\:line-through {
&:hover {
@media (hover: hover) {
text-decoration-line: line-through !important;
}
}
}
}
"
`)
"
`)
})
test('blocklisted candidates are not generated', async () => {
@@ -1468,8 +1505,9 @@ test('blocklisted candidates are not generated', async () => {
expect(compiler.build(['bg-white'])).toEqual('')
// underline will as will md:bg-white
expect(compiler.build(['underline', 'bg-white', 'md:bg-white'])).toMatchInlineSnapshot(`
".underline {
expect(pretty(compiler.build(['underline', 'bg-white', 'md:bg-white']))).toMatchInlineSnapshot(`
"
.underline {
text-decoration-line: underline;
}
.md\\:bg-white {
@@ -1669,8 +1707,9 @@ test('handles setting theme keys to null', async () => {
},
)
expect(compiler.build(['bg-red-50', 'bg-red-100', 'bg-red-200'])).toMatchInlineSnapshot(`
":root, :host {
expect(pretty(compiler.build(['bg-red-50', 'bg-red-100', 'bg-red-200']))).toMatchInlineSnapshot(`
"
:root, :host {
--color-red-50: oklch(0.971 0.013 17.38);
--color-red-100: oklch(0.936 0.032 17.717);
--color-red-200: oklch(0.885 0.062 18.334);
@@ -1709,8 +1748,9 @@ test('The theme() function does not try indexing into strings', async () => {
@tailwind utilities;
`)
expect(compiler.build([])).toMatchInlineSnapshot(`
":root, :host {
expect(pretty(compiler.build([]))).toMatchInlineSnapshot(`
"
:root, :host {
--color-what: light-dark(#f00, #f00);
}
.text-what {
@@ -1749,17 +1789,20 @@ test('camel case keys are preserved', async () => {
)
expect(
compiler.build([
// From CSS
'bg-blue-green', // should be output
'bg-blueGreen', // should not
pretty(
compiler.build([
// From CSS
'bg-blue-green', // should be output
'bg-blueGreen', // should not
// From JS config
'bg-light-green', // should not be output
'bg-lightGreen', // should be
]),
// From JS config
'bg-light-green', // should not be output
'bg-lightGreen', // should be
]),
),
).toMatchInlineSnapshot(`
".bg-blue-green {
"
.bg-blue-green {
background-color: var(--color-blue-green);
}
.bg-lightGreen {
@@ -1,5 +1,6 @@
import { expect, test } from 'vitest'
import { compile } from '..'
import { pretty } from '../test-utils/run'
const css = String.raw
@@ -31,8 +32,9 @@ test('creates a custom utility to extend the built-in container', async () => {
}),
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
".container {
expect(pretty(compiler.build(['container']))).toMatchInlineSnapshot(`
"
.container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -90,8 +92,9 @@ test('allows padding to be defined at custom breakpoints', async () => {
}),
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
".container {
expect(pretty(compiler.build(['container']))).toMatchInlineSnapshot(`
"
.container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -152,8 +155,9 @@ test('allows breakpoints to be overwritten', async () => {
}),
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
".container {
expect(pretty(compiler.build(['container']))).toMatchInlineSnapshot(`
"
.container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -219,8 +223,9 @@ test('padding applies to custom `container` screens', async () => {
}),
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
".container {
expect(pretty(compiler.build(['container']))).toMatchInlineSnapshot(`
"
.container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -283,8 +288,9 @@ test("an empty `screen` config will undo all custom media screens and won't appl
}),
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
".container {
expect(pretty(compiler.build(['container']))).toMatchInlineSnapshot(`
"
.container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -349,8 +355,9 @@ test('legacy container component does not interfere with new --container variabl
}),
})
expect(compiler.build(['max-w-sm'])).toMatchInlineSnapshot(`
":root, :host {
expect(pretty(compiler.build(['max-w-sm']))).toMatchInlineSnapshot(`
"
:root, :host {
--container-sm: 24rem;
}
.max-w-sm {
@@ -396,8 +403,9 @@ test('combines custom padding and screen overwrites', async () => {
}),
})
expect(compiler.build(['container', '!container'])).toMatchInlineSnapshot(`
".\\!container {
expect(pretty(compiler.build(['container', '!container']))).toMatchInlineSnapshot(`
"
.\\!container {
width: 100% !important;
@media (width >= 40rem) {
max-width: 40rem !important;
@@ -509,8 +517,9 @@ test('filters out complex breakpoints', async () => {
}),
})
expect(compiler.build(['container'])).toMatchInlineSnapshot(`
".container {
expect(pretty(compiler.build(['container']))).toMatchInlineSnapshot(`
"
.container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
@@ -21,7 +21,8 @@ test('bg-gradient-*', async () => {
],
),
).toMatchInlineSnapshot(`
".bg-gradient-to-b {
"
.bg-gradient-to-b {
--tw-gradient-position: to bottom in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
@@ -59,7 +60,8 @@ test('bg-gradient-*', async () => {
.bg-gradient-to-tr {
--tw-gradient-position: to top right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}"
}
"
`)
})
@@ -85,7 +87,8 @@ test('max-w-screen', async () => {
],
),
).toMatchInlineSnapshot(`
":root, :host {
"
:root, :host {
--breakpoint-sm: 40rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 64rem;
@@ -111,7 +114,8 @@ test('max-w-screen', async () => {
.max-w-screen-xl {
max-width: var(--breakpoint-xl);
}"
}
"
`)
expect(
await run([
@@ -134,7 +138,8 @@ test('max-w-screen', async () => {
test('box-decoration', async () => {
expect(await run(['decoration-slice', 'decoration-clone'])).toMatchInlineSnapshot(`
".decoration-clone {
"
.decoration-clone {
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
}
@@ -142,21 +147,25 @@ test('box-decoration', async () => {
.decoration-slice {
-webkit-box-decoration-break: slice;
box-decoration-break: slice;
}"
}
"
`)
})
test('overflow-ellipsis', async () => {
expect(await run(['overflow-ellipsis'])).toMatchInlineSnapshot(`
".overflow-ellipsis {
"
.overflow-ellipsis {
text-overflow: ellipsis;
}"
}
"
`)
})
test('flex-grow', async () => {
expect(await run(['flex-grow', 'flex-grow-0', 'flex-grow-[123]'])).toMatchInlineSnapshot(`
".flex-grow {
"
.flex-grow {
flex-grow: 1;
}
@@ -166,7 +175,8 @@ test('flex-grow', async () => {
.flex-grow-\\[123\\] {
flex-grow: 123;
}"
}
"
`)
expect(
await run([
@@ -185,7 +195,8 @@ test('flex-grow', async () => {
test('flex-shrink', async () => {
expect(await run(['flex-shrink', 'flex-shrink-0', 'flex-shrink-[123]'])).toMatchInlineSnapshot(`
".flex-shrink {
"
.flex-shrink {
flex-shrink: 1;
}
@@ -195,7 +206,8 @@ test('flex-shrink', async () => {
.flex-shrink-\\[123\\] {
flex-shrink: 123;
}"
}
"
`)
expect(
await run([
@@ -234,7 +246,8 @@ test('start', async () => {
],
),
).toMatchInlineSnapshot(`
":root, :host {
"
:root, :host {
--spacing: .25rem;
--inset-shadowned: 1940px;
}
@@ -269,7 +282,8 @@ test('start', async () => {
.start-shadowned {
inset-inline-start: var(--inset-shadowned);
}"
}
"
`)
expect(
await compileCss(
@@ -321,7 +335,8 @@ test('end', async () => {
],
),
).toMatchInlineSnapshot(`
":root, :host {
"
:root, :host {
--spacing: .25rem;
--inset-shadowned: 1940px;
}
@@ -356,7 +371,8 @@ test('end', async () => {
.end-shadowned {
inset-inline-end: var(--inset-shadowned);
}"
}
"
`)
expect(
await compileCss(
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
import { describe, expect, test } from 'vitest'
import { compile } from '..'
import { pretty } from '../test-utils/run'
const css = String.raw
@@ -36,18 +37,21 @@ test('CSS `--breakpoint-*` merge with JS config `screens`', async () => {
})
expect(
compiler.build([
'sm:flex',
'md:flex',
'lg:flex',
'min-sm:max-md:underline',
'min-md:max-lg:underline',
'max-w-screen-sm',
// Ensure other core variants appear at the end
'print:items-end',
]),
pretty(
compiler.build([
'sm:flex',
'md:flex',
'lg:flex',
'min-sm:max-md:underline',
'min-md:max-lg:underline',
'max-w-screen-sm',
// Ensure other core variants appear at the end
'print:items-end',
]),
),
).toMatchInlineSnapshot(`
".max-w-screen-sm {
"
.max-w-screen-sm {
max-width: 44rem;
}
.sm\\:flex {
@@ -121,22 +125,25 @@ test('JS config `screens` extend CSS `--breakpoint-*`', async () => {
})
expect(
compiler.build([
// Order is messed up on purpose
'md:flex',
'sm:flex',
'lg:flex',
'xs:flex',
'min-md:max-lg:underline',
'min-sm:max-md:underline',
'min-xs:flex',
'min-xs:max-md:underline',
pretty(
compiler.build([
// Order is messed up on purpose
'md:flex',
'sm:flex',
'lg:flex',
'xs:flex',
'min-md:max-lg:underline',
'min-sm:max-md:underline',
'min-xs:flex',
'min-xs:max-md:underline',
// Ensure other core variants appear at the end
'print:items-end',
]),
// Ensure other core variants appear at the end
'print:items-end',
]),
),
).toMatchInlineSnapshot(`
".min-xs\\:flex {
"
.min-xs\\:flex {
@media (width >= 30rem) {
display: flex;
}
@@ -214,19 +221,22 @@ test('JS config `screens` only setup, even if those match the default-theme expo
})
expect(
compiler.build([
// Order is messed up on purpose
'md:flex',
'sm:flex',
'lg:flex',
'min-md:max-lg:underline',
'min-sm:max-md:underline',
pretty(
compiler.build([
// Order is messed up on purpose
'md:flex',
'sm:flex',
'lg:flex',
'min-md:max-lg:underline',
'min-sm:max-md:underline',
// Ensure other core variants appear at the end
'print:items-end',
]),
// Ensure other core variants appear at the end
'print:items-end',
]),
),
).toMatchInlineSnapshot(`
".sm\\:flex {
"
.sm\\:flex {
@media (width >= 40rem) {
display: flex;
}
@@ -296,22 +306,25 @@ test('JS config `screens` overwrite CSS `--breakpoint-*`', async () => {
})
expect(
compiler.build([
'sm:flex',
'md:flex',
'mini:flex',
'midi:flex',
'maxi:flex',
'min-md:max-lg:underline',
'min-sm:max-md:underline',
'min-midi:max-maxi:underline',
'min-mini:max-midi:underline',
pretty(
compiler.build([
'sm:flex',
'md:flex',
'mini:flex',
'midi:flex',
'maxi:flex',
'min-md:max-lg:underline',
'min-sm:max-md:underline',
'min-midi:max-maxi:underline',
'min-mini:max-midi:underline',
// Ensure other core variants appear at the end
'print:items-end',
]),
// Ensure other core variants appear at the end
'print:items-end',
]),
),
).toMatchInlineSnapshot(`
".mini\\:flex {
"
.mini\\:flex {
@media (width >= 40rem) {
display: flex;
}
@@ -402,18 +415,21 @@ test('JS config with `theme: { extends }` should not include the `default-config
).toBe('')
expect(
compiler.build([
'mini:flex',
'midi:flex',
'maxi:flex',
'min-midi:max-maxi:underline',
'min-mini:max-midi:underline',
pretty(
compiler.build([
'mini:flex',
'midi:flex',
'maxi:flex',
'min-midi:max-maxi:underline',
'min-mini:max-midi:underline',
// Ensure other core variants appear at the end
'print:items-end',
]),
// Ensure other core variants appear at the end
'print:items-end',
]),
),
).toMatchInlineSnapshot(`
".mini\\:flex {
"
.mini\\:flex {
@media (width >= 40rem) {
display: flex;
}
@@ -485,19 +501,22 @@ describe('complex screen configs', () => {
expect(compiler.build(['min-sm:flex', 'min-md:flex', 'min-xl:flex', 'min-tall:flex'])).toBe('')
expect(
compiler.build([
'sm:flex',
'md:flex',
'lg:flex',
'min-lg:flex',
'xl:flex',
'tall:flex',
pretty(
compiler.build([
'sm:flex',
'md:flex',
'lg:flex',
'min-lg:flex',
'xl:flex',
'tall:flex',
// Ensure other core variants appear at the end
'print:items-end',
]),
// Ensure other core variants appear at the end
'print:items-end',
]),
),
).toMatchInlineSnapshot(`
".lg\\:flex {
"
.lg\\:flex {
@media (width >= 868px) {
display: flex;
}
@@ -564,18 +583,21 @@ describe('complex screen configs', () => {
})
expect(
compiler.build([
'sm:flex',
'md:flex',
'portrait:flex',
'min-sm:flex',
'min-md:flex',
'min-portrait:flex',
// Ensure other core variants appear at the end
'print:items-end',
]),
pretty(
compiler.build([
'sm:flex',
'md:flex',
'portrait:flex',
'min-sm:flex',
'min-md:flex',
'min-portrait:flex',
// Ensure other core variants appear at the end
'print:items-end',
]),
),
).toMatchInlineSnapshot(`
".min-sm\\:flex {
"
.min-sm\\:flex {
@media (width >= 40rem) {
display: flex;
}
@@ -644,5 +666,5 @@ test('JS config `screens` can overwrite default CSS `--breakpoint-*`', async ()
// currently.
expect(
compiler.build(['min-sm:flex', 'min-md:flex', 'min-lg:flex', 'min-xl:flex', 'min-2xl:flex']),
).toMatchInlineSnapshot(`""`)
).toEqual('')
})
+223 -113
View File
@@ -16,9 +16,11 @@ describe('--alpha(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".foo {
"
.foo {
margin: oklab(62.7955% .224 .125 / .5);
}"
}
"
`)
})
@@ -72,13 +74,15 @@ describe('--spacing(…)', () => {
}
`),
).toMatchInlineSnapshot(`
":root, :host {
"
:root, :host {
--spacing: .25rem;
}
.foo {
margin: calc(var(--spacing) * 4);
}"
}
"
`)
})
@@ -94,9 +98,11 @@ describe('--spacing(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".foo {
"
.foo {
margin: 1rem;
}"
}
"
`)
})
@@ -153,13 +159,15 @@ describe('--theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
":root, :host {
"
:root, :host {
--color-red-500: red;
}
.red {
color: var(--color-red-500);
}"
}
"
`)
})
@@ -174,9 +182,11 @@ describe('--theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -191,7 +201,8 @@ describe('--theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
":root, :host {
"
:root, :host {
--color-red-500: red;
}
@@ -203,7 +214,8 @@ describe('--theme(…)', () => {
.red {
color: color-mix(in oklab, var(--color-red-500) 50%, transparent);
}
}"
}
"
`)
})
@@ -218,9 +230,11 @@ describe('--theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: oklab(62.7955% .224863 .125846);
}"
}
"
`)
})
@@ -235,9 +249,11 @@ describe('--theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
font-family: var(--tw-default-font-family, Potato Sans, sans-serif);
}"
}
"
`)
})
@@ -252,9 +268,11 @@ describe('--theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
font-family: var(--tw-default-font-family, Potato Sans, sans-serif);
}"
}
"
`)
})
@@ -269,9 +287,11 @@ describe('--theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
font-family: Potato Sans, sans-serif;
}"
}
"
`)
})
@@ -286,9 +306,11 @@ describe('--theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
font-family: Potato Sans, sans-serif;
}"
}
"
`)
})
@@ -312,7 +334,8 @@ describe('--theme(…)', () => {
['tw:font-sans'],
),
).toMatchInlineSnapshot(`
":root, :host {
"
:root, :host {
--tw-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji";
--tw-default-font-family: var(--tw-font-sans);
@@ -326,7 +349,8 @@ describe('--theme(…)', () => {
.tw\\:font-sans {
font-family: var(--tw-font-sans);
}"
}
"
`)
})
@@ -350,7 +374,8 @@ describe('--theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
"@media (min-width: 48rem) {
"
@media (min-width: 48rem) {
.blue {
color: #00f;
}
@@ -360,7 +385,8 @@ describe('--theme(…)', () => {
.red {
color: red;
}
}"
}
"
`)
})
@@ -420,7 +446,8 @@ describe('--theme(…)', () => {
},
),
).toMatchInlineSnapshot(`
"@layer base {
"
@layer base {
html, :host {
font-family: var(--default-font-family, system-ui);
}
@@ -431,7 +458,8 @@ describe('--theme(…)', () => {
--font-sans: sans-serif;
--default-font-family: var(--font-sans);
}
}"
}
"
`)
})
})
@@ -450,9 +478,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -467,9 +497,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -484,9 +516,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -501,9 +535,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -518,9 +554,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -535,9 +573,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -552,9 +592,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: oklab(62.7955% .224 .125 / .75);
}"
}
"
`)
})
@@ -569,9 +611,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: oklab(62.7955% .224 .125 / .75);
}"
}
"
`)
})
@@ -586,9 +630,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: oklab(62.7955% .224 .125 / .75);
}"
}
"
`)
})
@@ -603,7 +649,8 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}
@@ -611,7 +658,8 @@ describe('theme(…)', () => {
.red {
color: color-mix(in oklab, red var(--opacity), transparent);
}
}"
}
"
`)
})
@@ -627,7 +675,8 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}
@@ -635,7 +684,8 @@ describe('theme(…)', () => {
.red {
color: color-mix(in oklab, red var(--opacity, 50%), transparent);
}
}"
}
"
`)
})
@@ -650,9 +700,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".space-on-the-left {
"
.space-on-the-left {
margin-left: 3rem;
}"
}
"
`)
})
@@ -667,9 +719,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".space-on-the-left {
"
.space-on-the-left {
margin-left: .625rem;
}"
}
"
`)
})
@@ -684,9 +738,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".space-on-the-left {
"
.space-on-the-left {
margin-left: calc(100vh - .625rem);
}"
}
"
`)
})
@@ -701,9 +757,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".radius {
"
.radius {
border-radius: .5rem;
}"
}
"
`)
})
@@ -719,9 +777,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".default-blur {
"
.default-blur {
filter: blur(8px);
}"
}
"
`)
})
@@ -738,10 +798,12 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".text {
"
.text {
font-size: 1337.75rem;
line-height: 1337rem;
}"
}
"
`)
})
@@ -758,9 +820,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".fam {
"
.fam {
font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
}"
}
"
`)
})
@@ -781,10 +845,12 @@ describe('theme(…)', () => {
},
)
expect(optimizeCss(compiled.build([])).trim()).toMatchInlineSnapshot(`
".fam {
expect(optimizeCss(compiled.build([]))).toMatchInlineSnapshot(`
"
.fam {
font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
}"
}
"
`)
})
})
@@ -810,9 +876,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -827,9 +895,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: oklab(62.7955% .224 .125 / .25);
}"
}
"
`)
})
@@ -841,9 +911,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".fam {
"
.fam {
font-family: Helvetica Neue, Helvetica, sans-serif;
}"
}
"
`)
})
@@ -861,9 +933,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".fam {
"
.fam {
font-family: Helvetica Neue, Helvetica, sans-serif;
}"
}
"
`)
})
})
@@ -881,9 +955,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -899,9 +975,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: oklab(62.7955% .224 .125 / .25);
}"
}
"
`)
})
})
@@ -918,9 +996,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -935,9 +1015,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: oklab(62.7955% .224 .125 / .5);
}"
}
"
`)
})
@@ -952,9 +1034,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
})
@@ -971,9 +1055,11 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
".blur {
"
.blur {
filter: blur(8px);
}"
}
"
`)
})
})
@@ -1026,11 +1112,13 @@ describe('theme(…)', () => {
['sm:[--color:theme(colors.red[500])]'],
),
).toMatchInlineSnapshot(`
"@media (min-width: 40rem) {
"
@media (min-width: 40rem) {
.sm\\:\\[--color\\:theme\\(colors\\.red\\[500\\]\\)\\] {
--color: red;
}
}"
}
"
`)
})
@@ -1051,9 +1139,11 @@ describe('theme(…)', () => {
],
),
).toMatchInlineSnapshot(`
".rounded-\\[theme\\(--radius-sm\\)\\] {
"
.rounded-\\[theme\\(--radius-sm\\)\\] {
border-radius: 2rem;
}"
}
"
`)
// This guarantees no output for the following candidates
@@ -1087,11 +1177,13 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
"@media (min-width: 48rem) and (max-width: 64rem) {
"
@media (min-width: 48rem) and (max-width: 64rem) {
.red {
color: red;
}
}"
}
"
`)
})
@@ -1109,11 +1201,13 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
"@media (min-width: 48rem) and (not (min-width: 64rem)) {
"
@media (min-width: 48rem) and (not (min-width: 64rem)) {
.red {
color: red;
}
}"
}
"
`)
})
})
@@ -1132,11 +1226,13 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
"@media (min-width: 48rem) {
"
@media (min-width: 48rem) {
.red {
color: red;
}
}"
}
"
`)
})
@@ -1153,11 +1249,13 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
"@container not (max-width: 48rem) {
"
@container not (max-width: 48rem) {
.red {
color: red;
}
}"
}
"
`)
})
@@ -1174,11 +1272,13 @@ describe('theme(…)', () => {
}
`),
).toMatchInlineSnapshot(`
"@supports (text-stroke: 0.75rem) {
"
@supports (text-stroke: 0.75rem) {
.red {
color: red;
}
}"
}
"
`)
})
})
@@ -1225,8 +1325,9 @@ describe('in plugins', () => {
},
)
expect(optimizeCss(compiled.build(['my-utility'])).trim()).toMatchInlineSnapshot(`
"@layer base {
expect(optimizeCss(compiled.build(['my-utility']))).toMatchInlineSnapshot(`
"
@layer base {
.my-base-rule {
color: oklch(62% .25 30);
background-color: oklch(45% .31 264);
@@ -1239,7 +1340,8 @@ describe('in plugins', () => {
.my-utility {
color: oklch(62% .25 30);
}
}"
}
"
`)
})
})
@@ -1292,8 +1394,9 @@ describe('in JS config files', () => {
},
)
expect(optimizeCss(compiled.build(['my-utility'])).trim()).toMatchInlineSnapshot(`
"@layer base {
expect(optimizeCss(compiled.build(['my-utility']))).toMatchInlineSnapshot(`
"
@layer base {
.my-base-rule {
color: orange;
background: red;
@@ -1304,7 +1407,8 @@ describe('in JS config files', () => {
.my-utility {
color: red;
}
}"
}
"
`)
})
})
@@ -1334,9 +1438,11 @@ test('replaces CSS theme() function with values inside imported stylesheets', as
},
),
).toMatchInlineSnapshot(`
".red {
"
.red {
color: red;
}"
}
"
`)
})
@@ -1355,9 +1461,11 @@ test('resolves paths ending with a 1', async () => {
[],
),
).toMatchInlineSnapshot(`
".foo {
"
.foo {
margin: .25rem;
}"
}
"
`)
})
@@ -1372,8 +1480,10 @@ test('upgrades to a full JS compat theme lookup if a value cannot be mapped to a
[],
),
).toMatchInlineSnapshot(`
".semi {
"
.semi {
font-weight: 600;
}"
}
"
`)
})
+14 -9
View File
@@ -1,6 +1,6 @@
import { expect, test } from 'vitest'
import { compile } from '.'
import { compileCss } from './test-utils/run'
import { compileCss, pretty } from './test-utils/run'
const css = String.raw
@@ -14,8 +14,9 @@ test('Utilities can be wrapped in a selector', async () => {
let compiler = await compile(input)
expect(compiler.build(['underline', 'hover:line-through'])).toMatchInlineSnapshot(`
"#app {
expect(pretty(compiler.build(['underline', 'hover:line-through']))).toMatchInlineSnapshot(`
"
#app {
.underline {
text-decoration-line: underline;
}
@@ -45,8 +46,9 @@ test('Utilities can be marked with important', async () => {
}),
})
expect(compiler.build(['underline', 'hover:line-through'])).toMatchInlineSnapshot(`
".underline {
expect(pretty(compiler.build(['underline', 'hover:line-through']))).toMatchInlineSnapshot(`
"
.underline {
text-decoration-line: underline !important;
}
.hover\\:line-through {
@@ -73,8 +75,9 @@ test('Utilities can be wrapped with a selector and marked as important', async (
let compiler = await compile(input)
expect(compiler.build(['underline', 'hover:line-through'])).toMatchInlineSnapshot(`
"#app {
expect(pretty(compiler.build(['underline', 'hover:line-through']))).toMatchInlineSnapshot(`
"
#app {
.underline {
text-decoration-line: underline !important;
}
@@ -102,7 +105,8 @@ test('variables in utilities should not be marked as important', async () => {
['ease-out!', 'z-10!'],
),
).toMatchInlineSnapshot(`
"@layer properties {
"
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
*, :before, :after, ::backdrop {
--tw-ease: initial;
@@ -126,6 +130,7 @@ test('variables in utilities should not be marked as important', async () => {
@property --tw-ease {
syntax: "*";
inherits: false
}"
}
"
`)
})
File diff suppressed because it is too large Load Diff
+7 -4
View File
@@ -1,6 +1,7 @@
import { expect, test } from 'vitest'
import { compile } from '.'
import plugin from './plugin'
import { pretty } from './test-utils/run'
const css = String.raw
@@ -23,8 +24,9 @@ test('plugin', async () => {
}),
})
expect(compiler.build([])).toMatchInlineSnapshot(`
"@layer base {
expect(pretty(compiler.build([]))).toMatchInlineSnapshot(`
"
@layer base {
body {
margin: 0;
}
@@ -54,8 +56,9 @@ test('plugin.withOptions', async () => {
}),
})
expect(compiler.build([])).toMatchInlineSnapshot(`
"@layer base {
expect(pretty(compiler.build([]))).toMatchInlineSnapshot(`
"
@layer base {
body {
margin: 1px;
}
+46 -32
View File
@@ -1,6 +1,7 @@
import { expect, test } from 'vitest'
import { compile } from '.'
import plugin from './plugin'
import { pretty } from './test-utils/run'
const css = String.raw
@@ -18,15 +19,18 @@ test('utilities must be prefixed', async () => {
// Prefixed utilities are generated
expect(
compiler.build([
'tw:underline',
'tw:hover:line-through',
'tw:custom',
'tw:group-hover:flex',
'tw:peer-hover:flex',
]),
pretty(
compiler.build([
'tw:underline',
'tw:hover:line-through',
'tw:custom',
'tw:group-hover:flex',
'tw:peer-hover:flex',
]),
),
).toMatchInlineSnapshot(`
".tw\\:custom {
"
.tw\\:custom {
color: red;
}
.tw\\:underline {
@@ -72,8 +76,9 @@ test('utilities used in @apply must be prefixed', async () => {
`)
// Prefixed utilities are generated
expect(compiler.build([])).toMatchInlineSnapshot(`
".my-underline {
expect(pretty(compiler.build([]))).toMatchInlineSnapshot(`
"
.my-underline {
text-decoration-line: underline;
}
"
@@ -105,8 +110,9 @@ test('CSS variables output by the theme are prefixed', async () => {
`)
// Prefixed utilities are generated
expect(compiler.build(['tw:text-red'])).toMatchInlineSnapshot(`
":root, :host {
expect(pretty(compiler.build(['tw:text-red']))).toMatchInlineSnapshot(`
"
:root, :host {
--tw-color-red: #f00;
}
.tw\\:text-red {
@@ -127,9 +133,10 @@ test('CSS theme functions do not use the prefix', async () => {
@tailwind utilities;
`)
expect(compiler.build(['tw:[color:theme(--color-red)]', 'tw:text-[theme(--color-red)]']))
expect(pretty(compiler.build(['tw:[color:theme(--color-red)]', 'tw:text-[theme(--color-red)]'])))
.toMatchInlineSnapshot(`
".tw\\:\\[color\\:theme\\(--color-red\\)\\] {
"
.tw\\:\\[color\\:theme\\(--color-red\\)\\] {
color: #f00;
}
.tw\\:text-\\[theme\\(--color-red\\)\\] {
@@ -186,8 +193,9 @@ test('JS theme functions do not use the prefix', async () => {
},
)
expect(compiler.build(['tw:my-custom'])).toMatchInlineSnapshot(`
".tw\\:my-custom {
expect(pretty(compiler.build(['tw:my-custom']))).toMatchInlineSnapshot(`
"
.tw\\:my-custom {
color: #f00;
}
"
@@ -220,16 +228,19 @@ test('a prefix can be configured via @import theme(…)', async () => {
// Prefixed utilities are generated
expect(
compiler.build([
'tw:underline',
'tw:bg-potato',
'tw:hover:line-through',
'tw:custom',
'flex',
'text-potato',
]),
pretty(
compiler.build([
'tw:underline',
'tw:bg-potato',
'tw:hover:line-through',
'tw:custom',
'flex',
'text-potato',
]),
),
).toMatchInlineSnapshot(`
".tw\\:bg-potato {
"
.tw\\:bg-potato {
background-color: var(--tw-color-potato, #7a4724);
}
.tw\\:custom {
@@ -263,7 +274,7 @@ test('a prefix can be configured via @import theme(…)', async () => {
},
})
expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(`""`)
expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toEqual('')
})
test('a prefix can be configured via @import prefix(…)', async () => {
@@ -290,9 +301,11 @@ test('a prefix can be configured via @import prefix(…)', async () => {
},
})
expect(compiler.build(['tw:underline', 'tw:bg-potato', 'tw:hover:line-through', 'tw:custom']))
.toMatchInlineSnapshot(`
":root, :host {
expect(
pretty(compiler.build(['tw:underline', 'tw:bg-potato', 'tw:hover:line-through', 'tw:custom'])),
).toMatchInlineSnapshot(`
"
:root, :host {
--tw-color-potato: #7a4724;
}
.tw\\:bg-potato {
@@ -330,7 +343,7 @@ test('a prefix can be configured via @import prefix(…)', async () => {
},
})
expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(`""`)
expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toEqual('')
})
test('a prefix must be letters only', async () => {
@@ -351,8 +364,9 @@ test('a candidate matching the prefix does not crash', async () => {
let compiler = await compile(input)
expect(compiler.build(['tomato', 'tomato:flex'])).toMatchInlineSnapshot(`
".tomato\\:flex {
expect(pretty(compiler.build(['tomato', 'tomato:flex']))).toMatchInlineSnapshot(`
"
.tomato\\:flex {
display: flex;
}
"
+9 -3
View File
@@ -7,14 +7,20 @@ export async function compileCss(
options: Parameters<typeof compile>[1] = {},
) {
let { build } = await compile(css, options)
return optimize(build(candidates)).code.trim()
return pretty(optimize(build(candidates)).code)
}
export async function run(candidates: string[]) {
let { build } = await compile('@tailwind utilities;')
return optimize(build(candidates)).code.trim()
return pretty(optimize(build(candidates)).code)
}
export function optimizeCss(input: string) {
return optimize(input).code
return pretty(optimize(input).code)
}
export function pretty(input: string) {
input = input.trim()
if (input === '') return ''
return `\n${input}\n`
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff