Skip to content

Commit b586863

Browse files
authored
fix(react-tabster): don't pass null to createKeyborg() (#34118)
1 parent e74fd1f commit b586863

File tree

3 files changed

+99
-2
lines changed

3 files changed

+99
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "fix: don't pass `null` to `createKeyborg()`",
4+
"packageName": "@fluentui/react-tabster",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { useFluent_unstable } from '@fluentui/react-shared-contexts';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import { createKeyborg, disposeKeyborg } from 'keyborg';
4+
5+
import { useKeyborgRef } from './useKeyborgRef';
6+
7+
jest.mock('keyborg', () => ({
8+
createKeyborg: jest.fn(),
9+
disposeKeyborg: jest.fn(),
10+
}));
11+
12+
jest.mock('@fluentui/react-shared-contexts', () => ({
13+
// eslint-disable-next-line @typescript-eslint/naming-convention
14+
useFluent_unstable: jest.fn(),
15+
}));
16+
17+
const createKeyborgMock = createKeyborg as jest.Mock;
18+
const disposeKeyborgMock = disposeKeyborg as jest.Mock;
19+
const useFluentMock = useFluent_unstable as jest.Mock;
20+
21+
describe('useKeyborgRef', () => {
22+
afterEach(() => {
23+
jest.clearAllMocks();
24+
});
25+
26+
it('should call createKeyborg() if a window is available', () => {
27+
const mockKeyborg = { foo: 'bar' };
28+
29+
useFluentMock.mockReturnValueOnce({ targetDocument: document });
30+
createKeyborgMock.mockReturnValueOnce(mockKeyborg);
31+
32+
const { result } = renderHook(() => useKeyborgRef());
33+
34+
expect(createKeyborg).toHaveBeenCalledWith(window);
35+
expect(result.current.current).toBe(mockKeyborg);
36+
});
37+
38+
it('should not call createKeyborg() targetDocument is not available', () => {
39+
useFluentMock.mockReturnValueOnce({ targetDocument: null });
40+
41+
const { result } = renderHook(() => useKeyborgRef());
42+
43+
expect(createKeyborg).not.toHaveBeenCalled();
44+
expect(result.current.current).toBeNull();
45+
});
46+
47+
it('should not call createKeyborg() targetWindow is not available', () => {
48+
useFluentMock.mockReturnValueOnce({ targetDocument: { defaultView: null } });
49+
50+
const { result } = renderHook(() => useKeyborgRef());
51+
52+
expect(createKeyborg).not.toHaveBeenCalled();
53+
expect(result.current.current).toBeNull();
54+
});
55+
56+
it('should dispose keyborg instance on unmount', () => {
57+
const mockKeyborg = { foo: 'bar' };
58+
59+
useFluentMock.mockReturnValueOnce({ targetDocument: document });
60+
createKeyborgMock.mockReturnValueOnce(mockKeyborg);
61+
62+
const { unmount } = renderHook(() => useKeyborgRef());
63+
64+
unmount();
65+
expect(disposeKeyborgMock).toHaveBeenCalledWith(mockKeyborg);
66+
});
67+
68+
it('should recreate keyborg when targetDocument changes', () => {
69+
const mockDocumentA = { defaultView: { devicePixelRatio: 1 } as Window } as Document;
70+
const mockDocumentB = { defaultView: { devicePixelRatio: 0.5 } as Window } as Document;
71+
72+
useFluentMock.mockReturnValueOnce({ targetDocument: mockDocumentA });
73+
74+
const { rerender } = renderHook(() => useKeyborgRef());
75+
76+
expect(createKeyborg).toHaveBeenCalledWith(mockDocumentA.defaultView);
77+
expect(disposeKeyborg).not.toHaveBeenCalled();
78+
79+
jest.clearAllMocks();
80+
81+
useFluentMock.mockReturnValueOnce({ targetDocument: mockDocumentB });
82+
rerender({});
83+
84+
expect(disposeKeyborg).toHaveBeenCalled();
85+
expect(createKeyborg).toHaveBeenCalledTimes(1);
86+
expect(createKeyborg).toHaveBeenCalledWith(mockDocumentB.defaultView);
87+
});
88+
});

packages/react-components/react-tabster/src/hooks/useKeyborgRef.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ export function useKeyborgRef() {
1212
const keyborgRef = React.useRef<Keyborg | null>(null);
1313

1414
React.useEffect(() => {
15-
if (targetDocument) {
16-
const keyborg = createKeyborg(targetDocument.defaultView!);
15+
const targetWindow = targetDocument?.defaultView;
16+
17+
if (targetWindow) {
18+
const keyborg = createKeyborg(targetWindow);
1719
keyborgRef.current = keyborg;
1820

1921
return () => {

0 commit comments

Comments
 (0)