feat(quick-260921-iwr): Rundenliste der Stoppuhr per Test belegt, MergeTab-Entfernen abgesichert

- Wirkungslose eslint-disable-Zeile in stopwatch-widget.tsx ersetzt durch
  Sachhinweis: Zeilen halten keinen Zustand, Rundennummer wird aus Laenge
  und Position berechnet. Keine neue Unterdrueckung, ARRAYKEY bleibt bei 19.
- Neuer Testfall in stopwatch-widget.test.tsx: zwei Runden nacheinander,
  neuere Runde steht oben, Rundennummern 2/1 stimmen zu ihrer eigenen Zeit.
- Neue MergeTab.test.tsx: Entfernen der mittleren Datei laesst genau erste
  und dritte Datei mit eigenem Namen und eigenem Entfernen-Knopf uebrig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 13:55:42 +02:00
parent cfba3c9532
commit 8716fa5234
4 changed files with 527 additions and 1 deletions
@@ -158,6 +158,43 @@ describe('StopwatchWidget', () => {
expect(lapItems.length).toBeGreaterThanOrEqual(1);
});
it('quick-260921-iwr: zwei Runden nacheinander — neuere Runde steht oben, Rundennummern 2 und 1 stimmen zu ihrer eigenen Zeit (belegt den Positionsschluessel bei einer von vorn wachsenden Liste)', async () => {
render(<StopwatchWidget instanceId="sw-laps" config={{}} isEditMode={false} />);
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: /stopwatch\.start/i }));
});
await act(async () => {
vi.advanceTimersByTime(1000);
});
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: /stopwatch\.lap/i }));
});
await act(async () => {
vi.advanceTimersByTime(1000);
});
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: /stopwatch\.lap/i }));
});
const lapItems = screen.getAllByRole('listitem');
expect(lapItems).toHaveLength(2);
// Oben steht die neuere Runde (Nummer 2), unten die aeltere (Nummer 1) —
// die Liste waechst von vorn, newest-first.
expect(lapItems[0]?.textContent).toContain('Runde 2');
expect(lapItems[1]?.textContent).toContain('Runde 1');
// Runde 2 wurde spaeter aufgezeichnet und zeigt daher die laengere Zeit,
// Runde 1 die kuerzere — jede Zeit steht bei ihrer eigenen Nummer.
expect(lapItems[0]?.textContent).toContain('00:02');
expect(lapItems[1]?.textContent).toContain('00:01');
});
it('reload reconstruction: renders ~7000ms elapsed from config with startedAt 5s ago', async () => {
// Fix system time so Date.now() is deterministic
const fixedNow = new Date('2026-07-01T12:00:00.000Z');
@@ -271,8 +271,10 @@ export function StopwatchWidget({ instanceId, config, isEditMode: _isEditMode }:
<div className="mt-1 max-h-32 overflow-y-auto border-t border-border pt-2">
<ul className="space-y-0.5">
{sw.laps.map((lapMs, idx) => (
// Positionsschluessel ist hier vertretbar: die Zeilen halten keinen
// eigenen Zustand, und die angezeigte Rundennummer wird ohnehin aus
// Laenge und Position berechnet (quick-260921-iwr, D-01/D-06).
<li
// eslint-disable-next-line react/no-array-index-key
key={idx}
className="flex justify-between px-1 py-0.5 text-xs text-muted-foreground"
>