mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-06-22 14:13:49 +08:00
fix(sources): hide unavailable update and management entries
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { render, screen } from "@testing-library/vue";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import AppSidebar from "@/components/AppSidebar.vue";
|
||||
|
||||
const renderSidebar = (
|
||||
overrides: Partial<InstanceType<typeof AppSidebar>["$props"]> = {},
|
||||
) => {
|
||||
return render(AppSidebar, {
|
||||
props: {
|
||||
categories: {},
|
||||
activeCategory: "all",
|
||||
categoryCounts: { all: 0 },
|
||||
themeMode: "auto",
|
||||
storeFilter: "both",
|
||||
sparkAvailable: true,
|
||||
apmAvailable: true,
|
||||
...overrides,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe("AppSidebar", () => {
|
||||
it("shows management and update entries when at least one source is usable", () => {
|
||||
renderSidebar({ sparkAvailable: true, apmAvailable: false });
|
||||
|
||||
expect(screen.getByText("应用管理")).toBeTruthy();
|
||||
expect(screen.getByText("软件更新")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("hides management and update entries when both sources are unavailable", () => {
|
||||
renderSidebar({ sparkAvailable: false, apmAvailable: false });
|
||||
|
||||
expect(screen.queryByText("应用管理")).toBeNull();
|
||||
expect(screen.queryByText("软件更新")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -35,6 +35,7 @@ describe("InstalledAppsModal", () => {
|
||||
error: "",
|
||||
activeOrigin: "spark",
|
||||
storeFilter: "both",
|
||||
sparkAvailable: true,
|
||||
apmAvailable: true,
|
||||
},
|
||||
});
|
||||
@@ -54,6 +55,7 @@ describe("InstalledAppsModal", () => {
|
||||
error: "",
|
||||
activeOrigin: "spark",
|
||||
storeFilter: "both",
|
||||
sparkAvailable: true,
|
||||
apmAvailable: true,
|
||||
},
|
||||
});
|
||||
@@ -71,6 +73,7 @@ describe("InstalledAppsModal", () => {
|
||||
error: "",
|
||||
activeOrigin: "spark",
|
||||
storeFilter: "both",
|
||||
sparkAvailable: true,
|
||||
apmAvailable: true,
|
||||
},
|
||||
});
|
||||
@@ -92,6 +95,7 @@ describe("InstalledAppsModal", () => {
|
||||
error: "",
|
||||
activeOrigin: "spark",
|
||||
storeFilter: "both",
|
||||
sparkAvailable: true,
|
||||
apmAvailable: true,
|
||||
},
|
||||
});
|
||||
@@ -113,6 +117,7 @@ describe("InstalledAppsModal", () => {
|
||||
error: "",
|
||||
activeOrigin: "spark",
|
||||
storeFilter: "both",
|
||||
sparkAvailable: true,
|
||||
apmAvailable: true,
|
||||
},
|
||||
});
|
||||
@@ -129,6 +134,7 @@ describe("InstalledAppsModal", () => {
|
||||
error: "",
|
||||
activeOrigin: "spark",
|
||||
storeFilter: "both",
|
||||
sparkAvailable: true,
|
||||
apmAvailable: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
getEffectiveStoreFilter,
|
||||
getAllowedInstalledOrigin,
|
||||
getDefaultInstalledOrigin,
|
||||
isOriginEnabled,
|
||||
isOriginUsable,
|
||||
} from "@/modules/storeFilter";
|
||||
|
||||
describe("storeFilter helpers", () => {
|
||||
it("reports whether an origin is enabled by the current store filter", () => {
|
||||
expect(isOriginEnabled("both", "spark")).toBe(true);
|
||||
expect(isOriginEnabled("both", "apm")).toBe(true);
|
||||
expect(isOriginEnabled("spark", "spark")).toBe(true);
|
||||
expect(isOriginEnabled("spark", "apm")).toBe(false);
|
||||
expect(isOriginEnabled("apm", "apm")).toBe(true);
|
||||
expect(isOriginEnabled("apm", "spark")).toBe(false);
|
||||
});
|
||||
|
||||
it("chooses the default installed origin from the active store filter", () => {
|
||||
expect(getDefaultInstalledOrigin("spark", { spark: true, apm: true })).toBe(
|
||||
"spark",
|
||||
);
|
||||
expect(getDefaultInstalledOrigin("apm", { spark: true, apm: true })).toBe(
|
||||
"apm",
|
||||
);
|
||||
expect(getDefaultInstalledOrigin("both", { spark: true, apm: true })).toBe(
|
||||
"apm",
|
||||
);
|
||||
expect(getDefaultInstalledOrigin("both", { spark: true, apm: false })).toBe(
|
||||
"spark",
|
||||
);
|
||||
expect(
|
||||
getDefaultInstalledOrigin("both", { spark: false, apm: false }),
|
||||
).toBe(null);
|
||||
});
|
||||
|
||||
it("redirects disallowed installed origins to an allowed one", () => {
|
||||
expect(
|
||||
getAllowedInstalledOrigin("spark", "apm", { spark: true, apm: true }),
|
||||
).toBe("spark");
|
||||
expect(
|
||||
getAllowedInstalledOrigin("apm", "spark", { spark: true, apm: true }),
|
||||
).toBe("apm");
|
||||
expect(
|
||||
getAllowedInstalledOrigin("both", "apm", { spark: true, apm: false }),
|
||||
).toBe("spark");
|
||||
expect(
|
||||
getAllowedInstalledOrigin("both", "spark", { spark: false, apm: false }),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("computes the effective runtime store filter from source availability", () => {
|
||||
expect(getEffectiveStoreFilter("both", { spark: true, apm: true })).toBe(
|
||||
"both",
|
||||
);
|
||||
expect(getEffectiveStoreFilter("both", { spark: true, apm: false })).toBe(
|
||||
"spark",
|
||||
);
|
||||
expect(getEffectiveStoreFilter("both", { spark: false, apm: true })).toBe(
|
||||
"apm",
|
||||
);
|
||||
expect(getEffectiveStoreFilter("both", { spark: false, apm: false })).toBe(
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
it("only treats enabled and installed origins as usable", () => {
|
||||
expect(isOriginUsable("both", "spark", { spark: true, apm: false })).toBe(
|
||||
true,
|
||||
);
|
||||
expect(isOriginUsable("both", "apm", { spark: true, apm: false })).toBe(
|
||||
false,
|
||||
);
|
||||
expect(isOriginUsable("spark", "apm", { spark: true, apm: true })).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -25,6 +25,9 @@ const APTSS_WEATHER_PRINT_URIS_KEY =
|
||||
const APTSS_NOTES_PRINT_URIS_KEY =
|
||||
"bash -lc /usr/bin/apt download spark-notes --print-uris -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf -o Dir::Etc::sourcelist=/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list -o Dir::Etc::sourceparts=/dev/null";
|
||||
|
||||
const WHICH_APTSS_KEY = "which aptss";
|
||||
const WHICH_APM_KEY = "which apm";
|
||||
|
||||
const loadUpdateCenterModule = async (
|
||||
remoteStore: Record<string, RemoteStoreResponse>,
|
||||
) => {
|
||||
@@ -106,6 +109,22 @@ afterEach(() => {
|
||||
describe("update-center load items", () => {
|
||||
it("enriches apm migration items with download metadata and remote fallback icons", async () => {
|
||||
const commandResults = new Map<string, CommandResult>([
|
||||
[
|
||||
WHICH_APTSS_KEY,
|
||||
{
|
||||
code: 0,
|
||||
stdout: "/usr/bin/aptss\n",
|
||||
stderr: "",
|
||||
},
|
||||
],
|
||||
[
|
||||
WHICH_APM_KEY,
|
||||
{
|
||||
code: 0,
|
||||
stdout: "/usr/bin/apm\n",
|
||||
stderr: "",
|
||||
},
|
||||
],
|
||||
[
|
||||
APTSS_LIST_UPGRADABLE_KEY,
|
||||
{
|
||||
@@ -217,6 +236,14 @@ describe("update-center load items", () => {
|
||||
const result = await loadUpdateCenterItems(async (command, args) => {
|
||||
const key = `${command} ${args.join(" ")}`;
|
||||
|
||||
if (key === WHICH_APTSS_KEY) {
|
||||
return { code: 0, stdout: "/usr/bin/aptss\n", stderr: "" };
|
||||
}
|
||||
|
||||
if (key === WHICH_APM_KEY) {
|
||||
return { code: 127, stdout: "", stderr: "apm: command not found" };
|
||||
}
|
||||
|
||||
if (key === APTSS_LIST_UPGRADABLE_KEY) {
|
||||
return {
|
||||
code: 0,
|
||||
@@ -279,10 +306,7 @@ describe("update-center load items", () => {
|
||||
sha512: "beadfeed",
|
||||
},
|
||||
]);
|
||||
expect(result.warnings).toEqual([
|
||||
"apm upgradable query failed: apm: command not found",
|
||||
"apm installed query failed: apm: command not found",
|
||||
]);
|
||||
expect(result.warnings).toEqual([]);
|
||||
});
|
||||
|
||||
it("retries category lookup after an earlier fetch failure in the same process", async () => {
|
||||
@@ -292,6 +316,14 @@ describe("update-center load items", () => {
|
||||
const runCommand = async (command: string, args: string[]) => {
|
||||
const key = `${command} ${args.join(" ")}`;
|
||||
|
||||
if (key === WHICH_APTSS_KEY) {
|
||||
return { code: 0, stdout: "/usr/bin/aptss\n", stderr: "" };
|
||||
}
|
||||
|
||||
if (key === WHICH_APM_KEY) {
|
||||
return { code: 127, stdout: "", stderr: "apm: command not found" };
|
||||
}
|
||||
|
||||
if (key === APTSS_LIST_UPGRADABLE_KEY) {
|
||||
return {
|
||||
code: 0,
|
||||
@@ -387,6 +419,14 @@ describe("update-center load items", () => {
|
||||
const result = await loadUpdateCenterItems(async (command, args) => {
|
||||
const key = `${command} ${args.join(" ")}`;
|
||||
|
||||
if (key === WHICH_APTSS_KEY) {
|
||||
return { code: 0, stdout: "/usr/bin/aptss\n", stderr: "" };
|
||||
}
|
||||
|
||||
if (key === WHICH_APM_KEY) {
|
||||
return { code: 127, stdout: "", stderr: "apm: command not found" };
|
||||
}
|
||||
|
||||
if (key === APTSS_LIST_UPGRADABLE_KEY) {
|
||||
return {
|
||||
code: 0,
|
||||
@@ -440,9 +480,122 @@ describe("update-center load items", () => {
|
||||
sha512: "beadfeed",
|
||||
},
|
||||
]);
|
||||
expect(result.warnings).toEqual([
|
||||
"apm upgradable query failed: apm: command not found",
|
||||
"apm installed query failed: apm: command not found",
|
||||
expect(result.warnings).toEqual([]);
|
||||
});
|
||||
|
||||
it("skips aptss commands when the store filter disables Spark", async () => {
|
||||
const { loadUpdateCenterItems } = await loadUpdateCenterModule({
|
||||
"https://erotica.spark-app.store/amd64-apm/categories.json": {
|
||||
tools: { zh: "Tools" },
|
||||
},
|
||||
"https://erotica.spark-app.store/amd64-apm/tools/applist.json": [
|
||||
{ Name: "Spark Clock", Pkgname: "spark-clock" },
|
||||
],
|
||||
});
|
||||
|
||||
const runCommand = vi.fn(async (command: string, args: string[]) => {
|
||||
const key = `${command} ${args.join(" ")}`;
|
||||
|
||||
if (key === WHICH_APM_KEY) {
|
||||
return { code: 0, stdout: "/usr/bin/apm\n", stderr: "" };
|
||||
}
|
||||
|
||||
if (key === "apm list --upgradable") {
|
||||
return {
|
||||
code: 0,
|
||||
stdout: "spark-clock/main 2.0.0 amd64 [upgradable from: 1.0.0]",
|
||||
stderr: "",
|
||||
};
|
||||
}
|
||||
|
||||
if (key === "apm list --installed") {
|
||||
return {
|
||||
code: 0,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
key ===
|
||||
"bash -lc amber-pm-debug /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf download spark-clock --print-uris"
|
||||
) {
|
||||
return {
|
||||
code: 0,
|
||||
stdout:
|
||||
"'https://example.invalid/spark-clock_2.0.0_amd64.deb' spark-clock_2.0.0_amd64.deb 1234 SHA512:feedface",
|
||||
stderr: "",
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected command ${key}`);
|
||||
});
|
||||
|
||||
await loadUpdateCenterItems(runCommand, "apm");
|
||||
|
||||
expect(runCommand).not.toHaveBeenCalledWith(
|
||||
"bash",
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining("apt list --upgradable"),
|
||||
]),
|
||||
);
|
||||
expect(runCommand).not.toHaveBeenCalledWith(
|
||||
"dpkg-query",
|
||||
expect.any(Array),
|
||||
);
|
||||
});
|
||||
|
||||
it("skips apm commands when the store filter disables APM", async () => {
|
||||
const { loadUpdateCenterItems } = await loadUpdateCenterModule({
|
||||
"https://erotica.spark-app.store/amd64-store/categories.json": {
|
||||
office: { zh: "Office" },
|
||||
},
|
||||
"https://erotica.spark-app.store/amd64-store/office/applist.json": [
|
||||
{ Name: "Spark Notes", Pkgname: "spark-notes" },
|
||||
],
|
||||
});
|
||||
|
||||
const runCommand = vi.fn(async (command: string, args: string[]) => {
|
||||
const key = `${command} ${args.join(" ")}`;
|
||||
|
||||
if (key === WHICH_APTSS_KEY) {
|
||||
return { code: 0, stdout: "/usr/bin/aptss\n", stderr: "" };
|
||||
}
|
||||
|
||||
if (key === APTSS_LIST_UPGRADABLE_KEY) {
|
||||
return {
|
||||
code: 0,
|
||||
stdout: "spark-notes/stable 2.0.0 amd64 [upgradable from: 1.0.0]",
|
||||
stderr: "",
|
||||
};
|
||||
}
|
||||
|
||||
if (key === DPKG_QUERY_INSTALLED_KEY) {
|
||||
return {
|
||||
code: 0,
|
||||
stdout: "spark-notes\tinstall ok installed\n",
|
||||
stderr: "",
|
||||
};
|
||||
}
|
||||
|
||||
if (key === APTSS_NOTES_PRINT_URIS_KEY) {
|
||||
return {
|
||||
code: 0,
|
||||
stdout:
|
||||
"'https://example.invalid/spark-notes_2.0.0_amd64.deb' spark-notes_2.0.0_amd64.deb 654321 SHA512:beadfeed",
|
||||
stderr: "",
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected command ${key}`);
|
||||
});
|
||||
|
||||
await loadUpdateCenterItems(runCommand, "spark");
|
||||
|
||||
expect(runCommand).not.toHaveBeenCalledWith("apm", [
|
||||
"list",
|
||||
"--upgradable",
|
||||
]);
|
||||
expect(runCommand).not.toHaveBeenCalledWith("apm", ["list", "--installed"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,8 +157,8 @@ describe("update-center/ipc", () => {
|
||||
await cancelHandler?.({}, "aptss:spark-weather");
|
||||
|
||||
expect(getStateHandler?.()).toEqual(snapshot);
|
||||
expect(service.open).toHaveBeenCalledTimes(1);
|
||||
expect(service.refresh).toHaveBeenCalledTimes(1);
|
||||
expect(service.open).toHaveBeenCalledWith("both");
|
||||
expect(service.refresh).toHaveBeenCalledWith("both");
|
||||
expect(service.ignore).toHaveBeenCalledWith({
|
||||
packageName: "spark-weather",
|
||||
newVersion: "2.0.0",
|
||||
@@ -176,6 +176,51 @@ describe("update-center/ipc", () => {
|
||||
expect(send).toHaveBeenCalledWith("update-center-state", snapshot);
|
||||
});
|
||||
|
||||
it("forwards store filter payloads to open and refresh", async () => {
|
||||
const handle = vi.fn();
|
||||
const snapshot: UpdateCenterServiceState = {
|
||||
items: [],
|
||||
tasks: [],
|
||||
warnings: [],
|
||||
hasRunningTasks: false,
|
||||
};
|
||||
const service = {
|
||||
open: vi.fn().mockResolvedValue(snapshot),
|
||||
refresh: vi.fn().mockResolvedValue(snapshot),
|
||||
ignore: vi.fn().mockResolvedValue(undefined),
|
||||
unignore: vi.fn().mockResolvedValue(undefined),
|
||||
start: vi.fn().mockResolvedValue(undefined),
|
||||
cancel: vi.fn().mockResolvedValue(undefined),
|
||||
getState: vi.fn().mockReturnValue(snapshot),
|
||||
subscribe: vi.fn(() => () => undefined),
|
||||
};
|
||||
|
||||
registerUpdateCenterIpc({ handle }, service);
|
||||
|
||||
const openHandler = handle.mock.calls.find(
|
||||
([channel]: [string]) => channel === "update-center-open",
|
||||
)?.[1] as
|
||||
| ((
|
||||
event: unknown,
|
||||
storeFilter?: "spark" | "apm" | "both",
|
||||
) => Promise<UpdateCenterServiceState>)
|
||||
| undefined;
|
||||
const refreshHandler = handle.mock.calls.find(
|
||||
([channel]: [string]) => channel === "update-center-refresh",
|
||||
)?.[1] as
|
||||
| ((
|
||||
event: unknown,
|
||||
storeFilter?: "spark" | "apm" | "both",
|
||||
) => Promise<UpdateCenterServiceState>)
|
||||
| undefined;
|
||||
|
||||
await openHandler?.({}, "apm");
|
||||
await refreshHandler?.({}, "spark");
|
||||
|
||||
expect(service.open).toHaveBeenCalledWith("apm");
|
||||
expect(service.refresh).toHaveBeenCalledWith("spark");
|
||||
});
|
||||
|
||||
it("service subscribers receive state updates after refresh start and ignore", async () => {
|
||||
let ignoredEntries = new Set<string>();
|
||||
const send = vi.fn();
|
||||
|
||||
@@ -61,9 +61,9 @@ describe("updateCenter store", () => {
|
||||
open.mockResolvedValue(snapshot);
|
||||
const store = createUpdateCenterStore();
|
||||
|
||||
await store.open();
|
||||
await store.open("apm");
|
||||
|
||||
expect(open).toHaveBeenCalledTimes(1);
|
||||
expect(open).toHaveBeenCalledWith("apm");
|
||||
expect(store.isOpen.value).toBe(true);
|
||||
expect(store.snapshot.value).toEqual(snapshot);
|
||||
expect(store.filteredItems.value).toEqual(snapshot.items);
|
||||
|
||||
Reference in New Issue
Block a user