Skip to content
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

fix: functional components in shallow #408

Merged
merged 1 commit into from
Feb 3, 2018
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
235 changes: 127 additions & 108 deletions dist/vue-test-utils.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
"setup": "node build/install-hooks.js",
"test": "npm run lint && npm run lint:docs && npm run flow && npm run test:types && npm run test:unit && npm run test:unit:karma",
"test:compat": "test/test.sh",
"test:unit": "npm run build:test && cross-env BABEL_ENV=test && mocha-webpack --webpack-config build/webpack.test.config.js test/unit/specs --recursive --require test/unit/setup/mocha.setup.js",
"test:unit:karma": "npm run build:test && cross-env BABEL_ENV=test TARGET=browser karma start test/unit/setup/karma.conf.js --single-run",
"test:unit": "npm run build:test && cross-env BABEL_ENV=test && mocha-webpack --webpack-config build/webpack.test.config.js test/specs --recursive --require test/setup/mocha.setup.js",
"test:unit:karma": "npm run build:test && cross-env BABEL_ENV=test TARGET=browser karma start test/setup/karma.conf.js --single-run",
"test:types": "tsc -p types",
"release": "bash build/release.sh",
"release:note": "node build/gen-release-note.js"
Expand Down
9 changes: 8 additions & 1 deletion src/lib/find-vue-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ export function vmFunctionalCtorMatchesSelector (component: VNode, Ctor: Object)
throwError('find for functional components is not support in Vue < 2.3')
}

if (!Ctor) {
return false
}

