Skip to content

Commit

Permalink
add more single tab stop context tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pan-kot committed Jan 22, 2025
1 parent a5b2484 commit 5114cbc
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ function Button(props: React.HTMLAttributes<HTMLButtonElement>) {
return <button {...props} ref={buttonRef} tabIndex={tabIndex} />;
}

test('subscribed components can be rendered outside single tab stop navigation context', () => {
render(<Button />);
expect(document.querySelector('button')).not.toHaveAttribute('tabIndex');
});

test('does not override tab index when keyboard navigation is not active', () => {
renderWithSingleTabStopNavigation(<Button id="button" />, { navigationActive: false });
expect(document.querySelector('#button')).not.toHaveAttribute('tabIndex');
Expand Down
146 changes: 146 additions & 0 deletions src/internal/single-tab-stop/__tests__/register.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import React, { createRef, useContext, useRef } from 'react';
import { render, waitFor } from '@testing-library/react';

import {
SingleTabStopNavigationAPI,
SingleTabStopNavigationContext,
SingleTabStopNavigationProvider,
useSingleTabStopNavigation,
} from '../';

function Button(props: React.HTMLAttributes<HTMLButtonElement>) {
const buttonRef = useRef<HTMLButtonElement>(null);
const { tabIndex } = useSingleTabStopNavigation(buttonRef, { tabIndex: props.tabIndex });
return <button {...props} ref={buttonRef} tabIndex={tabIndex} />;
}

function ButtonGroup({
buttons,
getNextFocusTarget,
onUnregisterActive,
apiRef,
}: {
buttons: string[];
getNextFocusTarget: () => null | HTMLElement;
onUnregisterActive?: (element: Element) => void;
apiRef: React.Ref<SingleTabStopNavigationAPI>;
}) {
return (
<SingleTabStopNavigationProvider
ref={apiRef}
navigationActive={true}
getNextFocusTarget={getNextFocusTarget}
onUnregisterActive={onUnregisterActive}
>
<div>
{buttons.map(id => (
<Button key={id} data-testid={id}>
{id}
</Button>
))}
<button data-testid="X" tabIndex={0}>
X
</button>
</div>
</SingleTabStopNavigationProvider>
);
}

function getButton(testId: string) {
return document.querySelector(`[data-testid="${testId}"]`) as null | HTMLElement;
}

test('registers focusable elements', () => {
const apiRef = createRef<SingleTabStopNavigationAPI>();
const { rerender } = render(
<ButtonGroup buttons={['A', 'B', 'C']} getNextFocusTarget={() => getButton('A')} apiRef={apiRef} />
);
const buttons = [getButton('A')!, getButton('B')!, getButton('C')!, getButton('X')!];

expect(apiRef.current!.isRegistered(buttons[0])).toBe(true);
expect(apiRef.current!.isRegistered(buttons[1])).toBe(true);
expect(apiRef.current!.isRegistered(buttons[2])).toBe(true);
expect(apiRef.current!.isRegistered(buttons[3])).toBe(false);

rerender(<ButtonGroup buttons={['A', 'B']} getNextFocusTarget={() => getButton('A')} apiRef={apiRef} />);

expect(apiRef.current!.isRegistered(buttons[0])).toBe(true);
expect(apiRef.current!.isRegistered(buttons[1])).toBe(true);
expect(apiRef.current!.isRegistered(buttons[2])).toBe(false);
expect(apiRef.current!.isRegistered(buttons[3])).toBe(false);
});

test('updates and retrieves focus target', () => {
const focusTarget = { current: 'A' };
const apiRef = createRef<SingleTabStopNavigationAPI>();
render(
<ButtonGroup buttons={['A', 'B']} getNextFocusTarget={() => getButton(focusTarget.current)} apiRef={apiRef} />
);

expect(getButton('A')).toHaveAttribute('tabIndex', '-1');
expect(getButton('B')).toHaveAttribute('tabIndex', '-1');
expect(getButton('X')).toHaveAttribute('tabIndex', '0');

apiRef.current!.updateFocusTarget();
expect(apiRef.current!.getFocusTarget()).toBe(getButton('A'));
expect(getButton('A')).toHaveAttribute('tabIndex', '0');
expect(getButton('B')).toHaveAttribute('tabIndex', '-1');
expect(getButton('X')).toHaveAttribute('tabIndex', '0');

focusTarget.current = 'B';
apiRef.current!.updateFocusTarget();
expect(apiRef.current!.getFocusTarget()).toBe(getButton('B'));
expect(getButton('A')).toHaveAttribute('tabIndex', '-1');
expect(getButton('B')).toHaveAttribute('tabIndex', '0');
expect(getButton('X')).toHaveAttribute('tabIndex', '0');

focusTarget.current = 'X';
apiRef.current!.updateFocusTarget();
expect(apiRef.current!.getFocusTarget()).toBe(getButton('X'));
expect(getButton('A')).toHaveAttribute('tabIndex', '-1');
expect(getButton('B')).toHaveAttribute('tabIndex', '-1');
expect(getButton('X')).toHaveAttribute('tabIndex', '0');
});

test('calls onUnregisterActive', async () => {
const apiRef = createRef<SingleTabStopNavigationAPI>();
const onUnregisterActive = jest.fn();
const { rerender } = render(
<ButtonGroup
buttons={['A', 'B']}
getNextFocusTarget={() => getButton('A')}
onUnregisterActive={onUnregisterActive}
apiRef={apiRef}
/>
);

getButton('A')!.focus();

rerender(
<ButtonGroup
buttons={['A', 'B']}
getNextFocusTarget={() => getButton('A')}
onUnregisterActive={onUnregisterActive}
apiRef={apiRef}
/>
);
await waitFor(() => {
expect(onUnregisterActive).toHaveBeenCalledWith(getButton('A'));
});
});

test('context defaults', () => {
function Button() {
const context = useContext(SingleTabStopNavigationContext);
// Checking the default registerFocusable is defined.
context.registerFocusable(document.createElement('div'), () => {})();
return <button>{context.navigationActive ? 'active' : 'inactive'}</button>;
}

render(<Button />);

expect(document.querySelector('button')!.textContent).toBe('inactive');
});

0 comments on commit 5114cbc

Please sign in to comment.