Repository: mlaursen/react-md
Pull request #83 (2016-05-12 16:39:52)

Enhancement/selection control tests
This should solve #53, #54, #55 


Patch:
diff --git a/src/js/Inks/__mocks__/index.js b/src/js/Inks/__mocks__/index.js
new file mode 100644
index 0000000000..4c39db5a0f
--- /dev/null
+++ b/src/js/Inks/__mocks__/index.js
@@ -0,0 +1,2 @@
+/*eslint-env jest*/
+export default jest.genMockFunction().mockImplementation(ComposedComponent => ComposedComponent);
diff --git a/src/js/SelectionControls/Checkbox.js b/src/js/SelectionControls/Checkbox.js
index a449af4883..93076a5434 100644
--- a/src/js/SelectionControls/Checkbox.js
+++ b/src/js/SelectionControls/Checkbox.js
@@ -4,6 +4,10 @@ import FontIcon from '../FontIcons';
 import ControlContainer from './ControlContainer';
 
 
+/**
+ * Checkboxes can be toggle by clicking the label of the checkbox or the checkbox itself. If the icon itself is clicked, ink will appear. There will be no
+ * ink when the label is clicked.
+ */
 export default class Checkbox extends Component {
   constructor(props) {
     super(props);
@@ -12,19 +16,67 @@ export default class Checkbox extends Component {
   }
 
   static propTypes = {
+    /**
+     * The optional className to apply to the surrounding label.
+     */
     className: PropTypes.string,
+
+    /**
+     * Boolean if the checkbox is disabled.
+     */
     disabled: PropTypes.bool,
+
+    /**
+     * An optional function to call when the checked state is called.
+     * The next checked state and change event will be passed.
+     *
+     * `onChange(!checked, event)`.
+     */
     onChange: PropTypes.func,
+
+    /**
+     * An optional value for the checkbox.
+     */
     value: PropTypes.oneOfType([
       PropTypes.string,
       PropTypes.number,
     ]),
-    checked: PropTypes.bool,
+
+    /**
+     * Boolean if the checkbox is checked by default.
+     */
     defaultChecked: PropTypes.bool.isRequired,
-    label: PropTypes.node,
-    labelBefore: PropTypes.bool.isRequired,
+
+    /**
+     * Boolean if this checkbox is currently checked. This will
+     * require the `onChange` prop to be defined since it will
+     * become a controlled component.
+     */
+    checked: PropTypes.bool,
+
+    /**
+     * The icon to use for the checked state.
+     */
     checkedIcon: PropTypes.node.isRequired,
+
+    /**
+     * The icon to use for the unchecked state.
+     */
     uncheckedIcon: PropTypes.node.isRequired,
+
+    /**
+     * An optional label to display with the checkbox.
+     */
+    label: PropTypes.node,
+
+    /**
+     * Boolean if the label should be displayed before or after the checkbox.
+     */
+    labelBefore: PropTypes.bool.isRequired,
+
+    /**
+     * An optional form name to give to the checkbox.
+     */
     name: PropTypes.string,
   };
 
diff --git a/src/js/SelectionControls/ControlContainer.js b/src/js/SelectionControls/ControlContainer.js
index 719d0b565a..f2d86d7bd2 100644
--- a/src/js/SelectionControls/ControlContainer.js
+++ b/src/js/SelectionControls/ControlContainer.js
@@ -3,6 +3,12 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
 import classnames from 'classnames';
 import InkedControl from './InkedControl';
 
+/**
+ * This is the component that is used for the `Checkbox` component
+ * and the `Radio` component. This is a label that includes
+ * the input type, an icon showing the state of the control,
+ * and an optional label.
+ */
 export default class ControlContainer extends Component {
   constructor(props) {
     super(props);
@@ -12,20 +18,69 @@ export default class ControlContainer extends Component {
   }
 
   static propTypes = {
+    /**
+     * The type of the selection control.
+     */
     type: PropTypes.oneOf(['radio', 'checkbox']).isRequired,
+
+    /**
+     * The optional className to apply to the surrounding label.
+     */
     className: PropTypes.string,
+
+    /**
+     * Boolean if the control is disabled.
+     */
     disabled: PropTypes.bool,
+
+    /**
+     * An optional function to call when the checked state is called.
+     */
     onChange: PropTypes.func,
+
+    /**
+     * An optional value for the control field.
+     */
     value: PropTypes.oneOfType([
       PropTypes.string,
       PropTypes.number,
     ]),
+
+    /**
+     * Boolean if the control field is checked by default.
+     */
     defaultChecked: PropTypes.bool.isRequired,
+
+    /**
+     * Boolean if this control is currently checked. This will
+     * require the `onChange` prop to be defined since it will
+     * become a controlled component.
+     */
     checked: PropTypes.bool,
+
+    /**
+     * The icon to use for the checked state.
+     */
     checkedIcon: PropTypes.node.isRequired,
+
+    /**
+     * The icon to use for the unchecked state.
+     */
     uncheckedIcon: PropTypes.node.isRequired,
+
+    /**
+     * An optional label to display with the control.
+     */
     label: PropTypes.node,
+
+    /**
+     * Boolean if the label should be displayed before or after the control field.
+     */
     labelBefore: PropTypes.bool.isRequired,
+
+    /**
+     * An optional form name to give to the control.
+     */
     name: PropTypes.string,
   };
 
@@ -34,21 +89,21 @@ export default class ControlContainer extends Component {
   };
 
   handleChange = (e) => {
-    const { onChange, value, checked } = this.props;
-    onChange && onChange(value, e);
+    const { onChange, value, type } = this.props;
+    const checked = !this.isChecked();
+    if(onChange) {
+      const arg = type === 'radio' ? value : checked;
+      onChange(arg, e);
+    }
+
     // prevents 2 change events triggering
     e.stopPropagation();
 
-    if(typeof checked === 'undefined') {
-      this.setState({ checked: !this.state.checked });
+    if(typeof this.props.checked === 'undefined') {
+      this.setState({ checked });
     }
   };
 
-  handleClick = (e) => {
-    // Prevent 2 onChange after a click event
-    e.stopPropagation();
-  };
-
   render() {
     const isChecked = this.isChecked();
     const {
@@ -80,7 +135,6 @@ export default class ControlContainer extends Component {
           onChange={this.handleChange}
           name={name}
           value={value}
-          onClick={this.handleClick}
         />
         <InkedControl
           type={type}
diff --git a/src/js/SelectionControls/Radio.js b/src/js/SelectionControls/Radio.js
index dceb694790..ae37a24600 100644
--- a/src/js/SelectionControls/Radio.js
+++ b/src/js/SelectionControls/Radio.js
@@ -4,6 +4,11 @@ import FontIcon from '../FontIcons';
 import ControlContainer from './ControlContainer';
 
 
+/**
+ * The `Radio` component can be used with the `RadioGroup` component for
+ * additional state management. It is completely optional to use these
+ * two components together though. It is just to eliminate some redundancies.
+ */
 export default class Radio extends Component {
   constructor(props) {
     super(props);
@@ -12,19 +17,70 @@ export default class Radio extends Component {
   }
 
   static propTypes = {
+    /**
+     * The optional className to apply to the surrounding label.
+     */
     className: PropTypes.string,
+
+    /**
+     * Boolean if the radio is disabled.
+     */
     disabled: PropTypes.bool,
+
+    /**
+     * An optional function to call when the checked state is called.
+     * The radio value and change event will be passed.
+     *
+     * `onChange(value, event)`.
+     */
     onChange: PropTypes.func,
+
+    /**
+     * The value for the radio
+     */
     value: PropTypes.oneOfType([
       PropTypes.string,
       PropTypes.number,
     ]).isRequired,
+
+    /**
+     * Boolean if the radio should be checked by default. Only use this
+     * if you are not using the `RadioGroup` component.
+     */
     defaultChecked: PropTypes.bool.isRequired,
+
+    /**
+     * Boolean if the radio button is checked. If you are using the
+     * `RadioGroup` component, this will be injected automatically
+     * for you. If this prop is defined, it has become a controlled component
+     * so you will need to use include the `onChange` prop as well.
+     */
     checked: PropTypes.bool,
+
+    /**
+     * The icon to use for the checked state of the radio.
+     */
     checkedIcon: PropTypes.node.isRequired,
+
+    /**
+     * The icon to use for the unchecked state of the radio.
+     */
     uncheckedIcon: PropTypes.node.isRequired,
+
+      /**
+    * The optional label to display with the radio button.
+    */
     label: PropTypes.node,
+
+    /**
+     * Boolean if the label should appear before the radio button.
+     */
     labelBefore: PropTypes.bool.isRequired,
+
+    /**
+     * The name to use for the radio. If you are usng the `RadioGroup` component,
+     * this will be injected automatically.
+     */
     name: PropTypes.string,
   };
 
diff --git a/src/js/SelectionControls/RadioGroup.js b/src/js/SelectionControls/RadioGroup.js
index 46af9aef3c..95cb54392e 100644
--- a/src/js/SelectionControls/RadioGroup.js
+++ b/src/js/SelectionControls/RadioGroup.js
@@ -2,6 +2,13 @@ import React, { Component, PropTypes } from 'react';
 import PureRenderMixin from 'react-addons-pure-render-mixin';
 import classnames from 'classnames';
 
+/**
+ * The `RadioGroup` component is a state manager for the `Radio` component.
+ * It will automatically inject the name, an onChange function, and
+ * determine whether a radio is checked.
+ *
+ * This is just a simple wrapper to reduce some prop redundancy.
+ */
 export default class RadioGroup extends Component {
   constructor(props) {
     super(props);
@@ -13,13 +20,57 @@ export default class RadioGroup extends Component {
   }
 
   static propTypes = {
+    /**
+     * The default value for the radio group. This will check the radio that
+     * has the same value first by default.
+     *
+     * If this is omitted, the first radio will be selected.
+     */
     defaultValue: PropTypes.string,
+
+    /**
+     * Boolean if the entire radio group is disabled.
+     */
+    disabled: PropTypes.bool,
+
+    /**
+     * An optional className to apply to the radio group.
+     */
     className: PropTypes.string,
-    children: PropTypes.arrayOf(PropTypes.node),
-    component: PropTypes.string,
+
+    /**
+     * The radio buttons to manage.
+     */
+    children: PropTypes.arrayOf(PropTypes.node).isRequired,
+
+    /**
+     * The component to render the radio group as.
+     */
+    component: PropTypes.string.isRequired,
+
+    /**
+     * Boolean if the radio buttons should be force to be inline.
+     */
     inline: PropTypes.bool,
+
+    /**
+     * An optional onChange function to call when any of the radio buttons are clicked.
+     * This will return the clicked radio's value and the change event.
+     *
+     * `onChange(value, event)`.
+     */
     onChange: PropTypes.func,
+
+    /**
+     * The name to give to all the radio buttons.
+     */
     name: PropTypes.string,
+
+    /**
+     * The current value for the radio group. This will convert the radio group
+     * into a controlled component which will require the onChange prop
+     * to be given.
+     */
     value: PropTypes.string,
   };
 
@@ -43,7 +94,7 @@ export default class RadioGroup extends Component {
   };
 
   render() {
-    const { component, className, children, name, inline, ...props } = this.props;
+    const { component, className, children, name, inline, disabled, ...props } = this.props;
     const fullProps = {
       ...props,
       className: classnames('md-radio-group', className),
@@ -57,6 +108,7 @@ export default class RadioGroup extends Component {
         onChange: this.handleChange,
         name: name || child.props.name,
         className: classnames({ inline }),
+        disabled: child.props.disabled || disabled,
       });
     }));
   }
diff --git a/src/js/SelectionControls/Switch.js b/src/js/SelectionControls/Switch.js
index e4c102c385..691b2a8a77 100644
--- a/src/js/SelectionControls/Switch.js
+++ b/src/js/SelectionControls/Switch.js
@@ -13,13 +13,54 @@ export default class Switch extends Component {
   }
 
   static propTypes = {
+    /**
+     * An optional style to apply.
+     */
+    style: PropTypes.object,
+
+    /**
+     * An optional className to apply.
+     */
     className: PropTypes.string,
+
+    /**
+     * Boolean if the switch is disabled.
+     */
     disabled: PropTypes.bool,
+
+    /**
+     * Boolean if the switch is toggled on by default.
+     */
     defaultToggled: PropTypes.bool.isRequired,
+
+    /**
+     * Boolean if the switch is toggled on. This will make the switch
+     * a controlled component which requires the `onChange` prop to be
+     * set.
+     */
     toggled: PropTypes.bool,
+
+    /**
+     * An optional function to call when the toggled state changes.
+     * It will be called with the next toggled state and the click event.
+     *
+     * `onChange(!toggled, event)`.
+     */
     onChange: PropTypes.func,
+
+    /**
+     * An optional value to apply to the switch.
+     */
     value: PropTypes.string,
+
+    /**
+     * An optional label to display with the switch.
+     */
     label: PropTypes.node,
+
+    /**
+     * Boolean if the label should appear before the switch.
+     */
     labelBefore: PropTypes.bool,
   };
 
@@ -29,9 +70,11 @@ export default class Switch extends Component {
 
   toggleCheck = (e) => {
     const { onChange } = this.props;
-    onChange && onChange(e);
+    const toggled = !this.isToggled();
+    onChange && onChange(toggled, e);
+
     if(typeof this.props.toggled === 'undefined') {
-      this.setState({ toggled: !this.state.toggled });
+      this.setState({ toggled });
     }
   };
 
@@ -58,14 +101,19 @@ export default class Switch extends Component {
 
   render() {
     const { active, leaving } = this.state;
-    const { className, label, labelBefore, disabled, ...props } = this.props;
+    const { className, label, labelBefore, disabled, style, ...props } = this.props;
     delete props.toggled;
 
     const labelClassName = classnames('md-control-container', className, { disabled });
 
     const spanLabel = label ? <span className="label">{label}</span> : null;
     return (
-      <label className={labelClassName} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
+      <label
+        className={labelClassName}
+        onMouseDown={this.handleMouseDown}
+        onMouseUp={this.handleMouseUp}
+        style={style}
+      >
         {labelBefore && spanLabel}
         <div className="md-switch-container">
           <input


Test patch:
diff --git a/src/js/SelectionControls/__tests__/Checkbox.js b/src/js/SelectionControls/__tests__/Checkbox.js
new file mode 100644
index 0000000000..67eb26f8d0
--- /dev/null
+++ b/src/js/SelectionControls/__tests__/Checkbox.js
@@ -0,0 +1,38 @@
+/*eslint-env jest*/
+jest.unmock('../Checkbox');
+jest.unmock('../ControlContainer');
+
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import {
+  Simulate,
+  renderIntoDocument,
+  findRenderedDOMComponentWithTag,
+} from 'react-addons-test-utils';
+
+import Checkbox from '../Checkbox';
+
+describe('Checkbox', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    const checkbox = renderIntoDocument(
+      <Checkbox style={style} className={className} value="A" />
+    );
+
+    const checkboxNode = findDOMNode(checkbox);
+    expect(checkboxNode.style.display).toBe(style.display);
+    expect(checkboxNode.classList.contains(className)).toBe(true);
+  });
+
+  it('calls the onChange function prop with the next checked state and the event', () => {
+    const onChange = jest.genMockFunction();
+    const checkbox = renderIntoDocument(<Checkbox onChange={onChange} value="A" />);
+
+    const input = findRenderedDOMComponentWithTag(checkbox, 'input');
+    Simulate.change(input, { target: { checked: true }});
+
+    expect(onChange.mock.calls.length).toBe(1);
+    expect(onChange.mock.calls[0][0]).toBe(true);
+  });
+});
diff --git a/src/js/SelectionControls/__tests__/ControlContainer.js b/src/js/SelectionControls/__tests__/ControlContainer.js
new file mode 100644
index 0000000000..dc13900a0b
--- /dev/null
+++ b/src/js/SelectionControls/__tests__/ControlContainer.js
@@ -0,0 +1,161 @@
+/*eslint-env jest*/
+jest.unmock('../ControlContainer');
+jest.unmock('../InkedControl');
+
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import {
+  Simulate,
+  renderIntoDocument,
+  findRenderedDOMComponentWithTag,
+} from 'react-addons-test-utils';
+
+import ControlContainer from '../ControlContainer';
+
+describe('ControlContainer', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    const controlContainer = renderIntoDocument(
+      <ControlContainer
+        style={style}
+        className={className}
+        type="checkbox"
+        checkedIcon="check"
+        uncheckedIcon="unchecked"
+        labelBefore={false}
+        defaultChecked={false}
+      />
+    );
+
+    const controlContainerNode = findDOMNode(controlContainer);
+    expect(controlContainerNode.style.display).toBe(style.display);
+    expect(controlContainerNode.classList.contains(className)).toBe(true);
+  });
+
+  it('renders an input of radio or checkbox', () => {
+    let controlContainer = renderIntoDocument(
+      <ControlContainer
+        type="checkbox"
+        checkedIcon="check"
+        uncheckedIcon="unchecked"
+        labelBefore={false}
+        defaultChecked={false}
+      />
+    );
+
+    let input = findRenderedDOMComponentWithTag(controlContainer, 'input');
+    expect(input.getAttribute('type')).toBe('checkbox');
+
+    controlContainer = renderIntoDocument(
+      <ControlContainer
+        type="radio"
+        checkedIcon="check"
+        uncheckedIcon="unchecked"
+        labelBefore={false}
+        defaultChecked={false}
+      />
+    );
+
+    input = findRenderedDOMComponentWithTag(controlContainer, 'input');
+    expect(input.getAttribute('type')).toBe('radio');
+  });
+
+  it('changes the icon when clicked', () => {
+    const props = {
+      type: 'checkbox',
+      checkedIcon: 'checked',
+      uncheckedIcon: 'unchecked',
+      labelBefore: false,
+      defaultChecked: false,
+    };
+
+    const control = renderIntoDocument(<ControlContainer {...props} />);
+
+    let content = findDOMNode(control).textContent;
+    expect(content).toBe(props.uncheckedIcon);
+
+    const input = findRenderedDOMComponentWithTag(control, 'input');
+    Simulate.change(input, { target: { checked: true }});
+
+    content = findDOMNode(control).textContent;
+    expect(content).toBe(props.checkedIcon);
+  });
+
+  it('calls the onChange prop with the next checked state and the event for checkboxes', () => {
+    const onChange = jest.genMockFunction();
+    const props = {
+      type: 'checkbox',
+      checkedIcon: 'checked',
+      uncheckedIcon: 'unchecked',
+      labelBefore: false,
+      defaultChecked: false,
+      value: 'test',
+      onChange,
+    };
+
+    const control = renderIntoDocument(<ControlContainer {...props} />);
+
+    const input = findRenderedDOMComponentWithTag(control, 'input');
+    Simulate.change(input, { target: { checked: true }});
+
+    expect(onChange.mock.calls.length).toBe(1);
+    expect(onChange.mock.calls[0][0]).toBe(true);
+    expect(onChange.mock.calls[0][1]).toBeDefined();
+  });
+
+  it('calls the onChange prop with the radio value and the event for radios', () => {
+    const onChange = jest.genMockFunction();
+    const props = {
+      type: 'radio',
+      checkedIcon: 'checked',
+      uncheckedIcon: 'unchecked',
+      labelBefore: false,
+      defaultChecked: false,
+      value: 'test',
+      onChange,
+    };
+
+    const control = renderIntoDocument(<ControlContainer {...props} />);
+
+    const input = findRenderedDOMComponentWithTag(control, 'input');
+    Simulate.change(input, { target: { checked: true }});
+
+    expect(onChange.mock.calls.length).toBe(1);
+    expect(onChange.mock.calls[0][0]).toBe(props.value);
+    expect(onChange.mock.calls[0][1]).toBeDefined();
+  });
+
+  it('can be a controlled component', () => {
+    let props = {
+      type: 'checkbox',
+      checkedIcon: 'checked',
+      uncheckedIcon: 'unchecked',
+      labelBefore: false,
+      defaultChecked: false,
+      checked: false,
+      value: 'test',
+    };
+
+    const onChange = jest.genMockFunction().mockImplementation((checked) => {
+      props = Object.assign({}, props, { checked });
+    });
+
+    let control = renderIntoDocument(<ControlContainer {...props} onChange={onChange} />);
+
+    let input = findRenderedDOMComponentWithTag(control, 'input');
+    Simulate.change(input, { target: { checked: true }});
+
+    control = renderIntoDocument(<ControlContainer {...props} onChange={onChange} />);
+    input = findRenderedDOMComponentWithTag(control, 'input');
+    expect(input.checked).toBe(true);
+    expect(onChange.mock.calls.length).toBe(1);
+
+    Simulate.change(input, { target: { checked: false }});
+
+    control = renderIntoDocument(<ControlContainer {...props} onChange={onChange} />);
+    input = findRenderedDOMComponentWithTag(control, 'input');
+    expect(input.checked).toBe(false);
+    expect(onChange.mock.calls.length).toBe(2);
+  });
+});
diff --git a/src/js/SelectionControls/__tests__/Radio.js b/src/js/SelectionControls/__tests__/Radio.js
new file mode 100644
index 0000000000..60b117f645
--- /dev/null
+++ b/src/js/SelectionControls/__tests__/Radio.js
@@ -0,0 +1,38 @@
+/*eslint-env jest*/
+jest.unmock('../Radio');
+jest.unmock('../ControlContainer');
+
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import {
+  Simulate,
+  renderIntoDocument,
+  findRenderedDOMComponentWithTag,
+} from 'react-addons-test-utils';
+
+import Radio from '../Radio';
+
+describe('Radio', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    const radio = renderIntoDocument(
+      <Radio style={style} className={className} value="A" />
+    );
+
+    const radioNode = findDOMNode(radio);
+    expect(radioNode.style.display).toBe(style.display);
+    expect(radioNode.classList.contains(className)).toBe(true);
+  });
+
+  it('calls the onChange function prop with the current value and the event', () => {
+    const onChange = jest.genMockFunction();
+    const radio = renderIntoDocument(<Radio onChange={onChange} value="A" />);
+
+    const input = findRenderedDOMComponentWithTag(radio, 'input');
+    Simulate.change(input, { target: { checked: true }});
+
+    expect(onChange.mock.calls.length).toBe(1);
+    expect(onChange.mock.calls[0][0]).toBe('A');
+  });
+});
diff --git a/src/js/SelectionControls/__tests__/RadioGroup.js b/src/js/SelectionControls/__tests__/RadioGroup.js
new file mode 100644
index 0000000000..7c6ebd2bdd
--- /dev/null
+++ b/src/js/SelectionControls/__tests__/RadioGroup.js
@@ -0,0 +1,44 @@
+/*eslint-env jest*/
+jest.unmock('../RadioGroup');
+jest.unmock('../Radio');
+
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import {
+  renderIntoDocument,
+  scryRenderedComponentsWithType,
+} from 'react-addons-test-utils';
+
+import RadioGroup from '../RadioGroup';
+import Radio from '../Radio';
+
+describe('RadioGroup', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    const radioGroup = renderIntoDocument(
+      <RadioGroup style={style} className={className}>
+        <Radio value="A" />
+        <Radio value="B" />
+      </RadioGroup>
+    );
+
+    const radioGroupNode = findDOMNode(radioGroup);
+    expect(radioGroupNode.style.display).toBe(style.display);
+    expect(radioGroupNode.classList.contains(className)).toBe(true);
+  });
+
+  it('injects a name into all the radio buttons', () => {
+    const radioGroup = renderIntoDocument(
+      <RadioGroup name="test">
+        <Radio value="A" />
+        <Radio value="B" />
+      </RadioGroup>
+    );
+
+    const radios = scryRenderedComponentsWithType(radioGroup, Radio);
+    expect(radios.length).toBe(2);
+    expect(radios[0].props.name).toBe('test');
+    expect(radios[1].props.name).toBe('test');
+  });
+});
diff --git a/src/js/SelectionControls/__tests__/Switch.js b/src/js/SelectionControls/__tests__/Switch.js
new file mode 100644
index 0000000000..078259d154
--- /dev/null
+++ b/src/js/SelectionControls/__tests__/Switch.js
@@ -0,0 +1,38 @@
+/*eslint-env jest*/
+jest.unmock('../Switch');
+
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import {
+  Simulate,
+  renderIntoDocument,
+  findRenderedDOMComponentWithTag,
+} from 'react-addons-test-utils';
+
+import Switch from '../Switch';
+
+describe('Switch', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    const switchR = renderIntoDocument(
+      <Switch style={style} className={className} />
+    );
+
+    const switchNode = findDOMNode(switchR);
+    expect(switchNode.style.display).toBe(style.display);
+    expect(switchNode.classList.contains(className)).toBe(true);
+  });
+
+  it('calls the onChange function with the next toggled state and the click event', () => {
+    const onChange = jest.genMockFunction();
+    const switchR = renderIntoDocument(<Switch onChange={onChange} />);
+
+    const switchInput = findRenderedDOMComponentWithTag(switchR, 'input');
+    Simulate.change(switchInput, { target: { checked: true }});
+
+    expect(onChange.mock.calls.length).toBe(1);
+    expect(onChange.mock.calls[0][0]).toBe(true);
+    expect(onChange.mock.calls[0][1]).toBeDefined();
+  });
+});