setupHelpers.test-d.ts 11 KB

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