setupHelpers.test-d.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. import {
  2. type Ref,
  3. type Slots,
  4. type VNode,
  5. defineComponent,
  6. defineEmits,
  7. defineModel,
  8. defineOptions,
  9. defineProps,
  10. defineSlots,
  11. toRefs,
  12. useAttrs,
  13. useModel,
  14. useSlots,
  15. withDefaults,
  16. } from 'vue'
  17. import { describe, expectType } from './utils'
  18. describe('defineProps w/ type declaration', () => {
  19. // type declaration
  20. const props = defineProps<{
  21. foo: string
  22. bool?: boolean
  23. boolAndUndefined: boolean | undefined
  24. file?: File | File[]
  25. }>()
  26. // explicitly declared type should be refined
  27. expectType<string>(props.foo)
  28. // @ts-expect-error
  29. props.bar
  30. expectType<boolean>(props.bool)
  31. expectType<boolean>(props.boolAndUndefined)
  32. })
  33. describe('defineProps w/ never prop', () => {
  34. const props = defineProps<{
  35. foo?: never
  36. bar: number
  37. }>()
  38. expectType<never | undefined>(props.foo)
  39. expectType<number>(props.bar)
  40. })
  41. describe('defineProps w/ generics', () => {
  42. function test<T extends boolean>() {
  43. const props = defineProps<{ foo: T; bar: string; x?: boolean }>()
  44. expectType<T>(props.foo)
  45. expectType<string>(props.bar)
  46. expectType<boolean>(props.x)
  47. }
  48. test()
  49. })
  50. describe('defineProps w/ type declaration + withDefaults', <T extends
  51. string>() => {
  52. const res = withDefaults(
  53. defineProps<{
  54. number?: number
  55. arr?: string[]
  56. obj?: { x: number }
  57. fn?: (e: string) => void
  58. genStr?: string
  59. x?: string
  60. y?: string
  61. z?: string
  62. bool?: boolean
  63. boolAndUndefined: boolean | undefined
  64. foo?: T
  65. }>(),
  66. {
  67. number: 123,
  68. arr: () => [],
  69. obj: () => ({ x: 123 }),
  70. fn: () => {},
  71. genStr: () => '',
  72. y: undefined,
  73. z: 'string',
  74. foo: '' as any,
  75. },
  76. )
  77. res.number + 1
  78. res.arr.push('hi')
  79. res.obj.x
  80. res.fn('hi')
  81. res.genStr.slice()
  82. // @ts-expect-error
  83. res.x.slice()
  84. // @ts-expect-error
  85. res.y.slice()
  86. expectType<string | undefined>(res.x)
  87. expectType<string | undefined>(res.y)
  88. expectType<string>(res.z)
  89. expectType<T>(res.foo)
  90. expectType<boolean>(res.bool)
  91. expectType<boolean>(res.boolAndUndefined)
  92. })
  93. describe('defineProps w/ union type declaration + withDefaults', () => {
  94. withDefaults(
  95. defineProps<{
  96. union1?: number | number[] | { x: number }
  97. union2?: number | number[] | { x: number }
  98. union3?: number | number[] | { x: number }
  99. union4?: number | number[] | { x: number }
  100. }>(),
  101. {
  102. union1: 123,
  103. union2: () => [123],
  104. union3: () => ({ x: 123 }),
  105. union4: () => 123,
  106. },
  107. )
  108. })
  109. describe('defineProps w/ object union + withDefaults', () => {
  110. const props = withDefaults(
  111. defineProps<
  112. {
  113. foo: string
  114. } & (
  115. | {
  116. type: 'hello'
  117. bar: string
  118. }
  119. | {
  120. type: 'world'
  121. bar: number
  122. }
  123. )
  124. >(),
  125. {
  126. foo: 'default value!',
  127. },
  128. )
  129. expectType<
  130. | {
  131. readonly type: 'hello'
  132. readonly bar: string
  133. readonly foo: string
  134. }
  135. | {
  136. readonly type: 'world'
  137. readonly bar: number
  138. readonly foo: string
  139. }
  140. >(props)
  141. })
  142. describe('defineProps w/ generic discriminate union + withDefaults', () => {
  143. interface B {
  144. b?: string
  145. }
  146. interface S<T> extends B {
  147. mode: 'single'
  148. v: T
  149. }
  150. interface M<T> extends B {
  151. mode: 'multiple'
  152. v: T[]
  153. }
  154. type Props = S<string> | M<string>
  155. const props = withDefaults(defineProps<Props>(), {
  156. b: 'b',
  157. })
  158. if (props.mode === 'single') {
  159. expectType<string>(props.v)
  160. }
  161. if (props.mode === 'multiple') {
  162. expectType<string[]>(props.v)
  163. }
  164. })
  165. describe('defineProps w/ generic type declaration + withDefaults', <T extends
  166. number, TA extends {
  167. a: string
  168. }, TString extends string>() => {
  169. const res = withDefaults(
  170. defineProps<{
  171. n?: number
  172. bool?: boolean
  173. s?: string
  174. generic1?: T[] | { x: T }
  175. generic2?: { x: T }
  176. generic3?: TString
  177. generic4?: TA
  178. }>(),
  179. {
  180. n: 123,
  181. generic1: () => [123, 33] as T[],
  182. generic2: () => ({ x: 123 }) as { x: T },
  183. generic3: () => 'test' as TString,
  184. generic4: () => ({ a: 'test' }) as TA,
  185. },
  186. )
  187. res.n + 1
  188. // @ts-expect-error should be readonly
  189. res.n++
  190. // @ts-expect-error should be readonly
  191. res.s = ''
  192. expectType<T[] | { x: T }>(res.generic1)
  193. expectType<{ x: T }>(res.generic2)
  194. expectType<TString>(res.generic3)
  195. expectType<TA>(res.generic4)
  196. expectType<boolean>(res.bool)
  197. })
  198. describe('withDefaults w/ boolean type', () => {
  199. const res1 = withDefaults(
  200. defineProps<{
  201. bool?: boolean
  202. }>(),
  203. { bool: false },
  204. )
  205. expectType<boolean>(res1.bool)
  206. const res2 = withDefaults(
  207. defineProps<{
  208. bool?: boolean
  209. }>(),
  210. {
  211. bool: undefined,
  212. },
  213. )
  214. expectType<boolean | undefined>(res2.bool)
  215. })
  216. describe('withDefaults w/ defineProp type is different from the defaults type', () => {
  217. const res1 = withDefaults(
  218. defineProps<{
  219. bool?: boolean
  220. }>(),
  221. { bool: false, value: false },
  222. )
  223. expectType<boolean>(res1.bool)
  224. // @ts-expect-error
  225. res1.value
  226. })
  227. describe('withDefaults w/ defineProp discriminate union type', () => {
  228. const props = withDefaults(
  229. defineProps<
  230. { type: 'button'; buttonType?: 'submit' } | { type: 'link'; href: string }
  231. >(),
  232. {
  233. type: 'button',
  234. },
  235. )
  236. if (props.type === 'button') {
  237. expectType<'submit' | undefined>(props.buttonType)
  238. }
  239. if (props.type === 'link') {
  240. expectType<string>(props.href)
  241. }
  242. })
  243. describe('defineProps w/ runtime declaration', () => {
  244. // runtime declaration
  245. const props = defineProps({
  246. foo: String,
  247. bar: {
  248. type: Number,
  249. default: 1,
  250. },
  251. baz: {
  252. type: Array,
  253. required: true,
  254. },
  255. })
  256. expectType<{
  257. foo?: string
  258. bar: number
  259. baz: unknown[]
  260. }>(props)
  261. props.foo && props.foo + 'bar'
  262. props.bar + 1
  263. // @ts-expect-error should be readonly
  264. props.bar++
  265. props.baz.push(1)
  266. const props2 = defineProps(['foo', 'bar'])
  267. props2.foo + props2.bar
  268. // @ts-expect-error
  269. props2.baz
  270. })
  271. describe('defineEmits w/ type declaration', () => {
  272. const emit = defineEmits<(e: 'change') => void>()
  273. emit('change')
  274. // @ts-expect-error
  275. emit()
  276. // @ts-expect-error
  277. emit('bar')
  278. type Emits = { (e: 'foo' | 'bar'): void; (e: 'baz', id: number): void }
  279. const emit2 = defineEmits<Emits>()
  280. emit2('foo')
  281. emit2('bar')
  282. emit2('baz', 123)
  283. // @ts-expect-error
  284. emit2('baz')
  285. })
  286. describe('defineEmits w/ interface declaration', () => {
  287. interface Emits {
  288. foo: [value: string]
  289. }
  290. const emit = defineEmits<Emits>()
  291. emit('foo', 'hi')
  292. })
  293. describe('defineEmits w/ alt type declaration', () => {
  294. const emit = defineEmits<{
  295. foo: [id: string]
  296. bar: any[]
  297. baz: []
  298. }>()
  299. emit('foo', 'hi')
  300. // @ts-expect-error
  301. emit('foo')
  302. emit('bar')
  303. emit('bar', 1, 2, 3)
  304. emit('baz')
  305. // @ts-expect-error
  306. emit('baz', 1)
  307. })
  308. describe('defineEmits w/ runtime declaration', () => {
  309. const emit = defineEmits({
  310. foo: () => {},
  311. bar: null,
  312. })
  313. emit('foo')
  314. emit('bar', 123)
  315. // @ts-expect-error
  316. emit('baz')
  317. const emit2 = defineEmits(['foo', 'bar'])
  318. emit2('foo')
  319. emit2('bar', 123)
  320. // @ts-expect-error
  321. emit2('baz')
  322. })
  323. describe('defineSlots', () => {
  324. // literal fn syntax (allow for specifying return type)
  325. const fnSlots = defineSlots<{
  326. default(props: { foo: string; bar: number }): any
  327. optional?(props: string): any
  328. }>()
  329. expectType<(scope: { foo: string; bar: number }) => VNode[]>(fnSlots.default)
  330. expectType<undefined | ((scope: string) => VNode[])>(fnSlots.optional)
  331. const slotsUntype = defineSlots()
  332. expectType<Slots>(slotsUntype)
  333. })
  334. describe('defineSlots generic', <T extends Record<string, any>>() => {
  335. const props = defineProps<{
  336. item: T
  337. }>()
  338. const slots = defineSlots<
  339. {
  340. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  341. } & {
  342. label?: (props: { item: T }) => any
  343. }
  344. >()
  345. for (const key of Object.keys(props.item) as (keyof T & string)[]) {
  346. slots[`slot-${String(key)}`]?.({
  347. item: props.item,
  348. })
  349. }
  350. slots.label?.({ item: props.item })
  351. // @ts-expect-error calling wrong slot
  352. slots.foo({})
  353. })
  354. describe('defineModel', () => {
  355. // overload 1
  356. const modelValueRequired = defineModel<boolean>({ required: true })
  357. expectType<Ref<boolean>>(modelValueRequired)
  358. // overload 2
  359. const modelValue = defineModel<string>()
  360. expectType<Ref<string | undefined>>(modelValue)
  361. modelValue.value = 'new value'
  362. const modelValueDefault = defineModel<boolean>({ default: true })
  363. expectType<Ref<boolean>>(modelValueDefault)
  364. // overload 3
  365. const countRequired = defineModel<number>('count', { required: false })
  366. expectType<Ref<number | undefined>>(countRequired)
  367. // overload 4
  368. const count = defineModel<number>('count')
  369. expectType<Ref<number | undefined>>(count)
  370. const countDefault = defineModel<number>('count', { default: 1 })
  371. expectType<Ref<number>>(countDefault)
  372. // infer type from default
  373. const inferred = defineModel({ default: 123 })
  374. expectType<Ref<number | undefined>>(inferred)
  375. const inferredRequired = defineModel({ default: 123, required: true })
  376. expectType<Ref<number>>(inferredRequired)
  377. // modifiers
  378. const [_, modifiers] = defineModel<string>()
  379. expectType<true | undefined>(modifiers.foo)
  380. // limit supported modifiers
  381. const [__, typedModifiers] = defineModel<string, 'trim' | 'capitalize'>()
  382. expectType<true | undefined>(typedModifiers.trim)
  383. expectType<true | undefined>(typedModifiers.capitalize)
  384. // @ts-expect-error
  385. typedModifiers.foo
  386. // transformers with type
  387. defineModel<string>({
  388. get(val) {
  389. return val.toLowerCase()
  390. },
  391. set(val) {
  392. return val.toUpperCase()
  393. },
  394. })
  395. // transformers with runtime type
  396. defineModel({
  397. type: String,
  398. get(val) {
  399. return val.toLowerCase()
  400. },
  401. set(val) {
  402. return val.toUpperCase()
  403. },
  404. })
  405. // @ts-expect-error type / default mismatch
  406. defineModel<string>({ default: 123 })
  407. // @ts-expect-error unknown props option
  408. defineModel({ foo: 123 })
  409. // unrelated getter and setter types
  410. {
  411. const modelVal = defineModel({
  412. get(_: string[]): string {
  413. return ''
  414. },
  415. set(_: number) {
  416. return 1
  417. },
  418. })
  419. expectType<string | undefined>(modelVal.value)
  420. modelVal.value = 1
  421. modelVal.value = undefined
  422. // @ts-expect-error
  423. modelVal.value = 'foo'
  424. const [modelVal2] = modelVal
  425. expectType<string | undefined>(modelVal2.value)
  426. modelVal2.value = 1
  427. modelVal2.value = undefined
  428. // @ts-expect-error
  429. modelVal.value = 'foo'
  430. const count = defineModel('count', {
  431. get(_: string[]): string {
  432. return ''
  433. },
  434. set(_: number) {
  435. return ''
  436. },
  437. })
  438. expectType<string | undefined>(count.value)
  439. count.value = 1
  440. count.value = undefined
  441. // @ts-expect-error
  442. count.value = 'foo'
  443. const [count2] = count
  444. expectType<string | undefined>(count2.value)
  445. count2.value = 1
  446. count2.value = undefined
  447. // @ts-expect-error
  448. count2.value = 'foo'
  449. }
  450. })
  451. describe('useModel', () => {
  452. defineComponent({
  453. props: ['foo'],
  454. setup(props) {
  455. const r = useModel(props, 'foo')
  456. expectType<Ref<any>>(r)
  457. // @ts-expect-error
  458. useModel(props, 'bar')
  459. },
  460. })
  461. defineComponent({
  462. props: {
  463. foo: String,
  464. bar: { type: Number, required: true },
  465. baz: { type: Boolean },
  466. },
  467. setup(props) {
  468. expectType<Ref<string | undefined>>(useModel(props, 'foo'))
  469. expectType<Ref<number>>(useModel(props, 'bar'))
  470. expectType<Ref<boolean>>(useModel(props, 'baz'))
  471. },
  472. })
  473. })
  474. describe('useAttrs', () => {
  475. const attrs = useAttrs()
  476. expectType<Record<string, unknown>>(attrs)
  477. })
  478. describe('useSlots', () => {
  479. const slots = useSlots()
  480. expectType<Slots>(slots)
  481. })
  482. describe('defineSlots generic', <T extends Record<string, any>>() => {
  483. const props = defineProps<{
  484. item: T
  485. }>()
  486. const slots = defineSlots<
  487. {
  488. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  489. } & {
  490. label?: (props: { item: T }) => any
  491. }
  492. >()
  493. // @ts-expect-error slots should be readonly
  494. slots.label = () => {}
  495. // @ts-expect-error non existing slot
  496. slots['foo-asdas']?.({
  497. item: props.item,
  498. })
  499. for (const key in props.item) {
  500. slots[`slot-${String(key)}`]?.({
  501. item: props.item,
  502. })
  503. slots[`slot-${String(key as keyof T)}`]?.({
  504. item: props.item,
  505. })
  506. }
  507. for (const key of Object.keys(props.item) as (keyof T)[]) {
  508. slots[`slot-${String(key)}`]?.({
  509. item: props.item,
  510. })
  511. }
  512. slots.label?.({ item: props.item })
  513. // @ts-expect-error calling wrong slot
  514. slots.foo({})
  515. })
  516. describe('defineSlots generic strict', <T extends {
  517. foo: 'foo'
  518. bar: 'bar'
  519. }>() => {
  520. const props = defineProps<{
  521. item: T
  522. }>()
  523. const slots = defineSlots<
  524. {
  525. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  526. } & {
  527. label?: (props: { item: T }) => any
  528. }
  529. >()
  530. // slot-bar/foo should be automatically inferred
  531. slots['slot-bar']?.({ item: props.item })
  532. slots['slot-foo']?.({ item: props.item })
  533. slots.label?.({ item: props.item })
  534. // @ts-expect-error not part of the extends
  535. slots['slot-RANDOM']?.({ item: props.item })
  536. // @ts-expect-error slots should be readonly
  537. slots.label = () => {}
  538. // @ts-expect-error calling wrong slot
  539. slots.foo({})
  540. })
  541. // #6420
  542. describe('toRefs w/ type declaration', () => {
  543. const props = defineProps<{
  544. file?: File | File[]
  545. }>()
  546. expectType<Ref<File | File[] | undefined>>(toRefs(props).file)
  547. })
  548. describe('defineOptions', () => {
  549. defineOptions({
  550. name: 'MyComponent',
  551. inheritAttrs: true,
  552. })
  553. defineOptions({
  554. // @ts-expect-error props should be defined via defineProps()
  555. props: ['props'],
  556. // @ts-expect-error emits should be defined via defineEmits()
  557. emits: ['emits'],
  558. // @ts-expect-error slots should be defined via defineSlots()
  559. slots: { default: 'default' },
  560. // @ts-expect-error expose should be defined via defineExpose()
  561. expose: ['expose'],
  562. })
  563. })