Преглед изворни кода

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
偏右 пре 8 година
родитељ
комит
2d1148ebe5

+ 3 - 1
.eslintrc

@@ -4,7 +4,9 @@
   "env": {
     "browser": true,
     "node": true,
-    "es6": true
+    "es6": true,
+    "mocha": true,
+    "jasmine": true
   },
   "rules": {
     "generator-star-spacing": [0],

+ 2 - 0
.gitignore

@@ -11,3 +11,5 @@
 # misc
 .DS_Store
 npm-debug.log*
+
+/coverage

+ 29 - 0
.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

+ 36 - 3
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": [
+      "<rootDir>/tests/setupTests.js"
+    ],
+    "testMatch": [
+      "**/?(*.)(spec|test|e2e).js?(x)"
+    ],
+    "setupTestFrameworkScriptFile": "<rootDir>/tests/jasmine.js",
+    "moduleFileExtensions": [
+      "js",
+      "jsx"
+    ],
+    "moduleNameMapper": {
+      "\\.(css|less)$": "<rootDir>/tests/styleMock.js"
+    }
   }
 }

+ 4 - 2
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: <Icon className={styles.error} type="close-circle" />,
     success: <Icon className={styles.success} type="check-circle" />,
@@ -18,4 +20,4 @@ export default ({ className, type, title, description, extra, actions, ...restPr
       {actions && <div className={styles.actions}>{actions}</div>}
     </div>
   );
-};
+}

+ 9 - 0
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('<h1>Ant Design Pro</h1>');
+  });
+});

+ 27 - 0
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('<h1>Ant Design Pro</h1>');
+  });
+});

+ 10 - 0
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(
+    <Dashboard.WrappedComponent user={{ list: [] }} />
+  );
+  expect(wrapper.find('Table').props().dataSource).toEqual([]);
+});

+ 9 - 0
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(<Success />);
+  expect(wrapper.find('Result').length).toBe(1);
+  expect(wrapper.find('Result').prop('type')).toBe('success');
+});

+ 2 - 7
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,
-};

+ 1 - 0
tests/jasmine.js

@@ -0,0 +1 @@
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;

+ 32 - 0
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();
+    });
+  }
+});

+ 11 - 0
tests/setupTests.js

@@ -0,0 +1,11 @@
+import { jsdom } from 'jsdom';
+
+// fixed jsdom miss
+const documentHTML = '<!doctype html><html><body><div id="root"></div></body></html>';
+global.document = jsdom(documentHTML);
+global.window = document.defaultView;
+global.navigator = global.window.navigator;
+
+global.requestAnimationFrame = global.requestAnimationFrame || function requestAnimationFrame(cb) {
+  return setTimeout(cb, 0);
+};

+ 1 - 0
tests/styleMock.js

@@ -0,0 +1 @@
+module.exports = {};