diff options
| author | Erik Larkö <erik@mullvad.net> | 2017-07-20 09:47:00 +0200 |
|---|---|---|
| committer | Erik Larkö <erik@mullvad.net> | 2017-07-20 09:47:00 +0200 |
| commit | 8c13b784a277db52f8100e3cd5bdea17f0736d1f (patch) | |
| tree | d64c0f23e9cb6cf30816b3e3c648168ec1715450 | |
| parent | 310cca89fcd29eb5df07d8a6b144e4fe8eeab56d (diff) | |
| parent | 2ea377f3bd30a1e8d1d6392fcd224e82ff418601 (diff) | |
| download | mullvadvpn-8c13b784a277db52f8100e3cd5bdea17f0736d1f.tar.xz mullvadvpn-8c13b784a277db52f8100e3cd5bdea17f0736d1f.zip | |
Merge branch 'switch-tests-rebased'
| -rw-r--r-- | app/components/Switch.js | 55 | ||||
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | test/components/Switch.spec.js | 100 | ||||
| -rw-r--r-- | test/global.js | 17 | ||||
| -rw-r--r-- | yarn.lock | 109 |
5 files changed, 213 insertions, 70 deletions
diff --git a/app/components/Switch.js b/app/components/Switch.js index 46baeb8b22..c278cfbd2c 100644 --- a/app/components/Switch.js +++ b/app/components/Switch.js @@ -6,43 +6,41 @@ import type { Point2d } from '../types'; const CLICK_TIMEOUT = 1000; const MOVE_THRESHOLD = 10; -export default class Switch extends Component { - props: { - isOn: boolean; - onChange: ?((isOn: boolean) => void); - } +export type SwitchProps = { + isOn: boolean; + onChange: ?((isOn: boolean) => void); +}; - defaultProps = { - isOn: false +export default class Switch extends Component { + props: SwitchProps; + static defaultProps: SwitchProps = { + isOn: false, + onChange: null } + isCapturingMouseEvents = false; ref: ?HTMLInputElement; onRef = (e: HTMLInputElement) => this.ref = e; state = { - isTracking: false, ignoreChange: false, - initialPos: (null: ?Point2d), + initialPos: ({x: 0, y: 0}: Point2d), startTime: (null: ?number) } handleMouseDown = (e: MouseEvent) => { - const { pageX: x, pageY: y } = e; + const { clientX: x, clientY: y } = e; + this.startCapturingMouseEvents(); this.setState({ - isTracking: true, initialPos: { x, y }, startTime: e.timeStamp }); } handleMouseMove = (e: MouseEvent) => { - if(!this.state.isTracking) { - return; - } - const inputElement = this.ref; const { x: x0 } = this.state.initialPos; - const { pageX: x, pageY: y } = e; + const { clientX: x, clientY: y } = e; const dx = Math.abs(x0 - x); if(dx < MOVE_THRESHOLD) { @@ -73,26 +71,17 @@ export default class Switch extends Component { } handleMouseUp = () => { - if(this.state.isTracking) { - this.setState({ - isTracking: false, - initialPos: null - }); - } + this.stopCapturingMouseEvents(); } handleChange = (e: Event) => { const startTime = this.state.startTime; - const eventTarget = e.target; + const eventTarget: Object = e.target; if(typeof(startTime) !== 'number') { throw new Error('startTime must be a number.'); } - if(!(eventTarget instanceof HTMLInputElement)) { - throw new Error('e.target must be an instance of HTMLInputElement.'); - } - const dt = e.timeStamp - startTime; if(this.state.ignoreChange) { @@ -112,14 +101,22 @@ export default class Switch extends Component { } } - componentDidMount() { + startCapturingMouseEvents() { + if(this.isCapturingMouseEvents) { + throw new Error('startCapturingMouseEvents() is called out of order.'); + } document.addEventListener('mousemove', this.handleMouseMove); document.addEventListener('mouseup', this.handleMouseUp); + this.isCapturingMouseEvents = true; } - componentWillUnmount() { + stopCapturingMouseEvents() { + if(!this.isCapturingMouseEvents) { + throw new Error('stopCapturingMouseEvents() is called out of order.'); + } document.removeEventListener('mousemove', this.handleMouseMove); document.removeEventListener('mouseup', this.handleMouseUp); + this.isCapturingMouseEvents = false; } render(): React.Element<*> { diff --git a/package.json b/package.json index 0d5b3a5044..772023a02d 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "flow-bin": "^0.49.1", "flow-typed": "^2.1.2", "isomorphic-fetch": "^2.2.1", - "jsdom": "^9.11.0", + "jsdom": "^11.1.0", "mocha": "^3.2.0", "npm-run-all": "^4.0.1", "redux-mock-store": "^1.2.2", diff --git a/test/components/Switch.spec.js b/test/components/Switch.spec.js new file mode 100644 index 0000000000..8cdc053c24 --- /dev/null +++ b/test/components/Switch.spec.js @@ -0,0 +1,100 @@ +// @flow + +import { expect } from 'chai'; +import React from 'react'; +import ReactTestUtils, { Simulate } from 'react-dom/test-utils'; +import Switch from '../../app/components/Switch'; + +describe('components/Switch', () => { + + it('should switch on', (done) => { + const onChange = (isOn) => { + expect(isOn).to.be.true; + done(); + }; + const component = ReactTestUtils.renderIntoDocument( + <Switch isOn={ false } onChange={ onChange } /> + ); + const domNode = ReactTestUtils.findRenderedDOMComponentWithTag(component, 'input'); + + Simulate.mouseDown(domNode, { clientX: 100, clientY: 0 }); + Simulate.mouseUp(domNode, { clientX: 100, clientY: 0 }); + Simulate.change(domNode, { target: { checked: true } }); + }); + + it('should switch off', (done) => { + const onChange = (isOn) => { + expect(isOn).to.be.false; + done(); + }; + const component = ReactTestUtils.renderIntoDocument( + <Switch isOn={ true } onChange={ onChange } /> + ); + const domNode = ReactTestUtils.findRenderedDOMComponentWithTag(component, 'input'); + + Simulate.mouseDown(domNode, { clientX: 100, clientY: 0 }); + Simulate.mouseUp(domNode, { clientX: 100, clientY: 0 }); + Simulate.change(domNode, { target: { checked: false } }); + }); + + it('should handle left to right swipe', (done) => { + const onChange = (isOn) => { + expect(isOn).to.be.true; + done(); + }; + const component = ReactTestUtils.renderIntoDocument( + <Switch isOn={ false } onChange={ onChange } /> + ); + const domNode = ReactTestUtils.findRenderedDOMComponentWithTag(component, 'input'); + + Simulate.mouseDown(domNode, { clientX: 100, clientY: 0 }); + + // Switch listens to events on document + document.dispatchEvent(new MouseEvent('mousemove', { clientX: 150, clientY: 0 })); + document.dispatchEvent(new MouseEvent('mouseup', { clientX: 150, clientY: 0 })); + }); + + it('should handle right to left swipe', (done) => { + const onChange = (isOn) => { + expect(isOn).to.be.false; + done(); + }; + const component = ReactTestUtils.renderIntoDocument( + <Switch isOn={ true } onChange={ onChange } /> + ); + const domNode = ReactTestUtils.findRenderedDOMComponentWithTag(component, 'input'); + + Simulate.mouseDown(domNode, { clientX: 150, clientY: 0 }); + + // Switch listens to events on document + document.dispatchEvent(new MouseEvent('mousemove', { clientX: 100, clientY: 0 })); + document.dispatchEvent(new MouseEvent('mouseup', { clientX: 100, clientY: 0 })); + }); + + it('should timeout when user holds knob for too long without moving', (done) => { + const onChange = () => { + throw new Error('onChange should not be called on timeout.'); + }; + + const component = ReactTestUtils.renderIntoDocument( + <Switch isOn={ false } onChange={ onChange } /> + ); + const domNode = ReactTestUtils.findRenderedDOMComponentWithTag(component, 'input'); + + Simulate.mouseDown(domNode, { clientX: 100, clientY: 0 }); + + setTimeout(() => { + // Switch listens to events on document + document.dispatchEvent(new MouseEvent('mouseup', { clientX: 100, clientY: 0 })); + + try { + // should not trigger onChange() + Simulate.change(domNode); + done(); + } catch(e) { + done(e); + } + }, 1000); + }); + +});
\ No newline at end of file diff --git a/test/global.js b/test/global.js index 4cf366b166..7b4af72b63 100644 --- a/test/global.js +++ b/test/global.js @@ -1,11 +1,18 @@ import log from 'electron-log'; -import jsdom from 'jsdom'; +import { JSDOM } from 'jsdom'; before(() => { - global.document = jsdom.jsdom('<!doctype html><html><body></body></html>'); - global.window = document.defaultView; - global.navigator = window.navigator; - log.transports.console.level = false; log.transports.file.level = false; }); + +beforeEach(() => { + const dom = new JSDOM('<!doctype html><html><body></body></html>'); + const window = dom.window; + global.window = window; + global.document = window.document; + global.navigator = window.navigator; + global.HTMLInputElement = window.HTMLInputElement; + global.Event = window.Event; + global.MouseEvent = window.MouseEvent; +}); @@ -56,6 +56,10 @@ version "4.5.2" resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-4.5.2.tgz#8450fc442d2a59494251a5a52ae520017e2dcf0d" +"@types/node@^6.0.46": + version "6.0.84" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.84.tgz#193ffe5a9f42864d425ffd9739d95b753c6a1eab" + "@types/node@^7.0.18": version "7.0.29" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.29.tgz#ccfcec5b7135c7caf6c4ffb8c7f33102340d99df" @@ -3385,6 +3389,32 @@ jschardet@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.4.2.tgz#2aa107f142af4121d145659d44f50830961e699a" +jsdom@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.1.0.tgz#6c48d7a48ffc5c300283c312904d15da8360509b" + dependencies: + abab "^1.0.3" + acorn "^4.0.4" + acorn-globals "^3.1.0" + array-equal "^1.0.0" + content-type-parser "^1.0.1" + cssom ">= 0.3.2 < 0.4.0" + cssstyle ">= 0.2.37 < 0.3.0" + escodegen "^1.6.1" + html-encoding-sniffer "^1.0.1" + nwmatcher "^1.4.1" + parse5 "^3.0.2" + pn "^1.0.0" + request "^2.79.0" + request-promise-native "^1.0.3" + sax "^1.2.1" + symbol-tree "^3.2.1" + tough-cookie "^2.3.2" + webidl-conversions "^4.0.0" + whatwg-encoding "^1.0.1" + whatwg-url "^6.1.0" + xml-name-validator "^2.0.1" + jsdom@^7.0.2: version "7.2.2" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-7.2.2.tgz#40b402770c2bda23469096bee91ab675e3b1fc6e" @@ -3405,30 +3435,6 @@ jsdom@^7.0.2: whatwg-url-compat "~0.6.5" xml-name-validator ">= 2.0.1 < 3.0.0" -jsdom@^9.11.0: - version "9.12.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" - dependencies: - abab "^1.0.3" - acorn "^4.0.4" - acorn-globals "^3.1.0" - array-equal "^1.0.0" - content-type-parser "^1.0.1" - cssom ">= 0.3.2 < 0.4.0" - cssstyle ">= 0.2.37 < 0.3.0" - escodegen "^1.6.1" - html-encoding-sniffer "^1.0.1" - nwmatcher ">= 1.3.9 < 2.0.0" - parse5 "^1.5.1" - request "^2.79.0" - sax "^1.2.1" - symbol-tree "^3.2.1" - tough-cookie "^2.3.2" - webidl-conversions "^4.0.0" - whatwg-encoding "^1.0.1" - whatwg-url "^4.3.0" - xml-name-validator "^2.0.1" - jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -3689,6 +3695,10 @@ lodash.some@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + lodash@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" @@ -4081,10 +4091,14 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -"nwmatcher@>= 1.3.7 < 2.0.0", "nwmatcher@>= 1.3.9 < 2.0.0": +"nwmatcher@>= 1.3.7 < 2.0.0": version "1.4.0" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.0.tgz#b4389362170e7ef9798c3c7716d80ebc0106fccf" +nwmatcher@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.1.tgz#7ae9b07b0ea804db7e25f05cb5fe4097d4e4949f" + oauth-sign@~0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" @@ -4263,6 +4277,12 @@ parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" +parse5@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.2.tgz#05eff57f0ef4577fb144a79f8b9a967a6cc44510" + dependencies: + "@types/node" "^6.0.46" + parsejson@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" @@ -4386,6 +4406,10 @@ pluralize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762" +pn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.0.0.tgz#1cf5a30b0d806cd18f88fc41a6b5d4ad615b3ba9" + point-geometry@0.0.0, point-geometry@^0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/point-geometry/-/point-geometry-0.0.0.tgz#6fcbcad7a803b6418247dd6e49c2853c584daff7" @@ -4810,6 +4834,20 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" +request-promise-core@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" + dependencies: + lodash "^4.13.1" + +request-promise-native@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.4.tgz#86988ec8eee408e45579fce83bfd05b3adf9a155" + dependencies: + request-promise-core "1.1.1" + stealthy-require "^1.1.0" + tough-cookie ">=2.3.0" + request@2.78.0: version "2.78.0" resolved "https://registry.yarnpkg.com/request/-/request-2.78.0.tgz#e1c8dec346e1c81923b24acdb337f11decabe9cc" @@ -5231,6 +5269,10 @@ static-module@^1.1.0: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" +stealthy-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + stream-combiner@~0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" @@ -5512,7 +5554,7 @@ to-utf8@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/to-utf8/-/to-utf8-0.0.1.tgz#d17aea72ff2fba39b9e43601be7b3ff72e089852" -tough-cookie@^2.2.0, tough-cookie@^2.3.2, tough-cookie@~2.3.0: +tough-cookie@>=2.3.0, tough-cookie@^2.2.0, tough-cookie@^2.3.2, tough-cookie@~2.3.0: version "2.3.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" dependencies: @@ -5761,11 +5803,7 @@ webidl-conversions@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-2.0.1.tgz#3bf8258f7d318c7443c36f2e169402a1a6703506" -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - -webidl-conversions@^4.0.0: +webidl-conversions@^4.0.0, webidl-conversions@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.1.tgz#8015a17ab83e7e1b311638486ace81da6ce206a0" @@ -5801,12 +5839,13 @@ whatwg-url-compat@~0.6.5: dependencies: tr46 "~0.0.1" -whatwg-url@^4.3.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" +whatwg-url@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.1.0.tgz#5fc8279b93d75483b9ced8b26239854847a18578" dependencies: + lodash.sortby "^4.7.0" tr46 "~0.0.3" - webidl-conversions "^3.0.0" + webidl-conversions "^4.0.1" whet.extend@~0.9.9: version "0.9.9" |
