refactor(FN-1240): remove dashboard Capacitor coupling in favor of @fusion/mobile

- Remove legacy dashboard Capacitor config, scripts, and package dependencies now managed by @fusion/mobile
- Rewrite network, splash-screen, and status-bar plugin managers to use guarded global Capacitor plugin adapters without direct @capacitor imports
- Update plugin tests to stub Capacitor globals and drop obsolete capacitor config test coverage
- Simplify dashboard mobile README guidance and add a @gsxdsm/fusion patch changeset documenting the migration
This commit is contained in:
gsxdsm
2026-04-08 08:10:17 -07:00
parent be204d6c52
commit b8672cf792
13 changed files with 185 additions and 274 deletions

View File

@@ -1,60 +0,0 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, it, expect } from "vitest";
import type { CapacitorConfig } from "@capacitor/cli";
const configPath = resolve(__dirname, "../../capacitor.config.ts");
function evaluateConfig(env: NodeJS.ProcessEnv = {}): CapacitorConfig {
const content = readFileSync(configPath, "utf8");
const executableSource = content
.replace('import type { CapacitorConfig } from "@capacitor/cli";\n\n', "")
.replace("const config: CapacitorConfig = {", "const config = {")
.replace("export default config;", "return config;");
const fn = new Function("process", executableSource) as (processLike: { env: NodeJS.ProcessEnv }) => CapacitorConfig;
return fn({ env });
}
describe("capacitor.config", () => {
it("exists and exports a TypeScript Capacitor config", () => {
const content = readFileSync(configPath, "utf8");
expect(content.length).toBeGreaterThan(0);
expect(content).toContain("import type { CapacitorConfig } from \"@capacitor/cli\"");
expect(content).toContain("const config: CapacitorConfig = {");
expect(content).toContain("export default config;");
});
it("sets webDir to dist/client", () => {
const config = evaluateConfig();
expect(config.webDir).toBe("dist/client");
});
it("uses the expected app identity", () => {
const config = evaluateConfig();
expect(config.appId).toBe("com.fusion.dashboard");
expect(config.appName).toBe("Fusion");
});
it("enables cleartext server access for local development", () => {
const config = evaluateConfig();
expect(config.server?.cleartext).toBe(true);
});
it("defaults server.url to undefined when FUSION_BACKEND_URL is unset", () => {
const config = evaluateConfig({});
expect(config.server?.url).toBeUndefined();
});
it("uses FUSION_BACKEND_URL when provided", () => {
const config = evaluateConfig({ FUSION_BACKEND_URL: "http://192.168.1.100:4040" });
expect(config.server?.url).toBe("http://192.168.1.100:4040");
});
it("references the FUSION_BACKEND_URL env variable in source", () => {
const content = readFileSync(configPath, "utf8");
expect(content).toContain("process.env.FUSION_BACKEND_URL || undefined");
});
});

View File

