Skip to content

Commit

Permalink
LG-3876 Combobox input value (#2193)
Browse files Browse the repository at this point in the history
* lg-3978 typed value fires onChange

* adds tests for inputValue

* updates selection change side effects

* fires onInputChange

* Create fuzzy-spiders-hug.md

* Update fuzzy-spiders-hug.md
  • Loading branch information
TheSonOfThomp authored Feb 6, 2024
1 parent 7b8176b commit 371aeca
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 90 deletions.
7 changes: 7 additions & 0 deletions .changeset/fuzzy-spiders-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@leafygreen-ui/combobox': major
---

- Adds optional `inputValue` and `onInputChange` props to Combobox. These props are used to control the value of the inner text input (not the selected combobox value itself).

- `onChange` callback now fires when the input is blurred and the input contains a valid selection value.
3 changes: 2 additions & 1 deletion packages/combobox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"@leafygreen-ui/leafygreen-provider": "^3.1.10"
},
"devDependencies": {
"@leafygreen-ui/button": "^21.0.12"
"@leafygreen-ui/button": "^21.0.12",
"@leafygreen-ui/testing-lib": "^0.4.0"
},
"homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/packages/combobox",
"repository": {
Expand Down
1 change: 1 addition & 0 deletions packages/combobox/src/Combobox.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const meta: StoryMetaType<typeof Combobox> = {
label: { control: 'text' },
description: { control: 'text' },
placeholder: { control: 'text' },
inputValue: { control: 'text' },
size: {
options: Object.values(ComboboxSize),
control: 'select',
Expand Down
146 changes: 90 additions & 56 deletions packages/combobox/src/Combobox/Combobox.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import isUndefined from 'lodash/isUndefined';

import Button from '@leafygreen-ui/button';
import { keyMap } from '@leafygreen-ui/lib';
import { eventContainingTargetValue } from '@leafygreen-ui/testing-lib';

import { OptionObject } from '../ComboboxOption/ComboboxOption.types';
import {
Expand Down Expand Up @@ -134,6 +135,13 @@ describe('packages/combobox', () => {
});
expect(clearButtonEl).not.toBeInTheDocument();
});

test('`inputValue` prop is rendered in the textbox', () => {
const { inputEl } = renderCombobox(select, {
inputValue: 'abc',
});
expect(inputEl).toHaveValue('abc');
});
});

