Marketplace UI plumbing: wrappers, singleton tab, settings section, view shell
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import MarketplaceSettings from "./MarketplaceSettings";
|
||||
import { useAppState, MARKETPLACE_TAB_KEY } from "../../store/appState";
|
||||
import type { AppSettings } from "../../lib/types";
|
||||
|
||||
const listMarketplaceUpdates = vi.fn();
|
||||
vi.mock("../../lib/tauri-commands", () => ({
|
||||
listMarketplaceUpdates: () => listMarketplaceUpdates(),
|
||||
}));
|
||||
|
||||
describe("MarketplaceSettings", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
useAppState.setState({
|
||||
tabOrder: [],
|
||||
activeTabKey: null,
|
||||
appSettings: {
|
||||
marketplaces: [{ id: "m1", name: "Starter", url: "https://x/y.git", branch: null, account_id: null }],
|
||||
global_marketplace_installs: [
|
||||
{ marketplace_id: "m1", kind: "agent", key: "a", commit: "a".repeat(40) },
|
||||
{ marketplace_id: "m1", kind: "hook", key: "h", commit: "a".repeat(40) },
|
||||
],
|
||||
marketplace_accounts: [],
|
||||
} as unknown as AppSettings,
|
||||
});
|
||||
listMarketplaceUpdates.mockResolvedValue([
|
||||
{ item: { marketplace_id: "m1", kind: "agent", key: "a" }, pinned: "a".repeat(40), head: "b".repeat(40) },
|
||||
]);
|
||||
});
|
||||
|
||||
it("summarises and opens the Marketplace tab", async () => {
|
||||
render(<MarketplaceSettings />);
|
||||
expect(screen.getByTestId("marketplace-summary")).toHaveTextContent("1 marketplace");
|
||||
expect(screen.getByTestId("marketplace-summary")).toHaveTextContent("2 installed for all projects");
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("marketplace-summary")).toHaveTextContent("1 update available"),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Open Marketplace" }));
|
||||
expect(useAppState.getState().activeTabKey).toBe(MARKETPLACE_TAB_KEY);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAppState } from "../../store/appState";
|
||||
import { listMarketplaceUpdates } from "../../lib/tauri-commands";
|
||||
import Button from "../ui/Button";
|
||||
|
||||
const plural = (n: number, one: string, many: string) => `${n} ${n === 1 ? one : many}`;
|
||||
|
||||
export default function MarketplaceSettings() {
|
||||
const appSettings = useAppState((s) => s.appSettings);
|
||||
const openMarketplace = useAppState((s) => s.openMarketplace);
|
||||
const [updateCount, setUpdateCount] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
listMarketplaceUpdates()
|
||||
.then((u) => {
|
||||
if (!cancelled) setUpdateCount(u.length);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setUpdateCount(null);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [appSettings?.marketplaces.length]);
|
||||
|
||||
const marketplaces = appSettings?.marketplaces.length ?? 0;
|
||||
const globalInstalls = appSettings?.global_marketplace_installs.length ?? 0;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<p data-testid="marketplace-summary" className="text-xs text-[var(--text-secondary)] leading-snug">
|
||||
{plural(marketplaces, "marketplace", "marketplaces")} ·{" "}
|
||||
{globalInstalls} installed for all projects
|
||||
{updateCount !== null && updateCount > 0 && (
|
||||
<> · {plural(updateCount, "update available", "updates available")}</>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] leading-snug">
|
||||
Agents, skills, commands, hooks and plugins from git repositories, installed for all
|
||||
projects or per project. Changes apply to new Claude sessions.
|
||||
</p>
|
||||
<Button size="md" variant="secondary" onClick={() => openMarketplace()}>
|
||||
Open Marketplace
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { resolveTerminalGpuRendering } from "../../lib/terminalRenderer";
|
||||
import WebTerminalSettings from "./WebTerminalSettings";
|
||||
import SttSettings from "./SttSettings";
|
||||
import SharedAuthSettings from "./SharedAuthSettings";
|
||||
import MarketplaceSettings from "./MarketplaceSettings";
|
||||
import CertificateSettings from "./CertificateSettings";
|
||||
import ExportSettingsModal from "./ExportSettingsModal";
|
||||
import ImportSettingsModal from "./ImportSettingsModal";
|
||||
@@ -171,6 +172,10 @@ export default function SettingsPanel() {
|
||||
<SharedAuthSettings />
|
||||
</AccordionSection>
|
||||
|
||||
<AccordionSection id="marketplace" title="Marketplace" defaultOpen={false}>
|
||||
<MarketplaceSettings />
|
||||
</AccordionSection>
|
||||
|
||||
<AccordionSection id="backends" title="Backends" defaultOpen={false}>
|
||||
<AwsSettings />
|
||||
<div className="pt-3 border-t border-[var(--border-color)]" />
|
||||
|
||||
Reference in New Issue
Block a user