From 1e1c439b05652978449e699d727852c29c27d015 Mon Sep 17 00:00:00 2001 From: davidasix Date: Fri, 24 Jul 2026 10:57:27 -0400 Subject: [PATCH 1/2] fix(mobile): keep dirty sheet gesture enabled --- apps/mobile/app/(app)/clients/new.tsx | 2 +- .../__tests__/client-creation.test.tsx | 37 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/apps/mobile/app/(app)/clients/new.tsx b/apps/mobile/app/(app)/clients/new.tsx index ad73e31..6a79e29 100644 --- a/apps/mobile/app/(app)/clients/new.tsx +++ b/apps/mobile/app/(app)/clients/new.tsx @@ -90,7 +90,7 @@ export default function NewClientScreen() { <> { dispatch: jest.fn(), setOptions: jest.fn(), }; - MockStack.Screen = function MockStackScreen() { - return null; - }; + MockStack.Screen = jest.fn(() => null); return { __esModule: true, router: { @@ -84,6 +83,10 @@ describe("NewClientScreen", () => { mockDb.select.mockReturnValue({ from: mockFrom }); }); + afterEach(() => { + jest.restoreAllMocks(); + }); + it("inserts a minimal client and dismisses only after persistence succeeds", async () => { renderWithTheme(); enterRequiredNames(); @@ -117,6 +120,34 @@ describe("NewClientScreen", () => { expect(mockValues).not.toHaveBeenCalled(); }); + it("keeps native dismissal enabled and confirms dirty navigation attempts", () => { + const alertSpy = jest.spyOn(Alert, "alert").mockImplementation(); + renderWithTheme(); + + const screenOptions = + mockExpoRouter.Stack.Screen.mock.calls.at(-1)[0].options; + expect(screenOptions.gestureEnabled).toBe(true); + + fireEvent.changeText(screen.getByLabelText("First name"), "Zara"); + + const beforeRemove = mockExpoRouter.navigation.addListener.mock.calls + .filter(([eventName]: [string]) => eventName === "beforeRemove") + .at(-1)[1]; + const action = { type: "GO_BACK" }; + const event = { data: { action }, preventDefault: jest.fn() }; + act(() => beforeRemove(event)); + + expect(event.preventDefault).toHaveBeenCalledTimes(1); + expect(alertSpy).toHaveBeenCalledWith( + "Discard this client?", + "Your changes have not been saved.", + expect.any(Array), + ); + const buttons = alertSpy.mock.calls[0][2]; + act(() => buttons?.[1].onPress?.()); + expect(mockExpoRouter.navigation.dispatch).toHaveBeenCalledWith(action); + }); + it("lets nullable clinical choices return to an unset value", async () => { renderWithTheme(); enterRequiredNames(); From 1fa9594325405bc82176630d71e1880a134e7f8d Mon Sep 17 00:00:00 2001 From: davidasix Date: Fri, 24 Jul 2026 10:58:25 -0400 Subject: [PATCH 2/2] fix(mobile): generalize validation toast --- apps/mobile/app/(app)/clients/new.tsx | 2 +- .../__tests__/client-creation.test.tsx | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/mobile/app/(app)/clients/new.tsx b/apps/mobile/app/(app)/clients/new.tsx index 6a79e29..cf41246 100644 --- a/apps/mobile/app/(app)/clients/new.tsx +++ b/apps/mobile/app/(app)/clients/new.tsx @@ -66,7 +66,7 @@ export default function NewClientScreen() { setErrors(result.errors); showErrorToast( "Review highlighted fields", - "First and last name are required.", + "Correct the highlighted fields and try again.", ); return; } diff --git a/apps/mobile/src/components/__tests__/client-creation.test.tsx b/apps/mobile/src/components/__tests__/client-creation.test.tsx index cd038cd..1c8af63 100644 --- a/apps/mobile/src/components/__tests__/client-creation.test.tsx +++ b/apps/mobile/src/components/__tests__/client-creation.test.tsx @@ -113,13 +113,32 @@ describe("NewClientScreen", () => { expect(mockShowErrorToast).toHaveBeenCalledWith( "Review highlighted fields", - "First and last name are required.", + "Correct the highlighted fields and try again.", ); expect(screen.getByText("First name is required.")).toBeTruthy(); expect(screen.getByText("Last name is required.")).toBeTruthy(); expect(mockValues).not.toHaveBeenCalled(); }); + it("uses generic toast guidance for non-name validation errors", () => { + renderWithTheme(); + enterRequiredNames(); + fireEvent.press(screen.getByLabelText("Expand Clinical section")); + fireEvent.changeText(screen.getByLabelText("Gravida"), "1"); + fireEvent.changeText(screen.getByLabelText("Parity"), "2"); + + fireEvent.press(screen.getByText("Add client")); + + expect(mockShowErrorToast).toHaveBeenCalledWith( + "Review highlighted fields", + "Correct the highlighted fields and try again.", + ); + expect( + screen.getByText("Parity cannot be greater than gravida."), + ).toBeTruthy(); + expect(mockValues).not.toHaveBeenCalled(); + }); + it("keeps native dismissal enabled and confirms dirty navigation attempts", () => { const alertSpy = jest.spyOn(Alert, "alert").mockImplementation(); renderWithTheme();