Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core/forms): Create a react version of the checklist component
- Loading branch information
Justin Reynolds
committed
Jan 19, 2018
1 parent
049d5f9
commit 953e52e
Showing
4 changed files
with
194 additions
and
0 deletions.
There are no files selected for viewing
113 changes: 113 additions & 0 deletions
113
app/scripts/modules/core/src/forms/checklist/Checklist.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
79
app/scripts/modules/core/src/forms/checklist/Checklist.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './checklist/Checklist'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters