Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function createEntry(options) {
const config = {
input,
external: [
'vue',
'vue',
'lodash/mergeWith',
'lodash/camelCase',
'lodash/upperFirst',
Expand All @@ -37,7 +37,7 @@ function createEntry(options) {
}

if (format === 'es') {
config.output.file = isBrowser ? pkg.browser : pkg.module
config.output.file = isBrowser ? pkg.browser : pkg.module
}
if (format === 'cjs') {
config.output.file = pkg.main
Expand Down
5 changes: 5 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { GlobalMountOptions } from './types'

export const config: { global: GlobalMountOptions } = {
global: {}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I add each of the properties here, with an empty array or object?

}
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { mount } from './mount'
import { RouterLinkStub } from './components/RouterLinkStub'
import { config } from './config'

export { mount, RouterLinkStub }
export { mount, RouterLinkStub, config }
51 changes: 21 additions & 30 deletions src/mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import {
VNode,
defineComponent,
VNodeNormalizedChildren,
ComponentOptions,
transformVNodeArgs,
Plugin,
Directive,
Component,
reactive,
ComponentPublicInstance,
ComponentOptionsWithObjectProps,
Expand All @@ -17,6 +13,9 @@ import {
ExtractPropTypes
} from 'vue'

import { config } from './config'
import { GlobalMountOptions } from './types'
import { mergeGlobalProperties } from './utils'
import { createWrapper, VueWrapper } from './vue-wrapper'
import { attachEmitListener } from './emitMixin'
import { createDataMixin } from './dataMixin'
Expand All @@ -36,17 +35,7 @@ interface MountingOptions<Props> {
default?: Slot
[key: string]: Slot
}
global?: {
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>
directives?: Record<string, Directive>
}
stubs?: Record<string, any>
global?: GlobalMountOptions
}

// Component declared with defineComponent
Expand Down Expand Up @@ -139,11 +128,13 @@ export function mount(
// create the app
const app = createApp(Parent)

const global = mergeGlobalProperties(config.global, options?.global)

// global mocks mixin
if (options?.global?.mocks) {
if (global?.mocks) {
const mixin = {
beforeCreate() {
for (const [k, v] of Object.entries(options.global?.mocks)) {
for (const [k, v] of Object.entries(global.mocks)) {
this[k] = v
}
}
Expand All @@ -153,30 +144,30 @@ export function mount(
}

// use and plugins from mounting options
if (options?.global?.plugins) {
for (const use of options?.global?.plugins) app.use(use)
if (global?.plugins) {
for (const use of global.plugins) app.use(use)
}

// use any mixins from mounting options
if (options?.global?.mixins) {
for (const mixin of options?.global?.mixins) app.mixin(mixin)
if (global?.mixins) {
for (const mixin of global.mixins) app.mixin(mixin)
}

if (options?.global?.components) {
for (const key of Object.keys(options?.global?.components))
app.component(key, options.global.components[key])
if (global?.components) {
for (const key of Object.keys(global.components))
app.component(key, global.components[key])
}

if (options?.global?.directives) {
for (const key of Object.keys(options?.global?.directives))
app.directive(key, options.global.directives[key])
if (global?.directives) {
for (const key of Object.keys(global.directives))
app.directive(key, global.directives[key])
}

// provide any values passed via provides mounting option
if (options?.global?.provide) {
for (const key of Reflect.ownKeys(options.global.provide)) {
if (global?.provide) {
for (const key of Reflect.ownKeys(global.provide)) {
// @ts-ignore: https://github.com/microsoft/TypeScript/issues/1863
app.provide(key, options.global.provide[key])
app.provide(key, global.provide[key])
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Component, ComponentOptions, Directive, Plugin } from 'vue'

import { DOMWrapper } from './dom-wrapper'
import { ErrorWrapper } from './error-wrapper'

Expand All @@ -23,3 +25,13 @@ interface NameSelector {

export type FindComponentSelector = RefSelector | NameSelector | string
export type FindAllComponentsSelector = NameSelector | string

export type GlobalMountOptions = {
plugins?: Plugin[]
mixins?: ComponentOptions[]
mocks?: Record<string, any>
provide?: Record<any, any>
components?: Record<string, Component | object>
directives?: Record<string, Directive>
stubs?: Record<any, any>
}
21 changes: 21 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,28 @@ import camelCase from 'lodash/camelCase'
import upperFirst from 'lodash/upperFirst'
import kebabCase from 'lodash/kebabCase'
import flow from 'lodash/flow'
import mergeWith from 'lodash/mergeWith'
import { GlobalMountOptions } from './types'

const pascalCase = flow(camelCase, upperFirst)

export { kebabCase, pascalCase }

export function mergeGlobalProperties(
configGlobal = {},
mountGlobal = {}
): GlobalMountOptions {
return mergeWith({}, configGlobal, mountGlobal, (objValue, srcValue, key) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 🙌

switch (key) {
case 'mocks':
case 'provide':
case 'components':
case 'directives':
case 'globalProperties':
return { ...objValue, ...srcValue }
case 'plugins':
case 'mixins':
return [...(objValue || []), ...(srcValue || [])].filter(Boolean)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not overwrite a plugin or mixin if defined globally first, it will concat them. That should be fine though?

}
})
}
96 changes: 96 additions & 0 deletions tests/config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { config, mount } from '../src'
import Hello from './components/Hello.vue'

describe('config', () => {
beforeEach(() => {
config.global = {
components: undefined,
directives: undefined,
mixins: undefined,
plugins: undefined,
mocks: undefined,
provide: undefined
}
})

describe('components', () => {
const Component = {
template: '<div>{{ msg }} <hello/></div>',
props: ['msg']
}

it('allows setting components globally', () => {
config.global.components = { Hello }
const wrapper1 = mount(Component, { props: { msg: 'Wrapper1' } })
const wrapper2 = mount(Component, { props: { msg: 'Wrapper2' } })
expect(wrapper1.text()).toEqual('Wrapper1 Hello world')
expect(wrapper2.text()).toEqual('Wrapper2 Hello world')
})

it('allows overwriting globally set component config on a per mount instance', () => {
config.global.components = { Hello }
const HelloLocal = { template: '<div>Hello Overwritten</div>' }
const wrapper1 = mount(Component, { props: { msg: 'Wrapper1' } })
const wrapper2 = mount(Component, {
props: { msg: 'Wrapper2' },
global: { components: { Hello: HelloLocal } }
})
expect(wrapper1.text()).toEqual('Wrapper1 Hello world')
expect(wrapper2.text()).toEqual('Wrapper2 Hello Overwritten')
})
})

describe('directives', () => {
const Directive = {
beforeMount(el: Element) {
el.classList.add('DirectiveAdded')
}
}
const Component = { template: '<div v-directive>msg</div>' }

it('allows setting directives globally', () => {
config.global.directives = { Directive }
expect(mount(Component).classes()).toContain('DirectiveAdded')
expect(mount(Component).classes()).toContain('DirectiveAdded')
})

it('allows overwriting globally set directives', () => {
config.global.directives = { Directive }
const LocalDirective = {
beforeMount(el: Element) {
el.classList.add('LocallyDirectiveAdded')
}
}

expect(mount(Component).classes()).toContain('DirectiveAdded')
expect(
mount(Component, {
global: { directives: { Directive: LocalDirective } }
}).classes()
).toContain('LocallyDirectiveAdded')
})
})

describe('mocks', () => {
it('sets mock everywhere', () => {
config.global.mocks = {
foo: 'bar'
}
const Component = { template: '<div>{{ foo }}</div>' }
expect(mount(Component).text()).toEqual('bar')
expect(mount(Component).text()).toEqual('bar')
})

it('allows overwriting a global mock', () => {
config.global.mocks = {
foo: 'bar'
}
const Component = { template: '<div>{{ foo }}</div>' }

expect(mount(Component).text()).toEqual('bar')
expect(
mount(Component, { global: { mocks: { foo: 'baz' } } }).text()
).toEqual('baz')
})
})
})