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:
2026-09-15 07:47:25 +02:00
parent fe120ee222
commit 46e89a4359
3 changed files with 327 additions and 359 deletions
+114 -160
View File
@@ -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()