Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ jobs:
npm run test:ci --workspace @google/gemini-cli
else
# Explicitly list non-cli packages to ensure they are sharded correctly
npm run test:ci --workspace @google/gemini-cli-core --workspace @google/gemini-cli-a2a-server --workspace gemini-cli-vscode-ide-companion --workspace @google/gemini-cli-test-utils --if-present
npm run test:ci --workspace @google/gemini-cli-core --workspace @google/gemini-cli-a2a-server --workspace gemini-cli-vscode-ide-companion --workspace @google/gemini-cli-test-utils --if-present -- --coverage.enabled=false
npm run test:scripts
fi

Expand Down
1 change: 0 additions & 1 deletion packages/cli/src/config/policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ describe('resolveWorkspacePolicyState', () => {
setAutoAcceptWorkspacePolicies(originalValue);
}
});

it('should not return workspace policies if cwd is the home directory', async () => {
const policiesDir = path.join(tempDir, '.gemini', 'policies');
fs.mkdirSync(policiesDir, { recursive: true });
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/ui/components/Footer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ describe('<Footer />', () => {
},
);
await waitUntilReady();
expect(lastFrame()).toContain('15%');
expect(lastFrame()).toContain('85%');
expect(normalizeFrame(lastFrame())).toMatchSnapshot();
unmount();
});
Expand Down
18 changes: 14 additions & 4 deletions packages/cli/src/ui/components/QuotaDisplay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,20 @@
*/

import { render } from '../../test-utils/render.js';
import { describe, it, expect } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { QuotaDisplay } from './QuotaDisplay.js';

