mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 19:52:02 +00:00
Sync upstream Gemini-CLI v0.8.2 (#838)
This commit is contained in:
parent
096fabb5d6
commit
eb95c131be
644 changed files with 70389 additions and 23709 deletions
|
|
@ -22,17 +22,33 @@
|
|||
*/
|
||||
|
||||
import { render } from 'ink-testing-library';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { SettingsDialog } from './SettingsDialog.js';
|
||||
import { LoadedSettings } from '../../config/settings.js';
|
||||
import { LoadedSettings, SettingScope } from '../../config/settings.js';
|
||||
import { VimModeProvider } from '../contexts/VimModeContext.js';
|
||||
import { KeypressProvider } from '../contexts/KeypressContext.js';
|
||||
import { act } from 'react';
|
||||
import { saveModifiedSettings, TEST_ONLY } from '../../utils/settingsUtils.js';
|
||||
import {
|
||||
getSettingsSchema,
|
||||
type SettingDefinition,
|
||||
type SettingsSchemaType,
|
||||
} from '../../config/settingsSchema.js';
|
||||
|
||||
// Mock the VimModeContext
|
||||
const mockToggleVimEnabled = vi.fn();
|
||||
const mockSetVimMode = vi.fn();
|
||||
|
||||
enum TerminalKeys {
|
||||
ENTER = '\u000D',
|
||||
TAB = '\t',
|
||||
UP_ARROW = '\u001B[A',
|
||||
DOWN_ARROW = '\u001B[B',
|
||||
LEFT_ARROW = '\u001B[D',
|
||||
RIGHT_ARROW = '\u001B[C',
|
||||
ESCAPE = '\u001B',
|
||||
}
|
||||
|
||||
const createMockSettings = (
|
||||
userSettings = {},
|
||||
systemSettings = {},
|
||||
|
|
@ -41,10 +57,16 @@ const createMockSettings = (
|
|||
new LoadedSettings(
|
||||
{
|
||||
settings: { ui: { customThemes: {} }, mcpServers: {}, ...systemSettings },
|
||||
originalSettings: {
|
||||
ui: { customThemes: {} },
|
||||
mcpServers: {},
|
||||
...systemSettings,
|
||||
},
|
||||
path: '/system/settings.json',
|
||||
},
|
||||
{
|
||||
settings: {},
|
||||
originalSettings: {},
|
||||
path: '/system/system-defaults.json',
|
||||
},
|
||||
{
|
||||
|
|
@ -53,6 +75,11 @@ const createMockSettings = (
|
|||
mcpServers: {},
|
||||
...userSettings,
|
||||
},
|
||||
originalSettings: {
|
||||
ui: { customThemes: {} },
|
||||
mcpServers: {},
|
||||
...userSettings,
|
||||
},
|
||||
path: '/user/settings.json',
|
||||
},
|
||||
{
|
||||
|
|
@ -61,33 +88,23 @@ const createMockSettings = (
|
|||
mcpServers: {},
|
||||
...workspaceSettings,
|
||||
},
|
||||
originalSettings: {
|
||||
ui: { customThemes: {} },
|
||||
mcpServers: {},
|
||||
...workspaceSettings,
|
||||
},
|
||||
path: '/workspace/settings.json',
|
||||
},
|
||||
[],
|
||||
true,
|
||||
new Set(),
|
||||
);
|
||||
|
||||
vi.mock('../contexts/SettingsContext.js', async () => {
|
||||
const actual = await vi.importActual('../contexts/SettingsContext.js');
|
||||
let settings = createMockSettings({ 'a.string.setting': 'initial' });
|
||||
vi.mock('../../config/settingsSchema.js', async (importOriginal) => {
|
||||
const original =
|
||||
await importOriginal<typeof import('../../config/settingsSchema.js')>();
|
||||
return {
|
||||
...actual,
|
||||
useSettings: () => ({
|
||||
settings,
|
||||
setSetting: (key: string, value: string) => {
|
||||
settings = createMockSettings({ [key]: value });
|
||||
},
|
||||
getSettingDefinition: (key: string) => {
|
||||
if (key === 'a.string.setting') {
|
||||
return {
|
||||
type: 'string',
|
||||
description: 'A string setting',
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
}),
|
||||
...original,
|
||||
getSettingsSchema: vi.fn(original.getSettingsSchema),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -134,10 +151,33 @@ vi.mock('../../utils/settingsUtils.js', async () => {
|
|||
// const originalConsoleError = console.error;
|
||||
|
||||
describe('SettingsDialog', () => {
|
||||
// Simple delay function for remaining tests that need gradual migration
|
||||
const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
// Custom waitFor utility for ink testing environment (not compatible with @testing-library/react)
|
||||
const waitFor = async (
|
||||
predicate: () => void,
|
||||
options: { timeout?: number; interval?: number } = {},
|
||||
) => {
|
||||
const { timeout = 1000, interval = 10 } = options;
|
||||
const start = Date.now();
|
||||
let lastError: unknown;
|
||||
while (Date.now() - start < timeout) {
|
||||
try {
|
||||
predicate();
|
||||
return;
|
||||
} catch (e) {
|
||||
lastError = e;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
}
|
||||
if (lastError) {
|
||||
throw lastError;
|
||||
}
|
||||
throw new Error('waitFor timed out');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Reset keypress mock state (variables are commented out)
|
||||
// currentKeypressHandler = null;
|
||||
// isKeypressActive = false;
|
||||
|
|
@ -147,6 +187,9 @@ describe('SettingsDialog', () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
TEST_ONLY.clearFlattenedSchema();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
// Reset keypress mock state (variables are commented out)
|
||||
// currentKeypressHandler = null;
|
||||
// isKeypressActive = false;
|
||||
|
|
@ -154,45 +197,6 @@ describe('SettingsDialog', () => {
|
|||
// console.error = originalConsoleError;
|
||||
});
|
||||
|
||||
const createMockSettings = (
|
||||
userSettings = {},
|
||||
systemSettings = {},
|
||||
workspaceSettings = {},
|
||||
) =>
|
||||
new LoadedSettings(
|
||||
{
|
||||
settings: {
|
||||
ui: { customThemes: {} },
|
||||
mcpServers: {},
|
||||
...systemSettings,
|
||||
},
|
||||
path: '/system/settings.json',
|
||||
},
|
||||
{
|
||||
settings: {},
|
||||
path: '/system/system-defaults.json',
|
||||
},
|
||||
{
|
||||
settings: {
|
||||
ui: { customThemes: {} },
|
||||
mcpServers: {},
|
||||
...userSettings,
|
||||
},
|
||||
path: '/user/settings.json',
|
||||
},
|
||||
{
|
||||
settings: {
|
||||
ui: { customThemes: {} },
|
||||
mcpServers: {},
|
||||
...workspaceSettings,
|
||||
},
|
||||
path: '/workspace/settings.json',
|
||||
},
|
||||
[],
|
||||
true,
|
||||
new Set(),
|
||||
);
|
||||
|
||||
describe('Initial Rendering', () => {
|
||||
it('should render the settings dialog with default state', () => {
|
||||
const settings = createMockSettings();
|
||||
|
|
@ -210,6 +214,26 @@ describe('SettingsDialog', () => {
|
|||
expect(output).toContain('Use Enter to select, Tab to change focus');
|
||||
});
|
||||
|
||||
it('should accept availableTerminalHeight prop without errors', () => {
|
||||
const settings = createMockSettings();
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog
|
||||
settings={settings}
|
||||
onSelect={onSelect}
|
||||
availableTerminalHeight={20}
|
||||
/>
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
// Should still render properly with the height prop
|
||||
expect(output).toContain('Settings');
|
||||
expect(output).toContain('Use Enter to select');
|
||||
});
|
||||
|
||||
it('should show settings list with default values', () => {
|
||||
const settings = createMockSettings();
|
||||
const onSelect = vi.fn();
|
||||
|
|
@ -246,15 +270,18 @@ describe('SettingsDialog', () => {
|
|||
const settings = createMockSettings();
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { stdin, unmount } = render(
|
||||
const { stdin, unmount, lastFrame } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
// Press down arrow
|
||||
stdin.write('\u001B[B'); // Down arrow
|
||||
await wait();
|
||||
act(() => {
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string); // Down arrow
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('● Disable Auto Update');
|
||||
|
||||
// The active index should have changed (tested indirectly through behavior)
|
||||
unmount();
|
||||
|
|
@ -271,9 +298,9 @@ describe('SettingsDialog', () => {
|
|||
);
|
||||
|
||||
// First go down, then up
|
||||
stdin.write('\u001B[B'); // Down arrow
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string); // Down arrow
|
||||
await wait();
|
||||
stdin.write('\u001B[A'); // Up arrow
|
||||
stdin.write(TerminalKeys.UP_ARROW as string);
|
||||
await wait();
|
||||
|
||||
unmount();
|
||||
|
|
@ -298,43 +325,203 @@ describe('SettingsDialog', () => {
|
|||
unmount();
|
||||
});
|
||||
|
||||
it('should not navigate beyond bounds', async () => {
|
||||
it('wraps around when at the top of the list', async () => {
|
||||
const settings = createMockSettings();
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { stdin, unmount } = render(
|
||||
const { stdin, unmount, lastFrame } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
// Try to go up from first item
|
||||
stdin.write('\u001B[A'); // Up arrow
|
||||
act(() => {
|
||||
stdin.write(TerminalKeys.UP_ARROW);
|
||||
});
|
||||
|
||||
await wait();
|
||||
|
||||
// Should still be on first item
|
||||
expect(lastFrame()).toContain('● Vision Model Preview');
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Settings Toggling', () => {
|
||||
it('should toggle setting with Enter key', async () => {
|
||||
vi.mocked(saveModifiedSettings).mockClear();
|
||||
|
||||
const settings = createMockSettings();
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { stdin, unmount } = render(
|
||||
const component = (
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
// Press Enter to toggle current setting
|
||||
stdin.write('\u000D'); // Enter key
|
||||
await wait();
|
||||
const { stdin, unmount, lastFrame } = render(component);
|
||||
|
||||
// Wait for initial render and verify we're on Vim Mode (first setting)
|
||||
await waitFor(() => {
|
||||
expect(lastFrame()).toContain('● Vim Mode');
|
||||
});
|
||||
|
||||
// Navigate to Disable Auto Update setting and verify we're there
|
||||
act(() => {
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(lastFrame()).toContain('● Disable Auto Update');
|
||||
});
|
||||
|
||||
// Toggle the setting
|
||||
act(() => {
|
||||
stdin.write(TerminalKeys.ENTER as string);
|
||||
});
|
||||
// Wait for the setting change to be processed
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
vi.mocked(saveModifiedSettings).mock.calls.length,
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// Wait for the mock to be called
|
||||
await waitFor(() => {
|
||||
expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalledWith(
|
||||
new Set<string>(['general.disableAutoUpdate']),
|
||||
{
|
||||
general: {
|
||||
disableAutoUpdate: true,
|
||||
},
|
||||
},
|
||||
expect.any(LoadedSettings),
|
||||
SettingScope.User,
|
||||
);
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
describe('enum values', () => {
|
||||
enum StringEnum {
|
||||
FOO = 'foo',
|
||||
BAR = 'bar',
|
||||
BAZ = 'baz',
|
||||
}
|
||||
|
||||
const SETTING: SettingDefinition = {
|
||||
type: 'enum',
|
||||
label: 'Theme',
|
||||
options: [
|
||||
{
|
||||
label: 'Foo',
|
||||
value: StringEnum.FOO,
|
||||
},
|
||||
{
|
||||
label: 'Bar',
|
||||
value: StringEnum.BAR,
|
||||
},
|
||||
{
|
||||
label: 'Baz',
|
||||
value: StringEnum.BAZ,
|
||||
},
|
||||
],
|
||||
category: 'UI',
|
||||
requiresRestart: false,
|
||||
default: StringEnum.BAR,
|
||||
description: 'The color theme for the UI.',
|
||||
showInDialog: true,
|
||||
};
|
||||
|
||||
const FAKE_SCHEMA: SettingsSchemaType = {
|
||||
ui: {
|
||||
showInDialog: false,
|
||||
properties: {
|
||||
theme: {
|
||||
...SETTING,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as SettingsSchemaType;
|
||||
|
||||
it('toggles enum values with the enter key', async () => {
|
||||
vi.mocked(saveModifiedSettings).mockClear();
|
||||
|
||||
vi.mocked(getSettingsSchema).mockReturnValue(FAKE_SCHEMA);
|
||||
const settings = createMockSettings();
|
||||
const onSelect = vi.fn();
|
||||
const component = (
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
const { stdin, unmount } = render(component);
|
||||
|
||||
// Press Enter to toggle current setting
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string);
|
||||
await wait();
|
||||
stdin.write(TerminalKeys.ENTER as string);
|
||||
await wait();
|
||||
await waitFor(() => {
|
||||
expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalledWith(
|
||||
new Set<string>(['ui.theme']),
|
||||
{
|
||||
ui: {
|
||||
theme: StringEnum.BAZ,
|
||||
},
|
||||
},
|
||||
expect.any(LoadedSettings),
|
||||
SettingScope.User,
|
||||
);
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('loops back when reaching the end of an enum', async () => {
|
||||
vi.mocked(saveModifiedSettings).mockClear();
|
||||
vi.mocked(getSettingsSchema).mockReturnValue(FAKE_SCHEMA);
|
||||
const settings = createMockSettings();
|
||||
settings.setValue(SettingScope.User, 'ui.theme', StringEnum.BAZ);
|
||||
const onSelect = vi.fn();
|
||||
const component = (
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>
|
||||
);
|
||||
|
||||
const { stdin, unmount } = render(component);
|
||||
|
||||
// Press Enter to toggle current setting
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string);
|
||||
await wait();
|
||||
stdin.write(TerminalKeys.ENTER as string);
|
||||
await wait();
|
||||
await waitFor(() => {
|
||||
expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalledWith(
|
||||
new Set<string>(['ui.theme']),
|
||||
{
|
||||
ui: {
|
||||
theme: StringEnum.FOO,
|
||||
},
|
||||
},
|
||||
expect.any(LoadedSettings),
|
||||
SettingScope.User,
|
||||
);
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it('should toggle setting with Space key', async () => {
|
||||
const settings = createMockSettings();
|
||||
const onSelect = vi.fn();
|
||||
|
|
@ -364,7 +551,7 @@ describe('SettingsDialog', () => {
|
|||
|
||||
// Navigate to vim mode setting and toggle it
|
||||
// This would require knowing the exact position, so we'll just test that the mock is called
|
||||
stdin.write('\u000D'); // Enter key
|
||||
stdin.write(TerminalKeys.ENTER as string); // Enter key
|
||||
await wait();
|
||||
|
||||
// The mock should potentially be called if vim mode was toggled
|
||||
|
|
@ -384,7 +571,7 @@ describe('SettingsDialog', () => {
|
|||
);
|
||||
|
||||
// Switch to scope focus
|
||||
stdin.write('\t'); // Tab key
|
||||
stdin.write(TerminalKeys.TAB); // Tab key
|
||||
await wait();
|
||||
|
||||
// Select different scope (numbers 1-3 typically available)
|
||||
|
|
@ -504,7 +691,7 @@ describe('SettingsDialog', () => {
|
|||
);
|
||||
|
||||
// Switch to scope selector
|
||||
stdin.write('\t'); // Tab
|
||||
stdin.write(TerminalKeys.TAB as string); // Tab
|
||||
await wait();
|
||||
|
||||
// Change scope
|
||||
|
|
@ -549,7 +736,7 @@ describe('SettingsDialog', () => {
|
|||
);
|
||||
|
||||
// Try to toggle a setting (this might trigger vim mode toggle)
|
||||
stdin.write('\u000D'); // Enter
|
||||
stdin.write(TerminalKeys.ENTER as string); // Enter
|
||||
await wait();
|
||||
|
||||
// Should not crash
|
||||
|
|
@ -569,13 +756,13 @@ describe('SettingsDialog', () => {
|
|||
);
|
||||
|
||||
// Toggle a setting
|
||||
stdin.write('\u000D'); // Enter
|
||||
stdin.write(TerminalKeys.ENTER as string); // Enter
|
||||
await wait();
|
||||
|
||||
// Toggle another setting
|
||||
stdin.write('\u001B[B'); // Down
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string); // Down
|
||||
await wait();
|
||||
stdin.write('\u000D'); // Enter
|
||||
stdin.write(TerminalKeys.ENTER as string); // Enter
|
||||
await wait();
|
||||
|
||||
// Should track multiple modified settings
|
||||
|
|
@ -594,7 +781,7 @@ describe('SettingsDialog', () => {
|
|||
|
||||
// Navigate down many times to test scrolling
|
||||
for (let i = 0; i < 10; i++) {
|
||||
stdin.write('\u001B[B'); // Down arrow
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string); // Down arrow
|
||||
await wait(10);
|
||||
}
|
||||
|
||||
|
|
@ -617,7 +804,7 @@ describe('SettingsDialog', () => {
|
|||
|
||||
// Navigate to and toggle vim mode setting
|
||||
// This would require knowing the exact position of vim mode setting
|
||||
stdin.write('\u000D'); // Enter
|
||||
stdin.write(TerminalKeys.ENTER as string); // Enter
|
||||
await wait();
|
||||
|
||||
unmount();
|
||||
|
|
@ -655,7 +842,7 @@ describe('SettingsDialog', () => {
|
|||
);
|
||||
|
||||
// Toggle a non-restart-required setting (like hideTips)
|
||||
stdin.write('\u000D'); // Enter - toggle current setting
|
||||
stdin.write(TerminalKeys.ENTER as string); // Enter - toggle current setting
|
||||
await wait();
|
||||
|
||||
// Should save immediately without showing restart prompt
|
||||
|
|
@ -752,8 +939,8 @@ describe('SettingsDialog', () => {
|
|||
|
||||
// Rapid navigation
|
||||
for (let i = 0; i < 5; i++) {
|
||||
stdin.write('\u001B[B'); // Down arrow
|
||||
stdin.write('\u001B[A'); // Up arrow
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string);
|
||||
stdin.write(TerminalKeys.UP_ARROW as string);
|
||||
}
|
||||
await wait(100);
|
||||
|
||||
|
|
@ -808,9 +995,9 @@ describe('SettingsDialog', () => {
|
|||
);
|
||||
|
||||
// Try to navigate when potentially at bounds
|
||||
stdin.write('\u001B[B'); // Down
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string);
|
||||
await wait();
|
||||
stdin.write('\u001B[A'); // Up
|
||||
stdin.write(TerminalKeys.UP_ARROW as string);
|
||||
await wait();
|
||||
|
||||
unmount();
|
||||
|
|
@ -897,7 +1084,7 @@ describe('SettingsDialog', () => {
|
|||
expect(lastFrame()).toContain('Settings'); // Title
|
||||
expect(lastFrame()).toContain('● Vim Mode'); // Active setting
|
||||
expect(lastFrame()).toContain('Apply To'); // Scope section
|
||||
expect(lastFrame()).toContain('1. User Settings'); // Scope options
|
||||
expect(lastFrame()).toContain('User Settings'); // Scope options (no numbers when settings focused)
|
||||
expect(lastFrame()).toContain(
|
||||
'(Use Enter to select, Tab to change focus)',
|
||||
); // Help text
|
||||
|
|
@ -919,19 +1106,19 @@ describe('SettingsDialog', () => {
|
|||
);
|
||||
|
||||
// Toggle first setting (should require restart)
|
||||
stdin.write('\u000D'); // Enter
|
||||
stdin.write(TerminalKeys.ENTER as string); // Enter
|
||||
await wait();
|
||||
|
||||
// Navigate to next setting and toggle it (should not require restart - e.g., vimMode)
|
||||
stdin.write('\u001B[B'); // Down
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string); // Down
|
||||
await wait();
|
||||
stdin.write('\u000D'); // Enter
|
||||
stdin.write(TerminalKeys.ENTER as string); // Enter
|
||||
await wait();
|
||||
|
||||
// Navigate to another setting and toggle it (should also require restart)
|
||||
stdin.write('\u001B[B'); // Down
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string); // Down
|
||||
await wait();
|
||||
stdin.write('\u000D'); // Enter
|
||||
stdin.write(TerminalKeys.ENTER as string); // Enter
|
||||
await wait();
|
||||
|
||||
// The test verifies that all changes are preserved and the dialog still works
|
||||
|
|
@ -950,13 +1137,13 @@ describe('SettingsDialog', () => {
|
|||
);
|
||||
|
||||
// Multiple scope changes
|
||||
stdin.write('\t'); // Tab to scope
|
||||
stdin.write(TerminalKeys.TAB as string); // Tab to scope
|
||||
await wait();
|
||||
stdin.write('2'); // Workspace
|
||||
await wait();
|
||||
stdin.write('\t'); // Tab to settings
|
||||
stdin.write(TerminalKeys.TAB as string); // Tab to settings
|
||||
await wait();
|
||||
stdin.write('\t'); // Tab to scope
|
||||
stdin.write(TerminalKeys.TAB as string); // Tab to scope
|
||||
await wait();
|
||||
stdin.write('1'); // User
|
||||
await wait();
|
||||
|
|
@ -1043,4 +1230,335 @@ describe('SettingsDialog', () => {
|
|||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Snapshot Tests', () => {
|
||||
/**
|
||||
* Snapshot tests for SettingsDialog component using ink-testing-library.
|
||||
* These tests capture the visual output of the component in various states:
|
||||
*
|
||||
* - Default rendering with no custom settings
|
||||
* - Various combinations of boolean settings (enabled/disabled)
|
||||
* - Mixed boolean and number settings configurations
|
||||
* - Different focus states (settings vs scope selector)
|
||||
* - Different scope selections (User, System, Workspace)
|
||||
* - Accessibility settings enabled
|
||||
* - File filtering configurations
|
||||
* - Tools and security settings
|
||||
* - All settings disabled state
|
||||
*
|
||||
* The snapshots help ensure UI consistency and catch unintended visual changes.
|
||||
*/
|
||||
|
||||
it('should render default state correctly', () => {
|
||||
const settings = createMockSettings();
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render with various boolean settings enabled', () => {
|
||||
const settings = createMockSettings({
|
||||
general: {
|
||||
vimMode: true,
|
||||
disableAutoUpdate: true,
|
||||
debugKeystrokeLogging: true,
|
||||
enablePromptCompletion: true,
|
||||
},
|
||||
ui: {
|
||||
hideWindowTitle: true,
|
||||
hideTips: true,
|
||||
showMemoryUsage: true,
|
||||
showLineNumbers: true,
|
||||
showCitations: true,
|
||||
accessibility: {
|
||||
disableLoadingPhrases: true,
|
||||
screenReader: true,
|
||||
},
|
||||
},
|
||||
ide: {
|
||||
enabled: true,
|
||||
},
|
||||
context: {
|
||||
loadMemoryFromIncludeDirectories: true,
|
||||
fileFiltering: {
|
||||
respectGitIgnore: true,
|
||||
respectQwenIgnore: true,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
enableInteractiveShell: true,
|
||||
autoAccept: true,
|
||||
useRipgrep: true,
|
||||
},
|
||||
security: {
|
||||
folderTrust: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render with mixed boolean and number settings', () => {
|
||||
const settings = createMockSettings({
|
||||
general: {
|
||||
vimMode: false,
|
||||
disableAutoUpdate: true,
|
||||
},
|
||||
ui: {
|
||||
showMemoryUsage: true,
|
||||
hideWindowTitle: false,
|
||||
},
|
||||
tools: {
|
||||
truncateToolOutputThreshold: 50000,
|
||||
truncateToolOutputLines: 1000,
|
||||
},
|
||||
context: {
|
||||
discoveryMaxDirs: 500,
|
||||
},
|
||||
model: {
|
||||
maxSessionTurns: 100,
|
||||
skipNextSpeakerCheck: false,
|
||||
},
|
||||
});
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render focused on scope selector', () => {
|
||||
const settings = createMockSettings();
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame, stdin } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
// Switch focus to scope selector with Tab
|
||||
stdin.write('\t');
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render with different scope selected (System)', () => {
|
||||
const settings = createMockSettings(
|
||||
{}, // userSettings
|
||||
{
|
||||
// systemSettings
|
||||
general: {
|
||||
vimMode: true,
|
||||
disableAutoUpdate: false,
|
||||
},
|
||||
ui: {
|
||||
showMemoryUsage: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame, stdin } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
// Switch to scope selector
|
||||
stdin.write('\t');
|
||||
// Navigate to System scope
|
||||
stdin.write('ArrowDown');
|
||||
stdin.write('\r'); // Enter to select
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render with different scope selected (Workspace)', () => {
|
||||
const settings = createMockSettings(
|
||||
{}, // userSettings
|
||||
{}, // systemSettings
|
||||
{
|
||||
// workspaceSettings
|
||||
general: {
|
||||
vimMode: false,
|
||||
debugKeystrokeLogging: true,
|
||||
},
|
||||
tools: {
|
||||
useRipgrep: true,
|
||||
enableInteractiveShell: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame, stdin } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
// Switch to scope selector
|
||||
stdin.write('\t');
|
||||
// Navigate to Workspace scope (down twice)
|
||||
stdin.write('ArrowDown');
|
||||
stdin.write('ArrowDown');
|
||||
stdin.write('\r'); // Enter to select
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render with accessibility settings enabled', () => {
|
||||
const settings = createMockSettings({
|
||||
ui: {
|
||||
accessibility: {
|
||||
disableLoadingPhrases: true,
|
||||
screenReader: true,
|
||||
},
|
||||
showMemoryUsage: true,
|
||||
showLineNumbers: true,
|
||||
},
|
||||
general: {
|
||||
vimMode: true,
|
||||
},
|
||||
});
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render with file filtering settings configured', () => {
|
||||
const settings = createMockSettings({
|
||||
context: {
|
||||
fileFiltering: {
|
||||
respectGitIgnore: false,
|
||||
respectQwemIgnore: true,
|
||||
enableRecursiveFileSearch: false,
|
||||
disableFuzzySearch: true,
|
||||
},
|
||||
loadMemoryFromIncludeDirectories: true,
|
||||
discoveryMaxDirs: 100,
|
||||
},
|
||||
});
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render with tools and security settings', () => {
|
||||
const settings = createMockSettings({
|
||||
tools: {
|
||||
enableInteractiveShell: true,
|
||||
autoAccept: false,
|
||||
useRipgrep: true,
|
||||
truncateToolOutputThreshold: 25000,
|
||||
truncateToolOutputLines: 500,
|
||||
},
|
||||
security: {
|
||||
folderTrust: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
model: {
|
||||
maxSessionTurns: 50,
|
||||
skipNextSpeakerCheck: true,
|
||||
},
|
||||
});
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render with all boolean settings disabled', () => {
|
||||
const settings = createMockSettings({
|
||||
general: {
|
||||
vimMode: false,
|
||||
disableAutoUpdate: false,
|
||||
debugKeystrokeLogging: false,
|
||||
enablePromptCompletion: false,
|
||||
},
|
||||
ui: {
|
||||
hideWindowTitle: false,
|
||||
hideTips: false,
|
||||
showMemoryUsage: false,
|
||||
showLineNumbers: false,
|
||||
showCitations: false,
|
||||
accessibility: {
|
||||
disableLoadingPhrases: false,
|
||||
screenReader: false,
|
||||
},
|
||||
},
|
||||
ide: {
|
||||
enabled: false,
|
||||
},
|
||||
context: {
|
||||
loadMemoryFromIncludeDirectories: false,
|
||||
fileFiltering: {
|
||||
respectGitIgnore: false,
|
||||
respectQwemIgnore: false,
|
||||
enableRecursiveFileSearch: false,
|
||||
disableFuzzySearch: false,
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
enableInteractiveShell: false,
|
||||
autoAccept: false,
|
||||
useRipgrep: false,
|
||||
},
|
||||
security: {
|
||||
folderTrust: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<KeypressProvider kittyProtocolEnabled={false}>
|
||||
<SettingsDialog settings={settings} onSelect={onSelect} />
|
||||
</KeypressProvider>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue