Skip to content

Commit 36c3d4f

Browse files
authored
fix: Allow find component without name in script setup by name (#1171)
1 parent a46b1c4 commit 36c3d4f

File tree

5 files changed

+118
-47
lines changed

5 files changed

+118
-47
lines changed

src/stubs.ts

+6-42
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ import {
1313
} from 'vue'
1414
import { hyphenate } from './utils/vueShared'
1515
import { matchName } from './utils/matchName'
16-
import { isComponent, isFunctionalComponent, isObjectComponent } from './utils'
16+
import { isComponent, isFunctionalComponent } from './utils'
1717
import { ComponentInternalInstance } from '@vue/runtime-core'
18-
import {
19-
isLegacyExtendedComponent,
20-
unwrapLegacyVueExtendComponent
21-
} from './utils/vueCompatSupport'
18+
import { unwrapLegacyVueExtendComponent } from './utils/vueCompatSupport'
2219
import { Stub, Stubs } from './types'
20+
import {
21+
getComponentName,
22+
getComponentRegisteredName
23+
} from './utils/componentName'
2324

2425
interface StubOptions {
2526
name: string
@@ -139,43 +140,6 @@ const resolveComponentStubByName = (componentName: string, stubs: Stubs) => {
139140
}
140141
}
141142

142-
const getComponentRegisteredName = (
143-
instance: ComponentInternalInstance | null,
144-
type: VNodeTypes
145-
): string | null => {
146-
if (!instance || !instance.parent) return null
147-
148-
// try to infer the name based on local resolution
149-
const registry = (instance.type as any).components
150-
for (const key in registry) {
151-
if (registry[key] === type) {
152-
return key
153-
}
154-
}
155-
156-
return null
157-
}
158-
159-
const getComponentName = (instance: any | null, type: VNodeTypes): string => {
160-
if (isObjectComponent(type)) {
161-
const defaultName = Object.keys(instance?.setupState || {}).find(
162-
(key) => instance.setupState[key] === type
163-
)
164-
165-
return defaultName || type.name || ''
166-
}
167-
168-
if (isLegacyExtendedComponent(type)) {
169-
return unwrapLegacyVueExtendComponent(type).name || ''
170-
}
171-
172-
if (isFunctionalComponent(type)) {
173-
return type.displayName || type.name
174-
}
175-
176-
return ''
177-
}
178-
179143
function createStubOnceForType(
180144
type: ConcreteComponent,
181145
factoryFn: () => ConcreteComponent,

src/utils/componentName.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ComponentInternalInstance } from '@vue/runtime-core'
2+
import { VNodeTypes } from 'vue'
3+
import { isFunctionalComponent, isObjectComponent } from '../utils'
4+
import {
5+
isLegacyExtendedComponent,
6+
unwrapLegacyVueExtendComponent
7+
} from './vueCompatSupport'
8+
9+
const getComponentNameInSetup = (
10+
instance: any | null,
11+
type: VNodeTypes
12+
): string | undefined =>
13+
Object.keys(instance?.setupState || {}).find(
14+
(key) => instance.setupState[key] === type
15+
)
16+
17+
export const getComponentRegisteredName = (
18+
instance: ComponentInternalInstance | null,
19+
type: VNodeTypes
20+
): string | null => {
21+
if (!instance || !instance.parent) return null
22+
23+
// try to infer the name based on local resolution
24+
const registry = (instance.type as any).components
25+
for (const key in registry) {
26+
if (registry[key] === type) {
27+
return key
28+
}
29+
}
30+
31+
// try to retrieve name imported in script setup
32+
return getComponentNameInSetup(instance.parent, type) || null
33+
}
34+
35+
export const getComponentName = (
36+
instance: any | null,
37+
type: VNodeTypes
38+
): string => {
39+
if (isObjectComponent(type)) {
40+
return getComponentNameInSetup(instance, type) || type.name || ''
41+
}
42+
43+
if (isLegacyExtendedComponent(type)) {
44+
return unwrapLegacyVueExtendComponent(type).name || ''
45+
}
46+
47+
if (isFunctionalComponent(type)) {
48+
return type.displayName || type.name
49+
}
50+
51+
return ''
52+
}

src/utils/find.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { isComponent } from '../utils'
1414
import { matchName } from './matchName'
1515
import { unwrapLegacyVueExtendComponent } from './vueCompatSupport'
16+
import { getComponentName, getComponentRegisteredName } from './componentName'
1617

1718
/**
1819
* Detect whether a selector matches a VNode
@@ -53,7 +54,7 @@ export function matches(
5354
}
5455

5556
let componentName: string | undefined
56-
componentName = nodeType.displayName || nodeType.name
57+
componentName = getComponentName(node.component, nodeType)
5758

5859
let selectorName = selector.name
5960

@@ -62,6 +63,9 @@ export function matches(
6263
return matchName(selectorName, componentName)
6364
}
6465

66+
componentName =
67+
getComponentRegisteredName(node.component, nodeType) || undefined
68+
6569
// if a name is missing, then check the locally registered components in the parent
6670
if (node.component.parent) {
6771
const registry = (node.component.parent as any).type.components
@@ -75,10 +79,10 @@ export function matches(
7579
componentName = key
7680
}
7781
}
78-
// we may have one or both missing names
79-
if (selectorName && componentName) {
80-
return matchName(selectorName, componentName)
81-
}
82+
}
83+
84+
if (selectorName && componentName) {
85+
return matchName(selectorName, componentName)
8286
}
8387

8488
return false

tests/findComponent.spec.ts

+23
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { defineComponent, h, nextTick } from 'vue'
22
import { mount } from '../src'
33
import Hello from './components/Hello.vue'
44
import ComponentWithoutName from './components/ComponentWithoutName.vue'
5+
import ScriptSetupWithChildren from './components/ScriptSetupWithChildren.vue'
56

67
const compC = defineComponent({
78
name: 'ComponentC',
@@ -83,6 +84,28 @@ describe('findComponent', () => {
8384
expect(wrapper.findComponent({ name: 'component-c' }).exists()).toBeTruthy()
8485
})
8586

87+
it('finds component within script setup by name', () => {
88+
const wrapper = mount(ScriptSetupWithChildren)
89+
expect(wrapper.findComponent({ name: 'Hello' }).text()).toBe('Hello world')
90+
expect(
91+
wrapper.findComponent({ name: 'ComponentWithInput' }).exists()
92+
).toBeTruthy()
93+
expect(
94+
wrapper.findComponent({ name: 'component-with-input' }).exists()
95+
).toBeTruthy()
96+
})
97+
98+
it('finds component within script setup without name', () => {
99+
const wrapper = mount(ScriptSetupWithChildren)
100+
expect(wrapper.findComponent({ name: 'ScriptSetup' }).exists()).toBeTruthy()
101+
expect(
102+
wrapper.findComponent({ name: 'ComponentWithoutName' }).exists()
103+
).toBeTruthy()
104+
expect(
105+
wrapper.findComponent({ name: 'component-without-name' }).exists()
106+
).toBeTruthy()
107+
})
108+
86109
it('finds root component', async () => {
87110
const Comp = defineComponent({
88111
name: 'C',

tests/mountingOptions/global.stubs.spec.ts

+28
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { config, flushPromises, mount, RouterLinkStub } from '../../src'
44
import Hello from '../components/Hello.vue'
55
import ComponentWithoutName from '../components/ComponentWithoutName.vue'
66
import ComponentWithSlots from '../components/ComponentWithSlots.vue'
7+
import ScriptSetupWithChildren from '../components/ScriptSetupWithChildren.vue'
78

89
describe('mounting options: stubs', () => {
910
let configStubsSave = config.global.stubs
@@ -364,6 +365,33 @@ describe('mounting options: stubs', () => {
364365
expect(wrapper.html()).toBe('<foo-bar-stub></foo-bar-stub>')
365366
})
366367

368+
it('stubs components within script setup', () => {
369+
const wrapper = mount(ScriptSetupWithChildren as any, {
370+
global: {
371+
stubs: {
372+
Hello: { template: '<span>Stubbed Hello</span>' },
373+
ComponentWithInput: {
374+
template: '<span>Stubbed ComponentWithInput</span>'
375+
},
376+
ComponentWithoutName: {
377+
template: '<span>Stubbed ComponentWithoutName</span>'
378+
},
379+
ComponentAsync: { template: '<span>Stubbed ComponentAsync</span>' },
380+
ScriptSetup: { template: '<span>Stubbed ScriptSetup</span>' },
381+
WithProps: { template: '<span>Stubbed WithProps</span>' }
382+
}
383+
}
384+
})
385+
expect(wrapper.html()).toBe(
386+
'<span>Stubbed Hello</span>\n' +
387+
'<span>Stubbed ComponentWithInput</span>\n' +
388+
'<span>Stubbed ComponentWithoutName</span>\n' +
389+
'<span>Stubbed ComponentAsync</span>\n' +
390+
'<span>Stubbed ScriptSetup</span>\n' +
391+
'<span>Stubbed WithProps</span>'
392+
)
393+
})
394+
367395
it('stubs transition by default', () => {
368396
const Comp = {
369397
template: `<transition><div id="content" /></transition>`

0 commit comments

Comments
 (0)