Issue#9751 - fix the gemini crash on startup in tmux environments (#11637)

This commit is contained in:
Megha Bansal
2025-11-10 19:57:43 -08:00
committed by GitHub
parent 5ba6bc7139
commit 22b0550520
2 changed files with 107 additions and 32 deletions

View File

@@ -5,50 +5,117 @@
*/
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 * as useTerminalSize from '../hooks/useTerminalSize.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('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 />', () => {
beforeEach(() => {});
beforeEach(() => {
vi.clearAllMocks();
});
it('renders the long logo on a wide terminal', () => {
vi.spyOn(useTerminalSize, 'useTerminalSize').mockReturnValue({
columns: 120,
rows: 20,
});
const { lastFrame, unmount } = render(
<Header version="1.0.0" nightly={false} />,
render(<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', () => {
const customArt = 'CUSTOM ART';
const { lastFrame, unmount } = render(
render(
<Header version="1.0.0" nightly={false} customAsciiArt={customArt} />,
);
expect(lastFrame()).toContain(customArt);
unmount();
expect(Text).toHaveBeenCalledWith(
expect.objectContaining({
children: customArt,
}),
undefined,
);
});
it('displays the version number when nightly is true', () => {
const { lastFrame, unmount } = render(
<Header version="1.0.0" nightly={true} />,
);
expect(lastFrame()).toContain('v1.0.0');
unmount();
render(<Header version="1.0.0" nightly={true} />);
const textCalls = (Text as Mock).mock.calls;
expect(textCalls[1][0].children.join('')).toBe('v1.0.0');
});
it('does not display the version number when nightly is false', () => {
const { lastFrame, unmount } = render(
<Header version="1.0.0" nightly={false} />,
render(<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();
});
});

View File

@@ -18,6 +18,26 @@ interface HeaderProps {
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> = ({
customAsciiArt,
version,
@@ -47,22 +67,10 @@ export const Header: React.FC<HeaderProps> = ({
flexShrink={0}
flexDirection="column"
>
{theme.ui.gradient ? (
<Gradient colors={theme.ui.gradient}>
<Text>{displayTitle}</Text>
</Gradient>
) : (
<Text>{displayTitle}</Text>
)}
<ThemedGradient>{displayTitle}</ThemedGradient>
{nightly && (
<Box width="100%" flexDirection="row" justifyContent="flex-end">
{theme.ui.gradient ? (
<Gradient colors={theme.ui.gradient}>
<Text>v{version}</Text>
</Gradient>
) : (
<Text>v{version}</Text>
)}
<ThemedGradient>v{version}</ThemedGradient>
</Box>
)}
</Box>