} from '@vue/runtime-test'
import { createBlock, normalizeVNode } from '../src/vnode'
import { createSlots } from '../src/helpers/createSlots'
-import { renderSlot } from '../src/helpers/renderSlot'
-import { setCurrentRenderingInstance } from '../src/componentRenderContext'
describe('component: slots', () => {
function renderWithSlots(slots: any): any {
createApp(App).mount(root)
expect(serializeInner(root)).toBe('foo')
})
-
- // in-DOM templates use kebab-case slot names
- describe('in-DOM template kebab-case slot name resolution', () => {
- beforeEach(() => {
- __BROWSER__ = true
- })
-
- afterEach(() => {
- __BROWSER__ = false
- })
-
- test('should resolve camelCase slot access to kebab-case via slots', () => {
- const Comp = {
- setup(_: any, { slots }: any) {
- // Access with camelCase, but slot is passed with kebab-case
- return () => slots.dropdownRender()
- },
- }
-
- const App = {
- setup() {
- // Parent passes slot with kebab-case name (simulating in-DOM template)
- return () =>
- h(Comp, null, { 'dropdown-render': () => 'dropdown content' })
- },
- }
-
- const root = nodeOps.createElement('div')
- createApp(App).mount(root)
- expect(serializeInner(root)).toBe('dropdown content')
- })
-
- test('should resolve camelCase slot access to kebab-case via slots (PROD)', () => {
- __DEV__ = false
- try {
- const Comp = {
- setup(_: any, { slots }: any) {
- // Access with camelCase, but slot is passed with kebab-case
- return () => slots.dropdownRender()
- },
- }
-
- const App = {
- setup() {
- // Parent passes slot with kebab-case name (simulating in-DOM template)
- return () =>
- h(Comp, null, { 'dropdown-render': () => 'dropdown content' })
- },
- }
-
- const root = nodeOps.createElement('div')
- createApp(App).mount(root)
- expect(serializeInner(root)).toBe('dropdown content')
- } finally {
- __DEV__ = true
- }
- })
-
- test('should prefer exact match over kebab-case conversion via slots', () => {
- const Comp = {
- setup(_: any, { slots }: any) {
- return () => slots.dropdownRender()
- },
- }
-
- const App = {
- setup() {
- // Both exact match and kebab-case exist
- return () =>
- h(Comp, null, {
- 'dropdown-render': () => 'kebab',
- dropdownRender: () => 'exact',
- })
- },
- }
-
- const root = nodeOps.createElement('div')
- createApp(App).mount(root)
- // exact match should take priority
- expect(serializeInner(root)).toBe('exact')
- })
-
- // renderSlot tests
- describe('renderSlot', () => {
- beforeEach(() => {
- setCurrentRenderingInstance({ type: {} } as any)
- })
-
- afterEach(() => {
- setCurrentRenderingInstance(null)
- })
-
- test('should resolve camelCase slot name to kebab-case via renderSlot', () => {
- let child: any
- const vnode = renderSlot(
- { 'dropdown-render': () => [(child = h('child'))] },
- 'dropdownRender',
- )
- expect(vnode.children).toEqual([child])
- })
-
- test('should prefer exact match over kebab-case conversion via renderSlot', () => {
- let exactChild: any
- const vnode = renderSlot(
- {
- 'dropdown-render': () => [h('kebab')],
- dropdownRender: () => [(exactChild = h('exact'))],
- },
- 'dropdownRender',
- )
- expect(vnode.children).toEqual([exactChild])
- })
- })
- })
})
ShapeFlags,
extend,
getGlobalThis,
- hyphenate,
isArray,
isFunction,
isObject,
},
}
-const createSlotsProxyHandlers = (
- instance: ComponentInternalInstance,
-): ProxyHandler<InternalSlots> => ({
- get(target, key: string | symbol) {
- if (__DEV__) {
+/**
+ * Dev-only
+ */
+function getSlotsProxy(instance: ComponentInternalInstance): Slots {
+ return new Proxy(instance.slots, {
+ get(target, key: string) {
track(instance, TrackOpTypes.GET, '$slots')
- }
- // in-DOM templates use kebab-case slot names, only relevant in browser
- return (
- target[key as string] ||
- (__BROWSER__ && typeof key === 'string' && target[hyphenate(key)])
- )
- },
-})
+ return target[key]
+ },
+ })
+}
export function createSetupContext(
instance: ComponentInternalInstance,
)
},
get slots() {
- return (
- slotsProxy ||
- (slotsProxy = new Proxy(
- instance.slots,
- createSlotsProxyHandlers(instance),
- ))
- )
+ return slotsProxy || (slotsProxy = getSlotsProxy(instance))
},
get emit() {
return (event: string, ...args: any[]) => instance.emit(event, ...args)
} else {
return {
attrs: new Proxy(instance.attrs, attrsProxyHandlers),
- slots: __BROWSER__
- ? new Proxy(instance.slots, createSlotsProxyHandlers(instance))
- : instance.slots,
+ slots: instance.slots,
emit: instance.emit,
expose,
}