Skip to content

Commit 942ee06

Browse files
committed
Add Code Block and Tabs, and Diagram component E2E test
1 parent 7997c18 commit 942ee06

7 files changed

Lines changed: 303 additions & 14 deletions

File tree

_TODO.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@
33

44
## E2E
55

6-
## Missing E2E Component Tests
7-
8-
- Code/CodeBlock
9-
- Code/CodeTabs
10-
- Diagram
11-
126
## Performance
137

148
Implement mitigations in test/e2e/specs/07-performance/PERFORMANCE.md

src/pages/testing/code.astro

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
import BaseLayout from '@layouts/BaseLayout.astro'
3+
import CodeBlockRegister from '@components/Code/CodeBlock/index.astro'
4+
import CodeTabsRegister from '@components/Code/CodeTabs/index.astro'
5+
6+
export const prerender = true
7+
8+
const pageTitle = 'Code Components Test Fixture'
9+
const fixtureDescription =
10+
'Dedicated route reserved for validating standalone code blocks and tabbed code examples.'
11+
const pagePath = '/testing/code'
12+
---
13+
14+
<BaseLayout pageTitle={pageTitle} path={pagePath} pageDescription={fixtureDescription} noindex>
15+
<CodeBlockRegister />
16+
<CodeTabsRegister />
17+
18+
<section class="mx-auto flex max-w-5xl flex-col gap-8 px-4 py-10">
19+
<header class="max-w-3xl text-center self-center space-y-4">
20+
<p class="text-xs uppercase tracking-widest text-content-active">Testing Fixture</p>
21+
<h1 class="text-3xl font-semibold leading-tight">{pageTitle}</h1>
22+
<p class="text-content-active">{fixtureDescription}</p>
23+
</header>
24+
25+
<article class="space-y-6 rounded-lg border border-trim bg-page-offset p-6 shadow-sm">
26+
<div class="space-y-2">
27+
<p class="text-xs uppercase tracking-wider text-content-active">CodeBlock</p>
28+
<h2 class="text-2xl font-semibold">Standalone block</h2>
29+
</div>
30+
31+
<code-block data-testid="code-block-fixture">
32+
<pre data-language="typescript"><code>const message = 'hello'</code></pre>
33+
</code-block>
34+
</article>
35+
36+
<article class="space-y-6 rounded-lg border border-trim bg-page-offset p-6 shadow-sm">
37+
<div class="space-y-2">
38+
<p class="text-xs uppercase tracking-wider text-content-active">CodeTabs</p>
39+
<h2 class="text-2xl font-semibold">Interactive tabs</h2>
40+
</div>
41+
42+
<code-tabs data-testid="code-tabs-multi-fixture">
43+
<pre data-code-tabs-tab="JavaScript" data-language="javascript"><code>console.log('A')</code></pre>
44+
<pre data-code-tabs-tab="TypeScript" data-language="typescript"><code>console.log('B')</code></pre>
45+
</code-tabs>
46+
</article>
47+
48+
<article class="space-y-6 rounded-lg border border-trim bg-page-offset p-6 shadow-sm">
49+
<div class="space-y-2">
50+
<p class="text-xs uppercase tracking-wider text-content-active">CodeTabs</p>
51+
<h2 class="text-2xl font-semibold">Single tab label</h2>
52+
</div>
53+
54+
<code-tabs data-testid="code-tabs-single-fixture">
55+
<pre data-language="typescript"><code>const x: number = 1</code></pre>
56+
</code-tabs>
57+
</article>
58+
59+
<article class="space-y-6 rounded-lg border border-trim bg-page-offset p-6 shadow-sm">
60+
<div class="space-y-2">
61+
<p class="text-xs uppercase tracking-wider text-content-active">CodeTabs</p>
62+
<h2 class="text-2xl font-semibold">Excluded language</h2>
63+
</div>
64+
65+
<code-tabs data-testid="code-tabs-excluded-fixture">
66+
<pre data-language="text"><code>plain text</code></pre>
67+
</code-tabs>
68+
</article>
69+
</section>
70+
</BaseLayout>

