compileStyle.spec.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /**
  2. * @jest-environment node
  3. */
  4. import {
  5. compileStyle,
  6. compileStyleAsync,
  7. SFCStyleCompileOptions
  8. } from '../src/compileStyle'
  9. import { mockWarn } from '@vue/shared'
  10. import path from 'path'
  11. describe('SFC scoped CSS', () => {
  12. mockWarn()
  13. function compileScoped(
  14. source: string,
  15. options?: Partial<SFCStyleCompileOptions>
  16. ): string {
  17. const res = compileStyle({
  18. source,
  19. filename: 'test.css',
  20. id: 'test',
  21. scoped: true,
  22. ...options
  23. })
  24. if (res.errors.length) {
  25. res.errors.forEach(err => {
  26. console.error(err)
  27. })
  28. expect(res.errors.length).toBe(0)
  29. }
  30. return res.code
  31. }
  32. test('simple selectors', () => {
  33. expect(compileScoped(`h1 { color: red; }`)).toMatch(
  34. `h1[test] { color: red;`
  35. )
  36. expect(compileScoped(`.foo { color: red; }`)).toMatch(
  37. `.foo[test] { color: red;`
  38. )
  39. })
  40. test('descendent selector', () => {
  41. expect(compileScoped(`h1 .foo { color: red; }`)).toMatch(
  42. `h1 .foo[test] { color: red;`
  43. )
  44. })
  45. test('multiple selectors', () => {
  46. expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
  47. `h1 .foo[test], .bar[test], .baz[test] { color: red;`
  48. )
  49. })
  50. test('pseudo class', () => {
  51. expect(compileScoped(`.foo:after { color: red; }`)).toMatch(
  52. `.foo[test]:after { color: red;`
  53. )
  54. })
  55. test('pseudo element', () => {
  56. expect(compileScoped(`::selection { display: none; }`)).toMatch(
  57. '[test]::selection {'
  58. )
  59. })
  60. test('spaces before pseudo element', () => {
  61. const code = compileScoped(`.abc, ::selection { color: red; }`)
  62. expect(code).toMatch('.abc[test],')
  63. expect(code).toMatch('[test]::selection {')
  64. })
  65. test('::v-deep', () => {
  66. expect(compileScoped(`:deep(.foo) { color: red; }`)).toMatchInlineSnapshot(`
  67. "[test] .foo { color: red;
  68. }"
  69. `)
  70. expect(compileScoped(`::v-deep(.foo) { color: red; }`))
  71. .toMatchInlineSnapshot(`
  72. "[test] .foo { color: red;
  73. }"
  74. `)
  75. expect(compileScoped(`::v-deep(.foo .bar) { color: red; }`))
  76. .toMatchInlineSnapshot(`
  77. "[test] .foo .bar { color: red;
  78. }"
  79. `)
  80. expect(compileScoped(`.baz .qux ::v-deep(.foo .bar) { color: red; }`))
  81. .toMatchInlineSnapshot(`
  82. ".baz .qux[test] .foo .bar { color: red;
  83. }"
  84. `)
  85. })
  86. test('::v-slotted', () => {
  87. expect(compileScoped(`:slotted(.foo) { color: red; }`))
  88. .toMatchInlineSnapshot(`
  89. ".foo[test-s] { color: red;
  90. }"
  91. `)
  92. expect(compileScoped(`::v-slotted(.foo) { color: red; }`))
  93. .toMatchInlineSnapshot(`
  94. ".foo[test-s] { color: red;
  95. }"
  96. `)
  97. expect(compileScoped(`::v-slotted(.foo .bar) { color: red; }`))
  98. .toMatchInlineSnapshot(`
  99. ".foo .bar[test-s] { color: red;
  100. }"
  101. `)
  102. expect(compileScoped(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
  103. .toMatchInlineSnapshot(`
  104. ".baz .qux .foo .bar[test-s] { color: red;
  105. }"
  106. `)
  107. })
  108. test('::v-global', () => {
  109. expect(compileScoped(`:global(.foo) { color: red; }`))
  110. .toMatchInlineSnapshot(`
  111. ".foo { color: red;
  112. }"
  113. `)
  114. expect(compileScoped(`::v-global(.foo) { color: red; }`))
  115. .toMatchInlineSnapshot(`
  116. ".foo { color: red;
  117. }"
  118. `)
  119. expect(compileScoped(`::v-global(.foo .bar) { color: red; }`))
  120. .toMatchInlineSnapshot(`
  121. ".foo .bar { color: red;
  122. }"
  123. `)
  124. // global ignores anything before it
  125. expect(compileScoped(`.baz .qux ::v-global(.foo .bar) { color: red; }`))
  126. .toMatchInlineSnapshot(`
  127. ".foo .bar { color: red;
  128. }"
  129. `)
  130. })
  131. test('media query', () => {
  132. expect(compileScoped(`@media print { .foo { color: red }}`))
  133. .toMatchInlineSnapshot(`
  134. "@media print {
  135. .foo[test] { color: red
  136. }}"
  137. `)
  138. })
  139. test('supports query', () => {
  140. expect(compileScoped(`@supports(display: grid) { .foo { display: grid }}`))
  141. .toMatchInlineSnapshot(`
  142. "@supports(display: grid) {
  143. .foo[test] { display: grid
  144. }}"
  145. `)
  146. })
  147. test('scoped keyframes', () => {
  148. const style = compileScoped(
  149. `
  150. .anim {
  151. animation: color 5s infinite, other 5s;
  152. }
  153. .anim-2 {
  154. animation-name: color;
  155. animation-duration: 5s;
  156. }
  157. .anim-3 {
  158. animation: 5s color infinite, 5s other;
  159. }
  160. .anim-multiple {
  161. animation: color 5s infinite, opacity 2s;
  162. }
  163. .anim-multiple-2 {
  164. animation-name: color, opacity;
  165. animation-duration: 5s, 2s;
  166. }
  167. @keyframes color {
  168. from { color: red; }
  169. to { color: green; }
  170. }
  171. @-webkit-keyframes color {
  172. from { color: red; }
  173. to { color: green; }
  174. }
  175. @keyframes opacity {
  176. from { opacity: 0; }
  177. to { opacity: 1; }
  178. }
  179. @-webkit-keyframes opacity {
  180. from { opacity: 0; }
  181. to { opacity: 1; }
  182. }
  183. `,
  184. { id: 'data-v-test' }
  185. )
  186. expect(style).toContain(
  187. `.anim[data-v-test] {\n animation: color-test 5s infinite, other 5s;`
  188. )
  189. expect(style).toContain(
  190. `.anim-2[data-v-test] {\n animation-name: color-test`
  191. )
  192. expect(style).toContain(
  193. `.anim-3[data-v-test] {\n animation: 5s color-test infinite, 5s other;`
  194. )
  195. expect(style).toContain(`@keyframes color-test {`)
  196. expect(style).toContain(`@-webkit-keyframes color-test {`)
  197. expect(style).toContain(
  198. `.anim-multiple[data-v-test] {\n animation: color-test 5s infinite,opacity-test 2s;`
  199. )
  200. expect(style).toContain(
  201. `.anim-multiple-2[data-v-test] {\n animation-name: color-test,opacity-test;`
  202. )
  203. expect(style).toContain(`@keyframes opacity-test {`)
  204. expect(style).toContain(`@-webkit-keyframes opacity-test {`)
  205. })
  206. // vue-loader/#1370
  207. test('spaces after selector', () => {
  208. expect(compileScoped(`.foo , .bar { color: red; }`)).toMatchInlineSnapshot(`
  209. ".foo[test], .bar[test] { color: red;
  210. }"
  211. `)
  212. })
  213. describe('deprecated syntax', () => {
  214. test('::v-deep as combinator', () => {
  215. expect(compileScoped(`::v-deep .foo { color: red; }`))
  216. .toMatchInlineSnapshot(`
  217. "[test] .foo { color: red;
  218. }"
  219. `)
  220. expect(compileScoped(`.bar ::v-deep .foo { color: red; }`))
  221. .toMatchInlineSnapshot(`
  222. ".bar[test] .foo { color: red;
  223. }"
  224. `)
  225. expect(
  226. `::v-deep usage as a combinator has been deprecated.`
  227. ).toHaveBeenWarned()
  228. })
  229. test('>>> (deprecated syntax)', () => {
  230. const code = compileScoped(`>>> .foo { color: red; }`)
  231. expect(code).toMatchInlineSnapshot(`
  232. "[test] .foo { color: red;
  233. }"
  234. `)
  235. expect(
  236. `the >>> and /deep/ combinators have been deprecated.`
  237. ).toHaveBeenWarned()
  238. })
  239. test('/deep/ (deprecated syntax)', () => {
  240. const code = compileScoped(`/deep/ .foo { color: red; }`)
  241. expect(code).toMatchInlineSnapshot(`
  242. "[test] .foo { color: red;
  243. }"
  244. `)
  245. expect(
  246. `the >>> and /deep/ combinators have been deprecated.`
  247. ).toHaveBeenWarned()
  248. })
  249. })
  250. describe('<style vars>', () => {
  251. test('should rewrite CSS vars in scoped mode', () => {
  252. const code = compileScoped(
  253. `.foo {
  254. color: var(--color);
  255. font-size: var(--global:font);
  256. }`,
  257. {
  258. id: 'data-v-test',
  259. vars: true
  260. }
  261. )
  262. expect(code).toMatchInlineSnapshot(`
  263. ".foo[data-v-test] {
  264. color: var(--test-color);
  265. font-size: var(--font);
  266. }"
  267. `)
  268. })
  269. })
  270. })
  271. describe('SFC CSS modules', () => {
  272. test('should include resulting classes object in result', async () => {
  273. const result = await compileStyleAsync({
  274. source: `.red { color: red }\n.green { color: green }\n:global(.blue) { color: blue }`,
  275. filename: `test.css`,
  276. id: 'test',
  277. modules: true
  278. })
  279. expect(result.modules).toBeDefined()
  280. expect(result.modules!.red).toMatch('_red_')
  281. expect(result.modules!.green).toMatch('_green_')
  282. expect(result.modules!.blue).toBeUndefined()
  283. })
  284. test('postcss-modules options', async () => {
  285. const result = await compileStyleAsync({
  286. source: `:local(.foo-bar) { color: red }\n.baz-qux { color: green }`,
  287. filename: `test.css`,
  288. id: 'test',
  289. modules: true,
  290. modulesOptions: {
  291. scopeBehaviour: 'global',
  292. generateScopedName: `[name]__[local]__[hash:base64:5]`,
  293. localsConvention: 'camelCaseOnly'
  294. }
  295. })
  296. expect(result.modules).toBeDefined()
  297. expect(result.modules!.fooBar).toMatch('__foo-bar__')
  298. expect(result.modules!.bazQux).toBeUndefined()
  299. })
  300. })
  301. describe('SFC style preprocessors', () => {
  302. test('scss @import', () => {
  303. const res = compileStyle({
  304. source: `
  305. @import "./import.scss";
  306. `,
  307. filename: path.resolve(__dirname, './fixture/test.scss'),
  308. id: '',
  309. preprocessLang: 'scss'
  310. })
  311. expect([...res.dependencies]).toStrictEqual([
  312. path.join(__dirname, './fixture/import.scss')
  313. ])
  314. })
  315. })