/**
Expand Down Expand Up @@ -420,7 +428,7 @@ describe('packages/combobox', () => {
/**
* Input element
*/
describe('Input interaction', () => {
describe('Typing (Input interaction)', () => {
test('Typing any character updates the input', () => {
const { inputEl } = renderCombobox(select);
userEvent.type(inputEl, 'zy');
Expand All @@ -434,70 +442,103 @@ describe('packages/combobox', () => {
expect(inputEl).toHaveValue(displayName);
expect(inputEl.scrollWidth).toBeGreaterThanOrEqual(inputEl.clientWidth);
});
});

/**
* Controlled
* (i.e. `value` prop)
*/
describe('When value is controlled', () => {
test('Typing any character updates the input', () => {
const value = select === 'multiple' ? [] : '';
const { inputEl } = renderCombobox(select, {
value,
});
expect(inputEl).toHaveValue('');
userEvent.type(inputEl, 'z');
expect(inputEl).toHaveValue('z');
test('Typing does not fire onChange callback', () => {
const onChange = jest.fn();
const { inputEl } = renderCombobox(select, { onChange });
userEvent.type(inputEl, 'Apple');
expect(onChange).not.toHaveBeenCalled();
});

testSingleSelect('Text input renders with value update', () => {
let value = 'apple';
const { inputEl, rerenderCombobox } = renderCombobox(select, {
value,
});
expect(inputEl).toHaveValue('Apple');
value = 'banana';
rerenderCombobox({ value });
expect(inputEl).toHaveValue('Banana');
test('Typing fires onInputChange callback', () => {
const onInputChange = jest.fn();
const { inputEl } = renderCombobox(select, { onInputChange });
userEvent.type(inputEl, 'abc');
expect(onInputChange).toHaveBeenCalledWith(
eventContainingTargetValue('abc'),
);
});

testSingleSelect('Invalid option passed as value is not selected', () => {
const value = 'jellybean';
const { inputEl } = renderCombobox(select, { value });
expect(inputEl).toHaveValue('');
test('Blurring the input after typing a valid value fires onChange', async () => {
const onChange = jest.fn();
const { inputEl, openMenu } = renderCombobox(select, { onChange });
const { menuContainerEl } = openMenu();
userEvent.type(inputEl, 'Apple');
userEvent.tab();
await waitForElementToBeRemoved(menuContainerEl);
if (select === 'multiple') {
expect(onChange).toHaveBeenCalledWith(['apple'], expect.anything());
} else {
expect(onChange).toHaveBeenCalledWith('apple');
}
});

testMultiSelect('Updating `value` updates the chips', () => {
let value = ['apple', 'banana'];
const { queryChipsByName, queryAllChips, rerenderCombobox } =
renderCombobox(select, {
/**
* Controlled
* (i.e. `value` prop is set)
*/
describe('When value is controlled', () => {
test('Typing any character updates the input', () => {
const value = select === 'multiple' ? [] : '';
const { inputEl } = renderCombobox(select, {
value,
});
waitFor(() => {
const allChips = queryChipsByName(['Apple', 'Banana']);
allChips?.forEach(chip => expect(chip).toBeInTheDocument());
expect(queryAllChips()).toHaveLength(2);
value = ['banana', 'carrot'];
expect(inputEl).toHaveValue('');
userEvent.type(inputEl, 'z');
expect(inputEl).toHaveValue('z');
});

testSingleSelect('Text input renders with value update', () => {
let value = 'apple';
const { inputEl, rerenderCombobox } = renderCombobox(select, {
value,
});
expect(inputEl).toHaveValue('Apple');
value = 'banana';
rerenderCombobox({ value });
expect(inputEl).toHaveValue('Banana');
});

testSingleSelect(
'Invalid option passed as value is not selected',
() => {
const value = 'jellybean';
const { inputEl } = renderCombobox(select, { value });
expect(inputEl).toHaveValue('');
},
);

testMultiSelect('Updating `value` updates the chips', () => {
let value = ['apple', 'banana'];
const { queryChipsByName, queryAllChips, rerenderCombobox } =
renderCombobox(select, {
value,
});
waitFor(() => {
const allChips = queryChipsByName(['Carrot', 'Banana']);
const allChips = queryChipsByName(['Apple', 'Banana']);
allChips?.forEach(chip => expect(chip).toBeInTheDocument());
expect(queryAllChips()).toHaveLength(2);
value = ['banana', 'carrot'];
rerenderCombobox({ value });
waitFor(() => {
const allChips = queryChipsByName(['Carrot', 'Banana']);
allChips?.forEach(chip => expect(chip).toBeInTheDocument());
expect(queryAllChips()).toHaveLength(2);
});
});
});
});

testMultiSelect('Invalid options are not selected', () => {
const value = ['apple', 'jellybean'];
const { queryChipsByName, queryAllChips } = renderCombobox(select, {
value,
});
waitFor(() => {
const allChips = queryChipsByName(['Apple']);
allChips?.forEach(chip => expect(chip).toBeInTheDocument());
expect(queryChipsByName('Jellybean')).not.toBeInTheDocument();
expect(queryAllChips()).toHaveLength(1);
testMultiSelect('Invalid options are not selected', () => {
const value = ['apple', 'jellybean'];
const { queryChipsByName, queryAllChips } = renderCombobox(select, {
value,
});
waitFor(() => {
const allChips = queryChipsByName(['Apple']);
allChips?.forEach(chip => expect(chip).toBeInTheDocument());
expect(queryChipsByName('Jellybean')).not.toBeInTheDocument();
expect(queryAllChips()).toHaveLength(1);
});
});
});
});
Expand Down Expand Up @@ -1538,13 +1579,6 @@ describe('packages/combobox', () => {
expect(onChange).toHaveBeenCalled();
});

test('Typing does not call onChange callback', () => {
const onChange = jest.fn();
const { inputEl } = renderCombobox(select, { onChange });
userEvent.type(inputEl, 'a');
expect(onChange).not.toHaveBeenCalled();
});

test('Closing the menu without making a selection does not call onChange callback', async () => {
const onChange = jest.fn();
const { containerEl, openMenu } = renderCombobox(select, { onChange });
Expand Down
Loading

0 comments on commit 371aeca

Please sign in to comment.