convert test runner from mocha to jest (#1637)

This commit is contained in:
Saturnino Abril 2018-04-30 22:55:01 +08:00 committed by Harrison Healey
parent 0d9ef6eecf
commit 8a152ac1e7
19 changed files with 3083 additions and 742 deletions

View file

@ -11,24 +11,27 @@
},
"parser": "babel-eslint",
"plugins": [
"react",
"mocha"
"react"
],
"env": {
"browser": true,
"node": true,
"jquery": true,
"es6": true
"es6": true,
"jest": true
},
"globals": {
"jest": true,
"describe": true,
"it": true,
"expect": true,
"before": true,
"beforeEach": true,
"after": true,
"afterEach": true
"afterAll": true,
"afterEach": true,
"before": true,
"beforeAll": true,
"beforeEach": true,
"describe": true,
"expect": true,
"it": true,
"jest": true,
"test": true
},
"rules": {
"array-bracket-spacing": [2, "never"],
@ -256,7 +259,6 @@
"vars-on-top": 0,
"wrap-iife": [2, "outside"],
"wrap-regex": 2,
"yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}],
"mocha/no-exclusive-tests": 2
"yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}]
}
}

View file

@ -0,0 +1,49 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {ViewTypes} from 'app/constants';
import {
handleLoginIdChanged,
handlePasswordChanged,
} from 'app/actions/views/login';
jest.mock('react-native-fetch-blob', () => ({
DocumentDir: () => jest.fn(),
fs: {dirs: {CacheDir: () => jest.fn()}},
}));
const mockStore = configureStore([thunk]);
describe('Actions.Views.Login', () => {
let store;
beforeEach(() => {
store = mockStore({});
});
test('handleLoginIdChanged', () => {
const loginId = 'email@example.com';
const action = {
type: ViewTypes.LOGIN_ID_CHANGED,
loginId,
};
store.dispatch(handleLoginIdChanged(loginId));
expect(store.getActions()).toEqual([action]);
});
test('handlePasswordChanged', () => {
const password = 'password';
const action = {
type: ViewTypes.PASSWORD_CHANGED,
password,
};
store.dispatch(handlePasswordChanged(password));
expect(store.getActions()).toEqual([action]);
});
});

View file

@ -0,0 +1,39 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {GeneralTypes} from 'mattermost-redux/action_types';
import {ViewTypes} from 'app/constants';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
jest.mock('react-native-fetch-blob', () => ({
DocumentDir: () => jest.fn(),
fs: {dirs: {CacheDir: () => jest.fn()}},
}));
const mockStore = configureStore([thunk]);
describe('Actions.Views.SelectServer', () => {
let store;
beforeEach(() => {
store = mockStore({});
});
test('handleServerUrlChanged', async () => {
const serverUrl = 'https://mattermost.example.com';
const actions = batchActions([
{type: GeneralTypes.CLIENT_CONFIG_RESET},
{type: GeneralTypes.CLIENT_LICENSE_RESET},
{type: ViewTypes.SERVER_URL_CHANGED, serverUrl},
]);
store.dispatch(handleServerUrlChanged(serverUrl));
expect(store.getActions()).toEqual([actions]);
});
});

View file

@ -0,0 +1,51 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {ViewTypes} from 'app/constants';
import {
handleCommentDraftChanged,
handleCommentDraftSelectionChanged,
} from 'app/actions/views/thread';
jest.mock('react-native-fetch-blob', () => ({
DocumentDir: () => jest.fn(),
fs: {dirs: {CacheDir: () => jest.fn()}},
}));
const mockStore = configureStore([thunk]);
describe('Actions.Views.Thread', () => {
let store;
beforeEach(() => {
store = mockStore({});
});
test('handleCommentDraftChanged', async () => {
const rootId = '1234';
const draft = 'draft1';
const action = {
type: ViewTypes.COMMENT_DRAFT_CHANGED,
rootId,
draft,
};
await store.dispatch(handleCommentDraftChanged(rootId, draft));
expect(store.getActions()).toEqual([action]);
});
test('handleCommentDraftSelectionChanged', async () => {
const rootId = '1234';
const cursorPosition = 'position';
const action = {
type: ViewTypes.COMMENT_DRAFT_SELECTION_CHANGED,
rootId,
cursorPosition,
};
await store.dispatch(handleCommentDraftSelectionChanged(rootId, cursorPosition));
expect(store.getActions()).toEqual([action]);
});
});

View file