src/pages/testing/diagram.astro

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
import BaseLayout from '@layouts/BaseLayout.astro'
3+
import DiagramComponent from '@components/Diagram/index.astro'
4+
import backstageBackground from '@assets/images/backstage-background.jpg'
5+
6+
export const prerender = true
7+
8+
const pageTitle = 'Diagram Component Test Fixture'
9+
const fixtureDescription =
10+
'Dedicated route reserved for validating diagram rendering, long-description disclosure, and image magnification.'
11+
const pagePath = '/testing/diagram'
12+
---
13+
14+
<BaseLayout pageTitle={pageTitle} path={pagePath} pageDescription={fixtureDescription} noindex>
15+
<section class="mx-auto flex max-w-4xl flex-col items-center gap-8 px-4 py-10">
16+
<header class="max-w-3xl text-center space-y-4">
17+
<p class="text-xs uppercase tracking-widest text-content-active">Testing Fixture</p>
18+
<h1 class="text-3xl font-semibold leading-tight">{pageTitle}</h1>
19+
<p class="text-content-active">{fixtureDescription}</p>
20+
</header>
21+
22+
<article class="w-full max-w-3xl space-y-6 rounded-lg border border-trim bg-page-offset p-6 shadow-sm">
23+
<div class="space-y-2">
24+
<p class="text-xs uppercase tracking-wider text-content-active">Fixture Diagram</p>
25+
<h2 class="text-2xl font-semibold">Service dependency flow</h2>
26+
<p class="text-content-active">
27+
Stable diagram metadata for validating the figure caption, disclosure summary, and enlarged preview.
28+
</p>
29+
</div>
30+
31+
<DiagramComponent
32+
src={backstageBackground}
33+
magnifyButtonPosition="top-left"
34+
size="xl"
35+
title="Service dependency flow for incident response escalation"
36+
description="A service map showing requests entering the public edge, moving through an API layer, then branching into application services, background workers, and supporting data stores used during incident response."
37+
>
38+
<p data-diagram-fixture-note class="text-sm text-content-active">
39+
Supporting note rendered through the Diagram default slot.
40+
</p>
41+
</DiagramComponent>
42+
</article>
43+
</section>
44+
</BaseLayout>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Code component tests
3+
* Covers the dedicated testing fixture for standalone code blocks and tabbed code examples.
4+
*/
5+
6+
import { BasePage, expect, test } from '@test/e2e/helpers'
7+
8+
const fixturePath = '/testing/code'
9+
const codeBlockSelector = '[data-testid="code-block-fixture"]'
10+
const multiTabsSelector = '[data-testid="code-tabs-multi-fixture"]'
11+
const singleTabsSelector = '[data-testid="code-tabs-single-fixture"]'
12+
const excludedTabsSelector = '[data-testid="code-tabs-excluded-fixture"]'
13+
14+
const loadCodeFixture = async (playwrightPage: Parameters<typeof BasePage.init>[0]) => {
15+
const page = await BasePage.init(playwrightPage)
16+
await page.goto(fixturePath)
17+
await expect(page.locator(codeBlockSelector)).toBeVisible()
18+
return page
19+
}
20+
21+
test.describe('Code Components', () => {
22+
test('@ready enhances a standalone code-block without changing its content', async ({ page: playwrightPage }) => {
23+
const page = await loadCodeFixture(playwrightPage)
24+
25+
await expect(playwrightPage.getByRole('heading', { name: 'Code Components Test Fixture' })).toBeVisible()
26+
await expect(page.locator(codeBlockSelector)).toHaveAttribute('data-enhanced', 'true')
27+
await expect(page.locator(`${codeBlockSelector} pre`)).toHaveAttribute('data-language', 'typescript')
28+
await expect(page.locator(`${codeBlockSelector} code`)).toHaveText("const message = 'hello'")
29+
})
30+
31+
test('@ready switches between tabbed code blocks in the multi-tab fixture', async ({ page: playwrightPage }) => {
32+
const page = await loadCodeFixture(playwrightPage)
33+
const multiTabs = page.locator(multiTabsSelector)
34+
const javascriptButton = multiTabs.getByRole('button', { name: 'JavaScript' })
35+
const typeScriptButton = multiTabs.getByRole('button', { name: 'TypeScript' })
36+
const javascriptPre = multiTabs.locator('pre').nth(0)
37+
const typeScriptPre = multiTabs.locator('pre').nth(1)
38+
39+
await expect(javascriptButton).toBeVisible()
40+
await expect(typeScriptButton).toBeVisible()
41+
await expect(javascriptPre).toBeVisible()
42+
await expect(typeScriptPre).toHaveClass(/hidden/)
43+
44+
await typeScriptButton.click()
45+
46+
await expect(javascriptPre).toHaveClass(/hidden/)
47+
await expect(typeScriptPre).toBeVisible()
48+
await expect(typeScriptButton).toHaveClass(/text-content/)
49+
await expect(javascriptButton).toHaveClass(/text-content-offset/)
50+
})
51+
52+
test('@ready renders the single-tab and excluded-language fallbacks correctly', async ({ page: playwrightPage }) => {
53+
const page = await loadCodeFixture(playwrightPage)
54+
const singleTabs = page.locator(singleTabsSelector)
55+
const excludedTabs = page.locator(excludedTabsSelector)
56+
57+
await expect(singleTabs.locator('button[data-code-tabs-button]')).toHaveCount(0)
58+
await expect(singleTabs.getByText('TypeScript')).toBeVisible()
59+
await expect(singleTabs.getByRole('button', { name: 'Copy "TypeScript"', exact: true })).toBeAttached()
60+
await expect(singleTabs.getByRole('button', { name: 'Copy', exact: true })).toBeVisible()
61+
62+
await expect(excludedTabs.locator('button[data-code-tabs-button]')).toHaveCount(0)
63+
await expect(excludedTabs.getByText('plain text')).toBeVisible()
64+
await expect(excludedTabs.getByRole('button', { name: 'Copy', exact: true })).toBeVisible()
65+
})
66+
67+
test('@ready copies the active code and single-tab label using the clipboard integration', async ({
68+
page: playwrightPage,
69+
}) => {
70+
const page = await loadCodeFixture(playwrightPage)
71+
72+
await playwrightPage.evaluate(() => {
73+
const windowWithClipboardState = window as Window & { __copiedValues?: string[] }
74+
windowWithClipboardState.__copiedValues = []
75+
76+
Object.defineProperty(navigator, 'clipboard', {
77+
configurable: true,
78+
value: {
79+
writeText: async (text: string) => {
80+
windowWithClipboardState.__copiedValues?.push(text)
81+
},
82+
},
83+
})
84+
})
85+
86+
const multiTabs = page.locator(multiTabsSelector)
87+
await multiTabs.getByRole('button', { name: 'TypeScript' }).click()
88+
await multiTabs.getByRole('button', { name: 'Copy' }).click()
89+
90+
const singleTabs = page.locator(singleTabsSelector)
91+
await singleTabs.getByRole('button', { name: 'Copy "TypeScript"', exact: true }).click()
92+
93+
await expect.poll(async () => {
94+
return await playwrightPage.evaluate(() => {
95+
const windowWithClipboardState = window as Window & { __copiedValues?: string[] }
96+
return windowWithClipboardState.__copiedValues ?? []
97+
})
98+
}).toEqual(["console.log('B')", 'TypeScript'])
99+
})
100+
})
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Diagram component tests
3+
* Covers the dedicated testing fixture for the Diagram caption and magnified image experience.
4+
*/
5+
6+
import { BasePage, expect, test } from '@test/e2e/helpers'
7+
8+
const fixturePath = '/testing/diagram'
9+
const figureSelector = 'figure'
10+
const detailsSelector = `${figureSelector} details`
11+
const summarySelector = `${detailsSelector} summary`
12+
const descriptionSelector = `${detailsSelector} p`
13+
const slotNoteSelector = '[data-diagram-fixture-note]'
14+
const contentImageSelector = '[data-testid="content-image"]'
15+
const imageTriggerSelector = `${contentImageSelector} [data-image-open]`
16+
const imageDialogSelector = '[data-image-dialog]'
17+
const modalImageSelector = '[data-image-modal-asset]'
18+
19+
const loadDiagramFixture = async (playwrightPage: Parameters<typeof BasePage.init>[0]) => {
20+
const page = await BasePage.init(playwrightPage)
21+
await page.goto(fixturePath, { skipCookieDismiss: true })
22+
await expect(page.locator(figureSelector)).toBeVisible()
23+
return page
24+
}
25+
26+
test.describe('Diagram Component', () => {
27+
test('@ready renders the fixture diagram with caption, image preview, and slot content', async ({
28+
page: playwrightPage,
29+
}) => {
30+
const page = await loadDiagramFixture(playwrightPage)
31+
32+
await expect(playwrightPage.getByRole('heading', { name: 'Diagram Component Test Fixture' })).toBeVisible()
33+
await expect(page.locator(contentImageSelector)).toBeVisible()
34+
await expect(page.locator(`${figureSelector} img`)).toHaveAttribute(
35+
'alt',
36+
'Service dependency flow for incident response escalation'
37+
)
38+
await expect(page.locator(imageTriggerSelector)).toHaveAttribute('data-image-open-position', 'top-left')
39+
await expect(page.locator(slotNoteSelector)).toHaveText(
40+
'Supporting note rendered through the Diagram default slot.'
41+
)
42+
await expect(page.locator(summarySelector)).toContainText(
43+
'Service dependency flow for incident response escalation'
44+
)
45+
})
46+
47+
test('@ready expands and collapses the long description disclosure', async ({ page: playwrightPage }) => {
48+
const page = await loadDiagramFixture(playwrightPage)
49+
50+
await expect(page.locator(detailsSelector)).not.toHaveAttribute('open', '')
51+
await expect(page.locator(descriptionSelector)).not.toBeVisible()
52+
53+
await page.locator(summarySelector).click()
54+
55+
await expect(page.locator(detailsSelector)).toHaveAttribute('open', '')
56+
await expect(page.locator(descriptionSelector)).toBeVisible()
57+
await expect(page.locator(descriptionSelector)).toHaveText(
58+
'A service map showing requests entering the public edge, moving through an API layer, then branching into application services, background workers, and supporting data stores used during incident response.'
59+
)
60+
61+
await page.locator(summarySelector).click()
62+
await expect(page.locator(detailsSelector)).not.toHaveAttribute('open', '')
63+
})
64+
65+
test('@ready opens the expanded image dialog and closes it with Escape', async ({ page: playwrightPage }) => {
66+
const page = await loadDiagramFixture(playwrightPage)
67+
68+
await page.locator(imageTriggerSelector).click()
69+
70+
await expect(page.locator(imageDialogSelector)).toBeVisible()
71+
await expect(playwrightPage.getByRole('dialog', {
72+
name: 'Expanded view for Service dependency flow for incident response escalation',
73+
})).toBeVisible()
74+
await expect(page.locator(modalImageSelector)).toHaveAttribute(
75+
'alt',
76+
'Service dependency flow for incident response escalation'
77+
)
78+
79+
await playwrightPage.keyboard.press('Escape')
80+
await expect(page.locator(imageDialogSelector)).not.toBeVisible()
81+
})
82+
})

