diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 39e3325..c56285e 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -41,6 +41,29 @@ jobs: working-directory: frontend run: npm run test + build: + name: Build Frontend + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + working-directory: frontend + run: npm ci + + - name: Build frontend + working-directory: frontend + run: npm run build + ui-tests: name: UI Tests runs-on: ubuntu-latest diff --git a/frontend/src/components/review/__tests__/SourceCodeViewer.test.tsx b/frontend/src/components/review/__tests__/SourceCodeViewer.test.tsx index f782325..4bae410 100644 --- a/frontend/src/components/review/__tests__/SourceCodeViewer.test.tsx +++ b/frontend/src/components/review/__tests__/SourceCodeViewer.test.tsx @@ -19,7 +19,7 @@ vi.mock("prism-react-renderer", () => ({ tokens: code .split("\n") .map((line: string) => [{content: line, types: ["plain"]}]), - getLineProps: ({line}: any) => ({}), + getLineProps: (_lineProps: any) => ({}), getTokenProps: ({token}: any) => ({children: token.content}), }), themes: {nightOwl: {plain: {}}, github: {plain: {}}}, diff --git a/frontend/src/components/utils/__tests__/Toaster.test.tsx b/frontend/src/components/utils/__tests__/Toaster.test.tsx index fea4349..c7f3106 100644 --- a/frontend/src/components/utils/__tests__/Toaster.test.tsx +++ b/frontend/src/components/utils/__tests__/Toaster.test.tsx @@ -2,7 +2,21 @@ import {render, screen} from "@testing-library/react"; import {beforeEach, describe, expect, it, vi} from "vitest"; import {Toaster} from "../Toaster"; -const mockSonner = vi.hoisted(() => vi.fn(() =>
)); +type SonnerProps = { + className?: string; + position?: string; + toastOptions?: { + classNames?: { + toast?: string; + success?: string; + error?: string; + }; + }; +}; + +const mockSonner = vi.hoisted(() => + vi.fn((_props: SonnerProps) =>
), +); vi.mock("sonner", () => ({ Toaster: (props: any) => mockSonner(props), @@ -19,7 +33,12 @@ describe("Toaster", () => { expect(screen.getByTestId("sonner")).toBeInTheDocument(); expect(mockSonner).toHaveBeenCalledTimes(1); - const sonnerProps = mockSonner.mock.calls[0][0]; + const sonnerProps = mockSonner.mock.calls[0]?.[0]; + expect(sonnerProps).toBeDefined(); + if (!sonnerProps) { + throw new Error("Expected Sonner to be called with props"); + } + expect(sonnerProps.className).toBe("toaster group"); expect(sonnerProps.position).toBe("bottom-right"); expect(sonnerProps.toastOptions).toEqual( diff --git a/frontend/src/hooks/mutations/__tests__/useAuthMutations.test.ts b/frontend/src/hooks/mutations/__tests__/useAuthMutations.test.ts index d8bb5c1..645ab6f 100644 --- a/frontend/src/hooks/mutations/__tests__/useAuthMutations.test.ts +++ b/frontend/src/hooks/mutations/__tests__/useAuthMutations.test.ts @@ -121,14 +121,14 @@ describe("useAuthMutations", () => { await act(async () => { result.current.mutate({ - old_password: "old", + current_password: "old", new_password: "new", }); }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(changePassword).toHaveBeenCalledWith({ - old_password: "old", + current_password: "old", new_password: "new", }); }); @@ -142,7 +142,7 @@ describe("useAuthMutations", () => { await act(async () => { result.current.mutate({ - old_password: "old", + current_password: "old", new_password: "new", }); }); diff --git a/frontend/src/routes/__tests__/-authRoute.test.ts b/frontend/src/routes/__tests__/-authRoute.test.ts index 38c3225..5f92262 100644 --- a/frontend/src/routes/__tests__/-authRoute.test.ts +++ b/frontend/src/routes/__tests__/-authRoute.test.ts @@ -55,7 +55,9 @@ describe("routes/_auth beforeLoad", () => { it("redirects to /login when auth_token is missing", async () => { const {Route, ensureQueryDataMock} = await loadAuthRoute(); - await expect(Route.options.beforeLoad?.()).rejects.toMatchObject({ + await expect( + Route.options.beforeLoad?.({} as never), + ).rejects.toMatchObject({ options: {to: "/login"}, }); expect(ensureQueryDataMock).not.toHaveBeenCalled(); @@ -66,7 +68,9 @@ describe("routes/_auth beforeLoad", () => { const {Route, queryKeysMock, ensureQueryDataMock, meMock} = await loadAuthRoute(); - await expect(Route.options.beforeLoad?.()).resolves.toBeUndefined(); + await expect( + Route.options.beforeLoad?.({} as never), + ).resolves.toBeUndefined(); expect(ensureQueryDataMock).toHaveBeenCalledWith( expect.objectContaining({ queryKey: queryKeysMock.auth.me, @@ -82,6 +86,8 @@ describe("routes/_auth beforeLoad", () => { const authError = new Error("me query failed"); meMock.mockRejectedValueOnce(authError); - await expect(Route.options.beforeLoad?.()).rejects.toBe(authError); + await expect(Route.options.beforeLoad?.({} as never)).rejects.toBe( + authError, + ); }); }); diff --git a/frontend/src/routes/__tests__/-redirectRoutes.test.ts b/frontend/src/routes/__tests__/-redirectRoutes.test.ts index 2eaae86..c495b6c 100644 --- a/frontend/src/routes/__tests__/-redirectRoutes.test.ts +++ b/frontend/src/routes/__tests__/-redirectRoutes.test.ts @@ -5,7 +5,7 @@ import {Route as ProjectIndexRoute} from "../_auth/project/$projectId/index"; describe("redirect-only routes", () => { it("redirects /_auth/ to /dashboard", async () => { await expect( - (async () => AuthIndexRoute.options.beforeLoad?.())(), + (async () => AuthIndexRoute.options.beforeLoad?.({} as never))(), ).rejects.toMatchObject({ options: {to: "/dashboard"}, }); @@ -13,7 +13,7 @@ describe("redirect-only routes", () => { it("redirects /_auth/project/$projectId/ to /dashboard with replace", async () => { await expect( - (async () => ProjectIndexRoute.options.beforeLoad?.())(), + (async () => ProjectIndexRoute.options.beforeLoad?.({} as never))(), ).rejects.toMatchObject({ options: { to: "/dashboard",