Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/utils/__tests__/__snapshots__/svgRenderer.test.ts.snap
Original file line number Diff line number Diff line change
@@ -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`] = `
"<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</title>
<desc id="svg-desc">A 3D isometric visualization of GitHub contribution activity.</desc>

<defs>
<polygon id="iso-top" points="0,0 8,-4 16,0 8,4" />
Expand Down
80 changes: 22 additions & 58 deletions src/utils/svgRenderer.accessibility.test.ts
Original file line number Diff line number Diff line change
@@ -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 = `
<svg xmlns="http://www.w3.org/2000/svg" id="svg-render-root" role="img" aria-labelledby="svg-label-title svg-label-desc" width="600" height="300">
<title id="svg-label-title">Repository Contribution Distribution Matrix</title>
<desc id="svg-label-desc">A graphical grid representing the frequency of weekly commit intervals across team members.</desc>

<g id="header-container" role="group">
<text font-size="16" role="heading" aria-level="1" y="25" x="20">Commit Pulse Render Engine Analytics</text>
</g>

<g class="interactive-grid-nodes">
<rect id="focusable-data-point-0" tabindex="0" role="button" aria-label="Monday: 12 additions" width="40" height="150" class="focus:outline-none focus:ring-2" />
</g>

<g class="navigation-trail">
<circle id="focusable-data-point-1" tabindex="0" role="button" aria-label="Tuesday: 4 modifications" cx="100" cy="80" r="5" />
</g>

<div id="render-engine-tooltip" role="tooltip" aria-describedby="tooltip-content-details">
<span id="tooltip-content-details">Variance threshold status: fully compliant steady state operations.</span>
</div>
</svg>
`;
});

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(/<title id="svg-label-title">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"');
});
});
4 changes: 3 additions & 1 deletion src/utils/svgRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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</title>
<desc id="svg-desc">A 3D isometric visualization of GitHub contribution activity.</desc>
${svgDefs}
<g id="monolith-grid">
${svgElements}
Expand Down
Loading