defineComponent.test-d.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  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. returnSomething() {
  283. return this.a
  284. }
  285. },
  286. render() {
  287. // props
  288. expectType<number | undefined>(this.a)
  289. // returned from setup()
  290. expectType<number>(this.b)
  291. // returned from data()
  292. expectType<number>(this.c)
  293. // computed
  294. expectType<number>(this.d)
  295. // method
  296. expectType<() => number | undefined>(this.returnSomething)
  297. }
  298. })
  299. })
  300. describe('with mixins', () => {
  301. const MixinA = defineComponent({
  302. props: {
  303. aP1: {
  304. type: String,
  305. default: 'aP1'
  306. },
  307. aP2: Boolean
  308. },
  309. data() {
  310. return {
  311. a: 1
  312. }
  313. }
  314. })
  315. const MixinB = defineComponent({
  316. props: ['bP1', 'bP2'],
  317. data() {
  318. return {
  319. b: 2
  320. }
  321. }
  322. })
  323. const MixinC = defineComponent({
  324. data() {
  325. return {
  326. c: 3
  327. }
  328. }
  329. })
  330. const MixinD = defineComponent({
  331. mixins: [MixinA],
  332. data() {
  333. return {
  334. d: 4
  335. }
  336. },
  337. computed: {
  338. dC1(): number {
  339. return this.d + this.a
  340. },
  341. dC2(): string {
  342. return this.aP1 + 'dC2'
  343. }
  344. }
  345. })
  346. const MyComponent = defineComponent({
  347. mixins: [MixinA, MixinB, MixinC, MixinD],
  348. props: {
  349. // required should make property non-void
  350. z: {
  351. type: String,
  352. required: true
  353. }
  354. },
  355. render() {
  356. const props = this.$props
  357. // props
  358. expectType<string>(props.aP1)
  359. expectType<boolean | undefined>(props.aP2)
  360. expectType<any>(props.bP1)
  361. expectType<any>(props.bP2)
  362. expectType<string>(props.z)
  363. const data = this.$data
  364. expectType<number>(data.a)
  365. expectType<number>(data.b)
  366. expectType<number>(data.c)
  367. expectType<number>(data.d)
  368. // should also expose declared props on `this`
  369. expectType<number>(this.a)
  370. expectType<string>(this.aP1)
  371. expectType<boolean | undefined>(this.aP2)
  372. expectType<number>(this.b)
  373. expectType<any>(this.bP1)
  374. expectType<number>(this.c)
  375. expectType<number>(this.d)
  376. expectType<number>(this.dC1)
  377. expectType<string>(this.dC2)
  378. // props should be readonly
  379. // @ts-expect-error
  380. expectError((this.aP1 = 'new'))
  381. // @ts-expect-error
  382. expectError((this.z = 1))
  383. // props on `this` should be readonly
  384. // @ts-expect-error
  385. expectError((this.bP1 = 1))
  386. // string value can not assigned to number type value
  387. // @ts-expect-error
  388. expectError((this.c = '1'))
  389. // setup context properties should be mutable
  390. this.d = 5
  391. return null
  392. }
  393. })
  394. // Test TSX
  395. expectType<JSX.Element>(
  396. <MyComponent aP1={'aP'} aP2 bP1={1} bP2={[1, 2]} z={'z'} />
  397. )
  398. // missing required props
  399. // @ts-expect-error
  400. expectError(<MyComponent />)
  401. // wrong prop types
  402. // @ts-expect-error
  403. expectError(<MyComponent aP1="ap" aP2={'wrong type'} bP1="b" z={'z'} />)
  404. // @ts-expect-error
  405. expectError(<MyComponent aP1={1} bP2={[1]} />)
  406. })
  407. describe('with extends', () => {
  408. const Base = defineComponent({
  409. props: {
  410. aP1: Boolean,
  411. aP2: {
  412. type: Number,
  413. default: 2
  414. }
  415. },
  416. data() {
  417. return {
  418. a: 1
  419. }
  420. },
  421. computed: {
  422. c(): number {
  423. return this.aP2 + this.a
  424. }
  425. }
  426. })
  427. const MyComponent = defineComponent({
  428. extends: Base,
  429. props: {
  430. // required should make property non-void
  431. z: {
  432. type: String,
  433. required: true
  434. }
  435. },
  436. render() {
  437. const props = this.$props
  438. // props
  439. expectType<boolean | undefined>(props.aP1)
  440. expectType<number>(props.aP2)
  441. expectType<string>(props.z)
  442. const data = this.$data
  443. expectType<number>(data.a)
  444. // should also expose declared props on `this`
  445. expectType<number>(this.a)
  446. expectType<boolean | undefined>(this.aP1)
  447. expectType<number>(this.aP2)
  448. // setup context properties should be mutable
  449. this.a = 5
  450. return null
  451. }
  452. })
  453. // Test TSX
  454. expectType<JSX.Element>(<MyComponent aP2={3} aP1 z={'z'} />)
  455. // missing required props
  456. // @ts-expect-error
  457. expectError(<MyComponent />)
  458. // wrong prop types
  459. // @ts-expect-error
  460. expectError(<MyComponent aP2={'wrong type'} z={'z'} />)
  461. // @ts-expect-error
  462. expectError(<MyComponent aP1={3} />)
  463. })
  464. describe('extends with mixins', () => {
  465. const Mixin = defineComponent({
  466. props: {
  467. mP1: {
  468. type: String,
  469. default: 'mP1'
  470. },
  471. mP2: Boolean
  472. },
  473. data() {
  474. return {
  475. a: 1
  476. }
  477. }
  478. })
  479. const Base = defineComponent({
  480. props: {
  481. p1: Boolean,
  482. p2: {
  483. type: Number,
  484. default: 2
  485. }
  486. },
  487. data() {
  488. return {
  489. b: 2
  490. }
  491. },
  492. computed: {
  493. c(): number {
  494. return this.p2 + this.b
  495. }
  496. }
  497. })
  498. const MyComponent = defineComponent({
  499. extends: Base,
  500. mixins: [Mixin],
  501. props: {
  502. // required should make property non-void
  503. z: {
  504. type: String,
  505. required: true
  506. }
  507. },
  508. render() {
  509. const props = this.$props
  510. // props
  511. expectType<boolean | undefined>(props.p1)
  512. expectType<number>(props.p2)
  513. expectType<string>(props.z)
  514. expectType<string>(props.mP1)
  515. expectType<boolean | undefined>(props.mP2)
  516. const data = this.$data
  517. expectType<number>(data.a)
  518. expectType<number>(data.b)
  519. // should also expose declared props on `this`
  520. expectType<number>(this.a)
  521. expectType<number>(this.b)
  522. expectType<boolean | undefined>(this.p1)
  523. expectType<number>(this.p2)
  524. expectType<string>(this.mP1)
  525. expectType<boolean | undefined>(this.mP2)
  526. // setup context properties should be mutable
  527. this.a = 5
  528. return null
  529. }
  530. })
  531. // Test TSX
  532. expectType<JSX.Element>(<MyComponent mP1="p1" mP2 p1 p2={1} z={'z'} />)
  533. // missing required props
  534. // @ts-expect-error
  535. expectError(<MyComponent />)
  536. // wrong prop types
  537. // @ts-expect-error
  538. expectError(<MyComponent p2={'wrong type'} z={'z'} />)
  539. // @ts-expect-error
  540. expectError(<MyComponent mP1={3} />)
  541. })
  542. describe('compatibility w/ createApp', () => {
  543. const comp = defineComponent({})
  544. createApp(comp).mount('#hello')
  545. const comp2 = defineComponent({
  546. props: { foo: String }
  547. })
  548. createApp(comp2).mount('#hello')
  549. const comp3 = defineComponent({
  550. setup() {
  551. return {
  552. a: 1
  553. }
  554. }
  555. })
  556. createApp(comp3).mount('#hello')
  557. })
  558. describe('defineComponent', () => {
  559. test('should accept components defined with defineComponent', () => {
  560. const comp = defineComponent({})
  561. defineComponent({
  562. components: { comp }
  563. })
  564. })
  565. })
  566. describe('emits', () => {
  567. // Note: for TSX inference, ideally we want to map emits to onXXX props,
  568. // but that requires type-level string constant concatenation as suggested in
  569. // https://github.com/Microsoft/TypeScript/issues/12754
  570. // The workaround for TSX users is instead of using emits, declare onXXX props
  571. // and call them instead. Since `v-on:click` compiles to an `onClick` prop,
  572. // this would also support other users consuming the component in templates
  573. // with `v-on` listeners.
  574. // with object emits
  575. defineComponent({
  576. emits: {
  577. click: (n: number) => typeof n === 'number',
  578. input: (b: string) => b.length > 1
  579. },
  580. setup(props, { emit }) {
  581. emit('click', 1)
  582. emit('input', 'foo')
  583. // @ts-expect-error
  584. expectError(emit('nope'))
  585. // @ts-expect-error
  586. expectError(emit('click'))
  587. // @ts-expect-error
  588. expectError(emit('click', 'foo'))
  589. // @ts-expect-error
  590. expectError(emit('input'))
  591. // @ts-expect-error
  592. expectError(emit('input', 1))
  593. },
  594. created() {
  595. this.$emit('click', 1)
  596. this.$emit('input', 'foo')
  597. // @ts-expect-error
  598. expectError(this.$emit('nope'))
  599. // @ts-expect-error
  600. expectError(this.$emit('click'))
  601. // @ts-expect-error
  602. expectError(this.$emit('click', 'foo'))
  603. // @ts-expect-error
  604. expectError(this.$emit('input'))
  605. // @ts-expect-error
  606. expectError(this.$emit('input', 1))
  607. }
  608. })
  609. // with array emits
  610. defineComponent({
  611. emits: ['foo', 'bar'],
  612. setup(props, { emit }) {
  613. emit('foo')
  614. emit('foo', 123)
  615. emit('bar')
  616. // @ts-expect-error
  617. expectError(emit('nope'))
  618. },
  619. created() {
  620. this.$emit('foo')
  621. this.$emit('foo', 123)
  622. this.$emit('bar')
  623. // @ts-expect-error
  624. expectError(this.$emit('nope'))
  625. }
  626. })
  627. // without emits
  628. defineComponent({
  629. setup(props, { emit }) {
  630. emit('test', 1)
  631. emit('test')
  632. }
  633. })
  634. // emit should be valid when ComponentPublicInstance is used.
  635. const instance = {} as ComponentPublicInstance
  636. instance.$emit('test', 1)
  637. instance.$emit('test')
  638. })
  639. describe('componentOptions setup should be `SetupContext`', () => {
  640. expect<ComponentOptions['setup']>({} as (
  641. props: Record<string, any>,
  642. ctx: SetupContext
  643. ) => any)
  644. })