From 79f0f1940fd04bd103c4478462084d76ce59fdd6 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Tue, 7 Jan 2020 15:20:25 -0800 Subject: [PATCH 1/7] update LabelSelect to use PF Select component --- .../screens/Template/shared/LabelSelect.jsx | 84 +++++++++++++++---- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx b/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx index 68cce85193..33830fe8bb 100644 --- a/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx +++ b/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx @@ -1,8 +1,17 @@ import React, { useState, useEffect } from 'react'; import { func, arrayOf, number, shape, string, oneOfType } from 'prop-types'; -import MultiSelect from '@components/MultiSelect'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; import { LabelsAPI } from '@api'; +function setToString(labels) { + labels.forEach(label => { + label.toString = function toString() { + return this.id; + }; + }); + return labels; +} + async function loadLabelOptions(setLabels, onError) { let labels; try { @@ -11,7 +20,7 @@ async function loadLabelOptions(setLabels, onError) { page_size: 200, order_by: 'name', }); - labels = data.results; + labels = setToString(data.results); setLabels(labels); if (data.next && data.next.includes('page=2')) { const { @@ -21,31 +30,71 @@ async function loadLabelOptions(setLabels, onError) { page_size: 200, order_by: 'name', }); - labels = labels.concat(results); + setLabels(labels.concat(setToString(results))); } - setLabels(labels); } catch (err) { onError(err); } } -function LabelSelect({ value, onChange, onError }) { +function LabelSelect({ value, placeholder, onChange, onError }) { const [options, setOptions] = useState([]); + const [currentValue, setCurrentValue] = useState([]); + const [isExpanded, setIsExpanded] = useState(false); + + const toggleExpanded = () => { + setIsExpanded(!isExpanded); + }; + + const handleSelect = (event, item) => { + if (currentValue.includes(item)) { + onChange(currentValue.filter(i => i.id !== item.id)); + } else { + onChange(currentValue.concat(item)); + } + }; + useEffect(() => { loadLabelOptions(setOptions, onError); }, [onError]); + useEffect(() => { + if (value !== currentValue && options.length) { + const syncedValue = value.map(item => + options.find(i => i.id === item.id) + ); + setCurrentValue(syncedValue); + } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [value, options]); + + const renderOptions = opts => { + return opts.map(option => ( + + {option.name} + + )); + }; + return ( - ({ - id: name, - name, - isNew: true, - })} - /> + ); } LabelSelect.propTypes = { @@ -55,7 +104,12 @@ LabelSelect.propTypes = { name: string.isRequired, }) ).isRequired, + placeholder: string, + onChange: func.isRequired, onError: func.isRequired, }; +LabelSelect.defaultProps = { + placeholder: '', +}; export default LabelSelect; From 7219c17d30b1ddffdec57260fec007152e1db850 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Thu, 9 Jan 2020 15:01:44 -0800 Subject: [PATCH 2/7] start usePFSelect hook --- .../screens/Template/shared/LabelSelect.jsx | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx b/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx index 33830fe8bb..c9072e2950 100644 --- a/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx +++ b/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx @@ -12,6 +12,31 @@ function setToString(labels) { return labels; } +// TODO: extract to shared file +function usePFSelect(value, options, onChange) { + const [currentValue, setCurrentValue] = useState([]); + + useEffect(() => { + if (value !== currentValue && options.length) { + const syncedValue = value.map(item => + options.find(i => i.id === item.id) + ); + setCurrentValue(syncedValue); + } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [value, options]); + + const handleSelect = (event, item) => { + if (currentValue.includes(item)) { + onChange(currentValue.filter(i => i.id !== item.id)); + } else { + onChange(currentValue.concat(item)); + } + }; + + return [currentValue, handleSelect]; +} + async function loadLabelOptions(setLabels, onError) { let labels; try { @@ -39,35 +64,17 @@ async function loadLabelOptions(setLabels, onError) { function LabelSelect({ value, placeholder, onChange, onError }) { const [options, setOptions] = useState([]); - const [currentValue, setCurrentValue] = useState([]); + const [currentValue, handleSelect] = usePFSelect(value, options, onChange); const [isExpanded, setIsExpanded] = useState(false); const toggleExpanded = () => { setIsExpanded(!isExpanded); }; - const handleSelect = (event, item) => { - if (currentValue.includes(item)) { - onChange(currentValue.filter(i => i.id !== item.id)); - } else { - onChange(currentValue.concat(item)); - } - }; - useEffect(() => { loadLabelOptions(setOptions, onError); }, [onError]); - useEffect(() => { - if (value !== currentValue && options.length) { - const syncedValue = value.map(item => - options.find(i => i.id === item.id) - ); - setCurrentValue(syncedValue); - } - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [value, options]); - const renderOptions = opts => { return opts.map(option => ( From 193a041ef9c7eea243a02b5712f019b35e5366ee Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Thu, 9 Jan 2020 15:40:06 -0800 Subject: [PATCH 3/7] finish usePFSelect hook --- .../src/components/MultiSelect/index.js | 1 + .../src/components/MultiSelect/usePFSelect.js | 51 ++++++++++++++++++ .../screens/Template/shared/LabelSelect.jsx | 52 ++++--------------- 3 files changed, 63 insertions(+), 41 deletions(-) create mode 100644 awx/ui_next/src/components/MultiSelect/usePFSelect.js diff --git a/awx/ui_next/src/components/MultiSelect/index.js b/awx/ui_next/src/components/MultiSelect/index.js index d983765272..145029344a 100644 --- a/awx/ui_next/src/components/MultiSelect/index.js +++ b/awx/ui_next/src/components/MultiSelect/index.js @@ -1,2 +1,3 @@ export { default } from './MultiSelect'; export { default as TagMultiSelect } from './TagMultiSelect'; +export { default as usePFSelect } from './usePFSelect'; diff --git a/awx/ui_next/src/components/MultiSelect/usePFSelect.js b/awx/ui_next/src/components/MultiSelect/usePFSelect.js new file mode 100644 index 0000000000..d6f2be8646 --- /dev/null +++ b/awx/ui_next/src/components/MultiSelect/usePFSelect.js @@ -0,0 +1,51 @@ +import { useState, useEffect } from 'react'; + +/* + Hook for using PatternFly's onChange([])} + onFilter={event => { + const str = event.target.value.toLowerCase(); + const matches = options.filter(o => o.name.toLowerCase().includes(str)); + return renderOptions(matches); }} - onAddNewItem={newItem => { - if (!options.find(o => o.name === newItem.name)) { - setOptions(options.concat(newItem)); - } + isCreatable + onCreateOption={name => { + // TODO check for duplicate in options + const newItem = { id: name, name }; + setOptions(options.concat(newItem)); + return newItem; }} - value={stringToArray(value)} - options={options} - createNewItem={name => ({ id: name, name })} - /> + selections={selections} + isExpanded={isExpanded} + ariaLabelledBy="tag-select" + > + {renderOptions(options)} + ); + // + // return ( + // { + // onChange(arrayToString(val)); + // }} + // onAddNewItem={newItem => { + // if (!options.find(o => o.name === newItem.name)) { + // setOptions(options.concat(newItem)); + // } + // }} + // value={stringToArray(value)} + // options={options} + // createNewItem={name => ({ id: name, name })} + // /> + // ); } TagMultiSelect.propTypes = { diff --git a/awx/ui_next/src/components/MultiSelect/usePFSelect.js b/awx/ui_next/src/components/MultiSelect/usePFSelect.js index d6f2be8646..74d4e89a1b 100644 --- a/awx/ui_next/src/components/MultiSelect/usePFSelect.js +++ b/awx/ui_next/src/components/MultiSelect/usePFSelect.js @@ -11,6 +11,7 @@ export default function usePFSelect(value, onChange) { useEffect(() => { if (value !== selections && options.length) { + console.log(value, typeof value); const syncedValue = value.map(item => options.find(i => i.id === item.id) ); diff --git a/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx b/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx index 24f1c2fe64..57c1413eca 100644 --- a/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx +++ b/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx @@ -56,7 +56,6 @@ function LabelSelect({ value, placeholder, onChange, onError }) { return ( --- .../components/MultiSelect/MultiSelect.jsx | 227 ------------------ .../MultiSelect/MultiSelect.test.jsx | 104 -------- .../components/MultiSelect/TagMultiSelect.jsx | 64 ++--- .../src/components/MultiSelect/index.js | 1 - .../src/components/MultiSelect/usePFSelect.js | 6 +- 5 files changed, 26 insertions(+), 376 deletions(-) delete mode 100644 awx/ui_next/src/components/MultiSelect/MultiSelect.jsx delete mode 100644 awx/ui_next/src/components/MultiSelect/MultiSelect.test.jsx diff --git a/awx/ui_next/src/components/MultiSelect/MultiSelect.jsx b/awx/ui_next/src/components/MultiSelect/MultiSelect.jsx deleted file mode 100644 index b8afb15855..0000000000 --- a/awx/ui_next/src/components/MultiSelect/MultiSelect.jsx +++ /dev/null @@ -1,227 +0,0 @@ -import React, { Component, Fragment } from 'react'; -import { shape, number, string, func, arrayOf, oneOfType } from 'prop-types'; -import { Chip, ChipGroup } from '@components/Chip'; -import { - Dropdown as PFDropdown, - DropdownItem, - TextInput as PFTextInput, - DropdownToggle, -} from '@patternfly/react-core'; -import styled from 'styled-components'; - -const InputGroup = styled.div` - border: 1px solid black; - margin-top: 2px; -`; - -const TextInput = styled(PFTextInput)` - border: none; - width: 100%; - padding-left: 8px; -`; - -const Dropdown = styled(PFDropdown)` - width: 100%; - .pf-c-dropdown__toggle.pf-m-plain { - display: none; - } - display: block; - .pf-c-dropdown__menu { - max-height: 200px; - overflow: scroll; - } - && button[disabled] { - color: var(--pf-c-button--m-plain--Color); - pointer-events: initial; - cursor: not-allowed; - color: var(--pf-global--disabled-color--200); - } -`; - -const Item = shape({ - id: oneOfType([number, string]).isRequired, - name: string.isRequired, -}); - -class MultiSelect extends Component { - static propTypes = { - value: arrayOf(Item).isRequired, - options: arrayOf(Item), - onAddNewItem: func, - onRemoveItem: func, - onChange: func, - createNewItem: func, - }; - - static defaultProps = { - onAddNewItem: () => {}, - onRemoveItem: () => {}, - onChange: () => {}, - options: [], - createNewItem: null, - }; - - constructor(props) { - super(props); - this.state = { - input: '', - isExpanded: false, - }; - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleInputChange = this.handleInputChange.bind(this); - this.removeItem = this.removeItem.bind(this); - this.handleClick = this.handleClick.bind(this); - this.createNewItem = this.createNewItem.bind(this); - } - - componentDidMount() { - document.addEventListener('mousedown', this.handleClick, false); - } - - componentWillUnmount() { - document.removeEventListener('mousedown', this.handleClick, false); - } - - handleClick(e, option) { - if (this.node && this.node.contains(e.target)) { - if (option) { - e.preventDefault(); - this.addItem(option); - } - } else { - this.setState({ input: '', isExpanded: false }); - } - } - - addItem(item) { - const { value, onAddNewItem, onChange } = this.props; - const items = value.concat(item); - onAddNewItem(item); - onChange(items); - this.close(); - } - - // TODO: UpArrow & DownArrow for menu navigation - handleKeyDown(event) { - const { value, options } = this.props; - const { input } = this.state; - if (event.key === 'Tab') { - this.close(); - return; - } - if (!input || event.key !== 'Enter') { - return; - } - - const isAlreadySelected = value.some(i => i.name === input); - if (isAlreadySelected) { - event.preventDefault(); - this.close(); - return; - } - - const match = options.find(item => item.name === input); - const isNewItem = !match || !value.find(item => item.id === match.id); - if (isNewItem) { - event.preventDefault(); - this.addItem(match || this.createNewItem(input)); - } - } - - close() { - this.setState({ - isExpanded: false, - input: '', - }); - } - - createNewItem(name) { - const { createNewItem } = this.props; - if (createNewItem) { - return createNewItem(name); - } - return { - id: Math.random(), - name, - }; - } - - handleInputChange(value) { - this.setState({ input: value, isExpanded: true }); - } - - removeItem(item) { - const { value, onRemoveItem, onChange } = this.props; - const remainingItems = value.filter(chip => chip.id !== item.id); - - onRemoveItem(item); - onChange(remainingItems); - } - - render() { - const { value, options } = this.props; - const { input, isExpanded } = this.state; - - const dropdownOptions = options.map(option => ( - - {option.name.includes(input) ? ( - item.id === option.id)} - value={option.name} - onClick={e => { - this.handleClick(e, option); - }} - > - {option.name} - - ) : null} - - )); - - return ( - - -
{ - this.node = node; - }} - > - this.setState({ isExpanded: true })} - onChange={this.handleInputChange} - onKeyDown={this.handleKeyDown} - /> - Labels} - // Above is not visible but is a required prop from Patternfly - isOpen={isExpanded} - dropdownItems={dropdownOptions} - /> -
-
- - {value.map(item => ( - { - this.removeItem(item); - }} - > - {item.name} - - ))} - -
-
-
- ); - } -} -export default MultiSelect; diff --git a/awx/ui_next/src/components/MultiSelect/MultiSelect.test.jsx b/awx/ui_next/src/components/MultiSelect/MultiSelect.test.jsx deleted file mode 100644 index 82627b4949..0000000000 --- a/awx/ui_next/src/components/MultiSelect/MultiSelect.test.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; -import { mount, shallow } from 'enzyme'; -import MultiSelect from './MultiSelect'; - -describe('', () => { - const value = [ - { name: 'Foo', id: 1, organization: 1 }, - { name: 'Bar', id: 2, organization: 1 }, - ]; - const options = [{ name: 'Angry', id: 3 }, { name: 'Potato', id: 4 }]; - - test('should render successfully', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find('Chip')).toHaveLength(2); - }); - - test('should add item when typed', async () => { - const onChange = jest.fn(); - const onAdd = jest.fn(); - const wrapper = mount( - - ); - const component = wrapper.find('MultiSelect'); - const input = component.find('TextInputBase'); - input.invoke('onChange')('Flabadoo'); - input.simulate('keydown', { key: 'Enter' }); - - expect(onAdd.mock.calls[0][0].name).toEqual('Flabadoo'); - const newVal = onChange.mock.calls[0][0]; - expect(newVal).toHaveLength(3); - expect(newVal[2].name).toEqual('Flabadoo'); - }); - - test('should add item when clicked from menu', () => { - const onAddNewItem = jest.fn(); - const onChange = jest.fn(); - const wrapper = mount( - - ); - - const input = wrapper.find('TextInputBase'); - input.simulate('focus'); - wrapper.update(); - const event = { - preventDefault: () => {}, - target: wrapper - .find('DropdownItem') - .at(1) - .getDOMNode(), - }; - wrapper - .find('DropdownItem') - .at(1) - .invoke('onClick')(event); - - expect(onAddNewItem).toHaveBeenCalledWith(options[1]); - const newVal = onChange.mock.calls[0][0]; - expect(newVal).toHaveLength(3); - expect(newVal[2]).toEqual(options[1]); - }); - - test('should remove item', () => { - const onRemoveItem = jest.fn(); - const onChange = jest.fn(); - const wrapper = mount( - - ); - - const chips = wrapper.find('PFChip'); - expect(chips).toHaveLength(2); - chips.at(1).invoke('onClick')(); - - expect(onRemoveItem).toHaveBeenCalledWith(value[1]); - const newVal = onChange.mock.calls[0][0]; - expect(newVal).toHaveLength(1); - expect(newVal).toEqual([value[0]]); - }); -}); diff --git a/awx/ui_next/src/components/MultiSelect/TagMultiSelect.jsx b/awx/ui_next/src/components/MultiSelect/TagMultiSelect.jsx index 4c988436f3..c52de1032f 100644 --- a/awx/ui_next/src/components/MultiSelect/TagMultiSelect.jsx +++ b/awx/ui_next/src/components/MultiSelect/TagMultiSelect.jsx @@ -1,41 +1,38 @@ import React, { useState } from 'react'; import { func, string } from 'prop-types'; import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; -import usePFSelect from '@components/MultiSelect/usePFSelect'; function arrayToString(tags) { - return tags.map(v => v.name).join(','); + return tags.join(','); } function stringToArray(value) { - return value - .split(',') - .filter(val => !!val) - .map(val => ({ - id: val, - name: val, - })); + return value.split(',').filter(val => !!val); } -/* - * Adapter providing a simplified API to a MultiSelect. The value - * is a comma-separated string. - */ function TagMultiSelect({ onChange, value }) { - const { selections, onSelect, options, setOptions } = usePFSelect( - value, // TODO: convert with stringToArray without triggering re-render loop - val => onChange(arrayToString(val)) - ); + const selections = stringToArray(value); + const [options, setOptions] = useState(selections); const [isExpanded, setIsExpanded] = useState(false); + const onSelect = (event, item) => { + let newValue; + if (selections.includes(item)) { + newValue = selections.filter(i => i !== item); + } else { + newValue = selections.concat(item); + } + onChange(arrayToString(newValue)); + }; + const toggleExpanded = () => { setIsExpanded(!isExpanded); }; const renderOptions = opts => { return opts.map(option => ( - - {option.name} + + {option} )); }; @@ -45,18 +42,19 @@ function TagMultiSelect({ onChange, value }) { variant={SelectVariant.typeaheadMulti} onToggle={toggleExpanded} onSelect={onSelect} - onClear={() => onChange([])} + onClear={() => onChange('')} onFilter={event => { const str = event.target.value.toLowerCase(); - const matches = options.filter(o => o.name.toLowerCase().includes(str)); + const matches = options.filter(o => o.toLowerCase().includes(str)); return renderOptions(matches); }} isCreatable onCreateOption={name => { - // TODO check for duplicate in options - const newItem = { id: name, name }; - setOptions(options.concat(newItem)); - return newItem; + name = name.trim(); + if (!options.includes(name)) { + setOptions(options.concat(name)); + } + return name; }} selections={selections} isExpanded={isExpanded} @@ -65,22 +63,6 @@ function TagMultiSelect({ onChange, value }) { {renderOptions(options)} ); - // - // return ( - // { - // onChange(arrayToString(val)); - // }} - // onAddNewItem={newItem => { - // if (!options.find(o => o.name === newItem.name)) { - // setOptions(options.concat(newItem)); - // } - // }} - // value={stringToArray(value)} - // options={options} - // createNewItem={name => ({ id: name, name })} - // /> - // ); } TagMultiSelect.propTypes = { diff --git a/awx/ui_next/src/components/MultiSelect/index.js b/awx/ui_next/src/components/MultiSelect/index.js index 145029344a..fc192d36cf 100644 --- a/awx/ui_next/src/components/MultiSelect/index.js +++ b/awx/ui_next/src/components/MultiSelect/index.js @@ -1,3 +1,2 @@ -export { default } from './MultiSelect'; export { default as TagMultiSelect } from './TagMultiSelect'; export { default as usePFSelect } from './usePFSelect'; diff --git a/awx/ui_next/src/components/MultiSelect/usePFSelect.js b/awx/ui_next/src/components/MultiSelect/usePFSelect.js index 74d4e89a1b..b347b7a919 100644 --- a/awx/ui_next/src/components/MultiSelect/usePFSelect.js +++ b/awx/ui_next/src/components/MultiSelect/usePFSelect.js @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react'; /* - Hook for using PatternFly's component when a pre-existing value + is loaded from somewhere other than the options. Guarantees object equality between objects in `value` and the corresponding objects loaded as `options` (based on matched id value). */ @@ -11,7 +12,6 @@ export default function usePFSelect(value, onChange) { useEffect(() => { if (value !== selections && options.length) { - console.log(value, typeof value); const syncedValue = value.map(item => options.find(i => i.id === item.id) ); @@ -48,5 +48,5 @@ function addToStringToObjects(items = []) { } function toString() { - return this.id; + return String(this.id); } From 1cc4e302f9f4267192410359ef5307c9769e4268 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Mon, 13 Jan 2020 12:53:38 -0800 Subject: [PATCH 6/7] update tests to check for PF Select --- .../MultiSelect/TagMultiSelect.test.jsx | 23 ++++++++--------- .../Template/shared/LabelSelect.test.jsx | 25 +++++++++++-------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/awx/ui_next/src/components/MultiSelect/TagMultiSelect.test.jsx b/awx/ui_next/src/components/MultiSelect/TagMultiSelect.test.jsx index 41f4aa5540..0e19d31c5d 100644 --- a/awx/ui_next/src/components/MultiSelect/TagMultiSelect.test.jsx +++ b/awx/ui_next/src/components/MultiSelect/TagMultiSelect.test.jsx @@ -3,19 +3,22 @@ import { mount } from 'enzyme'; import TagMultiSelect from './TagMultiSelect'; describe('', () => { - it('should render MultiSelect', () => { + it('should render Select', () => { const wrapper = mount( ); - expect(wrapper.find('MultiSelect').prop('options')).toEqual([ - { id: 'foo', name: 'foo' }, - { id: 'bar', name: 'bar' }, - ]); + wrapper.find('input').simulate('focus'); + const options = wrapper.find('SelectOption'); + expect(options).toHaveLength(2); + expect(options.at(0).prop('value')).toEqual('foo'); + expect(options.at(1).prop('value')).toEqual('bar'); }); it('should not treat empty string as an option', () => { const wrapper = mount(); - expect(wrapper.find('MultiSelect').prop('options')).toEqual([]); + wrapper.find('input').simulate('focus'); + expect(wrapper.find('Select').prop('isExpanded')).toEqual(true); + expect(wrapper.find('SelectOption')).toHaveLength(0); }); it('should trigger onChange', () => { @@ -23,13 +26,9 @@ describe('', () => { const wrapper = mount( ); + wrapper.find('input').simulate('focus'); - const select = wrapper.find('MultiSelect'); - select.invoke('onChange')([ - { name: 'foo' }, - { name: 'bar' }, - { name: 'baz' }, - ]); + wrapper.find('Select').invoke('onSelect')(null, 'baz'); expect(onChange).toHaveBeenCalledWith('foo,bar,baz'); }); }); diff --git a/awx/ui_next/src/screens/Template/shared/LabelSelect.test.jsx b/awx/ui_next/src/screens/Template/shared/LabelSelect.test.jsx index c4cc7ef8a1..88a76ae570 100644 --- a/awx/ui_next/src/screens/Template/shared/LabelSelect.test.jsx +++ b/awx/ui_next/src/screens/Template/shared/LabelSelect.test.jsx @@ -19,12 +19,16 @@ describe('', () => { }); let wrapper; await act(async () => { - wrapper = mount( {}} />); + wrapper = mount( + {}} onChange={() => {}} /> + ); }); - wrapper.update(); - expect(LabelsAPI.read).toHaveBeenCalledTimes(1); - expect(wrapper.find('MultiSelect').prop('options')).toEqual(options); + wrapper.find('input').simulate('focus'); + const selectOptions = wrapper.find('SelectOption'); + expect(selectOptions).toHaveLength(2); + expect(selectOptions.at(0).prop('value')).toEqual(options[0]); + expect(selectOptions.at(1).prop('value')).toEqual(options[1]); }); test('should fetch two pages labels if present', async () => { @@ -36,19 +40,20 @@ describe('', () => { }); LabelsAPI.read.mockReturnValueOnce({ data: { - results: options, + results: [{ id: 3, name: 'three' }, { id: 4, name: 'four' }], }, }); let wrapper; await act(async () => { - wrapper = mount( {}} />); + wrapper = mount( + {}} onChange={() => {}} /> + ); }); wrapper.update(); expect(LabelsAPI.read).toHaveBeenCalledTimes(2); - expect(wrapper.find('MultiSelect').prop('options')).toEqual([ - ...options, - ...options, - ]); + wrapper.find('input').simulate('focus'); + const selectOptions = wrapper.find('SelectOption'); + expect(selectOptions).toHaveLength(4); }); }); From 8850687d1b9a10e7b4d4491fd7612e50dd1fbf85 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Fri, 17 Jan 2020 08:47:31 -0800 Subject: [PATCH 7/7] rename usePFSelect to useSyncedSelectValue --- awx/ui_next/src/components/MultiSelect/index.js | 2 +- .../MultiSelect/{usePFSelect.js => useSyncedSelectValue.js} | 2 +- awx/ui_next/src/screens/Template/shared/LabelSelect.jsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename awx/ui_next/src/components/MultiSelect/{usePFSelect.js => useSyncedSelectValue.js} (95%) diff --git a/awx/ui_next/src/components/MultiSelect/index.js b/awx/ui_next/src/components/MultiSelect/index.js index fc192d36cf..cbe4fe49e2 100644 --- a/awx/ui_next/src/components/MultiSelect/index.js +++ b/awx/ui_next/src/components/MultiSelect/index.js @@ -1,2 +1,2 @@ export { default as TagMultiSelect } from './TagMultiSelect'; -export { default as usePFSelect } from './usePFSelect'; +export { default as useSyncedSelectValue } from './useSyncedSelectValue'; diff --git a/awx/ui_next/src/components/MultiSelect/usePFSelect.js b/awx/ui_next/src/components/MultiSelect/useSyncedSelectValue.js similarity index 95% rename from awx/ui_next/src/components/MultiSelect/usePFSelect.js rename to awx/ui_next/src/components/MultiSelect/useSyncedSelectValue.js index b347b7a919..2512ebe265 100644 --- a/awx/ui_next/src/components/MultiSelect/usePFSelect.js +++ b/awx/ui_next/src/components/MultiSelect/useSyncedSelectValue.js @@ -6,7 +6,7 @@ import { useState, useEffect } from 'react'; between objects in `value` and the corresponding objects loaded as `options` (based on matched id value). */ -export default function usePFSelect(value, onChange) { +export default function useSyncedSelectValue(value, onChange) { const [options, setOptions] = useState([]); const [selections, setSelections] = useState([]); diff --git a/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx b/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx index 57c1413eca..174116c9e6 100644 --- a/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx +++ b/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { func, arrayOf, number, shape, string, oneOfType } from 'prop-types'; import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; import { LabelsAPI } from '@api'; -import usePFSelect from '@components/MultiSelect/usePFSelect'; +import { useSyncedSelectValue } from '@components/MultiSelect'; async function loadLabelOptions(setLabels, onError) { let labels; @@ -30,7 +30,7 @@ async function loadLabelOptions(setLabels, onError) { } function LabelSelect({ value, placeholder, onChange, onError }) { - const { selections, onSelect, options, setOptions } = usePFSelect( + const { selections, onSelect, options, setOptions } = useSyncedSelectValue( value, onChange );