test: cross-product Instant Preview's evidence-key pair with the adapter's
`evidence-key-agreement.test.ts` cross-products 144 adversarial combinations across the mock adapter's gate and descriptor resolver, `validateWorkingCopy` and the shared pixel resolver -- but omitted `instant-preview.tsx`'s own gate and descriptor resolver. That pair is byte-parallel to the adapter's (it lives in `presentation/`, which may not import `adapters/`, so it carries its own copy of the legacy-key predicate) and it is exactly the pair three earlier fix rounds regressed. `instant-preview.tsx` now exports the two expressions it already used, so the test drives the production code rather than rebuilding it. Added assertions: gate agreement in both array orders, descriptor equality with the adapter's, pixel/descriptor agreement, and order independence. The one sanctioned divergence -- the legacy static key's `assetId`, a fixed literal here versus a registry-derived one there -- is pinned to that case rather than ignored. Mutation-checked: breaking the legacy predicate reports 96 of 144 combinations disagreeing, breaking the legacy descriptor path reports 64 of 144. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e2a0e695dd
commit
65f8528ccc
@@ -9,6 +9,16 @@
|
||||
// agreement itself, through production entry points only.
|
||||
//
|
||||
// Against fix round 3's code this reported 53 of 144 combinations disagreeing.
|
||||
//
|
||||
// The callers covered are the mock adapter's projection gate and descriptor
|
||||
// resolver, `validateWorkingCopy`, the shared pixel resolver, and -- added in
|
||||
// the final fix wave -- `instant-preview.tsx`'s own gate and descriptor
|
||||
// resolver. The last pair is byte-parallel to the adapter's (they live in
|
||||
// different layers: `presentation/` may not import `adapters/`), and is the
|
||||
// pair three earlier fix rounds regressed. Its one sanctioned divergence is
|
||||
// the legacy static key's `assetId`, which the assertions below pin rather
|
||||
// than ignore. Mutating either half of that pair reports 96 and 64 of 144
|
||||
// combinations disagreeing respectively.
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "vitest";
|
||||
@@ -18,6 +28,10 @@ import { projectWorkingCopy } from "../../../src/features/tech-log/adapters/mock
|
||||
import { validateWorkingCopy } from "../../../src/features/tech-log/adapters/mock/validate-working-copy.ts";
|
||||
import { isSupportedEvidenceKey } from "../../../src/features/tech-log/adapters/static/evidence-assets.ts";
|
||||
import { createAssetCatalogResolver } from "../../../src/features/tech-log/presentation/shared/public-render/asset-resolvers.ts";
|
||||
import {
|
||||
instantPreviewEvidenceKeyGate,
|
||||
resolveInstantPreviewAssetDescriptor,
|
||||
} from "../../../src/features/tech-log/presentation/studio/components/instant-preview.tsx";
|
||||
import { ContentFormatError } from "../../../src/features/tech-log/domain/content-format/parse-case-content.ts";
|
||||
|
||||
const LEGACY = "fetch-strategy-boundary";
|
||||
@@ -185,6 +199,17 @@ test("gate, projection resolver, pixel resolver and validation never disagree",
|
||||
// 1. Validation and the preview projection accept exactly the same keys.
|
||||
check(!unsupported === projected.accepted, `${where}: validation and projection disagree`);
|
||||
|
||||
// 1b. Instant Preview's gate is a fourth caller of the same decision,
|
||||
// living in `presentation/` (which may not import `adapters/`) and
|
||||
// therefore carrying its own copy of the legacy-key predicate. It
|
||||
// must accept exactly the same keys, in either array order.
|
||||
const previewAccepts = instantPreviewEvidenceKeyGate(assets)(key);
|
||||
check(previewAccepts === projected.accepted, `${where}: Instant Preview gate and the projection disagree`);
|
||||
check(
|
||||
instantPreviewEvidenceKeyGate([...assets].reverse())(key) === previewAccepts,
|
||||
`${where}: order-dependent Instant Preview gate`,
|
||||
);
|
||||
|
||||
if (!projected.accepted) {
|
||||
check(projected.error instanceof ContentFormatError, `${where}: expected a gate rejection, got ${String(projected.error)}`);
|
||||
check(
|
||||
@@ -227,6 +252,46 @@ test("gate, projection resolver, pixel resolver and validation never disagree",
|
||||
const emptyAlt = validate(key, "", assets);
|
||||
const altRequired = emptyAlt.issues.some((issue) => issue.code === "EVIDENCE_ALT_REQUIRED");
|
||||
check(altRequired === !projected.descriptor.decorative, `${where}: alt rule judged a different asset than the one rendered`);
|
||||
|
||||
// 7. Instant Preview's own pair agrees with the mock adapter's. The two
|
||||
// are byte-parallel expressions in different layers (`presentation/`
|
||||
// may not import `adapters/`), and the pair regressed in three
|
||||
// separate fix rounds, so the agreement is asserted rather than
|
||||
// assumed. The single sanctioned divergence is the legacy static
|
||||
// key's `assetId`: with no Asset backing it, Instant Preview returns
|
||||
// a fixed literal where the adapter derives one from the static
|
||||
// registry. Everything else -- acceptance, path, dimensions,
|
||||
// mediaType, decorative -- must match exactly, and the divergence is
|
||||
// pinned so a future change cannot widen it unnoticed.
|
||||
const previewDescriptor = resolveInstantPreviewAssetDescriptor(assets)(key);
|
||||
const { assetId: previewAssetId, ...previewRest } = previewDescriptor;
|
||||
const { assetId: mockAssetId, ...mockRest } = projected.descriptor as unknown as
|
||||
Record<string, unknown> & { assetId: string };
|
||||
check(
|
||||
JSON.stringify(previewRest) === JSON.stringify(mockRest),
|
||||
`${where}: Instant Preview descriptor differs from the adapter's beyond assetId (${JSON.stringify(previewRest)} vs ${JSON.stringify(mockRest)})`,
|
||||
);
|
||||
check(
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(previewAssetId),
|
||||
`${where}: Instant Preview assetId is not a stable UUID (${previewAssetId})`,
|
||||
);
|
||||
check(
|
||||
(previewAssetId === mockAssetId) === Boolean(assets.some((candidate) => candidate.assetKey === key && candidate.id === mockAssetId)),
|
||||
`${where}: assetId agreement is not confined to the legacy fallback`,
|
||||
);
|
||||
|
||||
// 8. Instant Preview's descriptor and the pixels it renders are one asset.
|
||||
check(
|
||||
pixels.src === previewDescriptor.publicPath,
|
||||
`${where}: Instant Preview descriptor (${previewDescriptor.publicPath}) and pixels (${pixels.src}) disagree`,
|
||||
);
|
||||
|
||||
// 9. Order independence holds for Instant Preview's resolver too.
|
||||
check(
|
||||
JSON.stringify(resolveInstantPreviewAssetDescriptor([...assets].reverse())(key)) ===
|
||||
JSON.stringify(previewDescriptor),
|
||||
`${where}: order-dependent Instant Preview descriptor`,
|
||||
);
|
||||
}
|
||||
}
|
||||
assert.ok(checked >= 100, `only ${checked} combinations checked`);
|
||||
|
||||
Reference in New Issue
Block a user