Skip to content

Commit

Permalink
chore(forms): forward component refs to DOM elements (#412)
Browse files Browse the repository at this point in the history
* chore(button): forward refs to button dom element

* chore(input): forward refs to input dom element

* chore(select): forward refs to select dom element

* chore(package): added test:debug script

* chore(coverage): add codecov config
  • Loading branch information
mhuggins authored Aug 26, 2018
1 parent 3a33779 commit af0adb2
Show file tree
Hide file tree
Showing 25 changed files with 304 additions and 165 deletions.
32 changes: 32 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
codecov:
notify:
require_ci_to_pass: yes

coverage:
precision: 2
round: down
range: "70...100"

status:
project: yes
patch: yes
changes: no

parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no

comment:
layout: "header, diff"
behavior: default
require_changes: no

ignore:
- "__mocks__"
- "__tests__"
- "config"
- "static"
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ yarn install && yarn start
// Testing command
yarn test
// Testing with debug (repl) command
yarn test:debug
// Distribution command
yarn dist
```
Expand Down
22 changes: 22 additions & 0 deletions __tests__/renderer/shared/components/Forms/Input/Input.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { shallow } from 'enzyme';

import Input from 'shared/components/Forms/Input/Input';

const mountContainer = (props = {}) => {
return shallow(<Input {...props} />);
};

describe('<Input />', () => {
it('renders an input', () => {
const props = { id: 'name', defaultValue: 'foo' };
const wrapper = mountContainer(props);
expect(wrapper.type()).toEqual('input');
expect(wrapper.props()).toEqual(expect.objectContaining(props));
});

it('applies a custom className', () => {
const wrapper = mountContainer({ className: 'passwordField' });
expect(wrapper.prop('className').split(' ')).toContain('passwordField');
});
});
20 changes: 7 additions & 13 deletions __tests__/renderer/shared/components/Forms/Input/index.test.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import React from 'react';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';

import Input from 'shared/components/Forms/Input';
import InputContainer from 'shared/components/Forms/Input';
import Input from 'shared/components/Forms/Input/Input';

const mountContainer = (props = {}) => {
return shallow(<Input {...props} />);
return mount(<InputContainer {...props} />);
};

describe('<Input />', () => {
it('renders an input', () => {
const props = { id: 'name', defaultValue: 'foo' };
const wrapper = mountContainer(props);
expect(wrapper.type()).toEqual('input');
expect(wrapper.props()).toEqual(expect.objectContaining(props));
});

it('applies a custom className', () => {
const wrapper = mountContainer({ className: 'passwordField' });
expect(wrapper.prop('className').split(' ')).toContain('passwordField');
it('forwards the ref to the component', () => {
const wrapper = mountContainer({ ref: React.createRef() });
expect(wrapper).toForwardRefTo(Input);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { mount } from 'enzyme';

import LabeledInput from 'shared/components/Forms/LabeledInput/LabeledInput';
import Label from 'shared/components/Forms/Label';
import Input from 'shared/components/Forms/Input';

const mountContainer = (props = {}) => {
return mount(<LabeledInput {...props} />);
};

describe('<LabeledInput />', () => {
it('renders a label', () => {
const wrapper = mountContainer({ id: 'name', label: 'Name' });
const label = wrapper.find(Label);
expect(label.exists()).toBe(true);
expect(label.props()).toEqual(expect.objectContaining({ label: 'Name', htmlFor: 'name' }));
});

it('renders an input', () => {
const inputProps = { id: 'pw', type: 'password', defaultValue: 'foo' };
const wrapper = mountContainer({ label: 'Password', ...inputProps });
const input = wrapper.find(Input);
expect(input.exists()).toBe(true);
expect(input.props()).toEqual(expect.objectContaining(inputProps));
});
});
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
import React from 'react';
import { mount } from 'enzyme';

import LabeledInput from 'shared/components/Forms/LabeledInput';
import Label from 'shared/components/Forms/Label';
import Input from 'shared/components/Forms/Input';
import LabeledInputContainer from 'shared/components/Forms/LabeledInput';
import LabeledInput from 'shared/components/Forms/LabeledInput/LabeledInput';

const mountContainer = (props = {}) => {
return mount(<LabeledInput {...props} />);
return mount(<LabeledInputContainer {...props} />);
};

describe('<LabeledInput />', () => {
it('renders a label', () => {
const wrapper = mountContainer({ id: 'name', label: 'Name' });
const label = wrapper.find(Label);
expect(label.exists()).toBe(true);
expect(label.props()).toEqual(expect.objectContaining({ label: 'Name', htmlFor: 'name' }));
});

it('renders an input', () => {
const inputProps = { id: 'pw', type: 'password', defaultValue: 'foo' };
const wrapper = mountContainer({ label: 'Password', ...inputProps });
const input = wrapper.find(Input);
expect(input.exists()).toBe(true);
expect(input.props()).toEqual(expect.objectContaining(inputProps));
it('forwards the ref to the component', () => {
const wrapper = mountContainer({ ref: React.createRef() });
expect(wrapper).toForwardRefTo(LabeledInput);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { mount } from 'enzyme';

import LabeledSelect from 'shared/components/Forms/LabeledSelect';
import Label from 'shared/components/Forms/Label';
import Select from 'shared/components/Forms/Select';

const mountContainer = (props = {}) => {
return mount(<LabeledSelect {...props} />);
};

describe('<LabeledSelect />', () => {
it('renders a label', () => {
const wrapper = mountContainer({ id: 'currency', label: 'Currency' });
const label = wrapper.find(Label);
expect(label.exists()).toBe(true);
expect(label.props()).toEqual(expect.objectContaining({ label: 'Currency', htmlFor: 'currency' }));
});

it('renders a select', () => {
const children = (
<React.Fragment>
<option value="USD">United States Dollar</option>
<option value="EUR">Euro</option>
</React.Fragment>
);
const wrapper = mountContainer({ id: 'currency', label: 'Currency', children });
const select = wrapper.find(Select);
expect(select.exists()).toBe(true);
expect(select.props()).toEqual(expect.objectContaining({ id: 'currency', children }));
});
});
Original file line number Diff line number Diff line change
@@ -1,32 +1,16 @@
import React from 'react';
import { mount } from 'enzyme';

import LabeledSelect from 'shared/components/Forms/LabeledSelect';
import Label from 'shared/components/Forms/Label';
import Select from 'shared/components/Forms/Select';
import LabeledSelectContainer from 'shared/components/Forms/LabeledSelect';
import LabeledSelect from 'shared/components/Forms/LabeledSelect/LabeledSelect';

const mountContainer = (props = {}) => {
return mount(<LabeledSelect {...props} />);
return mount(<LabeledSelectContainer {...props} />);
};

describe('<LabeledSelect />', () => {
it('renders a label', () => {
const wrapper = mountContainer({ id: 'currency', label: 'Currency' });
const label = wrapper.find(Label);
expect(label.exists()).toBe(true);
expect(label.props()).toEqual(expect.objectContaining({ label: 'Currency', htmlFor: 'currency' }));
});

it('renders a select', () => {
const children = (
<React.Fragment>
<option value="USD">United States Dollar</option>
<option value="EUR">Euro</option>
</React.Fragment>
);
const wrapper = mountContainer({ id: 'currency', label: 'Currency', children });
const select = wrapper.find(Select);
expect(select.exists()).toBe(true);
expect(select.props()).toEqual(expect.objectContaining({ id: 'currency', children }));
it('forwards the ref to the component', () => {
const wrapper = mountContainer({ ref: React.createRef() });
expect(wrapper).toForwardRefTo(LabeledSelect);
});
});
28 changes: 28 additions & 0 deletions __tests__/renderer/shared/components/Forms/Select/Select.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { shallow } from 'enzyme';

import Select from 'shared/components/Forms/Select/Select';

const mountContainer = (props = {}) => {
return shallow(<Select {...props} />);
};

describe('<Select />', () => {
it('renders a select', () => {
const children = (
<React.Fragment>
<option value="foo">Foo</option>
<option value="bar">Bar</option>
</React.Fragment>
);
const props = { id: 'name', defaultValue: 'foo', children };
const wrapper = mountContainer(props);
expect(wrapper.type()).toEqual('select');
expect(wrapper.props()).toEqual(expect.objectContaining(props));
});

it('applies a custom className', () => {
const wrapper = mountContainer({ className: 'currency' });
expect(wrapper.prop('className').split(' ')).toContain('currency');
});
});
26 changes: 7 additions & 19 deletions __tests__/renderer/shared/components/Forms/Select/index.test.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
import React from 'react';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';

import Select from 'shared/components/Forms/Select';
import SelectContainer from 'shared/components/Forms/Select';
import Select from 'shared/components/Forms/Select/Select';

const mountContainer = (props = {}) => {
return shallow(<Select {...props} />);
return mount(<SelectContainer {...props} />);
};

describe('<Select />', () => {
it('renders a select', () => {
const children = (
<React.Fragment>
<option value="foo">Foo</option>
<option value="bar">Bar</option>
</React.Fragment>
);
const props = { id: 'name', defaultValue: 'foo', children };
const wrapper = mountContainer(props);
expect(wrapper.type()).toEqual('select');
expect(wrapper.props()).toEqual(expect.objectContaining(props));
});

it('applies a custom className', () => {
const wrapper = mountContainer({ className: 'currency' });
expect(wrapper.prop('className').split(' ')).toContain('currency');
it('forwards the ref to the component', () => {
const wrapper = mountContainer({ ref: React.createRef() });
expect(wrapper).toForwardRefTo(Select);
});
});
12 changes: 12 additions & 0 deletions __tests__/setupFramework.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getDisplayName } from 'recompose';

expect.extend({
toContainObject(received, argument) {
const pass = this.equals(
Expand All @@ -11,6 +13,16 @@ expect.extend({
? () => `expected ${this.utils.printReceived(received)} not to contain object ${this.utils.printExpected(argument)}`
: () => `expected ${this.utils.printReceived(received)} to contain object ${this.utils.printExpected(argument)}`;

return { pass, message };
},

toForwardRefTo(received, argument) {
const pass = received.find('ForwardRef').find(argument).exists();

const message = pass
? () => `expected to not forward ref to ${this.utils.printExpected(getDisplayName(argument))}`
: () => `expected to forward ref to ${this.utils.printExpected(getDisplayName(argument))}`;

return { pass, message };
}
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"pack": "yarn dist --dir -c.compression=store -c.mac.identity=null",
"pretest": "yarn run lint && yarn run stylelint",
"test": "jest",
"test:debug": "node inspect ./node_modules/.bin/jest --runInBand",
"stylelint": "stylelint src/**/*.scss",
"lint": "eslint --env browser,node,server --ext .jsx,.js --color .",
"lint:fix": "yarn run lint --fix",
Expand Down
41 changes: 16 additions & 25 deletions src/renderer/shared/components/Forms/Button/Button.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,30 @@
import React from 'react';
import classNames from 'classnames';
import { string } from 'prop-types';
import { string, func } from 'prop-types';
import { omit } from 'lodash';

import styles from './Button.scss';

export default class Button extends React.PureComponent {
static propTypes = {
className: string,
type: string,
forwardedRef: func
};

static defaultProps = {
className: null,
type: 'button',
forwardedRef: null
};

render() {
return ( // eslint-disable-next-line react/button-has-type
<button
{...this.props}
ref={this.registerRef}
{...omit(this.props, 'forwardedRef')}
ref={this.props.forwardedRef}
className={classNames(styles.button, this.props.className)}
/>
);
}

registerRef = (el) => {
this.button = el;
}

focus = () => {
this.button.focus();
}

blur = () => {
this.button.blur();
}
}

Button.propTypes = {
className: string,
type: string
};

Button.defaultProps = {
className: null,
type: 'button'
};
6 changes: 5 additions & 1 deletion src/renderer/shared/components/Forms/Button/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export { default } from './Button';
import withForwardedRef from 'shared/hocs/withForwardedRef';

import Button from './Button';

export default withForwardedRef()(Button);
Loading

0 comments on commit af0adb2

Please sign in to comment.