Skip to content

Commit 4517528

Browse files
committed
improvement(test/setup): reimplement Browser Unit Tests
After converting to Jest, browser tests were removed due to capatability issues. This reimplements the browser tests with similar techniques. All related webpack/loaders have been updated to use the latest stable releases Chrome/HeadlessChrome is now being used to run these tests over the deprecated PhantomJS Karma and Jasmine are being used as a test runner while leveraging Jest's expect/assertion and mock/stubbing libraries Highly inspired by https://github.com/tom-sherman/blog/blob/master/posts/02-running-jest-tests-in-a-browser.md. 1629
1 parent cfd494f commit 4517528

24 files changed

+1746
-170
lines changed

Diff for: .circleci/config.yml

+26-2
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,31 @@ jobs:
4242
- run:
4343
name: yarn bootstrap
4444
command: yarn bootstrap
45+
build_test:
46+
<<: *defaults
47+
steps:
48+
- attach_workspace:
49+
at: ~/repo
50+
- *restore_node_modules
51+
- run:
52+
name: yarn build:test
53+
command: yarn build:test
4554
test:
4655
<<: *defaults
4756
steps:
4857
- attach_workspace:
4958
at: ~/repo
5059
- *restore_node_modules
5160
- run:
52-
name: yarn test:unit
53-
command: yarn test:unit -w 1
61+
name: yarn test
62+
command: yarn test
63+
test_compat:
64+
<<: *defaults
65+
steps:
66+
- attach_workspace:
67+
at: ~/repo
68+
- *restore_node_modules
69+
- run: yarn test:compat
5470
workflows:
5571
version: 2
5672
install-tests:
@@ -59,6 +75,14 @@ workflows:
5975
- bootstrap:
6076
requires:
6177
- install
78+
- build_test:
79+
requires:
80+
- install
6281
- test:
6382
requires:
6483
- install
84+
- test_compat:
85+
requires:
86+
- install
87+
- build_test
88+
- test

