diff --git a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
index 83de3752be37..1f19c7944fd7 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
@@ -25,6 +25,33 @@ import DateWithFormatter from '../src/utils/DateWithFormatter';
import testData from './testData';
import { ProviderWrapper } from './testHelpers';
+const expectValidAriaLabels = (container: HTMLElement) => {
+ const allCells = container.querySelectorAll('tbody td');
+ const cellsWithLabels = container.querySelectorAll(
+ 'tbody td[aria-labelledby]',
+ );
+
+ // Table must render data cells (catch empty table regression)
+ expect(allCells.length).toBeGreaterThan(0);
+
+ // ALL data cells must have aria-labelledby (no unlabeled cells)
+ expect(cellsWithLabels.length).toBe(allCells.length);
+
+ // ALL aria-labelledby values should be valid
+ cellsWithLabels.forEach(cell => {
+ const labelledBy = cell.getAttribute('aria-labelledby');
+ expect(labelledBy).not.toBeNull();
+ expect(labelledBy).toEqual(expect.stringMatching(/\S/));
+ const labelledByValue = labelledBy as string;
+ expect(labelledByValue).not.toMatch(/\s/);
+ expect(labelledByValue).not.toMatch(/[%#△]/);
+ const referencedHeader = container.querySelector(
+ `#${CSS.escape(labelledByValue)}`,
+ );
+ expect(referencedHeader).toBeTruthy();
+ });
+};
+
test('sanitizeHeaderId should sanitize percent sign', () => {
expect(sanitizeHeaderId('%pct_nice')).toBe('percentpct_nice');
});
@@ -602,7 +629,7 @@ describe('plugin-chart-table', () => {
// Uses originalLabel (e.g., "metric_1") which is sanitized for CSS safety
const props = transformProps(testData.comparison);
- const { container } = render();
+ render();
const headers = screen.getAllByRole('columnheader');
@@ -632,25 +659,16 @@ describe('plugin-chart-table', () => {
// IDs should only contain valid characters: alphanumeric, underscore, hyphen
expect(header.id).toMatch(/^header-[a-zA-Z0-9_-]+$/);
});
+ });
- // CRITICAL: Verify ALL cells reference valid headers (no broken ARIA)
- const cellsWithLabels = container.querySelectorAll(
- 'td[aria-labelledby]',
- );
- cellsWithLabels.forEach(cell => {
- const labelledBy = cell.getAttribute('aria-labelledby');
- if (labelledBy) {
- // Check that the ID doesn't contain spaces (would be interpreted as multiple IDs)
- expect(labelledBy).not.toMatch(/\s/);
- // Check that the ID doesn't contain special characters
- expect(labelledBy).not.toMatch(/[%#△]/);
- // Verify the referenced header actually exists
- const referencedHeader = container.querySelector(
- `#${CSS.escape(labelledBy)}`,
- );
- expect(referencedHeader).toBeTruthy();
- }
- });
+ test('should validate ARIA references for time-comparison table cells', () => {
+ // Test that ALL cells with aria-labelledby have valid references
+ // This is critical for screen reader accessibility
+ const props = transformProps(testData.comparison);
+
+ const { container } = render();
+
+ expectValidAriaLabels(container);
});
test('should set meaningful header IDs for regular table columns', () => {
@@ -711,25 +729,20 @@ describe('plugin-chart-table', () => {
// IDs should only contain valid CSS selector characters
expect(header.id).toMatch(/^header-[a-zA-Z0-9_-]+$/);
});
+ });
- // Test 6: Verify ALL cells reference valid headers (no broken ARIA)
- const cellsWithLabels = container.querySelectorAll(
- 'td[aria-labelledby]',
+ test('should validate ARIA references for regular table cells', () => {
+ // Test that ALL cells with aria-labelledby have valid references
+ // This is critical for screen reader accessibility
+ const props = transformProps(testData.advanced);
+
+ const { container } = render(
+ ProviderWrapper({
+ children: ,
+ }),
);
- cellsWithLabels.forEach(cell => {
- const labelledBy = cell.getAttribute('aria-labelledby');
- if (labelledBy) {
- // Verify no spaces (would be interpreted as multiple IDs)
- expect(labelledBy).not.toMatch(/\s/);
- // Verify no special characters
- expect(labelledBy).not.toMatch(/[%#△]/);
- // Verify the referenced header actually exists
- const referencedHeader = container.querySelector(
- `#${CSS.escape(labelledBy)}`,
- );
- expect(referencedHeader).toBeTruthy();
- }
- });
+
+ expectValidAriaLabels(container);
});
test('render cell bars properly, and only when it is toggled on in both regular and percent metrics', () => {