defineComponent.test-d.tsx 17 KB

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