diff --git a/src/components/settings/sections/PluginsSection.enableToggle.test.tsx b/src/components/settings/sections/PluginsSection.enableToggle.test.tsx new file mode 100644 index 000000000..e54e5f6ed --- /dev/null +++ b/src/components/settings/sections/PluginsSection.enableToggle.test.tsx @@ -0,0 +1,120 @@ +import { test, expect, vi, beforeEach, afterEach } from "vitest"; +import { render, screen, cleanup, fireEvent } from "@testing-library/react"; +import type { PluginManifest } from "@/plugins/api"; + +const manifest = (id: string, permissions: string[]): PluginManifest => + ({ id, name: id, version: "1.0.0", description: "", permissions } as PluginManifest); + +const DOCKER = manifest("plugin-docker", ["docker:read"]); +const THEME = manifest("emerald-night", ["themes"]); + +const loaded = vi.hoisted(() => ({ list: [] as PluginManifest[] })); +const runtime = vi.hoisted(() => ({ setPluginActive: vi.fn() })); +vi.mock("@/plugins/runtime", () => ({ + getLoadedPlugins: () => loaded.list, + setPluginActive: runtime.setPluginActive, + pluginStorageGet: vi.fn(async () => null), + pluginStorageSet: vi.fn(async () => {}), +})); +const marketplaceState = { + installedMeta: [] as unknown[], catalog: [] as unknown[], + installing: new Set(), + uninstallPlugin: vi.fn(async () => {}), + uninstallSeededPlugin: vi.fn(async () => {}), + reloadPlugin: vi.fn(async () => {}), + scanLocal: vi.fn(async () => {}), + installPlugin: vi.fn(async () => {}), + fetchManifest: vi.fn(async () => ({ manifest: { permissions: [] }, manifestText: "" })), + appVersion: null as string | null, + loadAppVersion: vi.fn(async () => {}), +}; +const FIRST_PARTY_SOURCE = vi.hoisted(() => ({ id: "voltius", name: "Voltius Marketplace", url: "", enabled: true, deletable: false })); +vi.mock("@/stores/marketplaceStore", () => ({ + useMarketplaceStore: (selector?: (s: typeof marketplaceState) => unknown) => + selector ? selector(marketplaceState) : marketplaceState, + FIRST_PARTY_SOURCE, +})); +vi.mock("@/stores/notificationStore", () => ({ + useNotificationStore: Object.assign(() => ({ push: vi.fn() }), { getState: () => ({ push: vi.fn() }) }), +})); +vi.mock("@/stores/toggleSettingsStore", () => ({ getToggle: () => false, useToggle: () => false })); +vi.mock("@/components/shared/ToolbarViewControls", () => ({ useFilterShortcut: () => {} })); +vi.mock("@/components/shared/Toggle", () => ({ + Toggle: ({ checked, onChange }: { checked: boolean; onChange: () => void }) => ( + - handleToggle(manifest.id, enabled)} /> + {manifest.permissions.length > 0 && (
@@ -501,6 +520,11 @@ export function InstalledTab() { {filteredExternal.map((meta) => { const manifest = externalManifests.find((m) => m.id === meta.id); const isLoaded = loadedIds.has(meta.id); + // `true` mirrors marketplaceStore's externalPluginActive: installing IS the + // opt-in, so an installed plugin is on unless the user turned it off. Kept + // as a literal rather than imported because that helper reads the store via + // getState(), which would not re-render this row when the override changes. + const enabled = isLoaded && isEnabled(meta.id, true); const isReloading = reloading.has(meta.id); const isUninstalling = uninstalling.has(meta.id); const update = availableUpdate(meta, catalog); @@ -511,11 +535,11 @@ export function InstalledTab() {
- +
@@ -580,6 +604,7 @@ export function InstalledTab() { > + {manifest && }
{manifest && manifest.permissions.length > 0 && (