Skip to content

Commit 72aa996

Browse files
committed
add more single tab stop context tests
1 parent a5b2484 commit 72aa996

File tree

3 files changed

+180
-0
lines changed

3 files changed

+180
-0
lines changed

src/dom/__tests__/node-belongs.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,8 @@ describe('nodeBelongs', () => {
6060
`;
6161
expect(nodeBelongs(div.querySelector('#container1'), div.querySelector('#node') as Node)).toBe(true);
6262
});
63+
64+
test('returns false if target is not a node', () => {
65+
expect(nodeBelongs(div.querySelector('#container1'), {} as any)).toBe(false);
66+
});
6367
});

src/internal/single-tab-stop/__tests__/index.test.tsx renamed to src/internal/single-tab-stop/__tests__/context.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ function Button(props: React.HTMLAttributes<HTMLButtonElement>) {
1313
return <button {...props} ref={buttonRef} tabIndex={tabIndex} />;
1414
}
1515

16+
test('subscribed components can be rendered outside single tab stop navigation context', () => {
17+
render(<Button />);
18+
expect(document.querySelector('button')).not.toHaveAttribute('tabIndex');
19+
});
20+
1621
test('does not override tab index when keyboard navigation is not active', () => {
1722
renderWithSingleTabStopNavigation(<Button id="button" />, { navigationActive: false });
1823
expect(document.querySelector('#button')).not.toHaveAttribute('tabIndex');
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import React, { createRef, useContext, useRef } from 'react';
5+
import { render, waitFor } from '@testing-library/react';
6+
7+
import {
8+
SingleTabStopNavigationAPI,
9+
SingleTabStopNavigationContext,
10+
SingleTabStopNavigationProvider,
11+
useSingleTabStopNavigation,
12+
} from '../';
13+
14+
function Button(props: React.HTMLAttributes<HTMLButtonElement>) {
15+
const buttonRef = useRef<HTMLButtonElement>(null);
16+
const { tabIndex } = useSingleTabStopNavigation(buttonRef, { tabIndex: props.tabIndex });
17+
return <button {...props} ref={buttonRef} tabIndex={tabIndex} />;
18+
}
19+
20+
function ButtonGroup({
21+
buttons,
22+
getNextFocusTarget,
23+
onUnregisterActive,
24+
isElementSuppressed,
25+
apiRef,
26+
}: {
27+
buttons: string[];
28+
getNextFocusTarget: () => null | HTMLElement;
29+
onUnregisterActive?: (element: Element) => void;
30+
isElementSuppressed?: (element: Element) => boolean;
31+
apiRef: React.Ref<SingleTabStopNavigationAPI>;
32+
}) {
33+
return (
34+
<SingleTabStopNavigationProvider
35+
ref={apiRef}
36+
navigationActive={true}
37+
getNextFocusTarget={getNextFocusTarget}
38+
onUnregisterActive={onUnregisterActive}
39+
isElementSuppressed={isElementSuppressed}
40+
>
41+
<div>
42+
{buttons.map(id => (
43+
<Button key={id} data-testid={id}>
44+
{id}
45+
</Button>
46+
))}
47+
<button data-testid="X" tabIndex={0}>
48+
X
49+
</button>
50+
</div>
51+
</SingleTabStopNavigationProvider>
52+
);
53+
}
54+
55+
function getButton(testId: string) {
56+
return document.querySelector(`[data-testid="${testId}"]`) as null | HTMLElement;
57+
}
58+
59+
test('registers focusable elements', () => {
60+
const apiRef = createRef<SingleTabStopNavigationAPI>();
61+
const { rerender } = render(
62+
<ButtonGroup buttons={['A', 'B', 'C']} getNextFocusTarget={() => getButton('A')} apiRef={apiRef} />
63+
);
64+
const buttons = [getButton('A')!, getButton('B')!, getButton('C')!, getButton('X')!];
65+
66+
expect(apiRef.current!.isRegistered(buttons[0])).toBe(true);
67+
expect(apiRef.current!.isRegistered(buttons[1])).toBe(true);
68+
expect(apiRef.current!.isRegistered(buttons[2])).toBe(true);
69+
expect(apiRef.current!.isRegistered(buttons[3])).toBe(false);
70+
71+
rerender(<ButtonGroup buttons={['A', 'B']} getNextFocusTarget={() => getButton('A')} apiRef={apiRef} />);
72+
73+
expect(apiRef.current!.isRegistered(buttons[0])).toBe(true);
74+
expect(apiRef.current!.isRegistered(buttons[1])).toBe(true);
75+
expect(apiRef.current!.isRegistered(buttons[2])).toBe(false);
76+
expect(apiRef.current!.isRegistered(buttons[3])).toBe(false);
77+
});
78+
79+
test('updates and retrieves focus target', () => {
80+
const focusTarget = { current: 'A' };
81+
const apiRef = createRef<SingleTabStopNavigationAPI>();
82+
render(
83+
<ButtonGroup buttons={['A', 'B']} getNextFocusTarget={() => getButton(focusTarget.current)} apiRef={apiRef} />
84+
);
85+
86+
expect(getButton('A')).toHaveAttribute('tabIndex', '-1');
87+
expect(getButton('B')).toHaveAttribute('tabIndex', '-1');
88+
expect(getButton('X')).toHaveAttribute('tabIndex', '0');
89+
90+
apiRef.current!.updateFocusTarget();
91+
expect(apiRef.current!.getFocusTarget()).toBe(getButton('A'));
92+
expect(getButton('A')).toHaveAttribute('tabIndex', '0');
93+
expect(getButton('B')).toHaveAttribute('tabIndex', '-1');
94+
expect(getButton('X')).toHaveAttribute('tabIndex', '0');
95+
96+
focusTarget.current = 'B';
97+
apiRef.current!.updateFocusTarget();
98+
expect(apiRef.current!.getFocusTarget()).toBe(getButton('B'));
99+
expect(getButton('A')).toHaveAttribute('tabIndex', '-1');
100+
expect(getButton('B')).toHaveAttribute('tabIndex', '0');
101+
expect(getButton('X')).toHaveAttribute('tabIndex', '0');
102+
103+
focusTarget.current = 'X';
104+
apiRef.current!.updateFocusTarget();
105+
expect(apiRef.current!.getFocusTarget()).toBe(getButton('X'));
106+
expect(getButton('A')).toHaveAttribute('tabIndex', '-1');
107+
expect(getButton('B')).toHaveAttribute('tabIndex', '-1');
108+
expect(getButton('X')).toHaveAttribute('tabIndex', '0');
109+
});
110+
111+
test('ignores elements that are suppressed', () => {
112+
const apiRef = createRef<SingleTabStopNavigationAPI>();
113+
const { rerender } = render(
114+
<ButtonGroup buttons={['A', 'B']} getNextFocusTarget={() => getButton('A')} apiRef={apiRef} />
115+
);
116+
117+
apiRef.current!.updateFocusTarget();
118+
expect(getButton('A')).toHaveAttribute('tabIndex', '0');
119+
expect(getButton('B')).toHaveAttribute('tabIndex', '-1');
120+
121+
rerender(
122+
<ButtonGroup
123+
buttons={['A', 'B']}
124+
getNextFocusTarget={() => getButton('A')}
125+
isElementSuppressed={element => element === getButton('B')}
126+
apiRef={apiRef}
127+
/>
128+
);
129+
expect(getButton('A')).toHaveAttribute('tabIndex', '0');
130+
expect(getButton('B')).toHaveAttribute('tabIndex', '0');
131+
});
132+
133+
test('calls onUnregisterActive', async () => {
134+
const apiRef = createRef<SingleTabStopNavigationAPI>();
135+
const onUnregisterActive = jest.fn();
136+
const { rerender } = render(
137+
<ButtonGroup
138+
buttons={['A', 'B']}
139+
getNextFocusTarget={() => getButton('A')}
140+
onUnregisterActive={onUnregisterActive}
141+
apiRef={apiRef}
142+
/>
143+
);
144+
145+
getButton('A')!.focus();
146+
147+
rerender(
148+
<ButtonGroup
149+
buttons={['A', 'B']}
150+
getNextFocusTarget={() => getButton('A')}
151+
onUnregisterActive={onUnregisterActive}
152+
apiRef={apiRef}
153+
/>
154+
);
155+
await waitFor(() => {
156+
expect(onUnregisterActive).toHaveBeenCalledWith(getButton('A'));
157+
});
158+
});
159+
160+
test('context defaults', () => {
161+
function Button() {
162+
const context = useContext(SingleTabStopNavigationContext);
163+
// Checking the default registerFocusable is defined.
164+
context.registerFocusable(document.createElement('div'), () => {})();
165+
return <button>{context.navigationActive ? 'active' : 'inactive'}</button>;
166+
}
167+
168+
render(<Button />);
169+
170+
expect(document.querySelector('button')!.textContent).toBe('inactive');
171+
});

0 commit comments

Comments
 (0)