feat(accessibility): add tabbing to chart menu in dashboard (#26138)

Co-authored-by: geido <diegopucci.me@gmail.com>
Co-authored-by: Diego Pucci <geido@Diegos-MBP.wind3.hub>
This commit is contained in:
Elizabeth Thompson
2024-04-08 09:40:57 -07:00
committed by GitHub
parent 662c1ed618
commit 34b1db219c
24 changed files with 965 additions and 154 deletions

View File

@@ -22,7 +22,11 @@ import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import { FeatureFlag } from '@superset-ui/core';
import mockState from 'spec/fixtures/mockState';
import SliceHeaderControls, { SliceHeaderControlsProps } from '.';
import { Menu } from 'src/components/Menu';
import SliceHeaderControls, {
SliceHeaderControlsProps,
handleDropdownNavigation,
} from '.';
jest.mock('src/components/Dropdown', () => {
const original = jest.requireActual('src/components/Dropdown');
@@ -194,8 +198,7 @@ test('Should "export to Excel"', async () => {
});
test('Export full CSV is under featureflag', async () => {
// @ts-ignore
global.featureFlags = {
(global as any).featureFlags = {
[FeatureFlag.AllowFullCsvExport]: false,
};
const props = createProps('table');
@@ -206,8 +209,7 @@ test('Export full CSV is under featureflag', async () => {
});
test('Should "export full CSV"', async () => {
// @ts-ignore
global.featureFlags = {
(global as any).featureFlags = {
[FeatureFlag.AllowFullCsvExport]: true,
};
const props = createProps('table');
@@ -220,8 +222,7 @@ test('Should "export full CSV"', async () => {
});
test('Should not show export full CSV if report is not table', async () => {
// @ts-ignore
global.featureFlags = {
(global as any).featureFlags = {
[FeatureFlag.AllowFullCsvExport]: true,
};
renderWrapper();
@@ -231,8 +232,7 @@ test('Should not show export full CSV if report is not table', async () => {
});
test('Export full Excel is under featureflag', async () => {
// @ts-ignore
global.featureFlags = {
(global as any).featureFlags = {
[FeatureFlag.AllowFullCsvExport]: false,
};
const props = createProps('table');
@@ -243,8 +243,7 @@ test('Export full Excel is under featureflag', async () => {
});
test('Should "export full Excel"', async () => {
// @ts-ignore
global.featureFlags = {
(global as any).featureFlags = {
[FeatureFlag.AllowFullCsvExport]: true,
};
const props = createProps('table');
@@ -257,8 +256,7 @@ test('Should "export full Excel"', async () => {
});
test('Should not show export full Excel if report is not table', async () => {
// @ts-ignore
global.featureFlags = {
(global as any).featureFlags = {
[FeatureFlag.AllowFullCsvExport]: true,
};
renderWrapper();
@@ -296,8 +294,7 @@ test('Should "Enter fullscreen"', () => {
});
test('Drill to detail modal is under featureflag', () => {
// @ts-ignore
global.featureFlags = {
(global as any).featureFlags = {
[FeatureFlag.DrillToDetail]: false,
};
const props = createProps();
@@ -306,8 +303,7 @@ test('Drill to detail modal is under featureflag', () => {
});
test('Should show "Drill to detail"', () => {
// @ts-ignore
global.featureFlags = {
(global as any).featureFlags = {
[FeatureFlag.DrillToDetail]: true,
};
const props = {
@@ -322,8 +318,7 @@ test('Should show "Drill to detail"', () => {
});
test('Should not show "Drill to detail"', () => {
// @ts-ignore
global.featureFlags = {
(global as any).featureFlags = {
[FeatureFlag.DrillToDetail]: true,
};
const props = {
@@ -400,3 +395,168 @@ test('Should not show the "Edit chart" button', () => {
});
expect(screen.queryByText('Edit chart')).not.toBeInTheDocument();
});
describe('handleDropdownNavigation', () => {
const mockToggleDropdown = jest.fn();
const mockSetSelectedKeys = jest.fn();
const mockSetOpenKeys = jest.fn();
const menu = (
<Menu selectedKeys={['item1']}>
<Menu.Item key="item1">Item 1</Menu.Item>
<Menu.Item key="item2">Item 2</Menu.Item>
<Menu.Item key="item3">Item 3</Menu.Item>
</Menu>
);
beforeEach(() => {
jest.clearAllMocks();
});
test('should continue with system tab navigation if dropdown is closed and tab key is pressed', () => {
const event = {
key: 'Tab',
preventDefault: jest.fn(),
} as unknown as React.KeyboardEvent<HTMLDivElement>;
handleDropdownNavigation(
event,
false,
<div />,
mockToggleDropdown,
mockSetSelectedKeys,
mockSetOpenKeys,
);
expect(mockToggleDropdown).not.toHaveBeenCalled();
expect(mockSetSelectedKeys).not.toHaveBeenCalled();
});
test(`should prevent default behavior and toggle dropdown if dropdown
is closed and action key is pressed`, () => {
const event = {
key: 'Enter',
preventDefault: jest.fn(),
} as unknown as React.KeyboardEvent<HTMLDivElement>;
handleDropdownNavigation(
event,
false,
<div />,
mockToggleDropdown,
mockSetSelectedKeys,
mockSetOpenKeys,
);
expect(mockToggleDropdown).toHaveBeenCalled();
expect(mockSetSelectedKeys).not.toHaveBeenCalled();
});
test(`should trigger menu item click,
clear selected keys, close dropdown, and focus on menu trigger
if action key is pressed and menu item is selected`, () => {
const event = {
key: 'Enter',
preventDefault: jest.fn(),
currentTarget: { focus: jest.fn() },
} as unknown as React.KeyboardEvent<HTMLDivElement>;
handleDropdownNavigation(
event,
true,
menu,
mockToggleDropdown,
mockSetSelectedKeys,
mockSetOpenKeys,
);
expect(mockToggleDropdown).toHaveBeenCalled();
expect(mockSetSelectedKeys).toHaveBeenCalledWith([]);
expect(event.currentTarget.focus).toHaveBeenCalled();
});
test('should select the next menu item if down arrow key is pressed', () => {
const event = {
key: 'ArrowDown',
preventDefault: jest.fn(),
} as unknown as React.KeyboardEvent<HTMLDivElement>;
handleDropdownNavigation(
event,
true,
menu,
mockToggleDropdown,
mockSetSelectedKeys,
mockSetOpenKeys,
);
expect(mockSetSelectedKeys).toHaveBeenCalledWith(['item2']);
});
test('should select the previous menu item if up arrow key is pressed', () => {
const event = {
key: 'ArrowUp',
preventDefault: jest.fn(),
} as unknown as React.KeyboardEvent<HTMLDivElement>;
handleDropdownNavigation(
event,
true,
menu,
mockToggleDropdown,
mockSetSelectedKeys,
mockSetOpenKeys,
);
expect(mockSetSelectedKeys).toHaveBeenCalledWith(['item1']);
});
test('should close dropdown menu if escape key is pressed', () => {
const event = {
key: 'Escape',
preventDefault: jest.fn(),
} as unknown as React.KeyboardEvent<HTMLDivElement>;
handleDropdownNavigation(
event,
true,
<div />,
mockToggleDropdown,
mockSetSelectedKeys,
mockSetOpenKeys,
);
expect(mockToggleDropdown).toHaveBeenCalled();
expect(mockSetSelectedKeys).not.toHaveBeenCalled();
});
test('should do nothing if an unsupported key is pressed', () => {
const event = {
key: 'Shift',
preventDefault: jest.fn(),
} as unknown as React.KeyboardEvent<HTMLDivElement>;
handleDropdownNavigation(
event,
true,
<div />,
mockToggleDropdown,
mockSetSelectedKeys,
mockSetOpenKeys,
);
expect(mockToggleDropdown).not.toHaveBeenCalled();
expect(mockSetSelectedKeys).not.toHaveBeenCalled();
});
test('should find a child element with a key', () => {
const item = {
props: {
children: [
<div key="1">Child 1</div>,
<div key="2">Child 2</div>,
<div key="3">Child 3</div>,
],
},
};
const childWithKey = item?.props?.children?.find(
(child: React.ReactElement) => child?.key,
);
expect(childWithKey).toBeDefined();
});
});