setupHelpers.test-d.ts 14 KB

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