diff --git a/src/components/sidebar/index.js b/src/components/sidebar/index.js index 7a862d8dc4..178714d209 100644 --- a/src/components/sidebar/index.js +++ b/src/components/sidebar/index.js @@ -687,7 +687,7 @@ function Sidebar({ container, toggler }) { return $sidebar; } -Sidebar.hide = () => $sidebar?.hide(); +Sidebar.hide = (hideIfTab = false) => $sidebar?.hide(hideIfTab); Sidebar.show = () => $sidebar?.show(); Sidebar.toggle = () => $sidebar?.toggle(); diff --git a/src/lib/acode.js b/src/lib/acode.js index 2870fc0a98..4e8fe4961d 100644 --- a/src/lib/acode.js +++ b/src/lib/acode.js @@ -241,6 +241,8 @@ class Acode { const sidebarAppsModule = { add: sidebarApps.add, get: sidebarApps.get, + open: sidebarApps.open, + close: sidebarApps.close, remove: sidebarApps.remove, }; diff --git a/src/sidebarApps/extensions/index.js b/src/sidebarApps/extensions/index.js index 72ed4378e7..bf6da8c7a1 100644 --- a/src/sidebarApps/extensions/index.js +++ b/src/sidebarApps/extensions/index.js @@ -1,5 +1,6 @@ import "./style.scss"; import fsOperation from "fileSystem"; +import sidebarApps from "sidebarApps"; import collapsableList from "components/collapsableList"; import Sidebar from "components/sidebar"; import alert from "dialogs/alert"; @@ -284,10 +285,7 @@ function waitForSearchInput() { } export async function openWithSearch(query) { - Sidebar.show(); - document - .querySelector('[data-action="sidebar-app"][data-id="extensions"]') - ?.click(); + sidebarApps.open("extensions"); const searchInput = await waitForSearchInput(); if (!searchInput) return; diff --git a/src/sidebarApps/index.js b/src/sidebarApps/index.js index 4172f79917..66784e1818 100644 --- a/src/sidebarApps/index.js +++ b/src/sidebarApps/index.js @@ -1,3 +1,4 @@ +import Sidebar from "components/sidebar"; import appSettings from "lib/settings"; import Sponsors from "pages/sponsors"; import SidebarApp from "./sidebarApp"; @@ -153,6 +154,37 @@ function get(id) { return app.container; } +/** + * Opens the sidebar and optionally activates an app. + * + * @param {string} [id] id of the sidebar app to activate + * @returns {boolean} whether the sidebar was opened + */ +function open(id) { + if (!$sidebar) return false; + + if (id !== undefined) { + const app = apps.find((app) => app.id === id); + if (!app) return false; + setActiveApp(id); + } + + Sidebar.show(); + return true; +} + +/** + * Closes the sidebar without changing the active app. + * + * @returns {boolean} whether the sidebar was closed + */ +function close() { + if (!$sidebar) return false; + + Sidebar.hide(true); + return true; +} + /** * Handles click on sidebar apps * @param {MouseEvent} e @@ -187,6 +219,8 @@ export default { init, add, get, + open, + close, remove, loadApps, ensureActiveApp, diff --git a/tests/unit/sidebarApps.test.js b/tests/unit/sidebarApps.test.js new file mode 100644 index 0000000000..fff8eabadc --- /dev/null +++ b/tests/unit/sidebarApps.test.js @@ -0,0 +1,84 @@ +// @vitest-environment happy-dom +import { afterEach, beforeEach, expect, it, vi } from "vitest"; + +const sidebar = vi.hoisted(() => ({ + show: vi.fn(), + hide: vi.fn(), +})); + +const settings = vi.hoisted(() => ({ + value: { showSponsorSidebarApp: false }, + on: vi.fn(), +})); + +const instances = vi.hoisted(() => []); + +vi.mock("components/sidebar", () => ({ default: sidebar })); +vi.mock("lib/settings", () => ({ default: settings })); +vi.mock("pages/sponsors", () => ({ default: vi.fn() })); +vi.mock("../../src/sidebarApps/sidebarApp.js", () => ({ + default: class SidebarApp { + static init = vi.fn(); + + constructor(icon, id, title, init, onSelected) { + this.id = id; + this.container = document.createElement("div"); + this.install = vi.fn(); + this.remove = vi.fn(); + this.active = false; + instances.push(this); + } + }, +})); + +let sidebarApps; + +beforeEach(async () => { + vi.resetModules(); + localStorage.clear(); + instances.length = 0; + sidebarApps = (await import("../../src/sidebarApps/index.js")).default; + const icons = document.createElement("div"); + sidebarApps.init({ + get: vi.fn(() => icons), + }); +}); + +afterEach(() => { + vi.clearAllMocks(); + document.body.replaceChildren(); +}); + +it("opens the sidebar and activates the requested app", () => { + sidebarApps.add("folder", "files", "Files"); + sidebarApps.add("extension", "extensions", "Extensions"); + + expect(sidebarApps.open("extensions")).toBe(true); + expect(sidebar.show).toHaveBeenCalledOnce(); + expect(instances.map((app) => app.active)).toEqual([false, true]); + expect(localStorage.getItem("sidebarAppsLastSection")).toBe("extensions"); +}); + +it("opens the current app when no app id is supplied", () => { + sidebarApps.add("folder", "files", "Files"); + + expect(sidebarApps.open()).toBe(true); + expect(sidebar.show).toHaveBeenCalledOnce(); + expect(instances[0].active).toBe(true); +}); + +it("does not open the sidebar for an unknown app", () => { + sidebarApps.add("folder", "files", "Files"); + + expect(sidebarApps.open("missing")).toBe(false); + expect(sidebar.show).not.toHaveBeenCalled(); + expect(instances[0].active).toBe(true); +}); + +it("closes the sidebar without changing the active app", () => { + sidebarApps.add("folder", "files", "Files"); + + expect(sidebarApps.close()).toBe(true); + expect(sidebar.hide).toHaveBeenCalledOnce(); + expect(instances[0].active).toBe(true); +});