Skip to content

Commit 4255671

Browse files
authored
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
1 parent 8c77989 commit 4255671

19 files changed

Lines changed: 3881 additions & 2172 deletions

packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

33
exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
4-
"@layer properties {
4+
"
5+
@layer properties {
56
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
67
*, :before, :after, ::backdrop {
78
--tw-font-weight: initial;
@@ -304,5 +305,6 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
304305
@property --tw-font-weight {
305306
syntax: "*";
306307
inherits: false
307-
}"
308+
}
309+
"
308310
`;

packages/@tailwindcss-postcss/src/index.test.ts

Lines changed: 62 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { tmpdir } from 'node:os'
44
import path from 'path'
55
import postcss from 'postcss'
66
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
7+
import { pretty } from '../../tailwindcss/src/test-utils/run'
78
import tailwindcss from './index'
89

910
// We give this file path to PostCSS for processing.
@@ -26,7 +27,7 @@ test("`@import 'tailwindcss'` is replaced with the generated CSS", async () => {
2627

2728
let result = await processor.process(`@import 'tailwindcss'`, { from: inputCssFilePath() })
2829

29-
expect(result.css.trim()).toMatchSnapshot()
30+
expect(pretty(result.css)).toMatchSnapshot()
3031

3132
// Check for dependency messages
3233
expect(result.messages).toContainEqual({
@@ -72,16 +73,18 @@ test('output is optimized by Lightning CSS', async () => {
7273
{ from: inputCssFilePath() },
7374
)
7475

75-
expect(result.css.trim()).toMatchInlineSnapshot(`
76-
"@layer utilities {
76+
expect(pretty(result.css)).toMatchInlineSnapshot(`
77+
"
78+
@layer utilities {
7779
.foo {
7880
color: #000;
7981
}
8082
8183
.bar {
8284
color: red;
8385
}
84-
}"
86+
}
87+
"
8588
`)
8689
})
8790

@@ -100,10 +103,12 @@ test('@apply can be used without emitting the theme in the CSS file', async () =
100103
{ from: inputCssFilePath() },
101104
)
102105

103-
expect(result.css.trim()).toMatchInlineSnapshot(`
104-
".foo {
106+
expect(pretty(result.css)).toMatchInlineSnapshot(`
107+
"
108+
.foo {
105109
color: var(--color-red-500, oklch(63.7% .237 25.331));
106-
}"
110+
}
111+
"
107112
`)
108113
})
109114

@@ -164,8 +169,9 @@ describe('plugins', () => {
164169
{ from: inputCssFilePath() },
165170
)
166171

167-
expect(result.css.trim()).toMatchInlineSnapshot(`
168-
".underline {
172+
expect(pretty(result.css)).toMatchInlineSnapshot(`
173+
"
174+
.underline {
169175
text-decoration-line: underline;
170176
}
171177
@@ -177,7 +183,8 @@ describe('plugins', () => {
177183
178184
.hocus\\:underline:focus, .hocus\\:underline:hover {
179185
text-decoration-line: underline;
180-
}"
186+
}
187+
"
181188
`)
182189
})
183190

@@ -194,8 +201,9 @@ describe('plugins', () => {
194201
{ from: `${__dirname}/fixtures/another-project/input.css` },
195202
)
196203

197-
expect(result.css.trim()).toMatchInlineSnapshot(`
198-
".underline {
204+
expect(pretty(result.css)).toMatchInlineSnapshot(`
205+
"
206+
.underline {
199207
text-decoration-line: underline;
200208
}
201209
@@ -207,7 +215,8 @@ describe('plugins', () => {
207215
208216
.hocus\\:underline:focus, .hocus\\:underline:hover {
209217
text-decoration-line: underline;
210-
}"
218+
}
219+
"
211220
`)
212221
})
213222

@@ -224,8 +233,9 @@ describe('plugins', () => {
224233
{ from: inputCssFilePath() },
225234
)
226235

227-
expect(result.css.trim()).toMatchInlineSnapshot(`
228-
".underline {
236+
expect(pretty(result.css)).toMatchInlineSnapshot(`
237+
"
238+
.underline {
229239
text-decoration-line: underline;
230240
}
231241
@@ -237,7 +247,8 @@ describe('plugins', () => {
237247
238248
.hocus\\:underline:focus, .hocus\\:underline:hover {
239249
text-decoration-line: underline;
240-
}"
250+
}
251+
"
241252
`)
242253
})
243254
})
@@ -260,10 +271,12 @@ test('bail early when Tailwind is not used', async () => {
260271
// didn't use `@tailwind utilities` we didn't scan for utilities.
261272
expect(result.css).not.toContain('.underline {')
262273

263-
expect(result.css.trim()).toMatchInlineSnapshot(`
264-
".custom-css {
274+
expect(pretty(result.css)).toMatchInlineSnapshot(`
275+
"
276+
.custom-css {
265277
color: red;
266-
}"
278+
}
279+
"
267280
`)
268281
})
269282

@@ -285,12 +298,14 @@ test('handle CSS when only using a `@reference` (we should not bail early)', asy
285298
{ from: inputCssFilePath() },
286299
)
287300

288-
expect(result.css.trim()).toMatchInlineSnapshot(`
289-
"@media (min-width: 48rem) {
301+
expect(pretty(result.css)).toMatchInlineSnapshot(`
302+
"
303+
@media (min-width: 48rem) {
290304
.foo {
291305
bar: baz;
292306
}
293-
}"
307+
}
308+
"
294309
`)
295310
})
296311

