feat: compose the portfolio journey layer
This commit is contained in:
@@ -29,22 +29,56 @@ describe('semantic portfolio document', () => {
|
||||
it('renders the complete landmark and heading reading structure on the server', async () => {
|
||||
const document = await renderedDocument();
|
||||
|
||||
expect(document.querySelectorAll('header')).toHaveLength(1);
|
||||
expect(document.querySelectorAll('.site-header')).toHaveLength(1);
|
||||
expect(document.querySelector('nav[aria-label="Portfolio sections"]')).not.toBeNull();
|
||||
expect(document.querySelectorAll('main')).toHaveLength(1);
|
||||
expect(document.querySelectorAll('footer')).toHaveLength(1);
|
||||
expect(document.querySelectorAll('h1')).toHaveLength(1);
|
||||
expect(document.querySelector('h1')?.textContent).toBe('Luca Pîslaru');
|
||||
expect(document.querySelector('#start h1')?.textContent).toBe('Luca Pîslaru');
|
||||
expect([...document.querySelectorAll('main > section')].map((section) => section.id)).toEqual([
|
||||
'start', 'profile', 'experience', 'infrastructure', 'selected-builds', 'additional-work', 'toolkit', 'contact',
|
||||
]);
|
||||
expect([...document.querySelectorAll('main > section')].every((section) => section.hasAttribute('aria-labelledby'))).toBe(true);
|
||||
expect([...document.querySelectorAll('main > section > h2')].map((heading) => heading.textContent)).toEqual([
|
||||
'START', 'PROFILE', 'EXPERIENCE', 'INFRASTRUCTURE', 'SELECTED BUILDS', 'ADDITIONAL WORK', 'TOOLKIT', 'CONTACT',
|
||||
'Profile', 'Experience', 'Infrastructure', 'Selected builds', 'Additional work', 'Toolkit', 'Contact',
|
||||
]);
|
||||
expect([...document.querySelectorAll('main > section')].map((section) => section.getAttribute('data-layout'))).toEqual([
|
||||
'hero', 'right-rail', 'left-timeline', 'full-diagram', 'project-stage', 'compact-rail', 'toolkit-field', 'destination',
|
||||
]);
|
||||
expect(document.querySelectorAll('.route-label')).toHaveLength(0);
|
||||
expect(document.querySelector('h2 + h4')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders the exact first-screen hierarchy and destination actions', async () => {
|
||||
const document = await renderedDocument();
|
||||
const hero = document.querySelector('.hero-journey__content')!;
|
||||
|
||||
expect([...hero.children].map((element) => element.textContent?.trim())).toEqual([
|
||||
'Luca Pîslaru',
|
||||
'System Administrator & Full-Stack Developer',
|
||||
'Building and operating reliable systems and software.',
|
||||
'Explore work',
|
||||
]);
|
||||
expect(hero.querySelector('a')?.getAttribute('href')).toBe('#selected-builds');
|
||||
expect(document.querySelector('a[href="mailto:hello@example.test"]')?.textContent).toBe('Start a conversation');
|
||||
expect(document.body.textContent).toContain("Thanks for making the trip. The road's open if you'd like to build something together.");
|
||||
expect(document.body.textContent).not.toMatch(/[–—]/u);
|
||||
});
|
||||
|
||||
it('assigns explicit project priority without promoting research exploration', async () => {
|
||||
const document = await renderedDocument();
|
||||
const priorities = [...document.querySelectorAll('#selected-builds article')].map((article) => [
|
||||
article.querySelector('h3')?.textContent,
|
||||
article.getAttribute('data-project-priority'),
|
||||
]);
|
||||
|
||||
expect(priorities).toEqual([
|
||||
['Private self-hosted medication tracker', 'primary'],
|
||||
['Footy Tracker', 'primary'],
|
||||
['Opinion Dynamics Simulation', 'supporting'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps native section links and visible contact available without JavaScript', async () => {
|
||||
const document = await renderedDocument();
|
||||
const navigation = document.querySelector('nav[aria-label="Portfolio sections"]')!;
|
||||
@@ -53,7 +87,7 @@ describe('semantic portfolio document', () => {
|
||||
'#start', '#profile', '#experience', '#infrastructure', '#selected-builds', '#additional-work', '#toolkit', '#contact',
|
||||
]);
|
||||
const email = document.querySelector('a[href="mailto:hello@example.test"]');
|
||||
expect(email?.textContent).toBe('hello@example.test');
|
||||
expect(email?.textContent).toBe('Start a conversation');
|
||||
expect(document.body.textContent).toContain('For a current CV or role-specific information, contact me directly.');
|
||||
});
|
||||
|
||||
@@ -89,7 +123,7 @@ describe('semantic portfolio document', () => {
|
||||
const document = await renderedDocumentFor(reorderedInputs);
|
||||
|
||||
expect([...document.querySelectorAll('main > section > h2')].map((heading) => heading.textContent)).toEqual([
|
||||
'START', 'PROFILE', 'EXPERIENCE', 'INFRASTRUCTURE', 'SELECTED BUILDS', 'ADDITIONAL WORK', 'TOOLKIT', 'CONTACT',
|
||||
'Profile', 'Experience', 'Infrastructure', 'Selected builds', 'Additional work', 'Toolkit', 'Contact',
|
||||
]);
|
||||
expect([...document.querySelectorAll('nav a')].map((link) => [link.getAttribute('href'), link.textContent])).toEqual([
|
||||
['#start', 'Start'], ['#profile', 'Profile'], ['#experience', 'Career'], ['#infrastructure', 'Infra'],
|
||||
|
||||
@@ -51,14 +51,15 @@ describe('semantic section components', () => {
|
||||
});
|
||||
|
||||
it('does not turn private project content into a link container', () => {
|
||||
const wrapper = mount(ProjectCaseStudy, { props: { project: privateProject } });
|
||||
const wrapper = mount(ProjectCaseStudy, { props: { project: privateProject, priority: 'primary' } });
|
||||
|
||||
expect(wrapper.get('article').findAll('a')).toHaveLength(0);
|
||||
expect(wrapper.get('h3').text()).toBe('Private self-hosted medication tracker');
|
||||
expect(wrapper.get('article').attributes('data-project-priority')).toBe('primary');
|
||||
});
|
||||
|
||||
it('uses a safe external link only for a public project with an approved URL', () => {
|
||||
const wrapper = mount(ProjectCaseStudy, { props: { project: publicProject } });
|
||||
const wrapper = mount(ProjectCaseStudy, { props: { project: publicProject, priority: 'supporting' } });
|
||||
const link = wrapper.get('article a');
|
||||
|
||||
expect(link.attributes('href')).toBe('https://github.com/example/footy-tracker');
|
||||
@@ -88,11 +89,11 @@ describe('semantic section components', () => {
|
||||
const wrapper = mount(ContactDestination, {
|
||||
props: {
|
||||
contact,
|
||||
identity: { name: 'Luca Pîslaru', role: 'System Administrator & Full-Stack Developer' },
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.get('a[href="mailto:hello@example.test"]').text()).toBe('hello@example.test');
|
||||
expect(wrapper.get('a[href="mailto:hello@example.test"]').text()).toBe('Start a conversation');
|
||||
expect(wrapper.text()).toContain("Thanks for making the trip. The road's open if you'd like to build something together.");
|
||||
const social = wrapper.get('a[href="https://github.com/example"]');
|
||||
expect(social.text()).toBe('GitHub');
|
||||
expect(social.attributes('rel')).toBe('noopener noreferrer');
|
||||
|
||||
Reference in New Issue
Block a user