defineVaporComponent.test-d.tsx 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  1. import {
  2. type AllowedComponentProps,
  3. type Block,
  4. type Component,
  5. type ComponentCustomProps,
  6. type DefineVaporComponent,
  7. type EmitsOptions,
  8. type ExtractPropTypes,
  9. type GenericComponentInstance,
  10. type PropType,
  11. type VaporComponentInstance,
  12. type VaporPublicProps,
  13. createApp,
  14. createComponent,
  15. createVaporApp,
  16. defineVaporComponent,
  17. reactive,
  18. ref,
  19. } from 'vue'
  20. import './jsx'
  21. import { type IsAny, type IsUnion, describe, expectType } from '../utils'
  22. import '../built.test-d'
  23. describe('with object props', () => {
  24. interface ExpectedProps {
  25. a?: number | undefined
  26. aa: number
  27. aaa: number | null
  28. aaaa: number | undefined
  29. b: string
  30. e?: Function
  31. h: boolean
  32. j: undefined | (() => string | undefined)
  33. bb: string
  34. bbb: string
  35. bbbb: string | undefined
  36. bbbbb: string | undefined
  37. cc?: string[] | undefined
  38. dd: { n: 1 }
  39. ee?: () => string
  40. ff?: (a: number, b: string) => { a: boolean }
  41. ccc?: string[] | undefined
  42. ddd: string[]
  43. eee: () => { a: string }
  44. fff: (a: number, b: string) => { a: boolean }
  45. hhh: boolean
  46. ggg: 'foo' | 'bar'
  47. ffff: (a: number, b: string) => { a: boolean }
  48. iii?: (() => string) | (() => number)
  49. jjj: ((arg1: string) => string) | ((arg1: string, arg2: string) => string)
  50. kkk?: any
  51. validated?: string
  52. date?: Date
  53. l?: Date
  54. ll?: Date | number
  55. lll?: string | number
  56. }
  57. type GT = string & { __brand: unknown }
  58. const props = {
  59. a: Number,
  60. aa: {
  61. type: Number as PropType<number | undefined>,
  62. default: 1,
  63. },
  64. aaa: {
  65. type: Number as PropType<number | null>,
  66. default: 1,
  67. },
  68. aaaa: {
  69. type: Number as PropType<number | undefined>,
  70. // `as const` prevents widening to `boolean` (keeps literal `true` type)
  71. required: true as const,
  72. },
  73. // required should make property non-void
  74. b: {
  75. type: String,
  76. required: true as true,
  77. },
  78. e: Function,
  79. h: Boolean,
  80. j: Function as PropType<undefined | (() => string | undefined)>,
  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. bbbb: {
  91. type: String,
  92. default: undefined,
  93. },
  94. bbbbb: {
  95. type: String,
  96. default: () => undefined,
  97. },
  98. // explicit type casting
  99. cc: Array as PropType<string[]>,
  100. // required + type casting
  101. dd: {
  102. type: Object as PropType<{ n: 1 }>,
  103. required: true as true,
  104. },
  105. // return type
  106. ee: Function as PropType<() => string>,
  107. // arguments + object return
  108. ff: Function as PropType<(a: number, b: string) => { a: boolean }>,
  109. // explicit type casting with constructor
  110. ccc: Array as () => string[],
  111. // required + constructor type casting
  112. ddd: {
  113. type: Array as () => string[],
  114. required: true as true,
  115. },
  116. // required + object return
  117. eee: {
  118. type: Function as PropType<() => { a: string }>,
  119. required: true as true,
  120. },
  121. // required + arguments + object return
  122. fff: {
  123. type: Function as PropType<(a: number, b: string) => { a: boolean }>,
  124. required: true as true,
  125. },
  126. hhh: {
  127. type: Boolean,
  128. required: true as true,
  129. },
  130. // default + type casting
  131. ggg: {
  132. type: String as PropType<'foo' | 'bar'>,
  133. default: 'foo',
  134. },
  135. // default + function
  136. ffff: {
  137. type: Function as PropType<(a: number, b: string) => { a: boolean }>,
  138. default: (a: number, b: string) => ({ a: a > +b }),
  139. },
  140. // union + function with different return types
  141. iii: Function as PropType<(() => string) | (() => number)>,
  142. // union + function with different args & same return type
  143. jjj: {
  144. type: Function as PropType<
  145. ((arg1: string) => string) | ((arg1: string, arg2: string) => string)
  146. >,
  147. required: true as true,
  148. },
  149. kkk: null,
  150. validated: {
  151. type: String,
  152. // validator requires explicit annotation
  153. validator: (val: unknown) => val !== '',
  154. },
  155. date: Date,
  156. l: [Date],
  157. ll: [Date, Number],
  158. lll: [String, Number],
  159. }
  160. const MyComponent = defineVaporComponent({
  161. props,
  162. setup(props) {
  163. // type assertion. See https://github.com/SamVerschueren/tsd
  164. expectType<ExpectedProps['a']>(props.a)
  165. expectType<ExpectedProps['aa']>(props.aa)
  166. expectType<ExpectedProps['aaa']>(props.aaa)
  167. // @ts-expect-error should included `undefined`
  168. expectType<number>(props.aaaa)
  169. expectType<ExpectedProps['aaaa']>(props.aaaa)
  170. expectType<ExpectedProps['b']>(props.b)
  171. expectType<ExpectedProps['e']>(props.e)
  172. expectType<ExpectedProps['h']>(props.h)
  173. expectType<ExpectedProps['j']>(props.j)
  174. expectType<ExpectedProps['bb']>(props.bb)
  175. expectType<ExpectedProps['bbb']>(props.bbb)
  176. expectType<ExpectedProps['bbbb']>(props.bbbb)
  177. expectType<ExpectedProps['bbbbb']>(props.bbbbb)
  178. expectType<ExpectedProps['cc']>(props.cc)
  179. expectType<ExpectedProps['dd']>(props.dd)
  180. expectType<ExpectedProps['ee']>(props.ee)
  181. expectType<ExpectedProps['ff']>(props.ff)
  182. expectType<ExpectedProps['ccc']>(props.ccc)
  183. expectType<ExpectedProps['ddd']>(props.ddd)
  184. expectType<ExpectedProps['eee']>(props.eee)
  185. expectType<ExpectedProps['fff']>(props.fff)
  186. expectType<ExpectedProps['hhh']>(props.hhh)
  187. expectType<ExpectedProps['ggg']>(props.ggg)
  188. expectType<ExpectedProps['ffff']>(props.ffff)
  189. if (typeof props.iii !== 'function') {
  190. expectType<undefined>(props.iii)
  191. }
  192. expectType<ExpectedProps['iii']>(props.iii)
  193. expectType<IsUnion<typeof props.jjj>>(true)
  194. expectType<ExpectedProps['jjj']>(props.jjj)
  195. expectType<ExpectedProps['kkk']>(props.kkk)
  196. expectType<ExpectedProps['validated']>(props.validated)
  197. expectType<ExpectedProps['date']>(props.date)
  198. expectType<ExpectedProps['l']>(props.l)
  199. expectType<ExpectedProps['ll']>(props.ll)
  200. expectType<ExpectedProps['lll']>(props.lll)
  201. // @ts-expect-error props should be readonly
  202. props.a = 1
  203. // setup context
  204. return {
  205. c: ref(1),
  206. d: {
  207. e: ref('hi'),
  208. },
  209. f: reactive({
  210. g: ref('hello' as GT),
  211. }),
  212. }
  213. },
  214. render(ctx, props) {
  215. expectType<ExpectedProps['a']>(props.a)
  216. expectType<ExpectedProps['aa']>(props.aa)
  217. expectType<ExpectedProps['aaa']>(props.aaa)
  218. expectType<ExpectedProps['b']>(props.b)
  219. expectType<ExpectedProps['e']>(props.e)
  220. expectType<ExpectedProps['h']>(props.h)
  221. expectType<ExpectedProps['bb']>(props.bb)
  222. expectType<ExpectedProps['cc']>(props.cc)
  223. expectType<ExpectedProps['dd']>(props.dd)
  224. expectType<ExpectedProps['ee']>(props.ee)
  225. expectType<ExpectedProps['ff']>(props.ff)
  226. expectType<ExpectedProps['ccc']>(props.ccc)
  227. expectType<ExpectedProps['ddd']>(props.ddd)
  228. expectType<ExpectedProps['eee']>(props.eee)
  229. expectType<ExpectedProps['fff']>(props.fff)
  230. expectType<ExpectedProps['hhh']>(props.hhh)
  231. expectType<ExpectedProps['ggg']>(props.ggg)
  232. if (typeof props.iii !== 'function') {
  233. expectType<undefined>(props.iii)
  234. }
  235. expectType<ExpectedProps['iii']>(props.iii)
  236. expectType<IsUnion<typeof props.jjj>>(true)
  237. expectType<ExpectedProps['jjj']>(props.jjj)
  238. expectType<ExpectedProps['kkk']>(props.kkk)
  239. // @ts-expect-error props should be readonly
  240. props.a = 1
  241. return []
  242. },
  243. })
  244. expectType<Component>(MyComponent)
  245. // Test TSX
  246. expectType<JSX.Element>(
  247. <MyComponent
  248. a={1}
  249. aaaa={1}
  250. b="b"
  251. bb="bb"
  252. e={() => {}}
  253. cc={['cc']}
  254. dd={{ n: 1 }}
  255. ee={() => 'ee'}
  256. ccc={['ccc']}
  257. ddd={['ddd']}
  258. eee={() => ({ a: 'eee' })}
  259. fff={(a, b) => ({ a: a > +b })}
  260. hhh={false}
  261. ggg="foo"
  262. jjj={() => ''}
  263. // should allow class/style as attrs
  264. class="bar"
  265. style={{ color: 'red' }}
  266. // should allow key
  267. key={'foo'}
  268. // should allow ref
  269. ref={'foo'}
  270. ref_for={true}
  271. />,
  272. )
  273. expectType<Component>(
  274. <MyComponent
  275. aaaa={1}
  276. b="b"
  277. dd={{ n: 1 }}
  278. ddd={['ddd']}
  279. eee={() => ({ a: 'eee' })}
  280. fff={(a, b) => ({ a: a > +b })}
  281. hhh={false}
  282. jjj={() => ''}
  283. />,
  284. )
  285. // @ts-expect-error missing required props
  286. let c = <MyComponent />
  287. // @ts-expect-error wrong prop types
  288. c = <MyComponent a={'wrong type'} b="foo" dd={{ n: 1 }} ddd={['foo']} />
  289. // @ts-expect-error wrong prop types
  290. c = <MyComponent ggg="baz" />
  291. // @ts-expect-error
  292. ;<MyComponent b="foo" dd={{ n: 'string' }} ddd={['foo']} />
  293. // `this` should be void inside of prop validators and prop default factories
  294. defineVaporComponent({
  295. props: {
  296. myProp: {
  297. type: Number,
  298. validator(val: unknown): boolean {
  299. // @ts-expect-error
  300. return val !== this.otherProp
  301. },
  302. default(): number {
  303. // @ts-expect-error
  304. return this.otherProp + 1
  305. },
  306. },
  307. otherProp: {
  308. type: Number,
  309. required: true,
  310. },
  311. },
  312. })
  313. })
  314. describe('type inference w/ optional props declaration', () => {
  315. const MyComponent = defineVaporComponent<{ a: string[]; msg: string }>({
  316. setup(props) {
  317. expectType<string>(props.msg)
  318. expectType<string[]>(props.a)
  319. return {
  320. b: 1,
  321. }
  322. },
  323. })
  324. expectType<JSX.Element>(<MyComponent msg="1" a={['1']} />)
  325. // @ts-expect-error
  326. ;<MyComponent />
  327. // @ts-expect-error
  328. ;<MyComponent msg="1" />
  329. })
  330. describe('type inference w/ direct setup function', () => {
  331. const MyComponent = defineVaporComponent((_props: { msg: string }) => [])
  332. expectType<JSX.Element>(<MyComponent msg="foo" />)
  333. // @ts-expect-error
  334. ;<MyComponent />
  335. // @ts-expect-error
  336. ;<MyComponent msg={1} />
  337. })
  338. describe('type inference w/ array props declaration', () => {
  339. const MyComponent = defineVaporComponent({
  340. props: ['a', 'b'],
  341. setup(props) {
  342. // @ts-expect-error props should be readonly
  343. props.a = 1
  344. expectType<any>(props.a)
  345. expectType<any>(props.b)
  346. return {
  347. c: 1,
  348. }
  349. },
  350. render(ctx, props) {
  351. expectType<any>(props.a)
  352. expectType<any>(props.b)
  353. // @ts-expect-error
  354. props.a = 1
  355. expectType<number>(ctx.c)
  356. return []
  357. },
  358. })
  359. expectType<JSX.Element>(<MyComponent a={[1, 2]} b="b" />)
  360. // @ts-expect-error
  361. ;<MyComponent other="other" />
  362. })
  363. // #4051
  364. describe('type inference w/ empty prop object', () => {
  365. const MyComponent = defineVaporComponent({
  366. props: {},
  367. setup(props) {
  368. return {}
  369. },
  370. render() {
  371. return []
  372. },
  373. })
  374. expectType<JSX.Element>(<MyComponent />)
  375. // AllowedComponentProps
  376. expectType<JSX.Element>(<MyComponent class={'foo'} />)
  377. // ComponentCustomProps
  378. expectType<JSX.Element>(<MyComponent custom={1} />)
  379. // VNodeProps
  380. expectType<JSX.Element>(<MyComponent key="1" />)
  381. // @ts-expect-error
  382. expectError(<MyComponent other="other" />)
  383. })
  384. describe('compatibility w/ createComponent', () => {
  385. const comp = defineVaporComponent({})
  386. createComponent(comp)
  387. const comp2 = defineVaporComponent({
  388. props: { foo: String },
  389. })
  390. createComponent(comp2)
  391. const comp3 = defineVaporComponent({
  392. setup() {
  393. return {
  394. a: 1,
  395. }
  396. },
  397. })
  398. createComponent(comp3)
  399. const comp4 = defineVaporComponent(() => [])
  400. createComponent(comp4)
  401. })
  402. describe('compatibility w/ createApp', () => {
  403. const comp = defineVaporComponent({})
  404. createApp(comp).mount('#hello')
  405. const comp2 = defineVaporComponent({
  406. props: { foo: String },
  407. })
  408. createVaporApp(comp2).mount('#hello')
  409. const comp3 = defineVaporComponent({
  410. setup() {
  411. return {
  412. a: 1,
  413. }
  414. },
  415. })
  416. createVaporApp(comp3).mount('#hello')
  417. })
  418. describe('emits', () => {
  419. // Note: for TSX inference, ideally we want to map emits to onXXX props,
  420. // but that requires type-level string constant concatenation as suggested in
  421. // https://github.com/Microsoft/TypeScript/issues/12754
  422. // The workaround for TSX users is instead of using emits, declare onXXX props
  423. // and call them instead. Since `v-on:click` compiles to an `onClick` prop,
  424. // this would also support other users consuming the component in templates
  425. // with `v-on` listeners.
  426. // with object emits
  427. defineVaporComponent({
  428. emits: {
  429. click: (n: number) => typeof n === 'number',
  430. input: (b: string) => b.length > 1,
  431. Focus: (f: boolean) => !!f,
  432. },
  433. setup(props, { emit }) {
  434. emit('click', 1)
  435. emit('input', 'foo')
  436. emit('Focus', true)
  437. // @ts-expect-error
  438. emit('nope')
  439. // @ts-expect-error
  440. emit('click')
  441. // @ts-expect-error
  442. emit('click', 'foo')
  443. // @ts-expect-error
  444. emit('input')
  445. // @ts-expect-error
  446. emit('input', 1)
  447. // @ts-expect-error
  448. emit('focus')
  449. // @ts-expect-error
  450. emit('focus', true)
  451. },
  452. })
  453. // with array emits
  454. defineVaporComponent({
  455. emits: ['foo', 'bar'],
  456. setup(props, { emit }) {
  457. emit('foo')
  458. emit('foo', 123)
  459. emit('bar')
  460. // @ts-expect-error
  461. emit('nope')
  462. },
  463. })
  464. // with tsx
  465. const Component = defineVaporComponent({
  466. emits: {
  467. click: (n: number) => typeof n === 'number',
  468. },
  469. setup(props, { emit }) {
  470. emit('click', 1)
  471. // @ts-expect-error
  472. emit('click')
  473. // @ts-expect-error
  474. emit('click', 'foo')
  475. },
  476. })
  477. defineVaporComponent({
  478. render() {
  479. return (
  480. <Component
  481. onClick={(n: number) => {
  482. return n + 1
  483. }}
  484. />
  485. )
  486. },
  487. })
  488. // #11803 manual props annotation in setup()
  489. const Hello = defineVaporComponent({
  490. name: 'HelloWorld',
  491. inheritAttrs: false,
  492. props: { foo: String },
  493. emits: {
  494. customClick: (args: string) => typeof args === 'string',
  495. },
  496. setup(props: { foo?: string }) {},
  497. })
  498. ;<Hello onCustomClick={() => {}} />
  499. // without emits
  500. defineVaporComponent({
  501. setup(props, { emit }) {
  502. emit('test', 1)
  503. emit('test')
  504. },
  505. })
  506. // emit should be valid when GenericComponentInstance is used.
  507. const instance = {} as GenericComponentInstance
  508. instance.emit('test', 1)
  509. instance.emit('test')
  510. // `this` should be void inside of emits validators
  511. defineVaporComponent({
  512. props: ['bar'],
  513. emits: {
  514. foo(): boolean {
  515. // @ts-expect-error
  516. return this.bar === 3
  517. },
  518. },
  519. })
  520. })
  521. describe('extract instance type', () => {
  522. const CompA = defineVaporComponent({
  523. props: {
  524. a: {
  525. type: Boolean,
  526. default: false,
  527. },
  528. b: {
  529. type: String,
  530. required: true,
  531. },
  532. c: Number,
  533. },
  534. })
  535. const compA = {} as InstanceType<typeof CompA>
  536. expectType<boolean | undefined>(compA.props.a)
  537. expectType<string>(compA.props.b)
  538. expectType<number | undefined>(compA.props.c)
  539. // @ts-expect-error
  540. compA.props.a = true
  541. // @ts-expect-error
  542. compA.props.b = 'foo'
  543. // @ts-expect-error
  544. compA.props.c = 1
  545. })
  546. describe('async setup', () => {
  547. type GT = string & { __brand: unknown }
  548. const Comp = defineVaporComponent({
  549. async setup() {
  550. // setup context
  551. return {
  552. a: ref(1),
  553. b: {
  554. c: ref('hi'),
  555. },
  556. d: reactive({
  557. e: ref('hello' as GT),
  558. }),
  559. }
  560. },
  561. render(ctx) {
  562. // assert setup context unwrapping
  563. expectType<number>(ctx.a)
  564. expectType<string>(ctx.b.c.value)
  565. expectType<GT>(ctx.d.e)
  566. // setup context properties should be mutable
  567. ctx.a = 2
  568. return []
  569. },
  570. })
  571. const vm = {} as InstanceType<typeof Comp>
  572. if (vm.exposed && vm.exposeProxy) {
  573. // assert setup context unwrapping
  574. expectType<number>(vm.exposeProxy.a)
  575. expectType<string>(vm.exposeProxy.b.c.value)
  576. expectType<GT>(vm.exposeProxy.d.e)
  577. // setup context properties should be mutable
  578. vm.exposed.a.value = 2
  579. }
  580. })
  581. // #5948
  582. describe('defineVaporComponent should infer correct types when assigning to Component', () => {
  583. let component: Component
  584. component = defineVaporComponent({
  585. setup(_, { attrs, slots }) {
  586. // @ts-expect-error should not be any
  587. expectType<[]>(attrs)
  588. // @ts-expect-error should not be any
  589. expectType<[]>(slots)
  590. },
  591. })
  592. expectType<Component>(component)
  593. })
  594. // #5969
  595. describe('should allow to assign props', () => {
  596. const Child = defineVaporComponent({
  597. props: {
  598. bar: String,
  599. },
  600. })
  601. const Parent = defineVaporComponent({
  602. props: {
  603. ...Child.props,
  604. foo: String,
  605. },
  606. })
  607. const child = new Child()
  608. expectType<JSX.Element>(<Parent {...child.props} />)
  609. })
  610. // #6052
  611. describe('prop starting with `on*` is broken', () => {
  612. defineVaporComponent({
  613. props: {
  614. onX: {
  615. type: Function as PropType<(a: 1) => void>,
  616. required: true,
  617. },
  618. },
  619. setup(props) {
  620. expectType<(a: 1) => void>(props.onX)
  621. props.onX(1)
  622. },
  623. })
  624. defineVaporComponent({
  625. props: {
  626. onX: {
  627. type: Function as PropType<(a: 1) => void>,
  628. required: true,
  629. },
  630. },
  631. emits: {
  632. test: (a: 1) => true,
  633. },
  634. setup(props) {
  635. expectType<(a: 1) => void>(props.onX)
  636. },
  637. })
  638. })
  639. describe('function syntax w/ generics', () => {
  640. const Comp = defineVaporComponent(
  641. // TODO: babel plugin to auto infer runtime props options from type
  642. // similar to defineProps<{...}>()
  643. <T extends string | number>(props: { msg: T; list: T[] }) => {
  644. // use Composition API here like in <script setup>
  645. const count = ref(0)
  646. return (
  647. <div>
  648. {props.msg} {count.value}
  649. </div>
  650. )
  651. },
  652. )
  653. expectType<JSX.Element>(<Comp msg="fse" list={['foo']} />)
  654. expectType<JSX.Element>(<Comp msg={123} list={[123]} />)
  655. expectType<JSX.Element>(
  656. // @ts-expect-error missing prop
  657. <Comp msg={123} />,
  658. )
  659. expectType<JSX.Element>(
  660. // @ts-expect-error generics don't match
  661. <Comp msg="fse" list={[123]} />,
  662. )
  663. expectType<JSX.Element>(
  664. // @ts-expect-error generics don't match
  665. <Comp msg={123} list={['123']} />,
  666. )
  667. })
  668. describe('function syntax w/ emits', () => {
  669. const Foo = defineVaporComponent(
  670. (props: { msg: string }, ctx) => {
  671. ctx.emit('foo')
  672. // @ts-expect-error
  673. ctx.emit('bar')
  674. return []
  675. },
  676. {
  677. emits: ['foo'],
  678. },
  679. )
  680. expectType<JSX.Element>(<Foo msg="hi" onFoo={() => {}} />)
  681. // @ts-expect-error
  682. expectType<JSX.Element>(<Foo msg="hi" onBar={() => {}} />)
  683. const Bar = defineVaporComponent(
  684. (props: { msg: string }, ctx) => {
  685. ctx.emit('foo', 'hi')
  686. // @ts-expect-error
  687. ctx.emit('foo')
  688. // @ts-expect-error
  689. ctx.emit('bar')
  690. return []
  691. },
  692. {
  693. emits: {
  694. foo: (a: string) => true,
  695. },
  696. },
  697. )
  698. expectType<JSX.Element>(<Bar msg="hi" onFoo={(a: string) => true} />)
  699. // @ts-expect-error
  700. expectType<JSX.Element>(<Foo msg="hi" onBar={() => {}} />)
  701. })
  702. describe('function syntax w/ slots', () => {
  703. // types
  704. type DefaultSlot = (props: { msg: string }) => []
  705. const Foo = defineVaporComponent(
  706. (_props, { slots }: { slots: { default: DefaultSlot } }) => {
  707. return slots.default({ msg: '' })
  708. },
  709. )
  710. const foo = new Foo()
  711. expectType<DefaultSlot>(foo.slots.default)
  712. // runtime
  713. const defaultSlot = (props: { msg: string }) => []
  714. const Bar = defineVaporComponent(
  715. (_props, { slots }) => {
  716. return slots.default({ msg: '' })
  717. },
  718. {
  719. slots: {
  720. default: defaultSlot,
  721. },
  722. },
  723. )
  724. const bar = new Bar()
  725. expectType<typeof defaultSlot>(bar.slots.default)
  726. })
  727. describe('function syntax w/ expose', () => {
  728. // types
  729. const Foo = defineVaporComponent(
  730. (
  731. props: { msg: string },
  732. { expose }: { expose: (exposed: { msg: string }) => void },
  733. ) => {
  734. expose({
  735. msg: props.msg,
  736. })
  737. return []
  738. },
  739. )
  740. const foo = new Foo()
  741. expectType<string>(foo.exposeProxy!.msg)
  742. // runtime
  743. const Bar = defineVaporComponent(
  744. () => {
  745. return []
  746. },
  747. {
  748. setup: () => ({ msg: '' }),
  749. },
  750. )
  751. const bar = new Bar()
  752. expectType<string>(bar.exposeProxy!.msg)
  753. })
  754. describe('function syntax w/ runtime props', () => {
  755. // with runtime props, the runtime props must match
  756. // manual type declaration
  757. defineVaporComponent(
  758. (_props: { msg: string }) => {
  759. return []
  760. },
  761. {
  762. props: ['msg'],
  763. },
  764. )
  765. defineVaporComponent(
  766. <T extends string>(_props: { msg: T }) => {
  767. return []
  768. },
  769. {
  770. props: ['msg'],
  771. },
  772. )
  773. defineVaporComponent(
  774. <T extends string>(_props: { msg: T }) => {
  775. return []
  776. },
  777. {
  778. props: {
  779. msg: String,
  780. },
  781. },
  782. )
  783. // @ts-expect-error string prop names don't match
  784. defineVaporComponent(
  785. (_props: { msg: string }) => {
  786. return []
  787. },
  788. {
  789. props: ['bar'],
  790. },
  791. )
  792. defineVaporComponent(
  793. (_props: { msg: string }) => {
  794. return []
  795. },
  796. {
  797. props: {
  798. // @ts-expect-error prop type mismatch
  799. msg: Number,
  800. },
  801. },
  802. )
  803. // @ts-expect-error prop keys don't match
  804. defineVaporComponent(
  805. (_props: { msg: string }, ctx) => {
  806. return []
  807. },
  808. {
  809. props: {
  810. msg: String,
  811. bar: String,
  812. },
  813. },
  814. )
  815. })
  816. // check if defineVaporComponent can be exported
  817. export default {
  818. // function components
  819. a: defineVaporComponent(_ => []),
  820. // no props
  821. b: defineVaporComponent({}),
  822. c: defineVaporComponent({
  823. props: ['a'],
  824. }),
  825. d: defineVaporComponent({
  826. props: {
  827. a: Number,
  828. },
  829. }),
  830. }
  831. describe('slots', () => {
  832. const comp1 = defineVaporComponent({
  833. slots: {} as {
  834. default: (scope: { foo: string; bar: number }) => Block
  835. optional?: (scope: { data: string }) => Block
  836. undefinedScope: (scope?: { data: string }) => Block
  837. optionalUndefinedScope?: (scope?: { data: string }) => Block
  838. },
  839. setup(props, { slots }) {
  840. expectType<(scope: { foo: string; bar: number }) => Block>(slots.default)
  841. expectType<((scope: { data: string }) => Block) | undefined>(
  842. slots.optional,
  843. )
  844. slots.default({ foo: 'foo', bar: 1 })
  845. // @ts-expect-error it's optional
  846. slots.optional({ data: 'foo' })
  847. slots.optional?.({ data: 'foo' })
  848. expectType<{
  849. (): Block
  850. (scope: undefined | { data: string }): Block
  851. }>(slots.undefinedScope)
  852. expectType<
  853. { (): Block; (scope: undefined | { data: string }): Block } | undefined
  854. >(slots.optionalUndefinedScope)
  855. slots.default({ foo: 'foo', bar: 1 })
  856. // @ts-expect-error it's optional
  857. slots.optional({ data: 'foo' })
  858. slots.optional?.({ data: 'foo' })
  859. slots.undefinedScope()
  860. slots.undefinedScope(undefined)
  861. // @ts-expect-error
  862. slots.undefinedScope('foo')
  863. slots.optionalUndefinedScope?.()
  864. slots.optionalUndefinedScope?.(undefined)
  865. slots.optionalUndefinedScope?.({ data: 'foo' })
  866. // @ts-expect-error
  867. slots.optionalUndefinedScope()
  868. // @ts-expect-error
  869. slots.optionalUndefinedScope?.('foo')
  870. expectType<typeof slots | undefined>(new comp1().slots)
  871. },
  872. })
  873. const comp2 = defineVaporComponent({
  874. setup(props, { slots }) {
  875. // unknown slots
  876. expectType<Record<string, ((...args: any[]) => Block) | undefined>>(slots)
  877. expectType<((...args: any[]) => Block) | undefined>(slots.default)
  878. },
  879. })
  880. expectType<Record<string, ((...args: any[]) => Block) | undefined>>(
  881. new comp2().slots,
  882. )
  883. const comp3 = defineVaporComponent({
  884. setup(
  885. props,
  886. { slots }: { slots: { default: (props: { foo: number }) => Block } },
  887. ) {
  888. expectType<(props: { foo: number }) => Block>(slots.default)
  889. },
  890. })
  891. expectType<Record<string, (props: { foo: number }) => Block>>(
  892. new comp3().slots,
  893. )
  894. })
  895. describe('render', () => {
  896. defineVaporComponent({
  897. props: {
  898. foo: Number,
  899. },
  900. emits: {
  901. change: (e: number) => {},
  902. },
  903. slots: {} as { default: () => [] },
  904. setup() {
  905. return {
  906. bar: '',
  907. }
  908. },
  909. render(ctx, props, emit, attrs, slots) {
  910. expectType<number | undefined>(props.foo)
  911. expectType<string>(ctx.bar)
  912. emit('change', 1)
  913. return slots.default()
  914. },
  915. })
  916. })
  917. // #5885
  918. describe('should work when props type is incompatible with setup returned type ', () => {
  919. type SizeType = 'small' | 'big'
  920. const Comp = defineVaporComponent({
  921. props: {
  922. size: {
  923. type: String as PropType<SizeType>,
  924. required: true,
  925. },
  926. },
  927. setup(props) {
  928. expectType<SizeType>(props.size)
  929. return {
  930. size: 1,
  931. }
  932. },
  933. })
  934. type CompInstance = InstanceType<typeof Comp>
  935. const CompA = {} as CompInstance
  936. expectType<
  937. VaporComponentInstance<{ size: SizeType }, {}, {}, { size: number }>
  938. >(CompA)
  939. expectType<number>(CompA.exposeProxy!.size)
  940. expectType<SizeType>(CompA.props.size)
  941. })
  942. describe('expose typing', () => {
  943. // types
  944. const Foo = defineVaporComponent(
  945. (
  946. props: { some?: string },
  947. { expose }: { expose: (exposed: { a: number; b: string }) => void },
  948. ) => {
  949. expose({ a: 1, b: '' })
  950. },
  951. )
  952. const foo = new Foo()
  953. // internal should still be exposed
  954. foo.props
  955. expectType<number>(foo.exposeProxy!.a)
  956. expectType<string>(foo.exposeProxy!.b)
  957. // runtime
  958. const Bar = defineVaporComponent({
  959. props: {
  960. some: String,
  961. },
  962. setup() {
  963. return { a: 1, b: '2', c: 1 }
  964. },
  965. })
  966. const bar = new Bar()
  967. // internal should still be exposed
  968. bar.props
  969. expectType<number>(bar.exposeProxy!.a)
  970. expectType<string>(bar.exposeProxy!.b)
  971. })
  972. // code generated by tsc / vue-tsc, make sure this continues to work
  973. // so we don't accidentally change the args order of DefineComponent
  974. declare const MyButton: DefineVaporComponent<
  975. {},
  976. string,
  977. EmitsOptions,
  978. string,
  979. {},
  980. {},
  981. Block,
  982. {},
  983. true,
  984. Readonly<ExtractPropTypes<{}>>,
  985. VaporPublicProps & AllowedComponentProps & ComponentCustomProps,
  986. {},
  987. {}
  988. >
  989. ;<MyButton class="x" />
  990. describe('__typeProps backdoor for union type for conditional props', () => {
  991. interface CommonProps {
  992. size?: 'xl' | 'l' | 'm' | 's' | 'xs'
  993. }
  994. type ConditionalProps =
  995. | {
  996. color?: 'normal' | 'primary' | 'secondary'
  997. appearance?: 'normal' | 'outline' | 'text'
  998. }
  999. | {
  1000. color: 'white'
  1001. appearance: 'outline'
  1002. }
  1003. type Props = CommonProps & ConditionalProps
  1004. const Comp = defineVaporComponent({
  1005. __typeProps: {} as Props,
  1006. })
  1007. // @ts-expect-error
  1008. ;<Comp color="white" />
  1009. // @ts-expect-error
  1010. ;<Comp color="white" appearance="normal" />
  1011. ;<Comp color="white" appearance="outline" />
  1012. const c = new Comp()
  1013. // @ts-expect-error
  1014. c.props = { color: 'white' }
  1015. // @ts-expect-error
  1016. c.props = { color: 'white', appearance: 'text' }
  1017. c.props = { color: 'white', appearance: 'outline' }
  1018. })
  1019. describe('__typeEmits backdoor, 3.3+ object syntax', () => {
  1020. type Emits = {
  1021. change: [id: number]
  1022. update: [value: string]
  1023. }
  1024. const Comp = defineVaporComponent({
  1025. __typeEmits: {} as Emits,
  1026. setup(props, { emit }) {
  1027. // @ts-expect-error
  1028. props.onChange?.('123')
  1029. // @ts-expect-error
  1030. props.onUpdate?.(123)
  1031. // @ts-expect-error
  1032. emit('foo')
  1033. emit('change', 123)
  1034. // @ts-expect-error
  1035. emit('change', '123')
  1036. emit('update', 'test')
  1037. // @ts-expect-error
  1038. emit('update', 123)
  1039. },
  1040. })
  1041. ;<Comp onChange={id => id.toFixed(2)} />
  1042. ;<Comp onUpdate={id => id.toUpperCase()} />
  1043. // @ts-expect-error
  1044. ;<Comp onChange={id => id.slice(1)} />
  1045. // @ts-expect-error
  1046. ;<Comp onUpdate={id => id.toFixed(2)} />
  1047. const c = new Comp()
  1048. // @ts-expect-error
  1049. c.emit('foo')
  1050. c.emit('change', 123)
  1051. // @ts-expect-error
  1052. c.emit('change', '123')
  1053. c.emit('update', 'test')
  1054. // @ts-expect-error
  1055. c.emit('update', 123)
  1056. })
  1057. describe('__typeEmits backdoor, call signature syntax', () => {
  1058. type Emits = {
  1059. (e: 'change', id: number): void
  1060. (e: 'update', value: string): void
  1061. }
  1062. const Comp = defineVaporComponent({
  1063. __typeEmits: {} as Emits,
  1064. setup(props, { emit }) {
  1065. // @ts-expect-error
  1066. props.onChange?.('123')
  1067. // @ts-expect-error
  1068. props.onUpdate?.(123)
  1069. // @ts-expect-error
  1070. emit('foo')
  1071. emit('change', 123)
  1072. // @ts-expect-error
  1073. emit('change', '123')
  1074. emit('update', 'test')
  1075. // @ts-expect-error
  1076. emit('update', 123)
  1077. },
  1078. })
  1079. ;<Comp onChange={id => id.toFixed(2)} />
  1080. ;<Comp onUpdate={id => id.toUpperCase()} />
  1081. // @ts-expect-error
  1082. ;<Comp onChange={id => id.slice(1)} />
  1083. // @ts-expect-error
  1084. ;<Comp onUpdate={id => id.toFixed(2)} />
  1085. const c = new Comp()
  1086. // @ts-expect-error
  1087. c.emit('foo')
  1088. c.emit('change', 123)
  1089. // @ts-expect-error
  1090. c.emit('change', '123')
  1091. c.emit('update', 'test')
  1092. // @ts-expect-error
  1093. c.emit('update', 123)
  1094. })
  1095. describe('__typeRefs backdoor, object syntax', () => {
  1096. type Refs = {
  1097. foo: number
  1098. }
  1099. const Parent = defineVaporComponent({
  1100. __typeRefs: {} as { child: InstanceType<typeof Child> },
  1101. })
  1102. const Child = defineVaporComponent({
  1103. __typeRefs: {} as Refs,
  1104. })
  1105. const c = new Parent()
  1106. const refs = c.refs
  1107. expectType<InstanceType<typeof Child>>(refs.child)
  1108. expectType<number>(refs.child.refs.foo)
  1109. })
  1110. describe('__typeEl backdoor', () => {
  1111. const Comp = defineVaporComponent({
  1112. __typeEl: {} as HTMLAnchorElement,
  1113. })
  1114. const c = new Comp()
  1115. expectType<HTMLAnchorElement>(c.block)
  1116. const Comp1 = defineVaporComponent({
  1117. render: () => document.createElement('a'),
  1118. })
  1119. const c1 = new Comp1()
  1120. expectType<HTMLAnchorElement>(c1.block)
  1121. const Comp2 = defineVaporComponent(() => document.createElement('a'))
  1122. const c2 = new Comp2()
  1123. expectType<HTMLAnchorElement>(c2.block)
  1124. const Comp3 = defineVaporComponent({
  1125. setup: () => document.createElement('a'),
  1126. })
  1127. const c3 = new Comp3()
  1128. expectType<HTMLAnchorElement>(c3.block)
  1129. })
  1130. defineVaporComponent({
  1131. props: {
  1132. foo: [String, null],
  1133. },
  1134. setup(props) {
  1135. expectType<IsAny<typeof props.foo>>(false)
  1136. expectType<string | null | undefined>(props.foo)
  1137. },
  1138. })
  1139. // #10843
  1140. createApp({}).component(
  1141. 'SomeComponent',
  1142. defineVaporComponent({
  1143. props: {
  1144. title: String,
  1145. },
  1146. setup(props) {
  1147. expectType<string | undefined>(props.title)
  1148. return {}
  1149. },
  1150. }),
  1151. )
  1152. const Comp = defineVaporComponent({
  1153. props: {
  1154. actionText: {
  1155. type: {} as PropType<string>,
  1156. default: 'Become a sponsor',
  1157. },
  1158. },
  1159. __typeProps: {} as {
  1160. actionText?: string
  1161. },
  1162. })
  1163. const instance = new Comp()
  1164. function expectString(s: string) {}
  1165. // public prop on props should be optional
  1166. // @ts-expect-error
  1167. expectString(instance.props.actionText)
  1168. // #12122
  1169. defineVaporComponent({
  1170. props: { foo: String },
  1171. render(ctx, props) {
  1172. expectType<{ readonly foo?: string }>(props)
  1173. // @ts-expect-error
  1174. expectType<string>(props)
  1175. return []
  1176. },
  1177. })