156 lines
6.5 KiB
TypeScript
156 lines
6.5 KiB
TypeScript
// @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 { ConnectionSettingsPanel } from './connection-settings-panel.js';
|
|
import type { SystemMaintenanceDataSource } from './system-maintenance-api-data-source.js';
|
|
|
|
afterEach(cleanup);
|
|
|
|
const defaults = { heartbeatSeconds: 30, offlineSeconds: 90, authorizationMode: 'auto' };
|
|
|
|
function source(overrides: Partial<SystemMaintenanceDataSource> = {}): SystemMaintenanceDataSource {
|
|
return {
|
|
connection: vi.fn(async () => defaults),
|
|
updateConnection: vi.fn(async (settings) => settings),
|
|
refreshHeartbeat: vi.fn(async () => ({
|
|
probed: 7,
|
|
failed: 2,
|
|
startedAt: '2026-09-05T00:00:00.000Z',
|
|
finishedAt: '2026-09-05T00:00:01.000Z',
|
|
})),
|
|
...overrides,
|
|
} as unknown as SystemMaintenanceDataSource;
|
|
}
|
|
|
|
async function mount(overrides: Partial<SystemMaintenanceDataSource> = {}) {
|
|
const data = source(overrides);
|
|
const view = render(<ConnectionSettingsPanel dataSource={data} />);
|
|
const region = await screen.findByRole('region', { name: '连接设置' });
|
|
if (!overrides.connection) {
|
|
await waitFor(() => {
|
|
expect(within(region).getByLabelText('心跳间隔(秒)').value).toBe('30');
|
|
});
|
|
}
|
|
return { ...view, data, region };
|
|
}
|
|
|
|
function field(region: HTMLElement, label: string): HTMLInputElement {
|
|
const node = within(region).getByLabelText(label) as HTMLInputElement;
|
|
return node;
|
|
}
|
|
|
|
function modeButton(region: HTMLElement, label: string): HTMLButtonElement {
|
|
return within(region).getByRole('button', { name: label }) as HTMLButtonElement;
|
|
}
|
|
|
|
describe('ConnectionSettingsPanel', () => {
|
|
it('loads the saved cadence into the inputs', async () => {
|
|
const { data, region } = await mount();
|
|
expect(data.connection).toHaveBeenCalledTimes(1);
|
|
expect(field(region, '心跳间隔(秒)').value).toBe('30');
|
|
expect(field(region, '离线判定(秒)').value).toBe('90');
|
|
});
|
|
|
|
it('starts new-device authorization in automatic mode', async () => {
|
|
const { region } = await mount();
|
|
|
|
expect(modeButton(region, '自动授权').getAttribute('aria-pressed')).toBe('true');
|
|
expect(modeButton(region, '人工确认').getAttribute('aria-pressed')).toBe('false');
|
|
});
|
|
|
|
it('saves a selected manual authorization mode and echoes it', async () => {
|
|
const { data, region } = await mount();
|
|
await userEvent.click(modeButton(region, '人工确认'));
|
|
|
|
const save = within(region).getByRole('button', { name: '保存设置' }) as HTMLButtonElement;
|
|
expect(save.disabled).toBe(false);
|
|
await userEvent.click(save);
|
|
|
|
expect(data.updateConnection).toHaveBeenCalledWith({
|
|
heartbeatSeconds: 30,
|
|
offlineSeconds: 90,
|
|
authorizationMode: 'manual',
|
|
});
|
|
const notice = await within(region).findByRole('status');
|
|
expect(notice.textContent).toContain('新设备人工确认');
|
|
});
|
|
|
|
it('keeps save disabled until a valid change is made', async () => {
|
|
const { region } = await mount();
|
|
const save = within(region).getByRole('button', { name: '保存设置' }) as HTMLButtonElement;
|
|
expect(save.disabled).toBe(true);
|
|
|
|
await userEvent.clear(field(region, '心跳间隔(秒)'));
|
|
await userEvent.type(field(region, '心跳间隔(秒)'), '4');
|
|
expect(save.disabled).toBe(true);
|
|
|
|
await userEvent.clear(field(region, '心跳间隔(秒)'));
|
|
await userEvent.type(field(region, '心跳间隔(秒)'), '45');
|
|
expect(save.disabled).toBe(false);
|
|
});
|
|
|
|
it('sends whole numbers and echoes the saved window', async () => {
|
|
const { data, region } = await mount();
|
|
await userEvent.clear(field(region, '心跳间隔(秒)'));
|
|
await userEvent.type(field(region, '心跳间隔(秒)'), '45');
|
|
await userEvent.clear(field(region, '离线判定(秒)'));
|
|
await userEvent.type(field(region, '离线判定(秒)'), '180');
|
|
await userEvent.click(within(region).getByRole('button', { name: '保存设置' }));
|
|
|
|
expect(data.updateConnection).toHaveBeenCalledWith({
|
|
heartbeatSeconds: 45,
|
|
offlineSeconds: 180,
|
|
authorizationMode: 'auto',
|
|
});
|
|
const notice = await within(region).findByRole('status');
|
|
expect(notice.textContent).toContain(
|
|
'连接设置已保存,新设备自动授权,心跳 45 秒,离线判定 180 秒。',
|
|
);
|
|
});
|
|
|
|
it('blocks a save when the offline window drops below twice the heartbeat', async () => {
|
|
const { data, region } = await mount();
|
|
await userEvent.clear(field(region, '离线判定(秒)'));
|
|
await userEvent.type(field(region, '离线判定(秒)'), '59');
|
|
const save = within(region).getByRole('button', { name: '保存设置' }) as HTMLButtonElement;
|
|
expect(save.disabled).toBe(true);
|
|
expect(data.updateConnection).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('runs an on-demand beat and reports the offline devices', async () => {
|
|
const { data, region } = await mount();
|
|
await userEvent.click(within(region).getByRole('button', { name: '立即刷新设备状态' }));
|
|
expect(data.refreshHeartbeat).toHaveBeenCalledTimes(1);
|
|
const notice = await within(region).findByRole('status');
|
|
expect(notice.textContent).toContain('已检查 9 台设备,2 台无响应。');
|
|
});
|
|
|
|
it('surfaces a failed save without losing the form', async () => {
|
|
const { region } = await mount({
|
|
updateConnection: vi.fn(async () => {
|
|
throw new Error('offline');
|
|
}),
|
|
});
|
|
await userEvent.clear(field(region, '心跳间隔(秒)'));
|
|
await userEvent.type(field(region, '心跳间隔(秒)'), '60');
|
|
await userEvent.clear(field(region, '离线判定(秒)'));
|
|
await userEvent.type(field(region, '离线判定(秒)'), '150');
|
|
await userEvent.click(within(region).getByRole('button', { name: '保存设置' }));
|
|
const problem = await within(region).findByRole('alert');
|
|
expect(problem.textContent).toContain('连接设置未保存,请检查各项设置的取值范围。');
|
|
expect(field(region, '心跳间隔(秒)').value).toBe('60');
|
|
});
|
|
|
|
it('explains itself when the cadence cannot be read', async () => {
|
|
const { region } = await mount({
|
|
connection: vi.fn(async () => {
|
|
throw new Error('offline');
|
|
}),
|
|
});
|
|
const problem = await within(region).findByRole('alert');
|
|
expect(problem.textContent).toContain('无法读取连接设置,请刷新页面重试。');
|
|
});
|
|
});
|