import { describe, it, expect, beforeEach, vi } from 'vitest' import { render, screen } from '@testing-library/react' import { PlanFold } from './PlanFold' import * as hooks from '@/hooks' import { makeLeaveWishesResult, makePlanProposalResult, makePersonalData, makeLeaveWish, makePlanProposal, makeWindowOutcome, makeSurplusAdvice, makePoolBalance, makeVacationBalance, } from '../../../tests/fixtures' vi.mock('@/hooks') const members = [ makePersonalData({ uuid: 'pd-a', name: 'Alex' }), makePersonalData({ uuid: 'pd-b', name: 'Sam' }), ] describe('PlanFold', () => { beforeEach(() => { vi.clearAllMocks() vi.mocked(hooks.useLeaveWishes).mockReturnValue(makeLeaveWishesResult({ data: [makeLeaveWish()] })) vi.mocked(hooks.usePlanProposal).mockReturnValue(makePlanProposalResult()) }) it('is collapsed by default (a native
element)', () => { render() const details = screen.getByTestId('plan-fold') expect(details.tagName).toBe('DETAILS') expect(details).not.toHaveAttribute('open') }) it('shows a hint instead of a proposal when there are no wishes', () => { vi.mocked(hooks.useLeaveWishes).mockReturnValue(makeLeaveWishesResult({ data: [] })) render() expect(screen.getByTestId('plan-fold-no-wishes')).toHaveTextContent( 'Lägg till önskemål under Planering för att se ett förslag här.', ) }) it('shows the proposal error when the read-only propose call fails', () => { vi.mocked(hooks.usePlanProposal).mockReturnValue( makePlanProposalResult({ error: 'Kunde inte beräkna förslag' }), ) render() expect(screen.getByTestId('plan-fold-proposal-error')).toHaveTextContent('Kunde inte beräkna förslag') }) it('auto-proposes once wishes are loaded, without a manual button press', () => { const propose = vi.fn() vi.mocked(hooks.usePlanProposal).mockReturnValue(makePlanProposalResult({ propose })) render() expect(propose).toHaveBeenCalled() }) it('does not propose again once a proposal is already loaded', () => { const propose = vi.fn() vi.mocked(hooks.usePlanProposal).mockReturnValue( makePlanProposalResult({ propose, data: makePlanProposal() }), ) render() expect(propose).not.toHaveBeenCalled() }) it('does not propose while there are no wishes yet', () => { const propose = vi.fn() vi.mocked(hooks.useLeaveWishes).mockReturnValue(makeLeaveWishesResult({ data: [] })) vi.mocked(hooks.usePlanProposal).mockReturnValue(makePlanProposalResult({ propose })) render() expect(propose).not.toHaveBeenCalled() }) it('does not propose while wishes are still loading', () => { const propose = vi.fn() vi.mocked(hooks.useLeaveWishes).mockReturnValue(makeLeaveWishesResult({ isLoading: true, data: [] })) vi.mocked(hooks.usePlanProposal).mockReturnValue(makePlanProposalResult({ propose })) render() expect(propose).not.toHaveBeenCalled() }) it('does not propose again while a propose call is already in flight', () => { const propose = vi.fn() vi.mocked(hooks.usePlanProposal).mockReturnValue(makePlanProposalResult({ propose, isLoading: true })) render() expect(propose).not.toHaveBeenCalled() }) it('renders per-period wished-vs-funded from the proposal', () => { vi.mocked(hooks.usePlanProposal).mockReturnValue( makePlanProposalResult({ data: makePlanProposal({ outcomes: [makeWindowOutcome({ wishId: 'lw-1', wishName: 'Sommar', year: 2026, weeksWished: 6, weeksFunded: 4 })], }), }), ) render() expect(screen.getByTestId('plan-fold-overview-year-2026')).toHaveTextContent('Sommar: 4/6 veckor') }) it('shows the empty-horizon message when the proposal has no outcomes', () => { vi.mocked(hooks.usePlanProposal).mockReturnValue( makePlanProposalResult({ data: makePlanProposal({ outcomes: [] }) }), ) render() expect(screen.getByTestId('plan-fold-overview')).toHaveTextContent('Inga fönster inom planeringshorisonten.') }) it('renders surplus advice with a resolved parent name, never a raw uuid', () => { vi.mocked(hooks.usePlanProposal).mockReturnValue( makePlanProposalResult({ data: makePlanProposal({ surplusAdvice: [ makeSurplusAdvice({ kind: 'wish_more_leave', messageKey: 'planSurplusAdvice.wish_more_leave', daysInvolved: 12, data: { parent_personal_data_uuid: 'pd-b', child_id: 'child-1', weeks: 2 }, }), ], }), }), ) render() const advice = screen.getByTestId('plan-fold-surplus-advice-0') expect(advice).toHaveTextContent('Sam') expect(advice).not.toHaveTextContent('pd-b') }) it('falls back to a generic label for surplus advice when no member matches', () => { vi.mocked(hooks.usePlanProposal).mockReturnValue( makePlanProposalResult({ data: makePlanProposal({ surplusAdvice: [ makeSurplusAdvice({ kind: 'wish_more_leave', messageKey: 'planSurplusAdvice.wish_more_leave', daysInvolved: 12, data: { parent_personal_data_uuid: 'pd-unknown', child_id: 'child-1', weeks: 2 }, }), ], }), }), ) render() const advice = screen.getByTestId('plan-fold-surplus-advice-0') expect(advice).not.toHaveTextContent('pd-unknown') }) it('renders an unrecognised surplus advice kind/messageKey by falling back to the raw value', () => { vi.mocked(hooks.usePlanProposal).mockReturnValue( makePlanProposalResult({ data: makePlanProposal({ surplusAdvice: [ makeSurplusAdvice({ kind: 'brand_new_kind', messageKey: 'planSurplusAdvice.brand_new_kind', data: {}, }), ], }), }), ) render() const advice = screen.getByTestId('plan-fold-surplus-advice-0') expect(advice).toHaveTextContent('brand_new_kind') }) it('does not render a surplus-advice section when there is none', () => { vi.mocked(hooks.usePlanProposal).mockReturnValue( makePlanProposalResult({ data: makePlanProposal({ surplusAdvice: [] }) }), ) render() expect(screen.queryByTestId('plan-fold-surplus-advice')).not.toBeInTheDocument() }) it('renders pool and vacation balance tables', () => { vi.mocked(hooks.usePlanProposal).mockReturnValue( makePlanProposalResult({ data: makePlanProposal({ poolBalances: [makePoolBalance({ parentPersonalDataUuid: 'pd-a', sjukpenningLeft: 300, lagstaLeft: 80, dubbeldagarLeft: 50 })], vacationBalances: [makeVacationBalance({ parentPersonalDataUuid: 'pd-a', year: 2026, alreadyUsed: 10, totalDays: 25, savedDaysIn: 0 })], }), }), ) render() expect(screen.getByTestId('plan-fold-pool-balances')).toHaveTextContent('Alex') expect(screen.getByTestId('plan-fold-pool-balances')).toHaveTextContent('300') expect(screen.getByTestId('plan-fold-vacation-balances')).toHaveTextContent('25') }) it('renders nothing beyond the summary/hint when there is no proposal yet', () => { vi.mocked(hooks.usePlanProposal).mockReturnValue(makePlanProposalResult({ data: null })) render() expect(screen.queryByTestId('plan-fold-overview')).not.toBeInTheDocument() expect(screen.queryByTestId('plan-fold-balances')).not.toBeInTheDocument() }) })