parse.spec.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import { parse } from '../src'
  2. import { baseParse, baseCompile } from '@vue/compiler-core'
  3. import { SourceMapConsumer } from 'source-map'
  4. describe('compiler:sfc', () => {
  5. describe('source map', () => {
  6. test('style block', () => {
  7. // Padding determines how many blank lines will there be before the style block
  8. const padding = Math.round(Math.random() * 10)
  9. const style = parse(
  10. `${'\n'.repeat(padding)}<style>\n.color {\n color: red;\n }\n</style>\n`
  11. ).descriptor.styles[0]
  12. expect(style.map).not.toBeUndefined()
  13. const consumer = new SourceMapConsumer(style.map!)
  14. consumer.eachMapping(mapping => {
  15. expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
  16. })
  17. })
  18. test('script block', () => {
  19. // Padding determines how many blank lines will there be before the style block
  20. const padding = Math.round(Math.random() * 10)
  21. const script = parse(
  22. `${'\n'.repeat(padding)}<script>\nconsole.log(1)\n }\n</script>\n`
  23. ).descriptor.script
  24. expect(script!.map).not.toBeUndefined()
  25. const consumer = new SourceMapConsumer(script!.map!)
  26. consumer.eachMapping(mapping => {
  27. expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
  28. })
  29. })
  30. test('custom block', () => {
  31. const padding = Math.round(Math.random() * 10)
  32. const custom = parse(
  33. `${'\n'.repeat(padding)}<i18n>\n{\n "greeting": "hello"\n}\n</i18n>\n`
  34. ).descriptor.customBlocks[0]
  35. expect(custom!.map).not.toBeUndefined()
  36. const consumer = new SourceMapConsumer(custom!.map!)
  37. consumer.eachMapping(mapping => {
  38. expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
  39. })
  40. })
  41. })
  42. test('pad content', () => {
  43. const content = `
  44. <template>
  45. <div></div>
  46. </template>
  47. <script>
  48. export default {}
  49. </script>
  50. <style>
  51. h1 { color: red }
  52. </style>
  53. <i18n>
  54. { "greeting": "hello" }
  55. </i18n>
  56. `
  57. const padFalse = parse(content.trim(), { pad: false }).descriptor
  58. expect(padFalse.template!.content).toBe('\n<div></div>\n')
  59. expect(padFalse.script!.content).toBe('\nexport default {}\n')
  60. expect(padFalse.styles[0].content).toBe('\nh1 { color: red }\n')
  61. expect(padFalse.customBlocks[0].content).toBe('\n{ "greeting": "hello" }\n')
  62. const padTrue = parse(content.trim(), { pad: true }).descriptor
  63. expect(padTrue.script!.content).toBe(
  64. Array(3 + 1).join('//\n') + '\nexport default {}\n'
  65. )
  66. expect(padTrue.styles[0].content).toBe(
  67. Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
  68. )
  69. expect(padTrue.customBlocks[0].content).toBe(
  70. Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n'
  71. )
  72. const padLine = parse(content.trim(), { pad: 'line' }).descriptor
  73. expect(padLine.script!.content).toBe(
  74. Array(3 + 1).join('//\n') + '\nexport default {}\n'
  75. )
  76. expect(padLine.styles[0].content).toBe(
  77. Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
  78. )
  79. expect(padLine.customBlocks[0].content).toBe(
  80. Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n'
  81. )
  82. const padSpace = parse(content.trim(), { pad: 'space' }).descriptor
  83. expect(padSpace.script!.content).toBe(
  84. `<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') +
  85. '\nexport default {}\n'
  86. )
  87. expect(padSpace.styles[0].content).toBe(
  88. `<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>`.replace(
  89. /./g,
  90. ' '
  91. ) + '\nh1 { color: red }\n'
  92. )
  93. expect(padSpace.customBlocks[0].content).toBe(
  94. `<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>\nh1 { color: red }\n</style>\n<i18n>`.replace(
  95. /./g,
  96. ' '
  97. ) + '\n{ "greeting": "hello" }\n'
  98. )
  99. })
  100. test('should keep template nodes with no content', () => {
  101. const { descriptor } = parse(`<template/>`)
  102. expect(descriptor.template).toBeTruthy()
  103. expect(descriptor.template!.content).toBeFalsy()
  104. })
  105. test('should ignore other nodes with no content', () => {
  106. expect(parse(`<script/>`).descriptor.script).toBe(null)
  107. expect(parse(`<style/>`).descriptor.styles.length).toBe(0)
  108. expect(parse(`<custom/>`).descriptor.customBlocks.length).toBe(0)
  109. })
  110. test('handle empty nodes with src attribute', () => {
  111. const { descriptor } = parse(`<script src="com"/>`)
  112. expect(descriptor.script).toBeTruthy()
  113. expect(descriptor.script!.content).toBeFalsy()
  114. expect(descriptor.script!.attrs['src']).toBe('com')
  115. })
  116. test('nested templates', () => {
  117. const content = `
  118. <template v-if="ok">ok</template>
  119. <div><div></div></div>
  120. `
  121. const { descriptor } = parse(`<template>${content}</template>`)
  122. expect(descriptor.template!.content).toBe(content)
  123. })
  124. test('treat empty lang attribute as the html', () => {
  125. const content = `<div><template v-if="ok">ok</template></div>`
  126. const { descriptor, errors } = parse(
  127. `<template lang="">${content}</template>`
  128. )
  129. expect(descriptor.template!.content).toBe(content)
  130. expect(errors.length).toBe(0)
  131. })
  132. // #1120
  133. test('alternative template lang should be treated as plain text', () => {
  134. const content = `p(v-if="1 < 2") test`
  135. const { descriptor, errors } = parse(
  136. `<template lang="pug">` + content + `</template>`
  137. )
  138. expect(errors.length).toBe(0)
  139. expect(descriptor.template!.content).toBe(content)
  140. })
  141. //#2566
  142. test('div lang should not be treated as plain text', () => {
  143. const { errors } = parse(`
  144. <template lang="pug">
  145. <div lang="">
  146. <div></div>
  147. </div>
  148. </template>
  149. `)
  150. expect(errors.length).toBe(0)
  151. })
  152. test('slotted detection', async () => {
  153. expect(parse(`<template>hi</template>`).descriptor.slotted).toBe(false)
  154. expect(
  155. parse(`<template>hi</template><style>h1{color:red;}</style>`).descriptor
  156. .slotted
  157. ).toBe(false)
  158. expect(
  159. parse(
  160. `<template>hi</template><style scoped>:slotted(h1){color:red;}</style>`
  161. ).descriptor.slotted
  162. ).toBe(true)
  163. expect(
  164. parse(
  165. `<template>hi</template><style scoped>::v-slotted(h1){color:red;}</style>`
  166. ).descriptor.slotted
  167. ).toBe(true)
  168. })
  169. test('error tolerance', () => {
  170. const { errors } = parse(`<template>`)
  171. expect(errors.length).toBe(1)
  172. })
  173. test('should parse as DOM by default', () => {
  174. const { errors } = parse(`<template><input></template>`)
  175. expect(errors.length).toBe(0)
  176. })
  177. test('custom compiler', () => {
  178. const { errors } = parse(`<template><input></template>`, {
  179. compiler: {
  180. parse: baseParse,
  181. compile: baseCompile
  182. }
  183. })
  184. expect(errors.length).toBe(1)
  185. })
  186. test('treat custom blocks as raw text', () => {
  187. const { errors, descriptor } = parse(`<foo> <-& </foo>`)
  188. expect(errors.length).toBe(0)
  189. expect(descriptor.customBlocks[0].content).toBe(` <-& `)
  190. })
  191. describe('warnings', () => {
  192. function assertWarning(errors: Error[], msg: string) {
  193. expect(errors.some(e => e.message.match(msg))).toBe(true)
  194. }
  195. test('should only allow single template element', () => {
  196. assertWarning(
  197. parse(`<template><div/></template><template><div/></template>`).errors,
  198. `Single file component can contain only one <template> element`
  199. )
  200. })
  201. test('should only allow single script element', () => {
  202. assertWarning(
  203. parse(`<script>console.log(1)</script><script>console.log(1)</script>`)
  204. .errors,
  205. `Single file component can contain only one <script> element`
  206. )
  207. })
  208. test('should only allow single script setup element', () => {
  209. assertWarning(
  210. parse(
  211. `<script setup>console.log(1)</script><script setup>console.log(1)</script>`
  212. ).errors,
  213. `Single file component can contain only one <script setup> element`
  214. )
  215. })
  216. test('should not warn script & script setup', () => {
  217. expect(
  218. parse(
  219. `<script setup>console.log(1)</script><script>console.log(1)</script>`
  220. ).errors.length
  221. ).toBe(0)
  222. })
  223. })
  224. })