describe('QuotaDisplay', () => {
beforeEach(() => {
vi.stubEnv('TZ', 'America/Los_Angeles');
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-03-02T20:29:00.000Z'));
});

afterEach(() => {
vi.useRealTimers();
vi.unstubAllEnvs();
});
it('should not render when remaining is undefined', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
<QuotaDisplay remaining={undefined} limit={100} />,
Expand Down Expand Up @@ -36,7 +46,7 @@ describe('QuotaDisplay', () => {
unmount();
});

it('should not render when usage > 20%', async () => {
it('should not render when usage < 80%', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
<QuotaDisplay remaining={85} limit={100} />,
);
Expand All @@ -45,7 +55,7 @@ describe('QuotaDisplay', () => {
unmount();
});

it('should render yellow when usage < 20%', async () => {
it('should render warning when used >= 80%', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
<QuotaDisplay remaining={15} limit={100} />,
);
Expand All @@ -54,7 +64,7 @@ describe('QuotaDisplay', () => {
unmount();
});

it('should render red when usage < 5%', async () => {
it('should render critical when used >= 95%', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
<QuotaDisplay remaining={4} limit={100} />,
);
Expand Down
44 changes: 24 additions & 20 deletions packages/cli/src/ui/components/QuotaDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import type React from 'react';
import { Text } from 'ink';
import {
getStatusColor,
QUOTA_THRESHOLD_HIGH,
QUOTA_THRESHOLD_MEDIUM,
getUsedStatusColor,
QUOTA_USED_WARNING_THRESHOLD,
QUOTA_USED_CRITICAL_THRESHOLD,
} from '../utils/displayUtils.js';
import { formatResetTime } from '../utils/formatters.js';

Expand All @@ -34,32 +34,36 @@ export const QuotaDisplay: React.FC<QuotaDisplayProps> = ({
return null;
}

const percentage = (remaining / limit) * 100;
const usedPercentage = 100 - (remaining / limit) * 100;

if (!forceShow && percentage > QUOTA_THRESHOLD_HIGH) {
if (!forceShow && usedPercentage < QUOTA_USED_WARNING_THRESHOLD) {
return null;
}

const color = getStatusColor(percentage, {
green: QUOTA_THRESHOLD_HIGH,
yellow: QUOTA_THRESHOLD_MEDIUM,
const color = getUsedStatusColor(usedPercentage, {
warning: QUOTA_USED_WARNING_THRESHOLD,
critical: QUOTA_USED_CRITICAL_THRESHOLD,
});

const resetInfo =
!terse && resetTime ? `, ${formatResetTime(resetTime)}` : '';

let text: string;
if (remaining === 0) {
let text = terse
? 'Limit reached'
: `/stats Limit reached${resetInfo}${!terse && '. /auth to continue.'}`;
if (lowercase) text = text.toLowerCase();
return <Text color={color}>{text}</Text>;
const resetMsg = resetTime
? `, resets in ${formatResetTime(resetTime, 'terse')}`
: '';
text = terse ? 'Limit reached' : `Limit reached${resetMsg}`;
} else {
text = terse
? `${usedPercentage.toFixed(0)}%`
: `${usedPercentage.toFixed(0)}% used${
resetTime
? ` (Limit resets in ${formatResetTime(resetTime, 'terse')})`
: ''
}`;
}

let text = terse
? `${percentage.toFixed(0)}%`
: `/stats ${percentage.toFixed(0)}% usage remaining${resetInfo}`;
if (lowercase) text = text.toLowerCase();
if (lowercase) {
text = text.toLowerCase();
}

return <Text color={color}>{text}</Text>;
};
27 changes: 17 additions & 10 deletions packages/cli/src/ui/components/QuotaStatsInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { formatResetTime } from '../utils/formatters.js';
import {
getStatusColor,
QUOTA_THRESHOLD_HIGH,
QUOTA_THRESHOLD_MEDIUM,
getUsedStatusColor,
QUOTA_USED_WARNING_THRESHOLD,
QUOTA_USED_CRITICAL_THRESHOLD,
} from '../utils/displayUtils.js';

interface QuotaStatsInfoProps {
Expand All @@ -31,19 +31,26 @@ export const QuotaStatsInfo: React.FC<QuotaStatsInfoProps> = ({
return null;
}

const percentage = (remaining / limit) * 100;
const color = getStatusColor(percentage, {
green: QUOTA_THRESHOLD_HIGH,
yellow: QUOTA_THRESHOLD_MEDIUM,
const usedPercentage = 100 - (remaining / limit) * 100;
const color = getUsedStatusColor(usedPercentage, {
warning: QUOTA_USED_WARNING_THRESHOLD,
critical: QUOTA_USED_CRITICAL_THRESHOLD,
});

return (
<Box flexDirection="column" marginTop={0} marginBottom={0}>
<Text color={color}>
{remaining === 0
? `Limit reached`
: `${percentage.toFixed(0)}% usage remaining`}
{resetTime && `, ${formatResetTime(resetTime)}`}
? `Limit reached${
resetTime
? `, resets in ${formatResetTime(resetTime, 'terse')}`
: ''
}`
: `${usedPercentage.toFixed(0)}% used${
resetTime
? ` (Limit resets in ${formatResetTime(resetTime, 'terse')})`
: ''
}`}
</Text>
{showDetails && (
<>
Expand Down
22 changes: 15 additions & 7 deletions packages/cli/src/ui/components/StatsDisplay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ const createTestMetrics = (
});

describe('<StatsDisplay />', () => {
beforeEach(() => {
vi.stubEnv('TZ', 'UTC');
});

afterEach(() => {
vi.unstubAllEnvs();
});

it('renders only the Performance section in its zero state', async () => {
const zeroMetrics = createTestMetrics();

Expand Down Expand Up @@ -465,9 +473,9 @@ describe('<StatsDisplay />', () => {
await waitUntilReady();
const output = lastFrame();

expect(output).toContain('Usage remaining');
expect(output).toContain('75.0%');
expect(output).toContain('resets in 1h 30m');
expect(output).toContain('Model usage');
expect(output).toContain('25%');
expect(output).toContain('Usage resets');
expect(output).toMatchSnapshot();

vi.useRealTimers();
Expand Down Expand Up @@ -521,8 +529,8 @@ describe('<StatsDisplay />', () => {
await waitUntilReady();
const output = lastFrame();

// (10 + 700) / (100 + 1000) = 710 / 1100 = 64.5%
expect(output).toContain('65% usage remaining');
// (1 - 710/1100) * 100 = 35.5%
expect(output).toContain('35%');
expect(output).toContain('Usage limit: 1,100');
expect(output).toMatchSnapshot();

Expand Down Expand Up @@ -571,8 +579,8 @@ describe('<StatsDisplay />', () => {

expect(output).toContain('gemini-2.5-flash');
expect(output).toContain('-'); // for requests
expect(output).toContain('50.0%');
expect(output).toContain('resets in 2h');
expect(output).toContain('50%');
expect(output).toContain('Usage resets');
expect(output).toMatchSnapshot();

vi.useRealTimers();
Expand Down
Loading
Loading