setupHelpers.test-d.ts 11 KB

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