@@ -310,10 +325,12 @@ test('handle CSS when using a `@variant` using variants that do not rely on the
310325
{ from: inputCssFilePath() },
311326
)
312327

313-
expect(result.css.trim()).toMatchInlineSnapshot(`
314-
".foo[data-is-hoverable] {
328+
expect(pretty(result.css)).toMatchInlineSnapshot(`
329+
"
330+
.foo[data-is-hoverable] {
315331
bar: baz;
316-
}"
332+
}
333+
"
317334
`)
318335
})
319336

@@ -348,23 +365,29 @@ test('runs `Once` plugins in the right order', async () => {
348365
{ from: inputCssFilePath() },
349366
)
350367

351-
expect(result.css.trim()).toMatchInlineSnapshot(`
352-
".custom-css {
368+
expect(pretty(result.css)).toMatchInlineSnapshot(`
369+
"
370+
.custom-css {
353371
color: red;
354-
}"
372+
}
373+
"
355374
`)
356-
expect(before).toMatchInlineSnapshot(`
357-
"@theme {
375+
expect(pretty(before)).toMatchInlineSnapshot(`
376+
"
377+
@theme {
358378
--color-red-500: red;
359379
}
360380
.custom-css {
361381
color: theme(--color-red-500);
362-
}"
382+
}
383+
"
363384
`)
364-
expect(after).toMatchInlineSnapshot(`
365-
".custom-css {
385+
expect(pretty(after)).toMatchInlineSnapshot(`
386+
"
387+
.custom-css {
366388
color: red;
367-
}"
389+
}
390+
"
368391
`)
369392
})
370393

@@ -438,10 +461,12 @@ test('does not register the input file as a dependency, even if it is passed in
438461

439462
let result = await processor.process(`@tailwind utilities`, { from: './input.css' })
440463

441-
expect(result.css.trim()).toMatchInlineSnapshot(`
442-
".underline {
464+
expect(pretty(result.css)).toMatchInlineSnapshot(`
465+
"
466+
.underline {
443467
text-decoration-line: underline;
444-
}"
468+
}
469+
"
445470
`)
446471

447472
// Check for dependency messages

packages/@tailwindcss-postcss/src/postcss-fix-relative-paths/index.test.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import postcss from 'postcss'
44
import atImport from 'postcss-import'
55
import { describe, expect, test } from 'vitest'
66
import fixRelativePathsPlugin from '.'
7+
import { pretty } from '../../../tailwindcss/src/test-utils/run'
78

89
describe('fixRelativePathsPlugin', () => {
910
test('rewrites @source and @plugin to be relative to the initial css file', async () => {
@@ -14,11 +15,13 @@ describe('fixRelativePathsPlugin', () => {
1415

1516
let result = await processor.process(css, { from: cssPath })
1617

17-
expect(result.css.trim()).toMatchInlineSnapshot(`
18-
"@source "../../example-project/src/**/*.ts";
18+
expect(pretty(result.css)).toMatchInlineSnapshot(`
19+
"
20+
@source "../../example-project/src/**/*.ts";
1921
@source "!../../example-project/src/**/*.ts";
2022
@plugin "../../example-project/src/plugin.js";
21-
@plugin "../../example-project/src/what\\"s-this.js";"
23+
@plugin "../../example-project/src/what\\"s-this.js";
24+
"
2225
`)
2326
})
2427

@@ -30,11 +33,13 @@ describe('fixRelativePathsPlugin', () => {
3033

3134
let result = await processor.process(css, { from: cssPath })
3235

33-
expect(result.css.trim()).toMatchInlineSnapshot(`
34-
"@plugin "/absolute/paths";
36+
expect(pretty(result.css)).toMatchInlineSnapshot(`
37+
"
38+
@plugin "/absolute/paths";
3539
@plugin "C:\\Program Files\\HAL 9000";
3640
@plugin "\\\\Media\\Pictures\\Worth\\1000 words";
37-
@plugin "some-node-dep";"
41+
@plugin "some-node-dep";
42+
"
3843
`)
3944
})
4045

@@ -46,13 +51,15 @@ describe('fixRelativePathsPlugin', () => {
4651

4752
let result = await processor.process(css, { from: cssPath })
4853

49-
expect(result.css.trim()).toMatchInlineSnapshot(`
50-
"@plugin './plugin-in-sibling.ts';
54+
expect(pretty(result.css)).toMatchInlineSnapshot(`
55+
"
56+
@plugin './plugin-in-sibling.ts';
5157
@plugin '../plugin-in-sibling.ts';
5258
@plugin 'plugin-in-sibling';
5359
@plugin './plugin-in-root.ts';
5460
@plugin '../plugin-in-root.ts';
55-
@plugin 'plugin-in-root';"
61+
@plugin 'plugin-in-root';
62+
"
5663
`)
5764
})
5865
})

packages/tailwindcss/src/__snapshots__/index.test.ts.snap

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

33
exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using the default theme 1`] = `
4-
"@layer properties {
4+
"
5+
@layer properties {
56
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
67
*, :before, :after, ::backdrop {
78
--tw-shadow: 0 0 #0000;
@@ -123,11 +124,13 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using
123124
syntax: "*";
124125
inherits: false;
125126
initial-value: 0 0 #0000;
126-
}"
127+
}
128+
"
127129
`;
128130

129131
exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = `
130-
"@layer theme {
132+
"
133+
@layer theme {
131134
:root, :host {
132135
--tw-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
133136
"Noto Color Emoji";
@@ -385,5 +388,6 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = `
385388
}
386389
}
387390
388-
@layer components, utilities;"
391+
@layer components, utilities;
392+
"
389393
`;

0 commit comments

Comments
 (0)