fix(NavigationMenu): improve generic types (#2482)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Yasser Lahbibi
2024-10-29 19:21:35 +01:00
committed by GitHub
parent 03dd1eba7e
commit fc2015bb0e
7 changed files with 71 additions and 24 deletions

View File

@@ -1,7 +1,8 @@
import { describe, it, expect } from 'vitest'
import { describe, it, expect, test } from 'vitest'
import NavigationMenu, { type NavigationMenuProps, type NavigationMenuSlots } from '../../src/runtime/components/NavigationMenu.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/navigation-menu'
import { expectSlotProps } from '../utils/types'
describe('NavigationMenu', () => {
const variants = Object.keys(theme.variants.variant) as any
@@ -96,8 +97,30 @@ describe('NavigationMenu', () => {
['with item-label slot', { props, slots: { 'item-label': () => 'Item label slot' } }],
['with item-trailing slot', { props, slots: { 'item-trailing': () => 'Item trailing slot' } }],
['with custom slot', { props, slots: { custom: () => 'Custom slot' } }]
])('renders %s correctly', async (nameOrHtml: string, options: { props?: NavigationMenuProps<typeof items[number][number]>, slots?: Partial<NavigationMenuSlots<any>> }) => {
])('renders %s correctly', async (nameOrHtml: string, options: { props?: NavigationMenuProps<typeof items>, slots?: Partial<NavigationMenuSlots<any>> }) => {
const html = await ComponentRender(nameOrHtml, options, NavigationMenu)
expect(html).toMatchSnapshot()
})
test('should have the correct types', () => {
// normal
expectSlotProps('item', () => NavigationMenu({
items: [{ label: 'foo', value: 'bar' }]
})).toEqualTypeOf<{ item: { label: string, value: string }, index: number, active?: boolean }>()
// groups
expectSlotProps('item', () => NavigationMenu({
items: [[{ label: 'foo', value: 'bar' }]]
})).toEqualTypeOf<{ item: { label: string, value: string }, index: number, active?: boolean }>()
// custom
expectSlotProps('item', () => NavigationMenu({
items: [{ label: 'foo', value: 'bar', custom: 'nice' }]
})).toEqualTypeOf<{ item: { label: string, value: string, custom: string }, index: number, active?: boolean }>()
// custom + groups
expectSlotProps('item', () => NavigationMenu({
items: [[{ label: 'foo', value: 'bar', custom: 'nice' }]]
})).toEqualTypeOf<{ item: { label: string, value: string, custom: string }, index: number, active?: boolean }>()
})
})

View File

@@ -8,6 +8,19 @@ export function expectEmitPayloadType<T extends VNode, E extends keyof Events<T>
return expectTypeOf<NonNullable<Events<T>[E]>>()
}
type Events<T> = T extends { __ctx?: { props: infer Props } } ? {
/**
* Expect the type of a slot props.
*/
export function expectSlotProps<T extends VNode, S extends keyof Slots<T>>(_name: S, _cb: () => T) {
return expectTypeOf<Slots<T>[S]>()
}
type Ctx<V extends VNode> = V extends { __ctx?: infer C } ? NonNullable<C> : never
type Slots<V extends VNode> = Ctx<V> extends { slots: infer S } ? {
[K in keyof S as S[K] extends never ? never : K]: S[K] extends (props: infer P) => any ? P : never
} : never
type Events<V extends VNode> = Ctx<V> extends { props: infer Props } ? {
[K in keyof Props as K extends `on${infer E}${infer Rest}` ? `${Lowercase<E>}${Rest}` : never]: NonNullable<Props[K]> extends (...args: infer P) => any ? P : never
} : never