Remove every as any from the test suite and enforce it

ESLint turned `no-explicit-any` off for test files, and 219 casts had
accumulated across 12 of them. Two shapes, very different risks: most
were `(useWorkspace as any).mockReturnValue(...)`, mere laziness that
`vi.mocked()` does properly; the rest cast partial fixtures onto API
types, which erases the contract exactly where a test should enforce it.

That second kind matters here specifically. This project's recurring bug
is a field silently dropped between backend and frontend -- the
hand-written mapping in models/plan.py has done it twice, and apiFetch
mishandling structured errors was the same shape. A test that casts an
incomplete fixture to `any` keeps passing through precisely that change.

A typed fixture layer already existed at tests/fixtures/mockData.ts and
was imported by exactly zero tests: the right tool was there and
invisible, so everyone reached for a cast. It is now a set of
`make*(overrides: Partial<T>) => T` factories returning complete, valid
objects, split by domain with a barrel. When a type gains a field, one
factory fails to compile instead of thirty tests silently passing.

With the count at zero the ESLint exemption is deleted, so this cannot
regrow. Removing it immediately caught two `}: any` prop stubs that a
grep for "as any" had missed; those mocks now use the real components'
exported prop types.

`as unknown as T` survives in five places, each a test deliberately
feeding invalid input (an undefined breakdown, missing tierData, a bogus
leave type). CLAUDE.md now documents that as the only accepted escape,
and points at the factories.

