mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Make startable stoppable support async starting
Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
952ff1deb9
commit
c7dba52cd0
@ -2,16 +2,20 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
|
import asyncFn from "@async-fn/jest";
|
||||||
import { getStartableStoppable } from "./get-startable-stoppable";
|
import { getStartableStoppable } from "./get-startable-stoppable";
|
||||||
|
import { getPromiseStatus } from "../test-utils/get-promise-status";
|
||||||
|
import { flushPromises } from "../test-utils/flush-promises";
|
||||||
|
|
||||||
describe("getStartableStoppable", () => {
|
describe("getStartableStoppable", () => {
|
||||||
let stopMock: jest.Mock<() => void>;
|
let stopMock: AsyncFnMock<() => Promise<void>>;
|
||||||
let startMock: jest.Mock<() => () => void>;
|
let startMock: AsyncFnMock<() => Promise<() => Promise<void>>>;
|
||||||
let actual: { stop: () => void; start: () => void; started: boolean };
|
let actual: { stop: () => Promise<void>; start: () => Promise<void>; started: boolean };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stopMock = jest.fn();
|
stopMock = asyncFn();
|
||||||
startMock = jest.fn(() => stopMock);
|
startMock = asyncFn();
|
||||||
|
|
||||||
actual = getStartableStoppable("some-id", startMock);
|
actual = getStartableStoppable("some-id", startMock);
|
||||||
});
|
});
|
||||||
@ -25,9 +29,7 @@ describe("getStartableStoppable", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("when stopping before ever starting, throws", () => {
|
it("when stopping before ever starting, throws", () => {
|
||||||
expect(() => {
|
expect(actual.stop).rejects.toThrow("Tried to stop \"some-id\", but it has not started yet.");
|
||||||
actual.stop();
|
|
||||||
}).toThrow("Tried to stop \"some-id\", but it has not started yet.");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is not started", () => {
|
it("is not started", () => {
|
||||||
@ -35,69 +37,180 @@ describe("getStartableStoppable", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when started", () => {
|
describe("when started", () => {
|
||||||
|
let startPromise: Promise<void>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
actual.start();
|
startPromise = actual.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("starts", () => {
|
it("starts starting", () => {
|
||||||
expect(startMock).toHaveBeenCalled();
|
expect(startMock).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is started", () => {
|
it("starting does not resolve yet", async () => {
|
||||||
expect(actual.started).toBe(true);
|
const promiseStatus = await getPromiseStatus(startPromise);
|
||||||
|
|
||||||
|
expect(promiseStatus.fulfilled).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when started again, throws", () => {
|
it("is not started yet", () => {
|
||||||
expect(() => {
|
expect(actual.started).toBe(false);
|
||||||
actual.start();
|
|
||||||
}).toThrow("Tried to start \"some-id\", but it has already started.");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not stop yet", () => {
|
describe("when starting finishes", () => {
|
||||||
expect(stopMock).not.toHaveBeenCalled();
|
beforeEach(async () => {
|
||||||
});
|
await startMock.resolve(stopMock);
|
||||||
|
|
||||||
describe("when stopped", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
actual.stop();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("stops", () => {
|
it("is started", () => {
|
||||||
expect(stopMock).toHaveBeenCalled();
|
expect(actual.started).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is not started", () => {
|
it("starting resolves", async () => {
|
||||||
expect(actual.started).toBe(false);
|
const promiseStatus = await getPromiseStatus(startPromise);
|
||||||
|
|
||||||
|
expect(promiseStatus.fulfilled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when stopped again, throws", () => {
|
it("when started again, throws", () => {
|
||||||
expect(() => {
|
expect(actual.start).rejects.toThrow("Tried to start \"some-id\", but it has already started.");
|
||||||
actual.stop();
|
|
||||||
}).toThrow("Tried to stop \"some-id\", but it has already stopped.");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when started again", () => {
|
it("does not stop yet", () => {
|
||||||
|
expect(stopMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when stopped", () => {
|
||||||
|
let stopPromise: Promise<void>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
startMock.mockClear();
|
stopPromise = actual.stop();
|
||||||
|
|
||||||
actual.start();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("starts", () => {
|
it("starts stopping", () => {
|
||||||
expect(startMock).toHaveBeenCalled();
|
expect(stopMock).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is started", () => {
|
it("stopping does not resolve yet", async () => {
|
||||||
|
const promiseStatus = await getPromiseStatus(stopPromise);
|
||||||
|
|
||||||
|
expect(promiseStatus.fulfilled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is not stopped yet", () => {
|
||||||
expect(actual.started).toBe(true);
|
expect(actual.started).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when stopped, stops", () => {
|
describe("when stopping finishes", () => {
|
||||||
stopMock.mockClear();
|
beforeEach(async () => {
|
||||||
|
await stopMock.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
actual.stop();
|
it("is not started", () => {
|
||||||
|
expect(actual.started).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stopping resolves", async () => {
|
||||||
|
const promiseStatus = await getPromiseStatus(stopPromise);
|
||||||
|
|
||||||
|
expect(promiseStatus.fulfilled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when stopped again, throws", () => {
|
||||||
|
expect(actual.stop).rejects.toThrow("Tried to stop \"some-id\", but it has already stopped.");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when started again", () => {
|
||||||
|
beforeEach(
|
||||||
|
() => {
|
||||||
|
startMock.mockClear();
|
||||||
|
|
||||||
|
actual.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("starts", () => {
|
||||||
|
expect(startMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is not started yet", () => {
|
||||||
|
expect(actual.started).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when starting finishes", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await startMock.resolve(stopMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is started", () => {
|
||||||
|
expect(actual.started).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when stopped again, starts stopping again", async () => {
|
||||||
|
stopMock.mockClear();
|
||||||
|
|
||||||
|
actual.stop();
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(stopMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when stopped before starting finishes", () => {
|
||||||
|
let stopPromise: Promise<void>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
stopPromise = actual.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not resolve yet", async () => {
|
||||||
|
const promiseStatus = await getPromiseStatus(stopPromise);
|
||||||
|
|
||||||
|
expect(promiseStatus.fulfilled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is not started yet", () => {
|
||||||
|
expect(actual.started).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when starting finishes", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await startMock.resolve(stopMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("starts stopping", () => {
|
||||||
expect(stopMock).toHaveBeenCalled();
|
expect(stopMock).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("is not stopped yet", () => {
|
||||||
|
expect(actual.started).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not resolve yet", async () => {
|
||||||
|
const promiseStatus = await getPromiseStatus(stopPromise);
|
||||||
|
|
||||||
|
expect(promiseStatus.fulfilled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when stopping finishes", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await stopMock.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is stopped", () => {
|
||||||
|
expect(actual.started).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves", async () => {
|
||||||
|
const promiseStatus = await getPromiseStatus(stopPromise);
|
||||||
|
|
||||||
|
expect(promiseStatus.fulfilled).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,32 +2,39 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type Stopper = () => Promise<void> | void;
|
||||||
|
type Starter = () => Promise<Stopper> | Stopper;
|
||||||
|
|
||||||
export const getStartableStoppable = (
|
export const getStartableStoppable = (
|
||||||
id: string,
|
id: string,
|
||||||
startAndGetStopCallback: () => () => void,
|
startAndGetStopCallback: Starter,
|
||||||
) => {
|
) => {
|
||||||
let dispose: () => void;
|
let stop: Stopper;
|
||||||
let stopped = false;
|
let stopped = false;
|
||||||
let started = false;
|
let started = false;
|
||||||
|
let startingPromise: Promise<Stopper> | Stopper;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get started() {
|
get started() {
|
||||||
return started;
|
return started;
|
||||||
},
|
},
|
||||||
|
|
||||||
start: () => {
|
start: async () => {
|
||||||
if (started) {
|
if (started) {
|
||||||
throw new Error(`Tried to start "${id}", but it has already started.`);
|
throw new Error(`Tried to start "${id}", but it has already started.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startingPromise = startAndGetStopCallback();
|
||||||
|
stop = await startingPromise;
|
||||||
|
|
||||||
stopped = false;
|
stopped = false;
|
||||||
|
|
||||||
dispose = startAndGetStopCallback();
|
|
||||||
|
|
||||||
started = true;
|
started = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
stop: () => {
|
stop: async () => {
|
||||||
|
await startingPromise;
|
||||||
|
|
||||||
if (stopped) {
|
if (stopped) {
|
||||||
throw new Error(`Tried to stop "${id}", but it has already stopped.`);
|
throw new Error(`Tried to stop "${id}", but it has already stopped.`);
|
||||||
}
|
}
|
||||||
@ -36,10 +43,9 @@ export const getStartableStoppable = (
|
|||||||
throw new Error(`Tried to stop "${id}", but it has not started yet.`);
|
throw new Error(`Tried to stop "${id}", but it has not started yet.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await stop();
|
||||||
|
|
||||||
started = false;
|
started = false;
|
||||||
|
|
||||||
dispose();
|
|
||||||
|
|
||||||
stopped = true;
|
stopped = true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,8 +13,8 @@ const startCatalogSyncInjectable = getInjectable({
|
|||||||
const catalogSyncToRenderer = di.inject(catalogSyncToRendererInjectable);
|
const catalogSyncToRenderer = di.inject(catalogSyncToRendererInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run: () => {
|
run: async () => {
|
||||||
catalogSyncToRenderer.start();
|
await catalogSyncToRenderer.start();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,9 +13,9 @@ const stopCatalogSyncInjectable = getInjectable({
|
|||||||
const catalogSyncToRenderer = di.inject(catalogSyncToRendererInjectable);
|
const catalogSyncToRenderer = di.inject(catalogSyncToRendererInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run: () => {
|
run: async () => {
|
||||||
if (catalogSyncToRenderer.started) {
|
if (catalogSyncToRenderer.started) {
|
||||||
catalogSyncToRenderer.stop();
|
await catalogSyncToRenderer.stop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,8 +15,8 @@ const startApplicationMenuInjectable = getInjectable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run: () => {
|
run: async () => {
|
||||||
applicationMenu.start();
|
await applicationMenu.start();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,8 +15,8 @@ const stopApplicationMenuInjectable = getInjectable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run: () => {
|
run: async () => {
|
||||||
applicationMenu.stop();
|
await applicationMenu.stop();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,8 +13,8 @@ const startTrayInjectable = getInjectable({
|
|||||||
const trayInitializer = di.inject(trayInjectable);
|
const trayInitializer = di.inject(trayInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run: () => {
|
run: async () => {
|
||||||
trayInitializer.start();
|
await trayInitializer.start();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,8 +13,8 @@ const stopTrayInjectable = getInjectable({
|
|||||||
const trayInitializer = di.inject(trayInjectable);
|
const trayInitializer = di.inject(trayInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run: () => {
|
run: async () => {
|
||||||
trayInitializer.stop();
|
await trayInitializer.stop();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import getAbsolutePathInjectable from "../../common/path/get-absolute-path.injectable";
|
|
||||||
import isDevelopmentInjectable from "../../common/vars/is-development.injectable";
|
|
||||||
import staticFilesDirectoryInjectable from "../../common/vars/static-files-directory.injectable";
|
|
||||||
|
|
||||||
const trayIconPathInjectable = getInjectable({
|
|
||||||
id: "tray-icon-path",
|
|
||||||
|
|
||||||
instantiate: (di) => {
|
|
||||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
|
||||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
|
||||||
const staticDir = di.inject(staticFilesDirectoryInjectable);
|
|
||||||
|
|
||||||
return getAbsolutePath(
|
|
||||||
staticDir,
|
|
||||||
isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras
|
|
||||||
"trayIconTemplate.png",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default trayIconPathInjectable;
|
|
||||||
@ -9,10 +9,8 @@ import navigateToPreferencesInjectable from "../../common/front-end-routing/rout
|
|||||||
import stopServicesAndExitAppInjectable from "../stop-services-and-exit-app.injectable";
|
import stopServicesAndExitAppInjectable from "../stop-services-and-exit-app.injectable";
|
||||||
import { getStartableStoppable } from "../../common/utils/get-startable-stoppable";
|
import { getStartableStoppable } from "../../common/utils/get-startable-stoppable";
|
||||||
import isAutoUpdateEnabledInjectable from "../is-auto-update-enabled.injectable";
|
import isAutoUpdateEnabledInjectable from "../is-auto-update-enabled.injectable";
|
||||||
import trayIconPathInjectable from "./tray-icon-path.injectable";
|
|
||||||
import showAboutInjectable from "../menu/show-about.injectable";
|
import showAboutInjectable from "../menu/show-about.injectable";
|
||||||
import showApplicationWindowInjectable
|
import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable";
|
||||||
from "../start-main-application/lens-window/show-application-window.injectable";
|
|
||||||
|
|
||||||
const trayInjectable = getInjectable({
|
const trayInjectable = getInjectable({
|
||||||
id: "tray",
|
id: "tray",
|
||||||
@ -22,7 +20,6 @@ const trayInjectable = getInjectable({
|
|||||||
const navigateToPreferences = di.inject(navigateToPreferencesInjectable);
|
const navigateToPreferences = di.inject(navigateToPreferencesInjectable);
|
||||||
const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable);
|
const stopServicesAndExitApp = di.inject(stopServicesAndExitAppInjectable);
|
||||||
const isAutoUpdateEnabled = di.inject(isAutoUpdateEnabledInjectable);
|
const isAutoUpdateEnabled = di.inject(isAutoUpdateEnabledInjectable);
|
||||||
const trayIconPath = di.inject(trayIconPathInjectable);
|
|
||||||
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
|
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
|
||||||
const showAboutPopup = di.inject(showAboutInjectable);
|
const showAboutPopup = di.inject(showAboutInjectable);
|
||||||
|
|
||||||
@ -32,7 +29,6 @@ const trayInjectable = getInjectable({
|
|||||||
navigateToPreferences,
|
navigateToPreferences,
|
||||||
stopServicesAndExitApp,
|
stopServicesAndExitApp,
|
||||||
isAutoUpdateEnabled,
|
isAutoUpdateEnabled,
|
||||||
trayIconPath,
|
|
||||||
showApplicationWindow,
|
showApplicationWindow,
|
||||||
showAboutPopup,
|
showAboutPopup,
|
||||||
),
|
),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user