Skip to content
This repository is currently being migrated. It's locked while the migration is in progress.

Commit 1f0d2cd

Browse files
humancompanion-usdsclaudejeana-adhocCopilot
authored
Add accessibility defect metrics to dashboard (#5679)
* Add accessibility defect metrics to dashboard (#4405) Add a new Accessibility section to the metrics dashboard that tracks a11y issues by defect severity level (0-3) over time as a multi-series line chart. Includes data collection in collect-issue-metrics.js, D3 visualization with quarter boundary markers, hover highlighting, interactive legend, and a table view with Jekyll-rendered data. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Resolve Copilot PR review comments on a11y defect metrics - Compute 5-year chart cutoff dynamically from latest data period - Fix y-axis rendering when all values are zero - Remove unused periodIndex variable - Add missing IDs to imposter data-freshness elements - Update test assertions to match current dashboard structure (9 sections) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Pad a11y monthly data with zero-count months and add keyboard accessibility to chart - Pad processA11yDefectMonthlyData to emit every month between min/max, ensuring table and chart show the same continuous timeline - Add focus/blur handlers to chart data points and legend items so tooltips and series highlighting work for keyboard and touch users - Add ARIA roles and labels to interactive chart elements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update src/_about/metrics/index.html Co-authored-by: Jeana Clark <117098918+jeana-adhoc@users.noreply.github.com> * Update src/_about/metrics/index.html Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix va-table stacked mode at mobile widths by toggling sortable dynamically va-table disables stacked (responsive) mode when sortable is set. Remove sortable from static HTML and add JS that toggles it based on viewport width using the same 29.99em breakpoint va-table uses for stacked CSS. Tables now stack on mobile and gain sort headers on wider screens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix chart a11y issues and regenerate padded metrics data - Add zero-width guard for chart rendering in hidden tab panels - Remove excessive tab stops from chart data points (200+ circles) - Change legend items from role="button" to role="img" with descriptive labels - Regenerate issue-metrics.json with fully padded monthly data (no gaps) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove redundant client-side month padding from a11y chart The collection script already pads monthly data to continuous periods, so the client-side padding loop was duplicating that work. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Make h1 count assertion resilient to layout changes Use toBeGreaterThanOrEqual(1) instead of toBe(2) so the test does not break if the Jekyll layout adds or removes headings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Jeana Clark <117098918+jeana-adhoc@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent d825b31 commit 1f0d2cd

9 files changed

Lines changed: 1800 additions & 314 deletions

File tree

__tests__/metrics-accessibility.test.js

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,19 @@ describe('Metrics Dashboard Accessibility Tests - Real Implementation', () => {
6262

6363
test('Dashboard contains all required va-table components', () => {
6464
const vaTables = document.querySelectorAll('va-table');
65-
expect(vaTables.length).toBe(4);
66-
65+
expect(vaTables.length).toBe(9);
66+
6767
// Check each table has proper accessibility attributes
6868
const expectedTableIds = [
6969
'quarterly-va-table',
7070
'components-va-table',
71+
'imposter-quarterly-va-table',
72+
'imposter-component-va-table',
73+
'imposter-instances-component-va-table',
74+
'imposter-instances-app-va-table',
7175
'velocity-va-table',
72-
'experimental-va-table'
76+
'experimental-va-table',
77+
'a11y-defect-va-table'
7378
];
7479

7580
expectedTableIds.forEach(tableId => {
@@ -78,20 +83,20 @@ describe('Metrics Dashboard Accessibility Tests - Real Implementation', () => {
7883
expect(table.tagName.toLowerCase()).toBe('va-table');
7984
expect(table).toHaveAttribute('table-title');
8085
expect(table).toHaveAttribute('stacked', 'true');
81-
expect(table).toHaveAttribute('sortable', 'true');
86+
// sortable is added dynamically via JS based on viewport width
8287
expect(table).toHaveAttribute('table-type', 'borderless');
8388
});
8489
});
8590

8691
test('All chart containers have proper accessibility attributes', () => {
8792
const chartContainers = document.querySelectorAll('.chart-container');
88-
expect(chartContainers.length).toBe(4);
89-
93+
expect(chartContainers.length).toBe(9);
94+
9095
chartContainers.forEach(container => {
9196
expect(container).toHaveAttribute('role', 'img');
9297
expect(container).toHaveAttribute('aria-label');
9398
expect(container).toHaveAttribute('tabindex', '0');
94-
99+
95100
const ariaLabel = container.getAttribute('aria-label');
96101
expect(ariaLabel.length).toBeGreaterThan(20);
97102
expect(ariaLabel.toLowerCase()).toContain('chart');
@@ -112,29 +117,26 @@ describe('Metrics Dashboard Accessibility Tests - Real Implementation', () => {
112117

113118
test('Metric cards have proper accessibility relationships', () => {
114119
const metricCards = document.querySelectorAll('.metric-card');
115-
expect(metricCards.length).toBe(6);
120+
expect(metricCards.length).toBe(8);
116121

117122
metricCards.forEach(card => {
118-
const heading = card.querySelector('h3[id]');
123+
const heading = card.querySelector('h3[id], h4[id]');
119124
expect(heading).toBeTruthy();
120-
125+
121126
const value = card.querySelector('.metric-value[aria-labelledby]');
122127
expect(value).toBeTruthy();
123-
128+
124129
const labelId = value.getAttribute('aria-labelledby');
125130
expect(labelId).toBe(heading.id);
126-
127-
const trend = card.querySelector('.metric-trend[aria-label]');
128-
expect(trend).toBeTruthy();
129131
});
130132
});
131133
});
132134

133135
describe('🔄 Tab Navigation and Structure', () => {
134136
test('All va-tabs have proper structure and attributes', () => {
135137
const vaTabs = document.querySelectorAll('va-tabs');
136-
expect(vaTabs.length).toBe(4);
137-
138+
expect(vaTabs.length).toBe(9);
139+
138140
vaTabs.forEach(tabGroup => {
139141
const tabItems = tabGroup.querySelectorAll('va-tab-item');
140142
const tabPanels = tabGroup.querySelectorAll('va-tab-panel');
@@ -159,7 +161,7 @@ describe('Metrics Dashboard Accessibility Tests - Real Implementation', () => {
159161
test('Tab switching maintains accessibility context', () => {
160162
// Test that tabs can be focused and have proper relationships
161163
const tabItems = document.querySelectorAll('va-tab-item');
162-
expect(tabItems.length).toBe(8); // 4 tab groups × 2 tabs each
164+
expect(tabItems.length).toBe(18); // 9 tab groups × 2 tabs each
163165

164166
tabItems.forEach(tabItem => {
165167
const targetId = tabItem.getAttribute('target-id');
@@ -247,7 +249,7 @@ describe('Metrics Dashboard Accessibility Tests - Real Implementation', () => {
247249
test('Tab order follows logical visual flow', () => {
248250
// Test that tab order follows graph-then-table pattern for each section
249251
const vaTabsContainers = Array.from(document.querySelectorAll('va-tabs'));
250-
expect(vaTabsContainers.length).toBe(4); // Should have 4 tab containers
252+
expect(vaTabsContainers.length).toBe(9); // Should have 9 tab containers
251253

252254
// Check each va-tabs container individually
253255
vaTabsContainers.forEach((tabContainer, index) => {
@@ -281,8 +283,8 @@ describe('Metrics Dashboard Accessibility Tests - Real Implementation', () => {
281283
const chartTabPanels = document.querySelectorAll('va-tab-panel[panel-id*="graph"]');
282284
const tableTabPanels = document.querySelectorAll('va-tab-panel[panel-id*="table"]');
283285

284-
expect(chartTabPanels.length).toBe(4);
285-
expect(tableTabPanels.length).toBe(4);
286+
expect(chartTabPanels.length).toBe(9);
287+
expect(tableTabPanels.length).toBe(9);
286288

287289
// Each chart panel should have a focusable chart
288290
chartTabPanels.forEach(panel => {
@@ -320,7 +322,7 @@ describe('Metrics Dashboard Accessibility Tests - Real Implementation', () => {
320322
test('Responsive design maintains accessibility', () => {
321323
// Test that key accessibility attributes persist regardless of viewport
322324
const vaTables = document.querySelectorAll('va-table[stacked="true"]');
323-
expect(vaTables.length).toBe(4);
325+
expect(vaTables.length).toBe(9);
324326

325327
// All tables should be configured for mobile responsiveness
326328
vaTables.forEach(table => {
@@ -367,9 +369,9 @@ describe('Metrics Dashboard Accessibility Tests - Real Implementation', () => {
367369
const chartContainers = document.querySelectorAll('.chart-container[role="img"]');
368370
const tabPanels = document.querySelectorAll('va-tab-panel[role="tabpanel"]');
369371

370-
expect(vaTables.length).toBe(4);
371-
expect(chartContainers.length).toBe(4);
372-
expect(tabPanels.length).toBe(8);
372+
expect(vaTables.length).toBe(9);
373+
expect(chartContainers.length).toBe(9);
374+
expect(tabPanels.length).toBe(18);
373375

374376
// All should have their accessibility attributes
375377
vaTables.forEach(table => {

__tests__/metrics-browser-compatibility.test.js

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('Metrics Dashboard Browser Compatibility Tests - Real Implementation',
4949

5050
expect(metricsDiv).toBeTruthy();
5151
expect(allSections.length).toBeGreaterThan(0);
52-
expect(vaTabs.length).toBe(4);
52+
expect(vaTabs.length).toBe(9);
5353
});
5454

5555
test('Should support modern CSS selectors across browsers', () => {
@@ -72,9 +72,9 @@ describe('Metrics Dashboard Browser Compatibility Tests - Real Implementation',
7272
const vaTables = document.querySelectorAll('va-table');
7373
const tabItems = document.querySelectorAll('va-tab-item');
7474

75-
expect(charts.length).toBe(4);
76-
expect(vaTables.length).toBe(4);
77-
expect(tabItems.length).toBe(8);
75+
expect(charts.length).toBe(9);
76+
expect(vaTables.length).toBe(9);
77+
expect(tabItems.length).toBe(18);
7878

7979
// Test getAttribute with real elements
8080
charts.forEach(chart => {
@@ -226,8 +226,8 @@ describe('Metrics Dashboard Browser Compatibility Tests - Real Implementation',
226226
test('Should provide proper ARIA roles for screen readers', () => {
227227
// Test ARIA roles that are critical for screen readers using real elements
228228
const expectedRoles = [
229-
{ selector: '[role="img"]', role: 'img', expectedCount: 4 },
230-
{ selector: '[role="tabpanel"]', role: 'tabpanel', expectedCount: 8 }
229+
{ selector: '[role="img"]', role: 'img', expectedCount: 9 },
230+
{ selector: '[role="tabpanel"]', role: 'tabpanel', expectedCount: 18 }
231231
// Note: main role is now provided by Jekyll layout, not the metrics content itself
232232
];
233233

@@ -268,13 +268,13 @@ describe('Metrics Dashboard Browser Compatibility Tests - Real Implementation',
268268

269269
test('Should provide va-table with proper accessibility attributes', () => {
270270
const vaTables = document.querySelectorAll('va-table');
271-
expect(vaTables.length).toBe(4);
272-
271+
expect(vaTables.length).toBe(9);
272+
273273
vaTables.forEach(table => {
274274
// va-table should have accessibility attributes from real implementation
275275
expect(table).toHaveAttribute('table-title');
276276
expect(table).toHaveAttribute('stacked', 'true');
277-
expect(table).toHaveAttribute('sortable', 'true');
277+
// sortable is added dynamically via JS based on viewport width
278278
expect(table).toHaveAttribute('table-type', 'borderless');
279279
});
280280
});
@@ -285,7 +285,7 @@ describe('Metrics Dashboard Browser Compatibility Tests - Real Implementation',
285285
const vaTabItems = document.querySelectorAll('va-tab-item');
286286

287287
expect(explicitlyFocusable.length).toBeGreaterThan(0);
288-
expect(vaTabItems.length).toBe(8);
288+
expect(vaTabItems.length).toBe(18);
289289

290290
explicitlyFocusable.forEach(element => {
291291
// Element should be in tab order
@@ -308,10 +308,10 @@ describe('Metrics Dashboard Browser Compatibility Tests - Real Implementation',
308308
const vaTabPanels = document.querySelectorAll('va-tab-panel');
309309
const vaTables = document.querySelectorAll('va-table');
310310

311-
expect(vaTabs.length).toBe(4);
312-
expect(vaTabItems.length).toBe(8); // 4 tabs × 2 items each
313-
expect(vaTabPanels.length).toBe(8); // 4 tabs × 2 panels each
314-
expect(vaTables.length).toBe(4);
311+
expect(vaTabs.length).toBe(9);
312+
expect(vaTabItems.length).toBe(18); // 9 tabs × 2 items each
313+
expect(vaTabPanels.length).toBe(18); // 9 tabs × 2 panels each
314+
expect(vaTables.length).toBe(9);
315315

316316
// Test custom attributes specific to VA components
317317
vaTabItems.forEach(item => {
@@ -327,7 +327,7 @@ describe('Metrics Dashboard Browser Compatibility Tests - Real Implementation',
327327
vaTables.forEach(table => {
328328
expect(table.getAttribute('table-title')).toBeTruthy();
329329
expect(table.getAttribute('stacked')).toBe('true');
330-
expect(table.getAttribute('sortable')).toBe('true');
330+
// sortable is added dynamically via JS based on viewport width
331331
});
332332
});
333333

@@ -457,8 +457,8 @@ describe('Metrics Dashboard Browser Compatibility Tests - Real Implementation',
457457
test('Should provide accessible fallbacks for complex interactions', () => {
458458
// Ensure that both charts and tables are available as alternatives
459459
const tabGroups = document.querySelectorAll('va-tabs');
460-
expect(tabGroups.length).toBe(4);
461-
460+
expect(tabGroups.length).toBe(9);
461+
462462
tabGroups.forEach(tabGroup => {
463463
const chartPanel = tabGroup.querySelector('va-tab-panel[panel-id*="graph"]');
464464
const tablePanel = tabGroup.querySelector('va-tab-panel[panel-id*="table"]');

__tests__/metrics-integration.test.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,18 @@ describe('Metrics Dashboard - Real Implementation Integration Tests', () => {
4444
describe('🏗️ Built Site Structure Validation', () => {
4545
test('Built site contains all expected va-table components', () => {
4646
const vaTables = document.querySelectorAll('va-table');
47-
expect(vaTables.length).toBe(4);
48-
47+
expect(vaTables.length).toBe(9);
48+
4949
const expectedTableIds = [
5050
'quarterly-va-table',
51-
'components-va-table',
51+
'components-va-table',
52+
'imposter-quarterly-va-table',
53+
'imposter-component-va-table',
54+
'imposter-instances-component-va-table',
55+
'imposter-instances-app-va-table',
5256
'velocity-va-table',
53-
'experimental-va-table'
57+
'experimental-va-table',
58+
'a11y-defect-va-table'
5459
];
5560

5661
expectedTableIds.forEach(tableId => {
@@ -59,7 +64,7 @@ describe('Metrics Dashboard - Real Implementation Integration Tests', () => {
5964
expect(table.tagName.toLowerCase()).toBe('va-table');
6065
expect(table).toHaveAttribute('table-title');
6166
expect(table).toHaveAttribute('stacked', 'true');
62-
expect(table).toHaveAttribute('sortable', 'true');
67+
// sortable is added dynamically via JS based on viewport width
6368
});
6469
});
6570

@@ -90,13 +95,13 @@ describe('Metrics Dashboard - Real Implementation Integration Tests', () => {
9095

9196
test('Built site contains all chart containers with proper accessibility', () => {
9297
const chartContainers = document.querySelectorAll('.chart-container');
93-
expect(chartContainers.length).toBe(4);
94-
98+
expect(chartContainers.length).toBe(9);
99+
95100
chartContainers.forEach(container => {
96101
expect(container).toHaveAttribute('role', 'img');
97102
expect(container).toHaveAttribute('aria-label');
98103
expect(container).toHaveAttribute('tabindex', '0');
99-
104+
100105
const ariaLabel = container.getAttribute('aria-label');
101106
expect(ariaLabel.length).toBeGreaterThan(20);
102107
expect(ariaLabel.toLowerCase()).toContain('chart');
@@ -105,7 +110,7 @@ describe('Metrics Dashboard - Real Implementation Integration Tests', () => {
105110

106111
test('Built site contains all va-tabs with proper structure', () => {
107112
const vaTabs = document.querySelectorAll('va-tabs');
108-
expect(vaTabs.length).toBe(4);
113+
expect(vaTabs.length).toBe(9);
109114

110115
vaTabs.forEach(tabGroup => {
111116
const tabItems = tabGroup.querySelectorAll('va-tab-item');
@@ -130,20 +135,17 @@ describe('Metrics Dashboard - Real Implementation Integration Tests', () => {
130135

131136
test('Built site metric cards have proper accessibility structure', () => {
132137
const metricCards = document.querySelectorAll('.metric-card');
133-
expect(metricCards.length).toBe(6);
138+
expect(metricCards.length).toBe(8);
134139

135140
metricCards.forEach(card => {
136-
const heading = card.querySelector('h3[id]');
141+
const heading = card.querySelector('h3[id], h4[id]');
137142
expect(heading).toBeTruthy();
138-
143+
139144
const value = card.querySelector('.metric-value[aria-labelledby]');
140145
expect(value).toBeTruthy();
141-
146+
142147
const labelId = value.getAttribute('aria-labelledby');
143148
expect(labelId).toBe(heading.id);
144-
145-
const trend = card.querySelector('.metric-trend[aria-label]');
146-
expect(trend).toBeTruthy();
147149
});
148150
});
149151
});
@@ -154,7 +156,7 @@ describe('Metrics Dashboard - Real Implementation Integration Tests', () => {
154156
expect(vaTable).toBeTruthy();
155157
expect(vaTable).toHaveAttribute('table-title');
156158
expect(vaTable).toHaveAttribute('stacked', 'true');
157-
expect(vaTable).toHaveAttribute('sortable', 'true');
159+
// sortable is added dynamically via JS based on viewport width
158160

159161
const tableTitle = vaTable.getAttribute('table-title');
160162
expect(tableTitle).toContain('Issue Activity');
@@ -196,7 +198,7 @@ describe('Metrics Dashboard - Real Implementation Integration Tests', () => {
196198
expect(vaTable).toBeTruthy();
197199
expect(vaTable).toHaveAttribute('table-title');
198200
expect(vaTable).toHaveAttribute('stacked', 'true');
199-
expect(vaTable).toHaveAttribute('sortable', 'true');
201+
// sortable is added dynamically via JS based on viewport width
200202

201203
const tableTitle = vaTable.getAttribute('table-title');
202204
expect(tableTitle).toContain('Components');
@@ -235,7 +237,7 @@ describe('Metrics Dashboard - Real Implementation Integration Tests', () => {
235237
expect(vaTable).toBeTruthy();
236238
expect(vaTable).toHaveAttribute('table-title');
237239
expect(vaTable).toHaveAttribute('stacked', 'true');
238-
expect(vaTable).toHaveAttribute('sortable', 'true');
240+
// sortable is added dynamically via JS based on viewport width
239241

240242
const tableTitle = vaTable.getAttribute('table-title');
241243
expect(tableTitle).toContain('Monthly');
@@ -274,7 +276,7 @@ describe('Metrics Dashboard - Real Implementation Integration Tests', () => {
274276
expect(vaTable).toBeTruthy();
275277
expect(vaTable).toHaveAttribute('table-title');
276278
expect(vaTable).toHaveAttribute('stacked', 'true');
277-
expect(vaTable).toHaveAttribute('sortable', 'true');
279+
// sortable is added dynamically via JS based on viewport width
278280

279281
const tableTitle = vaTable.getAttribute('table-title');
280282
expect(tableTitle).toContain('Experimental');
@@ -362,7 +364,7 @@ describe('Metrics Dashboard - Real Implementation Integration Tests', () => {
362364
test('Built site keyboard navigation works correctly', () => {
363365
// Test that all interactive elements are keyboard accessible
364366
const focusableElements = document.querySelectorAll(
365-
'va-tab-item, .chart-container[tabindex="0"], va-table[sortable="true"]'
367+
'va-tab-item, .chart-container[tabindex="0"], va-table[stacked="true"]'
366368
);
367369

368370
expect(focusableElements.length).toBeGreaterThan(0);

0 commit comments

Comments
 (0)