setupHelpers.test-d.ts 11 KB

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