diff options
| author | Andrej Mihajlov <and@codeispoetry.ru> | 2017-07-21 10:14:51 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@codeispoetry.ru> | 2017-07-21 10:14:51 +0100 |
| commit | fc7fbd09cb4cc77f56f8ab15af4e1f2669f385a7 (patch) | |
| tree | 5dd213beeea22d9c6f29297f7d515e26efc784bb | |
| parent | be75990c6ebdd7f0ee356775ecd017b6e3bc0fc0 (diff) | |
| parent | 14eb55140257e70242ac8aa7319c75dc62e1bd64 (diff) | |
| download | mullvadvpn-fc7fbd09cb4cc77f56f8ab15af4e1f2669f385a7.tar.xz mullvadvpn-fc7fbd09cb4cc77f56f8ab15af4e1f2669f385a7.zip | |
Merge branch 'settings-tests'
| -rw-r--r-- | app/components/AccountInput.js | 5 | ||||
| -rw-r--r-- | app/components/Settings.js | 32 | ||||
| -rw-r--r-- | app/components/Switch.js | 8 | ||||
| -rw-r--r-- | app/components/WindowChrome.js | 3 | ||||
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | test/components/SelectLocation.spec.js | 2 | ||||
| -rw-r--r-- | test/components/Settings.spec.js | 166 | ||||
| -rw-r--r-- | yarn.lock | 45 |
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']); + }); + +}); @@ -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" |
