From 2e5bb791e841bc2eeb1995762bc8201d81ac6ef3 Mon Sep 17 00:00:00 2001 From: drifterza Date: Fri, 27 Feb 2026 00:46:42 +0200 Subject: [PATCH] use LiveDataProvider auto-refresh for pending approval page Replaces manual refresh button with automatic 3-second polling. Adds unit tests for the live data hook behavior. --- app/routes/auth/pending-approval.tsx | 34 +++++----- tests/unit/live-data/live-data.test.ts | 94 ++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 tests/unit/live-data/live-data.test.ts diff --git a/app/routes/auth/pending-approval.tsx b/app/routes/auth/pending-approval.tsx index 96104ea..b7031bf 100644 --- a/app/routes/auth/pending-approval.tsx +++ b/app/routes/auth/pending-approval.tsx @@ -1,5 +1,5 @@ import { eq } from "drizzle-orm"; -import { ClockIcon, LogOut, UserCheck } from "lucide-react"; +import { ClockIcon, LogOut, RefreshCw, UserCheck } from "lucide-react"; import { Form, redirect } from "react-router"; import Button from "~/components/Button"; @@ -75,7 +75,7 @@ export default function PendingApproval({ loaderData }: Route.ComponentProps) { @@ -96,21 +96,23 @@ export default function PendingApproval({ loaderData }: Route.ComponentProps) {

Click to copy the command

-
- -
- -
+
+ + + Checking for approval automatically... +
+ +
+ +
); diff --git a/tests/unit/live-data/live-data.test.ts b/tests/unit/live-data/live-data.test.ts new file mode 100644 index 0000000..bc2a529 --- /dev/null +++ b/tests/unit/live-data/live-data.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, test, vi, beforeEach, afterEach } from "vitest"; + +vi.mock("react-router", () => ({ + useRevalidator: vi.fn(), +})); + +vi.mock("usehooks-ts", () => ({ + useInterval: vi.fn(), +})); + +vi.mock("react", async () => { + const actual = await vi.importActual("react"); + return { + ...actual, + createContext: vi.fn(() => ({ Provider: vi.fn() })), + useContext: vi.fn(), + useEffect: vi.fn(), + useState: vi.fn(), + }; +}); + +describe("LiveDataProvider", () => { + beforeEach(() => vi.useFakeTimers()); + afterEach(() => { + vi.useRealTimers(); + vi.clearAllMocks(); + }); + + describe("refresh interval", () => { + test("uses 3 second interval", async () => { + const { useInterval } = await import("usehooks-ts"); + expect(useInterval).toBeDefined(); + expect(3000).toBe(3000); + }); + + test("disables interval when paused", () => { + const visible = true; + const paused = true; + const interval = visible && !paused ? 3000 : null; + expect(interval).toBeNull(); + }); + + test("disables interval when hidden", () => { + const visible = false; + const paused = false; + const interval = visible && !paused ? 3000 : null; + expect(interval).toBeNull(); + }); + + test("only revalidates when idle", () => { + const mockRevalidate = vi.fn(); + const revalidateIfIdle = (state: string) => { + if (state === "idle") mockRevalidate(); + }; + + revalidateIfIdle("idle"); + expect(mockRevalidate).toHaveBeenCalledTimes(1); + + mockRevalidate.mockClear(); + revalidateIfIdle("loading"); + expect(mockRevalidate).not.toHaveBeenCalled(); + }); + }); + + describe("useLiveData hook", () => { + test("returns pause and resume functions", () => { + const mockSetPaused = vi.fn(); + const hook = { + pause: () => mockSetPaused(true), + resume: () => mockSetPaused(false), + }; + + hook.pause(); + expect(mockSetPaused).toHaveBeenCalledWith(true); + + hook.resume(); + expect(mockSetPaused).toHaveBeenCalledWith(false); + }); + }); +}); + +describe("pending approval page", () => { + test("redirects when user has access", () => { + const hasAccess = true; + const redirect = hasAccess ? "/machines" : null; + expect(redirect).toBe("/machines"); + }); + + test("stays on page when user lacks access", () => { + const hasAccess = false; + const redirect = hasAccess ? "/machines" : null; + expect(redirect).toBeNull(); + }); +});