interface PollResponse in useScraperFetch.ts is exported so its shape can
be derived rather than duplicated -- pollScraperSession declares
Promise<unknown>, so there was no other honest way to type it.

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 04:17:22 +02:00
parent da7ac84aca
commit fe120ee222
24 changed files with 1905 additions and 1137 deletions
+31
View File
@@ -0,0 +1,31 @@
/** Shared test fixtures — Dashboard analytics (`GET /api/analytics/overview`). */
import type { Age4Projection, WorkspaceOverview } from '@/types'
export function makeAge4Projection(overrides: Partial<Age4Projection> = {}): Age4Projection {
return {
personalDataUuid: 'pd-a',
childId: 'child-1',
birthDate: '2025-06-01',
fourthBirthday: '2029-06-01',
totalDays: 240,
daysUsed: 30,
plannedDaysBeforeB4: 10,
projectedLeftAtB4: 200,
savableAfterB4: 96,
expiringAtB4: 60,
...overrides,
}
}
export function makeWorkspaceOverview(overrides: Partial<WorkspaceOverview> = {}): WorkspaceOverview {
return {
workspaceId: 'ws-1',
childId: 'child-1',
parentProjections: [],
combinedDaysUsed: 30,
combinedDaysLeft: 270,
combinedExpiringAtB4: 100,
...overrides,
}
}
+26
View File
@@ -0,0 +1,26 @@
/** Shared test fixtures — calendar metadata (`GET /api/calendar/days`). */
import type { CalendarDayEntry, CalendarWindow } from '@/types'
export function makeCalendarDayEntry(overrides: Partial<CalendarDayEntry> = {}): CalendarDayEntry {
return {
date: '2026-01-01',
name: null,
dayType: 'working_day',
isOverride: false,
overrideIsWorkingDay: null,
isFree: false,
...overrides,
}
}
export function makeCalendarWindow(overrides: Partial<CalendarWindow> = {}): CalendarWindow {
return {
startYear: 2026,
endYear: 2026,
minYear: 2026,
maxYear: 2035,
days: [],
...overrides,
}
}
+254
View File
@@ -0,0 +1,254 @@
/**
* Shared test fixtures — return values of `src/context/*` and `src/hooks/*`.
*
* None of these hooks export their result interface (e.g. `useAnalytics`'s
* `UseAnalyticsResult` is private to useAnalytics.ts), and this file
* deliberately does not add exports to make that possible — `ReturnType<typeof
* theHook>` gets the same fully-typed result without touching source files.
* `import type` erases at compile time, so this doesn't pull the hooks'
* runtime code (or React) into the test bundle.
*/
import { vi } from 'vitest'
import type { useWorkspace } from '@/context/WorkspaceContext'
import type { useAuth } from '@/context/AuthContext'
import type { useAnalytics } from '@/hooks/useAnalytics'
import type { useParentalLeave } from '@/hooks/useParentalLeave'
import type { usePlannedDays } from '@/hooks/usePlannedDays'
import type { useScraperFetch } from '@/hooks/useScraperFetch'
import type { useWorkspaceMembers } from '@/hooks/useWorkspaceMembers'
import type { useOptimizationTips } from '@/hooks/useOptimizationTips'
import type { useDayOverrides } from '@/hooks/useDayOverrides'
import type { useLeaveWishes } from '@/hooks/useLeaveWishes'
import type { usePlanProposal } from '@/hooks/usePlanProposal'
import type { usePlanApply } from '@/hooks/usePlanApply'
import type { useVacationDays } from '@/hooks/useVacationDays'
import type { useChildSettings } from '@/hooks/useChildSettings'
import type { useCalendarDays } from '@/hooks/useCalendarDays'
import type { useShowWeekNumbers } from '@/hooks/useCalendarPreferences'
import { MOCK_CHILD, MOCK_PARENT_A, MOCK_VACATION_DAYS, MOCK_WORKSPACE, makeParentalLeaveFetch } from './mockData'
import { makeWorkspaceOverview } from './analytics'
import { makeCalendarWindow } from './calendar'
// ---------------------------------------------------------------------------
// Contexts
// ---------------------------------------------------------------------------
export function makeWorkspaceContextValue(
overrides: Partial<ReturnType<typeof useWorkspace>> = {},
): ReturnType<typeof useWorkspace> {
return {
workspace: MOCK_WORKSPACE,
isLoading: false,
error: null,
workspaceId: MOCK_WORKSPACE.id,
setWorkspaceId: vi.fn(),
refetch: vi.fn(),
children: [{ id: MOCK_CHILD.id, name: MOCK_CHILD.name, birth_date: MOCK_CHILD.birthDate }],
selectedChildId: MOCK_CHILD.id,
selectChild: vi.fn(),
personalDataUuid: MOCK_PARENT_A.uuid,
parentName: MOCK_PARENT_A.name,
refreshContext: vi.fn(),
...overrides,
}
}
export function makeAuthContextValue(
overrides: Partial<ReturnType<typeof useAuth>> = {},
): ReturnType<typeof useAuth> {
return {
accessToken: 'token-123',
isAuthenticated: true,
isLoading: false,
login: vi.fn(),
register: vi.fn(),
logout: vi.fn(),
...overrides,
}
}
// ---------------------------------------------------------------------------
// Data-fetching hooks
// ---------------------------------------------------------------------------
export function makeAnalyticsResult(
overrides: Partial<ReturnType<typeof useAnalytics>> = {},
): ReturnType<typeof useAnalytics> {
return {
data: makeWorkspaceOverview(),
isLoading: false,
error: null,
refresh: vi.fn(),
...overrides,
}
}
export function makeParentalLeaveResult(
overrides: Partial<ReturnType<typeof useParentalLeave>> = {},
): ReturnType<typeof useParentalLeave> {
return {
data: makeParentalLeaveFetch(),
isLoading: false,
error: null,
refresh: vi.fn(),
...overrides,
}
}
export function makePlannedDaysResult(
overrides: Partial<ReturnType<typeof usePlannedDays>> = {},
): ReturnType<typeof usePlannedDays> {
return {
data: [],
isLoading: false,
error: null,
refresh: vi.fn(),
add: vi.fn(),
update: vi.fn(),
remove: vi.fn(),
...overrides,
}
}
export function makeScraperFetchResult(
overrides: Partial<ReturnType<typeof useScraperFetch>> = {},
): ReturnType<typeof useScraperFetch> {
return {
sessionId: null,
pollData: null,
resolvedIds: null,
error: null,
isUnauthorized: false,
start: vi.fn(),
cancel: vi.fn(),
isRunning: false,
isTerminal: false,
...overrides,
}
}
export function makeWorkspaceMembersResult(
overrides: Partial<ReturnType<typeof useWorkspaceMembers>> = {},
): ReturnType<typeof useWorkspaceMembers> {
return {
data: [MOCK_PARENT_A],
isLoading: false,
error: null,
refresh: vi.fn(),
...overrides,
}
}
export function makeOptimizationTipsResult(
overrides: Partial<ReturnType<typeof useOptimizationTips>> = {},
): ReturnType<typeof useOptimizationTips> {
return {
data: [],
isLoading: false,
error: null,
refresh: vi.fn(),
dismiss: vi.fn(),
...overrides,
}
}
export function makeDayOverridesResult(
overrides: Partial<ReturnType<typeof useDayOverrides>> = {},
): ReturnType<typeof useDayOverrides> {
return {
data: [],
isLoading: false,
error: null,
upsert: vi.fn(),
remove: vi.fn(),
...overrides,
}
}
export function makeLeaveWishesResult(
overrides: Partial<ReturnType<typeof useLeaveWishes>> = {},
): ReturnType<typeof useLeaveWishes> {
return {
data: [],
isLoading: false,
isRefreshing: false,
error: null,
refresh: vi.fn(),
add: vi.fn(),
update: vi.fn(),
remove: vi.fn(),
...overrides,
}
}
export function makePlanProposalResult(
overrides: Partial<ReturnType<typeof usePlanProposal>> = {},
): ReturnType<typeof usePlanProposal> {
return {
data: null,
isLoading: false,
error: null,
propose: vi.fn(),
...overrides,
}
}
export function makePlanApplyResult(
overrides: Partial<ReturnType<typeof usePlanApply>> = {},
): ReturnType<typeof usePlanApply> {
return {
response: null,
isLoading: false,
error: null,
apply: vi.fn(),
...overrides,
}
}
export function makeVacationDaysResult(
overrides: Partial<ReturnType<typeof useVacationDays>> = {},
): ReturnType<typeof useVacationDays> {
return {
data: [MOCK_VACATION_DAYS],
isLoading: false,
error: null,
upsert: vi.fn(),
...overrides,
}
}
export function makeChildSettingsResult(
overrides: Partial<ReturnType<typeof useChildSettings>> = {},
): ReturnType<typeof useChildSettings> {
return {
data: MOCK_CHILD,
isLoading: false,
error: null,
update: vi.fn(),
...overrides,
}
}
export function makeCalendarDaysResult(
overrides: Partial<ReturnType<typeof useCalendarDays>> = {},
): ReturnType<typeof useCalendarDays> {
return {
data: makeCalendarWindow(),
isLoading: false,
error: null,
refetch: vi.fn(),
...overrides,
}
}
// ---------------------------------------------------------------------------
// Local-storage-backed preference hook (a tuple, not an object)
// ---------------------------------------------------------------------------
export function makeShowWeekNumbersTuple(
show = false,
setShow: (show: boolean) => void = vi.fn(),
): ReturnType<typeof useShowWeekNumbers> {
return [show, setShow]
}
+26
View File
@@ -0,0 +1,26 @@
/**
* Barrel for all typed test fixtures. Import from here:
*
* import { makeWorkspaceOverview, makePlanProposal, MOCK_WORKSPACE } from '../../tests/fixtures'
*
* See the sibling files for what each group covers:
* - mockData.ts domain entities (Workspace, PersonalData, Child, VacationDays,
* ParentalLeaveFetch/TierData, LeaveWish, PlannedLeaveDay,
* DayOverride, Tip)
* - plan.ts the planning engine (PlanProposal and everything nested in it,
* plus the plan-apply request/response shapes)
* - analytics.ts Dashboard analytics (Age4Projection, WorkspaceOverview)
* - calendar.ts calendar metadata (CalendarDayEntry, CalendarWindow)
* - scraper.ts the FK scraper session lifecycle
* - workspace.ts small inline API response shapes (addPartner, etc.)
* - hooks.ts return values of src/context/* and src/hooks/* — these are
* the ones most test files reach for
*/
export * from './mockData'
export * from './plan'
export * from './analytics'
export * from './calendar'
export * from './scraper'
export * from './workspace'
export * from './hooks'
+197 -58
View File
@@ -1,78 +1,217 @@
/**
* Shared test fixtures. Import from here rather than duplicating inline.
* Shared test fixtures — domain entities.
*
* Prefer the `make*` factories over the `MOCK_*` constants in new tests: a
* factory returns a complete, correctly-typed object, so overriding one field
* can't silently leave the rest `undefined`/wrong the way a hand-rolled
* partial object cast with `as any` can. See `frontend/tests/fixtures/index.ts`
* for the full barrel of factories (plan, analytics, calendar, scraper, hooks).
*/
import type { Child, ParentalLeaveFetch, PersonalData, VacationDays, Workspace } from '@/types'
import type {
Child,
DayOverride,
LeaveWish,
ParentalLeaveFetch,
PersonalData,
PlannedLeaveDay,
TierData,
Tip,
VacationDays,
Workspace,
} from '@/types'
export const MOCK_WORKSPACE: Workspace = {
id: 'ws-1',
name: 'Familjen Testsson',
planningGoal: 'balanced',
planningMode: 'solo',
createdAt: '2026-01-01T00:00:00',
// ---------------------------------------------------------------------------
// Workspace
// ---------------------------------------------------------------------------
export function makeWorkspace(overrides: Partial<Workspace> = {}): Workspace {
return {
id: 'ws-1',
name: 'Familjen Testsson',
planningGoal: 'balanced',
planningMode: 'solo',
createdAt: '2026-01-01T00:00:00',
...overrides,
}
}
export const MOCK_PARENT_A: PersonalData = {
uuid: 'pd-a',
workspaceId: 'ws-1',
name: 'Förälder A',
salaryType: 'monthly',
employmentStatus: 'employed',
createdAt: '2026-01-01T00:00:00',
export const MOCK_WORKSPACE: Workspace = makeWorkspace()
// ---------------------------------------------------------------------------
// Personal data (parents)
// ---------------------------------------------------------------------------
export function makePersonalData(overrides: Partial<PersonalData> = {}): PersonalData {
return {
uuid: 'pd-a',
workspaceId: 'ws-1',
name: 'Förälder A',
salaryType: 'monthly',
employmentStatus: 'employed',
createdAt: '2026-01-01T00:00:00',
...overrides,
}
}
export const MOCK_PARENT_B: PersonalData = {
uuid: 'pd-b',
workspaceId: 'ws-1',
name: 'Förälder B',
salaryType: 'monthly',
employmentStatus: 'employed',
createdAt: '2026-01-01T00:00:00',
export const MOCK_PARENT_A: PersonalData = makePersonalData({ uuid: 'pd-a', name: 'Förälder A' })
export const MOCK_PARENT_B: PersonalData = makePersonalData({ uuid: 'pd-b', name: 'Förälder B' })
// ---------------------------------------------------------------------------
// Child
// ---------------------------------------------------------------------------
export function makeChild(overrides: Partial<Child> = {}): Child {
return {
id: 'child-1',
workspaceId: 'ws-1',
name: 'Barnet',
birthDate: '2025-06-01',
preschoolStartDate: null,
preschoolPlanningDaysPerYear: 6,
createdAt: '2026-01-01T00:00:00',
...overrides,
}
}
export const MOCK_CHILD: Child = {
id: 'child-1',
workspaceId: 'ws-1',
name: 'Barnet',
birthDate: '2025-06-01',
preschoolStartDate: null,
preschoolPlanningDaysPerYear: 6,
createdAt: '2026-01-01T00:00:00',
export const MOCK_CHILD: Child = makeChild()
// ---------------------------------------------------------------------------
// Vacation days
// ---------------------------------------------------------------------------
export function makeVacationDays(overrides: Partial<VacationDays> = {}): VacationDays {
return {
id: 'vd-1',
personalDataUuid: 'pd-a',
workspaceId: 'ws-1',
year: 2026,
totalDays: 25,
savedDaysIn: 0,
createdAt: '2026-01-01T00:00:00',
updatedAt: '2026-01-01T00:00:00',
...overrides,
}
}
export const MOCK_VACATION_DAYS: VacationDays = {
id: 'vd-1',
personalDataUuid: 'pd-a',
workspaceId: 'ws-1',
year: 2026,
totalDays: 25,
savedDaysIn: 0,
createdAt: '2026-01-01T00:00:00',
updatedAt: '2026-01-01T00:00:00',
}
export const MOCK_VACATION_DAYS: VacationDays = makeVacationDays()
export const MOCK_PARENTAL_LEAVE: ParentalLeaveFetch = {
id: 'plf-1',
personalDataUuid: 'pd-a',
workspaceId: 'ws-1',
childId: 'child-1',
source: 'manual',
fetchedAt: '2026-05-01T10:00:00',
totalDays: 240,
daysUsed: 80,
daysLeft: 160,
reservedDaysTotal: 90,
reservedDaysLeft: 70,
transferableDaysLeft: 50,
dubbeldagarLeft: 55,
tierData: {
// ---------------------------------------------------------------------------
// Parental leave fetch (+ tier data)
// ---------------------------------------------------------------------------
export function makeTierData(overrides: Partial<TierData> = {}): TierData {
return {
sjukpenningTotal: 390,
sjukpenningUsed: 80,
sjukpenningLeft: 310,
lagstaNivaTotal: 90,
lagstaNivaUsed: 0,
lagstaNivaLeft: 90,
},
createdAt: '2026-05-01T10:00:00',
updatedAt: '2026-05-01T10:00:00',
...overrides,
}
}
export function makeParentalLeaveFetch(overrides: Partial<ParentalLeaveFetch> = {}): ParentalLeaveFetch {
return {
id: 'plf-1',
personalDataUuid: 'pd-a',
workspaceId: 'ws-1',
childId: 'child-1',
source: 'manual',
fetchedAt: '2026-05-01T10:00:00',
totalDays: 240,
daysUsed: 80,
daysLeft: 160,
reservedDaysTotal: 90,
reservedDaysLeft: 70,
transferableDaysLeft: 50,
dubbeldagarLeft: 55,
tierData: makeTierData(),
createdAt: '2026-05-01T10:00:00',
updatedAt: '2026-05-01T10:00:00',
...overrides,
}
}
export const MOCK_PARENTAL_LEAVE: ParentalLeaveFetch = makeParentalLeaveFetch()
// ---------------------------------------------------------------------------
// Leave wishes
// ---------------------------------------------------------------------------
export function makeLeaveWish(overrides: Partial<LeaveWish> = {}): LeaveWish {
return {
id: 'lw-1',
workspaceId: 'ws-1',
childId: null,
name: 'Sommar',
nameMessageKey: null,
windowStartMonth: 6,
windowStartDay: 15,
windowEndMonth: 7,
windowEndDay: 26,
targetWeeks: 6,
recurrence: 'yearly',
firstYear: 2026,
lastYear: null,
priority: 1,
parentMode: 'either',
parentPersonalDataUuid: null,
goalOverride: null,
windowMode: 'fixed',
createdAt: '2026-01-01T00:00:00',
updatedAt: '2026-01-01T00:00:00',
...overrides,
}
}
// ---------------------------------------------------------------------------
// Planned leave days
// ---------------------------------------------------------------------------
export function makePlannedLeaveDay(overrides: Partial<PlannedLeaveDay> = {}): PlannedLeaveDay {
return {
id: 'pld-1',
workspaceId: 'ws-1',
childId: 'child-1',
parentPersonalDataUuid: 'pd-a',
date: '2026-06-15',
dayType: 'vacation',
isDubbeldag: false,
createdAt: '2026-01-01T00:00:00',
updatedAt: '2026-01-01T00:00:00',
...overrides,
}
}
// ---------------------------------------------------------------------------
// Day overrides (calendar exceptions)
// ---------------------------------------------------------------------------
export function makeDayOverride(overrides: Partial<DayOverride> = {}): DayOverride {
return {
id: 'do-1',
workspaceId: 'ws-1',
date: '2026-12-24',
isWorkingDay: false,
note: null,
createdAt: '2026-01-01T00:00:00',
updatedAt: '2026-01-01T00:00:00',
...overrides,
}
}
// ---------------------------------------------------------------------------
// Tips
// ---------------------------------------------------------------------------
export function makeTip(overrides: Partial<Tip> = {}): Tip {
return {
id: 'tip-1',
severity: 'info',
messageKey: 'tips.example',
data: {},
...overrides,
}
}
+182
View File
@@ -0,0 +1,182 @@
/**
* Shared test fixtures — the planning engine (`POST /api/plan/propose`,
* `POST /api/plan/apply`) and its nested shapes.
*/
import type {
CostBreakdown,
DayCollision,
PlanAge4Projection,
PlanApplyResponse,
PlanProposal,
PlanWarning,
PoolBalance,
ProposedDay,
ProposedDayApplyItem,
Rejection,
ShortfallRemedy,
SurplusAdvice,
VacationBalance,
WindowOutcome,
} from '@/types'
export function makeCostBreakdown(overrides: Partial<CostBreakdown> = {}): CostBreakdown {
return { free: 0, vacation: 0, sjukpenning: 0, lagsta: 0, ...overrides }
}
export function makeShortfallRemedy(overrides: Partial<ShortfallRemedy> = {}): ShortfallRemedy {
return {
kind: 'dubbeldag',
messageKey: 'planRemedies.example',
data: {},
daysFreed: 0,
...overrides,
}
}
export function makeProposedDay(overrides: Partial<ProposedDay> = {}): ProposedDay {
return {
date: '2026-06-15',
parentPersonalDataUuid: 'pd-a',
childId: 'child-1',
dayType: 'sjukpenning',
isDubbeldag: false,
wishId: 'lw-1',
reason: 'Sommar',
...overrides,
}
}
/** The `/api/plan/apply` request-side counterpart of `ProposedDay` — no
* `wishId`/`reason`, and `dayType` additionally allows `'free'`. */
export function makeProposedDayApplyItem(
overrides: Partial<ProposedDayApplyItem> = {},
): ProposedDayApplyItem {
return {
date: '2026-06-15',
parentPersonalDataUuid: 'pd-a',
childId: 'child-1',
dayType: 'sjukpenning',
...overrides,
}
}
export function makeWindowOutcome(overrides: Partial<WindowOutcome> = {}): WindowOutcome {
return {
wishId: 'lw-1',
wishName: 'Sommar',
wishNameMessageKey: null,
year: 2026,
startDate: '2026-06-15',
endDate: '2026-07-26',
weeksWished: 6,
weeksFunded: 6,
shortfallDays: 0,
strategy: 'parent_swap',
messageKey: 'planOutcomes.windowExplanation',
data: {},
costBreakdown: makeCostBreakdown({ free: 12, vacation: 19, sjukpenning: 11 }),
remedies: [],
...overrides,
}
}
export function makePlanWarning(overrides: Partial<PlanWarning> = {}): PlanWarning {
return {
kind: 'days_lost_at_age_4',
messageKey: 'planWarnings.days_lost_at_age_4',
data: {},
...overrides,
}
}
export function makePoolBalance(overrides: Partial<PoolBalance> = {}): PoolBalance {
return {
parentPersonalDataUuid: 'pd-a',
childId: 'child-1',
sjukpenningLeft: 310,
lagstaLeft: 90,
dubbeldagarLeft: 55,
...overrides,
}
}
export function makeVacationBalance(overrides: Partial<VacationBalance> = {}): VacationBalance {
return {
parentPersonalDataUuid: 'pd-a',
year: 2026,
totalDays: 25,
savedDaysIn: 0,
alreadyUsed: 0,
isAssumed: false,
...overrides,
}
}
export function makeSurplusAdvice(overrides: Partial<SurplusAdvice> = {}): SurplusAdvice {
return {
kind: 'wish_more_leave',
messageKey: 'planSurplus.wish_more_leave',
daysInvolved: 0,
data: {},
...overrides,
}
}
export function makePlanAge4Projection(overrides: Partial<PlanAge4Projection> = {}): PlanAge4Projection {
return {
parentPersonalDataUuid: 'pd-a',
childId: 'child-1',
fourthBirthday: '2029-06-01',
projectedLeftAtB4: 0,
savableAfterB4: 96,
expiringAtB4: 0,
...overrides,
}
}
export function makePlanProposal(overrides: Partial<PlanProposal> = {}): PlanProposal {
return {
days: [makeProposedDay()],
outcomes: [makeWindowOutcome()],
warnings: [],
poolBalances: [],
vacationBalances: [],
surplusAdvice: [],
age4Projections: [],
...overrides,
}
}
export function makeDayCollision(overrides: Partial<DayCollision> = {}): DayCollision {
return {
date: '2026-06-15',
parentPersonalDataUuid: 'pd-a',
childId: 'child-1',
existingDayType: 'vacation',
proposedDayType: 'sjukpenning',
...overrides,
}
}
export function makeRejection(overrides: Partial<Rejection> = {}): Rejection {
return {
messageKey: 'planApply.rejection.insufficientBalance',
data: {},
...overrides,
}
}
export function makePlanApplyResponse(overrides: Partial<PlanApplyResponse> = {}): PlanApplyResponse {
return {
accepted: true,
createdCount: 0,
skippedIdenticalCount: 0,
skippedFreeCount: 0,
replacedCount: 0,
collisions: [],
rejections: [],
createdDays: [],
...overrides,
}
}
+37
View File
@@ -0,0 +1,37 @@
/**
* Shared test fixtures — the FK scraper session lifecycle
* (`startScraperSession` / `pollScraperSession`, and the `useScraperFetch`
* hook that polls it).
*
* `pollScraperSession`'s declared return type is `Promise<unknown>` (the
* hook casts it internally), so there is nothing to derive a poll-response
* type from at the API layer. `PollResponse` is exported from
* `useScraperFetch.ts` for exactly this reason — see the comment there.
*/
import type { startScraperSession } from '@/services/api'
import type { PollResponse } from '@/hooks/useScraperFetch'
export type ScraperSessionStartResponse = Awaited<ReturnType<typeof startScraperSession>>
export function makeScraperSessionStartResponse(
overrides: Partial<ScraperSessionStartResponse> = {},
): ScraperSessionStartResponse {
return {
session_id: 'sess-123',
status: 'launching',
workspace_id: 'ws-1',
personal_data_uuid: 'pd-a',
child_id: 'child-1',
...overrides,
}
}
/** One factory taking the `status` discriminant, rather than one factory per
* variant — `PollResponse` isn't a discriminated union in the type system
* (every field besides `status` is optional regardless of status), and every
* real call site only varies `status` plus one or two of the optional
* fields. */
export function makePollResponse(overrides: Partial<PollResponse> = {}): PollResponse {
return { status: 'launching', ...overrides }
}
+25
View File
@@ -0,0 +1,25 @@
/**
* Shared test fixtures — small ad hoc API response shapes that aren't a
* named type in `src/types/` (they're inline return-type object literals in
* `src/services/api.ts`). Derived via `Awaited<ReturnType<typeof fn>>`
* instead of redeclaring the shape, so a change to the real signature fails
* the fixture at compile time rather than drifting silently.
*/
import type { addPartner } from '@/services/api'
/** Shared by `addPartner` and `updateParentSettings` — both return the same
* `{ uuid, name, salary_type, employment_status }` wire shape. */
export type PartnerMemberResponse = Awaited<ReturnType<typeof addPartner>>
export function makePartnerMemberResponse(
overrides: Partial<PartnerMemberResponse> = {},
): PartnerMemberResponse {
return {
uuid: 'pd-b',
name: 'Partner',
salary_type: 'monthly',
employment_status: 'employed',
...overrides,
}
}