import { fireEvent, render, screen } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; vi.mock( '@kinvolk/headlamp-plugin/lib/CommonComponents', async () => await import('./__mocks__/commonComponents') ); let mockHash = ''; const mockPush = vi.fn(); vi.mock('react-router-dom', () => ({ useLocation: () => ({ pathname: '/tns-csi/storage-classes', hash: mockHash }), useHistory: () => ({ push: mockPush }), })); vi.mock('../api/TnsCsiDataContext'); import { useTnsCsiContext } from '../api/TnsCsiDataContext'; import { defaultContext, makeSamplePV, makeSampleStorageClass } from '../test-helpers'; import StorageClassesPage from './StorageClassesPage'; function mockContext(overrides?: Parameters[0]) { vi.mocked(useTnsCsiContext).mockReturnValue(defaultContext(overrides)); } describe('StorageClassesPage', () => { beforeEach(() => { mockPush.mockClear(); mockHash = ''; }); it('shows loader when loading', () => { mockContext({ loading: true }); render(); expect(screen.getByTestId('loader')).toHaveTextContent('Loading storage classes...'); }); it('shows error state', () => { mockContext({ error: 'fetch failed' }); render(); expect(screen.getByText('fetch failed')).toBeInTheDocument(); expect(screen.getByText('Error')).toBeInTheDocument(); }); it('shows empty message when no storage classes', () => { mockContext({ storageClasses: [] }); render(); expect(screen.getByText('No tns-csi StorageClasses found.')).toBeInTheDocument(); }); it('renders table with all columns populated', () => { const sc = makeSampleStorageClass(); const pv = makeSamplePV(); mockContext({ storageClasses: [sc], persistentVolumes: [pv], }); render(); expect(screen.getByText('tns-nfs')).toBeInTheDocument(); expect(screen.getByText('NFS')).toBeInTheDocument(); expect(screen.getByText('tank')).toBeInTheDocument(); expect(screen.getByText('10.0.0.1')).toBeInTheDocument(); expect(screen.getByText('Delete')).toBeInTheDocument(); expect(screen.getByText('Yes')).toBeInTheDocument(); // expansion expect(screen.getByText('1')).toBeInTheDocument(); // PV count }); it('opens detail panel when clicking SC name', () => { const sc = makeSampleStorageClass(); mockContext({ storageClasses: [sc] }); render(); fireEvent.click(screen.getByText('tns-nfs')); expect(mockPush).toHaveBeenCalledWith('/tns-csi/storage-classes#tns-nfs'); }); it('renders detail panel when hash is set', () => { mockHash = '#tns-nfs'; const sc = makeSampleStorageClass(); mockContext({ storageClasses: [sc], persistentVolumes: [] }); render(); expect(screen.getByText('StorageClass Details')).toBeInTheDocument(); }); it('closes panel via close button', () => { mockHash = '#tns-nfs'; const sc = makeSampleStorageClass(); mockContext({ storageClasses: [sc], persistentVolumes: [] }); render(); fireEvent.click(screen.getByLabelText('Close panel')); expect(mockPush).toHaveBeenCalledWith('/tns-csi/storage-classes'); }); it('closes panel via backdrop click', () => { mockHash = '#tns-nfs'; const sc = makeSampleStorageClass(); mockContext({ storageClasses: [sc], persistentVolumes: [] }); render(); fireEvent.click(screen.getByLabelText('Close panel backdrop')); expect(mockPush).toHaveBeenCalledWith('/tns-csi/storage-classes'); }); it('closes panel on Escape key', () => { mockHash = '#tns-nfs'; const sc = makeSampleStorageClass(); mockContext({ storageClasses: [sc], persistentVolumes: [] }); render(); fireEvent.keyDown(window, { key: 'Escape' }); expect(mockPush).toHaveBeenCalledWith('/tns-csi/storage-classes'); }); it('shows NFS protocol notes in detail panel', () => { mockHash = '#tns-nfs'; const sc = makeSampleStorageClass({ parameters: { protocol: 'nfs', pool: 'tank', server: '10.0.0.1' }, }); mockContext({ storageClasses: [sc], persistentVolumes: [] }); render(); expect(screen.getByText('Protocol Notes')).toBeInTheDocument(); expect(screen.getByText(/nfs-common/)).toBeInTheDocument(); }); it('shows NVMe-oF protocol notes', () => { mockHash = '#tns-nvmeof'; const sc = makeSampleStorageClass({ metadata: { name: 'tns-nvmeof' }, parameters: { protocol: 'nvmeof', pool: 'tank', server: '10.0.0.1' }, }); mockContext({ storageClasses: [sc], persistentVolumes: [] }); render(); expect(screen.getByText(/nvme-cli/)).toBeInTheDocument(); }); it('shows iSCSI protocol notes', () => { mockHash = '#tns-iscsi'; const sc = makeSampleStorageClass({ metadata: { name: 'tns-iscsi' }, parameters: { protocol: 'iscsi', pool: 'tank', server: '10.0.0.1' }, }); mockContext({ storageClasses: [sc], persistentVolumes: [] }); render(); expect(screen.getByText(/open-iscsi/)).toBeInTheDocument(); }); it('shows PV count for each storage class', () => { const sc1 = makeSampleStorageClass({ metadata: { name: 'sc-a' } }); const sc2 = makeSampleStorageClass({ metadata: { name: 'sc-b' } }); const pv1 = makeSamplePV({ spec: { ...makeSamplePV().spec, storageClassName: 'sc-a' } }); const pv2 = makeSamplePV({ metadata: { name: 'pv-2' }, spec: { ...makeSamplePV().spec, storageClassName: 'sc-a' }, }); mockContext({ storageClasses: [sc1, sc2], persistentVolumes: [pv1, pv2], }); render(); const cells = screen.getAllByRole('cell'); const pvCells = cells.filter(c => c.textContent === '2'); expect(pvCells.length).toBeGreaterThanOrEqual(1); }); });