summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2019-04-01 16:00:22 +0200
committerAndrej Mihajlov <and@mullvad.net>2019-04-01 16:00:22 +0200
commit33c7666cc6d03b393b4e003c04c4ca1d3a4ff5cc (patch)
tree6a155396bedff004b0f733ad7fc304b8cc215336
parentbcf731749bddebb2eb25ccc6a5b83b45de8fdb0e (diff)
parentc54a8e8af8ed5e53151ded7f66d9eea7cf7a5d20 (diff)
downloadmullvadvpn-33c7666cc6d03b393b4e003c04c4ca1d3a4ff5cc.tar.xz
mullvadvpn-33c7666cc6d03b393b4e003c04c4ca1d3a4ff5cc.zip
Merge branch 'fix-typescript-tests'
-rw-r--r--gui/package.json5
-rw-r--r--gui/src/main/jsonrpc-client.ts70
-rw-r--r--gui/src/main/keyframe-animation.ts28
-rw-r--r--gui/src/renderer/app.tsx121
-rw-r--r--gui/src/renderer/lib/account-data-cache.ts118
-rw-r--r--gui/src/renderer/lib/auth-failure.ts3
-rw-r--r--gui/test/account-data-cache.spec.ts41
-rw-r--r--gui/test/components/NotificationArea.spec.tsx102
-rw-r--r--gui/test/jsonrpc-transport.spec.ts119
-rw-r--r--gui/test/keyframe-animation.spec.ts22
-rw-r--r--gui/test/setup/main.js (renamed from gui/test/setup/main.ts)2
-rw-r--r--gui/yarn.lock30
12 files changed, 341 insertions, 320 deletions
diff --git a/gui/package.json b/gui/package.json
index 588dc9d855..431fa7dccd 100644
--- a/gui/package.json
+++ b/gui/package.json
@@ -18,7 +18,7 @@
"electron-log": "^2.2.8",
"gettext-parser": "^3.1.0",
"history": "^4.6.1",
- "jsonrpc-lite": "^2.0.1",
+ "jsonrpc-lite": "^2.0.5",
"mkdirp": "^0.5.1",
"moment": "^2.24.0",
"node-gettext": "^2.0.0",
@@ -68,7 +68,6 @@
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.7.0",
"gettext-extractor": "^3.4.2",
- "mock-socket": "^8.0.5",
"npm-run-all": "^4.0.1",
"prettier": "1.16.4",
"rimraf": "^2.5.4",
@@ -87,7 +86,7 @@
"format": "yarn run private:format --write",
"check-format": "yarn run private:format --list-different",
"develop": "cross-env run-s private:copy-assets private:watch",
- "test": "electron-mocha --renderer -R spec --require ts-node/register --require-main ts-node/register --require-main \"test/setup/main.ts\" --preload \"test/setup/renderer.ts\" \"test/*.spec.ts\" \"test/**/*.spec.ts\" \"test/**/*.spec.tsx\" || true",
+ "test": "cross-env NODE_ENV=test electron-mocha --renderer --reporter spec --require-main \"test/setup/main.js\" --require ts-node/register --require \"test/setup/renderer.ts\" \"test/**/*.{ts,tsx}\"",
"update-translations": "node scripts/extract-translations",
"pack:mac": "run-s build private:pack:mac private:postbuild:mac",
"pack:win": "run-s build private:pack:win",
diff --git a/gui/src/main/jsonrpc-client.ts b/gui/src/main/jsonrpc-client.ts
index 2d66aad50d..0a6ada62d8 100644
--- a/gui/src/main/jsonrpc-client.ts
+++ b/gui/src/main/jsonrpc-client.ts
@@ -246,9 +246,8 @@ export default class JsonRpcClient<T> extends EventEmitter {
}
private onMessage(obj: object) {
- let message: any;
+ let message: ReturnType<typeof jsonrpc.parseObject>;
try {
- // @ts-ignore
message = jsonrpc.parseObject(obj);
} catch (error) {
log.error(`Failed to parse JSON-RPC message: ${error} for object`);
@@ -256,9 +255,9 @@ export default class JsonRpcClient<T> extends EventEmitter {
}
if (message.type === 'notification') {
- this.onNotification(message);
+ this.onNotification(message as IJsonRpcNotification);
} else {
- this.onReply(message);
+ this.onReply(message as (IJsonRpcErrorResponse | IJsonRpcSuccess));
}
}
@@ -297,7 +296,7 @@ export default class JsonRpcClient<T> extends EventEmitter {
}
}
-interface ITransport<T> {
+export interface ITransport<T> {
onOpen: () => void;
onMessage: (data: object) => void;
onClose: (error?: Error) => void;
@@ -306,67 +305,6 @@ interface ITransport<T> {
connect(params: T): void;
}
-export class WebsocketTransport implements ITransport<string> {
- public ws?: WebSocket;
-
- constructor(ws?: WebSocket) {
- this.ws = ws;
- }
- public onOpen = () => {
- // no-op
- };
- public onMessage = (_message: object) => {
- // no-op
- };
- public onClose = (_error?: Error) => {
- // no-op
- };
-
- public close() {
- if (this.ws) {
- this.ws.close();
- }
- }
-
- public send(msg: string) {
- if (this.ws) {
- this.ws.send(msg);
- }
- }
-
- public connect(params: string): void {
- if (this.ws) {
- this.ws.close();
- }
- this.ws = new WebSocket(params);
- this.ws.onopen = (_event) => {
- this.onOpen();
- };
- this.ws.onmessage = (event) => {
- try {
- const data = event.data;
- if (typeof data === 'string') {
- const msg = JSON.parse(data);
- this.onMessage(msg);
- } else {
- throw event;
- }
- } catch (error) {
- log.error('Got invalid reply from server: ', error);
- }
- };
-
- this.ws.onclose = (event) => {
- log.info(`The websocket connection closed with code: ${event.code}`);
- if (event.code === 1000) {
- this.onClose();
- } else {
- this.onClose(new WebSocketError(event.code));
- }
- };
- }
-}
-
// Given the correct parameters, this transport supports named pipes/unix
// domain sockets, and also TCP/UDP sockets
export class SocketTransport implements ITransport<{ path: string }> {
diff --git a/gui/src/main/keyframe-animation.ts b/gui/src/main/keyframe-animation.ts
index 25cd83a5db..1a95e1a106 100644
--- a/gui/src/main/keyframe-animation.ts
+++ b/gui/src/main/keyframe-animation.ts
@@ -13,7 +13,7 @@ export default class KeyframeAnimation {
private onFrameValue?: OnFrameFn;
private onFinishValue?: OnFinishFn;
- private currentFrame: number = 0;
+ private currentFrameValue: number = 0;
private targetFrame: number = 0;
private isRunningValue: boolean = false;
@@ -21,6 +21,20 @@ export default class KeyframeAnimation {
private timeout?: NodeJS.Timeout;
+ get currentFrame(): number {
+ return this.currentFrameValue;
+ }
+
+ // This setter is only meant to be used when running tests
+ // @internal
+ set currentFrame(newValue: number) {
+ if (process.env.NODE_ENV === 'test') {
+ this.currentFrameValue = newValue;
+ } else {
+ throw new Error('The setter for currentFrame is only available in test environment.');
+ }
+ }
+
set onFrame(newValue: OnFrameFn | undefined) {
this.onFrameValue = newValue;
}
@@ -56,7 +70,7 @@ export default class KeyframeAnimation {
const { start, end } = options;
if (start !== undefined) {
- this.currentFrame = start;
+ this.currentFrameValue = start;
}
this.targetFrame = end;
@@ -88,7 +102,7 @@ export default class KeyframeAnimation {
private render() {
if (this.onFrameValue) {
- this.onFrameValue(this.currentFrame);
+ this.onFrameValue(this.currentFrameValue);
}
}
@@ -119,12 +133,12 @@ export default class KeyframeAnimation {
return;
}
- if (this.currentFrame === this.targetFrame) {
+ if (this.currentFrameValue === this.targetFrame) {
this.didFinish();
- } else if (this.currentFrame < this.targetFrame) {
- this.currentFrame += 1;
+ } else if (this.currentFrameValue < this.targetFrame) {
+ this.currentFrameValue += 1;
} else {
- this.currentFrame -= 1;
+ this.currentFrameValue -= 1;
}
}
}
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 7d2cd2a69a..54b2743467 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -25,11 +25,11 @@ import { IWindowShapeParameters } from '../main/window-controller';
import { loadTranslations } from '../shared/gettext';
import { IGuiSettingsState } from '../shared/gui-settings-state';
import { IpcRendererEventChannel } from '../shared/ipc-event-channel';
+import AccountDataCache, { AccountFetchRetryAction } from './lib/account-data-cache';
import AccountExpiry from './lib/account-expiry';
import {
AccountToken,
- IAccountData,
ILocation,
IRelayList,
ISettings,
@@ -38,6 +38,8 @@ import {
TunnelStateTransition,
} from '../shared/daemon-rpc-types';
+type AccountVerification = { status: 'verified' } | { status: 'deferred'; error: Error };
+
export default class AppRenderer {
private memoryHistory = createMemoryHistory();
private reduxStore = configureStore(this.memoryHistory);
@@ -597,120 +599,3 @@ export default class AppRenderer {
this.reduxActions.settings.updateAutoStart(autoStart);
}
}
-
-type AccountVerification = { status: 'verified' } | { status: 'deferred'; error: Error };
-export enum AccountFetchRetryAction {
- stop,
- retry,
-}
-interface IAccountFetchWatcher {
- onFinish: () => void;
- onError: (error: Error) => AccountFetchRetryAction;
-}
-
-// An account data cache that helps to throttle RPC requests to get_account_data and retain the
-// cached value for 1 minute.
-export class AccountDataCache {
- private currentAccount?: AccountToken;
- private expiresAt?: Date;
- private fetchAttempt = 0;
- private fetchRetryTimeout?: NodeJS.Timeout;
- private watchers: IAccountFetchWatcher[] = [];
-
- constructor(
- private fetchHandler: (token: AccountToken) => Promise<IAccountData>,
- private updateHandler: (data?: IAccountData) => void,
- ) {}
-
- public fetch(accountToken: AccountToken, watcher?: IAccountFetchWatcher) {
- // invalidate cache if account token has changed
- if (accountToken !== this.currentAccount) {
- this.invalidate();
- this.currentAccount = accountToken;
- }
-
- // Only fetch is value has expired
- if (this.isExpired()) {
- if (watcher) {
- this.watchers.push(watcher);
- }
-
- this.performFetch(accountToken);
- } else if (watcher) {
- watcher.onFinish();
- }
- }
-
- public invalidate() {
- if (this.fetchRetryTimeout) {
- clearTimeout(this.fetchRetryTimeout);
- this.fetchRetryTimeout = undefined;
- this.fetchAttempt = 0;
- }
-
- this.expiresAt = undefined;
- this.updateHandler();
- this.notifyWatchers((watcher) => {
- watcher.onError(new Error('Cancelled'));
- });
- }
-
- private setValue(value: IAccountData) {
- this.expiresAt = new Date(Date.now() + 60 * 1000); // 60s expiration
- this.updateHandler(value);
- this.notifyWatchers((watcher) => watcher.onFinish());
- }
-
- private isExpired() {
- return !this.expiresAt || this.expiresAt < new Date();
- }
-
- private async performFetch(accountToken: AccountToken) {
- try {
- // it's possible for invalidate() to be called or for a fetch for a different account token
- // to start before this fetch completes, so checking if the current account token is the one
- // used is necessary below.
- const accountData = await this.fetchHandler(accountToken);
-
- if (this.currentAccount === accountToken) {
- this.setValue(accountData);
- }
- } catch (error) {
- if (this.currentAccount === accountToken) {
- this.handleFetchError(accountToken, error);
- }
- }
- }
-
- private handleFetchError(accountToken: AccountToken, error: any) {
- let shouldRetry = true;
-
- this.notifyWatchers((watcher) => {
- if (watcher.onError(error) === AccountFetchRetryAction.stop) {
- shouldRetry = false;
- }
- });
-
- if (shouldRetry) {
- this.scheduleRetry(accountToken);
- }
- }
-
- private scheduleRetry(accountToken: AccountToken) {
- this.fetchAttempt += 1;
-
- // tslint:disable-next-line
- const delay = Math.min(2048, 1 << (this.fetchAttempt + 2)) * 1000;
-
- log.warn(`Failed to fetch account data. Retrying in ${delay} ms`);
-
- this.fetchRetryTimeout = global.setTimeout(() => {
- this.fetchRetryTimeout = undefined;
- this.performFetch(accountToken);
- }, delay);
- }
-
- private notifyWatchers(notify: (watcher: IAccountFetchWatcher) => void) {
- this.watchers.splice(0).forEach(notify);
- }
-}
diff --git a/gui/src/renderer/lib/account-data-cache.ts b/gui/src/renderer/lib/account-data-cache.ts
new file mode 100644
index 0000000000..51154c9792
--- /dev/null
+++ b/gui/src/renderer/lib/account-data-cache.ts
@@ -0,0 +1,118 @@
+import log from 'electron-log';
+import { AccountToken, IAccountData } from '../../shared/daemon-rpc-types';
+
+export enum AccountFetchRetryAction {
+ stop,
+ retry,
+}
+interface IAccountFetchWatcher {
+ onFinish: () => void;
+ onError: (error: Error) => AccountFetchRetryAction;
+}
+
+// An account data cache that helps to throttle RPC requests to get_account_data and retain the
+// cached value for 1 minute.
+export default class AccountDataCache {
+ private currentAccount?: AccountToken;
+ private expiresAt?: Date;
+ private fetchAttempt = 0;
+ private fetchRetryTimeout?: NodeJS.Timeout;
+ private watchers: IAccountFetchWatcher[] = [];
+
+ constructor(
+ private fetchHandler: (token: AccountToken) => Promise<IAccountData>,
+ private updateHandler: (data?: IAccountData) => void,
+ ) {}
+
+ public fetch(accountToken: AccountToken, watcher?: IAccountFetchWatcher) {
+ // invalidate cache if account token has changed
+ if (accountToken !== this.currentAccount) {
+ this.invalidate();
+ this.currentAccount = accountToken;
+ }
+
+ // Only fetch is value has expired
+ if (this.isExpired()) {
+ if (watcher) {
+ this.watchers.push(watcher);
+ }
+
+ this.performFetch(accountToken);
+ } else if (watcher) {
+ watcher.onFinish();
+ }
+ }
+
+ public invalidate() {
+ if (this.fetchRetryTimeout) {
+ clearTimeout(this.fetchRetryTimeout);
+ this.fetchRetryTimeout = undefined;
+ this.fetchAttempt = 0;
+ }
+
+ this.expiresAt = undefined;
+ this.updateHandler();
+ this.notifyWatchers((watcher) => {
+ watcher.onError(new Error('Cancelled'));
+ });
+ }
+
+ private setValue(value: IAccountData) {
+ this.expiresAt = new Date(Date.now() + 60 * 1000); // 60s expiration
+ this.updateHandler(value);
+ this.notifyWatchers((watcher) => watcher.onFinish());
+ }
+
+ private isExpired() {
+ return !this.expiresAt || this.expiresAt < new Date();
+ }
+
+ private async performFetch(accountToken: AccountToken) {
+ try {
+ // it's possible for invalidate() to be called or for a fetch for a different account token
+ // to start before this fetch completes, so checking if the current account token is the one
+ // used is necessary below.
+ const accountData = await this.fetchHandler(accountToken);
+
+ if (this.currentAccount === accountToken) {
+ this.setValue(accountData);
+ }
+ } catch (error) {
+ if (this.currentAccount === accountToken) {
+ this.handleFetchError(accountToken, error);
+ }
+ }
+ }
+
+ private handleFetchError(accountToken: AccountToken, error: any) {
+ let shouldRetry = true;
+
+ this.notifyWatchers((watcher) => {
+ if (watcher.onError(error) === AccountFetchRetryAction.stop) {
+ shouldRetry = false;
+ }
+ });
+
+ if (shouldRetry) {
+ this.scheduleRetry(accountToken);
+ }
+ }
+
+ private scheduleRetry(accountToken: AccountToken) {
+ this.fetchAttempt += 1;
+
+ // tslint:disable-next-line
+ const delay = Math.min(2048, 1 << (this.fetchAttempt + 2)) * 1000;
+
+ log.warn(`Failed to fetch account data. Retrying in ${delay} ms`);
+
+ this.fetchRetryTimeout = global.setTimeout(() => {
+ this.fetchRetryTimeout = undefined;
+ this.performFetch(accountToken);
+ }, delay);
+ }
+
+ private notifyWatchers(notify: (watcher: IAccountFetchWatcher) => void) {
+ this.watchers.splice(0).forEach(notify);
+ }
+}
diff --git a/gui/src/renderer/lib/auth-failure.ts b/gui/src/renderer/lib/auth-failure.ts
index ae381d5677..6428d0f65a 100644
--- a/gui/src/renderer/lib/auth-failure.ts
+++ b/gui/src/renderer/lib/auth-failure.ts
@@ -18,8 +18,7 @@ export function parseAuthFailure(rawFailureMessage?: string): IAuthFailure {
if (results && results.length === 3) {
const kind = parseRawFailureKind(results[1]);
- const message =
- kind === AuthFailureKind.unknown ? rawFailureMessage : messageForFailureKind(kind);
+ const message = kind === AuthFailureKind.unknown ? results[2] : messageForFailureKind(kind);
return {
kind,
diff --git a/gui/test/account-data-cache.spec.ts b/gui/test/account-data-cache.spec.ts
index 33cb80e556..f9838a6dbb 100644
--- a/gui/test/account-data-cache.spec.ts
+++ b/gui/test/account-data-cache.spec.ts
@@ -1,16 +1,11 @@
-import { AccountDataCache } from '../src/renderer/app';
-import { AccountData } from '../src/shared/daemon-rpc-types';
+import AccountDataCache, { AccountFetchRetryAction } from '../src/renderer/lib/account-data-cache';
+import { IAccountData } from '../src/shared/daemon-rpc-types';
import * as sinon from 'sinon';
-import chai from 'chai';
-import spies from 'chai-spies';
-import chaiAsPromised from 'chai-as-promised';
-import { it, describe, beforeEach, afterEach } from 'mocha';
+import { expect, spy } from 'chai';
-const { expect, spy } = chai;
-
-describe('AccountData cache', () => {
+describe('IAccountData cache', () => {
const dummyAccountToken = '9876543210';
- const dummyAccountData: AccountData = {
+ const dummyAccountData: IAccountData = {
expiry: new Date('2038-01-01').toISOString(),
};
@@ -35,7 +30,7 @@ describe('AccountData cache', () => {
onFinish: () => resolve(),
onError: (_error: Error) => {
reject();
- return 'stop';
+ return AccountFetchRetryAction.stop;
},
});
});
@@ -54,7 +49,7 @@ describe('AccountData cache', () => {
onFinish: (_reason?: any) => resolve(),
onError: (_error: Error) => {
reject();
- return 'stop';
+ return AccountFetchRetryAction.stop;
},
});
});
@@ -70,7 +65,7 @@ describe('AccountData cache', () => {
onFinish: spy(),
onError: (_error: Error) => {
reject();
- return 'stop';
+ return AccountFetchRetryAction.stop;
},
});
});
@@ -96,7 +91,7 @@ describe('AccountData cache', () => {
cache.fetch(dummyAccountToken, {
onFinish: () => reject(),
- onError: spy((_error: Error) => 'retry'),
+ onError: spy((_error: Error) => AccountFetchRetryAction.retry),
});
});
@@ -123,7 +118,7 @@ describe('AccountData cache', () => {
cache.fetch(dummyAccountToken, {
onFinish: spy(),
- onError: spy((_error: Error) => 'stop'),
+ onError: spy((_error: Error) => AccountFetchRetryAction.stop),
});
});
@@ -131,12 +126,12 @@ describe('AccountData cache', () => {
});
it('should cancel first fetch', async () => {
- const firstError = spy((_) => 'stop');
+ const firstError = spy((_error: Error) => AccountFetchRetryAction.stop);
const secondSuccess = spy();
- const update = new Promise((resolve, reject) => {
+ const update = new Promise<IAccountData>((resolve, reject) => {
let firstAttempt = true;
- const fetch = () => {
+ const fetch = (_token: string) => {
if (firstAttempt) {
firstAttempt = false;
@@ -144,18 +139,22 @@ describe('AccountData cache', () => {
onFinish: secondSuccess,
onError: () => {
reject();
- return 'stop';
+ return AccountFetchRetryAction.stop;
},
});
- return new Promise((resolve) => setTimeout(() => resolve(dummyAccountData), 1000));
+ return new Promise<IAccountData>((resolve) => {
+ setTimeout(() => resolve(dummyAccountData), 1000);
+ });
} else {
reject();
return Promise.resolve(dummyAccountData);
}
};
- const cache = new AccountDataCache(fetch, () => resolve());
+ const cache = new AccountDataCache(fetch, (_accountData?: IAccountData) => {
+ resolve();
+ });
setTimeout(resolve, 12000);
diff --git a/gui/test/components/NotificationArea.spec.tsx b/gui/test/components/NotificationArea.spec.tsx
index 54aae1f8a6..c117db914e 100644
--- a/gui/test/components/NotificationArea.spec.tsx
+++ b/gui/test/components/NotificationArea.spec.tsx
@@ -2,7 +2,9 @@ import moment from 'moment';
import * as React from 'react';
import { shallow } from 'enzyme';
import NotificationArea from '../../src/renderer/components/NotificationArea';
+import { AfterDisconnect } from '../../src/shared/daemon-rpc-types';
import AccountExpiry from '../../src/renderer/lib/account-expiry';
+import { expect } from 'chai';
describe('components/NotificationArea', () => {
const defaultVersion = {
@@ -12,7 +14,6 @@ describe('components/NotificationArea', () => {
current: '2018.2',
latest: '2018.2-beta1',
latestStable: '2018.2',
- nextUpgrade: null,
};
const defaultExpiry = new AccountExpiry(
@@ -23,35 +24,90 @@ describe('components/NotificationArea', () => {
);
it('handles disconnecting state', () => {
- for (const reason of ['nothing', 'block', 'reconnect']) {
+ for (const reason of ['nothing', 'block'] as AfterDisconnect[]) {
const component = shallow(
<NotificationArea
tunnelState={{
state: 'disconnecting',
- details: { reason },
+ details: reason,
}}
version={defaultVersion}
accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
/>,
);
expect(component.state('visible')).to.be.false;
}
});
- it('handles connected or disconnected states', () => {
- for (const state of ['connected', 'disconnected']) {
- const component = shallow(
- <NotificationArea
- tunnelState={{
- state,
- }}
- version={defaultVersion}
- accountExpiry={defaultExpiry}
- />,
- );
+ it('handles disconnecting state when reconnecting', () => {
+ const component = shallow(
+ <NotificationArea
+ tunnelState={{
+ state: 'disconnecting',
+ details: 'reconnect',
+ }}
+ version={defaultVersion}
+ accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
+ />,
+ );
+ expect(component.state('visible')).to.be.true;
+ });
- expect(component.state('visible')).to.be.false;
- }
+ it('handles connected state', () => {
+ const component = shallow(
+ <NotificationArea
+ tunnelState={{
+ state: 'connected',
+ details: {
+ address: '1.2.3.4',
+ protocol: 'tcp',
+ tunnel: 'openvpn',
+ },
+ }}
+ version={defaultVersion}
+ accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
+ />,
+ );
+
+ expect(component.state('visible')).to.be.false;
+ });
+
+ it('handles disconnected state', () => {
+ const component = shallow(
+ <NotificationArea
+ tunnelState={{
+ state: 'disconnected',
+ }}
+ version={defaultVersion}
+ accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
+ />,
+ );
+
+ expect(component.state('visible')).to.be.false;
+ });
+
+ it('handles disconnected state, blocking when connected', () => {
+ const component = shallow(
+ <NotificationArea
+ tunnelState={{
+ state: 'disconnected',
+ }}
+ version={defaultVersion}
+ accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={true}
+ />,
+ );
+
+ expect(component.state('visible')).to.be.true;
});
it('handles connecting state', () => {
@@ -62,6 +118,8 @@ describe('components/NotificationArea', () => {
}}
version={defaultVersion}
accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
/>,
);
@@ -80,6 +138,8 @@ describe('components/NotificationArea', () => {
}}
version={defaultVersion}
accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
/>,
);
@@ -98,6 +158,8 @@ describe('components/NotificationArea', () => {
consistent: false,
}}
accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
/>,
);
@@ -119,6 +181,8 @@ describe('components/NotificationArea', () => {
nextUpgrade: '2018.2',
}}
accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
/>,
);
@@ -141,6 +205,8 @@ describe('components/NotificationArea', () => {
nextUpgrade: '2018.3',
}}
accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
/>,
);
@@ -164,6 +230,8 @@ describe('components/NotificationArea', () => {
nextUpgrade: '2018.4-beta3',
}}
accountExpiry={defaultExpiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
/>,
);
@@ -186,6 +254,8 @@ describe('components/NotificationArea', () => {
}}
version={defaultVersion}
accountExpiry={expiry}
+ openExternalLink={() => {}}
+ blockWhenDisconnected={false}
/>,
);
diff --git a/gui/test/jsonrpc-transport.spec.ts b/gui/test/jsonrpc-transport.spec.ts
index 7e4e56c80a..26d812b793 100644
--- a/gui/test/jsonrpc-transport.spec.ts
+++ b/gui/test/jsonrpc-transport.spec.ts
@@ -1,56 +1,49 @@
import { expect } from 'chai';
-import { it, describe, beforeEach, afterEach } from 'mocha';
+import { it, describe, beforeEach } from 'mocha';
import jsonrpc from 'jsonrpc-lite';
-import { Server } from 'mock-socket';
-import JsonRpcClient, { WebsocketTransport, TimeOutError } from '../src/main/jsonrpc-client';
+import JsonRpcClient, { ITransport, TimeOutError } from '../src/main/jsonrpc-client';
describe('JSON RPC transport', () => {
- const WEBSOCKET_URL = 'ws://localhost:8080';
- let server: Server, transport: JsonRpcClient<string>;
+ let client: JsonRpcClient<string>, transport: MockTransport;
beforeEach(() => {
- server = new Server(WEBSOCKET_URL);
- transport = new JsonRpcClient(new WebsocketTransport());
- });
-
- afterEach(() => {
- server.close();
+ transport = new MockTransport();
+ client = new JsonRpcClient(transport);
+ return client.connect('');
});
it('should reject failed jsonrpc requests', async () => {
- server.on('connection', (socket) => {
- socket.on('message', (msg) => {
- const { payload } = jsonrpc.parse(msg);
- if (payload.method === 'invalid-method') {
- socket.send(
- JSON.stringify(
- jsonrpc.error(payload.id, new jsonrpc.JsonRpcError('Method not found', -32601)),
+ transport.onServerMessage = (msg) => {
+ const parsedMessage = jsonrpc.parseObject(msg);
+
+ if (parsedMessage.type === 'request' && parsedMessage.payload.method === 'invalid-method') {
+ transport.reply(
+ JSON.stringify(
+ jsonrpc.error(
+ parsedMessage.payload.id,
+ new jsonrpc.JsonRpcError('Method not found', -32601),
),
- );
- }
- });
- });
+ ),
+ );
+ }
+ };
- await transport.connect(WEBSOCKET_URL);
- const sendPromise = transport.send('invalid-method');
+ const sendPromise = client.send('invalid-method');
return expect(sendPromise).to.eventually.be.rejectedWith('Method not found');
});
it('should route reply to correct promise', async () => {
- server.on('connection', (socket) => {
- socket.on('message', (msg) => {
- const { payload } = jsonrpc.parse(msg);
- if (payload.method === 'a message') {
- socket.send(JSON.stringify(jsonrpc.success(payload.id, 'a reply')));
- }
- });
- });
+ transport.onServerMessage = (msg) => {
+ const parsedMessage = jsonrpc.parseObject(msg);
- await transport.connect(WEBSOCKET_URL);
+ if (parsedMessage.type === 'request' && parsedMessage.payload.method === 'a message') {
+ transport.reply(JSON.stringify(jsonrpc.success(parsedMessage.payload.id, 'a reply')));
+ }
+ };
- const decoyPromise = transport.send('a decoy', [], 100);
- const messagePromise = transport.send('a message', [], 100);
+ const decoyPromise = client.send('a decoy', [], 100);
+ const messagePromise = client.send('a message', [], 100);
return Promise.all([
expect(messagePromise).to.eventually.be.equal('a reply'),
@@ -59,26 +52,22 @@ describe('JSON RPC transport', () => {
});
it('should timeout if no response is returned', async () => {
- await transport.connect(WEBSOCKET_URL);
- const sendPromise = transport.send('timeout-message', {}, 1);
+ const sendPromise = client.send('timeout-message', {}, 1);
return expect(sendPromise).to.eventually.be.rejectedWith(TimeOutError, 'Request timed out');
});
it('should route notifications', async () => {
- server.on('connection', (socket) => {
- socket.on('message', (msg) => {
- const { payload } = jsonrpc.parse(msg);
- if (payload.method === 'event_subscribe') {
- socket.send(JSON.stringify(jsonrpc.success(payload.id, 1)));
- }
- });
- });
+ transport.onServerMessage = (msg) => {
+ const parsedMessage = jsonrpc.parseObject(msg);
- await transport.connect(WEBSOCKET_URL);
+ if (parsedMessage.type === 'request' && parsedMessage.payload.method === 'event_subscribe') {
+ transport.reply(JSON.stringify(jsonrpc.success(parsedMessage.payload.id, 1)));
+ }
+ };
const eventPromiseHelper = (() => {
- let borrowedResolve: (param: any) => void | undefined = undefined;
+ let borrowedResolve: ((param: any) => void) | undefined = undefined;
const promise = new Promise((resolve) => (borrowedResolve = resolve));
/* Flow does not understand that the body of Promise runs immediately.
see https://github.com/facebook/flow/issues/6711 */
@@ -91,13 +80,43 @@ describe('JSON RPC transport', () => {
};
})();
- await transport.subscribe('event', eventPromiseHelper.resolve);
+ await client.subscribe('event', eventPromiseHelper.resolve);
- server.emit(
- 'message',
+ transport.reply(
JSON.stringify(jsonrpc.notification('event', { subscription: 1, result: 'beacon' })),
);
return expect(eventPromiseHelper.promise).to.eventually.be.equal('beacon');
});
});
+
+class MockTransport implements ITransport<string> {
+ public onOpen = () => {
+ // no-op
+ };
+ public onMessage = (_message: object) => {
+ // no-op
+ };
+ public onServerMessage = (_message: object) => {
+ // no-op
+ };
+ public onClose = (_error?: Error) => {
+ // no-op
+ };
+
+ public close() {
+ this.onClose();
+ }
+
+ public send(msg: string) {
+ this.onServerMessage(JSON.parse(msg));
+ }
+
+ public reply(msg: string) {
+ this.onMessage(JSON.parse(msg));
+ }
+
+ public connect(_params: string) {
+ this.onOpen();
+ }
+}
diff --git a/gui/test/keyframe-animation.spec.ts b/gui/test/keyframe-animation.spec.ts
index 05efa6ad76..11eeaf0c54 100644
--- a/gui/test/keyframe-animation.spec.ts
+++ b/gui/test/keyframe-animation.spec.ts
@@ -19,7 +19,7 @@ describe('lib/keyframe-animation', function() {
};
animation.onFinish = () => {
expect(seq).to.be.deep.equal([0, 1, 2, 3, 4]);
- expect(animation._currentFrame).to.be.equal(4);
+ expect(animation.currentFrame).to.be.equal(4);
done();
};
@@ -34,7 +34,7 @@ describe('lib/keyframe-animation', function() {
};
animation.onFinish = () => {
expect(seq).to.be.deep.equal([3]);
- expect(animation._currentFrame).to.be.equal(3);
+ expect(animation.currentFrame).to.be.equal(3);
done();
};
@@ -49,7 +49,7 @@ describe('lib/keyframe-animation', function() {
};
animation.onFinish = () => {
expect(seq).to.be.deep.equal([2, 3, 4]);
- expect(animation._currentFrame).to.be.equal(4);
+ expect(animation.currentFrame).to.be.equal(4);
done();
};
@@ -64,7 +64,7 @@ describe('lib/keyframe-animation', function() {
};
animation.onFinish = () => {
expect(seq).to.be.deep.equal([4, 3, 2]);
- expect(animation._currentFrame).to.be.equal(2);
+ expect(animation.currentFrame).to.be.equal(2);
done();
};
@@ -79,11 +79,11 @@ describe('lib/keyframe-animation', function() {
};
animation.onFinish = () => {
expect(seq).to.be.deep.equal([0, 1, 2, 3, 4]);
- expect(animation._currentFrame).to.be.equal(4);
+ expect(animation.currentFrame).to.be.equal(4);
done();
};
- animation._currentFrame = 0;
+ animation.currentFrame = 0;
animation.play({ end: 4 });
});
@@ -95,11 +95,11 @@ describe('lib/keyframe-animation', function() {
};
animation.onFinish = () => {
expect(seq).to.be.deep.equal([4, 3, 2]);
- expect(animation._currentFrame).to.be.equal(2);
+ expect(animation.currentFrame).to.be.equal(2);
done();
};
- animation._currentFrame = 4;
+ animation.currentFrame = 4;
animation.play({ end: 2 });
});
@@ -111,11 +111,11 @@ describe('lib/keyframe-animation', function() {
};
animation.onFinish = () => {
expect(seq).to.be.deep.equal([4, 3, 2, 1]);
- expect(animation._currentFrame).to.be.equal(1);
+ expect(animation.currentFrame).to.be.equal(1);
done();
};
- animation._currentFrame = 4;
+ animation.currentFrame = 4;
animation.play({ end: 1 });
});
@@ -127,7 +127,7 @@ describe('lib/keyframe-animation', function() {
};
animation.onFinish = () => {
expect(seq).to.be.deep.equal([4, 3, 2, 1, 0]);
- expect(animation._currentFrame).to.be.equal(0);
+ expect(animation.currentFrame).to.be.equal(0);
done();
};
diff --git a/gui/test/setup/main.ts b/gui/test/setup/main.js
index 9b6ecc5ef7..dd458a30b6 100644
--- a/gui/test/setup/main.ts
+++ b/gui/test/setup/main.js
@@ -1,4 +1,4 @@
-import log from 'electron-log';
+const log = require('electron-log');
log.transports.console.level = false;
log.transports.file.level = false;
diff --git a/gui/yarn.lock b/gui/yarn.lock
index d4d0c9cf86..d4d6650fb2 100644
--- a/gui/yarn.lock
+++ b/gui/yarn.lock
@@ -3067,10 +3067,10 @@ jsonparse@^1.2.0:
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
-jsonrpc-lite@^2.0.1:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/jsonrpc-lite/-/jsonrpc-lite-2.0.4.tgz#a8f8e9db2830d1a383d21ee97bd259c3321cff25"
- integrity sha512-vr78eFnTrluTljM3lEydW9qDclgfGGpRoRDe1y/0i650oSx5fiTefW3PwDMUQ/+uJN76wG7yGPwTh0BVFaBP4Q==
+jsonrpc-lite@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/jsonrpc-lite/-/jsonrpc-lite-2.0.5.tgz#00d60b5ab0f1a81e19f7806ca9c236dad66bb5ff"
+ integrity sha512-3+WESfAxrlU7//u8qS+UeKYjkeTxcQuGwASwOwNdAjWU9lFCvo1/2yceKE2IXDPpL1YIDLZyBshEcu6cfTMZaA==
jsprim@^1.2.2:
version "1.4.1"
@@ -3474,13 +3474,6 @@ mocha@^5.2.0:
mkdirp "0.5.1"
supports-color "5.4.0"
-mock-socket@^8.0.5:
- version "8.0.5"
- resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-8.0.5.tgz#4ce8909601b2bcdf5f7680f35c2f7b34beb2afc4"
- integrity sha512-dE2EbcxJKQCeYLZSsI7BAiMZCe/bHbJ2LHb5aGwUuDmfoOINEJ8QI6qYJ85NHsSNkNa90F3s6onZcmt/+MppFA==
- dependencies:
- url-parse "^1.2.0"
-
moment@^2.24.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
@@ -4254,11 +4247,6 @@ qs@~6.5.2:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
-querystringify@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755"
- integrity sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==
-
quickselect@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-1.1.1.tgz#852e412ce418f237ad5b660d70cffac647ae94c2"
@@ -4628,7 +4616,7 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
-requires-port@1.x.x, requires-port@^1.0.0:
+requires-port@1.x.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
@@ -5587,14 +5575,6 @@ url-parse-lax@^1.0.0:
dependencies:
prepend-http "^1.0.1"
-url-parse@^1.2.0:
- version "1.4.3"
- resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.3.tgz#bfaee455c889023219d757e045fa6a684ec36c15"
- integrity sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==
- dependencies:
- querystringify "^2.0.0"
- requires-port "^1.0.0"
-
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"