This commit is contained in:
A.K.M. Adib
2026-01-20 11:19:52 -05:00
parent 1182168bd9
commit 275232b1be
6 changed files with 152 additions and 39 deletions

View File

@@ -200,4 +200,83 @@ describe('<AppHeader />', () => {
expect(lastFrame()).not.toContain('First line\\nSecond line');
unmount();
});
it('should render Tips when tipsShown is false', () => {
persistentStateMock.get.mockImplementation((key) => {
if (key === 'tipsShown') return false;
return {};
});
const mockConfig = makeFakeConfig();
const uiState = {
history: [],
bannerData: {
defaultText: 'First line\\nSecond line',
warningText: '',
},
bannerVisible: true,
};
const { lastFrame, unmount } = renderWithProviders(
<AppHeader version="1.0.0" />,
{ config: mockConfig, uiState },
);
expect(lastFrame()).toContain('Tips');
expect(persistentStateMock.set).toHaveBeenCalledWith('tipsShown', true);
unmount();
});
it('should NOT render Tips when tipsShown is true', () => {
persistentStateMock.get.mockImplementation((key) => {
if (key === 'tipsShown') return true;
return {};
});
const mockConfig = makeFakeConfig();
const { lastFrame, unmount } = renderWithProviders(
<AppHeader version="1.0.0" />,
{ config: mockConfig },
);
expect(lastFrame()).not.toContain('Tips');
unmount();
});
it('should show tips on the first run and hide them on the second run (persistence flow)', () => {
const fakeStore: Record<string, boolean> = {};
persistentStateMock.get.mockImplementation((key) => fakeStore[key]);
persistentStateMock.set.mockImplementation((key, val) => {
fakeStore[key] = val;
});
const mockConfig = makeFakeConfig();
const uiState = {
history: [],
bannerData: {
defaultText: 'First line\\nSecond line',
warningText: '',
},
bannerVisible: true,
};
const session1 = renderWithProviders(<AppHeader version="1.0.0" />, {
config: mockConfig,
uiState,
});
expect(session1.lastFrame()).toContain('Tips');
expect(fakeStore['tipsShown']).toBe(true);
session1.unmount();
const session2 = renderWithProviders(<AppHeader version="1.0.0" />, {
config: mockConfig,
});
expect(session2.lastFrame()).not.toContain('Tips');
session2.unmount();
});
});

View File

@@ -12,6 +12,7 @@ import { useConfig } from '../contexts/ConfigContext.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { Banner } from './Banner.js';
import { useBanner } from '../hooks/useBanner.js';
import { useTips } from '../hooks/useTips.js';
interface AppHeaderProps {
version: string;
@@ -23,6 +24,7 @@ export const AppHeader = ({ version }: AppHeaderProps) => {
const { nightly, mainAreaWidth, bannerData, bannerVisible } = useUIState();
const { bannerText } = useBanner(bannerData, config);
const tipsShown = useTips();
return (
<Box flexDirection="column">
@@ -38,9 +40,8 @@ export const AppHeader = ({ version }: AppHeaderProps) => {
)}
</>
)}
{!(settings.merged.ui.hideTips || config.getScreenReader()) && (
<Tips config={config} />
)}
{!(settings.merged.ui.hideTips || config.getScreenReader()) &&
!tipsShown && <Tips config={config} />}
</Box>
);
};

View File

@@ -10,12 +10,7 @@ exports[`<AppHeader /> > should not render the banner when no flags are set 1`]
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information."
"
`;
exports[`<AppHeader /> > should not render the banner when previewFeatures is enabled 1`] = `
@@ -28,12 +23,7 @@ exports[`<AppHeader /> > should not render the banner when previewFeatures is en
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information."
"
`;
exports[`<AppHeader /> > should not render the default banner if shown count is 5 or more 1`] = `
@@ -46,12 +36,7 @@ exports[`<AppHeader /> > should not render the default banner if shown count is
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
Tips for getting started:
1. Ask questions, edit files, or run commands.
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information."
"
`;
exports[`<AppHeader /> > should render the banner when previewFeatures is disabled 1`] = `
@@ -67,12 +52,7 @@ exports[`<AppHeader /> > should render the banner when previewFeatures is disabl
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ This is the default banner │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Tips for getting started:
1. Ask questions, edit files, or run commands.
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information."
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<AppHeader /> > should render the banner with default text 1`] = `
@@ -88,12 +68,7 @@ exports[`<AppHeader /> > should render the banner with default text 1`] = `
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ This is the default banner │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Tips for getting started:
1. Ask questions, edit files, or run commands.
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information."
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<AppHeader /> > should render the banner with warning text 1`] = `
@@ -109,10 +84,5 @@ exports[`<AppHeader /> > should render the banner with warning text 1`] = `
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ There are capacity issues │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Tips for getting started:
1. Ask questions, edit files, or run commands.
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information."
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;

View File

@@ -0,0 +1,42 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { renderHookWithProviders } from '../../test-utils/render.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useTips } from './useTips.js';
import { persistentState } from '../../utils/persistentState.js';
vi.mock('../../utils/persistentState.js', () => ({
persistentState: {
get: vi.fn(),
set: vi.fn(),
},
}));
describe('useTips()', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should return false and call set(true) if state is undefined', () => {
vi.mocked(persistentState.get).mockReturnValue(undefined);
const { result } = renderHookWithProviders(() => useTips());
expect(result.current).toBe(false);
expect(persistentState.set).toHaveBeenCalledWith('tipsShown', true);
});
it('should return true if state is already true', () => {
vi.mocked(persistentState.get).mockReturnValue(true);
const { result } = renderHookWithProviders(() => useTips());
expect(result.current).toBe(true);
expect(persistentState.set).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,20 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { useEffect, useState } from 'react';
import { persistentState } from '../../utils/persistentState.js';
export function useTips() {
const [tipsShown] = useState(() => !!persistentState.get('tipsShown'));
useEffect(() => {
if (!tipsShown) {
persistentState.set('tipsShown', true);
}
}, [tipsShown]);
return tipsShown;
}

View File

@@ -12,6 +12,7 @@ const STATE_FILENAME = 'state.json';
interface PersistentStateData {
defaultBannerShownCount?: Record<string, number>;
tipsShown?: boolean;
// Add other persistent state keys here as needed
}