Skip to content

Commit

Permalink
fix(stubs): Do not create stubs across multiple renders (#813)
Browse files Browse the repository at this point in the history
  • Loading branch information
xanf authored Aug 5, 2021
1 parent f5e910b commit 97926a2
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 16 deletions.
50 changes: 35 additions & 15 deletions src/stubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,35 @@ const getComponentName = (type: VNodeTypes): string => {
return ''
}

function createStubOnceForType(
type: {} & VNodeTypes,
factoryFn: () => ConcreteComponent,
cache: WeakMap<{} & VNodeTypes, ConcreteComponent>
): ConcreteComponent {
const cachedStub = cache.get(type)
if (cachedStub) {
return cachedStub
}

const stub = factoryFn()
stubsMap.set(stub, type)
cache.set(type, stub)
return stub
}

export function stubComponents(
stubs: Stubs = {},
shallow: boolean = false,
renderStubDefaultSlot: boolean = false
) {
const components: Record<string, ComponentOptions> = {}
const createdStubsMap: WeakMap<{} & VNodeTypes, ConcreteComponent> =
new WeakMap()

const createStubOnce = (
type: {} & VNodeTypes,
factoryFn: () => ConcreteComponent
) => createStubOnceForType(type, factoryFn, createdStubsMap)

transformVNodeArgs((args, instance: ComponentInternalInstance | null) => {
const [nodeType, props, children, patchFlag, dynamicProps] = args
const type = nodeType as VNodeTypes
Expand Down Expand Up @@ -184,13 +207,15 @@ export function stubComponents(
// case 2: custom implementation
if (isComponent(stub)) {
const stubFn = isFunctionalComponent(stub) ? stub : null

const specializedStub: ConcreteComponent = stubFn
const specializedStubComponent: ConcreteComponent = stubFn
? (...args) => stubFn(...args)
: { ...stub }
specializedStubComponent.props = stub.props

specializedStub.props = stub.props
stubsMap.set(specializedStub, type)
const specializedStub = createStubOnce(
type,
() => specializedStubComponent
)
// pass the props and children, for advanced stubbing
return [specializedStub, props, children, patchFlag, dynamicProps]
}
Expand All @@ -205,25 +230,20 @@ export function stubComponents(
// case 1: default stub
if (stub === true || shallow) {
// Set name when using shallow without stub
if (!name) {
name = registeredName || componentName
}
const stubName = name || registeredName || componentName

if (!isComponent(type)) {
throw new Error('Attempted to stub a non-component')
}

const propsDeclaration = type.props || {}
let newStub = components[name]
if (!newStub) {
newStub = createStub({
name,
const newStub = createStubOnce(type, () =>
createStub({
name: stubName,
propsDeclaration,
renderStubDefaultSlot
})
components[name] = newStub
stubsMap.set(newStub, type)
}
)
return [newStub, props, children, patchFlag, dynamicProps]
}
}
Expand Down
30 changes: 29 additions & 1 deletion tests/mountingOptions/global.stubs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,6 @@ describe('mounting options: stubs', () => {
}
}
})

expect(wrapper.html()).toBe('<span>StubComponent</span>')
})
})
Expand Down Expand Up @@ -694,4 +693,33 @@ describe('mounting options: stubs', () => {

expect(wrapper.html()).toBe('<anonymous-stub>test</anonymous-stub>')
})

it('should not recreate stub across multiple renders', async () => {
const FooBar = {
name: 'FooBar',
render: () => h('span', 'real foobar')
}

const Comp = defineComponent({
data: () => ({ value: 1 }),
render() {
return h('div', {}, [this.value, h(FooBar)])
}
})

const wrapper = mount(Comp, {
global: {
stubs: {
'foo-bar': { name: 'FooBar', template: 'stub' }
}
}
})

const stub = wrapper.findComponent({ name: 'FooBar' })
await wrapper.setData({ value: 2 })

const stubAfterSecondRender = wrapper.findComponent({ name: 'FooBar' })

expect(stub.vm).toBe(stubAfterSecondRender.vm)
})
})

0 comments on commit 97926a2

Please sign in to comment.