component.test-d.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. import {
  2. type Component,
  3. type ComponentPublicInstance,
  4. type EmitsOptions,
  5. type FunctionalComponent,
  6. type PropType,
  7. type Ref,
  8. type SetupContext,
  9. type ShallowUnwrapRef,
  10. defineComponent,
  11. ref,
  12. toRefs,
  13. } from 'vue'
  14. import { type IsAny, describe, expectAssignable, expectType } from './utils'
  15. declare function extractComponentOptions<
  16. Props,
  17. RawBindings,
  18. Emits extends EmitsOptions | Record<string, any[]>,
  19. Slots extends Record<string, any>,
  20. >(
  21. obj: Component<Props, RawBindings, any, any, any, Emits, Slots>,
  22. ): {
  23. props: Props
  24. emits: Emits
  25. slots: Slots
  26. rawBindings: RawBindings
  27. setup: ShallowUnwrapRef<RawBindings>
  28. }
  29. describe('object props', () => {
  30. interface ExpectedProps {
  31. a?: number | undefined
  32. b: string
  33. e?: Function
  34. bb: string
  35. bbb: string
  36. cc?: string[] | undefined
  37. dd: { n: 1 }
  38. ee?: () => string
  39. ff?: (a: number, b: string) => { a: boolean }
  40. ccc?: string[] | undefined
  41. ddd: string[]
  42. eee: () => { a: string }
  43. fff: (a: number, b: string) => { a: boolean }
  44. hhh: boolean
  45. ggg: 'foo' | 'bar'
  46. ffff: (a: number, b: string) => { a: boolean }
  47. validated?: string
  48. object?: object
  49. }
  50. interface ExpectedRefs {
  51. a: Ref<number | undefined>
  52. b: Ref<string>
  53. e: Ref<Function | undefined>
  54. bb: Ref<string>
  55. bbb: Ref<string>
  56. cc: Ref<string[] | undefined>
  57. dd: Ref<{ n: 1 }>
  58. ee: Ref<(() => string) | undefined>
  59. ff: Ref<((a: number, b: string) => { a: boolean }) | undefined>
  60. ccc: Ref<string[] | undefined>
  61. ddd: Ref<string[]>
  62. eee: Ref<() => { a: string }>
  63. fff: Ref<(a: number, b: string) => { a: boolean }>
  64. hhh: Ref<boolean>
  65. ggg: Ref<'foo' | 'bar'>
  66. ffff: Ref<(a: number, b: string) => { a: boolean }>
  67. validated: Ref<string | undefined>
  68. object: Ref<object | undefined>
  69. zzz: any
  70. }
  71. describe('defineComponent', () => {
  72. const MyComponent = defineComponent({
  73. props: {
  74. a: Number,
  75. // required should make property non-void
  76. b: {
  77. type: String,
  78. required: true,
  79. },
  80. e: Function,
  81. // default value should infer type and make it non-void
  82. bb: {
  83. default: 'hello',
  84. },
  85. bbb: {
  86. // Note: default function value requires arrow syntax + explicit
  87. // annotation
  88. default: (props: any) => (props.bb as string) || 'foo',
  89. },
  90. // explicit type casting
  91. cc: Array as PropType<string[]>,
  92. // required + type casting
  93. dd: {
  94. type: Object as PropType<{ n: 1 }>,
  95. required: true,
  96. },
  97. // return type
  98. ee: Function as PropType<() => string>,
  99. // arguments + object return
  100. ff: Function as PropType<(a: number, b: string) => { a: boolean }>,
  101. // explicit type casting with constructor
  102. ccc: Array as () => string[],
  103. // required + constructor type casting
  104. ddd: {
  105. type: Array as () => string[],
  106. required: true,
  107. },
  108. // required + object return
  109. eee: {
  110. type: Function as PropType<() => { a: string }>,
  111. required: true,
  112. },
  113. // required + arguments + object return
  114. fff: {
  115. type: Function as PropType<(a: number, b: string) => { a: boolean }>,
  116. required: true,
  117. },
  118. hhh: {
  119. type: Boolean,
  120. required: true,
  121. },
  122. // default + type casting
  123. ggg: {
  124. type: String as PropType<'foo' | 'bar'>,
  125. default: 'foo',
  126. },
  127. // default + function
  128. ffff: {
  129. type: Function as PropType<(a: number, b: string) => { a: boolean }>,
  130. default: (_a: number, _b: string) => ({ a: true }),
  131. },
  132. validated: {
  133. type: String,
  134. // validator requires explicit annotation
  135. validator: (val: unknown) => val !== '',
  136. },
  137. object: Object as PropType<object>,
  138. zzz: Object as PropType<any>,
  139. },
  140. setup(props) {
  141. const refs = toRefs(props)
  142. expectType<ExpectedRefs['a']>(refs.a)
  143. expectType<ExpectedRefs['b']>(refs.b)
  144. expectType<ExpectedRefs['e']>(refs.e)
  145. expectType<ExpectedRefs['bb']>(refs.bb)
  146. expectType<ExpectedRefs['bbb']>(refs.bbb)
  147. expectType<ExpectedRefs['cc']>(refs.cc)
  148. expectType<ExpectedRefs['dd']>(refs.dd)
  149. expectType<ExpectedRefs['ee']>(refs.ee)
  150. expectType<ExpectedRefs['ff']>(refs.ff)
  151. expectType<ExpectedRefs['ccc']>(refs.ccc)
  152. expectType<ExpectedRefs['ddd']>(refs.ddd)
  153. expectType<ExpectedRefs['eee']>(refs.eee)
  154. expectType<ExpectedRefs['fff']>(refs.fff)
  155. expectType<ExpectedRefs['hhh']>(refs.hhh)
  156. expectType<ExpectedRefs['ggg']>(refs.ggg)
  157. expectType<ExpectedRefs['ffff']>(refs.ffff)
  158. expectType<ExpectedRefs['validated']>(refs.validated)
  159. expectType<ExpectedRefs['object']>(refs.object)
  160. expectType<IsAny<typeof props.zzz>>(true)
  161. return {
  162. setupA: 1,
  163. setupB: ref(1),
  164. setupC: {
  165. a: ref(2),
  166. },
  167. setupD: undefined as Ref<number> | undefined,
  168. setupProps: props,
  169. }
  170. },
  171. })
  172. const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
  173. // props
  174. expectType<ExpectedProps['a']>(props.a)
  175. expectType<ExpectedProps['b']>(props.b)
  176. expectType<ExpectedProps['e']>(props.e)
  177. expectType<ExpectedProps['bb']>(props.bb)
  178. expectType<ExpectedProps['bbb']>(props.bbb)
  179. expectType<ExpectedProps['cc']>(props.cc)
  180. expectType<ExpectedProps['dd']>(props.dd)
  181. expectType<ExpectedProps['ee']>(props.ee)
  182. expectType<ExpectedProps['ff']>(props.ff)
  183. expectType<ExpectedProps['ccc']>(props.ccc)
  184. expectType<ExpectedProps['ddd']>(props.ddd)
  185. expectType<ExpectedProps['eee']>(props.eee)
  186. expectType<ExpectedProps['fff']>(props.fff)
  187. expectType<ExpectedProps['hhh']>(props.hhh)
  188. expectType<ExpectedProps['ggg']>(props.ggg)
  189. expectType<ExpectedProps['ffff']>(props.ffff)
  190. expectType<ExpectedProps['validated']>(props.validated)
  191. expectType<ExpectedProps['object']>(props.object)
  192. // raw bindings
  193. expectType<Number>(rawBindings.setupA)
  194. expectType<Ref<Number>>(rawBindings.setupB)
  195. expectType<Ref<Number>>(rawBindings.setupC.a)
  196. expectType<Ref<Number> | undefined>(rawBindings.setupD)
  197. // raw bindings props
  198. expectType<ExpectedProps['a']>(rawBindings.setupProps.a)
  199. expectType<ExpectedProps['b']>(rawBindings.setupProps.b)
  200. expectType<ExpectedProps['e']>(rawBindings.setupProps.e)
  201. expectType<ExpectedProps['bb']>(rawBindings.setupProps.bb)
  202. expectType<ExpectedProps['bbb']>(rawBindings.setupProps.bbb)
  203. expectType<ExpectedProps['cc']>(rawBindings.setupProps.cc)
  204. expectType<ExpectedProps['dd']>(rawBindings.setupProps.dd)
  205. expectType<ExpectedProps['ee']>(rawBindings.setupProps.ee)
  206. expectType<ExpectedProps['ff']>(rawBindings.setupProps.ff)
  207. expectType<ExpectedProps['ccc']>(rawBindings.setupProps.ccc)
  208. expectType<ExpectedProps['ddd']>(rawBindings.setupProps.ddd)
  209. expectType<ExpectedProps['eee']>(rawBindings.setupProps.eee)
  210. expectType<ExpectedProps['fff']>(rawBindings.setupProps.fff)
  211. expectType<ExpectedProps['hhh']>(rawBindings.setupProps.hhh)
  212. expectType<ExpectedProps['ggg']>(rawBindings.setupProps.ggg)
  213. expectType<ExpectedProps['ffff']>(rawBindings.setupProps.ffff)
  214. expectType<ExpectedProps['validated']>(rawBindings.setupProps.validated)
  215. // setup
  216. expectType<Number>(setup.setupA)
  217. expectType<Number>(setup.setupB)
  218. expectType<Ref<Number>>(setup.setupC.a)
  219. expectType<number | undefined>(setup.setupD)
  220. // raw bindings props
  221. expectType<ExpectedProps['a']>(setup.setupProps.a)
  222. expectType<ExpectedProps['b']>(setup.setupProps.b)
  223. expectType<ExpectedProps['e']>(setup.setupProps.e)
  224. expectType<ExpectedProps['bb']>(setup.setupProps.bb)
  225. expectType<ExpectedProps['bbb']>(setup.setupProps.bbb)
  226. expectType<ExpectedProps['cc']>(setup.setupProps.cc)
  227. expectType<ExpectedProps['dd']>(setup.setupProps.dd)
  228. expectType<ExpectedProps['ee']>(setup.setupProps.ee)
  229. expectType<ExpectedProps['ff']>(setup.setupProps.ff)
  230. expectType<ExpectedProps['ccc']>(setup.setupProps.ccc)
  231. expectType<ExpectedProps['ddd']>(setup.setupProps.ddd)
  232. expectType<ExpectedProps['eee']>(setup.setupProps.eee)
  233. expectType<ExpectedProps['fff']>(setup.setupProps.fff)
  234. expectType<ExpectedProps['hhh']>(setup.setupProps.hhh)
  235. expectType<ExpectedProps['ggg']>(setup.setupProps.ggg)
  236. expectType<ExpectedProps['ffff']>(setup.setupProps.ffff)
  237. expectType<ExpectedProps['validated']>(setup.setupProps.validated)
  238. // instance
  239. const instance = new MyComponent()
  240. expectType<number>(instance.setupA)
  241. expectType<number | undefined>(instance.setupD)
  242. // @ts-expect-error
  243. instance.notExist
  244. })
  245. describe('options', () => {
  246. const MyComponent = {
  247. props: {
  248. a: Number,
  249. // required should make property non-void
  250. b: {
  251. type: String,
  252. required: true,
  253. },
  254. e: Function,
  255. // default value should infer type and make it non-void
  256. bb: {
  257. default: 'hello',
  258. },
  259. bbb: {
  260. // Note: default function value requires arrow syntax + explicit
  261. // annotation
  262. default: (props: any) => (props.bb as string) || 'foo',
  263. },
  264. // explicit type casting
  265. cc: Array as PropType<string[]>,
  266. // required + type casting
  267. dd: {
  268. type: Object as PropType<{ n: 1 }>,
  269. required: true,
  270. },
  271. // return type
  272. ee: Function as PropType<() => string>,
  273. // arguments + object return
  274. ff: Function as PropType<(a: number, b: string) => { a: boolean }>,
  275. // explicit type casting with constructor
  276. ccc: Array as () => string[],
  277. // required + constructor type casting
  278. ddd: {
  279. type: Array as () => string[],
  280. required: true,
  281. },
  282. // required + object return
  283. eee: {
  284. type: Function as PropType<() => { a: string }>,
  285. required: true,
  286. },
  287. // required + arguments + object return
  288. fff: {
  289. type: Function as PropType<(a: number, b: string) => { a: boolean }>,
  290. required: true,
  291. },
  292. hhh: {
  293. type: Boolean,
  294. required: true,
  295. },
  296. // default + type casting
  297. ggg: {
  298. type: String as PropType<'foo' | 'bar'>,
  299. default: 'foo',
  300. },
  301. // default + function
  302. ffff: {
  303. type: Function as PropType<(a: number, b: string) => { a: boolean }>,
  304. default: (_a: number, _b: string) => ({ a: true }),
  305. },
  306. validated: {
  307. type: String,
  308. // validator requires explicit annotation
  309. validator: (val: unknown) => val !== '',
  310. },
  311. object: Object as PropType<object>,
  312. },
  313. setup() {
  314. return {
  315. setupA: 1,
  316. }
  317. },
  318. } as const
  319. const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
  320. // props
  321. expectType<ExpectedProps['a']>(props.a)
  322. expectType<ExpectedProps['b']>(props.b)
  323. expectType<ExpectedProps['e']>(props.e)
  324. expectType<ExpectedProps['bb']>(props.bb)
  325. expectType<ExpectedProps['bbb']>(props.bbb)
  326. expectType<ExpectedProps['cc']>(props.cc)
  327. expectType<ExpectedProps['dd']>(props.dd)
  328. expectType<ExpectedProps['ee']>(props.ee)
  329. expectType<ExpectedProps['ff']>(props.ff)
  330. expectType<ExpectedProps['ccc']>(props.ccc)
  331. expectType<ExpectedProps['ddd']>(props.ddd)
  332. expectType<ExpectedProps['eee']>(props.eee)
  333. expectType<ExpectedProps['fff']>(props.fff)
  334. expectType<ExpectedProps['hhh']>(props.hhh)
  335. expectType<ExpectedProps['ggg']>(props.ggg)
  336. // expectType<ExpectedProps['ffff']>(props.ffff) // todo fix
  337. expectType<ExpectedProps['validated']>(props.validated)
  338. expectType<ExpectedProps['object']>(props.object)
  339. // rawBindings
  340. expectType<Number>(rawBindings.setupA)
  341. //setup
  342. expectType<Number>(setup.setupA)
  343. })
  344. })
  345. describe('array props', () => {
  346. describe('defineComponent', () => {
  347. const MyComponent = defineComponent({
  348. props: ['a', 'b'],
  349. setup() {
  350. return {
  351. c: 1,
  352. }
  353. },
  354. })
  355. const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
  356. // @ts-expect-error props should be readonly
  357. props.a = 1
  358. expectType<any>(props.a)
  359. expectType<any>(props.b)
  360. expectType<number>(rawBindings.c)
  361. expectType<number>(setup.c)
  362. })
  363. describe('options', () => {
  364. const MyComponent = {
  365. props: ['a', 'b'] as const,
  366. setup() {
  367. return {
  368. c: 1,
  369. }
  370. },
  371. }
  372. const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
  373. // @ts-expect-error props should be readonly
  374. props.a = 1
  375. // TODO infer the correct keys
  376. // expectType<any>(props.a)
  377. // expectType<any>(props.b)
  378. expectType<number>(rawBindings.c)
  379. expectType<number>(setup.c)
  380. })
  381. })
  382. describe('no props', () => {
  383. describe('defineComponent', () => {
  384. const MyComponent = defineComponent({
  385. setup() {
  386. return {
  387. setupA: 1,
  388. }
  389. },
  390. })
  391. const { rawBindings, setup } = extractComponentOptions(MyComponent)
  392. expectType<number>(rawBindings.setupA)
  393. expectType<number>(setup.setupA)
  394. // instance
  395. const instance = new MyComponent()
  396. expectType<number>(instance.setupA)
  397. // @ts-expect-error
  398. instance.notExist
  399. })
  400. describe('options', () => {
  401. const MyComponent = {
  402. setup() {
  403. return {
  404. setupA: 1,
  405. }
  406. },
  407. }
  408. const { rawBindings, setup } = extractComponentOptions(MyComponent)
  409. expectType<number>(rawBindings.setupA)
  410. expectType<number>(setup.setupA)
  411. })
  412. })
  413. describe('functional', () => {
  414. // TODO `props.foo` is `number|undefined`
  415. // describe('defineComponent', () => {
  416. // const MyComponent = defineComponent((props: { foo: number }) => {})
  417. // const { props } = extractComponentOptions(MyComponent)
  418. // expectType<number>(props.foo)
  419. // })
  420. describe('function', () => {
  421. const MyComponent = (props: { foo: number }) => props.foo
  422. const { props } = extractComponentOptions(MyComponent)
  423. expectType<number>(props.foo)
  424. })
  425. describe('typed', () => {
  426. type Props = { foo: number }
  427. type Emits = { change: [value: string]; inc: [value: number] }
  428. type Slots = { default: (scope: { foo: string }) => any }
  429. const MyComponent: FunctionalComponent<Props, Emits, Slots> = (
  430. props,
  431. { emit, slots },
  432. ) => {
  433. expectType<Props>(props)
  434. expectType<{
  435. (event: 'change', value: string): void
  436. (event: 'inc', value: number): void
  437. }>(emit)
  438. expectType<Slots>(slots)
  439. }
  440. const { props, emits, slots } = extractComponentOptions(MyComponent)
  441. expectType<Props>(props)
  442. expectType<Emits>(emits)
  443. expectType<Slots>(slots)
  444. })
  445. })
  446. declare type VueClass<Props = {}> = {
  447. new (): ComponentPublicInstance<Props>
  448. }
  449. describe('class', () => {
  450. const MyComponent: VueClass<{ foo: number }> = {} as any
  451. const { props } = extractComponentOptions(MyComponent)
  452. expectType<number>(props.foo)
  453. })
  454. describe('SetupContext', () => {
  455. describe('can assign', () => {
  456. const wider: SetupContext<{ a: () => true; b: () => true }> = {} as any
  457. expectAssignable<SetupContext<{ b: () => true }>>(wider)
  458. })
  459. describe('short emits', () => {
  460. const {
  461. emit,
  462. }: SetupContext<{
  463. a: [val: string]
  464. b: [val: number]
  465. }> = {} as any
  466. expectType<{
  467. (event: 'a', val: string): void
  468. (event: 'b', val: number): void
  469. }>(emit)
  470. })
  471. })