Skip to content

Commit 5ab51de

Browse files
committed
feat(Select): Implement Select with search and multiple select
1 parent 2fd7bc8 commit 5ab51de

File tree

6 files changed

+431
-46
lines changed

6 files changed

+431
-46
lines changed
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React from 'react';
2+
import { Meta, StoryFn } from '@storybook/react';
3+
import { BoemlySelect as Select } from './Select';
4+
5+
export default {
6+
title: 'Components/Select',
7+
component: Select,
8+
argTypes: {
9+
placeholder: { control: { type: 'text' } },
10+
isDisabled: { control: { type: 'boolean' } },
11+
isInvalid: { control: { type: 'boolean' } },
12+
isFullWidth: { control: { type: 'boolean' } },
13+
isSearchable: { control: { type: 'boolean' } },
14+
isMultiple: { control: { type: 'boolean' } },
15+
onChange: { action: 'Select Changed' },
16+
options: { control: 'object' },
17+
},
18+
} as Meta<typeof Select>;
19+
20+
const Template: StoryFn<typeof Select> = (args) => <Select {...args} />;
21+
22+
export const Default = Template.bind({});
23+
Default.args = {};
24+
25+
export const WithSelectedValue = Template.bind({});
26+
WithSelectedValue.args = {
27+
value: 'option_2',
28+
placeholder: 'Select an option',
29+
options: [
30+
{ label: 'Option 1', value: 'option_1' },
31+
{ label: 'Option 2', value: 'option_2' },
32+
{ label: 'Option 3', value: 'option_3' },
33+
],
34+
};
35+
36+
export const WithPlaceholder = Template.bind({});
37+
WithPlaceholder.args = {
38+
placeholder: 'Placeholder',
39+
options: [
40+
{ label: 'Option 1', value: 'option_1' },
41+
{ label: 'Option 2', value: 'option_2' },
42+
{ label: 'Option 3', value: 'option_3' },
43+
],
44+
};
45+
46+
Default.args = {
47+
placeholder: 'Select an option',
48+
options: [
49+
{ label: 'Option 1', value: 'option_1' },
50+
{ label: 'Option 2', value: 'option_2' },
51+
{ label: 'Option 3', value: 'option_3' },
52+
],
53+
};
54+
55+
export const Searchable = Template.bind({});
56+
Searchable.args = {
57+
isSearchable: true,
58+
placeholder: 'Search options...',
59+
options: [
60+
{ label: 'Option 1', value: 'option_1' },
61+
{ label: 'Option 2', value: 'option_2' },
62+
{ label: 'Option 3', value: 'option_3' },
63+
],
64+
};
65+
66+
export const MultiSelect = Template.bind({});
67+
MultiSelect.args = {
68+
isMultiple: true,
69+
placeholder: 'Select multiple options',
70+
options: [
71+
{ label: 'Option 1', value: 'option_1' },
72+
{ label: 'Option 2', value: 'option_2' },
73+
{ label: 'Option 3', value: 'option_3' },
74+
],
75+
};
76+
77+
export const SearchableMultiSelect = Template.bind({});
78+
SearchableMultiSelect.args = {
79+
isSearchable: true,
80+
isMultiple: true,
81+
placeholder: 'Search and select multiple options',
82+
options: [
83+
{ label: 'Option 1', value: 'option_1' },
84+
{ label: 'Option 2', value: 'option_2' },
85+
{ label: 'Option 3', value: 'option_3' },
86+
{ label: 'Option 4', value: 'option_4' },
87+
],
88+
};

