compile.test.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. import {
  2. type RootNode,
  3. BindingTypes,
  4. ErrorCodes,
  5. DOMErrorCodes,
  6. } from '@vue/compiler-dom'
  7. import { type CompilerOptions, compile as _compile } from '../src'
  8. function compile(template: string | RootNode, options: CompilerOptions = {}) {
  9. let { code } = _compile(template, {
  10. ...options,
  11. mode: 'module',
  12. prefixIdentifiers: true,
  13. })
  14. return code
  15. }
  16. describe('compile', () => {
  17. test('static template', async () => {
  18. const code = await compile(
  19. `<div>
  20. <p>hello</p>
  21. <input />
  22. <span />
  23. </div>`,
  24. )
  25. expect(code).matchSnapshot()
  26. })
  27. test('dynamic root', async () => {
  28. const code = await compile(`{{ 1 }}{{ 2 }}`)
  29. expect(code).matchSnapshot()
  30. })
  31. test('dynamic root nodes and interpolation', async () => {
  32. const code = await compile(
  33. `<button @click="handleClick" :id="count">{{count}}foo{{count}}foo{{count}} </button>`,
  34. )
  35. expect(code).matchSnapshot()
  36. })
  37. test('static + dynamic root', async () => {
  38. const code = await compile(
  39. `{{ 1 }}{{ 2 }}3{{ 4 }}{{ 5 }}6{{ 7 }}{{ 8 }}9{{ 'A' }}{{ 'B' }}`,
  40. )
  41. expect(code).matchSnapshot()
  42. })
  43. test('fragment', async () => {
  44. const code = await compile(`<p/><span/><div/>`)
  45. expect(code).matchSnapshot()
  46. })
  47. test('bindings', async () => {
  48. const code = await compile(`<div>count is {{ count }}.</div>`, {
  49. bindingMetadata: {
  50. count: BindingTypes.SETUP_REF,
  51. },
  52. })
  53. expect(code).matchSnapshot()
  54. })
  55. describe('directives', () => {
  56. describe('v-bind', () => {
  57. test('simple expression', async () => {
  58. const code = await compile(`<div :id="id"></div>`, {
  59. bindingMetadata: {
  60. id: BindingTypes.SETUP_REF,
  61. },
  62. })
  63. expect(code).matchSnapshot()
  64. })
  65. test('should error if no expression', async () => {
  66. const onError = vi.fn()
  67. const code = await compile(`<div v-bind:arg="" />`, { onError })
  68. expect(onError.mock.calls[0][0]).toMatchObject({
  69. code: ErrorCodes.X_V_BIND_NO_EXPRESSION,
  70. loc: {
  71. start: {
  72. line: 1,
  73. column: 6,
  74. },
  75. end: {
  76. line: 1,
  77. column: 19,
  78. },
  79. },
  80. })
  81. expect(code).matchSnapshot()
  82. // the arg is static
  83. expect(code).contains(JSON.stringify('<div arg=""></div>'))
  84. })
  85. test('no expression', async () => {
  86. const code = await compile('<div v-bind:id />', {
  87. bindingMetadata: {
  88. id: BindingTypes.SETUP_REF,
  89. },
  90. })
  91. expect(code).matchSnapshot()
  92. expect(code).contains('_setAttr(n1, "id", undefined, _ctx.id)')
  93. })
  94. test('no expression (shorthand)', async () => {
  95. const code = await compile('<div :camel-case />', {
  96. bindingMetadata: {
  97. camelCase: BindingTypes.SETUP_REF,
  98. },
  99. })
  100. expect(code).matchSnapshot()
  101. expect(code).contains(
  102. '_setAttr(n1, "camel-case", undefined, _ctx.camelCase)',
  103. )
  104. })
  105. test('dynamic arg', async () => {
  106. const code = await compile('<div v-bind:[id]="id"/>', {
  107. bindingMetadata: {
  108. id: BindingTypes.SETUP_REF,
  109. },
  110. })
  111. expect(code).matchSnapshot()
  112. expect(code).contains('_setAttr(n1, _ctx.id, undefined, _ctx.id)')
  113. })
  114. // TODO: camel modifier for v-bind
  115. test.fails('.camel modifier', async () => {
  116. const code = await compile(`<div v-bind:foo-bar.camel="id"/>`)
  117. expect(code).matchSnapshot()
  118. expect(code).contains('fooBar')
  119. })
  120. })
  121. describe('v-on', () => {
  122. test('simple expression', async () => {
  123. const code = await compile(`<div @click="handleClick"></div>`, {
  124. bindingMetadata: {
  125. handleClick: BindingTypes.SETUP_CONST,
  126. },
  127. })
  128. expect(code).matchSnapshot()
  129. })
  130. test('should error if no expression AND no modifier', async () => {
  131. const onError = vi.fn()
  132. await compile(`<div v-on:click />`, { onError })
  133. expect(onError.mock.calls[0][0]).toMatchObject({
  134. code: ErrorCodes.X_V_ON_NO_EXPRESSION,
  135. loc: {
  136. start: {
  137. line: 1,
  138. column: 6,
  139. },
  140. end: {
  141. line: 1,
  142. column: 16,
  143. },
  144. },
  145. })
  146. })
  147. test('event modifier', async () => {
  148. const code = await compile(
  149. `<a @click.stop="handleEvent"></a>
  150. <form @submit.prevent="handleEvent"></form>
  151. <a @click.stop.prevent="handleEvent"></a>
  152. <div @click.self="handleEvent"></div>
  153. <div @click.capture="handleEvent"></div>
  154. <a @click.once="handleEvent"></a>
  155. <div @scroll.passive="handleEvent"></div>
  156. <input @click.right="handleEvent" />
  157. <input @click.left="handleEvent" />
  158. <input @click.middle="handleEvent" />
  159. <input @click.enter.right="handleEvent" />
  160. <input @keyup.enter="handleEvent" />
  161. <input @keyup.tab="handleEvent" />
  162. <input @keyup.delete="handleEvent" />
  163. <input @keyup.esc="handleEvent" />
  164. <input @keyup.space="handleEvent" />
  165. <input @keyup.up="handleEvent" />
  166. <input @keyup.down="handleEvent" />
  167. <input @keyup.left="handleEvent" />
  168. <input @keyup.middle="submit" />
  169. <input @keyup.middle.self="submit" />
  170. <input @keyup.self.enter="handleEvent" />`,
  171. {
  172. bindingMetadata: {
  173. handleEvent: BindingTypes.SETUP_CONST,
  174. },
  175. },
  176. )
  177. expect(code).matchSnapshot()
  178. })
  179. })
  180. describe('v-html', () => {
  181. test('simple expression', async () => {
  182. const code = await compile(`<div v-html="code"></div>`, {
  183. bindingMetadata: {
  184. code: BindingTypes.SETUP_REF,
  185. },
  186. })
  187. expect(code).matchSnapshot()
  188. })
  189. test('should raise error and ignore children when v-html is present', async () => {
  190. const onError = vi.fn()
  191. const code = await compile(`<div v-html="test">hello</div>`, {
  192. onError,
  193. })
  194. expect(code).matchSnapshot()
  195. expect(onError.mock.calls).toMatchObject([
  196. [{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }],
  197. ])
  198. })
  199. test('should raise error if has no expression', async () => {
  200. const onError = vi.fn()
  201. const code = await compile(`<div v-html></div>`, {
  202. onError,
  203. })
  204. expect(code).matchSnapshot()
  205. expect(onError.mock.calls).toMatchObject([
  206. [{ code: DOMErrorCodes.X_V_HTML_NO_EXPRESSION }],
  207. ])
  208. })
  209. })
  210. describe('v-text', () => {
  211. test('simple expression', async () => {
  212. const code = await compile(`<div v-text="str"></div>`, {
  213. bindingMetadata: {
  214. str: BindingTypes.SETUP_REF,
  215. },
  216. })
  217. expect(code).matchSnapshot()
  218. })
  219. test('no expression', async () => {
  220. const onError = vi.fn()
  221. const code = await compile(`<div v-text></div>`, { onError })
  222. expect(code).matchSnapshot()
  223. expect(onError.mock.calls).toMatchObject([
  224. [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }],
  225. ])
  226. })
  227. })
  228. describe('v-once', () => {
  229. test('basic', async () => {
  230. const code = await compile(
  231. `<div v-once>
  232. {{ msg }}
  233. <span :class="clz" />
  234. </div>`,
  235. {
  236. bindingMetadata: {
  237. msg: BindingTypes.SETUP_REF,
  238. clz: BindingTypes.SETUP_REF,
  239. },
  240. },
  241. )
  242. expect(code).matchSnapshot()
  243. })
  244. test('as root node', async () => {
  245. const code = await compile(`<div :id="foo" v-once />`)
  246. expect(code).toMatchSnapshot()
  247. expect(code).not.contains('effect')
  248. })
  249. })
  250. describe('v-pre', () => {
  251. test('basic', async () => {
  252. const code = await compile(
  253. `<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n`,
  254. {
  255. bindingMetadata: {
  256. foo: BindingTypes.SETUP_REF,
  257. bar: BindingTypes.SETUP_REF,
  258. },
  259. },
  260. )
  261. expect(code).toMatchSnapshot()
  262. expect(code).contains(
  263. JSON.stringify('<div :id="foo"><Comp></Comp>{{ bar }}</div>'),
  264. )
  265. expect(code).not.contains('effect')
  266. })
  267. // TODO: support multiple root nodes and components
  268. test('should not affect siblings after it', async () => {
  269. const code = await compile(
  270. `<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
  271. `<div :id="foo"><Comp/>{{ bar }}</div>`,
  272. {
  273. bindingMetadata: {
  274. foo: BindingTypes.SETUP_REF,
  275. bar: BindingTypes.SETUP_REF,
  276. },
  277. },
  278. )
  279. expect(code).toMatchSnapshot()
  280. // Waiting for TODO, There should be more here.
  281. })
  282. // TODO: support multiple root nodes and components
  283. test('self-closing v-pre', async () => {
  284. const code = await compile(
  285. `<div v-pre/>\n<div :id="foo"><Comp/>{{ bar }}</div>`,
  286. )
  287. expect(code).toMatchSnapshot()
  288. expect(code).contains('<div></div><div><Comp></Comp></div>')
  289. // Waiting for TODO, There should be more here.
  290. })
  291. })
  292. describe('v-cloak', () => {
  293. test('basic', async () => {
  294. const code = await compile(`<div v-cloak>test</div>`)
  295. expect(code).toMatchSnapshot()
  296. expect(code).not.contains('v-cloak')
  297. })
  298. })
  299. })
  300. describe('expression parsing', () => {
  301. test('interpolation', async () => {
  302. const code = await compile(`{{ a + b }}`, {
  303. inline: true,
  304. bindingMetadata: {
  305. b: BindingTypes.SETUP_REF,
  306. },
  307. })
  308. expect(code).matchSnapshot()
  309. expect(code).contains('a + b.value')
  310. })
  311. test('v-bind', async () => {
  312. const code = compile(`<div :[key+1]="foo[key+1]()" />`, {
  313. inline: true,
  314. bindingMetadata: {
  315. key: BindingTypes.SETUP_REF,
  316. foo: BindingTypes.SETUP_MAYBE_REF,
  317. },
  318. })
  319. expect(code).matchSnapshot()
  320. expect(code).contains('key.value+1')
  321. expect(code).contains('_unref(foo)[key.value+1]()')
  322. })
  323. // TODO: add more test for expression parsing (v-on, v-slot, v-for)
  324. })
  325. })