Improve code coverage for cli/src/ui/privacy package (#13493)

This commit is contained in:
Megha Bansal
2025-11-20 22:34:30 -08:00
committed by GitHub
parent 61582678bf
commit 8d082a904d
4 changed files with 305 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { render } from '../../test-utils/render.js';
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { CloudFreePrivacyNotice } from './CloudFreePrivacyNotice.js';
import { usePrivacySettings } from '../hooks/usePrivacySettings.js';
import { useKeypress } from '../hooks/useKeypress.js';
import type { Config } from '@google/gemini-cli-core';
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
// Mocks
vi.mock('../hooks/usePrivacySettings.js', () => ({
usePrivacySettings: vi.fn(),
}));
vi.mock('../components/shared/RadioButtonSelect.js', () => ({
RadioButtonSelect: vi.fn(),
}));
vi.mock('../hooks/useKeypress.js', () => ({
useKeypress: vi.fn(),
}));
const mockedUsePrivacySettings = usePrivacySettings as Mock;
const mockedUseKeypress = useKeypress as Mock;
const mockedRadioButtonSelect = RadioButtonSelect as Mock;
describe('CloudFreePrivacyNotice', () => {
const mockConfig = {} as Config;
const onExit = vi.fn();
const updateDataCollectionOptIn = vi.fn();
beforeEach(() => {
vi.resetAllMocks();
mockedUsePrivacySettings.mockReturnValue({
privacyState: {
isLoading: false,
error: undefined,
isFreeTier: true,
dataCollectionOptIn: undefined,
},
updateDataCollectionOptIn,
});
});
const defaultState = {
isLoading: false,
error: undefined,
isFreeTier: true,
dataCollectionOptIn: undefined,
};
it.each([
{
stateName: 'loading state',
mockState: { isLoading: true },
expectedText: 'Loading...',
},
{
stateName: 'error state',
mockState: { error: 'Something went wrong' },
expectedText: 'Error loading Opt-in settings',
},
{
stateName: 'non-free tier state',
mockState: { isFreeTier: false },
expectedText: 'Gemini Code Assist Privacy Notice',
},
{
stateName: 'free tier state',
mockState: { isFreeTier: true },
expectedText: 'Gemini Code Assist for Individuals Privacy Notice',
},
])('renders correctly in $stateName', ({ mockState, expectedText }) => {
mockedUsePrivacySettings.mockReturnValue({
privacyState: { ...defaultState, ...mockState },
updateDataCollectionOptIn,
});
const { lastFrame } = render(
<CloudFreePrivacyNotice config={mockConfig} onExit={onExit} />,
);
expect(lastFrame()).toContain(expectedText);
});
it.each([
{
stateName: 'error state',
mockState: { error: 'Something went wrong' },
shouldExit: true,
},
{
stateName: 'non-free tier state',
mockState: { isFreeTier: false },
shouldExit: true,
},
{
stateName: 'free tier state (no selection)',
mockState: { isFreeTier: true },
shouldExit: false,
},
])(
'exits on Escape in $stateName: $shouldExit',
({ mockState, shouldExit }) => {
mockedUsePrivacySettings.mockReturnValue({
privacyState: { ...defaultState, ...mockState },
updateDataCollectionOptIn,
});
render(<CloudFreePrivacyNotice config={mockConfig} onExit={onExit} />);
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
keypressHandler({ name: 'escape' });
if (shouldExit) {
expect(onExit).toHaveBeenCalled();
} else {
expect(onExit).not.toHaveBeenCalled();
}
},
);
describe('RadioButtonSelect interaction', () => {
it.each([
{ selection: true, label: 'Yes' },
{ selection: false, label: 'No' },
])('calls correct functions on selecting "$label"', ({ selection }) => {
render(<CloudFreePrivacyNotice config={mockConfig} onExit={onExit} />);
const onSelectHandler = mockedRadioButtonSelect.mock.calls[0][0].onSelect;
onSelectHandler(selection);
expect(updateDataCollectionOptIn).toHaveBeenCalledWith(selection);
expect(onExit).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,42 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { render } from '../../test-utils/render.js';
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { CloudPaidPrivacyNotice } from './CloudPaidPrivacyNotice.js';
import { useKeypress } from '../hooks/useKeypress.js';
// Mocks
vi.mock('../hooks/useKeypress.js', () => ({
useKeypress: vi.fn(),
}));
const mockedUseKeypress = useKeypress as Mock;
describe('CloudPaidPrivacyNotice', () => {
const onExit = vi.fn();
beforeEach(() => {
vi.resetAllMocks();
});
it('renders correctly', () => {
const { lastFrame } = render(<CloudPaidPrivacyNotice onExit={onExit} />);
expect(lastFrame()).toContain('Vertex AI Notice');
expect(lastFrame()).toContain('Service Specific Terms');
expect(lastFrame()).toContain('Press Esc to exit');
});
it('exits on Escape', () => {
render(<CloudPaidPrivacyNotice onExit={onExit} />);
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
keypressHandler({ name: 'escape' });
expect(onExit).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,42 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { render } from '../../test-utils/render.js';
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { GeminiPrivacyNotice } from './GeminiPrivacyNotice.js';
import { useKeypress } from '../hooks/useKeypress.js';
// Mocks
vi.mock('../hooks/useKeypress.js', () => ({
useKeypress: vi.fn(),
}));
const mockedUseKeypress = useKeypress as Mock;
describe('GeminiPrivacyNotice', () => {
const onExit = vi.fn();
beforeEach(() => {
vi.resetAllMocks();
});
it('renders correctly', () => {
const { lastFrame } = render(<GeminiPrivacyNotice onExit={onExit} />);
expect(lastFrame()).toContain('Gemini API Key Notice');
expect(lastFrame()).toContain('By using the Gemini API');
expect(lastFrame()).toContain('Press Esc to exit');
});
it('exits on Escape', () => {
render(<GeminiPrivacyNotice onExit={onExit} />);
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
keypressHandler({ name: 'escape' });
expect(onExit).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,79 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { render } from '../../test-utils/render.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { PrivacyNotice } from './PrivacyNotice.js';
import type {
AuthType,
Config,
ContentGeneratorConfig,
} from '@google/gemini-cli-core';
// Mock child components
vi.mock('./GeminiPrivacyNotice.js', async () => {
const { Text } = await import('ink');
return {
GeminiPrivacyNotice: () => <Text>GeminiPrivacyNotice</Text>,
};
});
vi.mock('./CloudPaidPrivacyNotice.js', async () => {
const { Text } = await import('ink');
return {
CloudPaidPrivacyNotice: () => <Text>CloudPaidPrivacyNotice</Text>,
};
});
vi.mock('./CloudFreePrivacyNotice.js', async () => {
const { Text } = await import('ink');
return {
CloudFreePrivacyNotice: () => <Text>CloudFreePrivacyNotice</Text>,
};
});
describe('PrivacyNotice', () => {
const onExit = vi.fn();
const mockConfig = {
getContentGeneratorConfig: vi.fn(),
} as unknown as Config;
beforeEach(() => {
vi.resetAllMocks();
});
it.each([
{
authType: 'gemini-api-key' as AuthType,
expectedComponent: 'GeminiPrivacyNotice',
},
{
authType: 'vertex-ai' as AuthType,
expectedComponent: 'CloudPaidPrivacyNotice',
},
{
authType: 'oauth-personal' as AuthType,
expectedComponent: 'CloudFreePrivacyNotice',
},
{
authType: 'UNKNOWN' as AuthType,
expectedComponent: 'CloudFreePrivacyNotice',
},
])(
'renders $expectedComponent when authType is $authType',
({ authType, expectedComponent }) => {
vi.mocked(mockConfig.getContentGeneratorConfig).mockReturnValue({
authType,
} as unknown as ContentGeneratorConfig);
const { lastFrame } = render(
<PrivacyNotice config={mockConfig} onExit={onExit} />,
);
expect(lastFrame()).toContain(expectedComponent);
},
);
});