Skip to content

Commit 8d082a9

Browse files
authored
Improve code coverage for cli/src/ui/privacy package (#13493)
1 parent 6158267 commit 8d082a9

File tree

4 files changed

+305
-0
lines changed

4 files changed

+305
-0
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { render } from '../../test-utils/render.js';
8+
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
9+
import { CloudFreePrivacyNotice } from './CloudFreePrivacyNotice.js';
10+
import { usePrivacySettings } from '../hooks/usePrivacySettings.js';
11+
import { useKeypress } from '../hooks/useKeypress.js';
12+
import type { Config } from '@google/gemini-cli-core';
13+
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
14+
15+
// Mocks
16+
vi.mock('../hooks/usePrivacySettings.js', () => ({
17+
usePrivacySettings: vi.fn(),
18+
}));
19+
20+
vi.mock('../components/shared/RadioButtonSelect.js', () => ({
21+
RadioButtonSelect: vi.fn(),
22+
}));
23+
24+
vi.mock('../hooks/useKeypress.js', () => ({
25+
useKeypress: vi.fn(),
26+
}));
27+
28+
const mockedUsePrivacySettings = usePrivacySettings as Mock;
29+
const mockedUseKeypress = useKeypress as Mock;
30+
const mockedRadioButtonSelect = RadioButtonSelect as Mock;
31+
32+
describe('CloudFreePrivacyNotice', () => {
33+
const mockConfig = {} as Config;
34+
const onExit = vi.fn();
35+
const updateDataCollectionOptIn = vi.fn();
36+
37+
beforeEach(() => {
38+
vi.resetAllMocks();
39+
mockedUsePrivacySettings.mockReturnValue({
40+
privacyState: {
41+
isLoading: false,
42+
error: undefined,
43+
isFreeTier: true,
44+
dataCollectionOptIn: undefined,
45+
},
46+
updateDataCollectionOptIn,
47+
});
48+
});
49+
50+
const defaultState = {
51+
isLoading: false,
52+
error: undefined,
53+
isFreeTier: true,
54+
dataCollectionOptIn: undefined,
55+
};
56+
57+
it.each([
58+
{
59+
stateName: 'loading state',
60+
mockState: { isLoading: true },
61+
expectedText: 'Loading...',
62+
},
63+
{
64+
stateName: 'error state',
65+
mockState: { error: 'Something went wrong' },
66+
expectedText: 'Error loading Opt-in settings',
67+
},
68+
{
69+
stateName: 'non-free tier state',
70+
mockState: { isFreeTier: false },
71+
expectedText: 'Gemini Code Assist Privacy Notice',
72+
},
73+
{
74+
stateName: 'free tier state',
75+
mockState: { isFreeTier: true },
76+
expectedText: 'Gemini Code Assist for Individuals Privacy Notice',
77+
},
78+
])('renders correctly in $stateName', ({ mockState, expectedText }) => {
79+
mockedUsePrivacySettings.mockReturnValue({
80+
privacyState: { ...defaultState, ...mockState },
81+
updateDataCollectionOptIn,
82+
});
83+
84+
const { lastFrame } = render(
85+
<CloudFreePrivacyNotice config={mockConfig} onExit={onExit} />,
86+
);
87+
88+
expect(lastFrame()).toContain(expectedText);
89+
});
90+
91+
it.each([
92+
{
93+
stateName: 'error state',
94+
mockState: { error: 'Something went wrong' },
95+
shouldExit: true,
96+
},
97+
{
98+
stateName: 'non-free tier state',
99+
mockState: { isFreeTier: false },
100+
shouldExit: true,
101+
},
102+
{
103+
stateName: 'free tier state (no selection)',
104+
mockState: { isFreeTier: true },
105+
shouldExit: false,
106+
},
107+
])(
108+
'exits on Escape in $stateName: $shouldExit',
109+
({ mockState, shouldExit }) => {
110+
mockedUsePrivacySettings.mockReturnValue({
111+
privacyState: { ...defaultState, ...mockState },
112+
updateDataCollectionOptIn,
113+
});
114+
115+
render(<CloudFreePrivacyNotice config={mockConfig} onExit={onExit} />);
116+
117+
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
118+
keypressHandler({ name: 'escape' });
119+
120+
if (shouldExit) {
121+
expect(onExit).toHaveBeenCalled();
122+
} else {
123+
expect(onExit).not.toHaveBeenCalled();
124+
}
125+
},
126+
);
127+
128+
describe('RadioButtonSelect interaction', () => {
129+
it.each([
130+
{ selection: true, label: 'Yes' },
131+
{ selection: false, label: 'No' },
132+
])('calls correct functions on selecting "$label"', ({ selection }) => {
133+
render(<CloudFreePrivacyNotice config={mockConfig} onExit={onExit} />);
134+
135+
const onSelectHandler = mockedRadioButtonSelect.mock.calls[0][0].onSelect;
136+
onSelectHandler(selection);
137+
138+
expect(updateDataCollectionOptIn).toHaveBeenCalledWith(selection);
139+
expect(onExit).toHaveBeenCalled();
140+
});
141+
});
142+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { render } from '../../test-utils/render.js';
8+
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
9+
import { CloudPaidPrivacyNotice } from './CloudPaidPrivacyNotice.js';
10+
import { useKeypress } from '../hooks/useKeypress.js';
11+
12+
// Mocks
13+
vi.mock('../hooks/useKeypress.js', () => ({
14+
useKeypress: vi.fn(),
15+
}));
16+
17+
const mockedUseKeypress = useKeypress as Mock;
18+
19+
describe('CloudPaidPrivacyNotice', () => {
20+
const onExit = vi.fn();
21+
22+
beforeEach(() => {
23+
vi.resetAllMocks();
24+
});
25+
26+
it('renders correctly', () => {
27+
const { lastFrame } = render(<CloudPaidPrivacyNotice onExit={onExit} />);
28+
29+
expect(lastFrame()).toContain('Vertex AI Notice');
30+
expect(lastFrame()).toContain('Service Specific Terms');
31+
expect(lastFrame()).toContain('Press Esc to exit');
32+
});
33+
34+
it('exits on Escape', () => {
35+
render(<CloudPaidPrivacyNotice onExit={onExit} />);
36+
37+
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
38+
keypressHandler({ name: 'escape' });
39+
40+
expect(onExit).toHaveBeenCalled();
41+
});
42+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { render } from '../../test-utils/render.js';
8+
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
9+
import { GeminiPrivacyNotice } from './GeminiPrivacyNotice.js';
10+
import { useKeypress } from '../hooks/useKeypress.js';
11+
12+
// Mocks
13+
vi.mock('../hooks/useKeypress.js', () => ({
14+
useKeypress: vi.fn(),
15+
}));
16+
17+
const mockedUseKeypress = useKeypress as Mock;
18+
19+
describe('GeminiPrivacyNotice', () => {
20+
const onExit = vi.fn();
21+
22+
beforeEach(() => {
23+
vi.resetAllMocks();
24+
});
25+
26+
it('renders correctly', () => {
27+
const { lastFrame } = render(<GeminiPrivacyNotice onExit={onExit} />);
28+
29+
expect(lastFrame()).toContain('Gemini API Key Notice');
30+
expect(lastFrame()).toContain('By using the Gemini API');
31+
expect(lastFrame()).toContain('Press Esc to exit');
32+
});
33+
34+
it('exits on Escape', () => {
35+
render(<GeminiPrivacyNotice onExit={onExit} />);
36+
37+
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
38+
keypressHandler({ name: 'escape' });
39+
40+
expect(onExit).toHaveBeenCalled();
41+
});
42+
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { render } from '../../test-utils/render.js';
8+
import { describe, it, expect, vi, beforeEach } from 'vitest';
9+
import { PrivacyNotice } from './PrivacyNotice.js';
10+
import type {
11+
AuthType,
12+
Config,
13+
ContentGeneratorConfig,
14+
} from '@google/gemini-cli-core';
15+
16+
// Mock child components
17+
vi.mock('./GeminiPrivacyNotice.js', async () => {
18+
const { Text } = await import('ink');
19+
return {
20+
GeminiPrivacyNotice: () => <Text>GeminiPrivacyNotice</Text>,
21+
};
22+
});
23+
24+
vi.mock('./CloudPaidPrivacyNotice.js', async () => {
25+
const { Text } = await import('ink');
26+
return {
27+
CloudPaidPrivacyNotice: () => <Text>CloudPaidPrivacyNotice</Text>,
28+
};
29+
});
30+
31+
vi.mock('./CloudFreePrivacyNotice.js', async () => {
32+
const { Text } = await import('ink');
33+
return {
34+
CloudFreePrivacyNotice: () => <Text>CloudFreePrivacyNotice</Text>,
35+
};
36+
});
37+
38+
describe('PrivacyNotice', () => {
39+
const onExit = vi.fn();
40+
const mockConfig = {
41+
getContentGeneratorConfig: vi.fn(),
42+
} as unknown as Config;
43+
44+
beforeEach(() => {
45+
vi.resetAllMocks();
46+
});
47+
48+
it.each([
49+
{
50+
authType: 'gemini-api-key' as AuthType,
51+
expectedComponent: 'GeminiPrivacyNotice',
52+
},
53+
{
54+
authType: 'vertex-ai' as AuthType,
55+
expectedComponent: 'CloudPaidPrivacyNotice',
56+
},
57+
{
58+
authType: 'oauth-personal' as AuthType,
59+
expectedComponent: 'CloudFreePrivacyNotice',
60+
},
61+
{
62+
authType: 'UNKNOWN' as AuthType,
63+
expectedComponent: 'CloudFreePrivacyNotice',
64+
},
65+
])(
66+
'renders $expectedComponent when authType is $authType',
67+
({ authType, expectedComponent }) => {
68+
vi.mocked(mockConfig.getContentGeneratorConfig).mockReturnValue({
69+
authType,
70+
} as unknown as ContentGeneratorConfig);
71+
72+
const { lastFrame } = render(
73+
<PrivacyNotice config={mockConfig} onExit={onExit} />,
74+
);
75+
76+
expect(lastFrame()).toContain(expectedComponent);
77+
},
78+
);
79+
});

0 commit comments

Comments
 (0)