/** * 测试共享 — 类型化断言辅助(v0.8.0) * * 背景:v0.8.0 起 tests/ 纳入类型检查(tsconfig.test.json)。测试里最常见的两类 * 类型错误是: * 1. `backend.read(key)` 返回 `ArrayBuffer | null` → 直接喂给 TextDecoder 报错; * 2. `db.query()` 返回 `unknown` → 取 `.length` / `[0].field` 报错。 * 旧做法是 `as any`,那等于把类型检查关掉;这里提供**断言 + 收窄**的辅助函数: * 断言失败会明确报出"期望非空",而不是让后续断言落空。 * * 用法: * expect(decode(await backend.read('k'))).toBe('v'); * const rows = rows<{ id: string }>(await db.query('SELECT * FROM t')); */ /** 断言非空并收窄(失败信息指向调用点,便于定位) */ export function nonNull(value: T | null | undefined, what = 'value'): T { if (value === null || value === undefined) { throw new Error(`expected ${what} to be non-null, got ${String(value)}`); } return value; } /** 解码字节为字符串(自动处理 null → 明确失败) */ export function decode(value: ArrayBuffer | Uint8Array | null | undefined, what = 'buffer'): string { if (value === null || value === undefined) { throw new Error(`expected ${what} to be non-null for decoding`); } return new TextDecoder().decode(value instanceof Uint8Array ? value : new Uint8Array(value)); } /** 把 query/executor 的 unknown 结果收窄为行数组 */ export function rows>(value: unknown): T[] { if (!Array.isArray(value)) { throw new Error(`expected query result to be an array, got ${typeof value}`); } return value as T[]; } /** 把 query 结果收窄为单行(断言至少一行并返回首行) */ export function firstRow>(value: unknown): T { const list = rows(value); if (list.length === 0) throw new Error('expected at least one row, got empty result'); return list[0]; } /** 把 query 结果收窄为对象(EXPLAIN 等返回单对象的场景) */ export function object>(value: unknown): T { if (typeof value !== 'object' || value === null || Array.isArray(value)) { throw new Error(`expected an object result, got ${Array.isArray(value) ? 'array' : typeof value}`); } return value as T; } /** 断言并收窄错误码(替代 `catch { threw = true }` 这种无法区分原因的写法) */ export async function expectCode(promise: Promise, code: string): Promise { try { await promise; } catch (error) { const actual = (error as { code?: string }).code; if (actual !== code) { throw new Error(`expected error code "${code}", got "${actual ?? '(none)'}": ${(error as Error).message}`); } return; } throw new Error(`expected rejection with code "${code}", but the promise resolved`); } /** * 取用引擎上的**可选**方法(如 clearAll / backup / repair / createIndex)。 * * `IStorageEngine` 把这些方法声明为可选,直接 `engine.clearAll()` 在类型上不成立。 * 测试里需要它们时用本函数:缺失即明确失败,而不是运行时 TypeError。 */ export function engineMethod(engine: unknown, name: string): M { const fn = (engine as Record | null)?.[name]; if (typeof fn !== 'function') { throw new Error(`engine is missing required test method "${name}"`); } return (fn as (...a: unknown[]) => unknown).bind(engine) as M; }