Files
crewli/apps/app/tests/unit/lib/timetable/capacity.test.ts
bert.hausmans 39fdc0fa3d test(timetable): Phase C — 67 new tests (pure logic + composables + store + schemas)
apps/app/tests/unit/lib/timetable/:
  - snap.test.ts (5)            — rounding, clamp, edge cases
  - time-grid.test.ts (6)       — px↔min↔ISO roundtrips, formatTickLabel
  - conflict.test.ts (8)        — overlap, endpoint-touching, lane/stage scoping, cancelled exclusion
  - b2b.test.ts (6)             — 0min, 2:59, 3:01, overlap, side-set mapping, threshold constant
  - capacity.test.ts (7)        — null capacity, missing data, warn/critical, crew+guests preference
  - lane.test.ts (8)            — Pass 1 + Pass 2, cascade-bump preview, cancelled exclusion

apps/app/tests/unit/composables/:
  - useTimetableMutations.test.ts (5) — Idempotency-Key header, optimistic + cascade,
                                         409 VersionMismatch surfaced, park sends null,
                                         createStage POST path
  - useDragOrClick.test.ts (3)        — onClick fires under threshold, onDragStart+End
                                         above threshold, Esc cancels mid-flight

apps/app/tests/unit/schemas/timetable.test.ts (8) — payload + response zod parsers
apps/app/tests/unit/lib/idempotencyKey.test.ts (3) — 6-30 char range, 24-hex, uniqueness
apps/app/tests/unit/stores/useTimetableStore.test.ts (5) — defaults, toggleStatus, drag state, null guard

Refactor: useTimetableMutations.move now throws Error instances (no-throw-literal)
so AxiosError.message and the VersionMismatchError shape both bubble through .catch().

Test count: 252 → 319 (+67). All 42 files pass.

Out of scope this session (added to BACKLOG):
- ART-PERFORMANCEBLOCK-COMPONENT-TESTS — Vuetify intentionally not loaded in
  vitest.config.ts; a Vuexy-stub setup for component-mount tests is one PR of
  its own. Pure rendering logic (capacity, B2B, conflict) is fully covered at
  the lib/ layer.
- ART-AXE-CORE-A11Y-TESTS — axe-core not yet installed in the repo. The
  aria-label structure on PerformanceBlock + aria-live on the page entry are
  authored to pass an axe scan when added.
- ART-INTEGRATION-FLOW-TEST — full add → drag → resize → park flow needs
  Vuetify + router + msw setup; defer with the component tests above.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 02:04:10 +02:00

77 lines
2.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest'
import { CAPACITY_TOLERANCE, evaluateCapacity } from '@/lib/timetable/capacity'
import type { ArtistEngagement, Performance, Stage } from '@/types/timetable'
const stage: Stage = {
id: 's1',
event_id: 'ev1',
name: 'Hardstyle',
color: '#ff0000',
capacity: 1000,
sort_order: 0,
created_at: null,
updated_at: null,
}
const perf: Performance = {
id: 'p1',
engagement_id: 'e1',
event_id: 'ev1',
stage_id: 's1',
lane: 0,
lane_resolved: 0,
start_at: null,
end_at: null,
version: 0,
notes: null,
warnings: [],
created_at: null,
updated_at: null,
deleted_at: null,
}
function eng(crew: number, guests: number, draw: number | null = null): ArtistEngagement {
return {
crew_count: crew,
guests_count: guests,
artist: draw === null ? undefined : { default_draw: draw } as ArtistEngagement['artist'],
} as ArtistEngagement
}
describe('evaluateCapacity', () => {
it('returns null when stage has no capacity', () => {
expect(evaluateCapacity(perf, { ...stage, capacity: null }, eng(0, 0, 500))).toBeNull()
})
it('returns null when no expected attendance is available', () => {
expect(evaluateCapacity(perf, stage, eng(0, 0))).toBeNull()
})
it('returns null when below the tolerance', () => {
expect(evaluateCapacity(perf, stage, eng(0, 0, 1100))).toBeNull()
})
it('returns warn when ratio between tolerance and 1.5×', () => {
const result = evaluateCapacity(perf, stage, eng(0, 0, 1200))
expect(result?.level).toBe('warn')
})
it('returns critical when ratio > 1.5', () => {
const result = evaluateCapacity(perf, stage, eng(0, 0, 1700))
expect(result?.level).toBe('critical')
})
it('prefers crew + guests when present', () => {
const result = evaluateCapacity(perf, stage, eng(800, 800))
expect(result?.expected).toBe(1600)
expect(result?.level).toBe('critical')
})
it('exposes the tolerance constant', () => {
expect(CAPACITY_TOLERANCE).toBeGreaterThan(1)
})
})