diff --git a/src/stubs.ts b/src/stubs.ts index 6eb05c2b6..d4a68e2e6 100644 --- a/src/stubs.ts +++ b/src/stubs.ts @@ -12,6 +12,7 @@ import { hyphenate } from './utils/vueShared' import { MOUNT_COMPONENT_REF, MOUNT_PARENT_NAME } from './constants' import { config } from './config' import { matchName } from './utils/matchName' +import { ComponentInternalInstance } from '@vue/runtime-core' interface StubOptions { name?: string @@ -74,18 +75,7 @@ export function stubComponents( stubs: Record = {}, shallow: boolean = false ) { - transformVNodeArgs((args) => { - const locallyRegisteredComponents = (args[0] as any).components as - | Record - | undefined - if (locallyRegisteredComponents) { - for (const registrationName in locallyRegisteredComponents) { - const component = locallyRegisteredComponents[registrationName] - if (!component['name'] && !component['displayName']) { - component['name'] = registrationName - } - } - } + transformVNodeArgs((args, instance: ComponentInternalInstance | null) => { const [nodeType, props, children, patchFlag, dynamicProps] = args const type = nodeType as VNodeTypes // args[0] can either be: @@ -102,7 +92,19 @@ export function stubComponents( } if (isComponent(type) || isFunctionalComponent(type)) { - const name = type['name'] || type['displayName'] + let name = type['name'] || type['displayName'] + + // if no name, then check the locally registered components in the parent + if (!name && instance && instance.parent) { + // try to infer the name based on local resolution + const registry = (instance.type as any).components + for (const key in registry) { + if (registry[key] === type) { + name = key + break + } + } + } if (!name && !shallow) { return args } diff --git a/src/utils/find.ts b/src/utils/find.ts index d25055bd0..de8dcba58 100644 --- a/src/utils/find.ts +++ b/src/utils/find.ts @@ -21,12 +21,40 @@ function matches(node: VNode, selector: FindAllComponentsSelector): boolean { return node.el?.matches?.(selector) } - if (typeof selector === 'object' && typeof node.type === 'object') { - if (selector === node.type) return true + const nodeType = node.type + if (typeof selector === 'object' && typeof nodeType === 'object') { + // we are looking for this exact component + if (selector === nodeType) { + return true + } - if (selector.name && ('name' in node.type || 'displayName' in node.type)) { + let componentName + if ('name' in nodeType || 'displayName' in nodeType) { // match normal component definitions or functional components - return matchName(selector.name, node.type.name || node.type.displayName) + componentName = nodeType.name || nodeType.displayName + } + let selectorName = selector.name + + // the component and selector both have a name + if (componentName && selectorName) { + return matchName(selectorName, componentName) + } + + // if a name is missing, then check the locally registered components in the parent + if (node.component.parent) { + const registry = (node.component.parent as any).components + for (const key in registry) { + // is it the selector + if (!selectorName && registry[key] === selector) { + selectorName = key + } + // is it the component + if (!componentName && registry[key] === nodeType) { + componentName = key + } + } + // we may have one or both missing names + return matchName(selectorName, componentName) } } diff --git a/src/utils/matchName.ts b/src/utils/matchName.ts index f1b54ac39..824a73f34 100644 --- a/src/utils/matchName.ts +++ b/src/utils/matchName.ts @@ -1,6 +1,6 @@ import { camelize, capitalize } from './vueShared' -export function matchName(target, sourceName) { +export function matchName(target: string, sourceName: string) { const camelized = camelize(target) const capitalized = capitalize(camelized)