test(team): Render-Harness, 151 neue Tests und README

Vorher gab es im Repo kein Muster für Komponententests — @testing-library/react
wurde ausschliesslich für renderHook verwendet. test/teamTestUtils.tsx stellt den
fehlenden Kontext bereit (Router, MUI-Theme, frischer QueryClient je Test).

test/setup.ts registriert jetzt afterEach(cleanup). vitest.config.ts setzt
`globals` nicht, deshalb erkennt Testing Library kein globales afterEach und
richtet sein automatisches Cleanup nie ein; ohne diese Zeile stapeln sich
gerenderte Komponenten und getByRole scheitert ab dem zweiten Test einer Datei.

Abgedeckt: Kennzahlenberechnung, Freigabe-, Zurückweisungs-, Anpassungs- und
Rückfrageprozess, lokale Persistenz samt Schemaversion und Demo-Reset,
Badge-Familie, Vorgangs- und Agentenkarte, Aufgaben-Aktivierung,
Einstellungsvalidierung und die Position des Navigationseintrags.

Die Tests haben fünf echte Mängel aufgedeckt, alle in diesem Commit behoben:
- AgentAvatar hatte keinen zugänglichen Namen. MUI reicht `alt` nur an den
  img-Slot weiter, und ohne `src` rendert Avatar gar kein <img> — der Wert landete
  nirgends im DOM. Jetzt role="img" + aria-label direkt am Element.
- WorkItemCard verschluckte objectLabel, wenn keine objectId gesetzt war. Genau
  dort steht bei zuordnungsfreien Vorgängen die entscheidende Einordnung.
- aria-current rendert nicht mehr "false", sondern entfällt.
- AgentChannelBadge war auf `string` statt AgentChannelType typisiert.

414 Tests grün (vorher 263), Build grün, ESLint über den Property-On-Code sauber,
check:tokens unverändert bei 2249.

README ersetzt das unveränderte Vite-Template durch Startanleitung und die
Architekturentscheidungen samt bekannter Altlasten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin SutterandClaude Opus 5 committed 2026-08-03 16:56:20 +02:00
1 parent 78e2dab655
commit a7e05fb7f0
6 files changed
+539 -64

No files matched your search

@@ -0,0 +1,116 @@
/**
* Property On — lokale Persistenz und «Demo zurücksetzen» (§16.2, §20.1).
*
* Die Zusicherung lautet: geänderte Einstellungen, Aufgabenschalter, erledigte
* Vorgänge, Verbindungsstatus und pausierte Mitarbeiter überleben einen Reload —
* und «Demo zurücksetzen» stellt exakt den Auslieferungszustand wieder her.
*/
import { describe, it, expect, beforeEach } from 'vitest'
import {
TEAM_STORAGE_KEYS,
TEAM_SCHEMA_VERSION,
loadPersisted,
loadVersioned,
persist,
persistVersioned,
clearTeamPersistence,
} from '../teamPersistence'
import { MockupTeamAgentProvider } from '../MockupTeamAgentProvider'
import { MockupAgentWorkItemProvider } from '../MockupAgentWorkItemProvider'
import { teamDemoService } from '../../services/teamDemoService'
import { AgentStatus } from '../../domain/teamAgent'
import { mockTeamAgents } from '../../mock-data/teamAgents'
beforeEach(async () => {
clearTeamPersistence()
await MockupTeamAgentProvider.reset()
await MockupAgentWorkItemProvider.reset()
})
describe('Speicherschicht', () => {
it('schreibt und liest einen Bestand', () => {
persist(TEAM_STORAGE_KEYS.AGENTS, { hello: 'welt' })
expect(loadPersisted<{ hello: string }>(TEAM_STORAGE_KEYS.AGENTS)).toEqual({ hello: 'welt' })
})
it('gibt null zurück, wenn nichts gespeichert ist', () => {
expect(loadPersisted(TEAM_STORAGE_KEYS.PROTOCOL)).toBeNull()
})
it('verschluckt unlesbaren Inhalt, statt die Anwendung zu blockieren', () => {
window.localStorage.setItem(TEAM_STORAGE_KEYS.AGENTS, '{ kein gültiges JSON')
expect(loadPersisted(TEAM_STORAGE_KEYS.AGENTS)).toBeNull()
})
it('verwirft Bestände einer fremden Schemaversion', () => {
window.localStorage.setItem(
TEAM_STORAGE_KEYS.AGENTS,
JSON.stringify({ version: TEAM_SCHEMA_VERSION + 99, data: [{ id: 'veraltet' }] }),
)
expect(loadVersioned(TEAM_STORAGE_KEYS.AGENTS)).toBeNull()
})
it('liest einen Bestand der aktuellen Schemaversion', () => {
persistVersioned(TEAM_STORAGE_KEYS.CONNECTIONS, [{ id: 'verbindung-1' }])
expect(loadVersioned<Array<{ id: string }>>(TEAM_STORAGE_KEYS.CONNECTIONS)).toEqual([
{ id: 'verbindung-1' },
])
})
it('löscht sämtliche Property-On-Schlüssel', () => {
for (const key of Object.values(TEAM_STORAGE_KEYS)) persist(key, { a: 1 })
clearTeamPersistence()
for (const key of Object.values(TEAM_STORAGE_KEYS)) {
expect(loadPersisted(key)).toBeNull()
}
})
})
describe('Persistenz der Mitarbeiter', () => {
it('schreibt einen pausierten Mitarbeiter in den Local Storage', async () => {
const target = mockTeamAgents[0]
await MockupTeamAgentProvider.update(target.id, { status: AgentStatus.PAUSED })
const stored = loadVersioned<Array<{ id: string; status: string }>>(TEAM_STORAGE_KEYS.AGENTS)
expect(stored).not.toBeNull()
expect(stored?.find(a => a.id === target.id)?.status).toBe(AgentStatus.PAUSED)
})
it('lässt die übrigen Mitarbeiter unangetastet', async () => {
const target = mockTeamAgents[0]
await MockupTeamAgentProvider.update(target.id, { status: AgentStatus.PAUSED })
const all = await MockupTeamAgentProvider.getAll()
const others = all.filter(a => a.id !== target.id)
expect(others.every(a => a.status === AgentStatus.ACTIVE)).toBe(true)
})
})
describe('Demo zurücksetzen', () => {
it('verwirft lokale Änderungen und stellt die Mockdaten wieder her', async () => {
const target = mockTeamAgents[0]
await MockupTeamAgentProvider.update(target.id, { status: AgentStatus.PAUSED })
expect((await MockupTeamAgentProvider.getById(target.id))?.status).toBe(AgentStatus.PAUSED)
await teamDemoService.reset()
expect((await MockupTeamAgentProvider.getById(target.id))?.status).toBe(AgentStatus.ACTIVE)
for (const key of Object.values(TEAM_STORAGE_KEYS)) {
expect(loadPersisted(key)).toBeNull()
}
})
it('stellt auch die Vorgänge wieder her', async () => {
const { 0: first } = await MockupAgentWorkItemProvider.getAll({ requiresDecision: true })
await MockupAgentWorkItemProvider.update(first.id, { requiresDecision: false })
const afterEdit = await MockupAgentWorkItemProvider.getAll({ requiresDecision: true })
expect(afterEdit.map(i => i.id)).not.toContain(first.id)
await teamDemoService.reset()
const afterReset = await MockupAgentWorkItemProvider.getAll({ requiresDecision: true })
expect(afterReset.map(i => i.id)).toContain(first.id)
})
})