defineComponent.test-d.tsx 16 KB

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