fix(test): retype petProfileSummary chain mock to satisfy tsc --project build
CI failed on PR #137 because `tsc --project .` (the build path used by the Docker image) is stricter than `pnpm typecheck` was reporting during local iteration — two TS2322 errors surfaced in the new mock: 1. `chain.from = (table: { _name: string }) => ...` was assigned through a `Record<string, (...args: unknown[]) => unknown>` index signature, and `{ _name: string }` is not assignable from `unknown`. 2. `chain.then = (onFulfilled?: (v: unknown[]) => unknown) => ...` was not assignable to the `PromiseLike<T>.then` signature TS now infers for the awaitable, because TS expects `onfulfilled` to also accept `null`. Replace the proxy-based loose chain with a typed `ChainLike` interface so the build typechecker is satisfied. Behaviour is unchanged — all 7 GRO-2014 regression tests still pass. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -81,12 +81,14 @@ function resetDb() {
|
||||
}
|
||||
|
||||
// ─── @groombook/db mock ──────────────────────────────────────────────────────
|
||||
//
|
||||
// Each select chain needs to know which table it's targeting and which columns
|
||||
// it's projecting so we can return the right mocked rows. We thread that state
|
||||
// through a per-call object whose chain methods all return `this`. The chain
|
||||
// is also `then`-able so any `await` position resolves to the rows.
|
||||
|
||||
vi.mock("@groombook/db", () => {
|
||||
// Each "select chain" needs to know which table it's targeting so we can
|
||||
// hand back the right mocked rows. We can't tell tables apart by reference
|
||||
// in Drizzle-land, so use named proxies and inspect them in `from()`.
|
||||
const named = (name: string) =>
|
||||
const namedTable = (name: string) =>
|
||||
new Proxy(
|
||||
{ _name: name },
|
||||
{
|
||||
@@ -97,27 +99,28 @@ vi.mock("@groombook/db", () => {
|
||||
}
|
||||
);
|
||||
|
||||
const pets = named("pets");
|
||||
const appointments = named("appointments");
|
||||
const services = named("services");
|
||||
const staff = named("staff");
|
||||
const pets = namedTable("pets");
|
||||
const appointments = namedTable("appointments");
|
||||
const services = namedTable("services");
|
||||
const staff = namedTable("staff");
|
||||
|
||||
function buildSelect(projection?: Record<string, unknown>) {
|
||||
// The full chain interface is intentionally loose — only `then` is exposed
|
||||
// with a typed signature so vitest's await resolves to the right shape.
|
||||
interface ChainLike {
|
||||
from: (table: { _name: string }) => ChainLike;
|
||||
where: (...args: unknown[]) => ChainLike;
|
||||
innerJoin: (...args: unknown[]) => ChainLike;
|
||||
leftJoin: (...args: unknown[]) => ChainLike;
|
||||
orderBy: (...args: unknown[]) => ChainLike;
|
||||
limit: (...args: unknown[]) => ChainLike;
|
||||
then: <T = unknown[]>(
|
||||
onfulfilled?: ((value: unknown[]) => T | PromiseLike<T>) | null
|
||||
) => Promise<T>;
|
||||
}
|
||||
|
||||
function buildSelect(projection?: Record<string, unknown>): ChainLike {
|
||||
let targetTable = "";
|
||||
const chain: Record<string, (...args: unknown[]) => unknown> = {};
|
||||
|
||||
chain.from = (table: { _name: string }) => {
|
||||
targetTable = table._name;
|
||||
return chain;
|
||||
};
|
||||
chain.innerJoin = () => chain;
|
||||
chain.leftJoin = () => chain;
|
||||
chain.orderBy = () => chain;
|
||||
chain.limit = () => chain;
|
||||
|
||||
// .where(...) on the pets-select is the terminal call in the route — it
|
||||
// is awaited directly. Other queries chain through .limit/.orderBy. We
|
||||
// make every chain "thenable" so any await position resolves to rows.
|
||||
const resolveRows = (): unknown[] => {
|
||||
if (targetTable === "pets") {
|
||||
if (dbState.throwOnPetSelect) {
|
||||
@@ -126,11 +129,6 @@ vi.mock("@groombook/db", () => {
|
||||
return dbState.petRow ? [dbState.petRow] : [];
|
||||
}
|
||||
if (targetTable === "appointments") {
|
||||
// Disambiguate by projection shape:
|
||||
// - linkage check projects `{ id: appointments.id }`
|
||||
// - recentHistory projects multiple columns including serviceName
|
||||
// - visit count projects `{ count: ... }`
|
||||
// - upcoming projects multiple columns including confirmationStatus
|
||||
const keys = projection ? Object.keys(projection) : [];
|
||||
if (projection && keys.length === 1 && keys[0] === "id") {
|
||||
return dbState.linkageRow ? [dbState.linkageRow] : [];
|
||||
@@ -145,16 +143,31 @@ vi.mock("@groombook/db", () => {
|
||||
}
|
||||
return [];
|
||||
};
|
||||
chain.where = (..._args: unknown[]) => {
|
||||
// After .where, the chain is still awaitable. Return chain itself so
|
||||
// .limit/.orderBy can follow, but also expose `then` for the case
|
||||
// where .where is the last call (pets-select).
|
||||
return chain;
|
||||
|
||||
const chain: ChainLike = {
|
||||
from(table) {
|
||||
targetTable = table._name;
|
||||
return chain;
|
||||
},
|
||||
where() {
|
||||
return chain;
|
||||
},
|
||||
innerJoin() {
|
||||
return chain;
|
||||
},
|
||||
leftJoin() {
|
||||
return chain;
|
||||
},
|
||||
orderBy() {
|
||||
return chain;
|
||||
},
|
||||
limit() {
|
||||
return chain;
|
||||
},
|
||||
then(onfulfilled) {
|
||||
return Promise.resolve(resolveRows()).then(onfulfilled ?? undefined);
|
||||
},
|
||||
};
|
||||
// Make the whole chain thenable so any await position works.
|
||||
(chain as unknown as PromiseLike<unknown[]>).then = (
|
||||
onFulfilled?: (v: unknown[]) => unknown
|
||||
) => Promise.resolve(resolveRows()).then(onFulfilled);
|
||||
|
||||
return chain;
|
||||
}
|
||||
@@ -167,14 +180,13 @@ vi.mock("@groombook/db", () => {
|
||||
appointments,
|
||||
services,
|
||||
staff,
|
||||
and: vi.fn((..._args: unknown[]) => ({ _op: "and" })),
|
||||
or: vi.fn((..._args: unknown[]) => ({ _op: "or" })),
|
||||
eq: vi.fn((..._args: unknown[]) => ({ _op: "eq" })),
|
||||
and: vi.fn(() => ({ _op: "and" })),
|
||||
or: vi.fn(() => ({ _op: "or" })),
|
||||
eq: vi.fn(() => ({ _op: "eq" })),
|
||||
desc: vi.fn((arg: unknown) => arg),
|
||||
exists: vi.fn((arg: unknown) => arg),
|
||||
sql: Object.assign(
|
||||
(..._args: unknown[]) => ({ _op: "sql" }),
|
||||
// tag template fallback
|
||||
() => ({ _op: "sql" }),
|
||||
{ [Symbol.toPrimitive]: () => "sql" }
|
||||
),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user