defineComponent.test-d.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  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)
  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 extraneous as attrs
  163. class="bar"
  164. // should allow key
  165. key={'foo'}
  166. // should allow ref
  167. ref={'foo'}
  168. />
  169. )
  170. // @ts-expect-error missing required props
  171. expectError(<MyComponent />)
  172. expectError(
  173. // @ts-expect-error wrong prop types
  174. <MyComponent a={'wrong type'} b="foo" dd={{ n: 1 }} ddd={['foo']} />
  175. )
  176. // @ts-expect-error
  177. expectError(<MyComponent b="foo" dd={{ n: 'string' }} ddd={['foo']} />)
  178. })
  179. // describe('type inference w/ optional props declaration', () => {
  180. // const MyComponent = defineComponent({
  181. // setup(_props: { msg: string }) {
  182. // return {
  183. // a: 1
  184. // }
  185. // },
  186. // render() {
  187. // expectType<string>(this.$props.msg)
  188. // // props should be readonly
  189. // expectError((this.$props.msg = 'foo'))
  190. // // should not expose on `this`
  191. // expectError(this.msg)
  192. // expectType<number>(this.a)
  193. // return null
  194. // }
  195. // })
  196. // expectType<JSX.Element>(<MyComponent msg="foo" />)
  197. // expectError(<MyComponent />)
  198. // expectError(<MyComponent msg={1} />)
  199. // })
  200. // describe('type inference w/ direct setup function', () => {
  201. // const MyComponent = defineComponent((_props: { msg: string }) => {})
  202. // expectType<JSX.Element>(<MyComponent msg="foo" />)
  203. // expectError(<MyComponent />)
  204. // expectError(<MyComponent msg={1} />)
  205. // })
  206. describe('type inference w/ array props declaration', () => {
  207. const MyComponent = defineComponent({
  208. props: ['a', 'b'],
  209. setup(props) {
  210. // @ts-expect-error props should be readonly
  211. expectError((props.a = 1))
  212. expectType<any>(props.a)
  213. expectType<any>(props.b)
  214. return {
  215. c: 1
  216. }
  217. },
  218. render() {
  219. expectType<any>(this.$props.a)
  220. expectType<any>(this.$props.b)
  221. // @ts-expect-error
  222. expectError((this.$props.a = 1))
  223. expectType<any>(this.a)
  224. expectType<any>(this.b)
  225. expectType<number>(this.c)
  226. }
  227. })
  228. expectType<JSX.Element>(<MyComponent a={[1, 2]} b="b" />)
  229. // @ts-expect-error
  230. expectError(<MyComponent other="other" />)
  231. })
  232. describe('type inference w/ options API', () => {
  233. defineComponent({
  234. props: { a: Number },
  235. setup() {
  236. return {
  237. b: 123
  238. }
  239. },
  240. data() {
  241. // Limitation: we cannot expose the return result of setup() on `this`
  242. // here in data() - somehow that would mess up the inference
  243. expectType<number | undefined>(this.a)
  244. return {
  245. c: this.a || 123
  246. }
  247. },
  248. computed: {
  249. d(): number {
  250. expectType<number>(this.b)
  251. return this.b + 1
  252. }
  253. },
  254. watch: {
  255. a() {
  256. expectType<number>(this.b)
  257. this.b + 1
  258. }
  259. },
  260. created() {
  261. // props
  262. expectType<number | undefined>(this.a)
  263. // returned from setup()
  264. expectType<number>(this.b)
  265. // returned from data()
  266. expectType<number>(this.c)
  267. // computed
  268. expectType<number>(this.d)
  269. },
  270. methods: {
  271. doSomething() {
  272. // props
  273. expectType<number | undefined>(this.a)
  274. // returned from setup()
  275. expectType<number>(this.b)
  276. // returned from data()
  277. expectType<number>(this.c)
  278. // computed
  279. expectType<number>(this.d)
  280. }
  281. },
  282. render() {
  283. // props
  284. expectType<number | undefined>(this.a)
  285. // returned from setup()
  286. expectType<number>(this.b)
  287. // returned from data()
  288. expectType<number>(this.c)
  289. // computed
  290. expectType<number>(this.d)
  291. }
  292. })
  293. })
  294. describe('with mixins', () => {
  295. const MixinA = defineComponent({
  296. props: {
  297. aP1: {
  298. type: String,
  299. default: 'aP1'
  300. },
  301. aP2: Boolean
  302. },
  303. data() {
  304. return {
  305. a: 1
  306. }
  307. }
  308. })
  309. const MixinB = defineComponent({
  310. props: ['bP1', 'bP2'],
  311. data() {
  312. return {
  313. b: 2
  314. }
  315. }
  316. })
  317. const MixinC = defineComponent({
  318. data() {
  319. return {
  320. c: 3
  321. }
  322. }
  323. })
  324. const MixinD = defineComponent({
  325. mixins: [MixinA],
  326. data() {
  327. return {
  328. d: 4
  329. }
  330. },
  331. computed: {
  332. dC1(): number {
  333. return this.d + this.a
  334. },
  335. dC2(): string {
  336. return this.aP1 + 'dC2'
  337. }
  338. }
  339. })
  340. const MyComponent = defineComponent({
  341. mixins: [MixinA, MixinB, MixinC, MixinD],
  342. props: {
  343. // required should make property non-void
  344. z: {
  345. type: String,
  346. required: true
  347. }
  348. },
  349. render() {
  350. const props = this.$props
  351. // props
  352. expectType<string>(props.aP1)
  353. expectType<boolean | undefined>(props.aP2)
  354. expectType<any>(props.bP1)
  355. expectType<any>(props.bP2)
  356. expectType<string>(props.z)
  357. const data = this.$data
  358. expectType<number>(data.a)
  359. expectType<number>(data.b)
  360. expectType<number>(data.c)
  361. expectType<number>(data.d)
  362. // should also expose declared props on `this`
  363. expectType<number>(this.a)
  364. expectType<string>(this.aP1)
  365. expectType<boolean | undefined>(this.aP2)
  366. expectType<number>(this.b)
  367. expectType<any>(this.bP1)
  368. expectType<number>(this.c)
  369. expectType<number>(this.d)
  370. expectType<number>(this.dC1)
  371. expectType<string>(this.dC2)
  372. // props should be readonly
  373. // @ts-expect-error
  374. expectError((this.aP1 = 'new'))
  375. // @ts-expect-error
  376. expectError((this.z = 1))
  377. // props on `this` should be readonly
  378. // @ts-expect-error
  379. expectError((this.bP1 = 1))
  380. // string value can not assigned to number type value
  381. // @ts-expect-error
  382. expectError((this.c = '1'))
  383. // setup context properties should be mutable
  384. this.d = 5
  385. return null
  386. }
  387. })
  388. // Test TSX
  389. expectType<JSX.Element>(
  390. <MyComponent aP1={'aP'} aP2 bP1={1} bP2={[1, 2]} z={'z'} />
  391. )
  392. // missing required props
  393. // @ts-expect-error
  394. expectError(<MyComponent />)
  395. // wrong prop types
  396. // @ts-expect-error
  397. expectError(<MyComponent aP1="ap" aP2={'wrong type'} bP1="b" z={'z'} />)
  398. // @ts-expect-error
  399. expectError(<MyComponent aP1={1} bP2={[1]} />)
  400. })
  401. describe('with extends', () => {
  402. const Base = defineComponent({
  403. props: {
  404. aP1: Boolean,
  405. aP2: {
  406. type: Number,
  407. default: 2
  408. }
  409. },
  410. data() {
  411. return {
  412. a: 1
  413. }
  414. },
  415. computed: {
  416. c(): number {
  417. return this.aP2 + this.a
  418. }
  419. }
  420. })
  421. const MyComponent = defineComponent({
  422. extends: Base,
  423. props: {
  424. // required should make property non-void
  425. z: {
  426. type: String,
  427. required: true
  428. }
  429. },
  430. render() {
  431. const props = this.$props
  432. // props
  433. expectType<boolean | undefined>(props.aP1)
  434. expectType<number>(props.aP2)
  435. expectType<string>(props.z)
  436. const data = this.$data
  437. expectType<number>(data.a)
  438. // should also expose declared props on `this`
  439. expectType<number>(this.a)
  440. expectType<boolean | undefined>(this.aP1)
  441. expectType<number>(this.aP2)
  442. // setup context properties should be mutable
  443. this.a = 5
  444. return null
  445. }
  446. })
  447. // Test TSX
  448. expectType<JSX.Element>(<MyComponent aP2={3} aP1 z={'z'} />)
  449. // missing required props
  450. // @ts-expect-error
  451. expectError(<MyComponent />)
  452. // wrong prop types
  453. // @ts-expect-error
  454. expectError(<MyComponent aP2={'wrong type'} z={'z'} />)
  455. // @ts-expect-error
  456. expectError(<MyComponent aP1={3} />)
  457. })
  458. describe('extends with mixins', () => {
  459. const Mixin = defineComponent({
  460. props: {
  461. mP1: {
  462. type: String,
  463. default: 'mP1'
  464. },
  465. mP2: Boolean
  466. },
  467. data() {
  468. return {
  469. a: 1
  470. }
  471. }
  472. })
  473. const Base = defineComponent({
  474. props: {
  475. p1: Boolean,
  476. p2: {
  477. type: Number,
  478. default: 2
  479. }
  480. },
  481. data() {
  482. return {
  483. b: 2
  484. }
  485. },
  486. computed: {
  487. c(): number {
  488. return this.p2 + this.b
  489. }
  490. }
  491. })
  492. const MyComponent = defineComponent({
  493. extends: Base,
  494. mixins: [Mixin],
  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.p1)
  506. expectType<number>(props.p2)
  507. expectType<string>(props.z)
  508. expectType<string>(props.mP1)
  509. expectType<boolean | undefined>(props.mP2)
  510. const data = this.$data
  511. expectType<number>(data.a)
  512. expectType<number>(data.b)
  513. // should also expose declared props on `this`
  514. expectType<number>(this.a)
  515. expectType<number>(this.b)
  516. expectType<boolean | undefined>(this.p1)
  517. expectType<number>(this.p2)
  518. expectType<string>(this.mP1)
  519. expectType<boolean | undefined>(this.mP2)
  520. // setup context properties should be mutable
  521. this.a = 5
  522. return null
  523. }
  524. })
  525. // Test TSX
  526. expectType<JSX.Element>(<MyComponent mP1="p1" mP2 p1 p2={1} z={'z'} />)
  527. // missing required props
  528. // @ts-expect-error
  529. expectError(<MyComponent />)
  530. // wrong prop types
  531. // @ts-expect-error
  532. expectError(<MyComponent p2={'wrong type'} z={'z'} />)
  533. // @ts-expect-error
  534. expectError(<MyComponent mP1={3} />)
  535. })
  536. describe('compatibility w/ createApp', () => {
  537. const comp = defineComponent({})
  538. createApp(comp).mount('#hello')
  539. const comp2 = defineComponent({
  540. props: { foo: String }
  541. })
  542. createApp(comp2).mount('#hello')
  543. const comp3 = defineComponent({
  544. setup() {
  545. return {
  546. a: 1
  547. }
  548. }
  549. })
  550. createApp(comp3).mount('#hello')
  551. })
  552. describe('defineComponent', () => {
  553. test('should accept components defined with defineComponent', () => {
  554. const comp = defineComponent({})
  555. defineComponent({
  556. components: { comp }
  557. })
  558. })
  559. })
  560. describe('emits', () => {
  561. // Note: for TSX inference, ideally we want to map emits to onXXX props,
  562. // but that requires type-level string constant concatenation as suggested in
  563. // https://github.com/Microsoft/TypeScript/issues/12754
  564. // The workaround for TSX users is instead of using emits, declare onXXX props
  565. // and call them instead. Since `v-on:click` compiles to an `onClick` prop,
  566. // this would also support other users consuming the component in templates
  567. // with `v-on` listeners.
  568. // with object emits
  569. defineComponent({
  570. emits: {
  571. click: (n: number) => typeof n === 'number',
  572. input: (b: string) => null
  573. },
  574. setup(props, { emit }) {
  575. emit('click', 1)
  576. emit('input', 'foo')
  577. // @ts-expect-error
  578. expectError(emit('nope'))
  579. // @ts-expect-error
  580. expectError(emit('click'))
  581. // @ts-expect-error
  582. expectError(emit('click', 'foo'))
  583. // @ts-expect-error
  584. expectError(emit('input'))
  585. // @ts-expect-error
  586. expectError(emit('input', 1))
  587. },
  588. created() {
  589. this.$emit('click', 1)
  590. this.$emit('input', 'foo')
  591. // @ts-expect-error
  592. expectError(this.$emit('nope'))
  593. // @ts-expect-error
  594. expectError(this.$emit('click'))
  595. // @ts-expect-error
  596. expectError(this.$emit('click', 'foo'))
  597. // @ts-expect-error
  598. expectError(this.$emit('input'))
  599. // @ts-expect-error
  600. expectError(this.$emit('input', 1))
  601. }
  602. })
  603. // with array emits
  604. defineComponent({
  605. emits: ['foo', 'bar'],
  606. setup(props, { emit }) {
  607. emit('foo')
  608. emit('foo', 123)
  609. emit('bar')
  610. // @ts-expect-error
  611. expectError(emit('nope'))
  612. },
  613. created() {
  614. this.$emit('foo')
  615. this.$emit('foo', 123)
  616. this.$emit('bar')
  617. // @ts-expect-error
  618. expectError(this.$emit('nope'))
  619. }
  620. })
  621. // without emits
  622. defineComponent({
  623. setup(props, { emit }) {
  624. emit('test', 1)
  625. emit('test')
  626. }
  627. })
  628. // emit should be valid when ComponentPublicInstance is used.
  629. const instance = {} as ComponentPublicInstance
  630. instance.$emit('test', 1)
  631. instance.$emit('test')
  632. })
  633. describe('componentOptions setup should be `SetupContext`', () => {
  634. expect<ComponentOptions['setup']>({} as (
  635. props: Record<string, any>,
  636. ctx: SetupContext
  637. ) => any)
  638. })