content: integrate approved project evidence
This commit is contained in:
@@ -21,6 +21,11 @@ const stylesheetFiles = [
|
||||
"src/styles/world.css",
|
||||
"src/styles/print.css",
|
||||
];
|
||||
const approvedMedicationAssetIds = new Set([
|
||||
"medication-mobile-logs",
|
||||
"medication-mobile-data-settings",
|
||||
"medication-mobile-notification-settings",
|
||||
]);
|
||||
|
||||
type ServerRenderer = { render: (url: string, inputs: unknown) => Promise<string> };
|
||||
type InputValidator = { validateProductionInputs: (content: unknown, manifest: unknown, site: unknown, preparedAssetRoot: URL) => unknown };
|
||||
@@ -49,6 +54,8 @@ function withSyntheticProjectMedia(contentInput: unknown, manifestInput: unknown
|
||||
] as const;
|
||||
const project = content.projects.selected[0]!;
|
||||
|
||||
manifest.assets = manifest.assets.filter((asset) =>
|
||||
typeof asset.id !== "string" || !approvedMedicationAssetIds.has(asset.id));
|
||||
project.assetIds = media.map((presentation) => presentation.assetId);
|
||||
project.media = media;
|
||||
project.mediaLayout = "portrait-strip";
|
||||
|
||||
@@ -40,7 +40,6 @@ test('F8 remains authenticated and request-free while the public manifest has no
|
||||
await page.goto(authorizedFaultUrl(distUrl, 'F8-video-failure'), { waitUntil: 'networkidle' });
|
||||
|
||||
expect((await readFaultSnapshot(page)).fault).toBe('F8-video-failure');
|
||||
await expect(page.locator('[data-project-media]')).toHaveCount(0);
|
||||
await expect(page.locator('video, video source')).toHaveCount(0);
|
||||
expect(mediaRequests).toEqual([]);
|
||||
await assertSemanticFaultOracle(page);
|
||||
|
||||
@@ -1,28 +1,150 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { expect, test } from '../helpers/serve-dist';
|
||||
import { parsePortfolioContent, parsePublicAssetManifest } from '../../src/content/schemas';
|
||||
|
||||
test('pending approval renders a complete text-first portfolio without media requests or broken placeholders', async ({ page, distUrl }) => {
|
||||
const mediaRequests: string[] = [];
|
||||
const fixture = (path: string): unknown =>
|
||||
JSON.parse(readFileSync(resolve(process.cwd(), path), 'utf8')) as unknown;
|
||||
const content = parsePortfolioContent(fixture('content/public/portfolio-content.json'));
|
||||
const manifest = parsePublicAssetManifest(fixture('content/public/asset-manifest.json'));
|
||||
const projectPresentations = [...content.projects.selected, ...content.projects.additional]
|
||||
.flatMap((project) => project.media ?? []);
|
||||
const approvedStillCount = projectPresentations.filter(({ kind }) => kind !== 'video').length;
|
||||
const expectedImages = projectPresentations.flatMap((presentation) => {
|
||||
if (presentation.kind === 'video') return [];
|
||||
const asset = manifest.assets.find(({ id }) => id === presentation.assetId)!;
|
||||
return [{
|
||||
src: `/assets/${asset.fileName}`,
|
||||
width: String(asset.width),
|
||||
height: String(asset.height),
|
||||
alt: presentation.alt,
|
||||
}];
|
||||
});
|
||||
|
||||
test('approved still evidence uses manifest assets and intrinsic lazy image semantics', async ({ page, distUrl }) => {
|
||||
const videoRequests: string[] = [];
|
||||
page.on('request', (request) => {
|
||||
if (/\.(?:avif|jpe?g|png|webp|mp4|webm)(?:$|\?)/iu.test(request.url())) mediaRequests.push(request.url());
|
||||
if (/\.(?:mp4|webm)(?:$|[?#])/iu.test(request.url())) videoRequests.push(request.url());
|
||||
});
|
||||
await page.goto(distUrl, { waitUntil: 'networkidle' });
|
||||
|
||||
await expect(page.locator('main > section')).toHaveCount(8);
|
||||
await expect(page.locator('#selected-builds article')).toHaveCount(3);
|
||||
await expect(page.locator('#additional-work article')).toHaveCount(4);
|
||||
await expect(page.locator('[data-project-media]')).toHaveCount(0);
|
||||
await expect(page.locator('[data-project-media]')).toHaveCount(approvedStillCount);
|
||||
await expect(page.locator('[data-project-media] img')).toHaveCount(expectedImages.length);
|
||||
expect(await page.locator('[data-project-media] img').evaluateAll((images) => images.map((image) => ({
|
||||
src: new URL((image as HTMLImageElement).src).pathname,
|
||||
width: image.getAttribute('width'),
|
||||
height: image.getAttribute('height'),
|
||||
alt: image.getAttribute('alt'),
|
||||
loading: (image as HTMLImageElement).loading,
|
||||
decoding: (image as HTMLImageElement).decoding,
|
||||
})))).toEqual(expectedImages.map((image) => ({ ...image, loading: 'lazy', decoding: 'async' })));
|
||||
await expect(page.locator('video, video source')).toHaveCount(0);
|
||||
await expect(page.locator('[data-project-id="private-medication-tracker"] a')).toHaveCount(0);
|
||||
await expect(page.locator('.journey-poster')).not.toHaveAttribute('src', /.+/u);
|
||||
expect(mediaRequests).toEqual([]);
|
||||
expect(videoRequests).toEqual([]);
|
||||
});
|
||||
|
||||
test('reduced motion does not introduce automatic project media payloads', async ({ page, distUrl }) => {
|
||||
test('Static and reduced motion keep approved stills without introducing video behavior', async ({ page, distUrl }) => {
|
||||
const videoRequests: string[] = [];
|
||||
page.on('request', (request) => {
|
||||
if (/\.(?:mp4|webm)(?:$|\?)/iu.test(request.url())) videoRequests.push(request.url());
|
||||
if (/\.(?:mp4|webm)(?:$|[?#])/iu.test(request.url())) videoRequests.push(request.url());
|
||||
});
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||
await page.addInitScript(() => localStorage.setItem('portfolio.motion.maximum-quality', 'static'));
|
||||
await page.goto(distUrl, { waitUntil: 'networkidle' });
|
||||
|
||||
await expect(page.locator('[data-project-media] img')).toHaveCount(approvedStillCount);
|
||||
await expect(page.locator('video[src], video source[src]')).toHaveCount(0);
|
||||
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||
await page.reload({ waitUntil: 'networkidle' });
|
||||
|
||||
await expect(page.locator('[data-project-media] img')).toHaveCount(approvedStillCount);
|
||||
await expect(page.locator('video[src], video source[src]')).toHaveCount(0);
|
||||
expect(videoRequests).toEqual([]);
|
||||
});
|
||||
|
||||
test('failed project images leave titles, captions, chart explanations, and alt text in the document', async ({ page, distUrl }) => {
|
||||
await page.route('**/assets/project-*.webp', (route) => route.abort('failed'));
|
||||
await page.goto(distUrl, { waitUntil: 'networkidle' });
|
||||
|
||||
await expect(page.locator('[data-project-media] img')).toHaveCount(approvedStillCount);
|
||||
await expect(page.locator('[data-project-id="private-medication-tracker"]')).toContainText('Private self-hosted medication tracker');
|
||||
await expect(page.locator('[data-project-id="private-medication-tracker"] figcaption')).toHaveCount(3);
|
||||
await expect(page.locator('[data-project-id="opinion-dynamics-simulation"] [data-testid="chart-conclusion"]')).toHaveCount(2);
|
||||
await expect(page.locator('[data-project-id="currency-converter"] img')).toHaveAttribute('alt', /conversion interface/iu);
|
||||
});
|
||||
|
||||
test('desktop chart descriptions and exact values occupy sequential full-width rows', async ({ page, distUrl }, testInfo) => {
|
||||
test.skip(testInfo.project.name !== 'desktop-chromium', 'Desktop chart grid geometry is verified once.');
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page.addInitScript(() => localStorage.setItem('portfolio.motion.maximum-quality', 'static'));
|
||||
await page.goto(distUrl, { waitUntil: 'networkidle' });
|
||||
|
||||
const chart = page.locator('[data-project-id="opinion-dynamics-simulation"] .project-media--chart').first();
|
||||
const geometry = await chart.evaluate((figure) => {
|
||||
const description = figure.querySelector<HTMLElement>('.chart-description')!;
|
||||
const table = figure.querySelector<HTMLElement>('.chart-values')!;
|
||||
const figureRect = figure.getBoundingClientRect();
|
||||
const descriptionStyle = getComputedStyle(description);
|
||||
const tableStyle = getComputedStyle(table);
|
||||
return {
|
||||
descriptionColumn: descriptionStyle.gridColumnStart,
|
||||
descriptionRow: descriptionStyle.gridRowStart,
|
||||
tableColumn: tableStyle.gridColumnStart,
|
||||
tableRow: tableStyle.gridRowStart,
|
||||
descriptionWidthRatio: description.getBoundingClientRect().width / figureRect.width,
|
||||
tableWidthRatio: table.getBoundingClientRect().width / figureRect.width,
|
||||
};
|
||||
});
|
||||
const opinionHeight = await page.locator('[data-project-id="opinion-dynamics-simulation"]')
|
||||
.evaluate((article) => article.getBoundingClientRect().height);
|
||||
|
||||
expect(geometry).toEqual({
|
||||
descriptionColumn: '1',
|
||||
descriptionRow: '3',
|
||||
tableColumn: '1',
|
||||
tableRow: '4',
|
||||
descriptionWidthRatio: expect.any(Number),
|
||||
tableWidthRatio: expect.any(Number),
|
||||
});
|
||||
expect(geometry.descriptionWidthRatio).toBeGreaterThanOrEqual(0.8);
|
||||
expect(geometry.tableWidthRatio).toBeGreaterThanOrEqual(0.8);
|
||||
expect(opinionHeight).toBeLessThan(3_000);
|
||||
});
|
||||
|
||||
test('print project captions and evidence text retain readable contrast', async ({ page, distUrl }, testInfo) => {
|
||||
test.skip(testInfo.project.name !== 'desktop-chromium', 'Print contrast is verified once.');
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page.emulateMedia({ media: 'print' });
|
||||
await page.goto(distUrl, { waitUntil: 'networkidle' });
|
||||
|
||||
const result = await page.locator('.project-media').evaluateAll((figures) => {
|
||||
const channel = (value: number): number => {
|
||||
const normalized = value / 255;
|
||||
return normalized <= 0.04045 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
|
||||
};
|
||||
const luminance = (color: string): number => {
|
||||
const values = color.match(/[\d.]+/gu)?.slice(0, 3).map(Number) ?? [0, 0, 0];
|
||||
return 0.2126 * channel(values[0]!) + 0.7152 * channel(values[1]!) + 0.0722 * channel(values[2]!);
|
||||
};
|
||||
const contrast = (foreground: string, background: string): number => {
|
||||
const [lighter, darker] = [luminance(foreground), luminance(background)].sort((a, b) => b - a);
|
||||
return (lighter! + 0.05) / (darker! + 0.05);
|
||||
};
|
||||
const samples = figures.flatMap((figure) => {
|
||||
const background = getComputedStyle(figure).backgroundColor;
|
||||
return [...figure.querySelectorAll<HTMLElement>('figcaption, .media-description, .chart-description, .chart-values')]
|
||||
.map((element) => contrast(getComputedStyle(element).color, background));
|
||||
});
|
||||
return {
|
||||
backgrounds: [...new Set(figures.map((figure) => getComputedStyle(figure).backgroundColor))],
|
||||
minimumContrast: Math.min(...samples),
|
||||
};
|
||||
});
|
||||
|
||||
expect(result.backgrounds).toEqual(['rgb(255, 255, 255)']);
|
||||
expect(result.minimumContrast).toBeGreaterThanOrEqual(4.5);
|
||||
});
|
||||
|
||||
@@ -37,6 +37,11 @@ const syntheticChartPair = [
|
||||
{ kind: 'image', assetId: 'synthetic-chart-wide', alt: 'Synthetic wide chart evidence.', caption: 'Fictional wide chart.' },
|
||||
{ kind: 'image', assetId: 'synthetic-chart-tall', alt: 'Synthetic tall chart evidence.', caption: 'Fictional tall chart.' },
|
||||
] as const;
|
||||
const approvedMedicationAssetIds = new Set([
|
||||
'medication-mobile-logs',
|
||||
'medication-mobile-data-settings',
|
||||
'medication-mobile-notification-settings',
|
||||
]);
|
||||
|
||||
function withSyntheticProjectMedia(contentInput: unknown, manifestInput: unknown): { content: unknown; manifest: unknown } {
|
||||
const content = structuredClone(contentInput) as {
|
||||
@@ -45,6 +50,8 @@ function withSyntheticProjectMedia(contentInput: unknown, manifestInput: unknown
|
||||
const manifest = structuredClone(manifestInput) as { assets: Array<Record<string, unknown>> };
|
||||
const project = content.projects.selected[0]!;
|
||||
|
||||
manifest.assets = manifest.assets.filter((asset) =>
|
||||
typeof asset.id !== 'string' || !approvedMedicationAssetIds.has(asset.id));
|
||||
project.assetIds = syntheticProjectMedia.map((media) => media.assetId);
|
||||
project.media = syntheticProjectMedia;
|
||||
project.mediaLayout = 'portrait-strip';
|
||||
@@ -221,7 +228,7 @@ test('aligns chart-pair captions for deliberately mixed source aspect ratios', a
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await loadInformationLayer(page);
|
||||
|
||||
const captions = page.locator('.project-media-gallery[data-media-layout="chart-pair"] figcaption');
|
||||
const captions = page.locator('[data-project-id="footy-tracker"] .project-media-gallery[data-media-layout="chart-pair"] figcaption');
|
||||
await expect(captions).toHaveCount(2);
|
||||
const tops = await captions.evaluateAll((nodes) => nodes.map((node) => node.getBoundingClientRect().top));
|
||||
expect(Math.abs(tops[0]! - tops[1]!)).toBeLessThanOrEqual(1);
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import sharp from "sharp";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import type { Locator, Page, TestInfo } from "@playwright/test";
|
||||
import { expect, test } from "../helpers/serve-dist";
|
||||
import { parsePortfolioContent } from "../../src/content/schemas";
|
||||
|
||||
const content = parsePortfolioContent(JSON.parse(
|
||||
readFileSync(resolve(process.cwd(), "content/public/portfolio-content.json"), "utf8"),
|
||||
) as unknown);
|
||||
const approvedProjectStillCount = [
|
||||
...content.projects.selected,
|
||||
...content.projects.additional,
|
||||
].flatMap((project) => project.media ?? []).filter(({ kind }) => kind !== "video").length;
|
||||
|
||||
interface VisualViewportCase {
|
||||
readonly label: string;
|
||||
@@ -301,7 +312,7 @@ test.describe("production visual composition", () => {
|
||||
expect(composition.textShadows.every((shadow) => shadow === "none")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(composition.publicProjectMediaCount).toBe(0);
|
||||
expect(composition.publicProjectMediaCount).toBe(approvedProjectStillCount);
|
||||
expect(composition.fixedSurfaceOverlaps).toEqual([]);
|
||||
expect(palette.dark).toBeGreaterThan(0.58);
|
||||
expect(palette.restrainedAccent).toBeGreaterThan(0.0005);
|
||||
|
||||
@@ -81,14 +81,17 @@ describe('raw prerendered document', () => {
|
||||
expect(readProductionInputsSnapshot(document)).toEqual(inputs);
|
||||
});
|
||||
|
||||
it('emits one canonical, one safe ProfilePage, and no fabricated preview image', async () => {
|
||||
const { html } = await renderRawArtifact();
|
||||
it('emits one canonical, one safe ProfilePage, and the approved preview image', async () => {
|
||||
const { html, inputs } = await renderRawArtifact();
|
||||
const document = new DOMParser().parseFromString(html, 'text/html');
|
||||
const jsonLdNodes = document.querySelectorAll('script[type="application/ld+json"]');
|
||||
const previewAsset = inputs.manifest.assets.find(({ id }) => id === inputs.content.metadata.socialPreviewAssetId)!;
|
||||
|
||||
expect(document.querySelectorAll('link[rel="canonical"]')).toHaveLength(1);
|
||||
expect(document.querySelector('link[rel="canonical"]')?.getAttribute('href')).toBe('https://portfolio.localhost/');
|
||||
expect(document.querySelectorAll('meta[property="og:image"]')).toHaveLength(0);
|
||||
expect(document.querySelectorAll('meta[property="og:image"]')).toHaveLength(1);
|
||||
expect(document.querySelector('meta[property="og:image"]')?.getAttribute('content'))
|
||||
.toBe(`https://portfolio.localhost/assets/${previewAsset.fileName}`);
|
||||
expect(jsonLdNodes).toHaveLength(1);
|
||||
const jsonLd = JSON.parse(jsonLdNodes[0]!.textContent ?? '');
|
||||
expect(jsonLd['@type']).toBe('ProfilePage');
|
||||
|
||||
@@ -1,12 +1,66 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { validateProductionInputs } from '@/content/schemas';
|
||||
import { parsePortfolioContent, validateProductionInputs } from '@/content/schemas';
|
||||
|
||||
const repositoryRoot = process.cwd();
|
||||
const readJson = async (relativePath: string): Promise<unknown> =>
|
||||
JSON.parse(await readFile(`${repositoryRoot}/${relativePath}`, 'utf8')) as unknown;
|
||||
|
||||
describe('approved public portfolio projection', () => {
|
||||
it('attaches only the approved project evidence in its reviewed presentation order', async () => {
|
||||
const content = parsePortfolioContent(await readJson('content/public/portfolio-content.json'));
|
||||
const medication = content.projects.selected[0]!;
|
||||
const footy = content.projects.selected[1]!;
|
||||
const opinion = content.projects.selected[2]!;
|
||||
const currency = content.projects.additional.find((project) => project.id === 'currency-converter')!;
|
||||
|
||||
expect(medication).toMatchObject({
|
||||
id: 'private-medication-tracker',
|
||||
mediaLayout: 'portrait-strip',
|
||||
assetIds: [
|
||||
'medication-mobile-logs',
|
||||
'medication-mobile-data-settings',
|
||||
'medication-mobile-notification-settings',
|
||||
],
|
||||
});
|
||||
expect(medication.media).toHaveLength(3);
|
||||
expect(medication.media?.every(({ alt }) => /fictional|demo/iu.test(alt))).toBe(true);
|
||||
expect(medication.url).toBeUndefined();
|
||||
|
||||
expect(footy.assetIds).toEqual([]);
|
||||
expect(footy.media).toBeUndefined();
|
||||
expect(footy.mediaLayout).toBeUndefined();
|
||||
|
||||
expect(opinion).toMatchObject({
|
||||
mediaLayout: 'chart-pair',
|
||||
assetIds: ['opinion-polarization-chart', 'opinion-final-distribution-chart'],
|
||||
});
|
||||
expect(opinion.media).toHaveLength(2);
|
||||
expect(opinion.media?.every(({ kind }) => kind === 'chart')).toBe(true);
|
||||
expect(opinion.media?.[0]).toMatchObject({
|
||||
kind: 'chart',
|
||||
assetId: 'opinion-polarization-chart',
|
||||
chart: {
|
||||
table: {
|
||||
columns: ['Step', 'Polarization index'],
|
||||
rows: [['0', '0.765'], ['14', '0.932']],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(opinion.media?.[1]).toMatchObject({
|
||||
kind: 'chart',
|
||||
assetId: 'opinion-final-distribution-chart',
|
||||
});
|
||||
if (opinion.media?.[1]?.kind === 'chart') expect(opinion.media[1].chart.table).toBeUndefined();
|
||||
|
||||
expect(currency).toMatchObject({
|
||||
mediaLayout: 'single',
|
||||
assetIds: ['currency-converter-interface'],
|
||||
media: [{ kind: 'image', assetId: 'currency-converter-interface' }],
|
||||
});
|
||||
expect(content.metadata.socialPreviewAssetId).toBe('social-preview-nightdrive');
|
||||
});
|
||||
|
||||
it('contains approved identity and balanced evidence without prohibited surfaces', async () => {
|
||||
const { content, manifest, site } = validateProductionInputs(
|
||||
await readJson('content/public/portfolio-content.json'),
|
||||
|
||||
@@ -8,6 +8,8 @@ import ProjectMediaGallery from '@/components/ProjectMediaGallery.vue';
|
||||
import { useLazyMedia } from '@/composables/useLazyMedia';
|
||||
import { parsePortfolioContent, parsePublicAssetManifest } from '@/content/schemas';
|
||||
import type { MediaPresentation, ProductionInputs, PublicAssetManifest } from '@/content/types';
|
||||
import approvedContentJson from '@/../content/public/portfolio-content.json';
|
||||
import approvedManifestJson from '@/../content/public/asset-manifest.json';
|
||||
|
||||
const sha = 'a'.repeat(64);
|
||||
const manifest: PublicAssetManifest = {
|
||||
@@ -226,6 +228,34 @@ describe('ProjectMedia', () => {
|
||||
});
|
||||
|
||||
describe('ProjectMediaGallery', () => {
|
||||
it('renders every approved project still with intrinsic, lazy, contextual semantics', () => {
|
||||
const content = parsePortfolioContent(structuredClone(approvedContentJson));
|
||||
const approvedManifest = parsePublicAssetManifest(structuredClone(approvedManifestJson));
|
||||
const approvedInputs = { manifest: approvedManifest, content, site: {} } as unknown as ProductionInputs;
|
||||
const projects = [...content.projects.selected, ...content.projects.additional];
|
||||
const mediaProjects = projects.filter((project) => project.media !== undefined);
|
||||
|
||||
expect(mediaProjects.map(({ id }) => id)).toEqual([
|
||||
'private-medication-tracker',
|
||||
'opinion-dynamics-simulation',
|
||||
'currency-converter',
|
||||
]);
|
||||
|
||||
const wrappers = mediaProjects.map((project) => mount(ProjectMediaGallery, {
|
||||
props: { media: project.media ?? [], layout: project.mediaLayout ?? 'single' },
|
||||
global: { provide: { [productionInputsKey as symbol]: approvedInputs } },
|
||||
}));
|
||||
const images = wrappers.flatMap((wrapper) => wrapper.findAll('img'));
|
||||
|
||||
expect(images).toHaveLength(6);
|
||||
expect(images.every((image) => image.attributes('loading') === 'lazy')).toBe(true);
|
||||
expect(images.every((image) => image.attributes('decoding') === 'async')).toBe(true);
|
||||
expect(images.every((image) => Number(image.attributes('width')) > 0 && Number(image.attributes('height')) > 0)).toBe(true);
|
||||
expect(wrappers[0]!.text()).toMatch(/fictional demo state|private self-hosted system/iu);
|
||||
expect(wrappers[1]!.findAll('[data-testid="chart-variables"]')).toHaveLength(2);
|
||||
expect(wrappers[1]!.findAll('table')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('keeps three portrait images lazy, contextual, and in their supplied order', () => {
|
||||
const wrapper = mountGallery([
|
||||
{ kind: 'image', assetId: 'project-image', alt: 'Synthetic profile screen.', caption: 'Fictional demo profile.' },
|
||||
|
||||
@@ -39,7 +39,10 @@ describe('client production-input snapshot', () => {
|
||||
|
||||
it('rejects a supplied asset reference absent from the supplied public manifest', () => {
|
||||
const invalid = validInputs();
|
||||
invalid.content.projects.selected[0]!.assetIds = ['unmanifested-snapshot-asset'];
|
||||
const medication = invalid.content.projects.selected[0]!;
|
||||
medication.assetIds = medication.assetIds.map((assetId) =>
|
||||
assetId === 'medication-mobile-logs' ? 'unmanifested-snapshot-asset' : assetId);
|
||||
medication.media![0]!.assetId = 'unmanifested-snapshot-asset';
|
||||
|
||||
expect(() => readProductionInputsSnapshot(snapshotDocument(invalid))).toThrow(/unmanifested asset reference/i);
|
||||
});
|
||||
|
||||
@@ -39,7 +39,8 @@ describe('safe metadata', () => {
|
||||
'Portfolio of Luca Pîslaru, a Bucharest-based System Administrator and Full-Stack Developer working across Linux infrastructure and end-to-end software delivery.',
|
||||
);
|
||||
expect(metadata.canonicalUrl).toBe('https://portfolio.example.test/');
|
||||
expect(metadata.openGraph.image).toBeUndefined();
|
||||
const previewAsset = manifest.assets.find(({ id }) => id === content.metadata.socialPreviewAssetId)!;
|
||||
expect(metadata.openGraph.image).toBe(`https://portfolio.example.test/assets/${previewAsset.fileName}`);
|
||||
expect(metadata.jsonLd).toEqual({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'ProfilePage',
|
||||
|
||||
Reference in New Issue
Block a user