setupHelpers.test-d.ts 12 KB

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