sfc-parser.spec.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import { parseComponent } from 'sfc/parser'
  2. describe('Single File Component parser', () => {
  3. it('should parse', () => {
  4. const res = parseComponent(`
  5. <template>
  6. <div>hi</div>
  7. </template>
  8. <style src="./test.css"></style>
  9. <style lang="stylus" scoped>
  10. h1
  11. color red
  12. h2
  13. color green
  14. </style>
  15. <style module>
  16. h1 { font-weight: bold }
  17. </style>
  18. <style bool-attr val-attr="test"></style>
  19. <script>
  20. export default {}
  21. </script>
  22. <div>
  23. <style>nested should be ignored</style>
  24. </div>
  25. `)
  26. expect(res.template!.content.trim()).toBe('<div>hi</div>')
  27. expect(res.styles.length).toBe(4)
  28. expect(res.styles[0].src).toBe('./test.css')
  29. expect(res.styles[1].lang).toBe('stylus')
  30. expect(res.styles[1].scoped).toBe(true)
  31. expect(res.styles[1].content.trim()).toBe(
  32. 'h1\n color red\nh2\n color green'
  33. )
  34. expect(res.styles[2].module).toBe(true)
  35. expect(res.styles[3].attrs['bool-attr']).toBe(true)
  36. expect(res.styles[3].attrs['val-attr']).toBe('test')
  37. expect(res.script!.content.trim()).toBe('export default {}')
  38. })
  39. it('should parse template with closed input', () => {
  40. const res = parseComponent(`
  41. <template>
  42. <input type="text"/>
  43. </template>
  44. `)
  45. expect(res.template!.content.trim()).toBe('<input type="text"/>')
  46. })
  47. it('should handle nested template', () => {
  48. const res = parseComponent(`
  49. <template>
  50. <div><template v-if="ok">hi</template></div>
  51. </template>
  52. `)
  53. expect(res.template!.content.trim()).toBe(
  54. '<div><template v-if="ok">hi</template></div>'
  55. )
  56. })
  57. it('deindent content', () => {
  58. const content = `
  59. <template>
  60. <div></div>
  61. </template>
  62. <script>
  63. export default {}
  64. </script>
  65. <style>
  66. h1 { color: red }
  67. </style>
  68. `
  69. const deindentDefault = parseComponent(content.trim(), { pad: false })
  70. const deindentEnabled = parseComponent(content.trim(), {
  71. pad: false,
  72. deindent: true
  73. })
  74. const deindentDisabled = parseComponent(content.trim(), {
  75. pad: false,
  76. deindent: false
  77. })
  78. expect(deindentDefault.template!.content).toBe('\n<div></div>\n')
  79. expect(deindentDefault.script!.content).toBe('\nexport default {}\n')
  80. expect(deindentDefault.styles[0].content).toBe('\nh1 { color: red }\n')
  81. expect(deindentEnabled.template!.content).toBe('\n<div></div>\n')
  82. expect(deindentEnabled.script!.content).toBe('\nexport default {}\n')
  83. expect(deindentEnabled.styles[0].content).toBe('\nh1 { color: red }\n')
  84. expect(deindentDisabled.template!.content).toBe(
  85. '\n <div></div>\n '
  86. )
  87. expect(deindentDisabled.script!.content).toBe(
  88. '\n export default {}\n '
  89. )
  90. expect(deindentDisabled.styles[0].content).toBe(
  91. '\n h1 { color: red }\n '
  92. )
  93. })
  94. it('pad content', () => {
  95. const content = `
  96. <template>
  97. <div></div>
  98. </template>
  99. <script>
  100. export default {}
  101. </script>
  102. <style>
  103. h1 { color: red }
  104. </style>
  105. `
  106. const padDefault = parseComponent(content.trim(), { pad: true })
  107. const padLine = parseComponent(content.trim(), { pad: 'line' })
  108. const padSpace = parseComponent(content.trim(), { pad: 'space' })
  109. expect(padDefault.script!.content).toBe(
  110. Array(3 + 1).join('//\n') + '\nexport default {}\n'
  111. )
  112. expect(padDefault.styles[0].content).toBe(
  113. Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
  114. )
  115. expect(padLine.script!.content).toBe(
  116. Array(3 + 1).join('//\n') + '\nexport default {}\n'
  117. )
  118. expect(padLine.styles[0].content).toBe(
  119. Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
  120. )
  121. expect(padSpace.script!.content).toBe(
  122. `<template>
  123. <div></div>
  124. </template>
  125. <script>`.replace(/./g, ' ') + '\nexport default {}\n'
  126. )
  127. expect(padSpace.styles[0].content).toBe(
  128. `<template>
  129. <div></div>
  130. </template>
  131. <script>
  132. export default {}
  133. </script>
  134. <style>`.replace(/./g, ' ') + '\nh1 { color: red }\n'
  135. )
  136. })
  137. it('should handle template blocks with lang as special text', () => {
  138. const res = parseComponent(`
  139. <template lang="pug">
  140. div
  141. h1(v-if='1 < 2') hello
  142. </template>
  143. `)
  144. expect(res.template!.content.trim()).toBe(`div\n h1(v-if='1 < 2') hello`)
  145. })
  146. it('should handle component contains "<" only', () => {
  147. const res = parseComponent(`
  148. <template>
  149. <span><</span>
  150. </template>
  151. `)
  152. expect(res.template!.content.trim()).toBe(`<span><</span>`)
  153. })
  154. it('should handle custom blocks without parsing them', () => {
  155. const res = parseComponent(`
  156. <template>
  157. <div></div>
  158. </template>
  159. <example name="simple">
  160. <my-button ref="button">Hello</my-button>
  161. </example>
  162. <example name="with props">
  163. <my-button color="red">Hello</my-button>
  164. </example>
  165. <test name="simple" foo="bar">
  166. export default function simple (vm) {
  167. describe('Hello', () => {
  168. it('should display Hello', () => {
  169. this.vm.$refs.button.$el.innerText.should.equal('Hello')
  170. }))
  171. }))
  172. }
  173. </test>
  174. `)
  175. expect(res.customBlocks.length).toBe(3)
  176. const simpleExample = res.customBlocks[0]
  177. expect(simpleExample.type).toBe('example')
  178. expect(simpleExample.content.trim()).toBe(
  179. '<my-button ref="button">Hello</my-button>'
  180. )
  181. expect(simpleExample.attrs.name).toBe('simple')
  182. const withProps = res.customBlocks[1]
  183. expect(withProps.type).toBe('example')
  184. expect(withProps.content.trim()).toBe(
  185. '<my-button color="red">Hello</my-button>'
  186. )
  187. expect(withProps.attrs.name).toBe('with props')
  188. const simpleTest = res.customBlocks[2]
  189. expect(simpleTest.type).toBe('test')
  190. expect(simpleTest.content.trim())
  191. .toBe(`export default function simple (vm) {
  192. describe('Hello', () => {
  193. it('should display Hello', () => {
  194. this.vm.$refs.button.$el.innerText.should.equal('Hello')
  195. }))
  196. }))
  197. }`)
  198. expect(simpleTest.attrs.name).toBe('simple')
  199. expect(simpleTest.attrs.foo).toBe('bar')
  200. })
  201. // Regression #4289
  202. it('accepts nested template tag', () => {
  203. const raw = `<div>
  204. <template v-if="true === true">
  205. <section class="section">
  206. <div class="container">
  207. Should be shown
  208. </div>
  209. </section>
  210. </template>
  211. <template v-else>
  212. <p>Should not be shown</p>
  213. </template>
  214. </div>`
  215. const res = parseComponent(`<template>${raw}</template>`)
  216. expect(res.template!.content.trim()).toBe(raw)
  217. })
  218. it('should not hang on trailing text', () => {
  219. const res = parseComponent(`<template>hi</`)
  220. expect(res.template!.content).toBe('hi')
  221. })
  222. it('should collect errors with source range', () => {
  223. const res = parseComponent(`<template>hi</`, { outputSourceRange: true })
  224. expect(res.errors.length).toBe(1)
  225. expect((res.errors[0] as WarningMessage).start).toBe(0)
  226. })
  227. })