Skip to content

Commit

Permalink
feat: combobox's new feature, autocomplete with typeahead (carbon-des…
Browse files Browse the repository at this point in the history
…ign-system#17268)

* feat: autocomplete is fked

* feat: autoComplete typeahead feature fixed

* feat: added tests for autocomplete

* feat: adds functionality test cases

* feat: matcsh case exactly with option list when Tab is pressed

* feat: adds tests, refines functions

* feat: updated prop name to component and tests

* refactor: fixed conflict

* test: covered allowcustomValue prop

---------

Co-authored-by: Nikhil Tomar <[email protected]>
Co-authored-by: Nikhil Tomar <[email protected]>
  • Loading branch information
3 people authored and annawen1 committed Oct 11, 2024
1 parent e11b039 commit e811736
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 13 deletions.
3 changes: 3 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,9 @@ Map {
"translateWithId": Object {
"type": "func",
},
"typeahead": Object {
"type": "bool",
},
"warn": Object {
"type": "bool",
},
Expand Down
147 changes: 145 additions & 2 deletions packages/react/src/components/ComboBox/ComboBox-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { AILabel } from '../AILabel';

const findInputNode = () => screen.getByRole('combobox');
const openMenu = async () => {
await userEvent.click(screen.getByTitle('Open'));
await userEvent.click(screen.getByRole('combobox'));
};

const prefix = 'cds';
Expand Down Expand Up @@ -456,7 +456,7 @@ describe('ComboBox', () => {
render(<ComboBox {...mockProps} allowCustomValue={false} />);
await userEvent.type(findInputNode(), '1');
expect(screen.getAllByRole('option')[1]).toHaveClass(
'cds--list-box__menu-item--highlighted'
'cds--list-box__menu-item'
);
});

Expand Down Expand Up @@ -497,4 +497,147 @@ describe('ComboBox', () => {
);
});
});

describe('ComboBox autocomplete', () => {
const items = [
{ id: 'option-1', text: 'Option 1' },
{ id: 'option-2', text: 'Option 2' },
{ id: 'option-3', text: 'Option 3' },
{ id: 'apple', text: 'Apple' },
{ id: 'banana', text: 'Banana' },
{ id: 'orange', text: 'Orange' },
{ id: 'orangeish', text: 'Orangeish' },
];

const mockProps = {
id: 'test-combobox',
items,
itemToString: (item) => (item ? item.text : ''),
onChange: jest.fn(),
};

it('should respect autocomplete prop', async () => {
render(<ComboBox {...mockProps} typeahead />);
await waitForPosition();
const inputNode = findInputNode();
expect(inputNode).toHaveAttribute('autocomplete');
});
it('should use autocompleteCustomFilter when autocomplete prop is true', async () => {
const user = userEvent.setup();
render(<ComboBox {...mockProps} typeahead />);

// Open the dropdown
const input = screen.getByRole('combobox');
user.click(input);

// Type 'op' which should match all options
await user.type(input, 'op');
expect(screen.getAllByRole('option')).toHaveLength(3);

// Type 'opt' which should still match all options
await user.type(input, 't');
expect(screen.getAllByRole('option')).toHaveLength(3);

// Type 'opti' which should match only 'Option 1'
await user.type(input, 'i');
expect(screen.getAllByRole('option')).toHaveLength(3);
expect(screen.getByText('Option 1')).toBeInTheDocument();
});

it('should use default filter when autocomplete prop is false', async () => {
const user = userEvent.setup();
render(<ComboBox {...mockProps} />);

// Open the dropdown
const input = screen.getByRole('combobox');
user.click(input);

// Type 'op' which should match all options
await user.type(input, 'op');
expect(screen.getAllByRole('option')).toHaveLength(7);

// Type 'opt' which should still match all options
await user.type(input, 't');
expect(screen.getAllByRole('option')).toHaveLength(7);

// Type 'opti' which should still match all options
await user.type(input, 'i');
expect(screen.getAllByRole('option')).toHaveLength(7);

// Type 'option' which should still match all options
await user.type(input, 'on');
expect(screen.getAllByRole('option')).toHaveLength(7);
});

it('should not autocomplete when no match is found', async () => {
const user = userEvent.setup();
render(<ComboBox {...mockProps} typeahead />);

const input = screen.getByRole('combobox');
user.click(input);

await user.type(input, 'xyz');
await user.keyboard('[Tab]');

expect(document.activeElement).not.toBe(input);
});
it('should suggest best matching typeahread suggestion and complete it in Tab key press', async () => {
const user = userEvent.setup();
render(<ComboBox {...mockProps} typeahead />);

// Open the dropdown
const input = screen.getByRole('combobox');
user.click(input);

// Type 'op' which should match all options
await user.type(input, 'Ap');

await user.keyboard('[Tab]');

expect(findInputNode()).toHaveDisplayValue('Apple');
});
it('should not autocomplete on Tab after backspace', async () => {
const user = userEvent.setup();
render(<ComboBox {...mockProps} allowCustomValue typeahead />);

const input = screen.getByRole('combobox');
user.click(input);

await user.type(input, 'App');
await user.keyboard('[Backspace]');

await user.keyboard('[Tab]');

expect(document.activeElement).not.toBe(input);
});
it('should autocomplete with the first matching suggestion when multiple matches exist', async () => {
const multipleMatchProps = {
...mockProps,
options: ['Apple', 'Application', 'Apricot'],
};
const user = userEvent.setup();
render(<ComboBox {...multipleMatchProps} allowCustomValue typeahead />);

const input = screen.getByRole('combobox');
user.click(input);

await user.type(input, 'App');
await user.keyboard('[Tab]');

expect(input).toHaveDisplayValue('Apple');
});

it('should match case exactly with option list when Tab is pressed', async () => {
const user = userEvent.setup();
render(<ComboBox {...mockProps} allowCustomValue typeahead />);

const input = screen.getByRole('combobox');
user.click(input);

await user.type(input, 'APpl');
await user.keyboard('[Tab]');

expect(input).toHaveDisplayValue('Apple');
});
});
});
20 changes: 20 additions & 0 deletions packages/react/src/components/ComboBox/ComboBox.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,22 @@ export const AllowCustomValue = (args) => {
</div>
);
};

export const AutocompleteWithTypeahead = (args) => {
return (
<div style={{ width: 300 }}>
<ComboBox
allowCustomValue
onChange={args.onChange}
helperText="Combobox helper text"
id="carbon-combobox"
items={['Apple', 'Orange', 'Banana', 'Pineapple', 'Raspberry', 'Lime']}
titleText="ComboBox title"
typeahead
/>
</div>
);
};
export const ExperimentalAutoAlign = () => (
<div style={{ width: 400 }}>
<div style={{ height: 300 }}></div>
Expand Down Expand Up @@ -273,6 +289,10 @@ export const Playground = (args) => (
</div>
);

AutocompleteWithTypeahead.argTypes = {
onChange: { action: 'onChange' },
};

Playground.argTypes = {
['aria-label']: {
table: {
Expand Down
Loading

0 comments on commit e811736

Please sign in to comment.