src/components/Select/Select.test.tsx

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import React from 'react';
2+
import { render, screen, fireEvent } from '@testing-library/react';
3+
import { Select } from '.';
4+
5+
const mockOptions = [
6+
{ label: 'Option 1', value: '1' },
7+
{ label: 'Option 2', value: '2' },
8+
{ label: 'Option 3', value: '3' },
9+
];
10+
11+
describe('Select', () => {
12+
test('renders with placeholder text', () => {
13+
render(<Select options={mockOptions} placeholder="Select an option from these options" />);
14+
expect(screen.getByText('Select an option from these options')).toBeInTheDocument();
15+
});
16+
17+
test('opens dropdown when clicked', () => {
18+
render(<Select options={mockOptions} />);
19+
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i }));
20+
expect(screen.getByText('Option 1')).toBeInTheDocument();
21+
});
22+
23+
test('selects an option when clicked', () => {
24+
render(<Select options={mockOptions} />);
25+
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i }));
26+
fireEvent.click(screen.getByText('Option 1'));
27+
expect(screen.getByText('Option 1')).toBeInTheDocument();
28+
});
29+
30+
test('handles single selection mode', () => {
31+
render(<Select options={mockOptions} value="1" />);
32+
expect(screen.getByText('Option 1')).toBeInTheDocument();
33+
});
34+
35+
test('handles multiple selection mode', () => {
36+
render(
37+
<Select
38+
options={mockOptions}
39+
isMultiple
40+
selectAllText="Select All"
41+
clearAllText="Clear All"
42+
/>
43+
);
44+
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i }));
45+
fireEvent.click(screen.getByText('Option 1'));
46+
fireEvent.click(screen.getByText('Option 2'));
47+
expect(screen.getByText('Option 1')).toBeInTheDocument();
48+
expect(screen.getByText('Option 2')).toBeInTheDocument();
49+
});
50+
51+
test('filters options when search term is entered', () => {
52+
render(
53+
<Select
54+
options={mockOptions}
55+
isSearchable
56+
selectAllText="Select All"
57+
clearAllText="Clear All"
58+
/>
59+
);
60+
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i }));
61+
fireEvent.change(screen.getByPlaceholderText('Select an option'), {
62+
target: { value: 'Option 1' },
63+
});
64+
expect(screen.getByText('Option 1')).toBeInTheDocument();
65+
expect(screen.queryByText('Option 2')).toBeNull();
66+
});
67+
68+
test('selects all options in multiple mode', () => {
69+
render(
70+
<Select
71+
options={mockOptions}
72+
isMultiple
73+
selectAllText="Select All"
74+
clearAllText="Clear All"
75+
/>
76+
);
77+
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i }));
78+
fireEvent.click(screen.getByText('Select All'));
79+
80+
// Find the options and navigate to their checkboxes
81+
const option1Text = screen.queryByText('Option 1');
82+
const option2Text = screen.queryByText('Option 2');
83+
const option3Text = screen.queryByText('Option 3');
84+
85+
// Get the closest parent div for each option
86+
const option1Div = option1Text?.closest('div');
87+
const option2Div = option2Text?.closest('div');
88+
const option3Div = option3Text?.closest('div');
89+
90+
// Check if the checkbox within the div have a data-checked attribute
91+
expect(option1Div?.querySelector('label')).toHaveAttribute('data-checked');
92+
expect(option2Div?.querySelector('label')).toHaveAttribute('data-checked');
93+
expect(option3Div?.querySelector('label')).toHaveAttribute('data-checked');
94+
});
95+
96+
test('clears all selected options in multiple mode', () => {
97+
render(
98+
<Select
99+
options={mockOptions}
100+
isMultiple
101+
selectAllText="Select All"
102+
clearAllText="Clear All"
103+
/>
104+
);
105+
106+
fireEvent.click(screen.getByRole('button', { name: /toggle dropdown/i }));
107+
108+
fireEvent.click(screen.getByText('Select All'));
109+
110+
fireEvent.click(screen.getByText('Clear All'));
111+
112+
// Find the options and navigate to their checkboxes
113+
const option1Text = screen.queryByText('Option 1');
114+
const option2Text = screen.queryByText('Option 2');
115+
const option3Text = screen.queryByText('Option 3');
116+
117+
// Get the closest parent div for each option
118+
const option1Div = option1Text?.closest('div');
119+
const option2Div = option2Text?.closest('div');
120+
const option3Div = option3Text?.closest('div');
121+
122+
// Check if the checkbox within the div does not have a data-checked attribute
123+
expect(option1Div?.querySelector('label')).not.toHaveAttribute('data-checked');
124+
expect(option2Div?.querySelector('label')).not.toHaveAttribute('data-checked');
125+
expect(option3Div?.querySelector('label')).not.toHaveAttribute('data-checked');
126+
});
127+
});

0 commit comments

Comments
 (0)