-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Select): implement select with search and multiple select
- Loading branch information
Showing
6 changed files
with
431 additions
and
46 deletions.
There are no files selected for viewing
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,88 @@ | ||
import React from 'react'; | ||
import { Meta, StoryFn } from '@storybook/react'; | ||
import { BoemlySelect as Select } from './Select'; | ||
|
||
export default { | ||
title: 'Components/Select', | ||
component: Select, | ||
argTypes: { | ||
placeholder: { control: { type: 'text' } }, | ||
isDisabled: { control: { type: 'boolean' } }, | ||
isInvalid: { control: { type: 'boolean' } }, | ||
isFullWidth: { control: { type: 'boolean' } }, | ||
isSearchable: { control: { type: 'boolean' } }, | ||
isMultiple: { control: { type: 'boolean' } }, | ||
onChange: { action: 'Select Changed' }, | ||
options: { control: 'object' }, | ||
}, | ||
} as Meta<typeof Select>; | ||
|
||
const Template: StoryFn<typeof Select> = (args) => <Select {...args} />; | ||
|
||
export const Default = Template.bind({}); | ||
Default.args = {}; | ||
|
||
export const WithSelectedValue = Template.bind({}); | ||
WithSelectedValue.args = { | ||
value: 'option_2', | ||
placeholder: 'Select an option', | ||
options: [ | ||
{ label: 'Option 1', value: 'option_1' }, | ||
{ label: 'Option 2', value: 'option_2' }, | ||
{ label: 'Option 3', value: 'option_3' }, | ||
], | ||
}; | ||
|
||
export const WithPlaceholder = Template.bind({}); | ||
WithPlaceholder.args = { | ||
placeholder: 'Placeholder', | ||
options: [ | ||
{ label: 'Option 1', value: 'option_1' }, | ||
{ label: 'Option 2', value: 'option_2' }, | ||
{ label: 'Option 3', value: 'option_3' }, | ||
], | ||
}; | ||
|
||
Default.args = { | ||
placeholder: 'Select an option', | ||
options: [ | ||
{ label: 'Option 1', value: 'option_1' }, | ||
{ label: 'Option 2', value: 'option_2' }, | ||
{ label: 'Option 3', value: 'option_3' }, | ||
], | ||
}; | ||
|
||
export const Searchable = Template.bind({}); | ||
Searchable.args = { | ||
isSearchable: true, | ||
placeholder: 'Search options...', | ||
options: [ | ||
{ label: 'Option 1', value: 'option_1' }, | ||
{ label: 'Option 2', value: 'option_2' }, | ||
{ label: 'Option 3', value: 'option_3' }, | ||
], | ||
}; | ||
|
||
export const MultiSelect = Template.bind({}); | ||
MultiSelect.args = { | ||
isMultiple: true, | ||
placeholder: 'Select multiple options', | ||
options: [ | ||
{ label: 'Option 1', value: 'option_1' }, | ||
{ label: 'Option 2', value: 'option_2' }, | ||
{ label: 'Option 3', value: 'option_3' }, | ||
], | ||
}; | ||
|
||
export const SearchableMultiSelect = Template.bind({}); | ||
SearchableMultiSelect.args = { | ||
isSearchable: true, | ||
isMultiple: true, | ||
placeholder: 'Search and select multiple options', | ||
options: [ | ||
{ label: 'Option 1', value: 'option_1' }, | ||
{ label: 'Option 2', value: 'option_2' }, | ||
{ label: 'Option 3', value: 'option_3' }, | ||
{ label: 'Option 4', value: 'option_4' }, | ||
], | ||
}; |
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,127 @@ | ||
import React from 'react'; | ||
import { render, screen, fireEvent } from '@testing-library/react'; | ||
import { Select } from '.'; | ||
|
||
const mockOptions = [ | ||
{ label: 'Option 1', value: '1' }, | ||
{ label: 'Option 2', value: '2' }, | ||
{ label: 'Option 3', value: '3' }, | ||
]; | ||
|
||
describe('Select', () => { | ||
test('renders with placeholder text', () => { | ||
render(<Select options={mockOptions} placeholder="Select an option from these options" />); | ||
expect(screen.getByText('Select an option from these options')).toBeInTheDocument(); | ||
}); | ||
|
||
test('opens dropdown when clicked', () => { | ||
render(<Select options={mockOptions} />); | ||
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i })); | ||
expect(screen.getByText('Option 1')).toBeInTheDocument(); | ||
}); | ||
|
||
test('selects an option when clicked', () => { | ||
render(<Select options={mockOptions} />); | ||
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i })); | ||
fireEvent.click(screen.getByText('Option 1')); | ||
expect(screen.getByText('Option 1')).toBeInTheDocument(); | ||
}); | ||
|
||
test('handles single selection mode', () => { | ||
render(<Select options={mockOptions} value="1" />); | ||
expect(screen.getByText('Option 1')).toBeInTheDocument(); | ||
}); | ||
|
||
test('handles multiple selection mode', () => { | ||
render( | ||
<Select | ||
options={mockOptions} | ||
isMultiple | ||
selectAllText="Select All" | ||
clearAllText="Clear All" | ||
/> | ||
); | ||
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i })); | ||
fireEvent.click(screen.getByText('Option 1')); | ||
fireEvent.click(screen.getByText('Option 2')); | ||
expect(screen.getByText('Option 1')).toBeInTheDocument(); | ||
expect(screen.getByText('Option 2')).toBeInTheDocument(); | ||
}); | ||
|
||
test('filters options when search term is entered', () => { | ||
render( | ||
<Select | ||
options={mockOptions} | ||
isSearchable | ||
selectAllText="Select All" | ||
clearAllText="Clear All" | ||
/> | ||
); | ||
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i })); | ||
fireEvent.change(screen.getByPlaceholderText('Select an option'), { | ||
target: { value: 'Option 1' }, | ||
}); | ||
expect(screen.getByText('Option 1')).toBeInTheDocument(); | ||
expect(screen.queryByText('Option 2')).toBeNull(); | ||
}); | ||
|
||
test('selects all options in multiple mode', () => { | ||
render( | ||
<Select | ||
options={mockOptions} | ||
isMultiple | ||
selectAllText="Select All" | ||
clearAllText="Clear All" | ||
/> | ||
); | ||
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i })); | ||
fireEvent.click(screen.getByText('Select All')); | ||
|
||
// Find the options and navigate to their checkboxes | ||
const option1Text = screen.queryByText('Option 1'); | ||
const option2Text = screen.queryByText('Option 2'); | ||
const option3Text = screen.queryByText('Option 3'); | ||
|
||
// Get the closest parent div for each option | ||
const option1Div = option1Text?.closest('div'); | ||
const option2Div = option2Text?.closest('div'); | ||
const option3Div = option3Text?.closest('div'); | ||
|
||
// Check if the checkbox within the div have a data-checked attribute | ||
expect(option1Div?.querySelector('label')).toHaveAttribute('data-checked'); | ||
expect(option2Div?.querySelector('label')).toHaveAttribute('data-checked'); | ||
expect(option3Div?.querySelector('label')).toHaveAttribute('data-checked'); | ||
}); | ||
|
||
test('clears all selected options in multiple mode', () => { | ||
render( | ||
<Select | ||
options={mockOptions} | ||
isMultiple | ||
selectAllText="Select All" | ||
clearAllText="Clear All" | ||
/> | ||
); | ||
|
||
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i })); | ||
|
||
fireEvent.click(screen.getByText('Select All')); | ||
|
||
fireEvent.click(screen.getByText('Clear All')); | ||
|
||
// Find the options and navigate to their checkboxes | ||
const option1Text = screen.queryByText('Option 1'); | ||
const option2Text = screen.queryByText('Option 2'); | ||
const option3Text = screen.queryByText('Option 3'); | ||
|
||
// Get the closest parent div for each option | ||
const option1Div = option1Text?.closest('div'); | ||
const option2Div = option2Text?.closest('div'); | ||
const option3Div = option3Text?.closest('div'); | ||
|
||
// Check if the checkbox within the div does not have a data-checked attribute | ||
expect(option1Div?.querySelector('label')).not.toHaveAttribute('data-checked'); | ||
expect(option2Div?.querySelector('label')).not.toHaveAttribute('data-checked'); | ||
expect(option3Div?.querySelector('label')).not.toHaveAttribute('data-checked'); | ||
}); | ||
}); |
Oops, something went wrong.