Adopt Portainer/Beszel-style fleet controls: prominent search, status facet chips with counts, active filter chips, result count, and primary-row sort/select. Keep advanced filters collapsed for secondary facets. Tests and production dist rebuild for gateway 8788.
141 lines
5.7 KiB
TypeScript
141 lines
5.7 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { FleetPage, type FleetSnapshot } from './fleet-page.js';
|
|
|
|
const snapshot: FleetSnapshot = {
|
|
instances: [
|
|
{
|
|
id: 'alpha',
|
|
name: 'Alpha modem',
|
|
url: 'http://192.168.1.2',
|
|
tags: [],
|
|
revision: 3,
|
|
},
|
|
],
|
|
statuses: new Map([
|
|
[
|
|
'alpha',
|
|
{
|
|
reachable: true,
|
|
authenticated: true,
|
|
summary: { freshness: 'fresh', resources: { cpuPercent: 24, memoryPercent: 51 } },
|
|
},
|
|
],
|
|
]),
|
|
};
|
|
|
|
afterEach(cleanup);
|
|
|
|
describe('FleetPage card navigation', () => {
|
|
it('makes the whole instance card open its dashboard while admin controls stay separately interactive', () => {
|
|
render(<FleetPage initialData={snapshot} />);
|
|
|
|
const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' });
|
|
const entry = within(card).getByRole('link', { name: '打开 Alpha modem 实例仪表盘' });
|
|
expect(entry.getAttribute('href')).toBe('/instances/alpha/overview');
|
|
expect(entry.classList.contains('fleet-card-entry')).toBe(true);
|
|
expect(within(card).getByRole('link', { name: '编辑 Alpha modem' })).toBeTruthy();
|
|
expect(within(card).getByRole('button', { name: '删除 Alpha modem' })).toBeTruthy();
|
|
expect(within(card).queryByRole('link', { name: /查看.*短信/ })).toBeNull();
|
|
expect(screen.getByRole('region', { name: '实例状态摘要' }).textContent).toMatch(
|
|
/实例总数\s*1.*在线\s*1.*需处理\s*0/s,
|
|
);
|
|
expect(within(card).getByRole('meter', { name: 'CPU 使用率' }).getAttribute('value')).toBe(
|
|
'24',
|
|
);
|
|
expect(within(card).getByRole('meter', { name: '内存使用率' }).getAttribute('value')).toBe(
|
|
'51',
|
|
);
|
|
expect(within(card).getByText('进入仪表盘')).toBeTruthy();
|
|
expect(within(card).getByRole('group', { name: '实例管理操作' })).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('FleetPage search and filter toolbar', () => {
|
|
it('filters by status chips and clears active search conditions', () => {
|
|
render(<FleetPage initialData={snapshot} />);
|
|
|
|
const search = screen.getByRole('search', { name: '实例搜索与筛选' });
|
|
expect(within(search).getByPlaceholderText('搜索名称、ID、标签、源地址…')).toBeTruthy();
|
|
expect(within(search).getByText(/显示/).textContent).toMatch(/显示\s*1\s*\/\s*1/);
|
|
|
|
fireEvent.click(within(search).getByRole('button', { name: /离线/ }));
|
|
expect(screen.queryByRole('article', { name: 'Alpha modem 实例概览' })).toBeNull();
|
|
expect(within(search).getByText(/显示/).textContent).toMatch(/显示\s*0\s*\/\s*1/);
|
|
expect(within(search).getByRole('button', { name: /移除筛选 状态:离线/ })).toBeTruthy();
|
|
|
|
fireEvent.change(within(search).getByPlaceholderText('搜索名称、ID、标签、源地址…'), {
|
|
target: { value: 'Alpha' },
|
|
});
|
|
expect(within(search).getByRole('button', { name: /移除筛选 搜索:Alpha/ })).toBeTruthy();
|
|
|
|
fireEvent.click(within(search).getByRole('button', { name: '清除全部筛选' }));
|
|
expect(screen.getByRole('article', { name: 'Alpha modem 实例概览' })).toBeTruthy();
|
|
expect(within(search).getByText(/显示/).textContent).toMatch(/显示\s*1\s*\/\s*1/);
|
|
});
|
|
});
|
|
|
|
describe('FleetPage batch and restart actions', () => {
|
|
it('exposes card restart controls and batch restart entry for selected instances', async () => {
|
|
const prepare = vi.fn(async () => ({
|
|
id: 'prep-1',
|
|
status: 'prepared',
|
|
operationId: 'postServiceRestart',
|
|
risk: 'R3',
|
|
expiresAt: '2030-01-01T00:00:00.000Z',
|
|
confirmationPrompt: 'Confirm restart',
|
|
targetCount: 1,
|
|
}));
|
|
const execute = vi.fn(async () => ({
|
|
id: 'job-1',
|
|
operationId: 'postServiceRestart',
|
|
status: 'succeeded',
|
|
rootJobId: 'job-1',
|
|
items: [{ id: 'item-1', targetId: 'alpha', state: 'succeeded' }],
|
|
attempts: [],
|
|
createdAt: '2029-01-01T00:00:00.000Z',
|
|
}));
|
|
const list = vi.fn(async () => ({
|
|
items: [
|
|
{
|
|
operationId: 'postServiceRestart',
|
|
title: 'Restart Service',
|
|
risk: 'R3',
|
|
capability: 'job',
|
|
batchable: false,
|
|
parameterSchemaId: 'simadmin.58e2204.postServiceRestart.parameters.v1',
|
|
},
|
|
{
|
|
operationId: 'postSystemReboot',
|
|
title: 'Reboot System',
|
|
risk: 'R3',
|
|
capability: 'job',
|
|
batchable: false,
|
|
parameterSchemaId: 'simadmin.58e2204.postSystemReboot.parameters.v1',
|
|
},
|
|
],
|
|
page: { page: 1, pageSize: 100, total: 2 },
|
|
}));
|
|
vi.stubGlobal('confirm', () => true);
|
|
render(
|
|
<FleetPage
|
|
initialData={snapshot}
|
|
operationClient={{ list, prepare, execute } as never}
|
|
/>,
|
|
);
|
|
const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' });
|
|
expect(within(card).getByRole('button', { name: '重启服务 Alpha modem' })).toBeTruthy();
|
|
expect(within(card).getByRole('button', { name: '系统重启 Alpha modem' })).toBeTruthy();
|
|
fireEvent.click(within(card).getByRole('checkbox', { name: '选择 Alpha modem' }));
|
|
expect(screen.getByText(/已选择 1 项/).textContent).toMatch(/已选择 1 项/);
|
|
fireEvent.click(screen.getByRole('button', { name: '批量操作' }));
|
|
const batch = await screen.findByRole('region', { name: '批量操作入口' });
|
|
expect(batch.textContent).toMatch(/已选择 1 项/);
|
|
fireEvent.click(screen.getByRole('button', { name: '批量重启服务' }));
|
|
await vi.waitFor(() => expect(prepare).toHaveBeenCalled());
|
|
expect(execute).toHaveBeenCalledWith('prep-1');
|
|
});
|
|
});
|