Skip to content

Commit 2e16508

Browse files
committed
test: add unit tests for MultipleChoiceFilter, SearchFilter, SortDropdown, TableControlBar, and useQuerySettings hooks
1 parent 46c79b0 commit 2e16508

5 files changed

Lines changed: 999 additions & 0 deletions

File tree

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import React from 'react';
2+
import {
3+
render, screen, fireEvent,
4+
} from '@testing-library/react';
5+
import MultipleChoiceFilter from './MultipleChoiceFilter';
6+
7+
describe('MultipleChoiceFilter', () => {
8+
const mockSetFilter = jest.fn();
9+
10+
const defaultProps = {
11+
Header: 'Test Filter',
12+
filterChoices: [
13+
{ name: 'Option 1', number: 5, value: 'option1' },
14+
{ name: 'Option 2', number: 3, value: 'option2' },
15+
{ name: 'Option 3', number: 0, value: 'option3' },
16+
],
17+
filterValue: [],
18+
setFilter: mockSetFilter,
19+
};
20+
21+
beforeEach(() => {
22+
jest.clearAllMocks();
23+
});
24+
25+
it('should render dropdown with correct header', () => {
26+
render(<MultipleChoiceFilter {...defaultProps} />);
27+
28+
expect(screen.getByRole('button')).toBeInTheDocument();
29+
expect(screen.getByText('Test Filter')).toBeInTheDocument();
30+
});
31+
32+
it('should render FilterList icon', () => {
33+
render(<MultipleChoiceFilter {...defaultProps} />);
34+
35+
const button = screen.getByRole('button');
36+
const icon = button.querySelector('svg');
37+
expect(icon).toBeInTheDocument();
38+
});
39+
40+
it('should show all filter choices when dropdown is opened', () => {
41+
render(<MultipleChoiceFilter {...defaultProps} />);
42+
43+
fireEvent.click(screen.getByRole('button'));
44+
45+
expect(screen.getByText('Option 1 (5)')).toBeInTheDocument();
46+
expect(screen.getByText('Option 2 (3)')).toBeInTheDocument();
47+
expect(screen.getByText('Option 3 (0)')).toBeInTheDocument();
48+
});
49+
50+
it('should add value to filter when checkbox is checked', () => {
51+
render(<MultipleChoiceFilter {...defaultProps} />);
52+
53+
fireEvent.click(screen.getByRole('button'));
54+
55+
const checkbox1 = screen.getByLabelText('Option 1');
56+
fireEvent.click(checkbox1);
57+
58+
expect(mockSetFilter).toHaveBeenCalledWith(['option1']);
59+
});
60+
61+
it('should remove value from filter when checkbox is unchecked', () => {
62+
const propsWithSelectedValue = {
63+
...defaultProps,
64+
filterValue: ['option1', 'option2'],
65+
};
66+
67+
render(<MultipleChoiceFilter {...propsWithSelectedValue} />);
68+
69+
fireEvent.click(screen.getByRole('button'));
70+
71+
const checkbox1 = screen.getByLabelText('Option 1');
72+
fireEvent.click(checkbox1);
73+
74+
expect(mockSetFilter).toHaveBeenCalledWith(['option2']);
75+
});
76+
77+
it('should show checked checkboxes for pre-selected values', () => {
78+
const propsWithSelectedValues = {
79+
...defaultProps,
80+
filterValue: ['option1', 'option3'],
81+
};
82+
83+
render(<MultipleChoiceFilter {...propsWithSelectedValues} />);
84+
85+
fireEvent.click(screen.getByRole('button'));
86+
87+
const checkbox1 = screen.getByLabelText('Option 1');
88+
const checkbox2 = screen.getByLabelText('Option 2');
89+
const checkbox3 = screen.getByLabelText('Option 3');
90+
91+
expect(checkbox1).toBeChecked();
92+
expect(checkbox2).not.toBeChecked();
93+
expect(checkbox3).toBeChecked();
94+
});
95+
96+
it('should call setFilter with correct array when adding to existing selections', () => {
97+
const propsWithExistingSelection = {
98+
...defaultProps,
99+
filterValue: ['option2'],
100+
};
101+
102+
render(<MultipleChoiceFilter {...propsWithExistingSelection} />);
103+
104+
fireEvent.click(screen.getByRole('button'));
105+
106+
const checkbox1 = screen.getByLabelText('Option 1');
107+
fireEvent.click(checkbox1);
108+
109+
expect(mockSetFilter).toHaveBeenCalledWith(['option2', 'option1']);
110+
});
111+
});
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React from 'react';
2+
import {
3+
render, screen, fireEvent,
4+
} from '@testing-library/react';
5+
import SearchFilter from './SearchFilter';
6+
7+
describe('SearchFilter', () => {
8+
const mockSetFilter = jest.fn();
9+
10+
const defaultProps = {
11+
filterValue: '',
12+
setFilter: mockSetFilter,
13+
placeholder: 'Search placeholder',
14+
};
15+
16+
beforeEach(() => {
17+
jest.clearAllMocks();
18+
});
19+
20+
it('should render search input with correct placeholder', () => {
21+
render(<SearchFilter {...defaultProps} />);
22+
23+
const input = screen.getByPlaceholderText('Search placeholder');
24+
expect(input).toBeInTheDocument();
25+
expect(input).toHaveAttribute('type', 'text');
26+
});
27+
28+
it('should display empty value when filterValue is undefined', () => {
29+
const propsWithUndefined = { ...defaultProps, filterValue: undefined as any };
30+
render(<SearchFilter {...propsWithUndefined} />);
31+
32+
const input = screen.getByPlaceholderText('Search placeholder');
33+
expect(input).toHaveValue('');
34+
});
35+
36+
it('should display filterValue if provided', () => {
37+
const propsWithString = { ...defaultProps, filterValue: 'test search' };
38+
render(<SearchFilter {...propsWithString} />);
39+
40+
const input = screen.getByPlaceholderText('Search placeholder');
41+
expect(input).toHaveValue('test search');
42+
});
43+
44+
it('should call setFilter with input value when typing', () => {
45+
render(<SearchFilter {...defaultProps} />);
46+
47+
const input = screen.getByPlaceholderText('Search placeholder');
48+
fireEvent.change(input, { target: { value: 'new search term' } });
49+
50+
expect(mockSetFilter).toHaveBeenCalledWith('new search term');
51+
});
52+
53+
it('should call setFilter with undefined when input is cleared', () => {
54+
const propsWithValue = { ...defaultProps, filterValue: 'existing value' as any };
55+
render(<SearchFilter {...propsWithValue} />);
56+
57+
const input = screen.getByPlaceholderText('Search placeholder');
58+
fireEvent.change(input, { target: { value: '' } });
59+
60+
expect(mockSetFilter).toHaveBeenCalledWith(undefined);
61+
});
62+
63+
it('should handle multiple character input correctly', () => {
64+
render(<SearchFilter {...defaultProps} />);
65+
66+
const input = screen.getByPlaceholderText('Search placeholder');
67+
68+
// Type multiple characters
69+
fireEvent.change(input, { target: { value: 'a' } });
70+
expect(mockSetFilter).toHaveBeenCalledWith('a');
71+
72+
fireEvent.change(input, { target: { value: 'ab' } });
73+
expect(mockSetFilter).toHaveBeenCalledWith('ab');
74+
75+
fireEvent.change(input, { target: { value: 'abc' } });
76+
expect(mockSetFilter).toHaveBeenCalledWith('abc');
77+
});
78+
79+
it('should handle different placeholder text', () => {
80+
const customPlaceholder = 'Enter search term here...';
81+
render(<SearchFilter {...defaultProps} placeholder={customPlaceholder} />);
82+
83+
const input = screen.getByPlaceholderText(customPlaceholder);
84+
expect(input).toBeInTheDocument();
85+
expect(input).toHaveAttribute('placeholder', customPlaceholder);
86+
});
87+
});
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import {
2+
render, screen, fireEvent, act,
3+
} from '@testing-library/react';
4+
import { IntlProvider } from '@edx/frontend-platform/i18n';
5+
import { DataTableContext } from '@openedx/paragon';
6+
import SortDropdown from './SortDropdown';
7+
8+
jest.mock('@edx/frontend-platform/i18n', () => jest.requireActual('@edx/frontend-platform/i18n'));
9+
10+
describe('SortDropdown', () => {
11+
const mockToggleSortBy = jest.fn();
12+
13+
const defaultDataTableState = {
14+
sortBy: [],
15+
filters: [],
16+
pageSize: 10,
17+
pageIndex: 0,
18+
};
19+
20+
const mockDataTableContext = {
21+
state: defaultDataTableState,
22+
toggleSortBy: mockToggleSortBy,
23+
};
24+
25+
const renderSortDropdown = (contextOverrides = {}) => {
26+
const contextValue = {
27+
...mockDataTableContext,
28+
...contextOverrides,
29+
};
30+
31+
return render(
32+
<IntlProvider locale="en">
33+
<DataTableContext.Provider value={contextValue}>
34+
<SortDropdown />
35+
</DataTableContext.Provider>
36+
</IntlProvider>,
37+
);
38+
};
39+
40+
beforeEach(() => {
41+
jest.clearAllMocks();
42+
});
43+
44+
it('should render the sort dropdown with default label', () => {
45+
renderSortDropdown();
46+
47+
expect(screen.getByRole('button')).toBeInTheDocument();
48+
expect(screen.getByText('Sort')).toBeInTheDocument();
49+
});
50+
51+
it('should render all sort options when dropdown is opened', () => {
52+
renderSortDropdown();
53+
54+
const toggleButton = screen.getByRole('button');
55+
fireEvent.click(toggleButton);
56+
57+
expect(screen.getByText('Name A-Z')).toBeInTheDocument();
58+
expect(screen.getByText('Name Z-A')).toBeInTheDocument();
59+
expect(screen.getByText('Newest')).toBeInTheDocument();
60+
expect(screen.getByText('Oldest')).toBeInTheDocument();
61+
});
62+
63+
it('should display current sort when a sort is active', () => {
64+
const contextWithSort = {
65+
state: {
66+
...defaultDataTableState,
67+
sortBy: [{ id: 'username', desc: false }],
68+
},
69+
};
70+
71+
renderSortDropdown(contextWithSort);
72+
73+
expect(screen.getByText('Name A-Z')).toBeInTheDocument();
74+
});
75+
76+
it('should display descending sort correctly', () => {
77+
const contextWithSort = {
78+
state: {
79+
...defaultDataTableState,
80+
sortBy: [{ id: 'username', desc: true }],
81+
},
82+
};
83+
84+
renderSortDropdown(contextWithSort);
85+
86+
expect(screen.getByText('Name Z-A')).toBeInTheDocument();
87+
});
88+
89+
it('should display newest sort correctly', () => {
90+
const contextWithSort = {
91+
state: {
92+
...defaultDataTableState,
93+
sortBy: [{ id: 'createdAt', desc: true }],
94+
},
95+
};
96+
97+
renderSortDropdown(contextWithSort);
98+
99+
expect(screen.getByText('Newest')).toBeInTheDocument();
100+
});
101+
102+
it('should display oldest sort correctly', () => {
103+
const contextWithSort = {
104+
state: {
105+
...defaultDataTableState,
106+
sortBy: [{ id: 'createdAt', desc: false }],
107+
},
108+
};
109+
110+
renderSortDropdown(contextWithSort);
111+
112+
expect(screen.getByText('Oldest')).toBeInTheDocument();
113+
});
114+
115+
it('should handle sort selection and call toggleSortBy', () => {
116+
renderSortDropdown();
117+
118+
const toggleButton = screen.getByRole('button');
119+
fireEvent.click(toggleButton);
120+
121+
const nameAZOption = screen.getByText('Name A-Z');
122+
123+
act(() => {
124+
fireEvent.click(nameAZOption);
125+
});
126+
127+
expect(mockToggleSortBy).toHaveBeenCalledWith('username', false);
128+
129+
const nameZAOption = screen.getByText('Name Z-A');
130+
131+
act(() => {
132+
fireEvent.click(nameZAOption);
133+
});
134+
135+
expect(mockToggleSortBy).toHaveBeenCalledWith('username', true);
136+
137+
const newestOption = screen.getByText('Newest');
138+
139+
act(() => {
140+
fireEvent.click(newestOption);
141+
});
142+
143+
expect(mockToggleSortBy).toHaveBeenCalledWith('createdAt', true);
144+
});
145+
146+
it('should mark the active sort option as active', () => {
147+
const contextWithSort = {
148+
state: {
149+
...defaultDataTableState,
150+
sortBy: [{ id: 'username', desc: false }],
151+
},
152+
};
153+
154+
renderSortDropdown(contextWithSort);
155+
156+
const toggleButton = screen.getByRole('button');
157+
fireEvent.click(toggleButton);
158+
159+
// Get all elements with "Name A-Z" text and find the dropdown item
160+
const nameAZOptions = screen.getAllByText('Name A-Z');
161+
const dropdownItem = nameAZOptions.find(element => element.closest('.dropdown-item'));
162+
expect(dropdownItem?.closest('.dropdown-item')).toHaveClass('active');
163+
});
164+
165+
it('should handle undefined sortBy', () => {
166+
const contextWithUndefinedSort = {
167+
state: {
168+
...defaultDataTableState,
169+
sortBy: undefined,
170+
},
171+
};
172+
173+
renderSortDropdown(contextWithUndefinedSort);
174+
175+
expect(screen.getByText('Sort')).toBeInTheDocument();
176+
});
177+
});

0 commit comments

Comments
 (0)