// @vitest-environment jsdom import { cleanup, fireEvent, render, screen, within } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { accessMethod } from './fleet-table-view-model.js'; 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: { version: '1.1.6', freshness: 'fresh', resources: { cpuPercent: 24, memoryPercent: 51, maxTemperatureCelsius: 42, phoneNumbers: ['+852 5550 0100'], hardwareOnline: true, controlOnline: true, simPresent: true, carrier: 'China Mobile', cellularRegistration: 'registered_home', accessTechnology: 'LTE', cellularOnline: true, signalPercent: 72, }, }, }, ], ]), }; afterEach(cleanup); describe('FleetPage card navigation', () => { it('masks both phone locations and reveals them only for the selected card', async () => { const privacySnapshot: FleetSnapshot = { instances: [ ...snapshot.instances, { id: 'beta', name: 'Beta modem', url: 'http://192.168.1.3', tags: [], revision: 1, }, ], statuses: new Map([ ...snapshot.statuses, [ 'beta', { reachable: true, authenticated: true, summary: { version: '1.2.0', freshness: 'fresh' as const, resources: { phoneNumbers: ['13812345678'] }, }, }, ], ]), }; const messagesDataSource = { load: vi.fn(async (instanceId: string) => instanceId === 'alpha' ? { latest: { id: 'sms-1', direction: 'incoming', phoneNumber: '13900139000', content: 'status', timestamp: '2026-07-19T08:30:00.000Z', }, } : {}, ), }; render(); const alpha = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); const beta = screen.getByRole('article', { name: 'Beta modem 实例概览' }); expect(within(alpha).getByText('+852 •••• 0100')).toBeTruthy(); expect(within(beta).getByText('138 •••• 5678')).toBeTruthy(); expect(await within(alpha).findByText('139 •••• 9000')).toBeTruthy(); expect(within(alpha).queryByText('+852 5550 0100')).toBeNull(); expect(within(alpha).queryByText('13900139000')).toBeNull(); const reveal = within(alpha).getByRole('button', { name: '显示 Alpha modem 手机号' }); expect(reveal.getAttribute('aria-pressed')).toBe('false'); fireEvent.click(reveal); expect(within(alpha).getByText('+852 5550 0100')).toBeTruthy(); expect(within(alpha).getByText('13900139000')).toBeTruthy(); expect(within(beta).getByText('138 •••• 5678')).toBeTruthy(); expect(within(beta).queryByText('13812345678')).toBeNull(); expect(within(alpha).getByRole('button', { name: '隐藏 Alpha modem 手机号' })).toBeTruthy(); }); it('does not show a privacy toggle when neither phone location has a number', () => { const noNumbers: FleetSnapshot = { ...snapshot, statuses: new Map([ [ 'alpha', { ...snapshot.statuses.get('alpha')!, summary: { ...snapshot.statuses.get('alpha')!.summary, resources: { cpuPercent: 24, memoryPercent: 51 }, }, }, ], ]), }; render(); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); expect(within(card).getByText('暂未获取')).toBeTruthy(); expect(within(card).queryByRole('button', { name: /手机号/ })).toBeNull(); }); it('makes the whole instance card open its dashboard while ops stay separate and config stays out of the card', () => { render(); 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).queryByRole('link', { name: /编辑/ })).toBeNull(); expect(within(card).queryByRole('button', { name: /删除/ })).toBeNull(); expect(within(card).queryByText('alpha')).toBeNull(); expect(within(card).queryByRole('link', { name: /查看.*短信/ })).toBeNull(); expect(screen.getByRole('region', { name: '实例状态摘要' }).textContent).toMatch( /实例总数\s*1.*在线\s*1.*需处理\s*0/s, ); expect(screen.queryByText('NODE DIRECTORY')).toBeNull(); expect(screen.queryByText(/LIVE NODE MATRIX/u)).toBeNull(); expect(screen.queryByText('已纳入统一管理')).toBeNull(); expect(screen.queryByText('连接与认证正常')).toBeNull(); expect(screen.queryByText('离线、认证或未知')).toBeNull(); expect(screen.queryByRole('region', { name: '节点资源健康' })).toBeNull(); expect(within(card).getByText('SimAdmin 1.1.6')).toBeTruthy(); expect( within(card).getByRole('progressbar', { name: 'CPU 使用率' }).getAttribute('aria-valuenow'), ).toBe('24'); expect( within(card).getByRole('progressbar', { name: '内存使用率' }).getAttribute('aria-valuenow'), ).toBe('51'); expect(within(card).getByText('进入仪表盘')).toBeTruthy(); const links = within(card).getByRole('region', { name: '链路状态' }); for (const label of ['硬件', '控制', 'SIM', '蜂窝']) { expect(within(links).getByText(label)).toBeTruthy(); } expect(links.textContent).toMatch(/硬件\s*正常.*控制\s*正常.*SIM\s*正常.*蜂窝\s*正常/s); expect(within(card).getByText('China Mobile')).toBeTruthy(); expect(within(card).getByText('LTE · 已注册')).toBeTruthy(); expect( within(card).getByRole('progressbar', { name: '蜂窝信号' }).getAttribute('aria-valuenow'), ).toBe('72'); const nodeEntry = within(card).getByRole('link', { name: '打开 Alpha modem 节点入口' }); expect(nodeEntry.getAttribute('href')).toBe('http://192.168.1.2'); expect(nodeEntry.getAttribute('target')).toBe('_blank'); expect(nodeEntry.getAttribute('rel')).toContain('noopener'); expect(within(card).queryByRole('group', { name: '实例运维操作' })).toBeNull(); expect(within(card).queryByRole('button', { name: '重启服务 Alpha modem' })).toBeNull(); const actionTrigger = within(card).getByRole('button', { name: '实例操作 Alpha modem' }); expect(actionTrigger.getAttribute('aria-expanded')).toBe('false'); fireEvent.click(actionTrigger); expect(actionTrigger.getAttribute('aria-expanded')).toBe('true'); expect(within(card).getByRole('menuitem', { name: '重启服务 Alpha modem' })).toBeTruthy(); expect(within(card).getByRole('menuitem', { name: '系统重启 Alpha modem' })).toBeTruthy(); const metadata = card.querySelector('.fleet-card-metadata'); const hardware = within(card).getByRole('group', { name: '节点硬件信息' }); const telemetry = within(card).getByRole('region', { name: '资源遥测' }); const footer = within(card).getByRole('region', { name: '短信状态' }); expect(metadata).toBeTruthy(); expect( Boolean(metadata!.compareDocumentPosition(hardware) & Node.DOCUMENT_POSITION_FOLLOWING), ).toBe(true); expect( Boolean(hardware.compareDocumentPosition(telemetry) & Node.DOCUMENT_POSITION_FOLLOWING), ).toBe(true); expect( Boolean(telemetry.compareDocumentPosition(footer) & Node.DOCUMENT_POSITION_FOLLOWING), ).toBe(true); }); it('opens the Hub-style device context drawer and keeps the dashboard one click away', async () => { const messagesDataSource = { load: vi.fn(async () => ({ latest: { id: 'sms-1', direction: 'incoming', phoneNumber: '13900139000', content: 'status', timestamp: '2026-07-19T08:30:00.000Z', }, })), }; render(); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); expect(screen.queryByRole('dialog', { name: 'Alpha modem 设备上下文' })).toBeNull(); fireEvent.click(within(card).getByRole('button', { name: '打开 Alpha modem 设备上下文' })); const drawer = screen.getByRole('dialog', { name: 'Alpha modem 设备上下文' }); const focusables = Array.from( drawer.querySelectorAll('a[href], button:not([disabled])'), ); fireEvent.focus(focusables[0]!); const dashboard = within(drawer).getByRole('link', { name: '打开 Alpha modem 实例仪表盘' }); expect(dashboard.getAttribute('href')).toBe('/instances/alpha/overview'); expect( within(drawer).getByRole('link', { name: '打开 Alpha modem 节点入口' }).getAttribute('href'), ).toBe('http://192.168.1.2'); expect(within(drawer).getByText('局域网接入')).toBeTruthy(); expect( within(drawer).getByRole('progressbar', { name: 'CPU 使用率' }).getAttribute('aria-valuenow'), ).toBe('24'); expect(await within(drawer).findByRole('region', { name: '短信状态' })).toBeTruthy(); fireEvent.click(within(drawer).getByRole('button', { name: '关闭设备上下文' })); expect(screen.queryByRole('dialog', { name: 'Alpha modem 设备上下文' })).toBeNull(); expect(document.activeElement).toBe( within(card).getByRole('button', { name: '打开 Alpha modem 设备上下文' }), ); fireEvent.click(within(card).getByRole('button', { name: '打开 Alpha modem 设备上下文' })); fireEvent.click(screen.getByRole('dialog', { name: 'Alpha modem 设备上下文' })); expect(screen.getByRole('dialog', { name: 'Alpha modem 设备上下文' })).toBeTruthy(); fireEvent.click( screen.getByRole('dialog', { name: 'Alpha modem 设备上下文' }).closest('.drawer-backdrop')!, ); expect(screen.queryByRole('dialog', { name: 'Alpha modem 设备上下文' })).toBeNull(); }); it('shows an explicit fallback when the upstream SimAdmin version is unavailable', () => { const withoutVersion: FleetSnapshot = { ...snapshot, statuses: new Map([ [ 'alpha', { ...snapshot.statuses.get('alpha')!, summary: { freshness: 'fresh', resources: { cpuPercent: 24, memoryPercent: 51 }, }, }, ], ]), }; render(); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); expect(within(card).getByText('版本未知')).toBeTruthy(); }); it('supports the menu button keyboard model and dismisses the menu from outside', () => { render(); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); const trigger = within(card).getByRole('button', { name: '实例操作 Alpha modem' }); trigger.focus(); fireEvent.keyDown(trigger, { key: 'ArrowDown' }); const messagesLink = within(card).getByRole('menuitem', { name: '打开 Alpha modem 短信中心', }); const serviceRestart = within(card).getByRole('menuitem', { name: '重启服务 Alpha modem', }); const basebandRestart = within(card).getByRole('menuitem', { name: '重启基带 Alpha modem', }); const systemReboot = within(card).getByRole('menuitem', { name: '系统重启 Alpha modem' }); expect(document.activeElement).toBe(messagesLink); fireEvent.keyDown(messagesLink, { key: 'ArrowDown' }); expect(document.activeElement).toBe(serviceRestart); fireEvent.keyDown(serviceRestart, { key: 'ArrowDown' }); expect(document.activeElement).toBe(basebandRestart); fireEvent.keyDown(basebandRestart, { key: 'ArrowDown' }); expect(document.activeElement).toBe(systemReboot); fireEvent.keyDown(systemReboot, { key: 'ArrowDown' }); expect(document.activeElement).toBe(messagesLink); fireEvent.keyDown(messagesLink, { key: 'ArrowUp' }); expect(document.activeElement).toBe(systemReboot); fireEvent.keyDown(systemReboot, { key: 'Escape' }); expect(within(card).queryByRole('menu')).toBeNull(); expect(document.activeElement).toBe(trigger); fireEvent.click(trigger); expect(within(card).getByRole('menu')).toBeTruthy(); fireEvent.pointerDown(document.body); expect(within(card).queryByRole('menu')).toBeNull(); }); }); describe('FleetPage search and filter toolbar', () => { it('filters by status chips and clears active search conditions', () => { render(); const search = screen.getByRole('search', { name: '实例搜索与筛选' }); expect(within(search).getByPlaceholderText('搜索名称、ID、标签、源地址…')).toBeTruthy(); expect(within(search).queryByText(/显示/)).toBeNull(); expect(document.querySelector('.fleet-result-count')?.textContent).toMatch(/显示\s*1\s*\/\s*1/); fireEvent.click(within(search).getByRole('button', { name: /离线/ })); expect(screen.queryByRole('article', { name: 'Alpha modem 实例概览' })).toBeNull(); expect(document.querySelector('.fleet-result-count')?.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(document.querySelector('.fleet-result-count')?.textContent).toMatch(/显示\s*1\s*\/\s*1/); }); it('filters devices by access method and reports the active condition', () => { const accessSnapshot: FleetSnapshot = { instances: [ ...snapshot.instances, { id: 'wan', name: 'Wan modem', url: 'https://wan.example', tags: [], }, ], statuses: new Map([...snapshot.statuses, ['wan', { reachable: true, authenticated: true }]]), }; render(); fireEvent.click(screen.getByText('更多筛选')); fireEvent.change(screen.getByLabelText('接入方式'), { target: { value: 'lan' }, }); expect(screen.getByRole('article', { name: 'Alpha modem 实例概览' })).toBeTruthy(); expect(screen.queryByRole('article', { name: 'Wan modem 实例概览' })).toBeNull(); expect(screen.getByRole('button', { name: '移除筛选 接入:局域网接入' })).toBeTruthy(); }); it('filters the matrix from the device group row without moving search out of the sidebar', () => { const groupedSnapshot: FleetSnapshot = { instances: [ { ...snapshot.instances[0]!, groupId: 'group-core' }, { id: 'beta', name: 'Beta modem', url: 'http://192.168.1.3', tags: ['外场'], groupId: 'group-field', revision: 1, }, ], statuses: new Map([ ...snapshot.statuses, ['beta', { reachable: false, authenticated: false }], ]), }; render( , ); const groups = screen.getByRole('group', { name: '节点分组' }); const betaCard = screen.getByRole('article', { name: 'Beta modem 实例概览' }); expect(screen.queryByRole('region', { name: '节点资源健康' })).toBeNull(); // CPU, 内存, 信号, 运行时长 and 最高温度 all fall back to a dash with no telemetry. expect(within(betaCard).getAllByText('--')).toHaveLength(5); // The group chip and the tag chip are separate rows even when they read the same. expect(betaCard.querySelector('.fleet-instance-groups')?.textContent).toContain('外场'); expect(betaCard.querySelector('.fleet-instance-tags')?.textContent).toContain('外场'); expect(within(groups).getByRole('button', { name: '全部节点' })).toBeTruthy(); fireEvent.click(within(groups).getByRole('button', { name: /核心/ })); expect(screen.getByRole('article', { name: 'Alpha modem 实例概览' })).toBeTruthy(); expect(screen.queryByRole('article', { name: 'Beta modem 实例概览' })).toBeNull(); expect(betaCard.isConnected).toBe(false); expect( screen .getByRole('search', { name: '实例搜索与筛选' }) .closest('aside') ?.classList.contains('fleet-sidebar'), ).toBe(true); }); }); describe('FleetPage batch and restart actions', () => { it('keeps selection controls hidden until batch-selection mode is entered', () => { render(); expect(screen.queryByRole('checkbox', { name: /选择 Alpha modem/ })).toBeNull(); fireEvent.click(screen.getByRole('button', { name: '批量选择' })); const checkbox = screen.getByRole('checkbox', { name: /选择 Alpha modem/ }); expect(checkbox).toBeTruthy(); expect(checkbox.closest('.fleet-card-header-meta')).toBeTruthy(); expect(screen.getByRole('button', { name: '全选本页' })).toBeTruthy(); fireEvent.click(screen.getByRole('button', { name: '退出批量选择' })); expect(screen.queryByRole('checkbox', { name: /选择 Alpha modem/ })).toBeNull(); }); 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: 'postBasebandRestart', title: 'Restart Baseband', risk: 'R3', capability: 'job', batchable: false, parameterSchemaId: 'simadmin.58e2204.postBasebandRestart.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: 3 }, })); vi.stubGlobal('confirm', () => true); render( , ); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); const cardMenuTrigger = within(card).getByRole('button', { name: '实例操作 Alpha modem' }); expect(cardMenuTrigger.getAttribute('aria-expanded')).toBe('false'); fireEvent.click(cardMenuTrigger); const messages = within(card).getByRole('menuitem', { name: '打开 Alpha modem 短信中心', }); expect(messages.getAttribute('href')).toBe('/fleet/messages?device=alpha'); expect(within(card).getByRole('menuitem', { name: '重启服务 Alpha modem' })).toBeTruthy(); expect(within(card).getByRole('menuitem', { name: '重启基带 Alpha modem' })).toBeTruthy(); expect(within(card).getByRole('menuitem', { name: '系统重启 Alpha modem' })).toBeTruthy(); fireEvent.click(within(card).getByRole('menuitem', { name: '重启基带 Alpha modem' })); await vi.waitFor(() => expect(prepare).toHaveBeenCalledWith( expect.objectContaining({ operationId: 'postBasebandRestart', parameters: expect.objectContaining({ parameterSchemaId: 'simadmin.58e2204.postBasebandRestart.parameters.v1', fields: [], }), }), ), ); expect(execute).toHaveBeenCalledWith('prep-1'); fireEvent.click(screen.getByRole('button', { name: '批量选择' })); fireEvent.click(within(card).getByRole('checkbox', { name: '选择 Alpha modem' })); expect( screen.getAllByText(/已选择 1 项/).some((node) => /已选择 1 项/.test(node.textContent ?? '')), ).toBe(true); 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'); }); }); describe('FleetPage access method', () => { it('classifies how the control plane reaches each node', () => { expect(accessMethod('http://127.0.0.1:3000/admin')).toEqual({ label: '本机直连', kind: 'local', }); expect(accessMethod('http://localhost:3000')).toEqual({ label: '本机直连', kind: 'local' }); expect(accessMethod('http://[::1]:3000')).toEqual({ label: '本机直连', kind: 'local' }); expect(accessMethod('http://192.168.2.69:3000')).toEqual({ label: '局域网接入', kind: 'lan' }); expect(accessMethod('http://172.16.5.4')).toEqual({ label: '局域网接入', kind: 'lan' }); expect(accessMethod('http://172.32.5.4')).toEqual({ label: '公网地址', kind: 'wan' }); expect(accessMethod('http://100.64.0.9')).toEqual({ label: '局域网接入', kind: 'lan' }); expect(accessMethod('http://203.0.113.9')).toEqual({ label: '公网地址', kind: 'wan' }); expect(accessMethod('https://modem.example.com/admin')).toEqual({ label: '域名接入', kind: 'wan', }); expect(accessMethod('not-a-url')).toBeNull(); }); it('shows the derived access method on the card origin row', () => { render(); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); const chip = card.querySelector('.fleet-card-access'); expect(chip?.textContent).toBe('局域网接入'); expect(chip?.getAttribute('data-kind')).toBe('lan'); }); }); describe('FleetPage heartbeat reporting', () => { const withConnection = (status: Record): FleetSnapshot => ({ instances: snapshot.instances, statuses: new Map([['alpha', { ...snapshot.statuses.get('alpha')!, ...status }]]), }); it('renders the age of the last heartbeat the console received', () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-09-05T12:00:00.000Z')); try { render( , ); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); const row = card.querySelector('.fleet-card-heartbeat'); const time = row?.querySelector('time'); expect(row?.getAttribute('data-stale')).toBe('false'); expect(time?.getAttribute('datetime')).toBe('2026-09-05T11:59:20.000Z'); expect(time?.textContent).toBe('40 秒前'); } finally { vi.useRealTimers(); } }); it('ages a long-dead heartbeat into days and flags the card stale', () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-09-05T12:00:00.000Z')); try { render( , ); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); const row = card.querySelector('.fleet-card-heartbeat'); expect(row?.getAttribute('data-stale')).toBe('true'); expect(row?.querySelector('time')?.textContent).toBe('3 天前'); } finally { vi.useRealTimers(); } }); it('renders the device uptime the node reports, down to seconds', () => { const withUptime = (uptimeSeconds: number | undefined): FleetSnapshot => ({ instances: snapshot.instances, statuses: new Map([ [ 'alpha', { ...snapshot.statuses.get('alpha')!, summary: { ...snapshot.statuses.get('alpha')!.summary, resources: { ...snapshot.statuses.get('alpha')!.summary?.resources, ...(uptimeSeconds === undefined ? {} : { uptimeSeconds }), }, }, }, ], ]), }); const { unmount } = render(); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); const values = [...card.querySelectorAll('.fleet-card-hardware dd')]; expect(values.map((node) => node.textContent)).toContain('1天 2小时'); unmount(); render(); expect( screen.getByRole('article', { name: 'Alpha modem 实例概览' }).textContent?.includes('45秒'), ).toBe(true); }); it('says 未上报 until the heartbeat has reached the node', () => { render(); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); const row = card.querySelector('.fleet-card-heartbeat'); expect(row?.getAttribute('data-stale')).toBe('true'); expect(row?.querySelector('time')).toBeNull(); expect(row?.textContent).toContain('未上报'); }); const withIdentity = (identity: Record | undefined): FleetSnapshot => ({ instances: snapshot.instances, statuses: new Map([ [ 'alpha', { ...snapshot.statuses.get('alpha')!, ...(identity ? { identity } : {}), }, ], ]), }); it('warns on the card that the identity guard is holding the node', () => { render( , ); const card = screen.getByRole('article', { name: 'Alpha modem 实例概览' }); expect(card.querySelector('.fleet-card-identity')?.textContent).toContain('身份待确认'); expect(card.querySelector('.fleet-card-identity')?.textContent).toContain('控制操作已暂停'); }); it('stays quiet for a confirmed node and for one that never reported identity', () => { render( , ); expect( screen .getByRole('article', { name: 'Alpha modem 实例概览' }) .querySelector('.fleet-card-identity'), ).toBeNull(); cleanup(); render(); expect( screen .getByRole('article', { name: 'Alpha modem 实例概览' }) .querySelector('.fleet-card-identity'), ).toBeNull(); }); it('opens the identity guard from the node toolbar', async () => { const identityDataSource = { list: vi.fn().mockResolvedValue({ items: [], tracked: 0, pending: 0 }), confirm: vi.fn(), refresh: vi.fn(), }; render( , ); fireEvent.click(screen.getByRole('button', { name: '设备身份核对' })); expect(await screen.findByRole('dialog', { name: '设备身份核对' })).toBeTruthy(); expect(identityDataSource.list).toHaveBeenCalled(); }); it('counts held nodes in the cross-health rail and opens the guard from there', async () => { const identityDataSource = { list: vi.fn().mockResolvedValue({ items: [], tracked: 0, pending: 0 }), confirm: vi.fn(), refresh: vi.fn(), }; render( , ); const rail = screen.getByRole('button', { name: '设备身份待确认节点' }); expect(rail.textContent).toContain('身份待确认'); expect(rail.querySelector('strong')?.textContent).toBe('1'); expect(rail.querySelector('strong')?.getAttribute('data-tone')).toBe('attention'); fireEvent.click(rail); expect(await screen.findByRole('dialog', { name: '设备身份核对' })).toBeTruthy(); }); it('shows a clean identity rail when every node matches its record', () => { render( , ); const rail = screen.getByRole('button', { name: '设备身份待确认节点' }); expect(rail.querySelector('strong')?.textContent).toBe('0'); expect(rail.querySelector('strong')?.getAttribute('data-tone')).toBe('ok'); }); });