修复项目工程恢复默认顺序失败后的分页状态
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled

远端第一页读取成功后统一切换排序模式与分页游标
补充恢复失败保留本地分页及重试成功的回归测试
同步后台项目工程恢复默认顺序的技术约定
This commit is contained in:
2026-09-30 08:12:41 +01:00
parent 3540c5660f
commit 9f6d60e6fd
3 changed files with 72 additions and 7 deletions
@@ -145,6 +145,63 @@ test('全量读取后按同步时间和身份排序,本地分页并支持刷
);
});
test('恢复默认顺序失败保留排序分页,重试成功后切换远端第一页及游标', async () => {
const projects = Array.from({ length: 45 }, (_, index) => ({
...entry,
projectId: `sorted-${index}`,
projectName: `排序项目 ${index}`,
syncedAtMs: entry.syncedAtMs - index,
}));
vi.mocked(listAdminProjectSnapshots)
.mockResolvedValueOnce({ items: [entry], nextCursor: null })
.mockResolvedValueOnce({ items: projects, nextCursor: null })
.mockRejectedValueOnce(new Error('恢复失败'))
.mockResolvedValueOnce({
items: [{ ...entry, projectName: '默认第一页' }],
nextCursor: 'default-next',
})
.mockResolvedValueOnce({
items: [{ ...entry, projectName: '默认第二页' }],
nextCursor: null,
});
render(<AdminProjectSnapshotsPage token="token" onUnauthorized={vi.fn()} />);
await screen.findByText('三消工程');
fireEvent.click(screen.getByRole('button', { name: '按同步时间排序' }));
await screen.findByText('排序项目 0');
fireEvent.click(screen.getByRole('button', { name: '下一页' }));
fireEvent.click(screen.getByRole('button', { name: '恢复默认顺序' }));
await screen.findByText('恢复失败');
expect(screen.getByText('排序项目 20')).toBeTruthy();
expect(screen.getByRole('navigation').textContent).toContain('第 2 页');
expect(screen.getByRole('navigation').textContent).toContain('共 45 个');
expect(screen.getByRole('button', { name: '恢复默认顺序' })).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '下一页' }));
expect(screen.getByText('排序项目 40')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '上一页' }));
expect(screen.getByText('排序项目 20')).toBeTruthy();
expect(listAdminProjectSnapshots).toHaveBeenCalledTimes(3);
fireEvent.click(screen.getByRole('button', { name: '恢复默认顺序' }));
await screen.findByText('默认第一页');
expect(screen.getByRole('navigation').textContent).toContain('第 1 页');
expect(screen.getByRole('button', { name: '按同步时间排序' })).toBeTruthy();
expect(listAdminProjectSnapshots).toHaveBeenNthCalledWith(
4,
'token',
{ cursor: null, limit: 20, channel: 'dev' },
expect.any(AbortSignal),
);
fireEvent.click(screen.getByRole('button', { name: '下一页' }));
await screen.findByText('默认第二页');
expect(screen.getByRole('navigation').textContent).toContain('第 2 页');
expect(listAdminProjectSnapshots).toHaveBeenNthCalledWith(
5,
'token',
{ cursor: 'default-next', limit: 20, channel: 'dev' },
expect.any(AbortSignal),
);
});
test('全量排序中途失败不发布部分结果,重试刷新失败保留完整排序', async () => {
vi.mocked(listAdminProjectSnapshots)
.mockResolvedValueOnce({ items: [entry], nextCursor: 'original-next' })
@@ -81,8 +81,13 @@ export function AdminProjectSnapshotsPage({
const sortedItems = useRef<AdminProjectSnapshotEntry[] | null>(null);
const loadPage = useCallback(
async (cursor: string | null, limit: number, page: number) => {
if (sortedItems.current !== null) {
async (
cursor: string | null,
limit: number,
page: number,
restoreDefault = false,
) => {
if (!restoreDefault && sortedItems.current !== null) {
setItems(sortedItems.current.slice((page - 1) * limit, page * limit));
setPageIndex(page);
setHasLoaded(true);
@@ -101,6 +106,12 @@ export function AdminProjectSnapshotsPage({
controller.signal,
);
if (controller.signal.aborted) return;
// 远端第一页读取成功后再切换模式,失败时保留完整排序和分页。
if (restoreDefault) {
sortedItems.current = null;
setIsSorted(false);
pageCursors.current = [null];
}
setItems(response.items);
setNextCursor(response.nextCursor);
setPageIndex(page);
@@ -278,10 +289,7 @@ export function AdminProjectSnapshotsPage({
}
function restoreDefaultOrder() {
sortedItems.current = null;
setIsSorted(false);
pageCursors.current = [null];
void loadPage(null, pageSize, 1);
void loadPage(null, pageSize, 1, true);
}
async function downloadProject(entry: AdminProjectSnapshotEntry) {