Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
# `runBackendConformance`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`runBackendConformance` is the shared, runner-neutral conformance
harness every concrete `@suaveplan/i18n-backend-*` package runs
against its `Backend` implementation. It exists so the universal
contract — name + capabilities surface, read-of-absent → typed
`I18nError`, optional `readMulti` merge, optional `save` round-trip,
optional `lastModified` monotonicity — gets one canonical test body
that every backend re-uses verbatim. New backends inherit the
guarantees automatically; no copy-pasted test code drifting between
implementations.
The harness lives under the `@suaveplan/i18n-shared/testing` subpath
so backend test files can import it without pulling the full i18n
core or any other backend. It deliberately uses
`@suaveplan/testing/runner` so it executes identically under Vitest
and Bun's test runner.
## Features
- One `describe(...)` per backend, one `it(...)` per universal
contract clause.
- Capability-gated assertions: `readMulti` / `save` /
`lastModified` tests early-return for backends that do not declare
the corresponding capability flag, keeping the suite green for
read-only backends like HTTP.
- Optional `seed` adapter on the factory return so backends with
`save` can pre-populate without exposing internals; backends
without `save` (HTTP-style) seed via their constructor options
instead.
- Runner-neutral: imports `describe` / `it` / `expect` from
`@suaveplan/testing/runner`, so the same harness runs under
Vitest or Bun's test runner without modification.
## Usage: Basic Example
```ts
import { runBackendConformance } from "@suaveplan/i18n-shared/testing";
import { MemoryBackend } from "@suaveplan/i18n-backend-memory";
runBackendConformance("MemoryBackend", async () => {
const backend = new MemoryBackend();
return {
backend,
async seed(locale, namespace, record) {
await backend.save(locale, namespace, record as never);
},
};
});
```
## Usage: Advanced Example
A read-only backend (no `save`, no `readMulti`) supplies its own
seeding via constructor options and omits `seed` from the factory
return. The harness inspects `backend.capabilities` and the
structural presence of optional methods to decide which assertions
to run; unsupported assertions exit early.
```ts
import { runBackendConformance } from "@suaveplan/i18n-shared/testing";
import { httpBackend } from "@suaveplan/i18n-backend-http";
runBackendConformance("HttpBackend (seeded fetch)", async () => {
const backend = httpBackend({
loadPath: "https://i18n.example/{{locale}}/{{ns}}.json",
fetch: async () =>
new Response(JSON.stringify({ login: { title: "Sign in" } })),
});
return { backend };
});
```
## API Reference
### `runBackendConformance(label, makeFactory)`
- `label: string` — Human-readable backend label inserted into the
outer `describe(...)` title.
- `makeFactory: () => Promise<ConformanceContext>` — Async factory
invoked once per `it(...)`; produces a fresh backend instance and
optional `seed` callback. Always producing a fresh backend per
call prevents cross-test state leakage.
- Returns: `void`. The harness registers `describe` / `it` blocks
with the active runner; assertions execute when the runner runs
the file.
### `ConformanceContext`
```ts
interface ConformanceContext {
readonly backend: Backend; // from @suaveplan/types
readonly seed?: (
locale: string,
namespace: string,
record: Record<string, unknown>
) => Promise<void>;
}
```
### `ConformanceFactory`
`type ConformanceFactory = () => Promise<ConformanceContext>;`
## Implementation Notes
- The harness gates each capability check on **both** the structural
presence of the method (`backend.save`) **and** the corresponding
capability flag (`backend.capabilities.canSave`). This catches
backends whose flags drift out of sync with their methods at
authoring time and keeps the harness honest for partial
implementations.
- The harness imports the `Backend` interface from
`@suaveplan/types`, never from `@suaveplan/i18n` core. Backends
consume the same type from the same location, so the harness adds
zero coupling to the slim core.
- `lastModified` monotonicity uses a 2-ms `setTimeout` between the
two `save` calls to defend against backends whose injected clock
has 1-ms resolution. Backends with deterministic counters always
satisfy `t2 >= t1`.
- This harness is the Wave 5 sequencing keystone: the five backend
packages (`memory`, `http`, `storage`, `fs-multifile`, `chained`)
all call it. Lifting it here lets every backend land independently
without copy-pasting test fixtures.