diff --git a/client/src/components/Packing/PackingListPanelWeightInput.test.tsx b/client/src/components/Packing/PackingListPanelWeightInput.test.tsx index b770de669..64ccaf188 100644 --- a/client/src/components/Packing/PackingListPanelWeightInput.test.tsx +++ b/client/src/components/Packing/PackingListPanelWeightInput.test.tsx @@ -5,7 +5,7 @@ import { fireEvent, render, screen, waitFor } from '../../../tests/helpers/rende import { WeightInput } from './PackingListPanelWeightInput'; /** A row whose stored weight changes only once a save answers, as the store does. */ -function SlowRow({ onSave }: { onSave: (grams: number | null) => void }) { +function SlowRow({ onSave, onStored }: { onSave: (grams: number | null) => void; onStored: () => void }) { const [stored, setStored] = useState(null); return ( void }) { width={36} onSave={(grams) => { onSave(grams); - setTimeout(() => setStored(grams), 300); + setTimeout(() => { + setStored(grams); + onStored(); + }, 300); }} /> ); @@ -22,7 +25,8 @@ function SlowRow({ onSave }: { onSave: (grams: number | null) => void }) { describe('WeightInput', () => { it('FE-COMP-PACKING-095: #3005 keeps every typed digit and saves the whole weight once', async () => { const onSave = vi.fn(); - render(); + const onStored = vi.fn(); + render(); const input = screen.getByRole('textbox') as HTMLInputElement; fireEvent.focus(input); @@ -35,7 +39,9 @@ describe('WeightInput', () => { fireEvent.blur(input); expect(onSave).toHaveBeenCalledTimes(1); expect(onSave).toHaveBeenCalledWith(250); - await waitFor(() => expect(input.value).toBe('250')); + // Wait for the save to answer, so its update lands while the test still runs. + await waitFor(() => expect(onStored).toHaveBeenCalled()); + expect(input.value).toBe('250'); }); it('FE-COMP-PACKING-096: saves on Enter and stores an empty field as no weight', () => { diff --git a/client/src/components/Planner/useDayPlanDragState.ts b/client/src/components/Planner/useDayPlanDragState.ts index a3f6900e1..1e81387f5 100644 --- a/client/src/components/Planner/useDayPlanDragState.ts +++ b/client/src/components/Planner/useDayPlanDragState.ts @@ -40,8 +40,9 @@ export function useDayPlanDragState(dragDataRef: MutableRefObject) { if (!draggingId && !dropTargetKey && !dragOverDayId) return; const settle = (e: MouseEvent | PointerEvent) => { if (e.type === 'mousemove' && e.buttons !== 0) return; - setDraggingId(null); - setDropTargetKey(null); + setDraggingIdState(null); + dropTargetRef.current = null; + setDropTargetKeyState(null); setDragOverDayId(null); dragDataRef.current = null; window.__dragData = null; @@ -52,9 +53,7 @@ export function useDayPlanDragState(dragDataRef: MutableRefObject) { document.removeEventListener('mousemove', settle); document.removeEventListener('pointerdown', settle, true); }; - // The setters close over nothing that changes; the effect follows the drag state only. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [draggingId, dropTargetKey, dragOverDayId]); + }, [draggingId, dropTargetKey, dragOverDayId, dragDataRef]); return { draggingId, diff --git a/client/src/native/keyboardFields.ts b/client/src/native/keyboardFields.ts index e9c028f2d..c0d256d18 100644 --- a/client/src/native/keyboardFields.ts +++ b/client/src/native/keyboardFields.ts @@ -115,7 +115,7 @@ export function prepareField(target: EventTarget | null): void { * the capture phase, so the field's text is read before any handler of the field * runs; whether one took the Enter is read off the event once it has gone round. */ -export function closeOnDone(e: KeyboardEvent): void { +function closeOnDone(e: KeyboardEvent): void { const el = e.target; if (e.key !== 'Enter' || e.isComposing) return; if (!(el instanceof HTMLInputElement) || el.getAttribute('enterkeyhint') !== 'done') return;