mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-06-22 14:13:49 +08:00
fix(reviews): restore modal detail review gating
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/vue";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import AppDetailModal from "@/components/AppDetailModal.vue";
|
||||
import type { App, ReviewTags } from "@/global/typedefinition";
|
||||
|
||||
vi.mock("axios", () => ({
|
||||
default: {
|
||||
get: vi.fn(async () => ({ status: 200, data: "42" })),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/ReviewsPanel.vue", () => ({
|
||||
default: {
|
||||
name: "ReviewsPanel",
|
||||
props: ["appKey", "tags", "loggedIn", "canSubmit"],
|
||||
template:
|
||||
'<div data-testid="reviews-panel" :data-app-key="appKey" :data-origin="tags.origin" :data-version="tags.version" :data-can-submit="String(canSubmit)"></div>',
|
||||
},
|
||||
}));
|
||||
|
||||
const app: App = {
|
||||
name: "WPS",
|
||||
pkgname: "wps",
|
||||
version: "1.0.0",
|
||||
filename: "wps_1.0.0_amd64.deb",
|
||||
torrent_address: "",
|
||||
author: "",
|
||||
contributor: "",
|
||||
website: "",
|
||||
update: "",
|
||||
size: "110M",
|
||||
more: "Office suite",
|
||||
tags: "office",
|
||||
img_urls: [],
|
||||
icons: "",
|
||||
category: "office",
|
||||
origin: "apm",
|
||||
currentStatus: "not-installed",
|
||||
};
|
||||
|
||||
const sparkApp: App = {
|
||||
...app,
|
||||
name: "WPS Spark",
|
||||
version: "2.0.0",
|
||||
filename: "wps_2.0.0_amd64.deb",
|
||||
origin: "spark",
|
||||
};
|
||||
|
||||
const apmApp: App = {
|
||||
...app,
|
||||
name: "WPS APM",
|
||||
origin: "apm",
|
||||
};
|
||||
|
||||
const mergedApp: App = {
|
||||
...sparkApp,
|
||||
isMerged: true,
|
||||
sparkApp,
|
||||
apmApp,
|
||||
viewingOrigin: "spark",
|
||||
};
|
||||
|
||||
const sparkTags: ReviewTags = {
|
||||
origin: "spark",
|
||||
category: "office",
|
||||
pkgname: "wps",
|
||||
version: "2.0.0",
|
||||
packageArch: "amd64",
|
||||
clientArch: "amd64",
|
||||
distro: "deepin 25",
|
||||
};
|
||||
|
||||
describe("AppDetailModal", () => {
|
||||
beforeEach(() => {
|
||||
window.apm_store.arch = "amd64";
|
||||
});
|
||||
|
||||
it("renders detail content inside a popup-style modal overlay", () => {
|
||||
const { container } = render(AppDetailModal, {
|
||||
attrs: { "data-app-modal": "detail" },
|
||||
props: {
|
||||
show: true,
|
||||
app,
|
||||
screenshots: [],
|
||||
sparkInstalled: false,
|
||||
apmInstalled: false,
|
||||
loggedIn: false,
|
||||
reviewAppKey: "apm:amd64-apm:office:wps",
|
||||
reviewTags: sparkTags,
|
||||
},
|
||||
});
|
||||
|
||||
const overlay = container.querySelector('[data-app-modal="detail"]');
|
||||
expect(overlay).toBeTruthy();
|
||||
expect(overlay?.className).toContain("fixed");
|
||||
expect(overlay?.querySelector(".modal-panel")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("updates review identity when switching a merged app origin", async () => {
|
||||
render(AppDetailModal, {
|
||||
props: {
|
||||
show: true,
|
||||
app: mergedApp,
|
||||
screenshots: [],
|
||||
sparkInstalled: true,
|
||||
apmInstalled: true,
|
||||
loggedIn: true,
|
||||
reviewAppKey: "spark:amd64-store:office:wps",
|
||||
reviewTags: sparkTags,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.getByTestId("reviews-panel")).toHaveAttribute(
|
||||
"data-app-key",
|
||||
"spark:amd64-store:office:wps",
|
||||
);
|
||||
|
||||
await fireEvent.click(screen.getByRole("button", { name: "APM" }));
|
||||
|
||||
expect(screen.getByTestId("reviews-panel")).toHaveAttribute(
|
||||
"data-app-key",
|
||||
"apm:amd64-apm:office:wps",
|
||||
);
|
||||
expect(screen.getByTestId("reviews-panel")).toHaveAttribute(
|
||||
"data-origin",
|
||||
"apm",
|
||||
);
|
||||
expect(screen.getByTestId("reviews-panel")).toHaveAttribute(
|
||||
"data-version",
|
||||
"1.0.0",
|
||||
);
|
||||
});
|
||||
|
||||
it("marks reviews read-only when the selected origin is not installed", () => {
|
||||
render(AppDetailModal, {
|
||||
props: {
|
||||
show: true,
|
||||
app,
|
||||
screenshots: [],
|
||||
sparkInstalled: false,
|
||||
apmInstalled: false,
|
||||
loggedIn: true,
|
||||
reviewAppKey: "apm:amd64-apm:office:wps",
|
||||
reviewTags: sparkTags,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.getByTestId("reviews-panel")).toHaveAttribute(
|
||||
"data-can-submit",
|
||||
"false",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -7,9 +7,9 @@ import type { App, ReviewTags } from "@/global/typedefinition";
|
||||
vi.mock("@/components/ReviewsPanel.vue", () => ({
|
||||
default: {
|
||||
name: "ReviewsPanel",
|
||||
props: ["appKey", "tags", "loggedIn"],
|
||||
props: ["appKey", "tags", "loggedIn", "canSubmit"],
|
||||
template:
|
||||
'<div data-testid="reviews-panel" :data-app-key="appKey" :data-origin="tags.origin" :data-version="tags.version"></div>',
|
||||
'<div data-testid="reviews-panel" :data-app-key="appKey" :data-origin="tags.origin" :data-version="tags.version" :data-can-submit="String(canSubmit)"></div>',
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -118,8 +118,8 @@ describe("AppDetailPage", () => {
|
||||
props: {
|
||||
app: mergedApp,
|
||||
screenshots: [],
|
||||
sparkInstalled: false,
|
||||
apmInstalled: false,
|
||||
sparkInstalled: true,
|
||||
apmInstalled: true,
|
||||
loggedIn: true,
|
||||
reviewAppKey: "spark:amd64-store:office:wps",
|
||||
reviewTags: sparkTags,
|
||||
@@ -150,4 +150,23 @@ describe("AppDetailPage", () => {
|
||||
"1.0.0",
|
||||
);
|
||||
});
|
||||
|
||||
it("marks reviews read-only when the selected origin is not installed", () => {
|
||||
render(AppDetailPage, {
|
||||
props: {
|
||||
app,
|
||||
screenshots: [],
|
||||
sparkInstalled: false,
|
||||
apmInstalled: false,
|
||||
loggedIn: true,
|
||||
reviewAppKey: "apm:amd64-apm:office:wps",
|
||||
reviewTags: sparkTags,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.getByTestId("reviews-panel")).toHaveAttribute(
|
||||
"data-can-submit",
|
||||
"false",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, screen } from "@testing-library/vue";
|
||||
import { fireEvent, render, screen } from "@testing-library/vue";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import ReviewsPanel from "@/components/ReviewsPanel.vue";
|
||||
@@ -56,6 +56,20 @@ describe("ReviewsPanel", () => {
|
||||
expect(fetchReviews).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("hides the submit form when reviews are read-only", () => {
|
||||
render(ReviewsPanel, {
|
||||
props: {
|
||||
appKey: "apm:amd64-apm:office:wps",
|
||||
tags,
|
||||
loggedIn: true,
|
||||
canSubmit: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.queryByRole("button", { name: "发表评论" })).toBeNull();
|
||||
expect(screen.getByText("安装应用后可发表评论。")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("ignores stale review responses after app key changes", async () => {
|
||||
let resolveFirstSummary!: (summary: RatingSummary) => void;
|
||||
let resolveFirstReviews!: (reviews: AppReview[]) => void;
|
||||
@@ -139,4 +153,21 @@ describe("ReviewsPanel", () => {
|
||||
expect(screen.queryByText("first review")).toBeNull();
|
||||
expect(screen.queryByText("1.0 / 5 (1)")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows a friendly submit error instead of raw network errors", async () => {
|
||||
vi.mocked(submitReview).mockRejectedValueOnce(new Error("Network Error"));
|
||||
render(ReviewsPanel, {
|
||||
props: { appKey: "apm:amd64-apm:office:wps", tags, loggedIn: true },
|
||||
});
|
||||
|
||||
await fireEvent.update(
|
||||
screen.getByPlaceholderText("分享你的使用体验"),
|
||||
"好用",
|
||||
);
|
||||
await fireEvent.click(screen.getByRole("button", { name: "发表评论" }));
|
||||
|
||||
expect(
|
||||
await screen.findByText("无法连接星火账号服务,请稍后重试。"),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from "axios";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { exchangeFlarumToken } from "@/modules/backendApi";
|
||||
import { exchangeFlarumToken, submitReview } from "@/modules/backendApi";
|
||||
|
||||
const axiosMocks = vi.hoisted(() => {
|
||||
const post = vi.fn();
|
||||
@@ -57,18 +57,48 @@ describe("backend API auth exchange", () => {
|
||||
},
|
||||
"Spark backend auth exchange failed",
|
||||
);
|
||||
expect(JSON.stringify(loggerMocks.error.mock.calls)).not.toContain("forum-token");
|
||||
expect(JSON.stringify(loggerMocks.error.mock.calls)).not.toContain(
|
||||
"forum-token",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps backend server failures to an update-required login error", async () => {
|
||||
const error = Object.assign(new Error("Request failed with status code 500"), {
|
||||
isAxiosError: true,
|
||||
response: { status: 500 },
|
||||
});
|
||||
const error = Object.assign(
|
||||
new Error("Request failed with status code 500"),
|
||||
{
|
||||
isAxiosError: true,
|
||||
response: { status: 500 },
|
||||
},
|
||||
);
|
||||
axiosMocks.post.mockRejectedValue(error);
|
||||
|
||||
await expect(
|
||||
exchangeFlarumToken({ flarumUserId: "42", flarumToken: "forum-token" }),
|
||||
).rejects.toThrow("星火账号服务异常,请确认后端数据库迁移已执行后重试。");
|
||||
});
|
||||
|
||||
it("maps review submission connection failures to a friendly error", async () => {
|
||||
const error = Object.assign(new Error("Network Error"), {
|
||||
code: "ERR_NETWORK",
|
||||
isAxiosError: true,
|
||||
request: {},
|
||||
});
|
||||
axiosMocks.post.mockRejectedValue(error);
|
||||
|
||||
await expect(
|
||||
submitReview("apm:amd64-apm:office:wps", {
|
||||
rating: 5,
|
||||
content: "好用",
|
||||
tags: {
|
||||
origin: "apm",
|
||||
category: "office",
|
||||
pkgname: "wps",
|
||||
version: "1.0.0",
|
||||
packageArch: "amd64",
|
||||
clientArch: "amd64",
|
||||
distro: "deepin 25",
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow("无法连接星火账号服务,请稍后重试。");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user