Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,57 @@ describe('PayrollConfiguration', () => {
expect(onEvent).not.toHaveBeenCalledWith('runPayroll/calculated', expect.anything())
})

it('does not fire RUN_PAYROLL_CALCULATED with stale data when re-calculating a previously calculated payroll', async () => {
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime })

const firstCalculatedAt = '2025-08-10T12:00:00Z'
const newCalculatedAt = '2025-08-10T14:00:00Z'

currentPayrollData = {
...mockPayrollData,
calculated_at: firstCalculatedAt,
processing_request: null,
}

server.use(
http.put(`${API_BASE_URL}/v1/companies/:company_id/payrolls/:payroll_id/calculate`, () => {
return new HttpResponse(null, { status: 202 })
}),
)

renderWithProviders(<PayrollConfiguration {...defaultProps} />)

await waitFor(() => {
expect(screen.getByText('Alice Anderson')).toBeInTheDocument()
})

const calculateButton = screen.getByRole('button', { name: /calculate/i })
await user.click(calculateButton)

await act(async () => {
await vi.advanceTimersByTimeAsync(6_000)
})

expect(onEvent).not.toHaveBeenCalledWith('runPayroll/calculated', expect.anything())

currentPayrollData = {
...mockPayrollData,
calculated_at: newCalculatedAt,
processing_request: { status: 'calculate_success', errors: [] },
}

await act(async () => {
await vi.advanceTimersByTimeAsync(6_000)
})

await waitFor(() => {
expect(onEvent).toHaveBeenCalledWith(
'runPayroll/calculated',
expect.objectContaining({ payrollId: 'payroll-uuid-1' }),
)
})
})

it('does not make prepare calls while polling', async () => {
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime })
let prepareCallCount = 0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState, type ReactNode } from 'react'
import { useEffect, useRef, useState, type ReactNode } from 'react'
import { usePayrollsGetSuspense } from '@gusto/embedded-api/react-query/payrollsGet'
import { payrollsCalculate } from '@gusto/embedded-api/funcs/payrollsCalculate'
import { useGustoEmbeddedContext } from '@gusto/embedded-api/react-query/_context'
Expand Down Expand Up @@ -66,6 +66,7 @@ export const Root = ({

const [isPolling, setIsPolling] = useState(false)
const [isCalculatingPayroll, setIsCalculatingPayroll] = useState(false)
const previousCalculatedAtRef = useRef<number | null>(null)
const gustoClient = useGustoEmbeddedContext()

const {
Expand Down Expand Up @@ -232,6 +233,7 @@ export const Root = ({

const onCalculatePayroll = async () => {
setPayrollBlockers([])
previousCalculatedAtRef.current = payrollData.payrollShow?.calculatedAt?.getTime() ?? null

await baseSubmitHandler({}, async () => {
const result = await payrollSubmitHandler(async () => {
Expand Down Expand Up @@ -306,14 +308,15 @@ export const Root = ({

useEffect(() => {
if (isCalculatingStatus(payrollData.payrollShow?.processingRequest) && !isPolling) {
previousCalculatedAtRef.current = payrollData.payrollShow?.calculatedAt?.getTime() ?? null
setIsPolling(true)
}
const currentCalculatedAt = payrollData.payrollShow?.calculatedAt
const isNewCalculation = currentCalculatedAt?.getTime() !== previousCalculatedAtRef.current
if (
isPolling &&
isCalculatedStatus(
payrollData.payrollShow?.processingRequest,
payrollData.payrollShow?.calculatedAt,
)
isNewCalculation &&
isCalculatedStatus(payrollData.payrollShow?.processingRequest, currentCalculatedAt)
) {
onEvent(componentEvents.RUN_PAYROLL_CALCULATED, {
payrollId,
Expand Down
Loading