Skip to content

Commit 9eb4acd

Browse files
committed
chore: Export TestSingleTabStopNavigationProvider to avoid a dependency on RTL
1 parent 1bdc64f commit 9eb4acd

File tree

3 files changed

+62
-26
lines changed

3 files changed

+62
-26
lines changed

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

Lines changed: 14 additions & 6 deletions
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+
getTestSingleTabStopNavigationProvider,
16+
renderWithSingleTabStopNavigation,
17+
TestSingleTabStopNavigationProvider,
18+
} from '../test-helpers';
1519

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

5862
test('does not override tab index when keyboard navigation is not active', () => {
59-
renderWithSingleTabStopNavigation(<Button id="button" />, { navigationActive: false });
63+
render(
64+
<TestSingleTabStopNavigationProvider navigationActive={false}>
65+
<Button id="button" />
66+
</TestSingleTabStopNavigationProvider>
67+
);
6068
expect(document.querySelector('#button')).not.toHaveAttribute('tabIndex');
6169
});
6270

@@ -84,13 +92,13 @@ test('does not override tab index for suppressed elements', () => {
8492
});
8593

8694
test('overrides tab index when keyboard navigation is active', () => {
87-
const { setCurrentTarget } = renderWithSingleTabStopNavigation(
88-
<div>
95+
render(
96+
<TestSingleTabStopNavigationProvider navigationActive={true}>
8997
<Button id="button1" />
9098
<Button id="button2" />
91-
</div>
99+
</TestSingleTabStopNavigationProvider>
92100
);
93-
setCurrentTarget(document.querySelector('#button1'));
101+
getTestSingleTabStopNavigationProvider().setCurrentTarget(document.querySelector('#button1'));
94102
expect(document.querySelector('#button1')).toHaveAttribute('tabIndex', '0');
95103
expect(document.querySelector('#button2')).toHaveAttribute('tabIndex', '-1');
96104
});
Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
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+
interface TestProviderAPI {
1011
setCurrentTarget(focusTarget: null | Element, suppressed?: (null | Element)[]): void;
1112
}
1213

13-
const FakeSingleTabStopNavigationProvider = forwardRef(
14-
(
15-
{ children, navigationActive }: { children: React.ReactNode; navigationActive: boolean },
16-
ref: React.Ref<ProviderRef>
17-
) => {
14+
const providerRegistry = new Map<string, TestProviderAPI>();
15+
16+
export const TestSingleTabStopNavigationProvider = forwardRef(
17+
({
18+
children,
19+
navigationActive,
20+
providerId: explicitProviderId,
21+
}: {
22+
children: React.ReactNode;
23+
navigationActive: boolean;
24+
providerId?: string;
25+
}) => {
1826
const focusablesRef = useRef(new Set<Element>());
1927
const focusHandlersRef = useRef(new Map<Element, FocusableChangeHandler>());
2028
const registerFocusable = useCallback((focusable: HTMLElement, changeHandler: FocusableChangeHandler) => {
@@ -26,14 +34,22 @@ const FakeSingleTabStopNavigationProvider = forwardRef(
2634
};
2735
}, []);
2836

29-
useImperativeHandle(ref, () => ({
37+
const providerIdFallback = useUniqueId();
38+
const providerId = explicitProviderId ?? providerIdFallback;
39+
providerRegistry.set(providerId, {
3040
setCurrentTarget: (focusTarget: null | Element, suppressed: Element[] = []) => {
3141
focusablesRef.current.forEach(focusable => {
3242
const handler = focusHandlersRef.current.get(focusable)!;
3343
handler(focusTarget === focusable || suppressed.includes(focusable));
3444
});
3545
},
36-
}));
46+
});
47+
useEffect(
48+
() => () => {
49+
providerRegistry.delete(providerId);
50+
},
51+
[providerId]
52+
);
3753

3854
return (
3955
<SingleTabStopNavigationContext.Provider
@@ -45,24 +61,32 @@ const FakeSingleTabStopNavigationProvider = forwardRef(
4561
}
4662
);
4763

64+
export function getTestSingleTabStopNavigationProvider(providerId?: string): TestProviderAPI {
65+
return {
66+
setCurrentTarget(focusTarget, suppressed) {
67+
if (providerId) {
68+
providerRegistry.get(providerId)?.setCurrentTarget(focusTarget, suppressed);
69+
} else {
70+
Array.from(providerRegistry).forEach(([, provider]) => provider.setCurrentTarget(focusTarget, suppressed));
71+
}
72+
},
73+
};
74+
}
75+
76+
/**
77+
* @deprecated - Use TestSingleTabStopNavigationProvider instead
78+
*/
4879
export function renderWithSingleTabStopNavigation(
4980
ui: React.ReactNode,
5081
{ navigationActive = true }: { navigationActive?: boolean } = {}
5182
) {
52-
const providerRef = createRef<ProviderRef>();
5383
const { container, rerender } = render(
54-
<FakeSingleTabStopNavigationProvider ref={providerRef} navigationActive={navigationActive}>
55-
{ui}
56-
</FakeSingleTabStopNavigationProvider>
84+
<TestSingleTabStopNavigationProvider navigationActive={navigationActive}>{ui}</TestSingleTabStopNavigationProvider>
5785
);
5886
return {
5987
container,
6088
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-
},
89+
setCurrentTarget: (focusTarget: null | Element, suppressed: (null | Element)[] = []) =>
90+
getTestSingleTabStopNavigationProvider().setCurrentTarget(focusTarget, suppressed),
6791
};
6892
}

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+
getTestSingleTabStopNavigationProvider,
12+
} from './single-tab-stop/test-helpers';

0 commit comments

Comments
 (0)