compileScript.spec.ts 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685
  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 compile JS syntax', () => {
  5. const { content } = compile(`
  6. <script setup lang='js'>
  7. const a = 1
  8. const b = 2
  9. </script>
  10. `)
  11. expect(content).toMatch(`return { a, b }`)
  12. assertCode(content)
  13. })
  14. test('should expose top level declarations', () => {
  15. const { content, bindings } = compile(`
  16. <script setup>
  17. import { x } from './x'
  18. let a = 1
  19. const b = 2
  20. function c() {}
  21. class d {}
  22. </script>
  23. <script>
  24. import { xx } from './x'
  25. let aa = 1
  26. const bb = 2
  27. function cc() {}
  28. class dd {}
  29. </script>
  30. `)
  31. expect(content).toMatch(
  32. `return { get aa() { return aa }, set aa(v) { aa = v }, ` +
  33. `bb, cc, dd, get a() { return a }, set a(v) { a = v }, b, c, d, ` +
  34. `get xx() { return xx }, get x() { return x } }`
  35. )
  36. expect(bindings).toStrictEqual({
  37. x: BindingTypes.SETUP_MAYBE_REF,
  38. a: BindingTypes.SETUP_LET,
  39. b: BindingTypes.SETUP_CONST,
  40. c: BindingTypes.SETUP_CONST,
  41. d: BindingTypes.SETUP_CONST,
  42. xx: BindingTypes.SETUP_MAYBE_REF,
  43. aa: BindingTypes.SETUP_LET,
  44. bb: BindingTypes.LITERAL_CONST,
  45. cc: BindingTypes.SETUP_CONST,
  46. dd: BindingTypes.SETUP_CONST
  47. })
  48. assertCode(content)
  49. })
  50. test('binding analysis for destructure', () => {
  51. const { content, bindings } = compile(`
  52. <script setup>
  53. const { foo, b: bar, ['x' + 'y']: baz, x: { y, zz: { z }}} = {}
  54. </script>
  55. `)
  56. expect(content).toMatch('return { foo, bar, baz, y, z }')
  57. expect(bindings).toStrictEqual({
  58. foo: BindingTypes.SETUP_MAYBE_REF,
  59. bar: BindingTypes.SETUP_MAYBE_REF,
  60. baz: BindingTypes.SETUP_MAYBE_REF,
  61. y: BindingTypes.SETUP_MAYBE_REF,
  62. z: BindingTypes.SETUP_MAYBE_REF
  63. })
  64. assertCode(content)
  65. })
  66. test('defineProps()', () => {
  67. const { content, bindings } = compile(`
  68. <script setup>
  69. const props = defineProps({
  70. foo: String
  71. })
  72. const bar = 1
  73. </script>
  74. `)
  75. // should generate working code
  76. assertCode(content)
  77. // should analyze bindings
  78. expect(bindings).toStrictEqual({
  79. foo: BindingTypes.PROPS,
  80. bar: BindingTypes.LITERAL_CONST,
  81. props: BindingTypes.SETUP_REACTIVE_CONST
  82. })
  83. // should remove defineOptions import and call
  84. expect(content).not.toMatch('defineProps')
  85. // should generate correct setup signature
  86. expect(content).toMatch(`setup(__props, { expose: __expose }) {`)
  87. // should assign user identifier to it
  88. expect(content).toMatch(`const props = __props`)
  89. // should include context options in default export
  90. expect(content).toMatch(`export default {
  91. props: {
  92. foo: String
  93. },`)
  94. })
  95. test('defineProps w/ external definition', () => {
  96. const { content } = compile(`
  97. <script setup>
  98. import { propsModel } from './props'
  99. const props = defineProps(propsModel)
  100. </script>
  101. `)
  102. assertCode(content)
  103. expect(content).toMatch(`export default {
  104. props: propsModel,`)
  105. })
  106. // #4764
  107. test('defineProps w/ leading code', () => {
  108. const { content } = compile(`
  109. <script setup>import { x } from './x'
  110. const props = defineProps({})
  111. </script>
  112. `)
  113. // props declaration should be inside setup, not moved along with the import
  114. expect(content).not.toMatch(`const props = __props\nimport`)
  115. assertCode(content)
  116. })
  117. test('defineEmits()', () => {
  118. const { content, bindings } = compile(`
  119. <script setup>
  120. const myEmit = defineEmits(['foo', 'bar'])
  121. </script>
  122. `)
  123. assertCode(content)
  124. expect(bindings).toStrictEqual({
  125. myEmit: BindingTypes.SETUP_CONST
  126. })
  127. // should remove defineEmits import and call
  128. expect(content).not.toMatch('defineEmits')
  129. // should generate correct setup signature
  130. expect(content).toMatch(
  131. `setup(__props, { expose: __expose, emit: myEmit }) {`
  132. )
  133. // should include context options in default export
  134. expect(content).toMatch(`export default {
  135. emits: ['foo', 'bar'],`)
  136. })
  137. test('defineProps/defineEmits in multi-variable declaration', () => {
  138. const { content } = compile(`
  139. <script setup>
  140. const props = defineProps(['item']),
  141. a = 1,
  142. emit = defineEmits(['a']);
  143. </script>
  144. `)
  145. assertCode(content)
  146. expect(content).toMatch(`const a = 1;`) // test correct removal
  147. expect(content).toMatch(`props: ['item'],`)
  148. expect(content).toMatch(`emits: ['a'],`)
  149. })
  150. // #6757
  151. test('defineProps/defineEmits in multi-variable declaration fix #6757 ', () => {
  152. const { content } = compile(`
  153. <script setup>
  154. const a = 1,
  155. props = defineProps(['item']),
  156. emit = defineEmits(['a']);
  157. </script>
  158. `)
  159. assertCode(content)
  160. expect(content).toMatch(`const a = 1;`) // test correct removal
  161. expect(content).toMatch(`props: ['item'],`)
  162. expect(content).toMatch(`emits: ['a'],`)
  163. })
  164. // #7422
  165. test('defineProps/defineEmits in multi-variable declaration fix #7422', () => {
  166. const { content } = compile(`
  167. <script setup>
  168. const props = defineProps(['item']),
  169. emits = defineEmits(['foo']),
  170. a = 0,
  171. b = 0;
  172. </script>
  173. `)
  174. assertCode(content)
  175. expect(content).toMatch(`props: ['item'],`)
  176. expect(content).toMatch(`emits: ['foo'],`)
  177. expect(content).toMatch(`const a = 0,`)
  178. expect(content).toMatch(`b = 0;`)
  179. })
  180. test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
  181. const { content } = compile(`
  182. <script setup>
  183. const props = defineProps(['item']),
  184. emit = defineEmits(['a']);
  185. </script>
  186. `)
  187. assertCode(content)
  188. expect(content).toMatch(`props: ['item'],`)
  189. expect(content).toMatch(`emits: ['a'],`)
  190. })
  191. describe('defineOptions()', () => {
  192. test('basic usage', () => {
  193. const { content } = compile(`
  194. <script setup>
  195. defineOptions({ name: 'FooApp' })
  196. </script>
  197. `)
  198. assertCode(content)
  199. // should remove defineOptions import and call
  200. expect(content).not.toMatch('defineOptions')
  201. // should include context options in default export
  202. expect(content).toMatch(
  203. `export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, `
  204. )
  205. })
  206. test('empty argument', () => {
  207. const { content } = compile(`
  208. <script setup>
  209. defineOptions()
  210. </script>
  211. `)
  212. assertCode(content)
  213. expect(content).toMatch(`export default {`)
  214. // should remove defineOptions import and call
  215. expect(content).not.toMatch('defineOptions')
  216. })
  217. it('should emit an error with two defineProps', () => {
  218. expect(() =>
  219. compile(`
  220. <script setup>
  221. defineOptions({ name: 'FooApp' })
  222. defineOptions({ name: 'BarApp' })
  223. </script>
  224. `)
  225. ).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call')
  226. })
  227. it('should emit an error with props or emits property', () => {
  228. expect(() =>
  229. compile(`
  230. <script setup>
  231. defineOptions({ props: { foo: String } })
  232. </script>
  233. `)
  234. ).toThrowError(
  235. '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.'
  236. )
  237. expect(() =>
  238. compile(`
  239. <script setup>
  240. defineOptions({ emits: ['update'] })
  241. </script>
  242. `)
  243. ).toThrowError(
  244. '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.'
  245. )
  246. expect(() =>
  247. compile(`
  248. <script setup>
  249. defineOptions({ expose: ['foo'] })
  250. </script>
  251. `)
  252. ).toThrowError(
  253. '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.'
  254. )
  255. expect(() =>
  256. compile(`
  257. <script setup>
  258. defineOptions({ slots: ['foo'] })
  259. </script>
  260. `)
  261. ).toThrowError(
  262. '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.'
  263. )
  264. })
  265. it('should emit an error with type generic', () => {
  266. expect(() =>
  267. compile(`
  268. <script setup lang="ts">
  269. defineOptions<{ name: 'FooApp' }>()
  270. </script>
  271. `)
  272. ).toThrowError(
  273. '[@vue/compiler-sfc] defineOptions() cannot accept type arguments'
  274. )
  275. })
  276. it('should emit an error with type assertion', () => {
  277. expect(() =>
  278. compile(`
  279. <script setup lang="ts">
  280. defineOptions({ props: [] } as any)
  281. </script>
  282. `)
  283. ).toThrowError(
  284. '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.'
  285. )
  286. })
  287. it('should emit an error with declaring props/emits/slots/expose', () => {
  288. expect(() =>
  289. compile(`
  290. <script setup>
  291. defineOptions({ props: ['foo'] })
  292. </script>
  293. `)
  294. ).toThrowError(
  295. '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead'
  296. )
  297. expect(() =>
  298. compile(`
  299. <script setup>
  300. defineOptions({ emits: ['update'] })
  301. </script>
  302. `)
  303. ).toThrowError(
  304. '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead'
  305. )
  306. expect(() =>
  307. compile(`
  308. <script setup>
  309. defineOptions({ expose: ['foo'] })
  310. </script>
  311. `)
  312. ).toThrowError(
  313. '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead'
  314. )
  315. expect(() =>
  316. compile(`
  317. <script setup lang="ts">
  318. defineOptions({ slots: Object })
  319. </script>
  320. `)
  321. ).toThrowError(
  322. '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead'
  323. )
  324. })
  325. })
  326. test('defineExpose()', () => {
  327. const { content } = compile(`
  328. <script setup>
  329. defineExpose({ foo: 123 })
  330. </script>
  331. `)
  332. assertCode(content)
  333. // should remove defineOptions import and call
  334. expect(content).not.toMatch('defineExpose')
  335. // should generate correct setup signature
  336. expect(content).toMatch(`setup(__props, { expose: __expose }) {`)
  337. // should replace callee
  338. expect(content).toMatch(/\b__expose\(\{ foo: 123 \}\)/)
  339. })
  340. describe('defineModel()', () => {
  341. test('basic usage', () => {
  342. const { content, bindings } = compile(
  343. `
  344. <script setup>
  345. const modelValue = defineModel({ required: true })
  346. const c = defineModel('count')
  347. </script>
  348. `,
  349. { defineModel: true }
  350. )
  351. assertCode(content)
  352. expect(content).toMatch('props: {')
  353. expect(content).toMatch('"modelValue": { required: true },')
  354. expect(content).toMatch('"count": {},')
  355. expect(content).toMatch('emits: ["update:modelValue", "update:count"],')
  356. expect(content).toMatch(
  357. `const modelValue = _useModel(__props, "modelValue")`
  358. )
  359. expect(content).toMatch(`const c = _useModel(__props, "count")`)
  360. expect(content).toMatch(`return { modelValue, c }`)
  361. expect(content).not.toMatch('defineModel')
  362. expect(bindings).toStrictEqual({
  363. modelValue: BindingTypes.SETUP_REF,
  364. count: BindingTypes.PROPS,
  365. c: BindingTypes.SETUP_REF
  366. })
  367. })
  368. test('w/ defineProps and defineEmits', () => {
  369. const { content, bindings } = compile(
  370. `
  371. <script setup>
  372. defineProps({ foo: String })
  373. defineEmits(['change'])
  374. const count = defineModel({ default: 0 })
  375. </script>
  376. `,
  377. { defineModel: true }
  378. )
  379. assertCode(content)
  380. expect(content).toMatch(`props: _mergeModels({ foo: String }`)
  381. expect(content).toMatch(`"modelValue": { default: 0 }`)
  382. expect(content).toMatch(`const count = _useModel(__props, "modelValue")`)
  383. expect(content).not.toMatch('defineModel')
  384. expect(bindings).toStrictEqual({
  385. count: BindingTypes.SETUP_REF,
  386. foo: BindingTypes.PROPS,
  387. modelValue: BindingTypes.PROPS
  388. })
  389. })
  390. test('w/ array props', () => {
  391. const { content, bindings } = compile(
  392. `
  393. <script setup>
  394. defineProps(['foo', 'bar'])
  395. const count = defineModel('count')
  396. </script>
  397. `,
  398. { defineModel: true }
  399. )
  400. assertCode(content)
  401. expect(content).toMatch(`props: _mergeModels(['foo', 'bar'], {
  402. "count": {},
  403. })`)
  404. expect(content).toMatch(`const count = _useModel(__props, "count")`)
  405. expect(content).not.toMatch('defineModel')
  406. expect(bindings).toStrictEqual({
  407. foo: BindingTypes.PROPS,
  408. bar: BindingTypes.PROPS,
  409. count: BindingTypes.SETUP_REF
  410. })
  411. })
  412. test('w/ local flag', () => {
  413. const { content } = compile(
  414. `<script setup>
  415. const foo = defineModel({ local: true, default: 1 })
  416. const bar = defineModel('bar', { [key]: true })
  417. const baz = defineModel('baz', { ...x })
  418. const qux = defineModel('qux', x)
  419. const foo2 = defineModel('foo2', { local: true, ...x })
  420. const local = true
  421. const hoist = defineModel('hoist', { local })
  422. </script>`,
  423. { defineModel: true }
  424. )
  425. assertCode(content)
  426. expect(content).toMatch(
  427. `_useModel(__props, "modelValue", { local: true })`
  428. )
  429. expect(content).toMatch(`_useModel(__props, "bar", { [key]: true })`)
  430. expect(content).toMatch(`_useModel(__props, "baz", { ...x })`)
  431. expect(content).toMatch(`_useModel(__props, "qux", x)`)
  432. expect(content).toMatch(`_useModel(__props, "foo2", { local: true })`)
  433. expect(content).toMatch(`_useModel(__props, "hoist", { local })`)
  434. })
  435. })
  436. test('<script> after <script setup> the script content not end with `\\n`', () => {
  437. const { content } = compile(`
  438. <script setup>
  439. import { x } from './x'
  440. </script>
  441. <script>const n = 1</script>
  442. `)
  443. assertCode(content)
  444. })
  445. describe('<script> and <script setup> co-usage', () => {
  446. test('script first', () => {
  447. const { content } = compile(`
  448. <script>
  449. export const n = 1
  450. export default {}
  451. </script>
  452. <script setup>
  453. import { x } from './x'
  454. x()
  455. </script>
  456. `)
  457. assertCode(content)
  458. })
  459. test('script setup first', () => {
  460. const { content } = compile(`
  461. <script setup>
  462. import { x } from './x'
  463. x()
  464. </script>
  465. <script>
  466. export const n = 1
  467. export default {}
  468. </script>
  469. `)
  470. assertCode(content)
  471. })
  472. test('script setup first, named default export', () => {
  473. const { content } = compile(`
  474. <script setup>
  475. import { x } from './x'
  476. x()
  477. </script>
  478. <script>
  479. export const n = 1
  480. const def = {}
  481. export { def as default }
  482. </script>
  483. `)
  484. assertCode(content)
  485. })
  486. // #4395
  487. test('script setup first, lang="ts", script block content export default', () => {
  488. const { content } = compile(`
  489. <script setup lang="ts">
  490. import { x } from './x'
  491. x()
  492. </script>
  493. <script lang="ts">
  494. export default {
  495. name: "test"
  496. }
  497. </script>
  498. `)
  499. // ensure __default__ is declared before used
  500. expect(content).toMatch(/const __default__[\S\s]*\.\.\.__default__/m)
  501. assertCode(content)
  502. })
  503. describe('spaces in ExportDefaultDeclaration node', () => {
  504. // #4371
  505. test('with many spaces and newline', () => {
  506. // #4371
  507. const { content } = compile(`
  508. <script>
  509. export const n = 1
  510. export default
  511. {
  512. some:'option'
  513. }
  514. </script>
  515. <script setup>
  516. import { x } from './x'
  517. x()
  518. </script>
  519. `)
  520. assertCode(content)
  521. })
  522. test('with minimal spaces', () => {
  523. const { content } = compile(`
  524. <script>
  525. export const n = 1
  526. export default{
  527. some:'option'
  528. }
  529. </script>
  530. <script setup>
  531. import { x } from './x'
  532. x()
  533. </script>
  534. `)
  535. assertCode(content)
  536. })
  537. })
  538. test('export call expression as default', () => {
  539. const { content } = compile(`
  540. <script>
  541. function fn() {
  542. return "hello, world";
  543. }
  544. export default fn();
  545. </script>
  546. <script setup>
  547. console.log('foo')
  548. </script>
  549. `)
  550. assertCode(content)
  551. })
  552. })
  553. describe('imports', () => {
  554. test('should hoist and expose imports', () => {
  555. assertCode(
  556. compile(`<script setup>
  557. import { ref } from 'vue'
  558. import 'foo/css'
  559. </script>`).content
  560. )
  561. })
  562. test('should extract comment for import or type declarations', () => {
  563. assertCode(
  564. compile(`
  565. <script setup>
  566. import a from 'a' // comment
  567. import b from 'b'
  568. </script>
  569. `).content
  570. )
  571. })
  572. // #2740
  573. test('should allow defineProps/Emit at the start of imports', () => {
  574. assertCode(
  575. compile(`<script setup>
  576. import { ref } from 'vue'
  577. defineProps(['foo'])
  578. defineEmits(['bar'])
  579. const r = ref(0)
  580. </script>`).content
  581. )
  582. })
  583. test('dedupe between user & helper', () => {
  584. const { content } = compile(
  585. `
  586. <script setup>
  587. import { ref } from 'vue'
  588. let foo = $ref(1)
  589. </script>
  590. `,
  591. { reactivityTransform: true }
  592. )
  593. assertCode(content)
  594. expect(content).toMatch(`import { ref } from 'vue'`)
  595. })
  596. test('import dedupe between <script> and <script setup>', () => {
  597. const { content } = compile(`
  598. <script>
  599. import { x } from './x'
  600. </script>
  601. <script setup>
  602. import { x } from './x'
  603. x()
  604. </script>
  605. `)
  606. assertCode(content)
  607. expect(content.indexOf(`import { x }`)).toEqual(
  608. content.lastIndexOf(`import { x }`)
  609. )
  610. })
  611. describe('import ref/reactive function from other place', () => {
  612. test('import directly', () => {
  613. const { bindings } = compile(`
  614. <script setup>
  615. import { ref, reactive } from './foo'
  616. const foo = ref(1)
  617. const bar = reactive(1)
  618. </script>
  619. `)
  620. expect(bindings).toStrictEqual({
  621. ref: BindingTypes.SETUP_MAYBE_REF,
  622. reactive: BindingTypes.SETUP_MAYBE_REF,
  623. foo: BindingTypes.SETUP_MAYBE_REF,
  624. bar: BindingTypes.SETUP_MAYBE_REF
  625. })
  626. })
  627. test('import w/ alias', () => {
  628. const { bindings } = compile(`
  629. <script setup>
  630. import { ref as _ref, reactive as _reactive } from './foo'
  631. const foo = ref(1)
  632. const bar = reactive(1)
  633. </script>
  634. `)
  635. expect(bindings).toStrictEqual({
  636. _reactive: BindingTypes.SETUP_MAYBE_REF,
  637. _ref: BindingTypes.SETUP_MAYBE_REF,
  638. foo: BindingTypes.SETUP_MAYBE_REF,
  639. bar: BindingTypes.SETUP_MAYBE_REF
  640. })
  641. })
  642. test('aliased usage before import site', () => {
  643. const { bindings } = compile(`
  644. <script setup>
  645. const bar = x(1)
  646. import { reactive as x } from 'vue'
  647. </script>
  648. `)
  649. expect(bindings).toStrictEqual({
  650. bar: BindingTypes.SETUP_REACTIVE_CONST,
  651. x: BindingTypes.SETUP_CONST
  652. })
  653. })
  654. })
  655. test('should support module string names syntax', () => {
  656. const { content, bindings } = compile(`
  657. <script>
  658. import { "😏" as foo } from './foo'
  659. </script>
  660. <script setup>
  661. import { "😏" as foo } from './foo'
  662. </script>
  663. `)
  664. assertCode(content)
  665. expect(bindings).toStrictEqual({
  666. foo: BindingTypes.SETUP_MAYBE_REF
  667. })
  668. })
  669. })
  670. // in dev mode, declared bindings are returned as an object from setup()
  671. // when using TS, users may import types which should not be returned as
  672. // values, so we need to check import usage in the template to determine
  673. // what to be returned.
  674. describe('dev mode import usage check', () => {
  675. test('components', () => {
  676. const { content } = compile(`
  677. <script setup lang="ts">
  678. import { FooBar, FooBaz, FooQux, foo } from './x'
  679. const fooBar: FooBar = 1
  680. </script>
  681. <template>
  682. <FooBaz></FooBaz>
  683. <foo-qux/>
  684. <foo/>
  685. FooBar
  686. </template>
  687. `)
  688. // FooBar: should not be matched by plain text or incorrect case
  689. // FooBaz: used as PascalCase component
  690. // FooQux: used as kebab-case component
  691. // foo: lowercase component
  692. expect(content).toMatch(
  693. `return { fooBar, get FooBaz() { return FooBaz }, ` +
  694. `get FooQux() { return FooQux }, get foo() { return foo } }`
  695. )
  696. assertCode(content)
  697. })
  698. test('directive', () => {
  699. const { content } = compile(`
  700. <script setup lang="ts">
  701. import { vMyDir } from './x'
  702. </script>
  703. <template>
  704. <div v-my-dir></div>
  705. </template>
  706. `)
  707. expect(content).toMatch(`return { get vMyDir() { return vMyDir } }`)
  708. assertCode(content)
  709. })
  710. // https://github.com/vuejs/core/issues/4599
  711. test('attribute expressions', () => {
  712. const { content } = compile(`
  713. <script setup lang="ts">
  714. import { bar, baz } from './x'
  715. const cond = true
  716. </script>
  717. <template>
  718. <div :class="[cond ? '' : bar(), 'default']" :style="baz"></div>
  719. </template>
  720. `)
  721. expect(content).toMatch(
  722. `return { cond, get bar() { return bar }, get baz() { return baz } }`
  723. )
  724. assertCode(content)
  725. })
  726. test('vue interpolations', () => {
  727. const { content } = compile(`
  728. <script setup lang="ts">
  729. import { x, y, z, x$y } from './x'
  730. </script>
  731. <template>
  732. <div :id="z + 'y'">{{ x }} {{ yy }} {{ x$y }}</div>
  733. </template>
  734. `)
  735. // x: used in interpolation
  736. // y: should not be matched by {{ yy }} or 'y' in binding exps
  737. // x$y: #4274 should escape special chars when creating Regex
  738. expect(content).toMatch(
  739. `return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }`
  740. )
  741. assertCode(content)
  742. })
  743. // #4340 interpolations in template strings
  744. test('js template string interpolations', () => {
  745. const { content } = compile(`
  746. <script setup lang="ts">
  747. import { VAR, VAR2, VAR3 } from './x'
  748. </script>
  749. <template>
  750. {{ \`\${VAR}VAR2\${VAR3}\` }}
  751. </template>
  752. `)
  753. // VAR2 should not be matched
  754. expect(content).toMatch(
  755. `return { get VAR() { return VAR }, get VAR3() { return VAR3 } }`
  756. )
  757. assertCode(content)
  758. })
  759. // edge case: last tag in template
  760. test('last tag', () => {
  761. const { content } = compile(`
  762. <script setup lang="ts">
  763. import { FooBaz, Last } from './x'
  764. </script>
  765. <template>
  766. <FooBaz></FooBaz>
  767. <Last/>
  768. </template>
  769. `)
  770. expect(content).toMatch(
  771. `return { get FooBaz() { return FooBaz }, get Last() { return Last } }`
  772. )
  773. assertCode(content)
  774. })
  775. test('TS annotations', () => {
  776. const { content } = compile(`
  777. <script setup lang="ts">
  778. import { Foo, Bar, Baz, Qux, Fred } from './x'
  779. const a = 1
  780. function b() {}
  781. </script>
  782. <template>
  783. {{ a as Foo }}
  784. {{ b<Bar>() }}
  785. {{ Baz }}
  786. <Comp v-slot="{ data }: Qux">{{ data }}</Comp>
  787. <div v-for="{ z = x as Qux } in list as Fred"/>
  788. </template>
  789. `)
  790. expect(content).toMatch(`return { a, b, get Baz() { return Baz } }`)
  791. assertCode(content)
  792. })
  793. // vuejs/vue#12591
  794. test('v-on inline statement', () => {
  795. // should not error
  796. compile(`
  797. <script setup lang="ts">
  798. import { foo } from './foo'
  799. </script>
  800. <template>
  801. <div @click="$emit('update:a');"></div>
  802. </template>
  803. `)
  804. })
  805. })
  806. describe('inlineTemplate mode', () => {
  807. test('should work', () => {
  808. const { content } = compile(
  809. `
  810. <script setup>
  811. import { ref } from 'vue'
  812. const count = ref(0)
  813. </script>
  814. <template>
  815. <div>{{ count }}</div>
  816. <div>static</div>
  817. </template>
  818. `,
  819. { inlineTemplate: true }
  820. )
  821. // check snapshot and make sure helper imports and
  822. // hoists are placed correctly.
  823. assertCode(content)
  824. // in inline mode, no need to call expose() since nothing is exposed
  825. // anyway!
  826. expect(content).not.toMatch(`expose()`)
  827. })
  828. test('with defineExpose()', () => {
  829. const { content } = compile(
  830. `
  831. <script setup>
  832. const count = ref(0)
  833. defineExpose({ count })
  834. </script>
  835. `,
  836. { inlineTemplate: true }
  837. )
  838. assertCode(content)
  839. expect(content).toMatch(`setup(__props, { expose: __expose })`)
  840. expect(content).toMatch(`expose({ count })`)
  841. })
  842. test('referencing scope components and directives', () => {
  843. const { content } = compile(
  844. `
  845. <script setup>
  846. import ChildComp from './Child.vue'
  847. import SomeOtherComp from './Other.vue'
  848. import vMyDir from './my-dir'
  849. </script>
  850. <template>
  851. <div v-my-dir></div>
  852. <ChildComp/>
  853. <some-other-comp/>
  854. </template>
  855. `,
  856. { inlineTemplate: true }
  857. )
  858. expect(content).toMatch('[_unref(vMyDir)]')
  859. expect(content).toMatch('_createVNode(ChildComp)')
  860. // kebab-case component support
  861. expect(content).toMatch('_createVNode(SomeOtherComp)')
  862. assertCode(content)
  863. })
  864. test('avoid unref() when necessary', () => {
  865. // function, const, component import
  866. const { content } = compile(
  867. `<script setup>
  868. import { ref } from 'vue'
  869. import Foo, { bar } from './Foo.vue'
  870. import other from './util'
  871. import * as tree from './tree'
  872. const count = ref(0)
  873. const constant = {}
  874. const maybe = foo()
  875. let lett = 1
  876. function fn() {}
  877. </script>
  878. <template>
  879. <Foo>{{ bar }}</Foo>
  880. <div @click="fn">{{ count }} {{ constant }} {{ maybe }} {{ lett }} {{ other }}</div>
  881. {{ tree.foo() }}
  882. </template>
  883. `,
  884. { inlineTemplate: true }
  885. )
  886. // no need to unref vue component import
  887. expect(content).toMatch(`createVNode(Foo,`)
  888. // #2699 should unref named imports from .vue
  889. expect(content).toMatch(`unref(bar)`)
  890. // should unref other imports
  891. expect(content).toMatch(`unref(other)`)
  892. // no need to unref constant literals
  893. expect(content).not.toMatch(`unref(constant)`)
  894. // should directly use .value for known refs
  895. expect(content).toMatch(`count.value`)
  896. // should unref() on const bindings that may be refs
  897. expect(content).toMatch(`unref(maybe)`)
  898. // should unref() on let bindings
  899. expect(content).toMatch(`unref(lett)`)
  900. // no need to unref namespace import (this also preserves tree-shaking)
  901. expect(content).toMatch(`tree.foo()`)
  902. // no need to unref function declarations
  903. expect(content).toMatch(`{ onClick: fn }`)
  904. // no need to mark constant fns in patch flag
  905. expect(content).not.toMatch(`PROPS`)
  906. assertCode(content)
  907. })
  908. test('v-model codegen', () => {
  909. const { content } = compile(
  910. `<script setup>
  911. import { ref } from 'vue'
  912. const count = ref(0)
  913. const maybe = foo()
  914. let lett = 1
  915. </script>
  916. <template>
  917. <input v-model="count">
  918. <input v-model="maybe">
  919. <input v-model="lett">
  920. </template>
  921. `,
  922. { inlineTemplate: true }
  923. )
  924. // known const ref: set value
  925. expect(content).toMatch(`(count).value = $event`)
  926. // const but maybe ref: assign if ref, otherwise do nothing
  927. expect(content).toMatch(`_isRef(maybe) ? (maybe).value = $event : null`)
  928. // let: handle both cases
  929. expect(content).toMatch(
  930. `_isRef(lett) ? (lett).value = $event : lett = $event`
  931. )
  932. assertCode(content)
  933. })
  934. test('v-model should not generate ref assignment code for non-setup bindings', () => {
  935. const { content } = compile(
  936. `<script setup>
  937. import { ref } from 'vue'
  938. const count = ref(0)
  939. </script>
  940. <script>
  941. export default {
  942. data() { return { foo: 123 } }
  943. }
  944. </script>
  945. <template>
  946. <input v-model="foo">
  947. </template>
  948. `,
  949. { inlineTemplate: true }
  950. )
  951. expect(content).not.toMatch(`_isRef(foo)`)
  952. })
  953. test('template assignment expression codegen', () => {
  954. const { content } = compile(
  955. `<script setup>
  956. import { ref } from 'vue'
  957. const count = ref(0)
  958. const maybe = foo()
  959. let lett = 1
  960. let v = ref(1)
  961. </script>
  962. <template>
  963. <div @click="count = 1"/>
  964. <div @click="maybe = count"/>
  965. <div @click="lett = count"/>
  966. <div @click="v += 1"/>
  967. <div @click="v -= 1"/>
  968. <div @click="() => {
  969. let a = '' + lett
  970. v = a
  971. }"/>
  972. <div @click="() => {
  973. // nested scopes
  974. (()=>{
  975. let x = a
  976. (()=>{
  977. let z = x
  978. let z2 = z
  979. })
  980. let lz = z
  981. })
  982. v = a
  983. }"/>
  984. </template>
  985. `,
  986. { inlineTemplate: true }
  987. )
  988. // known const ref: set value
  989. expect(content).toMatch(`count.value = 1`)
  990. // const but maybe ref: only assign after check
  991. expect(content).toMatch(`maybe.value = count.value`)
  992. // let: handle both cases
  993. expect(content).toMatch(
  994. `_isRef(lett) ? lett.value = count.value : lett = count.value`
  995. )
  996. expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)
  997. expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)
  998. expect(content).toMatch(`_isRef(v) ? v.value = a : v = a`)
  999. expect(content).toMatch(`_isRef(v) ? v.value = _ctx.a : v = _ctx.a`)
  1000. assertCode(content)
  1001. })
  1002. test('template update expression codegen', () => {
  1003. const { content } = compile(
  1004. `<script setup>
  1005. import { ref } from 'vue'
  1006. const count = ref(0)
  1007. const maybe = foo()
  1008. let lett = 1
  1009. </script>
  1010. <template>
  1011. <div @click="count++"/>
  1012. <div @click="--count"/>
  1013. <div @click="maybe++"/>
  1014. <div @click="--maybe"/>
  1015. <div @click="lett++"/>
  1016. <div @click="--lett"/>
  1017. </template>
  1018. `,
  1019. { inlineTemplate: true }
  1020. )
  1021. // known const ref: set value
  1022. expect(content).toMatch(`count.value++`)
  1023. expect(content).toMatch(`--count.value`)
  1024. // const but maybe ref (non-ref case ignored)
  1025. expect(content).toMatch(`maybe.value++`)
  1026. expect(content).toMatch(`--maybe.value`)
  1027. // let: handle both cases
  1028. expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`)
  1029. expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`)
  1030. assertCode(content)
  1031. })
  1032. test('template destructure assignment codegen', () => {
  1033. const { content } = compile(
  1034. `<script setup>
  1035. import { ref } from 'vue'
  1036. const val = {}
  1037. const count = ref(0)
  1038. const maybe = foo()
  1039. let lett = 1
  1040. </script>
  1041. <template>
  1042. <div @click="({ count } = val)"/>
  1043. <div @click="[maybe] = val"/>
  1044. <div @click="({ lett } = val)"/>
  1045. </template>
  1046. `,
  1047. { inlineTemplate: true }
  1048. )
  1049. // known const ref: set value
  1050. expect(content).toMatch(`({ count: count.value } = val)`)
  1051. // const but maybe ref (non-ref case ignored)
  1052. expect(content).toMatch(`[maybe.value] = val`)
  1053. // let: assumes non-ref
  1054. expect(content).toMatch(`{ lett: lett } = val`)
  1055. assertCode(content)
  1056. })
  1057. test('ssr codegen', () => {
  1058. const { content } = compile(
  1059. `
  1060. <script setup>
  1061. import { ref } from 'vue'
  1062. const count = ref(0)
  1063. </script>
  1064. <template>
  1065. <div>{{ count }}</div>
  1066. <div>static</div>
  1067. </template>
  1068. <style>
  1069. div { color: v-bind(count) }
  1070. </style>
  1071. `,
  1072. {
  1073. inlineTemplate: true,
  1074. templateOptions: {
  1075. ssr: true
  1076. }
  1077. }
  1078. )
  1079. expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
  1080. expect(content).toMatch(`return (_ctx, _push`)
  1081. expect(content).toMatch(`ssrInterpolate`)
  1082. expect(content).not.toMatch(`useCssVars`)
  1083. expect(content).toMatch(`"--${mockId}-count": (count.value)`)
  1084. assertCode(content)
  1085. })
  1086. })
  1087. describe('with TypeScript', () => {
  1088. test('hoist type declarations', () => {
  1089. const { content } = compile(`
  1090. <script setup lang="ts">
  1091. export interface Foo {}
  1092. type Bar = {}
  1093. </script>`)
  1094. assertCode(content)
  1095. })
  1096. test('defineProps/Emit w/ runtime options', () => {
  1097. const { content } = compile(`
  1098. <script setup lang="ts">
  1099. const props = defineProps({ foo: String })
  1100. const emit = defineEmits(['a', 'b'])
  1101. </script>
  1102. `)
  1103. assertCode(content)
  1104. expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
  1105. props: { foo: String },
  1106. emits: ['a', 'b'],
  1107. setup(__props, { expose: __expose, emit }) {`)
  1108. })
  1109. test('defineProps w/ type', () => {
  1110. const { content, bindings } = compile(`
  1111. <script setup lang="ts">
  1112. interface Test {}
  1113. type Alias = number[]
  1114. defineProps<{
  1115. string: string
  1116. number: number
  1117. boolean: boolean
  1118. object: object
  1119. objectLiteral: { a: number }
  1120. fn: (n: number) => void
  1121. functionRef: Function
  1122. objectRef: Object
  1123. dateTime: Date
  1124. array: string[]
  1125. arrayRef: Array<any>
  1126. tuple: [number, number]
  1127. set: Set<string>
  1128. literal: 'foo'
  1129. optional?: any
  1130. recordRef: Record<string, null>
  1131. interface: Test
  1132. alias: Alias
  1133. method(): void
  1134. symbol: symbol
  1135. extract: Extract<1 | 2 | boolean, 2>
  1136. exclude: Exclude<1 | 2 | boolean, 2>
  1137. uppercase: Uppercase<'foo'>
  1138. params: Parameters<(foo: any) => void>
  1139. nonNull: NonNullable<string | null>
  1140. objectOrFn: {
  1141. (): void
  1142. foo: string
  1143. }
  1144. union: string | number
  1145. literalUnion: 'foo' | 'bar'
  1146. literalUnionNumber: 1 | 2 | 3 | 4 | 5
  1147. literalUnionMixed: 'foo' | 1 | boolean
  1148. intersection: Test & {}
  1149. intersection2: 'foo' & ('foo' | 'bar')
  1150. foo: ((item: any) => boolean) | null
  1151. unknown: UnknownType
  1152. unknownUnion: UnknownType | string
  1153. unknownIntersection: UnknownType & Object
  1154. unknownUnionWithBoolean: UnknownType | boolean
  1155. unknownUnionWithFunction: UnknownType | (() => any)
  1156. }>()
  1157. </script>`)
  1158. assertCode(content)
  1159. expect(content).toMatch(`string: { type: String, required: true }`)
  1160. expect(content).toMatch(`number: { type: Number, required: true }`)
  1161. expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
  1162. expect(content).toMatch(`object: { type: Object, required: true }`)
  1163. expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
  1164. expect(content).toMatch(`fn: { type: Function, required: true }`)
  1165. expect(content).toMatch(`functionRef: { type: Function, required: true }`)
  1166. expect(content).toMatch(`objectRef: { type: Object, required: true }`)
  1167. expect(content).toMatch(`dateTime: { type: Date, required: true }`)
  1168. expect(content).toMatch(`array: { type: Array, required: true }`)
  1169. expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
  1170. expect(content).toMatch(`tuple: { type: Array, required: true }`)
  1171. expect(content).toMatch(`set: { type: Set, required: true }`)
  1172. expect(content).toMatch(`literal: { type: String, required: true }`)
  1173. expect(content).toMatch(`optional: { type: null, required: false }`)
  1174. expect(content).toMatch(`recordRef: { type: Object, required: true }`)
  1175. expect(content).toMatch(`interface: { type: Object, required: true }`)
  1176. expect(content).toMatch(`alias: { type: Array, required: true }`)
  1177. expect(content).toMatch(`method: { type: Function, required: true }`)
  1178. expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
  1179. expect(content).toMatch(
  1180. `objectOrFn: { type: [Function, Object], required: true },`
  1181. )
  1182. expect(content).toMatch(`extract: { type: Number, required: true }`)
  1183. expect(content).toMatch(
  1184. `exclude: { type: [Number, Boolean], required: true }`
  1185. )
  1186. expect(content).toMatch(`uppercase: { type: String, required: true }`)
  1187. expect(content).toMatch(`params: { type: Array, required: true }`)
  1188. expect(content).toMatch(`nonNull: { type: String, required: true }`)
  1189. expect(content).toMatch(
  1190. `union: { type: [String, Number], required: true }`
  1191. )
  1192. expect(content).toMatch(`literalUnion: { type: String, required: true }`)
  1193. expect(content).toMatch(
  1194. `literalUnionNumber: { type: Number, required: true }`
  1195. )
  1196. expect(content).toMatch(
  1197. `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
  1198. )
  1199. expect(content).toMatch(`intersection: { type: Object, required: true }`)
  1200. expect(content).toMatch(`intersection2: { type: String, required: true }`)
  1201. expect(content).toMatch(`foo: { type: [Function, null], required: true }`)
  1202. expect(content).toMatch(`unknown: { type: null, required: true }`)
  1203. // uninon containing unknown type: skip check
  1204. expect(content).toMatch(`unknownUnion: { type: null, required: true }`)
  1205. // intersection containing unknown type: narrow to the known types
  1206. expect(content).toMatch(
  1207. `unknownIntersection: { type: Object, required: true },`
  1208. )
  1209. expect(content).toMatch(
  1210. `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },`
  1211. )
  1212. expect(content).toMatch(
  1213. `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }`
  1214. )
  1215. expect(bindings).toStrictEqual({
  1216. string: BindingTypes.PROPS,
  1217. number: BindingTypes.PROPS,
  1218. boolean: BindingTypes.PROPS,
  1219. object: BindingTypes.PROPS,
  1220. objectLiteral: BindingTypes.PROPS,
  1221. fn: BindingTypes.PROPS,
  1222. functionRef: BindingTypes.PROPS,
  1223. objectRef: BindingTypes.PROPS,
  1224. dateTime: BindingTypes.PROPS,
  1225. array: BindingTypes.PROPS,
  1226. arrayRef: BindingTypes.PROPS,
  1227. tuple: BindingTypes.PROPS,
  1228. set: BindingTypes.PROPS,
  1229. literal: BindingTypes.PROPS,
  1230. optional: BindingTypes.PROPS,
  1231. recordRef: BindingTypes.PROPS,
  1232. interface: BindingTypes.PROPS,
  1233. alias: BindingTypes.PROPS,
  1234. method: BindingTypes.PROPS,
  1235. symbol: BindingTypes.PROPS,
  1236. objectOrFn: BindingTypes.PROPS,
  1237. extract: BindingTypes.PROPS,
  1238. exclude: BindingTypes.PROPS,
  1239. union: BindingTypes.PROPS,
  1240. literalUnion: BindingTypes.PROPS,
  1241. literalUnionNumber: BindingTypes.PROPS,
  1242. literalUnionMixed: BindingTypes.PROPS,
  1243. intersection: BindingTypes.PROPS,
  1244. intersection2: BindingTypes.PROPS,
  1245. foo: BindingTypes.PROPS,
  1246. uppercase: BindingTypes.PROPS,
  1247. params: BindingTypes.PROPS,
  1248. nonNull: BindingTypes.PROPS,
  1249. unknown: BindingTypes.PROPS,
  1250. unknownUnion: BindingTypes.PROPS,
  1251. unknownIntersection: BindingTypes.PROPS,
  1252. unknownUnionWithBoolean: BindingTypes.PROPS,
  1253. unknownUnionWithFunction: BindingTypes.PROPS
  1254. })
  1255. })
  1256. test('defineProps w/ interface', () => {
  1257. const { content, bindings } = compile(`
  1258. <script setup lang="ts">
  1259. interface Props { x?: number }
  1260. defineProps<Props>()
  1261. </script>
  1262. `)
  1263. assertCode(content)
  1264. expect(content).toMatch(`x: { type: Number, required: false }`)
  1265. expect(bindings).toStrictEqual({
  1266. x: BindingTypes.PROPS
  1267. })
  1268. })
  1269. test('defineProps w/ extends interface', () => {
  1270. const { content, bindings } = compile(`
  1271. <script lang="ts">
  1272. interface Foo { x?: number }
  1273. </script>
  1274. <script setup lang="ts">
  1275. interface Bar extends Foo { y?: number }
  1276. interface Props extends Bar {
  1277. z: number
  1278. y: string
  1279. }
  1280. defineProps<Props>()
  1281. </script>
  1282. `)
  1283. assertCode(content)
  1284. expect(content).toMatch(`z: { type: Number, required: true }`)
  1285. expect(content).toMatch(`y: { type: String, required: true }`)
  1286. expect(content).toMatch(`x: { type: Number, required: false }`)
  1287. expect(bindings).toStrictEqual({
  1288. x: BindingTypes.PROPS,
  1289. y: BindingTypes.PROPS,
  1290. z: BindingTypes.PROPS
  1291. })
  1292. })
  1293. test('defineProps w/ exported interface', () => {
  1294. const { content, bindings } = compile(`
  1295. <script setup lang="ts">
  1296. export interface Props { x?: number }
  1297. defineProps<Props>()
  1298. </script>
  1299. `)
  1300. assertCode(content)
  1301. expect(content).toMatch(`x: { type: Number, required: false }`)
  1302. expect(bindings).toStrictEqual({
  1303. x: BindingTypes.PROPS
  1304. })
  1305. })
  1306. test('defineProps w/ exported interface in normal script', () => {
  1307. const { content, bindings } = compile(`
  1308. <script lang="ts">
  1309. export interface Props { x?: number }
  1310. </script>
  1311. <script setup lang="ts">
  1312. defineProps<Props>()
  1313. </script>
  1314. `)
  1315. assertCode(content)
  1316. expect(content).toMatch(`x: { type: Number, required: false }`)
  1317. expect(bindings).toStrictEqual({
  1318. x: BindingTypes.PROPS
  1319. })
  1320. })
  1321. test('defineProps w/ type alias', () => {
  1322. const { content, bindings } = compile(`
  1323. <script setup lang="ts">
  1324. type Props = { x?: number }
  1325. defineProps<Props>()
  1326. </script>
  1327. `)
  1328. assertCode(content)
  1329. expect(content).toMatch(`x: { type: Number, required: false }`)
  1330. expect(bindings).toStrictEqual({
  1331. x: BindingTypes.PROPS
  1332. })
  1333. })
  1334. test('defineProps w/ exported type alias', () => {
  1335. const { content, bindings } = compile(`
  1336. <script setup lang="ts">
  1337. export type Props = { x?: number }
  1338. defineProps<Props>()
  1339. </script>
  1340. `)
  1341. assertCode(content)
  1342. expect(content).toMatch(`x: { type: Number, required: false }`)
  1343. expect(bindings).toStrictEqual({
  1344. x: BindingTypes.PROPS
  1345. })
  1346. })
  1347. test('defineProps w/ TS assertion', () => {
  1348. const { content, bindings } = compile(`
  1349. <script setup lang="ts">
  1350. defineProps(['foo'])! as any
  1351. </script>
  1352. `)
  1353. expect(content).toMatch(`props: ['foo']`)
  1354. assertCode(content)
  1355. expect(bindings).toStrictEqual({
  1356. foo: BindingTypes.PROPS
  1357. })
  1358. })
  1359. test('withDefaults (static)', () => {
  1360. const { content, bindings } = compile(`
  1361. <script setup lang="ts">
  1362. const props = withDefaults(defineProps<{
  1363. foo?: string
  1364. bar?: number;
  1365. baz: boolean;
  1366. qux?(): number;
  1367. quux?(): void
  1368. quuxx?: Promise<string>;
  1369. fred?: string
  1370. }>(), {
  1371. foo: 'hi',
  1372. qux() { return 1 },
  1373. ['quux']() { },
  1374. async quuxx() { return await Promise.resolve('hi') },
  1375. get fred() { return 'fred' }
  1376. })
  1377. </script>
  1378. `)
  1379. assertCode(content)
  1380. expect(content).toMatch(
  1381. `foo: { type: String, required: false, default: 'hi' }`
  1382. )
  1383. expect(content).toMatch(`bar: { type: Number, required: false }`)
  1384. expect(content).toMatch(`baz: { type: Boolean, required: true }`)
  1385. expect(content).toMatch(
  1386. `qux: { type: Function, required: false, default() { return 1 } }`
  1387. )
  1388. expect(content).toMatch(
  1389. `quux: { type: Function, required: false, default() { } }`
  1390. )
  1391. expect(content).toMatch(
  1392. `quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }`
  1393. )
  1394. expect(content).toMatch(
  1395. `fred: { type: String, required: false, get default() { return 'fred' } }`
  1396. )
  1397. expect(content).toMatch(`const props = __props`)
  1398. expect(bindings).toStrictEqual({
  1399. foo: BindingTypes.PROPS,
  1400. bar: BindingTypes.PROPS,
  1401. baz: BindingTypes.PROPS,
  1402. qux: BindingTypes.PROPS,
  1403. quux: BindingTypes.PROPS,
  1404. quuxx: BindingTypes.PROPS,
  1405. fred: BindingTypes.PROPS,
  1406. props: BindingTypes.SETUP_CONST
  1407. })
  1408. })
  1409. test('withDefaults (static) + normal script', () => {
  1410. const { content } = compile(`
  1411. <script lang="ts">
  1412. interface Props {
  1413. a?: string;
  1414. }
  1415. </script>
  1416. <script setup lang="ts">
  1417. const props = withDefaults(defineProps<Props>(), {
  1418. a: "a",
  1419. });
  1420. </script>
  1421. `)
  1422. assertCode(content)
  1423. })
  1424. // #7111
  1425. test('withDefaults (static) w/ production mode', () => {
  1426. const { content } = compile(
  1427. `
  1428. <script setup lang="ts">
  1429. const props = withDefaults(defineProps<{
  1430. foo: () => void
  1431. bar: boolean
  1432. baz: boolean | (() => void)
  1433. qux: string | number
  1434. }>(), {
  1435. baz: true,
  1436. qux: 'hi'
  1437. })
  1438. </script>
  1439. `,
  1440. { isProd: true }
  1441. )
  1442. assertCode(content)
  1443. expect(content).toMatch(`const props = __props`)
  1444. // foo has no default value, the Function can be dropped
  1445. expect(content).toMatch(`foo: {}`)
  1446. expect(content).toMatch(`bar: { type: Boolean }`)
  1447. expect(content).toMatch(
  1448. `baz: { type: [Boolean, Function], default: true }`
  1449. )
  1450. expect(content).toMatch(`qux: { default: 'hi' }`)
  1451. })
  1452. test('withDefaults (dynamic)', () => {
  1453. const { content } = compile(`
  1454. <script setup lang="ts">
  1455. import { defaults } from './foo'
  1456. const props = withDefaults(defineProps<{
  1457. foo?: string
  1458. bar?: number
  1459. baz: boolean
  1460. }>(), { ...defaults })
  1461. </script>
  1462. `)
  1463. assertCode(content)
  1464. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  1465. expect(content).toMatch(
  1466. `
  1467. _mergeDefaults({
  1468. foo: { type: String, required: false },
  1469. bar: { type: Number, required: false },
  1470. baz: { type: Boolean, required: true }
  1471. }, { ...defaults })`.trim()
  1472. )
  1473. })
  1474. test('withDefaults (reference)', () => {
  1475. const { content } = compile(`
  1476. <script setup lang="ts">
  1477. import { defaults } from './foo'
  1478. const props = withDefaults(defineProps<{
  1479. foo?: string
  1480. bar?: number
  1481. baz: boolean
  1482. }>(), defaults)
  1483. </script>
  1484. `)
  1485. assertCode(content)
  1486. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  1487. expect(content).toMatch(
  1488. `
  1489. _mergeDefaults({
  1490. foo: { type: String, required: false },
  1491. bar: { type: Number, required: false },
  1492. baz: { type: Boolean, required: true }
  1493. }, defaults)`.trim()
  1494. )
  1495. })
  1496. // #7111
  1497. test('withDefaults (dynamic) w/ production mode', () => {
  1498. const { content } = compile(
  1499. `
  1500. <script setup lang="ts">
  1501. import { defaults } from './foo'
  1502. const props = withDefaults(defineProps<{
  1503. foo: () => void
  1504. bar: boolean
  1505. baz: boolean | (() => void)
  1506. qux: string | number
  1507. }>(), { ...defaults })
  1508. </script>
  1509. `,
  1510. { isProd: true }
  1511. )
  1512. assertCode(content)
  1513. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  1514. expect(content).toMatch(
  1515. `
  1516. _mergeDefaults({
  1517. foo: { type: Function },
  1518. bar: { type: Boolean },
  1519. baz: { type: [Boolean, Function] },
  1520. qux: {}
  1521. }, { ...defaults })`.trim()
  1522. )
  1523. })
  1524. test('withDefaults w/ dynamic object method', () => {
  1525. const { content } = compile(`
  1526. <script setup lang="ts">
  1527. const props = withDefaults(defineProps<{
  1528. foo?: () => 'string'
  1529. }>(), {
  1530. ['fo' + 'o']() { return 'foo' }
  1531. })
  1532. </script>
  1533. `)
  1534. assertCode(content)
  1535. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  1536. expect(content).toMatch(
  1537. `
  1538. _mergeDefaults({
  1539. foo: { type: Function, required: false }
  1540. }, {
  1541. ['fo' + 'o']() { return 'foo' }
  1542. })`.trim()
  1543. )
  1544. })
  1545. test('defineEmits w/ type', () => {
  1546. const { content } = compile(`
  1547. <script setup lang="ts">
  1548. const emit = defineEmits<(e: 'foo' | 'bar') => void>()
  1549. </script>
  1550. `)
  1551. assertCode(content)
  1552. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1553. })
  1554. test('defineEmits w/ type (union)', () => {
  1555. const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
  1556. expect(() =>
  1557. compile(`
  1558. <script setup lang="ts">
  1559. const emit = defineEmits<${type}>()
  1560. </script>
  1561. `)
  1562. ).toThrow()
  1563. })
  1564. test('defineEmits w/ type (type literal w/ call signatures)', () => {
  1565. const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
  1566. const { content } = compile(`
  1567. <script setup lang="ts">
  1568. const emit = defineEmits<${type}>()
  1569. </script>
  1570. `)
  1571. assertCode(content)
  1572. expect(content).toMatch(`emits: ["foo", "bar", "baz"]`)
  1573. })
  1574. test('defineEmits w/ type (interface)', () => {
  1575. const { content } = compile(`
  1576. <script setup lang="ts">
  1577. interface Emits { (e: 'foo' | 'bar'): void }
  1578. const emit = defineEmits<Emits>()
  1579. </script>
  1580. `)
  1581. assertCode(content)
  1582. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1583. })
  1584. test('defineEmits w/ type (exported interface)', () => {
  1585. const { content } = compile(`
  1586. <script setup lang="ts">
  1587. export interface Emits { (e: 'foo' | 'bar'): void }
  1588. const emit = defineEmits<Emits>()
  1589. </script>
  1590. `)
  1591. assertCode(content)
  1592. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1593. })
  1594. test('defineEmits w/ type from normal script', () => {
  1595. const { content } = compile(`
  1596. <script lang="ts">
  1597. export interface Emits { (e: 'foo' | 'bar'): void }
  1598. </script>
  1599. <script setup lang="ts">
  1600. const emit = defineEmits<Emits>()
  1601. </script>
  1602. `)
  1603. assertCode(content)
  1604. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1605. })
  1606. test('defineEmits w/ type (type alias)', () => {
  1607. const { content } = compile(`
  1608. <script setup lang="ts">
  1609. type Emits = { (e: 'foo' | 'bar'): void }
  1610. const emit = defineEmits<Emits>()
  1611. </script>
  1612. `)
  1613. assertCode(content)
  1614. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1615. })
  1616. test('defineEmits w/ type (exported type alias)', () => {
  1617. const { content } = compile(`
  1618. <script setup lang="ts">
  1619. export type Emits = { (e: 'foo' | 'bar'): void }
  1620. const emit = defineEmits<Emits>()
  1621. </script>
  1622. `)
  1623. assertCode(content)
  1624. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1625. })
  1626. test('defineEmits w/ type (referenced function type)', () => {
  1627. const { content } = compile(`
  1628. <script setup lang="ts">
  1629. type Emits = (e: 'foo' | 'bar') => void
  1630. const emit = defineEmits<Emits>()
  1631. </script>
  1632. `)
  1633. assertCode(content)
  1634. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1635. })
  1636. test('defineEmits w/ type (referenced exported function type)', () => {
  1637. const { content } = compile(`
  1638. <script setup lang="ts">
  1639. export type Emits = (e: 'foo' | 'bar') => void
  1640. const emit = defineEmits<Emits>()
  1641. </script>
  1642. `)
  1643. assertCode(content)
  1644. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1645. })
  1646. // #5393
  1647. test('defineEmits w/ type (interface ts type)', () => {
  1648. const { content } = compile(`
  1649. <script setup lang="ts">
  1650. interface Emits { (e: 'foo'): void }
  1651. const emit: Emits = defineEmits(['foo'])
  1652. </script>
  1653. `)
  1654. assertCode(content)
  1655. expect(content).toMatch(`emits: ['foo']`)
  1656. })
  1657. test('defineEmits w/ type (property syntax)', () => {
  1658. const { content } = compile(`
  1659. <script setup lang="ts">
  1660. const emit = defineEmits<{ foo: [], bar: [] }>()
  1661. </script>
  1662. `)
  1663. expect(content).toMatch(`emits: ["foo", "bar"]`)
  1664. assertCode(content)
  1665. })
  1666. // #8040
  1667. test('defineEmits w/ type (property syntax string literal)', () => {
  1668. const { content } = compile(`
  1669. <script setup lang="ts">
  1670. const emit = defineEmits<{ 'foo:bar': [] }>()
  1671. </script>
  1672. `)
  1673. expect(content).toMatch(`emits: ["foo:bar"]`)
  1674. assertCode(content)
  1675. })
  1676. describe('defineSlots()', () => {
  1677. test('basic usage', () => {
  1678. const { content } = compile(`
  1679. <script setup lang="ts">
  1680. const slots = defineSlots<{
  1681. default: { msg: string }
  1682. }>()
  1683. </script>
  1684. `)
  1685. assertCode(content)
  1686. expect(content).toMatch(`const slots = _useSlots()`)
  1687. expect(content).not.toMatch('defineSlots')
  1688. })
  1689. test('w/o return value', () => {
  1690. const { content } = compile(`
  1691. <script setup lang="ts">
  1692. defineSlots<{
  1693. default: { msg: string }
  1694. }>()
  1695. </script>
  1696. `)
  1697. assertCode(content)
  1698. expect(content).not.toMatch('defineSlots')
  1699. expect(content).not.toMatch(`_useSlots`)
  1700. })
  1701. test('w/o generic params', () => {
  1702. const { content } = compile(`
  1703. <script setup>
  1704. const slots = defineSlots()
  1705. </script>
  1706. `)
  1707. assertCode(content)
  1708. expect(content).toMatch(`const slots = _useSlots()`)
  1709. expect(content).not.toMatch('defineSlots')
  1710. })
  1711. })
  1712. describe('defineModel()', () => {
  1713. test('basic usage', () => {
  1714. const { content, bindings } = compile(
  1715. `
  1716. <script setup lang="ts">
  1717. const modelValue = defineModel<boolean | string>()
  1718. const count = defineModel<number>('count')
  1719. const disabled = defineModel<number>('disabled', { required: false })
  1720. const any = defineModel<any | boolean>('any')
  1721. </script>
  1722. `,
  1723. { defineModel: true }
  1724. )
  1725. assertCode(content)
  1726. expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
  1727. expect(content).toMatch('"count": { type: Number }')
  1728. expect(content).toMatch(
  1729. '"disabled": { type: Number, ...{ required: false } }'
  1730. )
  1731. expect(content).toMatch('"any": { type: Boolean, skipCheck: true }')
  1732. expect(content).toMatch(
  1733. 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]'
  1734. )
  1735. expect(content).toMatch(
  1736. `const modelValue = _useModel(__props, "modelValue")`
  1737. )
  1738. expect(content).toMatch(`const count = _useModel(__props, "count")`)
  1739. expect(content).toMatch(
  1740. `const disabled = _useModel(__props, "disabled")`
  1741. )
  1742. expect(content).toMatch(`const any = _useModel(__props, "any")`)
  1743. expect(bindings).toStrictEqual({
  1744. modelValue: BindingTypes.SETUP_REF,
  1745. count: BindingTypes.SETUP_REF,
  1746. disabled: BindingTypes.SETUP_REF,
  1747. any: BindingTypes.SETUP_REF
  1748. })
  1749. })
  1750. test('w/ production mode', () => {
  1751. const { content, bindings } = compile(
  1752. `
  1753. <script setup lang="ts">
  1754. const modelValue = defineModel<boolean>()
  1755. const fn = defineModel<() => void>('fn')
  1756. const fnWithDefault = defineModel<() => void>('fnWithDefault', { default: () => null })
  1757. const str = defineModel<string>('str')
  1758. const optional = defineModel<string>('optional', { required: false })
  1759. </script>
  1760. `,
  1761. { defineModel: true, isProd: true }
  1762. )
  1763. assertCode(content)
  1764. expect(content).toMatch('"modelValue": { type: Boolean }')
  1765. expect(content).toMatch('"fn": {}')
  1766. expect(content).toMatch(
  1767. '"fnWithDefault": { type: Function, ...{ default: () => null } },'
  1768. )
  1769. expect(content).toMatch('"str": {}')
  1770. expect(content).toMatch('"optional": { required: false }')
  1771. expect(content).toMatch(
  1772. 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]'
  1773. )
  1774. expect(content).toMatch(
  1775. `const modelValue = _useModel(__props, "modelValue")`
  1776. )
  1777. expect(content).toMatch(`const fn = _useModel(__props, "fn")`)
  1778. expect(content).toMatch(`const str = _useModel(__props, "str")`)
  1779. expect(bindings).toStrictEqual({
  1780. modelValue: BindingTypes.SETUP_REF,
  1781. fn: BindingTypes.SETUP_REF,
  1782. fnWithDefault: BindingTypes.SETUP_REF,
  1783. str: BindingTypes.SETUP_REF,
  1784. optional: BindingTypes.SETUP_REF
  1785. })
  1786. })
  1787. })
  1788. test('runtime Enum', () => {
  1789. const { content, bindings } = compile(
  1790. `<script setup lang="ts">
  1791. enum Foo { A = 123 }
  1792. </script>`
  1793. )
  1794. assertCode(content)
  1795. expect(bindings).toStrictEqual({
  1796. Foo: BindingTypes.LITERAL_CONST
  1797. })
  1798. })
  1799. test('runtime Enum in normal script', () => {
  1800. const { content, bindings } = compile(
  1801. `<script lang="ts">
  1802. export enum D { D = "D" }
  1803. const enum C { C = "C" }
  1804. enum B { B = "B" }
  1805. </script>
  1806. <script setup lang="ts">
  1807. enum Foo { A = 123 }
  1808. </script>`
  1809. )
  1810. assertCode(content)
  1811. expect(bindings).toStrictEqual({
  1812. D: BindingTypes.LITERAL_CONST,
  1813. C: BindingTypes.LITERAL_CONST,
  1814. B: BindingTypes.LITERAL_CONST,
  1815. Foo: BindingTypes.LITERAL_CONST
  1816. })
  1817. })
  1818. test('const Enum', () => {
  1819. const { content, bindings } = compile(
  1820. `<script setup lang="ts">
  1821. const enum Foo { A = 123 }
  1822. </script>`,
  1823. { hoistStatic: true }
  1824. )
  1825. assertCode(content)
  1826. expect(bindings).toStrictEqual({
  1827. Foo: BindingTypes.LITERAL_CONST
  1828. })
  1829. })
  1830. test('runtime inference for Enum in defineProps', () => {
  1831. expect(
  1832. compile(
  1833. `<script setup lang="ts">
  1834. const enum Foo { A = 123 }
  1835. defineProps<{
  1836. foo: Foo
  1837. }>()
  1838. </script>`,
  1839. { hoistStatic: true }
  1840. ).content
  1841. ).toMatch(`foo: { type: Number`)
  1842. expect(
  1843. compile(
  1844. `<script setup lang="ts">
  1845. const enum Foo { A = '123' }
  1846. defineProps<{
  1847. foo: Foo
  1848. }>()
  1849. </script>`,
  1850. { hoistStatic: true }
  1851. ).content
  1852. ).toMatch(`foo: { type: String`)
  1853. expect(
  1854. compile(
  1855. `<script setup lang="ts">
  1856. const enum Foo { A = '123', B = 123 }
  1857. defineProps<{
  1858. foo: Foo
  1859. }>()
  1860. </script>`,
  1861. { hoistStatic: true }
  1862. ).content
  1863. ).toMatch(`foo: { type: [String, Number]`)
  1864. expect(
  1865. compile(
  1866. `<script setup lang="ts">
  1867. const enum Foo { A, B }
  1868. defineProps<{
  1869. foo: Foo
  1870. }>()
  1871. </script>`,
  1872. { hoistStatic: true }
  1873. ).content
  1874. ).toMatch(`foo: { type: Number`)
  1875. })
  1876. test('import type', () => {
  1877. const { content } = compile(
  1878. `<script setup lang="ts">
  1879. import type { Foo } from './main.ts'
  1880. import { type Bar, Baz } from './main.ts'
  1881. </script>`
  1882. )
  1883. expect(content).toMatch(`return { get Baz() { return Baz } }`)
  1884. assertCode(content)
  1885. })
  1886. })
  1887. describe('async/await detection', () => {
  1888. function assertAwaitDetection(code: string, shouldAsync = true) {
  1889. const { content } = compile(`<script setup>${code}</script>`, {
  1890. reactivityTransform: true
  1891. })
  1892. if (shouldAsync) {
  1893. expect(content).toMatch(`let __temp, __restore`)
  1894. }
  1895. expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
  1896. assertCode(content)
  1897. return content
  1898. }
  1899. test('expression statement', () => {
  1900. assertAwaitDetection(`await foo`)
  1901. })
  1902. test('variable', () => {
  1903. assertAwaitDetection(`const a = 1 + (await foo)`)
  1904. })
  1905. test('ref', () => {
  1906. assertAwaitDetection(`let a = $ref(1 + (await foo))`)
  1907. })
  1908. // #4448
  1909. test('nested await', () => {
  1910. assertAwaitDetection(`await (await foo)`)
  1911. assertAwaitDetection(`await ((await foo))`)
  1912. assertAwaitDetection(`await (await (await foo))`)
  1913. })
  1914. // should prepend semicolon
  1915. test('nested leading await in expression statement', () => {
  1916. const code = assertAwaitDetection(`foo()\nawait 1 + await 2`)
  1917. expect(code).toMatch(`foo()\n;(`)
  1918. })
  1919. // #4596 should NOT prepend semicolon
  1920. test('single line conditions', () => {
  1921. const code = assertAwaitDetection(`if (false) await foo()`)
  1922. expect(code).not.toMatch(`if (false) ;(`)
  1923. })
  1924. test('nested statements', () => {
  1925. assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
  1926. })
  1927. test('multiple `if` nested statements', () => {
  1928. assertAwaitDetection(`if (ok) {
  1929. let a = 'foo'
  1930. await 0 + await 1
  1931. await 2
  1932. } else if (a) {
  1933. await 10
  1934. if (b) {
  1935. await 0 + await 1
  1936. } else {
  1937. let a = 'foo'
  1938. await 2
  1939. }
  1940. if (b) {
  1941. await 3
  1942. await 4
  1943. }
  1944. } else {
  1945. await 5
  1946. }`)
  1947. })
  1948. test('multiple `if while` nested statements', () => {
  1949. assertAwaitDetection(`if (ok) {
  1950. while (d) {
  1951. await 5
  1952. }
  1953. while (d) {
  1954. await 5
  1955. await 6
  1956. if (c) {
  1957. let f = 10
  1958. 10 + await 7
  1959. } else {
  1960. await 8
  1961. await 9
  1962. }
  1963. }
  1964. }`)
  1965. })
  1966. test('multiple `if for` nested statements', () => {
  1967. assertAwaitDetection(`if (ok) {
  1968. for (let a of [1,2,3]) {
  1969. await a
  1970. }
  1971. for (let a of [1,2,3]) {
  1972. await a
  1973. await a
  1974. }
  1975. }`)
  1976. })
  1977. test('should ignore await inside functions', () => {
  1978. // function declaration
  1979. assertAwaitDetection(`async function foo() { await bar }`, false)
  1980. // function expression
  1981. assertAwaitDetection(`const foo = async () => { await bar }`, false)
  1982. // object method
  1983. assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
  1984. // class method
  1985. assertAwaitDetection(
  1986. `const cls = class Foo { async method() { await bar }}`,
  1987. false
  1988. )
  1989. })
  1990. })
  1991. describe('errors', () => {
  1992. test('<script> and <script setup> must have same lang', () => {
  1993. expect(() =>
  1994. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`)
  1995. ).toThrow(`<script> and <script setup> must have the same language type`)
  1996. })
  1997. const moduleErrorMsg = `cannot contain ES module exports`
  1998. test('non-type named exports', () => {
  1999. expect(() =>
  2000. compile(`<script setup>
  2001. export const a = 1
  2002. </script>`)
  2003. ).toThrow(moduleErrorMsg)
  2004. expect(() =>
  2005. compile(`<script setup>
  2006. export * from './foo'
  2007. </script>`)
  2008. ).toThrow(moduleErrorMsg)
  2009. expect(() =>
  2010. compile(`<script setup>
  2011. const bar = 1
  2012. export { bar as default }
  2013. </script>`)
  2014. ).toThrow(moduleErrorMsg)
  2015. })
  2016. test('defineProps/Emit() w/ both type and non-type args', () => {
  2017. expect(() => {
  2018. compile(`<script setup lang="ts">
  2019. defineProps<{}>({})
  2020. </script>`)
  2021. }).toThrow(`cannot accept both type and non-type arguments`)
  2022. expect(() => {
  2023. compile(`<script setup lang="ts">
  2024. defineEmits<{}>({})
  2025. </script>`)
  2026. }).toThrow(`cannot accept both type and non-type arguments`)
  2027. })
  2028. test('defineProps/Emit() referencing local var', () => {
  2029. expect(() =>
  2030. compile(`<script setup>
  2031. let bar = 1
  2032. defineProps({
  2033. foo: {
  2034. default: () => bar
  2035. }
  2036. })
  2037. </script>`)
  2038. ).toThrow(`cannot reference locally declared variables`)
  2039. expect(() =>
  2040. compile(`<script setup>
  2041. let bar = 'hello'
  2042. defineEmits([bar])
  2043. </script>`)
  2044. ).toThrow(`cannot reference locally declared variables`)
  2045. // #4644
  2046. expect(() =>
  2047. compile(`
  2048. <script>const bar = 1</script>
  2049. <script setup>
  2050. defineProps({
  2051. foo: {
  2052. default: () => bar
  2053. }
  2054. })
  2055. </script>`)
  2056. ).not.toThrow(`cannot reference locally declared variables`)
  2057. })
  2058. test('should allow defineProps/Emit() referencing scope var', () => {
  2059. assertCode(
  2060. compile(`<script setup>
  2061. const bar = 1
  2062. defineProps({
  2063. foo: {
  2064. default: bar => bar + 1
  2065. }
  2066. })
  2067. defineEmits({
  2068. foo: bar => bar > 1
  2069. })
  2070. </script>`).content
  2071. )
  2072. })
  2073. test('should allow defineProps/Emit() referencing imported binding', () => {
  2074. assertCode(
  2075. compile(`<script setup>
  2076. import { bar } from './bar'
  2077. defineProps({
  2078. foo: {
  2079. default: () => bar
  2080. }
  2081. })
  2082. defineEmits({
  2083. foo: () => bar > 1
  2084. })
  2085. </script>`).content
  2086. )
  2087. })
  2088. test('mixed usage of property / call signature in defineEmits', () => {
  2089. expect(() =>
  2090. compile(`<script setup lang="ts">
  2091. defineEmits<{
  2092. foo: []
  2093. (e: 'hi'): void
  2094. }>()
  2095. </script>`)
  2096. ).toThrow(
  2097. `defineEmits() type cannot mixed call signature and property syntax.`
  2098. )
  2099. })
  2100. })
  2101. })
  2102. describe('SFC analyze <script> bindings', () => {
  2103. it('can parse decorators syntax in typescript block', () => {
  2104. const { scriptAst } = compile(`
  2105. <script lang="ts">
  2106. import { Options, Vue } from 'vue-class-component';
  2107. @Options({
  2108. components: {
  2109. HelloWorld,
  2110. },
  2111. props: ['foo', 'bar']
  2112. })
  2113. export default class Home extends Vue {}
  2114. </script>
  2115. `)
  2116. expect(scriptAst).toBeDefined()
  2117. })
  2118. it('recognizes props array declaration', () => {
  2119. const { bindings } = compile(`
  2120. <script>
  2121. export default {
  2122. props: ['foo', 'bar']
  2123. }
  2124. </script>
  2125. `)
  2126. expect(bindings).toStrictEqual({
  2127. foo: BindingTypes.PROPS,
  2128. bar: BindingTypes.PROPS
  2129. })
  2130. expect(bindings!.__isScriptSetup).toBe(false)
  2131. })
  2132. it('recognizes props object declaration', () => {
  2133. const { bindings } = compile(`
  2134. <script>
  2135. export default {
  2136. props: {
  2137. foo: String,
  2138. bar: {
  2139. type: String,
  2140. },
  2141. baz: null,
  2142. qux: [String, Number]
  2143. }
  2144. }
  2145. </script>
  2146. `)
  2147. expect(bindings).toStrictEqual({
  2148. foo: BindingTypes.PROPS,
  2149. bar: BindingTypes.PROPS,
  2150. baz: BindingTypes.PROPS,
  2151. qux: BindingTypes.PROPS
  2152. })
  2153. expect(bindings!.__isScriptSetup).toBe(false)
  2154. })
  2155. it('recognizes setup return', () => {
  2156. const { bindings } = compile(`
  2157. <script>
  2158. const bar = 2
  2159. export default {
  2160. setup() {
  2161. return {
  2162. foo: 1,
  2163. bar
  2164. }
  2165. }
  2166. }
  2167. </script>
  2168. `)
  2169. expect(bindings).toStrictEqual({
  2170. foo: BindingTypes.SETUP_MAYBE_REF,
  2171. bar: BindingTypes.SETUP_MAYBE_REF
  2172. })
  2173. expect(bindings!.__isScriptSetup).toBe(false)
  2174. })
  2175. it('recognizes exported vars', () => {
  2176. const { bindings } = compile(`
  2177. <script>
  2178. export const foo = 2
  2179. </script>
  2180. <script setup>
  2181. console.log(foo)
  2182. </script>
  2183. `)
  2184. expect(bindings).toStrictEqual({
  2185. foo: BindingTypes.LITERAL_CONST
  2186. })
  2187. })
  2188. it('recognizes async setup return', () => {
  2189. const { bindings } = compile(`
  2190. <script>
  2191. const bar = 2
  2192. export default {
  2193. async setup() {
  2194. return {
  2195. foo: 1,
  2196. bar
  2197. }
  2198. }
  2199. }
  2200. </script>
  2201. `)
  2202. expect(bindings).toStrictEqual({
  2203. foo: BindingTypes.SETUP_MAYBE_REF,
  2204. bar: BindingTypes.SETUP_MAYBE_REF
  2205. })
  2206. expect(bindings!.__isScriptSetup).toBe(false)
  2207. })
  2208. it('recognizes data return', () => {
  2209. const { bindings } = compile(`
  2210. <script>
  2211. const bar = 2
  2212. export default {
  2213. data() {
  2214. return {
  2215. foo: null,
  2216. bar
  2217. }
  2218. }
  2219. }
  2220. </script>
  2221. `)
  2222. expect(bindings).toStrictEqual({
  2223. foo: BindingTypes.DATA,
  2224. bar: BindingTypes.DATA
  2225. })
  2226. })
  2227. it('recognizes methods', () => {
  2228. const { bindings } = compile(`
  2229. <script>
  2230. export default {
  2231. methods: {
  2232. foo() {}
  2233. }
  2234. }
  2235. </script>
  2236. `)
  2237. expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })
  2238. })
  2239. it('recognizes computeds', () => {
  2240. const { bindings } = compile(`
  2241. <script>
  2242. export default {
  2243. computed: {
  2244. foo() {},
  2245. bar: {
  2246. get() {},
  2247. set() {},
  2248. }
  2249. }
  2250. }
  2251. </script>
  2252. `)
  2253. expect(bindings).toStrictEqual({
  2254. foo: BindingTypes.OPTIONS,
  2255. bar: BindingTypes.OPTIONS
  2256. })
  2257. })
  2258. it('recognizes injections array declaration', () => {
  2259. const { bindings } = compile(`
  2260. <script>
  2261. export default {
  2262. inject: ['foo', 'bar']
  2263. }
  2264. </script>
  2265. `)
  2266. expect(bindings).toStrictEqual({
  2267. foo: BindingTypes.OPTIONS,
  2268. bar: BindingTypes.OPTIONS
  2269. })
  2270. })
  2271. it('recognizes injections object declaration', () => {
  2272. const { bindings } = compile(`
  2273. <script>
  2274. export default {
  2275. inject: {
  2276. foo: {},
  2277. bar: {},
  2278. }
  2279. }
  2280. </script>
  2281. `)
  2282. expect(bindings).toStrictEqual({
  2283. foo: BindingTypes.OPTIONS,
  2284. bar: BindingTypes.OPTIONS
  2285. })
  2286. })
  2287. it('works for mixed bindings', () => {
  2288. const { bindings } = compile(`
  2289. <script>
  2290. export default {
  2291. inject: ['foo'],
  2292. props: {
  2293. bar: String,
  2294. },
  2295. setup() {
  2296. return {
  2297. baz: null,
  2298. }
  2299. },
  2300. data() {
  2301. return {
  2302. qux: null
  2303. }
  2304. },
  2305. methods: {
  2306. quux() {}
  2307. },
  2308. computed: {
  2309. quuz() {}
  2310. }
  2311. }
  2312. </script>
  2313. `)
  2314. expect(bindings).toStrictEqual({
  2315. foo: BindingTypes.OPTIONS,
  2316. bar: BindingTypes.PROPS,
  2317. baz: BindingTypes.SETUP_MAYBE_REF,
  2318. qux: BindingTypes.DATA,
  2319. quux: BindingTypes.OPTIONS,
  2320. quuz: BindingTypes.OPTIONS
  2321. })
  2322. })
  2323. it('works for script setup', () => {
  2324. const { bindings } = compile(`
  2325. <script setup>
  2326. import { ref as r } from 'vue'
  2327. defineProps({
  2328. foo: String
  2329. })
  2330. const a = r(1)
  2331. let b = 2
  2332. const c = 3
  2333. const { d } = someFoo()
  2334. let { e } = someBar()
  2335. </script>
  2336. `)
  2337. expect(bindings).toStrictEqual({
  2338. r: BindingTypes.SETUP_CONST,
  2339. a: BindingTypes.SETUP_REF,
  2340. b: BindingTypes.SETUP_LET,
  2341. c: BindingTypes.LITERAL_CONST,
  2342. d: BindingTypes.SETUP_MAYBE_REF,
  2343. e: BindingTypes.SETUP_LET,
  2344. foo: BindingTypes.PROPS
  2345. })
  2346. })
  2347. describe('auto name inference', () => {
  2348. test('basic', () => {
  2349. const { content } = compile(
  2350. `<script setup>const a = 1</script>
  2351. <template>{{ a }}</template>`,
  2352. undefined,
  2353. {
  2354. filename: 'FooBar.vue'
  2355. }
  2356. )
  2357. expect(content).toMatch(`export default {
  2358. __name: 'FooBar'`)
  2359. assertCode(content)
  2360. })
  2361. test('do not overwrite manual name (object)', () => {
  2362. const { content } = compile(
  2363. `<script>
  2364. export default {
  2365. name: 'Baz'
  2366. }
  2367. </script>
  2368. <script setup>const a = 1</script>
  2369. <template>{{ a }}</template>`,
  2370. undefined,
  2371. {
  2372. filename: 'FooBar.vue'
  2373. }
  2374. )
  2375. expect(content).not.toMatch(`name: 'FooBar'`)
  2376. expect(content).toMatch(`name: 'Baz'`)
  2377. assertCode(content)
  2378. })
  2379. test('do not overwrite manual name (call)', () => {
  2380. const { content } = compile(
  2381. `<script>
  2382. import { defineComponent } from 'vue'
  2383. export default defineComponent({
  2384. name: 'Baz'
  2385. })
  2386. </script>
  2387. <script setup>const a = 1</script>
  2388. <template>{{ a }}</template>`,
  2389. undefined,
  2390. {
  2391. filename: 'FooBar.vue'
  2392. }
  2393. )
  2394. expect(content).not.toMatch(`name: 'FooBar'`)
  2395. expect(content).toMatch(`name: 'Baz'`)
  2396. assertCode(content)
  2397. })
  2398. })
  2399. })
  2400. describe('SFC genDefaultAs', () => {
  2401. test('normal <script> only', () => {
  2402. const { content } = compile(
  2403. `<script>
  2404. export default {}
  2405. </script>`,
  2406. {
  2407. genDefaultAs: '_sfc_'
  2408. }
  2409. )
  2410. expect(content).not.toMatch('export default')
  2411. expect(content).toMatch(`const _sfc_ = {}`)
  2412. assertCode(content)
  2413. })
  2414. test('normal <script> w/ cssVars', () => {
  2415. const { content } = compile(
  2416. `<script>
  2417. export default {}
  2418. </script>
  2419. <style>
  2420. .foo { color: v-bind(x) }
  2421. </style>`,
  2422. {
  2423. genDefaultAs: '_sfc_'
  2424. }
  2425. )
  2426. expect(content).not.toMatch('export default')
  2427. expect(content).not.toMatch('__default__')
  2428. expect(content).toMatch(`const _sfc_ = {}`)
  2429. assertCode(content)
  2430. })
  2431. test('<script> + <script setup>', () => {
  2432. const { content } = compile(
  2433. `<script>
  2434. export default {}
  2435. </script>
  2436. <script setup>
  2437. const a = 1
  2438. </script>`,
  2439. {
  2440. genDefaultAs: '_sfc_'
  2441. }
  2442. )
  2443. expect(content).not.toMatch('export default')
  2444. expect(content).toMatch(
  2445. `const _sfc_ = /*#__PURE__*/Object.assign(__default__`
  2446. )
  2447. assertCode(content)
  2448. })
  2449. test('<script> + <script setup>', () => {
  2450. const { content } = compile(
  2451. `<script>
  2452. export default {}
  2453. </script>
  2454. <script setup>
  2455. const a = 1
  2456. </script>`,
  2457. {
  2458. genDefaultAs: '_sfc_'
  2459. }
  2460. )
  2461. expect(content).not.toMatch('export default')
  2462. expect(content).toMatch(
  2463. `const _sfc_ = /*#__PURE__*/Object.assign(__default__`
  2464. )
  2465. assertCode(content)
  2466. })
  2467. test('<script setup> only', () => {
  2468. const { content } = compile(
  2469. `<script setup>
  2470. const a = 1
  2471. </script>`,
  2472. {
  2473. genDefaultAs: '_sfc_'
  2474. }
  2475. )
  2476. expect(content).not.toMatch('export default')
  2477. expect(content).toMatch(`const _sfc_ = {\n setup`)
  2478. assertCode(content)
  2479. })
  2480. test('<script setup> only w/ ts', () => {
  2481. const { content } = compile(
  2482. `<script setup lang="ts">
  2483. const a = 1
  2484. </script>`,
  2485. {
  2486. genDefaultAs: '_sfc_'
  2487. }
  2488. )
  2489. expect(content).not.toMatch('export default')
  2490. expect(content).toMatch(`const _sfc_ = /*#__PURE__*/_defineComponent(`)
  2491. assertCode(content)
  2492. })
  2493. test('<script> + <script setup> w/ ts', () => {
  2494. const { content } = compile(
  2495. `<script lang="ts">
  2496. export default {}
  2497. </script>
  2498. <script setup lang="ts">
  2499. const a = 1
  2500. </script>`,
  2501. {
  2502. genDefaultAs: '_sfc_'
  2503. }
  2504. )
  2505. expect(content).not.toMatch('export default')
  2506. expect(content).toMatch(
  2507. `const _sfc_ = /*#__PURE__*/_defineComponent({\n ...__default__`
  2508. )
  2509. assertCode(content)
  2510. })
  2511. })