@snapdragonsnursery/react-components 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/EmployeeSearchDemo.jsx +275 -0
- package/src/EmployeeSearchModal.jsx +817 -0
- package/src/EmployeeSearchPage.jsx +804 -0
- package/src/EmployeeSearchPage.test.jsx +240 -0
- package/src/components/ChildSearchFilters.test.jsx +2 -30
- package/src/components/EmployeeSearchFilters.jsx +418 -0
- package/src/components/EmployeeSearchFilters.test.jsx +546 -0
- package/src/index.js +7 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// Employee Search Page Component Tests
|
|
2
|
+
// Tests the EmployeeSearchPage component functionality including search, filtering, pagination, and selection
|
|
3
|
+
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
6
|
+
import '@testing-library/jest-dom';
|
|
7
|
+
import EmployeeSearchPage from './EmployeeSearchPage';
|
|
8
|
+
|
|
9
|
+
// Mock MSAL
|
|
10
|
+
jest.mock('@azure/msal-react', () => ({
|
|
11
|
+
useMsal: () => ({
|
|
12
|
+
instance: {
|
|
13
|
+
acquireTokenSilent: jest.fn(),
|
|
14
|
+
},
|
|
15
|
+
accounts: [{ localAccountId: 'test-user-id' }],
|
|
16
|
+
}),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// Mock telemetry
|
|
20
|
+
jest.mock('./telemetry', () => ({
|
|
21
|
+
trackEvent: jest.fn(),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
// Mock utils
|
|
25
|
+
jest.mock('./lib/utils', () => ({
|
|
26
|
+
cn: (...classes) => classes.filter(Boolean).join(' '),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Mock fetch
|
|
30
|
+
global.fetch = jest.fn();
|
|
31
|
+
|
|
32
|
+
// Mock process.env
|
|
33
|
+
process.env.VITE_COMMON_API_FUNCTION_KEY = 'test-key';
|
|
34
|
+
process.env.VITE_COMMON_API_BASE_URL = 'https://test-api.example.com';
|
|
35
|
+
|
|
36
|
+
// Mock the UI components
|
|
37
|
+
jest.mock('./components/ui/input', () => ({
|
|
38
|
+
Input: ({ value, onChange, placeholder, ...props }) => {
|
|
39
|
+
return (
|
|
40
|
+
<input
|
|
41
|
+
data-testid="input"
|
|
42
|
+
value={value}
|
|
43
|
+
onChange={onChange}
|
|
44
|
+
placeholder={placeholder}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
jest.mock('./components/ui/button', () => ({
|
|
52
|
+
Button: ({ children, onClick, variant, size, ...props }) => {
|
|
53
|
+
return (
|
|
54
|
+
<button
|
|
55
|
+
data-testid="button"
|
|
56
|
+
onClick={onClick}
|
|
57
|
+
data-variant={variant}
|
|
58
|
+
data-size={size}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
</button>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
jest.mock('./components/ui/table', () => ({
|
|
68
|
+
Table: ({ children, ...props }) => <table data-testid="table" {...props}>{children}</table>,
|
|
69
|
+
TableBody: ({ children, ...props }) => <tbody data-testid="table-body" {...props}>{children}</tbody>,
|
|
70
|
+
TableCell: ({ children, ...props }) => <td data-testid="table-cell" {...props}>{children}</td>,
|
|
71
|
+
TableHead: ({ children, ...props }) => <th data-testid="table-head" {...props}>{children}</th>,
|
|
72
|
+
TableHeader: ({ children, ...props }) => <thead data-testid="table-header" {...props}>{children}</thead>,
|
|
73
|
+
TableRow: ({ children, ...props }) => <tr data-testid="table-row" {...props}>{children}</tr>,
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
jest.mock('./components/ui/pagination', () => ({
|
|
77
|
+
Pagination: ({ children, ...props }) => <nav data-testid="pagination" {...props}>{children}</nav>,
|
|
78
|
+
PaginationContent: ({ children, ...props }) => <div data-testid="pagination-content" {...props}>{children}</div>,
|
|
79
|
+
PaginationEllipsis: ({ ...props }) => <span data-testid="pagination-ellipsis" {...props}>...</span>,
|
|
80
|
+
PaginationItem: ({ children, ...props }) => <div data-testid="pagination-item" {...props}>{children}</div>,
|
|
81
|
+
PaginationLink: ({ children, onClick, isActive, ...props }) => (
|
|
82
|
+
<button data-testid="pagination-link" onClick={onClick} data-active={isActive} {...props}>
|
|
83
|
+
{children}
|
|
84
|
+
</button>
|
|
85
|
+
),
|
|
86
|
+
PaginationNext: ({ children, onClick, ...props }) => (
|
|
87
|
+
<button data-testid="pagination-next" onClick={onClick} {...props}>
|
|
88
|
+
{children}
|
|
89
|
+
</button>
|
|
90
|
+
),
|
|
91
|
+
PaginationPrevious: ({ children, onClick, ...props }) => (
|
|
92
|
+
<button data-testid="pagination-previous" onClick={onClick} {...props}>
|
|
93
|
+
{children}
|
|
94
|
+
</button>
|
|
95
|
+
),
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
// Mock EmployeeSearchFilters
|
|
99
|
+
jest.mock('./components/EmployeeSearchFilters', () => {
|
|
100
|
+
return function MockEmployeeSearchFilters({ filters, onFiltersChange, onApplyFilters, onClearFilters, ...props }) {
|
|
101
|
+
return (
|
|
102
|
+
<div data-testid="employee-search-filters">
|
|
103
|
+
<button onClick={() => onFiltersChange({ ...filters, status: 'Inactive' })}>
|
|
104
|
+
Change Status
|
|
105
|
+
</button>
|
|
106
|
+
<button onClick={() => onApplyFilters(filters)}>
|
|
107
|
+
Apply Filters
|
|
108
|
+
</button>
|
|
109
|
+
<button onClick={onClearFilters}>
|
|
110
|
+
Clear Filters
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const defaultProps = {
|
|
118
|
+
title: 'Employee Search',
|
|
119
|
+
onSelect: jest.fn(),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
describe('EmployeeSearchPage', () => {
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
jest.clearAllMocks();
|
|
125
|
+
global.fetch.mockResolvedValue({
|
|
126
|
+
ok: true,
|
|
127
|
+
json: async () => ({
|
|
128
|
+
success: true,
|
|
129
|
+
data: {
|
|
130
|
+
employees: [],
|
|
131
|
+
pagination: {
|
|
132
|
+
page: 1,
|
|
133
|
+
pageSize: 20,
|
|
134
|
+
totalCount: 0,
|
|
135
|
+
totalPages: 0,
|
|
136
|
+
hasNextPage: false,
|
|
137
|
+
hasPreviousPage: false,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('Basic Rendering', () => {
|
|
145
|
+
it('renders the page title', () => {
|
|
146
|
+
render(<EmployeeSearchPage {...defaultProps} />);
|
|
147
|
+
|
|
148
|
+
expect(screen.getByText('Employee Search')).toBeInTheDocument();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('renders the search input', () => {
|
|
152
|
+
render(<EmployeeSearchPage {...defaultProps} />);
|
|
153
|
+
|
|
154
|
+
expect(screen.getByPlaceholderText('Search by name, employee ID, or email...')).toBeInTheDocument();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('renders the employee search filters', () => {
|
|
158
|
+
render(<EmployeeSearchPage {...defaultProps} />);
|
|
159
|
+
|
|
160
|
+
expect(screen.getByTestId('employee-search-filters')).toBeInTheDocument();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('shows loading state initially', () => {
|
|
164
|
+
render(<EmployeeSearchPage {...defaultProps} />);
|
|
165
|
+
|
|
166
|
+
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('Search Functionality', () => {
|
|
171
|
+
it('renders search input with correct placeholder', () => {
|
|
172
|
+
render(<EmployeeSearchPage {...defaultProps} />);
|
|
173
|
+
|
|
174
|
+
const searchInput = screen.getByPlaceholderText('Search by name, employee ID, or email...');
|
|
175
|
+
expect(searchInput).toBeInTheDocument();
|
|
176
|
+
expect(searchInput).toHaveAttribute('type', 'text');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('allows typing in search input', () => {
|
|
180
|
+
render(<EmployeeSearchPage {...defaultProps} />);
|
|
181
|
+
|
|
182
|
+
const searchInput = screen.getByPlaceholderText('Search by name, employee ID, or email...');
|
|
183
|
+
fireEvent.change(searchInput, { target: { value: 'John' } });
|
|
184
|
+
|
|
185
|
+
expect(searchInput).toHaveValue('John');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('Filter Integration', () => {
|
|
190
|
+
it('renders filter buttons', () => {
|
|
191
|
+
render(<EmployeeSearchPage {...defaultProps} />);
|
|
192
|
+
|
|
193
|
+
expect(screen.getByText('Change Status')).toBeInTheDocument();
|
|
194
|
+
expect(screen.getByText('Apply Filters')).toBeInTheDocument();
|
|
195
|
+
expect(screen.getByText('Clear Filters')).toBeInTheDocument();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('calls filter change handler when filter button is clicked', () => {
|
|
199
|
+
render(<EmployeeSearchPage {...defaultProps} />);
|
|
200
|
+
|
|
201
|
+
const changeStatusButton = screen.getByText('Change Status');
|
|
202
|
+
fireEvent.click(changeStatusButton);
|
|
203
|
+
|
|
204
|
+
// The mock should have been called
|
|
205
|
+
expect(changeStatusButton).toBeInTheDocument();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('Accessibility', () => {
|
|
210
|
+
it('has proper ARIA labels', () => {
|
|
211
|
+
render(<EmployeeSearchPage {...defaultProps} />);
|
|
212
|
+
|
|
213
|
+
const searchInput = screen.getByPlaceholderText('Search by name, employee ID, or email...');
|
|
214
|
+
expect(searchInput).toBeInTheDocument();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('supports keyboard navigation', () => {
|
|
218
|
+
render(<EmployeeSearchPage {...defaultProps} />);
|
|
219
|
+
|
|
220
|
+
const searchInput = screen.getByPlaceholderText('Search by name, employee ID, or email...');
|
|
221
|
+
searchInput.focus();
|
|
222
|
+
|
|
223
|
+
expect(searchInput).toHaveFocus();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('Props Handling', () => {
|
|
228
|
+
it('renders custom title', () => {
|
|
229
|
+
render(<EmployeeSearchPage {...defaultProps} title="Custom Title" />);
|
|
230
|
+
|
|
231
|
+
expect(screen.getByText('Custom Title')).toBeInTheDocument();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('renders custom search placeholder', () => {
|
|
235
|
+
render(<EmployeeSearchPage {...defaultProps} searchPlaceholder="Custom placeholder" />);
|
|
236
|
+
|
|
237
|
+
expect(screen.getByPlaceholderText('Custom placeholder')).toBeInTheDocument();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ChildSearchFilters Component Tests
|
|
2
|
-
// Tests the filter functionality including status, site, date of birth,
|
|
2
|
+
// Tests the filter functionality including status, site, date of birth, and age options
|
|
3
3
|
|
|
4
4
|
import React from "react";
|
|
5
5
|
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
@@ -68,8 +68,6 @@ describe("ChildSearchFilters", () => {
|
|
|
68
68
|
dobTo: "",
|
|
69
69
|
ageFrom: "",
|
|
70
70
|
ageTo: "",
|
|
71
|
-
sortBy: "last_name",
|
|
72
|
-
sortOrder: "asc",
|
|
73
71
|
};
|
|
74
72
|
|
|
75
73
|
const mockSites = [
|
|
@@ -107,8 +105,6 @@ describe("ChildSearchFilters", () => {
|
|
|
107
105
|
expect(screen.getByText("Date of Birth Range")).toBeInTheDocument();
|
|
108
106
|
expect(screen.getByText("Age From (months)")).toBeInTheDocument();
|
|
109
107
|
expect(screen.getByText("Age To (months)")).toBeInTheDocument();
|
|
110
|
-
expect(screen.getByText("Sort By")).toBeInTheDocument();
|
|
111
|
-
expect(screen.getByText("Sort Order")).toBeInTheDocument();
|
|
112
108
|
});
|
|
113
109
|
|
|
114
110
|
it("hides advanced filters when isAdvancedFiltersOpen is false", () => {
|
|
@@ -157,7 +153,7 @@ describe("ChildSearchFilters", () => {
|
|
|
157
153
|
);
|
|
158
154
|
|
|
159
155
|
const selects = screen.getAllByTestId("select");
|
|
160
|
-
expect(selects).toHaveLength(
|
|
156
|
+
expect(selects).toHaveLength(1); // Only Status filter (no site filter when sites=null)
|
|
161
157
|
});
|
|
162
158
|
|
|
163
159
|
it("shows apply button when filters are changed", () => {
|
|
@@ -220,22 +216,6 @@ describe("ChildSearchFilters", () => {
|
|
|
220
216
|
);
|
|
221
217
|
});
|
|
222
218
|
|
|
223
|
-
it("calls onApplyFilters when apply button is clicked with sort filter", () => {
|
|
224
|
-
render(<ChildSearchFilters {...defaultProps} isAdvancedFiltersOpen={true} />);
|
|
225
|
-
|
|
226
|
-
const sortBySelect = screen.getAllByTestId("select")[2];
|
|
227
|
-
fireEvent.change(sortBySelect, { target: { value: "first_name" } });
|
|
228
|
-
|
|
229
|
-
const applyButton = screen.getByText("Apply Filters");
|
|
230
|
-
fireEvent.click(applyButton);
|
|
231
|
-
|
|
232
|
-
expect(defaultProps.onApplyFilters).toHaveBeenCalledWith(
|
|
233
|
-
expect.objectContaining({
|
|
234
|
-
sortBy: "first_name"
|
|
235
|
-
})
|
|
236
|
-
);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
219
|
it("displays correct filter values", () => {
|
|
240
220
|
const filtersWithValues = {
|
|
241
221
|
...mockFilters,
|
|
@@ -245,8 +225,6 @@ describe("ChildSearchFilters", () => {
|
|
|
245
225
|
dobTo: "2020-12-31",
|
|
246
226
|
ageFrom: "12",
|
|
247
227
|
ageTo: "60",
|
|
248
|
-
sortBy: "first_name",
|
|
249
|
-
sortOrder: "desc",
|
|
250
228
|
};
|
|
251
229
|
|
|
252
230
|
render(
|
|
@@ -262,16 +240,12 @@ describe("ChildSearchFilters", () => {
|
|
|
262
240
|
const dateRangePicker = screen.getByTestId("date-range-picker");
|
|
263
241
|
const ageFromInput = screen.getAllByTestId("input")[0];
|
|
264
242
|
const ageToInput = screen.getAllByTestId("input")[1];
|
|
265
|
-
const sortBySelect = screen.getAllByTestId("select")[2];
|
|
266
|
-
const sortOrderSelect = screen.getAllByTestId("select")[3];
|
|
267
243
|
|
|
268
244
|
expect(statusSelect).toHaveValue("inactive");
|
|
269
245
|
expect(siteSelect).toHaveValue("1");
|
|
270
246
|
expect(dateRangePicker).toBeInTheDocument();
|
|
271
247
|
expect(ageFromInput).toHaveValue(12);
|
|
272
248
|
expect(ageToInput).toHaveValue(60);
|
|
273
|
-
expect(sortBySelect).toHaveValue("first_name");
|
|
274
|
-
expect(sortOrderSelect).toHaveValue("desc");
|
|
275
249
|
});
|
|
276
250
|
|
|
277
251
|
it("has correct accessibility attributes", () => {
|
|
@@ -283,8 +257,6 @@ describe("ChildSearchFilters", () => {
|
|
|
283
257
|
expect(screen.getByText("Date of Birth Range")).toBeInTheDocument();
|
|
284
258
|
expect(screen.getByText("Age From (months)")).toBeInTheDocument();
|
|
285
259
|
expect(screen.getByText("Age To (months)")).toBeInTheDocument();
|
|
286
|
-
expect(screen.getByText("Sort By")).toBeInTheDocument();
|
|
287
|
-
expect(screen.getByText("Sort Order")).toBeInTheDocument();
|
|
288
260
|
});
|
|
289
261
|
|
|
290
262
|
it("applies correct CSS classes for dark mode support", () => {
|