defineComponent.test-d.tsx 19 KB

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