setupHelpers.test-d.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  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. // unrelated getter and setter types
  379. {
  380. const modelVal = defineModel({
  381. get(_: string[]): string {
  382. return ''
  383. },
  384. set(_: number) {
  385. return 1
  386. },
  387. })
  388. expectType<string | undefined>(modelVal.value)
  389. modelVal.value = 1
  390. modelVal.value = undefined
  391. // @ts-expect-error
  392. modelVal.value = 'foo'
  393. const [modelVal2] = modelVal
  394. expectType<string | undefined>(modelVal2.value)
  395. modelVal2.value = 1
  396. modelVal2.value = undefined
  397. // @ts-expect-error
  398. modelVal.value = 'foo'
  399. const count = defineModel('count', {
  400. get(_: string[]): string {
  401. return ''
  402. },
  403. set(_: number) {
  404. return ''
  405. },
  406. })
  407. expectType<string | undefined>(count.value)
  408. count.value = 1
  409. count.value = undefined
  410. // @ts-expect-error
  411. count.value = 'foo'
  412. const [count2] = count
  413. expectType<string | undefined>(count2.value)
  414. count2.value = 1
  415. count2.value = undefined
  416. // @ts-expect-error
  417. count2.value = 'foo'
  418. }
  419. })
  420. describe('useModel', () => {
  421. defineComponent({
  422. props: ['foo'],
  423. setup(props) {
  424. const r = useModel(props, 'foo')
  425. expectType<Ref<any>>(r)
  426. // @ts-expect-error
  427. useModel(props, 'bar')
  428. },
  429. })
  430. defineComponent({
  431. props: {
  432. foo: String,
  433. bar: { type: Number, required: true },
  434. baz: { type: Boolean },
  435. },
  436. setup(props) {
  437. expectType<Ref<string | undefined>>(useModel(props, 'foo'))
  438. expectType<Ref<number>>(useModel(props, 'bar'))
  439. expectType<Ref<boolean>>(useModel(props, 'baz'))
  440. },
  441. })
  442. })
  443. describe('useAttrs', () => {
  444. const attrs = useAttrs()
  445. expectType<Record<string, unknown>>(attrs)
  446. })
  447. describe('useSlots', () => {
  448. const slots = useSlots()
  449. expectType<Slots>(slots)
  450. })
  451. describe('defineSlots generic', <T extends Record<string, any>>() => {
  452. const props = defineProps<{
  453. item: T
  454. }>()
  455. const slots = defineSlots<
  456. {
  457. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  458. } & {
  459. label?: (props: { item: T }) => any
  460. }
  461. >()
  462. // @ts-expect-error slots should be readonly
  463. slots.label = () => {}
  464. // @ts-expect-error non existing slot
  465. slots['foo-asdas']?.({
  466. item: props.item,
  467. })
  468. for (const key in props.item) {
  469. slots[`slot-${String(key)}`]?.({
  470. item: props.item,
  471. })
  472. slots[`slot-${String(key as keyof T)}`]?.({
  473. item: props.item,
  474. })
  475. }
  476. for (const key of Object.keys(props.item) as (keyof T)[]) {
  477. slots[`slot-${String(key)}`]?.({
  478. item: props.item,
  479. })
  480. }
  481. slots.label?.({ item: props.item })
  482. // @ts-expect-error calling wrong slot
  483. slots.foo({})
  484. })
  485. describe('defineSlots generic strict', <T extends {
  486. foo: 'foo'
  487. bar: 'bar'
  488. }>() => {
  489. const props = defineProps<{
  490. item: T
  491. }>()
  492. const slots = defineSlots<
  493. {
  494. [K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
  495. } & {
  496. label?: (props: { item: T }) => any
  497. }
  498. >()
  499. // slot-bar/foo should be automatically inferred
  500. slots['slot-bar']?.({ item: props.item })
  501. slots['slot-foo']?.({ item: props.item })
  502. slots.label?.({ item: props.item })
  503. // @ts-expect-error not part of the extends
  504. slots['slot-RANDOM']?.({ item: props.item })
  505. // @ts-expect-error slots should be readonly
  506. slots.label = () => {}
  507. // @ts-expect-error calling wrong slot
  508. slots.foo({})
  509. })
  510. // #6420
  511. describe('toRefs w/ type declaration', () => {
  512. const props = defineProps<{
  513. file?: File | File[]
  514. }>()
  515. expectType<Ref<File | File[] | undefined>>(toRefs(props).file)
  516. })
  517. describe('defineOptions', () => {
  518. defineOptions({
  519. name: 'MyComponent',
  520. inheritAttrs: true,
  521. })
  522. defineOptions({
  523. // @ts-expect-error props should be defined via defineProps()
  524. props: ['props'],
  525. // @ts-expect-error emits should be defined via defineEmits()
  526. emits: ['emits'],
  527. // @ts-expect-error slots should be defined via defineSlots()
  528. slots: { default: 'default' },
  529. // @ts-expect-error expose should be defined via defineExpose()
  530. expose: ['expose'],
  531. })
  532. })