mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 15:06:37 +08:00
[优化] 改名
This commit is contained in:
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
@@ -0,0 +1,258 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ThemeProvider } from '@/components/providers/theme-provider';
|
||||
import { DashboardOverview } from '@/features/dashboard/components/dashboard-overview';
|
||||
|
||||
vi.mock('echarts-for-react', () => ({
|
||||
default: () => <div data-testid="echarts-mock" />,
|
||||
}));
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('DashboardOverview', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
function stubMatchMedia() {
|
||||
vi.stubGlobal(
|
||||
'matchMedia',
|
||||
vi.fn().mockImplementation(() => ({
|
||||
matches: false,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
function renderDashboardOverview() {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider>
|
||||
<DashboardOverview />
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
it('renders dashboard summary cards', async () => {
|
||||
stubMatchMedia();
|
||||
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn((input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
|
||||
if (url.includes('/dashboard/overview')) {
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: '',
|
||||
data: {
|
||||
generated_at: '2026-03-14T08:00:00Z',
|
||||
summary: {
|
||||
total_nodes: 2,
|
||||
online_nodes: 2,
|
||||
offline_nodes: 0,
|
||||
pending_nodes: 0,
|
||||
unhealthy_nodes: 1,
|
||||
},
|
||||
traffic: {
|
||||
request_count: 900,
|
||||
unique_visitors: 200,
|
||||
error_count: 36,
|
||||
estimated_qps: 15,
|
||||
reported_nodes: 2,
|
||||
},
|
||||
capacity: {
|
||||
average_cpu_usage_percent: 68.5,
|
||||
average_memory_usage_percent: 71.8,
|
||||
high_cpu_nodes: 1,
|
||||
high_memory_nodes: 1,
|
||||
high_storage_nodes: 1,
|
||||
},
|
||||
distributions: {
|
||||
source_countries: [
|
||||
{ key: 'CN', value: 440 },
|
||||
{ key: 'US', value: 320 },
|
||||
{ key: 'SG', value: 140 },
|
||||
],
|
||||
status_codes: [
|
||||
{ key: '200', value: 820 },
|
||||
{ key: '502', value: 24 },
|
||||
{ key: '500', value: 12 },
|
||||
],
|
||||
top_domains: [
|
||||
{ key: 'app.example.com', value: 580 },
|
||||
{ key: 'api.example.com', value: 220 },
|
||||
],
|
||||
},
|
||||
trends: {
|
||||
traffic_24h: Array.from({ length: 24 }, (_, index) => ({
|
||||
bucket_started_at: `2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
||||
request_count: index * 10,
|
||||
error_count: index,
|
||||
unique_visitor_count: index * 3,
|
||||
})),
|
||||
capacity_24h: Array.from({ length: 24 }, (_, index) => ({
|
||||
bucket_started_at: `2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
||||
average_cpu_usage_percent: index,
|
||||
average_memory_usage_percent: index + 10,
|
||||
reported_nodes: 2,
|
||||
})),
|
||||
network_24h: Array.from({ length: 24 }, (_, index) => ({
|
||||
bucket_started_at: `2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
||||
network_rx_bytes: index * 100,
|
||||
network_tx_bytes: index * 120,
|
||||
openresty_rx_bytes: index * 140,
|
||||
openresty_tx_bytes: index * 160,
|
||||
reported_nodes: 2,
|
||||
})),
|
||||
disk_io_24h: Array.from({ length: 24 }, (_, index) => ({
|
||||
bucket_started_at: `2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
||||
disk_read_bytes: index * 50,
|
||||
disk_write_bytes: index * 70,
|
||||
reported_nodes: 2,
|
||||
})),
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
node_id: 'node-a',
|
||||
name: 'edge-a',
|
||||
geo_name: 'Shanghai',
|
||||
geo_latitude: 31.2304,
|
||||
geo_longitude: 121.4737,
|
||||
status: 'online',
|
||||
openresty_status: 'healthy',
|
||||
current_version: '20260314-001',
|
||||
last_seen_at: '2026-03-14T08:00:00Z',
|
||||
active_event_count: 0,
|
||||
cpu_usage_percent: 45,
|
||||
memory_usage_percent: 50,
|
||||
storage_usage_percent: 60,
|
||||
request_count: 600,
|
||||
error_count: 6,
|
||||
unique_visitor_count: 120,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
node_id: 'node-b',
|
||||
name: 'edge-b',
|
||||
geo_name: 'San Francisco',
|
||||
geo_latitude: 37.7749,
|
||||
geo_longitude: -122.4194,
|
||||
status: 'online',
|
||||
openresty_status: 'unhealthy',
|
||||
current_version: '20260313-001',
|
||||
last_seen_at: '2026-03-14T08:00:00Z',
|
||||
active_event_count: 2,
|
||||
cpu_usage_percent: 92,
|
||||
memory_usage_percent: 88,
|
||||
storage_usage_percent: 95,
|
||||
request_count: 300,
|
||||
error_count: 30,
|
||||
unique_visitor_count: 80,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(`Unhandled fetch: ${url}`));
|
||||
}),
|
||||
);
|
||||
|
||||
renderDashboardOverview();
|
||||
|
||||
expect(await screen.findByText('全球态势板')).toBeInTheDocument();
|
||||
expect(await screen.findByText('系统健康摘要')).toBeInTheDocument();
|
||||
expect(await screen.findByText('24 小时请求趋势')).toBeInTheDocument();
|
||||
expect(await screen.findByText('来源分布')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Top Domain')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Top 节点榜单')).toBeInTheDocument();
|
||||
expect(await screen.findByText('节点健康列表')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders empty state when dashboard arrays are returned as null', async () => {
|
||||
stubMatchMedia();
|
||||
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn((input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
|
||||
if (url.includes('/dashboard/overview')) {
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: '',
|
||||
data: {
|
||||
generated_at: '2026-03-14T08:00:00Z',
|
||||
summary: {
|
||||
total_nodes: 0,
|
||||
online_nodes: 0,
|
||||
offline_nodes: 0,
|
||||
pending_nodes: 0,
|
||||
unhealthy_nodes: 0,
|
||||
},
|
||||
traffic: {
|
||||
request_count: 0,
|
||||
unique_visitors: 0,
|
||||
error_count: 0,
|
||||
estimated_qps: 0,
|
||||
reported_nodes: 0,
|
||||
},
|
||||
capacity: {
|
||||
average_cpu_usage_percent: 0,
|
||||
average_memory_usage_percent: 0,
|
||||
high_cpu_nodes: 0,
|
||||
high_memory_nodes: 0,
|
||||
high_storage_nodes: 0,
|
||||
},
|
||||
distributions: {
|
||||
source_countries: null,
|
||||
status_codes: null,
|
||||
top_domains: null,
|
||||
},
|
||||
trends: {
|
||||
traffic_24h: null,
|
||||
capacity_24h: null,
|
||||
network_24h: null,
|
||||
disk_io_24h: null,
|
||||
},
|
||||
nodes: null,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(`Unhandled fetch: ${url}`));
|
||||
}),
|
||||
);
|
||||
|
||||
renderDashboardOverview();
|
||||
|
||||
expect(await screen.findByText('暂无节点')).toBeInTheDocument();
|
||||
expect(await screen.findByText('暂无来源分布数据')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
calculateNiceAxisMax,
|
||||
formatBytes,
|
||||
formatBytesPerSecond,
|
||||
formatPercent,
|
||||
} from '@/lib/utils/metrics';
|
||||
|
||||
describe('metrics utils', () => {
|
||||
it('formats byte values with readable units', () => {
|
||||
expect(formatBytes(0)).toBe('0 B');
|
||||
expect(formatBytes(1536)).toBe('1.5 KB');
|
||||
expect(formatBytes(183 * 1024 * 1024)).toBe('183 MB');
|
||||
expect(formatBytes(14.7 * 1024 * 1024)).toBe('14.7 MB');
|
||||
});
|
||||
|
||||
it('formats byte rates with per-second units', () => {
|
||||
expect(formatBytesPerSecond(0, 3600)).toBe('0 B/s');
|
||||
expect(formatBytesPerSecond(1630858.9, 3600)).toBe('453 B/s');
|
||||
expect(formatBytesPerSecond(22620490956.8, 3600)).toBe('6 MB/s');
|
||||
});
|
||||
|
||||
it('formats percent values without long decimals', () => {
|
||||
expect(formatPercent(58.36809884639982)).toBe('58.4%');
|
||||
});
|
||||
|
||||
it('rounds chart max values to friendly axis steps', () => {
|
||||
expect(calculateNiceAxisMax([0, 58.36809884639982])).toBe(80);
|
||||
expect(calculateNiceAxisMax([0, 1630858.9])).toBe(2000000);
|
||||
expect(calculateNiceAxisMax([0, 22620490956.8])).toBe(25000000000);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { getCurrentNavigationItem, isPathActive } from '@/lib/utils/navigation';
|
||||
|
||||
describe('navigation utils', () => {
|
||||
it('marks root path as active only for home', () => {
|
||||
expect(isPathActive('/', '/')).toBe(true);
|
||||
expect(isPathActive('/node', '/')).toBe(false);
|
||||
});
|
||||
|
||||
it('resolves current navigation item for nested paths', () => {
|
||||
expect(getCurrentNavigationItem('/node/abc')?.label).toBe('节点');
|
||||
expect(getCurrentNavigationItem('/website')?.label).toBe('网站');
|
||||
expect(getCurrentNavigationItem('/performance')?.label).toBe('性能');
|
||||
expect(getCurrentNavigationItem('/setting')?.label).toBe('设置');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { isThemeMode, resolveTheme } from '@/lib/theme/theme';
|
||||
|
||||
describe('theme utils', () => {
|
||||
it('validates theme mode values', () => {
|
||||
expect(isThemeMode('light')).toBe(true);
|
||||
expect(isThemeMode('dark')).toBe(true);
|
||||
expect(isThemeMode('system')).toBe(true);
|
||||
expect(isThemeMode('custom')).toBe(false);
|
||||
});
|
||||
|
||||
it('resolves system mode using system preference', () => {
|
||||
expect(resolveTheme('system', true)).toBe('dark');
|
||||
expect(resolveTheme('system', false)).toBe('light');
|
||||
expect(resolveTheme('dark', false)).toBe('dark');
|
||||
expect(resolveTheme('light', true)).toBe('light');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user