diff --git a/packages/cli/src/ui/components/Header.test.tsx b/packages/cli/src/ui/components/Header.test.tsx index f3f10382e2..8b4aa6b674 100644 --- a/packages/cli/src/ui/components/Header.test.tsx +++ b/packages/cli/src/ui/components/Header.test.tsx @@ -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('ink'); + return { + ...originalInk, + Text: vi.fn(originalInk.Text), + }; +}); describe('
', () => { - 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( -
, + render(
); + 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(
, ); - 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( -
, - ); - expect(lastFrame()).toContain('v1.0.0'); - unmount(); + render(
); + 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( -
, + render(
); + 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(
); + 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(
); + 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(
); + expect(Gradient.default).toHaveBeenCalledWith( + expect.objectContaining({ + colors: gradientColors, + }), + undefined, ); - expect(lastFrame()).not.toContain('v1.0.0'); - unmount(); }); }); diff --git a/packages/cli/src/ui/components/Header.tsx b/packages/cli/src/ui/components/Header.tsx index 694752312f..51e9cd8c71 100644 --- a/packages/cli/src/ui/components/Header.tsx +++ b/packages/cli/src/ui/components/Header.tsx @@ -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 ( + + {children} + + ); + } + + if (gradient && gradient.length === 1) { + return {children}; + } + + return {children}; +}; + export const Header: React.FC = ({ customAsciiArt, version, @@ -47,22 +67,10 @@ export const Header: React.FC = ({ flexShrink={0} flexDirection="column" > - {theme.ui.gradient ? ( - - {displayTitle} - - ) : ( - {displayTitle} - )} + {displayTitle} {nightly && ( - {theme.ui.gradient ? ( - - v{version} - - ) : ( - v{version} - )} + v{version} )}