summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoranderklander <anderklander@gmail.com>2017-12-14 13:31:20 +0100
committerErik Larkö <erik@mullvad.net>2017-12-22 13:43:35 +0100
commit77db271e9eff71ddb9f2d4ceb5bce3090861dd95 (patch)
treec60086492f17416e49048f06c69a2d8754094b96
parent44b813ea88ba30f35e58df196945f76ddf34000b (diff)
downloadmullvadvpn-77db271e9eff71ddb9f2d4ceb5bce3090861dd95.tar.xz
mullvadvpn-77db271e9eff71ddb9f2d4ceb5bce3090861dd95.zip
Settings in reactxp
All tests ok.
-rw-r--r--app/assets/css/style.css1
-rw-r--r--app/components/Settings.css125
-rw-r--r--app/components/Settings.js152
-rw-r--r--app/components/SettingsStyles.js140
-rw-r--r--test/components/Settings.spec.js77
5 files changed, 259 insertions, 236 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/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..5c991978e9 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, Image, 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,83 @@ 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>
- <CustomScrollbars autoHide={ true }>
- <div className="settings__content">
- <div className="settings__main">
+ <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>
- { /* show account options when logged in */ }
- <If condition={ isLoggedIn }>
- <Then>
- <div className="settings__account">
+ <View style={styles.settings__container}>
+ <View style={styles.settings__header}>
+ <Text style={styles.settings__title}>Settings</Text>
+ </View>
- <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>
+ <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>
+ <Img style={styles.settings__cell_disclosure} source="icon-chevron" tintColor="currentColor"/>
+ </View>
+ </Button>
+ <View style={styles.settings__cell_spacer} />
+ </View>
+ ) : 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>
+ {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__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>
+ <View style={styles.settings__external} testName="settings__external">
- <div className="settings__footer">
- <button className="settings__quit button button--negative" onClick={ this.props.onQuit }>Quit app</button>
- </div>
+ <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>
- </CustomScrollbars>
- </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>
+
+ <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>
+ </View>
+
+ <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>
+ </View>
+ </View>
+>>>>>>> Settings in reactxp
</Container>
</Layout>
);
diff --git a/app/components/SettingsStyles.js b/app/components/SettingsStyles.js
new file mode 100644
index 0000000000..b3cc72e84a
--- /dev/null
+++ b/app/components/SettingsStyles.js
@@ -0,0 +1,140 @@
+import { Styles } from "reactxp";
+
+const styles = {
+ settings:
+ Styles.createViewStyle({
+ backgroundColor: '#192E45',
+ height: '100%'
+ }),
+ settings__container:
+ Styles.createViewStyle({
+ flexDirection: "column",
+ height: '100%'
+ }),
+ settings__header:
+ Styles.createViewStyle({
+ flex: 0,
+ paddingTop: 12,
+ paddingBottom: 12,
+ paddingLeft: 24,
+ paddingRight: 24,
+ position: "relative" /* anchor for close button */
+ }),
+ settings__content:
+ Styles.createViewStyle({
+ flexDirection: "column",
+ flex: 1,
+ }),
+ settings__close:
+ Styles.createViewStyle({
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ marginTop: 0,
+ marginLeft: 12,
+ }),
+ settings__close_icon:
+ Styles.createViewStyle({
+ width: 24,
+ height: 24,
+ flex: 0,
+ opacity: 0.6,
+ marginRight: 8,
+ }),
+ settings__title:
+ Styles.createTextStyle({
+ fontFamily: "DINPro",
+ fontSize: 32,
+ fontWeight: "900",
+ lineHeight: 40,
+ color: "#FFFFFF"
+ }),
+ settings__cell:
+ Styles.createViewStyle({
+ 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:
+ Styles.createViewStyle({
+ marginLeft: 8
+ }),
+ settings__cell_spacer:
+ Styles.createViewStyle({
+ height: 24,
+ flex: 0
+ }),
+ settings__cell__active_hover:
+ Styles.createViewStyle({
+ backgroundColor: "rgba(41,71,115,0.9)"
+ }),
+ settings__cell_label:
+ Styles.createTextStyle({
+ fontFamily: "DINPro",
+ fontSize: 20,
+ fontWeight: "900",
+ lineHeight: 26,
+ color: "#FFFFFF"
+ }),
+ settings__cell_icon:
+ Styles.createViewStyle({
+ width: 16,
+ height: 16,
+ flex: 0,
+ marginRight: 8,
+ opacity: 0.8
+ }),
+ settings__account_paid_until_label:
+ Styles.createTextStyle({
+ fontFamily: "Open Sans",
+ fontSize: 13,
+ fontWeight: "800",
+ color: "rgba(255, 255, 255, 0.8)"
+ }),
+ settings__account_paid_until_label__error:
+ Styles.createViewStyle({
+ color: "#d0021b"
+ }),
+ settings__footer_button_label:
+ Styles.createTextStyle({
+ fontFamily: "DINPro",
+ fontSize: 20,
+ fontWeight: "900",
+ lineHeight: 26,
+ color: "rgba(255,255,255,0.8)"
+ }),
+ settings__footer_button:
+ Styles.createViewStyle({
+ backgroundColor: "rgba(208,2,27,1)",
+ paddingTop: 7,
+ paddingLeft: 12,
+ paddingRight: 12,
+ paddingBottom: 9,
+ borderRadius: 4,
+ justifyContent: "center",
+ alignItems: "center",
+ width: '100%',
+ }),
+ settings__footer:
+ Styles.createViewStyle({
+ width: '100%',
+ justifyContent: "center",
+ alignItems: "center",
+ paddingTop: 24,
+ paddingLeft: 24,
+ paddingRight: 24,
+ paddingBottom: 24,
+ })
+};
+
+module.exports = styles;
diff --git a/test/components/Settings.spec.js b/test/components/Settings.spec.js
index cc38c54616..2baede24a2 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,85 @@ 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');
+ getComponent(render(props), 'settings__quit');
});
it('should show quit button when logged in', () => {
const props = makeProps(loggedInAccountState, settingsState);
- ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__quit');
+ getComponent(render(props), 'settings__quit');
});
it('should show external links when logged out', () => {
const props = makeProps(loggedOutAccountState, settingsState);
- ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__external');
+ getComponent(render(props), 'settings__external');
});
it('should show external links when logged in', () => {
const props = makeProps(loggedInAccountState, settingsState);
- ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__external');
+ getComponent(render(props), 'settings__external');
});
it('should show account section when logged in', () => {
const props = makeProps(loggedInAccountState, settingsState);
- ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'settings__account');
+ getComponent(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;
+ 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.contains('OUT OF TIME')).to.equal(true);
});
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.contains('OUT OF TIME')).to.equal(false);
});
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 +149,29 @@ 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');
+ 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 hasChild(container, testName) {
+ return getComponent(container, testName).length > 0;
+}
+
+function click(component) {
+ component.prop('onPress')();
+}