chore: 前端代码风格统一并清理未使用导入;移除过时的 PR 流程规则
This commit is contained in:
@@ -1,96 +1,193 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { Refine } from '@refinedev/core'
|
||||
import { MemoryRouter } from 'react-router'
|
||||
import { NetworkExitList } from './NetworkExitsPage'
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
cleanup,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
within,
|
||||
} from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { Refine } from "@refinedev/core";
|
||||
import { MemoryRouter } from "react-router";
|
||||
import { NetworkExitList } from "./NetworkExitsPage";
|
||||
|
||||
afterEach(() => { cleanup(); vi.restoreAllMocks() })
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
const httpError = (message, status, body) => Object.assign(new Error(message), { status, body })
|
||||
const httpError = (message, status, body) =>
|
||||
Object.assign(new Error(message), { status, body });
|
||||
|
||||
const networkExit = { id: 'exit-a', protocol: 'socks5', host: 'proxy.example', port: 1080, username: 'proxy-user', password: 'plain-password', health_status: 'healthy', observed_public_ip: '203.0.113.1' }
|
||||
const networkExit = {
|
||||
id: "exit-a",
|
||||
protocol: "socks5",
|
||||
host: "proxy.example",
|
||||
port: 1080,
|
||||
username: "proxy-user",
|
||||
password: "plain-password",
|
||||
health_status: "healthy",
|
||||
observed_public_ip: "203.0.113.1",
|
||||
};
|
||||
|
||||
function provider(exits = [], overrides = {}) {
|
||||
return {
|
||||
getList: vi.fn(({ resource }) => Promise.resolve(resource === 'network-exits' ? { data: exits, total: exits.length } : { data: [], total: 0 })),
|
||||
getList: vi.fn(({ resource }) =>
|
||||
Promise.resolve(
|
||||
resource === "network-exits"
|
||||
? { data: exits, total: exits.length }
|
||||
: { data: [], total: 0 },
|
||||
),
|
||||
),
|
||||
create: vi.fn().mockResolvedValue({ data: networkExit }),
|
||||
networkExitAction: vi.fn().mockResolvedValue(networkExit),
|
||||
getOne: vi.fn(), getMany: vi.fn(), getManyReference: vi.fn(), update: vi.fn(), updateMany: vi.fn(), delete: vi.fn(), deleteMany: vi.fn(),
|
||||
getOne: vi.fn(),
|
||||
getMany: vi.fn(),
|
||||
getManyReference: vi.fn(),
|
||||
update: vi.fn(),
|
||||
updateMany: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
deleteMany: vi.fn(),
|
||||
...overrides,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const renderExits = dataProvider => render(
|
||||
<QueryClientProvider client={new QueryClient({ defaultOptions: { queries: { retry: false } } })}>
|
||||
<Refine dataProvider={dataProvider}>
|
||||
<MemoryRouter><NetworkExitList /></MemoryRouter>
|
||||
</Refine>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
const renderExits = (dataProvider) =>
|
||||
render(
|
||||
<QueryClientProvider
|
||||
client={
|
||||
new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||||
}
|
||||
>
|
||||
<Refine dataProvider={dataProvider}>
|
||||
<MemoryRouter>
|
||||
<NetworkExitList />
|
||||
</MemoryRouter>
|
||||
</Refine>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
describe('NetworkExitList', () => {
|
||||
it('shows the ordered empty-state instruction', async () => {
|
||||
renderExits(provider())
|
||||
expect(await screen.findByText(/创建并检测健康后/)).toBeTruthy()
|
||||
})
|
||||
describe("NetworkExitList", () => {
|
||||
it("shows the ordered empty-state instruction", async () => {
|
||||
renderExits(provider());
|
||||
expect(await screen.findByText(/创建并检测健康后/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows stored credentials and runs an explicit health check', async () => {
|
||||
const dataProvider = provider([networkExit])
|
||||
renderExits(dataProvider)
|
||||
it("shows stored credentials and runs an explicit health check", async () => {
|
||||
const dataProvider = provider([networkExit]);
|
||||
renderExits(dataProvider);
|
||||
|
||||
expect((await screen.findAllByText(/proxy-user/)).length).toBeGreaterThan(0)
|
||||
expect((await screen.findAllByText(/plain-password/)).length).toBeGreaterThan(0)
|
||||
fireEvent.click(screen.getAllByRole('button', { name: '检测' })[0])
|
||||
await waitFor(() => expect(dataProvider.networkExitAction).toHaveBeenCalledWith('exit-a', 'check'))
|
||||
})
|
||||
expect((await screen.findAllByText(/proxy-user/)).length).toBeGreaterThan(
|
||||
0,
|
||||
);
|
||||
expect(
|
||||
(await screen.findAllByText(/plain-password/)).length,
|
||||
).toBeGreaterThan(0);
|
||||
fireEvent.click(screen.getAllByRole("button", { name: "检测" })[0]);
|
||||
await waitFor(() =>
|
||||
expect(dataProvider.networkExitAction).toHaveBeenCalledWith(
|
||||
"exit-a",
|
||||
"check",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('creates with plain credentials and closes on success', async () => {
|
||||
const dataProvider = provider()
|
||||
renderExits(dataProvider)
|
||||
it("creates with plain credentials and closes on success", async () => {
|
||||
const dataProvider = provider();
|
||||
renderExits(dataProvider);
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: '创建网络出口' }))
|
||||
const dialog = screen.getByRole('dialog')
|
||||
fireEvent.change(within(dialog).getByRole('textbox', { name: '主机' }), { target: { value: 'proxy.example' } })
|
||||
fireEvent.change(within(dialog).getByRole('spinbutton', { name: '端口' }), { target: { value: '1080' } })
|
||||
fireEvent.change(within(dialog).getByRole('textbox', { name: '用户名(可选)' }), { target: { value: 'proxy-user' } })
|
||||
fireEvent.change(within(dialog).getByRole('textbox', { name: '密码(可选)' }), { target: { value: 'plain-password' } })
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: '创建网络出口' }))
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: "创建网络出口" }),
|
||||
);
|
||||
const dialog = screen.getByRole("dialog");
|
||||
fireEvent.change(within(dialog).getByRole("textbox", { name: "主机" }), {
|
||||
target: { value: "proxy.example" },
|
||||
});
|
||||
fireEvent.change(within(dialog).getByRole("spinbutton", { name: "端口" }), {
|
||||
target: { value: "1080" },
|
||||
});
|
||||
fireEvent.change(
|
||||
within(dialog).getByRole("textbox", { name: "用户名(可选)" }),
|
||||
{ target: { value: "proxy-user" } },
|
||||
);
|
||||
fireEvent.change(
|
||||
within(dialog).getByRole("textbox", { name: "密码(可选)" }),
|
||||
{ target: { value: "plain-password" } },
|
||||
);
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: "创建网络出口" }),
|
||||
);
|
||||
|
||||
await waitFor(() => expect(dataProvider.create).toHaveBeenCalledWith({ resource: 'network-exits', variables: {
|
||||
protocol: 'socks5', host: 'proxy.example', port: 1080, username: 'proxy-user', password: 'plain-password', expected_public_ip: '', expected_region: '',
|
||||
} }))
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull())
|
||||
})
|
||||
await waitFor(() =>
|
||||
expect(dataProvider.create).toHaveBeenCalledWith({
|
||||
resource: "network-exits",
|
||||
variables: {
|
||||
protocol: "socks5",
|
||||
host: "proxy.example",
|
||||
port: 1080,
|
||||
username: "proxy-user",
|
||||
password: "plain-password",
|
||||
expected_public_ip: "",
|
||||
expected_region: "",
|
||||
},
|
||||
}),
|
||||
);
|
||||
await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull());
|
||||
});
|
||||
|
||||
it('keeps the create modal open after a failure', async () => {
|
||||
const dataProvider = provider([], { create: vi.fn().mockRejectedValue(new Error('创建失败')) })
|
||||
renderExits(dataProvider)
|
||||
it("keeps the create modal open after a failure", async () => {
|
||||
const dataProvider = provider([], {
|
||||
create: vi.fn().mockRejectedValue(new Error("创建失败")),
|
||||
});
|
||||
renderExits(dataProvider);
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: '创建网络出口' }))
|
||||
const dialog = screen.getByRole('dialog')
|
||||
fireEvent.change(within(dialog).getByRole('textbox', { name: '主机' }), { target: { value: 'proxy.example' } })
|
||||
fireEvent.change(within(dialog).getByRole('spinbutton', { name: '端口' }), { target: { value: '1080' } })
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: '创建网络出口' }))
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: "创建网络出口" }),
|
||||
);
|
||||
const dialog = screen.getByRole("dialog");
|
||||
fireEvent.change(within(dialog).getByRole("textbox", { name: "主机" }), {
|
||||
target: { value: "proxy.example" },
|
||||
});
|
||||
fireEvent.change(within(dialog).getByRole("spinbutton", { name: "端口" }), {
|
||||
target: { value: "1080" },
|
||||
});
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: "创建网络出口" }),
|
||||
);
|
||||
|
||||
expect((await within(dialog).findByRole('alert')).textContent).toContain('创建失败')
|
||||
expect(screen.getByRole('dialog')).toBeTruthy()
|
||||
})
|
||||
expect((await within(dialog).findByRole("alert")).textContent).toContain(
|
||||
"创建失败",
|
||||
);
|
||||
expect(screen.getByRole("dialog")).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows unknown bindings and retries when browsers return 502', async () => {
|
||||
it("shows unknown bindings and retries when browsers return 502", async () => {
|
||||
const dataProvider = provider([networkExit], {
|
||||
getList: vi.fn(({ resource }) => resource === 'browsers'
|
||||
? Promise.reject(httpError('bad gateway', 502))
|
||||
: Promise.resolve({ data: [networkExit], total: 1 })),
|
||||
})
|
||||
renderExits(dataProvider)
|
||||
getList: vi.fn(({ resource }) =>
|
||||
resource === "browsers"
|
||||
? Promise.reject(httpError("bad gateway", 502))
|
||||
: Promise.resolve({ data: [networkExit], total: 1 }),
|
||||
),
|
||||
});
|
||||
renderExits(dataProvider);
|
||||
|
||||
expect(await screen.findByText(/环境不可用/)).toBeTruthy()
|
||||
expect((await screen.findAllByText(/绑定账号:状态未知|状态未知/)).length).toBeGreaterThan(0)
|
||||
expect(screen.queryByText(/绑定账号:未绑定/)).toBeNull()
|
||||
expect(screen.queryByText(/^未绑定$/)).toBeNull()
|
||||
expect(screen.getAllByRole('button', { name: '检测' })[0].disabled).toBe(false)
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试环境状态' }))
|
||||
await waitFor(() => expect(dataProvider.getList.mock.calls.filter(([call]) => call.resource === 'browsers').length).toBeGreaterThan(1))
|
||||
})
|
||||
})
|
||||
expect(await screen.findByText(/环境不可用/)).toBeTruthy();
|
||||
expect(
|
||||
(await screen.findAllByText(/绑定账号:状态未知|状态未知/)).length,
|
||||
).toBeGreaterThan(0);
|
||||
expect(screen.queryByText(/绑定账号:未绑定/)).toBeNull();
|
||||
expect(screen.queryByText(/^未绑定$/)).toBeNull();
|
||||
expect(screen.getAllByRole("button", { name: "检测" })[0].disabled).toBe(
|
||||
false,
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "重试环境状态" }));
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
dataProvider.getList.mock.calls.filter(
|
||||
([call]) => call.resource === "browsers",
|
||||
).length,
|
||||
).toBeGreaterThan(1),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user