Build plan-shape test data with the typed factories
PlanResults, Plan and usePlanProposal still hand-rolled proposal objects inline -- roughly 29 nested literals in PlanResults alone. Hand-writing a deep shape is the pressure that produced 219 `as any` casts across this suite, so the factories existing but going unused left the trap armed. Values an assertion depends on stay inline as overrides; the factories supply only the scaffolding a reader does not need to see. A type gaining a field now breaks one factory instead of silently passing thirty tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXMhc9nT6sNz1SL4j8dznL
This commit is contained in:
+114
-160
@@ -10,6 +10,14 @@ import {
|
||||
makeWorkspaceMembersResult,
|
||||
makePlanProposalResult,
|
||||
makePlanApplyResult,
|
||||
makeLeaveWish,
|
||||
makePlanProposal,
|
||||
makeProposedDay,
|
||||
makeWindowOutcome,
|
||||
makePlanWarning,
|
||||
makeDayCollision,
|
||||
makeRejection,
|
||||
makePlanApplyResponse,
|
||||
} from '../../tests/fixtures'
|
||||
|
||||
vi.mock('@/hooks')
|
||||
@@ -42,52 +50,38 @@ function renderPlanInEnglish() {
|
||||
}
|
||||
|
||||
|
||||
const mockWish = {
|
||||
const mockWish = makeLeaveWish({
|
||||
id: 'lw1',
|
||||
workspaceId: 'ws-1',
|
||||
childId: null,
|
||||
name: 'Sommar',
|
||||
nameMessageKey: 'presets.sommar',
|
||||
windowStartMonth: 6,
|
||||
windowStartDay: 15,
|
||||
windowEndMonth: 7,
|
||||
windowEndDay: 26,
|
||||
targetWeeks: 6,
|
||||
recurrence: 'yearly' as const,
|
||||
firstYear: 2026,
|
||||
lastYear: null,
|
||||
priority: 1,
|
||||
parentMode: 'either' as const,
|
||||
parentPersonalDataUuid: null,
|
||||
goalOverride: null,
|
||||
windowMode: 'fixed' as const,
|
||||
createdAt: '2026-01-01',
|
||||
updatedAt: '2026-01-01',
|
||||
}
|
||||
})
|
||||
|
||||
const mockProposal = {
|
||||
const mockProposal = makePlanProposal({
|
||||
days: [
|
||||
{
|
||||
makeProposedDay({
|
||||
date: '2026-06-15',
|
||||
parentPersonalDataUuid: 'pd-1',
|
||||
childId: 'c-1',
|
||||
dayType: 'sjukpenning' as const,
|
||||
isDubbeldag: false,
|
||||
dayType: 'sjukpenning',
|
||||
wishId: 'lw1',
|
||||
reason: 'Sommar',
|
||||
},
|
||||
{
|
||||
}),
|
||||
makeProposedDay({
|
||||
date: '2026-06-16',
|
||||
parentPersonalDataUuid: 'pd-1',
|
||||
childId: 'c-1',
|
||||
dayType: 'vacation' as const,
|
||||
isDubbeldag: false,
|
||||
dayType: 'vacation',
|
||||
wishId: 'lw1',
|
||||
reason: 'Sommar',
|
||||
},
|
||||
}),
|
||||
],
|
||||
outcomes: [
|
||||
{
|
||||
makeWindowOutcome({
|
||||
wishId: 'lw1',
|
||||
wishName: 'Sommar',
|
||||
wishNameMessageKey: 'presets.sommar',
|
||||
@@ -96,26 +90,15 @@ const mockProposal = {
|
||||
endDate: '2026-07-26',
|
||||
weeksWished: 6,
|
||||
weeksFunded: 6,
|
||||
shortfallDays: 0,
|
||||
strategy: 'parent_swap',
|
||||
messageKey: 'planOutcomes.windowExplanation',
|
||||
data: { wish_name: 'Sommar', year: 2026, weeks_wished: 6, weeks_funded: 6, shortfall_days: 0 },
|
||||
costBreakdown: { free: 12, vacation: 19, sjukpenning: 11, lagsta: 0 },
|
||||
remedies: [],
|
||||
},
|
||||
}),
|
||||
],
|
||||
warnings: [
|
||||
{
|
||||
kind: 'days_lost_at_age_4',
|
||||
messageKey: 'planWarnings.days_lost_at_age_4',
|
||||
makePlanWarning({
|
||||
data: { parent_personal_data_uuid: 'pd-1', expiring_days: 117, fourth_birthday: '2030-03-01' },
|
||||
},
|
||||
}),
|
||||
],
|
||||
poolBalances: [],
|
||||
vacationBalances: [],
|
||||
surplusAdvice: [],
|
||||
age4Projections: [],
|
||||
}
|
||||
})
|
||||
|
||||
describe('Plan', () => {
|
||||
let mockAddWish: ReturnType<typeof vi.fn>
|
||||
@@ -130,16 +113,12 @@ describe('Plan', () => {
|
||||
mockUpdateWish = vi.fn().mockResolvedValue(mockWish)
|
||||
mockRemoveWish = vi.fn().mockResolvedValue(undefined)
|
||||
mockPropose = vi.fn().mockResolvedValue(undefined)
|
||||
mockApply = vi.fn().mockResolvedValue({
|
||||
accepted: true,
|
||||
createdCount: 2,
|
||||
skippedIdenticalCount: 0,
|
||||
skippedFreeCount: 0,
|
||||
replacedCount: 0,
|
||||
collisions: [],
|
||||
rejections: [],
|
||||
createdDays: [],
|
||||
})
|
||||
mockApply = vi.fn().mockResolvedValue(
|
||||
makePlanApplyResponse({
|
||||
accepted: true,
|
||||
createdCount: 2,
|
||||
})
|
||||
)
|
||||
|
||||
vi.mocked(useWorkspace).mockReturnValue(
|
||||
makeWorkspaceContextValue({
|
||||
@@ -424,24 +403,20 @@ describe('Plan', () => {
|
||||
})
|
||||
)
|
||||
|
||||
mockApply.mockResolvedValue({
|
||||
accepted: false,
|
||||
createdCount: 0,
|
||||
skippedIdenticalCount: 0,
|
||||
skippedFreeCount: 0,
|
||||
replacedCount: 0,
|
||||
collisions: [
|
||||
{
|
||||
date: '2026-06-15',
|
||||
parentPersonalDataUuid: 'pd-1',
|
||||
childId: 'c-1',
|
||||
existingDayType: 'vacation',
|
||||
proposedDayType: 'sjukpenning',
|
||||
},
|
||||
],
|
||||
rejections: [],
|
||||
createdDays: [],
|
||||
})
|
||||
mockApply.mockResolvedValue(
|
||||
makePlanApplyResponse({
|
||||
accepted: false,
|
||||
collisions: [
|
||||
makeDayCollision({
|
||||
date: '2026-06-15',
|
||||
parentPersonalDataUuid: 'pd-1',
|
||||
childId: 'c-1',
|
||||
existingDayType: 'vacation',
|
||||
proposedDayType: 'sjukpenning',
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
renderPlan()
|
||||
|
||||
@@ -481,16 +456,13 @@ describe('Plan', () => {
|
||||
rejections: [],
|
||||
createdDays: [],
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
accepted: true,
|
||||
createdCount: 2,
|
||||
skippedIdenticalCount: 0,
|
||||
skippedFreeCount: 0,
|
||||
replacedCount: 1,
|
||||
collisions: [],
|
||||
rejections: [],
|
||||
createdDays: [],
|
||||
})
|
||||
.mockResolvedValueOnce(
|
||||
makePlanApplyResponse({
|
||||
accepted: true,
|
||||
createdCount: 2,
|
||||
replacedCount: 1,
|
||||
})
|
||||
)
|
||||
|
||||
renderPlan()
|
||||
|
||||
@@ -517,24 +489,20 @@ describe('Plan', () => {
|
||||
})
|
||||
)
|
||||
|
||||
mockApply.mockResolvedValue({
|
||||
accepted: false,
|
||||
createdCount: 0,
|
||||
skippedIdenticalCount: 0,
|
||||
skippedFreeCount: 0,
|
||||
replacedCount: 0,
|
||||
collisions: [
|
||||
{
|
||||
date: '2026-06-15',
|
||||
parentPersonalDataUuid: 'pd-1',
|
||||
childId: 'c-1',
|
||||
existingDayType: 'vacation',
|
||||
proposedDayType: 'sjukpenning',
|
||||
},
|
||||
],
|
||||
rejections: [],
|
||||
createdDays: [],
|
||||
})
|
||||
mockApply.mockResolvedValue(
|
||||
makePlanApplyResponse({
|
||||
accepted: false,
|
||||
collisions: [
|
||||
makeDayCollision({
|
||||
date: '2026-06-15',
|
||||
parentPersonalDataUuid: 'pd-1',
|
||||
childId: 'c-1',
|
||||
existingDayType: 'vacation',
|
||||
proposedDayType: 'sjukpenning',
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
renderPlan()
|
||||
|
||||
@@ -558,21 +526,18 @@ describe('Plan', () => {
|
||||
})
|
||||
)
|
||||
|
||||
mockApply.mockResolvedValue({
|
||||
accepted: true,
|
||||
createdCount: 1,
|
||||
skippedIdenticalCount: 0,
|
||||
skippedFreeCount: 0,
|
||||
replacedCount: 0,
|
||||
collisions: [],
|
||||
rejections: [
|
||||
{
|
||||
messageKey: 'planApply.rejection.insufficientBalance',
|
||||
data: { tier: 'sjukpenning', needed: 2, remaining: 1 },
|
||||
},
|
||||
],
|
||||
createdDays: [],
|
||||
})
|
||||
mockApply.mockResolvedValue(
|
||||
makePlanApplyResponse({
|
||||
accepted: true,
|
||||
createdCount: 1,
|
||||
rejections: [
|
||||
makeRejection({
|
||||
messageKey: 'planApply.rejection.insufficientBalance',
|
||||
data: { tier: 'sjukpenning', needed: 2, remaining: 1 },
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
renderPlan()
|
||||
|
||||
@@ -593,21 +558,18 @@ describe('Plan', () => {
|
||||
})
|
||||
)
|
||||
|
||||
mockApply.mockResolvedValue({
|
||||
accepted: true,
|
||||
createdCount: 1,
|
||||
skippedIdenticalCount: 0,
|
||||
skippedFreeCount: 0,
|
||||
replacedCount: 0,
|
||||
collisions: [],
|
||||
rejections: [
|
||||
{
|
||||
messageKey: 'planApply.rejection.insufficientBalance',
|
||||
data: { tier: 'sjukpenning', needed: 2, remaining: 1 },
|
||||
},
|
||||
],
|
||||
createdDays: [],
|
||||
})
|
||||
mockApply.mockResolvedValue(
|
||||
makePlanApplyResponse({
|
||||
accepted: true,
|
||||
createdCount: 1,
|
||||
rejections: [
|
||||
makeRejection({
|
||||
messageKey: 'planApply.rejection.insufficientBalance',
|
||||
data: { tier: 'sjukpenning', needed: 2, remaining: 1 },
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
vi.stubGlobal('localStorage', {
|
||||
getItem: () => null,
|
||||
@@ -654,21 +616,21 @@ describe('Plan', () => {
|
||||
it('clears success message when user proposes again after successful apply', async () => {
|
||||
// Create two different proposals
|
||||
const mockProposal1 = mockProposal
|
||||
const mockProposal2 = {
|
||||
...mockProposal,
|
||||
const mockProposal2 = makePlanProposal({
|
||||
days: [
|
||||
...mockProposal.days,
|
||||
{
|
||||
makeProposedDay({
|
||||
date: '2026-07-01',
|
||||
parentPersonalDataUuid: 'pd-1',
|
||||
childId: 'c-1',
|
||||
dayType: 'sjukpenning' as const,
|
||||
isDubbeldag: false,
|
||||
dayType: 'sjukpenning',
|
||||
wishId: 'lw1',
|
||||
reason: 'Sommar',
|
||||
},
|
||||
}),
|
||||
],
|
||||
}
|
||||
outcomes: mockProposal.outcomes,
|
||||
warnings: mockProposal.warnings,
|
||||
})
|
||||
|
||||
// Track proposal state changes
|
||||
let proposalData = mockProposal1
|
||||
@@ -743,24 +705,20 @@ describe('Plan', () => {
|
||||
)
|
||||
|
||||
// Mock apply to return a collision on first call
|
||||
mockApply.mockResolvedValue({
|
||||
accepted: false,
|
||||
createdCount: 0,
|
||||
skippedIdenticalCount: 0,
|
||||
skippedFreeCount: 0,
|
||||
replacedCount: 0,
|
||||
collisions: [
|
||||
{
|
||||
date: '2026-06-15',
|
||||
parentPersonalDataUuid: 'pd-1',
|
||||
childId: 'c-1',
|
||||
existingDayType: 'vacation',
|
||||
proposedDayType: 'sjukpenning',
|
||||
},
|
||||
],
|
||||
rejections: [],
|
||||
createdDays: [],
|
||||
})
|
||||
mockApply.mockResolvedValue(
|
||||
makePlanApplyResponse({
|
||||
accepted: false,
|
||||
collisions: [
|
||||
makeDayCollision({
|
||||
date: '2026-06-15',
|
||||
parentPersonalDataUuid: 'pd-1',
|
||||
childId: 'c-1',
|
||||
existingDayType: 'vacation',
|
||||
proposedDayType: 'sjukpenning',
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
const { rerender } = renderPlan()
|
||||
|
||||
@@ -797,16 +755,12 @@ describe('Plan', () => {
|
||||
})
|
||||
)
|
||||
|
||||
mockApply.mockResolvedValue({
|
||||
accepted: true,
|
||||
createdCount: 5,
|
||||
skippedIdenticalCount: 0,
|
||||
skippedFreeCount: 0,
|
||||
replacedCount: 0,
|
||||
collisions: [],
|
||||
rejections: [],
|
||||
createdDays: [],
|
||||
})
|
||||
mockApply.mockResolvedValue(
|
||||
makePlanApplyResponse({
|
||||
accepted: true,
|
||||
createdCount: 5,
|
||||
})
|
||||
)
|
||||
|
||||
renderPlan()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user