| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- import {
- type SFCStyleCompileOptions,
- compileStyle,
- compileStyleAsync,
- } from '../src/compileStyle'
- import path from 'node:path'
- export function compileScoped(
- source: string,
- options?: Partial<SFCStyleCompileOptions>,
- ): string {
- const res = compileStyle({
- source,
- filename: 'test.css',
- id: 'data-v-test',
- scoped: true,
- ...options,
- })
- if (res.errors.length) {
- res.errors.forEach(err => {
- console.error(err)
- })
- expect(res.errors.length).toBe(0)
- }
- return res.code
- }
- describe('SFC scoped CSS', () => {
- test('simple selectors', () => {
- expect(compileScoped(`h1 { color: red; }`)).toMatch(
- `h1[data-v-test] { color: red;`,
- )
- expect(compileScoped(`.foo { color: red; }`)).toMatch(
- `.foo[data-v-test] { color: red;`,
- )
- })
- test('descendent selector', () => {
- expect(compileScoped(`h1 .foo { color: red; }`)).toMatch(
- `h1 .foo[data-v-test] { color: red;`,
- )
- })
- test('nesting selector', () => {
- expect(compileScoped(`h1 { color: red; .foo { color: red; } }`)).toMatch(
- `h1 {\n&[data-v-test] { color: red;\n}\n.foo[data-v-test] { color: red;`,
- )
- })
- test('nesting selector with atrule and comment', () => {
- expect(
- compileScoped(
- `h1 {
- color: red;
- /*background-color: pink;*/
- @media only screen and (max-width: 800px) {
- background-color: green;
- .bar { color: white }
- }
- .foo { color: red; }
- }`,
- ),
- ).toMatch(
- `h1 {
- &[data-v-test] {
- color: red
- /*background-color: pink;*/
- }
- @media only screen and (max-width: 800px) {
- &[data-v-test] {
- background-color: green
- }
- .bar[data-v-test] { color: white
- }
- }
- .foo[data-v-test] { color: red;
- }
- }`,
- )
- })
- test('multiple selectors', () => {
- expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
- `h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`,
- )
- })
- test('pseudo class', () => {
- expect(compileScoped(`.foo:after { color: red; }`)).toMatch(
- `.foo[data-v-test]:after { color: red;`,
- )
- })
- test('pseudo element', () => {
- expect(compileScoped(`::selection { display: none; }`)).toMatch(
- '[data-v-test]::selection {',
- )
- })
- test('spaces before pseudo element', () => {
- const code = compileScoped(`.abc, ::selection { color: red; }`)
- expect(code).toMatch('.abc[data-v-test],')
- expect(code).toMatch('[data-v-test]::selection {')
- })
- test('::v-deep', () => {
- expect(compileScoped(`:deep(.foo) { color: red; }`)).toMatchInlineSnapshot(`
- "[data-v-test] .foo { color: red;
- }"
- `)
- expect(compileScoped(`::v-deep(.foo) { color: red; }`))
- .toMatchInlineSnapshot(`
- "[data-v-test] .foo { color: red;
- }"
- `)
- expect(compileScoped(`::v-deep(.foo .bar) { color: red; }`))
- .toMatchInlineSnapshot(`
- "[data-v-test] .foo .bar { color: red;
- }"
- `)
- expect(compileScoped(`.baz .qux ::v-deep(.foo .bar) { color: red; }`))
- .toMatchInlineSnapshot(`
- ".baz .qux[data-v-test] .foo .bar { color: red;
- }"
- `)
- expect(compileScoped(`:is(.foo :deep(.bar)) { color: red; }`))
- .toMatchInlineSnapshot(`
- ":is(.foo[data-v-test] .bar) { color: red;
- }"
- `)
- expect(compileScoped(`:where(.foo :deep(.bar)) { color: red; }`))
- .toMatchInlineSnapshot(`
- ":where(.foo[data-v-test] .bar) { color: red;
- }"
- `)
- expect(compileScoped(`:deep(.foo) { color: red; .bar { color: red; } }`))
- .toMatchInlineSnapshot(`
- "[data-v-test] .foo { color: red;
- .bar { color: red;
- }
- }"
- `)
- })
- test('::v-slotted', () => {
- expect(compileScoped(`:slotted(.foo) { color: red; }`))
- .toMatchInlineSnapshot(`
- ".foo[data-v-test-s] { color: red;
- }"
- `)
- expect(compileScoped(`::v-slotted(.foo) { color: red; }`))
- .toMatchInlineSnapshot(`
- ".foo[data-v-test-s] { color: red;
- }"
- `)
- expect(compileScoped(`::v-slotted(.foo .bar) { color: red; }`))
- .toMatchInlineSnapshot(`
- ".foo .bar[data-v-test-s] { color: red;
- }"
- `)
- expect(compileScoped(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
- .toMatchInlineSnapshot(`
- ".baz .qux .foo .bar[data-v-test-s] { color: red;
- }"
- `)
- })
- test('::v-global', () => {
- expect(compileScoped(`:global(.foo) { color: red; }`))
- .toMatchInlineSnapshot(`
- ".foo { color: red;
- }"
- `)
- expect(compileScoped(`::v-global(.foo) { color: red; }`))
- .toMatchInlineSnapshot(`
- ".foo { color: red;
- }"
- `)
- expect(compileScoped(`::v-global(.foo .bar) { color: red; }`))
- .toMatchInlineSnapshot(`
- ".foo .bar { color: red;
- }"
- `)
- // global ignores anything before it
- expect(compileScoped(`.baz .qux ::v-global(.foo .bar) { color: red; }`))
- .toMatchInlineSnapshot(`
- ".foo .bar { color: red;
- }"
- `)
- })
- test(':is() and :where() with multiple selectors', () => {
- expect(compileScoped(`:is(.foo) { color: red; }`)).toMatchInlineSnapshot(`
- ":is(.foo[data-v-test]) { color: red;
- }"
- `)
- expect(compileScoped(`:where(.foo, .bar) { color: red; }`))
- .toMatchInlineSnapshot(`
- ":where(.foo[data-v-test], .bar[data-v-test]) { color: red;
- }"
- `)
- expect(compileScoped(`:is(.foo, .bar) div { color: red; }`))
- .toMatchInlineSnapshot(`
- ":is(.foo, .bar) div[data-v-test] { color: red;
- }"
- `)
- })
- // #10511
- test(':is() and :where() in compound selectors', () => {
- expect(
- compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
- ).toMatchInlineSnapshot(`
- ".div[data-v-test] { color: red;
- }
- .div[data-v-test]:where(:hover) { color: blue;
- }"
- `)
- expect(
- compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
- ).toMatchInlineSnapshot(`
- ".div[data-v-test] { color: red;
- }
- .div[data-v-test]:is(:hover) { color: blue;
- }"
- `)
- expect(
- compileScoped(
- `.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
- ),
- ).toMatchInlineSnapshot(`
- ".div[data-v-test] { color: red;
- }
- .div[data-v-test]:where(.foo:hover) { color: blue;
- }"
- `)
- expect(
- compileScoped(
- `.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
- ),
- ).toMatchInlineSnapshot(`
- ".div[data-v-test] { color: red;
- }
- .div[data-v-test]:is(.foo:hover) { color: blue;
- }"
- `)
- })
- test('media query', () => {
- expect(compileScoped(`@media print { .foo { color: red }}`))
- .toMatchInlineSnapshot(`
- "@media print {
- .foo[data-v-test] { color: red
- }}"
- `)
- })
- test('supports query', () => {
- expect(compileScoped(`@supports(display: grid) { .foo { display: grid }}`))
- .toMatchInlineSnapshot(`
- "@supports(display: grid) {
- .foo[data-v-test] { display: grid
- }}"
- `)
- })
- test('scoped keyframes', () => {
- const style = compileScoped(
- `
- .anim {
- animation: color 5s infinite, other 5s;
- }
- .anim-2 {
- animation-name: color;
- animation-duration: 5s;
- }
- .anim-3 {
- animation: 5s color infinite, 5s other;
- }
- .anim-multiple {
- animation: color 5s infinite, opacity 2s;
- }
- .anim-multiple-2 {
- animation-name: color, opacity;
- animation-duration: 5s, 2s;
- }
- @keyframes color {
- from { color: red; }
- to { color: green; }
- }
- @-webkit-keyframes color {
- from { color: red; }
- to { color: green; }
- }
- @keyframes opacity {
- from { opacity: 0; }
- to { opacity: 1; }
- }
- @-webkit-keyframes opacity {
- from { opacity: 0; }
- to { opacity: 1; }
- }
- `,
- { id: 'data-v-test' },
- )
- expect(style).toContain(
- `.anim[data-v-test] {\n animation: color-test 5s infinite, other 5s;`,
- )
- expect(style).toContain(
- `.anim-2[data-v-test] {\n animation-name: color-test`,
- )
- expect(style).toContain(
- `.anim-3[data-v-test] {\n animation: 5s color-test infinite, 5s other;`,
- )
- expect(style).toContain(`@keyframes color-test {`)
- expect(style).toContain(`@-webkit-keyframes color-test {`)
- expect(style).toContain(
- `.anim-multiple[data-v-test] {\n animation: color-test 5s infinite,opacity-test 2s;`,
- )
- expect(style).toContain(
- `.anim-multiple-2[data-v-test] {\n animation-name: color-test,opacity-test;`,
- )
- expect(style).toContain(`@keyframes opacity-test {\nfrom { opacity: 0;`)
- expect(style).toContain(
- `@-webkit-keyframes opacity-test {\nfrom { opacity: 0;`,
- )
- })
- // vue-loader/#1370
- test('spaces after selector', () => {
- expect(compileScoped(`.foo , .bar { color: red; }`)).toMatchInlineSnapshot(`
- ".foo[data-v-test], .bar[data-v-test] { color: red;
- }"
- `)
- })
- describe('deprecated syntax', () => {
- test('::v-deep as combinator', () => {
- expect(compileScoped(`::v-deep .foo { color: red; }`))
- .toMatchInlineSnapshot(`
- "[data-v-test] .foo { color: red;
- }"
- `)
- expect(compileScoped(`.bar ::v-deep .foo { color: red; }`))
- .toMatchInlineSnapshot(`
- ".bar[data-v-test] .foo { color: red;
- }"
- `)
- expect(
- `::v-deep usage as a combinator has been deprecated.`,
- ).toHaveBeenWarned()
- })
- test('>>> (deprecated syntax)', () => {
- const code = compileScoped(`>>> .foo { color: red; }`)
- expect(code).toMatchInlineSnapshot(`
- "[data-v-test] .foo { color: red;
- }"
- `)
- expect(
- `the >>> and /deep/ combinators have been deprecated.`,
- ).toHaveBeenWarned()
- })
- test('/deep/ (deprecated syntax)', () => {
- const code = compileScoped(`/deep/ .foo { color: red; }`)
- expect(code).toMatchInlineSnapshot(`
- "[data-v-test] .foo { color: red;
- }"
- `)
- expect(
- `the >>> and /deep/ combinators have been deprecated.`,
- ).toHaveBeenWarned()
- })
- })
- })
- describe('SFC CSS modules', () => {
- test('should include resulting classes object in result', async () => {
- const result = await compileStyleAsync({
- source: `.red { color: red }\n.green { color: green }\n:global(.blue) { color: blue }`,
- filename: `test.css`,
- id: 'test',
- modules: true,
- })
- expect(result.modules).toBeDefined()
- expect(result.modules!.red).toMatch('_red_')
- expect(result.modules!.green).toMatch('_green_')
- expect(result.modules!.blue).toBeUndefined()
- })
- test('postcss-modules options', async () => {
- const result = await compileStyleAsync({
- source: `:local(.foo-bar) { color: red }\n.baz-qux { color: green }`,
- filename: `test.css`,
- id: 'test',
- modules: true,
- modulesOptions: {
- scopeBehaviour: 'global',
- generateScopedName: `[name]__[local]__[hash:base64:5]`,
- localsConvention: 'camelCaseOnly',
- },
- })
- expect(result.modules).toBeDefined()
- expect(result.modules!.fooBar).toMatch('__foo-bar__')
- expect(result.modules!.bazQux).toBeUndefined()
- })
- })
- describe('SFC style preprocessors', () => {
- test('scss @import', () => {
- const res = compileStyle({
- source: `
- @import "./import.scss";
- `,
- filename: path.resolve(__dirname, './fixture/test.scss'),
- id: '',
- preprocessLang: 'scss',
- })
- expect([...res.dependencies]).toStrictEqual([
- path.join(__dirname, './fixture/import.scss'),
- ])
- })
- test('scss respect user-defined string options.additionalData', () => {
- const res = compileStyle({
- preprocessOptions: {
- additionalData: `
- @mixin square($size) {
- width: $size;
- height: $size;
- }`,
- },
- source: `
- .square {
- @include square(100px);
- }
- `,
- filename: path.resolve(__dirname, './fixture/test.scss'),
- id: '',
- preprocessLang: 'scss',
- })
- expect(res.errors.length).toBe(0)
- })
- test('scss respect user-defined function options.additionalData', () => {
- const source = `
- .square {
- @include square(100px);
- }
- `
- const filename = path.resolve(__dirname, './fixture/test.scss')
- const res = compileStyle({
- preprocessOptions: {
- additionalData: (s: string, f: string) => {
- expect(s).toBe(source)
- expect(f).toBe(filename)
- return `
- @mixin square($size) {
- width: $size;
- height: $size;
- }`
- },
- },
- source,
- filename,
- id: '',
- preprocessLang: 'scss',
- })
- expect(res.errors.length).toBe(0)
- })
- test('should mount scope on correct selector when have universal selector', () => {
- expect(compileScoped(`* { color: red; }`)).toMatchInlineSnapshot(`
- "[data-v-test] { color: red;
- }"
- `)
- expect(compileScoped('* .foo { color: red; }')).toMatchInlineSnapshot(`
- ".foo[data-v-test] { color: red;
- }"
- `)
- expect(compileScoped(`*.foo { color: red; }`)).toMatchInlineSnapshot(`
- ".foo[data-v-test] { color: red;
- }"
- `)
- expect(compileScoped(`.foo * { color: red; }`)).toMatchInlineSnapshot(`
- ".foo[data-v-test] * { color: red;
- }"
- `)
- })
- })
|