Skip to content

Commit

Permalink
feat(Select): implement select with search and multiple select
Browse files Browse the repository at this point in the history
  • Loading branch information
HabRonan committed Sep 16, 2024
1 parent 2fd7bc8 commit b67fa91
Show file tree
Hide file tree
Showing 6 changed files with 431 additions and 46 deletions.
88 changes: 88 additions & 0 deletions src/components/Select/Select.stories.tsx
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' },
],
};
127 changes: 127 additions & 0 deletions src/components/Select/Select.test.tsx
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');
});
});
Loading

0 comments on commit b67fa91

Please sign in to comment.