defineComponent.test-d.tsx 17 KB

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