;
+ /** Suggested pills to show */
+ suggestedPillsData?: SuggestedPills;
+ /** Suggested-pill field used to match against selectedPillsValues */
+ suggestedPillsFilter?: SuggestedPillsFilter;
+ /** Label shown before the suggested pills */
+ title?: string;
+}
+
+const SuggestedPillsRow = ({
+ onSuggestedPillAdd = noop,
+ selectedPillsValues = [],
+ suggestedPillsData = [],
+ suggestedPillsFilter = 'id',
+ title,
+}: SuggestedPillsRowProps) => {
+ // Prevents pills from being rendered that are in the form by checking for value (id or custom value)
+ const filteredSuggestedPillData = suggestedPillsData.filter(item => {
+ const filterValue = item[suggestedPillsFilter];
+ return filterValue === undefined || !selectedPillsValues.includes(filterValue);
+ });
+
+ if (filteredSuggestedPillData.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {title}
+ {filteredSuggestedPillData.map(item => (
+
+ ))}
+
+ );
+};
+
+export default SuggestedPillsRow;
diff --git a/src/components/pill-selector-dropdown/__tests__/Pill.test.js b/src/components/pill-selector-dropdown/__tests__/Pill.test.tsx
similarity index 98%
rename from src/components/pill-selector-dropdown/__tests__/Pill.test.js
rename to src/components/pill-selector-dropdown/__tests__/Pill.test.tsx
index 01a9ca3184..e6b5ef9984 100644
--- a/src/components/pill-selector-dropdown/__tests__/Pill.test.js
+++ b/src/components/pill-selector-dropdown/__tests__/Pill.test.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { shallow } from 'enzyme';
import Pill from '../Pill';
diff --git a/src/components/pill-selector-dropdown/__tests__/PillSelector.test.js b/src/components/pill-selector-dropdown/__tests__/PillSelector.test.tsx
similarity index 87%
rename from src/components/pill-selector-dropdown/__tests__/PillSelector.test.js
rename to src/components/pill-selector-dropdown/__tests__/PillSelector.test.tsx
index 6b42d6e7f4..d5a5d386dc 100644
--- a/src/components/pill-selector-dropdown/__tests__/PillSelector.test.js
+++ b/src/components/pill-selector-dropdown/__tests__/PillSelector.test.tsx
@@ -4,8 +4,11 @@ import { List, Record } from 'immutable';
import sinon from 'sinon';
import { PillSelectorBase as PillSelector } from '../PillSelector';
+import type { Option, OptionValue } from '../flowTypes';
const sandbox = sinon.sandbox.create();
+const getInstance = (wrapper: { instance: () => React.Component }) =>
+ wrapper.instance() as InstanceType;
describe('components/pill-selector-dropdown/PillSelector', () => {
const onInputStub = sandbox.stub();
@@ -29,14 +32,14 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
const selector = wrapper.find('.bdl-PillSelector');
expect(wrapper.find('Tooltip').exists()).toBe(true);
- expect(selector.length).toBe(1);
- expect(input.length).toBe(1);
+ expect(selector).toHaveLength(1);
+ expect(input).toHaveLength(1);
expect(input.prop('onInput')).toEqual(onInputStub);
expect(input.prop('placeholder')).toEqual(placeholder);
});
test('should render disabled component', () => {
- const wrapper = shallow( {}} onRemove={() => {}} />);
+ const wrapper = shallow();
expect(wrapper).toMatchSnapshot();
});
@@ -52,14 +55,14 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
const wrapper = shallow();
wrapper.setState({ isFocused: true });
- expect(wrapper.find('.is-focused').length).toBe(1);
+ expect(wrapper.find('.is-focused')).toHaveLength(1);
});
test('should not add is-focused class when input is not focused', () => {
const wrapper = shallow();
wrapper.setState({ isFocused: false });
- expect(wrapper.find('.is-focused').length).toBe(0);
+ expect(wrapper.find('.is-focused')).toHaveLength(0);
});
test('should add bdl-PillSelector-input--nextLine class when prop is set to true', () => {
@@ -67,25 +70,25 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
,
);
- expect(wrapper.find('.bdl-PillSelector-input--nextLine').length).toBe(1);
+ expect(wrapper.find('.bdl-PillSelector-input--nextLine')).toHaveLength(1);
});
test('should not add bdl-PillSelector-input--nextLine class when prop is not given', () => {
const wrapper = shallow();
- expect(wrapper.find('.bdl-PillSelector-input--nextLine').length).toBe(0);
+ expect(wrapper.find('.bdl-PillSelector-input--nextLine')).toHaveLength(0);
});
test('should add show-error class when error is given', () => {
const wrapper = shallow();
- expect(wrapper.find('.show-error').length).toBe(1);
+ expect(wrapper.find('.show-error')).toHaveLength(1);
});
test('should not add show-error class when error is not given', () => {
const wrapper = shallow();
- expect(wrapper.find('.show-error').length).toBe(0);
+ expect(wrapper.find('.show-error')).toHaveLength(0);
});
test('should render pills when there are selected options using legacy text attribute', () => {
@@ -97,7 +100,7 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
,
);
- expect(wrapper.find('Pill').length).toBe(2);
+ expect(wrapper.find('Pill')).toHaveLength(2);
});
test('should render RoundPill instead of standard Pill when showRoundedPills prop is true', () => {
@@ -111,7 +114,7 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
/>,
);
- expect(wrapper.find('RoundPill').length).toBe(1);
+ expect(wrapper.find('RoundPill')).toHaveLength(1);
});
test('should render pills when there are selected options', () => {
@@ -124,16 +127,17 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
);
const pills = wrapper.find('Pill');
- expect(pills.length).toBe(2);
+ expect(pills).toHaveLength(2);
expect(pills.at(0).prop('isValid')).toBeTruthy();
expect(pills.at(1).prop('isValid')).toBeTruthy();
});
test('should render invalid pills when validator is provided and allowInvalidPills is true', () => {
- const validator = ({ displayText }) => {
+ const validator = (option: Option | OptionValue) => {
// W3C type="email" input validation
const pattern = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
- return pattern.test(displayText);
+ const displayText = typeof option === 'object' && option !== null ? option.displayText : String(option);
+ return pattern.test(displayText || '');
};
const options = [
{ displayText: 'test', value: 'test' },
@@ -149,13 +153,13 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
/>,
);
const pills = wrapper.find('Pill');
- expect(pills.length).toBe(2);
+ expect(pills).toHaveLength(2);
expect(pills.at(0).prop('isValid')).toBeFalsy();
expect(pills.at(1).prop('isValid')).toBeFalsy();
});
test('should render round pills using the class name returned by getPillClassName', () => {
- const getPillClassName = ({ className }) => className;
+ const getPillClassName = (option: Option & { className?: string }) => option.className || '';
const options = [
{ displayText: 'Pill 1', value: '1', className: 'MyClass1' },
{ displayText: 'Pill 2', value: '2', className: 'MyClass2' },
@@ -178,15 +182,15 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
});
test('should render pills when selected options are immutable', () => {
- const options = new List([
- new OptionRecord({ text: 'test', value: 'test' }),
- new OptionRecord({ text: 'blah', value: 'hi' }),
+ const options = List([
+ OptionRecord({ text: 'test', value: 'test' }),
+ OptionRecord({ text: 'blah', value: 'hi' }),
]);
const wrapper = shallow(
,
);
- expect(wrapper.find('Pill').length).toBe(2);
+ expect(wrapper.find('Pill')).toHaveLength(2);
});
test('should render pill as selected when selected index is set', () => {
@@ -207,9 +211,9 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
test('should render hidden pill selection helper', () => {
const wrapper = shallow();
const hidden = wrapper.find('[data-testid="pill-selection-helper"]');
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
- expect(hidden.length).toBe(1);
+ expect(hidden).toHaveLength(1);
expect(hidden.prop('onBlur')).toEqual(instance.resetSelectedIndex);
});
@@ -236,7 +240,7 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
test('should pass through additional props when specified', () => {
const value = 'test';
const wrapper = shallow(
- {}} onInput={onInputStub} onRemove={onRemoveStub} value={value} />,
+ ,
);
expect(wrapper.find('textarea').prop('value')).toEqual(value);
@@ -252,7 +256,7 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
});
test('should not render placeholder when there are immutable pills', () => {
- const options = new List([new OptionRecord({ text: 'test', value: 'test' })]);
+ const options = List([OptionRecord({ text: 'test', value: 'test' })]);
const wrapper = shallow(
,
);
@@ -292,12 +296,12 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
describe('onKeyDown - Backspace', () => {
test('should remove selected pill when backspace is pressed', () => {
- const option = { text: 'test', value: 'test' };
+ const option = { text: 'test', value: 'test' } as const;
const options = [option, { text: 'blah', value: 'blah' }];
const wrapper = mount(
,
);
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
act(() => {
wrapper.setState({ selectedIndex: 0 });
});
@@ -316,7 +320,7 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
test('should not prevent default when backspace is pressed and the input has value', () => {
const wrapper = mount(
- {}} onInput={onInputStub} onRemove={onRemoveStub} value="test" />,
+ ,
);
wrapper.find('.bdl-PillSelector').simulate('keyDown', {
key: 'Backspace',
@@ -326,7 +330,7 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
});
test('should call onRemove() when backspace is pressed and there are pills but no input value', () => {
- const option = { text: 'test', value: 'test' };
+ const option = { text: 'test', value: 'test' } as const;
const options = [{ text: 'blah', value: 'blah' }, option];
const wrapper = mount(
,
@@ -340,8 +344,8 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
});
test('should call onRemove() when backspace is pressed and there are immutable pills but no input value', () => {
- const option = new OptionRecord({ text: 'test', value: 'test' });
- const options = new List([new OptionRecord({ text: 'blah', value: 'blah' }), option]);
+ const option = OptionRecord({ text: 'test', value: 'test' });
+ const options = List([OptionRecord({ text: 'blah', value: 'blah' }), option]);
const wrapper = mount(
,
);
@@ -417,7 +421,7 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
test('should not prevent default when left arrow is pressed and the input has value', () => {
const wrapper = mount(
- {}} onInput={onInputStub} onRemove={onRemoveStub} value="test" />,
+ ,
);
wrapper.find('.bdl-PillSelector').simulate('keyDown', {
key: 'ArrowLeft',
@@ -436,7 +440,7 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
const wrapper = mount(
,
);
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
act(() => {
wrapper.setState({ selectedIndex: 1 });
});
@@ -484,22 +488,22 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
describe('onRemove', () => {
test('should call onRemove() when pill onRemove is triggered', () => {
- const option = { text: 'test', value: 'test' };
+ const option = { text: 'test', value: 'test' } as const;
const options = [option, { text: 'blah', value: 'blah' }];
const wrapper = shallow(
,
);
- wrapper.find('Pill').at(0).prop('onRemove')();
+ (wrapper.find('Pill').at(0).prop('onRemove') as () => void)();
expect(onRemoveStub.calledWith(option, 0)).toBe(true);
});
test('should call onRemove() when immutable pill onRemove is triggered', () => {
- const option = new OptionRecord({ text: 'test', value: 'test' });
- const options = new List([option, new OptionRecord({ text: 'blah', value: 'blah' })]);
+ const option = OptionRecord({ text: 'test', value: 'test' });
+ const options = List([option, OptionRecord({ text: 'blah', value: 'blah' })]);
const wrapper = shallow(
,
);
- wrapper.find('Pill').at(0).prop('onRemove')();
+ (wrapper.find('Pill').at(0).prop('onRemove') as () => void)();
expect(onRemoveStub.calledWith(option, 0)).toBe(true);
});
});
@@ -507,7 +511,7 @@ describe('components/pill-selector-dropdown/PillSelector', () => {
describe('resetSelectedIndex()', () => {
test('should reset selected index when called', () => {
const wrapper = shallow();
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
act(() => {
wrapper.setState({ selectedIndex: 1 });
});
diff --git a/src/components/pill-selector-dropdown/__tests__/PillSelectorDropdown.test.js b/src/components/pill-selector-dropdown/__tests__/PillSelectorDropdown.test.tsx
similarity index 84%
rename from src/components/pill-selector-dropdown/__tests__/PillSelectorDropdown.test.js
rename to src/components/pill-selector-dropdown/__tests__/PillSelectorDropdown.test.tsx
index 90be881378..437b59a492 100644
--- a/src/components/pill-selector-dropdown/__tests__/PillSelectorDropdown.test.js
+++ b/src/components/pill-selector-dropdown/__tests__/PillSelectorDropdown.test.tsx
@@ -1,7 +1,19 @@
import * as React from 'react';
+import { shallow } from 'enzyme';
import { List, Record } from 'immutable';
import PillSelectorDropdown from '../PillSelectorDropdown';
+import type { Option } from '../flowTypes';
+
+const getInstance = (wrapper: { instance: () => React.Component }) =>
+ wrapper.instance() as InstanceType & {
+ addPillsFromInput: (inputValue?: string) => void;
+ handleBlur: (event?: unknown) => void;
+ handleEnter: (event?: unknown) => void;
+ handlePaste: (event?: unknown) => void;
+ handleSelect: (index: number, event?: unknown) => void;
+ parsePills: ((inputValue: string) => unknown[]) & jest.Mock;
+ };
describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
const OptionRecord = Record({
@@ -9,7 +21,8 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
value: '',
});
- const getWrapper = (props, children) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const getWrapper = (props: any = {}, children?: any): any => {
const options = children || (
<>
Option 1
@@ -34,7 +47,7 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
const className = 'test';
const children = 'hi';
const wrapper = getWrapper({ className }, children);
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
const selectorDropdown = wrapper.find('SelectorDropdown');
expect(selectorDropdown.is('SelectorDropdown')).toBe(true);
@@ -46,20 +59,22 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
});
test('should render pill selector', () => {
- const inputProps = { 'aria-label': 'test' };
+ const inputProps = { 'aria-label': 'test' } as const;
const wrapper = getWrapper({ inputProps });
wrapper.setState({ inputValue: 'value' });
- const pillSelector = shallow(wrapper.find('SelectorDropdown').prop('selector'));
- const wrapperInstance = wrapper.instance();
+ const pillSelector = shallow(wrapper.find('SelectorDropdown').prop('selector') as React.ReactElement);
+ const wrapperInstance = getInstance(wrapper);
expect(pillSelector.prop('onInput')).toEqual(wrapperInstance.handleInput);
expect(pillSelector.prop('onPaste')).toEqual(wrapperInstance.handlePaste);
- expect(pillSelector.dive().instance().props.value).toEqual('value');
+ expect((pillSelector.dive().instance() as unknown as { props: { value: string } }).props.value).toEqual(
+ 'value',
+ );
});
test('should render disabled pill selector', () => {
const wrapper = getWrapper({ disabled: true });
- wrapper.setState();
+ wrapper.update();
expect(wrapper).toMatchSnapshot();
});
@@ -67,7 +82,7 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
test('should call addPillsFromInput when pill selector is blurred', () => {
const wrapper = getWrapper();
wrapper.setState({ inputValue: 'value' });
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
const addPillsFromInputMock = jest.fn();
instance.addPillsFromInput = addPillsFromInputMock;
instance.handleBlur();
@@ -77,8 +92,8 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
test.each([
['test', true],
['', false],
- ])('should render Label component when label exists', (value, expected) => {
- const labelProp = { label: value };
+ ])('should render Label component when label exists', (value: string, expected: boolean) => {
+ const labelProp = { label: value } as const;
const wrapper = getWrapper(labelProp);
expect(wrapper.exists('Label')).toBe(expected);
});
@@ -87,7 +102,7 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
describe('parsePills', () => {
test('should return a formatted map of pills', () => {
const wrapper = getWrapper();
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
const inputValues = 'value1, value2,value3';
wrapper.setState({ inputValue: inputValues });
@@ -100,14 +115,14 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
});
test('should only return pills that pass validator if one is provided and allowInvalidPills is false', () => {
- const validator = text => {
+ const validator = (text: string) => {
// W3C type="email" input validation
const pattern = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
return pattern.test(text);
};
const wrapper = getWrapper({ validator });
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
const inputValues = 'aaron@example.com, bademail,hello@gmail.com';
wrapper.setState({
inputValue: inputValues,
@@ -121,7 +136,7 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
});
test('should ignore validator if one is provided but allowInvalidPills is true', () => {
- const validator = text => {
+ const validator = (text: string) => {
// W3C type="email" input validation
const pattern = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
return pattern.test(text);
@@ -129,7 +144,7 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
const wrapper = getWrapper({ allowInvalidPills: true, validator });
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
const inputValues = 'aaron@example.com, bademail, hello@gmail.com';
wrapper.setState({
inputValue: inputValues,
@@ -147,10 +162,10 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
const wrapper = getWrapper({ allowInvalidPills: true });
wrapper.setState({ inputValue: 'a,b' });
- const { parsePills } = wrapper.instance();
- const stringParser = input => input.split(',');
- const optionParser = input =>
- input.split(',').map(token => ({
+ const { parsePills } = getInstance(wrapper);
+ const stringParser = (input: string) => input.split(',');
+ const optionParser = (input: string) =>
+ input.split(',').map((token: string) => ({
customProp: token,
}));
@@ -185,7 +200,7 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
const onPillCreateMock = jest.fn();
const onSelectMock = jest.fn();
const wrapper = getWrapper({ onPillCreate: onPillCreateMock, onSelect: onSelectMock });
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
const inputValue = 'value';
wrapper.setState({ inputValue });
@@ -212,7 +227,7 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
validateForError: validateForErrorMock,
});
- const instance = wrapper.instance();
+ const instance = getInstance(wrapper);
instance.parsePills = jest.fn().mockReturnValue(pills);
instance.addPillsFromInput();
@@ -224,7 +239,7 @@ describe('components/pill-selector-dropdown/PillSelectorDropdown', () => {
});
test('should call props.validateForError if no pills were added but input exists', () => {
- const pills = [];
+ const pills: Array