test/e2e/specs/12-performance/PERFORMANCE.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,10 @@ Latest run (Dec 2, 2025):
5959

6060
## Total Blocking Time (TBT) regressions (Chromium desktop + mobile)
6161

62-
63-
6462
Hero GSAP initialization plus consent logging (`initConsentSideEffects` immediately fetches `/_actions/gdpr.consentCreate`) execute in the same navigation task, so the long-task observer inside `measureTBT` still sees 200–400 ms of blocking work even after we wait for `networkidle`.
6563

6664
**Mitigations**
65+
6766
- Lazily hydrate carousels/testimonials (intersection observer + dynamic `import('embla-carousel')`) and skip Autoplay during CI/perf runs so fewer long tasks fall inside the measurement window.
6867
- Only create animation controllers once an animation actually needs lifecycle hooks, and wrap registration in `requestIdleCallback` so it lands outside the navigation task.
6968
- Guard consent logging and hero animation hydration with `window.isPlaywrightControlled` so Playwright perf tests can bypass those fetches and GSAP timelines when only static markup is required.

test/e2e/specs/12-performance/core-web-vitals.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ test.describe('Core Web Vitals', () => {
1414
await coreWebVitalsPage.goto('/')
1515
})
1616

17-
test.skip('@blocked Largest Contentful Paint under 2.5s', async () => {
17+
test('@ready Largest Contentful Paint under 2.5s', async () => {
1818
await coreWebVitalsPage.expectLCPUnder(2500)
1919
})
2020

21-
test.skip('@ready First Input Delay simulation', async () => {
21+
test('@ready First Input Delay simulation', async () => {
2222
await coreWebVitalsPage.expectFIDUnder(100)
2323
})
2424

25-
test.skip('@ready Cumulative Layout Shift under 0.1', async () => {
25+
test('@ready Cumulative Layout Shift under 0.1', async () => {
2626
// Wait for page to settle
2727
await coreWebVitalsPage.waitForPageComplete()
2828
await coreWebVitalsPage.expectCLSUnder(0.1)
@@ -32,11 +32,11 @@ test.describe('Core Web Vitals', () => {
3232
await coreWebVitalsPage.expectTTIUnder(3800)
3333
})
3434

35-
test.skip('@blocked First Contentful Paint under 1.8s', async () => {
35+
test('@ready First Contentful Paint under 1.8s', async () => {
3636
await coreWebVitalsPage.expectFCPUnder(1800)
3737
})
3838

39-
test.skip('@blocked Total Blocking Time under 200ms', async () => {
39+
test('@ready Total Blocking Time under 200ms', async () => {
4040
// Wait for page to fully load
4141
// NOTE: Avoid strict 'networkidle' gating on WebKit/mobile-safari (can hang on long-lived requests).
4242
await coreWebVitalsPage.waitForNetworkIdleBestEffort()
@@ -47,7 +47,7 @@ test.describe('Core Web Vitals', () => {
4747
await coreWebVitalsPage.expectSpeedIndexUnder(3400)
4848
})
4949

50-
test.skip('@blocked page load time under 3s', async () => {
50+
test('@ready page load time under 3s', async () => {
5151
// Create new page for fresh measurement
5252
const startTime = Date.now()
5353
await coreWebVitalsPage.goto('/')

0 commit comments

Comments
 (0)