feat(web): complete fleet and instance management slices
This commit is contained in:
@@ -6,7 +6,10 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { AppShell, type InstanceContext } from './app-shell.js';
|
||||
import { type FleetDataSource, type FleetSnapshot } from './fleet/fleet-page.js';
|
||||
|
||||
afterEach(cleanup);
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
const snapshot: FleetSnapshot = {
|
||||
instances: [
|
||||
@@ -19,8 +22,29 @@ const snapshot: FleetSnapshot = {
|
||||
{ id: 'alpha', name: 'Alpha', url: 'https://alpha.example/admin' },
|
||||
],
|
||||
statuses: new Map([
|
||||
['bravo', { reachable: true, authenticated: false, latencyMs: 40 }],
|
||||
['alpha', { reachable: true, authenticated: true, latencyMs: 10 }],
|
||||
[
|
||||
'bravo',
|
||||
{
|
||||
reachable: true,
|
||||
authenticated: false,
|
||||
latencyMs: 40,
|
||||
summary: {
|
||||
version: '2.0',
|
||||
capabilities: ['sms'],
|
||||
freshness: 'stale',
|
||||
anomalies: ['clock drift'],
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'alpha',
|
||||
{
|
||||
reachable: true,
|
||||
authenticated: true,
|
||||
latencyMs: 10,
|
||||
summary: { version: '1.0', capabilities: ['ussd'], freshness: 'fresh' },
|
||||
},
|
||||
],
|
||||
]),
|
||||
};
|
||||
|
||||
@@ -29,7 +53,8 @@ function source(load: FleetDataSource['load']): FleetDataSource {
|
||||
}
|
||||
|
||||
describe('React AppShell and Fleet vertical slice', () => {
|
||||
it('loads real injected data and renders canonical origins and owner routes', async () => {
|
||||
it('loads real injected data and renders canonical origins and owner routes without React key warnings', async () => {
|
||||
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||
let resolve!: (value: FleetSnapshot) => void;
|
||||
const dataSource = source(() => new Promise((done) => (resolve = done)));
|
||||
render(<AppShell pathname="/fleet" version="0.1.0" fleetDataSource={dataSource} />);
|
||||
@@ -49,6 +74,11 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
.getAttribute('href'),
|
||||
).toBe('http://bravo.example:8080');
|
||||
expect(screen.getByText('Version 0.1.0')).toBeTruthy();
|
||||
expect(
|
||||
consoleError.mock.calls.some((call) =>
|
||||
call.some((argument) => String(argument).includes('unique "key" prop')),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('supports accessible search, status filtering, sorting, and visible selection', async () => {
|
||||
@@ -102,6 +132,49 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('supports metadata filters, column visibility, pagination, and a batch-action entry', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<AppShell pathname="/fleet" fleetDataSource={source(async () => snapshot)} />);
|
||||
await screen.findByRole('row', { name: /Alpha/ });
|
||||
|
||||
await user.selectOptions(screen.getByRole('combobox', { name: 'Capability' }), 'sms');
|
||||
expect(screen.queryByRole('row', { name: /Alpha/ })).toBeNull();
|
||||
expect(screen.getByRole('row', { name: /Bravo/ }).textContent).toContain('clock drift');
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Choose columns' }));
|
||||
await user.click(screen.getByRole('checkbox', { name: 'Show anomalies column' }));
|
||||
expect(screen.queryByRole('columnheader', { name: 'Anomalies' })).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole('checkbox', { name: 'Select Bravo' }));
|
||||
expect(
|
||||
(screen.getByRole('button', { name: 'Batch actions' }) as HTMLButtonElement).disabled,
|
||||
).toBe(false);
|
||||
await user.click(screen.getByRole('button', { name: 'Batch actions' }));
|
||||
expect(screen.getByText('Choose an action for 1 selected instance.')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('paginates fleet rows and selects only the current page', async () => {
|
||||
const user = userEvent.setup();
|
||||
const many: FleetSnapshot = {
|
||||
instances: Array.from({ length: 11 }, (_, index) => ({
|
||||
id: `instance-${String(index + 1).padStart(2, '0')}`,
|
||||
name: `Instance ${String(index + 1).padStart(2, '0')}`,
|
||||
url: `https://instance-${index + 1}.example`,
|
||||
})),
|
||||
statuses: new Map(),
|
||||
};
|
||||
render(<AppShell pathname="/fleet" fleetDataSource={source(async () => many)} />);
|
||||
await screen.findByRole('row', { name: /Instance 01/ });
|
||||
|
||||
expect(screen.queryByRole('row', { name: /Instance 11/ })).toBeNull();
|
||||
expect(screen.getByText('Page 1 of 2')).toBeTruthy();
|
||||
await user.click(screen.getByRole('checkbox', { name: 'Select all visible instances' }));
|
||||
expect(screen.getByText('10 selected')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: 'Next page' }));
|
||||
expect(await screen.findByRole('row', { name: /Instance 11/ })).toBeTruthy();
|
||||
expect(screen.getByText('Page 2 of 2')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('binds instance context to the route owner and never leaks mismatched context', () => {
|
||||
const instance: InstanceContext = {
|
||||
id: 'owner',
|
||||
|
||||
Reference in New Issue
Block a user