From 20af2ebd32efb1e28572ff12bc48708d2ef0c583 Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Mon, 18 May 2026 10:23:44 +0200 Subject: [PATCH] =?UTF-8?q?feat(gui-v2):=20statusSeverity=20SoT=20map=20+?= =?UTF-8?q?=20bidirectional=20=C2=A78.X=20consistency=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../components-v2/shared/statusSeverity.ts | 59 +++++++++++++++++++ .../utils/statusSeverity.consistency.spec.ts | 30 ++++++++++ 2 files changed, 89 insertions(+) create mode 100644 apps/app/src/components-v2/shared/statusSeverity.ts create mode 100644 apps/app/tests/unit/utils/statusSeverity.consistency.spec.ts diff --git a/apps/app/src/components-v2/shared/statusSeverity.ts b/apps/app/src/components-v2/shared/statusSeverity.ts new file mode 100644 index 00000000..f497d5fa --- /dev/null +++ b/apps/app/src/components-v2/shared/statusSeverity.ts @@ -0,0 +1,59 @@ +// apps/app/src/components-v2/shared/statusSeverity.ts +// Single source of truth for status → PrimeVue Tag severity. +// Seeded VERBATIM from design spec §8 (commit ae0bd2da). Do NOT +// reinterpret here — a wrong severity is a spec-amendment process, +// not an edit to this file. Enforced bidirectionally by +// tests/unit/utils/statusSeverity.consistency.spec.ts (spec §8.X). + +export type TagSeverity = 'success' | 'warn' | 'info' | 'secondary' | 'danger' + +export const STATUS_SEVERITY: Readonly> = Object.freeze({ + // success — terminal-good / fully settled + approved: 'success', + completed: 'success', + confirmed: 'success', + contracted: 'success', + paid_in_full: 'success', + + // warn — organizer action required + pending_approval: 'warn', + pending: 'warn', + applied: 'warn', + option: 'warn', + offered: 'warn', + reverted: 'warn', + + // info — awaiting external party / in-progress, no viewer action + invited: 'info', + requested: 'info', + deposit_paid: 'info', + + // secondary — muted: absent / not-yet-live / archived + none: 'secondary', + draft: 'secondary', + dismissed: 'secondary', + + // danger — terminal-bad + rejected: 'danger', + cancelled: 'danger', + declined: 'danger', + no_show: 'danger', +}) + +const FALLBACK: TagSeverity = 'info' + +/** + * Resolve a status string to a Tag severity. The §8.X consistency + * test guarantees every live enum value is mapped, so the fallback + * is unreachable in a passing build; it exists only as defence in + * depth and emits a dev-only console warning so any gap is loud. + */ +export function statusSeverity(status: string): TagSeverity { + const hit = STATUS_SEVERITY[status] + if (hit) + return hit + if (import.meta.env.DEV) + console.warn(`[statusSeverity] unmapped status "${status}" — falling back to "${FALLBACK}". Add a §8 row + extend the §8.X test.`) + + return FALLBACK +} diff --git a/apps/app/tests/unit/utils/statusSeverity.consistency.spec.ts b/apps/app/tests/unit/utils/statusSeverity.consistency.spec.ts new file mode 100644 index 00000000..81002e6d --- /dev/null +++ b/apps/app/tests/unit/utils/statusSeverity.consistency.spec.ts @@ -0,0 +1,30 @@ +// apps/app/tests/unit/utils/statusSeverity.consistency.spec.ts +import { describe, expect, it } from 'vitest' +import { ShiftAssignmentStatus } from '@/types/shiftAssignment' +import { PersonStatus } from '@/types/person' +import { MatchStatus } from '@/types/identityMatch' +import { ArtistEngagementStatus, PaymentStatus } from '@/types/timetable' +import { STATUS_SEVERITY, statusSeverity } from '@/components-v2/shared/statusSeverity' + +const ENUMS = { ShiftAssignmentStatus, ArtistEngagementStatus, PaymentStatus, PersonStatus, MatchStatus } +const ALL_VALUES = Object.values(ENUMS).flatMap(e => Object.values(e)) as string[] + +describe('statusSeverity §8.X enforcement', () => { + it('completeness: every live enum value resolves to an explicit severity (no dev-fallback)', () => { + const missing = ALL_VALUES.filter(v => !(v in STATUS_SEVERITY)) + + expect(missing, `unmapped enum values: ${missing.join(', ')}`).toEqual([]) + }) + + it('no phantoms: every map key exists in at least one live enum', () => { + const orphans = Object.keys(STATUS_SEVERITY).filter(k => !ALL_VALUES.includes(k)) + + expect(orphans, `orphan keys: ${orphans.join(', ')}`).toEqual([]) + }) + + it('resolver returns the mapped severity and never throws on a known value', () => { + expect(statusSeverity('approved')).toBe('success') + expect(statusSeverity('no_show')).toBe('danger') + expect(statusSeverity('deposit_paid')).toBe('info') + }) +})