Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ const Menu = ({
const scaleY = useSharedValue(0);

const keyboardHeightRef = React.useRef(0);
const prevVisible = React.useRef<boolean | null>(null);
const prevVisible = React.useRef(visible);
const anchorRef = React.useRef<View | null>(null);
const menuRef = React.useRef<View | null>(null);
const isShownRef = React.useRef(false);
Expand Down
112 changes: 109 additions & 3 deletions src/components/__tests__/Menu.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { Dimensions, StyleSheet, View } from 'react-native';

import { expect, it, jest } from '@jest/globals';
import { Dimensions, Platform, StyleSheet, View } from 'react-native';

import {
afterEach,
beforeEach,
describe,
expect,
it,
jest,
} from '@jest/globals';
import { act, screen, waitFor } from '@testing-library/react-native';

import { render } from '../../test-utils';
Expand All @@ -10,6 +17,105 @@ import Button from '../Button/Button';
import Menu from '../Menu/Menu';
import Portal from '../Portal/Portal';

describe('web focus', () => {
const focus = jest.fn();
const originalPlatform = Platform.OS;
const originalHTMLElement = Object.getOwnPropertyDescriptor(
globalThis,
'HTMLElement'
);
const originalDocument = Object.getOwnPropertyDescriptor(
globalThis,
'document'
);
const originalQuerySelector = Object.getOwnPropertyDescriptor(
View.prototype,
'querySelector'
);
const originalMeasureInWindow = Object.getOwnPropertyDescriptor(
View.prototype,
'measureInWindow'
);

beforeEach(() => {
Platform.OS = 'web';
focus.mockClear();
// Native View refs stand in for DOM elements in the native test renderer.
Object.defineProperty(globalThis, 'HTMLElement', {
configurable: true,
value: View,
});
Object.defineProperty(globalThis, 'document', {
configurable: true,
value: {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
},
});
Object.defineProperty(View.prototype, 'querySelector', {
configurable: true,
value: () => ({ focus }),
});
const measureInWindow: View['measureInWindow'] = (fn) =>
fn(100, 100, 80, 32);
Object.defineProperty(View.prototype, 'measureInWindow', {
configurable: true,
value: measureInWindow,
});
});

afterEach(() => {
Platform.OS = originalPlatform;
for (const [target, key, descriptor] of [
[globalThis, 'HTMLElement', originalHTMLElement],
[globalThis, 'document', originalDocument],
[View.prototype, 'querySelector', originalQuerySelector],
[View.prototype, 'measureInWindow', originalMeasureInWindow],
] as const) {
if (descriptor) {
Object.defineProperty(target, key, descriptor);
} else {
Reflect.deleteProperty(target, key);
}
}
});

const makeMenu = (visible: boolean) => (
<Portal.Host>
<Menu visible={visible} anchor={<Button>Open menu</Button>}>
<Menu.Item title="Undo" />
</Menu>
</Portal.Host>
);

it('does not move focus when mounted hidden', async () => {
await render(makeMenu(false));
await act(() => jest.advanceTimersByTime(300));

expect(focus).not.toHaveBeenCalled();
});

it('moves focus when opened and restores it when closed', async () => {
const { rerender } = await render(makeMenu(false));
await rerender(makeMenu(true));
await act(() => jest.advanceTimersByTime(300));

expect(focus).toHaveBeenCalledTimes(1);

await rerender(makeMenu(false));
await act(() => jest.advanceTimersByTime(300));

expect(focus).toHaveBeenCalledTimes(2);
});

it('moves focus when mounted visible', async () => {
await render(makeMenu(true));
await act(() => jest.advanceTimersByTime(300));

expect(focus).toHaveBeenCalledTimes(1);
});
});

it('renders visible menu', async () => {
const tree = (
await render(
Expand Down
Loading