Update Jest & Enzyme, Add snapshot tests (#307)

* Add disclaimer modal to footer

* Remove duplicate code & unnecessary styles

* Fix formatting noise

* remove un-used css style

* Fix tslint error & add media query for modals

* Nest Media Query

* Update Jest & Enzyme, Add snapshot tests

* Fix tslint errors in /spec, Update mock localstorage

* Update types in tests, Fix tslint error

* Specify module versions for browser

* Update sendTransaction snapshot
This commit is contained in:
James Prado 2017-10-24 22:17:26 -04:00 committed by Daniel Ternyak
parent 9095fc0986
commit 70a2b3ca9a
29 changed files with 1444 additions and 857 deletions

View File

@ -12,10 +12,10 @@ interface ABITypedSlot {
interface ABIMethod {
name: string;
type: 'function';
constant: boolean;
type: string;
constant?: boolean;
inputs: ABITypedSlot[];
outputs: ABITypedSlot[];
outputs?: ABITypedSlot[];
// default - false
payable?: boolean;
}

View File

@ -0,0 +1 @@
module.exports = 'test-file-stub';

View File

@ -1,4 +1,4 @@
export default class LocalStorageMock {
class LocalStorageMock {
private store: object;
constructor() {
@ -21,3 +21,7 @@ export default class LocalStorageMock {
delete this.store[key];
}
}
Object.defineProperty(window, 'localStorage', {
value: new LocalStorageMock()
});

View File

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

View File

@ -1,17 +1,22 @@
{
"rootDir": "../",
"transform": {
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json"
],
"moduleDirectories": ["node_modules", "common"],
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
"<rootDir>/jest_config/__mocks__/fileMock.ts",
"\\.(css|scss|less)$": "<rootDir>/jest_config/__mocks__/styleMock.ts"
},
"testPathIgnorePatterns": ["<rootDir>/common/config"],
"setupFiles": ["<rootDir>/jest_config/setupJest.js"],
"setupFiles": [
"<rootDir>/jest_config/setupJest.js",
"<rootDir>/jest_config/__mocks__/localStorage.ts"
],
"automock": false,
"moduleDirectories": ["node_modules", "common"]
"snapshotSerializers": ["enzyme-to-json/serializer"],
"browser": true
}

1802
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -46,7 +46,7 @@
"@types/bignumber.js": "^4.0.2",
"@types/classnames": "^2.2.3",
"@types/history": "^4.6.0",
"@types/jest": "^21.1.0",
"@types/jest": "^21.1.4",
"@types/lodash": "^4.14.74",
"@types/moment": "^2.13.0",
"@types/prop-types": "^15.5.1",
@ -72,7 +72,9 @@
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.7",
"empty": "^0.10.1",
"enzyme": "^2.8.0",
"enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.2",
"enzyme-to-json": "^3.1.4",
"express": "^4.15.4",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^0.11.0",
@ -81,7 +83,7 @@
"html-webpack-plugin": "^2.30.1",
"husky": "^0.14.3",
"image-webpack-loader": "^3.4.2",
"jest": "^19.0.2",
"jest": "^21.2.1",
"less": "^2.7.2",
"less-loader": "^4.0.3",
"lint-staged": "^4.2.2",
@ -92,12 +94,14 @@
"prettier": "^1.7.0",
"progress": "^2.0.0",
"react-hot-loader": "^3.0.0-beta.6",
"react-test-renderer": "^16.0.0",
"redux-devtools-extension": "^2.13.0",
"redux-test-utils": "^0.1.3",
"resolve-url-loader": "^2.0.0",
"rimraf": "^2.6.2",
"sass-loader": "^6.0.2",
"style-loader": "^0.18.2",
"ts-jest": "^21.0.1",
"ts-jest": "^21.1.3",
"tslint": "^5.7.0",
"tslint-config-prettier": "^1.5.0",
"tslint-react": "^3.2.0",

View File

@ -1,5 +1,4 @@
import Contract from 'libs/contract';
import Big from 'bignumber.js';
describe('Contract', () => {
// From the ABI docs
@ -7,16 +6,16 @@ describe('Contract', () => {
const testAbi = [
{
type: 'event',
name: 'Event',
inputs: [
{ name: 'a', type: 'uint256', indexed: true },
{ name: 'b', type: 'bytes32', indexed: false }
],
name: 'Event'
]
},
{
type: 'function',
inputs: [{ name: 'a', type: 'uint256' }],
name: 'foo',
inputs: [{ name: 'a', type: 'uint256' }],
outputs: []
}
];

View File

@ -6,50 +6,52 @@ import {
decryptMnemonicToPrivKey
} from '../../common/libs/decrypt';
//deconstructed elements of a V1 encrypted priv key
const encpkey =
'U2FsdGVkX19us8qXfYyeQhxyzV7aFlXckG/KrRLajoCGBKO4/saefxGs/3PrCLWxZEbx2vn6V0VDWrkDUkL+8S4MK7FL9LCiIKxeCq/ciwX9YQepsRRetG2MExuUWkQ6365d';
const pass = 'testtesttest';
const salt = 'brPKl32MnkI=';
const ciphertext =
'HHLNXtoWVdyQb8qtEtqOgIYEo7j+xp5/Eaz/c+sItbFkRvHa+fpXRUNauQNSQv7xLgwrsUv0sKIgrF4Kr9yLBf1hB6mxFF60bYwTG5RaRDrfrl0=';
const iv = 'k9YWF8ZBCoyuFS6CfGS+7w==';
const key = 'u9uhwRmBQDJ12MUBkIrO5EzMQZTYEf6hTBDzSJBKJ2k=';
const pkey = 'a56d4f23449a10ddcdd94bad56f895640097800406840aa8fe545d324d422c02';
// Elements of a V1 encrypted priv key
const v1 = {
encpkey:
'U2FsdGVkX19us8qXfYyeQhxyzV7aFlXckG/KrRLajoCGBKO4/saefxGs/3PrCLWxZEbx2vn6V0VDWrkDUkL+8S4MK7FL9LCiIKxeCq/ciwX9YQepsRRetG2MExuUWkQ6365d',
pass: 'testtesttest',
salt: 'brPKl32MnkI=',
ciphertext:
'HHLNXtoWVdyQb8qtEtqOgIYEo7j+xp5/Eaz/c+sItbFkRvHa+fpXRUNauQNSQv7xLgwrsUv0sKIgrF4Kr9yLBf1hB6mxFF60bYwTG5RaRDrfrl0=',
iv: 'k9YWF8ZBCoyuFS6CfGS+7w==',
key: 'u9uhwRmBQDJ12MUBkIrO5EzMQZTYEf6hTBDzSJBKJ2k=',
pkey: 'a56d4f23449a10ddcdd94bad56f895640097800406840aa8fe545d324d422c02'
};
describe('decryptPrivKey', () => {
it('should decrypt encrypted pkey string to pkey buffer', () => {
const decrypt = decryptPrivKey(encpkey, pass);
const decrypt = decryptPrivKey(v1.encpkey, v1.pass);
expect(decrypt).toBeInstanceOf(Buffer);
expect(decrypt.toString('hex')).toEqual(pkey);
expect(decrypt.toString('hex')).toEqual(v1.pkey);
expect(decrypt.length).toEqual(32);
});
});
describe('decodeCryptojsSalt', () => {
it('should derive correct salt and ciphertext from pkey string', () => {
const decode = decodeCryptojsSalt(encpkey);
const decode = decodeCryptojsSalt(v1.encpkey);
expect(decode.salt).toBeInstanceOf(Buffer);
expect(decode.ciphertext).toBeInstanceOf(Buffer);
expect(decode.salt.toString('base64')).toEqual(salt);
expect(decode.ciphertext.toString('base64')).toEqual(ciphertext);
expect(decode.salt.toString('base64')).toEqual(v1.salt);
expect(decode.ciphertext.toString('base64')).toEqual(v1.ciphertext);
});
});
describe('evp_kdf', () => {
it('should derive correct key and iv', () => {
const result = evp_kdf(
new Buffer(pass, 'utf8'),
new Buffer(salt, 'base64'),
new Buffer(v1.pass, 'utf8'),
new Buffer(v1.salt, 'base64'),
{ keysize: 32, ivsize: 16 }
);
expect(result.key).toBeInstanceOf(Buffer);
expect(result.iv).toBeInstanceOf(Buffer);
expect(result.key.toString('base64')).toEqual(key);
expect(result.iv.toString('base64')).toEqual(iv);
expect(result.key.toString('base64')).toEqual(v1.key);
expect(result.iv.toString('base64')).toEqual(v1.iv);
});
});

View File

@ -1,6 +1,5 @@
import Big from 'bignumber.js';
import ERC20 from 'libs/erc20';
import abi from 'ethereumjs-abi';
const MEW_ADDRESS = '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8';
describe('ERC20', () => {

View File

@ -1,17 +1,17 @@
import Big from 'bignumber.js';
import { toWei, toUnit } from '../../common/libs/units';
import { toWei, toUnit, UnitKey } from '../../common/libs/units';
describe('Units', () => {
describe('toWei', () => {
const conversions = [
{
value: '0.001371',
unit: 'ether',
unit: 'ether' as UnitKey,
wei: '1371000000000000'
},
{
value: '9',
unit: 'gwei',
unit: 'gwei' as UnitKey,
wei: '9000000000'
}
];
@ -28,14 +28,14 @@ describe('Units', () => {
const conversions = [
{
value: '.41849',
fromUnit: 'ether',
toUnit: 'gwei',
fromUnit: 'ether' as UnitKey,
toUnit: 'gwei' as UnitKey,
output: '418490000'
},
{
value: '4924.71',
fromUnit: 'nanoether',
toUnit: 'szabo',
fromUnit: 'nanoether' as UnitKey,
toUnit: 'szabo' as UnitKey,
output: '4.92471'
}
];

16
spec/pages/ENS.spec.tsx Normal file
View File

@ -0,0 +1,16 @@
import React from 'react';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import ENS from 'containers/Tabs/ENS';
import shallowWithStore from '../utils/shallowWithStore';
import { createMockStore } from 'redux-test-utils';
Enzyme.configure({ adapter: new Adapter() });
it('render snapshot', () => {
const testState = {};
const store = createMockStore(testState);
const component = shallowWithStore(<ENS />, store);
expect(component).toMatchSnapshot();
});

View File

@ -0,0 +1,25 @@
import React from 'react';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import GenerateWallet from 'containers/Tabs/GenerateWallet';
import shallowWithStore from '../utils/shallowWithStore';
import { createMockStore } from 'redux-test-utils';
Enzyme.configure({ adapter: new Adapter() });
it('render snapshot', () => {
const testState = {
form: {
walletPasswordForm: {}
},
generateWallet: {
activeStep: {},
password: {},
wallet: {}
}
};
const store = createMockStore(testState);
const component = shallowWithStore(<GenerateWallet />, store);
expect(component).toMatchSnapshot();
});

View File

@ -0,0 +1,44 @@
import React from 'react';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import SendTransaction from 'containers/Tabs/SendTransaction';
import shallowWithStore from '../utils/shallowWithStore';
import { createMockStore } from 'redux-test-utils';
Enzyme.configure({ adapter: new Adapter() });
it('render snapshot', () => {
const testStateConfig = {
languageSelection: 'en',
nodeSelection: 'rop_mew',
gasPriceGwei: 21,
offline: false,
forceOffline: false
};
const testState = {
wallet: {},
balance: {},
tokenBalances: {},
node: {},
nodeLib: {},
network: {},
tokens: [],
gasPrice: {},
transactions: {},
offline: {},
forceOffline: {},
config: testStateConfig,
customTokens: []
};
const location = {
search:
'?to=73640ebefe93e4d0d6e9030ee9c1866ad1f3b9f1feeb403e978c4952d8369b39'
};
const store = createMockStore(testState);
const component = shallowWithStore(
<SendTransaction location={location} />,
store
);
expect(component).toMatchSnapshot();
});

35
spec/pages/Swap.spec.tsx Normal file
View File

@ -0,0 +1,35 @@
import React from 'react';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Swap from 'containers/Tabs/Swap';
import shallowWithStore from '../utils/shallowWithStore';
import { createMockStore } from 'redux-test-utils';
Enzyme.configure({ adapter: new Adapter() });
it('render snapshot', () => {
const testState = {
swap: {
originAmount: {},
destinationAmount: {},
originKind: {},
destinationKind: {},
destinationKindOptions: {},
originKindOptions: {},
step: {},
bityRates: {},
bityOrder: {},
destinationAddress: {},
isFetchingRates: {},
secondsRemaining: {},
outputTx: {},
isPostingOrder: {},
orderStatus: {},
paymentAddress: {}
}
};
const store = createMockStore(testState);
const component = shallowWithStore(<Swap />, store);
expect(component).toMatchSnapshot();
});

View File

@ -0,0 +1,16 @@
import React from 'react';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import ViewWallet from 'containers/Tabs/ViewWallet';
import shallowWithStore from '../utils/shallowWithStore';
import { createMockStore } from 'redux-test-utils';
Enzyme.configure({ adapter: new Adapter() });
it('render snapshot', () => {
const testState = {};
const store = createMockStore(testState);
const component = shallowWithStore(<ViewWallet />, store);
expect(component).toMatchSnapshot();
});

View File

@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`render snapshot 1`] = `
<ENS
dispatch={[Function]}
/>
`;

View File

@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`render snapshot 1`] = `
<GenerateWallet
activeStep={Object {}}
continueToPaper={[Function]}
generateNewWallet={[Function]}
password={Object {}}
resetGenerateWallet={[Function]}
wallet={Object {}}
walletPasswordForm={Object {}}
/>
`;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`render snapshot 1`] = `
<Swap
bityOrder={Object {}}
bityOrderCreateRequestedSwap={[Function]}
bityRates={Object {}}
changeStepSwap={[Function]}
destinationAddress={Object {}}
destinationAddressSwap={[Function]}
destinationAmount={Object {}}
destinationAmountSwap={[Function]}
destinationKind={Object {}}
destinationKindOptions={Object {}}
destinationKindSwap={[Function]}
isFetchingRates={Object {}}
isPostingOrder={Object {}}
loadBityRatesRequestedSwap={[Function]}
orderStatus={Object {}}
originAmount={Object {}}
originAmountSwap={[Function]}
originKind={Object {}}
originKindOptions={Object {}}
originKindSwap={[Function]}
outputTx={Object {}}
paymentAddress={Object {}}
restartSwap={[Function]}
secondsRemaining={Object {}}
showNotification={[Function]}
startOrderTimerSwap={[Function]}
startPollBityOrderStatus={[Function]}
step={Object {}}
stopLoadBityRatesSwap={[Function]}
stopOrderTimerSwap={[Function]}
stopPollBityOrderStatus={[Function]}
/>
`;

View File

@ -0,0 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`render snapshot 1`] = `
<Connect(TabSection)>
<section
className="container"
>
<div
className="tab-content"
>
<article
className="tab-pane active"
>
<article
className="collapse-container"
>
<div>
<h1>
View Wallet Info
</h1>
</div>
<div>
<p>
<Translate
translationKey="VIEWWALLET_Subtitle"
/>
</p>
</div>
</article>
</article>
</div>
</section>
</Connect(TabSection)>
`;

View File

@ -3,10 +3,6 @@ import * as configActions from 'actions/config';
import { NODES } from 'config/data';
describe('config reducer', () => {
it('should return the initial state', () => {
expect(config(undefined, {})).toEqual(INITIAL_STATE);
});
it('should handle CONFIG_LANGUAGE_CHANGE', () => {
const language = 'en';
expect(config(undefined, configActions.changeLanguage(language))).toEqual({
@ -16,7 +12,7 @@ describe('config reducer', () => {
});
it('should handle CONFIG_NODE_CHANGE', () => {
const node = NODES[0];
const node = Object.keys(NODES)[0];
expect(config(undefined, configActions.changeNode(node))).toEqual({
...INITIAL_STATE,

View File

@ -2,10 +2,6 @@ import { generateWallet, INITIAL_STATE } from 'reducers/generateWallet';
import * as generateWalletActions from 'actions/generateWallet';
describe('generateWallet reducer', () => {
it('should return the initial state', () => {
expect(generateWallet(undefined, {})).toEqual(INITIAL_STATE);
});
it('should handle GENERATE_WALLET_CONTINUE_TO_PAPER', () => {
expect(
generateWallet(undefined, generateWalletActions.continueToPaper())

View File

@ -8,10 +8,6 @@ import * as swapActions from 'actions/swap';
import without from 'lodash/without';
describe('swap reducer', () => {
it('should return the initial state', () => {
expect(swap(undefined, {})).toEqual(INITIAL_STATE);
});
it('should handle SWAP_ORIGIN_KIND', () => {
const newOriginKind = 'ETH';
const newDestinationKind = buildDestinationKind(
@ -84,7 +80,9 @@ describe('swap reducer', () => {
it('should handle SWAP_LOAD_BITY_RATES_SUCCEEDED', () => {
const bityRates = {
BTCETH: 0.01,
ETHREP: 10
ETHREP: 10,
ETHBTC: 0,
BTCREP: 0
};
expect(
swap(undefined, swapActions.loadBityRatesSucceededSwap(bityRates))
@ -134,14 +132,22 @@ describe('swap reducer', () => {
});
it('should handle SWAP_ORDER_CREATE_REQUESTED', () => {
expect(swap(undefined, { type: 'SWAP_ORDER_CREATE_REQUESTED' })).toEqual({
expect(
swap(undefined, {
type: 'SWAP_ORDER_CREATE_REQUESTED'
} as swapActions.SwapAction)
).toEqual({
...INITIAL_STATE,
isPostingOrder: true
});
});
it('should handle SWAP_ORDER_CREATE_FAILED', () => {
expect(swap(undefined, { type: 'SWAP_ORDER_CREATE_FAILED' })).toEqual({
expect(
swap(undefined, {
type: 'SWAP_ORDER_CREATE_FAILED'
} as swapActions.SwapAction)
).toEqual({
...INITIAL_STATE,
isPostingOrder: false
});
@ -166,7 +172,9 @@ describe('swap reducer', () => {
it('should handle SWAP_LOAD_BITY_RATES_REQUESTED', () => {
expect(
swap(undefined, { type: 'SWAP_LOAD_BITY_RATES_REQUESTED' })
swap(undefined, {
type: 'SWAP_LOAD_BITY_RATES_REQUESTED'
} as swapActions.SwapAction)
).toEqual({
...INITIAL_STATE,
isFetchingRates: true
@ -174,7 +182,11 @@ describe('swap reducer', () => {
});
it('should handle SWAP_STOP_LOAD_BITY_RATES', () => {
expect(swap(undefined, { type: 'SWAP_STOP_LOAD_BITY_RATES' })).toEqual({
expect(
swap(undefined, {
type: 'SWAP_STOP_LOAD_BITY_RATES'
} as swapActions.SwapAction)
).toEqual({
...INITIAL_STATE,
isFetchingRates: false
});

View File

@ -1,15 +1,18 @@
import { wallet, INITIAL_STATE } from 'reducers/wallet';
import * as walletActions from 'actions/wallet';
import Big from 'bignumber.js';
import { Ether } from 'libs/units';
describe('wallet reducer', () => {
it('should return the initial state', () => {
expect(wallet(undefined, {})).toEqual(INITIAL_STATE);
});
it('should handle WALLET_SET', () => {
const walletInstance = { wallet: true };
const doSomething = new Promise<string>(resolve => {
setTimeout(() => resolve('Success'), 1000);
});
const walletInstance = {
getAddress: () => doSomething,
signRawTransaction: () => doSomething
};
expect(wallet(undefined, walletActions.setWallet(walletInstance))).toEqual({
...INITIAL_STATE,
inst: walletInstance,

View File

@ -25,15 +25,18 @@ describe('formatNumber', () => {
const pairs = [
{
input: new Big('0.0127491'),
output: '0.013'
output: '0.013',
digits: undefined
},
{
input: new Big('21.87468421'),
output: '21.875'
output: '21.875',
digits: undefined
},
{
input: new Big(0),
output: '0'
output: '0',
digits: undefined
},
{
input: new Big('354.4728173'),
@ -48,7 +51,7 @@ describe('formatNumber', () => {
];
pairs.forEach(pair => {
const digits = pair.digits === undefined ? 'default' : pair.digits;
const digits = pair.digits;
it(`should convert ${pair.input.toString()} to ${pair.output} when using ${digits} digits`, () => {
expect(formatNumber(pair.input, pair.digits)).toEqual(pair.output);
});

View File

@ -1,4 +1,3 @@
import LocalStorageMock from '../mocks/localStorage';
import {
loadState,
saveState,
@ -6,15 +5,15 @@ import {
REDUX_STATE
} from '../../common/utils/localStorage';
window.localStorage = new LocalStorageMock();
describe('saveState', () => {
it('should serialize and persist state to local storage under key: "REDUX_STATE"', () => {
const persistMe = {
foo: 'bar'
};
saveState(persistMe);
expect(JSON.parse(localStorage.getItem(REDUX_STATE))).toEqual(persistMe);
expect(JSON.parse(localStorage.getItem(REDUX_STATE) as string)).toEqual(
persistMe
);
});
});

View File

@ -0,0 +1,10 @@
import { shallow } from 'enzyme';
const shallowWithStore = (component, store) => {
const context = {
store
};
return shallow(component, { context });
};
export default shallowWithStore;

View File

@ -17,6 +17,7 @@
},
"include": [
"./common/",
"spec",
"./node_modules/types-rlp/index.d.ts",
"./node_modules/types-bn/index.d.ts"
],