diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-07-23 17:06:48 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-07-23 17:06:48 +0200 |
| commit | d258c7614f6c41c6f78939e9d47edff2d44af19f (patch) | |
| tree | 850e39a41014918b89189be2c62283b78c099161 | |
| parent | 26258ff77bcd32ca362aa6b0e0087b9602076d2e (diff) | |
| parent | 858c95cdf3bb94ff4e75eb63c8c982520a5fb629 (diff) | |
| download | mullvadvpn-d258c7614f6c41c6f78939e9d47edff2d44af19f.tar.xz mullvadvpn-d258c7614f6c41c6f78939e9d47edff2d44af19f.zip | |
Merge branch 'add-settings-navigation'
| -rw-r--r-- | app/components/Account.js | 14 | ||||
| -rw-r--r-- | app/components/AccountStyles.js | 20 | ||||
| -rw-r--r-- | app/components/AdvancedSettings.js | 75 | ||||
| -rw-r--r-- | app/components/AdvancedSettingsStyles.js | 19 | ||||
| -rw-r--r-- | app/components/NavigationBar.js | 96 | ||||
| -rw-r--r-- | app/components/Preferences.js | 20 | ||||
| -rw-r--r-- | app/components/PreferencesStyles.js | 26 | ||||
| -rw-r--r-- | app/components/Settings.js | 12 | ||||
| -rw-r--r-- | app/components/SettingsStyles.js | 11 | ||||
| -rw-r--r-- | app/components/Support.js | 14 | ||||
| -rw-r--r-- | app/components/SupportStyles.js | 19 | ||||
| -rw-r--r-- | test/components/Account.spec.js | 15 | ||||
| -rw-r--r-- | test/components/HeaderBar.spec.js | 6 | ||||
| -rw-r--r-- | test/components/Login.spec.js | 7 | ||||
| -rw-r--r-- | test/components/Preferences.spec.js | 3 | ||||
| -rw-r--r-- | test/components/SelectLocation.spec.js | 10 | ||||
| -rw-r--r-- | test/components/Settings.spec.js | 27 | ||||
| -rw-r--r-- | test/components/Support.spec.js | 6 |
18 files changed, 184 insertions, 216 deletions
diff --git a/app/components/Account.js b/app/components/Account.js index 9806c7e286..dc26121c4c 100644 --- a/app/components/Account.js +++ b/app/components/Account.js @@ -1,9 +1,10 @@ // @flow import moment from 'moment'; import * as React from 'react'; -import { Button, Component, Text, View, App, Types } from 'reactxp'; +import { Component, Text, View, App, Types } from 'reactxp'; import * as AppButton from './AppButton'; import { Layout, Container } from './Layout'; +import NavigationBar, { BackBarItem } from './NavigationBar'; import styles from './AccountStyles'; import Img from './Img'; import { formatAccount } from '../lib/formatters'; @@ -63,13 +64,10 @@ export default class Account extends Component<AccountProps, AccountState> { <Layout> <Container> <View style={styles.account}> - <Button - style={styles.account__close} - onPress={this.props.onClose} - testName="account__close"> - <Img height={24} width={24} style={styles.account__close_icon} source="icon-back" /> - <Text style={styles.account__close_title}>Settings</Text> - </Button> + <NavigationBar> + <BackBarItem action={this.props.onClose} title={'Settings'} /> + </NavigationBar> + <View style={styles.account__container}> <View style={styles.account__header}> <Text style={styles.account__title}>Account</Text> diff --git a/app/components/AccountStyles.js b/app/components/AccountStyles.js index 67c16900a7..eda95d5dec 100644 --- a/app/components/AccountStyles.js +++ b/app/components/AccountStyles.js @@ -23,20 +23,6 @@ export default { paddingLeft: 24, paddingBottom: 12, }, - account__close: { - flexDirection: 'row', - alignItems: 'center', - alignSelf: 'flex-start', - marginLeft: 12, - marginTop: 24, - cursor: 'default', - }, - account__close_icon: { - width: 24, - height: 24, - opacity: 0.6, - marginRight: 8, - }, account__scrollview: { flexGrow: 1, flexShrink: 1, @@ -63,12 +49,6 @@ export default { }, }), ...createTextStyles({ - account__close_title: { - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - color: colors.white60, - }, account__title: { fontFamily: 'DINPro', fontSize: 32, diff --git a/app/components/AdvancedSettings.js b/app/components/AdvancedSettings.js index 21afbcd97e..52e8a634b0 100644 --- a/app/components/AdvancedSettings.js +++ b/app/components/AdvancedSettings.js @@ -3,6 +3,7 @@ import * as React from 'react'; import { Button, Component, Text, View } from 'reactxp'; import { Layout, Container } from './Layout'; +import NavigationBar, { BackBarItem } from './NavigationBar'; import CustomScrollbars from './CustomScrollbars'; import styles from './AdvancedSettingsStyles'; import Img from './Img'; @@ -26,20 +27,37 @@ export class AdvancedSettings extends Component<AdvancedSettingsProps> { } return ( - <BaseLayout onClose={this.props.onClose}> - <Selector - title={'Network protocols'} - values={['Automatic', 'UDP', 'TCP']} - value={protocol} - onSelect={(protocol) => { - this.props.onUpdate(protocol, 'Automatic'); - }} - /> + <Layout> + <Container> + <View style={styles.advanced_settings}> + <NavigationBar> + <BackBarItem action={this.props.onClose} title={'Settings'} /> + </NavigationBar> + + <View style={styles.advanced_settings__container}> + <View style={styles.advanced_settings__header}> + <Text style={styles.advanced_settings__title}>Advanced</Text> + </View> + <CustomScrollbars style={styles.advanced_settings__scrollview} autoHide={true}> + <View style={styles.advanced_settings__content}> + <Selector + title={'Network protocols'} + values={['Automatic', 'UDP', 'TCP']} + value={protocol} + onSelect={(protocol) => { + this.props.onUpdate(protocol, 'Automatic'); + }} + /> - <View style={styles.advanced_settings__cell_spacer} /> + <View style={styles.advanced_settings__cell_spacer} /> - {portSelector} - </BaseLayout> + {portSelector} + </View> + </CustomScrollbars> + </View> + </View> + </Container> + </Layout> ); } @@ -140,36 +158,3 @@ class Selector extends Component<SelectorProps<*>, SelectorState> { ); } } - -function BaseLayout(props) { - return ( - <Layout> - <Container> - <View style={styles.advanced_settings}> - <Button - style={styles.advanced_settings__close} - onPress={props.onClose} - testName="closeButton"> - <View style={styles.advanced_settings__close_content}> - <Img - height={24} - width={24} - style={styles.advanced_settings__close_icon} - source="icon-back" - /> - <Text style={styles.advanced_settings__close_title}>Settings</Text> - </View> - </Button> - <View style={styles.advanced_settings__container}> - <View style={styles.advanced_settings__header}> - <Text style={styles.advanced_settings__title}>Advanced</Text> - </View> - <CustomScrollbars style={styles.advanced_settings__scrollview} autoHide={true}> - <View style={styles.advanced_settings__content}>{props.children}</View> - </CustomScrollbars> - </View> - </View> - </Container> - </Layout> - ); -} diff --git a/app/components/AdvancedSettingsStyles.js b/app/components/AdvancedSettingsStyles.js index 6dc9546d36..6d23ee929b 100644 --- a/app/components/AdvancedSettingsStyles.js +++ b/app/components/AdvancedSettingsStyles.js @@ -22,19 +22,6 @@ export default { paddingLeft: 24, paddingBottom: 24, }, - advanced_settings__close: { - cursor: 'default', - }, - advanced_settings__close_content: { - flexDirection: 'row', - alignItems: 'center', - paddingLeft: 12, - paddingTop: 24, - }, - advanced_settings__close_icon: { - opacity: 0.6, - marginRight: 8, - }, advanced_settings__scrollview: { flexGrow: 1, flexShrink: 1, @@ -113,12 +100,6 @@ export default { lineHeight: 26, color: colors.white, }, - advanced_settings__close_title: { - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - color: colors.white60, - }, advanced_settings__title: { fontFamily: 'DINPro', fontSize: 32, diff --git a/app/components/NavigationBar.js b/app/components/NavigationBar.js new file mode 100644 index 0000000000..c1a5daab1e --- /dev/null +++ b/app/components/NavigationBar.js @@ -0,0 +1,96 @@ +// @flow + +import * as React from 'react'; +import { Button, Component, Text, View, Styles } from 'reactxp'; +import Img from './Img'; +import { colors } from '../config'; + +const styles = { + navigationBar: { + default: Styles.createViewStyle({ + flex: 0, + alignItems: 'flex-start', + marginLeft: 12, + }), + darwin: Styles.createViewStyle({ + marginTop: 24, + }), + windows: Styles.createViewStyle({ + marginTop: 24, + }), + linux: Styles.createViewStyle({ + marginTop: 12, + }), + }, + closeBarItem: { + default: Styles.createViewStyle({ + cursor: 'default', + }), + icon: Styles.createViewStyle({ + flex: 0, + opacity: 0.6, + }), + }, + backBarButton: { + default: Styles.createViewStyle({ + borderWidth: 0, + padding: 0, + margin: 0, + cursor: 'default', + }), + content: Styles.createViewStyle({ + flexDirection: 'row', + alignItems: 'center', + }), + label: Styles.createTextStyle({ + fontFamily: 'Open Sans', + fontSize: 13, + fontWeight: '600', + color: colors.white60, + }), + icon: Styles.createViewStyle({ + opacity: 0.6, + marginRight: 8, + }), + }, +}; + +export default class NavigationBar extends Component { + render() { + return ( + <View style={[styles.navigationBar.default, styles.navigationBar[process.platform]]}> + {this.props.children} + </View> + ); + } +} + +export class CloseBarItem extends Component { + props: { + action: () => void, + }; + render() { + return ( + <Button style={[styles.closeBarItem.default]} onPress={this.props.action}> + <Img height={24} width={24} style={[styles.closeBarItem.icon]} source="icon-close" /> + </Button> + ); + } +} + +export class BackBarItem extends Component { + props: { + title: string, + action: () => void, + }; + render() { + return ( + <Button style={styles.backBarButton.default} onPress={this.props.action}> + <View style={styles.backBarButton.content}> + <Img style={styles.backBarButton.icon} source="icon-back" /> + <Text style={styles.backBarButton.label}>{this.props.title}</Text> + </View> + </Button> + ); + } +} diff --git a/app/components/Preferences.js b/app/components/Preferences.js index 7082d1749e..9a0143edff 100644 --- a/app/components/Preferences.js +++ b/app/components/Preferences.js @@ -1,8 +1,8 @@ // @flow import React from 'react'; -import { Component, Text, Button, View } from 'reactxp'; -import { Layout, Container, Header } from './Layout'; -import Img from './Img'; +import { Component, Text, View } from 'reactxp'; +import { Layout, Container } from './Layout'; +import NavigationBar, { BackBarItem } from './NavigationBar'; import Switch from './Switch'; import styles from './PreferencesStyles'; @@ -33,18 +33,12 @@ export default class Preferences extends Component<PreferencesProps, State> { render() { return ( <Layout> - <Header hidden={true} style={'defaultDark'} /> <Container> <View style={styles.preferences}> - <Button - style={styles.preferences__close} - onPress={this.props.onClose} - testName="closeButton"> - <View style={styles.preferences__close_content}> - <Img style={styles.preferences__close_icon} source="icon-back" /> - <Text style={styles.preferences__close_title}>Settings</Text> - </View> - </Button> + <NavigationBar> + <BackBarItem action={this.props.onClose} title={'Settings'} /> + </NavigationBar> + <View style={styles.preferences__container}> <View style={styles.preferences__header}> <Text style={styles.preferences__title}>Preferences</Text> diff --git a/app/components/PreferencesStyles.js b/app/components/PreferencesStyles.js index 9d0192df2f..eaedf5f761 100644 --- a/app/components/PreferencesStyles.js +++ b/app/components/PreferencesStyles.js @@ -17,29 +17,11 @@ export default { flexGrow: 0, flexShrink: 0, flexBasis: 'auto', - paddingTop: 40, + paddingTop: 16, paddingRight: 24, paddingLeft: 24, paddingBottom: 24, }, - preferences__close: { - position: 'absolute', - top: 0, - left: 12, - borderWidth: 0, - padding: 0, - margin: 0, - zIndex: 1 /* part of .preferences__container covers the button */, - cursor: 'default', - }, - preferences__close_content: { - flexDirection: 'row', - alignItems: 'center', - }, - preferences__close_icon: { - opacity: 0.6, - marginRight: 8, - }, preferences__content: { flexDirection: 'column', flexGrow: 1, @@ -69,12 +51,6 @@ export default { }, }), ...createTextStyles({ - preferences__close_title: { - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - color: colors.white60, - }, preferences__title: { fontFamily: 'DINPro', fontSize: 32, diff --git a/app/components/Settings.js b/app/components/Settings.js index 1c57b026fc..260003b5c6 100644 --- a/app/components/Settings.js +++ b/app/components/Settings.js @@ -1,10 +1,11 @@ // @flow import moment from 'moment'; import * as React from 'react'; -import { Button, Component, Text, View } from 'reactxp'; +import { Component, Text, View } from 'reactxp'; import * as AppButton from './AppButton'; import * as Cell from './Cell'; import { Layout, Container } from './Layout'; +import NavigationBar, { CloseBarItem } from './NavigationBar'; import CustomScrollbars from './CustomScrollbars'; import styles from './SettingsStyles'; import Img from './Img'; @@ -31,12 +32,9 @@ export default class Settings extends Component<SettingsProps> { <Layout> <Container> <View style={styles.settings}> - <Button - style={styles.settings__close} - onPress={this.props.onClose} - testName="settings__close"> - <Img height={24} width={24} style={styles.settings__close_icon} source="icon-close" /> - </Button> + <NavigationBar> + <CloseBarItem action={this.props.onClose} /> + </NavigationBar> <View style={styles.settings__container}> <View style={styles.settings__header}> diff --git a/app/components/SettingsStyles.js b/app/components/SettingsStyles.js index 5e4eee192e..9bc7c16a08 100644 --- a/app/components/SettingsStyles.js +++ b/app/components/SettingsStyles.js @@ -31,17 +31,6 @@ export default { flexShrink: 1, flexBasis: '100%', }, - settings__close: { - marginLeft: 12, - marginTop: 24, - cursor: 'default', - }, - settings__close_icon: { - width: 24, - height: 24, - flex: 0, - opacity: 0.6, - }, settings__cell_spacer: { height: 24, flex: 0, diff --git a/app/components/Support.js b/app/components/Support.js index 9c411b1ff0..34fc360215 100644 --- a/app/components/Support.js +++ b/app/components/Support.js @@ -1,8 +1,9 @@ // @flow import * as React from 'react'; -import { Button, Component, Text, View, TextInput } from 'reactxp'; +import { Component, Text, View, TextInput } from 'reactxp'; import * as AppButton from './AppButton'; import { Layout, Container } from './Layout'; +import NavigationBar, { BackBarItem } from './NavigationBar'; import styles from './SupportStyles'; import Img from './Img'; @@ -154,13 +155,10 @@ export default class Support extends Component<SupportProps, SupportState> { <Layout> <Container> <View style={styles.support}> - <Button - style={styles.support__close} - onPress={this.props.onClose} - testName="support__close"> - <Img height={24} width={24} style={styles.support__close_icon} source="icon-back" /> - <Text style={styles.support__close_title}>Settings</Text> - </Button> + <NavigationBar> + <BackBarItem action={this.props.onClose} title={'Settings'} /> + </NavigationBar> + <View style={styles.support__container}> {header} diff --git a/app/components/SupportStyles.js b/app/components/SupportStyles.js index e6323aafd1..77b4609568 100644 --- a/app/components/SupportStyles.js +++ b/app/components/SupportStyles.js @@ -19,19 +19,6 @@ export default Object.assign( paddingLeft: 24, paddingRight: 24, }, - support__close: { - cursor: 'default', - paddingLeft: 12, - paddingTop: 24, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'flex-start', - }, - support__close_icon: { - flex: 0, - opacity: 0.6, - marginRight: 8, - }, support__content: { flex: 1, display: 'flex', @@ -75,12 +62,6 @@ export default Object.assign( }, }), createTextStyles({ - support__close_title: { - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - color: colors.white60, - }, support__title: { fontFamily: 'DINPro', fontSize: 32, diff --git a/test/components/Account.spec.js b/test/components/Account.spec.js index 2c0979bb50..ef2ffe6d16 100644 --- a/test/components/Account.spec.js +++ b/test/components/Account.spec.js @@ -3,6 +3,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import Account from '../../app/components/Account'; +import { BackBarItem } from '../../app/components/NavigationBar'; import type { AccountProps } from '../../app/components/Account'; describe('components/Account', () => { @@ -25,8 +26,10 @@ describe('components/Account', () => { const props = makeProps({ onClose: () => done(), }); - const component = getComponent(render(props), 'account__close'); - click(component); + const component = render(props) + .find(BackBarItem) + .dive(); + component.simulate('press'); }); it('should call logout callback', (done) => { @@ -34,7 +37,7 @@ describe('components/Account', () => { onLogout: () => done(), }); const component = getComponent(render(props), 'account__logout'); - click(component); + component.simulate('press'); }); it('should call "buy more" callback', (done) => { @@ -42,7 +45,7 @@ describe('components/Account', () => { onBuyMore: () => done(), }); const component = getComponent(render(props), 'account__buymore'); - click(component); + component.simulate('press'); }); it('should display "out of time" message when account expired', () => { @@ -67,7 +70,3 @@ function render(props) { function getComponent(container, testName) { return container.findWhere((n) => n.prop('testName') === testName); } - -function click(component) { - component.prop('onPress')(); -} diff --git a/test/components/HeaderBar.spec.js b/test/components/HeaderBar.spec.js index 6fcbfb54d7..a8eca00d53 100644 --- a/test/components/HeaderBar.spec.js +++ b/test/components/HeaderBar.spec.js @@ -43,7 +43,7 @@ describe('components/HeaderBar', () => { onSettings: () => done(), }); const settingsButton = getComponent(component, 'headerbar__settings'); - click(settingsButton); + settingsButton.simulate('press'); }); }); @@ -58,7 +58,3 @@ function getComponent(container, testName) { function hasChild(container, testName) { return getComponent(container, testName).length > 0; } - -function click(component) { - component.prop('onPress')(); -} diff --git a/test/components/Login.spec.js b/test/components/Login.spec.js index 76acbd6330..0db789c55d 100644 --- a/test/components/Login.spec.js +++ b/test/components/Login.spec.js @@ -59,7 +59,8 @@ describe('components/Login', () => { }, }); - click(getComponent(component, 'account-input-button')); + const accountInputButton = getComponent(component, 'account-input-button'); + accountInputButton.simulate('press'); }); }); @@ -79,7 +80,3 @@ const defaultProps = { function getComponent(container, testName) { return container.findWhere((n) => n.prop('testName') === testName); } - -function click(component) { - component.prop('onPress')(); -} diff --git a/test/components/Preferences.spec.js b/test/components/Preferences.spec.js index f8c38f9493..51be0dc473 100644 --- a/test/components/Preferences.spec.js +++ b/test/components/Preferences.spec.js @@ -3,12 +3,13 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import Preferences from '../../app/components/Preferences'; +import { BackBarItem } from '../../app/components/NavigationBar'; describe('components/Preferences', () => { it('Should call close handler', (done) => { const props = makeProps({ onClose: done }); const component = shallow(<Preferences {...props} />); - const button = component.find({ testName: 'closeButton' }); + const button = component.find(BackBarItem).dive(); expect(button).to.have.length(1); button.simulate('press'); }); diff --git a/test/components/SelectLocation.spec.js b/test/components/SelectLocation.spec.js index dbfe15221a..bb5256cf54 100644 --- a/test/components/SelectLocation.spec.js +++ b/test/components/SelectLocation.spec.js @@ -64,7 +64,7 @@ describe('components/SelectLocation', () => { onClose: () => done(), }); const node = getComponent(render(props), 'close'); - click(node); + node.simulate('press'); }); it('should call select callback for country', (done) => { @@ -82,7 +82,7 @@ describe('components/SelectLocation', () => { }); const elements = getComponent(render(props), 'country'); expect(elements).to.have.length(1); - click(elements.at(0)); + elements.at(0).simulate('press'); }); it('should call select callback for city', (done) => { @@ -100,14 +100,10 @@ describe('components/SelectLocation', () => { }); const elements = getComponent(render(props), 'city'); expect(elements).to.have.length(2); - click(elements.at(0)); + elements.at(0).simulate('press'); }); }); function getComponent(container, testName) { return container.findWhere((n) => n.prop('testName') === testName); } - -function click(component) { - component.prop('onPress')(); -} diff --git a/test/components/Settings.spec.js b/test/components/Settings.spec.js index 2a152e1cc8..9edf46fcc3 100644 --- a/test/components/Settings.spec.js +++ b/test/components/Settings.spec.js @@ -1,8 +1,9 @@ // @flow import * as React from 'react'; -import Settings from '../../app/components/Settings'; import { shallow } from 'enzyme'; +import Settings from '../../app/components/Settings'; +import { CloseBarItem } from '../../app/components/NavigationBar'; import type { AccountReduxState } from '../../app/redux/account/reducers'; import type { SettingsReduxState } from '../../app/redux/settings/reducers'; @@ -124,8 +125,10 @@ describe('components/Settings', () => { const props = makeProps(loggedOutAccountState, settingsState, { onClose: () => done(), }); - const component = getComponent(render(props), 'settings__close'); - click(component); + const component = render(props) + .find(CloseBarItem) + .dive(); + component.simulate('press'); }); it('should call quit callback', (done) => { @@ -133,7 +136,7 @@ describe('components/Settings', () => { onQuit: () => done(), }); const component = getComponent(render(props), 'settings__quit'); - click(component); + component.simulate('press'); }); it('should call account callback', (done) => { @@ -141,7 +144,7 @@ describe('components/Settings', () => { onViewAccount: () => done(), }); const component = getComponent(render(props), 'settings__account_paid_until_button'); - click(component); + component.simulate('press'); }); it('should call advanced settings callback', (done) => { @@ -149,7 +152,7 @@ describe('components/Settings', () => { onViewAdvancedSettings: () => done(), }); const component = getComponent(render(props), 'settings__advanced'); - click(component); + component.simulate('press'); }); it('should call preferences callback', (done) => { @@ -157,7 +160,7 @@ describe('components/Settings', () => { onViewPreferences: () => done(), }); const component = getComponent(render(props), 'settings__preferences'); - click(component); + component.simulate('press'); }); it('should call support callback', (done) => { @@ -165,7 +168,7 @@ describe('components/Settings', () => { onViewSupport: () => done(), }); const component = getComponent(render(props), 'settings__view_support'); - click(component); + component.simulate('press'); }); it('should call external links callback', () => { @@ -176,7 +179,9 @@ describe('components/Settings', () => { }, }); const container = getComponent(render(props), 'settings__external_link'); - container.find({ testName: 'settings__external_link' }).forEach((element) => click(element)); + container + .find({ testName: 'settings__external_link' }) + .forEach((element) => element.simulate('press')); expect(collectedExternalLinkTypes).to.include.ordered.members(['faq', 'guides']); }); @@ -189,7 +194,3 @@ function render(props) { function getComponent(container, testName) { return container.findWhere((n) => n.prop('testName') === testName); } - -function click(component) { - component.prop('onPress')(); -} diff --git a/test/components/Support.spec.js b/test/components/Support.spec.js index 59ddc25606..23172d3403 100644 --- a/test/components/Support.spec.js +++ b/test/components/Support.spec.js @@ -4,14 +4,16 @@ import * as React from 'react'; import Support from '../../app/components/Support'; import { shallow } from 'enzyme'; import type { SupportProps } from '../../app/components/Support'; +import { BackBarItem } from '../../app/components/NavigationBar'; describe('components/Support', () => { it('should call close callback', () => { const props = makeProps({ onClose: spy() }); const component = shallow(<Support {...props} />); - const closeButton = component.find({ testName: 'support__close' }); - click(closeButton); + const closeButton = component.find(BackBarItem).dive(); + closeButton.simulate('press'); + expect(props.onClose).to.have.been.called.once; }); |
