component.test-d.ts 14 KB

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