diff options
| author | Erik Larkö <erik@mullvad.net> | 2017-12-27 10:44:40 +0100 |
|---|---|---|
| committer | Erik Larkö <erik@mullvad.net> | 2017-12-27 10:44:40 +0100 |
| commit | 40ef5effa822a2681117fdf105f091866a3351cb (patch) | |
| tree | 0b216fb938d93c1713c0c6887a1ebcf296891cec | |
| parent | 44b813ea88ba30f35e58df196945f76ddf34000b (diff) | |
| parent | 185fb6f3cfc081b31d0e3c78d6e338854a61e299 (diff) | |
| download | mullvadvpn-40ef5effa822a2681117fdf105f091866a3351cb.tar.xz mullvadvpn-40ef5effa822a2681117fdf105f091866a3351cb.zip | |
Merge branch 'reactxp-settings'
| -rw-r--r-- | app/assets/css/style.css | 1 | ||||
| -rw-r--r-- | app/components/CustomScrollbars.css | 3 | ||||
| -rw-r--r-- | app/components/Settings.css | 125 | ||||
| -rw-r--r-- | app/components/Settings.js | 157 | ||||
| -rw-r--r-- | app/components/SettingsStyles.js | 121 | ||||
| -rw-r--r-- | app/lib/styles.js | 17 | ||||
| -rw-r--r-- | test/components/Settings.spec.js | 78 |
7 files changed, 267 insertions, 235 deletions
diff --git a/app/assets/css/style.css b/app/assets/css/style.css index eb4353b67e..d3da3124a0 100644 --- a/app/assets/css/style.css +++ b/app/assets/css/style.css @@ -10,7 +10,6 @@ @import '../../components/CustomScrollbars.css'; @import '../../components/Login.css'; @import '../../components/Connect.css'; -@import '../../components/Settings.css'; @import '../../components/AdvancedSettings.css'; @import '../../components/Account.css'; @import '../../components/Support.css'; diff --git a/app/components/CustomScrollbars.css b/app/components/CustomScrollbars.css index fb00d1b7cb..4604b1e413 100644 --- a/app/components/CustomScrollbars.css +++ b/app/components/CustomScrollbars.css @@ -20,10 +20,11 @@ transition: height 0.25s ease-in-out, opacity 0.25s ease-in-out; pointer-events: none; opacity: 0; + z-index: 99; } .custom-scrollbars__thumb--visible { /* thumb appears without animation */ transition: height 0.25s ease-in-out; opacity: 1; -}
\ No newline at end of file +} diff --git a/app/components/Settings.css b/app/components/Settings.css deleted file mode 100644 index 9e49c814f9..0000000000 --- a/app/components/Settings.css +++ /dev/null @@ -1,125 +0,0 @@ -.settings { - background: #192E45; - height: 100%; -} - -.settings__container { - display: flex; - flex-direction: column; - height: 100%; -} - -.settings__header { - flex: 0 0 auto; - padding: 40px 24px 24px; - position: relative; /* anchor for close button */ -} - -.settings__content { - flex: 1 1 auto; - display: flex; - flex-direction: column; - justify-content: space-between; -} - -.settings__close { - position: absolute; - display: block; - border: 0; - padding: 0; - margin: 0; - width: 24px; - height: 24px; - top: 24px; - left: 12px; - background-color: transparent; - background-image: url(../assets/images/icon-close.svg); - opacity: 0.6; - z-index: 1; /* part of .settings__container covers the button */ -} - -.settings__title { - font-family: DINPro; - font-size: 32px; - font-weight: 900; - line-height: 40px; - color: #FFFFFF; -} - -.settings__cell { - background-color:rgba(41,71,115,1); - padding: 15px 24px; - display: flex; - flex-direction: row; - align-items: center; -} - -.settings__cell-disclosure { - display: block; - margin-left: 8px; - color: rgba(255, 255, 255, 0.8); -} - -.settings__cell-spacer { - height: 24px; -} - -.settings__cell--selected, -.settings__cell--selected:hover { - background-color: #44AD4D; -} - -.settings__cell--active:hover { - background-color: rgba(41,71,115,0.9); -} - -.settings__cell + .settings__cell { - margin-top: 1px; -} - -.settings__cell-label { - font-family: DINPro; - font-size: 20px; - font-weight: 900; - line-height: 26px; - color: #FFFFFF; - flex: 1 0 auto; -} - -.settings__cell-icon { - width: 16px; - flex: 0 0 auto; - color: rgba(255, 255, 255, 0.8); -} - -.settings__cell-value { - flex: 0 0 auto; -} - -.settings__account-paid-until-label { - font-family: "Open Sans"; - font-size: 13px; - font-weight: 800; - line-height: 26px; /* matches .cell-label */ - color: rgba(255, 255, 255, 0.8); - text-transform: uppercase; -} - -.settings__account-paid-until-label--error { - color: #d0021b; -} - -.settings__cell-footer { - padding: 8px 24px 24px; - font-family: "Open Sans"; - font-size: 13px; - font-weight: 600; - line-height: 20px; - color: rgba(255,255,255,0.8); -} - -.settings__footer { - padding: 24px; -} - -.settings__footer .button + .button { margin-top: 16px; } diff --git a/app/components/Settings.js b/app/components/Settings.js index 25a3e6df47..effd366ba7 100644 --- a/app/components/Settings.js +++ b/app/components/Settings.js @@ -1,9 +1,10 @@ // @flow import moment from 'moment'; -import React, { Component } from 'react'; -import { If, Then, Else } from 'react-if'; +import React from 'react'; +import { Component, Text, Button, View } from 'reactxp'; import { Layout, Container, Header } from './Layout'; import CustomScrollbars from './CustomScrollbars'; +import styles from './SettingsStyles'; import Img from './Img'; import type { AccountReduxState } from '../redux/account/reducers'; @@ -39,88 +40,92 @@ export default class Settings extends Component { <Layout> <Header hidden={ true } style={ 'defaultDark' } /> <Container> - <div className="settings"> - <button className="settings__close" onClick={ this.props.onClose } /> - <div className="settings__container"> - <div className="settings__header"> - <h2 className="settings__title">Settings</h2> - </div> + <View style={styles.settings}> + <Button style={styles.settings__close} onPress={ this.props.onClose } testName='settings__close'> + <Img style={styles.settings__close_icon} source='icon-close' tintColor='currentColor'/> + </Button> + + <View style={styles.settings__container}> + <View style={styles.settings__header}> + <Text style={styles.settings__title}>Settings</Text> + </View> + <CustomScrollbars autoHide={ true }> - <div className="settings__content"> - <div className="settings__main"> - { /* show account options when logged in */ } - <If condition={ isLoggedIn }> - <Then> - <div className="settings__account"> + <View style={styles.settings__content}> + <View style={styles.settings__main}> + {/* show account options when logged in */} + {isLoggedIn ? ( + <View style={styles.settings_account} testName='settings__account'> + <Button onPress={ this.props.onViewAccount } testName='settings__view_account'> + <View style={styles.settings__cell}> + <Text style={styles.settings__cell_label}>Account</Text> + <View style={styles.settings__cell_value}> + {isOutOfTime ? ( + <Text style={styles.settings__account_paid_until_label__error} testName='settings__account_paid_until_label'>OUT OF TIME</Text> + ) : ( + <Text style={styles.settings__account_paid_until_label} testName='settings__account_paid_until_label'>{formattedExpiry}</Text> + )} + </View> + {/* HERE */} + <Img style={styles.settings__cell_disclosure} source='icon-chevron'/> + </View> + </Button> + <View style={styles.settings__cell_spacer} /> + </View> + ) : null} + + {isLoggedIn ? ( + <Button onPress={ this.props.onViewAdvancedSettings }> + <View style={styles.settings__cell}> + <Text style={styles.settings__cell_label}>Advanced</Text> + <Img style={styles.settings__cell_disclosure} source='icon-chevron' tintColor='currentColor' /> + </View> + <View style={styles.settings__cell_spacer}></View> + </Button> + ) : null} - <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 }> - <Then> - <span className="settings__account-paid-until-label settings__account-paid-until-label--error">OUT OF TIME</span> - </Then> - <Else> - <span className="settings__account-paid-until-label">{ formattedExpiry }</span> - </Else> - </If> - </div> - <div className="settings__cell-disclosure"> - <Img source="icon-chevron" tintColor="currentColor" /> - </div> - </div> - <div className="settings__cell-spacer"></div> - </div> - </Then> - </If> + {isLoggedIn ? ( + <Button onPress={ this.props.onViewAdvancedSettings }> + <View style={styles.settings__cell}> + <Text style={styles.settings__cell_label}>Advanced</Text> + <Img style={styles.settings__cell_disclosure} source='icon-chevron' tintColor='currentColor'/> + </View> + <View style={styles.settings__cell_spacer}></View> + </Button> + ) : null} - <If condition={ isLoggedIn }> - <Then> - <div className="settings__advanced"> - <div className="settings__cell settings__cell--active" onClick={ this.props.onViewAdvancedSettings }> - <div className="settings__cell-label">Advanced</div> - <div className="settings__cell-value"> - <div className="settings__cell-disclosure"> - <Img source="icon-chevron" tintColor="currentColor" /> - </div> - </div> - </div> - <div className="settings__cell-spacer"></div> - </div> - </Then> - </If> + <Button onPress={ this.props.onExternalLink.bind(this, 'faq') } testName='settings__external_link'> + <View style={styles.settings__cell}> + <Text style={styles.settings__cell_label}>FAQs</Text> + <Img style={styles.settings__cell_icon} source='icon-extLink' tintColor='currentColor'/> + </View> + </Button> - <div className="settings__external"> - <div className="settings__cell settings__cell--active" onClick={ this.props.onExternalLink.bind(this, 'faq') }> - <div className="settings__cell-label">FAQs</div> - <div className="settings__cell-icon"> - <Img source="icon-extLink" tintColor="currentColor" /> - </div> - </div> - <div className="settings__cell settings__cell--active" onClick={ this.props.onExternalLink.bind(this, 'guides') }> - <div className="settings__cell-label">Guides</div> - <div className="settings__cell-icon"> - <Img source="icon-extLink" tintColor="currentColor" /> - </div> - </div> - <div className="settings__view-support settings__cell settings__cell--active" onClick={ this.props.onViewSupport }> - <div className="settings__cell-label">Report a problem</div> - <div className="settings__cell-disclosure"> - <Img source="icon-extLink" tintColor="currentColor" /> - </div> - </div> - </div> - </div> + <Button onPress={ this.props.onExternalLink.bind(this, 'guides') } testName='settings__external_link'> + <View style={styles.settings__cell}> + <Text style={styles.settings__cell_label}>Guides</Text> + <Img style={styles.settings__cell_icon} source='icon-extLink' tintColor='currentColor'/> + </View> + </Button> - <div className="settings__footer"> - <button className="settings__quit button button--negative" onClick={ this.props.onQuit }>Quit app</button> - </div> + <Button onPress={ this.props.onViewSupport } testName='settings__view_support'> + <View style={styles.settings__cell}> + <Text style={styles.settings__cell_label}>Contact support</Text> + <Img style={styles.settings__cell_icon} source='icon-chevron' tintColor='currentColor'/> + </View> + </Button> + </View> - </div> + <View style={styles.settings__footer}> + <Button style={styles.settings__footer_button} onPress={this.props.onQuit} testName='settings__quit'> + <Text style={styles.settings__footer_button_label}>Quit app</Text> + </Button> + </View> + </View> </CustomScrollbars> - </div> - </div> + </View> + </View> </Container> </Layout> ); diff --git a/app/components/SettingsStyles.js b/app/components/SettingsStyles.js new file mode 100644 index 0000000000..d5daef85b5 --- /dev/null +++ b/app/components/SettingsStyles.js @@ -0,0 +1,121 @@ +import { createViewStyles, createTextStyles } from '../lib/styles'; + +export default Object.assign(createViewStyles({ + settings: { + backgroundColor: '#192E45', + height: '100%' + }, + settings__container:{ + flexDirection: 'column', + height: '100%' + }, + settings__header:{ + flex: 0, + paddingTop: 12, + paddingBottom: 12, + paddingLeft: 24, + paddingRight: 24, + position: 'relative' /* anchor for close button */ + }, + settings__content:{ + flexDirection: 'column', + flex: 1, + }, + settings__close:{ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + marginTop: 0, + marginLeft: 12, + }, + settings__close_icon:{ + width: 24, + height: 24, + flex: 0, + opacity: 0.6, + marginRight: 8, + }, + settings__cell:{ + backgroundColor: 'rgba(41,71,115,1)', + paddingTop: 15, + paddingBottom: 15, + paddingLeft: 24, + paddingRight: 24, + marginLeft: -6, //Because of button.css, when removed remove this + marginRight: -6, //Because of button.css, when removed remove this + marginTop: -1, //Because of button.css, when removed remove this + marginBottom: 0, //Because of button.css, when removed remove this + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between' + }, + settings__cell_disclosure:{ + marginLeft: 8, + color: 'rgba(255, 255, 255, 0.8)', + }, + settings__cell_spacer:{ + height: 24, + flex: 0 + }, + settings__cell__active_hover:{ + backgroundColor: 'rgba(41,71,115,0.9)' + }, + settings__cell_icon:{ + width: 16, + height: 16, + flex: 0, + color: 'rgba(255, 255, 255, 0.8)', + }, + settings__account_paid_until_label__error:{ + color: '#d0021b' + }, + settings__footer_button:{ + backgroundColor: 'rgba(208,2,27,1)', + paddingTop: 7, + paddingLeft: 12, + paddingRight: 12, + paddingBottom: 9, + borderRadius: 4, + justifyContent: 'center', + alignItems: 'center', + width: '100%', + }, + settings__footer:{ + width: '100%', + justifyContent: 'center', + alignItems: 'center', + paddingTop: 24, + paddingLeft: 24, + paddingRight: 24, + paddingBottom: 24, + }, +}), createTextStyles({ + settings__title:{ + fontFamily: 'DINPro', + fontSize: 32, + fontWeight: '900', + lineHeight: 40, + color: '#FFFFFF' + }, + settings__cell_label:{ + fontFamily: 'DINPro', + fontSize: 20, + fontWeight: '900', + lineHeight: 26, + color: '#FFFFFF' + }, + settings__footer_button_label:{ + fontFamily: 'DINPro', + fontSize: 20, + fontWeight: '900', + lineHeight: 26, + color: 'rgba(255,255,255,0.8)' + }, + settings__account_paid_until_label:{ + fontFamily: 'Open Sans', + fontSize: 13, + fontWeight: '800', + color: 'rgba(255, 255, 255, 0.8)' + }, +})); diff --git a/app/lib/styles.js b/app/lib/styles.js new file mode 100644 index 0000000000..c75d73236b --- /dev/null +++ b/app/lib/styles.js @@ -0,0 +1,17 @@ +import { Styles } from 'reactxp'; + +export function createViewStyles(styles: { [string]: Object }) { + const viewStyles = {}; + for (const style of Object.keys(styles)) { + viewStyles[style] = Styles.createViewStyle(styles[style]); + } + return viewStyles; +} + +export function createTextStyles(styles: { [string]: Object }) { + const textStyles = {}; + for (const style of Object.keys(styles)) { + textStyles[style] = Styles.createTextStyle(styles[style]); + } + return textStyles; +}
\ No newline at end of file diff --git a/test/components/Settings.spec.js b/test/components/Settings.spec.js index cc38c54616..7f949cb6c3 100644 --- a/test/components/Settings.spec.js +++ b/test/components/Settings.spec.js @@ -2,9 +2,11 @@ import { expect } from 'chai'; import React from 'react'; -import ReactTestUtils, { Simulate } from 'react-dom/test-utils'; import Settings from '../../app/components/Settings'; +import { shallow } from 'enzyme'; +require('../setup/enzyme'); + import type { AccountReduxState } from '../../app/redux/account/reducers'; import type { SettingsReduxState } from '../../app/redux/settings/reducers'; import type { SettingsProps } from '../../app/components/Settings'; @@ -59,91 +61,90 @@ describe('components/Settings', () => { 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'); + const component = getComponent(render(props), 'settings__quit'); + expect(component).to.have.length(1); }); it('should show quit button when logged in', () => { const props = makeProps(loggedInAccountState, settingsState); - ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__quit'); + const component = getComponent(render(props), 'settings__quit'); + expect(component).to.have.length(1); }); it('should show external links when logged out', () => { const props = makeProps(loggedOutAccountState, settingsState); - ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__external'); + const component = getComponent(render(props), 'settings__external_link'); + expect(component.length).to.be.above(0); }); it('should show external links when logged in', () => { const props = makeProps(loggedInAccountState, settingsState); - ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__external'); + const component = getComponent(render(props), 'settings__external_link'); + expect(component.length).to.be.above(0); }); it('should show account section when logged in', () => { const props = makeProps(loggedInAccountState, settingsState); - ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__account'); + const component = getComponent(render(props), 'settings__account'); + expect(component).to.have.length(1); }); 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; + const elements = getComponent(render(props), 'settings__account'); + expect(elements).to.have.length(0); }); 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; + const elements = getComponent(render(props), 'settings__view_account'); + expect(elements).to.have.length(0); }); 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'); + const component = getComponent(render(props), 'settings__account_paid_until_label'); + expect(component.children().text()).to.equal('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'); + const component = getComponent(render(props), 'settings__account_paid_until_label'); + expect(component.children().text()).not.to.equal('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); + const component = getComponent(render(props), 'settings__close'); + click(component); }); it('should call quit callback', (done) => { const props = makeProps(loggedOutAccountState, settingsState, { onQuit: () => done() }); - const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__quit'); - Simulate.click(domNode); + const component = getComponent(render(props), 'settings__quit'); + click(component); }); 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); + const component = getComponent(render(props), 'settings__view_account'); + click(component); }); it('should call support callback', (done) => { const props = makeProps(loggedInAccountState, settingsState, { onViewSupport: () => done() }); - const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__view-support'); - Simulate.click(domNode); + const component = getComponent(render(props), 'settings__view_support'); + click(component); }); it('should call external links callback', () => { @@ -153,12 +154,25 @@ describe('components/Settings', () => { 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)); + const container = getComponent(render(props), 'settings__external_link'); + container.find({ testName: 'settings__external_link' }) + .forEach((element) => click(element)); expect(collectedExternalLinkTypes).to.include.ordered.members(['faq', 'guides']); }); }); + +function render(props) { + return shallow( + <Settings {...props} /> + ); +} + +function getComponent(container, testName) { + return container.findWhere( n => n.prop('testName') === testName); +} + +function click(component) { + component.prop('onPress')(); +} |
