feat(web): localize and redesign operations console
This commit is contained in:
@@ -20,7 +20,7 @@ const snapshot: FleetSnapshot = {
|
||||
{
|
||||
id: 'bravo',
|
||||
name: 'Bravo',
|
||||
url: 'http://user:secret@bravo.example:8080/private',
|
||||
url: 'http://user:***@bravo.example:8080/private',
|
||||
tags: ['west'],
|
||||
},
|
||||
{ id: 'alpha', name: 'Alpha', url: 'https://alpha.example/admin' },
|
||||
@@ -69,8 +69,8 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
const dataSource = source(() => new Promise((done) => (resolve = done)));
|
||||
render(<AppShell pathname="/fleet" version="0.1.0" fleetDataSource={dataSource} />);
|
||||
|
||||
expect(screen.getByRole('status', { name: 'Fleet loading status' }).textContent).toContain(
|
||||
'Loading instances',
|
||||
expect(screen.getByRole('status', { name: '实例加载状态' }).textContent).toContain(
|
||||
'正在加载实例',
|
||||
);
|
||||
resolve(snapshot);
|
||||
|
||||
@@ -80,10 +80,10 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
);
|
||||
expect(
|
||||
within(screen.getByRole('row', { name: /Bravo/ }))
|
||||
.getByRole('link', { name: 'Open Bravo origin' })
|
||||
.getByRole('link', { name: '打开 Bravo 的源站' })
|
||||
.getAttribute('href'),
|
||||
).toBe('http://bravo.example:8080');
|
||||
expect(screen.getByText('Version 0.1.0')).toBeTruthy();
|
||||
expect(screen.getByText('版本 0.1.0')).toBeTruthy();
|
||||
expect(
|
||||
consoleError.mock.calls.some((call) =>
|
||||
call.some((argument) => String(argument).includes('unique "key" prop')),
|
||||
@@ -96,23 +96,23 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
render(<AppShell pathname="/fleet" fleetDataSource={source(async () => snapshot)} />);
|
||||
await screen.findByRole('row', { name: /Alpha/ });
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Sort by latency/i }));
|
||||
await user.click(screen.getByRole('button', { name: /按延迟排序/i }));
|
||||
let rows = screen.getAllByRole('row').slice(1);
|
||||
expect(rows[0]?.textContent).toContain('Alpha');
|
||||
await user.click(screen.getByRole('button', { name: /Sort by latency/i }));
|
||||
await user.click(screen.getByRole('button', { name: /按延迟排序/i }));
|
||||
rows = screen.getAllByRole('row').slice(1);
|
||||
expect(rows[0]?.textContent).toContain('Bravo');
|
||||
|
||||
await user.selectOptions(screen.getByRole('combobox', { name: 'Status' }), 'auth');
|
||||
await user.selectOptions(screen.getByRole('combobox', { name: '状态' }), 'auth');
|
||||
expect(screen.queryByRole('row', { name: /Alpha/ })).toBeNull();
|
||||
await user.click(screen.getByRole('checkbox', { name: 'Select all visible instances' }));
|
||||
expect(
|
||||
(screen.getByRole('checkbox', { name: 'Select Bravo' }) as HTMLInputElement).checked,
|
||||
).toBe(true);
|
||||
expect(screen.getByText('1 selected')).toBeTruthy();
|
||||
await user.click(screen.getByRole('checkbox', { name: '选择当前页全部实例' }));
|
||||
expect((screen.getByRole('checkbox', { name: '选择 Bravo' }) as HTMLInputElement).checked).toBe(
|
||||
true,
|
||||
);
|
||||
expect(screen.getByText('已选择 1 项')).toBeTruthy();
|
||||
|
||||
await user.type(screen.getByRole('searchbox', { name: 'Search instances' }), 'missing');
|
||||
expect(screen.getByText(/No instances match your search/i)).toBeTruthy();
|
||||
await user.type(screen.getByRole('searchbox', { name: '搜索实例' }), 'missing');
|
||||
expect(screen.getByText(/没有实例符合搜索与筛选条件/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('exposes error, retry, configured-empty, and unsafe-origin states without claiming a backend', async () => {
|
||||
@@ -126,10 +126,10 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
});
|
||||
const first = render(<AppShell pathname="/fleet" fleetDataSource={source(load)} />);
|
||||
|
||||
expect((await screen.findByRole('alert')).textContent).toContain('collector unavailable');
|
||||
await user.click(screen.getByRole('button', { name: 'Retry loading instances' }));
|
||||
expect((await screen.findByRole('alert')).textContent).toContain('实例加载失败');
|
||||
await user.click(screen.getByRole('button', { name: '重试加载实例' }));
|
||||
expect(await screen.findByRole('row', { name: /Unsafe/ })).toBeTruthy();
|
||||
expect(screen.queryByRole('link', { name: 'Open Unsafe origin' })).toBeNull();
|
||||
expect(screen.queryByRole('link', { name: '打开 Unsafe 的源站' })).toBeNull();
|
||||
first.unmount();
|
||||
|
||||
const { unmount } = render(
|
||||
@@ -138,7 +138,7 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
fleetDataSource={source(async () => ({ instances: [], statuses: new Map() }))}
|
||||
/>,
|
||||
);
|
||||
expect(await screen.findByText(/No instances are configured/i)).toBeTruthy();
|
||||
expect(await screen.findByText(/尚未配置实例/i)).toBeTruthy();
|
||||
unmount();
|
||||
});
|
||||
|
||||
@@ -147,20 +147,20 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
render(<AppShell pathname="/fleet" fleetDataSource={source(async () => snapshot)} />);
|
||||
await screen.findByRole('row', { name: /Alpha/ });
|
||||
|
||||
await user.selectOptions(screen.getByRole('combobox', { name: 'Capability' }), 'sms');
|
||||
await user.selectOptions(screen.getByRole('combobox', { name: '能力' }), '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('button', { name: '选择列' }));
|
||||
await user.click(screen.getByRole('checkbox', { name: '显示异常列' }));
|
||||
expect(screen.queryByRole('columnheader', { name: '异常' })).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();
|
||||
await user.click(screen.getByRole('checkbox', { name: '选择 Bravo' }));
|
||||
expect((screen.getByRole('button', { name: '批量操作' }) as HTMLButtonElement).disabled).toBe(
|
||||
false,
|
||||
);
|
||||
await user.click(screen.getByRole('button', { name: '批量操作' }));
|
||||
expect(screen.getByText('请为已选择的 1 项选择操作。')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('paginates fleet rows and selects only the current page', async () => {
|
||||
@@ -177,12 +177,12 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
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(screen.getByText('第 1 页,共 2 页')).toBeTruthy();
|
||||
await user.click(screen.getByRole('checkbox', { name: '选择当前页全部实例' }));
|
||||
expect(screen.getByText('已选择 10 项')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '下一页' }));
|
||||
expect(await screen.findByRole('row', { name: /Instance 11/ })).toBeTruthy();
|
||||
expect(screen.getByText('Page 2 of 2')).toBeTruthy();
|
||||
expect(screen.getByText('第 2 页,共 2 页')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('binds instance context to the route owner and never leaks mismatched context', () => {
|
||||
@@ -197,27 +197,27 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
const { rerender } = render(
|
||||
<AppShell pathname="/instances/owner/messages" instance={instance} />,
|
||||
);
|
||||
expect(screen.getByRole('heading', { name: 'Messages' })).toBeTruthy();
|
||||
expect(screen.getByRole('heading', { name: '消息' })).toBeTruthy();
|
||||
expect(screen.getByText('Owner modem')).toBeTruthy();
|
||||
expect(screen.getByRole('link', { name: 'Open original site' }).getAttribute('href')).toBe(
|
||||
expect(screen.getByRole('link', { name: '打开源站' }).getAttribute('href')).toBe(
|
||||
'https://owner.example',
|
||||
);
|
||||
|
||||
rerender(<AppShell pathname="/instances/someone-else/messages" instance={instance} />);
|
||||
expect(screen.queryByText('Owner modem')).toBeNull();
|
||||
expect(screen.getByText(/Instance context is unavailable/i)).toBeTruthy();
|
||||
expect(screen.getByText(/此路由缺少实例上下文/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
pathname: '/jobs',
|
||||
endpoint: '/api/v1/jobs?page=1&pageSize=25&sort=createdAt&direction=desc',
|
||||
emptyMessage: /No jobs match the current query/i,
|
||||
emptyMessage: /没有任务符合当前查询/i,
|
||||
},
|
||||
{
|
||||
pathname: '/audit',
|
||||
endpoint: '/api/v1/audit?sort=occurredAt&direction=desc&page=1&pageSize=25',
|
||||
emptyMessage: /No audit events match the current query/i,
|
||||
emptyMessage: /没有审计事件符合当前查询/i,
|
||||
},
|
||||
])(
|
||||
'uses the default HTTP data source for $pathname',
|
||||
@@ -247,12 +247,12 @@ describe('React AppShell and Fleet vertical slice', () => {
|
||||
{
|
||||
pathname: '/jobs',
|
||||
sourceProp: 'jobsDataSource' as const,
|
||||
emptyMessage: /No jobs match the current query/i,
|
||||
emptyMessage: /没有任务符合当前查询/i,
|
||||
},
|
||||
{
|
||||
pathname: '/audit',
|
||||
sourceProp: 'auditDataSource' as const,
|
||||
emptyMessage: /No audit events match the current query/i,
|
||||
emptyMessage: /没有审计事件符合当前查询/i,
|
||||
},
|
||||
])(
|
||||
'uses an explicitly injected data source instead of global fetch for $pathname',
|
||||
|
||||
Reference in New Issue
Block a user