feat(desktop): split daemon and release settings to main process
Move daemon management and release channel settings out of the renderer's AsyncStorage and into a desktop-owned settings store persisted by the main process. Adds a "Keep daemon running after quit" option, a daemon lifecycle section in settings, and a one-time migration of legacy renderer-owned desktop settings.
This commit is contained in:
@@ -5,15 +5,33 @@ const asyncStorageMock = vi.hoisted(() => ({
|
||||
setItem: vi.fn<(_: string, __: string) => Promise<void>>(),
|
||||
}));
|
||||
|
||||
const electronRuntimeState = vi.hoisted(() => ({
|
||||
isElectron: false,
|
||||
}));
|
||||
|
||||
const desktopSettingsMock = vi.hoisted(() => ({
|
||||
loadDesktopSettings: vi.fn<() => Promise<unknown>>(),
|
||||
migrateLegacyDesktopSettings: vi.fn<(_: unknown) => Promise<void>>(),
|
||||
}));
|
||||
|
||||
vi.mock("@react-native-async-storage/async-storage", () => ({
|
||||
default: asyncStorageMock,
|
||||
}));
|
||||
|
||||
vi.mock("@/desktop/host", () => ({
|
||||
isElectronRuntime: () => electronRuntimeState.isElectron,
|
||||
}));
|
||||
|
||||
vi.mock("@/desktop/settings/desktop-settings", () => desktopSettingsMock);
|
||||
|
||||
describe("use-settings", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
asyncStorageMock.getItem.mockReset();
|
||||
asyncStorageMock.setItem.mockReset();
|
||||
electronRuntimeState.isElectron = false;
|
||||
desktopSettingsMock.loadDesktopSettings.mockReset();
|
||||
desktopSettingsMock.migrateLegacyDesktopSettings.mockReset();
|
||||
});
|
||||
|
||||
it("defaults built-in daemon management to enabled when storage is empty", async () => {
|
||||
@@ -26,7 +44,7 @@ describe("use-settings", () => {
|
||||
expect(result).toEqual(mod.DEFAULT_APP_SETTINGS);
|
||||
expect(asyncStorageMock.setItem).toHaveBeenCalledWith(
|
||||
mod.APP_SETTINGS_KEY,
|
||||
JSON.stringify(mod.DEFAULT_APP_SETTINGS),
|
||||
JSON.stringify(mod.DEFAULT_CLIENT_SETTINGS),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -50,7 +68,7 @@ describe("use-settings", () => {
|
||||
expect(result.releaseChannel).toBe("stable");
|
||||
});
|
||||
|
||||
it("loads persisted built-in daemon management state", async () => {
|
||||
it("ignores renderer-owned daemon management state outside Electron", async () => {
|
||||
asyncStorageMock.getItem.mockImplementation(async (key: string) => {
|
||||
if (key === "@paseo:app-settings") {
|
||||
return JSON.stringify({
|
||||
@@ -66,14 +84,13 @@ describe("use-settings", () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
theme: "light",
|
||||
manageBuiltInDaemon: false,
|
||||
manageBuiltInDaemon: true,
|
||||
sendBehavior: "interrupt",
|
||||
releaseChannel: "stable",
|
||||
});
|
||||
expect(asyncStorageMock.setItem).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("loads persisted beta release channel", async () => {
|
||||
it("ignores renderer-owned release channel outside Electron", async () => {
|
||||
asyncStorageMock.getItem.mockImplementation(async (key: string) => {
|
||||
if (key === "@paseo:app-settings") {
|
||||
return JSON.stringify({
|
||||
@@ -86,6 +103,89 @@ describe("use-settings", () => {
|
||||
const mod = await import("./use-settings");
|
||||
const result = await mod.loadSettingsFromStorage();
|
||||
|
||||
expect(result.releaseChannel).toBe("beta");
|
||||
expect(result.releaseChannel).toBe("stable");
|
||||
});
|
||||
|
||||
it("keeps legacy AsyncStorage migration for client settings only", async () => {
|
||||
asyncStorageMock.getItem.mockImplementation(async (key: string) => {
|
||||
if (key === "@paseo:app-settings") {
|
||||
return null;
|
||||
}
|
||||
if (key === "@paseo:settings") {
|
||||
return JSON.stringify({
|
||||
theme: "dark",
|
||||
manageBuiltInDaemon: false,
|
||||
releaseChannel: "beta",
|
||||
});
|
||||
}
|
||||
return null;
|
||||
});
|
||||
asyncStorageMock.setItem.mockResolvedValue();
|
||||
|
||||
const mod = await import("./use-settings");
|
||||
const result = await mod.loadAppSettingsFromStorage();
|
||||
|
||||
expect(result).toEqual({
|
||||
theme: "dark",
|
||||
sendBehavior: "interrupt",
|
||||
});
|
||||
expect(asyncStorageMock.setItem).toHaveBeenCalledWith(
|
||||
mod.APP_SETTINGS_KEY,
|
||||
JSON.stringify(result),
|
||||
);
|
||||
});
|
||||
|
||||
it("migrates legacy desktop-owned settings through Electron before reading effective settings", async () => {
|
||||
electronRuntimeState.isElectron = true;
|
||||
asyncStorageMock.getItem.mockImplementation(async (key: string) => {
|
||||
if (key === "@paseo:app-settings") {
|
||||
return JSON.stringify({
|
||||
theme: "light",
|
||||
manageBuiltInDaemon: false,
|
||||
releaseChannel: "beta",
|
||||
});
|
||||
}
|
||||
return null;
|
||||
});
|
||||
desktopSettingsMock.migrateLegacyDesktopSettings.mockResolvedValue();
|
||||
desktopSettingsMock.loadDesktopSettings.mockResolvedValue({
|
||||
releaseChannel: "beta",
|
||||
daemon: {
|
||||
manageBuiltInDaemon: false,
|
||||
keepRunningAfterQuit: true,
|
||||
},
|
||||
});
|
||||
|
||||
const mod = await import("./use-settings");
|
||||
const result = await mod.loadSettingsFromStorage();
|
||||
|
||||
expect(desktopSettingsMock.migrateLegacyDesktopSettings).toHaveBeenCalledWith({
|
||||
manageBuiltInDaemon: false,
|
||||
releaseChannel: "beta",
|
||||
});
|
||||
expect(result).toEqual({
|
||||
theme: "light",
|
||||
sendBehavior: "interrupt",
|
||||
manageBuiltInDaemon: false,
|
||||
releaseChannel: "beta",
|
||||
});
|
||||
});
|
||||
|
||||
it("skips desktop IPC when loading effective settings outside Electron", async () => {
|
||||
asyncStorageMock.getItem.mockResolvedValue(
|
||||
JSON.stringify({
|
||||
theme: "light",
|
||||
}),
|
||||
);
|
||||
|
||||
const mod = await import("./use-settings");
|
||||
const result = await mod.loadSettingsFromStorage();
|
||||
|
||||
expect(result).toEqual({
|
||||
theme: "light",
|
||||
sendBehavior: "interrupt",
|
||||
manageBuiltInDaemon: true,
|
||||
releaseChannel: "stable",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user