defineComponent.test-d.tsx 23 KB

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