feat: add evidence-led project media layouts
This commit is contained in:
@@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { productionInputsKey } from '@/app';
|
||||
import AccessibleVideo from '@/components/AccessibleVideo.vue';
|
||||
import ProjectMedia from '@/components/ProjectMedia.vue';
|
||||
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';
|
||||
@@ -21,6 +22,11 @@ const manifest: PublicAssetManifest = {
|
||||
width: 1000, height: 700, durationMs: null, rightsHolder: 'Luca Pîslaru',
|
||||
license: 'All rights reserved', attribution: 'Luca Pîslaru',
|
||||
},
|
||||
{
|
||||
id: 'project-portrait', fileName: 'project-portrait.webp', sha256: sha, mediaType: 'image/webp',
|
||||
width: 800, height: 1200, durationMs: null, rightsHolder: 'Luca Pîslaru',
|
||||
license: 'All rights reserved', attribution: 'Luca Pîslaru',
|
||||
},
|
||||
{
|
||||
id: 'project-loop', fileName: 'project-loop.webm', sha256: sha, mediaType: 'video/webm',
|
||||
width: 1280, height: 720, durationMs: 5_000, rightsHolder: 'Luca Pîslaru',
|
||||
@@ -83,6 +89,11 @@ const mountMedia = (presentation: MediaPresentation, allowAutomaticVideo = false
|
||||
global: { provide: { [productionInputsKey as symbol]: injectedInputs } },
|
||||
});
|
||||
|
||||
const mountGallery = (media: readonly MediaPresentation[], layout: 'single' | 'portrait-strip' | 'chart-pair') => mount(ProjectMediaGallery, {
|
||||
props: { media, layout },
|
||||
global: { provide: { [productionInputsKey as symbol]: injectedInputs } },
|
||||
});
|
||||
|
||||
describe('ProjectMedia', () => {
|
||||
it('resolves only manifest assets and keeps contextual wording in content', () => {
|
||||
const wrapper = mountMedia({
|
||||
@@ -214,6 +225,52 @@ describe('ProjectMedia', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProjectMediaGallery', () => {
|
||||
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.' },
|
||||
{ kind: 'image', assetId: 'project-chart', alt: 'Synthetic chart screen.', caption: 'Fictional demo chart.' },
|
||||
{ kind: 'image', assetId: 'project-portrait', alt: 'Synthetic portrait screen.', caption: 'Fictional demo profile settings.' },
|
||||
], 'portrait-strip');
|
||||
|
||||
expect(wrapper.attributes('data-media-layout')).toBe('portrait-strip');
|
||||
expect(wrapper.findAll('figure')).toHaveLength(3);
|
||||
expect(wrapper.findAll('img').map((image) => image.attributes('loading'))).toEqual(['lazy', 'lazy', 'lazy']);
|
||||
expect(wrapper.text()).toContain('Fictional demo profile');
|
||||
});
|
||||
|
||||
it('preserves image and chart media semantics across single and chart-pair layouts', () => {
|
||||
const single = mountGallery([
|
||||
{ kind: 'image', assetId: 'project-image', alt: 'Synthetic application screen.', caption: 'Fictional demo profile.' },
|
||||
], 'single');
|
||||
const chartPair = mountGallery([
|
||||
{
|
||||
kind: 'chart', assetId: 'project-chart', alt: 'Synthetic trend chart.', caption: 'Fictional demo chart.',
|
||||
chart: {
|
||||
variables: 'Horizontal: month. Vertical: completion rate.',
|
||||
trend: 'The modeled rate rises.',
|
||||
conclusion: 'The modeled run finishes above its starting point.',
|
||||
},
|
||||
},
|
||||
{ kind: 'image', assetId: 'project-image', alt: 'Synthetic supporting screen.', caption: 'Fictional supporting screen.' },
|
||||
], 'chart-pair');
|
||||
|
||||
expect(single.attributes('data-media-layout')).toBe('single');
|
||||
expect(single.get('img').attributes('loading')).toBe('lazy');
|
||||
expect(single.get('figcaption').text()).toBe('Fictional demo profile.');
|
||||
expect(chartPair.attributes('data-media-layout')).toBe('chart-pair');
|
||||
expect(chartPair.findAll('figure')).toHaveLength(2);
|
||||
expect(chartPair.get('[data-testid="chart-variables"]').text()).toContain('completion rate');
|
||||
expect(chartPair.get('img').attributes('loading')).toBe('lazy');
|
||||
});
|
||||
|
||||
it('renders no gallery container for an empty media list', () => {
|
||||
const wrapper = mountGallery([], 'single');
|
||||
|
||||
expect(wrapper.find('.project-media-gallery').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useLazyMedia', () => {
|
||||
it('fails closed without an observer and supports an explicit request', async () => {
|
||||
vi.stubGlobal('IntersectionObserver', undefined);
|
||||
|
||||
Reference in New Issue
Block a user