defineComponent.test-d.tsx 17 KB

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