fix(installed-apps): restore open and detail actions

This commit is contained in:
2026-04-15 22:10:02 +08:00
parent fcdd982637
commit 1410a80df5
5 changed files with 386 additions and 1 deletions
+2
View File
@@ -123,6 +123,8 @@
:apm-available="apmAvailable"
@close="closeInstalledModal"
@refresh="refreshInstalledApps"
@open-app="openDownloadedApp($event.pkgname, $event.origin)"
@open-detail="openDetail"
@uninstall="uninstallInstalledApp"
@switch-origin="handleSwitchOrigin"
/>
+114 -1
View File
@@ -1,7 +1,29 @@
import { render, screen } from "@testing-library/vue";
import { fireEvent, render, screen } from "@testing-library/vue";
import { describe, expect, it } from "vitest";
import InstalledAppsModal from "@/components/InstalledAppsModal.vue";
import type { App } from "@/global/typedefinition";
const createApp = (overrides: Partial<App> = {}): App => ({
name: "Spark Notes",
pkgname: "spark-notes",
version: "1.0.0",
filename: "spark-notes.deb",
torrent_address: "",
author: "",
contributor: "",
website: "",
update: "",
size: "1 MB",
more: "",
tags: "",
img_urls: [],
icons: "",
category: "office",
origin: "spark",
currentStatus: "installed",
...overrides,
});
describe("InstalledAppsModal", () => {
it("keeps scroll chaining inside the modal list", () => {
@@ -22,4 +44,95 @@ describe("InstalledAppsModal", () => {
expect(scrollContainer?.className).toContain("overscroll-contain");
});
it("renders open and detail actions for a store-backed installed app", () => {
render(InstalledAppsModal, {
props: {
show: true,
apps: [createApp()],
loading: false,
error: "",
activeOrigin: "spark",
storeFilter: "both",
apmAvailable: true,
},
});
expect(screen.getByRole("button", { name: "打开" })).toBeTruthy();
expect(screen.getByRole("button", { name: "查看详情" })).toBeTruthy();
});
it("emits open-app when clicking 打开", async () => {
const rendered = render(InstalledAppsModal, {
props: {
show: true,
apps: [createApp()],
loading: false,
error: "",
activeOrigin: "spark",
storeFilter: "both",
apmAvailable: true,
},
});
await fireEvent.click(screen.getByRole("button", { name: "打开" }));
expect(rendered.emitted("open-app")).toHaveLength(1);
expect(rendered.emitted("open-app")?.[0]?.[0]).toMatchObject({
pkgname: "spark-notes",
});
});
it("emits open-detail when clicking 查看详情", async () => {
const rendered = render(InstalledAppsModal, {
props: {
show: true,
apps: [createApp()],
loading: false,
error: "",
activeOrigin: "spark",
storeFilter: "both",
apmAvailable: true,
},
});
await fireEvent.click(screen.getByRole("button", { name: "查看详情" }));
expect(rendered.emitted("open-detail")).toHaveLength(1);
expect(rendered.emitted("open-detail")?.[0]?.[0]).toMatchObject({
pkgname: "spark-notes",
});
});
it("shows 查看详情 for metadata-rich unknown-category apps", () => {
render(InstalledAppsModal, {
props: {
show: true,
apps: [createApp({ category: "unknown", more: "Has store metadata" })],
loading: false,
error: "",
activeOrigin: "spark",
storeFilter: "both",
apmAvailable: true,
},
});
expect(screen.getByRole("button", { name: "查看详情" })).toBeTruthy();
});
it("hides 查看详情 for unknown-category apps", () => {
render(InstalledAppsModal, {
props: {
show: true,
apps: [createApp({ category: "unknown" })],
loading: false,
error: "",
activeOrigin: "spark",
storeFilter: "both",
apmAvailable: true,
},
});
expect(screen.queryByRole("button", { name: "查看详情" })).toBeNull();
});
});
+29
View File
@@ -146,6 +146,23 @@
</div>
</div>
</div>
<button
type="button"
class="inline-flex items-center gap-2 rounded-2xl border border-slate-300/70 px-4 py-2 text-sm font-semibold text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800"
@click="$emit('open-app', app)"
>
<i class="fas fa-play"></i>
打开
</button>
<button
v-if="canOpenDetail(app)"
type="button"
class="inline-flex items-center gap-2 rounded-2xl border border-brand/30 px-4 py-2 text-sm font-semibold text-brand transition hover:bg-brand/10"
@click="$emit('open-detail', app)"
>
<i class="fas fa-circle-info"></i>
查看详情
</button>
<button
type="button"
class="inline-flex items-center gap-2 rounded-2xl border border-rose-300/60 px-4 py-2 text-sm font-semibold text-rose-600 transition hover:bg-rose-50 disabled:opacity-50"
@@ -178,6 +195,16 @@ const getIconUrl = (app: App) => {
return `${APM_STORE_BASE_URL}/${finalArch}/${app.category}/${app.pkgname}/icon.png`;
};
const canOpenDetail = (app: App) => {
return (
app.category !== "unknown" ||
Boolean(app.more) ||
Boolean(app.website) ||
Boolean(app.author) ||
(app.img_urls?.length ?? 0) > 0
);
};
defineProps<{
show: boolean;
apps: App[];
@@ -193,6 +220,8 @@ defineEmits<{
(e: "refresh"): void;
(e: "uninstall", app: App): void;
(e: "switch-origin", origin: "apm" | "spark"): void;
(e: "open-app", app: App): void;
(e: "open-detail", app: App): void;
}>();
const onOverlayWheel = (e: WheelEvent) => {