From 2d1148ebe53ec46062da2c0a7f8950e38b242565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=81=8F=E5=8F=B3?= Date: Tue, 12 Sep 2017 22:33:03 +0800 Subject: [PATCH] Add UI Test (#10) * Add jest and enzyme * Add test for connected component * fix lint * update travis * Add e2e test * fix ci * Add e2e test * update travis.yml * Fix global jasmine timeout * update test scripts * fix jest glob patterns * short timeout * fix travis * uitest => unit-test * Add ls in travis.yml * use electron on travis https://github.com/segmentio/nightmare/issues/313#issuecomment-152274351 * clear travis.yml * change setup file name * ignore coverage * unit-test => unit * remove helpers/visit * update test script * clean up test scripts * ignore test case --- .eslintrc | 4 +++- .gitignore | 2 ++ .travis.yml | 29 +++++++++++++++++++++++ package.json | 39 ++++++++++++++++++++++++++++--- src/components/Result/index.js | 6 +++-- src/e2e/home.e2e.js | 9 +++++++ src/e2e/login.e2e.js | 27 +++++++++++++++++++++ src/routes/Dashboard.test.js | 10 ++++++++ src/routes/Result/Success.test.js | 9 +++++++ src/utils/utils.js | 9 ++----- tests/jasmine.js | 1 + tests/run-tests.js | 32 +++++++++++++++++++++++++ tests/setupTests.js | 11 +++++++++ tests/styleMock.js | 1 + 14 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 src/e2e/home.e2e.js create mode 100644 src/e2e/login.e2e.js create mode 100644 src/routes/Dashboard.test.js create mode 100644 src/routes/Result/Success.test.js create mode 100644 tests/jasmine.js create mode 100644 tests/run-tests.js create mode 100644 tests/setupTests.js create mode 100644 tests/styleMock.js diff --git a/.eslintrc b/.eslintrc index d3a7a2e1..3bd5636c 100755 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,9 @@ "env": { "browser": true, "node": true, - "es6": true + "es6": true, + "mocha": true, + "jasmine": true }, "rules": { "generator-star-spacing": [0], diff --git a/.gitignore b/.gitignore index d9750f32..c03ea9a9 100755 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ # misc .DS_Store npm-debug.log* + +/coverage diff --git a/.travis.yml b/.travis.yml index bdb3d6a8..7115c316 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,32 @@ language: node_js node_js: - "8" + +env: + matrix: + - TEST_TYPE=lint + - TEST_TYPE=test-all + - TEST_TYPE=test-dist + +addons: + apt: + packages: + - xvfb + +install: + - export DISPLAY=':99.0' + - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & + - npm install + +script: + - | + if [ "$TEST_TYPE" = lint ]; then + npm run lint + elif [ "$TEST_TYPE" = test-all ]; then + npm run test:all + elif [ "$TEST_TYPE" = test-dist ]; then + npm run site + mv dist/* ./ + php -S localhost:8000 & + npm test .e2e.js + fi diff --git a/package.json b/package.json index ceef972c..4e43a268 100755 --- a/package.json +++ b/package.json @@ -4,10 +4,11 @@ "scripts": { "start": "roadhog server", "build": "roadhog build", - "lint": "eslint --ext .js src test mock", - "test": "npm run lint", + "lint": "eslint --ext .js src mock tests", "precommit": "npm run lint", - "site": "roadhog-api-doc static" + "site": "roadhog-api-doc static", + "test": "jest", + "test:all": "node ./tests/run-tests.js" }, "dependencies": { "antd": "next", @@ -25,11 +26,14 @@ }, "devDependencies": { "babel-eslint": "^7.1.1", + "babel-jest": "^21.0.0", "babel-plugin-dva-hmr": "^0.3.2", "babel-plugin-import": "^1.2.1", "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-runtime": "^6.9.0", "babel-runtime": "^6.9.2", + "cross-port-killer": "^1.0.1", + "enzyme": "^2.9.1", "eslint": "^3.0.0", "eslint-config-airbnb": "latest", "eslint-plugin-babel": "^4.0.0", @@ -39,9 +43,38 @@ "eslint-plugin-react": "^7.0.1", "gh-pages": "^1.0.0", "husky": "^0.13.4", + "jest": "^21.0.1", "mockjs": "^1.0.1-beta3", + "nightmare": "^2.10.0", + "react-test-renderer": "^15.6.1", "redbox-react": "^1.3.2", "roadhog": "^1.0.2", "roadhog-api-doc": "^0.1.5" + }, + "babel": { + "presets": [ + "es2015", + "stage-0", + "react" + ], + "plugins": [ + "transform-decorators-legacy" + ] + }, + "jest": { + "setupFiles": [ + "/tests/setupTests.js" + ], + "testMatch": [ + "**/?(*.)(spec|test|e2e).js?(x)" + ], + "setupTestFrameworkScriptFile": "/tests/jasmine.js", + "moduleFileExtensions": [ + "js", + "jsx" + ], + "moduleNameMapper": { + "\\.(css|less)$": "/tests/styleMock.js" + } } } diff --git a/src/components/Result/index.js b/src/components/Result/index.js index 4af5310f..389a4ef5 100644 --- a/src/components/Result/index.js +++ b/src/components/Result/index.js @@ -3,7 +3,9 @@ import classNames from 'classnames'; import { Icon } from 'antd'; import styles from './index.less'; -export default ({ className, type, title, description, extra, actions, ...restProps }) => { +export default function Result({ + className, type, title, description, extra, actions, ...restProps +}) { const iconMap = { error: , success: , @@ -18,4 +20,4 @@ export default ({ className, type, title, description, extra, actions, ...restPr {actions &&
{actions}
} ); -}; +} diff --git a/src/e2e/home.e2e.js b/src/e2e/home.e2e.js new file mode 100644 index 00000000..6de577b9 --- /dev/null +++ b/src/e2e/home.e2e.js @@ -0,0 +1,9 @@ +import Nightmare from 'nightmare'; + +describe('Homepage', () => { + it('it should have logo text', async () => { + const page = Nightmare().goto('http://localhost:8000'); + const text = await page.evaluate(() => document.body.innerHTML).end(); + expect(text).toContain('

Ant Design Pro

'); + }); +}); diff --git a/src/e2e/login.e2e.js b/src/e2e/login.e2e.js new file mode 100644 index 00000000..2e46578e --- /dev/null +++ b/src/e2e/login.e2e.js @@ -0,0 +1,27 @@ +import Nightmare from 'nightmare'; + +describe('Login', () => { + let page; + beforeEach(() => { + page = Nightmare(); + page.goto('http://localhost:8000/#/user/login'); + }); + + it('should login with failure', async () => { + await page.type('#userName', 'mockuser') + .type('#password', 'wrong_password') + .click('button[type="submit"]') + .wait('.ant-alert-error') // should display error + .end(); + }); + + xit('should login successfully', async () => { + const text = await page.type('#userName', 'admin') + .type('#password', '888888') + .click('button[type="submit"]') + .wait('.ant-layout-sider h1') // should display error + .evaluate(() => document.body.innerHTML) + .end(); + expect(text).toContain('

Ant Design Pro

'); + }); +}); diff --git a/src/routes/Dashboard.test.js b/src/routes/Dashboard.test.js new file mode 100644 index 00000000..5e28863f --- /dev/null +++ b/src/routes/Dashboard.test.js @@ -0,0 +1,10 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Dashboard from './Dashboard'; + +it('renders Dashboard', () => { + const wrapper = shallow( + + ); + expect(wrapper.find('Table').props().dataSource).toEqual([]); +}); diff --git a/src/routes/Result/Success.test.js b/src/routes/Result/Success.test.js new file mode 100644 index 00000000..9bc9b8df --- /dev/null +++ b/src/routes/Result/Success.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Success from './Success'; + +it('renders with Result', () => { + const wrapper = shallow(); + expect(wrapper.find('Result').length).toBe(1); + expect(wrapper.find('Result').prop('type')).toBe('success'); +}); diff --git a/src/utils/utils.js b/src/utils/utils.js index 47366503..18b3284d 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -1,10 +1,10 @@ import moment from 'moment'; -function fixedZero(val) { +export function fixedZero(val) { return val * 1 < 10 ? `0${val}` : val; } -function getTimeDistance(type) { +export function getTimeDistance(type) { const now = new Date(); const oneDay = 1000 * 60 * 60 * 24; @@ -48,8 +48,3 @@ function getTimeDistance(type) { return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)]; } } - -export default { - fixedZero, - getTimeDistance, -}; diff --git a/tests/jasmine.js b/tests/jasmine.js new file mode 100644 index 00000000..5ff26bff --- /dev/null +++ b/tests/jasmine.js @@ -0,0 +1 @@ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000; diff --git a/tests/run-tests.js b/tests/run-tests.js new file mode 100644 index 00000000..adf22a67 --- /dev/null +++ b/tests/run-tests.js @@ -0,0 +1,32 @@ +const { spawn } = require('child_process'); +const { kill } = require('cross-port-killer'); + +const env = Object.create(process.env); +env.BROWSER = 'none'; +const startServer = spawn('npm', ['start'], { + env, +}); + +startServer.stderr.on('data', (data) => { + // eslint-disable-next-line + console.log(data); +}); + +startServer.on('exit', () => { + kill(process.env.PORT || 8000); +}); + +// eslint-disable-next-line +console.log('Starting development server for e2e tests...'); +startServer.stdout.on('data', (data) => { + if (data.toString().indexOf('The app is running at') >= 0) { + // eslint-disable-next-line + console.log('Development server is started, ready to run tests.'); + const testCmd = spawn('npm', ['run', 'jest'], { + stdio: 'inherit', + }); + testCmd.on('exit', () => { + startServer.kill(); + }); + } +}); diff --git a/tests/setupTests.js b/tests/setupTests.js new file mode 100644 index 00000000..51ed96ee --- /dev/null +++ b/tests/setupTests.js @@ -0,0 +1,11 @@ +import { jsdom } from 'jsdom'; + +// fixed jsdom miss +const documentHTML = '
'; +global.document = jsdom(documentHTML); +global.window = document.defaultView; +global.navigator = global.window.navigator; + +global.requestAnimationFrame = global.requestAnimationFrame || function requestAnimationFrame(cb) { + return setTimeout(cb, 0); +}; diff --git a/tests/styleMock.js b/tests/styleMock.js new file mode 100644 index 00000000..f053ebf7 --- /dev/null +++ b/tests/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; -- GitLab