Skip to content

Commit 9d76c29

Browse files
committed
refactor: extract payroll config data fetching to usePayrollConfigurationData hook
1 parent 05c0d8a commit 9d76c29

File tree

6 files changed

+630
-115
lines changed

6 files changed

+630
-115
lines changed
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
import { screen, waitFor } from '@testing-library/react'
3+
import userEvent from '@testing-library/user-event'
4+
import { http, HttpResponse } from 'msw'
5+
import { PayrollConfiguration } from './PayrollConfiguration'
6+
import { server } from '@/test/mocks/server'
7+
import { renderWithProviders } from '@/test-utils/renderWithProviders'
8+
import { API_BASE_URL } from '@/test/constants'
9+
10+
const createEmployee = (uuid: string, firstName: string, lastName: string, rate = '25.00') => ({
11+
uuid,
12+
first_name: firstName,
13+
last_name: lastName,
14+
payment_method: 'Direct Deposit',
15+
jobs: [
16+
{
17+
uuid: `job-${uuid}`,
18+
title: 'Software Engineer',
19+
primary: true,
20+
compensations: [
21+
{
22+
uuid: `comp-${uuid}`,
23+
rate,
24+
payment_unit: 'Hour',
25+
flsa_status: 'Nonexempt',
26+
},
27+
],
28+
},
29+
],
30+
})
31+
32+
const createCompensation = (employeeUuid: string, grossPay = 1000) => ({
33+
excluded: false,
34+
payment_method: 'Direct Deposit',
35+
memo: null,
36+
fixed_compensations: [],
37+
hourly_compensations: [
38+
{
39+
flsa_status: 'Nonexempt',
40+
name: 'Regular Hours',
41+
job_uuid: `job-${employeeUuid}`,
42+
amount: String(grossPay),
43+
compensation_multiplier: 1.0,
44+
hours: '40.000',
45+
},
46+
],
47+
employee_uuid: employeeUuid,
48+
version: 'v1',
49+
paid_time_off: [],
50+
gross_pay: grossPay,
51+
net_pay: grossPay * 0.8,
52+
check_amount: grossPay * 0.8,
53+
})
54+
55+
const page1Employees = [
56+
createEmployee('emp-1', 'Alice', 'Anderson'),
57+
createEmployee('emp-2', 'Bob', 'Baker'),
58+
createEmployee('emp-3', 'Charlie', 'Clark'),
59+
createEmployee('emp-4', 'Diana', 'Davis'),
60+
createEmployee('emp-5', 'Eve', 'Evans'),
61+
createEmployee('emp-6', 'Frank', 'Foster'),
62+
createEmployee('emp-7', 'Grace', 'Green'),
63+
createEmployee('emp-8', 'Henry', 'Harris'),
64+
createEmployee('emp-9', 'Ivy', 'Irving'),
65+
createEmployee('emp-10', 'Jack', 'Johnson'),
66+
]
67+
68+
const page2Employees = [
69+
createEmployee('emp-11', 'Kate', 'King'),
70+
createEmployee('emp-12', 'Leo', 'Lewis'),
71+
]
72+
73+
const allEmployees = [...page1Employees, ...page2Employees]
74+
75+
const allCompensations = allEmployees.map(emp => createCompensation(emp.uuid))
76+
77+
const mockPayrollData = {
78+
uuid: 'payroll-uuid-1',
79+
payroll_uuid: 'payroll-uuid-1',
80+
company_uuid: 'company-123',
81+
off_cycle: false,
82+
processed: false,
83+
check_date: '2025-08-15',
84+
payroll_deadline: '2025-08-11T17:00:00-07:00',
85+
pay_period: {
86+
start_date: '2025-07-30',
87+
end_date: '2025-08-13',
88+
pay_schedule_uuid: 'schedule-1',
89+
},
90+
employee_compensations: allCompensations,
91+
totals: {
92+
gross_pay: '4000.00',
93+
net_pay: '3200.00',
94+
},
95+
processing_request: null,
96+
}
97+
98+
const mockPaySchedule = {
99+
uuid: 'schedule-1',
100+
frequency: 'Every week',
101+
anchor_pay_date: '2024-01-01',
102+
anchor_end_of_pay_period: '2024-01-07',
103+
custom_name: 'Weekly Schedule',
104+
active: true,
105+
version: 'v1',
106+
}
107+
108+
describe('PayrollConfiguration', () => {
109+
const onEvent = vi.fn()
110+
const defaultProps = {
111+
companyId: 'company-123',
112+
payrollId: 'payroll-uuid-1',
113+
onEvent,
114+
}
115+
116+
beforeEach(() => {
117+
onEvent.mockClear()
118+
119+
server.use(
120+
http.get(`${API_BASE_URL}/v1/companies/:company_id/employees`, ({ request }) => {
121+
const url = new URL(request.url)
122+
const page = parseInt(url.searchParams.get('page') || '1', 10)
123+
const per = parseInt(url.searchParams.get('per') || '10', 10)
124+
125+
const allEmps = allEmployees
126+
const totalCount = allEmps.length
127+
const totalPages = Math.ceil(totalCount / per)
128+
129+
const startIndex = (page - 1) * per
130+
const endIndex = startIndex + per
131+
const pageEmployees = allEmps.slice(startIndex, endIndex)
132+
133+
return HttpResponse.json(pageEmployees, {
134+
headers: {
135+
'x-total-pages': String(totalPages),
136+
'x-total-count': String(totalCount),
137+
'x-page': String(page),
138+
'x-per-page': String(per),
139+
},
140+
})
141+
}),
142+
143+
http.get(`${API_BASE_URL}/v1/companies/:company_id/payrolls/:payroll_id`, () => {
144+
return HttpResponse.json(mockPayrollData)
145+
}),
146+
147+
http.put(
148+
`${API_BASE_URL}/v1/companies/:company_id/payrolls/:payroll_id/prepare`,
149+
async ({ request }) => {
150+
const body = (await request.json()) as { employee_uuids?: string[] } | null
151+
const employeeUuids = body?.employee_uuids
152+
153+
if (employeeUuids && employeeUuids.length > 0) {
154+
const filteredCompensations = allCompensations.filter(comp =>
155+
employeeUuids.includes(comp.employee_uuid),
156+
)
157+
return HttpResponse.json({
158+
...mockPayrollData,
159+
employee_compensations: filteredCompensations,
160+
})
161+
}
162+
163+
return HttpResponse.json(mockPayrollData)
164+
},
165+
),
166+
167+
http.get(`${API_BASE_URL}/v1/companies/:company_id/pay_schedules/:pay_schedule_id`, () => {
168+
return HttpResponse.json(mockPaySchedule)
169+
}),
170+
)
171+
})
172+
173+
describe('initial render', () => {
174+
it('renders employee data correctly on initial load', async () => {
175+
renderWithProviders(<PayrollConfiguration {...defaultProps} />)
176+
177+
await waitFor(() => {
178+
expect(screen.getByText('Alice Anderson')).toBeInTheDocument()
179+
})
180+
181+
expect(screen.getByText('Bob Baker')).toBeInTheDocument()
182+
})
183+
184+
it('displays the payroll configuration page title', async () => {
185+
renderWithProviders(<PayrollConfiguration {...defaultProps} />)
186+
187+
await waitFor(() => {
188+
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument()
189+
})
190+
})
191+
192+
it('shows calculate payroll button', async () => {
193+
renderWithProviders(<PayrollConfiguration {...defaultProps} />)
194+
195+
await waitFor(() => {
196+
expect(screen.getByRole('button', { name: /calculate/i })).toBeInTheDocument()
197+
})
198+
})
199+
})
200+
201+
describe('pagination', () => {
202+
it('shows pagination controls when there are multiple pages', async () => {
203+
renderWithProviders(<PayrollConfiguration {...defaultProps} />)
204+
205+
await waitFor(() => {
206+
expect(screen.getByText('Alice Anderson')).toBeInTheDocument()
207+
})
208+
209+
const nextButton = screen.getByTestId('pagination-next')
210+
expect(nextButton).toBeInTheDocument()
211+
})
212+
213+
it('clicking next page shows different employees', async () => {
214+
const user = userEvent.setup()
215+
216+
renderWithProviders(<PayrollConfiguration {...defaultProps} />)
217+
218+
await waitFor(() => {
219+
expect(screen.getByText('Alice Anderson')).toBeInTheDocument()
220+
})
221+
expect(screen.getByText('Jack Johnson')).toBeInTheDocument()
222+
expect(screen.queryByText('Kate King')).not.toBeInTheDocument()
223+
224+
const nextButton = screen.getByTestId('pagination-next')
225+
await user.click(nextButton)
226+
227+
await waitFor(() => {
228+
expect(screen.getByText('Kate King')).toBeInTheDocument()
229+
})
230+
expect(screen.getByText('Leo Lewis')).toBeInTheDocument()
231+
expect(screen.queryByText('Alice Anderson')).not.toBeInTheDocument()
232+
})
233+
234+
it('clicking previous page returns to prior data', async () => {
235+
const user = userEvent.setup()
236+
237+
renderWithProviders(<PayrollConfiguration {...defaultProps} />)
238+
239+
await waitFor(() => {
240+
expect(screen.getByText('Alice Anderson')).toBeInTheDocument()
241+
})
242+
243+
const nextButton = screen.getByTestId('pagination-next')
244+
await user.click(nextButton)
245+
246+
await waitFor(() => {
247+
expect(screen.getByText('Kate King')).toBeInTheDocument()
248+
})
249+
250+
const prevButton = screen.getByTestId('pagination-previous')
251+
await user.click(prevButton)
252+
253+
await waitFor(() => {
254+
expect(screen.getByText('Alice Anderson')).toBeInTheDocument()
255+
})
256+
expect(screen.getByText('Bob Baker')).toBeInTheDocument()
257+
})
258+
259+
it('employee compensations stay in sync with employee details across page changes', async () => {
260+
const user = userEvent.setup()
261+
262+
renderWithProviders(<PayrollConfiguration {...defaultProps} />)
263+
264+
await waitFor(() => {
265+
expect(screen.getByText('Alice Anderson')).toBeInTheDocument()
266+
})
267+
expect(screen.getByText('Jack Johnson')).toBeInTheDocument()
268+
269+
const nextButton = screen.getByTestId('pagination-next')
270+
await user.click(nextButton)
271+
272+
await waitFor(() => {
273+
expect(screen.getByText('Kate King')).toBeInTheDocument()
274+
})
275+
expect(screen.getByText('Leo Lewis')).toBeInTheDocument()
276+
expect(screen.queryByText('Alice Anderson')).not.toBeInTheDocument()
277+
expect(screen.queryByText('Jack Johnson')).not.toBeInTheDocument()
278+
})
279+
})
280+
})

0 commit comments

Comments
 (0)