2026-01-30 09:53:09 -08:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2025 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { render } from '../../test-utils/render.js';
|
2026-02-09 21:04:34 -05:00
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
2026-01-30 09:53:09 -08:00
|
|
|
import { BackgroundShellDisplay } from './BackgroundShellDisplay.js';
|
|
|
|
|
import { type BackgroundShell } from '../hooks/shellCommandProcessor.js';
|
|
|
|
|
import { ShellExecutionService } from '@google/gemini-cli-core';
|
|
|
|
|
import { act } from 'react';
|
|
|
|
|
import { type Key, type KeypressHandler } from '../contexts/KeypressContext.js';
|
|
|
|
|
import { ScrollProvider } from '../contexts/ScrollProvider.js';
|
|
|
|
|
import { Box } from 'ink';
|
|
|
|
|
|
|
|
|
|
// Mock dependencies
|
|
|
|
|
const mockDismissBackgroundShell = vi.fn();
|
|
|
|
|
const mockSetActiveBackgroundShellPid = vi.fn();
|
|
|
|
|
const mockSetIsBackgroundShellListOpen = vi.fn();
|
|
|
|
|
|
|
|
|
|
vi.mock('../contexts/UIActionsContext.js', () => ({
|
|
|
|
|
useUIActions: () => ({
|
|
|
|
|
dismissBackgroundShell: mockDismissBackgroundShell,
|
|
|
|
|
setActiveBackgroundShellPid: mockSetActiveBackgroundShellPid,
|
|
|
|
|
setIsBackgroundShellListOpen: mockSetIsBackgroundShellListOpen,
|
|
|
|
|
}),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
|
|
|
|
const actual =
|
|
|
|
|
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
|
|
|
|
return {
|
|
|
|
|
...actual,
|
|
|
|
|
ShellExecutionService: {
|
|
|
|
|
resizePty: vi.fn(),
|
|
|
|
|
subscribe: vi.fn(() => vi.fn()),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Mock AnsiOutputText since it's a complex component
|
|
|
|
|
vi.mock('./AnsiOutput.js', () => ({
|
|
|
|
|
AnsiOutputText: ({ data }: { data: string | unknown }) => {
|
|
|
|
|
if (typeof data === 'string') return <>{data}</>;
|
|
|
|
|
// Simple serialization for object data
|
|
|
|
|
return <>{JSON.stringify(data)}</>;
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Mock useKeypress
|
|
|
|
|
let keypressHandlers: Array<{ handler: KeypressHandler; isActive: boolean }> =
|
|
|
|
|
[];
|
|
|
|
|
vi.mock('../hooks/useKeypress.js', () => ({
|
|
|
|
|
useKeypress: vi.fn((handler, { isActive }) => {
|
|
|
|
|
keypressHandlers.push({ handler, isActive });
|
|
|
|
|
}),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const simulateKey = (key: Partial<Key>) => {
|
|
|
|
|
const fullKey: Key = createMockKey(key);
|
|
|
|
|
keypressHandlers.forEach(({ handler, isActive }) => {
|
|
|
|
|
if (isActive) {
|
|
|
|
|
handler(fullKey);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vi.mock('../contexts/MouseContext.js', () => ({
|
|
|
|
|
useMouseContext: vi.fn(() => ({
|
|
|
|
|
subscribe: vi.fn(),
|
|
|
|
|
unsubscribe: vi.fn(),
|
|
|
|
|
})),
|
|
|
|
|
useMouse: vi.fn(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Mock ScrollableList
|
|
|
|
|
vi.mock('./shared/ScrollableList.js', () => ({
|
|
|
|
|
SCROLL_TO_ITEM_END: 999999,
|
|
|
|
|
ScrollableList: vi.fn(
|
|
|
|
|
({
|
|
|
|
|
data,
|
|
|
|
|
renderItem,
|
|
|
|
|
}: {
|
|
|
|
|
data: BackgroundShell[];
|
|
|
|
|
renderItem: (props: {
|
|
|
|
|
item: BackgroundShell;
|
|
|
|
|
index: number;
|
|
|
|
|
}) => React.ReactNode;
|
|
|
|
|
}) => (
|
|
|
|
|
<Box flexDirection="column">
|
|
|
|
|
{data.map((item: BackgroundShell, index: number) => (
|
|
|
|
|
<Box key={index}>{renderItem({ item, index })}</Box>
|
|
|
|
|
))}
|
|
|
|
|
</Box>
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
}));
|
|
|
|
|
|
2026-02-09 21:04:34 -05:00
|
|
|
afterEach(() => {
|
|
|
|
|
vi.restoreAllMocks();
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-30 09:53:09 -08:00
|
|
|
const createMockKey = (overrides: Partial<Key>): Key => ({
|
|
|
|
|
name: '',
|
|
|
|
|
ctrl: false,
|
|
|
|
|
alt: false,
|
|
|
|
|
cmd: false,
|
|
|
|
|
shift: false,
|
|
|
|
|
insertable: false,
|
|
|
|
|
sequence: '',
|
|
|
|
|
...overrides,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('<BackgroundShellDisplay />', () => {
|
|
|
|
|
const mockShells = new Map<number, BackgroundShell>();
|
|
|
|
|
const shell1: BackgroundShell = {
|
|
|
|
|
pid: 1001,
|
|
|
|
|
command: 'npm start',
|
|
|
|
|
output: 'Starting server...',
|
|
|
|
|
isBinary: false,
|
|
|
|
|
binaryBytesReceived: 0,
|
|
|
|
|
status: 'running',
|
|
|
|
|
};
|
|
|
|
|
const shell2: BackgroundShell = {
|
|
|
|
|
pid: 1002,
|
|
|
|
|
command: 'tail -f log.txt',
|
|
|
|
|
output: 'Log entry 1',
|
|
|
|
|
isBinary: false,
|
|
|
|
|
binaryBytesReceived: 0,
|
|
|
|
|
status: 'running',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
mockShells.clear();
|
|
|
|
|
mockShells.set(shell1.pid, shell1);
|
|
|
|
|
mockShells.set(shell2.pid, shell2);
|
|
|
|
|
keypressHandlers = [];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders the output of the active shell', async () => {
|
2026-02-18 16:46:50 -08:00
|
|
|
const width = 80;
|
|
|
|
|
const { lastFrame, waitUntilReady, unmount } = render(
|
2026-01-30 09:53:09 -08:00
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={shell1.pid}
|
2026-02-18 16:46:50 -08:00
|
|
|
width={width}
|
2026-01-30 09:53:09 -08:00
|
|
|
height={24}
|
|
|
|
|
isFocused={false}
|
|
|
|
|
isListOpenProp={false}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
2026-02-18 16:46:50 -08:00
|
|
|
width,
|
2026-01-30 09:53:09 -08:00
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot();
|
2026-02-18 16:46:50 -08:00
|
|
|
unmount();
|
2026-01-30 09:53:09 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders tabs for multiple shells', async () => {
|
2026-02-18 16:46:50 -08:00
|
|
|
const width = 100;
|
|
|
|
|
const { lastFrame, waitUntilReady, unmount } = render(
|
2026-01-30 09:53:09 -08:00
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={shell1.pid}
|
2026-02-18 16:46:50 -08:00
|
|
|
width={width}
|
2026-01-30 09:53:09 -08:00
|
|
|
height={24}
|
|
|
|
|
isFocused={false}
|
|
|
|
|
isListOpenProp={false}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
2026-02-18 16:46:50 -08:00
|
|
|
width,
|
2026-01-30 09:53:09 -08:00
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot();
|
2026-02-18 16:46:50 -08:00
|
|
|
unmount();
|
2026-01-30 09:53:09 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('highlights the focused state', async () => {
|
2026-02-18 16:46:50 -08:00
|
|
|
const width = 80;
|
|
|
|
|
const { lastFrame, waitUntilReady, unmount } = render(
|
2026-01-30 09:53:09 -08:00
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={shell1.pid}
|
2026-02-18 16:46:50 -08:00
|
|
|
width={width}
|
2026-01-30 09:53:09 -08:00
|
|
|
height={24}
|
2026-02-18 16:46:50 -08:00
|
|
|
isFocused={true}
|
2026-01-30 09:53:09 -08:00
|
|
|
isListOpenProp={false}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
2026-02-18 16:46:50 -08:00
|
|
|
width,
|
2026-01-30 09:53:09 -08:00
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot();
|
2026-02-18 16:46:50 -08:00
|
|
|
unmount();
|
2026-01-30 09:53:09 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('resizes the PTY on mount and when dimensions change', async () => {
|
2026-02-18 16:46:50 -08:00
|
|
|
const width = 80;
|
|
|
|
|
const { rerender, waitUntilReady, unmount } = render(
|
2026-01-30 09:53:09 -08:00
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={shell1.pid}
|
2026-02-18 16:46:50 -08:00
|
|
|
width={width}
|
2026-01-30 09:53:09 -08:00
|
|
|
height={24}
|
|
|
|
|
isFocused={false}
|
|
|
|
|
isListOpenProp={false}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
2026-02-18 16:46:50 -08:00
|
|
|
width,
|
2026-01-30 09:53:09 -08:00
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(ShellExecutionService.resizePty).toHaveBeenCalledWith(
|
|
|
|
|
shell1.pid,
|
|
|
|
|
76,
|
|
|
|
|
21,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
rerender(
|
|
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={shell1.pid}
|
|
|
|
|
width={100}
|
|
|
|
|
height={30}
|
|
|
|
|
isFocused={false}
|
|
|
|
|
isListOpenProp={false}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
|
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(ShellExecutionService.resizePty).toHaveBeenCalledWith(
|
|
|
|
|
shell1.pid,
|
|
|
|
|
96,
|
|
|
|
|
27,
|
|
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
unmount();
|
2026-01-30 09:53:09 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders the process list when isListOpenProp is true', async () => {
|
2026-02-18 16:46:50 -08:00
|
|
|
const width = 80;
|
|
|
|
|
const { lastFrame, waitUntilReady, unmount } = render(
|
2026-01-30 09:53:09 -08:00
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={shell1.pid}
|
2026-02-18 16:46:50 -08:00
|
|
|
width={width}
|
2026-01-30 09:53:09 -08:00
|
|
|
height={24}
|
|
|
|
|
isFocused={true}
|
|
|
|
|
isListOpenProp={true}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
2026-02-18 16:46:50 -08:00
|
|
|
width,
|
2026-01-30 09:53:09 -08:00
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot();
|
2026-02-18 16:46:50 -08:00
|
|
|
unmount();
|
2026-01-30 09:53:09 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('selects the current process and closes the list when Ctrl+L is pressed in list view', async () => {
|
2026-02-18 16:46:50 -08:00
|
|
|
const width = 80;
|
|
|
|
|
const { waitUntilReady, unmount } = render(
|
2026-01-30 09:53:09 -08:00
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={shell1.pid}
|
2026-02-18 16:46:50 -08:00
|
|
|
width={width}
|
2026-01-30 09:53:09 -08:00
|
|
|
height={24}
|
|
|
|
|
isFocused={true}
|
|
|
|
|
isListOpenProp={true}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
2026-02-18 16:46:50 -08:00
|
|
|
width,
|
2026-01-30 09:53:09 -08:00
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
// Simulate down arrow to select the second process (handled by RadioButtonSelect)
|
2026-02-18 16:46:50 -08:00
|
|
|
await act(async () => {
|
2026-01-30 09:53:09 -08:00
|
|
|
simulateKey({ name: 'down' });
|
|
|
|
|
});
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
// Simulate Ctrl+L (handled by BackgroundShellDisplay)
|
2026-02-18 16:46:50 -08:00
|
|
|
await act(async () => {
|
2026-01-30 09:53:09 -08:00
|
|
|
simulateKey({ name: 'l', ctrl: true });
|
|
|
|
|
});
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(mockSetActiveBackgroundShellPid).toHaveBeenCalledWith(shell2.pid);
|
|
|
|
|
expect(mockSetIsBackgroundShellListOpen).toHaveBeenCalledWith(false);
|
2026-02-18 16:46:50 -08:00
|
|
|
unmount();
|
2026-01-30 09:53:09 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('kills the highlighted process when Ctrl+K is pressed in list view', async () => {
|
2026-02-18 16:46:50 -08:00
|
|
|
const width = 80;
|
|
|
|
|
const { waitUntilReady, unmount } = render(
|
2026-01-30 09:53:09 -08:00
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={shell1.pid}
|
2026-02-18 16:46:50 -08:00
|
|
|
width={width}
|
2026-01-30 09:53:09 -08:00
|
|
|
height={24}
|
|
|
|
|
isFocused={true}
|
|
|
|
|
isListOpenProp={true}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
2026-02-18 16:46:50 -08:00
|
|
|
width,
|
2026-01-30 09:53:09 -08:00
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
// Initial state: shell1 (active) is highlighted
|
|
|
|
|
|
|
|
|
|
// Move to shell2
|
2026-02-18 16:46:50 -08:00
|
|
|
await act(async () => {
|
2026-01-30 09:53:09 -08:00
|
|
|
simulateKey({ name: 'down' });
|
|
|
|
|
});
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
// Press Ctrl+K
|
2026-02-18 16:46:50 -08:00
|
|
|
await act(async () => {
|
2026-01-30 09:53:09 -08:00
|
|
|
simulateKey({ name: 'k', ctrl: true });
|
|
|
|
|
});
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(mockDismissBackgroundShell).toHaveBeenCalledWith(shell2.pid);
|
2026-02-18 16:46:50 -08:00
|
|
|
unmount();
|
2026-01-30 09:53:09 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('kills the active process when Ctrl+K is pressed in output view', async () => {
|
2026-02-18 16:46:50 -08:00
|
|
|
const width = 80;
|
|
|
|
|
const { waitUntilReady, unmount } = render(
|
2026-01-30 09:53:09 -08:00
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={shell1.pid}
|
2026-02-18 16:46:50 -08:00
|
|
|
width={width}
|
2026-01-30 09:53:09 -08:00
|
|
|
height={24}
|
|
|
|
|
isFocused={true}
|
|
|
|
|
isListOpenProp={false}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
2026-02-18 16:46:50 -08:00
|
|
|
width,
|
2026-01-30 09:53:09 -08:00
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
2026-02-18 16:46:50 -08:00
|
|
|
await act(async () => {
|
2026-01-30 09:53:09 -08:00
|
|
|
simulateKey({ name: 'k', ctrl: true });
|
|
|
|
|
});
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(mockDismissBackgroundShell).toHaveBeenCalledWith(shell1.pid);
|
2026-02-18 16:46:50 -08:00
|
|
|
unmount();
|
2026-01-30 09:53:09 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('scrolls to active shell when list opens', async () => {
|
|
|
|
|
// shell2 is active
|
2026-02-18 16:46:50 -08:00
|
|
|
const width = 80;
|
|
|
|
|
const { lastFrame, waitUntilReady, unmount } = render(
|
2026-01-30 09:53:09 -08:00
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={shell2.pid}
|
2026-02-18 16:46:50 -08:00
|
|
|
width={width}
|
2026-01-30 09:53:09 -08:00
|
|
|
height={24}
|
|
|
|
|
isFocused={true}
|
|
|
|
|
isListOpenProp={true}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
2026-02-18 16:46:50 -08:00
|
|
|
width,
|
2026-01-30 09:53:09 -08:00
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot();
|
2026-02-18 16:46:50 -08:00
|
|
|
unmount();
|
2026-01-30 09:53:09 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('keeps exit code status color even when selected', async () => {
|
|
|
|
|
const exitedShell: BackgroundShell = {
|
|
|
|
|
pid: 1003,
|
|
|
|
|
command: 'exit 0',
|
|
|
|
|
output: '',
|
|
|
|
|
isBinary: false,
|
|
|
|
|
binaryBytesReceived: 0,
|
|
|
|
|
status: 'exited',
|
|
|
|
|
exitCode: 0,
|
|
|
|
|
};
|
|
|
|
|
mockShells.set(exitedShell.pid, exitedShell);
|
|
|
|
|
|
2026-02-18 16:46:50 -08:00
|
|
|
const width = 80;
|
|
|
|
|
const { lastFrame, waitUntilReady, unmount } = render(
|
2026-01-30 09:53:09 -08:00
|
|
|
<ScrollProvider>
|
|
|
|
|
<BackgroundShellDisplay
|
|
|
|
|
shells={mockShells}
|
|
|
|
|
activePid={exitedShell.pid}
|
2026-02-18 16:46:50 -08:00
|
|
|
width={width}
|
2026-01-30 09:53:09 -08:00
|
|
|
height={24}
|
|
|
|
|
isFocused={true}
|
|
|
|
|
isListOpenProp={true}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollProvider>,
|
2026-02-18 16:46:50 -08:00
|
|
|
width,
|
2026-01-30 09:53:09 -08:00
|
|
|
);
|
2026-02-18 16:46:50 -08:00
|
|
|
await waitUntilReady();
|
2026-01-30 09:53:09 -08:00
|
|
|
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot();
|
2026-02-18 16:46:50 -08:00
|
|
|
unmount();
|
2026-01-30 09:53:09 -08:00
|
|
|
});
|
|
|
|
|
});
|