mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-06-22 14:13:49 +08:00
fix(account): gate reviews and stale refreshes
This commit is contained in:
+13
-3
@@ -1464,6 +1464,16 @@ const clearRestoreState = () => {
|
||||
showRestoreModal.value = false;
|
||||
};
|
||||
|
||||
const nextFavoriteRequestGeneration = (): number => {
|
||||
favoriteRequestGeneration.value += 1;
|
||||
return favoriteRequestGeneration.value;
|
||||
};
|
||||
|
||||
const nextDownloadedRequestGeneration = (): number => {
|
||||
downloadedRequestGeneration.value += 1;
|
||||
return downloadedRequestGeneration.value;
|
||||
};
|
||||
|
||||
const isCurrentFavoriteRequest = (generation: number): boolean =>
|
||||
favoriteRequestGeneration.value === generation && isLoggedIn.value;
|
||||
|
||||
@@ -1522,7 +1532,7 @@ const loadDownloadedHistory = async (): Promise<void> => {
|
||||
if (!requireLogin("请登录后查看和管理账号信息。")) return;
|
||||
const userId = currentUser.value?.id;
|
||||
if (userId === undefined) return;
|
||||
const generation = downloadedRequestGeneration.value;
|
||||
const generation = nextDownloadedRequestGeneration();
|
||||
|
||||
downloadedLoading.value = true;
|
||||
downloadedError.value = "";
|
||||
@@ -1741,7 +1751,7 @@ const loadActiveFavoriteItems = async (
|
||||
};
|
||||
|
||||
const refreshFavorites = async (): Promise<void> => {
|
||||
const generation = favoriteRequestGeneration.value;
|
||||
const generation = nextFavoriteRequestGeneration();
|
||||
favoriteLoading.value = true;
|
||||
favoriteError.value = "";
|
||||
try {
|
||||
@@ -1806,7 +1816,7 @@ const openFavoriteManagement = async () => {
|
||||
};
|
||||
|
||||
const selectFavoriteFolder = async (folderId: number) => {
|
||||
const generation = favoriteRequestGeneration.value;
|
||||
const generation = nextFavoriteRequestGeneration();
|
||||
activeFavoriteFolderId.value = folderId;
|
||||
favoriteLoading.value = true;
|
||||
favoriteError.value = "";
|
||||
|
||||
@@ -462,6 +462,108 @@ describe("App account placeholders", () => {
|
||||
expect(screen.getByText("暂无下载记录。")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("ignores older downloaded history refreshes for the same user", async () => {
|
||||
const firstHistory = createDeferred<DownloadedAppList>();
|
||||
const secondHistory = createDeferred<DownloadedAppList>();
|
||||
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("用户管理"));
|
||||
await fireEvent.click(screen.getByRole("button", { name: "刷新" }));
|
||||
|
||||
secondHistory.resolve(
|
||||
downloadedList([
|
||||
{
|
||||
id: 88,
|
||||
appKey: "app:office:new-app",
|
||||
pkgname: "new-app",
|
||||
name: "新下载应用",
|
||||
category: "office",
|
||||
selectedOrigin: "apm",
|
||||
version: "2.0.0",
|
||||
packageArch: "amd64",
|
||||
downloadedAt: "2026-05-18T00:00:00Z",
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(await screen.findByText("新下载应用")).toBeTruthy();
|
||||
|
||||
firstHistory.resolve(
|
||||
downloadedList([
|
||||
{
|
||||
id: 77,
|
||||
appKey: "app:office:old-app",
|
||||
pkgname: "old-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();
|
||||
});
|
||||
|
||||
it("ignores older favorite folder refreshes for the same user", async () => {
|
||||
const firstFolders = createDeferred<FavoriteFolder[]>();
|
||||
const secondFolders = createDeferred<FavoriteFolder[]>();
|
||||
vi.mocked(listFavoriteFolders)
|
||||
.mockReturnValueOnce(firstFolders.promise)
|
||||
.mockReturnValueOnce(secondFolders.promise);
|
||||
render(App);
|
||||
|
||||
await fireEvent.click(
|
||||
await screen.findByRole("button", { name: /^Momen$/ }),
|
||||
);
|
||||
await fireEvent.click(screen.getByRole("button", { name: "我的收藏" }));
|
||||
await fireEvent.click(
|
||||
await screen.findByRole("button", { name: /^Momen$/ }),
|
||||
);
|
||||
if (!screen.queryByRole("button", { name: "我的收藏" })) {
|
||||
await fireEvent.click(
|
||||
await screen.findByRole("button", { name: /^Momen$/ }),
|
||||
);
|
||||
}
|
||||
await fireEvent.click(screen.getByRole("button", { name: "我的收藏" }));
|
||||
|
||||
secondFolders.resolve([
|
||||
{
|
||||
id: 42,
|
||||
name: "新收藏夹",
|
||||
itemCount: 0,
|
||||
createdAt: "2026-05-18T00:00:00Z",
|
||||
updatedAt: "2026-05-18T00:00:00Z",
|
||||
},
|
||||
]);
|
||||
expect(await screen.findByText("新收藏夹 (0)")).toBeTruthy();
|
||||
|
||||
firstFolders.resolve([
|
||||
{
|
||||
id: 41,
|
||||
name: "旧收藏夹",
|
||||
itemCount: 0,
|
||||
createdAt: "2026-05-18T00:00:00Z",
|
||||
updatedAt: "2026-05-18T00:00:00Z",
|
||||
},
|
||||
]);
|
||||
await firstFolders.promise;
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(screen.queryByText("旧收藏夹 (0)")).toBeNull();
|
||||
expect(screen.getByText("新收藏夹 (0)")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("restores cloud apps by origin and package when category changed", async () => {
|
||||
vi.mocked(fetchSyncedAppList).mockResolvedValueOnce({
|
||||
snapshotName: "默认列表",
|
||||
|
||||
@@ -91,6 +91,28 @@ describe("AppDetailPage", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("gates reviews for anonymous users", async () => {
|
||||
const rendered = render(AppDetailPage, {
|
||||
props: {
|
||||
app,
|
||||
screenshots: [],
|
||||
sparkInstalled: false,
|
||||
apmInstalled: false,
|
||||
loggedIn: false,
|
||||
reviewAppKey: "apm:amd64-apm:office:wps",
|
||||
reviewTags: sparkTags,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId("reviews-panel")).toBeNull();
|
||||
await fireEvent.click(
|
||||
screen.getByRole("button", { name: "登录后查看评价" }),
|
||||
);
|
||||
expect(rendered.emitted("request-login")?.[0]?.[0]).toBe(
|
||||
"登录后查看和发表评论。",
|
||||
);
|
||||
});
|
||||
|
||||
it("updates review identity when switching a merged app origin", async () => {
|
||||
render(AppDetailPage, {
|
||||
props: {
|
||||
@@ -98,7 +120,7 @@ describe("AppDetailPage", () => {
|
||||
screenshots: [],
|
||||
sparkInstalled: false,
|
||||
apmInstalled: false,
|
||||
loggedIn: false,
|
||||
loggedIn: true,
|
||||
reviewAppKey: "spark:amd64-store:office:wps",
|
||||
reviewTags: sparkTags,
|
||||
},
|
||||
|
||||
@@ -52,6 +52,8 @@ describe("ReviewsPanel", () => {
|
||||
expect(screen.getByText("登录后发表评论")).toBeTruthy();
|
||||
expect(screen.getByText("1.0.0")).toBeTruthy();
|
||||
expect(screen.getByText("deepin 25")).toBeTruthy();
|
||||
expect(fetchRatingSummary).not.toHaveBeenCalled();
|
||||
expect(fetchReviews).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores stale review responses after app key changes", async () => {
|
||||
@@ -84,10 +86,10 @@ describe("ReviewsPanel", () => {
|
||||
);
|
||||
|
||||
const rendered = render(ReviewsPanel, {
|
||||
props: { appKey: "first", tags, loggedIn: false },
|
||||
props: { appKey: "first", tags, loggedIn: true },
|
||||
});
|
||||
|
||||
await rendered.rerender({ appKey: "second", tags, loggedIn: false });
|
||||
await rendered.rerender({ appKey: "second", tags, loggedIn: true });
|
||||
|
||||
resolveSecondSummary({ averageRating: 5, reviewCount: 1, starCounts: {} });
|
||||
resolveSecondReviews([
|
||||
|
||||
@@ -196,12 +196,31 @@
|
||||
</div>
|
||||
|
||||
<ReviewsPanel
|
||||
v-if="reviewAppKey && reviewTags"
|
||||
v-if="loggedIn && reviewAppKey && reviewTags"
|
||||
:app-key="reviewAppKey"
|
||||
:tags="reviewTags"
|
||||
:logged-in="loggedIn"
|
||||
@request-login="$emit('request-login', $event)"
|
||||
/>
|
||||
<section
|
||||
v-else-if="reviewAppKey && reviewTags"
|
||||
class="rounded-2xl border border-slate-200/60 bg-slate-50/50 p-5 dark:border-slate-800/60 dark:bg-slate-800/30"
|
||||
>
|
||||
<h2 class="mb-2 flex items-center gap-2 text-base font-semibold">
|
||||
<i class="fas fa-comments text-slate-400"></i>
|
||||
应用评价
|
||||
</h2>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||||
登录星火账号后可查看评价并发表评论。
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="mt-4 inline-flex items-center rounded-xl bg-slate-800 px-4 py-2 text-sm font-medium text-white transition hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600"
|
||||
@click="emit('request-login', '登录后查看和发表评论。')"
|
||||
>
|
||||
登录后查看评价
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -152,8 +152,19 @@ const ratingText = computed(() => {
|
||||
return `${summary.value.averageRating.toFixed(1)} / 5 (${summary.value.reviewCount})`;
|
||||
});
|
||||
|
||||
const clearReviewState = () => {
|
||||
loadGeneration.value += 1;
|
||||
reviews.value = [];
|
||||
summary.value = null;
|
||||
loading.value = false;
|
||||
error.value = "";
|
||||
};
|
||||
|
||||
const loadReviews = async () => {
|
||||
if (!props.appKey) return;
|
||||
if (!props.loggedIn || !props.appKey) {
|
||||
clearReviewState();
|
||||
return;
|
||||
}
|
||||
const generation = loadGeneration.value + 1;
|
||||
loadGeneration.value = generation;
|
||||
const appKey = props.appKey;
|
||||
@@ -202,5 +213,5 @@ const submit = async () => {
|
||||
};
|
||||
|
||||
onMounted(loadReviews);
|
||||
watch(() => props.appKey, loadReviews);
|
||||
watch(() => [props.appKey, props.loggedIn], loadReviews);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user