diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-04-11 17:30:58 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-04-11 17:30:58 +0200 |
| commit | 0c6c767e265e186495dbaabc859c5844aeb15b41 (patch) | |
| tree | 93882e8ff46ba96c89a00441a8846ecb49b08dc6 | |
| parent | c1cb78f55c8e346c1d2c097df4bf368c56e91abf (diff) | |
| parent | f146d3e0eb3c74107c8bda124ccfae4048000d33 (diff) | |
| download | mullvadvpn-0c6c767e265e186495dbaabc859c5844aeb15b41.tar.xz mullvadvpn-0c6c767e265e186495dbaabc859c5844aeb15b41.zip | |
Merge branch 'xp-connect-select'
| -rw-r--r-- | app/assets/css/buttons.css | 131 | ||||
| -rw-r--r-- | app/assets/css/style.css | 3 | ||||
| -rw-r--r-- | app/components/Accordion.js | 4 | ||||
| -rw-r--r-- | app/components/Connect.css | 190 | ||||
| -rw-r--r-- | app/components/Connect.js | 191 | ||||
| -rw-r--r-- | app/components/ConnectStyles.js | 146 | ||||
| -rw-r--r-- | app/components/SelectLocation.css | 146 | ||||
| -rw-r--r-- | app/components/SelectLocation.js | 140 | ||||
| -rw-r--r-- | app/components/SelectLocationStyles.js | 119 | ||||
| -rw-r--r-- | app/components/styled/AppButton.js | 77 | ||||
| -rw-r--r-- | app/components/styled/AppButtonStyles.js | 68 | ||||
| -rw-r--r-- | app/components/styled/BlurAppButtonStyles.android.js | 20 | ||||
| -rw-r--r-- | app/components/styled/BlurAppButtonStyles.js | 24 | ||||
| -rw-r--r-- | app/components/styled/CellButton.js | 6 | ||||
| -rw-r--r-- | app/components/styled/index.js | 3 | ||||
| -rw-r--r-- | test/components/Connect.spec.js | 65 | ||||
| -rw-r--r-- | test/components/SelectLocation.spec.js | 27 |
17 files changed, 589 insertions, 771 deletions
diff --git a/app/assets/css/buttons.css b/app/assets/css/buttons.css deleted file mode 100644 index 805d9e9a74..0000000000 --- a/app/assets/css/buttons.css +++ /dev/null @@ -1,131 +0,0 @@ -.button { - display: flex; - width: 100%; - padding: 7px 12px 9px; - border-radius: 4px; - border: 0; - font-family: DINPro; - font-size: 20px; - font-weight: 900; - line-height: 26px; - justify-content: center; - align-items: center; - transition: 0.25s opacity; -} - -.button:disabled { - opacity: 0.5; -} - -.button-label { - margin: 0 auto; -} - -/* make negative margin to center label within button */ -.button-label + .button-icon--16 { - margin-left: -16px; -} - -.button--blur { - backdrop-filter: blur(4px); -} - -.button--primary { - background-color: rgba(41,71,115,1); - color: rgba(255,255,255,0.8); -} - -.button--primary .button-icon path { - fill: rgba(255,255,255,0.8); -} - -.button--primary:not(:disabled):hover { - background-color: rgba(41,71,115,0.9); - color: rgba(255,255,255,1); -} - -.button--primary:not(:disabled):hover .button-icon path { - fill: rgba(255,255,255,1); -} - -.button--primary:active { - background-color: rgba(41,71,115,1); -} - -.button--secondary { - background-color: rgba(41,71,115,0.4); - color: rgba(255,255,255,0.6); -} - -.button--secondary:not(:disabled):hover { - background-color: rgba(41,71,115,0.5); - color: rgba(255,255,255,0.8); -} - -.button--secondary:active { - background-color: rgba(41,71,115,0.4); -} - -.button--negative { - background-color: rgba(208,2,27,1); - color: rgba(255,255,255,0.8); -} - -.button--negative:not(:disabled):hover { - background-color: rgba(208,2,27,0.95); - color: rgba(255,255,255,1); -} - -.button--negative:active { - background-color: rgba(208,2,27,1); -} - -.button--negative-light { - background-color: rgba(208,2,27,0.4); - color: rgba(255,255,255,0.6); -} - -.button--negative-light:not(:disabled):hover { - background-color: rgba(208,2,27,0.45); - color: rgba(255,255,255,0.8); -} - -.button--negative-light:active { - background-color: rgba(208,2,27,0.4); -} - -.button--positive { - background-color: rgba(63,173,77,1); - color: rgba(255,255,255,0.8); -} - -.button--positive .button-icon path { - fill: rgba(255,255,255,0.8); -} - -.button--positive:not(:disabled):hover { - background-color: rgba(63,173,77,0.9); - color: rgba(255,255,255,1); -} - -.button--positive:not(:disabled):hover .button-icon path { - fill: rgba(255,255,255,1); -} - -.button--positive:active { - background-color: rgba(63,173,77,1); -} - -.button--neutral { - background-color: rgba(255,255,255,0.2); - color: rgba(255,255,255,0.8); -} - -.button--neutral:not(:disabled):hover { - background-color: rgba(255,255,255,0.25); - color: rgba(255,255,255,1); -} - -.button--neutral:active { - background-color: rgba(255,255,255,0.2); -} diff --git a/app/assets/css/style.css b/app/assets/css/style.css index 27403aaedf..619e40daf3 100644 --- a/app/assets/css/style.css +++ b/app/assets/css/style.css @@ -2,12 +2,9 @@ @import 'reset.css'; @import 'fonts.css'; @import 'global.css'; -@import 'buttons.css'; /* app */ @import '../../components/PlatformWindow.css'; @import '../../components/CustomScrollbars.css'; -@import '../../components/Connect.css'; -@import '../../components/SelectLocation.css'; @import '../../components/Layout.css'; @import '../../components/Switch.css'; diff --git a/app/components/Accordion.js b/app/components/Accordion.js index 0b2f2929d3..116d257a40 100644 --- a/app/components/Accordion.js +++ b/app/components/Accordion.js @@ -59,8 +59,8 @@ export default class Accordion extends Component<AccordionProps, AccordionState> } render() { - const { height: _height, children, animationDuration: _animationDuration, ...otherProps } = this.props; - const containerStyles = []; + const { style: style, height: _height, children, animationDuration: _animationDuration, ...otherProps } = this.props; + const containerStyles = [style]; if(this.state.animatedValue !== null) { const animatedStyle = Styles.createAnimatedViewStyle({ diff --git a/app/components/Connect.css b/app/components/Connect.css deleted file mode 100644 index 592e824ea2..0000000000 --- a/app/components/Connect.css +++ /dev/null @@ -1,190 +0,0 @@ -.connect { - height: 100%; - position: relative; -} - -.connect__map { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 0; -} - -.connect__container { - display: flex; - flex-direction: column; - height: 100%; - position: relative; /* need this for z-index to work to cover map */ - z-index: 1; -} - -.connect__footer { - display: flex; - flex-direction: column; - padding: 42px 24px 24px; -} - -.connect__row + .connect__row{ - margin-top: 16px; -} - -.connect__blocking-container { - color: rgba(255,255,255,0.8); - max-height: 0px; - height: 100%; - width: 100%; - overflow: hidden; - - text-transform: uppercase; - - position: absolute; - border-bottom: 1px solid rgba(255,255,255,0.8); - transition: max-height 0.5s; -} -.connect__blocking-container.show { - max-height: 36px; -} -.connect__blocking-container.hide { - max-height: 0px; - border-bottom: 0px solid rgb(208, 2, 27); - transition: max-height 0.5s, border-bottom 0.1s 0.4s; -} - -.connect__blocking-message { - display: flex; - flex-direction: row; - margin: 8px 20px; - font-family: "Open Sans"; - font-size: 12px; - font-weight: 800; - line-height: 17px; -} - -.connect__blocking-icon { - width: 10px; - height: 10px; - border-radius: 5px; - margin-top: 4px; - margin-right: 8px; - - background-color: rgb(208, 2, 27); -} - -.connect__server { - padding: 7px 12px 9px; - background-color: rgba(255,255,255,0.2); - border-radius: 4px; - display: flex; - flex-direction: row; - align-items: center; - backdrop-filter: blur(4px); -} - -.connect__server-label { - flex: 1 1 auto; - text-align: center; -} - -.connect__server-chevron { - flex: 0 0 auto; - width: 7px; - margin-left: -7px; /* let .connect__server-label extend to occupy the entire space */ -} - -.connect__footer-button { - display: block; - width:100%; - border: 0; - padding: 7px 12px 9px; - border-radius: 4px; - font-family: DINPro; - font-size: 20px; - font-weight: 900; - text-align: center; - line-height: 26px; - color: #FFFFFF; -} - -.connect__status { - padding: 0 24px; - margin-top: 94px; - margin-bottom: auto; -} - -.connect__error-title { - font-family: DINPro; - font-size: 32px; - font-weight: 900; - line-height: 1.25; - color: #fff; - margin-bottom: 8px; -} - -.connect__error-message { - font-family: "Open Sans"; - font-size: 13px; - font-weight: 600; - line-height: normal; - color: #fff; - margin-bottom: 24px; -} - -.connect__status-security { - font-family: "Open Sans"; - font-size: 16px; - font-weight: 800; - line-height: 22px; - margin-bottom: 4px; - color: #FFFFFF; - text-transform: uppercase; -} - -.connect__status-security--unsecured { - color: #D0021B; -} - -.connect__status-security--secure { - color: #44AD4D; -} - -.connect__status-location { - font-family: DINPro; - font-size: 38px; - font-weight: 900; - line-height: 1.16em; - max-height: calc(1.16em * 2); - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - letter-spacing: -0.9px; - color: #FFFFFF; - margin-bottom: 4px; -} - -.connect__status-location-icon { - display: inline-block; - margin-right: 8px; -} - -.connect__status-ipaddress { - font-family: "Open Sans"; - font-size: 16px; - font-weight: 800; - line-height: normal; - color: #FFFFFF; -} - -.connect__status-ipaddress--invisible { - visibility: hidden; -} - -.connect__status-icon { - text-align: center; - margin-bottom: 32px; -} - -.connect__status-icon--hidden { - visibility: hidden; -} diff --git a/app/components/Connect.js b/app/components/Connect.js index 3408e40b8d..9f8b2b993f 100644 --- a/app/components/Connect.js +++ b/app/components/Connect.js @@ -3,12 +3,15 @@ import moment from 'moment'; import * as React from 'react'; import { Layout, Container, Header } from './Layout'; +import { Component, Text, View, Types } from 'reactxp'; +import Img from './Img'; +import { TransparentButton, RedTransparentButton, GreenButton, Label } from './styled'; +import Accordion from './Accordion'; +import styles from './ConnectStyles'; + import { BackendError } from '../lib/backend'; import Map from './Map'; -import ExternalLinkSVG from '../assets/images/icon-extLink.svg'; -import ChevronRightSVG from '../assets/images/icon-chevron.svg'; - import type { HeaderBarStyle } from './HeaderBar'; import type { ConnectionReduxState } from '../redux/connection/reducers'; @@ -29,7 +32,7 @@ type ConnectState = { mapOffset: [number, number], }; -export default class Connect extends React.Component<ConnectProps, ConnectState> { +export default class Connect extends Component<ConnectProps, ConnectState> { state = { showCopyIPMessage: false, mapOffset: [0, 0], @@ -66,7 +69,7 @@ export default class Connect extends React.Component<ConnectProps, ConnectState> return ( <Layout> - <Header style={ this.headerStyle() } showSettings={ true } onSettings={ this.props.onSettings } /> + <Header style={ this.headerStyle() } showSettings={ true } onSettings={ this.props.onSettings } testName='header'/> <Container> { child } </Container> @@ -76,28 +79,28 @@ export default class Connect extends React.Component<ConnectProps, ConnectState> renderError(error: BackendError) { return ( - <div className="connect"> - <div className="connect__status"> - <div className="connect__status-icon"> - <img src="./assets/images/icon-fail.svg" alt="" /> - </div> - <div className="connect__error-title"> + <View style={styles.connect}> + <View style={styles.status}> + <View style={styles.status_icon}> + <Img source="icon-fail" height="60" width="60" alt="" /> + </View> + <View style={styles.error_title}> { error.title } - </div> - <div className="connect__error-message"> + </View> + <View style={styles.error_message}> { error.message } - </div> + </View> { error.type === 'NO_CREDIT' ? - <div> - <button className="button button--positive" onClick={ this.onExternalLink.bind(this, 'purchase') }> - <span className="button-label">Buy more time</span> - <ExternalLinkSVG className="button-icon button-icon--16" /> - </button> - </div> + <View> + <GreenButton onPress={ this.onExternalLink.bind(this, 'purchase') }> + <Label>Buy more time</Label> + <Img source='icon-extLink' height='16' width='16' /> + </GreenButton> + </View> : null } - </div> - </div> + </View> + </View> ); } @@ -150,20 +153,25 @@ export default class Connect extends React.Component<ConnectProps, ConnectState> } return ( - <div className="connect"> - <div className="connect__map"> + <View style={styles.connect}> + <View style={styles.map}> <Map style={{ width: '100%', height: '100%' }} { ...this._getMapProps() } /> - </div> - <div className="connect__container"> + </View> + <View style={styles.container}> { this._renderIsBlockingInternetMessage() } - <div className="connect__status"> - { /* show spinner when connecting */ } - <div className={ this.spinnerClass() }> - <img src="./assets/images/icon-spinner.svg" alt="" ref={ this._updateMapOffset } /> - </div> - <div className={ this.networkSecurityClass() }>{ this.networkSecurityMessage() }</div> + { /* show spinner when connecting */ } + { isConnecting ? + <View style={ styles.status_icon }> + <Img source="icon-spinner" height="60" width="60" alt="" ref={ this._updateMapOffset } /> + </View> + : null + } + + <View style={styles.status}> + + <View style={ this.networkSecurityStyle() } testName='networkSecurityMessage'>{ this.networkSecurityMessage() }</View> { /* ********************************** @@ -173,19 +181,19 @@ export default class Connect extends React.Component<ConnectProps, ConnectState> { /* location when connecting or disconnected */ } { isConnecting || isDisconnected ? - <div className="connect__status-location"> - <span>{ this.props.connection.country }</span> - </div> + <Text style={styles.status_location} testName='location'> + { this.props.connection.country } + </Text> : null } { /* location when connected */ } { isConnected ? - <div className="connect__status-location"> + <Text style={styles.status_location} testName='location'> { this.props.connection.city } { this.props.connection.city && <br/> } { this.props.connection.country } - </div> + </Text> :null } @@ -195,15 +203,15 @@ export default class Connect extends React.Component<ConnectProps, ConnectState> ********************************** */ } - <div className={ this.ipAddressClass() } onClick={ this.onIPAddressClick.bind(this) }> + <Text style={ this.ipAddressStyle() } onPress={ this.onIPAddressClick.bind(this) }> { (isConnected || isDisconnected) ? ( - <span>{ + <Text testName='ipAddress'>{ this.state.showCopyIPMessage ? 'IP copied to clipboard!' : this.props.connection.ip - }</span>) : null } - </div> - </div> + }</Text>) : null } + </Text> + </View> { /* @@ -214,46 +222,31 @@ export default class Connect extends React.Component<ConnectProps, ConnectState> { /* footer when disconnected */ } { isDisconnected ? - <div className="connect__footer"> - <div className="connect__row"> - <button className="connect__server button button--neutral button--blur" onClick={ this.props.onSelectLocation }> - <div className="connect__server-label">{ this.props.selectedRelayName }</div> - <div className="connect__server-chevron"><ChevronRightSVG /></div> - </button> - </div> - - <div className="connect__row"> - <button className="button button--positive" onClick={ this.props.onConnect }>Secure my connection</button> - </div> - </div> + <View style={styles.footer}> + <TransparentButton onPress={ this.props.onSelectLocation }> + <Label>{ this.props.selectedRelayName }</Label> + <Img height='12' width='7' source='icon-chevron' /> + </TransparentButton> + <GreenButton onPress={ this.props.onConnect } testName='secureConnection'>Secure my connection</GreenButton> + </View> : null } { /* footer when connecting */ } { isConnecting ? - <div className="connect__footer"> - <div className="connect__row"> - <button className="button button--neutral button--blur" onClick={ this.props.onSelectLocation }>Switch location</button> - </div> - - <div className="connect__row"> - <button className="button button--negative-light button--blur" onClick={ this.props.onDisconnect }>Cancel</button> - </div> - </div> + <View style={styles.footer}> + <TransparentButton onPress={ this.props.onSelectLocation }>Switch location</TransparentButton> + <RedTransparentButton onPress={ this.props.onDisconnect }>Cancel</RedTransparentButton> + </View> : null } { /* footer when connected */ } { isConnected ? - <div className="connect__footer"> - <div className="connect__row"> - <button className="button button--neutral button--blur" onClick={ this.props.onSelectLocation }>Switch location</button> - </div> - - <div className="connect__row"> - <button className="button button--negative-light button--blur" onClick={ this.props.onDisconnect }>Disconnect</button> - </div> - </div> + <View style={styles.footer}> + <TransparentButton onPress={ this.props.onSelectLocation }>Switch location</TransparentButton> + <RedTransparentButton onPress={ this.props.onDisconnect } testName='disconnect'>Disconnect</RedTransparentButton> + </View> : null } @@ -263,23 +256,18 @@ export default class Connect extends React.Component<ConnectProps, ConnectState> ********************************** */ } - </div> - </div> + </View> + </View> ); } _renderIsBlockingInternetMessage() { - let animationClass = 'hide'; - if (this.props.connection.status === 'connecting') { - animationClass = 'show'; - } - - return <div className={`connect__blocking-container ${animationClass}`}> - <div className="connect__blocking-message"> - <div className="connect__blocking-icon"> </div> - blocking internet - </div> - </div>; + return <Accordion style={styles.blocking_container} height={ (this.props.connection.status === 'connecting') ? 'auto' : 0 }> + <Text style={styles.blocking_message}> + <Text style={styles.blocking_icon}> </Text> + <Text>BLOCKING INTERNET</Text> + </Text> + </Accordion>; } // Handlers @@ -308,39 +296,30 @@ export default class Connect extends React.Component<ConnectProps, ConnectState> throw new Error('Invalid ConnectionState'); } - networkSecurityClass(): string { - let classes = ['connect__status-security']; + networkSecurityStyle(): Types.Style { + let classes = [styles.status_security]; if(this.props.connection.status === 'connected') { - classes.push('connect__status-security--secure'); + classes.push(styles.status_security__secure); } else if(this.props.connection.status === 'disconnected') { - classes.push('connect__status-security--unsecured'); + classes.push(styles.status_security__unsecured); } - - return classes.join(' '); + return classes; } networkSecurityMessage(): string { switch(this.props.connection.status) { - case 'connected': return 'Secure connection'; - case 'connecting': return 'Creating secure connection'; - default: return 'Unsecured connection'; - } - } - - spinnerClass(): string { - var classes = ['connect__status-icon']; - if(this.props.connection.status !== 'connecting') { - classes.push('connect__status-icon--hidden'); + case 'connected': return 'SECURE CONNECTION'; + case 'connecting': return 'CREATING SECURE CONNECTION'; + default: return 'UNSECURED CONNECTION'; } - return classes.join(' '); } - ipAddressClass(): string { - var classes = ['connect__status-ipaddress']; + ipAddressStyle(): Types.Style { + var classes = [styles.status_ipaddress]; if(this.props.connection.status === 'connecting') { - classes.push('connect__status-ipaddress--invisible'); + classes.push(styles.status_ipaddress__invisible); } - return classes.join(' '); + return classes; } displayError(): ?BackendError { diff --git a/app/components/ConnectStyles.js b/app/components/ConnectStyles.js new file mode 100644 index 0000000000..413d2f0c1d --- /dev/null +++ b/app/components/ConnectStyles.js @@ -0,0 +1,146 @@ +// @flow +import { createViewStyles, createTextStyles } from '../lib/styles'; +import { colors } from '../config'; + +export default { + ...createViewStyles({ + connect: { + height: '100%', + flex: 1, + }, + map: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + zIndex: 0, + height: '100%', + width: '100%', + }, + container: { + flexDirection: 'column', + flex: 1, + position: 'relative', /* need this for z-index to work to cover map */ + zIndex: 1, + + }, + footer:{ + flex: 0, + marginBottom: 16, + }, + blocking_container: { + width: '100%', + position: 'absolute', + }, + blocking_icon: { + width: 10, + height: 10, + flex: 0, + display: 'flex', + borderRadius: 5, + marginTop: 4, + marginRight: 8, + backgroundColor: colors.red, + }, + server: { + paddingTop: 7, + paddingLeft: 12, + paddingRight: 12, + paddingBottom: 9, + backgroundColor: colors.white20, + borderRadius: 4, + flexDirection: 'row', + alignItems: 'center', + }, + status: { + paddingTop: 0, + paddingLeft: 24, + paddingRight: 24, + paddingBottom: 0, + marginTop: 186, + flex: 1, + }, + status_icon: { + position:'absolute', + alignSelf: 'center', + width: 60, + height: 60, + marginTop: 94, + }, + }), + ...createTextStyles({ + blocking_message: { + display: 'flex', + flexDirection: 'row', + fontFamily: 'Open Sans', + fontSize: 12, + fontWeight: '800', + lineHeight: 17, + paddingTop: 8, + paddingLeft: 20, + paddingRight: 20, + paddingBottom: 8, + color: colors.white60, + backgroundColor: colors.blue, + }, + server_label: { + fontFamily: 'DINPro', + fontSize: 32, + fontWeight: '900', + lineHeight: 44, + letterSpacing: -0.7, + color: colors.white, + marginBottom: 7, + flex:0, + }, + error_title: { + fontFamily: 'DINPro', + fontSize: 32, + fontWeight: '900', + lineHeight: 40, + color: colors.white, + marginBottom: 8, + }, + error_message: { + fontFamily: 'Open Sans', + fontSize: 13, + fontWeight: '600', + color: colors.white, + marginBottom: 24, + }, + status_security: { + fontFamily: 'Open Sans', + fontSize: 16, + fontWeight: '800', + lineHeight: 22, + marginBottom: 4, + color: colors.white, + }, + status_security__secure: { + color: colors.green, + }, + status_security__unsecured: { + color: colors.red, + }, + status_ipaddress: { + fontFamily: 'Open Sans', + fontSize: 16, + fontWeight: '800', + color: colors.white, + }, + status_ipaddress__invisible: { + opacity: 0, + }, + status_location: { + fontFamily: 'DINPro', + fontSize: 38, + fontWeight: '900', + lineHeight: 40, + overflow: 'hidden', + letterSpacing: -0.9, + color: colors.white, + marginBottom: 4, + }, + }), +};
\ No newline at end of file diff --git a/app/components/SelectLocation.css b/app/components/SelectLocation.css deleted file mode 100644 index 7e107f237f..0000000000 --- a/app/components/SelectLocation.css +++ /dev/null @@ -1,146 +0,0 @@ -.select-location { - background: #192E45; - height: 100%; -} - -.select-location__container { - display: flex; - flex-direction: column; - height: 100%; -} - -.select-location__header { - flex: 0 0 auto; - padding: 40px 24px 16px; - position: relative; /* anchor for close button */ -} - -.select-location__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 .select-location__container covers the button */ -} - -.select-location__title { - font-family: DINPro; - font-size: 32px; - font-weight: 900; - line-height: 1.25em; - color: #FFFFFF; -} - -.select-location__subtitle { - font-family: "Open Sans"; - font-size: 13px; - font-weight: 600; - line-height: normal; - color: rgba(255,255,255,0.8); - padding: 0 24px 24px; -} - -.select-location__cell { - background-color: rgba(41,71,115,1); - display: flex; - flex-direction: row; - align-items: stretch; -} - -.select-location__cell-content { - padding: 15px 24px; - display: flex; - flex: 1 1 auto; - flex-direction: row; - align-items: center; -} - -.select-location__cell--selectable:hover { - background-color:rgba(41,71,115,0.9); -} - -.select-location__cell--selected, -.select-location__cell--selected:hover { - background-color: #44AD4D; -} - -.select-location__country + .select-location__country { - margin-top: 1px; -} - -.select-location__cell-label { - font-family: DINPro; - font-size: 20px; - font-weight: 900; - line-height: 26px; - color: #FFFFFF; -} - -.select-location__cell-label--inactive { - color: rgba(255, 255, 255, 0.2); -} - -.select-location__cell-icon { - width: 24px; - height: 24px; - flex: 0 0 auto; - margin-right: 8px; - align-items: center; - justify-content: center; - display: flex; - color: #fff; -} - -.select-location__collapse-button { - border: 0; - background: transparent; - padding: 0; - margin: 0 0 0 auto; - display: flex; - align-items: stretch; - padding: 12px; -} - -.select-location__collapse-icon { - color: #fff; -} - -.select-location__sub-cell { - background-color: rgb(36, 57, 84); - padding: 15px 24px 15px 40px; - display: flex; - flex-direction: row; - align-items: center; - margin-top: 1px; -} - -.select-location__sub-cell--selectable:hover { - background-color: rgba(41,71,115,0.9); -} - -.select-location__sub-cell--selected, -.select-location__sub-cell--selected:hover { - background-color: #44AD4D; -} - -.select-location-relay-status { - width: 16px; - height: 16px; - border-radius: 8px; -} - -.select-location-relay-status--inactive { - background: rgba(208, 2, 27, 0.95); -} - -.select-location-relay-status--active { - background: rgba(68, 173, 77, 0.9); -}
\ No newline at end of file diff --git a/app/components/SelectLocation.js b/app/components/SelectLocation.js index 88f1415b01..5a0b8c1101 100644 --- a/app/components/SelectLocation.js +++ b/app/components/SelectLocation.js @@ -1,12 +1,13 @@ // @flow import * as React from 'react'; -import { Layout, Container, Header } from './Layout'; +import { Layout, Container } from './Layout'; import CustomScrollbars from './CustomScrollbars'; +import { Text, View } from 'reactxp'; +import { Button, CellButton, Label } from './styled'; +import styles from './SelectLocationStyles'; +import Img from './Img'; import Accordion from './Accordion'; -import ChevronDownSVG from '../assets/images/icon-chevron-down.svg'; -import ChevronUpSVG from '../assets/images/icon-chevron-up.svg'; -import TickSVG from '../assets/images/icon-tick.svg'; import type { SettingsReduxState, RelayLocationRedux, RelayLocationCityRedux } from '../redux/settings/reducers'; import type { RelayLocation } from '../lib/ipc-facade'; @@ -22,7 +23,7 @@ type State = { }; export default class SelectLocation extends React.Component<SelectLocationProps, State> { - _selectedCell: ?HTMLElement; + _selectedCell: ?CellButton; _scrollView: ?CustomScrollbars; state = { @@ -52,36 +53,38 @@ export default class SelectLocation extends React.Component<SelectLocationProps, const scrollView = this._scrollView; if(scrollView && cell) { - scrollView.scrollToElement(cell, 'middle'); + //TODO: fix this when repairing the auto-scroll in customscrollbars. + //scrollView.scrollToElement(cell, 'middle'); } } render() { return ( <Layout> - <Header hidden={ true } style={ 'defaultDark' } /> <Container> - <div className="select-location"> - <button className="select-location__close" onClick={ this.props.onClose } /> - <div className="select-location__container"> - <div className="select-location__header"> - <h2 className="select-location__title">Select location</h2> - </div> + <View style={styles.select_location}> + <Button style={styles.close} onPress={ this.props.onClose } testName='close'> + <Img style={styles.close_icon} source='icon-close'/> + </Button> + <View style={styles.container}> + <View style={styles.header}> + <Text style={styles.title}>Select location</Text> + </View> - <CustomScrollbars autoHide={ true } ref={ (ref) => this._scrollView = ref }> - <div> - <div className="select-location__subtitle"> + <CustomScrollbars autoHide={ true } ref={ (ref) => this._scrollView = ref }> + <View> + <Text style={styles.subtitle}> While connected, your real location is masked with a private and secure location in the selected region - </div> + </Text> { this.props.settings.relayLocations.map((relayCountry) => { return this._renderCountry(relayCountry); }) } - </div> + </View> </CustomScrollbars> - </div> - </div> + </View> + </View> </Container> </Layout> ); @@ -121,10 +124,12 @@ export default class SelectLocation extends React.Component<SelectLocationProps, }); } - _relayStatusIndicator(active: boolean) { - const statusClass = active ? 'select-location-relay-status--active' : 'select-location-relay-status--inactive'; + _relayStatusIndicator(active: boolean, isSelected: boolean) { + const statusClass = active ? styles.relay_status__active : styles.relay_status__inactive; - return (<div className={ 'select-location-relay-status ' + statusClass }></div>); + return ( isSelected ? + <Img style={ styles.tick_icon } source='icon-tick' height={24} width={24} /> : + <View style={[ styles.relay_status, statusClass ]}></View>); } _renderCountry(relayCountry: RelayLocationRedux) { @@ -142,47 +147,37 @@ export default class SelectLocation extends React.Component<SelectLocationProps, e.stopPropagation(); }; - const countryClass = 'select-location__cell' + - (isSelected ? ' select-location__cell--selected' : '') + - (relayCountry.hasActiveRelays ? ' select-location__cell--selectable' : ''); - - const onRef = isSelected ? (element) => { - this._selectedCell = element; - } : undefined; - return ( - <div key={ relayCountry.code } className="select-location__country"> - <div className={ countryClass } - onClick={ handleSelect } - ref={ onRef }> - <div className="select-location__cell-content"> - - <div className="select-location__cell-icon"> - { isSelected ? - <TickSVG /> : - this._relayStatusIndicator(relayCountry.hasActiveRelays) } - </div> + <View key={ relayCountry.code } style={styles.country}> + <CellButton + cellHoverStyle={ isSelected ? styles.cell_selected : null } + style={ isSelected ? styles.cell_selected : styles.cell } + onPress={ handleSelect } + disabled={!relayCountry.hasActiveRelays} + testName='country'> - <div className={ 'select-location__cell-label' + - (relayCountry.hasActiveRelays ? '' : ' select-location__cell-label--inactive') }> - { relayCountry.name } - </div> - </div> + { this._relayStatusIndicator(relayCountry.hasActiveRelays, isSelected) } - { relayCountry.cities.length > 1 && <button type="button" className="select-location__collapse-button" onClick={ handleCollapse }> - { isExpanded ? - <ChevronUpSVG className="select-location__collapse-icon" /> : - <ChevronDownSVG className="select-location__collapse-icon" /> } - </button> } + <Label> + { relayCountry.name } + </Label> - </div> + { relayCountry.cities.length > 1 ? + <Img style={styles.collapse_button} + hoverStyle={styles.expand_chevron_hover} + onPress={ handleCollapse } + source={isExpanded ? 'icon-chevron-up' : 'icon-chevron-down'} + height={24} + width={24} /> + : null } + </CellButton> { relayCountry.cities.length > 1 && - (<Accordion className="select-location__cities" height={ isExpanded ? 'auto' : 0 }> + (<Accordion height={ isExpanded ? 'auto' : 0 }> { relayCountry.cities.map((relayCity) => this._renderCity(relayCountry.code, relayCity)) } </Accordion>) } - </div> + </View> ); } @@ -191,36 +186,29 @@ export default class SelectLocation extends React.Component<SelectLocationProps, const isSelected = this._isSelected(relayLocation); - const cityClass = 'select-location__sub-cell' + - (isSelected ? ' select-location__sub-cell--selected' : '') + - (relayCity.hasActiveRelays ? ' select-location__sub-cell--selectable' : ''); + const onRef = isSelected ? (element) => { + this._selectedCell = element; + } : undefined; const handleSelect = (relayCity.hasActiveRelays && !isSelected) ? () => { this.props.onSelect(relayLocation); } : undefined; - const onRef = isSelected ? (element) => { - this._selectedCell = element; - } : undefined; - return ( - <div key={ `${countryCode}_${relayCity.code}` } - className={ cityClass } - onClick={ handleSelect } - ref={ onRef }> + <CellButton key={ `${countryCode}_${relayCity.code}` } + onPress={ handleSelect } + disabled={!relayCity.hasActiveRelays} + cellHoverStyle={isSelected ? styles.sub_cell__selected : null} + style={isSelected ? styles.sub_cell__selected : styles.sub_cell} + testName='city' + ref={onRef}> - <div className="select-location__cell-icon"> - { isSelected ? - <TickSVG /> : - this._relayStatusIndicator(relayCity.hasActiveRelays) } - </div> + { this._relayStatusIndicator(relayCity.hasActiveRelays, isSelected) } - <div className={ 'select-location__cell-label' + - (relayCity.hasActiveRelays ? '' : ' select-location__cell-label--inactive') }> + <Label> { relayCity.name } - </div> - </div> + </Label> + </CellButton> ); } - } diff --git a/app/components/SelectLocationStyles.js b/app/components/SelectLocationStyles.js new file mode 100644 index 0000000000..079e28dd93 --- /dev/null +++ b/app/components/SelectLocationStyles.js @@ -0,0 +1,119 @@ +// @flow +import { createViewStyles, createTextStyles } from '../lib/styles'; +import { colors } from '../config'; + +export default { + ...createViewStyles({ + select_location: { + backgroundColor: colors.darkBlue, + height: '100%', + }, + container: { + flexDirection: 'column', + flex: 1, + }, + header:{ + flex: 0, + marginBottom: 16, + }, + close: { + marginLeft: 12, + marginTop: 24, + cursor: 'default', + }, + close_icon:{ + width: 24, + height: 24, + flex: 0, + opacity: 0.6, + }, + relay_status: { + width: 16, + height: 16, + borderRadius: 8, + marginLeft: 4, + marginRight: 4, + marginTop: 19.5, + marginBottom: 19.5, + }, + relay_status__inactive: { + backgroundColor: colors.red95, + }, + relay_status__active: { + backgroundColor: colors.green90, + }, + tick_icon: { + color: colors.white, + marginLeft: 0, + marginRight: 0, + marginTop: 15.5, + marginBottom: 15.5, + }, + country: { + flexDirection: 'column', + flex: 0, + }, + collapse_button: { + flex: 0, + alignSelf: 'stretch', + justifyContent: 'center', + paddingRight: 16, + paddingLeft: 16, + }, + cell: { + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 20, + paddingRight: 0, + }, + sub_cell: { + paddingTop: 0, + paddingBottom: 0, + paddingRight: 0, + paddingLeft: 40, + backgroundColor: colors.blue40, + }, + sub_cell__selected: { + paddingTop: 0, + paddingBottom: 0, + paddingRight: 0, + paddingLeft: 40, + backgroundColor: colors.green, + }, + cell_selected: { + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 20, + paddingRight: 0, + backgroundColor: colors.green, + }, + expand_chevron_hover: { + color: colors.white, + }, + }), + ...createTextStyles({ + title: { + fontFamily: 'DINPro', + fontSize: 32, + fontWeight: '900', + lineHeight: 40, + color: colors.white, + paddingTop: 16, + paddingLeft: 24, + paddingRight: 24, + }, + subtitle: { + fontFamily: 'Open Sans', + fontSize: 13, + fontWeight: '600', + lineHeight: 20, + color: colors.white60, + letterSpacing: -0.2, + paddingTop: 0, + paddingLeft: 24, + paddingRight: 24, + paddingBottom: 24, + flex: 0, + }, + }), +};
\ No newline at end of file diff --git a/app/components/styled/AppButton.js b/app/components/styled/AppButton.js index fdb6d35dfa..59cba4eefb 100644 --- a/app/components/styled/AppButton.js +++ b/app/components/styled/AppButton.js @@ -2,75 +2,8 @@ import * as React from 'react'; import { Text, Component } from 'reactxp'; import { Button } from './Button'; -import { colors } from '../../config'; - -import { createViewStyles, createTextStyles } from '../../lib/styles'; - -const styles = { - ...createViewStyles({ - red:{ - backgroundColor: colors.red95, - }, - redHover: { - backgroundColor: colors.red, - }, - green:{ - backgroundColor: colors.green, - }, - greenHover:{ - backgroundColor: colors.green90, - }, - blue:{ - backgroundColor: colors.blue80, - }, - blueHover:{ - backgroundColor: colors.blue60, - }, - transparent:{ - backgroundColor: colors.white20, - }, - transparentHover:{ - backgroundColor: colors.white40, - }, - white80:{ - color: colors.white80, - }, - white: { - color: colors.white, - }, - icon:{ - position: 'absolute', - alignSelf: 'flex-end', - right: 8, - marginLeft: 8, - }, - common:{ - paddingTop: 9, - paddingLeft: 9, - paddingRight: 9, - paddingBottom: 9, - marginTop: 8, - marginBottom: 8, - marginLeft: 24, - marginRight: 24, - borderRadius: 4, - flex: 1, - flexDirection: 'column', - alignContent: 'center', - justifyContent: 'center', - }, - }), - ...createTextStyles({ - label:{ - alignSelf: 'center', - fontFamily: 'DINPro', - fontSize: 20, - fontWeight: '900', - lineHeight: 26, - flex: 1, - }, - }), -}; +import styles from './AppButtonStyles'; +import blurStyles from './BlurAppButtonStyles'; export class Label extends Text {} @@ -132,5 +65,9 @@ export class BlueButton extends BaseButton { } export class TransparentButton extends BaseButton { - backgroundStyle = () => this.state.hovered ? styles.transparentHover : styles.transparent; + backgroundStyle = () => this.state.hovered ? blurStyles.transparentHover : blurStyles.transparent; +} + +export class RedTransparentButton extends BaseButton { + backgroundStyle = () => this.state.hovered ? blurStyles.redTransparentHover : blurStyles.redTransparent; }
\ No newline at end of file diff --git a/app/components/styled/AppButtonStyles.js b/app/components/styled/AppButtonStyles.js new file mode 100644 index 0000000000..477a0cc54a --- /dev/null +++ b/app/components/styled/AppButtonStyles.js @@ -0,0 +1,68 @@ +import { colors } from '../../config'; + +import { createViewStyles, createTextStyles } from '../../lib/styles'; + +export default { + ...createViewStyles({ + red:{ + backgroundColor: colors.red95, + }, + redHover: { + backgroundColor: colors.red, + }, + green:{ + backgroundColor: colors.green, + }, + greenHover:{ + backgroundColor: colors.green90, + }, + blue:{ + backgroundColor: colors.blue80, + }, + blueHover:{ + backgroundColor: colors.blue60, + }, + white80:{ + color: colors.white80, + }, + white: { + color: colors.white, + }, + icon:{ + position: 'absolute', + alignSelf: 'flex-end', + right: 8, + marginLeft: 8, + }, + iconTransparent:{ + position: 'absolute', + alignSelf: 'flex-end', + right: 42, + }, + common:{ + paddingTop: 9, + paddingLeft: 9, + paddingRight: 9, + paddingBottom: 9, + marginTop: 8, + marginBottom: 8, + marginLeft: 24, + marginRight: 24, + borderRadius: 4, + flex: 1, + flexDirection: 'column', + alignContent: 'center', + justifyContent: 'center', + }, + }), + ...createTextStyles({ + label:{ + alignSelf: 'center', + fontFamily: 'DINPro', + fontSize: 20, + fontWeight: '900', + lineHeight: 26, + flex: 1, + }, + }), +};
\ No newline at end of file diff --git a/app/components/styled/BlurAppButtonStyles.android.js b/app/components/styled/BlurAppButtonStyles.android.js new file mode 100644 index 0000000000..9243a28729 --- /dev/null +++ b/app/components/styled/BlurAppButtonStyles.android.js @@ -0,0 +1,20 @@ +import { colors } from '../../config'; + +import { createViewStyles } from '../../lib/styles'; + +export default { + ...createViewStyles({ + transparent:{ + backgroundColor: colors.white20, + }, + transparentHover:{ + backgroundColor: colors.white40, + }, + redTransparent:{ + backgroundColor: colors.red40, + }, + redTransparentHover:{ + backgroundColor: colors.red45, + }, + }) +};
\ No newline at end of file diff --git a/app/components/styled/BlurAppButtonStyles.js b/app/components/styled/BlurAppButtonStyles.js new file mode 100644 index 0000000000..9e7b213754 --- /dev/null +++ b/app/components/styled/BlurAppButtonStyles.js @@ -0,0 +1,24 @@ +import { colors } from '../../config'; + +import { createViewStyles } from '../../lib/styles'; + +export default { + ...createViewStyles({ + transparent:{ + backgroundColor: colors.white20, + backdropFilter: 'blur(4px)', + }, + transparentHover:{ + backgroundColor: colors.white40, + backdropFilter: 'blur(4px)', + }, + redTransparent:{ + backgroundColor: colors.red40, + backdropFilter: 'blur(4px)', + }, + redTransparentHover:{ + backgroundColor: colors.red45, + backdropFilter: 'blur(4px)', + }, + }) +};
\ No newline at end of file diff --git a/app/components/styled/CellButton.js b/app/components/styled/CellButton.js index 400a7ac6de..1a0e055b01 100644 --- a/app/components/styled/CellButton.js +++ b/app/components/styled/CellButton.js @@ -82,9 +82,9 @@ type State = { hovered: boolean }; export default class CellButton extends Component<CellButtonProps, State> { state = { hovered: false }; - textStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle || styles.white80 : null; - iconStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle || styles.white40 : null; - subtextStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle || styles.white40 : null; + textStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle : null; + iconStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle : null; + subtextStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle : null; backgroundStyle = (cellHoverStyle?: Types.ViewStyle) => this.state.hovered ? cellHoverStyle || styles.blueHover : null; onHoverStart = () => !this.props.disabled ? this.setState({ hovered: true }) : null; diff --git a/app/components/styled/index.js b/app/components/styled/index.js index de18529cac..7efeb28389 100644 --- a/app/components/styled/index.js +++ b/app/components/styled/index.js @@ -2,7 +2,7 @@ import { Button } from './Button'; import CellButton, { Label, SubText } from './CellButton'; -import { RedButton, GreenButton, BlueButton, TransparentButton } from './AppButton'; +import { RedButton, GreenButton, BlueButton, TransparentButton, RedTransparentButton } from './AppButton'; export { Button, @@ -11,6 +11,7 @@ export { GreenButton, BlueButton, TransparentButton, + RedTransparentButton, Label, SubText, }; diff --git a/test/components/Connect.spec.js b/test/components/Connect.spec.js index 9c6ed0d109..59079082c5 100644 --- a/test/components/Connect.spec.js +++ b/test/components/Connect.spec.js @@ -2,10 +2,9 @@ import { expect } from 'chai'; import React from 'react'; -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; import Connect from '../../app/components/Connect'; -import Header from '../../app/components/HeaderBar'; import type { ConnectProps } from '../../app/components/Connect'; @@ -19,13 +18,12 @@ describe('components/Connect', () => { } }); - const header = component.find(Header); - const securityMessage = component.find('.connect__status-security--unsecured'); - const connectButton = component.find('.button .button--positive'); - + const header = getComponent(component, 'header'); + const securityMessage = getComponent(component, 'networkSecurityMessage'); + const connectButton = getComponent(component, 'secureConnection'); expect(header.prop('style')).to.equal('error'); - expect(securityMessage.text().toLowerCase()).to.contain('unsecured'); - expect(connectButton.text()).to.equal('Secure my connection'); + expect(securityMessage.html()).to.contain('UNSECURED'); + expect(connectButton.html()).to.contain('Secure my connection'); }); it('shows secured hints when connected', () => { @@ -36,13 +34,12 @@ describe('components/Connect', () => { } }); - const header = component.find(Header); - const securityMessage = component.find('.connect__status-security--secure'); - const disconnectButton = component.find('.button .button--negative-light'); - + const header = getComponent(component, 'header'); + const securityMessage = getComponent(component, 'networkSecurityMessage'); + const disconnectButton = getComponent(component, 'disconnect'); expect(header.prop('style')).to.equal('success'); - expect(securityMessage.text().toLowerCase()).to.contain('secure'); - expect(disconnectButton.text()).to.equal('Disconnect'); + expect(securityMessage.html()).to.contain('SECURE '); + expect(disconnectButton.html()).to.contain('Disconnect'); }); it('shows the connection location when connecting', () => { @@ -54,12 +51,12 @@ describe('components/Connect', () => { city: 'Oslo', } }); - const countryAndCity = component.find('.connect__status-location'); - const ipAddr = component.find('.connect__status-ipaddress'); + const countryAndCity = getComponent(component, 'location'); + const ipAddr = getComponent(component, 'ipAddress'); - expect(countryAndCity.text()).to.contain('Norway'); - expect(countryAndCity.text()).not.to.contain('Oslo'); - expect(ipAddr.text()).to.be.empty; + expect(countryAndCity.html()).to.contain('Norway'); + expect(countryAndCity.html()).not.to.contain('Oslo'); + expect(ipAddr.length).to.equal(0); }); it('shows the connection location when connected', () => { @@ -72,12 +69,12 @@ describe('components/Connect', () => { ip: '4.3.2.1', } }); - const countryAndCity = component.find('.connect__status-location'); - const ipAddr = component.find('.connect__status-ipaddress'); + const countryAndCity = getComponent(component, 'location'); + const ipAddr = getComponent(component, 'ipAddress'); - expect(countryAndCity.text()).to.contain('Norway'); - expect(countryAndCity.text()).to.contain('Oslo'); - expect(ipAddr.text()).to.contain('4.3.2.1'); + expect(countryAndCity.html()).to.contain('Norway'); + expect(countryAndCity.html()).to.contain('Oslo'); + expect(ipAddr.html()).to.contain('4.3.2.1'); }); it('shows the connection location when disconnected', () => { @@ -90,12 +87,12 @@ describe('components/Connect', () => { ip: '4.3.2.1', } }); - const countryAndCity = component.find('.connect__status-location'); - const ipAddr = component.find('.connect__status-ipaddress'); + const countryAndCity = getComponent(component, 'location'); + const ipAddr = getComponent(component, 'ipAddress'); - expect(countryAndCity.text()).to.contain('Norway'); - expect(countryAndCity.text()).to.not.contain('Oslo'); - expect(ipAddr.text()).to.contain('4.3.2.1'); + expect(countryAndCity.html()).to.contain('Norway'); + expect(countryAndCity.html()).to.not.contain('Oslo'); + expect(ipAddr.html()).to.contain('4.3.2.1'); }); it('invokes the onConnect prop', (done) => { @@ -106,9 +103,9 @@ describe('components/Connect', () => { status: 'disconnected', } }); - const connectButton = component.find('.button .button--positive'); + const connectButton = getComponent(component, 'secureConnection'); - connectButton.simulate('click'); + connectButton.prop('onPress')(); }); }); @@ -134,5 +131,9 @@ const defaultProps: ConnectProps = { function renderWithProps(customProps: $Shape<ConnectProps>) { const props = { ...defaultProps, ...customProps }; - return mount( <Connect { ...props } /> ); + return shallow( <Connect { ...props } /> ); } + +function getComponent(container, testName) { + return container.findWhere( n => n.prop('testName') === testName); +}
\ No newline at end of file diff --git a/test/components/SelectLocation.spec.js b/test/components/SelectLocation.spec.js index 59a8dc9770..4e2265c6dd 100644 --- a/test/components/SelectLocation.spec.js +++ b/test/components/SelectLocation.spec.js @@ -2,7 +2,7 @@ import { expect } from 'chai'; import * as React from 'react'; -import ReactTestUtils, { Simulate } from 'react-dom/test-utils'; +import { shallow } from 'enzyme'; import SelectLocation from '../../app/components/SelectLocation'; import type { SettingsReduxState } from '../../app/redux/settings/reducers'; @@ -48,7 +48,7 @@ describe('components/SelectLocation', () => { }; const render = (props: SelectLocationProps) => { - return ReactTestUtils.renderIntoDocument( + return shallow( <SelectLocation { ...props } /> ); }; @@ -57,11 +57,8 @@ describe('components/SelectLocation', () => { const props = makeProps(state, { onClose: () => done() }); - const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'select-location__close'); - // See: https://github.com/facebook/flow/pull/5841 - if(domNode) { - Simulate.click(domNode); - } + const node = getComponent(render(props), 'close'); + click(node); }); it('should call select callback for country', (done) => { @@ -77,9 +74,9 @@ describe('components/SelectLocation', () => { } } }); - const elements = ReactTestUtils.scryRenderedDOMComponentsWithClass(render(props), 'select-location__cell'); + const elements = getComponent(render(props), 'country'); expect(elements).to.have.length(1); - Simulate.click(elements[0]); + click(elements.at(0)); }); it('should call select callback for city', (done) => { @@ -95,9 +92,17 @@ describe('components/SelectLocation', () => { } } }); - const elements = ReactTestUtils.scryRenderedDOMComponentsWithClass(render(props), 'select-location__sub-cell'); + const elements = getComponent(render(props), 'city'); expect(elements).to.have.length(2); - Simulate.click(elements[0]); + click(elements.at(0)); }); }); + +function getComponent(container, testName) { + return container.findWhere( n => n.prop('testName') === testName); +} + +function click(component) { + component.prop('onPress')(); +} |
