diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2017-10-19 14:17:44 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2017-10-19 14:17:44 +0200 |
| commit | 934f9287c6639ca6ffa761d7322666c85cec07f7 (patch) | |
| tree | 74267c2e8d8c7dae1c991b99dcab8ea29640e6f8 | |
| parent | 47a585cc0bec626836c53538b6fa17701d182af5 (diff) | |
| parent | d92d943bdef837b8f1bd638d76ba037ef718b7ba (diff) | |
| download | mullvadvpn-934f9287c6639ca6ffa761d7322666c85cec07f7.tar.xz mullvadvpn-934f9287c6639ca6ffa761d7322666c85cec07f7.zip | |
Merge branch 'problem-report'
| -rw-r--r-- | app/assets/css/buttons.css | 21 | ||||
| -rw-r--r-- | app/assets/css/style.css | 1 | ||||
| -rw-r--r-- | app/assets/images/icon-email.svg | 6 | ||||
| -rw-r--r-- | app/components/Account.css | 7 | ||||
| -rw-r--r-- | app/components/SelectLocation.css | 2 | ||||
| -rw-r--r-- | app/components/Settings.css | 3 | ||||
| -rw-r--r-- | app/components/Settings.js | 22 | ||||
| -rw-r--r-- | app/components/Support.css | 134 | ||||
| -rw-r--r-- | app/components/Support.js | 108 | ||||
| -rw-r--r-- | app/containers/AccountPage.js | 1 | ||||
| -rw-r--r-- | app/containers/SettingsPage.js | 1 | ||||
| -rw-r--r-- | app/containers/SupportPage.js | 17 | ||||
| -rw-r--r-- | app/routes.js | 6 | ||||
| -rw-r--r-- | app/transitions.js | 101 | ||||
| -rw-r--r-- | test/components/Settings.spec.js | 14 | ||||
| -rw-r--r-- | test/components/Support.spec.js | 70 |
16 files changed, 425 insertions, 89 deletions
diff --git a/app/assets/css/buttons.css b/app/assets/css/buttons.css index 7c4f0203c1..805d9e9a74 100644 --- a/app/assets/css/buttons.css +++ b/app/assets/css/buttons.css @@ -10,6 +10,11 @@ line-height: 26px; justify-content: center; align-items: center; + transition: 0.25s opacity; +} + +.button:disabled { + opacity: 0.5; } .button-label { @@ -34,12 +39,12 @@ fill: rgba(255,255,255,0.8); } -.button--primary:hover { +.button--primary:not(:disabled):hover { background-color: rgba(41,71,115,0.9); color: rgba(255,255,255,1); } -.button--primary:hover .button-icon path { +.button--primary:not(:disabled):hover .button-icon path { fill: rgba(255,255,255,1); } @@ -52,7 +57,7 @@ color: rgba(255,255,255,0.6); } -.button--secondary:hover { +.button--secondary:not(:disabled):hover { background-color: rgba(41,71,115,0.5); color: rgba(255,255,255,0.8); } @@ -66,7 +71,7 @@ color: rgba(255,255,255,0.8); } -.button--negative:hover { +.button--negative:not(:disabled):hover { background-color: rgba(208,2,27,0.95); color: rgba(255,255,255,1); } @@ -80,7 +85,7 @@ color: rgba(255,255,255,0.6); } -.button--negative-light:hover { +.button--negative-light:not(:disabled):hover { background-color: rgba(208,2,27,0.45); color: rgba(255,255,255,0.8); } @@ -98,12 +103,12 @@ fill: rgba(255,255,255,0.8); } -.button--positive:hover { +.button--positive:not(:disabled):hover { background-color: rgba(63,173,77,0.9); color: rgba(255,255,255,1); } -.button--positive:hover .button-icon path { +.button--positive:not(:disabled):hover .button-icon path { fill: rgba(255,255,255,1); } @@ -116,7 +121,7 @@ color: rgba(255,255,255,0.8); } -.button--neutral:hover { +.button--neutral:not(:disabled):hover { background-color: rgba(255,255,255,0.25); color: rgba(255,255,255,1); } diff --git a/app/assets/css/style.css b/app/assets/css/style.css index 95e601a060..8b0bd30f23 100644 --- a/app/assets/css/style.css +++ b/app/assets/css/style.css @@ -12,6 +12,7 @@ @import '../../components/Connect.css'; @import '../../components/Settings.css'; @import '../../components/Account.css'; +@import '../../components/Support.css'; @import '../../components/SelectLocation.css'; @import '../../components/HeaderBar.css'; @import '../../components/Layout.css'; diff --git a/app/assets/images/icon-email.svg b/app/assets/images/icon-email.svg deleted file mode 100644 index 5a8a8c0945..0000000000 --- a/app/assets/images/icon-email.svg +++ /dev/null @@ -1,6 +0,0 @@ -<svg width="16px" height="12px" viewBox="0 0 16 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> - <title>path</title> - <desc>Mullvad VPN app</desc> - <defs></defs> - <path d="M3.80277564,2 L8,4.79814957 L8,4.79814957 L12.1972244,2 L3.80277564,2 Z M14,3.20185043 L8.57398104,6.8191964 C8.40423955,6.93806521 8.20219416,7.00036557 7.98908928,7.00003642 C7.7891813,6.9976758 7.59213144,6.93552384 7.42601896,6.8191964 L2,3.20185043 L2,10 L14,10 L14,3.20185043 Z M0,1.00247329 C0,0.448822582 0.444630861,0 1.00087166,0 L14.9991283,0 C15.5518945,0 16,0.455760956 16,1.00247329 L16,10.9975267 C16,11.5511774 15.5553691,12 14.9991283,12 L1.00087166,12 C0.448105505,12 0,11.544239 0,10.9975267 L0,1.00247329 Z" id="path" fill="#FFFFFF" fill-rule="evenodd"></path> -</svg>
\ No newline at end of file diff --git a/app/components/Account.css b/app/components/Account.css index 21aefef292..ab94518034 100644 --- a/app/components/Account.css +++ b/app/components/Account.css @@ -24,8 +24,7 @@ margin: 0; top: 24px; left: 12px; - z-index: 1; /* part of .account__container convers the button */ - + z-index: 1; /* part of .account__container covers the button */ } .account__close-icon { @@ -35,8 +34,8 @@ .account__close-title { font-family: "Open Sans"; - font-size: 13px; - font-weight: 600; + font-size: 13px; + font-weight: 600; color: rgba(255, 255, 255, 0.6); } diff --git a/app/components/SelectLocation.css b/app/components/SelectLocation.css index 72684711a1..cb63f61115 100644 --- a/app/components/SelectLocation.css +++ b/app/components/SelectLocation.css @@ -28,7 +28,7 @@ background-color: transparent; background-image: url(../assets/images/icon-close.svg); opacity: 0.6; - z-index: 1; /* part of .select-location__container convers the button */ + z-index: 1; /* part of .select-location__container covers the button */ } .select-location__title { diff --git a/app/components/Settings.css b/app/components/Settings.css index 4bd2de5929..7bb3dfcec7 100644 --- a/app/components/Settings.css +++ b/app/components/Settings.css @@ -35,7 +35,7 @@ background-color: transparent; background-image: url(../assets/images/icon-close.svg); opacity: 0.6; - z-index: 1; /* part of .settings__container convers the button */ + z-index: 1; /* part of .settings__container covers the button */ } .settings__title { @@ -95,6 +95,7 @@ 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; } diff --git a/app/components/Settings.js b/app/components/Settings.js index 9dddb2267d..4c906a9dc1 100644 --- a/app/components/Settings.js +++ b/app/components/Settings.js @@ -14,21 +14,15 @@ export type SettingsProps = { onQuit: () => void, onClose: () => void, onViewAccount: () => void, - onExternalLink: (type: string) => void, - onUpdateSettings: (update: $Shape<SettingsReduxState>) => void + onViewSupport: () => void, + onExternalLink: (type: string) => void }; export default class Settings extends Component { props: SettingsProps; - onClose = () => this.props.onClose(); - - onExternalLink(type: string) { - this.props.onExternalLink(type); - } - - render(): React.Element<*> { + render() { const isLoggedIn = this.props.account.status === 'ok'; let isOutOfTime = false, formattedExpiry = ''; let expiryIso = this.props.account.expiry; @@ -44,7 +38,7 @@ export default class Settings extends Component { <Header hidden={ true } style={ 'defaultDark' } /> <Container> <div className="settings"> - <button className="settings__close" onClick={ this.onClose } /> + <button className="settings__close" onClick={ this.props.onClose } /> <div className="settings__container"> <div className="settings__header"> <h2 className="settings__title">Settings</h2> @@ -80,17 +74,17 @@ export default class Settings extends Component { </If> <div className="settings__external"> - <div className="settings__cell settings__cell--active" onClick={ this.onExternalLink.bind(this, 'faq') }> + <div className="settings__cell settings__cell--active" onClick={ this.props.onExternalLink.bind(this, 'faq') }> <div className="settings__cell-label">FAQs</div> <img className="settings__cell-icon" src="./assets/images/icon-extLink.svg" /> </div> - <div className="settings__cell settings__cell--active" onClick={ this.onExternalLink.bind(this, 'guides') }> + <div className="settings__cell settings__cell--active" onClick={ this.props.onExternalLink.bind(this, 'guides') }> <div className="settings__cell-label">Guides</div> <img className="settings__cell-icon" src="./assets/images/icon-extLink.svg" /> </div> - <div className="settings__cell settings__cell--active" onClick={ this.onExternalLink.bind(this, 'supportEmail') }> + <div className="settings__view-support settings__cell settings__cell--active" onClick={ this.props.onViewSupport }> <div className="settings__cell-label">Contact support</div> - <img className="settings__cell-icon" src="./assets/images/icon-email.svg" /> + <img className="settings__cell-disclosure" src="assets/images/icon-chevron.svg" /> </div> </div> </div> diff --git a/app/components/Support.css b/app/components/Support.css new file mode 100644 index 0000000000..58f4df3a20 --- /dev/null +++ b/app/components/Support.css @@ -0,0 +1,134 @@ +.support { + background: #192E45; + height: 100%; +} + +.support__container { + display: flex; + flex-direction: column; + height: 100%; +} + +.support__header { + flex: 0 0 auto; + padding: 40px 24px 24px; + position: relative; /* anchor for close button */ +} + +.support__close { + position: absolute; + display: flex; + align-items: center; + border: 0; + padding: 0; + margin: 0; + top: 24px; + left: 12px; + z-index: 1; /* part of .support__container covers the button */ +} + +.support__close-icon { + opacity: 0.6; + margin-right: 8px; +} + +.support__close-title { + font-family: "Open Sans"; + font-size: 13px; + font-weight: 600; + color: rgba(255, 255, 255, 0.6); +} + +.support__title { + font-family: DINPro; + font-size: 32px; + font-weight: 900; + line-height: 40px; + color: #FFFFFF; + margin-bottom: 16px; +} + +.support__subtitle { + font-family: "Open Sans"; + font-size: 13px; + font-weight: 600; + line-height: normal; + color: rgba(255,255,255,0.8); +} + +.support__content { + flex: 1 1 auto; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.support__form { + display: flex; + flex: 1 1 auto; + flex-direction: column; +} + +.support__form-row { + padding: 0 24px; +} + +.support__form-row + .support__form-row { + margin-top: 8px; +} + +.support__form-row--description { + display: flex; + flex: 1 1 auto; +} + +.support__form-email { + width: 100%; + border-radius: 4px; + border: 0; + overflow: hidden; + padding: 10px 12px 12px 12px; + font-family: "Open Sans"; + font-size: 13px; + font-weight: 600; + line-height: 26px; + color: #294D73; + background-color: #fff; +} + +.support__form-email::-webkit-input-placeholder { + color: rgba(41,77,115,0.4); +} + +.support__form-description-scroll-wrap { + width: 100%; + display: flex; + border-radius: 4px; + overflow: hidden; +} + +.support__form-description { + width: 100%; + border: 0; + overflow-y: scroll; + resize: none; + padding: 10px 12px 12px 12px; + font-family: "Open Sans"; + font-size: 13px; + font-weight: 600; + line-height: 1.4em; + color: #294D73; + background-color: #fff; +} + +.support__form-description::-webkit-input-placeholder { + color: rgba(41,77,115,0.4); +} + +.support__footer { + padding: 16px 24px 24px; +} + +.support__footer .button + .button { + margin-top: 16px; +}
\ No newline at end of file diff --git a/app/components/Support.js b/app/components/Support.js new file mode 100644 index 0000000000..783133568f --- /dev/null +++ b/app/components/Support.js @@ -0,0 +1,108 @@ +// @flow +import React, { Component } from 'react'; +import { Layout, Container, Header } from './Layout'; +import ExternalLinkSVG from '../assets/images/icon-extLink.svg'; + +export type SupportReport = { + email: string, + description: string +}; + +export type SupportState = SupportReport; +export type SupportProps = { + onClose: () => void; + onViewLogs: () => void; + onSend: (report: SupportReport) => void; +}; + +export default class Support extends Component { + props: SupportProps; + state: SupportState = { + email: '', + description: '' + } + + validate() { + return this.state.description.trim().length > 0; + } + + onChangeEmail = (e: Event) => { + const input = e.target; + if(!(input instanceof HTMLInputElement)) { + throw new Error('input must be an instance of HTMLInputElement'); + } + this.setState({ email: input.value }); + } + + onChangeDescription = (e: Event) => { + const input = e.target; + if(!(input instanceof HTMLTextAreaElement)) { + throw new Error('input must be an instance of HTMLTextAreaElement'); + } + this.setState({ description: input.value }); + } + + onSend = () => { + this.props.onSend(this.state); + } + + render() { + return ( + <Layout> + <Header hidden={ true } style={ 'defaultDark' } /> + <Container> + <div className="support"> + <div className="support__close" onClick={ this.props.onClose }> + <img className="support__close-icon" src="./assets/images/icon-back.svg" /> + <span className="support__close-title">Settings</span> + </div> + <div className="support__container"> + + <div className="support__header"> + <h2 className="support__title">Contact support</h2> + <div className="support__subtitle"> + { `To help you more effectively, your app's log file will be attached to this message. + Your data will remain secure and private, as it is encrypted & anonymised before sending.` } + </div> + </div> + + <div className="support__content"> + <div className="support__form"> + <div className="support__form-row"> + <input className="support__form-email" + type="email" + placeholder="Your email" + value={ this.state.email } + onChange={ this.onChangeEmail } + autoFocus={ true } /> + </div> + <div className="support__form-row support__form-row--description"> + <div className="support__form-description-scroll-wrap"> + <textarea className="support__form-description" + placeholder="Describe your problem" + value={ this.state.description } + onChange={ this.onChangeDescription } /> + </div> + </div> + <div className="support__footer"> + <button type="button" + className="button button--primary" + onClick={ this.props.onViewLogs }> + <span className="support__form-view-logs button-label">View app logs</span> + <ExternalLinkSVG className="button-icon button-icon--16" /> + </button> + <button type="button" + className="support__form-send button button--positive" + disabled={ !this.validate() } + onClick={ this.onSend }>Send</button> + </div> + </div> + </div> + + </div> + </div> + </Container> + </Layout> + ); + } +} diff --git a/app/containers/AccountPage.js b/app/containers/AccountPage.js index b68ce4f84c..1727c1e5be 100644 --- a/app/containers/AccountPage.js +++ b/app/containers/AccountPage.js @@ -15,7 +15,6 @@ const mapDispatchToProps = (dispatch, props) => { return { onLogout: () => logout(props.backend), onClose: () => dispatch(push('/settings')), - onViewAccount: () => dispatch(push('/settings/account')), onBuyMore: () => shell.openExternal(links['purchase']) }; }; diff --git a/app/containers/SettingsPage.js b/app/containers/SettingsPage.js index 4f5ef06968..520f27ce6e 100644 --- a/app/containers/SettingsPage.js +++ b/app/containers/SettingsPage.js @@ -13,6 +13,7 @@ const mapDispatchToProps = (dispatch, _props) => { onQuit: () => remote.app.quit(), onClose: () => dispatch(push('/connect')), onViewAccount: () => dispatch(push('/settings/account')), + onViewSupport: () => dispatch(push('/settings/support')), onExternalLink: (type) => shell.openExternal(links[type]), }; }; diff --git a/app/containers/SupportPage.js b/app/containers/SupportPage.js new file mode 100644 index 0000000000..fe579395f6 --- /dev/null +++ b/app/containers/SupportPage.js @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; +import { push } from 'react-router-redux'; +import Support from '../components/Support'; + +const mapStateToProps = (state) => { + return state; +}; + +const mapDispatchToProps = (dispatch, _props) => { + return { + onClose: () => dispatch(push('/settings')), + onViewLogs: () => {}, + onSend: (_report) => {} + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(Support); diff --git a/app/routes.js b/app/routes.js index 0770785ba4..5736e7f37a 100644 --- a/app/routes.js +++ b/app/routes.js @@ -8,6 +8,7 @@ import LoginPage from './containers/LoginPage'; import ConnectPage from './containers/ConnectPage'; import SettingsPage from './containers/SettingsPage'; import AccountPage from './containers/AccountPage'; +import SupportPage from './containers/SupportPage'; import SelectLocationPage from './containers/SelectLocationPage'; import { getTransitionProps } from './transitions'; @@ -94,8 +95,9 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar <LoginRoute exact path="/" component={ LoginPage } /> <PrivateRoute exact path="/connect" component={ ConnectPage } /> <PublicRoute exact path="/settings" component={ SettingsPage } /> - <PrivateRoute path="/settings/account" component={ AccountPage } /> - <PrivateRoute path="/select-location" component={ SelectLocationPage } /> + <PrivateRoute exact path="/settings/account" component={ AccountPage } /> + <PublicRoute exact path="/settings/support" component={ SupportPage } /> + <PrivateRoute exact path="/select-location" component={ SelectLocationPage } /> </Switch> </CSSTransitionGroup> </WindowChrome> diff --git a/app/transitions.js b/app/transitions.js index 5bdc76471c..0ce94e2d16 100644 --- a/app/transitions.js +++ b/app/transitions.js @@ -18,12 +18,49 @@ type TransitionMap = { }; /** + * Transition descriptors + */ +const transitions: TransitionMap = { + slide: { + forward: { + name: 'slide-up-transition', + duration: 450 + }, + backward: { + name: 'slide-down-transition', + duration: 450 + } + }, + push: { + forward: { + name: 'push-transition', + duration: 450 + }, + backward: { + name: 'pop-transition', + duration: 450 + } + } +}; + +/** + * Transition rules + * (null) is used to indicate any route. + */ +const transitionRules = [ + r('/settings', '/settings/account', transitions.push), + r('/settings', '/settings/support', transitions.push), + r(null, '/settings', transitions.slide), + r(null, '/select-location', transitions.slide) +]; + +/** * Calculate CSSTransitionGroup props. * * @param {string} [fromRoute] - source route * @param {string} toRoute - target route */ -export const getTransitionProps = (fromRoute: ?string, toRoute: string): CSSTransitionGroupProps => { +export function getTransitionProps(fromRoute: ?string, toRoute: string): CSSTransitionGroupProps { // ignore initial transition and transition between the same routes if(!fromRoute || fromRoute === toRoute) { return noTransitionProps(); @@ -37,13 +74,13 @@ export const getTransitionProps = (fromRoute: ?string, toRoute: string): CSSTran } return noTransitionProps(); -}; +} /** * Integrate TransitionDescriptor into CSSTransitionGroupProps * @param {TransitionDescriptor} descriptor */ -const toCSSTransitionGroupProps = (descriptor: TransitionDescriptor): CSSTransitionGroupProps => { +function toCSSTransitionGroupProps(descriptor: TransitionDescriptor): CSSTransitionGroupProps { const {name, duration} = descriptor; return { transitionName: name, @@ -52,58 +89,24 @@ const toCSSTransitionGroupProps = (descriptor: TransitionDescriptor): CSSTransit transitionEnter: true, transitionLeave: true }; -}; +} /** * Returns default props with animations disabled */ -const noTransitionProps = (): CSSTransitionGroupProps => ({ - transitionName: '', - transitionEnterTimeout: 0, - transitionLeaveTimeout: 0, - transitionEnter: false, - transitionLeave: false -}); - -/** - * Transition descriptors - */ -const transitions: TransitionMap = { - slide: { - forward: { - name: 'slide-up-transition', - duration: 450 - }, - backward: { - name: 'slide-down-transition', - duration: 450 - } - }, - push: { - forward: { - name: 'push-transition', - duration: 450 - }, - backward: { - name: 'pop-transition', - duration: 450 - } - } -}; +function noTransitionProps(): CSSTransitionGroupProps { + return { + transitionName: '', + transitionEnterTimeout: 0, + transitionLeaveTimeout: 0, + transitionEnter: false, + transitionLeave: false + }; +} /** * Shortcut to create TransitionRule */ -const r = (from: ?string, to: string, fork: TransitionFork): TransitionRule => { +function r(from: ?string, to: string, fork: TransitionFork): TransitionRule { return new TransitionRule(from, to, fork); -}; - -/** - * Transition rules - * (null) is used to indicate any route. - */ -const transitionRules = [ - r('/settings', '/settings/account', transitions.push), - r(null, '/settings', transitions.slide), - r(null, '/select-location', transitions.slide) -];
\ No newline at end of file +} diff --git a/test/components/Settings.spec.js b/test/components/Settings.spec.js index 5655e1e5fa..f90d658f9b 100644 --- a/test/components/Settings.spec.js +++ b/test/components/Settings.spec.js @@ -43,8 +43,8 @@ describe('components/Settings', () => { onQuit: () => {}, onClose: () => {}, onViewAccount: () => {}, - onExternalLink: (_type) => {}, - onUpdateSettings: (_update) => {} + onViewSupport: () => {}, + onExternalLink: (_type) => {} }; return Object.assign({}, defaultProps, mergeProps); }; @@ -128,6 +128,14 @@ describe('components/Settings', () => { Simulate.click(domNode); }); + 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); + }); + it('should call external links callback', () => { let collectedExternalLinkTypes: Array<string> = []; const props = makeProps(loggedOutAccountState, settingsState, { @@ -140,7 +148,7 @@ describe('components/Settings', () => { .filter((elm: HTMLElement) => elm.classList.contains('settings__cell')) .forEach((elm) => Simulate.click(elm)); - expect(collectedExternalLinkTypes).to.include.ordered.members(['faq', 'guides', 'supportEmail']); + expect(collectedExternalLinkTypes).to.include.ordered.members(['faq', 'guides']); }); }); diff --git a/test/components/Support.spec.js b/test/components/Support.spec.js new file mode 100644 index 0000000000..bcd871855b --- /dev/null +++ b/test/components/Support.spec.js @@ -0,0 +1,70 @@ +// @flow + +import { expect } from 'chai'; +import React from 'react'; +import ReactTestUtils, { Simulate } from 'react-dom/test-utils'; +import Support from '../../app/components/Support'; + +import type { SupportProps } from '../../app/components/Support'; + +describe('components/Support', () => { + + const makeProps = (mergeProps: $Shape<SupportProps> = {}): SupportProps => { + const defaultProps: SupportProps = { + onClose: () => {}, + onViewLogs: () => {}, + onSend: (_report) => {} + }; + return Object.assign({}, defaultProps, mergeProps); + }; + + const render = (props: SupportProps): Support => { + return ReactTestUtils.renderIntoDocument( + <Support { ...props } /> + ); + }; + + it('should call close callback', (done) => { + const props = makeProps({ + onClose: () => done() + }); + const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'support__close'); + Simulate.click(domNode); + }); + + it('should call view logs callback', (done) => { + const props = makeProps({ + onViewLogs: () => done() + }); + const domNode = ReactTestUtils.findRenderedDOMComponentWithClass(render(props), 'support__form-view-logs'); + Simulate.click(domNode); + }); + + it('should call send callback when description filled in', (done) => { + const props = makeProps({ + onSend: (_report) => done() + }); + + const component = render(props); + + const descriptionField = ReactTestUtils.findRenderedDOMComponentWithClass(component, 'support__form-description'); + descriptionField.value = 'Lorem Ipsum'; + Simulate.change(descriptionField); + + const sendButton = ReactTestUtils.findRenderedDOMComponentWithClass(component, 'support__form-send'); + expect(sendButton.disabled).to.be.false; + Simulate.click(sendButton); + }); + + it('should not call send callback when description is empty', () => { + const component = render(makeProps()); + + const descriptionField = ReactTestUtils.findRenderedDOMComponentWithClass(component, 'support__form-description'); + descriptionField.value = ''; + Simulate.change(descriptionField); + + const sendButton = ReactTestUtils.findRenderedDOMComponentWithClass(component, 'support__form-send'); + expect(sendButton.disabled).to.be.true; + }); + +}); |
