From 9238e00c9fd5df981b0c60e45f0be32f0a73ab0d Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Thu, 14 Aug 2025 22:47:53 +0530 Subject: [PATCH] test: add postcssIsolateStyles snapshots --- .../__snapshots__/isolateStyles.test.ts.snap | 77 ++++++++++++++ .../unit/node/postcss/isolateStyles.test.ts | 100 +++++++++++++++--- 2 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 __tests__/unit/node/postcss/__snapshots__/isolateStyles.test.ts.snap diff --git a/__tests__/unit/node/postcss/__snapshots__/isolateStyles.test.ts.snap b/__tests__/unit/node/postcss/__snapshots__/isolateStyles.test.ts.snap new file mode 100644 index 00000000..c781b64c --- /dev/null +++ b/__tests__/unit/node/postcss/__snapshots__/isolateStyles.test.ts.snap @@ -0,0 +1,77 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`node/postcss/isolateStyles > transforms selectors and skips keyframes 1`] = ` +" +/* simple classes */ +.example:not(:where(.vp-raw, .vp-raw *)) { color: red; } +.class-a:not(:where(.vp-raw, .vp-raw *)) { color: coral; } +.class-b:not(:where(.vp-raw, .vp-raw *)) { color: deepskyblue; } + +/* escaped colon in class */ +.baz\\:not\\(.bar\\):not(:where(.vp-raw, .vp-raw *)) { display: block; } +.disabled\\:opacity-50:not(:where(.vp-raw, .vp-raw *)):disabled { opacity: .5; } + +/* pseudos (class + element) */ +.button:not(:where(.vp-raw, .vp-raw *)):hover { color: pink; } +.button:not(:where(.vp-raw, .vp-raw *)):focus:hover { color: hotpink; } +.item:not(:where(.vp-raw, .vp-raw *))::before { content: '•'; } +:not(:where(.vp-raw, .vp-raw *))::first-letter { color: pink; } +:not(:where(.vp-raw, .vp-raw *))::before { content: ''; } + +/* universal + :not */ +*:not(:where(.vp-raw, .vp-raw *)) { background-color: red; } +*:not(:where(.vp-raw, .vp-raw *)):not(.b) { text-transform: uppercase; } + +/* combinators */ +.foo:hover .bar:not(:where(.vp-raw, .vp-raw *)) { background: blue; } +ul > li.active:not(:where(.vp-raw, .vp-raw *)) { color: green; } +a + b ~ c:not(:where(.vp-raw, .vp-raw *)) { color: orange; } + +/* ids + attribute selectors */ +#wow:not(:where(.vp-raw, .vp-raw *)) { color: yellow; } +[data-world] .d:not(:where(.vp-raw, .vp-raw *)) { padding: 10px 20px; } + +/* :root and chained tags */ +:not(:where(.vp-raw, .vp-raw *)):root { --bs-blue: #0d6efd; } +:root .a:not(:where(.vp-raw, .vp-raw *)) { --bs-green: #bada55; } +html:not(:where(.vp-raw, .vp-raw *)) { margin: 0; } +body:not(:where(.vp-raw, .vp-raw *)) { padding: 0; } +html body div:not(:where(.vp-raw, .vp-raw *)) { color: blue; } + +/* grouping with commas */ +.a:not(:where(.vp-raw, .vp-raw *)), .b:not(:where(.vp-raw, .vp-raw *)) { color: red; } + +/* multiple repeated groups to ensure stability */ +.a:not(:where(.vp-raw, .vp-raw *)), .b:not(:where(.vp-raw, .vp-raw *)) { color: coral; } +.a:not(:where(.vp-raw, .vp-raw *)) { animation: glow 1s linear infinite alternate; } + +/* nested blocks */ +.foo:not(:where(.vp-raw, .vp-raw *)) { + svg:not(:where(.vp-raw, .vp-raw *)) { display: none; } + .bar:not(:where(.vp-raw, .vp-raw *)) { display: inline; } +} + +/* standalone pseudos */ +:not(:where(.vp-raw, .vp-raw *)):first-child { color: pink; } +:not(:where(.vp-raw, .vp-raw *)):hover { color: blue; } +:not(:where(.vp-raw, .vp-raw *)):active { color: red; } + +/* keyframes (should be ignored) */ +@keyframes fade { + from { opacity: 0; } + to { opacity: 1; } +} +@-webkit-keyframes glow { + from { color: coral; } + to { color: red; } +} +@-moz-keyframes glow { + from { color: coral; } + to { color: red; } +} +@-o-keyframes glow { + from { color: coral; } + to { color: red; } +} +" +`; diff --git a/__tests__/unit/node/postcss/isolateStyles.test.ts b/__tests__/unit/node/postcss/isolateStyles.test.ts index 25fc5649..609a4463 100644 --- a/__tests__/unit/node/postcss/isolateStyles.test.ts +++ b/__tests__/unit/node/postcss/isolateStyles.test.ts @@ -1,27 +1,93 @@ import { postcssIsolateStyles } from 'node/postcss/isolateStyles' import postcss from 'postcss' -function apply(selector: string) { - const { root } = postcss([postcssIsolateStyles()]).process(`${selector} {}`) - return (root.nodes[0] as any).selector +const INPUT_CSS = ` +/* simple classes */ +.example { color: red; } +.class-a { color: coral; } +.class-b { color: deepskyblue; } + +/* escaped colon in class */ +.baz\\:not\\(.bar\\) { display: block; } +.disabled\\:opacity-50:disabled { opacity: .5; } + +/* pseudos (class + element) */ +.button:hover { color: pink; } +.button:focus:hover { color: hotpink; } +.item::before { content: '•'; } +::first-letter { color: pink; } +::before { content: ''; } + +/* universal + :not */ +* { background-color: red; } +*:not(.b) { text-transform: uppercase; } + +/* combinators */ +.foo:hover .bar { background: blue; } +ul > li.active { color: green; } +a + b ~ c { color: orange; } + +/* ids + attribute selectors */ +#wow { color: yellow; } +[data-world] .d { padding: 10px 20px; } + +/* :root and chained tags */ +:root { --bs-blue: #0d6efd; } +:root .a { --bs-green: #bada55; } +html { margin: 0; } +body { padding: 0; } +html body div { color: blue; } + +/* grouping with commas */ +.a, .b { color: red; } + +/* multiple repeated groups to ensure stability */ +.a, .b { color: coral; } +.a { animation: glow 1s linear infinite alternate; } + +/* nested blocks */ +.foo { + svg { display: none; } + .bar { display: inline; } } -describe('node/postcss/isolateStyles', () => { - test('splitSelectorPseudo skips escaped colon', () => { - expect(apply('.foo\\:bar')).toBe( - '.foo\\:bar:not(:where(.vp-raw, .vp-raw *))' - ) - }) +/* standalone pseudos */ +:first-child { color: pink; } +:hover { color: blue; } +:active { color: red; } - test('splitSelectorPseudo splits on pseudo selectors', () => { - expect(apply('.button:hover')).toBe( - '.button:not(:where(.vp-raw, .vp-raw *)):hover' - ) +/* keyframes (should be ignored) */ +@keyframes fade { + from { opacity: 0; } + to { opacity: 1; } +} +@-webkit-keyframes glow { + from { color: coral; } + to { color: red; } +} +@-moz-keyframes glow { + from { color: coral; } + to { color: red; } +} +@-o-keyframes glow { + from { color: coral; } + to { color: red; } +} +` + +describe('node/postcss/isolateStyles', () => { + test('transforms selectors and skips keyframes', () => { + const out = run(INPUT_CSS) + expect(out.css).toMatchSnapshot() }) - test('postcssIsolateStyles inserts :not(...) in the right place', () => { - expect(apply('.disabled\\:opacity-50:disabled')).toBe( - '.disabled\\:opacity-50:not(:where(.vp-raw, .vp-raw *)):disabled' - ) + test('idempotent (running twice produces identical CSS)', () => { + const first = run(INPUT_CSS).css + const second = run(first).css + expect(second).toBe(first) }) }) + +function run(css: string, from = 'src/styles/vp-doc.css') { + return postcss([postcssIsolateStyles()]).process(css, { from }) +}