defineComponent.test-d.tsx 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136
  1. import {
  2. describe,
  3. Component,
  4. defineComponent,
  5. PropType,
  6. ref,
  7. reactive,
  8. createApp,
  9. expectError,
  10. expectType,
  11. ComponentPublicInstance,
  12. ComponentOptions,
  13. SetupContext,
  14. IsUnion,
  15. h
  16. } from './index'
  17. describe('with object props', () => {
  18. interface ExpectedProps {
  19. a?: number | undefined
  20. b: string
  21. e?: Function
  22. h: boolean
  23. bb: string
  24. bbb: string
  25. bbbb: string | undefined
  26. bbbbb: string | undefined
  27. cc?: string[] | undefined
  28. dd: { n: 1 }
  29. ee?: () => string
  30. ff?: (a: number, b: string) => { a: boolean }
  31. ccc?: string[] | undefined
  32. ddd: string[]
  33. eee: () => { a: string }
  34. fff: (a: number, b: string) => { a: boolean }
  35. hhh: boolean
  36. ggg: 'foo' | 'bar'
  37. ffff: (a: number, b: string) => { a: boolean }
  38. iii?: (() => string) | (() => number)
  39. jjj: ((arg1: string) => string) | ((arg1: string, arg2: string) => string)
  40. kkk?: any
  41. validated?: string
  42. date?: Date
  43. }
  44. type GT = string & { __brand: unknown }
  45. const MyComponent = defineComponent({
  46. props: {
  47. a: Number,
  48. // required should make property non-void
  49. b: {
  50. type: String,
  51. required: true
  52. },
  53. e: Function,
  54. h: Boolean,
  55. // default value should infer type and make it non-void
  56. bb: {
  57. default: 'hello'
  58. },
  59. bbb: {
  60. // Note: default function value requires arrow syntax + explicit
  61. // annotation
  62. default: (props: any) => (props.bb as string) || 'foo'
  63. },
  64. bbbb: {
  65. type: String,
  66. default: undefined
  67. },
  68. bbbbb: {
  69. type: String,
  70. default: () => undefined
  71. },
  72. // explicit type casting
  73. cc: Array as PropType<string[]>,
  74. // required + type casting
  75. dd: {
  76. type: Object as PropType<{ n: 1 }>,
  77. required: true
  78. },
  79. // return type
  80. ee: Function as PropType<() => string>,
  81. // arguments + object return
  82. ff: Function as PropType<(a: number, b: string) => { a: boolean }>,
  83. // explicit type casting with constructor
  84. ccc: Array as () => string[],
  85. // required + contructor type casting
  86. ddd: {
  87. type: Array as () => string[],
  88. required: true
  89. },
  90. // required + object return
  91. eee: {
  92. type: Function as PropType<() => { a: string }>,
  93. required: true
  94. },
  95. // required + arguments + object return
  96. fff: {
  97. type: Function as PropType<(a: number, b: string) => { a: boolean }>,
  98. required: true
  99. },
  100. hhh: {
  101. type: Boolean,
  102. required: true
  103. },
  104. // default + type casting
  105. ggg: {
  106. type: String as PropType<'foo' | 'bar'>,
  107. default: 'foo'
  108. },
  109. // default + function
  110. ffff: {
  111. type: Function as PropType<(a: number, b: string) => { a: boolean }>,
  112. default: (a: number, b: string) => ({ a: a > +b })
  113. },
  114. // union + function with different return types
  115. iii: Function as PropType<(() => string) | (() => number)>,
  116. // union + function with different args & same return type
  117. jjj: {
  118. type: Function as PropType<
  119. ((arg1: string) => string) | ((arg1: string, arg2: string) => string)
  120. >,
  121. required: true
  122. },
  123. kkk: null,
  124. validated: {
  125. type: String,
  126. // validator requires explicit annotation
  127. validator: (val: unknown) => val !== ''
  128. },
  129. date: Date
  130. },
  131. setup(props) {
  132. // type assertion. See https://github.com/SamVerschueren/tsd
  133. expectType<ExpectedProps['a']>(props.a)
  134. expectType<ExpectedProps['b']>(props.b)
  135. expectType<ExpectedProps['e']>(props.e)
  136. expectType<ExpectedProps['h']>(props.h)
  137. expectType<ExpectedProps['bb']>(props.bb)
  138. expectType<ExpectedProps['bbb']>(props.bbb)
  139. expectType<ExpectedProps['bbbb']>(props.bbbb)
  140. expectType<ExpectedProps['bbbbb']>(props.bbbbb)
  141. expectType<ExpectedProps['cc']>(props.cc)
  142. expectType<ExpectedProps['dd']>(props.dd)
  143. expectType<ExpectedProps['ee']>(props.ee)
  144. expectType<ExpectedProps['ff']>(props.ff)
  145. expectType<ExpectedProps['ccc']>(props.ccc)
  146. expectType<ExpectedProps['ddd']>(props.ddd)
  147. expectType<ExpectedProps['eee']>(props.eee)
  148. expectType<ExpectedProps['fff']>(props.fff)
  149. expectType<ExpectedProps['hhh']>(props.hhh)
  150. expectType<ExpectedProps['ggg']>(props.ggg)
  151. expectType<ExpectedProps['ffff']>(props.ffff)
  152. if (typeof props.iii !== 'function') {
  153. expectType<undefined>(props.iii)
  154. }
  155. expectType<ExpectedProps['iii']>(props.iii)
  156. expectType<IsUnion<typeof props.jjj>>(true)
  157. expectType<ExpectedProps['jjj']>(props.jjj)
  158. expectType<ExpectedProps['kkk']>(props.kkk)
  159. expectType<ExpectedProps['validated']>(props.validated)
  160. expectType<ExpectedProps['date']>(props.date)
  161. // @ts-expect-error props should be readonly
  162. expectError((props.a = 1))
  163. // setup context
  164. return {
  165. c: ref(1),
  166. d: {
  167. e: ref('hi')
  168. },
  169. f: reactive({
  170. g: ref('hello' as GT)
  171. })
  172. }
  173. },
  174. render() {
  175. const props = this.$props
  176. expectType<ExpectedProps['a']>(props.a)
  177. expectType<ExpectedProps['b']>(props.b)
  178. expectType<ExpectedProps['e']>(props.e)
  179. expectType<ExpectedProps['h']>(props.h)
  180. expectType<ExpectedProps['bb']>(props.bb)
  181. expectType<ExpectedProps['cc']>(props.cc)
  182. expectType<ExpectedProps['dd']>(props.dd)
  183. expectType<ExpectedProps['ee']>(props.ee)
  184. expectType<ExpectedProps['ff']>(props.ff)
  185. expectType<ExpectedProps['ccc']>(props.ccc)
  186. expectType<ExpectedProps['ddd']>(props.ddd)
  187. expectType<ExpectedProps['eee']>(props.eee)
  188. expectType<ExpectedProps['fff']>(props.fff)
  189. expectType<ExpectedProps['hhh']>(props.hhh)
  190. expectType<ExpectedProps['ggg']>(props.ggg)
  191. if (typeof props.iii !== 'function') {
  192. expectType<undefined>(props.iii)
  193. }
  194. expectType<ExpectedProps['iii']>(props.iii)
  195. expectType<IsUnion<typeof props.jjj>>(true)
  196. expectType<ExpectedProps['jjj']>(props.jjj)
  197. expectType<ExpectedProps['kkk']>(props.kkk)
  198. // @ts-expect-error props should be readonly
  199. expectError((props.a = 1))
  200. // should also expose declared props on `this`
  201. expectType<ExpectedProps['a']>(this.a)
  202. expectType<ExpectedProps['b']>(this.b)
  203. expectType<ExpectedProps['e']>(this.e)
  204. expectType<ExpectedProps['h']>(this.h)
  205. expectType<ExpectedProps['bb']>(this.bb)
  206. expectType<ExpectedProps['cc']>(this.cc)
  207. expectType<ExpectedProps['dd']>(this.dd)
  208. expectType<ExpectedProps['ee']>(this.ee)
  209. expectType<ExpectedProps['ff']>(this.ff)
  210. expectType<ExpectedProps['ccc']>(this.ccc)
  211. expectType<ExpectedProps['ddd']>(this.ddd)
  212. expectType<ExpectedProps['eee']>(this.eee)
  213. expectType<ExpectedProps['fff']>(this.fff)
  214. expectType<ExpectedProps['hhh']>(this.hhh)
  215. expectType<ExpectedProps['ggg']>(this.ggg)
  216. if (typeof this.iii !== 'function') {
  217. expectType<undefined>(this.iii)
  218. }
  219. expectType<ExpectedProps['iii']>(this.iii)
  220. const { jjj } = this
  221. expectType<IsUnion<typeof jjj>>(true)
  222. expectType<ExpectedProps['jjj']>(this.jjj)
  223. expectType<ExpectedProps['kkk']>(this.kkk)
  224. // @ts-expect-error props on `this` should be readonly
  225. expectError((this.a = 1))
  226. // assert setup context unwrapping
  227. expectType<number>(this.c)
  228. expectType<string>(this.d.e.value)
  229. expectType<GT>(this.f.g)
  230. // setup context properties should be mutable
  231. this.c = 2
  232. return null
  233. }
  234. })
  235. expectType<Component>(MyComponent)
  236. // Test TSX
  237. expectType<JSX.Element>(
  238. <MyComponent
  239. a={1}
  240. b="b"
  241. bb="bb"
  242. e={() => {}}
  243. cc={['cc']}
  244. dd={{ n: 1 }}
  245. ee={() => 'ee'}
  246. ccc={['ccc']}
  247. ddd={['ddd']}
  248. eee={() => ({ a: 'eee' })}
  249. fff={(a, b) => ({ a: a > +b })}
  250. hhh={false}
  251. ggg="foo"
  252. jjj={() => ''}
  253. // should allow class/style as attrs
  254. class="bar"
  255. style={{ color: 'red' }}
  256. // should allow key
  257. key={'foo'}
  258. // should allow ref
  259. ref={'foo'}
  260. />
  261. )
  262. expectType<Component>(
  263. <MyComponent
  264. b="b"
  265. dd={{ n: 1 }}
  266. ddd={['ddd']}
  267. eee={() => ({ a: 'eee' })}
  268. fff={(a, b) => ({ a: a > +b })}
  269. hhh={false}
  270. jjj={() => ''}
  271. />
  272. )
  273. // @ts-expect-error missing required props
  274. expectError(<MyComponent />)
  275. expectError(
  276. // @ts-expect-error wrong prop types
  277. <MyComponent a={'wrong type'} b="foo" dd={{ n: 1 }} ddd={['foo']} />
  278. )
  279. expectError(
  280. // @ts-expect-error wrong prop types
  281. <MyComponent ggg="baz" />
  282. )
  283. // @ts-expect-error
  284. expectError(<MyComponent b="foo" dd={{ n: 'string' }} ddd={['foo']} />)
  285. // `this` should be void inside of prop validators and prop default factories
  286. defineComponent({
  287. props: {
  288. myProp: {
  289. type: Number,
  290. validator(val: unknown): boolean {
  291. // @ts-expect-error
  292. return val !== this.otherProp
  293. },
  294. default(): number {
  295. // @ts-expect-error
  296. return this.otherProp + 1
  297. }
  298. },
  299. otherProp: {
  300. type: Number,
  301. required: true
  302. }
  303. }
  304. })
  305. })
  306. // describe('type inference w/ optional props declaration', () => {
  307. // const MyComponent = defineComponent({
  308. // setup(_props: { msg: string }) {
  309. // return {
  310. // a: 1
  311. // }
  312. // },
  313. // render() {
  314. // expectType<string>(this.$props.msg)
  315. // // props should be readonly
  316. // expectError((this.$props.msg = 'foo'))
  317. // // should not expose on `this`
  318. // expectError(this.msg)
  319. // expectType<number>(this.a)
  320. // return null
  321. // }
  322. // })
  323. // expectType<JSX.Element>(<MyComponent msg="foo" />)
  324. // expectError(<MyComponent />)
  325. // expectError(<MyComponent msg={1} />)
  326. // })
  327. // describe('type inference w/ direct setup function', () => {
  328. // const MyComponent = defineComponent((_props: { msg: string }) => {})
  329. // expectType<JSX.Element>(<MyComponent msg="foo" />)
  330. // expectError(<MyComponent />)
  331. // expectError(<MyComponent msg={1} />)
  332. // })
  333. describe('type inference w/ array props declaration', () => {
  334. const MyComponent = defineComponent({
  335. props: ['a', 'b'],
  336. setup(props) {
  337. // @ts-expect-error props should be readonly
  338. expectError((props.a = 1))
  339. expectType<any>(props.a)
  340. expectType<any>(props.b)
  341. return {
  342. c: 1
  343. }
  344. },
  345. render() {
  346. expectType<any>(this.$props.a)
  347. expectType<any>(this.$props.b)
  348. // @ts-expect-error
  349. expectError((this.$props.a = 1))
  350. expectType<any>(this.a)
  351. expectType<any>(this.b)
  352. expectType<number>(this.c)
  353. }
  354. })
  355. expectType<JSX.Element>(<MyComponent a={[1, 2]} b="b" />)
  356. // @ts-expect-error
  357. expectError(<MyComponent other="other" />)
  358. })
  359. describe('type inference w/ options API', () => {
  360. defineComponent({
  361. props: { a: Number },
  362. setup() {
  363. return {
  364. b: 123
  365. }
  366. },
  367. data() {
  368. // Limitation: we cannot expose the return result of setup() on `this`
  369. // here in data() - somehow that would mess up the inference
  370. expectType<number | undefined>(this.a)
  371. return {
  372. c: this.a || 123,
  373. someRef: ref(0)
  374. }
  375. },
  376. computed: {
  377. d() {
  378. expectType<number>(this.b)
  379. return this.b + 1
  380. },
  381. e: {
  382. get() {
  383. expectType<number>(this.b)
  384. expectType<number>(this.d)
  385. return this.b + this.d
  386. },
  387. set(v: number) {
  388. expectType<number>(this.b)
  389. expectType<number>(this.d)
  390. expectType<number>(v)
  391. }
  392. }
  393. },
  394. watch: {
  395. a() {
  396. expectType<number>(this.b)
  397. this.b + 1
  398. }
  399. },
  400. created() {
  401. // props
  402. expectType<number | undefined>(this.a)
  403. // returned from setup()
  404. expectType<number>(this.b)
  405. // returned from data()
  406. expectType<number>(this.c)
  407. // computed
  408. expectType<number>(this.d)
  409. // computed get/set
  410. expectType<number>(this.e)
  411. expectType<number>(this.someRef)
  412. },
  413. methods: {
  414. doSomething() {
  415. // props
  416. expectType<number | undefined>(this.a)
  417. // returned from setup()
  418. expectType<number>(this.b)
  419. // returned from data()
  420. expectType<number>(this.c)
  421. // computed
  422. expectType<number>(this.d)
  423. // computed get/set
  424. expectType<number>(this.e)
  425. },
  426. returnSomething() {
  427. return this.a
  428. }
  429. },
  430. render() {
  431. // props
  432. expectType<number | undefined>(this.a)
  433. // returned from setup()
  434. expectType<number>(this.b)
  435. // returned from data()
  436. expectType<number>(this.c)
  437. // computed
  438. expectType<number>(this.d)
  439. // computed get/set
  440. expectType<number>(this.e)
  441. // method
  442. expectType<() => number | undefined>(this.returnSomething)
  443. }
  444. })
  445. })
  446. describe('with mixins', () => {
  447. const MixinA = defineComponent({
  448. emits: ['bar'],
  449. props: {
  450. aP1: {
  451. type: String,
  452. default: 'aP1'
  453. },
  454. aP2: Boolean
  455. },
  456. data() {
  457. return {
  458. a: 1
  459. }
  460. }
  461. })
  462. const MixinB = defineComponent({
  463. props: ['bP1', 'bP2'],
  464. data() {
  465. return {
  466. b: 2
  467. }
  468. }
  469. })
  470. const MixinC = defineComponent({
  471. data() {
  472. return {
  473. c: 3
  474. }
  475. }
  476. })
  477. const MixinD = defineComponent({
  478. mixins: [MixinA],
  479. data() {
  480. //@ts-expect-error computed are not available on data()
  481. expectError<number>(this.dC1)
  482. //@ts-expect-error computed are not available on data()
  483. expectError<string>(this.dC2)
  484. return {
  485. d: 4
  486. }
  487. },
  488. setup(props) {
  489. expectType<string>(props.aP1)
  490. },
  491. computed: {
  492. dC1() {
  493. return this.d + this.a
  494. },
  495. dC2() {
  496. return this.aP1 + 'dC2'
  497. }
  498. }
  499. })
  500. const MyComponent = defineComponent({
  501. mixins: [MixinA, MixinB, MixinC, MixinD],
  502. emits: ['click'],
  503. props: {
  504. // required should make property non-void
  505. z: {
  506. type: String,
  507. required: true
  508. }
  509. },
  510. data(vm) {
  511. expectType<number>(vm.a)
  512. expectType<number>(vm.b)
  513. expectType<number>(vm.c)
  514. expectType<number>(vm.d)
  515. // should also expose declared props on `this`
  516. expectType<number>(this.a)
  517. expectType<string>(this.aP1)
  518. expectType<boolean | undefined>(this.aP2)
  519. expectType<number>(this.b)
  520. expectType<any>(this.bP1)
  521. expectType<number>(this.c)
  522. expectType<number>(this.d)
  523. return {}
  524. },
  525. setup(props) {
  526. expectType<string>(props.z)
  527. // props
  528. expectType<((...args: any[]) => any) | undefined>(props.onClick)
  529. // from Base
  530. expectType<((...args: any[]) => any) | undefined>(props.onBar)
  531. expectType<string>(props.aP1)
  532. expectType<boolean | undefined>(props.aP2)
  533. expectType<any>(props.bP1)
  534. expectType<any>(props.bP2)
  535. expectType<string>(props.z)
  536. },
  537. render() {
  538. const props = this.$props
  539. // props
  540. expectType<((...args: any[]) => any) | undefined>(props.onClick)
  541. // from Base
  542. expectType<((...args: any[]) => any) | undefined>(props.onBar)
  543. expectType<string>(props.aP1)
  544. expectType<boolean | undefined>(props.aP2)
  545. expectType<any>(props.bP1)
  546. expectType<any>(props.bP2)
  547. expectType<string>(props.z)
  548. const data = this.$data
  549. expectType<number>(data.a)
  550. expectType<number>(data.b)
  551. expectType<number>(data.c)
  552. expectType<number>(data.d)
  553. // should also expose declared props on `this`
  554. expectType<number>(this.a)
  555. expectType<string>(this.aP1)
  556. expectType<boolean | undefined>(this.aP2)
  557. expectType<number>(this.b)
  558. expectType<any>(this.bP1)
  559. expectType<number>(this.c)
  560. expectType<number>(this.d)
  561. expectType<number>(this.dC1)
  562. expectType<string>(this.dC2)
  563. // props should be readonly
  564. // @ts-expect-error
  565. expectError((this.aP1 = 'new'))
  566. // @ts-expect-error
  567. expectError((this.z = 1))
  568. // props on `this` should be readonly
  569. // @ts-expect-error
  570. expectError((this.bP1 = 1))
  571. // string value can not assigned to number type value
  572. // @ts-expect-error
  573. expectError((this.c = '1'))
  574. // setup context properties should be mutable
  575. this.d = 5
  576. return null
  577. }
  578. })
  579. // Test TSX
  580. expectType<JSX.Element>(
  581. <MyComponent aP1={'aP'} aP2 bP1={1} bP2={[1, 2]} z={'z'} />
  582. )
  583. // missing required props
  584. // @ts-expect-error
  585. expectError(<MyComponent />)
  586. // wrong prop types
  587. // @ts-expect-error
  588. expectError(<MyComponent aP1="ap" aP2={'wrong type'} bP1="b" z={'z'} />)
  589. // @ts-expect-error
  590. expectError(<MyComponent aP1={1} bP2={[1]} />)
  591. })
  592. describe('with extends', () => {
  593. const Base = defineComponent({
  594. props: {
  595. aP1: Boolean,
  596. aP2: {
  597. type: Number,
  598. default: 2
  599. }
  600. },
  601. data() {
  602. return {
  603. a: 1
  604. }
  605. },
  606. computed: {
  607. c(): number {
  608. return this.aP2 + this.a
  609. }
  610. }
  611. })
  612. const MyComponent = defineComponent({
  613. extends: Base,
  614. props: {
  615. // required should make property non-void
  616. z: {
  617. type: String,
  618. required: true
  619. }
  620. },
  621. render() {
  622. const props = this.$props
  623. // props
  624. expectType<boolean | undefined>(props.aP1)
  625. expectType<number>(props.aP2)
  626. expectType<string>(props.z)
  627. const data = this.$data
  628. expectType<number>(data.a)
  629. // should also expose declared props on `this`
  630. expectType<number>(this.a)
  631. expectType<boolean | undefined>(this.aP1)
  632. expectType<number>(this.aP2)
  633. // setup context properties should be mutable
  634. this.a = 5
  635. return null
  636. }
  637. })
  638. // Test TSX
  639. expectType<JSX.Element>(<MyComponent aP2={3} aP1 z={'z'} />)
  640. // missing required props
  641. // @ts-expect-error
  642. expectError(<MyComponent />)
  643. // wrong prop types
  644. // @ts-expect-error
  645. expectError(<MyComponent aP2={'wrong type'} z={'z'} />)
  646. // @ts-expect-error
  647. expectError(<MyComponent aP1={3} />)
  648. })
  649. describe('extends with mixins', () => {
  650. const Mixin = defineComponent({
  651. emits: ['bar'],
  652. props: {
  653. mP1: {
  654. type: String,
  655. default: 'mP1'
  656. },
  657. mP2: Boolean,
  658. mP3: {
  659. type: Boolean,
  660. required: true
  661. }
  662. },
  663. data() {
  664. return {
  665. a: 1
  666. }
  667. }
  668. })
  669. const Base = defineComponent({
  670. emits: ['foo'],
  671. props: {
  672. p1: Boolean,
  673. p2: {
  674. type: Number,
  675. default: 2
  676. },
  677. p3: {
  678. type: Boolean,
  679. required: true
  680. }
  681. },
  682. data() {
  683. return {
  684. b: 2
  685. }
  686. },
  687. computed: {
  688. c(): number {
  689. return this.p2 + this.b
  690. }
  691. }
  692. })
  693. const MyComponent = defineComponent({
  694. extends: Base,
  695. mixins: [Mixin],
  696. emits: ['click'],
  697. props: {
  698. // required should make property non-void
  699. z: {
  700. type: String,
  701. required: true
  702. }
  703. },
  704. render() {
  705. const props = this.$props
  706. // props
  707. expectType<((...args: any[]) => any) | undefined>(props.onClick)
  708. // from Mixin
  709. expectType<((...args: any[]) => any) | undefined>(props.onBar)
  710. // from Base
  711. expectType<((...args: any[]) => any) | undefined>(props.onFoo)
  712. expectType<boolean | undefined>(props.p1)
  713. expectType<number>(props.p2)
  714. expectType<string>(props.z)
  715. expectType<string>(props.mP1)
  716. expectType<boolean | undefined>(props.mP2)
  717. const data = this.$data
  718. expectType<number>(data.a)
  719. expectType<number>(data.b)
  720. // should also expose declared props on `this`
  721. expectType<number>(this.a)
  722. expectType<number>(this.b)
  723. expectType<boolean | undefined>(this.p1)
  724. expectType<number>(this.p2)
  725. expectType<string>(this.mP1)
  726. expectType<boolean | undefined>(this.mP2)
  727. // setup context properties should be mutable
  728. this.a = 5
  729. return null
  730. }
  731. })
  732. // Test TSX
  733. expectType<JSX.Element>(<MyComponent mP1="p1" mP2 mP3 p1 p2={1} p3 z={'z'} />)
  734. // mP1, mP2, p1, and p2 have default value. these are not required
  735. expectType<JSX.Element>(<MyComponent mP3 p3 z={'z'} />)
  736. // missing required props
  737. // @ts-expect-error
  738. expectError(<MyComponent mP3 p3 /* z='z' */ />)
  739. // missing required props from mixin
  740. // @ts-expect-error
  741. expectError(<MyComponent /* mP3 */ p3 z="z" />)
  742. // missing required props from extends
  743. // @ts-expect-error
  744. expectError(<MyComponent mP3 /* p3 */ z="z" />)
  745. // wrong prop types
  746. // @ts-expect-error
  747. expectError(<MyComponent p2={'wrong type'} z={'z'} />)
  748. // @ts-expect-error
  749. expectError(<MyComponent mP1={3} />)
  750. // #3468
  751. const CompWithD = defineComponent({
  752. data() {
  753. return { foo: 1 }
  754. }
  755. })
  756. const CompWithC = defineComponent({
  757. computed: {
  758. foo() {
  759. return 1
  760. }
  761. }
  762. })
  763. const CompWithM = defineComponent({ methods: { foo() {} } })
  764. const CompEmpty = defineComponent({})
  765. defineComponent({
  766. mixins: [CompWithD, CompEmpty],
  767. mounted() {
  768. expectType<number>(this.foo)
  769. }
  770. })
  771. defineComponent({
  772. mixins: [CompWithC, CompEmpty],
  773. mounted() {
  774. expectType<number>(this.foo)
  775. }
  776. })
  777. defineComponent({
  778. mixins: [CompWithM, CompEmpty],
  779. mounted() {
  780. expectType<() => void>(this.foo)
  781. }
  782. })
  783. })
  784. describe('compatibility w/ createApp', () => {
  785. const comp = defineComponent({})
  786. createApp(comp).mount('#hello')
  787. const comp2 = defineComponent({
  788. props: { foo: String }
  789. })
  790. createApp(comp2).mount('#hello')
  791. const comp3 = defineComponent({
  792. setup() {
  793. return {
  794. a: 1
  795. }
  796. }
  797. })
  798. createApp(comp3).mount('#hello')
  799. })
  800. describe('defineComponent', () => {
  801. test('should accept components defined with defineComponent', () => {
  802. const comp = defineComponent({})
  803. defineComponent({
  804. components: { comp }
  805. })
  806. })
  807. test('should accept class components with receiving constructor arguments', () => {
  808. class Comp {
  809. static __vccOpts = {}
  810. constructor(_props: { foo: string }) {}
  811. }
  812. defineComponent({
  813. components: { Comp }
  814. })
  815. })
  816. })
  817. describe('emits', () => {
  818. // Note: for TSX inference, ideally we want to map emits to onXXX props,
  819. // but that requires type-level string constant concatenation as suggested in
  820. // https://github.com/Microsoft/TypeScript/issues/12754
  821. // The workaround for TSX users is instead of using emits, declare onXXX props
  822. // and call them instead. Since `v-on:click` compiles to an `onClick` prop,
  823. // this would also support other users consuming the component in templates
  824. // with `v-on` listeners.
  825. // with object emits
  826. defineComponent({
  827. emits: {
  828. click: (n: number) => typeof n === 'number',
  829. input: (b: string) => b.length > 1
  830. },
  831. setup(props, { emit }) {
  832. expectType<((n: number) => boolean) | undefined>(props.onClick)
  833. expectType<((b: string) => boolean) | undefined>(props.onInput)
  834. emit('click', 1)
  835. emit('input', 'foo')
  836. // @ts-expect-error
  837. expectError(emit('nope'))
  838. // @ts-expect-error
  839. expectError(emit('click'))
  840. // @ts-expect-error
  841. expectError(emit('click', 'foo'))
  842. // @ts-expect-error
  843. expectError(emit('input'))
  844. // @ts-expect-error
  845. expectError(emit('input', 1))
  846. },
  847. created() {
  848. this.$emit('click', 1)
  849. this.$emit('input', 'foo')
  850. // @ts-expect-error
  851. expectError(this.$emit('nope'))
  852. // @ts-expect-error
  853. expectError(this.$emit('click'))
  854. // @ts-expect-error
  855. expectError(this.$emit('click', 'foo'))
  856. // @ts-expect-error
  857. expectError(this.$emit('input'))
  858. // @ts-expect-error
  859. expectError(this.$emit('input', 1))
  860. },
  861. mounted() {
  862. // #3599
  863. this.$nextTick(function () {
  864. // this should be bound to this instance
  865. this.$emit('click', 1)
  866. this.$emit('input', 'foo')
  867. // @ts-expect-error
  868. expectError(this.$emit('nope'))
  869. // @ts-expect-error
  870. expectError(this.$emit('click'))
  871. // @ts-expect-error
  872. expectError(this.$emit('click', 'foo'))
  873. // @ts-expect-error
  874. expectError(this.$emit('input'))
  875. // @ts-expect-error
  876. expectError(this.$emit('input', 1))
  877. })
  878. }
  879. })
  880. // with array emits
  881. defineComponent({
  882. emits: ['foo', 'bar'],
  883. setup(props, { emit }) {
  884. expectType<((...args: any[]) => any) | undefined>(props.onFoo)
  885. expectType<((...args: any[]) => any) | undefined>(props.onBar)
  886. emit('foo')
  887. emit('foo', 123)
  888. emit('bar')
  889. // @ts-expect-error
  890. expectError(emit('nope'))
  891. },
  892. created() {
  893. this.$emit('foo')
  894. this.$emit('foo', 123)
  895. this.$emit('bar')
  896. // @ts-expect-error
  897. expectError(this.$emit('nope'))
  898. }
  899. })
  900. // with tsx
  901. const Component = defineComponent({
  902. emits: {
  903. click: (n: number) => typeof n === 'number'
  904. },
  905. setup(props, { emit }) {
  906. expectType<((n: number) => any) | undefined>(props.onClick)
  907. emit('click', 1)
  908. // @ts-expect-error
  909. expectError(emit('click'))
  910. // @ts-expect-error
  911. expectError(emit('click', 'foo'))
  912. }
  913. })
  914. defineComponent({
  915. render() {
  916. return (
  917. <Component
  918. onClick={(n: number) => {
  919. return n + 1
  920. }}
  921. />
  922. )
  923. }
  924. })
  925. // without emits
  926. defineComponent({
  927. setup(props, { emit }) {
  928. emit('test', 1)
  929. emit('test')
  930. }
  931. })
  932. // emit should be valid when ComponentPublicInstance is used.
  933. const instance = {} as ComponentPublicInstance
  934. instance.$emit('test', 1)
  935. instance.$emit('test')
  936. // `this` should be void inside of emits validators
  937. defineComponent({
  938. props: ['bar'],
  939. emits: {
  940. foo(): boolean {
  941. // @ts-expect-error
  942. return this.bar === 3
  943. }
  944. }
  945. })
  946. })
  947. describe('componentOptions setup should be `SetupContext`', () => {
  948. expect<ComponentOptions['setup']>(
  949. {} as (props: Record<string, any>, ctx: SetupContext) => any
  950. )
  951. })
  952. describe('extract instance type', () => {
  953. const Base = defineComponent({
  954. props: {
  955. baseA: {
  956. type: Number,
  957. default: 1
  958. }
  959. }
  960. })
  961. const MixinA = defineComponent({
  962. props: {
  963. mA: {
  964. type: String,
  965. default: ''
  966. }
  967. }
  968. })
  969. const CompA = defineComponent({
  970. extends: Base,
  971. mixins: [MixinA],
  972. props: {
  973. a: {
  974. type: Boolean,
  975. default: false
  976. },
  977. b: {
  978. type: String,
  979. required: true
  980. },
  981. c: Number
  982. }
  983. })
  984. const compA = {} as InstanceType<typeof CompA>
  985. expectType<boolean>(compA.a)
  986. expectType<string>(compA.b)
  987. expectType<number | undefined>(compA.c)
  988. // mixins
  989. expectType<string>(compA.mA)
  990. // extends
  991. expectType<number>(compA.baseA)
  992. // @ts-expect-error
  993. expectError((compA.a = true))
  994. // @ts-expect-error
  995. expectError((compA.b = 'foo'))
  996. // @ts-expect-error
  997. expectError((compA.c = 1))
  998. // @ts-expect-error
  999. expectError((compA.mA = 'foo'))
  1000. // @ts-expect-error
  1001. expectError((compA.baseA = 1))
  1002. })
  1003. describe('async setup', () => {
  1004. type GT = string & { __brand: unknown }
  1005. const Comp = defineComponent({
  1006. async setup() {
  1007. // setup context
  1008. return {
  1009. a: ref(1),
  1010. b: {
  1011. c: ref('hi')
  1012. },
  1013. d: reactive({
  1014. e: ref('hello' as GT)
  1015. })
  1016. }
  1017. },
  1018. render() {
  1019. // assert setup context unwrapping
  1020. expectType<number>(this.a)
  1021. expectType<string>(this.b.c.value)
  1022. expectType<GT>(this.d.e)
  1023. // setup context properties should be mutable
  1024. this.a = 2
  1025. }
  1026. })
  1027. const vm = {} as InstanceType<typeof Comp>
  1028. // assert setup context unwrapping
  1029. expectType<number>(vm.a)
  1030. expectType<string>(vm.b.c.value)
  1031. expectType<GT>(vm.d.e)
  1032. // setup context properties should be mutable
  1033. vm.a = 2
  1034. })
  1035. // check if defineComponent can be exported
  1036. export default {
  1037. // function components
  1038. a: defineComponent(_ => h('div')),
  1039. // no props
  1040. b: defineComponent({
  1041. data() {
  1042. return {}
  1043. }
  1044. }),
  1045. c: defineComponent({
  1046. props: ['a']
  1047. }),
  1048. d: defineComponent({
  1049. props: {
  1050. a: Number
  1051. }
  1052. })
  1053. }