File size: 4,507 Bytes
9332ccf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | /**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { renderHook } from '../../test-utils/render.js';
import { vi, type Mock } from 'vitest';
import { useFlickerDetector } from './useFlickerDetector.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { recordFlickerFrame, type Config } from '@google/gemini-cli-core';
import { type DOMElement, measureElement } from 'ink';
import { useUIState } from '../contexts/UIStateContext.js';
import { appEvents, AppEvent } from '../../utils/events.js';
// Mock dependencies
vi.mock('../contexts/ConfigContext.js');
vi.mock('../contexts/UIStateContext.js');
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
recordFlickerFrame: vi.fn(),
GEMINI_DIR: '.gemini',
};
});
vi.mock('ink', async (importOriginal) => {
const original = await importOriginal<typeof import('ink')>();
return {
...original,
measureElement: vi.fn(),
};
});
vi.mock('../../utils/events.js', () => ({
appEvents: {
emit: vi.fn(),
},
AppEvent: {
Flicker: 'flicker',
},
}));
const mockUseConfig = useConfig as Mock;
const mockUseUIState = useUIState as Mock;
const mockRecordFlickerFrame = recordFlickerFrame as Mock;
const mockMeasureElement = measureElement as Mock;
const mockAppEventsEmit = appEvents.emit as Mock;
describe('useFlickerDetector', () => {
const mockConfig = {} as Config;
let mockRef: React.RefObject<DOMElement | null>;
beforeEach(() => {
mockUseConfig.mockReturnValue(mockConfig);
mockRef = { current: { yogaNode: {} } as DOMElement };
// Default UI state
mockUseUIState.mockReturnValue({ constrainHeight: true });
});
afterEach(() => {
vi.clearAllMocks();
});
it('should not record a flicker when height is less than terminal height', async () => {
mockMeasureElement.mockReturnValue({ width: 80, height: 20 });
await renderHook(() => useFlickerDetector(mockRef, 25));
expect(mockRecordFlickerFrame).not.toHaveBeenCalled();
expect(mockAppEventsEmit).not.toHaveBeenCalled();
});
it('should not record a flicker when height is equal to terminal height', async () => {
mockMeasureElement.mockReturnValue({ width: 80, height: 25 });
await renderHook(() => useFlickerDetector(mockRef, 25));
expect(mockRecordFlickerFrame).not.toHaveBeenCalled();
expect(mockAppEventsEmit).not.toHaveBeenCalled();
});
it('should record a flicker when height is greater than terminal height and height is constrained', async () => {
mockMeasureElement.mockReturnValue({ width: 80, height: 30 });
await renderHook(() => useFlickerDetector(mockRef, 25));
expect(mockRecordFlickerFrame).toHaveBeenCalledTimes(1);
expect(mockRecordFlickerFrame).toHaveBeenCalledWith(mockConfig);
expect(mockAppEventsEmit).toHaveBeenCalledTimes(1);
expect(mockAppEventsEmit).toHaveBeenCalledWith(AppEvent.Flicker);
});
it('should NOT record a flicker when height is greater than terminal height but height is NOT constrained', async () => {
// Override default UI state for this test
mockUseUIState.mockReturnValue({ constrainHeight: false });
mockMeasureElement.mockReturnValue({ width: 80, height: 30 });
await renderHook(() => useFlickerDetector(mockRef, 25));
expect(mockRecordFlickerFrame).not.toHaveBeenCalled();
expect(mockAppEventsEmit).not.toHaveBeenCalled();
});
it('should not check for flicker if the ref is not set', async () => {
mockRef.current = null;
mockMeasureElement.mockReturnValue({ width: 80, height: 30 });
await renderHook(() => useFlickerDetector(mockRef, 25));
expect(mockMeasureElement).not.toHaveBeenCalled();
expect(mockRecordFlickerFrame).not.toHaveBeenCalled();
expect(mockAppEventsEmit).not.toHaveBeenCalled();
});
it('should re-evaluate on re-render', async () => {
// Start with a valid height
mockMeasureElement.mockReturnValue({ width: 80, height: 20 });
const { rerender } = await renderHook(() =>
useFlickerDetector(mockRef, 25),
);
expect(mockRecordFlickerFrame).not.toHaveBeenCalled();
// Now, simulate a re-render where the height is too great
mockMeasureElement.mockReturnValue({ width: 80, height: 30 });
rerender();
expect(mockRecordFlickerFrame).toHaveBeenCalledTimes(1);
expect(mockAppEventsEmit).toHaveBeenCalledTimes(1);
});
});
|