compileScript.spec.ts 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694
  1. import { BindingTypes } from '@vue/compiler-core'
  2. import { compileSFCScript as compile, assertCode, mockId } from './utils'
  3. describe('SFC compile <script setup>', () => {
  4. test('should expose top level declarations', () => {
  5. const { content, bindings } = 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. <script>
  14. import { xx } from './x'
  15. let aa = 1
  16. const bb = 2
  17. function cc() {}
  18. class dd {}
  19. </script>
  20. `)
  21. expect(content).toMatch('return { aa, bb, cc, dd, a, b, c, d, xx, x }')
  22. expect(bindings).toStrictEqual({
  23. x: BindingTypes.SETUP_MAYBE_REF,
  24. a: BindingTypes.SETUP_LET,
  25. b: BindingTypes.SETUP_CONST,
  26. c: BindingTypes.SETUP_CONST,
  27. d: BindingTypes.SETUP_CONST,
  28. xx: BindingTypes.SETUP_MAYBE_REF,
  29. aa: BindingTypes.SETUP_LET,
  30. bb: BindingTypes.SETUP_CONST,
  31. cc: BindingTypes.SETUP_CONST,
  32. dd: BindingTypes.SETUP_CONST
  33. })
  34. assertCode(content)
  35. })
  36. test('binding analysis for destructure', () => {
  37. const { content, bindings } = compile(`
  38. <script setup>
  39. const { foo, b: bar, ['x' + 'y']: baz, x: { y, zz: { z }}} = {}
  40. </script>
  41. `)
  42. expect(content).toMatch('return { foo, bar, baz, y, z }')
  43. expect(bindings).toStrictEqual({
  44. foo: BindingTypes.SETUP_MAYBE_REF,
  45. bar: BindingTypes.SETUP_MAYBE_REF,
  46. baz: BindingTypes.SETUP_MAYBE_REF,
  47. y: BindingTypes.SETUP_MAYBE_REF,
  48. z: BindingTypes.SETUP_MAYBE_REF
  49. })
  50. assertCode(content)
  51. })
  52. test('defineProps()', () => {
  53. const { content, bindings } = compile(`
  54. <script setup>
  55. const props = defineProps({
  56. foo: String
  57. })
  58. const bar = 1
  59. </script>
  60. `)
  61. // should generate working code
  62. assertCode(content)
  63. // should analyze bindings
  64. expect(bindings).toStrictEqual({
  65. foo: BindingTypes.PROPS,
  66. bar: BindingTypes.SETUP_CONST,
  67. props: BindingTypes.SETUP_REACTIVE_CONST
  68. })
  69. // should remove defineOptions import and call
  70. expect(content).not.toMatch('defineProps')
  71. // should generate correct setup signature
  72. expect(content).toMatch(`setup(__props, { expose }) {`)
  73. // should assign user identifier to it
  74. expect(content).toMatch(`const props = __props`)
  75. // should include context options in default export
  76. expect(content).toMatch(`export default {
  77. props: {
  78. foo: String
  79. },`)
  80. })
  81. test('defineProps w/ external definition', () => {
  82. const { content } = compile(`
  83. <script setup>
  84. import { propsModel } from './props'
  85. const props = defineProps(propsModel)
  86. </script>
  87. `)
  88. assertCode(content)
  89. expect(content).toMatch(`export default {
  90. props: propsModel,`)
  91. })
  92. // #4764
  93. test('defineProps w/ leading code', () => {
  94. const { content } = compile(`
  95. <script setup>import { x } from './x'
  96. const props = defineProps({})
  97. </script>
  98. `)
  99. // props declaration should be inside setup, not moved along with the import
  100. expect(content).not.toMatch(`const props = __props\nimport`)
  101. assertCode(content)
  102. })
  103. test('defineEmits()', () => {
  104. const { content, bindings } = compile(`
  105. <script setup>
  106. const myEmit = defineEmits(['foo', 'bar'])
  107. </script>
  108. `)
  109. assertCode(content)
  110. expect(bindings).toStrictEqual({
  111. myEmit: BindingTypes.SETUP_CONST
  112. })
  113. // should remove defineOptions import and call
  114. expect(content).not.toMatch('defineEmits')
  115. // should generate correct setup signature
  116. expect(content).toMatch(`setup(__props, { expose, emit: myEmit }) {`)
  117. // should include context options in default export
  118. expect(content).toMatch(`export default {
  119. emits: ['foo', 'bar'],`)
  120. })
  121. test('defineProps/defineEmits in multi-variable declaration', () => {
  122. const { content } = compile(`
  123. <script setup>
  124. const props = defineProps(['item']),
  125. a = 1,
  126. emit = defineEmits(['a']);
  127. </script>
  128. `)
  129. assertCode(content)
  130. expect(content).toMatch(`const a = 1;`) // test correct removal
  131. expect(content).toMatch(`props: ['item'],`)
  132. expect(content).toMatch(`emits: ['a'],`)
  133. })
  134. test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
  135. const { content } = compile(`
  136. <script setup>
  137. const props = defineProps(['item']),
  138. emit = defineEmits(['a']);
  139. </script>
  140. `)
  141. assertCode(content)
  142. expect(content).toMatch(`props: ['item'],`)
  143. expect(content).toMatch(`emits: ['a'],`)
  144. })
  145. test('defineExpose()', () => {
  146. const { content } = compile(`
  147. <script setup>
  148. defineExpose({ foo: 123 })
  149. </script>
  150. `)
  151. assertCode(content)
  152. // should remove defineOptions import and call
  153. expect(content).not.toMatch('defineExpose')
  154. // should generate correct setup signature
  155. expect(content).toMatch(`setup(__props, { expose }) {`)
  156. // should replace callee
  157. expect(content).toMatch(/\bexpose\(\{ foo: 123 \}\)/)
  158. })
  159. test('<script> after <script setup> the script content not end with `\\n`', () => {
  160. const { content } = compile(`
  161. <script setup>
  162. import { x } from './x'
  163. </script>
  164. <script>const n = 1</script>
  165. `)
  166. assertCode(content)
  167. })
  168. describe('<script> and <script setup> co-usage', () => {
  169. test('script first', () => {
  170. const { content } = compile(`
  171. <script>
  172. export const n = 1
  173. export default {}
  174. </script>
  175. <script setup>
  176. import { x } from './x'
  177. x()
  178. </script>
  179. `)
  180. assertCode(content)
  181. })
  182. test('script setup first', () => {
  183. const { content } = compile(`
  184. <script setup>
  185. import { x } from './x'
  186. x()
  187. </script>
  188. <script>
  189. export const n = 1
  190. export default {}
  191. </script>
  192. `)
  193. assertCode(content)
  194. })
  195. test('script setup first, named default export', () => {
  196. const { content } = compile(`
  197. <script setup>
  198. import { x } from './x'
  199. x()
  200. </script>
  201. <script>
  202. export const n = 1
  203. const def = {}
  204. export { def as default }
  205. </script>
  206. `)
  207. assertCode(content)
  208. })
  209. // #4395
  210. test('script setup first, lang="ts", script block content export default', () => {
  211. const { content } = compile(`
  212. <script setup lang="ts">
  213. import { x } from './x'
  214. x()
  215. </script>
  216. <script lang="ts">
  217. export default {
  218. name: "test"
  219. }
  220. </script>
  221. `)
  222. // ensure __default__ is declared before used
  223. expect(content).toMatch(/const __default__[\S\s]*\.\.\.__default__/m)
  224. assertCode(content)
  225. })
  226. describe('spaces in ExportDefaultDeclaration node', () => {
  227. // #4371
  228. test('with many spaces and newline', () => {
  229. // #4371
  230. const { content } = compile(`
  231. <script>
  232. export const n = 1
  233. export default
  234. {
  235. some:'option'
  236. }
  237. </script>
  238. <script setup>
  239. import { x } from './x'
  240. x()
  241. </script>
  242. `)
  243. assertCode(content)
  244. })
  245. test('with minimal spaces', () => {
  246. const { content } = compile(`
  247. <script>
  248. export const n = 1
  249. export default{
  250. some:'option'
  251. }
  252. </script>
  253. <script setup>
  254. import { x } from './x'
  255. x()
  256. </script>
  257. `)
  258. assertCode(content)
  259. })
  260. })
  261. })
  262. describe('imports', () => {
  263. test('should hoist and expose imports', () => {
  264. assertCode(
  265. compile(`<script setup>
  266. import { ref } from 'vue'
  267. import 'foo/css'
  268. </script>`).content
  269. )
  270. })
  271. test('should extract comment for import or type declarations', () => {
  272. assertCode(
  273. compile(`
  274. <script setup>
  275. import a from 'a' // comment
  276. import b from 'b'
  277. </script>
  278. `).content
  279. )
  280. })
  281. // #2740
  282. test('should allow defineProps/Emit at the start of imports', () => {
  283. assertCode(
  284. compile(`<script setup>
  285. import { ref } from 'vue'
  286. defineProps(['foo'])
  287. defineEmits(['bar'])
  288. const r = ref(0)
  289. </script>`).content
  290. )
  291. })
  292. test('dedupe between user & helper', () => {
  293. const { content } = compile(
  294. `
  295. <script setup>
  296. import { ref } from 'vue'
  297. let foo = $ref(1)
  298. </script>
  299. `,
  300. { reactivityTransform: true }
  301. )
  302. assertCode(content)
  303. expect(content).toMatch(`import { ref } from 'vue'`)
  304. })
  305. test('import dedupe between <script> and <script setup>', () => {
  306. const { content } = compile(`
  307. <script>
  308. import { x } from './x'
  309. </script>
  310. <script setup>
  311. import { x } from './x'
  312. x()
  313. </script>
  314. `)
  315. assertCode(content)
  316. expect(content.indexOf(`import { x }`)).toEqual(
  317. content.lastIndexOf(`import { x }`)
  318. )
  319. })
  320. })
  321. // in dev mode, declared bindings are returned as an object from setup()
  322. // when using TS, users may import types which should not be returned as
  323. // values, so we need to check import usage in the template to determine
  324. // what to be returned.
  325. describe('dev mode import usage check', () => {
  326. test('components', () => {
  327. const { content } = compile(`
  328. <script setup lang="ts">
  329. import { FooBar, FooBaz, FooQux, foo } from './x'
  330. const fooBar: FooBar = 1
  331. </script>
  332. <template>
  333. <FooBaz></FooBaz>
  334. <foo-qux/>
  335. <foo/>
  336. FooBar
  337. </template>
  338. `)
  339. // FooBar: should not be matched by plain text or incorrect case
  340. // FooBaz: used as PascalCase component
  341. // FooQux: used as kebab-case component
  342. // foo: lowercase component
  343. expect(content).toMatch(`return { fooBar, FooBaz, FooQux, foo }`)
  344. assertCode(content)
  345. })
  346. test('directive', () => {
  347. const { content } = compile(`
  348. <script setup lang="ts">
  349. import { vMyDir } from './x'
  350. </script>
  351. <template>
  352. <div v-my-dir></div>
  353. </template>
  354. `)
  355. expect(content).toMatch(`return { vMyDir }`)
  356. assertCode(content)
  357. })
  358. // https://github.com/vuejs/core/issues/4599
  359. test('attribute expressions', () => {
  360. const { content } = compile(`
  361. <script setup lang="ts">
  362. import { bar, baz } from './x'
  363. const cond = true
  364. </script>
  365. <template>
  366. <div :class="[cond ? '' : bar(), 'default']" :style="baz"></div>
  367. </template>
  368. `)
  369. expect(content).toMatch(`return { cond, bar, baz }`)
  370. assertCode(content)
  371. })
  372. test('vue interpolations', () => {
  373. const { content } = compile(`
  374. <script setup lang="ts">
  375. import { x, y, z, x$y } from './x'
  376. </script>
  377. <template>
  378. <div :id="z + 'y'">{{ x }} {{ yy }} {{ x$y }}</div>
  379. </template>
  380. `)
  381. // x: used in interpolation
  382. // y: should not be matched by {{ yy }} or 'y' in binding exps
  383. // x$y: #4274 should escape special chars when creating Regex
  384. expect(content).toMatch(`return { x, z, x$y }`)
  385. assertCode(content)
  386. })
  387. // #4340 interpolations in template strings
  388. test('js template string interpolations', () => {
  389. const { content } = compile(`
  390. <script setup lang="ts">
  391. import { VAR, VAR2, VAR3 } from './x'
  392. </script>
  393. <template>
  394. {{ \`\${VAR}VAR2\${VAR3}\` }}
  395. </template>
  396. `)
  397. // VAR2 should not be matched
  398. expect(content).toMatch(`return { VAR, VAR3 }`)
  399. assertCode(content)
  400. })
  401. // edge case: last tag in template
  402. test('last tag', () => {
  403. const { content } = compile(`
  404. <script setup lang="ts">
  405. import { FooBaz, Last } from './x'
  406. </script>
  407. <template>
  408. <FooBaz></FooBaz>
  409. <Last/>
  410. </template>
  411. `)
  412. expect(content).toMatch(`return { FooBaz, Last }`)
  413. assertCode(content)
  414. })
  415. test('TS annotations', () => {
  416. const { content } = compile(`
  417. <script setup lang="ts">
  418. import { Foo, Bar, Baz } from './x'
  419. const a = 1
  420. function b() {}
  421. </script>
  422. <template>
  423. {{ a as Foo }}
  424. {{ b<Bar>() }}
  425. {{ Baz }}
  426. </template>
  427. `)
  428. expect(content).toMatch(`return { a, b, Baz }`)
  429. assertCode(content)
  430. })
  431. })
  432. describe('inlineTemplate mode', () => {
  433. test('should work', () => {
  434. const { content } = compile(
  435. `
  436. <script setup>
  437. import { ref } from 'vue'
  438. const count = ref(0)
  439. </script>
  440. <template>
  441. <div>{{ count }}</div>
  442. <div>static</div>
  443. </template>
  444. `,
  445. { inlineTemplate: true }
  446. )
  447. // check snapshot and make sure helper imports and
  448. // hoists are placed correctly.
  449. assertCode(content)
  450. // in inline mode, no need to call expose() since nothing is exposed
  451. // anyway!
  452. expect(content).not.toMatch(`expose()`)
  453. })
  454. test('with defineExpose()', () => {
  455. const { content } = compile(
  456. `
  457. <script setup>
  458. const count = ref(0)
  459. defineExpose({ count })
  460. </script>
  461. `,
  462. { inlineTemplate: true }
  463. )
  464. assertCode(content)
  465. expect(content).toMatch(`setup(__props, { expose })`)
  466. expect(content).toMatch(`expose({ count })`)
  467. })
  468. test('referencing scope components and directives', () => {
  469. const { content } = compile(
  470. `
  471. <script setup>
  472. import ChildComp from './Child.vue'
  473. import SomeOtherComp from './Other.vue'
  474. import vMyDir from './my-dir'
  475. </script>
  476. <template>
  477. <div v-my-dir></div>
  478. <ChildComp/>
  479. <some-other-comp/>
  480. </template>
  481. `,
  482. { inlineTemplate: true }
  483. )
  484. expect(content).toMatch('[_unref(vMyDir)]')
  485. expect(content).toMatch('_createVNode(ChildComp)')
  486. // kebab-case component support
  487. expect(content).toMatch('_createVNode(SomeOtherComp)')
  488. assertCode(content)
  489. })
  490. test('avoid unref() when necessary', () => {
  491. // function, const, component import
  492. const { content } = compile(
  493. `<script setup>
  494. import { ref } from 'vue'
  495. import Foo, { bar } from './Foo.vue'
  496. import other from './util'
  497. import * as tree from './tree'
  498. const count = ref(0)
  499. const constant = {}
  500. const maybe = foo()
  501. let lett = 1
  502. function fn() {}
  503. </script>
  504. <template>
  505. <Foo>{{ bar }}</Foo>
  506. <div @click="fn">{{ count }} {{ constant }} {{ maybe }} {{ lett }} {{ other }}</div>
  507. {{ tree.foo() }}
  508. </template>
  509. `,
  510. { inlineTemplate: true }
  511. )
  512. // no need to unref vue component import
  513. expect(content).toMatch(`createVNode(Foo,`)
  514. // #2699 should unref named imports from .vue
  515. expect(content).toMatch(`unref(bar)`)
  516. // should unref other imports
  517. expect(content).toMatch(`unref(other)`)
  518. // no need to unref constant literals
  519. expect(content).not.toMatch(`unref(constant)`)
  520. // should directly use .value for known refs
  521. expect(content).toMatch(`count.value`)
  522. // should unref() on const bindings that may be refs
  523. expect(content).toMatch(`unref(maybe)`)
  524. // should unref() on let bindings
  525. expect(content).toMatch(`unref(lett)`)
  526. // no need to unref namespace import (this also preserves tree-shaking)
  527. expect(content).toMatch(`tree.foo()`)
  528. // no need to unref function declarations
  529. expect(content).toMatch(`{ onClick: fn }`)
  530. // no need to mark constant fns in patch flag
  531. expect(content).not.toMatch(`PROPS`)
  532. assertCode(content)
  533. })
  534. test('v-model codegen', () => {
  535. const { content } = compile(
  536. `<script setup>
  537. import { ref } from 'vue'
  538. const count = ref(0)
  539. const maybe = foo()
  540. let lett = 1
  541. </script>
  542. <template>
  543. <input v-model="count">
  544. <input v-model="maybe">
  545. <input v-model="lett">
  546. </template>
  547. `,
  548. { inlineTemplate: true }
  549. )
  550. // known const ref: set value
  551. expect(content).toMatch(`(count).value = $event`)
  552. // const but maybe ref: assign if ref, otherwise do nothing
  553. expect(content).toMatch(`_isRef(maybe) ? (maybe).value = $event : null`)
  554. // let: handle both cases
  555. expect(content).toMatch(
  556. `_isRef(lett) ? (lett).value = $event : lett = $event`
  557. )
  558. assertCode(content)
  559. })
  560. test('template assignment expression codegen', () => {
  561. const { content } = compile(
  562. `<script setup>
  563. import { ref } from 'vue'
  564. const count = ref(0)
  565. const maybe = foo()
  566. let lett = 1
  567. let v = ref(1)
  568. </script>
  569. <template>
  570. <div @click="count = 1"/>
  571. <div @click="maybe = count"/>
  572. <div @click="lett = count"/>
  573. <div @click="v += 1"/>
  574. <div @click="v -= 1"/>
  575. <div @click="() => {
  576. let a = '' + lett
  577. v = a
  578. }"/>
  579. <div @click="() => {
  580. // nested scopes
  581. (()=>{
  582. let x = a
  583. (()=>{
  584. let z = x
  585. let z2 = z
  586. })
  587. let lz = z
  588. })
  589. v = a
  590. }"/>
  591. </template>
  592. `,
  593. { inlineTemplate: true }
  594. )
  595. // known const ref: set value
  596. expect(content).toMatch(`count.value = 1`)
  597. // const but maybe ref: only assign after check
  598. expect(content).toMatch(`maybe.value = count.value`)
  599. // let: handle both cases
  600. expect(content).toMatch(
  601. `_isRef(lett) ? lett.value = count.value : lett = count.value`
  602. )
  603. expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)
  604. expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)
  605. expect(content).toMatch(`_isRef(v) ? v.value = a : v = a`)
  606. expect(content).toMatch(`_isRef(v) ? v.value = _ctx.a : v = _ctx.a`)
  607. assertCode(content)
  608. })
  609. test('template update expression codegen', () => {
  610. const { content } = compile(
  611. `<script setup>
  612. import { ref } from 'vue'
  613. const count = ref(0)
  614. const maybe = foo()
  615. let lett = 1
  616. </script>
  617. <template>
  618. <div @click="count++"/>
  619. <div @click="--count"/>
  620. <div @click="maybe++"/>
  621. <div @click="--maybe"/>
  622. <div @click="lett++"/>
  623. <div @click="--lett"/>
  624. </template>
  625. `,
  626. { inlineTemplate: true }
  627. )
  628. // known const ref: set value
  629. expect(content).toMatch(`count.value++`)
  630. expect(content).toMatch(`--count.value`)
  631. // const but maybe ref (non-ref case ignored)
  632. expect(content).toMatch(`maybe.value++`)
  633. expect(content).toMatch(`--maybe.value`)
  634. // let: handle both cases
  635. expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`)
  636. expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`)
  637. assertCode(content)
  638. })
  639. test('template destructure assignment codegen', () => {
  640. const { content } = compile(
  641. `<script setup>
  642. import { ref } from 'vue'
  643. const val = {}
  644. const count = ref(0)
  645. const maybe = foo()
  646. let lett = 1
  647. </script>
  648. <template>
  649. <div @click="({ count } = val)"/>
  650. <div @click="[maybe] = val"/>
  651. <div @click="({ lett } = val)"/>
  652. </template>
  653. `,
  654. { inlineTemplate: true }
  655. )
  656. // known const ref: set value
  657. expect(content).toMatch(`({ count: count.value } = val)`)
  658. // const but maybe ref (non-ref case ignored)
  659. expect(content).toMatch(`[maybe.value] = val`)
  660. // let: assumes non-ref
  661. expect(content).toMatch(`{ lett: lett } = val`)
  662. assertCode(content)
  663. })
  664. test('ssr codegen', () => {
  665. const { content } = compile(
  666. `
  667. <script setup>
  668. import { ref } from 'vue'
  669. const count = ref(0)
  670. </script>
  671. <template>
  672. <div>{{ count }}</div>
  673. <div>static</div>
  674. </template>
  675. <style>
  676. div { color: v-bind(count) }
  677. </style>
  678. `,
  679. {
  680. inlineTemplate: true,
  681. templateOptions: {
  682. ssr: true
  683. }
  684. }
  685. )
  686. expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
  687. expect(content).toMatch(`return (_ctx, _push`)
  688. expect(content).toMatch(`ssrInterpolate`)
  689. expect(content).not.toMatch(`useCssVars`)
  690. expect(content).toMatch(`"--${mockId}-count": (count.value)`)
  691. assertCode(content)
  692. })
  693. })
  694. describe('with TypeScript', () => {
  695. test('hoist type declarations', () => {
  696. const { content } = compile(`
  697. <script setup lang="ts">
  698. export interface Foo {}
  699. type Bar = {}
  700. </script>`)
  701. assertCode(content)
  702. })
  703. test('defineProps/Emit w/ runtime options', () => {
  704. const { content } = compile(`
  705. <script setup lang="ts">
  706. const props = defineProps({ foo: String })
  707. const emit = defineEmits(['a', 'b'])
  708. </script>
  709. `)
  710. assertCode(content)
  711. expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
  712. props: { foo: String },
  713. emits: ['a', 'b'],
  714. setup(__props, { expose, emit }) {`)
  715. })
  716. test('defineProps w/ type', () => {
  717. const { content, bindings } = compile(`
  718. <script setup lang="ts">
  719. interface Test {}
  720. type Alias = number[]
  721. defineProps<{
  722. string: string
  723. number: number
  724. boolean: boolean
  725. object: object
  726. objectLiteral: { a: number }
  727. fn: (n: number) => void
  728. functionRef: Function
  729. objectRef: Object
  730. dateTime: Date
  731. array: string[]
  732. arrayRef: Array<any>
  733. tuple: [number, number]
  734. set: Set<string>
  735. literal: 'foo'
  736. optional?: any
  737. recordRef: Record<string, null>
  738. interface: Test
  739. alias: Alias
  740. method(): void
  741. symbol: symbol
  742. union: string | number
  743. literalUnion: 'foo' | 'bar'
  744. literalUnionNumber: 1 | 2 | 3 | 4 | 5
  745. literalUnionMixed: 'foo' | 1 | boolean
  746. intersection: Test & {}
  747. foo: ((item: any) => boolean) | null
  748. }>()
  749. </script>`)
  750. assertCode(content)
  751. expect(content).toMatch(`string: { type: String, required: true }`)
  752. expect(content).toMatch(`number: { type: Number, required: true }`)
  753. expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
  754. expect(content).toMatch(`object: { type: Object, required: true }`)
  755. expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
  756. expect(content).toMatch(`fn: { type: Function, required: true }`)
  757. expect(content).toMatch(`functionRef: { type: Function, required: true }`)
  758. expect(content).toMatch(`objectRef: { type: Object, required: true }`)
  759. expect(content).toMatch(`dateTime: { type: Date, required: true }`)
  760. expect(content).toMatch(`array: { type: Array, required: true }`)
  761. expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
  762. expect(content).toMatch(`tuple: { type: Array, required: true }`)
  763. expect(content).toMatch(`set: { type: Set, required: true }`)
  764. expect(content).toMatch(`literal: { type: String, required: true }`)
  765. expect(content).toMatch(`optional: { type: null, required: false }`)
  766. expect(content).toMatch(`recordRef: { type: Object, required: true }`)
  767. expect(content).toMatch(`interface: { type: Object, required: true }`)
  768. expect(content).toMatch(`alias: { type: Array, required: true }`)
  769. expect(content).toMatch(`method: { type: Function, required: true }`)
  770. expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
  771. expect(content).toMatch(
  772. `union: { type: [String, Number], required: true }`
  773. )
  774. expect(content).toMatch(`literalUnion: { type: String, required: true }`)
  775. expect(content).toMatch(
  776. `literalUnionNumber: { type: Number, required: true }`
  777. )
  778. expect(content).toMatch(
  779. `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
  780. )
  781. expect(content).toMatch(`intersection: { type: Object, required: true }`)
  782. expect(content).toMatch(`foo: { type: [Function, null], required: true }`)
  783. expect(bindings).toStrictEqual({
  784. string: BindingTypes.PROPS,
  785. number: BindingTypes.PROPS,
  786. boolean: BindingTypes.PROPS,
  787. object: BindingTypes.PROPS,
  788. objectLiteral: BindingTypes.PROPS,
  789. fn: BindingTypes.PROPS,
  790. functionRef: BindingTypes.PROPS,
  791. objectRef: BindingTypes.PROPS,
  792. dateTime: BindingTypes.PROPS,
  793. array: BindingTypes.PROPS,
  794. arrayRef: BindingTypes.PROPS,
  795. tuple: BindingTypes.PROPS,
  796. set: BindingTypes.PROPS,
  797. literal: BindingTypes.PROPS,
  798. optional: BindingTypes.PROPS,
  799. recordRef: BindingTypes.PROPS,
  800. interface: BindingTypes.PROPS,
  801. alias: BindingTypes.PROPS,
  802. method: BindingTypes.PROPS,
  803. symbol: BindingTypes.PROPS,
  804. union: BindingTypes.PROPS,
  805. literalUnion: BindingTypes.PROPS,
  806. literalUnionNumber: BindingTypes.PROPS,
  807. literalUnionMixed: BindingTypes.PROPS,
  808. intersection: BindingTypes.PROPS,
  809. foo: BindingTypes.PROPS
  810. })
  811. })
  812. test('defineProps w/ interface', () => {
  813. const { content, bindings } = compile(`
  814. <script setup lang="ts">
  815. interface Props { x?: number }
  816. defineProps<Props>()
  817. </script>
  818. `)
  819. assertCode(content)
  820. expect(content).toMatch(`x: { type: Number, required: false }`)
  821. expect(bindings).toStrictEqual({
  822. x: BindingTypes.PROPS
  823. })
  824. })
  825. test('defineProps w/ exported interface', () => {
  826. const { content, bindings } = compile(`
  827. <script setup lang="ts">
  828. export interface Props { x?: number }
  829. defineProps<Props>()
  830. </script>
  831. `)
  832. assertCode(content)
  833. expect(content).toMatch(`x: { type: Number, required: false }`)
  834. expect(bindings).toStrictEqual({
  835. x: BindingTypes.PROPS
  836. })
  837. })
  838. test('defineProps w/ exported interface in normal script', () => {
  839. const { content, bindings } = compile(`
  840. <script lang="ts">
  841. export interface Props { x?: number }
  842. </script>
  843. <script setup lang="ts">
  844. defineProps<Props>()
  845. </script>
  846. `)
  847. assertCode(content)
  848. expect(content).toMatch(`x: { type: Number, required: false }`)
  849. expect(bindings).toStrictEqual({
  850. x: BindingTypes.PROPS
  851. })
  852. })
  853. test('defineProps w/ type alias', () => {
  854. const { content, bindings } = compile(`
  855. <script setup lang="ts">
  856. type Props = { x?: number }
  857. defineProps<Props>()
  858. </script>
  859. `)
  860. assertCode(content)
  861. expect(content).toMatch(`x: { type: Number, required: false }`)
  862. expect(bindings).toStrictEqual({
  863. x: BindingTypes.PROPS
  864. })
  865. })
  866. test('defineProps w/ exported type alias', () => {
  867. const { content, bindings } = compile(`
  868. <script setup lang="ts">
  869. export type Props = { x?: number }
  870. defineProps<Props>()
  871. </script>
  872. `)
  873. assertCode(content)
  874. expect(content).toMatch(`x: { type: Number, required: false }`)
  875. expect(bindings).toStrictEqual({
  876. x: BindingTypes.PROPS
  877. })
  878. })
  879. test('withDefaults (static)', () => {
  880. const { content, bindings } = compile(`
  881. <script setup lang="ts">
  882. const props = withDefaults(defineProps<{
  883. foo?: string
  884. bar?: number;
  885. baz: boolean;
  886. qux?(): number
  887. }>(), {
  888. foo: 'hi',
  889. qux() { return 1 }
  890. })
  891. </script>
  892. `)
  893. assertCode(content)
  894. expect(content).toMatch(
  895. `foo: { type: String, required: false, default: 'hi' }`
  896. )
  897. expect(content).toMatch(`bar: { type: Number, required: false }`)
  898. expect(content).toMatch(`baz: { type: Boolean, required: true }`)
  899. expect(content).toMatch(
  900. `qux: { type: Function, required: false, default() { return 1 } }`
  901. )
  902. expect(content).toMatch(
  903. `{ foo: string, bar?: number, baz: boolean, qux(): number }`
  904. )
  905. expect(content).toMatch(`const props = __props`)
  906. expect(bindings).toStrictEqual({
  907. foo: BindingTypes.PROPS,
  908. bar: BindingTypes.PROPS,
  909. baz: BindingTypes.PROPS,
  910. qux: BindingTypes.PROPS,
  911. props: BindingTypes.SETUP_CONST
  912. })
  913. })
  914. test('withDefaults (dynamic)', () => {
  915. const { content } = compile(`
  916. <script setup lang="ts">
  917. import { defaults } from './foo'
  918. const props = withDefaults(defineProps<{
  919. foo?: string
  920. bar?: number
  921. baz: boolean
  922. }>(), { ...defaults })
  923. </script>
  924. `)
  925. assertCode(content)
  926. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  927. expect(content).toMatch(
  928. `
  929. _mergeDefaults({
  930. foo: { type: String, required: false },
  931. bar: { type: Number, required: false },
  932. baz: { type: Boolean, required: true }
  933. }, { ...defaults })`.trim()
  934. )
  935. })
  936. test('defineEmits w/ type', () => {
  937. const { content } = compile(`
  938. <script setup lang="ts">
  939. const emit = defineEmits<(e: 'foo' | 'bar') => void>()
  940. </script>
  941. `)
  942. assertCode(content)
  943. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  944. expect(content).toMatch(`emits: ["foo", "bar"]`)
  945. })
  946. test('defineEmits w/ type (union)', () => {
  947. const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
  948. expect(() =>
  949. compile(`
  950. <script setup lang="ts">
  951. const emit = defineEmits<${type}>()
  952. </script>
  953. `)
  954. ).toThrow()
  955. })
  956. test('defineEmits w/ type (type literal w/ call signatures)', () => {
  957. const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
  958. const { content } = compile(`
  959. <script setup lang="ts">
  960. const emit = defineEmits<${type}>()
  961. </script>
  962. `)
  963. assertCode(content)
  964. expect(content).toMatch(`emit: (${type}),`)
  965. expect(content).toMatch(`emits: ["foo", "bar", "baz"]`)
  966. })
  967. test('defineEmits w/ type (interface)', () => {
  968. const { content } = compile(`
  969. <script setup lang="ts">
  970. interface Emits { (e: 'foo' | 'bar'): void }
  971. const emit = defineEmits<Emits>()
  972. </script>
  973. `)
  974. assertCode(content)
  975. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  976. expect(content).toMatch(`emits: ["foo", "bar"]`)
  977. })
  978. test('defineEmits w/ type (exported interface)', () => {
  979. const { content } = compile(`
  980. <script setup lang="ts">
  981. export interface Emits { (e: 'foo' | 'bar'): void }
  982. const emit = defineEmits<Emits>()
  983. </script>
  984. `)
  985. assertCode(content)
  986. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  987. expect(content).toMatch(`emits: ["foo", "bar"]`)
  988. })
  989. test('defineEmits w/ type (type alias)', () => {
  990. const { content } = compile(`
  991. <script setup lang="ts">
  992. type Emits = { (e: 'foo' | 'bar'): void }
  993. const emit = defineEmits<Emits>()
  994. </script>
  995. `)
  996. assertCode(content)
  997. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  998. expect(content).toMatch(`emits: ["foo", "bar"]`)
  999. })
  1000. test('defineEmits w/ type (exported type alias)', () => {
  1001. const { content } = compile(`
  1002. <script setup lang="ts">
  1003. export type Emits = { (e: 'foo' | 'bar'): void }
  1004. const emit = defineEmits<Emits>()
  1005. </script>
  1006. `)
  1007. assertCode(content)
  1008. expect(content).toMatch(`emit: ({ (e: 'foo' | 'bar'): void }),`)
  1009. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1010. })
  1011. test('defineEmits w/ type (referenced function type)', () => {
  1012. const { content } = compile(`
  1013. <script setup lang="ts">
  1014. type Emits = (e: 'foo' | 'bar') => void
  1015. const emit = defineEmits<Emits>()
  1016. </script>
  1017. `)
  1018. assertCode(content)
  1019. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  1020. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1021. })
  1022. test('defineEmits w/ type (referenced exported function type)', () => {
  1023. const { content } = compile(`
  1024. <script setup lang="ts">
  1025. export type Emits = (e: 'foo' | 'bar') => void
  1026. const emit = defineEmits<Emits>()
  1027. </script>
  1028. `)
  1029. assertCode(content)
  1030. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  1031. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1032. })
  1033. test('runtime Enum', () => {
  1034. const { content, bindings } = compile(
  1035. `<script setup lang="ts">
  1036. enum Foo { A = 123 }
  1037. </script>`
  1038. )
  1039. assertCode(content)
  1040. expect(bindings).toStrictEqual({
  1041. Foo: BindingTypes.SETUP_CONST
  1042. })
  1043. })
  1044. test('runtime Enum in normal script', () => {
  1045. const { content, bindings } = compile(
  1046. `<script lang="ts">
  1047. export enum D { D = "D" }
  1048. const enum C { C = "C" }
  1049. enum B { B = "B" }
  1050. </script>
  1051. <script setup lang="ts">
  1052. enum Foo { A = 123 }
  1053. </script>`
  1054. )
  1055. assertCode(content)
  1056. expect(bindings).toStrictEqual({
  1057. D: BindingTypes.SETUP_CONST,
  1058. C: BindingTypes.SETUP_CONST,
  1059. B: BindingTypes.SETUP_CONST,
  1060. Foo: BindingTypes.SETUP_CONST
  1061. })
  1062. })
  1063. test('const Enum', () => {
  1064. const { content, bindings } = compile(
  1065. `<script setup lang="ts">
  1066. const enum Foo { A = 123 }
  1067. </script>`
  1068. )
  1069. assertCode(content)
  1070. expect(bindings).toStrictEqual({
  1071. Foo: BindingTypes.SETUP_CONST
  1072. })
  1073. })
  1074. test('import type', () => {
  1075. const { content } = compile(
  1076. `<script setup lang="ts">
  1077. import type { Foo } from './main.ts'
  1078. import { type Bar, Baz } from './main.ts'
  1079. </script>`
  1080. )
  1081. expect(content).toMatch(`return { Baz }`)
  1082. assertCode(content)
  1083. })
  1084. })
  1085. describe('async/await detection', () => {
  1086. function assertAwaitDetection(code: string, shouldAsync = true) {
  1087. const { content } = compile(`<script setup>${code}</script>`, {
  1088. reactivityTransform: true
  1089. })
  1090. if (shouldAsync) {
  1091. expect(content).toMatch(`let __temp, __restore`)
  1092. }
  1093. expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
  1094. assertCode(content)
  1095. return content
  1096. }
  1097. test('expression statement', () => {
  1098. assertAwaitDetection(`await foo`)
  1099. })
  1100. test('variable', () => {
  1101. assertAwaitDetection(`const a = 1 + (await foo)`)
  1102. })
  1103. test('ref', () => {
  1104. assertAwaitDetection(`let a = $ref(1 + (await foo))`)
  1105. })
  1106. // #4448
  1107. test('nested await', () => {
  1108. assertAwaitDetection(`await (await foo)`)
  1109. assertAwaitDetection(`await ((await foo))`)
  1110. assertAwaitDetection(`await (await (await foo))`)
  1111. })
  1112. // should prepend semicolon
  1113. test('nested leading await in expression statement', () => {
  1114. const code = assertAwaitDetection(`foo()\nawait 1 + await 2`)
  1115. expect(code).toMatch(`foo()\n;(`)
  1116. })
  1117. // #4596 should NOT prepend semicolon
  1118. test('single line conditions', () => {
  1119. const code = assertAwaitDetection(`if (false) await foo()`)
  1120. expect(code).not.toMatch(`if (false) ;(`)
  1121. })
  1122. test('nested statements', () => {
  1123. assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
  1124. })
  1125. test('multiple `if` nested statements', () => {
  1126. assertAwaitDetection(`if (ok) {
  1127. let a = 'foo'
  1128. await 0 + await 1
  1129. await 2
  1130. } else if (a) {
  1131. await 10
  1132. if (b) {
  1133. await 0 + await 1
  1134. } else {
  1135. let a = 'foo'
  1136. await 2
  1137. }
  1138. if (b) {
  1139. await 3
  1140. await 4
  1141. }
  1142. } else {
  1143. await 5
  1144. }`)
  1145. })
  1146. test('multiple `if while` nested statements', () => {
  1147. assertAwaitDetection(`if (ok) {
  1148. while (d) {
  1149. await 5
  1150. }
  1151. while (d) {
  1152. await 5
  1153. await 6
  1154. if (c) {
  1155. let f = 10
  1156. 10 + await 7
  1157. } else {
  1158. await 8
  1159. await 9
  1160. }
  1161. }
  1162. }`)
  1163. })
  1164. test('multiple `if for` nested statements', () => {
  1165. assertAwaitDetection(`if (ok) {
  1166. for (let a of [1,2,3]) {
  1167. await a
  1168. }
  1169. for (let a of [1,2,3]) {
  1170. await a
  1171. await a
  1172. }
  1173. }`)
  1174. })
  1175. test('should ignore await inside functions', () => {
  1176. // function declaration
  1177. assertAwaitDetection(`async function foo() { await bar }`, false)
  1178. // function expression
  1179. assertAwaitDetection(`const foo = async () => { await bar }`, false)
  1180. // object method
  1181. assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
  1182. // class method
  1183. assertAwaitDetection(
  1184. `const cls = class Foo { async method() { await bar }}`,
  1185. false
  1186. )
  1187. })
  1188. })
  1189. describe('errors', () => {
  1190. test('<script> and <script setup> must have same lang', () => {
  1191. expect(() =>
  1192. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`)
  1193. ).toThrow(`<script> and <script setup> must have the same language type`)
  1194. })
  1195. const moduleErrorMsg = `cannot contain ES module exports`
  1196. test('non-type named exports', () => {
  1197. expect(() =>
  1198. compile(`<script setup>
  1199. export const a = 1
  1200. </script>`)
  1201. ).toThrow(moduleErrorMsg)
  1202. expect(() =>
  1203. compile(`<script setup>
  1204. export * from './foo'
  1205. </script>`)
  1206. ).toThrow(moduleErrorMsg)
  1207. expect(() =>
  1208. compile(`<script setup>
  1209. const bar = 1
  1210. export { bar as default }
  1211. </script>`)
  1212. ).toThrow(moduleErrorMsg)
  1213. })
  1214. test('defineProps/Emit() w/ both type and non-type args', () => {
  1215. expect(() => {
  1216. compile(`<script setup lang="ts">
  1217. defineProps<{}>({})
  1218. </script>`)
  1219. }).toThrow(`cannot accept both type and non-type arguments`)
  1220. expect(() => {
  1221. compile(`<script setup lang="ts">
  1222. defineEmits<{}>({})
  1223. </script>`)
  1224. }).toThrow(`cannot accept both type and non-type arguments`)
  1225. })
  1226. test('defineProps/Emit() referencing local var', () => {
  1227. expect(() =>
  1228. compile(`<script setup>
  1229. const bar = 1
  1230. defineProps({
  1231. foo: {
  1232. default: () => bar
  1233. }
  1234. })
  1235. </script>`)
  1236. ).toThrow(`cannot reference locally declared variables`)
  1237. expect(() =>
  1238. compile(`<script setup>
  1239. const bar = 'hello'
  1240. defineEmits([bar])
  1241. </script>`)
  1242. ).toThrow(`cannot reference locally declared variables`)
  1243. // #4644
  1244. expect(() =>
  1245. compile(`
  1246. <script>const bar = 1</script>
  1247. <script setup>
  1248. defineProps({
  1249. foo: {
  1250. default: () => bar
  1251. }
  1252. })
  1253. </script>`)
  1254. ).not.toThrow(`cannot reference locally declared variables`)
  1255. })
  1256. test('should allow defineProps/Emit() referencing scope var', () => {
  1257. assertCode(
  1258. compile(`<script setup>
  1259. const bar = 1
  1260. defineProps({
  1261. foo: {
  1262. default: bar => bar + 1
  1263. }
  1264. })
  1265. defineEmits({
  1266. foo: bar => bar > 1
  1267. })
  1268. </script>`).content
  1269. )
  1270. })
  1271. test('should allow defineProps/Emit() referencing imported binding', () => {
  1272. assertCode(
  1273. compile(`<script setup>
  1274. import { bar } from './bar'
  1275. defineProps({
  1276. foo: {
  1277. default: () => bar
  1278. }
  1279. })
  1280. defineEmits({
  1281. foo: () => bar > 1
  1282. })
  1283. </script>`).content
  1284. )
  1285. })
  1286. })
  1287. })
  1288. describe('SFC analyze <script> bindings', () => {
  1289. it('can parse decorators syntax in typescript block', () => {
  1290. const { scriptAst } = compile(`
  1291. <script lang="ts">
  1292. import { Options, Vue } from 'vue-class-component';
  1293. @Options({
  1294. components: {
  1295. HelloWorld,
  1296. },
  1297. props: ['foo', 'bar']
  1298. })
  1299. export default class Home extends Vue {}
  1300. </script>
  1301. `)
  1302. expect(scriptAst).toBeDefined()
  1303. })
  1304. it('recognizes props array declaration', () => {
  1305. const { bindings } = compile(`
  1306. <script>
  1307. export default {
  1308. props: ['foo', 'bar']
  1309. }
  1310. </script>
  1311. `)
  1312. expect(bindings).toStrictEqual({
  1313. foo: BindingTypes.PROPS,
  1314. bar: BindingTypes.PROPS
  1315. })
  1316. expect(bindings!.__isScriptSetup).toBe(false)
  1317. })
  1318. it('recognizes props object declaration', () => {
  1319. const { bindings } = compile(`
  1320. <script>
  1321. export default {
  1322. props: {
  1323. foo: String,
  1324. bar: {
  1325. type: String,
  1326. },
  1327. baz: null,
  1328. qux: [String, Number]
  1329. }
  1330. }
  1331. </script>
  1332. `)
  1333. expect(bindings).toStrictEqual({
  1334. foo: BindingTypes.PROPS,
  1335. bar: BindingTypes.PROPS,
  1336. baz: BindingTypes.PROPS,
  1337. qux: BindingTypes.PROPS
  1338. })
  1339. expect(bindings!.__isScriptSetup).toBe(false)
  1340. })
  1341. it('recognizes setup return', () => {
  1342. const { bindings } = compile(`
  1343. <script>
  1344. const bar = 2
  1345. export default {
  1346. setup() {
  1347. return {
  1348. foo: 1,
  1349. bar
  1350. }
  1351. }
  1352. }
  1353. </script>
  1354. `)
  1355. expect(bindings).toStrictEqual({
  1356. foo: BindingTypes.SETUP_MAYBE_REF,
  1357. bar: BindingTypes.SETUP_MAYBE_REF
  1358. })
  1359. expect(bindings!.__isScriptSetup).toBe(false)
  1360. })
  1361. it('recognizes exported vars', () => {
  1362. const { bindings } = compile(`
  1363. <script>
  1364. export const foo = 2
  1365. </script>
  1366. <script setup>
  1367. console.log(foo)
  1368. </script>
  1369. `)
  1370. expect(bindings).toStrictEqual({
  1371. foo: BindingTypes.SETUP_CONST
  1372. })
  1373. })
  1374. it('recognizes async setup return', () => {
  1375. const { bindings } = compile(`
  1376. <script>
  1377. const bar = 2
  1378. export default {
  1379. async setup() {
  1380. return {
  1381. foo: 1,
  1382. bar
  1383. }
  1384. }
  1385. }
  1386. </script>
  1387. `)
  1388. expect(bindings).toStrictEqual({
  1389. foo: BindingTypes.SETUP_MAYBE_REF,
  1390. bar: BindingTypes.SETUP_MAYBE_REF
  1391. })
  1392. expect(bindings!.__isScriptSetup).toBe(false)
  1393. })
  1394. it('recognizes data return', () => {
  1395. const { bindings } = compile(`
  1396. <script>
  1397. const bar = 2
  1398. export default {
  1399. data() {
  1400. return {
  1401. foo: null,
  1402. bar
  1403. }
  1404. }
  1405. }
  1406. </script>
  1407. `)
  1408. expect(bindings).toStrictEqual({
  1409. foo: BindingTypes.DATA,
  1410. bar: BindingTypes.DATA
  1411. })
  1412. })
  1413. it('recognizes methods', () => {
  1414. const { bindings } = compile(`
  1415. <script>
  1416. export default {
  1417. methods: {
  1418. foo() {}
  1419. }
  1420. }
  1421. </script>
  1422. `)
  1423. expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })
  1424. })
  1425. it('recognizes computeds', () => {
  1426. const { bindings } = compile(`
  1427. <script>
  1428. export default {
  1429. computed: {
  1430. foo() {},
  1431. bar: {
  1432. get() {},
  1433. set() {},
  1434. }
  1435. }
  1436. }
  1437. </script>
  1438. `)
  1439. expect(bindings).toStrictEqual({
  1440. foo: BindingTypes.OPTIONS,
  1441. bar: BindingTypes.OPTIONS
  1442. })
  1443. })
  1444. it('recognizes injections array declaration', () => {
  1445. const { bindings } = compile(`
  1446. <script>
  1447. export default {
  1448. inject: ['foo', 'bar']
  1449. }
  1450. </script>
  1451. `)
  1452. expect(bindings).toStrictEqual({
  1453. foo: BindingTypes.OPTIONS,
  1454. bar: BindingTypes.OPTIONS
  1455. })
  1456. })
  1457. it('recognizes injections object declaration', () => {
  1458. const { bindings } = compile(`
  1459. <script>
  1460. export default {
  1461. inject: {
  1462. foo: {},
  1463. bar: {},
  1464. }
  1465. }
  1466. </script>
  1467. `)
  1468. expect(bindings).toStrictEqual({
  1469. foo: BindingTypes.OPTIONS,
  1470. bar: BindingTypes.OPTIONS
  1471. })
  1472. })
  1473. it('works for mixed bindings', () => {
  1474. const { bindings } = compile(`
  1475. <script>
  1476. export default {
  1477. inject: ['foo'],
  1478. props: {
  1479. bar: String,
  1480. },
  1481. setup() {
  1482. return {
  1483. baz: null,
  1484. }
  1485. },
  1486. data() {
  1487. return {
  1488. qux: null
  1489. }
  1490. },
  1491. methods: {
  1492. quux() {}
  1493. },
  1494. computed: {
  1495. quuz() {}
  1496. }
  1497. }
  1498. </script>
  1499. `)
  1500. expect(bindings).toStrictEqual({
  1501. foo: BindingTypes.OPTIONS,
  1502. bar: BindingTypes.PROPS,
  1503. baz: BindingTypes.SETUP_MAYBE_REF,
  1504. qux: BindingTypes.DATA,
  1505. quux: BindingTypes.OPTIONS,
  1506. quuz: BindingTypes.OPTIONS
  1507. })
  1508. })
  1509. it('works for script setup', () => {
  1510. const { bindings } = compile(`
  1511. <script setup>
  1512. import { ref as r } from 'vue'
  1513. defineProps({
  1514. foo: String
  1515. })
  1516. const a = r(1)
  1517. let b = 2
  1518. const c = 3
  1519. const { d } = someFoo()
  1520. let { e } = someBar()
  1521. </script>
  1522. `)
  1523. expect(bindings).toStrictEqual({
  1524. r: BindingTypes.SETUP_CONST,
  1525. a: BindingTypes.SETUP_REF,
  1526. b: BindingTypes.SETUP_LET,
  1527. c: BindingTypes.SETUP_CONST,
  1528. d: BindingTypes.SETUP_MAYBE_REF,
  1529. e: BindingTypes.SETUP_LET,
  1530. foo: BindingTypes.PROPS
  1531. })
  1532. })
  1533. describe('auto name inference', () => {
  1534. test('basic', () => {
  1535. const { content } = compile(
  1536. `<script setup>const a = 1</script>
  1537. <template>{{ a }}</template>`,
  1538. undefined,
  1539. {
  1540. filename: 'FooBar.vue'
  1541. }
  1542. )
  1543. expect(content).toMatch(`export default {
  1544. name: 'FooBar'`)
  1545. assertCode(content)
  1546. })
  1547. test('do not overwrite manual name (object)', () => {
  1548. const { content } = compile(
  1549. `<script>
  1550. export default {
  1551. name: 'Baz'
  1552. }
  1553. </script>
  1554. <script setup>const a = 1</script>
  1555. <template>{{ a }}</template>`,
  1556. undefined,
  1557. {
  1558. filename: 'FooBar.vue'
  1559. }
  1560. )
  1561. expect(content).not.toMatch(`name: 'FooBar'`)
  1562. expect(content).toMatch(`name: 'Baz'`)
  1563. assertCode(content)
  1564. })
  1565. test('do not overwrite manual name (call)', () => {
  1566. const { content } = compile(
  1567. `<script>
  1568. import { defineComponent } from 'vue'
  1569. export default defineComponent({
  1570. name: 'Baz'
  1571. })
  1572. </script>
  1573. <script setup>const a = 1</script>
  1574. <template>{{ a }}</template>`,
  1575. undefined,
  1576. {
  1577. filename: 'FooBar.vue'
  1578. }
  1579. )
  1580. expect(content).not.toMatch(`name: 'FooBar'`)
  1581. expect(content).toMatch(`name: 'Baz'`)
  1582. assertCode(content)
  1583. })
  1584. })
  1585. })