defineComponent.test-d.tsx 18 KB

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