Repository: react-materialize/react-materialize
Pull request #803 (2019-04-02 17:12:14)

Add defaultProps to TimePicker component
# Description

Fixing some `TimePicker` `propTypes`, adding `defaultProps`;

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

# How Has This Been Tested?

Adding new spec;
Adding new snapshots;

# Checklist:

- [x] I have commented my code, particularly in hard-to-understand areas
- [x] My changes generate no new warnings
- [x] I have not generated a new package version. (the maintainers will handle that)



Patch:
diff --git a/src/Textarea.js b/src/Textarea.js
index ce7fd5d..2cf7cc0 100644
--- a/src/Textarea.js
+++ b/src/Textarea.js
@@ -1,9 +1,8 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
-import cx from 'classnames';
 import idgen from './idgen';
 import Icon from './Icon';
-import constants from './constants';
+import TextInput from './TextInput';
 
 class Textarea extends Component {
   componentDidUpdate() {
@@ -15,36 +14,12 @@ class Textarea extends Component {
   }
 
   render() {
-    const {
-      s,
-      m,
-      l,
-      xl,
-      noLayout,
-      iconClassName,
-      icon,
-      label,
-      id,
-      onChange,
-      ...other
-    } = this.props;
+    const { iconClassName, icon, label, id, onChange, ...other } = this.props;
 
     const computedId = id || idgen();
 
-    const sizes = { s, m, l, xl };
-
-    let responsiveClasses;
-    if (!noLayout) {
-      responsiveClasses = { col: true };
-      constants.SIZES.forEach(size => {
-        responsiveClasses[size + sizes[size]] = sizes[size];
-      });
-    }
-
-    const wrapperClasses = cx('input-field', responsiveClasses);
-
     return (
-      <div className={wrapperClasses}>
+      <div className="input-field">
         {icon && this.renderIcon(icon, iconClassName)}
         <textarea
           ref={input => {
@@ -62,26 +37,6 @@ class Textarea extends Component {
 }
 
 Textarea.propTypes = {
-  /*
- * Strip away all layout classes such as col and sX
- */
-  noLayout: PropTypes.bool,
-  /*
-   * Responsive size for Mobile Devices
-   */
-  s: PropTypes.number,
-  /*
-   * Responsive size for Tablet Devices
-   */
-  m: PropTypes.number,
-  /*
-   * Responsive size for Desktop Devices
-   */
-  l: PropTypes.number,
-  /**
-   *  Responsive size for Large Desktop Devices
-   */
-  xl: PropTypes.number,
   /*
    * render icon next to input
    */
diff --git a/src/TimePicker.js b/src/TimePicker.js
index 3987fe1..1fc21a2 100644
--- a/src/TimePicker.js
+++ b/src/TimePicker.js
@@ -1,6 +1,5 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import cx from 'classnames';
 import TextInput from './TextInput';
 import idgen from './idgen';
 
@@ -13,8 +12,13 @@ class TimePicker extends React.Component {
 
   componentDidMount() {
     if (typeof M !== 'undefined') {
+      const { onChange } = this.props;
       const elem = document.getElementById(this.id);
-      this.instance = M.Timepicker.init(elem, this.props.options);
+      const options = onChange
+        ? { ...this.props.options, onSelect: onChange }
+        : this.props.options;
+
+      this.instance = M.Timepicker.init(elem, options);
     }
   }
 
@@ -25,16 +29,8 @@ class TimePicker extends React.Component {
   }
 
   render() {
-    const { id, onChange, ...other } = this.props;
-    const onSelect = onChange;
-
     return (
-      <TextInput
-        id={this.id}
-        inputClassName="timepicker"
-        onSelect={onSelect}
-        {...other}
-      />
+      <TextInput id={this.id} inputClassName="timepicker" {...this.props} />
     );
   }
 }
@@ -82,7 +78,11 @@ TimePicker.propTypes = {
      * Internationalization options.
      * @default See i18n documentation.
      */
-    i18n: PropTypes.any,
+    i18n: PropTypes.shape({
+      cancel: PropTypes.string,
+      clear: PropTypes.string,
+      done: PropTypes.string
+    }),
     /**
      * Automatically close picker when minute is selected.
      * @default false
@@ -102,28 +102,51 @@ TimePicker.propTypes = {
      * Callback function called before modal is opened.
      * @default null
      */
-    onOpenStart: PropTypes.any,
+    onOpenStart: PropTypes.func,
     /**
      * Callback function called after modal is opened.
      * @default null
      */
-    onOpenEnd: PropTypes.any,
+    onOpenEnd: PropTypes.func,
     /**
      * Callback function called before modal is closed.
      * @default null
      */
-    onCloseStart: PropTypes.any,
+    onCloseStart: PropTypes.func,
     /**
      * Callback function called after modal is closed.
      * @default null
      */
-    onCloseEnd: PropTypes.any,
+    onCloseEnd: PropTypes.func,
     /**
      * Callback function when a time is selected, first parameter is the hour and the second is the minute.
      * @default null
      */
-    onSelect: PropTypes.any
+    onSelect: PropTypes.func
   })
 };
 
+TimePicker.defaultProps = {
+  options: {
+    duration: 350,
+    container: null,
+    showClearBtn: false,
+    defaultTime: 'now',
+    fromNow: 0,
+    i18n: {
+      cancel: 'Cancel',
+      clear: 'Clear',
+      done: 'Ok'
+    },
+    autoClose: false,
+    twelveHour: true,
+    vibrate: true,
+    onOpenStart: null,
+    onOpenEnd: null,
+    onCloseStart: null,
+    onCloseEnd: null,
+    onSelect: null
+  }
+};
+
 export default TimePicker;
diff --git a/src/index.js b/src/index.js
index 29e7d2f..0dffaf3 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,7 +2,6 @@ export { default as Autocomplete } from './Autocomplete';
 export { default as Badge } from './Badge';
 export { default as Breadcrumb } from './Breadcrumb';
 export { default as Button } from './Button';
-export { default as Caption } from './Caption';
 export { default as Card } from './Card';
 export { default as CardPanel } from './CardPanel';
 export { default as CardTitle } from './CardTitle';
diff --git a/stories/Media.stories.js b/stories/Media.stories.js
index 6c08bd4..6a8541d 100644
--- a/stories/Media.stories.js
+++ b/stories/Media.stories.js
@@ -3,7 +3,6 @@ import { storiesOf } from '@storybook/react';
 import MediaBox from '../src/MediaBox';
 import Slider from '../src/Slider';
 import Slide from '../src/Slide';
-import Caption from '../src/Caption';
 
 const stories = storiesOf('Javascript|Media', module);
 
@@ -22,37 +21,50 @@ stories.add('Default', () => (
 
 stories.add('Slider', () => (
   <Slider>
-    <Slide image={<img src="http://lorempixel.com/780/580/nature/1" alt="" />}>
-      <Caption>
+    <Slide
+      image={
+        <MediaBox
+          src="http://lorempixel.com/780/580/nature/1"
+          caption="A demo media box1"
+        />
+      }
+    >
+      <div className="caption center-align">
         <h3>This is our big Tagline!</h3>
         <h5 className="light grey-text text-lighten-3">
           Here's our small slogan.
         </h5>
-      </Caption>
+      </div>
     </Slide>
-    <Slide image={<img src="http://lorempixel.com/780/580/nature/2" alt="" />}>
-      <Caption placement="left">
+    <Slide
+      image={
+        <MediaBox
+          src="http://lorempixel.com/780/580//nature/2"
+          caption="A demo media box1"
+        />
+      }
+    >
+      <div className="caption left-align">
         <h3>Left Aligned Caption</h3>
         <h5 className="light grey-text text-lighten-3">
           Here's our small slogan.
         </h5>
-      </Caption>
+      </div>
     </Slide>
-    <Slide image={<img src="https://lorempixel.com/780/580/nature/3" alt="" />}>
-      <Caption placement="right">
+    <Slide
+      image={
+        <MediaBox
+          src="http://lorempixel.com/780/580//nature/3"
+          caption="A demo media box1"
+        />
+      }
+    >
+      <div className="caption right-align">
         <h3>Right Aligned Caption</h3>
         <h5 className="light grey-text text-lighten-3">
           Here's our small slogan.
         </h5>
-      </Caption>
-    </Slide>
-    <Slide image={<img src="https://lorempixel.com/580/250/nature/4" alt="" />}>
-      <Caption>
-        <h3>This is our big Tagline!</h3>
-        <h5 className="light grey-text text-lighten-3">
-          Here's our small slogan.
-        </h5>
-      </Caption>
+      </div>
     </Slide>
   </Slider>
 ));
diff --git a/stories/Textarea.stories.js b/stories/Textarea.stories.js
index 8481e33..3568c5a 100644
--- a/stories/Textarea.stories.js
+++ b/stories/Textarea.stories.js
@@ -1,11 +1,8 @@
 import React from 'react';
 import { storiesOf } from '@storybook/react';
-import { text, boolean, number, withKnobs } from '@storybook/addon-knobs';
-import Row from '../src/Row';
 import Textarea from '../src/Textarea';
 
 const stories = storiesOf('Components|Textarea', module);
-stories.addDecorator(withKnobs);
 
 stories.addParameters({
   info: {
@@ -16,40 +13,12 @@ stories.addParameters({
   }
 });
 
-stories.add('Textarea', () => {
-  const defaultValue = 12;
-  const options = {
-    range: true,
-    min: 1,
-    max: 12,
-    step: 1
-  };
+stories.add('Textarea', () => <Textarea />);
 
-  return (
-    <Row>
-      <Textarea
-        s={number('s', defaultValue, options)}
-        m={number('m', defaultValue, options)}
-        l={number('l', defaultValue, options)}
-        xl={number('xl', defaultValue, options)}
-      />
-    </Row>
-  );
-});
-
-stories.add('Disabled', () => (
-  <Textarea
-    value={text('value', 'I was here')}
-    disabled={boolean('disabled', true)}
-  />
-));
+stories.add('Disabled', () => <Textarea value="I was here" disabled />);
 
-stories.add('Placeholder', () => (
-  <Textarea placeholder={text('placeholder', 'I was here')} />
-));
+stories.add('Placeholder', () => <Textarea placeholder="I was here" />);
 
-stories.add('Label', () => (
-  <Textarea label={text('label', 'Write something here...')} />
-));
+stories.add('Label', () => <Textarea label="Write something here..." />);
 
-stories.add('Icon', () => <Textarea icon={text('icon', 'mode_edit')} />);
+stories.add('Icon', () => <Textarea icon="mode_edit" />);


Test patch:
diff --git a/test/Textarea.spec.js b/test/Textarea.spec.js
index 7c9b3ab..a3785eb 100644
--- a/test/Textarea.spec.js
+++ b/test/Textarea.spec.js
@@ -10,24 +10,6 @@ describe('<Textarea />', () => {
     expect(wrapper).toMatchSnapshot();
   });
 
-  test('Handles layout classes', () => {
-    wrapper = shallow(<Textarea s={4} m={6} />);
-
-    expect(wrapper.hasClass('input-field')).toBeTruthy();
-    expect(wrapper.hasClass('col')).toBeTruthy();
-    expect(wrapper.hasClass('s4')).toBeTruthy();
-    expect(wrapper.hasClass('m6')).toBeTruthy();
-  });
-
-  test('Handles removing layout classes', () => {
-    wrapper = shallow(<Textarea s={4} m={6} noLayout />);
-
-    expect(wrapper.hasClass('input-field')).toBeTruthy();
-    expect(wrapper.hasClass('col')).toBeFalsy();
-    expect(wrapper.hasClass('s4')).toBeFalsy();
-    expect(wrapper.hasClass('m6')).toBeFalsy();
-  });
-
   test('Disabled', () => {
     wrapper = shallow(<Textarea value="I was here" disabled />);
     expect(wrapper).toMatchSnapshot();
diff --git a/test/TimePicker.spec.js b/test/TimePicker.spec.js
new file mode 100644
index 0000000..4d171b4
--- /dev/null
+++ b/test/TimePicker.spec.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import { shallow, mount } from 'enzyme';
+import TimePicker from '../src/TimePicker';
+import mocker from './helper/new-mocker';
+
+describe('<TimePicker />', () => {
+  let wrapper;
+
+  test('renders', () => {
+    wrapper = shallow(<TimePicker />);
+    expect(wrapper).toMatchSnapshot();
+  });
+
+  test('renders with a label', () => {
+    wrapper = shallow(<TimePicker label="TimePicker label" />);
+    expect(wrapper).toMatchSnapshot();
+  });
+
+  test('accepts external `id` value', () => {
+    wrapper = shallow(<TimePicker id="ID" />);
+    expect(wrapper).toMatchSnapshot();
+  });
+
+  describe('initialises', () => {
+    const timePickerInitMock = jest.fn();
+    const timePickerInstanceDestroyMock = jest.fn();
+    const timePickerMock = {
+      init: (el, options) => {
+        timePickerInitMock(options);
+        return {
+          destroy: timePickerInstanceDestroyMock
+        };
+      }
+    };
+
+    const restore = mocker('Timepicker', timePickerMock);
+
+    beforeEach(() => {
+      timePickerInitMock.mockClear();
+      timePickerInstanceDestroyMock.mockClear();
+    });
+
+    afterAll(() => {
+      restore();
+    });
+
+    test('uses default options if none are given', () => {
+      wrapper = mount(<TimePicker />);
+
+      expect(timePickerInitMock).toHaveBeenCalledWith({
+        duration: 350,
+        container: null,
+        showClearBtn: false,
+        defaultTime: 'now',
+        fromNow: 0,
+        i18n: {
+          cancel: 'Cancel',
+          clear: 'Clear',
+          done: 'Ok'
+        },
+        autoClose: false,
+        twelveHour: true,
+        vibrate: true,
+        onOpenStart: null,
+        onOpenEnd: null,
+        onCloseStart: null,
+        onCloseEnd: null,
+        onSelect: null
+      });
+    });
+  });
+});
diff --git a/test/__snapshots__/Textarea.spec.js.snap b/test/__snapshots__/Textarea.spec.js.snap
index ca97ec1..fff9088 100644
--- a/test/__snapshots__/Textarea.spec.js.snap
+++ b/test/__snapshots__/Textarea.spec.js.snap
@@ -2,7 +2,7 @@
 
 exports[`<Textarea /> Disabled 1`] = `
 <div
-  className="input-field col"
+  className="input-field"
 >
   <textarea
     className="materialize-textarea"
@@ -18,7 +18,7 @@ exports[`<Textarea /> Disabled 1`] = `
 
 exports[`<Textarea /> Icon 1`] = `
 <div
-  className="input-field col"
+  className="input-field"
 >
   <Icon
     className="material-icons prefix"
@@ -37,7 +37,7 @@ exports[`<Textarea /> Icon 1`] = `
 
 exports[`<Textarea /> Label 1`] = `
 <div
-  className="input-field col"
+  className="input-field"
 >
   <textarea
     className="materialize-textarea"
@@ -53,7 +53,7 @@ exports[`<Textarea /> Label 1`] = `
 
 exports[`<Textarea /> Placeholder 1`] = `
 <div
-  className="input-field col"
+  className="input-field"
 >
   <textarea
     className="materialize-textarea"
@@ -68,7 +68,7 @@ exports[`<Textarea /> Placeholder 1`] = `
 
 exports[`<Textarea /> Textarea 1`] = `
 <div
-  className="input-field col"
+  className="input-field"
 >
   <textarea
     className="materialize-textarea"
diff --git a/test/__snapshots__/TimePicker.spec.js.snap b/test/__snapshots__/TimePicker.spec.js.snap
new file mode 100644
index 0000000..a14596b
--- /dev/null
+++ b/test/__snapshots__/TimePicker.spec.js.snap
@@ -0,0 +1,89 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<TimePicker /> accepts external \`id\` value 1`] = `
+<TextInput
+  id="ID"
+  inputClassName="timepicker"
+  options={
+    Object {
+      "autoClose": false,
+      "container": null,
+      "defaultTime": "now",
+      "duration": 350,
+      "fromNow": 0,
+      "i18n": Object {
+        "cancel": "Cancel",
+        "clear": "Clear",
+        "done": "Ok",
+      },
+      "onCloseEnd": null,
+      "onCloseStart": null,
+      "onOpenEnd": null,
+      "onOpenStart": null,
+      "onSelect": null,
+      "showClearBtn": false,
+      "twelveHour": true,
+      "vibrate": true,
+    }
+  }
+/>
+`;
+
+exports[`<TimePicker /> renders 1`] = `
+<TextInput
+  id="timepickermockId"
+  inputClassName="timepicker"
+  options={
+    Object {
+      "autoClose": false,
+      "container": null,
+      "defaultTime": "now",
+      "duration": 350,
+      "fromNow": 0,
+      "i18n": Object {
+        "cancel": "Cancel",
+        "clear": "Clear",
+        "done": "Ok",
+      },
+      "onCloseEnd": null,
+      "onCloseStart": null,
+      "onOpenEnd": null,
+      "onOpenStart": null,
+      "onSelect": null,
+      "showClearBtn": false,
+      "twelveHour": true,
+      "vibrate": true,
+    }
+  }
+/>
+`;
+
+exports[`<TimePicker /> renders with a label 1`] = `
+<TextInput
+  id="timepickermockId"
+  inputClassName="timepicker"
+  label="TimePicker label"
+  options={
+    Object {
+      "autoClose": false,
+      "container": null,
+      "defaultTime": "now",
+      "duration": 350,
+      "fromNow": 0,
+      "i18n": Object {
+        "cancel": "Cancel",
+        "clear": "Clear",
+        "done": "Ok",
+      },
+      "onCloseEnd": null,
+      "onCloseStart": null,
+      "onOpenEnd": null,
+      "onOpenStart": null,
+      "onSelect": null,
+      "showClearBtn": false,
+      "twelveHour": true,
+      "vibrate": true,
+    }
+  }
+/>
+`;