Spaces:
Sleeping
Sleeping
File size: 5,696 Bytes
cd99321 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | import { describe, it, expect, vi, beforeEach } from 'vitest'
import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { render } from '../../../tests/helpers/render'
import { resetAllStores, seedStore } from '../../../tests/helpers/store'
import { useVacayStore } from '../../store/vacayStore'
import { useAuthStore } from '../../store/authStore'
import VacayStats from './VacayStats'
const buildStat = (overrides: Record<string, unknown> = {}) => ({
user_id: 1,
person_name: 'Alice',
person_color: '#6366f1',
vacation_days: 25,
used: 10,
remaining: 15,
carried_over: 0,
total_available: 25,
...overrides,
})
const mockLoadStats = vi.fn().mockResolvedValue(undefined)
const mockUpdateVacationDays = vi.fn().mockResolvedValue(undefined)
beforeEach(() => {
resetAllStores()
vi.clearAllMocks()
seedStore(useVacayStore, {
stats: [],
selectedYear: 2025,
isFused: false,
loadStats: mockLoadStats,
updateVacationDays: mockUpdateVacationDays,
})
})
describe('VacayStats', () => {
it('FE-COMP-VACAYSTATS-001: Shows empty state when no stats', () => {
render(<VacayStats />)
expect(screen.getByText('No data')).toBeInTheDocument()
})
it('FE-COMP-VACAYSTATS-002: Calls loadStats on mount', () => {
render(<VacayStats />)
expect(mockLoadStats).toHaveBeenCalledWith(2025)
})
it('FE-COMP-VACAYSTATS-003: Renders stat card with username and values', () => {
seedStore(useVacayStore, { stats: [buildStat()] })
render(<VacayStats />)
expect(screen.getByText('Alice')).toBeInTheDocument()
// used tile shows "10", remaining tile shows "15", vacation_days tile shows "25"
expect(screen.getByText('10')).toBeInTheDocument()
expect(screen.getByText('15')).toBeInTheDocument()
expect(screen.getAllByText('25').length).toBeGreaterThanOrEqual(1)
})
it('FE-COMP-VACAYSTATS-004: Current user stat shows "(you)" label', () => {
seedStore(useAuthStore, { user: { id: 1 } })
seedStore(useVacayStore, { stats: [buildStat({ user_id: 1 })] })
render(<VacayStats />)
expect(screen.getByText(/\(you\)/)).toBeInTheDocument()
})
it('FE-COMP-VACAYSTATS-005: Remaining shown in green when > 3', () => {
// used:5 so fraction is "5/20", remaining:10 is unique
seedStore(useVacayStore, {
stats: [buildStat({ remaining: 10, used: 5, vacation_days: 20, total_available: 20 })],
})
render(<VacayStats />)
expect(screen.getByText('10')).toHaveStyle({ color: '#22c55e' })
})
it('FE-COMP-VACAYSTATS-006: Remaining shown in amber when 1–3', () => {
// used:3, vacation_days:5 so remaining:2 is unique
seedStore(useVacayStore, {
stats: [buildStat({ remaining: 2, used: 3, vacation_days: 5, total_available: 5 })],
})
render(<VacayStats />)
expect(screen.getByText('2')).toHaveStyle({ color: '#f59e0b' })
})
it('FE-COMP-VACAYSTATS-007: Remaining shown in red when negative', () => {
seedStore(useVacayStore, {
stats: [buildStat({ remaining: -3, used: 28, vacation_days: 25, total_available: 25 })],
})
render(<VacayStats />)
expect(screen.getByText('-3')).toHaveStyle({ color: '#ef4444' })
})
it('FE-COMP-VACAYSTATS-008: Clicking entitlement tile opens inline editor', async () => {
const user = userEvent.setup()
seedStore(useAuthStore, { user: { id: 1 } })
seedStore(useVacayStore, { stats: [buildStat({ user_id: 1 })] })
render(<VacayStats />)
// The vacation_days tile shows "25" as a standalone div; click it to trigger edit
await user.click(screen.getByText('25'))
expect(screen.getByRole('spinbutton')).toBeInTheDocument()
})
it('FE-COMP-VACAYSTATS-009: Pressing Enter in editor calls updateVacationDays', async () => {
const user = userEvent.setup()
seedStore(useAuthStore, { user: { id: 1 } })
seedStore(useVacayStore, { stats: [buildStat({ user_id: 1 })] })
render(<VacayStats />)
await user.click(screen.getByText('25'))
const input = screen.getByRole('spinbutton')
await user.clear(input)
await user.type(input, '30')
await user.keyboard('{Enter}')
expect(mockUpdateVacationDays).toHaveBeenCalledWith(2025, 30, 1)
})
it('FE-COMP-VACAYSTATS-010: Pressing Escape cancels edit without saving', async () => {
const user = userEvent.setup()
seedStore(useAuthStore, { user: { id: 1 } })
seedStore(useVacayStore, { stats: [buildStat({ user_id: 1 })] })
render(<VacayStats />)
await user.click(screen.getByText('25'))
const input = screen.getByRole('spinbutton')
await user.clear(input)
await user.type(input, '99')
await user.keyboard('{Escape}')
expect(screen.queryByRole('spinbutton')).not.toBeInTheDocument()
expect(mockUpdateVacationDays).not.toHaveBeenCalled()
})
it('FE-COMP-VACAYSTATS-011: Carry-over badge shown when carried_over > 0', () => {
seedStore(useVacayStore, {
stats: [buildStat({ carried_over: 5 })],
selectedYear: 2025,
})
render(<VacayStats />)
// Renders "+5 from 2024"
expect(screen.getByText(/\+5/)).toBeInTheDocument()
expect(screen.getByText(/2024/)).toBeInTheDocument()
})
it('FE-COMP-VACAYSTATS-012: Non-owner can edit when isFused is true', async () => {
const user = userEvent.setup()
// current user is id:2, stat belongs to id:1 — but isFused=true grants canEdit
seedStore(useAuthStore, { user: { id: 2 } })
seedStore(useVacayStore, {
stats: [buildStat({ user_id: 1 })],
isFused: true,
})
render(<VacayStats />)
await user.click(screen.getByText('25'))
expect(screen.getByRole('spinbutton')).toBeInTheDocument()
})
})
|