Skip to content

Commit

Permalink
improvement(test/setup): reimplement Browser Unit Tests
Browse files Browse the repository at this point in the history
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
  • Loading branch information
AtofStryker committed Aug 3, 2020
1 parent cfd494f commit df19c1b
Show file tree
Hide file tree
Showing 23 changed files with 1,743 additions and 169 deletions.
28 changes: 26 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,31 @@ jobs:
- run:
name: yarn bootstrap
command: yarn bootstrap
build_test:
<<: *defaults
steps:
- attach_workspace:
at: ~/repo
- *restore_node_modules
- run:
name: yarn build:test
command: yarn build:test
test:
<<: *defaults
steps:
- attach_workspace:
at: ~/repo
- *restore_node_modules
- run:
name: yarn test:unit
command: yarn test:unit -w 1
name: yarn test
command: yarn test
test_compat:
<<: *defaults
steps:
- attach_workspace:
at: ~/repo
- *restore_node_modules
- run: yarn test:compat
workflows:
version: 2
install-tests:
Expand All @@ -59,6 +75,14 @@ workflows:
- bootstrap:
requires:
- install
- build_test:
requires:
- install
- test:
requires:
- install
- test_compat:
requires:
- install
- build_test
- test
24 changes: 23 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
const defaultOptions = {
presets: [
[
'@babel/preset-env',
Expand All @@ -19,3 +19,25 @@ module.exports = {
],
comments: false
}

module.exports = api => {
if (api.env('browser')) {
// If running in the browser, use core-js to polyfill potentially missing functionality
return {
...defaultOptions,
presets: [
[
'@babel/preset-env',
{
// currently, there are dependency resolution issues with older versions of vuepress. Once vuepress is upgraded, core-js can be moved to version 3
corejs: 2,
useBuiltIns: 'entry'
}
],
'@vue/babel-preset-jsx'
]
}
} else {
return defaultOptions
}
}
28 changes: 26 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,18 @@
],
"scripts": {
"bootstrap": "lerna bootstrap",
"build:test": "lerna run build:test",
"docs": "vuepress dev docs",
"docs:build": "vuepress build docs",
"flow": "flow check",
"lint": "eslint --ext js,vue .",
"lint:docs": "eslint --ext js,vue,md docs --ignore-path .gitignore",
"format": "prettier --write \"**/*.{js,json,vue,md}\"",
"format:check": "prettier --check \"**/*.{js,json,vue,md}\"",
"test": "yarn format:check && yarn lint && yarn lint:docs && yarn flow && yarn test:types && yarn test:unit -w 1 && yarn test:unit:browser",
"test:unit": "cross-env TARGET=dev yarn jest",
"test:unit:browser": "cross-env TEST_ENV=browser TARGET=browser NODE_ENV=browser karma start ./test/setup/karma.config.js",
"test:compat": "scripts/test-compat.sh",
"test:types": "tsc -p packages/test-utils/types && tsc -p packages/server-test-utils/types"
},
"dependencies": {
Expand Down Expand Up @@ -53,21 +62,36 @@
"@babel/preset-env": "^7.0.0",
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"@vue/babel-preset-jsx": "^1.1.2",
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
"@vue/babel-preset-jsx": "^1.1.2",
"@vue/composition-api": "^0.6.4",
"babel-eslint": "^9.0.0",
"babel-jest": "^26.0.1",
"babel-loader": "^8.1.0",
"commitizen": "^4.0.3",
"core-js": "2",
"css-loader": "^4.2.0",
"cz-conventional-changelog": "^3.0.2",
"expect": "^26.2.0",
"husky": "^3.1.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.0.1",
"jest-mock": "^26.2.0",
"karma": "^5.1.1",
"karma-chrome-launcher": "^3.1.0",
"karma-jasmine": "^3.3.1",
"karma-spec-reporter": "^0.0.32",
"karma-webpack": "^4.0.2",
"lint-staged": "^9.5.0",
"prettier": "^1.16.0",
"puppeteer": "^5.2.1",
"rollup-plugin-delete": "^1.2.0",
"rollup-plugin-replace": "^2.2.0",
"vue-jest": "^4.0.0-beta.3"
"vue-jest": "^4.0.0-beta.3",
"vue-loader": "^15.9.3",
"vue-style-loader": "^4.1.2",
"webpack": "^4.44.1",
"webpack-node-externals": "^2.5.0"
},
"config": {
"commitizen": {
Expand Down
17 changes: 13 additions & 4 deletions scripts/test-compat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@

set -e

apt-get install bc

run() {
echo "running unit tests with Vue $1"
yarn add --pure-lockfile --non-interactive -W -D "vue@$1" "vue-template-compiler@$1" "vue-server-renderer@$1"
yarn test:unit:only
yarn test:unit:karma:only
# Only run tests for vue versions above 2.1.
# 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
browserTestCutoff="2.1"

if [ 1 -eq "$(echo "${browserTestCutoff} < ${1}" | bc)" ]
then
echo "running unit tests with Vue $1"
yarn add --pure-lockfile --non-interactive -W -D "vue@$1" "vue-template-compiler@$1" "vue-server-renderer@$1"
yarn test:unit -w 1
yarn test:unit:browser
fi
}

yarn build:test
Expand Down
4 changes: 2 additions & 2 deletions test/resources/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export const isRunningJSDOM =
navigator.userAgent.includes &&
navigator.userAgent.includes('jsdom')

export const isRunningPhantomJS =
export const isRunningChrome =
typeof navigator !== 'undefined' &&
navigator.userAgent.includes &&
navigator.userAgent.match(/PhantomJS/i)
navigator.userAgent.match(/Chrome/i)

export const injectSupported = vueVersion > 2.2

Expand Down
42 changes: 42 additions & 0 deletions test/setup/karma.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const webpackConfig = require('./webpack.test.config.js')

if (process.env.CI) {
// For CI runs, an installation of chrome/chromium is required
// see https://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer for more details
process.env.CHROME_BIN = require('puppeteer').executablePath()
}

module.exports = config => {
config.set({
browsers: [process.env.CI ? 'ChromeHeadlessNoSandbox' : 'Chrome'],
...(process.env.CI
? {
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
}
}
: {}),
singleRun: !!process.env.CI,
plugins: [
'karma-webpack',
'karma-jasmine',
'karma-chrome-launcher',
'karma-spec-reporter'
],
basePath: '../../',
reporters: ['spec'],
frameworks: ['jasmine'],
files: ['./test/setup/karma.setup.js', './test/setup/load-tests.js'],
preprocessors: {
'./test/setup/karma.setup.js': ['webpack'],
'./test/setup/load-tests.js': ['webpack']
},
webpack: webpackConfig,
webpackMiddleware: {
noInfo: true
}
})
}
22 changes: 22 additions & 0 deletions test/setup/karma.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Since running in the browser, polyfill missing functionality with core-js,
* as well as include teh regenerator runtime.
* Please see https://babeljs.io/docs/en/babel-polyfill and https://github.com/zloirock/core-js for more details
*/
import 'core-js'
import 'regenerator-runtime/runtime'

import jest from 'jest-mock'
import expect from 'expect'

// Add Jest API to the global scope / browser window
// Jasmine will be used as the test runner while leveraging Jest's expect/assertion and mock/stubbing libraries
window.test = window.it
window.test.each = inputs => (testName, test) =>
inputs.forEach(args => window.it(testName, () => test(...args)))
window.test.todo = window.test.skip = () => {
return undefined
}

window.jest = jest
window.expect = expect
10 changes: 10 additions & 0 deletions test/setup/load-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* require.context is used in order to build one bundle with karma-webpack.
* If globstars are used, a bundle is created per glob match.
* This creates obvious memory issues and is not desired.
*
* Please see https://github.com/webpack-contrib/karma-webpack#alternative-usage for more details
*/
const testsContext = require.context('../specs', true, /\.spec\.(js|vue)$/)

testsContext.keys().forEach(testsContext)
77 changes: 77 additions & 0 deletions test/setup/webpack.test.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-disable max-len */

const nodeExternals = require('webpack-node-externals')
const webpack = require('webpack')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

const browser = process.env.TARGET === 'browser'
const path = require('path')

const projectRoot = path.resolve(__dirname, '../../')

const rules = [].concat(
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: 'vue-style-loader'
},
{
loader: 'css-loader'
}
]
}
)
const externals = nodeExternals({
// we need to allowlist both `create-instance` and files in `shared` package. Otherwise webpack won't bundle them in the test dev env
allowlist: [
'@vue/test-utils',
'@vue/server-test-utils',
'create-instance',
/^shared\/.*/
]
})
// define the default aliases
let aliasedFiles = {}
if (process.env.TARGET === 'browser') {
// if we are in dev test mode, we want to alias all files to the src file, not dist
aliasedFiles = {
'@vue/server-test-utils': `@vue/server-test-utils/src/index.js`,
'@vue/test-utils': `@vue/test-utils/src/index.js`
}
}

