feat: refine portfolio content, contact, and supporting media

This commit is contained in:
2026-09-05 20:08:57 +03:00
parent 15bcdf7cec
commit 103427f95b
53 changed files with 824 additions and 204 deletions

View File

@@ -160,6 +160,10 @@ describe('ProjectMedia', () => {
const video = wrapper.get('video');
expect(video.attributes('preload')).toBe('none');
expect(video.attributes()).toHaveProperty('loop');
expect(video.attributes()).toHaveProperty('muted');
expect(video.attributes()).not.toHaveProperty('autoplay');
expect(wrapper.get('.video-fallback').attributes('href')).toBe('/assets/project-loop.mp4');
expect(video.attributes()).not.toHaveProperty('controls');
expect(video.findAll('source')).toHaveLength(0);
expect(wrapper.get('button').text()).toBe('Play video');
@@ -175,15 +179,16 @@ describe('ProjectMedia', () => {
'/assets/project-loop.mp4',
]);
expect(HTMLMediaElement.prototype.play).toHaveBeenCalledOnce();
expect(HTMLMediaElement.prototype.load).toHaveBeenCalledOnce();
});
it('attaches inside 300 CSS pixels when automatic media is allowed and detaches offscreen', async () => {
it('attaches in the viewport when automatic media is allowed and detaches offscreen', async () => {
const wrapper = mountMedia({
kind: 'video', assetId: 'project-loop', posterAssetId: 'project-loop-poster',
alt: 'Gameplay excerpt.', caption: 'Gameplay.', longDescription: 'A gameplay loop.',
}, true);
const observer = IntersectionObserverFake.instances[0]!;
expect(observer.options?.rootMargin).toBe('300px 0px 300px 0px');
expect(observer.options?.rootMargin).toBe('0px');
observer.emit(true);
await nextTick();
@@ -238,6 +243,8 @@ describe('ProjectMediaGallery', () => {
expect(mediaProjects.map(({ id }) => id)).toEqual([
'private-medication-tracker',
'opinion-dynamics-simulation',
'private-game-prototype',
'private-healthcare-platform',
'currency-converter',
]);
@@ -247,7 +254,7 @@ describe('ProjectMediaGallery', () => {
}));
const images = wrappers.flatMap((wrapper) => wrapper.findAll('img'));
expect(images).toHaveLength(6);
expect(images).toHaveLength(7);
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);
@@ -345,7 +352,7 @@ describe('media content schema', () => {
additional: [{ id: 'extra', title: 'Extra', visibility: 'public', maturity: 'learning', summary: 'S.', problem: 'P.', contribution: 'C.', technicalDecisions: ['D.'], outcome: 'O.', assetIds: [], media: [] }],
},
toolkit: [{ title: 'Tools', technologies: [{ name: 'Linux' }] }],
contact: { introduction: 'Open to roles.', email: 'hello@example.test', social: [], cvMessage: 'Contact directly.' },
contact: { introduction: 'Open to roles.', email: { localPart: 'hello', domain: 'example.test' }, social: [], cvMessage: 'Contact directly.' },
metadata: { title: 'Title.', description: 'Description.' },
sceneDescription: 'Scene.',
}));