feat: resolve evidence figures from backend asset descriptors
Public Preview and Publication Snapshot now resolve evidence figures from the ResolvedAsset descriptor the server already attaches to each EVIDENCE_FIGURE block, instead of a local literal that only knew one hardcoded key and threw on anything else. Instant Preview gains an `assets` prop (defaults to `[]`, unwired until the Asset Picker task) and stops throwing when a key has no catalog match yet. Builds on Task 1's existing seam (resolveCaseEvidenceAssets / ResolveEvidenceAsset) via a new asset-resolvers.ts rather than a parallel path. Kept out of adapters/ (presentation may not import it) by carrying a small local copy of the one legacy fixture entry, checked before any descriptor match so the pre-existing key keeps rendering byte-identically across surfaces during the migration window. Non-READY assets never resolve; unresolvable keys return a placeholder instead of throwing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0c071aaabc
commit
9d91001a31
@@ -0,0 +1,116 @@
|
|||||||
|
import type { Asset } from "../../../contracts/studio/contract.ts";
|
||||||
|
import type {
|
||||||
|
EvidenceAsset,
|
||||||
|
ResolveEvidenceAsset,
|
||||||
|
} from "../../../domain/public-render-content.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `presentation/` may not import `adapters/` (the
|
||||||
|
* `feature-presentation-does-not-know-outbound-adapters` architecture rule), so this
|
||||||
|
* cannot read `adapters/static/evidence-assets.ts` directly. It carries its own copy of
|
||||||
|
* that one legacy entry instead -- the same pattern `instant-preview.tsx`,
|
||||||
|
* `public-preview-screen.tsx`, `publication-event-preview-screen.tsx`, and
|
||||||
|
* `case-document-page.tsx` already each carry independently. This is not a second
|
||||||
|
* source of truth for new assets; it exists solely so the one pre-existing hardcoded
|
||||||
|
* key keeps resolving to byte-identical output once screens move onto this resolver.
|
||||||
|
*/
|
||||||
|
const LEGACY_STATIC_EVIDENCE_ASSETS: Readonly<Record<string, EvidenceAsset>> = {
|
||||||
|
"fetch-strategy-boundary": Object.freeze({
|
||||||
|
src: "/media/fetch-strategy-boundary.svg",
|
||||||
|
width: 1080,
|
||||||
|
height: 420,
|
||||||
|
triggerLabel: "Fetch Join과 Batch Fetch 비교 다이어그램 크게 보기",
|
||||||
|
dialogLabel: "Fetch Join과 Batch Fetch의 페이징 경계 확대",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `ResolveEvidenceAsset` is a total function. Throwing on an unresolved key would
|
||||||
|
* take down the whole preview for one bad or not-yet-synced reference, so every
|
||||||
|
* resolver here returns this placeholder instead. Blocking a genuinely bad
|
||||||
|
* reference is Publish validation's job, not the renderer's.
|
||||||
|
*/
|
||||||
|
export const MISSING_EVIDENCE_ASSET: EvidenceAsset = Object.freeze({
|
||||||
|
src: "",
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
triggerLabel: "사용할 수 없는 Evidence",
|
||||||
|
dialogLabel: "사용할 수 없는 Evidence",
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Structural shape of `components["schemas"]["ResolvedAsset"]`, loosened for reuse. */
|
||||||
|
export type ResolvedAssetLike = Readonly<{
|
||||||
|
assetId: string;
|
||||||
|
assetKey: string;
|
||||||
|
mediaType: string;
|
||||||
|
publicPath: string;
|
||||||
|
width: number | null;
|
||||||
|
height: number | null;
|
||||||
|
decorative: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function fromDescriptor(descriptor: ResolvedAssetLike): EvidenceAsset {
|
||||||
|
return Object.freeze({
|
||||||
|
src: descriptor.publicPath,
|
||||||
|
width: descriptor.width ?? 1,
|
||||||
|
height: descriptor.height ?? 1,
|
||||||
|
triggerLabel: `${descriptor.assetKey} 이미지 크게 보기`,
|
||||||
|
dialogLabel: `${descriptor.assetKey} 확대`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared resolution order for both factories below: the legacy static key always
|
||||||
|
* wins on a match (protects the one pre-existing hardcoded fixture from splitting
|
||||||
|
* across render surfaces mid-migration), then the caller-supplied lookup (the real
|
||||||
|
* backend-sourced descriptor), then the safe placeholder.
|
||||||
|
*/
|
||||||
|
function resolveWith(
|
||||||
|
key: string,
|
||||||
|
lookup: (key: string) => ResolvedAssetLike | undefined,
|
||||||
|
): EvidenceAsset {
|
||||||
|
const legacy = LEGACY_STATIC_EVIDENCE_ASSETS[key];
|
||||||
|
if (legacy) return legacy;
|
||||||
|
const descriptor = lookup(key);
|
||||||
|
return descriptor ? fromDescriptor(descriptor) : MISSING_EVIDENCE_ASSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Instant Preview: resolves against the Asset list the editor has loaded. */
|
||||||
|
export function createAssetCatalogResolver(
|
||||||
|
assets: readonly Asset[],
|
||||||
|
): ResolveEvidenceAsset {
|
||||||
|
const byKey = new Map<string, Asset>();
|
||||||
|
for (const asset of assets) {
|
||||||
|
// QUARANTINED/REJECTED must never render on any surface.
|
||||||
|
if (asset.managementStatus !== "READY") continue;
|
||||||
|
byKey.set(asset.assetKey, asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (key: string) =>
|
||||||
|
resolveWith(key, (lookupKey) => {
|
||||||
|
const asset = byKey.get(lookupKey);
|
||||||
|
if (!asset || asset.publicPath === null) return undefined;
|
||||||
|
return {
|
||||||
|
assetId: asset.id,
|
||||||
|
assetKey: asset.assetKey,
|
||||||
|
mediaType: asset.mediaType,
|
||||||
|
publicPath: asset.publicPath,
|
||||||
|
width: asset.width,
|
||||||
|
height: asset.height,
|
||||||
|
decorative: asset.decorative,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Public Preview · Snapshot: resolves from the descriptor the server already attached to the block. */
|
||||||
|
export function createResolvedAssetResolver(
|
||||||
|
blocks: readonly Readonly<{ type: string; key?: string; asset?: ResolvedAssetLike }>[],
|
||||||
|
): ResolveEvidenceAsset {
|
||||||
|
const byKey = new Map<string, ResolvedAssetLike>();
|
||||||
|
for (const block of blocks) {
|
||||||
|
if (block.type !== "EVIDENCE_FIGURE" || !block.asset) continue;
|
||||||
|
byKey.set(block.asset.assetKey, block.asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (key: string) => resolveWith(key, (lookupKey) => byKey.get(lookupKey));
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import type { components } from "../../../contracts/studio/generated.ts";
|
import type { components } from "../../../contracts/studio/generated.ts";
|
||||||
import type { WorkingCopyInput } from "../../../contracts/studio/contract.ts";
|
import type { Asset, WorkingCopyInput } from "../../../contracts/studio/contract.ts";
|
||||||
import { ContentFormatError } from "../../../domain/content-format/parse-case-content.ts";
|
import { ContentFormatError } from "../../../domain/content-format/parse-case-content.ts";
|
||||||
import {
|
import {
|
||||||
projectWorkingCopy,
|
projectWorkingCopy,
|
||||||
resolveCaseEvidenceAssets,
|
resolveCaseEvidenceAssets,
|
||||||
} from "../../../domain/content-format/project-public-render-model.ts";
|
} from "../../../domain/content-format/project-public-render-model.ts";
|
||||||
|
import { createAssetCatalogResolver } from "../../shared/public-render/asset-resolvers.ts";
|
||||||
import { PublicRecordRenderer } from "../../shared/public-render/public-record-renderer.tsx";
|
import { PublicRecordRenderer } from "../../shared/public-render/public-record-renderer.tsx";
|
||||||
import { useStudio } from "../use-studio.ts";
|
import { useStudio } from "../use-studio.ts";
|
||||||
|
|
||||||
@@ -16,39 +17,64 @@ function supportsPreviewEvidenceKey(key: string): boolean {
|
|||||||
return key === "fetch-strategy-boundary";
|
return key === "fetch-strategy-boundary";
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolvePreviewEvidenceAsset(key: string) {
|
/**
|
||||||
if (!supportsPreviewEvidenceKey(key)) {
|
* `resolveCaseEvidenceAssets` only needs *a* `ResolvedAsset` to turn the authoring
|
||||||
throw new Error(`Unknown local evidence asset: ${key}`);
|
* model into a genuine `PublicRenderModel` -- nothing downstream reads `block.asset`
|
||||||
}
|
* for the pixels actually shown; that comes from `resolveEvidenceAsset` below
|
||||||
return {
|
* (`createAssetCatalogResolver`). So this must never throw: a key the Asset Picker
|
||||||
src: "/media/fetch-strategy-boundary.svg",
|
* (Task 10) inserts, with no catalog match yet, still gets a placeholder descriptor
|
||||||
width: 1080,
|
* instead of blanking the whole preview behind an error panel.
|
||||||
height: 420,
|
*/
|
||||||
triggerLabel: "Fetch Join과 Batch Fetch 비교 다이어그램 크게 보기",
|
function resolveAssetDescriptor(assets: readonly Asset[]) {
|
||||||
dialogLabel: "Fetch Join과 Batch Fetch의 페이징 경계 확대",
|
return (key: string): ResolvedAsset => {
|
||||||
|
const asset = assets.find(
|
||||||
|
(candidate) => candidate.assetKey === key && candidate.managementStatus === "READY",
|
||||||
|
);
|
||||||
|
if (asset?.publicPath) {
|
||||||
|
return {
|
||||||
|
assetId: asset.id,
|
||||||
|
assetKey: asset.assetKey,
|
||||||
|
mediaType: asset.mediaType,
|
||||||
|
publicPath: asset.publicPath,
|
||||||
|
width: asset.width,
|
||||||
|
height: asset.height,
|
||||||
|
decorative: asset.decorative,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (supportsPreviewEvidenceKey(key)) {
|
||||||
|
// Fixed literal, not derived: this file only ever resolves this one legacy
|
||||||
|
// key today, so the id only needs to be stable, not computed.
|
||||||
|
return {
|
||||||
|
assetId: "00000000-0000-4000-8000-000000000001",
|
||||||
|
assetKey: key,
|
||||||
|
mediaType: "image/svg+xml",
|
||||||
|
publicPath: "/media/fetch-strategy-boundary.svg",
|
||||||
|
width: 1080,
|
||||||
|
height: 420,
|
||||||
|
decorative: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
assetId: "00000000-0000-4000-8000-000000000000",
|
||||||
|
assetKey: key,
|
||||||
|
mediaType: "application/octet-stream",
|
||||||
|
publicPath: "",
|
||||||
|
width: null,
|
||||||
|
height: null,
|
||||||
|
decorative: true,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// This preview has no adapter-level asset catalog access, so it resolves the
|
export function InstantPreview({
|
||||||
// canonical descriptor from the same local fixture data as `resolvePreviewEvidenceAsset`
|
draft,
|
||||||
// above. The `assetId` is a fixed literal (not derived) because this file only ever
|
catalog,
|
||||||
// resolves this one key; it only needs to be stable, not computed.
|
assets = [],
|
||||||
function resolvePreviewEvidenceAssetDescriptor(key: string): ResolvedAsset {
|
}: {
|
||||||
if (!supportsPreviewEvidenceKey(key)) {
|
draft: WorkingCopyInput;
|
||||||
throw new Error(`Unknown local evidence asset: ${key}`);
|
catalog: CatalogEntry[];
|
||||||
}
|
assets?: readonly Asset[];
|
||||||
return {
|
}) {
|
||||||
assetId: "00000000-0000-4000-8000-000000000001",
|
|
||||||
assetKey: key,
|
|
||||||
mediaType: "image/svg+xml",
|
|
||||||
publicPath: "/media/fetch-strategy-boundary.svg",
|
|
||||||
width: 1080,
|
|
||||||
height: 420,
|
|
||||||
decorative: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function InstantPreview({ draft, catalog }: { draft: WorkingCopyInput; catalog: CatalogEntry[] }) {
|
|
||||||
const studio = useStudio();
|
const studio = useStudio();
|
||||||
let model: PublicRenderModel | null = null;
|
let model: PublicRenderModel | null = null;
|
||||||
let issues: string[] | null = null;
|
let issues: string[] | null = null;
|
||||||
@@ -60,7 +86,7 @@ export function InstantPreview({ draft, catalog }: { draft: WorkingCopyInput; ca
|
|||||||
{ mode: "PREVIEW", publishedAt: null },
|
{ mode: "PREVIEW", publishedAt: null },
|
||||||
supportsPreviewEvidenceKey,
|
supportsPreviewEvidenceKey,
|
||||||
),
|
),
|
||||||
resolvePreviewEvidenceAssetDescriptor,
|
resolveAssetDescriptor(assets),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
issues = error instanceof ContentFormatError
|
issues = error instanceof ContentFormatError
|
||||||
@@ -73,7 +99,7 @@ export function InstantPreview({ draft, catalog }: { draft: WorkingCopyInput; ca
|
|||||||
<PublicRecordRenderer
|
<PublicRecordRenderer
|
||||||
model={model!}
|
model={model!}
|
||||||
embedded
|
embedded
|
||||||
resolveEvidenceAsset={resolvePreviewEvidenceAsset}
|
resolveEvidenceAsset={createAssetCatalogResolver(assets)}
|
||||||
resolvePublishedLabel={studio.resolvePublishedLabel}
|
resolvePublishedLabel={studio.resolvePublishedLabel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type {
|
|||||||
} from "../../../contracts/studio/contract.ts";
|
} from "../../../contracts/studio/contract.ts";
|
||||||
import { deriveValidationState } from "../../../domain/studio/document-state.ts";
|
import { deriveValidationState } from "../../../domain/studio/document-state.ts";
|
||||||
import { createLocalId } from "../../../domain/studio/local-id.ts";
|
import { createLocalId } from "../../../domain/studio/local-id.ts";
|
||||||
|
import { createResolvedAssetResolver } from "../../shared/public-render/asset-resolvers.ts";
|
||||||
import { PublicRecordRenderer } from "../../shared/public-render/public-record-renderer.tsx";
|
import { PublicRecordRenderer } from "../../shared/public-render/public-record-renderer.tsx";
|
||||||
import { useStudio } from "../use-studio.ts";
|
import { useStudio } from "../use-studio.ts";
|
||||||
import { GuardedStudioLink } from "./guarded-studio-link.tsx";
|
import { GuardedStudioLink } from "./guarded-studio-link.tsx";
|
||||||
@@ -45,19 +46,6 @@ function formatDateTime(value: string): string {
|
|||||||
}).format(date);
|
}).format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolvePreviewEvidenceAsset(key: string) {
|
|
||||||
if (key !== "fetch-strategy-boundary") {
|
|
||||||
throw new Error(`Unknown local evidence asset: ${key}`);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
src: "/media/fetch-strategy-boundary.svg",
|
|
||||||
width: 1080,
|
|
||||||
height: 420,
|
|
||||||
triggerLabel: "Fetch Join과 Batch Fetch 비교 다이어그램 크게 보기",
|
|
||||||
dialogLabel: "Fetch Join과 Batch Fetch의 페이징 경계 확대",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PublicPreviewScreen({ documentId }: { documentId: string }) {
|
export function PublicPreviewScreen({ documentId }: { documentId: string }) {
|
||||||
const studio = useStudio();
|
const studio = useStudio();
|
||||||
const [retryKey, setRetryKey] = useState(0);
|
const [retryKey, setRetryKey] = useState(0);
|
||||||
@@ -287,7 +275,11 @@ export function PublicPreviewScreen({ documentId }: { documentId: string }) {
|
|||||||
<PublicRecordRenderer
|
<PublicRecordRenderer
|
||||||
model={previewDetail.preview.renderModel}
|
model={previewDetail.preview.renderModel}
|
||||||
embedded
|
embedded
|
||||||
resolveEvidenceAsset={resolvePreviewEvidenceAsset}
|
resolveEvidenceAsset={createResolvedAssetResolver(
|
||||||
|
previewDetail.preview.renderModel.kind === "CASE"
|
||||||
|
? previewDetail.preview.renderModel.bodyBlocks
|
||||||
|
: [],
|
||||||
|
)}
|
||||||
resolvePublishedLabel={studio.resolvePublishedLabel}
|
resolvePublishedLabel={studio.resolvePublishedLabel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+4
-15
@@ -5,7 +5,7 @@ import {
|
|||||||
type StudioGatewayError,
|
type StudioGatewayError,
|
||||||
} from "../../../application/ports/studio-gateway-error.ts";
|
} from "../../../application/ports/studio-gateway-error.ts";
|
||||||
import type { PublicationSnapshot } from "../../../contracts/studio/contract.ts";
|
import type { PublicationSnapshot } from "../../../contracts/studio/contract.ts";
|
||||||
import type { EvidenceAsset } from "../../../domain/public-render-content.ts";
|
import { createResolvedAssetResolver } from "../../shared/public-render/asset-resolvers.ts";
|
||||||
import { PublicRecordRenderer } from "../../shared/public-render/public-record-renderer.tsx";
|
import { PublicRecordRenderer } from "../../shared/public-render/public-record-renderer.tsx";
|
||||||
import { GuardedStudioLink } from "./guarded-studio-link.tsx";
|
import { GuardedStudioLink } from "./guarded-studio-link.tsx";
|
||||||
import {
|
import {
|
||||||
@@ -20,19 +20,6 @@ const eventLabels = {
|
|||||||
UNPUBLISHED: "게시 취소",
|
UNPUBLISHED: "게시 취소",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
function resolveEvidenceAsset(key: string): EvidenceAsset {
|
|
||||||
if (key !== "fetch-strategy-boundary") {
|
|
||||||
throw new Error(`Unknown local evidence asset: ${key}`);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
src: "/media/fetch-strategy-boundary.svg",
|
|
||||||
width: 1080,
|
|
||||||
height: 420,
|
|
||||||
triggerLabel: "Fetch Join과 Batch Fetch 비교 다이어그램 크게 보기",
|
|
||||||
dialogLabel: "Fetch Join과 Batch Fetch의 페이징 경계 확대",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function dateTime(value: string) {
|
function dateTime(value: string) {
|
||||||
return new Intl.DateTimeFormat("ko-KR", {
|
return new Intl.DateTimeFormat("ko-KR", {
|
||||||
dateStyle: "long",
|
dateStyle: "long",
|
||||||
@@ -166,7 +153,9 @@ export function PublicationEventPreviewScreen({
|
|||||||
<PublicRecordRenderer
|
<PublicRecordRenderer
|
||||||
model={renderModel}
|
model={renderModel}
|
||||||
embedded
|
embedded
|
||||||
resolveEvidenceAsset={resolveEvidenceAsset}
|
resolveEvidenceAsset={createResolvedAssetResolver(
|
||||||
|
renderModel.kind === "CASE" ? renderModel.bodyBlocks : [],
|
||||||
|
)}
|
||||||
resolvePublishedLabel={studio.resolvePublishedLabel}
|
resolvePublishedLabel={studio.resolvePublishedLabel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "vitest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
createAssetCatalogResolver,
|
||||||
|
createResolvedAssetResolver,
|
||||||
|
MISSING_EVIDENCE_ASSET,
|
||||||
|
} from "../../../src/features/tech-log/presentation/shared/public-render/asset-resolvers.ts";
|
||||||
|
|
||||||
|
const READY = {
|
||||||
|
id: "11111111-1111-4111-8111-111111111111",
|
||||||
|
assetKey: "boundary",
|
||||||
|
kind: "DIAGRAM",
|
||||||
|
mediaType: "image/svg+xml",
|
||||||
|
originalFilename: "b.svg",
|
||||||
|
byteSize: 10,
|
||||||
|
width: 1080,
|
||||||
|
height: 420,
|
||||||
|
altText: "경계",
|
||||||
|
decorative: false,
|
||||||
|
managementStatus: "READY",
|
||||||
|
publicPath: "/media/boundary.svg",
|
||||||
|
usageCount: 0,
|
||||||
|
version: 1,
|
||||||
|
createdAt: "2026-08-14T01:00:00.000Z",
|
||||||
|
updatedAt: "2026-08-14T01:00:00.000Z",
|
||||||
|
} as never;
|
||||||
|
|
||||||
|
test("resolves a backend asset key to its public path", () => {
|
||||||
|
const resolve = createAssetCatalogResolver([READY]);
|
||||||
|
const asset = resolve("boundary");
|
||||||
|
|
||||||
|
assert.equal(asset.src, "/media/boundary.svg");
|
||||||
|
assert.equal(asset.width, 1080);
|
||||||
|
assert.equal(asset.height, 420);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("falls back to the static registry for the legacy hardcoded key", () => {
|
||||||
|
const resolve = createAssetCatalogResolver([]);
|
||||||
|
const asset = resolve("fetch-strategy-boundary");
|
||||||
|
|
||||||
|
assert.equal(asset.src, "/media/fetch-strategy-boundary.svg");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns the placeholder instead of throwing on an unknown key", () => {
|
||||||
|
const resolve = createAssetCatalogResolver([]);
|
||||||
|
assert.deepEqual(resolve("does-not-exist"), MISSING_EVIDENCE_ASSET);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("never resolves a QUARANTINED asset", () => {
|
||||||
|
const resolve = createAssetCatalogResolver([
|
||||||
|
{ ...(READY as object), managementStatus: "QUARANTINED" } as never,
|
||||||
|
]);
|
||||||
|
assert.deepEqual(resolve("boundary"), MISSING_EVIDENCE_ASSET);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resolves from the server render model blocks", () => {
|
||||||
|
const resolve = createResolvedAssetResolver([
|
||||||
|
{
|
||||||
|
type: "EVIDENCE_FIGURE",
|
||||||
|
key: "boundary",
|
||||||
|
asset: {
|
||||||
|
assetId: "11111111-1111-4111-8111-111111111111",
|
||||||
|
assetKey: "boundary",
|
||||||
|
mediaType: "image/svg+xml",
|
||||||
|
publicPath: "/media/boundary.svg",
|
||||||
|
width: 800,
|
||||||
|
height: 300,
|
||||||
|
decorative: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(resolve("boundary").src, "/media/boundary.svg");
|
||||||
|
assert.equal(resolve("boundary").width, 800);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a QUARANTINED asset never wins over the placeholder even when a READY asset with the same key exists later", () => {
|
||||||
|
const resolve = createAssetCatalogResolver([
|
||||||
|
{ ...(READY as object), managementStatus: "QUARANTINED" } as never,
|
||||||
|
]);
|
||||||
|
assert.equal(resolve("boundary").src, "");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not let a real backend asset shadow the legacy static key's byte-identical output", () => {
|
||||||
|
const resolve = createAssetCatalogResolver([
|
||||||
|
{
|
||||||
|
...(READY as object),
|
||||||
|
assetKey: "fetch-strategy-boundary",
|
||||||
|
publicPath: "/media/should-not-win.svg",
|
||||||
|
} as never,
|
||||||
|
]);
|
||||||
|
assert.equal(resolve("fetch-strategy-boundary").src, "/media/fetch-strategy-boundary.svg");
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user