fix(client): work through the findings from the coverage pass

214 items came out of writing the tests. Real defects are corrected, dead code
is removed rather than covered, and the tests that pinned the old behaviour
move with them.

Also drops two agent coverage scratch files that slipped into the coverage PR
and adds the ignore rule that keeps them out.
This commit is contained in:
Maurice
2026-07-29 11:57:24 +02:00
committed by Maurice
parent a4bf815d24
commit 72a82b3c44
425 changed files with 3155 additions and 1968 deletions
@@ -229,24 +229,28 @@ describe('KategorieGruppe — bulk actions', () => {
await waitFor(() => expect(puts).toEqual([1]))
})
it('FE-W5CAT-012: a throwing toggle during Check All surfaces a save error', async () => {
useTripStore.setState({ togglePackingItem: async () => { throw new Error('offline') } })
it('FE-W5CAT-012: a failing PUT during Check All is reported by the store, once', async () => {
server.use(http.put('/api/trips/1/packing/:itemId', () => new HttpResponse(null, { status: 500 })))
seedStore(useTripStore, { packingItems: [buildPackingItem({ id: 1, name: 'Tent', checked: 0 })] })
const { container } = setup({ items: [buildPackingItem({ id: 1, name: 'Tent', checked: 0 })] })
openCategoryMenu(container)
fireEvent.click(screen.getByText('Check All'))
await waitFor(() => expect(toastSpy).toHaveBeenCalledWith('Failed to save', 'error', undefined))
await waitFor(() => expect(toastSpy).toHaveBeenCalledWith('Error updating item', 'error', undefined))
expect(toastSpy).toHaveBeenCalledTimes(1)
})
it('FE-W5CAT-013: a throwing toggle during Uncheck All surfaces a save error', async () => {
useTripStore.setState({ togglePackingItem: async () => { throw new Error('offline') } })
it('FE-W5CAT-013: a failing PUT during Uncheck All is reported by the store, once', async () => {
server.use(http.put('/api/trips/1/packing/:itemId', () => new HttpResponse(null, { status: 500 })))
seedStore(useTripStore, { packingItems: [buildPackingItem({ id: 1, name: 'Tent', checked: 1 })] })
const { container } = setup({ items: [buildPackingItem({ id: 1, name: 'Tent', checked: 1 })] })
openCategoryMenu(container)
fireEvent.click(screen.getByText('Uncheck All'))
await waitFor(() => expect(toastSpy).toHaveBeenCalledWith('Failed to save', 'error', undefined))
await waitFor(() => expect(toastSpy).toHaveBeenCalledWith('Error updating item', 'error', undefined))
expect(toastSpy).toHaveBeenCalledTimes(1)
})
it('FE-W5CAT-014: Delete List hands the whole category to the panel and closes the menu', async () => {
@@ -98,19 +98,17 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
catch { toast.error(t('packing.toast.renameError')) }
}
// togglePackingItem rolls its own optimistic update back and reports the
// failure itself, so the bulk actions just drive it item by item.
const handleCheckAll = async () => {
try {
for (const item of Array.from(items)) {
if (!item.checked) await togglePackingItem(tripId, item.id, true)
}
} catch { toast.error(t('packing.toast.saveError')) }
for (const item of Array.from(items)) {
if (!item.checked) await togglePackingItem(tripId, item.id, true)
}
}
const handleUncheckAll = async () => {
try {
for (const item of Array.from(items)) {
if (item.checked) await togglePackingItem(tripId, item.id, false)
}
} catch { toast.error(t('packing.toast.saveError')) }
for (const item of Array.from(items)) {
if (item.checked) await togglePackingItem(tripId, item.id, false)
}
}
const handleDeleteAll = async () => {
await onDeleteAll(items)
@@ -304,7 +302,8 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
placeholder={t('packing.addItemPlaceholder')}
style={{ flex: 1, padding: '6px 10px', borderRadius: 8, border: '1px solid var(--border-primary)', fontSize: 'calc(12.5px * var(--fs-scale-body, 1))', fontFamily: 'inherit', outline: 'none', color: 'var(--text-primary)', background: 'var(--bg-input)' }}
/>
<button onClick={() => { if (newItemName.trim()) { onAddItem(kategorie, newItemName.trim()); setNewItemName(''); setTimeout(() => addItemRef.current?.focus(), 30) } }}
{/* disabled while the field is empty, so no extra guard here */}
<button onClick={() => { onAddItem(kategorie, newItemName.trim()); setNewItemName(''); setTimeout(() => addItemRef.current?.focus(), 30) }}
disabled={!newItemName.trim()}
style={{ padding: '5px 8px', borderRadius: 8, border: 'none', background: newItemName.trim() ? 'var(--text-primary)' : 'var(--border-primary)', color: 'var(--bg-primary)', cursor: newItemName.trim() ? 'pointer' : 'default', display: 'flex' }}>
<Plus size={14} />
@@ -120,7 +120,7 @@ function Row({ icon, label, sub, active, onClick }: { icon: React.ReactNode; lab
<button onClick={onClick}
style={{ display: 'flex', alignItems: 'flex-start', gap: 8, width: '100%', padding: '7px 10px', borderRadius: 7, border: 'none', cursor: 'pointer', background: active ? 'var(--bg-tertiary)' : 'none', fontFamily: 'inherit', textAlign: 'left' }}
onMouseEnter={e => { if (!active) e.currentTarget.style.background = 'var(--bg-tertiary)' }}
onMouseLeave={e => { if (!active) e.currentTarget.style.background = active ? 'var(--bg-tertiary)' : 'none' }}>
onMouseLeave={e => { if (!active) e.currentTarget.style.background = 'none' }}>
<span style={{ color: active ? 'var(--accent)' : 'var(--text-muted)', marginTop: 1 }}>{icon}</span>
<span style={{ flex: 1, minWidth: 0 }}>
<span style={{ display: 'block', fontSize: 'calc(12.5px * var(--fs-scale-body, 1))', fontWeight: 600, color: 'var(--text-primary)' }}>{label}</span>