@@ -1,24 +1,14 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { Network } from "@capacitor/network";
import { NetworkManager } from "../plugins/network.js";
const { getStatusMock, addListenerMock, removeListenerMock } = vi.hoisted(() => ({
getStatusMock: vi.fn(),
addListenerMock: vi.fn(),
removeListenerMock: vi.fn(),
}));
const getStatusMock = vi.fn();
const addListenerMock = vi.fn();
const removeListenerMock = vi.fn();
let networkStatusChangeHandler:
| ((status: { connected: boolean; connectionType: "wifi" | "cellular" | "none" | "unknown" }) => void)
| null = null;
vi.mock("@capacitor/network", () => ({
Network: {
getStatus: getStatusMock,
addListener: addListenerMock,
},
}));
describe("NetworkManager", () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -40,6 +30,15 @@ describe("NetworkManager", () => {
}
return { remove: removeListenerMock };
});
vi.stubGlobal("Capacitor", {
Plugins: {
Network: {
getStatus: getStatusMock,
addListener: addListenerMock,
},
},
});
});
it("initialize() queries current network status", async () => {
@@ -47,7 +46,7 @@ describe("NetworkManager", () => {
await manager.initialize();
expect(Network.getStatus).toHaveBeenCalledTimes(1);
expect(getStatusMock).toHaveBeenCalledTimes(1);
expect(manager.getStatus()).toEqual({ connected: true, connectionType: "wifi" });
});
@@ -56,8 +55,8 @@ describe("NetworkManager", () => {
await manager.startMonitoring();
expect(Network.addListener).toHaveBeenCalledTimes(1);
expect(Network.addListener).toHaveBeenCalledWith("networkStatusChange", expect.any(Function));
expect(addListenerMock).toHaveBeenCalledTimes(1);
expect(addListenerMock).toHaveBeenCalledWith("networkStatusChange", expect.any(Function));
expect(manager.isMonitoring).toBe(true);
});

View File

@@ -1,23 +1,28 @@
import { beforeEach, afterEach, describe, expect, it, vi } from "vitest";
import { SplashScreen } from "@capacitor/splash-screen";
import { SplashScreenManager } from "../plugins/splash-screen.js";
vi.mock("@capacitor/splash-screen", () => ({
SplashScreen: {
hide: vi.fn(),
show: vi.fn(),
},
}));
const hideMock = vi.fn();
const showMock = vi.fn();
describe("SplashScreenManager", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(SplashScreen.hide).mockResolvedValue(undefined);
vi.mocked(SplashScreen.show).mockResolvedValue(undefined);
hideMock.mockResolvedValue(undefined);
showMock.mockResolvedValue(undefined);
vi.stubGlobal("Capacitor", {
Plugins: {
SplashScreen: {
hide: hideMock,
show: showMock,
},
},
});
});
afterEach(() => {
vi.useRealTimers();
vi.unstubAllGlobals();
});
it("initialize() with autoHide=true triggers hide after delay", async () => {
@@ -25,12 +30,12 @@ describe("SplashScreenManager", () => {
const manager = new SplashScreenManager({ autoHide: true, hideDelay: 100 });
await manager.initialize();
expect(SplashScreen.hide).not.toHaveBeenCalled();
expect(hideMock).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(100);
expect(SplashScreen.hide).toHaveBeenCalledTimes(1);
expect(SplashScreen.hide).toHaveBeenCalledWith({ fadeOutDuration: 300 });
expect(hideMock).toHaveBeenCalledTimes(1);
expect(hideMock).toHaveBeenCalledWith({ fadeOutDuration: 300 });
});
it("initialize() with autoHide=false does not auto-hide", async () => {
@@ -40,7 +45,7 @@ describe("SplashScreenManager", () => {
await manager.initialize();
await vi.advanceTimersByTimeAsync(500);
expect(SplashScreen.hide).not.toHaveBeenCalled();
expect(hideMock).not.toHaveBeenCalled();
});
it("hide() delegates to SplashScreen.hide()", async () => {
@@ -48,8 +53,8 @@ describe("SplashScreenManager", () => {
await manager.hide();
expect(SplashScreen.hide).toHaveBeenCalledTimes(1);
expect(SplashScreen.hide).toHaveBeenCalledWith({ fadeOutDuration: 300 });
expect(hideMock).toHaveBeenCalledTimes(1);
expect(hideMock).toHaveBeenCalledWith({ fadeOutDuration: 300 });
});
it("show() delegates to SplashScreen.show()", async () => {
@@ -57,8 +62,8 @@ describe("SplashScreenManager", () => {
await manager.show();
expect(SplashScreen.show).toHaveBeenCalledTimes(1);
expect(SplashScreen.show).toHaveBeenCalledWith({ autoHide: false });
expect(showMock).toHaveBeenCalledTimes(1);
expect(showMock).toHaveBeenCalledWith({ autoHide: false });
});
it("initialize() is idempotent", async () => {
@@ -69,11 +74,11 @@ describe("SplashScreenManager", () => {
await manager.initialize();
await vi.advanceTimersByTimeAsync(50);
expect(SplashScreen.hide).toHaveBeenCalledTimes(1);
expect(hideMock).toHaveBeenCalledTimes(1);
});
it("hide() swallows errors gracefully", async () => {
vi.mocked(SplashScreen.hide).mockRejectedValue(new Error("unavailable"));
hideMock.mockRejectedValue(new Error("unavailable"));
const manager = new SplashScreenManager();
await expect(manager.hide()).resolves.toBeUndefined();

View File

@@ -1,26 +1,27 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { StatusBar, Style } from "@capacitor/status-bar";
import { StatusBarManager } from "../plugins/status-bar.js";
vi.mock("@capacitor/status-bar", () => ({
StatusBar: {
setStyle: vi.fn(),
},
Style: {
Dark: "DARK",
Light: "LIGHT",
},
}));
const setStyleMock = vi.fn();
describe("StatusBarManager", () => {
const originalMatchMedia = globalThis.window?.matchMedia;
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(StatusBar.setStyle).mockResolvedValue(undefined);
setStyleMock.mockResolvedValue(undefined);
vi.stubGlobal("Capacitor", {
Plugins: {
StatusBar: {
setStyle: setStyleMock,
},
},
});
});
afterEach(() => {
vi.unstubAllGlobals();
if (globalThis.window) {
Object.defineProperty(globalThis.window, "matchMedia", {
configurable: true,
@@ -35,8 +36,8 @@ describe("StatusBarManager", () => {
await manager.initialize();
expect(StatusBar.setStyle).toHaveBeenCalledTimes(1);
expect(StatusBar.setStyle).toHaveBeenCalledWith({ style: Style.Dark });
expect(setStyleMock).toHaveBeenCalledTimes(1);
expect(setStyleMock).toHaveBeenCalledWith({ style: "DARK" });
});
it("setTheme('dark') sets dark style", async () => {
@@ -44,7 +45,7 @@ describe("StatusBarManager", () => {
await manager.setTheme("dark");
expect(StatusBar.setStyle).toHaveBeenCalledWith({ style: Style.Dark });
expect(setStyleMock).toHaveBeenCalledWith({ style: "DARK" });
expect(manager.getTheme()).toBe("dark");
});
@@ -53,7 +54,7 @@ describe("StatusBarManager", () => {
await manager.setTheme("light");
expect(StatusBar.setStyle).toHaveBeenCalledWith({ style: Style.Light });
expect(setStyleMock).toHaveBeenCalledWith({ style: "LIGHT" });
expect(manager.getTheme()).toBe("light");
});
@@ -68,7 +69,7 @@ describe("StatusBarManager", () => {
await manager.setTheme("system");
expect(StatusBar.setStyle).toHaveBeenLastCalledWith({ style: Style.Dark });
expect(setStyleMock).toHaveBeenLastCalledWith({ style: "DARK" });
Object.defineProperty(window, "matchMedia", {
configurable: true,
@@ -78,7 +79,7 @@ describe("StatusBarManager", () => {
await manager.setTheme("system");
expect(StatusBar.setStyle).toHaveBeenLastCalledWith({ style: Style.Light });
expect(setStyleMock).toHaveBeenLastCalledWith({ style: "LIGHT" });
});
it("onThemeChange callback fires on theme change", async () => {
@@ -105,7 +106,7 @@ describe("StatusBarManager", () => {
});
it("initialize() swallows errors", async () => {
vi.mocked(StatusBar.setStyle).mockRejectedValue(new Error("not available"));
setStyleMock.mockRejectedValue(new Error("not available"));
const manager = new StatusBarManager({ themeMode: "dark" });
await expect(manager.initialize()).resolves.toBeUndefined();