compileStyle.spec.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import {
  2. compileStyle,
  3. compileStyleAsync,
  4. SFCStyleCompileOptions
  5. } from '../src/compileStyle'
  6. import path from '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. })
  81. test('::v-slotted', () => {
  82. expect(compileScoped(`:slotted(.foo) { color: red; }`))
  83. .toMatchInlineSnapshot(`
  84. ".foo[data-v-test-s] { color: red;
  85. }"
  86. `)
  87. expect(compileScoped(`::v-slotted(.foo) { color: red; }`))
  88. .toMatchInlineSnapshot(`
  89. ".foo[data-v-test-s] { color: red;
  90. }"
  91. `)
  92. expect(compileScoped(`::v-slotted(.foo .bar) { color: red; }`))
  93. .toMatchInlineSnapshot(`
  94. ".foo .bar[data-v-test-s] { color: red;
  95. }"
  96. `)
  97. expect(compileScoped(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
  98. .toMatchInlineSnapshot(`
  99. ".baz .qux .foo .bar[data-v-test-s] { color: red;
  100. }"
  101. `)
  102. })
  103. test('::v-global', () => {
  104. expect(compileScoped(`:global(.foo) { color: red; }`))
  105. .toMatchInlineSnapshot(`
  106. ".foo { color: red;
  107. }"
  108. `)
  109. expect(compileScoped(`::v-global(.foo) { color: red; }`))
  110. .toMatchInlineSnapshot(`
  111. ".foo { color: red;
  112. }"
  113. `)
  114. expect(compileScoped(`::v-global(.foo .bar) { color: red; }`))
  115. .toMatchInlineSnapshot(`
  116. ".foo .bar { color: red;
  117. }"
  118. `)
  119. // global ignores anything before it
  120. expect(compileScoped(`.baz .qux ::v-global(.foo .bar) { color: red; }`))
  121. .toMatchInlineSnapshot(`
  122. ".foo .bar { color: red;
  123. }"
  124. `)
  125. })
  126. test('media query', () => {
  127. expect(compileScoped(`@media print { .foo { color: red }}`))
  128. .toMatchInlineSnapshot(`
  129. "@media print {
  130. .foo[data-v-test] { color: red
  131. }}"
  132. `)
  133. })
  134. test('supports query', () => {
  135. expect(compileScoped(`@supports(display: grid) { .foo { display: grid }}`))
  136. .toMatchInlineSnapshot(`
  137. "@supports(display: grid) {
  138. .foo[data-v-test] { display: grid
  139. }}"
  140. `)
  141. })
  142. test('scoped keyframes', () => {
  143. const style = compileScoped(
  144. `
  145. .anim {
  146. animation: color 5s infinite, other 5s;
  147. }
  148. .anim-2 {
  149. animation-name: color;
  150. animation-duration: 5s;
  151. }
  152. .anim-3 {
  153. animation: 5s color infinite, 5s other;
  154. }
  155. .anim-multiple {
  156. animation: color 5s infinite, opacity 2s;
  157. }
  158. .anim-multiple-2 {
  159. animation-name: color, opacity;
  160. animation-duration: 5s, 2s;
  161. }
  162. @keyframes color {
  163. from { color: red; }
  164. to { color: green; }
  165. }
  166. @-webkit-keyframes color {
  167. from { color: red; }
  168. to { color: green; }
  169. }
  170. @keyframes opacity {
  171. from { opacity: 0; }
  172. to { opacity: 1; }
  173. }
  174. @-webkit-keyframes opacity {
  175. from { opacity: 0; }
  176. to { opacity: 1; }
  177. }
  178. `,
  179. { id: 'data-v-test' }
  180. )
  181. expect(style).toContain(
  182. `.anim[data-v-test] {\n animation: color-test 5s infinite, other 5s;`
  183. )
  184. expect(style).toContain(
  185. `.anim-2[data-v-test] {\n animation-name: color-test`
  186. )
  187. expect(style).toContain(
  188. `.anim-3[data-v-test] {\n animation: 5s color-test infinite, 5s other;`
  189. )
  190. expect(style).toContain(`@keyframes color-test {`)
  191. expect(style).toContain(`@-webkit-keyframes color-test {`)
  192. expect(style).toContain(
  193. `.anim-multiple[data-v-test] {\n animation: color-test 5s infinite,opacity-test 2s;`
  194. )
  195. expect(style).toContain(
  196. `.anim-multiple-2[data-v-test] {\n animation-name: color-test,opacity-test;`
  197. )
  198. expect(style).toContain(`@keyframes opacity-test {\nfrom { opacity: 0;`)
  199. expect(style).toContain(
  200. `@-webkit-keyframes opacity-test {\nfrom { opacity: 0;`
  201. )
  202. })
  203. // vue-loader/#1370
  204. test('spaces after selector', () => {
  205. expect(compileScoped(`.foo , .bar { color: red; }`)).toMatchInlineSnapshot(`
  206. ".foo[data-v-test], .bar[data-v-test] { color: red;
  207. }"
  208. `)
  209. })
  210. describe('deprecated syntax', () => {
  211. test('::v-deep as combinator', () => {
  212. expect(compileScoped(`::v-deep .foo { color: red; }`))
  213. .toMatchInlineSnapshot(`
  214. "[data-v-test] .foo { color: red;
  215. }"
  216. `)
  217. expect(compileScoped(`.bar ::v-deep .foo { color: red; }`))
  218. .toMatchInlineSnapshot(`
  219. ".bar[data-v-test] .foo { color: red;
  220. }"
  221. `)
  222. expect(
  223. `::v-deep usage as a combinator has been deprecated.`
  224. ).toHaveBeenWarned()
  225. })
  226. test('>>> (deprecated syntax)', () => {
  227. const code = compileScoped(`>>> .foo { color: red; }`)
  228. expect(code).toMatchInlineSnapshot(`
  229. "[data-v-test] .foo { color: red;
  230. }"
  231. `)
  232. expect(
  233. `the >>> and /deep/ combinators have been deprecated.`
  234. ).toHaveBeenWarned()
  235. })
  236. test('/deep/ (deprecated syntax)', () => {
  237. const code = compileScoped(`/deep/ .foo { color: red; }`)
  238. expect(code).toMatchInlineSnapshot(`
  239. "[data-v-test] .foo { color: red;
  240. }"
  241. `)
  242. expect(
  243. `the >>> and /deep/ combinators have been deprecated.`
  244. ).toHaveBeenWarned()
  245. })
  246. })
  247. })
  248. describe('SFC CSS modules', () => {
  249. test('should include resulting classes object in result', async () => {
  250. const result = await compileStyleAsync({
  251. source: `.red { color: red }\n.green { color: green }\n:global(.blue) { color: blue }`,
  252. filename: `test.css`,
  253. id: 'test',
  254. modules: true
  255. })
  256. expect(result.modules).toBeDefined()
  257. expect(result.modules!.red).toMatch('_red_')
  258. expect(result.modules!.green).toMatch('_green_')
  259. expect(result.modules!.blue).toBeUndefined()
  260. })
  261. test('postcss-modules options', async () => {
  262. const result = await compileStyleAsync({
  263. source: `:local(.foo-bar) { color: red }\n.baz-qux { color: green }`,
  264. filename: `test.css`,
  265. id: 'test',
  266. modules: true,
  267. modulesOptions: {
  268. scopeBehaviour: 'global',
  269. generateScopedName: `[name]__[local]__[hash:base64:5]`,
  270. localsConvention: 'camelCaseOnly'
  271. }
  272. })
  273. expect(result.modules).toBeDefined()
  274. expect(result.modules!.fooBar).toMatch('__foo-bar__')
  275. expect(result.modules!.bazQux).toBeUndefined()
  276. })
  277. })
  278. describe('SFC style preprocessors', () => {
  279. test('scss @import', () => {
  280. const res = compileStyle({
  281. source: `
  282. @import "./import.scss";
  283. `,
  284. filename: path.resolve(__dirname, './fixture/test.scss'),
  285. id: '',
  286. preprocessLang: 'scss'
  287. })
  288. expect([...res.dependencies]).toStrictEqual([
  289. path.join(__dirname, './fixture/import.scss')
  290. ])
  291. })
  292. test('scss respect user-defined string options.additionalData', () => {
  293. const res = compileStyle({
  294. preprocessOptions: {
  295. additionalData: `
  296. @mixin square($size) {
  297. width: $size;
  298. height: $size;
  299. }`
  300. },
  301. source: `
  302. .square {
  303. @include square(100px);
  304. }
  305. `,
  306. filename: path.resolve(__dirname, './fixture/test.scss'),
  307. id: '',
  308. preprocessLang: 'scss'
  309. })
  310. expect(res.errors.length).toBe(0)
  311. })
  312. test('scss respect user-defined function options.additionalData', () => {
  313. const source = `
  314. .square {
  315. @include square(100px);
  316. }
  317. `
  318. const filename = path.resolve(__dirname, './fixture/test.scss')
  319. const res = compileStyle({
  320. preprocessOptions: {
  321. additionalData: (s: string, f: string) => {
  322. expect(s).toBe(source)
  323. expect(f).toBe(filename)
  324. return `
  325. @mixin square($size) {
  326. width: $size;
  327. height: $size;
  328. }`
  329. }
  330. },
  331. source,
  332. filename,
  333. id: '',
  334. preprocessLang: 'scss'
  335. })
  336. expect(res.errors.length).toBe(0)
  337. })
  338. })