diff --git a/src/utils/__tests__/__snapshots__/svgRenderer.test.ts.snap b/src/utils/__tests__/__snapshots__/svgRenderer.test.ts.snap index a60528cdd..67735b4fb 100644 --- a/src/utils/__tests__/__snapshots__/svgRenderer.test.ts.snap +++ b/src/utils/__tests__/__snapshots__/svgRenderer.test.ts.snap @@ -1,7 +1,9 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`generateOptimizedSvg > should match the optimized contribution graph SVG structure snapshot 1`] = ` -" +" + GitHub Contribution Graph + A 3D isometric visualization of GitHub contribution activity. diff --git a/src/utils/svgRenderer.accessibility.test.ts b/src/utils/svgRenderer.accessibility.test.ts index 367d1d962..885145705 100644 --- a/src/utils/svgRenderer.accessibility.test.ts +++ b/src/utils/svgRenderer.accessibility.test.ts @@ -1,66 +1,30 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; - -describe('src/utils/svgRenderer — Accessibility Standards & Screen Reader ARIA Compliance (Variation 4)', () => { - let renderedSvgMarkup: string; - - beforeEach(() => { - vi.clearAllMocks(); - vi.spyOn(console, 'log').mockImplementation(() => {}); - - // Mimic the precise structural layout string provided by the svgRenderer utility engine - renderedSvgMarkup = ` - - Repository Contribution Distribution Matrix - A graphical grid representing the frequency of weekly commit intervals across team members. - - - Commit Pulse Render Engine Analytics - - - - - - - - - - - - - `; - }); - - it('inspects markup to verify correct implementation of accessible label references via aria-labelledby', () => { - expect(renderedSvgMarkup).toMatch(/role="img"/); - expect(renderedSvgMarkup).toMatch(/aria-labelledby="svg-label-title svg-label-desc"/); - expect(renderedSvgMarkup).toMatch(/Repository/); - expect(renderedSvgMarkup).toMatch(/<desc id="svg-label-desc">A graphical/); - }); - - it('asserts elements that accept keyboard focus have valid structural role attributes and clear focus triggers', () => { - expect(renderedSvgMarkup).toMatch(/id="focusable-data-point-0"/); - expect(renderedSvgMarkup).toMatch(/tabindex="0"/); - expect(renderedSvgMarkup).toMatch(/role="button"/); - expect(renderedSvgMarkup).toMatch(/focus:ring-2/); +import { describe, it, expect } from 'vitest'; +import { generateOptimizedSvg, ContributionNode } from './svgRenderer'; + +const mockData: ContributionNode[] = [ + { date: '2026-01-01', count: 0, x: 0, y: 0 }, + { date: '2026-01-02', count: 5, x: 0, y: 1 }, + { date: '2026-01-03', count: 12, x: 1, y: 1 }, +]; + +describe('generateOptimizedSvg — Accessibility', () => { + it('includes role="img" on the root svg element', () => { + const svg = generateOptimizedSvg(mockData); + expect(svg).toContain('role="img"'); }); - it('verifies that rendering canvas tooltips present themselves with specific screen-reader helper roles', () => { - expect(renderedSvgMarkup).toMatch(/role="tooltip"/); - expect(renderedSvgMarkup).toMatch(/aria-describedby="tooltip-content-details"/); + it('includes a <title> element for screen readers', () => { + const svg = generateOptimizedSvg(mockData); + expect(svg).toMatch(/<title id="svg-title">/); }); - it('ensures consecutive interactive focus items are configured cleanly to allow orderly keyboard tabbing paths', () => { - expect(renderedSvgMarkup).toMatch(/id="focusable-data-point-1"/); - const matches = renderedSvgMarkup.match(/tabindex="0"/g); - expect(matches).not.toBeNull(); - expect(matches?.length).toBeGreaterThanOrEqual(2); + it('includes a <desc> element describing the chart', () => { + const svg = generateOptimizedSvg(mockData); + expect(svg).toMatch(/<desc id="svg-desc">/); }); - it('confirms canvas text vectors expose standard heading structures in a logical hierarchical layout order', () => { - expect(renderedSvgMarkup).toMatch(/role="heading"/); - expect(renderedSvgMarkup).toMatch(/aria-level="1"/); - expect(renderedSvgMarkup).toMatch(/Commit Pulse Render Engine Analytics/); + it('includes aria-labelledby referencing title and desc', () => { + const svg = generateOptimizedSvg(mockData); + expect(svg).toContain('aria-labelledby="svg-title svg-desc"'); }); }); diff --git a/src/utils/svgRenderer.ts b/src/utils/svgRenderer.ts index 2e19d551f..fd818292b 100644 --- a/src/utils/svgRenderer.ts +++ b/src/utils/svgRenderer.ts @@ -92,7 +92,9 @@ export function generateOptimizedSvg(contributionData: ContributionNode[]): stri // STEP 6: OUTPUT FINISHED ROOT SVG STRING // ========================================== return ` -<svg xmlns="http://www.w3.org/2000/svg" viewBox="-200 -50 800 600" width="100%" height="100%"> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="-200 -50 800 600" width="100%" height="100%" role="img" aria-labelledby="svg-title svg-desc"> + <title id="svg-title">GitHub Contribution Graph + A 3D isometric visualization of GitHub contribution activity. ${svgDefs} ${svgElements}