From 546bf65b8b321511631061546350a346a7401834 Mon Sep 17 00:00:00 2001 From: Jackson Roberts Date: Wed, 8 Apr 2026 01:45:12 +0000 Subject: [PATCH] test(ui): i corrected UI Testing --- .github/workflows/ui-tests.yaml | 29 ++++++++ ui/.bun-tests/.gitkeep | 0 ui/.bun-tests/placeholder.test.ts | 9 +++ ui/bunfig.toml | 12 ++++ .../optima-api/modules/api-modules.spec.ts | 2 +- ui/src/lib/optima-api/modules/cw.spec.ts | 2 +- ui/src/lib/optima-api/modules/user.spec.ts | 66 ++++++++----------- ui/src/routes/(auth)/login/+page.server.ts | 2 - ui/src/routes/admin/users/page.server.spec.ts | 1 + .../opportunity/[id]/page.server.spec.ts | 2 + 10 files changed, 84 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/ui-tests.yaml create mode 100644 ui/.bun-tests/.gitkeep create mode 100644 ui/.bun-tests/placeholder.test.ts create mode 100644 ui/bunfig.toml diff --git a/.github/workflows/ui-tests.yaml b/.github/workflows/ui-tests.yaml new file mode 100644 index 0000000..d0e44f9 --- /dev/null +++ b/.github/workflows/ui-tests.yaml @@ -0,0 +1,29 @@ +name: UI - Tests + +on: + push: + branches: ["**"] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + defaults: + run: + working-directory: ui + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.11" + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run unit tests + run: bun run test:unit -- --run + env: + PUBLIC_API_URL: "https://api.example.com" diff --git a/ui/.bun-tests/.gitkeep b/ui/.bun-tests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ui/.bun-tests/placeholder.test.ts b/ui/.bun-tests/placeholder.test.ts new file mode 100644 index 0000000..3a12d07 --- /dev/null +++ b/ui/.bun-tests/placeholder.test.ts @@ -0,0 +1,9 @@ +import { expect, test } from "bun:test"; + +// UI unit tests use Vitest, not Bun's native test runner. +// Run them with: bun run test:unit -- --run +// +// This placeholder ensures `bun test` exits 0 rather than failing with "No tests found". +test("UI tests use Vitest - run with: bun run test:unit -- --run", () => { + expect(true).toBe(true); +}); diff --git a/ui/bunfig.toml b/ui/bunfig.toml new file mode 100644 index 0000000..9303462 --- /dev/null +++ b/ui/bunfig.toml @@ -0,0 +1,12 @@ +# The UI project uses Vitest (via `bun run test:unit`) for unit tests, not Bun's native test runner. +# Bun's runner lacks support for vi.hoisted, SvelteKit virtual modules ($env, $app), and jsdom. +# +# To run tests: +# Unit tests: bun run test:unit -- --run +# E2E tests: bun run test:e2e +# All tests: bun run test +# +# This configuration points Bun's test discovery at an isolated directory so that +# `bun test` does not erroneously pick up Vitest-formatted spec files. +[test] +root = ".bun-tests" diff --git a/ui/src/lib/optima-api/modules/api-modules.spec.ts b/ui/src/lib/optima-api/modules/api-modules.spec.ts index 5661707..1e8bb69 100644 --- a/ui/src/lib/optima-api/modules/api-modules.spec.ts +++ b/ui/src/lib/optima-api/modules/api-modules.spec.ts @@ -10,7 +10,7 @@ const { mockApi } = vi.hoisted(() => ({ }, })); -vi.mock("../axios", () => ({ +vi.mock("$lib/optima-api/axios", () => ({ default: mockApi, api: mockApi, })); diff --git a/ui/src/lib/optima-api/modules/cw.spec.ts b/ui/src/lib/optima-api/modules/cw.spec.ts index ef21f06..512e8fa 100644 --- a/ui/src/lib/optima-api/modules/cw.spec.ts +++ b/ui/src/lib/optima-api/modules/cw.spec.ts @@ -6,7 +6,7 @@ const { mockApi } = vi.hoisted(() => ({ }, })); -vi.mock("../axios", () => ({ +vi.mock("$lib/optima-api/axios", () => ({ default: mockApi, api: mockApi, })); diff --git a/ui/src/lib/optima-api/modules/user.spec.ts b/ui/src/lib/optima-api/modules/user.spec.ts index 53b6e7e..70eff8d 100644 --- a/ui/src/lib/optima-api/modules/user.spec.ts +++ b/ui/src/lib/optima-api/modules/user.spec.ts @@ -1,9 +1,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -const { mockRedirect, mockAxiosPost, mockIo, mockApi } = vi.hoisted(() => ({ +const { mockRedirect, mockAxiosPost, mockApi } = vi.hoisted(() => ({ mockRedirect: vi.fn(), mockAxiosPost: vi.fn(), - mockIo: vi.fn(), mockApi: { get: vi.fn(), post: vi.fn(), @@ -27,13 +26,13 @@ vi.mock("axios", () => ({ post: mockAxiosPost, })); -vi.mock("../axios", () => ({ +vi.mock("$lib/optima-api/axios", () => ({ default: mockApi, api: mockApi, })); vi.mock("socket.io-client", () => ({ - io: mockIo, + io: vi.fn(), })); import { user } from "./user"; @@ -128,45 +127,38 @@ describe("user module", () => { expect(result).toBe(fakeRedirect); }); - it("awaitAuthCallback resolves when socket event delivers tokens", async () => { - const handlers: Record void> = {}; - const disconnect = vi.fn(); + it("awaitAuthCallback resolves when polling returns 200 with tokens", async () => { + const mockFetch = vi + .fn() + .mockResolvedValueOnce({ + status: 202, + }) + .mockResolvedValueOnce({ + status: 200, + json: vi.fn().mockResolvedValue({ + data: { accessToken: "access", refreshToken: "refresh" }, + }), + }); + vi.stubGlobal("fetch", mockFetch); - mockIo.mockReturnValueOnce({ - on: vi.fn((event: string, callback: (payload?: any) => void) => { - handlers[event] = callback; - }), - disconnect, - }); + const result = await user.awaitAuthCallback("cb-key"); - const promise = user.awaitAuthCallback("cb-key"); + expect(result).toEqual({ accessToken: "access", refreshToken: "refresh" }); + expect(mockFetch).toHaveBeenCalledWith( + "https://api.example.com/v1/auth/callback/cb-key" + ); - handlers["auth:login:callback:cb-key"]?.({ - accessToken: "access", - refreshToken: "refresh", - }); - - await expect(promise).resolves.toEqual({ - accessToken: "access", - refreshToken: "refresh", - }); - expect(disconnect).toHaveBeenCalled(); + vi.unstubAllGlobals(); }); - it("awaitAuthCallback rejects on connect_error", async () => { - const handlers: Record void> = {}; + it("awaitAuthCallback rejects when polling returns unexpected status", async () => { + const mockFetch = vi.fn().mockResolvedValue({ status: 500 }); + vi.stubGlobal("fetch", mockFetch); - mockIo.mockReturnValueOnce({ - on: vi.fn((event: string, callback: (payload?: any) => void) => { - handlers[event] = callback; - }), - disconnect: vi.fn(), - }); + await expect(user.awaitAuthCallback("cb-key")).rejects.toThrow( + "Unexpected status 500" + ); - const promise = user.awaitAuthCallback("cb-key"); - - handlers.connect_error?.(new Error("socket failed")); - - await expect(promise).rejects.toThrow("socket failed"); + vi.unstubAllGlobals(); }); }); diff --git a/ui/src/routes/(auth)/login/+page.server.ts b/ui/src/routes/(auth)/login/+page.server.ts index 0e2981e..8a80560 100644 --- a/ui/src/routes/(auth)/login/+page.server.ts +++ b/ui/src/routes/(auth)/login/+page.server.ts @@ -1,6 +1,5 @@ import { Actions, redirect } from "@sveltejs/kit"; import { optima } from "$lib"; -import { INTERNAL_API_URL } from "$env/static/private"; export const actions: Actions = { login: async (event) => { @@ -8,7 +7,6 @@ export const actions: Actions = { const tokens = await optima.user.awaitAuthCallback( data.get("callbackKey") as string, - INTERNAL_API_URL, ); event.cookies.set("accessToken", tokens.accessToken, { diff --git a/ui/src/routes/admin/users/page.server.spec.ts b/ui/src/routes/admin/users/page.server.spec.ts index 87d1eb8..32d9908 100644 --- a/ui/src/routes/admin/users/page.server.spec.ts +++ b/ui/src/routes/admin/users/page.server.spec.ts @@ -90,6 +90,7 @@ describe("admin/users +page.server.ts", () => { return { get: (key: string) => entries[key] ?? null, getAll: (key: string) => (entries[key] ? [entries[key]] : []), + has: (key: string) => key in entries, }; } diff --git a/ui/src/routes/sales/opportunity/[id]/page.server.spec.ts b/ui/src/routes/sales/opportunity/[id]/page.server.spec.ts index 08f8e0a..9a35de8 100644 --- a/ui/src/routes/sales/opportunity/[id]/page.server.spec.ts +++ b/ui/src/routes/sales/opportunity/[id]/page.server.spec.ts @@ -6,6 +6,7 @@ const { mockOptima, mockCheckPermissions, mockHandleApiError } = vi.hoisted( sales: { fetchOne: vi.fn(), fetchWorkflowStatus: vi.fn(), + fetchOpportunityTypes: vi.fn(), }, }, mockCheckPermissions: vi.fn(), @@ -34,6 +35,7 @@ describe("sales/opportunity/[id] +page.server.ts load", () => { beforeEach(() => { vi.clearAllMocks(); vi.spyOn(console, "error").mockImplementation(() => {}); + mockOptima.sales.fetchOpportunityTypes.mockResolvedValue({ data: [] }); }); it("returns empty data when no token", async () => {