mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-06-22 22:23:49 +08:00
fix(favorites): honor source priority and installed state
This commit is contained in:
+1
-1
@@ -1274,7 +1274,7 @@ const refreshFavorites = async (): Promise<void> => {
|
|||||||
favoriteLoading.value = true;
|
favoriteLoading.value = true;
|
||||||
favoriteError.value = "";
|
favoriteError.value = "";
|
||||||
try {
|
try {
|
||||||
await loadFavoriteFolders();
|
await Promise.all([refreshInstalledApps(), loadFavoriteFolders()]);
|
||||||
await loadActiveFavoriteItems();
|
await loadActiveFavoriteItems();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
favoriteError.value = (error as Error)?.message || "读取收藏夹失败";
|
favoriteError.value = (error as Error)?.message || "读取收藏夹失败";
|
||||||
|
|||||||
@@ -3,12 +3,59 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|||||||
|
|
||||||
import App from "@/App.vue";
|
import App from "@/App.vue";
|
||||||
import { setAuthSession } from "@/global/authState";
|
import { setAuthSession } from "@/global/authState";
|
||||||
|
import type { FavoriteFolder, FavoriteItem } from "@/global/typedefinition";
|
||||||
|
|
||||||
const invoke = vi.fn();
|
const invoke = vi.fn();
|
||||||
|
|
||||||
|
const favoriteFolders: FavoriteFolder[] = [
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "默认收藏夹",
|
||||||
|
itemCount: 1,
|
||||||
|
createdAt: "2026-05-18T00:00:00Z",
|
||||||
|
updatedAt: "2026-05-18T00:00:00Z",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const favoriteItems: FavoriteItem[] = [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
appKey: "app:office:wps",
|
||||||
|
pkgname: "wps",
|
||||||
|
name: "WPS",
|
||||||
|
category: "office",
|
||||||
|
iconUrl: "",
|
||||||
|
createdAt: "2026-05-18T00:00:00Z",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
vi.mock("axios", () => {
|
vi.mock("axios", () => {
|
||||||
const get = vi.fn(async (url: string) => {
|
const get = vi.fn(async (url: string) => {
|
||||||
if (url.includes("categories.json")) return { data: {} };
|
if (url.includes("categories.json")) {
|
||||||
|
return { data: { office: { zh: "办公" } } };
|
||||||
|
}
|
||||||
|
if (url.includes("/office/applist.json")) {
|
||||||
|
return {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
Name: "WPS",
|
||||||
|
Pkgname: "wps",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Filename: "wps_1.0.0_amd64.deb",
|
||||||
|
Torrent_address: "",
|
||||||
|
Author: "",
|
||||||
|
Contributor: "",
|
||||||
|
Website: "",
|
||||||
|
Update: "",
|
||||||
|
Size: "",
|
||||||
|
More: "",
|
||||||
|
Tags: "",
|
||||||
|
img_urls: [],
|
||||||
|
icons: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
return { data: [] };
|
return { data: [] };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -47,6 +94,16 @@ vi.mock("@/modules/updateCenter", () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/modules/backendApi", () => ({
|
||||||
|
addFavoriteItem: vi.fn(),
|
||||||
|
bulkDeleteFavoriteItems: vi.fn(),
|
||||||
|
createFavoriteFolder: vi.fn(),
|
||||||
|
exchangeFlarumToken: vi.fn(),
|
||||||
|
listFavoriteFolders: vi.fn(async () => favoriteFolders),
|
||||||
|
listFavoriteItems: vi.fn(async () => favoriteItems),
|
||||||
|
setBackendToken: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe("App account placeholders", () => {
|
describe("App account placeholders", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
invoke.mockReset();
|
invoke.mockReset();
|
||||||
@@ -114,4 +171,42 @@ describe("App account placeholders", () => {
|
|||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(screen.queryByText("请登录后查看我的收藏。")).toBeNull();
|
expect(screen.queryByText("请登录后查看我的收藏。")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("refreshes installed apps before resolving favorite management state", async () => {
|
||||||
|
invoke.mockImplementation(async (channel: string) => {
|
||||||
|
if (channel === "get-store-filter") return "both";
|
||||||
|
if (channel === "check-spark-available") return true;
|
||||||
|
if (channel === "check-apm-available") return true;
|
||||||
|
if (channel === "get-app-version") return "5.0.0";
|
||||||
|
if (channel === "list-installed") {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
pkgname: "wps",
|
||||||
|
name: "WPS",
|
||||||
|
version: "1.0.0",
|
||||||
|
arch: "amd64",
|
||||||
|
flags: "installed",
|
||||||
|
origin: "apm",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
render(App);
|
||||||
|
|
||||||
|
await fireEvent.click(await screen.findByRole("button", { name: /Momen/ }));
|
||||||
|
await fireEvent.click(screen.getByText("我的收藏"));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByRole("heading", { name: "我的收藏" }),
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(await screen.findByText("已安装")).toBeTruthy();
|
||||||
|
expect(invoke).toHaveBeenCalledWith("list-installed", {
|
||||||
|
origin: "apm",
|
||||||
|
pkgnameList: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import { resolveFavoriteItems } from "@/modules/favoriteAvailability";
|
import { resolveFavoriteItems } from "@/modules/favoriteAvailability";
|
||||||
|
import { loadPriorityConfig } from "@/global/storeConfig";
|
||||||
import type { App, FavoriteItem } from "@/global/typedefinition";
|
import type { App, FavoriteItem } from "@/global/typedefinition";
|
||||||
|
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
|
||||||
const app = (origin: "spark" | "apm", overrides: Partial<App> = {}): App => ({
|
const app = (origin: "spark" | "apm", overrides: Partial<App> = {}): App => ({
|
||||||
name: "WPS",
|
name: "WPS",
|
||||||
pkgname: "wps",
|
pkgname: "wps",
|
||||||
@@ -36,6 +39,14 @@ const favorite: FavoriteItem = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe("favoriteAvailability", () => {
|
describe("favoriteAvailability", () => {
|
||||||
|
afterEach(async () => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: false } as Response);
|
||||||
|
await loadPriorityConfig("amd64");
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it("marks downlisted favorites", () => {
|
it("marks downlisted favorites", () => {
|
||||||
expect(
|
expect(
|
||||||
resolveFavoriteItems(
|
resolveFavoriteItems(
|
||||||
@@ -48,7 +59,16 @@ describe("favoriteAvailability", () => {
|
|||||||
).toBe("downlisted");
|
).toBe("downlisted");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("selects preferred installable variant", () => {
|
it("selects preferred installable variant", async () => {
|
||||||
|
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
sparkPriority: { pkgnames: [], categories: [], tags: [] },
|
||||||
|
apmPriority: { pkgnames: [], categories: [], tags: [] },
|
||||||
|
}),
|
||||||
|
} as Response);
|
||||||
|
await loadPriorityConfig("amd64");
|
||||||
|
|
||||||
const resolved = resolveFavoriteItems(
|
const resolved = resolveFavoriteItems(
|
||||||
[favorite],
|
[favorite],
|
||||||
[app("spark"), app("apm")],
|
[app("spark"), app("apm")],
|
||||||
@@ -60,6 +80,28 @@ describe("favoriteAvailability", () => {
|
|||||||
expect(resolved.selectedApp?.origin).toBe("apm");
|
expect(resolved.selectedApp?.origin).toBe("apm");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("selects Spark when hybrid priority config prefers Spark", async () => {
|
||||||
|
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
sparkPriority: { pkgnames: ["wps"], categories: [], tags: [] },
|
||||||
|
apmPriority: { pkgnames: [], categories: [], tags: [] },
|
||||||
|
}),
|
||||||
|
} as Response);
|
||||||
|
await loadPriorityConfig("amd64");
|
||||||
|
|
||||||
|
const resolved = resolveFavoriteItems(
|
||||||
|
[favorite],
|
||||||
|
[app("spark"), app("apm")],
|
||||||
|
[],
|
||||||
|
{ spark: true, apm: true },
|
||||||
|
"both",
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
expect(resolved.status).toBe("installable");
|
||||||
|
expect(resolved.selectedApp?.origin).toBe("spark");
|
||||||
|
});
|
||||||
|
|
||||||
it("marks installed favorites", () => {
|
it("marks installed favorites", () => {
|
||||||
const resolved = resolveFavoriteItems(
|
const resolved = resolveFavoriteItems(
|
||||||
[favorite],
|
[favorite],
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import {
|
import { getHybridDefaultOrigin } from "@/global/storeConfig";
|
||||||
HYBRID_DEFAULT_PRIORITY,
|
|
||||||
getHybridDefaultOrigin,
|
|
||||||
} from "@/global/storeConfig";
|
|
||||||
import type {
|
import type {
|
||||||
App,
|
App,
|
||||||
FavoriteItem,
|
FavoriteItem,
|
||||||
@@ -20,6 +17,10 @@ const normalizeArch = (arch: string): string =>
|
|||||||
const appMatchesFavorite = (app: App, item: FavoriteItem): boolean =>
|
const appMatchesFavorite = (app: App, item: FavoriteItem): boolean =>
|
||||||
app.pkgname === item.pkgname && app.category === item.category;
|
app.pkgname === item.pkgname && app.category === item.category;
|
||||||
|
|
||||||
|
const installedAppMatchesFavorite = (app: App, item: FavoriteItem): boolean =>
|
||||||
|
app.pkgname === item.pkgname &&
|
||||||
|
(app.category === item.category || app.category === "unknown");
|
||||||
|
|
||||||
const appMatchesClientArch = (app: App, clientArch: string): boolean => {
|
const appMatchesClientArch = (app: App, clientArch: string): boolean => {
|
||||||
if (!app.arch) return true;
|
if (!app.arch) return true;
|
||||||
return normalizeArch(app.arch) === normalizeArch(clientArch);
|
return normalizeArch(app.arch) === normalizeArch(clientArch);
|
||||||
@@ -39,10 +40,7 @@ const choosePreferredApp = (apps: App[]): App => {
|
|||||||
if (apps.length === 1) return apps[0];
|
if (apps.length === 1) return apps[0];
|
||||||
|
|
||||||
const referenceApp = apps.find((app) => app.origin === "spark") ?? apps[0];
|
const referenceApp = apps.find((app) => app.origin === "spark") ?? apps[0];
|
||||||
const preferredOrigin =
|
const preferredOrigin = getHybridDefaultOrigin(referenceApp);
|
||||||
getHybridDefaultOrigin(referenceApp) === "spark"
|
|
||||||
? HYBRID_DEFAULT_PRIORITY
|
|
||||||
: getHybridDefaultOrigin(referenceApp);
|
|
||||||
return apps.find((app) => app.origin === preferredOrigin) ?? apps[0];
|
return apps.find((app) => app.origin === preferredOrigin) ?? apps[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,7 +67,7 @@ export const resolveFavoriteItems = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const installedMatch = installedApps.find((app) =>
|
const installedMatch = installedApps.find((app) =>
|
||||||
appMatchesFavorite(app, item),
|
installedAppMatchesFavorite(app, item),
|
||||||
);
|
);
|
||||||
if (installedMatch) {
|
if (installedMatch) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user