diff --git a/src/App.vue b/src/App.vue index e2c98bb3..1bd1927a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -419,6 +419,7 @@ const favoriteRequestGeneration = ref(0); const downloadedApps = ref([]); const downloadedLoading = ref(false); const downloadedError = ref(""); +const downloadedRequestGeneration = ref(0); const systemInfo = ref({ distro: "unknown" }); type PendingDownloadRecord = Omit< DownloadedAppRecord, @@ -1411,6 +1412,7 @@ const clearFavoriteState = () => { }; const clearDownloadedState = () => { + downloadedRequestGeneration.value += 1; downloadedApps.value = []; downloadedLoading.value = false; downloadedError.value = ""; @@ -1419,6 +1421,13 @@ const clearDownloadedState = () => { const isCurrentFavoriteRequest = (generation: number): boolean => favoriteRequestGeneration.value === generation && isLoggedIn.value; +const isCurrentDownloadedRequest = ( + generation: number, + userId: number, +): boolean => + downloadedRequestGeneration.value === generation && + currentUser.value?.id === userId; + const handleLogout = () => { logout(); pendingDownloadRecords.clear(); @@ -1455,17 +1464,24 @@ const handleFlarumLogin = async (payload: FlarumLoginPayload) => { const loadDownloadedHistory = async (): Promise => { if (!requireLogin("请登录后查看和管理账号信息。")) return; + const userId = currentUser.value?.id; + if (userId === undefined) return; + const generation = downloadedRequestGeneration.value; downloadedLoading.value = true; downloadedError.value = ""; try { const result = await listDownloadedApps(1, 50); + if (!isCurrentDownloadedRequest(generation, userId)) return; downloadedApps.value = result.items; } catch (error: unknown) { + if (!isCurrentDownloadedRequest(generation, userId)) return; downloadedApps.value = []; downloadedError.value = (error as Error)?.message || "读取下载历史失败"; } finally { - downloadedLoading.value = false; + if (isCurrentDownloadedRequest(generation, userId)) { + downloadedLoading.value = false; + } } }; diff --git a/src/__tests__/unit/App.account-placeholders.test.ts b/src/__tests__/unit/App.account-placeholders.test.ts index 45c89532..bd683dcc 100644 --- a/src/__tests__/unit/App.account-placeholders.test.ts +++ b/src/__tests__/unit/App.account-placeholders.test.ts @@ -2,9 +2,13 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/vue"; import { beforeEach, describe, expect, it, vi } from "vitest"; import App from "@/App.vue"; -import { listFavoriteFolders } from "@/modules/backendApi"; +import { listDownloadedApps, listFavoriteFolders } from "@/modules/backendApi"; import { setAuthSession } from "@/global/authState"; -import type { FavoriteFolder, FavoriteItem } from "@/global/typedefinition"; +import type { + DownloadedAppList, + FavoriteFolder, + FavoriteItem, +} from "@/global/typedefinition"; const invoke = vi.fn(); @@ -39,6 +43,31 @@ const createDeferred = () => { return { promise, resolve }; }; +const downloadedList = ( + items: DownloadedAppList["items"], +): DownloadedAppList => ({ + items, + total: items.length, + page: 1, + pageSize: 50, +}); + +const setSecondUserSession = () => { + setAuthSession({ + accessToken: "backend-token-b", + tokenType: "bearer", + user: { + id: 2, + flarumUserId: "84", + username: "second", + displayName: "Second User", + avatarUrl: "https://bbs.spark-app.store/avatar-b.png", + forumLevel: "用户", + forumGroups: ["用户"], + }, + }); +}; + vi.mock("axios", () => { const get = vi.fn(async (url: string) => { if (url.includes("categories.json")) { @@ -109,6 +138,7 @@ vi.mock("@/modules/backendApi", () => ({ bulkDeleteFavoriteItems: vi.fn(), createFavoriteFolder: vi.fn(), exchangeFlarumToken: vi.fn(), + listDownloadedApps: vi.fn(async () => downloadedList([])), listFavoriteFolders: vi.fn(async () => favoriteFolders), listFavoriteItems: vi.fn(async () => favoriteItems), setBackendToken: vi.fn(), @@ -351,4 +381,60 @@ describe("App account placeholders", () => { expect(screen.queryByText("旧账号收藏夹")).toBeNull(); expect(screen.queryByRole("dialog", { name: "选择收藏夹" })).toBeNull(); }); + + it("ignores downloaded history that resolves after switching users", async () => { + const firstHistory = createDeferred(); + const secondHistory = createDeferred(); + vi.mocked(listDownloadedApps) + .mockReturnValueOnce(firstHistory.promise) + .mockReturnValueOnce(secondHistory.promise); + render(App); + + await fireEvent.click(await screen.findByRole("button", { name: /Momen/ })); + await fireEvent.click(screen.getByText("用户管理")); + expect(await screen.findByText("正在加载下载历史...")).toBeTruthy(); + + await fireEvent.click( + await screen.findByRole("button", { name: /^Momen$/ }), + ); + if (!screen.queryByText("退出登录")) { + await fireEvent.click( + await screen.findByRole("button", { name: /^Momen$/ }), + ); + } + await fireEvent.click(screen.getByText("退出登录")); + setSecondUserSession(); + + const secondUserButton = await screen.findByRole("button", { + name: /^Second User$/, + }); + if (!screen.queryByText("用户管理")) { + await fireEvent.click(secondUserButton); + } + await fireEvent.click(await screen.findByText("用户管理")); + secondHistory.resolve(downloadedList([])); + expect(await screen.findByText("暂无下载记录。")).toBeTruthy(); + + firstHistory.resolve( + downloadedList([ + { + id: 77, + appKey: "app:office:old-account-app", + pkgname: "old-account-app", + name: "旧账号应用", + category: "office", + selectedOrigin: "apm", + version: "1.0.0", + packageArch: "amd64", + downloadedAt: "2026-05-18T00:00:00Z", + }, + ]), + ); + await firstHistory.promise; + await Promise.resolve(); + await Promise.resolve(); + + expect(screen.queryByText("旧账号应用")).toBeNull(); + expect(screen.getByText("暂无下载记录。")).toBeTruthy(); + }); });