compileScript.spec.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. import { parse, SFCScriptCompileOptions, compileScript } from '../src'
  2. import { parse as babelParse } from '@babel/parser'
  3. import { babelParserDefaultPlugins } from '@vue/shared'
  4. function compile(src: string, options?: SFCScriptCompileOptions) {
  5. const { descriptor } = parse(src)
  6. return compileScript(descriptor, options)
  7. }
  8. function assertCode(code: string) {
  9. // parse the generated code to make sure it is valid
  10. try {
  11. babelParse(code, {
  12. sourceType: 'module',
  13. plugins: [...babelParserDefaultPlugins, 'typescript']
  14. })
  15. } catch (e) {
  16. console.log(code)
  17. throw e
  18. }
  19. expect(code).toMatchSnapshot()
  20. }
  21. describe('SFC compile <script setup>', () => {
  22. test('should expose top level declarations', () => {
  23. const { content } = compile(`
  24. <script setup>
  25. import { x } from './x'
  26. let a = 1
  27. const b = 2
  28. function c() {}
  29. class d {}
  30. </script>
  31. `)
  32. assertCode(content)
  33. expect(content).toMatch('return { a, b, c, d, x }')
  34. })
  35. test('defineOptions()', () => {
  36. const { content, bindings } = compile(`
  37. <script setup>
  38. import { defineOptions } from 'vue'
  39. const { props, emit } = defineOptions({
  40. props: {
  41. foo: String
  42. },
  43. emit: ['a', 'b']
  44. })
  45. const bar = 1
  46. </script>
  47. `)
  48. // should generate working code
  49. assertCode(content)
  50. // should anayze bindings
  51. expect(bindings).toStrictEqual({
  52. foo: 'props',
  53. bar: 'const',
  54. props: 'const',
  55. emit: 'const'
  56. })
  57. // should remove defineOptions import and call
  58. expect(content).not.toMatch('defineOptions')
  59. // should generate correct setup signature
  60. expect(content).toMatch(`setup(__props, { props, emit }) {`)
  61. // should include context options in default export
  62. expect(content).toMatch(`export default {
  63. expose: [],
  64. props: {
  65. foo: String
  66. },
  67. emit: ['a', 'b'],`)
  68. })
  69. describe('imports', () => {
  70. test('should hoist and expose imports', () => {
  71. assertCode(
  72. compile(`<script setup>import { ref } from 'vue'</script>`).content
  73. )
  74. })
  75. test('should extract comment for import or type declarations', () => {
  76. assertCode(
  77. compile(`
  78. <script setup>
  79. import a from 'a' // comment
  80. import b from 'b'
  81. </script>
  82. `).content
  83. )
  84. })
  85. test('dedupe between user & helper', () => {
  86. const { content } = compile(`
  87. <script setup>
  88. import { ref } from 'vue'
  89. ref: foo = 1
  90. </script>
  91. `)
  92. assertCode(content)
  93. expect(content).toMatch(`import { ref } from 'vue'`)
  94. })
  95. test('import dedupe between <script> and <script setup>', () => {
  96. const { content } = compile(`
  97. <script>
  98. import { x } from './x'
  99. </script>
  100. <script setup>
  101. import { x } from './x'
  102. x()
  103. </script>
  104. `)
  105. assertCode(content)
  106. expect(content.indexOf(`import { x }`)).toEqual(
  107. content.lastIndexOf(`import { x }`)
  108. )
  109. })
  110. })
  111. describe('inlineTemplate mode', () => {
  112. test('should work', () => {
  113. const { content } = compile(
  114. `
  115. <script setup>
  116. import { ref } from 'vue'
  117. const count = ref(0)
  118. </script>
  119. <template>
  120. <div>{{ count }}</div>
  121. <div>static</div>
  122. </template>
  123. `,
  124. { inlineTemplate: true }
  125. )
  126. // check snapshot and make sure helper imports and
  127. // hoists are placed correctly.
  128. assertCode(content)
  129. })
  130. test('avoid unref() when necessary', () => {
  131. // function, const, component import
  132. const { content } = compile(
  133. `
  134. <script setup>
  135. import { ref, defineOptions } from 'vue'
  136. import Foo from './Foo.vue'
  137. import other from './util'
  138. const count = ref(0)
  139. const constant = {}
  140. function fn() {}
  141. </script>
  142. <template>
  143. <Foo/>
  144. <div @click="fn">{{ count }} {{ constant }} {{ other }}</div>
  145. </template>
  146. `,
  147. { inlineTemplate: true }
  148. )
  149. assertCode(content)
  150. // no need to unref vue component import
  151. expect(content).toMatch(`createVNode(Foo)`)
  152. // should unref other imports
  153. expect(content).toMatch(`unref(other)`)
  154. // no need to unref constant literals
  155. expect(content).not.toMatch(`unref(constant)`)
  156. // should unref const w/ call init (e.g. ref())
  157. expect(content).toMatch(`unref(count)`)
  158. // no need to unref function declarations
  159. expect(content).toMatch(`{ onClick: fn }`)
  160. // no need to mark constant fns in patch flag
  161. expect(content).not.toMatch(`PROPS`)
  162. })
  163. })
  164. describe('with TypeScript', () => {
  165. test('hoist type declarations', () => {
  166. const { content } = compile(`
  167. <script setup lang="ts">
  168. export interface Foo {}
  169. type Bar = {}
  170. </script>`)
  171. assertCode(content)
  172. })
  173. test('defineOptions w/ runtime options', () => {
  174. const { content } = compile(`
  175. <script setup lang="ts">
  176. import { defineOptions } from 'vue'
  177. const { props, emit } = defineOptions({
  178. props: { foo: String },
  179. emits: ['a', 'b']
  180. })
  181. </script>
  182. `)
  183. assertCode(content)
  184. expect(content).toMatch(`export default _defineComponent({
  185. expose: [],
  186. props: { foo: String },
  187. emits: ['a', 'b'],
  188. setup(__props, { props, emit }) {`)
  189. })
  190. test('defineOptions w/ type / extract props', () => {
  191. const { content, bindings } = compile(`
  192. <script setup lang="ts">
  193. import { defineOptions } from 'vue'
  194. interface Test {}
  195. type Alias = number[]
  196. defineOptions<{
  197. props: {
  198. string: string
  199. number: number
  200. boolean: boolean
  201. object: object
  202. objectLiteral: { a: number }
  203. fn: (n: number) => void
  204. functionRef: Function
  205. objectRef: Object
  206. array: string[]
  207. arrayRef: Array<any>
  208. tuple: [number, number]
  209. set: Set<string>
  210. literal: 'foo'
  211. optional?: any
  212. recordRef: Record<string, null>
  213. interface: Test
  214. alias: Alias
  215. union: string | number
  216. literalUnion: 'foo' | 'bar'
  217. literalUnionMixed: 'foo' | 1 | boolean
  218. intersection: Test & {}
  219. }
  220. }>()
  221. </script>`)
  222. assertCode(content)
  223. expect(content).toMatch(`string: { type: String, required: true }`)
  224. expect(content).toMatch(`number: { type: Number, required: true }`)
  225. expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
  226. expect(content).toMatch(`object: { type: Object, required: true }`)
  227. expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
  228. expect(content).toMatch(`fn: { type: Function, required: true }`)
  229. expect(content).toMatch(`functionRef: { type: Function, required: true }`)
  230. expect(content).toMatch(`objectRef: { type: Object, required: true }`)
  231. expect(content).toMatch(`array: { type: Array, required: true }`)
  232. expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
  233. expect(content).toMatch(`tuple: { type: Array, required: true }`)
  234. expect(content).toMatch(`set: { type: Set, required: true }`)
  235. expect(content).toMatch(`literal: { type: String, required: true }`)
  236. expect(content).toMatch(`optional: { type: null, required: false }`)
  237. expect(content).toMatch(`recordRef: { type: Object, required: true }`)
  238. expect(content).toMatch(`interface: { type: Object, required: true }`)
  239. expect(content).toMatch(`alias: { type: Array, required: true }`)
  240. expect(content).toMatch(
  241. `union: { type: [String, Number], required: true }`
  242. )
  243. expect(content).toMatch(
  244. `literalUnion: { type: [String, String], required: true }`
  245. )
  246. expect(content).toMatch(
  247. `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
  248. )
  249. expect(content).toMatch(`intersection: { type: Object, required: true }`)
  250. expect(bindings).toStrictEqual({
  251. string: 'props',
  252. number: 'props',
  253. boolean: 'props',
  254. object: 'props',
  255. objectLiteral: 'props',
  256. fn: 'props',
  257. functionRef: 'props',
  258. objectRef: 'props',
  259. array: 'props',
  260. arrayRef: 'props',
  261. tuple: 'props',
  262. set: 'props',
  263. literal: 'props',
  264. optional: 'props',
  265. recordRef: 'props',
  266. interface: 'props',
  267. alias: 'props',
  268. union: 'props',
  269. literalUnion: 'props',
  270. literalUnionMixed: 'props',
  271. intersection: 'props'
  272. })
  273. })
  274. test('defineOptions w/ type / extract emits', () => {
  275. const { content } = compile(`
  276. <script setup lang="ts">
  277. import { defineOptions } from 'vue'
  278. const { emit } = defineOptions<{
  279. emit: (e: 'foo' | 'bar') => void
  280. }>()
  281. </script>
  282. `)
  283. assertCode(content)
  284. expect(content).toMatch(`props: {},\n emit: (e: 'foo' | 'bar') => void,`)
  285. expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
  286. })
  287. test('defineOptions w/ type / extract emits (union)', () => {
  288. const { content } = compile(`
  289. <script setup lang="ts">
  290. import { defineOptions } from 'vue'
  291. const { emit } = defineOptions<{
  292. emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)
  293. }>()
  294. </script>
  295. `)
  296. assertCode(content)
  297. expect(content).toMatch(
  298. `props: {},\n emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void),`
  299. )
  300. expect(content).toMatch(
  301. `emits: ["foo", "bar", "baz"] as unknown as undefined`
  302. )
  303. })
  304. })
  305. describe('CSS vars injection', () => {
  306. test('<script> w/ no default export', () => {
  307. assertCode(
  308. compile(
  309. `<script>const a = 1</script>\n` +
  310. `<style vars="{ color }">div{ color: var(--color); }</style>`
  311. ).content
  312. )
  313. })
  314. test('<script> w/ default export', () => {
  315. assertCode(
  316. compile(
  317. `<script>export default { setup() {} }</script>\n` +
  318. `<style vars="{ color }">div{ color: var(--color); }</style>`
  319. ).content
  320. )
  321. })
  322. test('<script> w/ default export in strings/comments', () => {
  323. assertCode(
  324. compile(
  325. `<script>
  326. // export default {}
  327. export default {}
  328. </script>\n` +
  329. `<style vars="{ color }">div{ color: var(--color); }</style>`
  330. ).content
  331. )
  332. })
  333. test('w/ <script setup>', () => {
  334. assertCode(
  335. compile(
  336. `<script setup>const color = 'red'</script>\n` +
  337. `<style vars="{ color }">div{ color: var(--color); }</style>`
  338. ).content
  339. )
  340. })
  341. })
  342. describe('async/await detection', () => {
  343. function assertAwaitDetection(code: string, shouldAsync = true) {
  344. const { content } = compile(`<script setup>${code}</script>`)
  345. expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup()`)
  346. }
  347. test('expression statement', () => {
  348. assertAwaitDetection(`await foo`)
  349. })
  350. test('variable', () => {
  351. assertAwaitDetection(`const a = 1 + (await foo)`)
  352. })
  353. test('ref', () => {
  354. assertAwaitDetection(`ref: a = 1 + (await foo)`)
  355. })
  356. test('nested statements', () => {
  357. assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
  358. })
  359. test('should ignore await inside functions', () => {
  360. // function declaration
  361. assertAwaitDetection(`async function foo() { await bar }`, false)
  362. // function expression
  363. assertAwaitDetection(`const foo = async () => { await bar }`, false)
  364. // object method
  365. assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
  366. // class method
  367. assertAwaitDetection(
  368. `const cls = class Foo { async method() { await bar }}`,
  369. false
  370. )
  371. })
  372. })
  373. describe('ref: syntax sugar', () => {
  374. test('convert ref declarations', () => {
  375. const { content, bindings } = compile(`<script setup>
  376. ref: foo
  377. ref: a = 1
  378. ref: b = {
  379. count: 0
  380. }
  381. let c = () => {}
  382. let d
  383. </script>`)
  384. expect(content).toMatch(`import { ref as _ref } from 'vue'`)
  385. expect(content).not.toMatch(`ref: foo`)
  386. expect(content).not.toMatch(`ref: a`)
  387. expect(content).not.toMatch(`ref: b`)
  388. expect(content).toMatch(`const foo = _ref()`)
  389. expect(content).toMatch(`const a = _ref(1)`)
  390. expect(content).toMatch(`
  391. const b = _ref({
  392. count: 0
  393. })
  394. `)
  395. // normal declarations left untouched
  396. expect(content).toMatch(`let c = () => {}`)
  397. expect(content).toMatch(`let d`)
  398. assertCode(content)
  399. expect(bindings).toStrictEqual({
  400. foo: 'setup',
  401. a: 'setup',
  402. b: 'setup',
  403. c: 'setup',
  404. d: 'setup'
  405. })
  406. })
  407. test('multi ref declarations', () => {
  408. const { content, bindings } = compile(`<script setup>
  409. ref: a = 1, b = 2, c = {
  410. count: 0
  411. }
  412. </script>`)
  413. expect(content).toMatch(`
  414. const a = _ref(1), b = _ref(2), c = _ref({
  415. count: 0
  416. })
  417. `)
  418. expect(content).toMatch(`return { a, b, c }`)
  419. assertCode(content)
  420. expect(bindings).toStrictEqual({
  421. a: 'setup',
  422. b: 'setup',
  423. c: 'setup'
  424. })
  425. })
  426. test('should not convert non ref labels', () => {
  427. const { content } = compile(`<script setup>
  428. foo: a = 1, b = 2, c = {
  429. count: 0
  430. }
  431. </script>`)
  432. expect(content).toMatch(`foo: a = 1, b = 2`)
  433. assertCode(content)
  434. })
  435. test('accessing ref binding', () => {
  436. const { content } = compile(`<script setup>
  437. ref: a = 1
  438. console.log(a)
  439. function get() {
  440. return a + 1
  441. }
  442. </script>`)
  443. expect(content).toMatch(`console.log(a.value)`)
  444. expect(content).toMatch(`return a.value + 1`)
  445. assertCode(content)
  446. })
  447. test('cases that should not append .value', () => {
  448. const { content } = compile(`<script setup>
  449. ref: a = 1
  450. console.log(b.a)
  451. function get(a) {
  452. return a + 1
  453. }
  454. </script>`)
  455. expect(content).not.toMatch(`a.value`)
  456. })
  457. test('mutating ref binding', () => {
  458. const { content } = compile(`<script setup>
  459. ref: a = 1
  460. ref: b = { count: 0 }
  461. function inc() {
  462. a++
  463. a = a + 1
  464. b.count++
  465. b.count = b.count + 1
  466. }
  467. </script>`)
  468. expect(content).toMatch(`a.value++`)
  469. expect(content).toMatch(`a.value = a.value + 1`)
  470. expect(content).toMatch(`b.value.count++`)
  471. expect(content).toMatch(`b.value.count = b.value.count + 1`)
  472. assertCode(content)
  473. })
  474. test('using ref binding in property shorthand', () => {
  475. const { content } = compile(`<script setup>
  476. ref: a = 1
  477. const b = { a }
  478. function test() {
  479. const { a } = b
  480. }
  481. </script>`)
  482. expect(content).toMatch(`const b = { a: a.value }`)
  483. // should not convert destructure
  484. expect(content).toMatch(`const { a } = b`)
  485. assertCode(content)
  486. })
  487. test('object destructure', () => {
  488. const { content, bindings } = compile(`<script setup>
  489. ref: n = 1, ({ a, b: c, d = 1, e: f = 2, ...g } = useFoo())
  490. console.log(n, a, c, d, f, g)
  491. </script>`)
  492. expect(content).toMatch(
  493. `const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()`
  494. )
  495. expect(content).toMatch(`\nconst a = _ref(__a);`)
  496. expect(content).not.toMatch(`\nconst b = _ref(__b);`)
  497. expect(content).toMatch(`\nconst c = _ref(__c);`)
  498. expect(content).toMatch(`\nconst d = _ref(__d);`)
  499. expect(content).not.toMatch(`\nconst e = _ref(__e);`)
  500. expect(content).toMatch(`\nconst f = _ref(__f);`)
  501. expect(content).toMatch(`\nconst g = _ref(__g);`)
  502. expect(content).toMatch(
  503. `console.log(n.value, a.value, c.value, d.value, f.value, g.value)`
  504. )
  505. expect(content).toMatch(`return { n, a, c, d, f, g }`)
  506. expect(bindings).toStrictEqual({
  507. n: 'setup',
  508. a: 'setup',
  509. c: 'setup',
  510. d: 'setup',
  511. f: 'setup',
  512. g: 'setup'
  513. })
  514. assertCode(content)
  515. })
  516. test('array destructure', () => {
  517. const { content, bindings } = compile(`<script setup>
  518. ref: n = 1, [a, b = 1, ...c] = useFoo()
  519. console.log(n, a, b, c)
  520. </script>`)
  521. expect(content).toMatch(
  522. `const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()`
  523. )
  524. expect(content).toMatch(`\nconst a = _ref(__a);`)
  525. expect(content).toMatch(`\nconst b = _ref(__b);`)
  526. expect(content).toMatch(`\nconst c = _ref(__c);`)
  527. expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
  528. expect(content).toMatch(`return { n, a, b, c }`)
  529. expect(bindings).toStrictEqual({
  530. n: 'setup',
  531. a: 'setup',
  532. b: 'setup',
  533. c: 'setup'
  534. })
  535. assertCode(content)
  536. })
  537. test('nested destructure', () => {
  538. const { content, bindings } = compile(`<script setup>
  539. ref: [{ a: { b }}] = useFoo()
  540. ref: ({ c: [d, e] } = useBar())
  541. console.log(b, d, e)
  542. </script>`)
  543. expect(content).toMatch(`const [{ a: { b: __b }}] = useFoo()`)
  544. expect(content).toMatch(`const { c: [__d, __e] } = useBar()`)
  545. expect(content).not.toMatch(`\nconst a = _ref(__a);`)
  546. expect(content).not.toMatch(`\nconst c = _ref(__c);`)
  547. expect(content).toMatch(`\nconst b = _ref(__b);`)
  548. expect(content).toMatch(`\nconst d = _ref(__d);`)
  549. expect(content).toMatch(`\nconst e = _ref(__e);`)
  550. expect(content).toMatch(`return { b, d, e }`)
  551. expect(bindings).toStrictEqual({
  552. b: 'setup',
  553. d: 'setup',
  554. e: 'setup'
  555. })
  556. assertCode(content)
  557. })
  558. })
  559. describe('errors', () => {
  560. test('<script> and <script setup> must have same lang', () => {
  561. expect(() =>
  562. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`)
  563. ).toThrow(`<script> and <script setup> must have the same language type`)
  564. })
  565. const moduleErrorMsg = `cannot contain ES module exports`
  566. test('non-type named exports', () => {
  567. expect(() =>
  568. compile(`<script setup>
  569. export const a = 1
  570. </script>`)
  571. ).toThrow(moduleErrorMsg)
  572. expect(() =>
  573. compile(`<script setup>
  574. export * from './foo'
  575. </script>`)
  576. ).toThrow(moduleErrorMsg)
  577. expect(() =>
  578. compile(`<script setup>
  579. const bar = 1
  580. export { bar as default }
  581. </script>`)
  582. ).toThrow(moduleErrorMsg)
  583. })
  584. test('ref: non-assignment expressions', () => {
  585. expect(() =>
  586. compile(`<script setup>
  587. ref: a = 1, foo()
  588. </script>`)
  589. ).toThrow(`ref: statements can only contain assignment expressions`)
  590. })
  591. test('defineOptions() w/ both type and non-type args', () => {
  592. expect(() => {
  593. compile(`<script setup lang="ts">
  594. import { defineOptions } from 'vue'
  595. defineOptions<{}>({})
  596. </script>`)
  597. }).toThrow(`cannot accept both type and non-type arguments`)
  598. })
  599. test('defineOptions() referencing local var', () => {
  600. expect(() =>
  601. compile(`<script setup>
  602. import { defineOptions } from 'vue'
  603. const bar = 1
  604. defineOptions({
  605. props: {
  606. foo: {
  607. default: () => bar
  608. }
  609. }
  610. })
  611. </script>`)
  612. ).toThrow(`cannot reference locally declared variables`)
  613. })
  614. test('defineOptions() referencing ref declarations', () => {
  615. expect(() =>
  616. compile(`<script setup>
  617. import { defineOptions } from 'vue'
  618. ref: bar = 1
  619. defineOptions({
  620. props: { bar }
  621. })
  622. </script>`)
  623. ).toThrow(`cannot reference locally declared variables`)
  624. })
  625. test('should allow defineOptions() referencing scope var', () => {
  626. assertCode(
  627. compile(`<script setup>
  628. import { defineOptions } from 'vue'
  629. const bar = 1
  630. defineOptions({
  631. props: {
  632. foo: {
  633. default: bar => bar + 1
  634. }
  635. }
  636. })
  637. </script>`).content
  638. )
  639. })
  640. test('should allow defineOptions() referencing imported binding', () => {
  641. assertCode(
  642. compile(`<script setup>
  643. import { defineOptions } from 'vue'
  644. import { bar } from './bar'
  645. defineOptions({
  646. props: {
  647. foo: {
  648. default: () => bar
  649. }
  650. }
  651. })
  652. </script>`).content
  653. )
  654. })
  655. })
  656. })
  657. describe('SFC analyze <script> bindings', () => {
  658. it('can parse decorators syntax in typescript block', () => {
  659. const { scriptAst } = compile(`
  660. <script lang="ts">
  661. import { Options, Vue } from 'vue-class-component';
  662. @Options({
  663. components: {
  664. HelloWorld,
  665. },
  666. props: ['foo', 'bar']
  667. })
  668. export default class Home extends Vue {}
  669. </script>
  670. `)
  671. expect(scriptAst).toBeDefined()
  672. })
  673. it('recognizes props array declaration', () => {
  674. const { bindings } = compile(`
  675. <script>
  676. export default {
  677. props: ['foo', 'bar']
  678. }
  679. </script>
  680. `)
  681. expect(bindings).toStrictEqual({ foo: 'props', bar: 'props' })
  682. })
  683. it('recognizes props object declaration', () => {
  684. const { bindings } = compile(`
  685. <script>
  686. export default {
  687. props: {
  688. foo: String,
  689. bar: {
  690. type: String,
  691. },
  692. baz: null,
  693. qux: [String, Number]
  694. }
  695. }
  696. </script>
  697. `)
  698. expect(bindings).toStrictEqual({
  699. foo: 'props',
  700. bar: 'props',
  701. baz: 'props',
  702. qux: 'props'
  703. })
  704. })
  705. it('recognizes setup return', () => {
  706. const { bindings } = compile(`
  707. <script>
  708. const bar = 2
  709. export default {
  710. setup() {
  711. return {
  712. foo: 1,
  713. bar
  714. }
  715. }
  716. }
  717. </script>
  718. `)
  719. expect(bindings).toStrictEqual({ foo: 'setup', bar: 'setup' })
  720. })
  721. it('recognizes async setup return', () => {
  722. const { bindings } = compile(`
  723. <script>
  724. const bar = 2
  725. export default {
  726. async setup() {
  727. return {
  728. foo: 1,
  729. bar
  730. }
  731. }
  732. }
  733. </script>
  734. `)
  735. expect(bindings).toStrictEqual({ foo: 'setup', bar: 'setup' })
  736. })
  737. it('recognizes data return', () => {
  738. const { bindings } = compile(`
  739. <script>
  740. const bar = 2
  741. export default {
  742. data() {
  743. return {
  744. foo: null,
  745. bar
  746. }
  747. }
  748. }
  749. </script>
  750. `)
  751. expect(bindings).toStrictEqual({ foo: 'data', bar: 'data' })
  752. })
  753. it('recognizes methods', () => {
  754. const { bindings } = compile(`
  755. <script>
  756. export default {
  757. methods: {
  758. foo() {}
  759. }
  760. }
  761. </script>
  762. `)
  763. expect(bindings).toStrictEqual({ foo: 'options' })
  764. })
  765. it('recognizes computeds', () => {
  766. const { bindings } = compile(`
  767. <script>
  768. export default {
  769. computed: {
  770. foo() {},
  771. bar: {
  772. get() {},
  773. set() {},
  774. }
  775. }
  776. }
  777. </script>
  778. `)
  779. expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' })
  780. })
  781. it('recognizes injections array declaration', () => {
  782. const { bindings } = compile(`
  783. <script>
  784. export default {
  785. inject: ['foo', 'bar']
  786. }
  787. </script>
  788. `)
  789. expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' })
  790. })
  791. it('recognizes injections object declaration', () => {
  792. const { bindings } = compile(`
  793. <script>
  794. export default {
  795. inject: {
  796. foo: {},
  797. bar: {},
  798. }
  799. }
  800. </script>
  801. `)
  802. expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' })
  803. })
  804. it('works for mixed bindings', () => {
  805. const { bindings } = compile(`
  806. <script>
  807. export default {
  808. inject: ['foo'],
  809. props: {
  810. bar: String,
  811. },
  812. setup() {
  813. return {
  814. baz: null,
  815. }
  816. },
  817. data() {
  818. return {
  819. qux: null
  820. }
  821. },
  822. methods: {
  823. quux() {}
  824. },
  825. computed: {
  826. quuz() {}
  827. }
  828. }
  829. </script>
  830. `)
  831. expect(bindings).toStrictEqual({
  832. foo: 'options',
  833. bar: 'props',
  834. baz: 'setup',
  835. qux: 'data',
  836. quux: 'options',
  837. quuz: 'options'
  838. })
  839. })
  840. it('works for script setup', () => {
  841. const { bindings } = compile(`
  842. <script setup>
  843. import { defineOptions } from 'vue'
  844. defineOptions({
  845. props: {
  846. foo: String,
  847. }
  848. })
  849. </script>
  850. `)
  851. expect(bindings).toStrictEqual({
  852. foo: 'props'
  853. })
  854. })
  855. })