Skip to content

Commit

Permalink
Merge pull request #336 from airbnb/fix_dateinput_server_render
Browse files Browse the repository at this point in the history
[Fix] `DateInput`: `isTouchDevice` should only be determined in `componentDidMount`
  • Loading branch information
ljharb authored Feb 23, 2017
2 parents 333bbe5 + d978e54 commit 04d08d4
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 3 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"rules": {
"no-mixed-operators": [2, { "allowSamePrecedence": true }],
"jsx-a11y/no-static-element-interactions": 1, // TODO: enable
"react/no-did-mount-set-state": 0, // necessary for server-rendering
}
}
12 changes: 9 additions & 3 deletions src/components/DateInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,15 @@ export default class DateInput extends React.Component {
super(props);
this.state = {
dateString: '',
isTouchDevice: false,
};

this.onChange = this.onChange.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}

this.isTouchDevice = isTouchDevice();
componentDidMount() {
this.setState({ isTouchDevice: isTouchDevice() });
}

componentWillReceiveProps(nextProps) {
Expand Down Expand Up @@ -89,7 +92,10 @@ export default class DateInput extends React.Component {
}

render() {
const { dateString } = this.state;
const {
dateString,
isTouchDevice: isTouch,
} = this.state;
const {
id,
placeholder,
Expand Down Expand Up @@ -128,7 +134,7 @@ export default class DateInput extends React.Component {
placeholder={placeholder}
autoComplete="off"
disabled={disabled}
readOnly={this.isTouchDevice}
readOnly={isTouch}
required={required}
aria-describedby={screenReaderMessage && screenReaderMessageId}
/>
Expand Down
13 changes: 13 additions & 0 deletions test/_helpers/withTouchSupport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import wrap from 'mocha-wrap';
import withGlobal from 'mocha-wrap/withGlobal';
import withOverride from 'mocha-wrap/withOverride';

function withTouchSupport() {
return this
.use(withGlobal, 'window', () => (typeof window !== 'undefined' ? window : {}))
.use(withOverride, () => window, 'ontouchstart', () => window.ontouchstart || (() => {}))
.use(withGlobal, 'navigator', () => (typeof navigator !== 'undefined' ? navigator : {}))
.use(withOverride, () => navigator, 'maxTouchPoints', () => navigator.maxTouchPoints || true);
}

wrap.register(withTouchSupport);
28 changes: 28 additions & 0 deletions test/components/DateInput_spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon-sandbox';
import wrap from 'mocha-wrap';

import DateInput from '../../src/components/DateInput';

Expand Down Expand Up @@ -206,4 +207,31 @@ describe('DateInput', () => {
expect(onKeyDownTabStub.callCount).to.equal(0);
});
});

describe('touch device detection', () => {
it('indicates no touch support on the client', () => {
const wrapper = shallow(<DateInput id="date" />);
expect(wrapper.state()).to.contain.keys({ isTouchDevice: false });
});

it('does not set readOnly when not a touch device', () => {
const wrapper = shallow(<DateInput id="date" />);
expect(!!wrapper.find('input').prop('readOnly')).to.equal(false);
});

it('sets readOnly when a touch device', () => {
const wrapper = shallow(<DateInput id="date" />);
wrapper.setState({ isTouchDevice: true });
wrapper.update();
expect(!!wrapper.find('input').prop('readOnly')).to.equal(true);
});

wrap()
.withTouchSupport()
.it('sets isTouchDevice state when is a touch device', () => {
const wrapper = shallow(<DateInput id="date" />);
wrapper.instance().componentDidMount();
expect(wrapper.state()).to.contain.keys({ isTouchDevice: true });
});
});
});

0 comments on commit 04d08d4

Please sign in to comment.