Skip to content

Commit

Permalink
grid-nav unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pan-kot committed Jul 31, 2023
1 parent 9d7b9ca commit 2bfbbcf
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 4 deletions.
193 changes: 193 additions & 0 deletions src/table/table-role/__tests__/use-grid-navigation.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import React, { useRef } from 'react';
import { render, fireEvent } from '@testing-library/react';
import { useGridNavigation } from '../use-grid-navigation';
import { KeyCode } from '../../../internal/keycode';

// TODO: test mutation observer logic with integ test

function SimpleTable({ tableRole = 'grid' }: { tableRole?: 'grid' | 'table' }) {
const tableRef = useRef<HTMLTableElement>(null);
useGridNavigation({ tableRole, pageSize: 2, getTable: () => tableRef.current });
return (
<table role={tableRole} ref={tableRef}>
<thead>
<tr aria-rowindex={1}>
<th aria-colindex={1}>header-1</th>
<th aria-colindex={2}>header-2</th>
<th aria-colindex={3}>header-3</th>
</tr>
</thead>
<tbody>
<tr aria-rowindex={2}>
<td aria-colindex={1}>cell-1-1</td>
<td aria-colindex={2}>cell-1-2</td>
<td aria-colindex={3}>cell-1-3</td>
</tr>
</tbody>
</table>
);
}

function InteractiveTable({ actionsWidget = false, pageSize = 2 }: { actionsWidget?: boolean; pageSize?: number }) {
const tableRef = useRef<HTMLTableElement>(null);
useGridNavigation({ tableRole: 'grid', pageSize, getTable: () => tableRef.current });
return (
<table role="grid" ref={tableRef}>
<thead>
<tr aria-rowindex={1}>
<th aria-colindex={1}>
header-1 <button>desc</button>
</th>
<th aria-colindex={2}>header-2</th>
<th aria-colindex={3}>header-3</th>
</tr>
</thead>
<tbody>
<tr aria-rowindex={2}>
<td aria-colindex={1}>
<a href="#">link-1-1</a>
</td>
<td aria-colindex={2}>cell-1-2</td>
<td aria-colindex={3} data-widget-cell={actionsWidget}>
<button>action-1-3-1</button> <button>action-1-3-2</button>
</td>
</tr>
<tr aria-rowindex={3}>
<td aria-colindex={1}>
<a href="#">link-2-1</a>
</td>
<td aria-colindex={2}>cell-2-2</td>
<td aria-colindex={3} data-widget-cell={actionsWidget}>
<button>action-2-3-1</button> <button>action-2-3-2</button>
</td>
</tr>
</tbody>
</table>
);
}

function getActiveElement() {
return [document.activeElement?.tagName.toLowerCase(), document.activeElement?.textContent];
}

test('not activated for "table" role', () => {
const { container } = render(<SimpleTable tableRole="table" />);
expect(container.querySelectorAll('td[tabIndex],th[tabIndex]')).toHaveLength(0);
});

test('updates cell tab indices', () => {
const { container } = render(<SimpleTable tableRole="grid" />);
const focusableCells = container.querySelectorAll('td[tabIndex="-1"],th[tabIndex="-1"]');
const userFocusableCells = container.querySelectorAll('td[tabIndex="0"],th[tabIndex="0"]');

expect(focusableCells).toHaveLength(4);
expect(userFocusableCells).toHaveLength(2);
expect(userFocusableCells[0].textContent).toBe('header-1');
expect(userFocusableCells[1].textContent).toBe('cell-1-3');
});

test('does not make cells with interactive content user-focusable', () => {
const { container } = render(<InteractiveTable />);
const focusableCells = container.querySelectorAll('td[tabIndex="-1"],th[tabIndex="-1"]');

expect(focusableCells).toHaveLength(9);
});

test('makes edge widget cells user-focusable', () => {
const { container } = render(<InteractiveTable actionsWidget={true} />);
const focusableCells = container.querySelectorAll('td[tabIndex="-1"],th[tabIndex="-1"]');
const userFocusableCells = container.querySelectorAll('td[tabIndex="0"],th[tabIndex="0"]');

expect(focusableCells).toHaveLength(8);
expect(userFocusableCells).toHaveLength(1);
expect(userFocusableCells[0].textContent).toBe('action-2-3-1 action-2-3-2');
});

