compileScript.spec.ts 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. import { BindingTypes } from '@vue/compiler-core'
  2. import { compileSFCScript as compile, assertCode } from './utils'
  3. describe('SFC compile <script setup>', () => {
  4. test('should expose top level declarations', () => {
  5. const { content } = compile(`
  6. <script setup>
  7. import { x } from './x'
  8. let a = 1
  9. const b = 2
  10. function c() {}
  11. class d {}
  12. </script>
  13. `)
  14. assertCode(content)
  15. expect(content).toMatch('return { a, b, c, d, x }')
  16. })
  17. test('defineProps()', () => {
  18. const { content, bindings } = compile(`
  19. <script setup>
  20. const props = defineProps({
  21. foo: String
  22. })
  23. const bar = 1
  24. </script>
  25. `)
  26. // should generate working code
  27. assertCode(content)
  28. // should anayze bindings
  29. expect(bindings).toStrictEqual({
  30. foo: BindingTypes.PROPS,
  31. bar: BindingTypes.SETUP_CONST,
  32. props: BindingTypes.SETUP_CONST
  33. })
  34. // should remove defineOptions import and call
  35. expect(content).not.toMatch('defineProps')
  36. // should generate correct setup signature
  37. expect(content).toMatch(`setup(__props, { expose }) {`)
  38. // should assign user identifier to it
  39. expect(content).toMatch(`const props = __props`)
  40. // should include context options in default export
  41. expect(content).toMatch(`export default {
  42. props: {
  43. foo: String
  44. },`)
  45. })
  46. test('defineProps w/ external definition', () => {
  47. const { content } = compile(`
  48. <script setup>
  49. import { propsModel } from './props'
  50. const props = defineProps(propsModel)
  51. </script>
  52. `)
  53. assertCode(content)
  54. expect(content).toMatch(`export default {
  55. props: propsModel,`)
  56. })
  57. test('defineEmit() (deprecated)', () => {
  58. const { content, bindings } = compile(`
  59. <script setup>
  60. const myEmit = defineEmit(['foo', 'bar'])
  61. </script>
  62. `)
  63. assertCode(content)
  64. expect(bindings).toStrictEqual({
  65. myEmit: BindingTypes.SETUP_CONST
  66. })
  67. // should remove defineOptions import and call
  68. expect(content).not.toMatch(/defineEmits?/)
  69. // should generate correct setup signature
  70. expect(content).toMatch(`setup(__props, { expose, emit: myEmit }) {`)
  71. // should include context options in default export
  72. expect(content).toMatch(`export default {
  73. emits: ['foo', 'bar'],`)
  74. })
  75. test('defineEmits()', () => {
  76. const { content, bindings } = compile(`
  77. <script setup>
  78. const myEmit = defineEmits(['foo', 'bar'])
  79. </script>
  80. `)
  81. assertCode(content)
  82. expect(bindings).toStrictEqual({
  83. myEmit: BindingTypes.SETUP_CONST
  84. })
  85. // should remove defineOptions import and call
  86. expect(content).not.toMatch('defineEmits')
  87. // should generate correct setup signature
  88. expect(content).toMatch(`setup(__props, { expose, emit: myEmit }) {`)
  89. // should include context options in default export
  90. expect(content).toMatch(`export default {
  91. emits: ['foo', 'bar'],`)
  92. })
  93. test('defineProps/defineEmits in multi-variable decalration', () => {
  94. const { content } = compile(`
  95. <script setup>
  96. const props = defineProps(['item']),
  97. a = 1,
  98. emit = defineEmits(['a']);
  99. </script>
  100. `)
  101. assertCode(content)
  102. expect(content).toMatch(`const a = 1;`) // test correct removal
  103. expect(content).toMatch(`props: ['item'],`)
  104. expect(content).toMatch(`emits: ['a'],`)
  105. })
  106. test('defineProps/defineEmits in multi-variable decalration (full removal)', () => {
  107. const { content } = compile(`
  108. <script setup>
  109. const props = defineProps(['item']),
  110. emit = defineEmits(['a']);
  111. </script>
  112. `)
  113. assertCode(content)
  114. expect(content).toMatch(`props: ['item'],`)
  115. expect(content).toMatch(`emits: ['a'],`)
  116. })
  117. test('defineExpose()', () => {
  118. const { content } = compile(`
  119. <script setup>
  120. defineExpose({ foo: 123 })
  121. </script>
  122. `)
  123. assertCode(content)
  124. // should remove defineOptions import and call
  125. expect(content).not.toMatch('defineExpose')
  126. // should generate correct setup signature
  127. expect(content).toMatch(`setup(__props, { expose }) {`)
  128. // should replace callee
  129. expect(content).toMatch(/\bexpose\(\{ foo: 123 \}\)/)
  130. })
  131. describe('<script> and <script setup> co-usage', () => {
  132. test('script first', () => {
  133. const { content } = compile(`
  134. <script>
  135. export const n = 1
  136. </script>
  137. <script setup>
  138. import { x } from './x'
  139. x()
  140. </script>
  141. `)
  142. assertCode(content)
  143. })
  144. test('script setup first', () => {
  145. const { content } = compile(`
  146. <script setup>
  147. import { x } from './x'
  148. x()
  149. </script>
  150. <script>
  151. export const n = 1
  152. </script>
  153. `)
  154. assertCode(content)
  155. })
  156. })
  157. describe('imports', () => {
  158. test('should hoist and expose imports', () => {
  159. assertCode(
  160. compile(`<script setup>
  161. import { ref } from 'vue'
  162. import 'foo/css'
  163. </script>`).content
  164. )
  165. })
  166. test('should extract comment for import or type declarations', () => {
  167. assertCode(
  168. compile(`
  169. <script setup>
  170. import a from 'a' // comment
  171. import b from 'b'
  172. </script>
  173. `).content
  174. )
  175. })
  176. // #2740
  177. test('should allow defineProps/Emit at the start of imports', () => {
  178. assertCode(
  179. compile(`<script setup>
  180. import { ref } from 'vue'
  181. defineProps(['foo'])
  182. defineEmits(['bar'])
  183. const r = ref(0)
  184. </script>`).content
  185. )
  186. })
  187. test('dedupe between user & helper', () => {
  188. const { content } = compile(
  189. `
  190. <script setup>
  191. import { ref } from 'vue'
  192. ref: foo = 1
  193. </script>
  194. `,
  195. { refSugar: true }
  196. )
  197. assertCode(content)
  198. expect(content).toMatch(`import { ref } from 'vue'`)
  199. })
  200. test('import dedupe between <script> and <script setup>', () => {
  201. const { content } = compile(`
  202. <script>
  203. import { x } from './x'
  204. </script>
  205. <script setup>
  206. import { x } from './x'
  207. x()
  208. </script>
  209. `)
  210. assertCode(content)
  211. expect(content.indexOf(`import { x }`)).toEqual(
  212. content.lastIndexOf(`import { x }`)
  213. )
  214. })
  215. })
  216. describe('inlineTemplate mode', () => {
  217. test('should work', () => {
  218. const { content } = compile(
  219. `
  220. <script setup>
  221. import { ref } from 'vue'
  222. const count = ref(0)
  223. </script>
  224. <template>
  225. <div>{{ count }}</div>
  226. <div>static</div>
  227. </template>
  228. `,
  229. { inlineTemplate: true }
  230. )
  231. // check snapshot and make sure helper imports and
  232. // hoists are placed correctly.
  233. assertCode(content)
  234. // in inline mode, no need to call expose() since nothing is exposed
  235. // anyway!
  236. expect(content).not.toMatch(`expose()`)
  237. })
  238. test('with defineExpose()', () => {
  239. const { content } = compile(
  240. `
  241. <script setup>
  242. const count = ref(0)
  243. defineExpose({ count })
  244. </script>
  245. `,
  246. { inlineTemplate: true }
  247. )
  248. assertCode(content)
  249. expect(content).toMatch(`setup(__props, { expose })`)
  250. expect(content).toMatch(`expose({ count })`)
  251. })
  252. test('referencing scope components and directives', () => {
  253. const { content } = compile(
  254. `
  255. <script setup>
  256. import ChildComp from './Child.vue'
  257. import SomeOtherComp from './Other.vue'
  258. import vMyDir from './my-dir'
  259. </script>
  260. <template>
  261. <div v-my-dir></div>
  262. <ChildComp/>
  263. <some-other-comp/>
  264. </template>
  265. `,
  266. { inlineTemplate: true }
  267. )
  268. expect(content).toMatch('[_unref(vMyDir)]')
  269. expect(content).toMatch('_createVNode(ChildComp)')
  270. // kebab-case component support
  271. expect(content).toMatch('_createVNode(SomeOtherComp)')
  272. assertCode(content)
  273. })
  274. test('avoid unref() when necessary', () => {
  275. // function, const, component import
  276. const { content } = compile(
  277. `<script setup>
  278. import { ref } from 'vue'
  279. import Foo, { bar } from './Foo.vue'
  280. import other from './util'
  281. const count = ref(0)
  282. const constant = {}
  283. const maybe = foo()
  284. let lett = 1
  285. function fn() {}
  286. </script>
  287. <template>
  288. <Foo>{{ bar }}</Foo>
  289. <div @click="fn">{{ count }} {{ constant }} {{ maybe }} {{ lett }} {{ other }}</div>
  290. </template>
  291. `,
  292. { inlineTemplate: true }
  293. )
  294. // no need to unref vue component import
  295. expect(content).toMatch(`createVNode(Foo,`)
  296. // #2699 should unref named imports from .vue
  297. expect(content).toMatch(`unref(bar)`)
  298. // should unref other imports
  299. expect(content).toMatch(`unref(other)`)
  300. // no need to unref constant literals
  301. expect(content).not.toMatch(`unref(constant)`)
  302. // should directly use .value for known refs
  303. expect(content).toMatch(`count.value`)
  304. // should unref() on const bindings that may be refs
  305. expect(content).toMatch(`unref(maybe)`)
  306. // should unref() on let bindings
  307. expect(content).toMatch(`unref(lett)`)
  308. // no need to unref function declarations
  309. expect(content).toMatch(`{ onClick: fn }`)
  310. // no need to mark constant fns in patch flag
  311. expect(content).not.toMatch(`PROPS`)
  312. assertCode(content)
  313. })
  314. test('v-model codegen', () => {
  315. const { content } = compile(
  316. `<script setup>
  317. import { ref } from 'vue'
  318. const count = ref(0)
  319. const maybe = foo()
  320. let lett = 1
  321. </script>
  322. <template>
  323. <input v-model="count">
  324. <input v-model="maybe">
  325. <input v-model="lett">
  326. </template>
  327. `,
  328. { inlineTemplate: true }
  329. )
  330. // known const ref: set value
  331. expect(content).toMatch(`count.value = $event`)
  332. // const but maybe ref: also assign .value directly since non-ref
  333. // won't work
  334. expect(content).toMatch(`maybe.value = $event`)
  335. // let: handle both cases
  336. expect(content).toMatch(
  337. `_isRef(lett) ? lett.value = $event : lett = $event`
  338. )
  339. assertCode(content)
  340. })
  341. test('template assignment expression codegen', () => {
  342. const { content } = compile(
  343. `<script setup>
  344. import { ref } from 'vue'
  345. const count = ref(0)
  346. const maybe = foo()
  347. let lett = 1
  348. let v = ref(1)
  349. </script>
  350. <template>
  351. <div @click="count = 1"/>
  352. <div @click="maybe = count"/>
  353. <div @click="lett = count"/>
  354. <div @click="v += 1"/>
  355. <div @click="v -= 1"/>
  356. </template>
  357. `,
  358. { inlineTemplate: true }
  359. )
  360. // known const ref: set value
  361. expect(content).toMatch(`count.value = 1`)
  362. // const but maybe ref: only assign after check
  363. expect(content).toMatch(`maybe.value = count.value`)
  364. // let: handle both cases
  365. expect(content).toMatch(
  366. `_isRef(lett) ? lett.value = count.value : lett = count.value`
  367. )
  368. expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)
  369. expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)
  370. assertCode(content)
  371. })
  372. test('template update expression codegen', () => {
  373. const { content } = compile(
  374. `<script setup>
  375. import { ref } from 'vue'
  376. const count = ref(0)
  377. const maybe = foo()
  378. let lett = 1
  379. </script>
  380. <template>
  381. <div @click="count++"/>
  382. <div @click="--count"/>
  383. <div @click="maybe++"/>
  384. <div @click="--maybe"/>
  385. <div @click="lett++"/>
  386. <div @click="--lett"/>
  387. </template>
  388. `,
  389. { inlineTemplate: true }
  390. )
  391. // known const ref: set value
  392. expect(content).toMatch(`count.value++`)
  393. expect(content).toMatch(`--count.value`)
  394. // const but maybe ref (non-ref case ignored)
  395. expect(content).toMatch(`maybe.value++`)
  396. expect(content).toMatch(`--maybe.value`)
  397. // let: handle both cases
  398. expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`)
  399. expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`)
  400. assertCode(content)
  401. })
  402. test('template destructure assignment codegen', () => {
  403. const { content } = compile(
  404. `<script setup>
  405. import { ref } from 'vue'
  406. const val = {}
  407. const count = ref(0)
  408. const maybe = foo()
  409. let lett = 1
  410. </script>
  411. <template>
  412. <div @click="({ count } = val)"/>
  413. <div @click="[maybe] = val"/>
  414. <div @click="({ lett } = val)"/>
  415. </template>
  416. `,
  417. { inlineTemplate: true }
  418. )
  419. // known const ref: set value
  420. expect(content).toMatch(`({ count: count.value } = val)`)
  421. // const but maybe ref (non-ref case ignored)
  422. expect(content).toMatch(`[maybe.value] = val`)
  423. // let: assumes non-ref
  424. expect(content).toMatch(`{ lett: lett } = val`)
  425. assertCode(content)
  426. })
  427. test('ssr codegen', () => {
  428. const { content } = compile(
  429. `
  430. <script setup>
  431. import { ref } from 'vue'
  432. const count = ref(0)
  433. </script>
  434. <template>
  435. <div>{{ count }}</div>
  436. <div>static</div>
  437. </template>
  438. <style>
  439. div { color: v-bind(count) }
  440. </style>
  441. `,
  442. {
  443. inlineTemplate: true,
  444. templateOptions: {
  445. ssr: true
  446. }
  447. }
  448. )
  449. expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
  450. expect(content).toMatch(`return (_ctx, _push`)
  451. expect(content).toMatch(`ssrInterpolate`)
  452. assertCode(content)
  453. })
  454. })
  455. describe('with TypeScript', () => {
  456. test('hoist type declarations', () => {
  457. const { content } = compile(`
  458. <script setup lang="ts">
  459. export interface Foo {}
  460. type Bar = {}
  461. </script>`)
  462. assertCode(content)
  463. })
  464. test('defineProps/Emit w/ runtime options', () => {
  465. const { content } = compile(`
  466. <script setup lang="ts">
  467. const props = defineProps({ foo: String })
  468. const emit = defineEmits(['a', 'b'])
  469. </script>
  470. `)
  471. assertCode(content)
  472. expect(content).toMatch(`export default _defineComponent({
  473. props: { foo: String },
  474. emits: ['a', 'b'],
  475. setup(__props, { expose, emit }) {`)
  476. })
  477. test('defineProps w/ type', () => {
  478. const { content, bindings } = compile(`
  479. <script setup lang="ts">
  480. interface Test {}
  481. type Alias = number[]
  482. defineProps<{
  483. string: string
  484. number: number
  485. boolean: boolean
  486. object: object
  487. objectLiteral: { a: number }
  488. fn: (n: number) => void
  489. functionRef: Function
  490. objectRef: Object
  491. array: string[]
  492. arrayRef: Array<any>
  493. tuple: [number, number]
  494. set: Set<string>
  495. literal: 'foo'
  496. optional?: any
  497. recordRef: Record<string, null>
  498. interface: Test
  499. alias: Alias
  500. method(): void
  501. union: string | number
  502. literalUnion: 'foo' | 'bar'
  503. literalUnionMixed: 'foo' | 1 | boolean
  504. intersection: Test & {}
  505. }>()
  506. </script>`)
  507. assertCode(content)
  508. expect(content).toMatch(`string: { type: String, required: true }`)
  509. expect(content).toMatch(`number: { type: Number, required: true }`)
  510. expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
  511. expect(content).toMatch(`object: { type: Object, required: true }`)
  512. expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
  513. expect(content).toMatch(`fn: { type: Function, required: true }`)
  514. expect(content).toMatch(`functionRef: { type: Function, required: true }`)
  515. expect(content).toMatch(`objectRef: { type: Object, required: true }`)
  516. expect(content).toMatch(`array: { type: Array, required: true }`)
  517. expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
  518. expect(content).toMatch(`tuple: { type: Array, required: true }`)
  519. expect(content).toMatch(`set: { type: Set, required: true }`)
  520. expect(content).toMatch(`literal: { type: String, required: true }`)
  521. expect(content).toMatch(`optional: { type: null, required: false }`)
  522. expect(content).toMatch(`recordRef: { type: Object, required: true }`)
  523. expect(content).toMatch(`interface: { type: Object, required: true }`)
  524. expect(content).toMatch(`alias: { type: Array, required: true }`)
  525. expect(content).toMatch(`method: { type: Function, required: true }`)
  526. expect(content).toMatch(
  527. `union: { type: [String, Number], required: true }`
  528. )
  529. expect(content).toMatch(
  530. `literalUnion: { type: [String, String], required: true }`
  531. )
  532. expect(content).toMatch(
  533. `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
  534. )
  535. expect(content).toMatch(`intersection: { type: Object, required: true }`)
  536. expect(bindings).toStrictEqual({
  537. string: BindingTypes.PROPS,
  538. number: BindingTypes.PROPS,
  539. boolean: BindingTypes.PROPS,
  540. object: BindingTypes.PROPS,
  541. objectLiteral: BindingTypes.PROPS,
  542. fn: BindingTypes.PROPS,
  543. functionRef: BindingTypes.PROPS,
  544. objectRef: BindingTypes.PROPS,
  545. array: BindingTypes.PROPS,
  546. arrayRef: BindingTypes.PROPS,
  547. tuple: BindingTypes.PROPS,
  548. set: BindingTypes.PROPS,
  549. literal: BindingTypes.PROPS,
  550. optional: BindingTypes.PROPS,
  551. recordRef: BindingTypes.PROPS,
  552. interface: BindingTypes.PROPS,
  553. alias: BindingTypes.PROPS,
  554. method: BindingTypes.PROPS,
  555. union: BindingTypes.PROPS,
  556. literalUnion: BindingTypes.PROPS,
  557. literalUnionMixed: BindingTypes.PROPS,
  558. intersection: BindingTypes.PROPS
  559. })
  560. })
  561. test('defineProps w/ interface', () => {
  562. const { content, bindings } = compile(`
  563. <script setup lang="ts">
  564. interface Props { x?: number }
  565. defineProps<Props>()
  566. </script>
  567. `)
  568. assertCode(content)
  569. expect(content).toMatch(`x: { type: Number, required: false }`)
  570. expect(bindings).toStrictEqual({
  571. x: BindingTypes.PROPS
  572. })
  573. })
  574. test('defineProps w/ exported interface', () => {
  575. const { content, bindings } = compile(`
  576. <script setup lang="ts">
  577. export interface Props { x?: number }
  578. defineProps<Props>()
  579. </script>
  580. `)
  581. assertCode(content)
  582. expect(content).toMatch(`x: { type: Number, required: false }`)
  583. expect(bindings).toStrictEqual({
  584. x: BindingTypes.PROPS
  585. })
  586. })
  587. test('defineProps w/ type alias', () => {
  588. const { content, bindings } = compile(`
  589. <script setup lang="ts">
  590. type Props = { x?: number }
  591. defineProps<Props>()
  592. </script>
  593. `)
  594. assertCode(content)
  595. expect(content).toMatch(`x: { type: Number, required: false }`)
  596. expect(bindings).toStrictEqual({
  597. x: BindingTypes.PROPS
  598. })
  599. })
  600. test('defineProps w/ exported type alias', () => {
  601. const { content, bindings } = compile(`
  602. <script setup lang="ts">
  603. export type Props = { x?: number }
  604. defineProps<Props>()
  605. </script>
  606. `)
  607. assertCode(content)
  608. expect(content).toMatch(`x: { type: Number, required: false }`)
  609. expect(bindings).toStrictEqual({
  610. x: BindingTypes.PROPS
  611. })
  612. })
  613. test('withDefaults (static)', () => {
  614. const { content, bindings } = compile(`
  615. <script setup lang="ts">
  616. const props = withDefaults(defineProps<{
  617. foo?: string
  618. bar?: number
  619. }>(), {
  620. foo: 'hi'
  621. })
  622. </script>
  623. `)
  624. assertCode(content)
  625. expect(content).toMatch(
  626. `foo: { type: String, required: false, default: 'hi' }`
  627. )
  628. expect(content).toMatch(`bar: { type: Number, required: false }`)
  629. expect(content).toMatch(`const props = __props`)
  630. expect(bindings).toStrictEqual({
  631. foo: BindingTypes.PROPS,
  632. bar: BindingTypes.PROPS,
  633. props: BindingTypes.SETUP_CONST
  634. })
  635. })
  636. test('withDefaults (dynamic)', () => {
  637. const { content } = compile(`
  638. <script setup lang="ts">
  639. import { defaults } from './foo'
  640. const props = withDefaults(defineProps<{
  641. foo?: string
  642. bar?: number
  643. }>(), { ...defaults })
  644. </script>
  645. `)
  646. assertCode(content)
  647. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  648. expect(content).toMatch(
  649. `
  650. _mergeDefaults({
  651. foo: { type: String, required: false },
  652. bar: { type: Number, required: false }
  653. }, { ...defaults })`.trim()
  654. )
  655. })
  656. test('defineEmits w/ type', () => {
  657. const { content } = compile(`
  658. <script setup lang="ts">
  659. const emit = defineEmits<(e: 'foo' | 'bar') => void>()
  660. </script>
  661. `)
  662. assertCode(content)
  663. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  664. expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
  665. })
  666. test('defineEmits w/ type (union)', () => {
  667. const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
  668. expect(() =>
  669. compile(`
  670. <script setup lang="ts">
  671. const emit = defineEmits<${type}>()
  672. </script>
  673. `)
  674. ).toThrow()
  675. })
  676. test('defineEmits w/ type (type literal w/ call signatures)', () => {
  677. const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
  678. const { content } = compile(`
  679. <script setup lang="ts">
  680. const emit = defineEmits<${type}>()
  681. </script>
  682. `)
  683. assertCode(content)
  684. expect(content).toMatch(`emit: (${type}),`)
  685. expect(content).toMatch(
  686. `emits: ["foo", "bar", "baz"] as unknown as undefined`
  687. )
  688. })
  689. test('defineEmits w/ type (interface)', () => {
  690. const { content } = compile(`
  691. <script setup lang="ts">
  692. interface Emits { (e: 'foo' | 'bar'): void }
  693. const emit = defineEmits<Emits>()
  694. </script>
  695. `)
  696. assertCode(content)
  697. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  698. expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
  699. })
  700. test('defineEmits w/ type (exported interface)', () => {
  701. const { content } = compile(`
  702. <script setup lang="ts">
  703. export interface Emits { (e: 'foo' | 'bar'): void }
  704. const emit = defineEmits<Emits>()
  705. </script>
  706. `)
  707. assertCode(content)
  708. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  709. expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
  710. })
  711. test('defineEmits w/ type (type alias)', () => {
  712. const { content } = compile(`
  713. <script setup lang="ts">
  714. type Emits = { (e: 'foo' | 'bar'): void }
  715. const emit = defineEmits<Emits>()
  716. </script>
  717. `)
  718. assertCode(content)
  719. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  720. expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
  721. })
  722. test('defineEmits w/ type (exported type alias)', () => {
  723. const { content } = compile(`
  724. <script setup lang="ts">
  725. export type Emits = { (e: 'foo' | 'bar'): void }
  726. const emit = defineEmits<Emits>()
  727. </script>
  728. `)
  729. assertCode(content)
  730. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  731. expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
  732. })
  733. test('defineEmits w/ type (referenced function type)', () => {
  734. const { content } = compile(`
  735. <script setup lang="ts">
  736. type Emits = (e: 'foo' | 'bar') => void
  737. const emit = defineEmits<Emits>()
  738. </script>
  739. `)
  740. assertCode(content)
  741. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  742. expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
  743. })
  744. test('defineEmits w/ type (referenced exported function type)', () => {
  745. const { content } = compile(`
  746. <script setup lang="ts">
  747. export type Emits = (e: 'foo' | 'bar') => void
  748. const emit = defineEmits<Emits>()
  749. </script>
  750. `)
  751. assertCode(content)
  752. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  753. expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
  754. })
  755. test('runtime Enum', () => {
  756. const { content, bindings } = compile(
  757. `<script setup lang="ts">
  758. enum Foo { A = 123 }
  759. </script>`
  760. )
  761. assertCode(content)
  762. expect(bindings).toStrictEqual({
  763. Foo: BindingTypes.SETUP_CONST
  764. })
  765. })
  766. })
  767. describe('async/await detection', () => {
  768. function assertAwaitDetection(
  769. code: string,
  770. expected: string | ((content: string) => boolean),
  771. shouldAsync = true
  772. ) {
  773. const { content } = compile(`<script setup>${code}</script>`, {
  774. refSugar: true
  775. })
  776. if (shouldAsync) {
  777. expect(content).toMatch(`let __temp, __restore`)
  778. }
  779. expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
  780. if (typeof expected === 'string') {
  781. expect(content).toMatch(expected)
  782. } else {
  783. expect(expected(content)).toBe(true)
  784. }
  785. }
  786. test('expression statement', () => {
  787. assertAwaitDetection(
  788. `await foo`,
  789. `;(([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore())`
  790. )
  791. })
  792. test('variable', () => {
  793. assertAwaitDetection(
  794. `const a = 1 + (await foo)`,
  795. `1 + ((([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore(),__temp))`
  796. )
  797. })
  798. test('ref', () => {
  799. assertAwaitDetection(
  800. `ref: a = 1 + (await foo)`,
  801. `1 + ((([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore(),__temp))`
  802. )
  803. })
  804. test('nested statements', () => {
  805. assertAwaitDetection(`if (ok) { await foo } else { await bar }`, code => {
  806. return (
  807. code.includes(
  808. `;(([__temp,__restore]=_withAsyncContext(()=>(foo))),__temp=await __temp,__restore())`
  809. ) &&
  810. code.includes(
  811. `;(([__temp,__restore]=_withAsyncContext(()=>(bar))),__temp=await __temp,__restore())`
  812. )
  813. )
  814. })
  815. })
  816. test('should ignore await inside functions', () => {
  817. // function declaration
  818. assertAwaitDetection(
  819. `async function foo() { await bar }`,
  820. `await bar`,
  821. false
  822. )
  823. // function expression
  824. assertAwaitDetection(
  825. `const foo = async () => { await bar }`,
  826. `await bar`,
  827. false
  828. )
  829. // object method
  830. assertAwaitDetection(
  831. `const obj = { async method() { await bar }}`,
  832. `await bar`,
  833. false
  834. )
  835. // class method
  836. assertAwaitDetection(
  837. `const cls = class Foo { async method() { await bar }}`,
  838. `await bar`,
  839. false
  840. )
  841. })
  842. })
  843. describe('errors', () => {
  844. test('<script> and <script setup> must have same lang', () => {
  845. expect(() =>
  846. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`)
  847. ).toThrow(`<script> and <script setup> must have the same language type`)
  848. })
  849. const moduleErrorMsg = `cannot contain ES module exports`
  850. test('non-type named exports', () => {
  851. expect(() =>
  852. compile(`<script setup>
  853. export const a = 1
  854. </script>`)
  855. ).toThrow(moduleErrorMsg)
  856. expect(() =>
  857. compile(`<script setup>
  858. export * from './foo'
  859. </script>`)
  860. ).toThrow(moduleErrorMsg)
  861. expect(() =>
  862. compile(`<script setup>
  863. const bar = 1
  864. export { bar as default }
  865. </script>`)
  866. ).toThrow(moduleErrorMsg)
  867. })
  868. test('defineProps/Emit() w/ both type and non-type args', () => {
  869. expect(() => {
  870. compile(`<script setup lang="ts">
  871. defineProps<{}>({})
  872. </script>`)
  873. }).toThrow(`cannot accept both type and non-type arguments`)
  874. expect(() => {
  875. compile(`<script setup lang="ts">
  876. defineEmits<{}>({})
  877. </script>`)
  878. }).toThrow(`cannot accept both type and non-type arguments`)
  879. })
  880. test('defineProps/Emit() referencing local var', () => {
  881. expect(() =>
  882. compile(`<script setup>
  883. const bar = 1
  884. defineProps({
  885. foo: {
  886. default: () => bar
  887. }
  888. })
  889. </script>`)
  890. ).toThrow(`cannot reference locally declared variables`)
  891. expect(() =>
  892. compile(`<script setup>
  893. const bar = 'hello'
  894. defineEmits([bar])
  895. </script>`)
  896. ).toThrow(`cannot reference locally declared variables`)
  897. })
  898. test('should allow defineProps/Emit() referencing scope var', () => {
  899. assertCode(
  900. compile(`<script setup>
  901. const bar = 1
  902. defineProps({
  903. foo: {
  904. default: bar => bar + 1
  905. }
  906. })
  907. defineEmits({
  908. foo: bar => bar > 1
  909. })
  910. </script>`).content
  911. )
  912. })
  913. test('should allow defineProps/Emit() referencing imported binding', () => {
  914. assertCode(
  915. compile(`<script setup>
  916. import { bar } from './bar'
  917. defineProps({
  918. foo: {
  919. default: () => bar
  920. }
  921. })
  922. defineEmits({
  923. foo: () => bar > 1
  924. })
  925. </script>`).content
  926. )
  927. })
  928. })
  929. })
  930. describe('SFC analyze <script> bindings', () => {
  931. it('can parse decorators syntax in typescript block', () => {
  932. const { scriptAst } = compile(`
  933. <script lang="ts">
  934. import { Options, Vue } from 'vue-class-component';
  935. @Options({
  936. components: {
  937. HelloWorld,
  938. },
  939. props: ['foo', 'bar']
  940. })
  941. export default class Home extends Vue {}
  942. </script>
  943. `)
  944. expect(scriptAst).toBeDefined()
  945. })
  946. it('recognizes props array declaration', () => {
  947. const { bindings } = compile(`
  948. <script>
  949. export default {
  950. props: ['foo', 'bar']
  951. }
  952. </script>
  953. `)
  954. expect(bindings).toStrictEqual({
  955. foo: BindingTypes.PROPS,
  956. bar: BindingTypes.PROPS
  957. })
  958. expect(bindings!.__isScriptSetup).toBe(false)
  959. })
  960. it('recognizes props object declaration', () => {
  961. const { bindings } = compile(`
  962. <script>
  963. export default {
  964. props: {
  965. foo: String,
  966. bar: {
  967. type: String,
  968. },
  969. baz: null,
  970. qux: [String, Number]
  971. }
  972. }
  973. </script>
  974. `)
  975. expect(bindings).toStrictEqual({
  976. foo: BindingTypes.PROPS,
  977. bar: BindingTypes.PROPS,
  978. baz: BindingTypes.PROPS,
  979. qux: BindingTypes.PROPS
  980. })
  981. expect(bindings!.__isScriptSetup).toBe(false)
  982. })
  983. it('recognizes setup return', () => {
  984. const { bindings } = compile(`
  985. <script>
  986. const bar = 2
  987. export default {
  988. setup() {
  989. return {
  990. foo: 1,
  991. bar
  992. }
  993. }
  994. }
  995. </script>
  996. `)
  997. expect(bindings).toStrictEqual({
  998. foo: BindingTypes.SETUP_MAYBE_REF,
  999. bar: BindingTypes.SETUP_MAYBE_REF
  1000. })
  1001. expect(bindings!.__isScriptSetup).toBe(false)
  1002. })
  1003. it('recognizes async setup return', () => {
  1004. const { bindings } = compile(`
  1005. <script>
  1006. const bar = 2
  1007. export default {
  1008. async setup() {
  1009. return {
  1010. foo: 1,
  1011. bar
  1012. }
  1013. }
  1014. }
  1015. </script>
  1016. `)
  1017. expect(bindings).toStrictEqual({
  1018. foo: BindingTypes.SETUP_MAYBE_REF,
  1019. bar: BindingTypes.SETUP_MAYBE_REF
  1020. })
  1021. expect(bindings!.__isScriptSetup).toBe(false)
  1022. })
  1023. it('recognizes data return', () => {
  1024. const { bindings } = compile(`
  1025. <script>
  1026. const bar = 2
  1027. export default {
  1028. data() {
  1029. return {
  1030. foo: null,
  1031. bar
  1032. }
  1033. }
  1034. }
  1035. </script>
  1036. `)
  1037. expect(bindings).toStrictEqual({
  1038. foo: BindingTypes.DATA,
  1039. bar: BindingTypes.DATA
  1040. })
  1041. })
  1042. it('recognizes methods', () => {
  1043. const { bindings } = compile(`
  1044. <script>
  1045. export default {
  1046. methods: {
  1047. foo() {}
  1048. }
  1049. }
  1050. </script>
  1051. `)
  1052. expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })
  1053. })
  1054. it('recognizes computeds', () => {
  1055. const { bindings } = compile(`
  1056. <script>
  1057. export default {
  1058. computed: {
  1059. foo() {},
  1060. bar: {
  1061. get() {},
  1062. set() {},
  1063. }
  1064. }
  1065. }
  1066. </script>
  1067. `)
  1068. expect(bindings).toStrictEqual({
  1069. foo: BindingTypes.OPTIONS,
  1070. bar: BindingTypes.OPTIONS
  1071. })
  1072. })
  1073. it('recognizes injections array declaration', () => {
  1074. const { bindings } = compile(`
  1075. <script>
  1076. export default {
  1077. inject: ['foo', 'bar']
  1078. }
  1079. </script>
  1080. `)
  1081. expect(bindings).toStrictEqual({
  1082. foo: BindingTypes.OPTIONS,
  1083. bar: BindingTypes.OPTIONS
  1084. })
  1085. })
  1086. it('recognizes injections object declaration', () => {
  1087. const { bindings } = compile(`
  1088. <script>
  1089. export default {
  1090. inject: {
  1091. foo: {},
  1092. bar: {},
  1093. }
  1094. }
  1095. </script>
  1096. `)
  1097. expect(bindings).toStrictEqual({
  1098. foo: BindingTypes.OPTIONS,
  1099. bar: BindingTypes.OPTIONS
  1100. })
  1101. })
  1102. it('works for mixed bindings', () => {
  1103. const { bindings } = compile(`
  1104. <script>
  1105. export default {
  1106. inject: ['foo'],
  1107. props: {
  1108. bar: String,
  1109. },
  1110. setup() {
  1111. return {
  1112. baz: null,
  1113. }
  1114. },
  1115. data() {
  1116. return {
  1117. qux: null
  1118. }
  1119. },
  1120. methods: {
  1121. quux() {}
  1122. },
  1123. computed: {
  1124. quuz() {}
  1125. }
  1126. }
  1127. </script>
  1128. `)
  1129. expect(bindings).toStrictEqual({
  1130. foo: BindingTypes.OPTIONS,
  1131. bar: BindingTypes.PROPS,
  1132. baz: BindingTypes.SETUP_MAYBE_REF,
  1133. qux: BindingTypes.DATA,
  1134. quux: BindingTypes.OPTIONS,
  1135. quuz: BindingTypes.OPTIONS
  1136. })
  1137. })
  1138. it('works for script setup', () => {
  1139. const { bindings } = compile(`
  1140. <script setup>
  1141. import { ref as r } from 'vue'
  1142. defineProps({
  1143. foo: String
  1144. })
  1145. const a = r(1)
  1146. let b = 2
  1147. const c = 3
  1148. const { d } = someFoo()
  1149. let { e } = someBar()
  1150. </script>
  1151. `)
  1152. expect(bindings).toStrictEqual({
  1153. r: BindingTypes.SETUP_CONST,
  1154. a: BindingTypes.SETUP_REF,
  1155. b: BindingTypes.SETUP_LET,
  1156. c: BindingTypes.SETUP_CONST,
  1157. d: BindingTypes.SETUP_MAYBE_REF,
  1158. e: BindingTypes.SETUP_LET,
  1159. foo: BindingTypes.PROPS
  1160. })
  1161. })
  1162. })