@ -0,0 +1,327 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AnnouncementBanner should match snapshot 1`] = `
ShallowWrapper {
"length": 1,
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <AnnouncementBanner
actions={
Object {
"dismissBanner": [MockFunction],
}
}
allowDismissal={true}
bannerColor="#ddd"
bannerDismissed={false}
bannerEnabled={true}
bannerText="Batter Text"
bannerTextColor="#fff"
/>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
"render": [Function],
"simulateEvent": [Function],
"unmount": [Function],
},
Symbol(enzyme.__node__): Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": <TouchableOpacity
activeOpacity={0.2}
onPress={[Function]}
style={
Object {
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
}
}
>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
numberOfLines={1}
style={
Array [
Object {
"flex": 1,
"fontSize": 14,
"marginRight": 5,
},
Object {
"color": "#fff",
},
]
}
>
Batter Text
</Text>
<Icon
allowFontScaling={false}
color="#fff"
name="info"
size={16}
/>
</TouchableOpacity>,
"style": Array [
Object {
"overflow": "hidden",
"paddingHorizontal": 10,
"position": "absolute",
"top": 0,
"width": "100%",
},
Object {
"backgroundColor": "#ddd",
"height": 0,
},
],
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"activeOpacity": 0.2,
"children": Array [
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
numberOfLines={1}
style={
Array [
Object {
"flex": 1,
"fontSize": 14,
"marginRight": 5,
},
Object {
"color": "#fff",
},
]
}
>
Batter Text
</Text>,
<Icon
allowFontScaling={false}
color="#fff"
name="info"
size={16}
/>,
],
"onPress": [Function],
"style": Object {
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
},
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"accessible": true,
"allowFontScaling": true,
"children": "Batter Text",
"ellipsizeMode": "tail",
"numberOfLines": 1,
"style": Array [
Object {
"flex": 1,
"fontSize": 14,
"marginRight": 5,
},
Object {
"color": "#fff",
},
],
},
"ref": null,
"rendered": "Batter Text",
"type": [Function],
},
Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"allowFontScaling": false,
"color": "#fff",
"name": "info",
"size": 16,
},
"ref": null,
"rendered": null,
"type": [Function],
},
],
"type": [Function],
},
"type": [Function],
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": <TouchableOpacity
activeOpacity={0.2}
onPress={[Function]}
style={
Object {
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
}
}
>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
numberOfLines={1}
style={
Array [
Object {
"flex": 1,
"fontSize": 14,
"marginRight": 5,
},
Object {
"color": "#fff",
},
]
}
>
Batter Text
</Text>
<Icon
allowFontScaling={false}
color="#fff"
name="info"
size={16}
/>
</TouchableOpacity>,
"style": Array [
Object {
"overflow": "hidden",
"paddingHorizontal": 10,
"position": "absolute",
"top": 0,
"width": "100%",
},
Object {
"backgroundColor": "#ddd",
"height": 0,
},
],
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"activeOpacity": 0.2,
"children": Array [
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
numberOfLines={1}
style={
Array [
Object {
"flex": 1,
"fontSize": 14,
"marginRight": 5,
},
Object {
"color": "#fff",
},
]
}
>
Batter Text
</Text>,
<Icon
allowFontScaling={false}
color="#fff"
name="info"
size={16}
/>,
],
"onPress": [Function],
"style": Object {
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
},
},
"ref": null,
"rendered": Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"accessible": true,
"allowFontScaling": true,
"children": "Batter Text",
"ellipsizeMode": "tail",
"numberOfLines": 1,
"style": Array [
Object {
"flex": 1,
"fontSize": 14,
"marginRight": 5,
},
Object {
"color": "#fff",
},
],
},
"ref": null,
"rendered": "Batter Text",
"type": [Function],
},
Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"allowFontScaling": false,
"color": "#fff",
"name": "info",
"size": 16,
},
"ref": null,
"rendered": null,
"type": [Function],
},
],
"type": [Function],
},
"type": [Function],
},
],
Symbol(enzyme.__options__): Object {
"adapter": ReactSixteenAdapter {
"options": Object {
"enableComponentDidUpdateOnSetState": true,
},
},
},
}
`;

View file

@ -0,0 +1,45 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {configure, shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({adapter: new Adapter()});
import AnnouncementBanner from './announcement_banner.js';
jest.useFakeTimers();
describe('AnnouncementBanner', () => {
const baseProps = {
actions: {
dismissBanner: jest.fn(),
},
allowDismissal: true,
bannerColor: '#ddd',
bannerDismissed: false,
bannerEnabled: true,
bannerText: 'Batter Text',
bannerTextColor: '#fff',
};
test('should match snapshot', () => {
const wrapper = shallow(
<AnnouncementBanner {...baseProps}/>
);
expect(wrapper).toMatchSnapshot();
});
test('should call actions.dismissBanner on handleDismiss', () => {
const actions = {dismissBanner: jest.fn()};
const props = {...baseProps, actions};
const wrapper = shallow(
<AnnouncementBanner {...props}/>
);
wrapper.instance().handleDismiss();
expect(actions.dismissBanner).toHaveBeenCalledTimes(1);
expect(actions.dismissBanner).toHaveBeenCalledWith(props.bannerText);
});
});

View file

@ -9,6 +9,8 @@ import {
pullOutImages,
} from 'app/components/markdown/transform';
/* eslint-disable max-nested-callbacks, no-console */
describe('Components.Markdown.transform', () => {
const parser = new Parser();

View file

@ -12,6 +12,8 @@ import {
import {Posts, Preferences} from 'mattermost-redux/constants';
import {getPreferenceKey} from 'mattermost-redux/utils/preference_utils';
/* eslint-disable max-nested-callbacks, no-console */
describe('Selectors.PostList', () => {
describe('makePreparePostIdsForPostList', () => {
it('filter join/leave posts', () => {

3009
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -78,40 +78,49 @@
"babel-preset-react-native": "4.0.0",
"babel-preset-stage-0": "6.24.1",
"babel-register": "6.26.0",
"chai": "4.1.2",
"chai-enzyme": "0.8.0",
"chai-fetch-mock": "2.0.0",
"deep-freeze": "0.0.1",
"enzyme": "3.3.0",
"enzyme-adapter-react-16": "1.1.1",
"enzyme-to-json": "3.3.3",
"eslint": "4.19.1",
"eslint-plugin-mocha": "5.0.0",
"eslint-plugin-jest": "21.15.1",
"eslint-plugin-react": "7.7.0",
"fetch-mock": "6.3.0",
"jest": "22.4.3",
"jest-cli": "22.4.3",
"jsdom-global": "3.0.2",
"mocha": "5.1.1",
"mocha-react-native": "0.6.0",
"mock-async-storage": "2.0.2",
"mockery": "2.1.0",
"nyc": "11.7.1",
"react-addons-test-utils": "15.6.2",
"react-dom": "16.3.2",
"react-native-mock": "0.3.1",
"react-native-svg-mock": "2.0.0",
"react-test-renderer": "16.3.2",
"redux-mock-store": "1.5.1",
"remote-redux-devtools": "0.5.12",
"remotedev-rn-debugger": "0.8.3",
"socketcluster": "11.4.2",
"underscore": "1.9.0"
},
"scripts": {
"test": "NODE_ENV=test nyc --reporter=text mocha --exit --opts test/mocha.opts",
"check": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet .",
"fix": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet . --fix",
"postinstall": "make post-install"
"postinstall": "make post-install",
"test": "jest --forceExit",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"rnpm": {
"assets": [
"./assets/fonts"
]
},
"jest": {
"clearMocks": true,
"collectCoverageFrom": [
"app/**/*.{js,jsx}"
],
"coverageReporters": [
"lcov",
"text-summary"
],
"preset": "react-native",
"testPathIgnorePatterns": [
"/node_modules/"
]
}
}

View file

@ -1,13 +0,0 @@
{
"rules": {
"no-console": 0,
"global-require": 0,
"func-names": 0,
"prefer-arrow-callback": 0,
"no-magic-numbers": 0,
"no-unreachable": 0,
"new-cap": 0,
"max-nested-callbacks": 0,
"no-undefined": 0
}
}

View file

@ -1,29 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import * as Actions from 'app/actions/views/login';
import configureStore from 'app/store';
import TestHelper from 'test/test_helper';
describe('Actions.Views.Login', () => {
let store;
beforeEach(async () => {
store = configureStore();
await TestHelper.wait();
});
it('handleLoginIdChanged', async () => {
await Actions.handleLoginIdChanged('email@example.com')(store.dispatch, store.getState);
const loginId = store.getState().views.login.loginId;
assert.equal('email@example.com', loginId);
});
it('handlePasswordChanged', async () => {
await Actions.handlePasswordChanged('password')(store.dispatch, store.getState);
const password = store.getState().views.login.password;
assert.equal('password', password);
});
});

View file

@ -1,20 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import * as Actions from 'app/actions/views/select_server';
import configureStore from 'app/store';
import TestHelper from 'test/test_helper';
describe('Actions.Views.SelectServer', () => {
it('handleServerUrlChanged', async () => {
const store = configureStore();
await TestHelper.wait();
await Actions.handleServerUrlChanged('https://mattermost.example.com')(store.dispatch, store.getState);
const serverUrl = store.getState().views.selectServer.serverUrl;
assert.equal('https://mattermost.example.com', serverUrl);
});
});

View file

@ -1,52 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import assert from 'assert';
import * as ThreadActions from 'app/actions/views/thread';
import configureStore from 'app/store';
import TestHelper from 'test/test_helper';
describe('Actions.Views.Thread', () => {
it('handleCommentDraftChanged', async () => {
const store = configureStore();
await TestHelper.wait();
await ThreadActions.handleCommentDraftChanged('1234', 'draft1')(store.dispatch, store.getState);
assert.deepEqual(store.getState().views.thread, {
drafts: {
1234: {
draft: 'draft1',
},
},
});
await ThreadActions.handleCommentDraftChanged('1235', 'draft2')(store.dispatch, store.getState);
assert.deepEqual(store.getState().views.thread, {
drafts: {
1234: {
draft: 'draft1',
},
1235: {
draft: 'draft2',
},
},
});
await ThreadActions.handleCommentDraftChanged('1235', 'draft3')(store.dispatch, store.getState);
assert.deepEqual(store.getState().views.thread, {
drafts: {
1234: {
draft: 'draft1',
},
1235: {
draft: 'draft3',
},
},
});
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

View file

@ -1,4 +0,0 @@
--require test/setup.js
--compilers js:babel-core/register
--require babel-polyfill
--recursive

View file

@ -1,9 +0,0 @@
import {addMock} from 'mocha-react-native';
addMock('react-native-device-info', {
getBuildNumber: () => true,
getVersion: () => true,
getDeviceLocale: () => 'en',
});
addMock('react-native-linear-gradient', {});
addMock('UIManager', {});

View file

@ -1,29 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import 'react-native';
global.WebSocket = require('ws');
// Set up a global hooks to make debugging tests less of a pain
before(() => {
process.on('unhandledRejection', (reason) => {
// Rethrow so that tests will actually fail and not just timeout
throw reason;
});
});
// Ensure that everything is imported correctly for testing
describe('Sanity test', () => {
it('Promise', (done) => {
Promise.resolve(true).then(() => {
done();
}).catch((err) => {
done(err);
});
});
it('async/await', async () => {
await Promise.resolve(true);
});
});

View file

@ -1,80 +0,0 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
// Setup recommendation from the following blog:
// https://blog.addjam.com/testing-react-native-with-mocha-and-enzyme-6b77cd9e52a1#.2awpwqwwb
/* eslint-disable */
import fs from 'fs';
import path from 'path';
import register from 'babel-core/register';
import mockery from 'mockery';
import MockAsyncStorage from 'mock-async-storage';
window = {};
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false
});
mockery.registerMock('react-native', {
Dimensions: {
get: () => {
return {width: 0, height: 0}
}
},
AsyncStorage: new MockAsyncStorage(),
NativeModules: {},
NetInfo: {
isConnected: {
addEventListener: () => true,
fetch: () => Promise.resolve(true)
}
},
Platform: {
OS: 'ios'
}
});
mockery.registerMock('react-native-device-info', {
getDeviceLocale() {
return 'en';
},
getBuildNumber: () => true,
getVersion: () => true
});
mockery.registerMock('react-native-sentry', {
Sentry: {
captureBreadcrumb() {}
}
});
mockery.registerMock('react-native-fetch-blob', {
RNFetchBlob: {
DocumentDir: ''
},
fs: {
dirs: {
DocumentDir: '',
CacheDir: ''
}
}
});
// Ignore all node_modules except these
const modulesToCompile = [
'react-native'
].map((moduleName) => new RegExp(`/node_modules/${moduleName}`));
const rcPath = path.join(__dirname, '..', '.babelrc');
const source = fs.readFileSync(rcPath).toString();
const config = JSON.parse(source);
config.ignore = function(filename) {
if (!(/\/node_modules\//).test(filename)) {
return false;
}
const matches = modulesToCompile.filter((regex) => regex.test(filename));
const shouldIgnore = matches.length === 0;
return shouldIgnore;
};
register(config);