修复项目工程恢复默认顺序失败后的分页状态
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
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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user