-
Notifications
You must be signed in to change notification settings - Fork 262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: stubs mounting option #56
Changes from 7 commits
0db36c3
361019d
d4897f1
61b5e2b
11f47af
d082064
d515f3f
1a5024e
d14dc59
2355f55
1c65bf7
485b1ac
126d5dd
de4e38d
da5d3cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -5,6 +5,7 @@ import { | |||||
defineComponent, | ||||||
VNodeNormalizedChildren, | ||||||
ComponentOptions, | ||||||
transformVNodeArgs, | ||||||
Plugin, | ||||||
Directive, | ||||||
Component, | ||||||
|
@@ -15,6 +16,7 @@ import { createWrapper } from './vue-wrapper' | |||||
import { createEmitMixin } from './emitMixin' | ||||||
import { createDataMixin } from './dataMixin' | ||||||
import { MOUNT_ELEMENT_ID } from './constants' | ||||||
import { createStub } from './stub' | ||||||
|
||||||
type Slot = VNode | string | { render: Function } | ||||||
|
||||||
|
@@ -29,6 +31,7 @@ interface MountingOptions { | |||||
plugins?: Plugin[] | ||||||
mixins?: ComponentOptions[] | ||||||
mocks?: Record<string, any> | ||||||
stubs?: Record<any, any> | ||||||
provide?: Record<any, any> | ||||||
// TODO how to type `defineComponent`? Using `any` for now. | ||||||
components?: Record<string, Component | object> | ||||||
|
@@ -72,6 +75,7 @@ export function mount(originalComponent: any, options?: MountingOptions) { | |||||
|
||||||
// create the wrapper component | ||||||
const Parent = defineComponent({ | ||||||
name: 'VTU_COMPONENT', | ||||||
render() { | ||||||
return h(component, props, slots) | ||||||
} | ||||||
|
@@ -133,6 +137,46 @@ export function mount(originalComponent: any, options?: MountingOptions) { | |||||
const { emitMixin, events } = createEmitMixin() | ||||||
vm.mixin(emitMixin) | ||||||
|
||||||
// stubs | ||||||
if (options?.global?.stubs) { | ||||||
transformVNodeArgs((args) => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can destructure like this, so its easier to understand context later down?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the suggestion, but I am not really a fan of this refactor. This variable can be a string (eg 'div'), or an object of options, so componentOptions is a bit misleading. I guess the ideal name is |
||||||
// regular HTML Element. Do not stubs these | ||||||
if (Array.isArray(args) && typeof args[0] === 'string') { | ||||||
afontcu marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
return args | ||||||
} | ||||||
|
||||||
// don't care about comments/fragments | ||||||
if (typeof args[0] === 'symbol') { | ||||||
return args | ||||||
} | ||||||
|
||||||
// do not stub the VTU Parent component | ||||||
if (typeof args[0] === 'object' && args[0]['name'] === 'VTU_COMPONENT') { | ||||||
return args | ||||||
} | ||||||
|
||||||
if ( | ||||||
typeof args[0] === 'object' && | ||||||
args[0]['name'] in options?.global?.stubs | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably we should do that magic with the Name transformation here? PascalCase, snake-case, that sort of stuff. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this is probably a good idea, make it easier to find the stubs, I will implement this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I matched Vue 3, exact name, kebab case, pacal case (aka capital case) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also did that for the findComponent by name Branch. Need to sync these up later, so we don't duplicate :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, sounds good! |
||||||
) { | ||||||
const name = args[0]['name'] | ||||||
// default stub | ||||||
if (options?.global?.stubs[name] === true) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are already in the conditional check, its there.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. heh, copy pasted... thanks, I'll clean this up |
||||||
return [createStub({ name: args[0]['name'] })] | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dobromir-hristov we one of us gets to working on
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, we will check it out. |
||||||
|
||||||
// custom stub implementation | ||||||
if (typeof options?.global?.stubs[name] === 'object') { | ||||||
return [options?.global?.stubs[name]] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. very good |
||||||
} | ||||||
} | ||||||
|
||||||
return args | ||||||
}) | ||||||
} else { | ||||||
transformVNodeArgs() | ||||||
} | ||||||
|
||||||
// mount the app! | ||||||
const app = vm.mount(el) | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { h } from 'vue' | ||
|
||
interface IStubOptions { | ||
name?: string | ||
} | ||
|
||
export const createStub = (options: IStubOptions) => { | ||
const tag = options.name ? `${options.name}-stub` : 'anonymous-stub' | ||
const render = () => h(tag) | ||
|
||
return { name: tag, render } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import { h, ComponentOptions } from 'vue' | ||
|
||
import { mount } from '../../src' | ||
import Hello from '../components/Hello.vue' | ||
|
||
describe('mounting options: stubs', () => { | ||
it('stubs in a fragment', () => { | ||
const Foo = { | ||
name: 'Foo', | ||
render() { | ||
return h('p') | ||
} | ||
} | ||
const Component: ComponentOptions = { | ||
render() { | ||
return h(() => [h('div'), h(Foo)]) | ||
} | ||
} | ||
|
||
const wrapper = mount(Component, { | ||
global: { | ||
stubs: { | ||
Foo: true | ||
} | ||
} | ||
}) | ||
|
||
expect(wrapper.html()).toBe('<div></div><foo-stub></foo-stub>') | ||
}) | ||
|
||
it('prevents lifecycle hooks triggering in a stub', () => { | ||
const onBeforeMount = jest.fn() | ||
const beforeCreate = jest.fn() | ||
const Foo = { | ||
name: 'Foo', | ||
setup() { | ||
onBeforeMount(onBeforeMount) | ||
return () => h('div') | ||
}, | ||
beforeCreate | ||
} | ||
const Comp = { | ||
render() { | ||
return h(Foo) | ||
} | ||
} | ||
|
||
const wrapper = mount(Comp, { | ||
global: { | ||
stubs: { | ||
Foo: true | ||
} | ||
} | ||
}) | ||
|
||
expect(wrapper.html()).toBe('<foo-stub></foo-stub>') | ||
expect(onBeforeMount).not.toHaveBeenCalled() | ||
expect(beforeCreate).not.toHaveBeenCalled() | ||
}) | ||
|
||
it('uses a custom stub implementation', () => { | ||
const onBeforeMount = jest.fn() | ||
const FooStub = { | ||
name: 'FooStub', | ||
setup() { | ||
onBeforeMount(onBeforeMount) | ||
return () => h('div', 'foo stub') | ||
} | ||
} | ||
const Foo = { | ||
name: 'Foo', | ||
render() { | ||
return h('div', 'real foo') | ||
} | ||
} | ||
|
||
const Comp = { | ||
render() { | ||
return h(Foo) | ||
} | ||
} | ||
|
||
const wrapper = mount(Comp, { | ||
global: { | ||
stubs: { | ||
Foo: FooStub | ||
} | ||
} | ||
}) | ||
|
||
expect(onBeforeMount).toHaveBeenCalled() | ||
expect(wrapper.html()).toBe('<div>foo stub</div>') | ||
}) | ||
|
||
it('uses an sfc as a custom stub', () => { | ||
const created = jest.fn() | ||
const HelloComp = { | ||
name: 'Hello', | ||
created() { | ||
created() | ||
}, | ||
render() { | ||
return h('span', 'real implementation') | ||
} | ||
} | ||
|
||
const Comp = { | ||
render() { | ||
return h(HelloComp) | ||
} | ||
} | ||
|
||
const wrapper = mount(Comp, { | ||
global: { | ||
stubs: { | ||
Hello: Hello | ||
} | ||
} | ||
}) | ||
|
||
expect(created).not.toHaveBeenCalled() | ||
expect(wrapper.html()).toBe( | ||
'<div id="root"><div id="msg">Hello world</div></div>' | ||
) | ||
}) | ||
|
||
it('stubs using inline components', () => { | ||
const Foo = { | ||
name: 'Foo', | ||
render() { | ||
return h('p') | ||
} | ||
} | ||
const Bar = { | ||
name: 'Bar', | ||
render() { | ||
return h('p') | ||
} | ||
} | ||
const Component: ComponentOptions = { | ||
render() { | ||
return h(() => [h(Foo), h(Bar)]) | ||
} | ||
} | ||
|
||
const wrapper = mount(Component, { | ||
global: { | ||
stubs: { | ||
Foo: { | ||
template: '<span />' | ||
}, | ||
Bar: { | ||
render() { | ||
return h('div') | ||
} | ||
} | ||
} | ||
} | ||
}) | ||
|
||
expect(wrapper.html()).toBe('<span></span><div></div>') | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typing Vue components is very difficult, will need to investigate the Vue codebase to figure out how best to do this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed this too. Am interested to hear about what you find.