test('supports arrow keys navigation', () => {
const { container } = render(<InteractiveTable />);
const table = container.querySelector('table')!;
const focusables = container.querySelectorAll('a,button,*[tabIndex="0"]') as NodeListOf<HTMLElement>;

focusables[0].focus();
expect(getActiveElement()).toEqual(['button', 'desc']);

fireEvent.keyDown(table, { keyCode: KeyCode.right });
expect(getActiveElement()).toEqual(['th', 'header-2']);

fireEvent.keyDown(table, { keyCode: KeyCode.down });
expect(getActiveElement()).toEqual(['td', 'cell-1-2']);

fireEvent.keyDown(table, { keyCode: KeyCode.left });
expect(getActiveElement()).toEqual(['a', 'link-1-1']);

fireEvent.keyDown(table, { keyCode: KeyCode.up });
expect(getActiveElement()).toEqual(['button', 'desc']);
});

test('supports key combination navigation', () => {
const { container } = render(<InteractiveTable />);
const table = container.querySelector('table')!;
const focusables = container.querySelectorAll('a,button,*[tabIndex="0"]') as NodeListOf<HTMLElement>;

focusables[0].focus();
expect(getActiveElement()).toEqual(['button', 'desc']);

fireEvent.keyDown(table, { keyCode: KeyCode.pageDown });
expect(getActiveElement()).toEqual(['a', 'link-2-1']);

fireEvent.keyDown(table, { keyCode: KeyCode.pageUp });
expect(getActiveElement()).toEqual(['button', 'desc']);

fireEvent.keyDown(table, { keyCode: KeyCode.end });
expect(getActiveElement()).toEqual(['th', 'header-3']);

fireEvent.keyDown(table, { keyCode: KeyCode.home });
expect(getActiveElement()).toEqual(['button', 'desc']);

fireEvent.keyDown(table, { keyCode: KeyCode.end, ctrlKey: true });
expect(getActiveElement()).toEqual(['button', 'action-2-3-1']);

fireEvent.keyDown(table, { keyCode: KeyCode.home, ctrlKey: true });
expect(getActiveElement()).toEqual(['button', 'desc']);
});

test('support widget cell navigation', () => {
const { container } = render(<InteractiveTable actionsWidget={true} />);
const table = container.querySelector('table')!;
const focusables = container.querySelectorAll('a,button,*[tabIndex="0"]') as NodeListOf<HTMLElement>;

focusables[0].focus();
fireEvent.keyDown(table, { keyCode: KeyCode.pageDown });
fireEvent.keyDown(table, { keyCode: KeyCode.right });
fireEvent.keyDown(table, { keyCode: KeyCode.right });
expect(getActiveElement()).toEqual(['td', 'action-2-3-1 action-2-3-2']);

fireEvent.keyDown(table, { keyCode: KeyCode.enter });
expect(getActiveElement()).toEqual(['button', 'action-2-3-1']);

fireEvent.keyDown(table, { keyCode: KeyCode.right });
expect(getActiveElement()).toEqual(['button', 'action-2-3-2']);

fireEvent.keyDown(table, { keyCode: KeyCode.escape });
expect(getActiveElement()).toEqual(['td', 'action-2-3-1 action-2-3-2']);
});

test('updates page size', () => {
const { container, rerender } = render(<InteractiveTable />);
const table = container.querySelector('table')!;
const focusables = container.querySelectorAll('a,button,*[tabIndex="0"]') as NodeListOf<HTMLElement>;

focusables[0].focus();
expect(getActiveElement()).toEqual(['button', 'desc']);

fireEvent.keyDown(table, { keyCode: KeyCode.pageDown });
expect(getActiveElement()).toEqual(['a', 'link-2-1']);

rerender(<InteractiveTable pageSize={1} />);

fireEvent.keyDown(table, { keyCode: KeyCode.pageUp });
expect(getActiveElement()).toEqual(['a', 'link-1-1']);
});
1 change: 1 addition & 0 deletions src/table/table-role/use-grid-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class GridNavigationModel {

private onFocusin = (event: FocusEvent) => {
const cell = findFocusinCell(event);

if (!cell) {
return;
}
Expand Down
8 changes: 4 additions & 4 deletions src/table/table-role/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ export function updateTableIndices(table: HTMLTableElement) {
for (let i = 1; i < tableCells.length - 1; i++) {
tableCells[i].tabIndex = -1;
}
if (firstCell && !getFirstFocusable(firstCell)) {
firstCell.tabIndex = 0;
if (firstCell) {
firstCell.tabIndex = !getFirstFocusable(firstCell) || isWidgetCell(firstCell) ? 0 : -1;
}
if (lastCell && !getFirstFocusable(lastCell)) {
lastCell.tabIndex = 0;
if (lastCell) {
lastCell.tabIndex = !getFirstFocusable(lastCell) || isWidgetCell(lastCell) ? 0 : -1;
}
}

Expand Down

0 comments on commit 2bfbbcf

Please sign in to comment.