import { ReactNode } from 'react'; import { render, screen } from '@testing-library/react'; import { vi } from 'vitest'; import { EnvironmentType } from '@/react/portainer/environments/types'; import { useEnvironment } from '@/react/portainer/environments/queries'; import { withTestQueryProvider } from '@/react/test-utils/withTestQuery'; import { useDockerStorageUsageQuery } from '../queries/useDockerStorageUsageQuery'; import { DockerStorageInfo } from './DockerStorageInfo'; vi.mock('../queries/useDockerStorageUsageQuery', () => ({ useDockerStorageUsageQuery: vi.fn(), })); vi.mock('@/react/portainer/environments/queries', () => ({ useEnvironment: vi.fn(), })); // Tippy renders tooltip content in a portal which is not accessible without // user interaction in jsdom. Mock Tooltip to render its message inline so // tests can assert on the message text. vi.mock('@@/Tip/Tooltip/Tooltip', () => ({ Tooltip: ({ message }: { message: ReactNode }) => ( {message} ), })); const Wrapped = withTestQueryProvider(() => ( )); function mockEnvironmentLoading() { vi.mocked(useEnvironment).mockReturnValue({ isLoading: true, isFetched: false, data: undefined, isError: false, } as ReturnType); } function mockEnvironmentReady( agentVersion: string | undefined = '2.42.0', envType = EnvironmentType.AgentOnDocker ) { vi.mocked(useEnvironment).mockReturnValue({ isLoading: false, isFetched: true, isError: false, data: { Type: envType, Agent: { Version: agentVersion }, }, } as ReturnType); } function mockEnvironmentNoData() { vi.mocked(useEnvironment).mockReturnValue({ isLoading: false, isFetched: true, isError: false, data: undefined, } as ReturnType); } function mockStorageLoading() { vi.mocked(useDockerStorageUsageQuery).mockReturnValue({ isLoading: true, isFetched: false, isError: false, data: undefined, } as ReturnType); } function mockStorageIdle() { vi.mocked(useDockerStorageUsageQuery).mockReturnValue({ isLoading: false, isFetched: false, isError: false, data: undefined, } as ReturnType); } function mockStorageError(message = 'connection refused') { vi.mocked(useDockerStorageUsageQuery).mockReturnValue({ isLoading: false, isFetched: true, isError: true, error: new Error(message), data: undefined, } as ReturnType); } function mockStorageNoData() { vi.mocked(useDockerStorageUsageQuery).mockReturnValue({ isLoading: false, isFetched: true, isError: false, data: undefined, } as ReturnType); } const MOCK_STORAGE = { rootDir: '/var/lib/docker', totalBytes: 500_000_000, dockerBytes: 100_000_000, availableBytes: 300_000_000, imageBytes: 60_000_000, containerBytes: 20_000_000, volumeBytes: 15_000_000, buildCacheBytes: 5_000_000, }; function mockStorageSuccess(data = MOCK_STORAGE) { vi.mocked(useDockerStorageUsageQuery).mockReturnValue({ isLoading: false, isFetched: true, isError: false, data, } as ReturnType); } afterEach(() => { vi.clearAllMocks(); }); describe('DockerStorageInfo', () => { describe('loading state', () => { it('shows loading text while environment is loading', () => { mockEnvironmentLoading(); mockStorageIdle(); render(); expect(screen.getByText('Loading storage information...')).toBeVisible(); }); it('shows loading text while storage query is loading', () => { mockEnvironmentReady(); mockStorageLoading(); render(); expect(screen.getByText('Loading storage information...')).toBeVisible(); }); }); describe('error state', () => { it('shows "Not available" when storage query fails', () => { mockEnvironmentReady(); mockStorageError('Unable to connect to agent'); render(); expect(screen.getByText('Not available')).toBeVisible(); }); it('displays the error message in the tooltip', () => { mockEnvironmentReady(); mockStorageError('Unable to connect to agent'); render(); expect( screen.getByText('Unable to connect to agent') ).toBeInTheDocument(); }); }); describe('agent upgrade required', () => { it('shows "Not available" when agent version is older than required', () => { mockEnvironmentReady('2.39.0'); mockStorageIdle(); render(); expect(screen.getByText('Not available')).toBeVisible(); }); it('shows "Not available" when agent version is missing (pre-2.15 agent)', () => { mockEnvironmentReady(undefined); mockStorageIdle(); render(); expect(screen.getByText('Not available')).toBeVisible(); }); it('shows upgrade tooltip message', () => { mockEnvironmentReady('2.39.0'); mockStorageError(); render(); expect( screen.getByText(/Upgrade your agent to enable this feature/i) ).toBeInTheDocument(); }); }); describe('storage unavailable (no data from agent)', () => { it('shows "Not available" when agent returns 204 (totalBytes undefined)', () => { mockEnvironmentReady(); mockStorageNoData(); render(); expect(screen.getByText('Not available')).toBeVisible(); }); it('shows Docker socket tooltip message', () => { mockEnvironmentReady(); mockStorageNoData(); render(); expect( screen.getByText(/Docker socket and host filesystem are accessible/i) ).toBeInTheDocument(); }); }); describe('success state', () => { it('renders the complete storage breakdown', () => { mockEnvironmentReady(); mockStorageSuccess(); render(); // Legend items with humanized sizes // Use exact match for Docker to avoid matching the partition path "/var/lib/docker" expect(screen.getByText('Docker (100 MB)')).toBeVisible(); expect(screen.getByText(/Other/i)).toBeVisible(); expect(screen.getByText(/Free/i)).toBeVisible(); // Partition expect(screen.getByText(/Partition:/i)).toBeVisible(); expect(screen.getByText('/var/lib/docker')).toBeVisible(); expect(screen.queryByText('Not available')).not.toBeInTheDocument(); }); it('shows Docker artifact breakdown in the tooltip', () => { mockEnvironmentReady(); mockStorageSuccess(); render(); expect(screen.getByText('Images')).toBeInTheDocument(); expect(screen.getByText('Containers')).toBeInTheDocument(); expect(screen.getByText('Volumes')).toBeInTheDocument(); expect(screen.getByText('Build cache')).toBeInTheDocument(); }); }); describe('no environment data', () => { it('shows "Not available" when environment data is missing after loading', () => { mockEnvironmentNoData(); // When environment has no data, needsUpgrade will be true (no agentVersion), // so dockerStorage query won't run. Simulate it returning no data. mockStorageNoData(); render(); expect(screen.getByText('Not available')).toBeVisible(); }); }); });