feat: 新增路由批量禁用/启用功能 (#295)
* feat: Preserve manual models during discovery and refresh, and add Claude configuration files and project documentation.feat: Preserve manual models during discovery and refresh, and add Claude configuration and project documentation files. * fix: fix Feishu and Larksuite webhook support to the notification service with updated UI and tests. * feat: 新增路由批量禁用/启用功能 - 后端新增 POST /api/routes/batch 接口,支持批量 enable/disable - 前端新增批量操作模式,支持 checkbox 选择路由 - 浮动操作栏显示选中数量,支持全选/取消全选 - 批量操作完成后自动退出选择模式并刷新列表 * fix: complete route batch actions on mobile --------- Co-authored-by: Xiang Li <xiangli> Co-authored-by: cita-777 <juricek.chen@gmail.com>
This commit is contained in:
@@ -11,6 +11,7 @@ const { apiMock, getBrandMock } = vi.hoisted(() => ({
|
||||
getModelTokenCandidates: vi.fn(),
|
||||
getRouteDecisionsBatch: vi.fn(),
|
||||
getRouteWideDecisionsBatch: vi.fn(),
|
||||
batchUpdateRoutes: vi.fn(),
|
||||
updateRoute: vi.fn(),
|
||||
addRoute: vi.fn(),
|
||||
},
|
||||
@@ -67,6 +68,7 @@ async function flushMicrotasks() {
|
||||
|
||||
describe('TokenRoutes mobile actions', () => {
|
||||
const originalIntersectionObserver = globalThis.IntersectionObserver;
|
||||
const originalWindow = (globalThis as { window?: { confirm?: (message?: string) => boolean } }).window;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -115,13 +117,23 @@ describe('TokenRoutes mobile actions', () => {
|
||||
apiMock.getModelTokenCandidates.mockResolvedValue({ models: {} });
|
||||
apiMock.getRouteDecisionsBatch.mockResolvedValue({ decisions: {} });
|
||||
apiMock.getRouteWideDecisionsBatch.mockResolvedValue({ decisions: {} });
|
||||
apiMock.batchUpdateRoutes.mockResolvedValue({ success: true, updatedCount: 1 });
|
||||
apiMock.updateRoute.mockResolvedValue({});
|
||||
apiMock.addRoute.mockResolvedValue({});
|
||||
(globalThis as { window?: { confirm?: (message?: string) => boolean } }).window = {
|
||||
...(originalWindow || {}),
|
||||
confirm: vi.fn(() => true),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
globalThis.IntersectionObserver = originalIntersectionObserver;
|
||||
if (originalWindow) {
|
||||
(globalThis as { window?: { confirm?: (message?: string) => boolean } }).window = originalWindow;
|
||||
} else {
|
||||
delete (globalThis as { window?: { confirm?: (message?: string) => boolean } }).window;
|
||||
}
|
||||
});
|
||||
|
||||
it('shows mobile detail expansion and direct management actions', async () => {
|
||||
@@ -166,4 +178,50 @@ describe('TokenRoutes mobile actions', () => {
|
||||
root?.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('lets mobile users select a route and batch disable it', async () => {
|
||||
let root!: WebTestRenderer;
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
<MemoryRouter initialEntries={['/routes']}>
|
||||
<ToastProvider>
|
||||
<TokenRoutes />
|
||||
</ToastProvider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
const batchModeButton = findButtonByText(root!.root, '批量操作');
|
||||
await act(async () => {
|
||||
batchModeButton.props.onClick();
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
const routeCheckbox = root!.root.find((node) => (
|
||||
node.type === 'input'
|
||||
&& node.props.type === 'checkbox'
|
||||
&& node.props['data-testid'] === 'route-select-1'
|
||||
));
|
||||
|
||||
await act(async () => {
|
||||
routeCheckbox.props.onChange({ target: { checked: true } });
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
const batchDisableButton = findButtonByText(root!.root, '批量禁用');
|
||||
await act(async () => {
|
||||
batchDisableButton.props.onClick();
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(globalThis.window.confirm).toHaveBeenCalledWith('确认批量禁用 1 条路由?');
|
||||
expect(apiMock.batchUpdateRoutes).toHaveBeenCalledWith({ ids: [1], action: 'disable' });
|
||||
expect(collectText(root!.root)).toContain('批量操作');
|
||||
} finally {
|
||||
root?.unmount();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user