defineComponent.test-d.tsx 19 KB

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