compileStyle.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. import {
  2. type SFCStyleCompileOptions,
  3. compileStyle,
  4. compileStyleAsync,
  5. } from '../src/compileStyle'
  6. import path from 'node:path'
  7. export function compileScoped(
  8. source: string,
  9. options?: Partial<SFCStyleCompileOptions>,
  10. ): string {
  11. const res = compileStyle({
  12. source,
  13. filename: 'test.css',
  14. id: 'data-v-test',
  15. scoped: true,
  16. ...options,
  17. })
  18. if (res.errors.length) {
  19. res.errors.forEach(err => {
  20. console.error(err)
  21. })
  22. expect(res.errors.length).toBe(0)
  23. }
  24. return res.code
  25. }
  26. describe('SFC scoped CSS', () => {
  27. test('simple selectors', () => {
  28. expect(compileScoped(`h1 { color: red; }`)).toMatch(
  29. `h1[data-v-test] { color: red;`,
  30. )
  31. expect(compileScoped(`.foo { color: red; }`)).toMatch(
  32. `.foo[data-v-test] { color: red;`,
  33. )
  34. })
  35. test('descendent selector', () => {
  36. expect(compileScoped(`h1 .foo { color: red; }`)).toMatch(
  37. `h1 .foo[data-v-test] { color: red;`,
  38. )
  39. })
  40. test('multiple selectors', () => {
  41. expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
  42. `h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`,
  43. )
  44. })
  45. test('pseudo class', () => {
  46. expect(compileScoped(`.foo:after { color: red; }`)).toMatch(
  47. `.foo[data-v-test]:after { color: red;`,
  48. )
  49. })
  50. test('pseudo element', () => {
  51. expect(compileScoped(`::selection { display: none; }`)).toMatch(
  52. '[data-v-test]::selection {',
  53. )
  54. })
  55. test('spaces before pseudo element', () => {
  56. const code = compileScoped(`.abc, ::selection { color: red; }`)
  57. expect(code).toMatch('.abc[data-v-test],')
  58. expect(code).toMatch('[data-v-test]::selection {')
  59. })
  60. test('::v-deep', () => {
  61. expect(compileScoped(`:deep(.foo) { color: red; }`)).toMatchInlineSnapshot(`
  62. "[data-v-test] .foo { color: red;
  63. }"
  64. `)
  65. expect(compileScoped(`::v-deep(.foo) { color: red; }`))
  66. .toMatchInlineSnapshot(`
  67. "[data-v-test] .foo { color: red;
  68. }"
  69. `)
  70. expect(compileScoped(`::v-deep(.foo .bar) { color: red; }`))
  71. .toMatchInlineSnapshot(`
  72. "[data-v-test] .foo .bar { color: red;
  73. }"
  74. `)
  75. expect(compileScoped(`.baz .qux ::v-deep(.foo .bar) { color: red; }`))
  76. .toMatchInlineSnapshot(`
  77. ".baz .qux[data-v-test] .foo .bar { color: red;
  78. }"
  79. `)
  80. expect(compileScoped(`:is(.foo :deep(.bar)) { color: red; }`))
  81. .toMatchInlineSnapshot(`
  82. ":is(.foo[data-v-test] .bar) { color: red;
  83. }"
  84. `)
  85. expect(compileScoped(`:where(.foo :deep(.bar)) { color: red; }`))
  86. .toMatchInlineSnapshot(`
  87. ":where(.foo[data-v-test] .bar) { color: red;
  88. }"
  89. `)
  90. })
  91. test('::v-slotted', () => {
  92. expect(compileScoped(`:slotted(.foo) { color: red; }`))
  93. .toMatchInlineSnapshot(`
  94. ".foo[data-v-test-s] { color: red;
  95. }"
  96. `)
  97. expect(compileScoped(`::v-slotted(.foo) { color: red; }`))
  98. .toMatchInlineSnapshot(`
  99. ".foo[data-v-test-s] { color: red;
  100. }"
  101. `)
  102. expect(compileScoped(`::v-slotted(.foo .bar) { color: red; }`))
  103. .toMatchInlineSnapshot(`
  104. ".foo .bar[data-v-test-s] { color: red;
  105. }"
  106. `)
  107. expect(compileScoped(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
  108. .toMatchInlineSnapshot(`
  109. ".baz .qux .foo .bar[data-v-test-s] { color: red;
  110. }"
  111. `)
  112. })
  113. test('::v-global', () => {
  114. expect(compileScoped(`:global(.foo) { color: red; }`))
  115. .toMatchInlineSnapshot(`
  116. ".foo { color: red;
  117. }"
  118. `)
  119. expect(compileScoped(`::v-global(.foo) { color: red; }`))
  120. .toMatchInlineSnapshot(`
  121. ".foo { color: red;
  122. }"
  123. `)
  124. expect(compileScoped(`::v-global(.foo .bar) { color: red; }`))
  125. .toMatchInlineSnapshot(`
  126. ".foo .bar { color: red;
  127. }"
  128. `)
  129. // global ignores anything before it
  130. expect(compileScoped(`.baz .qux ::v-global(.foo .bar) { color: red; }`))
  131. .toMatchInlineSnapshot(`
  132. ".foo .bar { color: red;
  133. }"
  134. `)
  135. })
  136. test(':is() and :where() with multiple selectors', () => {
  137. expect(compileScoped(`:is(.foo) { color: red; }`)).toMatchInlineSnapshot(`
  138. ":is(.foo[data-v-test]) { color: red;
  139. }"
  140. `)
  141. expect(compileScoped(`:where(.foo, .bar) { color: red; }`))
  142. .toMatchInlineSnapshot(`
  143. ":where(.foo[data-v-test], .bar[data-v-test]) { color: red;
  144. }"
  145. `)
  146. expect(compileScoped(`:is(.foo, .bar) div { color: red; }`))
  147. .toMatchInlineSnapshot(`
  148. ":is(.foo, .bar) div[data-v-test] { color: red;
  149. }"
  150. `)
  151. })
  152. // #10511
  153. test(':is() and :where() in compound selectors', () => {
  154. expect(
  155. compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
  156. ).toMatchInlineSnapshot(`
  157. ".div[data-v-test] { color: red;
  158. }
  159. .div[data-v-test]:where(:hover) { color: blue;
  160. }"`)
  161. expect(
  162. compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
  163. ).toMatchInlineSnapshot(`
  164. ".div[data-v-test] { color: red;
  165. }
  166. .div[data-v-test]:is(:hover) { color: blue;
  167. }"`)
  168. expect(
  169. compileScoped(
  170. `.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
  171. ),
  172. ).toMatchInlineSnapshot(`
  173. ".div[data-v-test] { color: red;
  174. }
  175. .div[data-v-test]:where(.foo:hover) { color: blue;
  176. }"`)
  177. expect(
  178. compileScoped(
  179. `.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
  180. ),
  181. ).toMatchInlineSnapshot(`
  182. ".div[data-v-test] { color: red;
  183. }
  184. .div[data-v-test]:is(.foo:hover) { color: blue;
  185. }"`)
  186. })
  187. test('media query', () => {
  188. expect(compileScoped(`@media print { .foo { color: red }}`))
  189. .toMatchInlineSnapshot(`
  190. "@media print {
  191. .foo[data-v-test] { color: red
  192. }}"
  193. `)
  194. })
  195. test('supports query', () => {
  196. expect(compileScoped(`@supports(display: grid) { .foo { display: grid }}`))
  197. .toMatchInlineSnapshot(`
  198. "@supports(display: grid) {
  199. .foo[data-v-test] { display: grid
  200. }}"
  201. `)
  202. })
  203. test('scoped keyframes', () => {
  204. const style = compileScoped(
  205. `
  206. .anim {
  207. animation: color 5s infinite, other 5s;
  208. }
  209. .anim-2 {
  210. animation-name: color;
  211. animation-duration: 5s;
  212. }
  213. .anim-3 {
  214. animation: 5s color infinite, 5s other;
  215. }
  216. .anim-multiple {
  217. animation: color 5s infinite, opacity 2s;
  218. }
  219. .anim-multiple-2 {
  220. animation-name: color, opacity;
  221. animation-duration: 5s, 2s;
  222. }
  223. @keyframes color {
  224. from { color: red; }
  225. to { color: green; }
  226. }
  227. @-webkit-keyframes color {
  228. from { color: red; }
  229. to { color: green; }
  230. }
  231. @keyframes opacity {
  232. from { opacity: 0; }
  233. to { opacity: 1; }
  234. }
  235. @-webkit-keyframes opacity {
  236. from { opacity: 0; }
  237. to { opacity: 1; }
  238. }
  239. `,
  240. { id: 'data-v-test' },
  241. )
  242. expect(style).toContain(
  243. `.anim[data-v-test] {\n animation: color-test 5s infinite, other 5s;`,
  244. )
  245. expect(style).toContain(
  246. `.anim-2[data-v-test] {\n animation-name: color-test`,
  247. )
  248. expect(style).toContain(
  249. `.anim-3[data-v-test] {\n animation: 5s color-test infinite, 5s other;`,
  250. )
  251. expect(style).toContain(`@keyframes color-test {`)
  252. expect(style).toContain(`@-webkit-keyframes color-test {`)
  253. expect(style).toContain(
  254. `.anim-multiple[data-v-test] {\n animation: color-test 5s infinite,opacity-test 2s;`,
  255. )
  256. expect(style).toContain(
  257. `.anim-multiple-2[data-v-test] {\n animation-name: color-test,opacity-test;`,
  258. )
  259. expect(style).toContain(`@keyframes opacity-test {\nfrom { opacity: 0;`)
  260. expect(style).toContain(
  261. `@-webkit-keyframes opacity-test {\nfrom { opacity: 0;`,
  262. )
  263. })
  264. // vue-loader/#1370
  265. test('spaces after selector', () => {
  266. expect(compileScoped(`.foo , .bar { color: red; }`)).toMatchInlineSnapshot(`
  267. ".foo[data-v-test], .bar[data-v-test] { color: red;
  268. }"
  269. `)
  270. })
  271. describe('deprecated syntax', () => {
  272. test('::v-deep as combinator', () => {
  273. expect(compileScoped(`::v-deep .foo { color: red; }`))
  274. .toMatchInlineSnapshot(`
  275. "[data-v-test] .foo { color: red;
  276. }"
  277. `)
  278. expect(compileScoped(`.bar ::v-deep .foo { color: red; }`))
  279. .toMatchInlineSnapshot(`
  280. ".bar[data-v-test] .foo { color: red;
  281. }"
  282. `)
  283. expect(
  284. `::v-deep usage as a combinator has been deprecated.`,
  285. ).toHaveBeenWarned()
  286. })
  287. test('>>> (deprecated syntax)', () => {
  288. const code = compileScoped(`>>> .foo { color: red; }`)
  289. expect(code).toMatchInlineSnapshot(`
  290. "[data-v-test] .foo { color: red;
  291. }"
  292. `)
  293. expect(
  294. `the >>> and /deep/ combinators have been deprecated.`,
  295. ).toHaveBeenWarned()
  296. })
  297. test('/deep/ (deprecated syntax)', () => {
  298. const code = compileScoped(`/deep/ .foo { color: red; }`)
  299. expect(code).toMatchInlineSnapshot(`
  300. "[data-v-test] .foo { color: red;
  301. }"
  302. `)
  303. expect(
  304. `the >>> and /deep/ combinators have been deprecated.`,
  305. ).toHaveBeenWarned()
  306. })
  307. })
  308. })
  309. describe('SFC CSS modules', () => {
  310. test('should include resulting classes object in result', async () => {
  311. const result = await compileStyleAsync({
  312. source: `.red { color: red }\n.green { color: green }\n:global(.blue) { color: blue }`,
  313. filename: `test.css`,
  314. id: 'test',
  315. modules: true,
  316. })
  317. expect(result.modules).toBeDefined()
  318. expect(result.modules!.red).toMatch('_red_')
  319. expect(result.modules!.green).toMatch('_green_')
  320. expect(result.modules!.blue).toBeUndefined()
  321. })
  322. test('postcss-modules options', async () => {
  323. const result = await compileStyleAsync({
  324. source: `:local(.foo-bar) { color: red }\n.baz-qux { color: green }`,
  325. filename: `test.css`,
  326. id: 'test',
  327. modules: true,
  328. modulesOptions: {
  329. scopeBehaviour: 'global',
  330. generateScopedName: `[name]__[local]__[hash:base64:5]`,
  331. localsConvention: 'camelCaseOnly',
  332. },
  333. })
  334. expect(result.modules).toBeDefined()
  335. expect(result.modules!.fooBar).toMatch('__foo-bar__')
  336. expect(result.modules!.bazQux).toBeUndefined()
  337. })
  338. })
  339. describe('SFC style preprocessors', () => {
  340. test('scss @import', () => {
  341. const res = compileStyle({
  342. source: `
  343. @import "./import.scss";
  344. `,
  345. filename: path.resolve(__dirname, './fixture/test.scss'),
  346. id: '',
  347. preprocessLang: 'scss',
  348. })
  349. expect([...res.dependencies]).toStrictEqual([
  350. path.join(__dirname, './fixture/import.scss'),
  351. ])
  352. })
  353. test('scss respect user-defined string options.additionalData', () => {
  354. const res = compileStyle({
  355. preprocessOptions: {
  356. additionalData: `
  357. @mixin square($size) {
  358. width: $size;
  359. height: $size;
  360. }`,
  361. },
  362. source: `
  363. .square {
  364. @include square(100px);
  365. }
  366. `,
  367. filename: path.resolve(__dirname, './fixture/test.scss'),
  368. id: '',
  369. preprocessLang: 'scss',
  370. })
  371. expect(res.errors.length).toBe(0)
  372. })
  373. test('scss respect user-defined function options.additionalData', () => {
  374. const source = `
  375. .square {
  376. @include square(100px);
  377. }
  378. `
  379. const filename = path.resolve(__dirname, './fixture/test.scss')
  380. const res = compileStyle({
  381. preprocessOptions: {
  382. additionalData: (s: string, f: string) => {
  383. expect(s).toBe(source)
  384. expect(f).toBe(filename)
  385. return `
  386. @mixin square($size) {
  387. width: $size;
  388. height: $size;
  389. }`
  390. },
  391. },
  392. source,
  393. filename,
  394. id: '',
  395. preprocessLang: 'scss',
  396. })
  397. expect(res.errors.length).toBe(0)
  398. })
  399. test('should mount scope on correct selector when have universal selector', () => {
  400. expect(compileScoped(`* { color: red; }`)).toMatchInlineSnapshot(`
  401. "[data-v-test] { color: red;
  402. }"
  403. `)
  404. expect(compileScoped('* .foo { color: red; }')).toMatchInlineSnapshot(`
  405. ".foo[data-v-test] { color: red;
  406. }"
  407. `)
  408. expect(compileScoped(`*.foo { color: red; }`)).toMatchInlineSnapshot(`
  409. ".foo[data-v-test] { color: red;
  410. }"
  411. `)
  412. expect(compileScoped(`.foo * { color: red; }`)).toMatchInlineSnapshot(`
  413. ".foo[data-v-test] * { color: red;
  414. }"
  415. `)
  416. })
  417. })