From 6481483074583b42568501f571bec4b20ece6452 Mon Sep 17 00:00:00 2001 From: James Player Date: Fri, 25 Jul 2025 15:29:06 +1200 Subject: [PATCH] fix(app/sidebar): Custom logo UI issue [r8s-435] (#939) --- app/react/sidebar/Header.test.tsx | 143 ++++++++++++++++++++++++++++++ app/react/sidebar/Header.tsx | 6 +- 2 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 app/react/sidebar/Header.test.tsx diff --git a/app/react/sidebar/Header.test.tsx b/app/react/sidebar/Header.test.tsx new file mode 100644 index 000000000..e8fc9104a --- /dev/null +++ b/app/react/sidebar/Header.test.tsx @@ -0,0 +1,143 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { withTestRouter } from '@/react/test-utils/withRouter'; + +import { Context } from './useSidebarState'; +import { Header } from './Header'; + +vi.mock('@/react/portainer/feature-flags/feature-flags.service', () => ({ + isBE: false, +})); + +function renderComponent( + props = {}, + sidebarState = { isOpen: true, toggle: vi.fn() } +) { + const Wrapped = withTestRouter(Header); + + return render( + + + + ); +} + +describe('Header', () => { + it('should render with default logo when no custom logo provided', () => { + renderComponent(); + + const logo = screen.getByAltText('Logo'); + expect(logo).toBeInTheDocument(); + expect(logo).toHaveAttribute( + 'src', + expect.stringContaining('portainer_logo-CE.svg') + ); + }); + + it('should render with custom logo when provided', () => { + const customLogo = 'https://example.com/custom-logo.png'; + renderComponent({ logo: customLogo }); + + const logo = screen.getByAltText('Logo'); + expect(logo).toBeInTheDocument(); + expect(logo).toHaveAttribute('src', customLogo); + }); + + it('should show "Powered by" section when sidebar is open and custom logo is provided', () => { + const customLogo = 'https://example.com/custom-logo.png'; + renderComponent({ logo: customLogo }); + + expect(screen.getByText('Powered by')).toBeInTheDocument(); + expect(screen.getByText('portainer community')).toBeInTheDocument(); + }); + + it('should not show "Powered by" section when no custom logo', () => { + renderComponent(); + + expect(screen.queryByText('Powered by')).not.toBeInTheDocument(); + }); + + it('should not show "Powered by" section when sidebar is closed', () => { + const customLogo = 'https://example.com/custom-logo.png'; + renderComponent({ logo: customLogo }, { isOpen: false, toggle: vi.fn() }); + + expect(screen.queryByText('Powered by')).not.toBeInTheDocument(); + }); + + it('should apply flex-wrap class to logo container', () => { + renderComponent(); + + const logoContainer = screen.getByTestId( + 'portainerSidebar-homeImage' + ).parentElement; + expect(logoContainer).toHaveClass('flex-wrap'); + }); + + it('should apply justify-center class when sidebar is closed', () => { + renderComponent({}, { isOpen: false, toggle: vi.fn() }); + + const logoContainer = screen.getByTestId( + 'portainerSidebar-homeImage' + ).parentElement; + expect(logoContainer).toHaveClass('justify-center'); + }); + + it('should not apply justify-center class when sidebar is open', () => { + renderComponent(); + + const logoContainer = screen.getByTestId( + 'portainerSidebar-homeImage' + ).parentElement; + expect(logoContainer).not.toHaveClass('justify-center'); + }); + + it('should toggle sidebar when toggle button is clicked', async () => { + const user = userEvent.setup(); + const mockToggle = vi.fn(); + renderComponent({}, { isOpen: true, toggle: mockToggle }); + + const toggleButton = screen.getByRole('button', { name: 'Toggle Sidebar' }); + await user.click(toggleButton); + + expect(mockToggle).toHaveBeenCalledTimes(1); + }); + + it('should show chevron left icon when sidebar is open', () => { + renderComponent(); + + const toggleButton = screen.getByRole('button', { name: 'Toggle Sidebar' }); + expect(toggleButton).toBeInTheDocument(); + }); + + it('should show chevron right icon when sidebar is closed', () => { + renderComponent({}, { isOpen: false, toggle: vi.fn() }); + + const toggleButton = screen.getByRole('button', { name: 'Toggle Sidebar' }); + expect(toggleButton).toBeInTheDocument(); + }); + + it('should use small logo when sidebar is closed', () => { + renderComponent({}, { isOpen: false, toggle: vi.fn() }); + + const logo = screen.getByAltText('Logo'); + expect(logo).toHaveAttribute( + 'src', + expect.stringContaining('logomark-CE.svg') + ); + }); + + it('should apply max-height class to logo when sidebar is closed', () => { + renderComponent({}, { isOpen: false, toggle: vi.fn() }); + + const logo = screen.getByAltText('Logo'); + expect(logo).toHaveClass('!max-h-[27px]'); + }); + + it('should not apply max-height class to logo when sidebar is open', () => { + renderComponent(); + + const logo = screen.getByAltText('Logo'); + expect(logo).not.toHaveClass('!max-h-[27px]'); + }); +}); diff --git a/app/react/sidebar/Header.tsx b/app/react/sidebar/Header.tsx index b69aa7d3a..4a4a5036c 100644 --- a/app/react/sidebar/Header.tsx +++ b/app/react/sidebar/Header.tsx @@ -21,7 +21,11 @@ export function Header({ logo: customLogo }: Props) { return (
-
+