mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
Issue#9751 - fix the gemini crash on startup in tmux environments (#11637)
This commit is contained in:
@@ -5,50 +5,117 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { render } from '../../test-utils/render.js';
|
import { render } from '../../test-utils/render.js';
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||||
import { Header } from './Header.js';
|
import { Header } from './Header.js';
|
||||||
import * as useTerminalSize from '../hooks/useTerminalSize.js';
|
import * as useTerminalSize from '../hooks/useTerminalSize.js';
|
||||||
import { longAsciiLogo } from './AsciiArt.js';
|
import { longAsciiLogo } from './AsciiArt.js';
|
||||||
|
import * as semanticColors from '../semantic-colors.js';
|
||||||
|
import { Text } from 'ink';
|
||||||
|
import type React from 'react';
|
||||||
|
|
||||||
vi.mock('../hooks/useTerminalSize.js');
|
vi.mock('../hooks/useTerminalSize.js');
|
||||||
|
vi.mock('ink-gradient', () => {
|
||||||
|
const MockGradient = ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<>{children}</>
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
default: vi.fn(MockGradient),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
vi.mock('../semantic-colors.js');
|
||||||
|
vi.mock('ink', async () => {
|
||||||
|
const originalInk = await vi.importActual<typeof import('ink')>('ink');
|
||||||
|
return {
|
||||||
|
...originalInk,
|
||||||
|
Text: vi.fn(originalInk.Text),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('<Header />', () => {
|
describe('<Header />', () => {
|
||||||
beforeEach(() => {});
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders the long logo on a wide terminal', () => {
|
it('renders the long logo on a wide terminal', () => {
|
||||||
vi.spyOn(useTerminalSize, 'useTerminalSize').mockReturnValue({
|
vi.spyOn(useTerminalSize, 'useTerminalSize').mockReturnValue({
|
||||||
columns: 120,
|
columns: 120,
|
||||||
rows: 20,
|
rows: 20,
|
||||||
});
|
});
|
||||||
const { lastFrame, unmount } = render(
|
render(<Header version="1.0.0" nightly={false} />);
|
||||||
<Header version="1.0.0" nightly={false} />,
|
expect(Text).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
children: longAsciiLogo,
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toContain(longAsciiLogo);
|
|
||||||
unmount();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders custom ASCII art when provided', () => {
|
it('renders custom ASCII art when provided', () => {
|
||||||
const customArt = 'CUSTOM ART';
|
const customArt = 'CUSTOM ART';
|
||||||
const { lastFrame, unmount } = render(
|
render(
|
||||||
<Header version="1.0.0" nightly={false} customAsciiArt={customArt} />,
|
<Header version="1.0.0" nightly={false} customAsciiArt={customArt} />,
|
||||||
);
|
);
|
||||||
expect(lastFrame()).toContain(customArt);
|
expect(Text).toHaveBeenCalledWith(
|
||||||
unmount();
|
expect.objectContaining({
|
||||||
|
children: customArt,
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays the version number when nightly is true', () => {
|
it('displays the version number when nightly is true', () => {
|
||||||
const { lastFrame, unmount } = render(
|
render(<Header version="1.0.0" nightly={true} />);
|
||||||
<Header version="1.0.0" nightly={true} />,
|
const textCalls = (Text as Mock).mock.calls;
|
||||||
);
|
expect(textCalls[1][0].children.join('')).toBe('v1.0.0');
|
||||||
expect(lastFrame()).toContain('v1.0.0');
|
|
||||||
unmount();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not display the version number when nightly is false', () => {
|
it('does not display the version number when nightly is false', () => {
|
||||||
const { lastFrame, unmount } = render(
|
render(<Header version="1.0.0" nightly={false} />);
|
||||||
<Header version="1.0.0" nightly={false} />,
|
expect(Text).not.toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
children: 'v1.0.0',
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with no gradient when theme.ui.gradient is undefined', async () => {
|
||||||
|
vi.spyOn(semanticColors, 'theme', 'get').mockReturnValue({
|
||||||
|
ui: { gradient: undefined },
|
||||||
|
} as typeof semanticColors.theme);
|
||||||
|
const Gradient = await import('ink-gradient');
|
||||||
|
render(<Header version="1.0.0" nightly={false} />);
|
||||||
|
expect(Gradient.default).not.toHaveBeenCalled();
|
||||||
|
const textCalls = (Text as Mock).mock.calls;
|
||||||
|
expect(textCalls[0][0]).not.toHaveProperty('color');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with a single color when theme.ui.gradient has one color', async () => {
|
||||||
|
const singleColor = '#FF0000';
|
||||||
|
vi.spyOn(semanticColors, 'theme', 'get').mockReturnValue({
|
||||||
|
ui: { gradient: [singleColor] },
|
||||||
|
} as typeof semanticColors.theme);
|
||||||
|
const Gradient = await import('ink-gradient');
|
||||||
|
render(<Header version="1.0.0" nightly={false} />);
|
||||||
|
expect(Gradient.default).not.toHaveBeenCalled();
|
||||||
|
const textCalls = (Text as Mock).mock.calls;
|
||||||
|
console.log(JSON.stringify(textCalls, null, 2));
|
||||||
|
expect(textCalls.length).toBe(1);
|
||||||
|
expect(textCalls[0][0]).toHaveProperty('color', singleColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with a gradient when theme.ui.gradient has two or more colors', async () => {
|
||||||
|
const gradientColors = ['#FF0000', '#00FF00'];
|
||||||
|
vi.spyOn(semanticColors, 'theme', 'get').mockReturnValue({
|
||||||
|
ui: { gradient: gradientColors },
|
||||||
|
} as typeof semanticColors.theme);
|
||||||
|
const Gradient = await import('ink-gradient');
|
||||||
|
render(<Header version="1.0.0" nightly={false} />);
|
||||||
|
expect(Gradient.default).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
colors: gradientColors,
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
);
|
);
|
||||||
expect(lastFrame()).not.toContain('v1.0.0');
|
|
||||||
unmount();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,6 +18,26 @@ interface HeaderProps {
|
|||||||
nightly: boolean;
|
nightly: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ThemedGradient: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const gradient = theme.ui.gradient;
|
||||||
|
|
||||||
|
if (gradient && gradient.length >= 2) {
|
||||||
|
return (
|
||||||
|
<Gradient colors={gradient}>
|
||||||
|
<Text>{children}</Text>
|
||||||
|
</Gradient>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gradient && gradient.length === 1) {
|
||||||
|
return <Text color={gradient[0]}>{children}</Text>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Text>{children}</Text>;
|
||||||
|
};
|
||||||
|
|
||||||
export const Header: React.FC<HeaderProps> = ({
|
export const Header: React.FC<HeaderProps> = ({
|
||||||
customAsciiArt,
|
customAsciiArt,
|
||||||
version,
|
version,
|
||||||
@@ -47,22 +67,10 @@ export const Header: React.FC<HeaderProps> = ({
|
|||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
>
|
>
|
||||||
{theme.ui.gradient ? (
|
<ThemedGradient>{displayTitle}</ThemedGradient>
|
||||||
<Gradient colors={theme.ui.gradient}>
|
|
||||||
<Text>{displayTitle}</Text>
|
|
||||||
</Gradient>
|
|
||||||
) : (
|
|
||||||
<Text>{displayTitle}</Text>
|
|
||||||
)}
|
|
||||||
{nightly && (
|
{nightly && (
|
||||||
<Box width="100%" flexDirection="row" justifyContent="flex-end">
|
<Box width="100%" flexDirection="row" justifyContent="flex-end">
|
||||||
{theme.ui.gradient ? (
|
<ThemedGradient>v{version}</ThemedGradient>
|
||||||
<Gradient colors={theme.ui.gradient}>
|
|
||||||
<Text>v{version}</Text>
|
|
||||||
</Gradient>
|
|
||||||
) : (
|
|
||||||
<Text>v{version}</Text>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user