Repository: mlaursen/react-md
Pull request #78 (2016-05-10 17:44:23)

Added initial Data Table Tests


Patch:
diff --git a/src/js/DataTables/DataTable.js b/src/js/DataTables/DataTable.js
index 786644d954..b4433cf83f 100644
--- a/src/js/DataTables/DataTable.js
+++ b/src/js/DataTables/DataTable.js
@@ -163,9 +163,9 @@ export default class DataTable extends Component {
   };
 
   render() {
-    const { className, children, plain, style, responsive } = this.props;
+    const { className, children, plain, responsive, ...props } = this.props;
     const table = (
-      <table className={classnames('md-data-table', className, { 'full-width': plain })} style={style}>
+      <table className={classnames('md-data-table', className, { 'full-width': plain })} {...props}>
         {children}
       </table>
     );
diff --git a/src/js/DataTables/EditDialogColumn.js b/src/js/DataTables/EditDialogColumn.js
index 87f86541c3..97e6e4e5df 100644
--- a/src/js/DataTables/EditDialogColumn.js
+++ b/src/js/DataTables/EditDialogColumn.js
@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
 import PureRenderMixin from 'react-addons-pure-render-mixin';
 import classnames from 'classnames';
 
-import { FlatButton } from '../Buttons';
+import FlatButton from '../Buttons/FlatButton';
 import TableColumn from './TableColumn';
 import TextField from '../TextFields';
 import { ENTER, TAB, ESC } from '../constants/keyCodes';
@@ -27,11 +27,21 @@ export default class EditDialogColumn extends Component {
   }
 
   static propTypes = {
+    /**
+     * The optional style to apply to the edit dialog.
+     */
+    style: PropTypes.object,
+
     /**
      * The optional className to apply to the edit dialog.
      */
     className: PropTypes.string,
 
+    /**
+     * The optional style to apply to the column.
+     */
+    columnStyle: PropTypes.object,
+
     /**
      * The optional className to apply to the column.
      */
@@ -146,8 +156,6 @@ export default class EditDialogColumn extends Component {
     okOnOutsideClick: true,
     okLabel: 'Save',
     cancelLabel: 'Cancel',
-    onOkClick: () => {},
-    onCancelClick: () => {},
   };
 
   componentWillUpdate(nextProps, nextState) {
@@ -205,7 +213,7 @@ export default class EditDialogColumn extends Component {
 
     const key = e.which || e.keyCode;
     if(key === ENTER) {
-      this.save();
+      this.save(e);
     } else if(key === TAB) {
       this.overrideTab(e);
     } else if(key === ESC) {
@@ -220,7 +228,7 @@ export default class EditDialogColumn extends Component {
   };
 
   handleCancelClick = (e) => {
-    this.props.onCancelClick(this.state.cancelValue, e);
+    this.props.onCancelClick && this.props.onCancelClick(this.state.cancelValue, e);
 
     this.setState({ active: false, value: this.state.cancelValue });
   };
@@ -265,7 +273,9 @@ export default class EditDialogColumn extends Component {
   render() {
     const { active, animating } = this.state;
     const {
+      columnStyle,
       columnClassName,
+      style,
       className,
       maxLength,
       title,
@@ -295,13 +305,14 @@ export default class EditDialogColumn extends Component {
     }
 
     return (
-      <TableColumn className={classnames(columnClassName, 'prevent-grow md-edit-dialog-column')} ref="column">
+      <TableColumn className={classnames('prevent-grow md-edit-dialog-column', columnClassName)} ref="column" style={columnStyle}>
         <div
           className={classnames('md-edit-dialog', className, {
             animating,
             active,
             large,
           })}
+          style={style}
         >
           {largeTitle}
           <TextField


Test patch:
diff --git a/src/js/DataTables/__tests__/DataTable.js b/src/js/DataTables/__tests__/DataTable.js
new file mode 100644
index 0000000000..1bb407ebe0
--- /dev/null
+++ b/src/js/DataTables/__tests__/DataTable.js
@@ -0,0 +1,89 @@
+/*eslint-env jest*/
+jest.unmock('../DataTable');
+
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import {
+  Simulate,
+  renderIntoDocument,
+  findRenderedDOMComponentWithTag,
+} from 'react-addons-test-utils';
+
+import DataTable from '../DataTable';
+
+describe('DataTable', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    // added c to make required children prop happy
+    const table = renderIntoDocument(
+      <DataTable style={style} className={className}>c</DataTable>
+    );
+
+    const tableNode = findRenderedDOMComponentWithTag(table, 'table');
+    expect(tableNode.style.display).toBe(style.display);
+    expect(tableNode.classList.contains(className)).toBe(true);
+  });
+
+  it('adds any event listeners', () => {
+    const onClick = jest.genMockFunction();
+    const onMouseDown = jest.genMockFunction();
+    const onMouseUp = jest.genMockFunction();
+    const onMouseOver = jest.genMockFunction();
+    const onMouseLeave = jest.genMockFunction();
+    const onTouchStart = jest.genMockFunction();
+    const onTouchEnd = jest.genMockFunction();
+    const onTouchCancel = jest.genMockFunction();
+
+    const table = renderIntoDocument(
+      <DataTable
+        onClick={onClick}
+        onMouseDown={onMouseDown}
+        onMouseUp={onMouseUp}
+        onMouseOver={onMouseOver}
+        onMouseLeave={onMouseLeave}
+        onTouchStart={onTouchStart}
+        onTouchEnd={onTouchEnd}
+        onTouchCancel={onTouchCancel}
+        children="c"
+      />
+    );
+
+    const tableNode = findRenderedDOMComponentWithTag(table, 'table');
+    Simulate.click(tableNode);
+    expect(onClick).toBeCalled();
+
+    Simulate.mouseOver(tableNode);
+    expect(onMouseOver).toBeCalled();
+
+    Simulate.mouseLeave(tableNode);
+    expect(onMouseLeave).toBeCalled();
+
+    Simulate.mouseDown(tableNode);
+    expect(onMouseDown).toBeCalled();
+
+    Simulate.mouseUp(tableNode);
+    expect(onMouseUp).toBeCalled();
+
+    Simulate.touchStart(tableNode);
+    expect(onTouchStart).toBeCalled();
+
+    Simulate.touchEnd(tableNode);
+    expect(onTouchEnd).toBeCalled();
+
+    Simulate.touchCancel(tableNode);
+    expect(onTouchCancel).toBeCalled();
+  });
+
+  it('wraps the table in a responsive container when the responsive prop is true', () => {
+    let table = renderIntoDocument(<DataTable>c</DataTable>);
+    let tableNode = findDOMNode(table);
+    expect(tableNode.className).toBe('md-data-table-container');
+    expect(tableNode.nodeName).toBe('DIV');
+
+    table = renderIntoDocument(<DataTable responsive={false}>c</DataTable>);
+    tableNode = findDOMNode(table);
+    expect(tableNode.className).toBe('md-data-table');
+    expect(tableNode.nodeName).toBe('TABLE');
+  });
+});
diff --git a/src/js/DataTables/__tests__/EditDialogColumn.js b/src/js/DataTables/__tests__/EditDialogColumn.js
new file mode 100644
index 0000000000..c1c7779c6d
--- /dev/null
+++ b/src/js/DataTables/__tests__/EditDialogColumn.js
@@ -0,0 +1,252 @@
+/*eslint-env jest*/
+/*eslint-disable react/prop-types*/
+jest.unmock('../EditDialogColumn');
+jest.unmock('../TableColumn');
+jest.unmock('../DataTable');
+jest.unmock('../TableBody');
+jest.unmock('../TableRow');
+jest.unmock('../../Tooltips');
+jest.unmock('../../Tooltips/injectTooltip');
+jest.unmock('../../TextFields');
+jest.unmock('../../TextFields/TextField');
+jest.unmock('../../TextFields/TextDivider');
+jest.unmock('../../TextFields/TextFieldMessage');
+jest.unmock('../../TextFields/FloatingLabel');
+jest.unmock('../../constants/keyCodes');
+
+import React from 'react';
+import {
+  Simulate,
+  renderIntoDocument,
+  findRenderedComponentWithType,
+  scryRenderedComponentsWithType,
+  findRenderedDOMComponentWithTag,
+  findRenderedDOMComponentWithClass,
+  scryRenderedDOMComponentsWithClass,
+} from 'react-addons-test-utils';
+
+import DataTable from '../DataTable';
+import TableBody from '../TableBody';
+import TableRow from '../TableRow';
+import EditDialogColumn from '../EditDialogColumn';
+import FlatButton from '../../Buttons/FlatButton';
+import TextField from '../../TextFields/TextField';
+import { ENTER, ESC } from '../../constants/keyCodes';
+
+class Table extends React.Component {
+  render() {
+    return (
+      <DataTable>
+        <TableBody>
+          <TableRow>
+            <th>A</th>
+            {this.props.children}
+          </TableRow>
+        </TableBody>
+      </DataTable>
+    );
+  }
+}
+
+describe('EditDialogColumn', () => {
+  it('merges className and style', () => {
+    const columnStyle = { fontWeight: 'bold' };
+    const columnClassName = 'col-test';
+    const style = { display: 'block' };
+    const className = 'test';
+    const edit = renderIntoDocument(
+      <Table>
+        <EditDialogColumn
+          style={style}
+          className={className}
+          columnClassName={columnClassName}
+          columnStyle={columnStyle}
+        />
+      </Table>
+    );
+
+    const editNodeTD = findRenderedDOMComponentWithTag(edit, 'td');
+    const editNode = findRenderedDOMComponentWithClass(edit, 'md-edit-dialog');
+
+    expect(editNodeTD.style.fontWeight).toBe(columnStyle.fontWeight);
+    expect(editNodeTD.classList.contains(columnClassName)).toBe(true);
+
+    expect(editNode.style.display).toBe(style.display);
+    expect(editNode.classList.contains(className)).toBe(true);
+  });
+
+  it('prevents the column from automatically growing by adding the prevent-grow className', () => {
+    const edit = renderIntoDocument(<Table><EditDialogColumn /></Table>);
+    const editNodeTD = findRenderedDOMComponentWithTag(edit, 'td');
+
+    expect(editNodeTD.classList.contains('prevent-grow')).toBe(true);
+  });
+
+  it('adds the animating className when the focus state changes on the text field', () => {
+    const edit = renderIntoDocument(
+      <Table>
+        <EditDialogColumn />
+      </Table>
+    );
+
+    let editNode = findRenderedDOMComponentWithClass(edit, 'md-edit-dialog');
+    let textNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+
+    expect(editNode.classList.contains('animating')).toBe(false);
+    expect(editNode.classList.contains('active')).toBe(false);
+
+    Simulate.focus(textNode);
+    editNode = findRenderedDOMComponentWithClass(edit, 'md-edit-dialog');
+    expect(editNode.classList.contains('animating')).toBe(true);
+    expect(editNode.classList.contains('active')).toBe(true);
+
+    jest.runOnlyPendingTimers();
+    editNode = findRenderedDOMComponentWithClass(edit, 'md-edit-dialog');
+    expect(editNode.classList.contains('animating')).toBe(false);
+    expect(editNode.classList.contains('active')).toBe(true);
+
+    Simulate.keyDown(textNode, { which: ENTER, keyCode: ENTER });
+    editNode = findRenderedDOMComponentWithClass(edit, 'md-edit-dialog');
+    expect(editNode.classList.contains('animating')).toBe(true);
+    expect(editNode.classList.contains('active')).toBe(false);
+
+    jest.runOnlyPendingTimers();
+    expect(editNode.classList.contains('animating')).toBe(false);
+    expect(editNode.classList.contains('active')).toBe(false);
+  });
+
+  it('does not render the maxLength prop on the text field when it is not active', () => {
+    const edit = renderIntoDocument(
+      <Table>
+        <EditDialogColumn maxLength={140} />
+      </Table>
+    );
+
+    let textField = findRenderedComponentWithType(edit, TextField);
+    const textFieldNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+
+    expect(textField.props.maxLength).toBe(null);
+    Simulate.focus(textFieldNode);
+
+    // Skip animating
+    jest.runOnlyPendingTimers();
+    textField = findRenderedComponentWithType(edit, TextField);
+    expect(textField.props.maxLength).toBe(140);
+  });
+
+  it('saves the value when the enter key is pressed', () => {
+    const edit = renderIntoDocument(
+      <Table>
+        <EditDialogColumn />
+      </Table>
+    );
+
+    const editComp = findRenderedComponentWithType(edit, EditDialogColumn);
+    editComp.save = jest.genMockFunction();
+
+    let textNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+    Simulate.change(textNode, { target: { value: 'abc' } });
+
+    textNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+    expect(textNode.value).toBe('abc');
+
+    Simulate.keyDown(textNode, { which: ENTER, keyCode: ENTER });
+    expect(editComp.save.mock.calls.length).toBe(1);
+  });
+
+  it('calls the onOkClick function on save with the current value and the click or enter keypress event', () => {
+    const onOkClick = jest.genMockFunction();
+    const edit = renderIntoDocument(
+      <Table>
+        <EditDialogColumn onOkClick={onOkClick} />
+      </Table>
+    );
+
+    let textNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+    Simulate.change(textNode, { target: { value: 'abc' } });
+    textNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+    expect(textNode.value).toBe('abc');
+
+    Simulate.keyDown(textNode, { which: ENTER, keyCode: ENTER });
+    expect(onOkClick.mock.calls.length).toBe(1);
+    expect(onOkClick.mock.calls[0].length).toBe(2);
+    expect(onOkClick.mock.calls[0][0]).toBe('abc');
+  });
+
+  it('resets the value when the escape key is pressed', () => {
+    const edit = renderIntoDocument(
+      <Table>
+        <EditDialogColumn defaultValue="abc" />
+      </Table>
+    );
+
+    let textNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+    Simulate.focus(textNode); // Focus sets the cancel value
+    Simulate.change(textNode, { target: { value: 'Where\'s Waldo' } });
+
+    textNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+    expect(textNode.value).toBe('Where\'s Waldo');
+
+    Simulate.keyDown(textNode, { which: ESC, keyCode: ESC });
+    textNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+    expect(textNode.value).toBe('abc');
+  });
+
+  it('calls the onCancelClick function on canceling the changes with the initial value and the event', () => {
+    const onCancelClick = jest.genMockFunction();
+    const edit = renderIntoDocument(
+      <Table>
+        <EditDialogColumn onCancelClick={onCancelClick} defaultValue="abc" />
+      </Table>
+    );
+
+    let textNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+    Simulate.focus(textNode);
+    Simulate.change(textNode, { target: { value: 'Hello, World' } });
+    textNode = findRenderedDOMComponentWithClass(edit, 'md-text-field');
+    expect(textNode.value).toBe('Hello, World');
+
+    Simulate.keyDown(textNode, { which: ESC, keyCode: ESC });
+    expect(onCancelClick.mock.calls.length).toBe(1);
+    expect(onCancelClick.mock.calls[0].length).toBe(2);
+    expect(onCancelClick.mock.calls[0][0]).toBe('abc');
+  });
+
+  it('displays a modal title when the column is large and active', () => {
+    const edit = renderIntoDocument(
+      <Table>
+        <EditDialogColumn large={true} title="Test" />
+      </Table>
+    );
+
+    let dialogNode = findRenderedDOMComponentWithClass(edit, 'md-edit-dialog');
+    expect(dialogNode.textContent).toBe('');
+
+    Simulate.focus(findRenderedDOMComponentWithClass(edit, 'md-text-field'));
+    jest.runOnlyPendingTimers();
+
+    dialogNode = findRenderedDOMComponentWithClass(edit, 'md-edit-dialog');
+    expect(dialogNode.textContent).toBe('Test');
+  });
+
+  it('displays a dialog footer with a cancel and ok button in a large dialog', () => {
+    const edit = renderIntoDocument(
+      <Table>
+        <EditDialogColumn large={true} title="Test" />
+      </Table>
+    );
+
+    let footerNodes = scryRenderedDOMComponentsWithClass(edit, 'md-dialog-footer');
+    let buttons = scryRenderedComponentsWithType(edit, FlatButton);
+    expect(footerNodes.length).toBe(0);
+    expect(buttons.length).toBe(0);
+
+    Simulate.focus(findRenderedDOMComponentWithClass(edit, 'md-text-field'));
+    jest.runOnlyPendingTimers();
+
+    footerNodes = scryRenderedDOMComponentsWithClass(edit, 'md-dialog-footer');
+    buttons = scryRenderedComponentsWithType(edit, FlatButton);
+    expect(footerNodes.length).toBe(1);
+    expect(buttons.length).toBe(2);
+  });
+});
diff --git a/src/js/DataTables/__tests__/TableColumn.js b/src/js/DataTables/__tests__/TableColumn.js
new file mode 100644
index 0000000000..fddfe38231
--- /dev/null
+++ b/src/js/DataTables/__tests__/TableColumn.js
@@ -0,0 +1,161 @@
+/*eslint-env jest*/
+/*eslint-disable react/prop-types*/
+jest.unmock('../DataTable');
+jest.unmock('../TableBody');
+jest.unmock('../TableRow');
+jest.unmock('../TableColumn');
+jest.unmock('../../Tooltips');
+jest.unmock('../../Tooltips/injectTooltip');
+jest.unmock('../../FontIcons');
+jest.unmock('../../FontIcons/FontIcon');
+
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import {
+  Simulate,
+  renderIntoDocument,
+  scryRenderedComponentsWithType,
+  scryRenderedDOMComponentsWithTag,
+} from 'react-addons-test-utils';
+
+import DataTable from '../DataTable';
+import TableBody from '../TableBody';
+import TableRow from '../TableRow';
+import TableColumn from '../TableColumn';
+import FontIcon from '../../FontIcons/FontIcon';
+
+// need valid table nesting
+class Table extends React.Component {
+  render() {
+    return (
+      <DataTable>
+        <TableBody>
+          <TableRow>
+            {this.props.children}
+            <td>dummy</td>
+          </TableRow>
+        </TableBody>
+      </DataTable>
+    );
+  }
+}
+
+describe('TableColumn', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    const table = renderIntoDocument(
+      <Table>
+        <TableColumn style={style} className={className}>A</TableColumn>
+      </Table>
+    );
+
+    const tableColumnNode = scryRenderedDOMComponentsWithTag(table, 'td')[0];
+    expect(tableColumnNode.style.display).toBe(style.display);
+    expect(tableColumnNode.classList.contains(className)).toBe(true);
+  });
+
+  it('applies the correct event listeners', () => {
+    const onClick = jest.genMockFunction();
+    const onMouseDown = jest.genMockFunction();
+    const onMouseUp = jest.genMockFunction();
+    const onMouseOver = jest.genMockFunction();
+    const onMouseLeave = jest.genMockFunction();
+    const onTouchStart = jest.genMockFunction();
+    const onTouchEnd = jest.genMockFunction();
+    const onTouchCancel = jest.genMockFunction();
+
+    const table = renderIntoDocument(
+      <Table>
+        <TableColumn
+          onClick={onClick}
+          onMouseDown={onMouseDown}
+          onMouseUp={onMouseUp}
+          onMouseOver={onMouseOver}
+          onMouseLeave={onMouseLeave}
+          onTouchStart={onTouchStart}
+          onTouchEnd={onTouchEnd}
+          onTouchCancel={onTouchCancel}
+          children="c"
+        />
+      </Table>
+    );
+
+    const tableColumnNode = scryRenderedDOMComponentsWithTag(table, 'td')[0];
+    Simulate.click(tableColumnNode);
+    expect(onClick).toBeCalled();
+
+    Simulate.mouseOver(tableColumnNode);
+    expect(onMouseOver).toBeCalled();
+
+    Simulate.mouseLeave(tableColumnNode);
+    expect(onMouseLeave).toBeCalled();
+
+    Simulate.mouseDown(tableColumnNode);
+    expect(onMouseDown).toBeCalled();
+
+    Simulate.mouseUp(tableColumnNode);
+    expect(onMouseUp).toBeCalled();
+
+    Simulate.touchStart(tableColumnNode);
+    expect(onTouchStart).toBeCalled();
+
+    Simulate.touchEnd(tableColumnNode);
+    expect(onTouchEnd).toBeCalled();
+
+    Simulate.touchCancel(tableColumnNode);
+    expect(onTouchCancel).toBeCalled();
+  });
+
+  it('renders as a th tag if the header props is true', () => {
+    const table = renderIntoDocument(
+      <Table>
+        <TableColumn header={true}>A</TableColumn>
+      </Table>
+    );
+
+    const ths = scryRenderedDOMComponentsWithTag(table, 'th');
+    expect(ths.length).toBe(1);
+  });
+
+  it('injects a sorting icon if it sorted prop is defined as a boolean', () => {
+    const table = renderIntoDocument(
+      <Table>
+        <TableColumn sorted={true}>c</TableColumn>
+      </Table>
+    );
+
+    const icons = scryRenderedComponentsWithType(table, FontIcon);
+    expect(icons.length).toBe(1);
+
+    const icon = findDOMNode(icons[0]);
+    expect(icon.classList.contains('material-icons')).toBe(true);
+    expect(icon.textContent).toBe('arrow_upward');
+  });
+
+  it('adds a the flipped className to the sort icon when the sorted prop is false', () => {
+    let table = renderIntoDocument(
+      <Table>
+        <TableColumn sorted={false}>c</TableColumn>
+      </Table>
+    );
+
+    let icons = scryRenderedComponentsWithType(table, FontIcon);
+    expect(icons.length).toBe(1);
+
+    let icon = findDOMNode(icons[0]);
+    expect(icon.classList.contains('flipped')).toBe(true);
+
+    table = renderIntoDocument(
+      <Table>
+        <TableColumn sorted={true}>c</TableColumn>
+      </Table>
+    );
+
+    icons = scryRenderedComponentsWithType(table, FontIcon);
+    expect(icons.length).toBe(1);
+
+    icon = findDOMNode(icons[0]);
+    expect(icon.classList.contains('flipped')).toBe(false);
+  });
+});
diff --git a/src/js/DataTables/__tests__/TableRow.js b/src/js/DataTables/__tests__/TableRow.js
new file mode 100644
index 0000000000..9adac18a07
--- /dev/null
+++ b/src/js/DataTables/__tests__/TableRow.js
@@ -0,0 +1,131 @@
+/*eslint-env jest*/
+jest.unmock('../DataTable');
+jest.unmock('../TableBody');
+jest.unmock('../TableRow');
+jest.unmock('../TableColumn');
+jest.unmock('../../Tooltips');
+jest.unmock('../../Tooltips/injectTooltip');
+jest.unmock('../../FontIcons');
+jest.unmock('../../FontIcons/FontIcon');
+
+import React from 'react';
+import {
+  Simulate,
+  renderIntoDocument,
+  scryRenderedComponentsWithType,
+  findRenderedDOMComponentWithTag,
+  scryRenderedDOMComponentsWithTag,
+} from 'react-addons-test-utils';
+
+import DataTable from '../DataTable';
+import TableBody from '../TableBody';
+import TableRow from '../TableRow';
+import TableColumn from '../TableColumn';
+import TableCheckbox from '../TableCheckbox';
+
+describe('TableRow', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    const table = renderIntoDocument(
+      <DataTable>
+        <TableBody>
+          <TableRow style={style} className={className}>
+            <TableColumn>c</TableColumn>
+            <TableColumn>c</TableColumn>
+          </TableRow>
+        </TableBody>
+      </DataTable>
+    );
+
+    const tableRowNode = findRenderedDOMComponentWithTag(table, 'tr');
+    expect(tableRowNode.style.display).toBe(style.display);
+    expect(tableRowNode.classList.contains(className)).toBe(true);
+  });
+
+  it('applies the correct event listeners', () => {
+    const onClick = jest.genMockFunction();
+    const onMouseDown = jest.genMockFunction();
+    const onMouseUp = jest.genMockFunction();
+    const onMouseOver = jest.genMockFunction();
+    const onMouseLeave = jest.genMockFunction();
+    const onTouchStart = jest.genMockFunction();
+    const onTouchEnd = jest.genMockFunction();
+    const onTouchCancel = jest.genMockFunction();
+
+    const table = renderIntoDocument(
+      <DataTable>
+        <TableBody>
+          <TableRow
+            onClick={onClick}
+            onMouseDown={onMouseDown}
+            onMouseUp={onMouseUp}
+            onMouseOver={onMouseOver}
+            onMouseLeave={onMouseLeave}
+            onTouchStart={onTouchStart}
+            onTouchEnd={onTouchEnd}
+            onTouchCancel={onTouchCancel}
+          >
+            <TableColumn>c</TableColumn>
+            <TableColumn>c</TableColumn>
+          </TableRow>
+        </TableBody>
+      </DataTable>
+    );
+
+    const trNode = scryRenderedDOMComponentsWithTag(table, 'tr')[0];
+    Simulate.click(trNode);
+    expect(onClick).toBeCalled();
+
+    Simulate.mouseOver(trNode);
+    expect(onMouseOver).toBeCalled();
+
+    Simulate.mouseLeave(trNode);
+    expect(onMouseLeave).toBeCalled();
+
+    Simulate.mouseDown(trNode);
+    expect(onMouseDown).toBeCalled();
+
+    Simulate.mouseUp(trNode);
+    expect(onMouseUp).toBeCalled();
+
+    Simulate.touchStart(trNode);
+    expect(onTouchStart).toBeCalled();
+
+    Simulate.touchEnd(trNode);
+    expect(onTouchEnd).toBeCalled();
+
+    Simulate.touchCancel(trNode);
+    expect(onTouchCancel).toBeCalled();
+  });
+
+  it('injects a checkbox if it is not a plain table', () => {
+    let table = renderIntoDocument(
+      <DataTable plain={true}>
+        <TableBody>
+          <TableRow>
+            <TableColumn>A</TableColumn>
+            <TableColumn>B</TableColumn>
+          </TableRow>
+        </TableBody>
+      </DataTable>
+    );
+
+    let checkboxes = scryRenderedComponentsWithType(table, TableCheckbox);
+    expect(checkboxes.length).toBe(0);
+
+    table = renderIntoDocument(
+      <DataTable>
+        <TableBody>
+          <TableRow>
+            <TableColumn>A</TableColumn>
+            <TableColumn>B</TableColumn>
+          </TableRow>
+        </TableBody>
+      </DataTable>
+    );
+
+    checkboxes = scryRenderedComponentsWithType(table, TableCheckbox);
+    expect(checkboxes.length).toBe(1);
+  });
+});