setupHelpers.test-d.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  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/ alt type declaration', () => {
  279. const emit = defineEmits<{
  280. foo: [id: string]
  281. bar: any[]
  282. baz: []
  283. }>()
  284. emit('foo', 'hi')
  285. // @ts-expect-error
  286. emit('foo')
  287. emit('bar')
  288. emit('bar', 1, 2, 3)
  289. emit('baz')
  290. // @ts-expect-error
  291. emit('baz', 1)
  292. })
  293. describe('defineEmits w/ runtime declaration', () => {
  294. const emit = defineEmits({
  295. foo: () => {},
  296. bar: null,
  297. })
  298. emit('foo')
  299. emit('bar', 123)
  300. // @ts-expect-error
  301. emit('baz')
  302. const emit2 = defineEmits(['foo', 'bar'])
  303. emit2('foo')
  304. emit2('bar', 123)
  305. // @ts-expect-error
  306. emit2('baz')
  307. })
  308. describe('defineSlots', () => {
  309. // literal fn syntax (allow for specifying return type)
  310. const fnSlots = defineSlots<{
  311. default(props: { foo: string; bar: number }): any
  312. optional?(props: string): any
  313. }>()
  314. expectType<(scope: { foo: string; bar: number }) => VNode[]>(fnSlots.default)
  315. expectType<undefined | ((scope: string) => VNode[])>(fnSlots.optional)
  316. const slotsUntype = defineSlots()
  317. expectType<Slots>(slotsUntype)
  318. })
  319. describe('defineSlots generic', <T extends Record<string, any>>() => {
  320. const props = defineProps<{
  321. item: T
  322. }>()
  323. const slots = defineSlots<
  324. {
  325. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  326. } & {
  327. label?: (props: { item: T }) => any
  328. }
  329. >()
  330. for (const key of Object.keys(props.item) as (keyof T & string)[]) {
  331. slots[`slot-${String(key)}`]?.({
  332. item: props.item,
  333. })
  334. }
  335. slots.label?.({ item: props.item })
  336. // @ts-expect-error calling wrong slot
  337. slots.foo({})
  338. })
  339. describe('defineModel', () => {
  340. // overload 1
  341. const modelValueRequired = defineModel<boolean>({ required: true })
  342. expectType<Ref<boolean>>(modelValueRequired)
  343. // overload 2
  344. const modelValue = defineModel<string>()
  345. expectType<Ref<string | undefined>>(modelValue)
  346. modelValue.value = 'new value'
  347. const modelValueDefault = defineModel<boolean>({ default: true })
  348. expectType<Ref<boolean>>(modelValueDefault)
  349. // overload 3
  350. const countRequired = defineModel<number>('count', { required: false })
  351. expectType<Ref<number | undefined>>(countRequired)
  352. // overload 4
  353. const count = defineModel<number>('count')
  354. expectType<Ref<number | undefined>>(count)
  355. const countDefault = defineModel<number>('count', { default: 1 })
  356. expectType<Ref<number>>(countDefault)
  357. // infer type from default
  358. const inferred = defineModel({ default: 123 })
  359. expectType<Ref<number | undefined>>(inferred)
  360. const inferredRequired = defineModel({ default: 123, required: true })
  361. expectType<Ref<number>>(inferredRequired)
  362. // modifiers
  363. const [_, modifiers] = defineModel<string>()
  364. expectType<true | undefined>(modifiers.foo)
  365. // limit supported modifiers
  366. const [__, typedModifiers] = defineModel<string, 'trim' | 'capitalize'>()
  367. expectType<true | undefined>(typedModifiers.trim)
  368. expectType<true | undefined>(typedModifiers.capitalize)
  369. // @ts-expect-error
  370. typedModifiers.foo
  371. // transformers with type
  372. defineModel<string>({
  373. get(val) {
  374. return val.toLowerCase()
  375. },
  376. set(val) {
  377. return val.toUpperCase()
  378. },
  379. })
  380. // transformers with runtime type
  381. defineModel({
  382. type: String,
  383. get(val) {
  384. return val.toLowerCase()
  385. },
  386. set(val) {
  387. return val.toUpperCase()
  388. },
  389. })
  390. // @ts-expect-error type / default mismatch
  391. defineModel<string>({ default: 123 })
  392. // @ts-expect-error unknown props option
  393. defineModel({ foo: 123 })
  394. // unrelated getter and setter types
  395. {
  396. const modelVal = defineModel({
  397. get(_: string[]): string {
  398. return ''
  399. },
  400. set(_: number) {
  401. return 1
  402. },
  403. })
  404. expectType<string | undefined>(modelVal.value)
  405. modelVal.value = 1
  406. modelVal.value = undefined
  407. // @ts-expect-error
  408. modelVal.value = 'foo'
  409. const [modelVal2] = modelVal
  410. expectType<string | undefined>(modelVal2.value)
  411. modelVal2.value = 1
  412. modelVal2.value = undefined
  413. // @ts-expect-error
  414. modelVal.value = 'foo'
  415. const count = defineModel('count', {
  416. get(_: string[]): string {
  417. return ''
  418. },
  419. set(_: number) {
  420. return ''
  421. },
  422. })
  423. expectType<string | undefined>(count.value)
  424. count.value = 1
  425. count.value = undefined
  426. // @ts-expect-error
  427. count.value = 'foo'
  428. const [count2] = count
  429. expectType<string | undefined>(count2.value)
  430. count2.value = 1
  431. count2.value = undefined
  432. // @ts-expect-error
  433. count2.value = 'foo'
  434. }
  435. })
  436. describe('useModel', () => {
  437. defineComponent({
  438. props: ['foo'],
  439. setup(props) {
  440. const r = useModel(props, 'foo')
  441. expectType<Ref<any>>(r)
  442. // @ts-expect-error
  443. useModel(props, 'bar')
  444. },
  445. })
  446. defineComponent({
  447. props: {
  448. foo: String,
  449. bar: { type: Number, required: true },
  450. baz: { type: Boolean },
  451. },
  452. setup(props) {
  453. expectType<Ref<string | undefined>>(useModel(props, 'foo'))
  454. expectType<Ref<number>>(useModel(props, 'bar'))
  455. expectType<Ref<boolean>>(useModel(props, 'baz'))
  456. },
  457. })
  458. })
  459. describe('useAttrs', () => {
  460. const attrs = useAttrs()
  461. expectType<Record<string, unknown>>(attrs)
  462. })
  463. describe('useSlots', () => {
  464. const slots = useSlots()
  465. expectType<Slots>(slots)
  466. })
  467. describe('defineSlots generic', <T extends Record<string, any>>() => {
  468. const props = defineProps<{
  469. item: T
  470. }>()
  471. const slots = defineSlots<
  472. {
  473. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  474. } & {
  475. label?: (props: { item: T }) => any
  476. }
  477. >()
  478. // @ts-expect-error slots should be readonly
  479. slots.label = () => {}
  480. // @ts-expect-error non existing slot
  481. slots['foo-asdas']?.({
  482. item: props.item,
  483. })
  484. for (const key in props.item) {
  485. slots[`slot-${String(key)}`]?.({
  486. item: props.item,
  487. })
  488. slots[`slot-${String(key as keyof T)}`]?.({
  489. item: props.item,
  490. })
  491. }
  492. for (const key of Object.keys(props.item) as (keyof T)[]) {
  493. slots[`slot-${String(key)}`]?.({
  494. item: props.item,
  495. })
  496. }
  497. slots.label?.({ item: props.item })
  498. // @ts-expect-error calling wrong slot
  499. slots.foo({})
  500. })
  501. describe('defineSlots generic strict', <T extends {
  502. foo: 'foo'
  503. bar: 'bar'
  504. }>() => {
  505. const props = defineProps<{
  506. item: T
  507. }>()
  508. const slots = defineSlots<
  509. {
  510. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  511. } & {
  512. label?: (props: { item: T }) => any
  513. }
  514. >()
  515. // slot-bar/foo should be automatically inferred
  516. slots['slot-bar']?.({ item: props.item })
  517. slots['slot-foo']?.({ item: props.item })
  518. slots.label?.({ item: props.item })
  519. // @ts-expect-error not part of the extends
  520. slots['slot-RANDOM']?.({ item: props.item })
  521. // @ts-expect-error slots should be readonly
  522. slots.label = () => {}
  523. // @ts-expect-error calling wrong slot
  524. slots.foo({})
  525. })
  526. // #6420
  527. describe('toRefs w/ type declaration', () => {
  528. const props = defineProps<{
  529. file?: File | File[]
  530. }>()
  531. expectType<Ref<File | File[] | undefined>>(toRefs(props).file)
  532. })
  533. describe('defineOptions', () => {
  534. defineOptions({
  535. name: 'MyComponent',
  536. inheritAttrs: true,
  537. })
  538. defineOptions({
  539. // @ts-expect-error props should be defined via defineProps()
  540. props: ['props'],
  541. // @ts-expect-error emits should be defined via defineEmits()
  542. emits: ['emits'],
  543. // @ts-expect-error slots should be defined via defineSlots()
  544. slots: { default: 'default' },
  545. // @ts-expect-error expose should be defined via defineExpose()
  546. expose: ['expose'],
  547. })
  548. })