diff --git a/src/behaviours/update-app/installing-update-using-tray.test.ts b/src/behaviours/update-app/installing-update-using-tray.test.ts index eca7ef4367..a1961daef3 100644 --- a/src/behaviours/update-app/installing-update-using-tray.test.ts +++ b/src/behaviours/update-app/installing-update-using-tray.test.ts @@ -18,12 +18,14 @@ import downloadPlatformUpdateInjectable from "../../main/update-app/download-pla import selectedUpdateChannelInjectable from "../../main/update-app/selected-update-channel.injectable"; import progressOfUpdateDownloadInjectable from "../../main/update-app/progress-of-update-download.injectable"; import type { IComputedValue } from "mobx"; +import setUpdateOnQuitInjectable from "../../main/electron-app/features/set-update-on-quit.injectable"; describe("installing update using tray", () => { let applicationBuilder: ApplicationBuilder; let quitAndInstallUpdateMock: jest.Mock; let checkForPlatformUpdatesMock: AsyncFnMock; let downloadPlatformUpdateMock: AsyncFnMock<() => void>; + let setUpdateOnQuitMock: jest.Mock; beforeEach(() => { applicationBuilder = getApplicationBuilder(); @@ -32,6 +34,9 @@ describe("installing update using tray", () => { quitAndInstallUpdateMock = jest.fn(); checkForPlatformUpdatesMock = asyncFn(); downloadPlatformUpdateMock = asyncFn(); + setUpdateOnQuitMock = jest.fn(); + + mainDi.override(setUpdateOnQuitInjectable, () => setUpdateOnQuitMock); mainDi.override( checkForPlatformUpdatesInjectable, @@ -150,7 +155,7 @@ describe("installing update using tray", () => { await checkForUpdatesPromise; }); - it("starts downloading the update!?!?", () => { + it("starts downloading the update", () => { expect(downloadPlatformUpdateMock).toHaveBeenCalled(); }); @@ -289,7 +294,9 @@ describe("installing update using tray", () => { it("when update is discovered, does not check update from other update channels", async () => { checkForPlatformUpdatesMock.mockClear(); - await checkForPlatformUpdatesMock.resolve({ updateWasDiscovered: true }); + await checkForPlatformUpdatesMock.resolve({ + updateWasDiscovered: true, + }); expect(checkForPlatformUpdatesMock).not.toHaveBeenCalled(); }); @@ -298,7 +305,9 @@ describe("installing update using tray", () => { beforeEach(async () => { checkForPlatformUpdatesMock.mockClear(); - await checkForPlatformUpdatesMock.resolve({ updateWasDiscovered: false }); + await checkForPlatformUpdatesMock.resolve({ + updateWasDiscovered: false, + }); }); it('checks updates from update channel "beta"', () => { @@ -310,7 +319,9 @@ describe("installing update using tray", () => { it("when update is discovered, does not check update from other update channels", async () => { checkForPlatformUpdatesMock.mockClear(); - await checkForPlatformUpdatesMock.resolve({ updateWasDiscovered: true }); + await checkForPlatformUpdatesMock.resolve({ + updateWasDiscovered: true, + }); expect(checkForPlatformUpdatesMock).not.toHaveBeenCalled(); }); @@ -319,7 +330,9 @@ describe("installing update using tray", () => { beforeEach(async () => { checkForPlatformUpdatesMock.mockClear(); - await checkForPlatformUpdatesMock.resolve({ updateWasDiscovered: false }); + await checkForPlatformUpdatesMock.resolve({ + updateWasDiscovered: false, + }); }); it('finally checks updates from update channel "latest"', () => { @@ -331,13 +344,67 @@ describe("installing update using tray", () => { it("when update is discovered, does not check update from other update channels", async () => { checkForPlatformUpdatesMock.mockClear(); - await checkForPlatformUpdatesMock.resolve({ updateWasDiscovered: true }); + await checkForPlatformUpdatesMock.resolve({ + updateWasDiscovered: true, + }); expect(checkForPlatformUpdatesMock).not.toHaveBeenCalled(); }); }); }); }); + + describe('given update channel "beta" is selected', () => { + let selectedUpdateChannel: { + value: IComputedValue; + setValue: (channelId: UpdateChannelId) => void; + }; + + beforeEach(() => { + selectedUpdateChannel = applicationBuilder.dis.mainDi.inject( + selectedUpdateChannelInjectable, + ); + + selectedUpdateChannel.setValue(updateChannels.beta.id); + }); + + describe("when checking for updates", () => { + beforeEach(() => { + applicationBuilder.tray.click("check-for-updates"); + }); + + describe('when update from "beta" channel is discovered', () => { + beforeEach(async () => { + await checkForPlatformUpdatesMock.resolve({ + updateWasDiscovered: true, + version: "some-beta-version", + }); + }); + + describe("when update is downloaded", () => { + beforeEach(async () => { + await downloadPlatformUpdateMock.resolve(); + }); + + it("when user would close the application, installs the update", () => { + expect(setUpdateOnQuitMock).toHaveBeenLastCalledWith(true); + }); + + it('given user changes update channel to "latest", when user would close the application, does not install the update for not being stable enough', () => { + selectedUpdateChannel.setValue(updateChannels.latest.id); + + expect(setUpdateOnQuitMock).toHaveBeenLastCalledWith(false); + }); + + it('given user changes update channel to "alpha", when user would close the application, installs the update for being stable enough', () => { + selectedUpdateChannel.setValue(updateChannels.alpha.id); + + expect(setUpdateOnQuitMock).toHaveBeenLastCalledWith(false); + }); + }); + }); + }); + }); }); xdescribe("given a non-installed update is already downloaded, when started", () => { @@ -445,7 +512,9 @@ describe("installing update using tray", () => { await checkForUpdatesPromise; }); - it("starts downloading the update!?!?", () => {}); + it("starts downloading the update", () => { + expect(downloadPlatformUpdateMock).toHaveBeenCalled(); + }); it("user cannot check for updates again yet", () => { expect( diff --git a/src/main/electron-app/features/set-update-on-quit.injectable.ts b/src/main/electron-app/features/set-update-on-quit.injectable.ts new file mode 100644 index 0000000000..43693f8eed --- /dev/null +++ b/src/main/electron-app/features/set-update-on-quit.injectable.ts @@ -0,0 +1,20 @@ +/** + * 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 electronUpdaterInjectable from "./electron-updater.injectable"; + +const setUpdateOnQuitInjectable = getInjectable({ + id: "set-update-on-quit", + + instantiate: (di) => { + const electronUpdater = di.inject(electronUpdaterInjectable); + + return (updateOnQuit: boolean) => { + electronUpdater.autoInstallOnAppQuit = updateOnQuit; + }; + }, +}); + +export default setUpdateOnQuitInjectable; diff --git a/src/main/update-app/version-update.injectable.ts b/src/main/update-app/version-update.injectable.ts index 6b426e78d1..91838eeec1 100644 --- a/src/main/update-app/version-update.injectable.ts +++ b/src/main/update-app/version-update.injectable.ts @@ -22,9 +22,11 @@ const versionUpdateInjectable = getInjectable({ const discoveredVersionState = observable.box(); const downloadingState = observable.box(false); const checkingState = observable.box(false); + const discoveredFromUpdateChannelState = observable.box(); return { discoveredVersion: computed(() => discoveredVersionState.get()), + discoveredFromUpdateChannel: computed(() => discoveredFromUpdateChannelState.get()), downloading: computed(() => downloadingState.get()), checking: computed(() => checkingState.get()), @@ -33,6 +35,7 @@ const versionUpdateInjectable = getInjectable({ discoveredVersionState, checkingState, selectedUpdateChannel.value, + discoveredFromUpdateChannelState, ), downloadUpdate: downloadUpdateFor( @@ -68,6 +71,7 @@ const checkForUpdatesFor = discoveredVersionState: IObservableValue, checkingState: IObservableValue, selectedUpdateChannel: IComputedValue, + discoveredFromUpdateChannelState: IObservableValue, ) => async () => { runInAction(() => { @@ -77,11 +81,12 @@ const checkForUpdatesFor = const checkForUpdatesStartingFromChannel = checkForUpdatesStartingFromChannelFor(checkForPlatformUpdates); - const { updateWasDiscovered, version } = await checkForUpdatesStartingFromChannel( + const { updateWasDiscovered, version, actualUpdateChannel } = await checkForUpdatesStartingFromChannel( selectedUpdateChannel.get(), ); runInAction(() => { + discoveredFromUpdateChannelState.set(actualUpdateChannel); discoveredVersionState.set(version); checkingState.set(false); }); @@ -90,12 +95,24 @@ const checkForUpdatesFor = }; -const checkForUpdatesStartingFromChannelFor = (checkForPlatformUpdates: CheckForPlatformUpdates) => { - const _recursiveCheck = async (updateChannel: UpdateChannel): Promise<{ updateWasDiscovered: boolean; version?: string }> => { +const checkForUpdatesStartingFromChannelFor = ( + checkForPlatformUpdates: CheckForPlatformUpdates, +) => { + const _recursiveCheck = async ( + updateChannel: UpdateChannel, + ): Promise<{ + updateWasDiscovered: boolean; + version?: string; + actualUpdateChannel?: UpdateChannel; + }> => { const result = await checkForPlatformUpdates(updateChannel); if (result.updateWasDiscovered) { - return { updateWasDiscovered: true, version: result.version }; + return { + updateWasDiscovered: true, + version: result.version, + actualUpdateChannel: updateChannel, + }; } if (updateChannel.moreStableUpdateChannel) { diff --git a/src/main/update-app/watch-if-update-should-happen-on-quit/start-watching-if-update-should-happen-on-quit.injectable.ts b/src/main/update-app/watch-if-update-should-happen-on-quit/start-watching-if-update-should-happen-on-quit.injectable.ts new file mode 100644 index 0000000000..ef31cf5db5 --- /dev/null +++ b/src/main/update-app/watch-if-update-should-happen-on-quit/start-watching-if-update-should-happen-on-quit.injectable.ts @@ -0,0 +1,25 @@ +/** + * 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 { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token"; +import watchIfUpdateShouldHappenOnQuitInjectable from "./watch-if-update-should-happen-on-quit.injectable"; + +const startWatchingIfUpdateShouldHappenOnQuitInjectable = getInjectable({ + id: "start-watching-if-update-should-happen-on-quit", + + instantiate: (di) => { + const watchIfUpdateShouldHappenOnQuit = di.inject(watchIfUpdateShouldHappenOnQuitInjectable); + + return { + run: () => { + watchIfUpdateShouldHappenOnQuit.start(); + }, + }; + }, + + injectionToken: onLoadOfApplicationInjectionToken, +}); + +export default startWatchingIfUpdateShouldHappenOnQuitInjectable; diff --git a/src/main/update-app/watch-if-update-should-happen-on-quit/stop-watching-if-update-should-happen-on-quit.injectable.ts b/src/main/update-app/watch-if-update-should-happen-on-quit/stop-watching-if-update-should-happen-on-quit.injectable.ts new file mode 100644 index 0000000000..b66cf927f2 --- /dev/null +++ b/src/main/update-app/watch-if-update-should-happen-on-quit/stop-watching-if-update-should-happen-on-quit.injectable.ts @@ -0,0 +1,25 @@ +/** + * 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 watchIfUpdateShouldHappenOnQuitInjectable from "./watch-if-update-should-happen-on-quit.injectable"; +import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token"; + +const stopWatchingIfUpdateShouldHappenOnQuitInjectable = getInjectable({ + id: "stop-watching-if-update-should-happen-on-quit", + + instantiate: (di) => { + const watchIfUpdateShouldHappenOnQuit = di.inject(watchIfUpdateShouldHappenOnQuitInjectable); + + return { + run: () => { + watchIfUpdateShouldHappenOnQuit.stop(); + }, + }; + }, + + injectionToken: beforeQuitOfBackEndInjectionToken, +}); + +export default stopWatchingIfUpdateShouldHappenOnQuitInjectable; diff --git a/src/main/update-app/watch-if-update-should-happen-on-quit/watch-if-update-should-happen-on-quit.injectable.ts b/src/main/update-app/watch-if-update-should-happen-on-quit/watch-if-update-should-happen-on-quit.injectable.ts new file mode 100644 index 0000000000..2f91597146 --- /dev/null +++ b/src/main/update-app/watch-if-update-should-happen-on-quit/watch-if-update-should-happen-on-quit.injectable.ts @@ -0,0 +1,48 @@ +/** + * 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 { autorun } from "mobx"; +import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable"; +import setUpdateOnQuitInjectable from "../../electron-app/features/set-update-on-quit.injectable"; +import versionUpdateInjectable from "../version-update.injectable"; +import selectedUpdateChannelInjectable from "../selected-update-channel.injectable"; +import type { UpdateChannel } from "../update-channels"; + +const watchIfUpdateShouldHappenOnQuitInjectable = getInjectable({ + id: "watch-if-update-should-happen-on-quit", + + instantiate: (di) => { + const setUpdateOnQuit = di.inject(setUpdateOnQuitInjectable); + const versionUpdate = di.inject(versionUpdateInjectable); + const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable); + + return getStartableStoppable("watch-if-update-should-happen-on-quit", () => + autorun(() => { + const sufficientlyStableUpdateChannels = + getSufficientlyStableUpdateChannels(selectedUpdateChannel.value.get()); + + const updateIsDiscoveredFromChannel = versionUpdate.discoveredFromUpdateChannel.get(); + + const updateOnQuit = sufficientlyStableUpdateChannels.includes(updateIsDiscoveredFromChannel); + + setUpdateOnQuit(updateOnQuit); + }), + ); + }, +}); + +const getSufficientlyStableUpdateChannels = (updateChannel: UpdateChannel): UpdateChannel[] => { + if (!updateChannel.moreStableUpdateChannel) { + return [updateChannel]; + } + + return [ + updateChannel, + + ...getSufficientlyStableUpdateChannels(updateChannel.moreStableUpdateChannel), + ]; +}; + +export default watchIfUpdateShouldHappenOnQuitInjectable;