From dac62b1566fdae65b3773964183a90212a6bb4ea Mon Sep 17 00:00:00 2001 From: Matt Goo Date: Fri, 21 Dec 2018 07:43:55 -1000 Subject: [PATCH] feat(select): typescript (#540) --- package-lock.json | 12 +- .../{NativeControl.js => NativeControl.tsx} | 111 +++++----- packages/select/{index.js => index.tsx} | 194 +++++++++--------- test/screenshot/golden.json | 2 +- test/screenshot/select/index.js | 79 ------- test/screenshot/select/index.tsx | 89 ++++++++ ...Control.test.js => NativeControl.test.tsx} | 72 ++++--- .../select/{index.test.js => index.test.tsx} | 188 ++++++++++------- 8 files changed, 389 insertions(+), 358 deletions(-) rename packages/select/{NativeControl.js => NativeControl.tsx} (62%) rename packages/select/{index.js => index.tsx} (63%) delete mode 100644 test/screenshot/select/index.js create mode 100644 test/screenshot/select/index.tsx rename test/unit/select/{NativeControl.test.js => NativeControl.test.tsx} (60%) rename test/unit/select/{index.test.js => index.test.tsx} (67%) diff --git a/package-lock.json b/package-lock.json index 884fc20c3..ef90e4b5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -819,9 +819,9 @@ } }, "@types/react": { - "version": "16.7.7", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.7.7.tgz", - "integrity": "sha512-dJiq7CKxD1XJ/GqmbnsQisFnzG4z5lntKBw9X9qeSrguxFbrrhGa8cK9s0ONBp8wL1EfGfofEDVhjen26U46pw==", + "version": "16.7.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.7.17.tgz", + "integrity": "sha512-YcXcaoXaxo7A76mBCGlKlN2aZu3REQfF0DTrhiyXVJLA7PDdxVCr+wiQOrkVNn44D/zLlIyDSn3U918Ve0AaEA==", "dev": true, "requires": { "@types/prop-types": "*", @@ -5546,9 +5546,9 @@ } }, "csstype": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.5.7.tgz", - "integrity": "sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw==", + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.5.8.tgz", + "integrity": "sha512-r4DbsyNJ7slwBSKoGesxDubRWJ71ghG8W2+1HcsDlAo12KGca9dDLv0u98tfdFw7ldBdoA7XmCnI6Q8LpAJXaQ==", "dev": true }, "currently-unhandled": { diff --git a/packages/select/NativeControl.js b/packages/select/NativeControl.tsx similarity index 62% rename from packages/select/NativeControl.js rename to packages/select/NativeControl.tsx index ea060324b..4cc766c94 100644 --- a/packages/select/NativeControl.js +++ b/packages/select/NativeControl.tsx @@ -20,14 +20,40 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; +import * as React from 'react'; +import * as classnames from 'classnames'; +// no mdc .d.ts file +// @ts-ignore +import {MDCSelectFoundation} from '@material/select/dist/mdc.select'; -export default class NativeControl extends React.Component { - nativeControl_ = React.createRef(); +export interface NativeControlProps extends React.HTMLProps { + className: string; + disabled: boolean; + foundation: MDCSelectFoundation; + setRippleCenter: (lineRippleCenter: number) => void; + handleDisabled: (disabled: boolean) => void; +} + +export default class NativeControl extends React.Component< + NativeControlProps, + {} + > { + nativeControl_: React.RefObject = React.createRef(); + + static defaultProps: NativeControlProps = { + className: '', + children: null, + disabled: false, + foundation: { + handleFocus: () => {}, + handleBlur: () => {}, + }, + setRippleCenter: () => {}, + handleDisabled: () => {}, + }; - componentDidUpdate(prevProps) { + + componentDidUpdate(prevProps: NativeControlProps) { if (this.props.disabled !== prevProps.disabled) { this.props.handleDisabled(this.props.disabled); } @@ -37,37 +63,37 @@ export default class NativeControl extends React.Component { return classnames('mdc-select__native-control', this.props.className); } - handleFocus = (evt) => { + handleFocus = (evt: React.FocusEvent) => { const {foundation, onFocus} = this.props; foundation.handleFocus(evt); - onFocus(evt); - } + onFocus && onFocus(evt); + }; - handleBlur = (evt) => { + handleBlur = (evt: React.FocusEvent) => { const {foundation, onBlur} = this.props; foundation.handleBlur(evt); - onBlur(evt); - } + onBlur && onBlur(evt); + }; - handleMouseDown = (evt) => { + handleMouseDown = (evt: React.MouseEvent) => { const {onMouseDown} = this.props; - this.setRippleCenter(evt.clientX, evt.target); - onMouseDown(evt); - } + this.setRippleCenter(evt.clientX, evt.target as HTMLSelectElement); + onMouseDown && onMouseDown(evt); + }; - handleTouchStart = (evt) => { + handleTouchStart = (evt: React.TouchEvent) => { const {onTouchStart} = this.props; const clientX = evt.touches[0] && evt.touches[0].clientX; - this.setRippleCenter(clientX, evt.target); - onTouchStart(evt); - } + this.setRippleCenter(clientX, evt.target as HTMLSelectElement); + onTouchStart && onTouchStart(evt); + }; - setRippleCenter = (xCoordinate, target) => { + setRippleCenter = (xCoordinate: number, target: HTMLSelectElement) => { if (target !== this.nativeControl_.current) return; const targetClientRect = target.getBoundingClientRect(); const normalizedX = xCoordinate - targetClientRect.left; this.props.setRippleCenter(normalizedX); - } + }; render() { const { @@ -104,44 +130,3 @@ export default class NativeControl extends React.Component { ); } } - -NativeControl.propTypes = { - className: PropTypes.string, - children: PropTypes.node, - disabled: PropTypes.bool, - foundation: PropTypes.shape({ - handleFocus: PropTypes.func, - handleBlur: PropTypes.func, - }), - id: PropTypes.string, - onBlur: PropTypes.func, - onChange: PropTypes.func, - onFocus: PropTypes.func, - onTouchStart: PropTypes.func, - onMouseDown: PropTypes.func, - setRippleCenter: PropTypes.func, - handleDisabled: PropTypes.func, - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), -}; - -NativeControl.defaultProps = { - className: '', - children: null, - disabled: false, - foundation: { - handleFocus: () => {}, - handleBlur: () => {}, - }, - id: null, - onBlur: () => {}, - onChange: () => {}, - onFocus: () => {}, - onTouchStart: () => {}, - onMouseDown: () => {}, - setRippleCenter: () => {}, - handleDisabled: () => {}, - value: '', -}; diff --git a/packages/select/index.js b/packages/select/index.tsx similarity index 63% rename from packages/select/index.js rename to packages/select/index.tsx index c8a460bfb..998399a58 100644 --- a/packages/select/index.js +++ b/packages/select/index.tsx @@ -20,70 +20,106 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import {MDCSelectFoundation} from '@material/select/dist/mdc.select'; - +import * as React from 'react'; +import * as classnames from 'classnames'; +// no mdc .d.ts file +// @ts-ignore +import {MDCSelectFoundation, MDCSelectAdapter} from '@material/select/dist/mdc.select'; import FloatingLabel from '@material/react-floating-label'; import LineRipple from '@material/react-line-ripple'; import NotchedOutline from '@material/react-notched-outline'; import NativeControl from './NativeControl'; -export default class Select extends React.Component { - foundation_ = null; +type SelectOptionsType = (string | React.HTMLProps)[]; + +export interface SelectProps extends React.HTMLProps { + box: boolean; + className: string; + disabled: boolean; + floatingLabelClassName?: string; + isRtl: boolean; + label: string; + lineRippleClassName: string; + nativeControlClassName: string; + notchedOutlineClassName: string; + outlined: boolean; + options: SelectOptionsType; +} - constructor(props) { - super(props); +interface SelectState { + value?: string | string[] | number, + classList: Set; + disabled: boolean; + labelIsFloated: boolean; + labelWidth: number; + activeLineRipple: boolean; + lineRippleCenter?: number; + outlineIsNotched: boolean; +}; +export default class Select extends React.Component { + foundation?: MDCSelectFoundation; + + constructor(props: SelectProps) { + super(props); this.state = { classList: new Set(), disabled: props.disabled, value: props.value, - // floating label state labelIsFloated: false, labelWidth: 0, - // line ripple state activeLineRipple: false, - lineRippleCenter: null, - + lineRippleCenter: undefined, // notched outline state outlineIsNotched: false, }; } + static defaultProps: Partial = { + box: false, + className: '', + disabled: false, + floatingLabelClassName: '', + isRtl: false, + lineRippleClassName: '', + nativeControlClassName: '', + notchedOutlineClassName: '', + outlined: false, + options: [], + onChange: () => {}, + value: '', + }; + componentDidMount() { - this.foundation_ = new MDCSelectFoundation(this.adapter); - this.foundation_.init(); - this.foundation_.handleChange(); + this.foundation = new MDCSelectFoundation(this.adapter); + this.foundation.init(); + this.foundation.handleChange(); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps: SelectProps, prevState: SelectState) { // this is to fix onChange being called twice if (this.props.value !== prevProps.value) { this.setState({value: this.props.value}); } if (this.state.value !== prevState.value) { - this.foundation_.handleChange(); + this.foundation.handleChange(); } } componentWillUnmount() { - this.foundation_.destroy(); + this.foundation.destroy(); } - - onChange = (evt) => { - this.props.onChange(evt); + onChange = (evt: React.ChangeEvent) => { + this.props.onChange && this.props.onChange(evt); const {value} = evt.target; this.setState({value}); - } + }; /** - * getters - */ - + * getters + */ get classes() { const {classList, disabled} = this.state; const {className, box, outlined} = this.props; @@ -94,62 +130,58 @@ export default class Select extends React.Component { }); } - get adapter() { + get adapter(): MDCSelectAdapter { const rootAdapterMethods = { - addClass: (className) => { + addClass: (className: string) => { const classList = new Set(this.state.classList); classList.add(className); this.setState({classList}); }, - removeClass: (className) => { + removeClass: (className: string) => { const classList = new Set(this.state.classList); classList.delete(className); this.setState({classList}); }, - hasClass: (className) => this.classes.split(' ').includes(className), + hasClass: (className: string) => this.classes.split(' ').includes(className), isRtl: () => this.props.isRtl, getValue: () => this.state.value, }; - const labelAdapter = { - floatLabel: (labelIsFloated) => this.setState({labelIsFloated}), + floatLabel: (labelIsFloated: boolean) => this.setState({labelIsFloated}), hasLabel: () => !!this.props.label, getLabelWidth: () => this.state.labelWidth, }; - const lineRippleAdapter = { activateBottomLine: () => this.setState({activeLineRipple: true}), deactivateBottomLine: () => this.setState({activeLineRipple: false}), }; - const notchedOutlineAdapter = { notchOutline: () => this.setState({outlineIsNotched: true}), closeOutline: () => this.setState({outlineIsNotched: false}), hasOutline: () => !!this.props.outlined, }; - - return Object.assign({}, - rootAdapterMethods, - labelAdapter, - lineRippleAdapter, - notchedOutlineAdapter, - ); + return { + ...rootAdapterMethods, + ...labelAdapter, + ...lineRippleAdapter, + ...notchedOutlineAdapter, + }; } - - setRippleCenter = (lineRippleCenter) => this.setState({lineRippleCenter}); - setDisabled = (disabled) => this.setState({disabled}); + setRippleCenter = (lineRippleCenter: number) => this.setState({lineRippleCenter}); + setDisabled = (disabled: boolean) => this.setState({disabled}); /** - * render methods - */ - + * render methods + */ render() { return ( -
+
{this.renderSelect()} {this.renderLabel()} - {this.props.outlined ? this.renderNotchedOutline() : this.renderLineRipple()} + {this.props.outlined + ? this.renderNotchedOutline() + : this.renderLineRipple()}
); } @@ -166,6 +198,7 @@ export default class Select extends React.Component { notchedOutlineClassName, outlined, onChange, + ref, /* eslint-enable */ ...otherProps } = this.props; @@ -173,7 +206,7 @@ export default class Select extends React.Component { return ( { if (typeof optionData === 'string') { return ( - + ); } const {label, ...nonLabelOptionData} = optionData; return ( - + // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31485 + // @ts-ignore + ); }); } @@ -211,8 +250,7 @@ export default class Select extends React.Component { this.setState({labelWidth})} + handleWidthChange={(labelWidth) => this.setState({labelWidth})} htmlFor={id} > {label} @@ -245,47 +283,3 @@ export default class Select extends React.Component { ); } } - -Select.propTypes = { - box: PropTypes.bool, - children: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.element), - PropTypes.element, - ]), - className: PropTypes.string, - disabled: PropTypes.bool, - floatingLabelClassName: PropTypes.string, - id: PropTypes.string, - isRtl: PropTypes.bool, - label: PropTypes.string.isRequired, - lineRippleClassName: PropTypes.string, - nativeControlClassName: PropTypes.string, - notchedOutlineClassName: PropTypes.string, - onChange: PropTypes.func, - outlined: PropTypes.bool, - options: PropTypes.arrayOf(PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object, - ])), - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.bool, - ]), -}; - -Select.defaultProps = { - box: false, - className: '', - disabled: false, - floatingLabelClassName: '', - id: null, - isRtl: false, - lineRippleClassName: '', - nativeControlClassName: '', - notchedOutlineClassName: '', - onChange: () => {}, - outlined: false, - options: [], - value: '', -}; diff --git a/test/screenshot/golden.json b/test/screenshot/golden.json index 99a02e506..3dc5340e0 100644 --- a/test/screenshot/golden.json +++ b/test/screenshot/golden.json @@ -13,7 +13,7 @@ "menu-surface": "f5face1a24fe166e86e8a3dc35ea85b2d4431469a3d06bf6fc1a30fbdc175aff", "notched-outline": "7770dd381c27608a1f43b6f83da92507fe53963f5e4409bd73184b86275538fe", "radio": "adfce0bbfa2711c67a52e1c3c3c7e980314be0147e340dac733152c80c385765", - "select": "ff57b7fec25dd375b6583a7245c9756afa3713ada13829dfa11757fbbf5faacf", + "select": "10b82843806ddc961e85192d231ba5c3bc5aef7c20c14ffda7dd1e857b5a8c9f", "switch": "dd8a3ec00447e0c586b5bbefdc633681d29e6f04ff8b517a68209bd1f4a6a4e4", "tab": "0e53fa0ca9b2de4ff7941169a9b6a929a83b18e517c18404adeb40f7e644a2f1", "tab-bar": "6c28ec268b2baf308459e7df9d7471fb7907b6473240b9a28a81be54a335f932", diff --git a/test/screenshot/select/index.js b/test/screenshot/select/index.js deleted file mode 100644 index 99ecf5232..000000000 --- a/test/screenshot/select/index.js +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; - -import Select from '../../../packages/select/index'; -import '../../../packages/select/index.scss'; -import './index.scss'; - -class SelectTest extends React.Component { - constructor(props) { - super(props); - this.state = {value: props.value || ''}; // eslint-disable-line react/prop-types - } - - render() { - const { - disabled, id, isRtl, ...otherProps // eslint-disable-line react/prop-types - } = this.props; - return ( -
- -
- ); - } -} - -const variants = [ - {}, - {box: true}, - {outlined: true}, -]; - -const rtlMap = [ - {}, - {isRtl: true}, -]; - -const disabledMap = [ - {}, - {value: 'pomsky'}, - {disabled: true}, -]; - -const selects = variants.map((variant) => { - return rtlMap.map((isRtl) => { - return disabledMap.map((disabled) => { - const props = Object.assign({}, variant, disabled, isRtl); - const variantKey = Object.keys(variant)[0] || ''; - const rtlKey = Object.keys(isRtl)[0] || ''; - const disabledKey = Object.keys(disabled)[0] || ''; - const key = `${variantKey}-${disabledKey}--${rtlKey}`; - - return ; - }); - }); -}); - -const SelectScreenshotTest = () => { - return ( -
- {selects} -
- ); -}; - -export default SelectScreenshotTest; diff --git a/test/screenshot/select/index.tsx b/test/screenshot/select/index.tsx new file mode 100644 index 000000000..25101e985 --- /dev/null +++ b/test/screenshot/select/index.tsx @@ -0,0 +1,89 @@ +import * as React from 'react'; +import Select, {SelectProps} from '../../../packages/select/index'; +import '../../../packages/select/index.scss'; +import './index.scss'; + +interface SelectTestState { + value: any +} + +class SelectTest extends React.Component { + constructor(props: SelectProps) { + super(props); + this.state = {value: props.value || ''}; // eslint-disable-line react/prop-types + } + + static defaultProps: Partial = { + box: false, + className: '', + disabled: false, + floatingLabelClassName: '', + isRtl: false, + lineRippleClassName: '', + nativeControlClassName: '', + notchedOutlineClassName: '', + outlined: false, + options: [], + onChange: () => {}, + } + + onChange = (evt: React.ChangeEvent) => ( + this.setState({value: evt.target.value}) + ); + + render() { + const { + disabled, + id, + isRtl, + ref, // eslint-disable-line no-unused-vars + ...otherProps // eslint-disable-line react/prop-types + } = this.props; + return ( +
+ +
+ ); + } +} + +const variants = [{}, {box: true}, {outlined: true}]; +const rtlMap = [{}, {isRtl: true}]; +const disabledMap = [{}, {disabled: true}]; +const valueMap = [{}, {value: 'pomsky'}]; + +const selects = variants.map((variant) => { + return rtlMap.map((isRtl) => { + return disabledMap.map((disabled) => { + return valueMap.map((value) => { + const props = Object.assign({}, variant, disabled, isRtl, value); + const valueKey = Object.keys(value)[0] || ''; + const variantKey = Object.keys(variant)[0] || ''; + const rtlKey = Object.keys(isRtl)[0] || ''; + const disabledKey = Object.keys(disabled)[0] || ''; + const key = `${variantKey}-${disabledKey}-${valueKey}--${rtlKey}`; + return ; + }); + }); + }); +}); + +const SelectScreenshotTest = () => { + return
{selects}
; +}; +export default SelectScreenshotTest; diff --git a/test/unit/select/NativeControl.test.js b/test/unit/select/NativeControl.test.tsx similarity index 60% rename from test/unit/select/NativeControl.test.js rename to test/unit/select/NativeControl.test.tsx index 2a099dadd..def9168be 100644 --- a/test/unit/select/NativeControl.test.js +++ b/test/unit/select/NativeControl.test.tsx @@ -1,18 +1,23 @@ -import React from 'react'; -import td from 'testdouble'; +import * as React from 'react'; +import * as td from 'testdouble'; import {assert} from 'chai'; import {shallow, mount} from 'enzyme'; import NativeControl from '../../../packages/select/NativeControl'; +import {coerceForTesting} from '../helpers/types'; suite('Select Native Input'); -const testEvt = {test: 'test', clientX: 20, target: { - getBoundingClientRect: () => ({left: 15}), - value: 'value', -}}; +const testEvt = { + test: 'test', + clientX: 20, + target: { + getBoundingClientRect: () => ({left: 15}), + value: 'value', + }, +}; test('has mdc-select__native-control class', () => { - const wrapper = shallow(); + const wrapper = shallow(); assert.isTrue(wrapper.hasClass('mdc-select__native-control')); }); @@ -22,7 +27,7 @@ test('classNames adds classes', () => { }); test('calls props.handleDisabled if props.disabled updates', () => { - const handleDisabled = td.func(); + const handleDisabled = coerceForTesting<(d: boolean) => void>(td.func()); const wrapper = shallow(); wrapper.setProps({disabled: true}); td.verify(handleDisabled(true), {times: 1}); @@ -36,51 +41,52 @@ test('#event.focus calls #foundation.handleFocus', () => { }); test('#event.focus calls #props.onFocus', () => { - const onFocus = td.func(); + const onFocus = coerceForTesting>(td.func()); const wrapper = shallow(); wrapper.simulate('focus', testEvt); - td.verify(onFocus(testEvt), {times: 1}); + td.verify(onFocus(coerceForTesting>(testEvt)), {times: 1}); }); test('#event.blur calls #foundation.handleBlur', () => { - const foundation = {handleBlur: td.func()}; + const foundation = {handleBlur: coerceForTesting>(td.func())}; const wrapper = shallow(); wrapper.simulate('blur', testEvt); - td.verify(foundation.handleBlur(testEvt), {times: 1}); + td.verify(foundation.handleBlur(coerceForTesting>(testEvt)), {times: 1}); }); test('#event.blur calls #props.onBlur', () => { - const onBlur = td.func(); + const onBlur = coerceForTesting>(td.func()); const wrapper = shallow(); wrapper.simulate('blur', testEvt); - td.verify(onBlur(testEvt), {times: 1}); + td.verify(onBlur(coerceForTesting>(testEvt)), {times: 1}); }); test('#event.change calls #props.onChange', () => { - const onChange = td.func(); + const onChange = coerceForTesting>(td.func()); const wrapper = shallow(); wrapper.simulate('change', testEvt); - td.verify(onChange(testEvt), {times: 1}); + td.verify(onChange(coerceForTesting>(testEvt)), {times: 1}); }); test('#event.mousedown calls #props.onMouseDown', () => { - const onMouseDown = td.func(); + const onMouseDown = coerceForTesting>(td.func()); const wrapper = shallow(); wrapper.simulate('mousedown', testEvt); - td.verify(onMouseDown(testEvt), {times: 1}); + td.verify(onMouseDown(coerceForTesting>(testEvt)), {times: 1}); }); test('#event.mousedown calls #props.setRippleCenter if target is nativeControl', () => { - const setRippleCenter = td.func(); - const wrapper = mount(); - wrapper.instance().nativeControl_ = {current: testEvt.target}; + const setRippleCenter = coerceForTesting<(rippleCenter: number) => void>(td.func()); + const wrapper = mount(); + wrapper.instance().nativeControl_ + = coerceForTesting>({current: testEvt.target}); wrapper.simulate('mousedown', testEvt); const left = testEvt.target.getBoundingClientRect().left; td.verify(setRippleCenter(testEvt.clientX - left), {times: 1}); }); test('#event.mousedown does not call #props.setRippleCenter if target is not nativeControl', () => { - const setRippleCenter = td.func(); + const setRippleCenter = coerceForTesting<(rippleCenter: number) => void>(td.func()); const wrapper = mount(); wrapper.simulate('mousedown', testEvt); const left = testEvt.target.getBoundingClientRect().left; @@ -88,23 +94,23 @@ test('#event.mousedown does not call #props.setRippleCenter if target is not nat }); test('#event.touchstart calls #props.onTouchStart', () => { - const onTouchStart = td.func(); + const onTouchStart = coerceForTesting>(td.func()); const wrapper = shallow(); - const evt = { + const evt = coerceForTesting>({ test: 'test', touches: [{clientX: 20}], target: { getBoundingClientRect: () => ({left: 15}), value: 'value', }, - }; + }); wrapper.simulate('touchstart', evt); td.verify(onTouchStart(evt), {times: 1}); }); test('#event.touchstart calls #props.setRippleCenter if target is nativeControl', () => { - const setRippleCenter = td.func(); - const wrapper = mount(); + const setRippleCenter = coerceForTesting<(rippleCenter: number) => void>(td.func()); + const wrapper = mount(); const evt = { test: 'test', touches: [{clientX: 20}], @@ -113,14 +119,14 @@ test('#event.touchstart calls #props.setRippleCenter if target is nativeControl' value: 'value', }, }; - wrapper.instance().nativeControl_ = {current: evt.target}; + wrapper.instance().nativeControl_ = coerceForTesting>({current: evt.target}); wrapper.simulate('touchstart', evt); const left = evt.target.getBoundingClientRect().left; td.verify(setRippleCenter(20 - left), {times: 1}); }); test('#event.touchstart does not call #props.setRippleCenter if target is not nativeControl', () => { - const setRippleCenter = td.func(); + const setRippleCenter = coerceForTesting<(rippleCenter: number) => void>(td.func()); const wrapper = mount(); const evt = { test: 'test', @@ -136,8 +142,10 @@ test('#event.touchstart does not call #props.setRippleCenter if target is not na }); test('renders children', () => { - const wrapper = shallow( - - ); + const wrapper = shallow( + + + + ); assert.equal(wrapper.find('option[value="test"]').length, 1); }); diff --git a/test/unit/select/index.test.js b/test/unit/select/index.test.tsx similarity index 67% rename from test/unit/select/index.test.js rename to test/unit/select/index.test.tsx index 4b122ea03..b06e358e5 100644 --- a/test/unit/select/index.test.js +++ b/test/unit/select/index.test.tsx @@ -1,9 +1,10 @@ -import React from 'react'; -import td from 'testdouble'; +import * as React from 'react'; +import * as td from 'testdouble'; import {assert} from 'chai'; import {mount, shallow} from 'enzyme'; import Select from '../../../packages/select/index'; import NativeControl from '../../../packages/select/NativeControl'; +import {coerceForTesting} from '../helpers/types'; suite('Select'); @@ -13,36 +14,35 @@ test('has mdc-select class', () => { }); test('classNames adds classes', () => { - const wrapper = shallow( + ); assert.isTrue(wrapper.hasClass('test-class-name')); }); test('creates foundation', () => { - const wrapper = mount((); - wrapper.instance().foundation_.handleChange = td.func(); +test('#foundation.handleChange gets called when state.value updates', () => { + const wrapper = shallow); + wrapper.instance().foundation.handleChange = td.func(); const value = 'value'; wrapper.setState({value}); - td.verify(wrapper.instance().foundation_.handleChange(), {times: 1}); + td.verify(wrapper.instance().foundation.handleChange(), {times: 1}); }); test('state.value updates when props.value changes', () => { - const wrapper = shallow((); - const foundation = wrapper.instance().foundation_; + const wrapper = shallow); + const foundation = wrapper.instance().foundation; foundation.destroy = td.func(); wrapper.unmount(); td.verify(foundation.destroy(), {times: 1}); @@ -70,103 +70,107 @@ test('a class in state.classList will be added to the select', () => { }); test('#adapter.addClass adds to state.classList', () => { - const wrapper = shallow((); + const wrapper = shallow); wrapper.setState({classList: new Set(['my-added-class'])}); wrapper.instance().adapter.removeClass('my-added-class'); assert.isFalse(wrapper.state().classList.has('my-added-class')); }); test('#adapter.hasClass returns true if the string is in state.classList', () => { - const wrapper = shallow((); + const wrapper = shallow); assert.isFalse(wrapper.instance().adapter.hasClass('my-added-class')); }); test('#adapter.isRtl returns true if props.isRtl is true', () => { - const wrapper = mount((); - assert.isFalse(wrapper.instance().foundation_.adapter_.isRtl()); + const wrapper = mount); + assert.isFalse(wrapper.instance().foundation.adapter_.isRtl()); }); test('adapter.getValue returns state.value', () => { - const wrapper = shallow((); + const wrapper = shallow); wrapper.instance().adapter.floatLabel(true); assert.isTrue(wrapper.state().labelIsFloated); }); test('#adapter.hasLabel returns true if label exists', () => { - const wrapper = shallow((); + const wrapper = shallow); const labelWidth = 59; wrapper.setState({labelWidth}); assert.equal(wrapper.instance().adapter.getLabelWidth(), 59); }); test('#adapter.activateBottomLine sets state.activeLineRipple to true', () => { - const wrapper = shallow((); + const wrapper = shallow); wrapper.setState({activeLineRipple: false}); wrapper.instance().adapter.deactivateBottomLine(); assert.isFalse(wrapper.state().activeLineRipple); }); test('NativeControl.props.setRippleCenter sets state.lineRippleCenter', () => { - const wrapper = shallow((); + const wrapper = shallow); wrapper.instance().adapter.notchOutline(); assert.isTrue(wrapper.state().outlineIsNotched); }); test('#adapter.closeOutline sets state.outlineIsNotched to false', () => { - const wrapper = shallow((); + const wrapper = shallow); assert.isTrue(wrapper.instance().adapter.hasOutline()); }); test('#adapter.hasOutline returns false if props.outlined is false', () => { - const wrapper = shallow((); + const wrapper = shallow( + ); + const wrapper = shallow); const evt = {target: {value: 'orange'}}; - wrapper.childAt(0).props().onChange(evt); + wrapper + .childAt(0) + .props() + .onChange(evt); assert.equal(wrapper.state().value, 'orange'); }); test('#NativeControl.onChange will call this.props.onChange', () => { - const onChange = td.func(); - const wrapper = shallow((); - wrapper.childAt(0).props().handleDisabled(true); + const wrapper = shallow); + wrapper + .childAt(0) + .props() + .handleDisabled(true); assert.equal(wrapper.state().disabled, true); }); test('passes foundation to NativeControl', () => { - const wrapper = mount(( - {options} - ); + const options = ; + const wrapper = shallow(); assert.equal(wrapper.find('option').length, 1); }); test('renders options passed as children', () => { - const options = ( - - - ); - const wrapper = shallow(); + const options = ( + + + + + ); + const wrapper = shallow(); assert.equal(wrapper.find('option').length, 2); }); test('renders options passed as array of 1 string', () => { - const wrapper = shallow(); assert.equal(wrapper.find('option[value="opt 1"]').length, 1); }); test('renders options passed as array of strings', () => { - const wrapper = shallow( + ); assert.equal(wrapper.find('option').length, 3); }); test('renders options passed as array of 1 object', () => { - const wrapper = shallow( + ); assert.equal(wrapper.find('option[value="opt-1"]').length, 1); }); test('renders options passed as array of objects', () => { - const wrapper = shallow( + ); assert.equal(wrapper.find('option').length, 3); }); test('renders options as disabled', () => { - const wrapper = shallow( + ); assert.equal(wrapper.find('option[disabled]').length, 1); }); test('passes classNames to FloatingLabel through props.floatingLabelClassName', () => { const className = 'floating-class'; - const wrapper = shallow( + ); assert.isTrue(wrapper.childAt(1).hasClass(className)); }); @@ -293,14 +323,16 @@ test('updates float prop with state.labelIsFloated', () => { }); test('#floatingLabel.handleWidthChange updates state.labelWidth', () => { - const wrapper = shallow((); + const wrapper = shallow( + ); + const wrapper = shallow( +