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 ──────────────────────────────────────────────────────
|
// ─── @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", () => {
|
vi.mock("@groombook/db", () => {
|
||||||
// Each "select chain" needs to know which table it's targeting so we can
|
const namedTable = (name: string) =>
|
||||||
// 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) =>
|
|
||||||
new Proxy(
|
new Proxy(
|
||||||
{ _name: name },
|
{ _name: name },
|
||||||
{
|
{
|
||||||
@@ -97,27 +99,28 @@ vi.mock("@groombook/db", () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const pets = named("pets");
|
const pets = namedTable("pets");
|
||||||
const appointments = named("appointments");
|
const appointments = namedTable("appointments");
|
||||||
const services = named("services");
|
const services = namedTable("services");
|
||||||
const staff = named("staff");
|
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 = "";
|
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[] => {
|
const resolveRows = (): unknown[] => {
|
||||||
if (targetTable === "pets") {
|
if (targetTable === "pets") {
|
||||||
if (dbState.throwOnPetSelect) {
|
if (dbState.throwOnPetSelect) {
|
||||||
@@ -126,11 +129,6 @@ vi.mock("@groombook/db", () => {
|
|||||||
return dbState.petRow ? [dbState.petRow] : [];
|
return dbState.petRow ? [dbState.petRow] : [];
|
||||||
}
|
}
|
||||||
if (targetTable === "appointments") {
|
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) : [];
|
const keys = projection ? Object.keys(projection) : [];
|
||||||
if (projection && keys.length === 1 && keys[0] === "id") {
|
if (projection && keys.length === 1 && keys[0] === "id") {
|
||||||
return dbState.linkageRow ? [dbState.linkageRow] : [];
|
return dbState.linkageRow ? [dbState.linkageRow] : [];
|
||||||
@@ -145,16 +143,31 @@ vi.mock("@groombook/db", () => {
|
|||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
chain.where = (..._args: unknown[]) => {
|
|
||||||
// After .where, the chain is still awaitable. Return chain itself so
|
const chain: ChainLike = {
|
||||||
// .limit/.orderBy can follow, but also expose `then` for the case
|
from(table) {
|
||||||
// where .where is the last call (pets-select).
|
targetTable = table._name;
|
||||||
return chain;
|
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;
|
return chain;
|
||||||
}
|
}
|
||||||
@@ -167,14 +180,13 @@ vi.mock("@groombook/db", () => {
|
|||||||
appointments,
|
appointments,
|
||||||
services,
|
services,
|
||||||
staff,
|
staff,
|
||||||
and: vi.fn((..._args: unknown[]) => ({ _op: "and" })),
|
and: vi.fn(() => ({ _op: "and" })),
|
||||||
or: vi.fn((..._args: unknown[]) => ({ _op: "or" })),
|
or: vi.fn(() => ({ _op: "or" })),
|
||||||
eq: vi.fn((..._args: unknown[]) => ({ _op: "eq" })),
|
eq: vi.fn(() => ({ _op: "eq" })),
|
||||||
desc: vi.fn((arg: unknown) => arg),
|
desc: vi.fn((arg: unknown) => arg),
|
||||||
exists: vi.fn((arg: unknown) => arg),
|
exists: vi.fn((arg: unknown) => arg),
|
||||||
sql: Object.assign(
|
sql: Object.assign(
|
||||||
(..._args: unknown[]) => ({ _op: "sql" }),
|
() => ({ _op: "sql" }),
|
||||||
// tag template fallback
|
|
||||||
{ [Symbol.toPrimitive]: () => "sql" }
|
{ [Symbol.toPrimitive]: () => "sql" }
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user