Skip to content

Commit

Permalink
feat(core/forms): Create a react version of the checklist component
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Reynolds committed Jan 19, 2018
1 parent 049d5f9 commit 953e52e
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 0 deletions.
113 changes: 113 additions & 0 deletions app/scripts/modules/core/src/forms/checklist/Checklist.spec.tsx
@@ -0,0 +1,113 @@
import * as React from 'react';
import { shallow } from 'enzyme';

import { noop } from 'core/utils';
import { Checklist } from './Checklist';

describe('<Checklist />', () => {
it('initializes properly with provided values', () => {
const checked = new Set(['a', 'b', 'c']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const component = shallow(<Checklist checked={checked} items={items} onChange={noop}/>);
expect(component.find('input[type="checkbox"]').length).toBe(4);
expect(component.find('input[type="checkbox"][checked=true]').length).toBe(3);
});

it('updates items when an item is added externally', () => {
const checked = new Set(['a', 'b', 'c']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const component = shallow(<Checklist checked={checked} items={items} onChange={noop}/>);

expect(component.find('input[type="checkbox"]').length).toBe(4);
expect(component.find('input[type="checkbox"][checked=true]').length).toBe(3);
items.add('e');
component.setProps({ items });
expect(component.find('input[type="checkbox"]').length).toBe(5);
expect(component.find('input[type="checkbox"][checked=true]').length).toBe(3);
});

it('updates items when an item is removed externally', () => {
const checked = new Set(['a', 'b', 'c']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const component = shallow(<Checklist checked={checked} items={items} onChange={noop}/>);

expect(component.find('input[type="checkbox"]').length).toBe(4);
expect(component.find('input[type="checkbox"][checked=true]').length).toBe(3);
items.delete('c');
component.setProps({ items });
expect(component.find('input[type="checkbox"]').length).toBe(3);
expect(component.find('input[type="checkbox"][checked=true]').length).toBe(2);
});

it('updates checked items when an item is checked externally', () => {
const checked = new Set(['a', 'b', 'c']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const component = shallow(<Checklist checked={checked} items={items} onChange={noop}/>);

expect(component.find('input[type="checkbox"]').length).toBe(4);
expect(component.find('input[type="checkbox"][checked=true]').length).toBe(3);
checked.add('d');
component.setProps({ checked });
expect(component.find('input[type="checkbox"]').length).toBe(4);
expect(component.find('input[type="checkbox"][checked=true]').length).toBe(4);
});

it('updates checked items when an item is unchecked externally', () => {
const checked = new Set(['a', 'b', 'c']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const component = shallow(<Checklist checked={checked} items={items} onChange={noop}/>);

expect(component.find('input[type="checkbox"]').length).toBe(4);
expect(component.find('input[type="checkbox"][checked=true]').length).toBe(3);
checked.delete('c');
component.setProps({ checked });
expect(component.find('input[type="checkbox"]').length).toBe(4);
expect(component.find('input[type="checkbox"][checked=true]').length).toBe(2);
});

it('shows the select all button when necessary', () => {
const checked = new Set(['a', 'b', 'c']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const component = shallow(<Checklist checked={checked} items={items} onChange={noop} includeSelectAllButton={true} />);
expect(component.find('a').length).toBe(1);
});

it('does not show the select all button when necessary', () => {
const checked = new Set(['a', 'b', 'c']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const component = shallow(<Checklist checked={checked} items={items} onChange={noop} includeSelectAllButton={false} />);
expect(component.find('a').length).toBe(0);
});

it('shows correct text for the select all button when not all the items are checked', () => {
const checked = new Set(['a', 'b', 'c']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const component = shallow(<Checklist checked={checked} items={items} onChange={noop} includeSelectAllButton={true} />);

expect(component.find('a').text()).toBe('Select All');
});

it('shows correct text for the select all button when all the items are checked', () => {
const checked = new Set(['a', 'b', 'c', 'd']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const component = shallow(<Checklist checked={checked} items={items} onChange={noop} includeSelectAllButton={true} />);

expect(component.find('a').text()).toBe('Deselect All');
});

it('passes an empty list to the onChange handler when deselect all clicked', () => {
const checked = new Set(['a', 'b', 'c', 'd']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const onChange = (i: Set<string>): void => { expect(i.size).toBe(0); };
const component = shallow(<Checklist checked={checked} items={items} onChange={onChange} includeSelectAllButton={true} />);
component.find('a').simulate('click');
});

it('passes a complete list to the onChange handler when select all clicked', () => {
const checked = new Set(['a']);
const items = new Set([ 'a', 'b', 'c', 'd' ]);
const onChange = (i: Set<string>): void => { expect(i.size).toBe(4); };
const component = shallow(<Checklist checked={checked} items={items} onChange={onChange} includeSelectAllButton={true} />);
component.find('a').simulate('click');
});
});
79 changes: 79 additions & 0 deletions app/scripts/modules/core/src/forms/checklist/Checklist.tsx
@@ -0,0 +1,79 @@
import * as React from 'react';
import { xor } from 'lodash';
import { BindAll } from 'lodash-decorators';

export interface IChecklistProps {
includeSelectAllButton?: boolean;
inline?: boolean;
items: Set<string>;
checked: Set<string>;
onChange: (checked: Set<string>) => void;
}

export interface IChecklistState {
}

@BindAll()
export class Checklist extends React.Component<IChecklistProps, IChecklistState> {
constructor(props: IChecklistProps) {
super(props);
}

private handleChecked(event: React.ChangeEvent<HTMLInputElement>): void {
const name = event.target.name;
const isChecked = event.target.checked;
const checked = new Set(this.props.checked);

isChecked ? checked.add(name) : checked.delete(name);
this.props.onChange(checked);
}

public render(): React.ReactElement<Checklist> {
const { checked, includeSelectAllButton, inline, items, onChange } = this.props;

const showSelectAll = includeSelectAllButton && items.size > 1;
const allSelected = xor([...items], [...checked]).length === 0;
const selectAllLabel = allSelected ? 'Deselect All' : 'Select All';
const handleSelectAll = () => allSelected ? onChange(new Set()) : onChange(new Set(items));

if (!inline) {
return (
<ul className="checklist">
{[...items].map((item) => (
<li key={item}>
<label>
<input name={item} checked={checked.has(item)} type="checkbox" onChange={this.handleChecked}/>
{' ' + item}
</label>
</li>
))}
{showSelectAll && (
<li>
<a className="btn btn-default btn-xs push-left clickable" onClick={handleSelectAll}>{selectAllLabel}</a>
</li>
)}
</ul>
);
}

return (
<span>
{[...items].map((item) => (
<label key={item} className="checkbox-inline">
<input name={item} checked={checked.has(item)} type="checkbox" onChange={this.handleChecked}/>
{item}
</label>
))}
{showSelectAll && (
<a
className="btn btn-default btn-xs clickable"
style={{ margin: '8px 0 0 10px' }}
onClick={handleSelectAll}
>
{selectAllLabel}
</a>
)}
</span>
);
}
}
1 change: 1 addition & 0 deletions app/scripts/modules/core/src/forms/index.ts
@@ -0,0 +1 @@
export * from './checklist/Checklist';
1 change: 1 addition & 0 deletions app/scripts/modules/core/src/index.ts
Expand Up @@ -25,6 +25,7 @@ export * from './artifact';
export * from './event/EventBus';

export * from './filterModel';
export * from './forms';

export * from './healthCounts';
export * from './help';
Expand Down

0 comments on commit 953e52e

Please sign in to comment.