setupHelpers.test-d.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  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('defineProps w/ runtime declaration', () => {
  220. // runtime declaration
  221. const props = defineProps({
  222. foo: String,
  223. bar: {
  224. type: Number,
  225. default: 1,
  226. },
  227. baz: {
  228. type: Array,
  229. required: true,
  230. },
  231. })
  232. expectType<{
  233. foo?: string
  234. bar: number
  235. baz: unknown[]
  236. }>(props)
  237. props.foo && props.foo + 'bar'
  238. props.bar + 1
  239. // @ts-expect-error should be readonly
  240. props.bar++
  241. props.baz.push(1)
  242. const props2 = defineProps(['foo', 'bar'])
  243. props2.foo + props2.bar
  244. // @ts-expect-error
  245. props2.baz
  246. })
  247. describe('defineEmits w/ type declaration', () => {
  248. const emit = defineEmits<(e: 'change') => void>()
  249. emit('change')
  250. // @ts-expect-error
  251. emit()
  252. // @ts-expect-error
  253. emit('bar')
  254. type Emits = { (e: 'foo' | 'bar'): void; (e: 'baz', id: number): void }
  255. const emit2 = defineEmits<Emits>()
  256. emit2('foo')
  257. emit2('bar')
  258. emit2('baz', 123)
  259. // @ts-expect-error
  260. emit2('baz')
  261. })
  262. describe('defineEmits w/ alt type declaration', () => {
  263. const emit = defineEmits<{
  264. foo: [id: string]
  265. bar: any[]
  266. baz: []
  267. }>()
  268. emit('foo', 'hi')
  269. // @ts-expect-error
  270. emit('foo')
  271. emit('bar')
  272. emit('bar', 1, 2, 3)
  273. emit('baz')
  274. // @ts-expect-error
  275. emit('baz', 1)
  276. })
  277. describe('defineEmits w/ runtime declaration', () => {
  278. const emit = defineEmits({
  279. foo: () => {},
  280. bar: null,
  281. })
  282. emit('foo')
  283. emit('bar', 123)
  284. // @ts-expect-error
  285. emit('baz')
  286. const emit2 = defineEmits(['foo', 'bar'])
  287. emit2('foo')
  288. emit2('bar', 123)
  289. // @ts-expect-error
  290. emit2('baz')
  291. })
  292. describe('defineSlots', () => {
  293. // literal fn syntax (allow for specifying return type)
  294. const fnSlots = defineSlots<{
  295. default(props: { foo: string; bar: number }): any
  296. optional?(props: string): any
  297. }>()
  298. expectType<(scope: { foo: string; bar: number }) => VNode[]>(fnSlots.default)
  299. expectType<undefined | ((scope: string) => VNode[])>(fnSlots.optional)
  300. const slotsUntype = defineSlots()
  301. expectType<Slots>(slotsUntype)
  302. })
  303. describe('defineSlots generic', <T extends Record<string, any>>() => {
  304. const props = defineProps<{
  305. item: T
  306. }>()
  307. const slots = defineSlots<
  308. {
  309. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  310. } & {
  311. label?: (props: { item: T }) => any
  312. }
  313. >()
  314. for (const key of Object.keys(props.item) as (keyof T & string)[]) {
  315. slots[`slot-${String(key)}`]?.({
  316. item: props.item,
  317. })
  318. }
  319. slots.label?.({ item: props.item })
  320. // @ts-expect-error calling wrong slot
  321. slots.foo({})
  322. })
  323. describe('defineModel', () => {
  324. // overload 1
  325. const modelValueRequired = defineModel<boolean>({ required: true })
  326. expectType<Ref<boolean>>(modelValueRequired)
  327. // overload 2
  328. const modelValue = defineModel<string>()
  329. expectType<Ref<string | undefined>>(modelValue)
  330. modelValue.value = 'new value'
  331. const modelValueDefault = defineModel<boolean>({ default: true })
  332. expectType<Ref<boolean>>(modelValueDefault)
  333. // overload 3
  334. const countRequired = defineModel<number>('count', { required: false })
  335. expectType<Ref<number | undefined>>(countRequired)
  336. // overload 4
  337. const count = defineModel<number>('count')
  338. expectType<Ref<number | undefined>>(count)
  339. const countDefault = defineModel<number>('count', { default: 1 })
  340. expectType<Ref<number>>(countDefault)
  341. // infer type from default
  342. const inferred = defineModel({ default: 123 })
  343. expectType<Ref<number | undefined>>(inferred)
  344. const inferredRequired = defineModel({ default: 123, required: true })
  345. expectType<Ref<number>>(inferredRequired)
  346. // modifiers
  347. const [_, modifiers] = defineModel<string>()
  348. expectType<true | undefined>(modifiers.foo)
  349. // limit supported modifiers
  350. const [__, typedModifiers] = defineModel<string, 'trim' | 'capitalize'>()
  351. expectType<true | undefined>(typedModifiers.trim)
  352. expectType<true | undefined>(typedModifiers.capitalize)
  353. // @ts-expect-error
  354. typedModifiers.foo
  355. // transformers with type
  356. defineModel<string>({
  357. get(val) {
  358. return val.toLowerCase()
  359. },
  360. set(val) {
  361. return val.toUpperCase()
  362. },
  363. })
  364. // transformers with runtime type
  365. defineModel({
  366. type: String,
  367. get(val) {
  368. return val.toLowerCase()
  369. },
  370. set(val) {
  371. return val.toUpperCase()
  372. },
  373. })
  374. // @ts-expect-error type / default mismatch
  375. defineModel<string>({ default: 123 })
  376. // @ts-expect-error unknown props option
  377. defineModel({ foo: 123 })
  378. })
  379. describe('useModel', () => {
  380. defineComponent({
  381. props: ['foo'],
  382. setup(props) {
  383. const r = useModel(props, 'foo')
  384. expectType<Ref<any>>(r)
  385. // @ts-expect-error
  386. useModel(props, 'bar')
  387. },
  388. })
  389. defineComponent({
  390. props: {
  391. foo: String,
  392. bar: { type: Number, required: true },
  393. baz: { type: Boolean },
  394. },
  395. setup(props) {
  396. expectType<Ref<string | undefined>>(useModel(props, 'foo'))
  397. expectType<Ref<number>>(useModel(props, 'bar'))
  398. expectType<Ref<boolean>>(useModel(props, 'baz'))
  399. },
  400. })
  401. })
  402. describe('useAttrs', () => {
  403. const attrs = useAttrs()
  404. expectType<Record<string, unknown>>(attrs)
  405. })
  406. describe('useSlots', () => {
  407. const slots = useSlots()
  408. expectType<Slots>(slots)
  409. })
  410. describe('defineSlots generic', <T extends Record<string, any>>() => {
  411. const props = defineProps<{
  412. item: T
  413. }>()
  414. const slots = defineSlots<
  415. {
  416. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  417. } & {
  418. label?: (props: { item: T }) => any
  419. }
  420. >()
  421. // @ts-expect-error slots should be readonly
  422. slots.label = () => {}
  423. // @ts-expect-error non existing slot
  424. slots['foo-asdas']?.({
  425. item: props.item,
  426. })
  427. for (const key in props.item) {
  428. slots[`slot-${String(key)}`]?.({
  429. item: props.item,
  430. })
  431. slots[`slot-${String(key as keyof T)}`]?.({
  432. item: props.item,
  433. })
  434. }
  435. for (const key of Object.keys(props.item) as (keyof T)[]) {
  436. slots[`slot-${String(key)}`]?.({
  437. item: props.item,
  438. })
  439. }
  440. slots.label?.({ item: props.item })
  441. // @ts-expect-error calling wrong slot
  442. slots.foo({})
  443. })
  444. describe('defineSlots generic strict', <T extends {
  445. foo: 'foo'
  446. bar: 'bar'
  447. }>() => {
  448. const props = defineProps<{
  449. item: T
  450. }>()
  451. const slots = defineSlots<
  452. {
  453. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  454. } & {
  455. label?: (props: { item: T }) => any
  456. }
  457. >()
  458. // slot-bar/foo should be automatically inferred
  459. slots['slot-bar']?.({ item: props.item })
  460. slots['slot-foo']?.({ item: props.item })
  461. slots.label?.({ item: props.item })
  462. // @ts-expect-error not part of the extends
  463. slots['slot-RANDOM']?.({ item: props.item })
  464. // @ts-expect-error slots should be readonly
  465. slots.label = () => {}
  466. // @ts-expect-error calling wrong slot
  467. slots.foo({})
  468. })
  469. // #6420
  470. describe('toRefs w/ type declaration', () => {
  471. const props = defineProps<{
  472. file?: File | File[]
  473. }>()
  474. expectType<Ref<File | File[] | undefined>>(toRefs(props).file)
  475. })
  476. describe('defineOptions', () => {
  477. defineOptions({
  478. name: 'MyComponent',
  479. inheritAttrs: true,
  480. })
  481. defineOptions({
  482. // @ts-expect-error props should be defined via defineProps()
  483. props: ['props'],
  484. // @ts-expect-error emits should be defined via defineEmits()
  485. emits: ['emits'],
  486. // @ts-expect-error slots should be defined via defineSlots()
  487. slots: { default: 'default' },
  488. // @ts-expect-error expose should be defined via defineExpose()
  489. expose: ['expose'],
  490. })
  491. })