defineComponent.test-d.tsx 28 KB

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