2025-08-07 15:55:53 -07:00
|
|
|
/**
|
|
|
|
|
* @license
|
2026-02-09 21:53:10 -05:00
|
|
|
* Copyright 2026 Google LLC
|
2025-08-07 15:55:53 -07:00
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
2026-02-16 13:13:00 -05:00
|
|
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
2026-02-03 17:08:10 -08:00
|
|
|
import { renderWithProviders } from '../../test-utils/render.js';
|
2025-08-07 15:55:53 -07:00
|
|
|
import { Footer } from './Footer.js';
|
2026-02-16 12:54:39 -05:00
|
|
|
import { createMockSettings } from '../../test-utils/settings.js';
|
2025-08-07 15:55:53 -07:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
const customMockSessionStats = {
|
|
|
|
|
sessionId: 'test-session-id',
|
2025-10-17 16:16:12 -07:00
|
|
|
sessionStartTime: new Date(),
|
|
|
|
|
promptCount: 0,
|
2026-02-16 12:54:39 -05:00
|
|
|
lastPromptTokenCount: 150000,
|
2025-10-17 16:16:12 -07:00
|
|
|
metrics: {
|
2026-02-16 12:54:39 -05:00
|
|
|
files: {
|
|
|
|
|
totalLinesAdded: 12,
|
|
|
|
|
totalLinesRemoved: 4,
|
|
|
|
|
},
|
2025-10-17 16:16:12 -07:00
|
|
|
tools: {
|
2026-02-16 12:54:39 -05:00
|
|
|
count: 0,
|
2025-10-17 16:16:12 -07:00
|
|
|
totalCalls: 0,
|
|
|
|
|
totalSuccess: 0,
|
|
|
|
|
totalFail: 0,
|
|
|
|
|
totalDurationMs: 0,
|
|
|
|
|
totalDecisions: {
|
|
|
|
|
accept: 0,
|
|
|
|
|
reject: 0,
|
|
|
|
|
modify: 0,
|
2026-02-16 12:54:39 -05:00
|
|
|
auto_accept: 0,
|
2025-10-17 16:16:12 -07:00
|
|
|
},
|
|
|
|
|
byName: {},
|
2026-02-16 12:54:39 -05:00
|
|
|
latency: { avg: 0, max: 0, min: 0 },
|
2025-10-17 16:16:12 -07:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
models: {
|
|
|
|
|
'gemini-pro': {
|
|
|
|
|
api: {
|
|
|
|
|
totalRequests: 0,
|
|
|
|
|
totalErrors: 0,
|
|
|
|
|
totalLatencyMs: 0,
|
|
|
|
|
},
|
|
|
|
|
tokens: {
|
|
|
|
|
input: 0,
|
|
|
|
|
prompt: 0,
|
|
|
|
|
candidates: 0,
|
|
|
|
|
total: 1500,
|
|
|
|
|
cached: 0,
|
|
|
|
|
thoughts: 0,
|
|
|
|
|
tool: 0,
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-10-17 16:16:12 -07:00
|
|
|
},
|
|
|
|
|
},
|
2025-08-07 15:55:53 -07:00
|
|
|
};
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
const defaultProps = {
|
|
|
|
|
model: 'gemini-pro',
|
|
|
|
|
targetDir: '/long/path/to/some/deeply/nested/directories/to/make/it/long',
|
|
|
|
|
debugMode: false,
|
|
|
|
|
branchName: 'main',
|
|
|
|
|
errorCount: 0,
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-07 15:55:53 -07:00
|
|
|
describe('<Footer />', () => {
|
2026-02-16 12:54:39 -05:00
|
|
|
it('renders the component', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: { sessionStats: customMockSessionStats },
|
|
|
|
|
});
|
2025-08-07 15:55:53 -07:00
|
|
|
expect(lastFrame()).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('path display', () => {
|
2026-02-16 12:54:39 -05:00
|
|
|
it('should display a shortened path on a narrow terminal', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 40,
|
|
|
|
|
uiState: {
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
const output = lastFrame();
|
|
|
|
|
expect(output).toBeDefined();
|
|
|
|
|
expect(output!.length).toBeLessThanOrEqual(120); // 40 width * 3? it depends.
|
2025-08-07 15:55:53 -07:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('should use wide layout at 80 columns', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 80,
|
2026-02-18 16:46:50 -08:00
|
|
|
uiState: {
|
2026-02-16 12:54:39 -05:00
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
const output = lastFrame();
|
|
|
|
|
expect(output).toBeDefined();
|
|
|
|
|
});
|
2025-08-07 15:55:53 -07:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('displays the branch name when provided', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
branchName: defaultProps.branchName,
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toContain(defaultProps.branchName);
|
2025-08-07 15:55:53 -07:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('does not display the branch name when not provided', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: { branchName: undefined, sessionStats: customMockSessionStats },
|
|
|
|
|
});
|
|
|
|
|
expect(lastFrame()).not.toContain('(');
|
2025-10-09 19:27:20 -07:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('displays the model name and context percentage', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
currentModel: 'gemini-pro',
|
|
|
|
|
sessionStats: {
|
|
|
|
|
...customMockSessionStats,
|
|
|
|
|
lastPromptTokenCount: 1000,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
hideContextPercentage: false,
|
2026-02-09 21:53:10 -05:00
|
|
|
},
|
|
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toContain('gemini-pro');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('displays the usage indicator when usage is low', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
quota: {
|
|
|
|
|
stats: { remaining: 15, limit: 100 },
|
|
|
|
|
userTier: 'free',
|
|
|
|
|
proQuotaRequest: null,
|
|
|
|
|
validationRequest: null,
|
|
|
|
|
},
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-09 21:53:10 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
2026-02-09 21:53:10 -05:00
|
|
|
expect(lastFrame()).toContain('15%');
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('hides the usage indicator when usage is not near limit', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
quota: {
|
|
|
|
|
stats: { remaining: 85, limit: 100 },
|
|
|
|
|
userTier: 'free',
|
|
|
|
|
proQuotaRequest: null,
|
|
|
|
|
validationRequest: null,
|
2026-02-09 21:53:10 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-09 21:53:10 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
2026-02-09 21:53:10 -05:00
|
|
|
expect(lastFrame()).not.toContain('Usage remaining');
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('displays "Limit reached" message when remaining is 0', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
quota: {
|
|
|
|
|
stats: { remaining: 0, limit: 100 },
|
|
|
|
|
userTier: 'free',
|
|
|
|
|
proQuotaRequest: null,
|
|
|
|
|
validationRequest: null,
|
2026-02-09 21:53:10 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-09 21:53:10 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
expect(lastFrame()?.toLowerCase()).toContain('limit reached');
|
2026-02-09 21:53:10 -05:00
|
|
|
expect(lastFrame()).toMatchSnapshot();
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('displays the model name and abbreviated context percentage', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 80,
|
|
|
|
|
uiState: {
|
|
|
|
|
currentModel: 'gemini-pro',
|
|
|
|
|
sessionStats: {
|
|
|
|
|
...customMockSessionStats,
|
|
|
|
|
lastPromptTokenCount: 500,
|
|
|
|
|
},
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
hideContextPercentage: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toContain('gemini-pro');
|
2025-08-07 15:55:53 -07:00
|
|
|
});
|
2025-08-14 11:15:48 -07:00
|
|
|
|
|
|
|
|
describe('sandbox and trust info', () => {
|
2026-02-16 13:13:00 -05:00
|
|
|
afterEach(() => {
|
|
|
|
|
vi.unstubAllEnvs();
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('should display untrusted when isTrustedFolder is false', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
isTrustedFolder: false,
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
2025-08-14 11:15:48 -07:00
|
|
|
expect(lastFrame()).toContain('untrusted');
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display custom sandbox info when SANDBOX env is set', () => {
|
|
|
|
|
vi.stubEnv('SANDBOX', 'gemini-test-sandbox');
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
isTrustedFolder: true,
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toContain('test-sandbox');
|
2025-08-14 11:15:48 -07:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('should display macOS Seatbelt info when SANDBOX is sandbox-exec', () => {
|
2025-08-14 11:15:48 -07:00
|
|
|
vi.stubEnv('SANDBOX', 'sandbox-exec');
|
|
|
|
|
vi.stubEnv('SEATBELT_PROFILE', 'test-profile');
|
2026-02-16 12:54:39 -05:00
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
isTrustedFolder: true,
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toContain('macOS Seatbelt');
|
|
|
|
|
expect(lastFrame()).toContain('test-profile');
|
2025-08-14 11:15:48 -07:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('should display "no sandbox" when SANDBOX is not set and folder is trusted', () => {
|
2025-08-14 11:15:48 -07:00
|
|
|
vi.stubEnv('SANDBOX', '');
|
2026-02-16 12:54:39 -05:00
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
isTrustedFolder: true,
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
2025-08-14 11:15:48 -07:00
|
|
|
expect(lastFrame()).toContain('no sandbox');
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('should prioritize untrusted message over sandbox info', () => {
|
|
|
|
|
vi.stubEnv('SANDBOX', 'gemini-test-sandbox');
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
isTrustedFolder: false,
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
2025-08-14 11:15:48 -07:00
|
|
|
expect(lastFrame()).toContain('untrusted');
|
2026-02-16 12:54:39 -05:00
|
|
|
expect(lastFrame()).not.toContain('test-sandbox');
|
2025-08-14 11:15:48 -07:00
|
|
|
});
|
|
|
|
|
});
|
2025-09-11 20:16:09 -04:00
|
|
|
|
|
|
|
|
describe('footer configuration filtering (golden snapshots)', () => {
|
2026-02-16 12:54:39 -05:00
|
|
|
it('renders complete footer with all sections visible (baseline)', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
currentModel: 'gemini-pro',
|
|
|
|
|
sessionStats: {
|
|
|
|
|
...customMockSessionStats,
|
|
|
|
|
lastPromptTokenCount: 0,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: [
|
|
|
|
|
'cwd',
|
|
|
|
|
'sandbox-status',
|
|
|
|
|
'model-name',
|
|
|
|
|
'context-remaining',
|
|
|
|
|
],
|
|
|
|
|
hideContextPercentage: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot('complete-footer-wide');
|
2026-01-21 09:58:23 -08:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('renders footer with all optional sections hidden (minimal footer)', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: { sessionStats: customMockSessionStats },
|
|
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: [],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot('footer-minimal');
|
2026-01-21 09:58:23 -08:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('renders footer with only model info hidden (partial filtering)', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: ['cwd', 'sandbox-status'],
|
2025-10-09 19:27:20 -07:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
2025-09-11 20:16:09 -04:00
|
|
|
expect(lastFrame()).toMatchSnapshot('footer-no-model');
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders footer with CWD and model info hidden to test alignment (only sandbox visible)', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: { sessionStats: customMockSessionStats },
|
|
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: ['sandbox-status'],
|
2025-10-09 19:27:20 -07:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
2025-09-11 20:16:09 -04:00
|
|
|
expect(lastFrame()).toMatchSnapshot('footer-only-sandbox');
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('hides the context percentage when hideContextPercentage is true', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
currentModel: 'gemini-pro',
|
|
|
|
|
sessionStats: {
|
|
|
|
|
...customMockSessionStats,
|
|
|
|
|
lastPromptTokenCount: 1000,
|
|
|
|
|
},
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: ['model-name', 'context-remaining'],
|
|
|
|
|
hideContextPercentage: true,
|
2025-10-30 19:18:25 -04:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
expect(lastFrame()).not.toContain('left');
|
2025-09-11 20:16:09 -04:00
|
|
|
});
|
2026-02-27 14:15:10 -05:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('shows the context percentage when hideContextPercentage is false', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
currentModel: 'gemini-pro',
|
|
|
|
|
sessionStats: {
|
|
|
|
|
...customMockSessionStats,
|
|
|
|
|
lastPromptTokenCount: 1000,
|
2026-02-27 14:15:10 -05:00
|
|
|
},
|
|
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: ['model-name', 'context-remaining'],
|
|
|
|
|
hideContextPercentage: false,
|
|
|
|
|
},
|
2026-02-27 14:15:10 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toContain('left');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders complete footer in narrow terminal (baseline narrow)', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 80,
|
|
|
|
|
uiState: {
|
|
|
|
|
currentModel: 'gemini-pro',
|
|
|
|
|
sessionStats: {
|
|
|
|
|
...customMockSessionStats,
|
|
|
|
|
lastPromptTokenCount: 0,
|
2026-02-27 14:15:10 -05:00
|
|
|
},
|
|
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: [
|
|
|
|
|
'cwd',
|
|
|
|
|
'sandbox-status',
|
|
|
|
|
'model-name',
|
|
|
|
|
'context-remaining',
|
|
|
|
|
],
|
|
|
|
|
hideContextPercentage: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toMatchSnapshot('complete-footer-narrow');
|
2026-02-27 14:15:10 -05:00
|
|
|
});
|
|
|
|
|
});
|
2025-10-27 15:33:12 -07:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
describe('fallback mode display', () => {
|
|
|
|
|
it('should display Flash model when in fallback mode, not the configured Pro model', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
2026-02-18 16:46:50 -08:00
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
2026-02-16 12:54:39 -05:00
|
|
|
currentModel: 'gemini-1.5-flash',
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toContain('gemini-1.5-flash');
|
|
|
|
|
});
|
2025-10-27 15:33:12 -07:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('should display Pro model when NOT in fallback mode', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
2026-02-18 16:46:50 -08:00
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
2026-02-16 12:54:39 -05:00
|
|
|
currentModel: 'gemini-pro',
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-18 16:46:50 -08:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
});
|
|
|
|
|
expect(lastFrame()).toContain('gemini-pro');
|
|
|
|
|
});
|
2025-10-27 15:33:12 -07:00
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
describe('Footer Token Formatting', () => {
|
|
|
|
|
const renderWithTokens = (tokens: number) =>
|
|
|
|
|
renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
sessionStats: {
|
|
|
|
|
...customMockSessionStats,
|
|
|
|
|
metrics: {
|
|
|
|
|
...customMockSessionStats.metrics,
|
|
|
|
|
models: {
|
|
|
|
|
'gemini-pro': {
|
|
|
|
|
api: {
|
|
|
|
|
totalRequests: 0,
|
|
|
|
|
totalErrors: 0,
|
|
|
|
|
totalLatencyMs: 0,
|
|
|
|
|
},
|
|
|
|
|
tokens: {
|
|
|
|
|
input: 0,
|
|
|
|
|
prompt: 0,
|
|
|
|
|
candidates: 0,
|
|
|
|
|
total: tokens,
|
|
|
|
|
cached: 0,
|
|
|
|
|
thoughts: 0,
|
|
|
|
|
tool: 0,
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-02-13 09:21:48 -05:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
},
|
|
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: ['token-count'],
|
2026-02-13 09:26:46 -05:00
|
|
|
},
|
2026-02-13 09:21:48 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
}),
|
|
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('formats thousands with k', () => {
|
|
|
|
|
const { lastFrame } = renderWithTokens(1500);
|
|
|
|
|
expect(lastFrame()).toContain('1.5k tokens');
|
2026-02-13 09:21:48 -05:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('formats millions with m', () => {
|
|
|
|
|
const { lastFrame } = renderWithTokens(1500000);
|
|
|
|
|
expect(lastFrame()).toContain('1.5m tokens');
|
|
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('formats billions with b', () => {
|
|
|
|
|
const { lastFrame } = renderWithTokens(1500000000);
|
|
|
|
|
expect(lastFrame()).toContain('1.5b tokens');
|
|
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('formats small numbers without suffix', () => {
|
|
|
|
|
const { lastFrame } = renderWithTokens(500);
|
|
|
|
|
expect(lastFrame()).toContain('500 tokens');
|
|
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
describe('Footer Custom Items', () => {
|
|
|
|
|
it('renders items in the specified order', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
currentModel: 'gemini-pro',
|
|
|
|
|
sessionStats: customMockSessionStats,
|
2026-02-13 09:21:48 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: ['model-name', 'cwd'],
|
|
|
|
|
},
|
2026-02-13 09:21:48 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
}),
|
|
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
const output = lastFrame();
|
|
|
|
|
const modelIdx = output!.indexOf('/model');
|
|
|
|
|
const cwdIdx = output!.indexOf('Path');
|
|
|
|
|
expect(modelIdx).toBeLessThan(cwdIdx);
|
|
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('renders multiple items with proper alignment', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
sessionStats: customMockSessionStats,
|
|
|
|
|
branchName: 'main',
|
2026-02-13 09:21:48 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
settings: createMockSettings({
|
|
|
|
|
vimMode: {
|
|
|
|
|
vimMode: true,
|
2026-02-13 09:21:48 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
ui: {
|
|
|
|
|
footer: {
|
2026-02-16 13:13:00 -05:00
|
|
|
items: ['cwd', 'git-branch', 'sandbox-status', 'model-name'],
|
2026-02-16 12:54:39 -05:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const output = lastFrame();
|
|
|
|
|
expect(output).toBeDefined();
|
|
|
|
|
// Headers should be present
|
|
|
|
|
expect(output).toContain('Path');
|
|
|
|
|
expect(output).toContain('Branch');
|
|
|
|
|
expect(output).toContain('/docs');
|
|
|
|
|
expect(output).toContain('/model');
|
|
|
|
|
// Data should be present
|
|
|
|
|
expect(output).toContain('main*');
|
|
|
|
|
expect(output).toContain('gemini-pro');
|
2026-02-13 09:21:48 -05:00
|
|
|
});
|
|
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('handles empty items array', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: { sessionStats: customMockSessionStats },
|
|
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: [],
|
|
|
|
|
},
|
2026-02-13 09:21:48 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
}),
|
|
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
const output = lastFrame();
|
|
|
|
|
expect(output).toBeDefined();
|
|
|
|
|
expect(output!.trim()).toBe('');
|
|
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
it('does not render items that are conditionally hidden', () => {
|
|
|
|
|
const { lastFrame } = renderWithProviders(<Footer />, {
|
|
|
|
|
width: 120,
|
|
|
|
|
uiState: {
|
|
|
|
|
sessionStats: customMockSessionStats,
|
|
|
|
|
branchName: undefined, // No branch
|
2026-02-13 09:21:48 -05:00
|
|
|
},
|
2026-02-16 12:54:39 -05:00
|
|
|
settings: createMockSettings({
|
|
|
|
|
ui: {
|
|
|
|
|
footer: {
|
|
|
|
|
items: ['cwd', 'git-branch', 'model-name'],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
|
2026-02-16 12:54:39 -05:00
|
|
|
const output = lastFrame();
|
|
|
|
|
expect(output).toBeDefined();
|
|
|
|
|
expect(output).not.toContain('Branch');
|
|
|
|
|
expect(output).toContain('Path');
|
|
|
|
|
expect(output).toContain('/model');
|
|
|
|
|
});
|
2026-02-13 09:21:48 -05:00
|
|
|
});
|
|
|
|
|
});
|