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 <noreply@anthropic.com>
This commit is contained in:
Julien Herr
2026-05-20 22:54:32 +02:00
parent 3ed9d2ee22
commit 29446a2aac
3 changed files with 32 additions and 38 deletions
+2 -1
View File
@@ -10,7 +10,8 @@
"deploy": "wrangler deploy --env production", "deploy": "wrangler deploy --env production",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"test:coverage": "vitest run --coverage" "test:coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit"
}, },
"author": "", "author": "",
"license": "MIT", "license": "MIT",
+6 -6
View File
@@ -11,10 +11,10 @@ describe("Admin Routes", () => {
let loginAndGetCookie: () => Promise<string>; let loginAndGetCookie: () => Promise<string>;
beforeEach(() => { beforeEach(() => {
mockEnv = createMockEnv(); mockEnv = createMockEnv() as unknown as Env;
testApp = new Hono(); testApp = new Hono();
testApp.route("/admin", app); testApp.route("/admin", app);
request = (path, init = {}) => testApp.request(path, init, mockEnv); request = (path, init = {}) => Promise.resolve(testApp.request(path, init, mockEnv));
loginAndGetCookie = async () => { loginAndGetCookie = async () => {
const formData = new FormData(); const formData = new FormData();
formData.append("password", "test-password"); formData.append("password", "test-password");
@@ -161,8 +161,8 @@ describe("Admin Routes", () => {
"json", "json",
); );
expect(feedConfig).toBeTruthy(); expect(feedConfig).toBeTruthy();
expect(feedConfig.title).toBe("Test Feed"); expect((feedConfig as any).title).toBe("Test Feed");
expect(feedConfig.description).toBe("Test Description"); expect((feedConfig as any).description).toBe("Test Description");
}); });
it("should reject feed creation with missing title", async () => { it("should reject feed creation with missing title", async () => {
@@ -297,7 +297,7 @@ describe("Admin Routes", () => {
); );
expect(deleteRes.status).toBe(200); expect(deleteRes.status).toBe(200);
const payload = await deleteRes.json(); const payload = (await deleteRes.json()) as any;
expect(payload.ok).toBe(true); expect(payload.ok).toBe(true);
expect(payload.feedId).toBe(feedId); expect(payload.feedId).toBe(feedId);
}); });
@@ -419,7 +419,7 @@ describe("Admin Routes", () => {
); );
expect(deleteRes.status).toBe(200); expect(deleteRes.status).toBe(200);
const payload = await deleteRes.json(); const payload = (await deleteRes.json()) as any;
expect(payload.ok).toBe(true); expect(payload.ok).toBe(true);
expect(payload.emailKey).toBe(emailKey); expect(payload.emailKey).toBe(emailKey);
+24 -31
View File
@@ -1,26 +1,10 @@
import { beforeAll, afterAll, afterEach } from "vitest"; import { beforeAll, afterAll, afterEach } from "vitest";
import { setupServer } from "msw/node"; import { setupServer } from "msw/node";
/** // Minimal Node.js built-ins used only in this test setup file.
* Mock implementation of Cloudflare Workers runtime environment // Declared locally to avoid pulling in the full @types/node package,
* Based on: https://developers.cloudflare.com/workers/testing/ // which would conflict with @cloudflare/workers-types globals.
*/ declare function require(id: string): any;
// 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;
}
/** /**
* Mock KV namespace implementation * Mock KV namespace implementation
@@ -122,36 +106,45 @@ beforeAll(() => {
// Setup MSW server // Setup MSW server
server.listen({ onUnhandledRequest: "error" }); 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 // Mock Cloudflare Workers runtime globals
global.caches = { g.caches = {
default: new MockCache(), default: new MockCache(),
open: async () => new MockCache(), open: async () => new MockCache(),
} as unknown as CacheStorage; } as unknown as CacheStorage;
// Mock crypto for generating random values // Mock crypto for generating random values
if (!global.crypto) { if (!g.crypto) {
global.crypto = require("crypto").webcrypto; // eslint-disable-next-line @typescript-eslint/no-require-imports
g.crypto = require("crypto").webcrypto;
} }
// Ensure other required globals are available // 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"); 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"); 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"); 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"); const { Response } = require("undici");
global.Response = Response; g.Response = Response;
} }
}); });