Skip to content

Commit 15f08d8

Browse files
authored
chore: Export TestSingleTabStopNavigationProvider to avoid a dependency on RTL (#159)
1 parent 1bdc64f commit 15f08d8

File tree

3 files changed

+60
-23
lines changed

3 files changed

+60
-23
lines changed

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ import {
1111
SingleTabStopNavigationReset,
1212
useSingleTabStopNavigation,
1313
} from '../';
14-
import { renderWithSingleTabStopNavigation } from './utils';
14+
import {
15+
setTestSingleTabStopNavigationTarget,
16+
renderWithSingleTabStopNavigation,
17+
TestSingleTabStopNavigationProvider,
18+
} from '../test-helpers';
1519

1620
// Simple STSN subscriber component
1721
function Button(props: React.HTMLAttributes<HTMLButtonElement>) {
@@ -56,6 +60,15 @@ function findGroupButton(groupId: string, buttonIndex: number) {
5660
}
5761

5862
test('does not override tab index when keyboard navigation is not active', () => {
63+
render(
64+
<TestSingleTabStopNavigationProvider navigationActive={false}>
65+
<Button id="button" />
66+
</TestSingleTabStopNavigationProvider>
67+
);
68+
expect(document.querySelector('#button')).not.toHaveAttribute('tabIndex');
69+
});
70+
71+
test('(legacy coverage) does not override tab index when keyboard navigation is not active', () => {
5972
renderWithSingleTabStopNavigation(<Button id="button" />, { navigationActive: false });
6073
expect(document.querySelector('#button')).not.toHaveAttribute('tabIndex');
6174
});
@@ -84,6 +97,18 @@ test('does not override tab index for suppressed elements', () => {
8497
});
8598

8699
test('overrides tab index when keyboard navigation is active', () => {
100+
render(
101+
<TestSingleTabStopNavigationProvider navigationActive={true}>
102+
<Button id="button1" />
103+
<Button id="button2" />
104+
</TestSingleTabStopNavigationProvider>
105+
);
106+
setTestSingleTabStopNavigationTarget(document.querySelector('#button1'));
107+
expect(document.querySelector('#button1')).toHaveAttribute('tabIndex', '0');
108+
expect(document.querySelector('#button2')).toHaveAttribute('tabIndex', '-1');
109+
});
110+
111+
test('(legacy coverage) overrides tab index when keyboard navigation is active', () => {
87112
const { setCurrentTarget } = renderWithSingleTabStopNavigation(
88113
<div>
89114
<Button id="button1" />
Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import React, { createRef, forwardRef, useCallback, useImperativeHandle, useRef } from 'react';
4+
import React, { forwardRef, useCallback, useEffect, useRef } from 'react';
55
import { render } from '@testing-library/react';
66

77
import { FocusableChangeHandler, SingleTabStopNavigationContext } from '../';
8+
import { useUniqueId } from '../../use-unique-id';
89

9-
interface ProviderRef {
10-
setCurrentTarget(focusTarget: null | Element, suppressed?: (null | Element)[]): void;
10+
type SetTarget = (focusTarget: null | Element, suppressed?: (null | Element)[]) => void;
11+
12+
interface TestProviderAPI {
13+
setCurrentTarget: SetTarget;
1114
}
1215

13-
const FakeSingleTabStopNavigationProvider = forwardRef(
14-
(
15-
{ children, navigationActive }: { children: React.ReactNode; navigationActive: boolean },
16-
ref: React.Ref<ProviderRef>
17-
) => {
16+
const providerRegistry = new Map<string, TestProviderAPI>();
17+
18+
export const TestSingleTabStopNavigationProvider = forwardRef(
19+
({ children, navigationActive }: { children: React.ReactNode; navigationActive: boolean }) => {
1820
const focusablesRef = useRef(new Set<Element>());
1921
const focusHandlersRef = useRef(new Map<Element, FocusableChangeHandler>());
2022
const registerFocusable = useCallback((focusable: HTMLElement, changeHandler: FocusableChangeHandler) => {
@@ -26,14 +28,21 @@ const FakeSingleTabStopNavigationProvider = forwardRef(
2628
};
2729
}, []);
2830

29-
useImperativeHandle(ref, () => ({
30-
setCurrentTarget: (focusTarget: null | Element, suppressed: Element[] = []) => {
31+
const providerId = useUniqueId();
32+
providerRegistry.set(providerId, {
33+
setCurrentTarget: (focusTarget, suppressed = []) => {
3134
focusablesRef.current.forEach(focusable => {
3235
const handler = focusHandlersRef.current.get(focusable)!;
3336
handler(focusTarget === focusable || suppressed.includes(focusable));
3437
});
3538
},
36-
}));
39+
});
40+
useEffect(
41+
() => () => {
42+
providerRegistry.delete(providerId);
43+
},
44+
[providerId]
45+
);
3746

3847
return (
3948
<SingleTabStopNavigationContext.Provider
@@ -45,24 +54,23 @@ const FakeSingleTabStopNavigationProvider = forwardRef(
4554
}
4655
);
4756

57+
export const setTestSingleTabStopNavigationTarget: SetTarget = (focusTarget, suppressed) => {
58+
Array.from(providerRegistry).forEach(([, provider]) => provider.setCurrentTarget(focusTarget, suppressed));
59+
};
60+
61+
/**
62+
* @deprecated - Use TestSingleTabStopNavigationProvider instead
63+
*/
4864
export function renderWithSingleTabStopNavigation(
4965
ui: React.ReactNode,
5066
{ navigationActive = true }: { navigationActive?: boolean } = {}
5167
) {
52-
const providerRef = createRef<ProviderRef>();
5368
const { container, rerender } = render(
54-
<FakeSingleTabStopNavigationProvider ref={providerRef} navigationActive={navigationActive}>
55-
{ui}
56-
</FakeSingleTabStopNavigationProvider>
69+
<TestSingleTabStopNavigationProvider navigationActive={navigationActive}>{ui}</TestSingleTabStopNavigationProvider>
5770
);
5871
return {
5972
container,
6073
rerender,
61-
setCurrentTarget: (focusTarget: null | Element, suppressed: (null | Element)[] = []) => {
62-
if (!providerRef.current) {
63-
throw new Error('Provider is not ready');
64-
}
65-
providerRef.current.setCurrentTarget(focusTarget, suppressed);
66-
},
74+
setCurrentTarget: setTestSingleTabStopNavigationTarget,
6775
};
6876
}

src/internal/testing.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@ export { clearOneTimeMetricsCache } from './base-component/metrics/metrics';
55
export { clearMessageCache } from './logging';
66
export { setGlobalFlag } from './global-flags';
77
export { clearVisualRefreshState } from './visual-mode';
8-
export { renderWithSingleTabStopNavigation } from './single-tab-stop/__tests__/utils';
8+
export {
9+
renderWithSingleTabStopNavigation,
10+
TestSingleTabStopNavigationProvider,
11+
setTestSingleTabStopNavigationTarget,
12+
} from './single-tab-stop/test-helpers';

0 commit comments

Comments
 (0)