From 29446a2aac0187fa6f625875b3e6aa06682f61c5 Mon Sep 17 00:00:00 2001 From: Julien Herr Date: Wed, 20 May 2026 22:54:32 +0200 Subject: [PATCH] chore: add typecheck script and fix pre-existing TypeScript errors - Add `typecheck` script (`tsc --noEmit`) to package.json - Remove conflicting `declare global` from test/setup.ts (superseded by @cloudflare/workers-types); use `globalThis as any` for test globals and declare minimal `require` locally to avoid pulling in @types/node - Cast `createMockEnv()` and `deleteRes.json()` results in admin.test.ts to silence strict `unknown` / MockKV-vs-KVNamespace errors Co-Authored-By: Claude Sonnet 4.5 --- package.json | 3 ++- src/routes/admin.test.ts | 12 ++++----- src/test/setup.ts | 55 ++++++++++++++++++---------------------- 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index d49c3ee..a0d3401 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "deploy": "wrangler deploy --env production", "test": "vitest run", "test:watch": "vitest", - "test:coverage": "vitest run --coverage" + "test:coverage": "vitest run --coverage", + "typecheck": "tsc --noEmit" }, "author": "", "license": "MIT", diff --git a/src/routes/admin.test.ts b/src/routes/admin.test.ts index 777723d..7cc293a 100644 --- a/src/routes/admin.test.ts +++ b/src/routes/admin.test.ts @@ -11,10 +11,10 @@ describe("Admin Routes", () => { let loginAndGetCookie: () => Promise; beforeEach(() => { - mockEnv = createMockEnv(); + mockEnv = createMockEnv() as unknown as Env; testApp = new Hono(); testApp.route("/admin", app); - request = (path, init = {}) => testApp.request(path, init, mockEnv); + request = (path, init = {}) => Promise.resolve(testApp.request(path, init, mockEnv)); loginAndGetCookie = async () => { const formData = new FormData(); formData.append("password", "test-password"); @@ -161,8 +161,8 @@ describe("Admin Routes", () => { "json", ); expect(feedConfig).toBeTruthy(); - expect(feedConfig.title).toBe("Test Feed"); - expect(feedConfig.description).toBe("Test Description"); + expect((feedConfig as any).title).toBe("Test Feed"); + expect((feedConfig as any).description).toBe("Test Description"); }); it("should reject feed creation with missing title", async () => { @@ -297,7 +297,7 @@ describe("Admin Routes", () => { ); expect(deleteRes.status).toBe(200); - const payload = await deleteRes.json(); + const payload = (await deleteRes.json()) as any; expect(payload.ok).toBe(true); expect(payload.feedId).toBe(feedId); }); @@ -419,7 +419,7 @@ describe("Admin Routes", () => { ); expect(deleteRes.status).toBe(200); - const payload = await deleteRes.json(); + const payload = (await deleteRes.json()) as any; expect(payload.ok).toBe(true); expect(payload.emailKey).toBe(emailKey); diff --git a/src/test/setup.ts b/src/test/setup.ts index 096a1bb..c48a9a0 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -1,26 +1,10 @@ import { beforeAll, afterAll, afterEach } from "vitest"; import { setupServer } from "msw/node"; -/** - * Mock implementation of Cloudflare Workers runtime environment - * Based on: https://developers.cloudflare.com/workers/testing/ - */ - -// Define Cloudflare Workers runtime globals -declare global { - // CF Worker specific globals - var caches: CacheStorage; - var crypto: Crypto; - var Response: typeof Response; - var Request: typeof Request; - var URLSearchParams: typeof URLSearchParams; - var URL: typeof URL; - var Headers: typeof Headers; - var FormData: typeof FormData; - var Blob: typeof Blob; - var atob: (data: string) => string; - var btoa: (data: string) => string; -} +// Minimal Node.js built-ins used only in this test setup file. +// Declared locally to avoid pulling in the full @types/node package, +// which would conflict with @cloudflare/workers-types globals. +declare function require(id: string): any; /** * Mock KV namespace implementation @@ -122,36 +106,45 @@ beforeAll(() => { // Setup MSW server server.listen({ onUnhandledRequest: "error" }); + // Type-safe access to Node's global object for setting Workers-like globals in tests + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const g = globalThis as any; + // Mock Cloudflare Workers runtime globals - global.caches = { + g.caches = { default: new MockCache(), open: async () => new MockCache(), } as unknown as CacheStorage; // Mock crypto for generating random values - if (!global.crypto) { - global.crypto = require("crypto").webcrypto; + if (!g.crypto) { + // eslint-disable-next-line @typescript-eslint/no-require-imports + g.crypto = require("crypto").webcrypto; } // Ensure other required globals are available - if (!global.FormData) { + if (!g.FormData) { + // eslint-disable-next-line @typescript-eslint/no-require-imports const { FormData } = require("undici"); - global.FormData = FormData; + g.FormData = FormData; } - if (!global.Headers) { + if (!g.Headers) { + // eslint-disable-next-line @typescript-eslint/no-require-imports const { Headers } = require("undici"); - global.Headers = Headers; + g.Headers = Headers; } - if (!global.Request) { + if (!g.Request) { + // eslint-disable-next-line @typescript-eslint/no-require-imports const { Request } = require("undici"); - global.Request = Request; + g.Request = Request; } - if (!global.Response) { + if (!g.Response) { + // eslint-disable-next-line @typescript-eslint/no-require-imports const { Response } = require("undici"); - global.Response = Response; + g.Response = Response; } });