feat(web): rebuild the console around the fused control plane
Render the Hub feature set in the existing Animal Island styling: fleet messages, notification centre, log centre, organisation panel, per-instance device module panels and the settings backup, connection and maintenance surfaces.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
// @vitest-environment jsdom
|
||||
import { cleanup, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { DeviceDiscoveryPanel, type DiscoveryDataSource } from './device-discovery-panel.js';
|
||||
import type { DeviceProbe, DiscoverySession } from './discovery-api-data-source.js';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const completed: DiscoverySession = {
|
||||
sessionId: '7a1f0c22-0000-4000-8000-000000000001',
|
||||
status: 'completed',
|
||||
createdAt: '2026-09-05T02:00:00.000Z',
|
||||
expiresAt: '2026-09-05T02:01:00.000Z',
|
||||
scanned: 508,
|
||||
total: 508,
|
||||
ranges: ['192.168.68.0/24'],
|
||||
devices: [
|
||||
{
|
||||
origin: 'http://192.168.68.1:3000',
|
||||
address: '192.168.68.1',
|
||||
port: 3000,
|
||||
secure: false,
|
||||
httpStatus: 200,
|
||||
identity: { model: 'iPhone 14', firmware_version: '16.1' },
|
||||
knownInstanceId: null,
|
||||
},
|
||||
{
|
||||
origin: 'http://192.168.68.2:3000',
|
||||
address: '192.168.68.2',
|
||||
port: 3000,
|
||||
secure: false,
|
||||
httpStatus: 401,
|
||||
identity: {},
|
||||
knownInstanceId: null,
|
||||
},
|
||||
{
|
||||
origin: 'http://192.168.68.3:3000',
|
||||
address: '192.168.68.3',
|
||||
port: 3000,
|
||||
secure: false,
|
||||
httpStatus: 200,
|
||||
identity: { brand: 'Google' },
|
||||
knownInstanceId: 'inst-9',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const reachable: DeviceProbe = {
|
||||
origin: 'http://192.168.68.9:3000',
|
||||
reachable: true,
|
||||
httpStatus: 200,
|
||||
identity: { model: 'Pixel 7' },
|
||||
knownInstanceId: null,
|
||||
};
|
||||
|
||||
function source(overrides: Partial<DiscoveryDataSource> = {}): DiscoveryDataSource {
|
||||
return {
|
||||
start: vi.fn(async () => completed),
|
||||
status: vi.fn(async () => completed),
|
||||
renew: vi.fn(async () => completed),
|
||||
stop: vi.fn(async () => undefined),
|
||||
probe: vi.fn(async () => reachable),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
async function mount(dataSource: DiscoveryDataSource): Promise<HTMLElement> {
|
||||
render(<DeviceDiscoveryPanel dataSource={dataSource} onSelect={vi.fn()} />);
|
||||
return screen.findByRole('region', { name: '局域网设备发现' });
|
||||
}
|
||||
|
||||
describe('DeviceDiscoveryPanel', () => {
|
||||
it('shows discovered devices with a status tag per row', async () => {
|
||||
const region = await mount(source());
|
||||
const table = await within(region).findByRole('table', { name: '发现的设备' });
|
||||
expect(within(table).getByText('http://192.168.68.1:3000')).toBeTruthy();
|
||||
expect(within(table).getByText('型号 iPhone 14 · 固件版本 16.1')).toBeTruthy();
|
||||
expect(within(table).getByText('未公开身份信息')).toBeTruthy();
|
||||
expect(within(table).getAllByText('可接入')).toHaveLength(1);
|
||||
expect(within(table).getByText('需要登录')).toBeTruthy();
|
||||
expect(within(table).getByText('已接入')).toBeTruthy();
|
||||
expect(within(region).getByText(/扫描完成 · 已检查 508 个地址 · 发现 3 台设备/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('hands the chosen device to the parent and locks a device already added', async () => {
|
||||
const onSelect = vi.fn();
|
||||
const dataSource = source();
|
||||
render(<DeviceDiscoveryPanel dataSource={dataSource} onSelect={onSelect} />);
|
||||
const table = await screen.findByRole('table', { name: '发现的设备' });
|
||||
|
||||
const rows = within(table).getAllByRole('button', { name: '接入' });
|
||||
expect(rows).toHaveLength(3);
|
||||
expect((rows[2] as HTMLButtonElement).disabled).toBe(true);
|
||||
await userEvent.click(rows[0] as HTMLButtonElement);
|
||||
expect(onSelect).toHaveBeenCalledWith({
|
||||
origin: 'http://192.168.68.1:3000',
|
||||
name: 'iPhone 14',
|
||||
});
|
||||
});
|
||||
|
||||
it('restarts the scan and stops the lease when unmounted', async () => {
|
||||
const dataSource = source();
|
||||
const { unmount } = render(<DeviceDiscoveryPanel dataSource={dataSource} onSelect={vi.fn()} />);
|
||||
await screen.findByRole('table', { name: '发现的设备' });
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: '重新扫描' }));
|
||||
await waitFor(() => expect(dataSource.start).toHaveBeenCalledTimes(2));
|
||||
|
||||
unmount();
|
||||
await waitFor(() => expect(dataSource.stop).toHaveBeenCalledWith(completed.sessionId));
|
||||
});
|
||||
|
||||
it('probes a manual address and offers to fill the form', async () => {
|
||||
const onSelect = vi.fn();
|
||||
const dataSource = source();
|
||||
render(<DeviceDiscoveryPanel dataSource={dataSource} onSelect={onSelect} />);
|
||||
const region = await screen.findByRole('region', { name: '局域网设备发现' });
|
||||
|
||||
const field = within(region).getByLabelText('设备地址');
|
||||
expect((field as HTMLInputElement).disabled).toBe(false);
|
||||
await userEvent.type(field, '192.168.68.9:3000');
|
||||
await userEvent.click(within(region).getByRole('button', { name: '检测地址' }));
|
||||
|
||||
expect(dataSource.probe).toHaveBeenCalledWith('192.168.68.9:3000');
|
||||
await userEvent.click(await within(region).findByRole('button', { name: '填入添加表单' }));
|
||||
expect(onSelect).toHaveBeenCalledWith({
|
||||
origin: 'http://192.168.68.9:3000',
|
||||
name: 'Pixel 7',
|
||||
});
|
||||
});
|
||||
|
||||
it('reports an unusable address without throwing', async () => {
|
||||
const dataSource = source({
|
||||
probe: vi.fn(async () => {
|
||||
throw new Error('Discovery request failed.');
|
||||
}),
|
||||
});
|
||||
render(<DeviceDiscoveryPanel dataSource={dataSource} onSelect={vi.fn()} />);
|
||||
const region = await screen.findByRole('region', { name: '局域网设备发现' });
|
||||
|
||||
await userEvent.type(within(region).getByLabelText('设备地址'), 'http://example.com');
|
||||
await userEvent.click(within(region).getByRole('button', { name: '检测地址' }));
|
||||
expect(await within(region).findByText(/设备地址无法识别/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('falls back to manual entry when discovery cannot start', async () => {
|
||||
const dataSource = source({
|
||||
start: vi.fn(async () => {
|
||||
throw new Error('Discovery request failed.');
|
||||
}),
|
||||
});
|
||||
render(<DeviceDiscoveryPanel dataSource={dataSource} onSelect={vi.fn()} />);
|
||||
const region = await screen.findByRole('region', { name: '局域网设备发现' });
|
||||
|
||||
expect(await within(region).findByRole('alert')).toBeTruthy();
|
||||
expect(within(region).getByRole('alert').textContent).toBe(
|
||||
'无法启动局域网发现,请改用设备地址接入。',
|
||||
);
|
||||
expect(within(region).queryByRole('table', { name: '发现的设备' })).toBeNull();
|
||||
expect(within(region).getByLabelText('设备地址')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user