mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-11 14:40:52 -07:00
241 lines
7.0 KiB
TypeScript
241 lines
7.0 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import {
|
|
computeTerminalTitle,
|
|
type TerminalTitleOptions,
|
|
} from './windowTitle.js';
|
|
import { StreamingState } from '../ui/types.js';
|
|
|
|
describe('computeTerminalTitle', () => {
|
|
afterEach(() => {
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
description: 'idle state title with folder name',
|
|
args: {
|
|
streamingState: StreamingState.Idle,
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName: 'my-project',
|
|
showThoughts: false,
|
|
useDynamicTitle: true,
|
|
} as TerminalTitleOptions,
|
|
expected: '◇ Ready (my-project)',
|
|
},
|
|
{
|
|
description: 'legacy title when useDynamicTitle is false',
|
|
args: {
|
|
streamingState: StreamingState.Responding,
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName: 'my-project',
|
|
showThoughts: true,
|
|
useDynamicTitle: false,
|
|
} as TerminalTitleOptions,
|
|
expected: 'Gemini CLI (my-project)'.padEnd(80, ' '),
|
|
exact: true,
|
|
},
|
|
{
|
|
description:
|
|
'active state title with "Working…" when thoughts are disabled',
|
|
args: {
|
|
streamingState: StreamingState.Responding,
|
|
thoughtSubject: 'Reading files',
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName: 'my-project',
|
|
showThoughts: false,
|
|
useDynamicTitle: true,
|
|
} as TerminalTitleOptions,
|
|
expected: '✦ Working… (my-project)',
|
|
},
|
|
{
|
|
description:
|
|
'active state title with thought subject and suffix when thoughts are short enough',
|
|
args: {
|
|
streamingState: StreamingState.Responding,
|
|
thoughtSubject: 'Short thought',
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName: 'my-project',
|
|
showThoughts: true,
|
|
useDynamicTitle: true,
|
|
} as TerminalTitleOptions,
|
|
expected: '✦ Short thought (my-project)',
|
|
},
|
|
{
|
|
description:
|
|
'fallback active title with suffix if no thought subject is provided even when thoughts are enabled',
|
|
args: {
|
|
streamingState: StreamingState.Responding,
|
|
thoughtSubject: undefined,
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName: 'my-project',
|
|
showThoughts: true,
|
|
useDynamicTitle: true,
|
|
} as TerminalTitleOptions,
|
|
expected: '✦ Working… (my-project)'.padEnd(80, ' '),
|
|
exact: true,
|
|
},
|
|
{
|
|
description: 'action required state when confirming',
|
|
args: {
|
|
streamingState: StreamingState.Idle,
|
|
isConfirming: true,
|
|
isSilentWorking: false,
|
|
folderName: 'my-project',
|
|
showThoughts: false,
|
|
useDynamicTitle: true,
|
|
} as TerminalTitleOptions,
|
|
expected: '✋ Action Required (my-project)',
|
|
},
|
|
{
|
|
description: 'silent working state',
|
|
args: {
|
|
streamingState: StreamingState.Responding,
|
|
isConfirming: false,
|
|
isSilentWorking: true,
|
|
folderName: 'my-project',
|
|
showThoughts: false,
|
|
useDynamicTitle: true,
|
|
} as TerminalTitleOptions,
|
|
expected: '⏲ Working… (my-project)',
|
|
},
|
|
])('should return $description', ({ args, expected, exact }) => {
|
|
const title = computeTerminalTitle(args);
|
|
if (exact) {
|
|
expect(title).toBe(expected);
|
|
} else {
|
|
expect(title).toContain(expected);
|
|
}
|
|
expect(title.length).toBe(80);
|
|
});
|
|
|
|
it('should return active state title with thought subject and NO suffix when thoughts are very long', () => {
|
|
const longThought = 'A'.repeat(70);
|
|
const title = computeTerminalTitle({
|
|
streamingState: StreamingState.Responding,
|
|
thoughtSubject: longThought,
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName: 'my-project',
|
|
showThoughts: true,
|
|
useDynamicTitle: true,
|
|
});
|
|
|
|
expect(title).not.toContain('(my-project)');
|
|
expect(title).toContain('✦ AAAAAAAAAAAAAAAA');
|
|
expect(title.length).toBe(80);
|
|
});
|
|
|
|
it('should truncate long thought subjects when thoughts are enabled', () => {
|
|
const longThought = 'A'.repeat(100);
|
|
const title = computeTerminalTitle({
|
|
streamingState: StreamingState.Responding,
|
|
thoughtSubject: longThought,
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName: 'my-project',
|
|
showThoughts: true,
|
|
useDynamicTitle: true,
|
|
});
|
|
|
|
expect(title.length).toBe(80);
|
|
expect(title).toContain('…');
|
|
expect(title.trimEnd().length).toBe(80);
|
|
});
|
|
|
|
it('should strip control characters from the title', () => {
|
|
const title = computeTerminalTitle({
|
|
streamingState: StreamingState.Responding,
|
|
thoughtSubject: 'BadTitle\x00 With\x07Control\x1BChars',
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName: 'my-project',
|
|
showThoughts: true,
|
|
useDynamicTitle: true,
|
|
});
|
|
|
|
expect(title).toContain('BadTitle WithControlChars');
|
|
expect(title).not.toContain('\x00');
|
|
expect(title).not.toContain('\x07');
|
|
expect(title).not.toContain('\x1B');
|
|
expect(title.length).toBe(80);
|
|
});
|
|
|
|
it('should prioritize CLI_TITLE environment variable over folder name when thoughts are disabled', () => {
|
|
vi.stubEnv('CLI_TITLE', 'EnvOverride');
|
|
|
|
const title = computeTerminalTitle({
|
|
streamingState: StreamingState.Idle,
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName: 'my-project',
|
|
showThoughts: false,
|
|
useDynamicTitle: true,
|
|
});
|
|
|
|
expect(title).toContain('◇ Ready (EnvOverride)');
|
|
expect(title).not.toContain('my-project');
|
|
expect(title.length).toBe(80);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: 'folder name',
|
|
folderName: 'A'.repeat(100),
|
|
expected: '◇ Ready (AAAAA',
|
|
},
|
|
{
|
|
name: 'CLI_TITLE',
|
|
folderName: 'my-project',
|
|
envTitle: 'B'.repeat(100),
|
|
expected: '◇ Ready (BBBBB',
|
|
},
|
|
])(
|
|
'should truncate very long $name to fit within 80 characters',
|
|
({ folderName, envTitle, expected }) => {
|
|
if (envTitle) {
|
|
vi.stubEnv('CLI_TITLE', envTitle);
|
|
}
|
|
|
|
const title = computeTerminalTitle({
|
|
streamingState: StreamingState.Idle,
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName,
|
|
showThoughts: false,
|
|
useDynamicTitle: true,
|
|
});
|
|
|
|
expect(title.length).toBe(80);
|
|
expect(title).toContain(expected);
|
|
expect(title).toContain('…)');
|
|
},
|
|
);
|
|
|
|
it('should truncate long folder name when useDynamicTitle is false', () => {
|
|
const longFolderName = 'C'.repeat(100);
|
|
const title = computeTerminalTitle({
|
|
streamingState: StreamingState.Responding,
|
|
isConfirming: false,
|
|
isSilentWorking: false,
|
|
folderName: longFolderName,
|
|
showThoughts: true,
|
|
useDynamicTitle: false,
|
|
});
|
|
|
|
expect(title.length).toBe(80);
|
|
expect(title).toContain('Gemini CLI (CCCCC');
|
|
expect(title).toContain('…)');
|
|
});
|
|
});
|