defineVaporComponent.test-d.tsx 31 KB

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