defineProps.spec.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. import { BindingTypes } from '@vue/compiler-core'
  2. import { assertCode, compileSFCScript as compile } from '../utils'
  3. describe('defineProps', () => {
  4. test('basic usage', () => {
  5. const { content, bindings } = compile(`
  6. <script setup>
  7. const props = defineProps({
  8. foo: String
  9. })
  10. const bar = 1
  11. </script>
  12. `)
  13. // should generate working code
  14. assertCode(content)
  15. // should analyze bindings
  16. expect(bindings).toStrictEqual({
  17. foo: BindingTypes.PROPS,
  18. bar: BindingTypes.LITERAL_CONST,
  19. props: BindingTypes.SETUP_REACTIVE_CONST,
  20. })
  21. // should remove defineOptions import and call
  22. expect(content).not.toMatch('defineProps')
  23. // should generate correct setup signature
  24. expect(content).toMatch(`setup(__props, { expose: __expose }) {`)
  25. // should assign user identifier to it
  26. expect(content).toMatch(`const props = __props`)
  27. // should include context options in default export
  28. expect(content).toMatch(`export default {
  29. props: {
  30. foo: String
  31. },`)
  32. })
  33. test('w/ external definition', () => {
  34. const { content } = compile(`
  35. <script setup>
  36. import { propsModel } from './props'
  37. const props = defineProps(propsModel)
  38. </script>
  39. `)
  40. assertCode(content)
  41. expect(content).toMatch(`export default {
  42. props: propsModel,`)
  43. })
  44. // #12254
  45. test('w/ leading intersection type ignore', () => {
  46. const { content, bindings } = compile(`<script setup lang="ts">
  47. type Foo = { foo: number }
  48. type Bar = { bar: boolean }
  49. defineProps</* @vue-ignore */ Foo & Bar>()
  50. </script>`)
  51. expect(content).not.toMatch(`foo: { type`)
  52. expect(content).toMatch(`bar: { type: Boolean, required: true }`)
  53. expect(bindings).toStrictEqual({
  54. bar: BindingTypes.PROPS,
  55. })
  56. })
  57. // #4764
  58. test('w/ leading code', () => {
  59. const { content } = compile(`
  60. <script setup>import { x } from './x'
  61. const props = defineProps({})
  62. </script>
  63. `)
  64. // props declaration should be inside setup, not moved along with the import
  65. expect(content).not.toMatch(`const props = __props\nimport`)
  66. assertCode(content)
  67. })
  68. test('defineProps w/ runtime options', () => {
  69. const { content } = compile(`
  70. <script setup lang="ts">
  71. const props = defineProps({ foo: String })
  72. </script>
  73. `)
  74. assertCode(content)
  75. expect(content).toMatch(`export default /*@__PURE__*/_defineComponent({
  76. props: { foo: String },
  77. setup(__props, { expose: __expose }) {`)
  78. })
  79. test('w/ type', () => {
  80. const { content, bindings } = compile(`
  81. <script setup lang="ts">
  82. interface Test {}
  83. type Alias = number[]
  84. defineProps<{
  85. string: string
  86. number: number
  87. boolean: boolean
  88. object: object
  89. objectLiteral: { a: number }
  90. fn: (n: number) => void
  91. functionRef: Function
  92. objectRef: Object
  93. dateTime: Date
  94. array: string[]
  95. arrayRef: Array<any>
  96. tuple: [number, number]
  97. set: Set<string>
  98. literal: 'foo'
  99. optional?: any
  100. recordRef: Record<string, null>
  101. interface: Test
  102. alias: Alias
  103. method(): void
  104. symbol: symbol
  105. error: Error
  106. extract: Extract<1 | 2 | boolean, 2>
  107. exclude: Exclude<1 | 2 | boolean, 2>
  108. uppercase: Uppercase<'foo'>
  109. params: Parameters<(foo: any) => void>
  110. nonNull: NonNullable<string | null>
  111. objectOrFn: {
  112. (): void
  113. foo: string
  114. }
  115. union: string | number
  116. literalUnion: 'foo' | 'bar'
  117. literalUnionNumber: 1 | 2 | 3 | 4 | 5
  118. literalUnionMixed: 'foo' | 1 | boolean
  119. intersection: Test & {}
  120. intersection2: 'foo' & ('foo' | 'bar')
  121. foo: ((item: any) => boolean) | null
  122. unknown: UnknownType
  123. unknownUnion: UnknownType | string
  124. unknownIntersection: UnknownType & Object
  125. unknownUnionWithBoolean: UnknownType | boolean
  126. unknownUnionWithFunction: UnknownType | (() => any)
  127. }>()
  128. </script>`)
  129. assertCode(content)
  130. expect(content).toMatch(`string: { type: String, required: true }`)
  131. expect(content).toMatch(`number: { type: Number, required: true }`)
  132. expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
  133. expect(content).toMatch(`object: { type: Object, required: true }`)
  134. expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
  135. expect(content).toMatch(`fn: { type: Function, required: true }`)
  136. expect(content).toMatch(`functionRef: { type: Function, required: true }`)
  137. expect(content).toMatch(`objectRef: { type: Object, required: true }`)
  138. expect(content).toMatch(`dateTime: { type: Date, required: true }`)
  139. expect(content).toMatch(`array: { type: Array, required: true }`)
  140. expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
  141. expect(content).toMatch(`tuple: { type: Array, required: true }`)
  142. expect(content).toMatch(`set: { type: Set, required: true }`)
  143. expect(content).toMatch(`literal: { type: String, required: true }`)
  144. expect(content).toMatch(`optional: { type: null, required: false }`)
  145. expect(content).toMatch(`recordRef: { type: Object, required: true }`)
  146. expect(content).toMatch(`interface: { type: Object, required: true }`)
  147. expect(content).toMatch(`alias: { type: Array, required: true }`)
  148. expect(content).toMatch(`method: { type: Function, required: true }`)
  149. expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
  150. expect(content).toMatch(`error: { type: Error, required: true }`)
  151. expect(content).toMatch(
  152. `objectOrFn: { type: [Function, Object], required: true },`,
  153. )
  154. expect(content).toMatch(`extract: { type: Number, required: true }`)
  155. expect(content).toMatch(
  156. `exclude: { type: [Number, Boolean], required: true }`,
  157. )
  158. expect(content).toMatch(`uppercase: { type: String, required: true }`)
  159. expect(content).toMatch(`params: { type: Array, required: true }`)
  160. expect(content).toMatch(`nonNull: { type: String, required: true }`)
  161. expect(content).toMatch(`union: { type: [String, Number], required: true }`)
  162. expect(content).toMatch(`literalUnion: { type: String, required: true }`)
  163. expect(content).toMatch(
  164. `literalUnionNumber: { type: Number, required: true }`,
  165. )
  166. expect(content).toMatch(
  167. `literalUnionMixed: { type: [String, Number, Boolean], required: true }`,
  168. )
  169. expect(content).toMatch(`intersection: { type: Object, required: true }`)
  170. expect(content).toMatch(`intersection2: { type: String, required: true }`)
  171. expect(content).toMatch(`foo: { type: [Function, null], required: true }`)
  172. expect(content).toMatch(`unknown: { type: null, required: true }`)
  173. // uninon containing unknown type: skip check
  174. expect(content).toMatch(`unknownUnion: { type: null, required: true }`)
  175. // intersection containing unknown type: narrow to the known types
  176. expect(content).toMatch(
  177. `unknownIntersection: { type: Object, required: true },`,
  178. )
  179. expect(content).toMatch(
  180. `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },`,
  181. )
  182. expect(content).toMatch(
  183. `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }`,
  184. )
  185. expect(bindings).toStrictEqual({
  186. string: BindingTypes.PROPS,
  187. number: BindingTypes.PROPS,
  188. boolean: BindingTypes.PROPS,
  189. object: BindingTypes.PROPS,
  190. objectLiteral: BindingTypes.PROPS,
  191. fn: BindingTypes.PROPS,
  192. functionRef: BindingTypes.PROPS,
  193. objectRef: BindingTypes.PROPS,
  194. dateTime: BindingTypes.PROPS,
  195. array: BindingTypes.PROPS,
  196. arrayRef: BindingTypes.PROPS,
  197. tuple: BindingTypes.PROPS,
  198. set: BindingTypes.PROPS,
  199. literal: BindingTypes.PROPS,
  200. optional: BindingTypes.PROPS,
  201. recordRef: BindingTypes.PROPS,
  202. interface: BindingTypes.PROPS,
  203. alias: BindingTypes.PROPS,
  204. method: BindingTypes.PROPS,
  205. symbol: BindingTypes.PROPS,
  206. error: BindingTypes.PROPS,
  207. objectOrFn: BindingTypes.PROPS,
  208. extract: BindingTypes.PROPS,
  209. exclude: BindingTypes.PROPS,
  210. union: BindingTypes.PROPS,
  211. literalUnion: BindingTypes.PROPS,
  212. literalUnionNumber: BindingTypes.PROPS,
  213. literalUnionMixed: BindingTypes.PROPS,
  214. intersection: BindingTypes.PROPS,
  215. intersection2: BindingTypes.PROPS,
  216. foo: BindingTypes.PROPS,
  217. uppercase: BindingTypes.PROPS,
  218. params: BindingTypes.PROPS,
  219. nonNull: BindingTypes.PROPS,
  220. unknown: BindingTypes.PROPS,
  221. unknownUnion: BindingTypes.PROPS,
  222. unknownIntersection: BindingTypes.PROPS,
  223. unknownUnionWithBoolean: BindingTypes.PROPS,
  224. unknownUnionWithFunction: BindingTypes.PROPS,
  225. })
  226. })
  227. test('w/ interface', () => {
  228. const { content, bindings } = compile(`
  229. <script setup lang="ts">
  230. interface Props { x?: number }
  231. defineProps<Props>()
  232. </script>
  233. `)
  234. assertCode(content)
  235. expect(content).toMatch(`x: { type: Number, required: false }`)
  236. expect(bindings).toStrictEqual({
  237. x: BindingTypes.PROPS,
  238. })
  239. })
  240. test('w/ extends interface', () => {
  241. const { content, bindings } = compile(`
  242. <script lang="ts">
  243. interface Foo { x?: number }
  244. </script>
  245. <script setup lang="ts">
  246. interface Bar extends Foo { y?: number }
  247. interface Props extends Bar {
  248. z: number
  249. y: string
  250. }
  251. defineProps<Props>()
  252. </script>
  253. `)
  254. assertCode(content)
  255. expect(content).toMatch(`z: { type: Number, required: true }`)
  256. expect(content).toMatch(`y: { type: String, required: true }`)
  257. expect(content).toMatch(`x: { type: Number, required: false }`)
  258. expect(bindings).toStrictEqual({
  259. x: BindingTypes.PROPS,
  260. y: BindingTypes.PROPS,
  261. z: BindingTypes.PROPS,
  262. })
  263. })
  264. test('w/ extends intersection type', () => {
  265. const { content, bindings } = compile(`
  266. <script setup lang="ts">
  267. type Foo = {
  268. x?: number;
  269. };
  270. interface Props extends Foo {
  271. z: number
  272. y: string
  273. }
  274. defineProps<Props>()
  275. </script>
  276. `)
  277. assertCode(content)
  278. expect(content).toMatch(`z: { type: Number, required: true }`)
  279. expect(content).toMatch(`y: { type: String, required: true }`)
  280. expect(content).toMatch(`x: { type: Number, required: false }`)
  281. expect(bindings).toStrictEqual({
  282. x: BindingTypes.PROPS,
  283. y: BindingTypes.PROPS,
  284. z: BindingTypes.PROPS,
  285. })
  286. })
  287. test('w/ intersection type', () => {
  288. const { content, bindings } = compile(`
  289. <script setup lang="ts">
  290. type Foo = {
  291. x?: number;
  292. };
  293. type Bar = {
  294. y: string;
  295. };
  296. defineProps<Foo & Bar>()
  297. </script>
  298. `)
  299. assertCode(content)
  300. expect(content).toMatch(`y: { type: String, required: true }`)
  301. expect(content).toMatch(`x: { type: Number, required: false }`)
  302. expect(bindings).toStrictEqual({
  303. x: BindingTypes.PROPS,
  304. y: BindingTypes.PROPS,
  305. })
  306. })
  307. test('w/ exported interface', () => {
  308. const { content, bindings } = compile(`
  309. <script setup lang="ts">
  310. export interface Props { x?: number }
  311. defineProps<Props>()
  312. </script>
  313. `)
  314. assertCode(content)
  315. expect(content).toMatch(`x: { type: Number, required: false }`)
  316. expect(bindings).toStrictEqual({
  317. x: BindingTypes.PROPS,
  318. })
  319. })
  320. test('w/ exported interface in normal script', () => {
  321. const { content, bindings } = compile(`
  322. <script lang="ts">
  323. export interface Props { x?: number }
  324. </script>
  325. <script setup lang="ts">
  326. defineProps<Props>()
  327. </script>
  328. `)
  329. assertCode(content)
  330. expect(content).toMatch(`x: { type: Number, required: false }`)
  331. expect(bindings).toStrictEqual({
  332. x: BindingTypes.PROPS,
  333. })
  334. })
  335. test('w/ type alias', () => {
  336. const { content, bindings } = compile(`
  337. <script setup lang="ts">
  338. type Props = { x?: number }
  339. defineProps<Props>()
  340. </script>
  341. `)
  342. assertCode(content)
  343. expect(content).toMatch(`x: { type: Number, required: false }`)
  344. expect(bindings).toStrictEqual({
  345. x: BindingTypes.PROPS,
  346. })
  347. })
  348. test('w/ exported type alias', () => {
  349. const { content, bindings } = compile(`
  350. <script setup lang="ts">
  351. export type Props = { x?: number }
  352. defineProps<Props>()
  353. </script>
  354. `)
  355. assertCode(content)
  356. expect(content).toMatch(`x: { type: Number, required: false }`)
  357. expect(bindings).toStrictEqual({
  358. x: BindingTypes.PROPS,
  359. })
  360. })
  361. test('w/ TS assertion', () => {
  362. const { content, bindings } = compile(`
  363. <script setup lang="ts">
  364. defineProps(['foo'])! as any
  365. </script>
  366. `)
  367. expect(content).toMatch(`props: ['foo']`)
  368. assertCode(content)
  369. expect(bindings).toStrictEqual({
  370. foo: BindingTypes.PROPS,
  371. })
  372. })
  373. test('withDefaults (static)', () => {
  374. const { content, bindings } = compile(`
  375. <script setup lang="ts">
  376. const props = withDefaults(defineProps<{
  377. foo?: string
  378. bar?: number;
  379. baz: boolean;
  380. qux?(): number;
  381. quux?(): void
  382. quuxx?: Promise<string>;
  383. quuux?: number;
  384. fred?: string
  385. }>(), {
  386. foo: 'hi',
  387. qux() { return 1 },
  388. ['quux']() { },
  389. async quuxx() { return await Promise.resolve('hi') },
  390. quuux(a, [b, ...c], {d, ...e}, ...f) { return 1 },
  391. get fred() { return 'fred' }
  392. })
  393. </script>
  394. `)
  395. assertCode(content)
  396. expect(content).toMatch(
  397. `foo: { type: String, required: false, default: 'hi' }`,
  398. )
  399. expect(content).toMatch(`bar: { type: Number, required: false }`)
  400. expect(content).toMatch(`baz: { type: Boolean, required: true }`)
  401. expect(content).toMatch(
  402. `qux: { type: Function, required: false, default() { return 1 } }`,
  403. )
  404. expect(content).toMatch(
  405. `quux: { type: Function, required: false, default() { } }`,
  406. )
  407. expect(content).toMatch(
  408. `quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }`,
  409. )
  410. expect(content).toMatch(
  411. `quuux: { type: Number, required: false, default(a, [b, ...c], {d, ...e}, ...f) { return 1 } }`,
  412. )
  413. expect(content).toMatch(
  414. `fred: { type: String, required: false, get default() { return 'fred' } }`,
  415. )
  416. expect(content).toMatch(`const props = __props`)
  417. expect(bindings).toStrictEqual({
  418. foo: BindingTypes.PROPS,
  419. bar: BindingTypes.PROPS,
  420. baz: BindingTypes.PROPS,
  421. qux: BindingTypes.PROPS,
  422. quux: BindingTypes.PROPS,
  423. quuxx: BindingTypes.PROPS,
  424. quuux: BindingTypes.PROPS,
  425. fred: BindingTypes.PROPS,
  426. props: BindingTypes.SETUP_CONST,
  427. })
  428. })
  429. test('withDefaults (static) + normal script', () => {
  430. const { content } = compile(`
  431. <script lang="ts">
  432. interface Props {
  433. a?: string;
  434. }
  435. </script>
  436. <script setup lang="ts">
  437. const props = withDefaults(defineProps<Props>(), {
  438. a: "a",
  439. });
  440. </script>
  441. `)
  442. assertCode(content)
  443. })
  444. // #7111
  445. test('withDefaults (static) w/ production mode', () => {
  446. const { content } = compile(
  447. `
  448. <script setup lang="ts">
  449. const props = withDefaults(defineProps<{
  450. foo: () => void
  451. bar: boolean
  452. baz: boolean | (() => void)
  453. qux: string | number
  454. }>(), {
  455. baz: true,
  456. qux: 'hi'
  457. })
  458. </script>
  459. `,
  460. { isProd: true },
  461. )
  462. assertCode(content)
  463. expect(content).toMatch(`const props = __props`)
  464. // foo has no default value, the Function can be dropped
  465. expect(content).toMatch(`foo: {}`)
  466. expect(content).toMatch(`bar: { type: Boolean }`)
  467. expect(content).toMatch(`baz: { type: [Boolean, Function], default: true }`)
  468. expect(content).toMatch(`qux: { default: 'hi' }`)
  469. })
  470. test('withDefaults (dynamic)', () => {
  471. const { content } = compile(`
  472. <script setup lang="ts">
  473. import { defaults } from './foo'
  474. const props = withDefaults(defineProps<{
  475. foo?: string
  476. bar?: number
  477. baz: boolean
  478. }>(), { ...defaults })
  479. </script>
  480. `)
  481. assertCode(content)
  482. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  483. expect(content).toMatch(
  484. `
  485. _mergeDefaults({
  486. foo: { type: String, required: false },
  487. bar: { type: Number, required: false },
  488. baz: { type: Boolean, required: true }
  489. }, { ...defaults })`.trim(),
  490. )
  491. })
  492. test('withDefaults (reference)', () => {
  493. const { content } = compile(`
  494. <script setup lang="ts">
  495. import { defaults } from './foo'
  496. const props = withDefaults(defineProps<{
  497. foo?: string
  498. bar?: number
  499. baz: boolean
  500. }>(), defaults)
  501. </script>
  502. `)
  503. assertCode(content)
  504. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  505. expect(content).toMatch(
  506. `
  507. _mergeDefaults({
  508. foo: { type: String, required: false },
  509. bar: { type: Number, required: false },
  510. baz: { type: Boolean, required: true }
  511. }, defaults)`.trim(),
  512. )
  513. })
  514. // #7111
  515. test('withDefaults (dynamic) w/ production mode', () => {
  516. const { content } = compile(
  517. `
  518. <script setup lang="ts">
  519. import { defaults } from './foo'
  520. const props = withDefaults(defineProps<{
  521. foo: () => void
  522. bar: boolean
  523. baz: boolean | (() => void)
  524. qux: string | number
  525. }>(), { ...defaults })
  526. </script>
  527. `,
  528. { isProd: true },
  529. )
  530. assertCode(content)
  531. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  532. expect(content).toMatch(
  533. `
  534. _mergeDefaults({
  535. foo: { type: Function },
  536. bar: { type: Boolean },
  537. baz: { type: [Boolean, Function] },
  538. qux: {}
  539. }, { ...defaults })`.trim(),
  540. )
  541. })
  542. test('withDefaults w/ dynamic object method', () => {
  543. const { content } = compile(`
  544. <script setup lang="ts">
  545. const props = withDefaults(defineProps<{
  546. foo?: () => 'string'
  547. }>(), {
  548. ['fo' + 'o']() { return 'foo' }
  549. })
  550. </script>
  551. `)
  552. assertCode(content)
  553. expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
  554. expect(content).toMatch(
  555. `
  556. _mergeDefaults({
  557. foo: { type: Function, required: false }
  558. }, {
  559. ['fo' + 'o']() { return 'foo' }
  560. })`.trim(),
  561. )
  562. })
  563. test('runtime inference for Enum', () => {
  564. expect(
  565. compile(
  566. `<script setup lang="ts">
  567. const enum Foo { A = 123 }
  568. defineProps<{
  569. foo: Foo
  570. }>()
  571. </script>`,
  572. { hoistStatic: true },
  573. ).content,
  574. ).toMatch(`foo: { type: Number`)
  575. expect(
  576. compile(
  577. `<script setup lang="ts">
  578. const enum Foo { A = '123' }
  579. defineProps<{
  580. foo: Foo
  581. }>()
  582. </script>`,
  583. { hoistStatic: true },
  584. ).content,
  585. ).toMatch(`foo: { type: String`)
  586. expect(
  587. compile(
  588. `<script setup lang="ts">
  589. const enum Foo { A = '123', B = 123 }
  590. defineProps<{
  591. foo: Foo
  592. }>()
  593. </script>`,
  594. { hoistStatic: true },
  595. ).content,
  596. ).toMatch(`foo: { type: [String, Number]`)
  597. expect(
  598. compile(
  599. `<script setup lang="ts">
  600. const enum Foo { A, B }
  601. defineProps<{
  602. foo: Foo
  603. }>()
  604. </script>`,
  605. { hoistStatic: true },
  606. ).content,
  607. ).toMatch(`foo: { type: Number`)
  608. })
  609. // #8148
  610. test('should not override local bindings', () => {
  611. const { bindings } = compile(`
  612. <script setup lang="ts">
  613. import { computed } from 'vue'
  614. defineProps<{ bar: string }>()
  615. const bar = computed(() => 1)
  616. </script>
  617. `)
  618. expect(bindings).toStrictEqual({
  619. bar: BindingTypes.SETUP_REF,
  620. computed: BindingTypes.SETUP_CONST,
  621. })
  622. })
  623. // #8289
  624. test('destructure without enabling reactive destructure', () => {
  625. const { content, bindings } = compile(
  626. `<script setup lang="ts">
  627. const { foo } = defineProps<{
  628. foo: Foo
  629. }>()
  630. </script>`,
  631. {
  632. propsDestructure: false,
  633. },
  634. )
  635. expect(content).toMatch(`const { foo } = __props`)
  636. expect(content).toMatch(`return { foo }`)
  637. expect(bindings).toStrictEqual({
  638. foo: BindingTypes.SETUP_CONST,
  639. })
  640. assertCode(content)
  641. })
  642. test('prohibiting reactive destructure', () => {
  643. expect(() =>
  644. compile(
  645. `<script setup lang="ts">
  646. const { foo } = defineProps<{
  647. foo: Foo
  648. }>()
  649. </script>`,
  650. {
  651. propsDestructure: 'error',
  652. },
  653. ),
  654. ).toThrow()
  655. })
  656. describe('errors', () => {
  657. test('w/ both type and non-type args', () => {
  658. expect(() => {
  659. compile(`<script setup lang="ts">
  660. defineProps<{}>({})
  661. </script>`)
  662. }).toThrow(`cannot accept both type and non-type arguments`)
  663. })
  664. })
  665. test('should escape names w/ special symbols', () => {
  666. const { content, bindings } = compile(`
  667. <script setup lang="ts">
  668. defineProps<{
  669. 'spa ce': unknown
  670. 'exclamation!mark': unknown
  671. 'double"quote': unknown
  672. 'hash#tag': unknown
  673. 'dollar$sign': unknown
  674. 'percentage%sign': unknown
  675. 'amper&sand': unknown
  676. "single'quote": unknown
  677. 'round(brack)ets': unknown
  678. 'aste*risk': unknown
  679. 'pl+us': unknown
  680. 'com,ma': unknown
  681. 'do.t': unknown
  682. 'sla/sh': unknown
  683. 'co:lon': unknown
  684. 'semi;colon': unknown
  685. 'angle<brack>ets': unknown
  686. 'equal=sign': unknown
  687. 'question?mark': unknown
  688. 'at@sign': unknown
  689. 'square[brack]ets': unknown
  690. 'back\\\\slash': unknown
  691. 'ca^ret': unknown
  692. 'back\`tick': unknown
  693. 'curly{bra}ces': unknown
  694. 'pi|pe': unknown
  695. 'til~de': unknown
  696. 'da-sh': unknown
  697. }>()
  698. </script>`)
  699. assertCode(content)
  700. expect(content).toMatch(`"spa ce": { type: null, required: true }`)
  701. expect(content).toMatch(
  702. `"exclamation!mark": { type: null, required: true }`,
  703. )
  704. expect(content).toMatch(`"double\\"quote": { type: null, required: true }`)
  705. expect(content).toMatch(`"hash#tag": { type: null, required: true }`)
  706. expect(content).toMatch(`"dollar$sign": { type: null, required: true }`)
  707. expect(content).toMatch(`"percentage%sign": { type: null, required: true }`)
  708. expect(content).toMatch(`"amper&sand": { type: null, required: true }`)
  709. expect(content).toMatch(`"single'quote": { type: null, required: true }`)
  710. expect(content).toMatch(`"round(brack)ets": { type: null, required: true }`)
  711. expect(content).toMatch(`"aste*risk": { type: null, required: true }`)
  712. expect(content).toMatch(`"pl+us": { type: null, required: true }`)
  713. expect(content).toMatch(`"com,ma": { type: null, required: true }`)
  714. expect(content).toMatch(`"do.t": { type: null, required: true }`)
  715. expect(content).toMatch(`"sla/sh": { type: null, required: true }`)
  716. expect(content).toMatch(`"co:lon": { type: null, required: true }`)
  717. expect(content).toMatch(`"semi;colon": { type: null, required: true }`)
  718. expect(content).toMatch(`"angle<brack>ets": { type: null, required: true }`)
  719. expect(content).toMatch(`"equal=sign": { type: null, required: true }`)
  720. expect(content).toMatch(`"question?mark": { type: null, required: true }`)
  721. expect(content).toMatch(`"at@sign": { type: null, required: true }`)
  722. expect(content).toMatch(
  723. `"square[brack]ets": { type: null, required: true }`,
  724. )
  725. expect(content).toMatch(`"back\\\\slash": { type: null, required: true }`)
  726. expect(content).toMatch(`"ca^ret": { type: null, required: true }`)
  727. expect(content).toMatch(`"back\`tick": { type: null, required: true }`)
  728. expect(content).toMatch(`"curly{bra}ces": { type: null, required: true }`)
  729. expect(content).toMatch(`"pi|pe": { type: null, required: true }`)
  730. expect(content).toMatch(`"til~de": { type: null, required: true }`)
  731. expect(content).toMatch(`"da-sh": { type: null, required: true }`)
  732. expect(bindings).toStrictEqual({
  733. 'spa ce': BindingTypes.PROPS,
  734. 'exclamation!mark': BindingTypes.PROPS,
  735. 'double"quote': BindingTypes.PROPS,
  736. 'hash#tag': BindingTypes.PROPS,
  737. dollar$sign: BindingTypes.PROPS,
  738. 'percentage%sign': BindingTypes.PROPS,
  739. 'amper&sand': BindingTypes.PROPS,
  740. "single'quote": BindingTypes.PROPS,
  741. 'round(brack)ets': BindingTypes.PROPS,
  742. 'aste*risk': BindingTypes.PROPS,
  743. 'pl+us': BindingTypes.PROPS,
  744. 'com,ma': BindingTypes.PROPS,
  745. 'do.t': BindingTypes.PROPS,
  746. 'sla/sh': BindingTypes.PROPS,
  747. 'co:lon': BindingTypes.PROPS,
  748. 'semi;colon': BindingTypes.PROPS,
  749. 'angle<brack>ets': BindingTypes.PROPS,
  750. 'equal=sign': BindingTypes.PROPS,
  751. 'question?mark': BindingTypes.PROPS,
  752. 'at@sign': BindingTypes.PROPS,
  753. 'square[brack]ets': BindingTypes.PROPS,
  754. 'back\\slash': BindingTypes.PROPS,
  755. 'ca^ret': BindingTypes.PROPS,
  756. 'back`tick': BindingTypes.PROPS,
  757. 'curly{bra}ces': BindingTypes.PROPS,
  758. 'pi|pe': BindingTypes.PROPS,
  759. 'til~de': BindingTypes.PROPS,
  760. 'da-sh': BindingTypes.PROPS,
  761. })
  762. })
  763. // #8989
  764. test('custom element retains the props type & production mode', () => {
  765. const { content } = compile(
  766. `<script setup lang="ts">
  767. const props = defineProps<{ foo: number}>()
  768. </script>`,
  769. { isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
  770. { filename: 'app.ce.vue' },
  771. )
  772. expect(content).toMatch(`foo: {type: Number}`)
  773. assertCode(content)
  774. })
  775. test('custom element retains the props type & default value & production mode', () => {
  776. const { content } = compile(
  777. `<script setup lang="ts">
  778. interface Props {
  779. foo?: number;
  780. }
  781. const props = withDefaults(defineProps<Props>(), {
  782. foo: 5.5,
  783. });
  784. </script>`,
  785. { isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
  786. { filename: 'app.ce.vue' },
  787. )
  788. expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
  789. assertCode(content)
  790. })
  791. })