if (!component[FUNCTIONAL_OPTIONS]) {
return false
}
Expand All @@ -86,7 +90,10 @@ export default function findVueComponents (
const nodes = root._vnode
? findAllFunctionalComponentsFromVnode(root._vnode)
: findAllFunctionalComponentsFromVnode(root)
return nodes.filter(node => vmFunctionalCtorMatchesSelector(node, selector._Ctor))
return nodes.filter(node =>
vmFunctionalCtorMatchesSelector(node, selector._Ctor) ||
node[FUNCTIONAL_OPTIONS].name === selector.name
)
}
const components = root._isVue
? findAllVueComponentsFromVm(root)
Expand Down
6 changes: 4 additions & 2 deletions src/lib/stub-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ function getCoreProperties (component: Component): Object {
staticStyle: component.staticStyle,
style: component.style,
normalizedStyle: component.normalizedStyle,
nativeOn: component.nativeOn
nativeOn: component.nativeOn,
functional: component.functional
}
}
function createStubFromString (templateString: string, originalComponent: Component): Object {
Expand All @@ -51,7 +52,7 @@ function createStubFromString (templateString: string, originalComponent: Compon
function createBlankStub (originalComponent: Component) {
return {
...getCoreProperties(originalComponent),
render: () => {}
render: h => h('')
}
}

Expand Down Expand Up @@ -126,6 +127,7 @@ function stubComponents (components: Object, stubbedComponents: Object) {
Object.keys(components).forEach(component => {
// Remove cached constructor
delete components[component]._Ctor
console.log(components[component].name)
if (!components[component].name) {
components[component].name = component
}
Expand Down
29 changes: 29 additions & 0 deletions test/resources/test-utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/* global describe, it*/

import Vue from 'vue'
import { shallow, mount } from '~vue-test-utils'

export const vueVersion = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`)

Expand All @@ -17,3 +20,29 @@ export function listenersSupported () {
export function functionalSFCsSupported () {
return vueVersion >= 2.5
}

export function describeWithShallowAndMount (spec, cb) {
;[mount, shallow].forEach(method => {
describe(`${spec} with ${method.name}`, () => cb(method))
})
}

describeWithShallowAndMount.skip = function (spec, cb) {
;[mount, shallow].forEach(method => {
describe.skip(`${spec} with ${method.name}`, () => cb(method))
})
}

describeWithShallowAndMount.only = function (spec, cb) {
;[mount, shallow].forEach(method => {
describe.only(`${spec} with ${method.name}`, () => cb(method))
})
}

export function itSkipIf (predicate, spec, cb) {
if (predicate) {
it.skip(spec, cb)
} else {
it(spec, cb)
}
}
4 changes: 2 additions & 2 deletions test/unit/setup/karma.conf.js → test/setup/karma.conf.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const webpackConfig = require('../../../build/webpack.test.config.js')
const webpackConfig = require('../../build/webpack.test.config.js')

module.exports = function (config) {
config.set({
browsers: ['ChromeHeadless'],
frameworks: ['mocha', 'sinon-chai'],
reporters: ['spec'],
files: [
'../../../node_modules/babel-polyfill/dist/polyfill.js',
'../../node_modules/babel-polyfill/dist/polyfill.js',
'../specs/**/*.+(vue|js)'
],
preprocessors: {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ describe('mount.slots', () => {
it('throws error if passed string in default slot object and vue-template-compiler is undefined', () => {
const compilerSave = require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions
require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = undefined
delete require.cache[require.resolve('../../../../../src/mount')]
const mountFresh = require('../../../../../src/mount').default
delete require.cache[require.resolve('../../../src/mount')]
const mountFresh = require('../../../src/mount').default
const message = '[vue-test-utils]: vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'
const fn = () => mountFresh(ComponentWithSlots, { slots: { default: '<span />' }})
try {
Expand All @@ -95,8 +95,8 @@ describe('mount.slots', () => {
it('throws error if passed string in default slot array vue-template-compiler is undefined', () => {
const compilerSave = require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions
require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = undefined
delete require.cache[require.resolve('../../../../../src/mount')]
const mountFresh = require('../../../../../src/mount').default
delete require.cache[require.resolve('../../../src/mount')]
const mountFresh = require('../../../src/mount').default
const message = '[vue-test-utils]: vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'
const fn = () => mountFresh(ComponentWithSlots, { slots: { default: ['<span />'] }})
try {
Expand Down Expand Up @@ -255,8 +255,8 @@ describe('mount.slots', () => {
}
const compilerSave = require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions
require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = undefined
delete require.cache[require.resolve('../../../../../src/mount')]
const mountFresh = require('../../../../../src/mount').default
delete require.cache[require.resolve('../../../src/mount')]
const mountFresh = require('../../../src/mount').default
const message = '[vue-test-utils]: vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'
const fn = () => mountFresh(TestComponent, { slots: { default: ['<span />'] }})
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ describe('mount.stub', () => {
it('throws error if passed string in object when vue-template-compiler is undefined', () => {
const compilerSave = require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions
require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = undefined
delete require.cache[require.resolve('../../../../../src/mount')]
const mountFresh = require('../../../../../src/mount').default
delete require.cache[require.resolve('../../../src/mount')]
const mountFresh = require('../../../src/mount').default
const message = '[vue-test-utils]: vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'
const fn = () => mountFresh(Component, {
stubs: {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { compileToFunctions } from 'vue-template-compiler'
import { mount } from '~vue-test-utils'
import { describeWithShallowAndMount } from '~resources/test-utils'

describe('at', () => {
describeWithShallowAndMount('at', (mountingMethod) => {
it('throws an error', () => {
const compiled = compileToFunctions('<div />')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
const message = '[vue-test-utils]: at() must be called on a WrapperArray'
const fn = () => wrapper.at()
expect(fn).to.throw().with.property('message', message)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { compileToFunctions } from 'vue-template-compiler'
import { mount } from '~vue-test-utils'
import { describeWithShallowAndMount } from '~resources/test-utils'

describe('attributes', () => {
describeWithShallowAndMount('attributes', (mountingMethod) => {
it('returns true if wrapper contains attribute matching value', () => {
const attribute = 'attribute'
const value = 'value'
const compiled = compileToFunctions(`<div ${attribute}=${value}></div>`)
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
expect(wrapper.attributes()).to.eql({ attribute: value })
})

it('returns empty object if wrapper does not contain any attributes', () => {
const compiled = compileToFunctions('<div />')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
expect(wrapper.attributes()).to.eql({})
})

it('returns empty object if wrapper element is null', () => {
const compiled = compileToFunctions('<div />')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
wrapper.element = null
expect(wrapper.attributes()).to.eql({})
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@

import { describeWithShallowAndMount } from '~resources/test-utils'
import { compileToFunctions } from 'vue-template-compiler'
import { mount } from '~vue-test-utils'
import ComponentWithCssModules from '~resources/components/component-with-css-modules.vue'

describe('classes', () => {
describeWithShallowAndMount('classes', (mountingMethod) => {
it('returns array of class names if wrapper has class names', () => {
const compiled = compileToFunctions('<div class="a-class b-class" />')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
expect(wrapper.classes()).to.contain('a-class')
expect(wrapper.classes()).to.contain('b-class')
})

it('returns empty array if wrapper has no classes', () => {
const compiled = compileToFunctions('<div />')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
expect(wrapper.classes().length).to.equal(0)
})

it('returns original class names when element mapped in css modules', () => {
const wrapper = mount(ComponentWithCssModules)
const wrapper = mountingMethod(ComponentWithCssModules)
expect(wrapper.classes()).to.eql(['extension', 'color-red'])
})
})
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { compileToFunctions } from 'vue-template-compiler'
import { mount } from '~vue-test-utils'
import ComponentWithChild from '~resources/components/component-with-child.vue'
import Component from '~resources/components/component.vue'
import FunctionalComponent from '~resources/components/functional-component.vue'
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
import { functionalSFCsSupported } from '~resources/test-utils'
import {
functionalSFCsSupported,
describeWithShallowAndMount,
itSkipIf
} from '~resources/test-utils'
import ComponentWithoutName from '~resources/components/component-without-name.vue'

describe('contains', () => {
describeWithShallowAndMount('contains', (mountingMethod) => {
it('returns true if wrapper contains element', () => {
const compiled = compileToFunctions('<div><input /></div>')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
expect(wrapper.contains('input')).to.equal(true)
})

it('returns true if wrapper contains Vue component', () => {
const wrapper = mount(ComponentWithChild)
const wrapper = mountingMethod(ComponentWithChild)
expect(wrapper.contains(Component)).to.equal(true)
})

Expand All @@ -33,11 +36,13 @@ describe('contains', () => {
FunctionalComponent
}
}
const wrapper = mount(TestComponent)

const wrapper = mountingMethod(TestComponent)
expect(wrapper.contains(FunctionalComponent)).to.equal(true)
})

it('returns true if wrapper contains Vue class component', () => {
itSkipIf(mountingMethod.name === 'shallow',
'returns true if wrapper contains Vue class component', () => {
const TestComponent = {
template: `
<div>
Expand All @@ -48,19 +53,19 @@ describe('contains', () => {
ComponentAsAClass
}
}
const wrapper = mount(TestComponent)
const wrapper = mountingMethod(TestComponent)
expect(wrapper.contains(ComponentAsAClass)).to.equal(true)
})

it('returns true if wrapper contains element specified by ref selector', () => {
const compiled = compileToFunctions('<div><input ref="foo" /></div>')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
expect(wrapper.contains({ ref: 'foo' })).to.equal(true)
})

it('throws an error when ref selector is called on a wrapper that is not a Vue component', () => {
const compiled = compileToFunctions('<div><a href="/"></a></div>')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
const a = wrapper.find('a')
const message = '[vue-test-utils]: $ref selectors can only be used on Vue component wrappers'
const fn = () => a.contains({ ref: 'foo' })
Expand All @@ -69,13 +74,13 @@ describe('contains', () => {

it('returns true when wrapper contains root element', () => {
const compiled = compileToFunctions('<div><input /></div>')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
expect(wrapper.contains('doesntexist')).to.equal(false)
})

it('returns true if wrapper root element matches contains', () => {
const compiled = compileToFunctions('<div><input /></div>')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
expect(wrapper.contains('doesntexist')).to.equal(false)
})

Expand All @@ -90,24 +95,24 @@ describe('contains', () => {
ComponentWithoutName
}
}
const wrapper = mount(TestComponent)
const wrapper = mountingMethod(TestComponent)
expect(wrapper.contains(ComponentWithoutName)).to.equal(true)
})

it('returns true if wrapper root Component matches selector', () => {
const wrapper = mount(Component)
const wrapper = mountingMethod(Component)
expect(wrapper.contains(Component)).to.equal(true)
})

it('returns false if wrapper does not contain element', () => {
const compiled = compileToFunctions('<div></div>')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
expect(wrapper.contains('div')).to.equal(true)
})

it('returns false if wrapper does not contain element specified by ref selector', () => {
const compiled = compileToFunctions('<div><input ref="bar" /></div>')
const wrapper = mount(compiled)
const wrapper = mountingMethod(compiled)
expect(wrapper.contains({ ref: 'foo' })).to.equal(false)
})

Expand All @@ -121,13 +126,13 @@ describe('contains', () => {
})
}
}
const wrapper = mount(TestComponent)
const wrapper = mountingMethod(TestComponent)
expect(wrapper.contains('svg')).to.equal(true)
expect(wrapper.find('svg').contains('svg')).to.equal(true)
})

it('throws an error if selector is not a valid selector', () => {
const wrapper = mount(Component)
const wrapper = mountingMethod(Component)
const invalidSelectors = [
undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, { ref: 'foo', nope: true }, []
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { compileToFunctions } from 'vue-template-compiler'
import { mount } from '~vue-test-utils'
import { describeWithShallowAndMount } from '~resources/test-utils'
import sinon from 'sinon'

describe('destroy', () => {
describeWithShallowAndMount('destroy', (mountingMethod) => {
it('should trigger beforeDestroy ', () => {
const spy = sinon.stub()
mount({
mountingMethod({
render: null,
beforeDestroy () {
spy()
Expand All @@ -16,7 +16,7 @@ describe('destroy', () => {

it('should trigger destroy ', () => {
const spy = sinon.stub()
mount({
mountingMethod({
render: null,
destroyed () {
spy()
Expand All @@ -27,7 +27,7 @@ describe('destroy', () => {

it('should remove element from document.body', () => {
const compiled = compileToFunctions('<div></div>')
const wrapper = mount(compiled, { attachToDocument: true })
const wrapper = mountingMethod(compiled, { attachToDocument: true })
expect(wrapper.vm.$el.parentNode).to.equal(document.body)
wrapper.destroy()
expect(wrapper.vm.$el.parentNode).to.be.null
Expand Down
Loading