module.exports = {
// since NODE_ENV is used heavily in the testing suite, using `production` mode in CI will cause side effects
mode: 'development',
module: {
rules
},
externals: !browser ? [externals] : undefined,
resolve: {
alias: {
...aliasedFiles,
'~resources': `${projectRoot}/test/resources`,
packages: path.resolve(projectRoot, 'packages')
}
},
output: {
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]'
},
devtool: '#inline-cheap-module-source-map',
node: {
fs: 'empty',
module: 'empty'
},
plugins: [new webpack.EnvironmentPlugin(['TEST_ENV']), new VueLoaderPlugin()]
}
4 changes: 2 additions & 2 deletions test/specs/create-dom-event.spec.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import createDOMEvent from '../../packages/test-utils/src/create-dom-event'
import { isRunningPhantomJS } from '~resources/utils'
import { isRunningChrome } from '~resources/utils'
import { itDoNotRunIf } from 'conditional-specs'

describe('createDOMEvent', () => {
itDoNotRunIf(isRunningPhantomJS, 'returns cancelable event', () => {
itDoNotRunIf(isRunningChrome, 'returns cancelable event', () => {
const event = createDOMEvent('click', {})
expect(event.bubbles).toEqual(true)
expect(event.cancelable).toEqual(true)
Expand Down
4 changes: 3 additions & 1 deletion test/specs/mount.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
const windowSave = window

afterEach(() => {
window = windowSave // eslint-disable-line no-native-reassign
if (process.env.TEST_ENV !== 'browser') {
window = windowSave // eslint-disable-line no-native-reassign
}
})

it('returns new VueWrapper with mounted Vue instance if no options are passed', () => {
Expand Down
4 changes: 2 additions & 2 deletions test/specs/mounting-options/attrs.spec.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { attrsSupported } from '~resources/utils'
import {
describeWithShallowAndMount,
isRunningPhantomJS,
isRunningChrome,
vueVersion
} from '~resources/utils'
import { itSkipIf, itDoNotRunIf } from 'conditional-specs'

describeWithShallowAndMount('options.attrs', mountingMethod => {
itDoNotRunIf(
vueVersion < 2.4 || isRunningPhantomJS,
vueVersion < 2.4 || isRunningChrome,
'handles inherit attrs',
() => {
if (!attrsSupported) return
Expand Down
8 changes: 5 additions & 3 deletions test/specs/mounting-options/listeners.spec.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { listenersSupported } from '~resources/utils'
import {
describeWithShallowAndMount,
isRunningPhantomJS,
isRunningChrome,
vueVersion
} from '~resources/utils'
import { itDoNotRunIf } from 'conditional-specs'

describeWithShallowAndMount('options.listeners', mountingMethod => {
it.skip('placeholder for potentially empty test describe', () => {})

itDoNotRunIf(
isRunningPhantomJS || !listenersSupported,
isRunningChrome || !listenersSupported,
'handles inherit listeners',
() => {
const aListener = () => {}
Expand All @@ -28,7 +30,7 @@ describeWithShallowAndMount('options.listeners', mountingMethod => {
)

itDoNotRunIf(
isRunningPhantomJS || !listenersSupported,
isRunningChrome || !listenersSupported,
'passes listeners to functional components',
() => {
const TestComponent = {
Expand Down
Loading

0 comments on commit df19c1b

Please sign in to comment.