mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-18 22:36:19 +00:00
54119be0c2
Rebalance several capabilities from the paid tier to the free Community tier: - Managed Trivy auto-update toggle is admin-only, no longer tier-gated. - Node labels (assign, view, manage) are available on every tier; cordon and FleetSync anchor reset stay paid. - Fleet topology layout modes (Hub, Grouped, Free) are available on Community. - Single-scan SBOM export (SPDX and CycloneDX) is admin-only on Community; SARIF export stays paid via a dedicated canExportSarif capability split out from the former shared SBOM flag. Backend route guards and frontend affordances are updated together, with tier and admin-role tests covering the Community-allowed and still-paid paths.
68 lines
2.6 KiB
TypeScript
68 lines
2.6 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { render, screen } from '@testing-library/react';
|
|
|
|
// Stub the heavy children so this test exercises OverviewTab's own
|
|
// branch-selection logic, not NodeCard/FleetTopology internals.
|
|
vi.mock('../NodeCard', () => ({ NodeCard: ({ node }: { node: { name: string } }) => <div data-testid="node-card">{node.name}</div> }));
|
|
vi.mock('../../fleet/FleetTopology', () => ({ FleetTopology: () => <div data-testid="topology" /> }));
|
|
|
|
import { OverviewTab } from '../OverviewTab';
|
|
import type { FleetNode, FleetPreferences } from '../types';
|
|
|
|
const PREFS: FleetPreferences = { sortBy: 'name', sortDir: 'asc', filterStatus: 'all', filterType: 'all', filterCritical: false };
|
|
|
|
function node(id: number, name: string): FleetNode {
|
|
return { id, name, type: 'remote', status: 'online', stats: null, systemStats: null, stacks: null, cordoned: false, cordoned_at: null, cordoned_reason: null };
|
|
}
|
|
|
|
function props(overrides: Partial<React.ComponentProps<typeof OverviewTab>> = {}) {
|
|
return {
|
|
loading: false,
|
|
nodes: [node(1, 'Alpha')],
|
|
processedNodes: [node(1, 'Alpha')],
|
|
allNodes: [node(1, 'Alpha')],
|
|
topologyNodes: [],
|
|
viewMode: 'grid' as const,
|
|
onViewModeChange: vi.fn(),
|
|
searchQuery: '',
|
|
onSearchQueryChange: vi.fn(),
|
|
prefs: PREFS,
|
|
onPrefsChange: vi.fn(),
|
|
fleetPalette: [],
|
|
labelFilters: new Set<string>(),
|
|
onLabelFiltersChange: vi.fn(),
|
|
onClearFilters: vi.fn(),
|
|
fleetStackLabelMap: {},
|
|
updateStatusMap: new Map(),
|
|
onNavigateToNode: vi.fn(),
|
|
updatingNodeId: null,
|
|
topologyMode: 'hub' as const,
|
|
onTopologyModeChange: vi.fn(),
|
|
topologyPositions: {},
|
|
onTopologyPositionsChange: vi.fn(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('OverviewTab', () => {
|
|
it('renders node cards when nodes are present', () => {
|
|
render(<OverviewTab {...props()} />);
|
|
expect(screen.getByTestId('node-card')).toHaveTextContent('Alpha');
|
|
});
|
|
|
|
it('shows the empty state when no nodes are configured', () => {
|
|
render(<OverviewTab {...props({ nodes: [], processedNodes: [], allNodes: [] })} />);
|
|
expect(screen.getByText('No nodes configured')).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows the no-match state when filters exclude every node', () => {
|
|
render(<OverviewTab {...props({ processedNodes: [], allNodes: [] })} />);
|
|
expect(screen.getByText('No nodes match your filters')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders the topology view in topology mode', () => {
|
|
render(<OverviewTab {...props({ viewMode: 'topology' })} />);
|
|
expect(screen.getByTestId('topology')).toBeInTheDocument();
|
|
});
|
|
});
|