c1c5e8a37d
- Fix PVC bind loop leak on unmount via cancellation ref - Fix DeleteOptions body structure for proper foreground propagation - Filter snapshots to tns-csi driver only (was showing all drivers) - Fix stale closures in Escape key handlers with useCallback - Add loading state to cleanup delete button, remove window.confirm/alert - Use CSS custom properties for protocol chart colors (dark mode support) - Fix all 35 ESLint warnings (import sort, indent, boolean attrs) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
162 lines
5.9 KiB
TypeScript
162 lines
5.9 KiB
TypeScript
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<typeof defaultContext>[0]) {
|
|
vi.mocked(useTnsCsiContext).mockReturnValue(defaultContext(overrides));
|
|
}
|
|
|
|
describe('StorageClassesPage', () => {
|
|
beforeEach(() => {
|
|
mockPush.mockClear();
|
|
mockHash = '';
|
|
});
|
|
|
|
it('shows loader when loading', () => {
|
|
mockContext({ loading: true });
|
|
render(<StorageClassesPage />);
|
|
expect(screen.getByTestId('loader')).toHaveTextContent('Loading storage classes...');
|
|
});
|
|
|
|
it('shows error state', () => {
|
|
mockContext({ error: 'fetch failed' });
|
|
render(<StorageClassesPage />);
|
|
expect(screen.getByText('fetch failed')).toBeInTheDocument();
|
|
expect(screen.getByText('Error')).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows empty message when no storage classes', () => {
|
|
mockContext({ storageClasses: [] });
|
|
render(<StorageClassesPage />);
|
|
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(<StorageClassesPage />);
|
|
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(<StorageClassesPage />);
|
|
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(<StorageClassesPage />);
|
|
expect(screen.getByText('StorageClass Details')).toBeInTheDocument();
|
|
});
|
|
|
|
it('closes panel via close button', () => {
|
|
mockHash = '#tns-nfs';
|
|
const sc = makeSampleStorageClass();
|
|
mockContext({ storageClasses: [sc], persistentVolumes: [] });
|
|
render(<StorageClassesPage />);
|
|
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(<StorageClassesPage />);
|
|
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(<StorageClassesPage />);
|
|
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(<StorageClassesPage />);
|
|
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(<StorageClassesPage />);
|
|
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(<StorageClassesPage />);
|
|
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(<StorageClassesPage />);
|
|
const cells = screen.getAllByRole('cell');
|
|
const pvCells = cells.filter(c => c.textContent === '2');
|
|
expect(pvCells.length).toBeGreaterThanOrEqual(1);
|
|
});
|
|
});
|