Diff for: babel.config.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module.exports = {
1+
const defaultOptions = {
22
presets: [
33
[
44
'@babel/preset-env',
@@ -19,3 +19,25 @@ module.exports = {
1919
],
2020
comments: false
2121
}
22+
23+
module.exports = api => {
24+
if (api.env('browser')) {
25+
// If running in the browser, use core-js to polyfill potentially missing functionality
26+
return {
27+
...defaultOptions,
28+
presets: [
29+
[
30+
'@babel/preset-env',
31+
{
32+
// currently, there are dependency resolution issues with older versions of vuepress. Once vuepress is upgraded, core-js can be moved to version 3
33+
corejs: 2,
34+
useBuiltIns: 'entry'
35+
}
36+
],
37+
'@vue/babel-preset-jsx'
38+
]
39+
}
40+
} else {
41+
return defaultOptions
42+
}
43+
}

Diff for: package.json

+26-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@
66
],
77
"scripts": {
88
"bootstrap": "lerna bootstrap",
9+
"build:test": "lerna run build:test",
910
"docs": "vuepress dev docs",
1011
"docs:build": "vuepress build docs",
12+
"flow": "flow check",
13+
"lint": "eslint --ext js,vue .",
14+
"lint:docs": "eslint --ext js,vue,md docs --ignore-path .gitignore",
15+
"format": "prettier --write \"**/*.{js,json,vue,md}\"",
16+
"format:check": "prettier --check \"**/*.{js,json,vue,md}\"",
17+
"test": "yarn format:check && yarn lint && yarn lint:docs && yarn flow && yarn test:types && yarn test:unit -w 1 && yarn test:unit:browser",
18+
"test:compat": "scripts/test-compat.sh",
1119
"test:unit": "cross-env TARGET=dev yarn jest",
20+
"test:unit:browser": "cross-env TEST_ENV=browser TARGET=browser NODE_ENV=browser karma start ./test/setup/karma.config.js",
1221
"test:types": "tsc -p packages/test-utils/types && tsc -p packages/server-test-utils/types"
1322
},
1423
"dependencies": {
@@ -53,21 +62,36 @@
5362
"@babel/preset-env": "^7.0.0",
5463
"@commitlint/cli": "^8.2.0",
5564
"@commitlint/config-conventional": "^8.2.0",
56-
"@vue/babel-preset-jsx": "^1.1.2",
5765
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
66+
"@vue/babel-preset-jsx": "^1.1.2",
5867
"@vue/composition-api": "^0.6.4",
5968
"babel-eslint": "^9.0.0",
6069
"babel-jest": "^26.0.1",
70+
"babel-loader": "^8.1.0",
6171
"commitizen": "^4.0.3",
72+
"core-js": "2",
73+
"css-loader": "^4.2.0",
6274
"cz-conventional-changelog": "^3.0.2",
75+
"expect": "^26.2.0",
6376
"husky": "^3.1.0",
6477
"identity-obj-proxy": "^3.0.0",
6578
"jest": "^26.0.1",
79+
"jest-mock": "^26.2.0",
80+
"karma": "^5.1.1",
81+
"karma-chrome-launcher": "^3.1.0",
82+
"karma-jasmine": "^3.3.1",
83+
"karma-spec-reporter": "^0.0.32",
84+
"karma-webpack": "^4.0.2",
6685
"lint-staged": "^9.5.0",
6786
"prettier": "^1.16.0",
87+
"puppeteer": "^5.2.1",
6888
"rollup-plugin-delete": "^1.2.0",
6989
"rollup-plugin-replace": "^2.2.0",
70-
"vue-jest": "^4.0.0-beta.3"
90+
"vue-jest": "^4.0.0-beta.3",
91+
"vue-loader": "^15.9.3",
92+
"vue-style-loader": "^4.1.2",
93+
"webpack": "^4.44.1",
94+
"webpack-node-externals": "^2.5.0"
7195
},
7296
"config": {
7397
"commitizen": {

Diff for: packages/test-utils/src/wrapper.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,9 @@ export default class Wrapper implements BaseWrapper {
716716
const isUpdated = Object.keys(data).some(key => {
717717
return (
718718
// $FlowIgnore : Problem with possibly null this.vm
719-
this.vm[key] === data[key] || this.vm.$attrs[key] === data[key]
719+
this.vm[key] === data[key] ||
720+
// $FlowIgnore : Problem with possibly null this.vm
721+
(this.vm.$attrs && this.vm.$attrs[key] === data[key])
720722
)
721723
})
722724
return !isUpdated ? this.setProps(data).then(resolve()) : resolve()

Diff for: scripts/test-compat.sh

+13-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@
22

33
set -e
44

5+
apt-get install bc
6+
57
run() {
6-
echo "running unit tests with Vue $1"
7-
yarn add --pure-lockfile --non-interactive -W -D "vue@$1" "vue-template-compiler@$1" "vue-server-renderer@$1"
8-
yarn test:unit:only
9-
yarn test:unit:karma:only
8+
# Only run tests for vue versions above 2.1.
9+
# There are quite a few errors present with running the tests in Vue 2.1 and Vue 2.0, including in Node and in browser
10+
browserTestCutoff="2.1"
11+
12+
if [ 1 -eq "$(echo "${browserTestCutoff} < ${1}" | bc)" ]
13+
then
14+
echo "running unit tests with Vue $1"
15+
yarn add --pure-lockfile --non-interactive -W -D "vue@$1" "vue-template-compiler@$1" "vue-server-renderer@$1"
16+
yarn test:unit -w 1
17+
yarn test:unit:browser
18+
fi
1019
}
1120

1221
yarn build:test

Diff for: test/resources/utils.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ export const isRunningJSDOM =
1313
navigator.userAgent.includes &&
1414
navigator.userAgent.includes('jsdom')
1515

16-
export const isRunningPhantomJS =
16+
export const isRunningChrome =
1717
typeof navigator !== 'undefined' &&
1818
navigator.userAgent.includes &&
19-
navigator.userAgent.match(/PhantomJS/i)
19+
navigator.userAgent.match(/Chrome/i)
2020

2121
export const injectSupported = vueVersion > 2.2
2222

Diff for: test/setup/karma.config.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const webpackConfig = require('./webpack.test.config.js')
2+
3+
if (process.env.CI) {
4+
// For CI runs, an installation of chrome/chromium is required
5+
// see https://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer for more details
6+
process.env.CHROME_BIN = require('puppeteer').executablePath()
7+
}
8+
9+
module.exports = config => {
10+
config.set({
11+
browsers: [process.env.CI ? 'ChromeHeadlessNoSandbox' : 'Chrome'],
12+
...(process.env.CI
13+
? {
14+
customLaunchers: {
15+
ChromeHeadlessNoSandbox: {
16+
base: 'ChromeHeadless',
17+
flags: ['--no-sandbox']
18+
}
19+
}
20+
}
21+
: {}),
22+
singleRun: !!process.env.CI,
23+
plugins: [
24+
'karma-webpack',
25+
'karma-jasmine',
26+
'karma-chrome-launcher',
27+
'karma-spec-reporter'
28+
],
29+
basePath: '../../',
30+
reporters: ['spec'],
31+
frameworks: ['jasmine'],
32+
files: ['./test/setup/karma.setup.js', './test/setup/load-tests.js'],
33+
preprocessors: {
34+
'./test/setup/karma.setup.js': ['webpack'],
35+
'./test/setup/load-tests.js': ['webpack']
36+
},
37+
webpack: webpackConfig,
38+
webpackMiddleware: {
39+
noInfo: true
40+
}
41+
})
42+
}

Diff for: test/setup/karma.setup.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Since running in the browser, polyfill missing functionality with core-js,
3+
* as well as include the regenerator runtime.
4+
* Please see https://babeljs.io/docs/en/babel-polyfill and https://github.com/zloirock/core-js for more details
5+
*/
6+
import 'core-js'
7+
import 'regenerator-runtime/runtime'
8+
9+
import jest from 'jest-mock'
10+
import expect from 'expect'
11+
12+
// Add Jest API to the global scope / browser window
13+
// Jasmine will be used as the test runner while leveraging Jest's expect/assertion and mock/stubbing libraries
14+
window.test = window.it
15+
window.test.each = inputs => (testName, test) =>
16+
inputs.forEach(args => window.it(testName, () => test(...args)))
17+
window.test.todo = window.test.skip = () => {
18+
return undefined
19+
}
20+
21+
window.jest = jest
22+
window.expect = expect

Diff for: test/setup/load-tests.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* require.context is used in order to build one bundle with karma-webpack.
3+
* If globstars are used, a bundle is created per glob match.
4+
* This creates obvious memory issues and is not desired.
5+
*
6+
* Please see https://github.com/webpack-contrib/karma-webpack#alternative-usage for more details
7+
*/
8+
const testsContext = require.context('../specs', true, /\.spec\.(js|vue)$/)
9+
10+
testsContext.keys().forEach(testsContext)

Diff for: test/setup/webpack.test.config.js

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/* eslint-disable max-len */
2+
3+
const nodeExternals = require('webpack-node-externals')
4+
const webpack = require('webpack')
5+
const VueLoaderPlugin = require('vue-loader/lib/plugin')
6+
7+
const browser = process.env.TARGET === 'browser'
8+
const path = require('path')
9+
10+
const projectRoot = path.resolve(__dirname, '../../')
11+
12+
const rules = [].concat(
13+
{
14+
test: /\.vue$/,
15+
use: 'vue-loader'
16+
},
17+
{
18+
test: /\.js$/,
19+
use: 'babel-loader',
20+
exclude: /node_modules/
21+
},
22+
{
23+
test: /\.css$/,
24+
use: [
25+
{
26+
loader: 'vue-style-loader'
27+
},
28+
{
29+
loader: 'css-loader'
30+
}
31+
]
32+
}
33+
)
34+
const externals = nodeExternals({
35+
// we need to allowlist both `create-instance` and files in `shared` package. Otherwise webpack won't bundle them in the test dev env
36+
allowlist: [
37+
'@vue/test-utils',
38+
'@vue/server-test-utils',
39+
'create-instance',
40+
/^shared\/.*/
41+
]
42+
})
43+
// define the default aliases
44+
let aliasedFiles = {}
45+
if (process.env.TARGET === 'browser') {
46+
// if we are in dev test mode, we want to alias all files to the src file, not dist
47+
aliasedFiles = {
48+
'@vue/server-test-utils': `@vue/server-test-utils/src/index.js`,
49+
'@vue/test-utils': `@vue/test-utils/src/index.js`
50+
}
51+
}
52+
53+
module.exports = {
54+
// since NODE_ENV is used heavily in the testing suite, using `production` mode in CI will cause side effects
55+
mode: 'development',
56+
module: {
57+
rules
58+
},
59+
externals: !browser ? [externals] : undefined,
60+
resolve: {
61+
alias: {
62+
...aliasedFiles,
63+
'~resources': `${projectRoot}/test/resources`,
64+
packages: path.resolve(projectRoot, 'packages')
65+
}
66+
},
67+
output: {
68+
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
69+
devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]'
70+
},
71+
devtool: '#inline-cheap-module-source-map',
72+
node: {
73+
fs: 'empty',
74+
module: 'empty'
75+
},
76+
plugins: [new webpack.EnvironmentPlugin(['TEST_ENV']), new VueLoaderPlugin()]
77+
}

Diff for: test/specs/create-dom-event.spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import createDOMEvent from '../../packages/test-utils/src/create-dom-event'
2-
import { isRunningPhantomJS } from '~resources/utils'
2+
import { isRunningChrome } from '~resources/utils'
33
import { itDoNotRunIf } from 'conditional-specs'
44

55
describe('createDOMEvent', () => {
6-
itDoNotRunIf(isRunningPhantomJS, 'returns cancelable event', () => {
6+
itDoNotRunIf(isRunningChrome, 'returns cancelable event', () => {
77
const event = createDOMEvent('click', {})
88
expect(event.bubbles).toEqual(true)
99
expect(event.cancelable).toEqual(true)

Diff for: test/specs/mount.spec.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
1414
const windowSave = window
1515

1616
afterEach(() => {
17-
window = windowSave // eslint-disable-line no-native-reassign
17+
if (process.env.TEST_ENV !== 'browser') {
18+
window = windowSave // eslint-disable-line no-native-reassign
19+
}
1820
})
1921

2022
it('returns new VueWrapper with mounted Vue instance if no options are passed', () => {

Diff for: test/specs/mounting-options/attrs.spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { attrsSupported } from '~resources/utils'
22
import {
33
describeWithShallowAndMount,
4-
isRunningPhantomJS,
4+
isRunningChrome,
55
vueVersion
66
} from '~resources/utils'
77
import { itSkipIf, itDoNotRunIf } from 'conditional-specs'
88

99
describeWithShallowAndMount('options.attrs', mountingMethod => {
1010
itDoNotRunIf(
11-
vueVersion < 2.4 || isRunningPhantomJS,
11+
vueVersion < 2.4 || isRunningChrome,
1212
'handles inherit attrs',
1313
() => {
1414
if (!attrsSupported) return

0 commit comments

Comments
 (0)