summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@codeispoetry.ru>2017-07-21 10:14:51 +0100
committerAndrej Mihajlov <and@codeispoetry.ru>2017-07-21 10:14:51 +0100
commitfc7fbd09cb4cc77f56f8ab15af4e1f2669f385a7 (patch)
tree5dd213beeea22d9c6f29297f7d515e26efc784bb
parentbe75990c6ebdd7f0ee356775ecd017b6e3bc0fc0 (diff)
parent14eb55140257e70242ac8aa7319c75dc62e1bd64 (diff)
downloadmullvadvpn-fc7fbd09cb4cc77f56f8ab15af4e1f2669f385a7.tar.xz
mullvadvpn-fc7fbd09cb4cc77f56f8ab15af4e1f2669f385a7.zip
Merge branch 'settings-tests'
-rw-r--r--app/components/AccountInput.js5
-rw-r--r--app/components/Settings.js32
-rw-r--r--app/components/Switch.js8
-rw-r--r--app/components/WindowChrome.js3
-rw-r--r--package.json2
-rw-r--r--test/components/SelectLocation.spec.js2
-rw-r--r--test/components/Settings.spec.js166
-rw-r--r--yarn.lock45
8 files changed, 225 insertions, 38 deletions
diff --git a/app/components/AccountInput.js b/app/components/AccountInput.js
index dc61aeffc9..1759423437 100644
--- a/app/components/AccountInput.js
+++ b/app/components/AccountInput.js
@@ -5,8 +5,7 @@ import { formatAccount } from '../lib/formatters';
// @TODO: move it into types.js
// ESLint issue: https://github.com/babel/babel-eslint/issues/445
-/* eslint-disable no-unused-vars */
-declare class ClipboardData {
+declare class ClipboardData { // eslint-disable-line no-unused-vars
setData(type: string, data: string): void;
getData(type: string): string;
}
@@ -77,7 +76,7 @@ export default class AccountInput extends Component {
render(): React.Element<*> {
const displayString = formatAccount(this.state.value || '');
- const { value, onChange, onEnter, ...otherProps } = this.props;
+ const { value, onChange, onEnter, ...otherProps } = this.props; // eslint-disable-line no-unused-vars
return (
<input { ...otherProps }
type="text"
diff --git a/app/components/Settings.js b/app/components/Settings.js
index ade9dba8f5..df6947a258 100644
--- a/app/components/Settings.js
+++ b/app/components/Settings.js
@@ -58,9 +58,9 @@ export default class Settings extends Component {
{ /* show account options when logged in */ }
<If condition={ isLoggedIn }>
<Then>
- <div>
+ <div className="settings__account">
- <div className="settings__cell settings__cell--active" onClick={ this.props.onViewAccount }>
+ <div className="settings__view-account settings__cell settings__cell--active" onClick={ this.props.onViewAccount }>
<div className="settings__cell-label">Account</div>
<div className="settings__cell-value">
<If condition={ isOutOfTime }>
@@ -79,7 +79,7 @@ export default class Settings extends Component {
<div className="settings__cell">
<div className="settings__cell-label">Auto-connect</div>
<div className="settings__cell-value">
- <Switch onChange={ this.onAutoSecure } isOn={ this.props.settings.autoSecure } />
+ <Switch className="settings__autosecure" onChange={ this.onAutoSecure } isOn={ this.props.settings.autoSecure } />
</div>
</div>
<div className="settings__cell-footer">
@@ -89,22 +89,24 @@ export default class Settings extends Component {
</Then>
</If>
- <div className="settings__cell settings__cell--active" onClick={ this.onExternalLink.bind(this, 'faq') }>
- <div className="settings__cell-label">FAQs</div>
- <img className="settings__cell-icon" src="./assets/images/icon-extLink.svg" />
- </div>
- <div className="settings__cell settings__cell--active" onClick={ this.onExternalLink.bind(this, 'guides') }>
- <div className="settings__cell-label">Guides</div>
- <img className="settings__cell-icon" src="./assets/images/icon-extLink.svg" />
- </div>
- <div className="settings__cell settings__cell--active" onClick={ this.onExternalLink.bind(this, 'supportEmail') }>
- <div className="settings__cell-label">Contact support</div>
- <img className="settings__cell-icon" src="./assets/images/icon-email.svg" />
+ <div className="settings__external">
+ <div className="settings__cell settings__cell--active" onClick={ this.onExternalLink.bind(this, 'faq') }>
+ <div className="settings__cell-label">FAQs</div>
+ <img className="settings__cell-icon" src="./assets/images/icon-extLink.svg" />
+ </div>
+ <div className="settings__cell settings__cell--active" onClick={ this.onExternalLink.bind(this, 'guides') }>
+ <div className="settings__cell-label">Guides</div>
+ <img className="settings__cell-icon" src="./assets/images/icon-extLink.svg" />
+ </div>
+ <div className="settings__cell settings__cell--active" onClick={ this.onExternalLink.bind(this, 'supportEmail') }>
+ <div className="settings__cell-label">Contact support</div>
+ <img className="settings__cell-icon" src="./assets/images/icon-email.svg" />
+ </div>
</div>
</div>
<div className="settings__footer">
- <button className="button button--negative" onClick={ this.props.onQuit }>Quit app</button>
+ <button className="settings__quit button button--negative" onClick={ this.props.onQuit }>Quit app</button>
</div>
</div>
diff --git a/app/components/Switch.js b/app/components/Switch.js
index c278cfbd2c..4f97c4d992 100644
--- a/app/components/Switch.js
+++ b/app/components/Switch.js
@@ -120,8 +120,14 @@ export default class Switch extends Component {
}
render(): React.Element<*> {
+ const { isOn, onChange, ...otherProps } = this.props; // eslint-disable-line no-unused-vars
+ let className = ('switch' + ' ' + (otherProps.className || '')).trim();
return (
- <input type="checkbox" ref={ this.onRef } className="switch" checked={ this.props.isOn }
+ <input { ...otherProps }
+ type="checkbox"
+ ref={ this.onRef }
+ className={ className }
+ checked={ isOn }
onMouseDown={ this.handleMouseDown }
onChange={ this.handleChange } />
);
diff --git a/app/components/WindowChrome.js b/app/components/WindowChrome.js
index 141c99fdb4..d7094813db 100644
--- a/app/components/WindowChrome.js
+++ b/app/components/WindowChrome.js
@@ -5,8 +5,7 @@ export default class WindowChrome extends Component {
props: {
children: Array<React.Element<*>> | React.Element<*>
}
-
- render() {
+ render(): React.Element<*> {
const chromeClass = ['window-chrome', 'window-chrome--' + process.platform];
return (
<div className={ chromeClass.join(' ') }>
diff --git a/package.json b/package.json
index 772023a02d..832f3057d0 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
"babel-preset-react": "^6.22.0",
"babel-preset-stage-0": "^6.1.18",
"browser-sync": "^2.9.3",
- "chai": "^3.4.1",
+ "chai": "^4.1.0",
"electron": "^1.6.11",
"electron-builder": "^19.12.0",
"electron-devtools-installer": "^2.1.0",
diff --git a/test/components/SelectLocation.spec.js b/test/components/SelectLocation.spec.js
index f25338dc47..28e21eb697 100644
--- a/test/components/SelectLocation.spec.js
+++ b/test/components/SelectLocation.spec.js
@@ -9,7 +9,7 @@ import { defaultServer } from '../../app/config';
import type { SettingsReduxState } from '../../app/redux/settings/reducers';
import type { SelectLocationProps } from '../../app/components/SelectLocation';
-describe('components/Account', () => {
+describe('components/SelectLocation', () => {
const state: SettingsReduxState = {
autoSecure: true,
preferredServer: defaultServer
diff --git a/test/components/Settings.spec.js b/test/components/Settings.spec.js
new file mode 100644
index 0000000000..705da60098
--- /dev/null
+++ b/test/components/Settings.spec.js
@@ -0,0 +1,166 @@
+// @flow
+
+import { expect } from 'chai';
+import React from 'react';
+import ReactTestUtils, { Simulate } from 'react-dom/test-utils';
+import Settings from '../../app/components/Settings';
+import { defaultServer } from '../../app/config';
+
+import type { AccountReduxState } from '../../app/redux/account/reducers';
+import type { SettingsReduxState } from '../../app/redux/settings/reducers';
+import type { SettingsProps } from '../../app/components/Settings';
+
+describe('components/Settings', () => {
+ const loggedOutAccountState: AccountReduxState = {
+ accountNumber: null,
+ paidUntil: null,
+ status: 'none',
+ error: null
+ };
+
+ const loggedInAccountState: AccountReduxState = {
+ accountNumber: '1234',
+ paidUntil: (new Date('2038-01-01')).toISOString(),
+ status: 'ok',
+ error: null
+ };
+
+ const unpaidAccountState: AccountReduxState = {
+ accountNumber: '1234',
+ paidUntil: (new Date('2001-01-01')).toISOString(),
+ status: 'ok',
+ error: null
+ };
+
+ const settingsState: SettingsReduxState = {
+ autoSecure: true,
+ preferredServer: defaultServer
+ };
+
+ const makeProps = (anAccountState: AccountReduxState, aSettingsState: SettingsReduxState, mergeProps: $Shape<SettingsProps> = {}): SettingsProps => {
+ const defaultProps: SettingsProps = {
+ account: anAccountState,
+ settings: aSettingsState,
+ onQuit: () => {},
+ onClose: () => {},
+ onViewAccount: () => {},
+ onExternalLink: (_type) => {},
+ onUpdateSettings: (_update) => {}
+ };
+ return Object.assign({}, defaultProps, mergeProps);
+ };
+
+ const render = (props: SettingsProps): Settings => {
+ return ReactTestUtils.renderIntoDocument(
+ <Settings { ...props } />
+ );
+ };
+
+ it('should show quit button when logged out', () => {
+ const props = makeProps(loggedOutAccountState, settingsState);
+ ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__quit');
+ });
+
+ it('should show quit button when logged in', () => {
+ const props = makeProps(loggedInAccountState, settingsState);
+ ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__quit');
+ });
+
+ it('should show external links when logged out', () => {
+ const props = makeProps(loggedOutAccountState, settingsState);
+ ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__external');
+ });
+
+ it('should show external links when logged in', () => {
+ const props = makeProps(loggedInAccountState, settingsState);
+ ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__external');
+ });
+
+ it('should show account section when logged in', () => {
+ const props = makeProps(loggedInAccountState, settingsState);
+ ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__account');
+ });
+
+ it('should hide account section when logged out', () => {
+ const props = makeProps(loggedOutAccountState, settingsState);
+ const elements = ReactTestUtils.scryRenderedDOMComponentsWithClass(render(props), 'settings__account');
+ expect(elements).to.be.empty;
+ });
+
+ it('should hide account link when not logged in', () => {
+ const props = makeProps(loggedOutAccountState, settingsState);
+ const elements = ReactTestUtils.scryRenderedDOMComponentsWithClass(render(props), 'settings__view-account');
+ expect(elements).to.be.empty;
+ });
+
+ it('should show out-of-time message for unpaid account', () => {
+ const props = makeProps(unpaidAccountState, settingsState);
+ const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__account-paid-until-label');
+ expect(domNode.textContent).to.contain('OUT OF TIME');
+ });
+
+ it('should hide out-of-time message for paid account', () => {
+ const props = makeProps(loggedInAccountState, settingsState);
+ const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__account-paid-until-label');
+ expect(domNode.textContent).to.not.contain('OUT OF TIME');
+ });
+
+ it('should call close callback', (done) => {
+ const props = makeProps(loggedOutAccountState, settingsState, {
+ onClose: () => done()
+ });
+ const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__close');
+ Simulate.click(domNode);
+ });
+
+ it('should call quit callback', (done) => {
+ const props = makeProps(loggedOutAccountState, settingsState, {
+ onQuit: () => done()
+ });
+ const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__quit');
+ Simulate.click(domNode);
+ });
+
+ it('should call account callback', (done) => {
+ const props = makeProps(loggedInAccountState, settingsState, {
+ onViewAccount: () => done()
+ });
+ const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__view-account');
+ Simulate.click(domNode);
+ });
+
+ it('should call update callback', (done) => {
+ const props = makeProps(loggedInAccountState, settingsState, {
+ onUpdateSettings: (update) => {
+ try {
+ expect(update).to.include({ autoSecure: false });
+ done();
+ } catch(e) {
+ done(e);
+ }
+ }
+ });
+ const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__autosecure');
+
+ // TODO(Andrej): Add click handler to Switch to avoid calling that horrible chain of events.
+ Simulate.mouseDown(domNode);
+ Simulate.mouseUp(domNode);
+ Simulate.change(domNode, { target: { checked: false } });
+ });
+
+ it('should call external links callback', () => {
+ let collectedExternalLinkTypes: Array<string> = [];
+ const props = makeProps(loggedOutAccountState, settingsState, {
+ onExternalLink: (type) => {
+ collectedExternalLinkTypes.push(type);
+ }
+ });
+ const container = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__external');
+ Array.from(container.childNodes)
+ .filter((elm: HTMLElement) => elm.classList.contains('settings__cell'))
+ .forEach((elm) => Simulate.click(elm));
+
+ expect(collectedExternalLinkTypes).to.include.ordered.members(['faq', 'guides', 'supportEmail']);
+ });
+
+});
diff --git a/yarn.lock b/yarn.lock
index 891d85459a..64501d8c43 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1370,13 +1370,16 @@ caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
-chai@^3.4.1:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247"
+chai@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.0.tgz#331a0391b55c3af8740ae9c3b7458bc1c3805e6d"
dependencies:
assertion-error "^1.0.1"
- deep-eql "^0.1.3"
- type-detect "^1.0.0"
+ check-error "^1.0.1"
+ deep-eql "^2.0.1"
+ get-func-name "^2.0.0"
+ pathval "^1.0.0"
+ type-detect "^4.0.0"
chain-function@^1.0.0:
version "1.0.0"
@@ -1414,6 +1417,10 @@ cheap-ruler@^2.4.1:
version "2.5.0"
resolved "https://registry.yarnpkg.com/cheap-ruler/-/cheap-ruler-2.5.0.tgz#776df3968090d9f36dfb34569fc6d7043b1a3aab"
+check-error@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
+
cheerio@0.20.0:
version "0.20.0"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.20.0.tgz#5c710f2bab95653272842ba01c6ea61b3545ec35"
@@ -1786,11 +1793,11 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
-deep-eql@^0.1.3:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2"
+deep-eql@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-2.0.2.tgz#b1bac06e56f0a76777686d50c9feb75c2ed7679a"
dependencies:
- type-detect "0.1.1"
+ type-detect "^3.0.0"
deep-equal@^1.0.0, deep-equal@^1.0.1:
version "1.0.1"
@@ -2785,6 +2792,10 @@ get-caller-file@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
+get-func-name@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
+
get-stdin@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
@@ -4351,6 +4362,10 @@ path-type@^2.0.0:
dependencies:
pify "^2.0.0"
+pathval@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
+
pause-stream@0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
@@ -5602,13 +5617,13 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
-type-detect@0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822"
+type-detect@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-3.0.0.tgz#46d0cc8553abb7b13a352b0d6dea2fd58f2d9b55"
-type-detect@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2"
+type-detect@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.3.tgz#0e3f2670b44099b0b46c284d136a7ef49c74c2ea"
typedarray@^0.0.6:
version "0.0.6"