compileScript.spec.ts 21 KB

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