feat(settings): Add availableTerminalHeight prop and optimize settings dialog height management (#7697)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
Co-authored-by: Arya Gummadi <aryagummadi@google.com>
This commit is contained in:
Ali Al Jufairi
2025-09-18 02:51:46 +09:00
committed by GitHub
parent 79e72a94fe
commit efb57e1cef
5 changed files with 820 additions and 26 deletions

View File

@@ -191,6 +191,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();
@@ -1178,4 +1198,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,
respectGeminiIgnore: true,
enableRecursiveFileSearch: true,
disableFuzzySearch: false,
},
},
tools: {
usePty: 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,
usePty: 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,
respectGeminiIgnore: 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: {
usePty: 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,
respectGeminiIgnore: false,
enableRecursiveFileSearch: false,
disableFuzzySearch: false,
},
},
tools: {
usePty: 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();
});
});
});