defineComponent.test-d.tsx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. import {
  2. describe,
  3. Component,
  4. defineComponent,
  5. PropType,
  6. ref,
  7. reactive,
  8. createApp,
  9. expectError,
  10. expectType,
  11. ComponentPublicInstance,
  12. ComponentOptions,
  13. SetupContext
  14. } from './index'
  15. describe('with object props', () => {
  16. interface ExpectedProps {
  17. a?: number | undefined
  18. b: string
  19. e?: Function
  20. bb: string
  21. bbb: string
  22. cc?: string[] | undefined
  23. dd: { n: 1 }
  24. ee?: () => string
  25. ff?: (a: number, b: string) => { a: boolean }
  26. ccc?: string[] | undefined
  27. ddd: string[]
  28. eee: () => { a: string }
  29. fff: (a: number, b: string) => { a: boolean }
  30. hhh: boolean
  31. ggg: 'foo' | 'bar'
  32. validated?: string
  33. }
  34. type GT = string & { __brand: unknown }
  35. const MyComponent = defineComponent({
  36. props: {
  37. a: Number,
  38. // required should make property non-void
  39. b: {
  40. type: String,
  41. required: true
  42. },
  43. e: Function,
  44. // default value should infer type and make it non-void
  45. bb: {
  46. default: 'hello'
  47. },
  48. bbb: {
  49. // Note: default function value requires arrow syntax + explicit
  50. // annotation
  51. default: (props: any) => (props.bb as string) || 'foo'
  52. },
  53. // explicit type casting
  54. cc: Array as PropType<string[]>,
  55. // required + type casting
  56. dd: {
  57. type: Object as PropType<{ n: 1 }>,
  58. required: true
  59. },
  60. // return type
  61. ee: Function as PropType<() => string>,
  62. // arguments + object return
  63. ff: Function as PropType<(a: number, b: string) => { a: boolean }>,
  64. // explicit type casting with constructor
  65. ccc: Array as () => string[],
  66. // required + contructor type casting
  67. ddd: {
  68. type: Array as () => string[],
  69. required: true
  70. },
  71. // required + object return
  72. eee: {
  73. type: Function as PropType<() => { a: string }>,
  74. required: true
  75. },
  76. // required + arguments + object return
  77. fff: {
  78. type: Function as PropType<(a: number, b: string) => { a: boolean }>,
  79. required: true
  80. },
  81. hhh: {
  82. type: Boolean,
  83. required: true
  84. },
  85. // default + type casting
  86. ggg: {
  87. type: String as PropType<'foo' | 'bar'>,
  88. default: 'foo'
  89. },
  90. validated: {
  91. type: String,
  92. // validator requires explicit annotation
  93. validator: (val: unknown) => val !== ''
  94. }
  95. },
  96. setup(props) {
  97. // type assertion. See https://github.com/SamVerschueren/tsd
  98. expectType<ExpectedProps['a']>(props.a)
  99. expectType<ExpectedProps['b']>(props.b)
  100. expectType<ExpectedProps['e']>(props.e)
  101. expectType<ExpectedProps['bb']>(props.bb)
  102. expectType<ExpectedProps['bbb']>(props.bbb)
  103. expectType<ExpectedProps['cc']>(props.cc)
  104. expectType<ExpectedProps['dd']>(props.dd)
  105. expectType<ExpectedProps['ee']>(props.ee)
  106. expectType<ExpectedProps['ff']>(props.ff)
  107. expectType<ExpectedProps['ccc']>(props.ccc)
  108. expectType<ExpectedProps['ddd']>(props.ddd)
  109. expectType<ExpectedProps['eee']>(props.eee)
  110. expectType<ExpectedProps['fff']>(props.fff)
  111. expectType<ExpectedProps['hhh']>(props.hhh)
  112. expectType<ExpectedProps['ggg']>(props.ggg)
  113. expectType<ExpectedProps['validated']>(props.validated)
  114. // @ts-expect-error props should be readonly
  115. expectError((props.a = 1))
  116. // setup context
  117. return {
  118. c: ref(1),
  119. d: {
  120. e: ref('hi')
  121. },
  122. f: reactive({
  123. g: ref('hello' as GT)
  124. })
  125. }
  126. },
  127. render() {
  128. const props = this.$props
  129. expectType<ExpectedProps['a']>(props.a)
  130. expectType<ExpectedProps['b']>(props.b)
  131. expectType<ExpectedProps['e']>(props.e)
  132. expectType<ExpectedProps['bb']>(props.bb)
  133. expectType<ExpectedProps['cc']>(props.cc)
  134. expectType<ExpectedProps['dd']>(props.dd)
  135. expectType<ExpectedProps['ee']>(props.ee)
  136. expectType<ExpectedProps['ff']>(props.ff)
  137. expectType<ExpectedProps['ccc']>(props.ccc)
  138. expectType<ExpectedProps['ddd']>(props.ddd)
  139. expectType<ExpectedProps['eee']>(props.eee)
  140. expectType<ExpectedProps['fff']>(props.fff)
  141. expectType<ExpectedProps['hhh']>(props.hhh)
  142. expectType<ExpectedProps['ggg']>(props.ggg)
  143. // @ts-expect-error props should be readonly
  144. expectError((props.a = 1))
  145. // should also expose declared props on `this`
  146. expectType<ExpectedProps['a']>(this.a)
  147. expectType<ExpectedProps['b']>(this.b)
  148. expectType<ExpectedProps['e']>(this.e)
  149. expectType<ExpectedProps['bb']>(this.bb)
  150. expectType<ExpectedProps['cc']>(this.cc)
  151. expectType<ExpectedProps['dd']>(this.dd)
  152. expectType<ExpectedProps['ee']>(this.ee)
  153. expectType<ExpectedProps['ff']>(this.ff)
  154. expectType<ExpectedProps['ccc']>(this.ccc)
  155. expectType<ExpectedProps['ddd']>(this.ddd)
  156. expectType<ExpectedProps['eee']>(this.eee)
  157. expectType<ExpectedProps['fff']>(this.fff)
  158. expectType<ExpectedProps['hhh']>(this.hhh)
  159. expectType<ExpectedProps['ggg']>(this.ggg)
  160. // @ts-expect-error props on `this` should be readonly
  161. expectError((this.a = 1))
  162. // assert setup context unwrapping
  163. expectType<number>(this.c)
  164. expectType<string>(this.d.e.value)
  165. expectType<GT>(this.f.g)
  166. // setup context properties should be mutable
  167. this.c = 2
  168. return null
  169. }
  170. })
  171. expectType<Component>(MyComponent)
  172. // Test TSX
  173. expectType<JSX.Element>(
  174. <MyComponent
  175. a={1}
  176. b="b"
  177. bb="bb"
  178. e={() => {}}
  179. cc={['cc']}
  180. dd={{ n: 1 }}
  181. ee={() => 'ee'}
  182. ccc={['ccc']}
  183. ddd={['ddd']}
  184. eee={() => ({ a: 'eee' })}
  185. fff={(a, b) => ({ a: a > +b })}
  186. hhh={false}
  187. ggg="foo"
  188. // should allow class/style as attrs
  189. class="bar"
  190. style={{ color: 'red' }}
  191. // should allow key
  192. key={'foo'}
  193. // should allow ref
  194. ref={'foo'}
  195. />
  196. )
  197. expectType<Component>(
  198. <MyComponent
  199. b="b"
  200. dd={{ n: 1 }}
  201. ddd={['ddd']}
  202. eee={() => ({ a: 'eee' })}
  203. fff={(a, b) => ({ a: a > +b })}
  204. hhh={false}
  205. />
  206. )
  207. // @ts-expect-error missing required props
  208. expectError(<MyComponent />)
  209. expectError(
  210. // @ts-expect-error wrong prop types
  211. <MyComponent a={'wrong type'} b="foo" dd={{ n: 1 }} ddd={['foo']} />
  212. )
  213. expectError(
  214. // @ts-expect-error wrong prop types
  215. <MyComponent ggg="baz" />
  216. )
  217. // @ts-expect-error
  218. expectError(<MyComponent b="foo" dd={{ n: 'string' }} ddd={['foo']} />)
  219. // `this` should be void inside of prop validators and prop default factories
  220. defineComponent({
  221. props: {
  222. myProp: {
  223. type: Number,
  224. validator(val: unknown): boolean {
  225. // @ts-expect-error
  226. return val !== this.otherProp
  227. },
  228. default(): number {
  229. // @ts-expect-error
  230. return this.otherProp + 1
  231. }
  232. },
  233. otherProp: {
  234. type: Number,
  235. required: true
  236. }
  237. }
  238. })
  239. })
  240. // describe('type inference w/ optional props declaration', () => {
  241. // const MyComponent = defineComponent({
  242. // setup(_props: { msg: string }) {
  243. // return {
  244. // a: 1
  245. // }
  246. // },
  247. // render() {
  248. // expectType<string>(this.$props.msg)
  249. // // props should be readonly
  250. // expectError((this.$props.msg = 'foo'))
  251. // // should not expose on `this`
  252. // expectError(this.msg)
  253. // expectType<number>(this.a)
  254. // return null
  255. // }
  256. // })
  257. // expectType<JSX.Element>(<MyComponent msg="foo" />)
  258. // expectError(<MyComponent />)
  259. // expectError(<MyComponent msg={1} />)
  260. // })
  261. // describe('type inference w/ direct setup function', () => {
  262. // const MyComponent = defineComponent((_props: { msg: string }) => {})
  263. // expectType<JSX.Element>(<MyComponent msg="foo" />)
  264. // expectError(<MyComponent />)
  265. // expectError(<MyComponent msg={1} />)
  266. // })
  267. describe('type inference w/ array props declaration', () => {
  268. const MyComponent = defineComponent({
  269. props: ['a', 'b'],
  270. setup(props) {
  271. // @ts-expect-error props should be readonly
  272. expectError((props.a = 1))
  273. expectType<any>(props.a)
  274. expectType<any>(props.b)
  275. return {
  276. c: 1
  277. }
  278. },
  279. render() {
  280. expectType<any>(this.$props.a)
  281. expectType<any>(this.$props.b)
  282. // @ts-expect-error
  283. expectError((this.$props.a = 1))
  284. expectType<any>(this.a)
  285. expectType<any>(this.b)
  286. expectType<number>(this.c)
  287. }
  288. })
  289. expectType<JSX.Element>(<MyComponent a={[1, 2]} b="b" />)
  290. // @ts-expect-error
  291. expectError(<MyComponent other="other" />)
  292. })
  293. describe('type inference w/ options API', () => {
  294. defineComponent({
  295. props: { a: Number },
  296. setup() {
  297. return {
  298. b: 123
  299. }
  300. },
  301. data() {
  302. // Limitation: we cannot expose the return result of setup() on `this`
  303. // here in data() - somehow that would mess up the inference
  304. expectType<number | undefined>(this.a)
  305. return {
  306. c: this.a || 123
  307. }
  308. },
  309. computed: {
  310. d(): number {
  311. expectType<number>(this.b)
  312. return this.b + 1
  313. },
  314. e: {
  315. get(): number {
  316. expectType<number>(this.b)
  317. expectType<number>(this.d)
  318. return this.b + this.d
  319. },
  320. set(v: number) {
  321. expectType<number>(this.b)
  322. expectType<number>(this.d)
  323. expectType<number>(v)
  324. }
  325. }
  326. },
  327. watch: {
  328. a() {
  329. expectType<number>(this.b)
  330. this.b + 1
  331. }
  332. },
  333. created() {
  334. // props
  335. expectType<number | undefined>(this.a)
  336. // returned from setup()
  337. expectType<number>(this.b)
  338. // returned from data()
  339. expectType<number>(this.c)
  340. // computed
  341. expectType<number>(this.d)
  342. // computed get/set
  343. expectType<number>(this.e)
  344. },
  345. methods: {
  346. doSomething() {
  347. // props
  348. expectType<number | undefined>(this.a)
  349. // returned from setup()
  350. expectType<number>(this.b)
  351. // returned from data()
  352. expectType<number>(this.c)
  353. // computed
  354. expectType<number>(this.d)
  355. // computed get/set
  356. expectType<number>(this.e)
  357. },
  358. returnSomething() {
  359. return this.a
  360. }
  361. },
  362. render() {
  363. // props
  364. expectType<number | undefined>(this.a)
  365. // returned from setup()
  366. expectType<number>(this.b)
  367. // returned from data()
  368. expectType<number>(this.c)
  369. // computed
  370. expectType<number>(this.d)
  371. // computed get/set
  372. expectType<number>(this.e)
  373. // method
  374. expectType<() => number | undefined>(this.returnSomething)
  375. }
  376. })
  377. })
  378. describe('with mixins', () => {
  379. const MixinA = defineComponent({
  380. props: {
  381. aP1: {
  382. type: String,
  383. default: 'aP1'
  384. },
  385. aP2: Boolean
  386. },
  387. data() {
  388. return {
  389. a: 1
  390. }
  391. }
  392. })
  393. const MixinB = defineComponent({
  394. props: ['bP1', 'bP2'],
  395. data() {
  396. return {
  397. b: 2
  398. }
  399. }
  400. })
  401. const MixinC = defineComponent({
  402. data() {
  403. return {
  404. c: 3
  405. }
  406. }
  407. })
  408. const MixinD = defineComponent({
  409. mixins: [MixinA],
  410. data() {
  411. return {
  412. d: 4
  413. }
  414. },
  415. computed: {
  416. dC1(): number {
  417. return this.d + this.a
  418. },
  419. dC2(): string {
  420. return this.aP1 + 'dC2'
  421. }
  422. }
  423. })
  424. const MyComponent = defineComponent({
  425. mixins: [MixinA, MixinB, MixinC, MixinD],
  426. props: {
  427. // required should make property non-void
  428. z: {
  429. type: String,
  430. required: true
  431. }
  432. },
  433. render() {
  434. const props = this.$props
  435. // props
  436. expectType<string>(props.aP1)
  437. expectType<boolean | undefined>(props.aP2)
  438. expectType<any>(props.bP1)
  439. expectType<any>(props.bP2)
  440. expectType<string>(props.z)
  441. const data = this.$data
  442. expectType<number>(data.a)
  443. expectType<number>(data.b)
  444. expectType<number>(data.c)
  445. expectType<number>(data.d)
  446. // should also expose declared props on `this`
  447. expectType<number>(this.a)
  448. expectType<string>(this.aP1)
  449. expectType<boolean | undefined>(this.aP2)
  450. expectType<number>(this.b)
  451. expectType<any>(this.bP1)
  452. expectType<number>(this.c)
  453. expectType<number>(this.d)
  454. expectType<number>(this.dC1)
  455. expectType<string>(this.dC2)
  456. // props should be readonly
  457. // @ts-expect-error
  458. expectError((this.aP1 = 'new'))
  459. // @ts-expect-error
  460. expectError((this.z = 1))
  461. // props on `this` should be readonly
  462. // @ts-expect-error
  463. expectError((this.bP1 = 1))
  464. // string value can not assigned to number type value
  465. // @ts-expect-error
  466. expectError((this.c = '1'))
  467. // setup context properties should be mutable
  468. this.d = 5
  469. return null
  470. }
  471. })
  472. // Test TSX
  473. expectType<JSX.Element>(
  474. <MyComponent aP1={'aP'} aP2 bP1={1} bP2={[1, 2]} z={'z'} />
  475. )
  476. // missing required props
  477. // @ts-expect-error
  478. expectError(<MyComponent />)
  479. // wrong prop types
  480. // @ts-expect-error
  481. expectError(<MyComponent aP1="ap" aP2={'wrong type'} bP1="b" z={'z'} />)
  482. // @ts-expect-error
  483. expectError(<MyComponent aP1={1} bP2={[1]} />)
  484. })
  485. describe('with extends', () => {
  486. const Base = defineComponent({
  487. props: {
  488. aP1: Boolean,
  489. aP2: {
  490. type: Number,
  491. default: 2
  492. }
  493. },
  494. data() {
  495. return {
  496. a: 1
  497. }
  498. },
  499. computed: {
  500. c(): number {
  501. return this.aP2 + this.a
  502. }
  503. }
  504. })
  505. const MyComponent = defineComponent({
  506. extends: Base,
  507. props: {
  508. // required should make property non-void
  509. z: {
  510. type: String,
  511. required: true
  512. }
  513. },
  514. render() {
  515. const props = this.$props
  516. // props
  517. expectType<boolean | undefined>(props.aP1)
  518. expectType<number>(props.aP2)
  519. expectType<string>(props.z)
  520. const data = this.$data
  521. expectType<number>(data.a)
  522. // should also expose declared props on `this`
  523. expectType<number>(this.a)
  524. expectType<boolean | undefined>(this.aP1)
  525. expectType<number>(this.aP2)
  526. // setup context properties should be mutable
  527. this.a = 5
  528. return null
  529. }
  530. })
  531. // Test TSX
  532. expectType<JSX.Element>(<MyComponent aP2={3} aP1 z={'z'} />)
  533. // missing required props
  534. // @ts-expect-error
  535. expectError(<MyComponent />)
  536. // wrong prop types
  537. // @ts-expect-error
  538. expectError(<MyComponent aP2={'wrong type'} z={'z'} />)
  539. // @ts-expect-error
  540. expectError(<MyComponent aP1={3} />)
  541. })
  542. describe('extends with mixins', () => {
  543. const Mixin = defineComponent({
  544. props: {
  545. mP1: {
  546. type: String,
  547. default: 'mP1'
  548. },
  549. mP2: Boolean
  550. },
  551. data() {
  552. return {
  553. a: 1
  554. }
  555. }
  556. })
  557. const Base = defineComponent({
  558. props: {
  559. p1: Boolean,
  560. p2: {
  561. type: Number,
  562. default: 2
  563. }
  564. },
  565. data() {
  566. return {
  567. b: 2
  568. }
  569. },
  570. computed: {
  571. c(): number {
  572. return this.p2 + this.b
  573. }
  574. }
  575. })
  576. const MyComponent = defineComponent({
  577. extends: Base,
  578. mixins: [Mixin],
  579. props: {
  580. // required should make property non-void
  581. z: {
  582. type: String,
  583. required: true
  584. }
  585. },
  586. render() {
  587. const props = this.$props
  588. // props
  589. expectType<boolean | undefined>(props.p1)
  590. expectType<number>(props.p2)
  591. expectType<string>(props.z)
  592. expectType<string>(props.mP1)
  593. expectType<boolean | undefined>(props.mP2)
  594. const data = this.$data
  595. expectType<number>(data.a)
  596. expectType<number>(data.b)
  597. // should also expose declared props on `this`
  598. expectType<number>(this.a)
  599. expectType<number>(this.b)
  600. expectType<boolean | undefined>(this.p1)
  601. expectType<number>(this.p2)
  602. expectType<string>(this.mP1)
  603. expectType<boolean | undefined>(this.mP2)
  604. // setup context properties should be mutable
  605. this.a = 5
  606. return null
  607. }
  608. })
  609. // Test TSX
  610. expectType<JSX.Element>(<MyComponent mP1="p1" mP2 p1 p2={1} z={'z'} />)
  611. // missing required props
  612. // @ts-expect-error
  613. expectError(<MyComponent />)
  614. // wrong prop types
  615. // @ts-expect-error
  616. expectError(<MyComponent p2={'wrong type'} z={'z'} />)
  617. // @ts-expect-error
  618. expectError(<MyComponent mP1={3} />)
  619. })
  620. describe('compatibility w/ createApp', () => {
  621. const comp = defineComponent({})
  622. createApp(comp).mount('#hello')
  623. const comp2 = defineComponent({
  624. props: { foo: String }
  625. })
  626. createApp(comp2).mount('#hello')
  627. const comp3 = defineComponent({
  628. setup() {
  629. return {
  630. a: 1
  631. }
  632. }
  633. })
  634. createApp(comp3).mount('#hello')
  635. })
  636. describe('defineComponent', () => {
  637. test('should accept components defined with defineComponent', () => {
  638. const comp = defineComponent({})
  639. defineComponent({
  640. components: { comp }
  641. })
  642. })
  643. })
  644. describe('emits', () => {
  645. // Note: for TSX inference, ideally we want to map emits to onXXX props,
  646. // but that requires type-level string constant concatenation as suggested in
  647. // https://github.com/Microsoft/TypeScript/issues/12754
  648. // The workaround for TSX users is instead of using emits, declare onXXX props
  649. // and call them instead. Since `v-on:click` compiles to an `onClick` prop,
  650. // this would also support other users consuming the component in templates
  651. // with `v-on` listeners.
  652. // with object emits
  653. defineComponent({
  654. emits: {
  655. click: (n: number) => typeof n === 'number',
  656. input: (b: string) => b.length > 1
  657. },
  658. setup(props, { emit }) {
  659. emit('click', 1)
  660. emit('input', 'foo')
  661. // @ts-expect-error
  662. expectError(emit('nope'))
  663. // @ts-expect-error
  664. expectError(emit('click'))
  665. // @ts-expect-error
  666. expectError(emit('click', 'foo'))
  667. // @ts-expect-error
  668. expectError(emit('input'))
  669. // @ts-expect-error
  670. expectError(emit('input', 1))
  671. },
  672. created() {
  673. this.$emit('click', 1)
  674. this.$emit('input', 'foo')
  675. // @ts-expect-error
  676. expectError(this.$emit('nope'))
  677. // @ts-expect-error
  678. expectError(this.$emit('click'))
  679. // @ts-expect-error
  680. expectError(this.$emit('click', 'foo'))
  681. // @ts-expect-error
  682. expectError(this.$emit('input'))
  683. // @ts-expect-error
  684. expectError(this.$emit('input', 1))
  685. }
  686. })
  687. // with array emits
  688. defineComponent({
  689. emits: ['foo', 'bar'],
  690. setup(props, { emit }) {
  691. emit('foo')
  692. emit('foo', 123)
  693. emit('bar')
  694. // @ts-expect-error
  695. expectError(emit('nope'))
  696. },
  697. created() {
  698. this.$emit('foo')
  699. this.$emit('foo', 123)
  700. this.$emit('bar')
  701. // @ts-expect-error
  702. expectError(this.$emit('nope'))
  703. }
  704. })
  705. // without emits
  706. defineComponent({
  707. setup(props, { emit }) {
  708. emit('test', 1)
  709. emit('test')
  710. }
  711. })
  712. // emit should be valid when ComponentPublicInstance is used.
  713. const instance = {} as ComponentPublicInstance
  714. instance.$emit('test', 1)
  715. instance.$emit('test')
  716. // `this` should be void inside of emits validators
  717. defineComponent({
  718. props: ['bar'],
  719. emits: {
  720. foo(): boolean {
  721. // @ts-expect-error
  722. return this.bar === 3
  723. }
  724. }
  725. })
  726. })
  727. describe('componentOptions setup should be `SetupContext`', () => {
  728. expect<ComponentOptions['setup']>({} as (
  729. props: Record<string, any>,
  730. ctx: SetupContext
  731. ) => any)
  732. })