mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Force update after thirty days since update was downloaded (#5776)
This commit is contained in:
parent
eb6cc70143
commit
6e5c8e0427
@ -0,0 +1,708 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`force user to update when too long since update was downloaded when application is started given checking for updates and it resolves, when update was downloaded renders 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="ClusterManager"
|
||||
>
|
||||
<div
|
||||
class="topBar"
|
||||
>
|
||||
<div
|
||||
class="items"
|
||||
>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
data-testid="home-button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="home"
|
||||
>
|
||||
home
|
||||
</span>
|
||||
</i>
|
||||
<i
|
||||
class="Icon material interactive disabled focusable"
|
||||
data-testid="history-back"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_back"
|
||||
>
|
||||
arrow_back
|
||||
</span>
|
||||
</i>
|
||||
<i
|
||||
class="Icon material interactive disabled focusable"
|
||||
data-testid="history-forward"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_forward"
|
||||
>
|
||||
arrow_forward
|
||||
</span>
|
||||
</i>
|
||||
<button
|
||||
class="updateButton"
|
||||
data-testid="update-button"
|
||||
data-warning-level="light"
|
||||
id="update-lens-button"
|
||||
>
|
||||
Update
|
||||
<i
|
||||
class="Icon icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="items"
|
||||
/>
|
||||
</div>
|
||||
<main>
|
||||
<div
|
||||
id="lens-views"
|
||||
/>
|
||||
<div
|
||||
class="flex justify-center Welcome align-center"
|
||||
data-testid="welcome-page"
|
||||
>
|
||||
<div
|
||||
data-testid="welcome-banner-container"
|
||||
style="width: 320px;"
|
||||
>
|
||||
<i
|
||||
class="Icon logo svg focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
/>
|
||||
</i>
|
||||
<div
|
||||
class="flex justify-center"
|
||||
>
|
||||
<div
|
||||
data-testid="welcome-text-container"
|
||||
style="width: 320px;"
|
||||
>
|
||||
<h2>
|
||||
Welcome to OpenLens 5!
|
||||
</h2>
|
||||
<p>
|
||||
To get you started we have auto-detected your clusters in your
|
||||
|
||||
kubeconfig file and added them to the catalog, your centralized
|
||||
|
||||
view for managing all your cloud-native resources.
|
||||
<br />
|
||||
<br />
|
||||
If you have any questions or feedback, please join our
|
||||
<a
|
||||
class="link"
|
||||
href="https://join.slack.com/t/k8slens/shared_invite/zt-wcl8jq3k-68R5Wcmk1o95MLBE5igUDQ"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Lens Community slack channel
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<ul
|
||||
class="block"
|
||||
data-testid="welcome-menu-container"
|
||||
style="width: 320px;"
|
||||
>
|
||||
<li
|
||||
class="flex grid-12"
|
||||
>
|
||||
<i
|
||||
class="Icon box col-1 material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="view_list"
|
||||
>
|
||||
view_list
|
||||
</span>
|
||||
</i>
|
||||
<a
|
||||
class="box col-10"
|
||||
>
|
||||
Browse Clusters in Catalog
|
||||
</a>
|
||||
<i
|
||||
class="Icon box col-1 material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="navigate_next"
|
||||
>
|
||||
navigate_next
|
||||
</span>
|
||||
</i>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<div
|
||||
class="HotbarMenu flex column"
|
||||
>
|
||||
<div
|
||||
class="HotbarItems flex column gaps"
|
||||
/>
|
||||
<div
|
||||
class="HotbarSelector"
|
||||
>
|
||||
<i
|
||||
class="Icon Icon previous material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_left"
|
||||
>
|
||||
arrow_left
|
||||
</span>
|
||||
</i>
|
||||
<div
|
||||
class="HotbarIndex"
|
||||
>
|
||||
<div
|
||||
class="badge Badge small clickable"
|
||||
id="hotbarIndex"
|
||||
>
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_right"
|
||||
>
|
||||
arrow_right
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="StatusBar"
|
||||
>
|
||||
<div
|
||||
class="leftSide"
|
||||
data-testid="status-bar-left"
|
||||
/>
|
||||
<div
|
||||
class="rightSide"
|
||||
data-testid="status-bar-right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="Notifications flex column align-flex-end"
|
||||
/>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`force user to update when too long since update was downloaded when application is started given checking for updates and it resolves, when update was downloaded when enough time passes to consider that update must be installed renders 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="ClusterManager"
|
||||
>
|
||||
<div
|
||||
class="topBar"
|
||||
>
|
||||
<div
|
||||
class="items"
|
||||
>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
data-testid="home-button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="home"
|
||||
>
|
||||
home
|
||||
</span>
|
||||
</i>
|
||||
<i
|
||||
class="Icon material interactive disabled focusable"
|
||||
data-testid="history-back"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_back"
|
||||
>
|
||||
arrow_back
|
||||
</span>
|
||||
</i>
|
||||
<i
|
||||
class="Icon material interactive disabled focusable"
|
||||
data-testid="history-forward"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_forward"
|
||||
>
|
||||
arrow_forward
|
||||
</span>
|
||||
</i>
|
||||
<button
|
||||
class="updateButton"
|
||||
data-testid="update-button"
|
||||
data-warning-level="light"
|
||||
id="update-lens-button"
|
||||
>
|
||||
Update
|
||||
<i
|
||||
class="Icon icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="items"
|
||||
/>
|
||||
</div>
|
||||
<main>
|
||||
<div
|
||||
id="lens-views"
|
||||
/>
|
||||
<div
|
||||
class="flex justify-center Welcome align-center"
|
||||
data-testid="welcome-page"
|
||||
>
|
||||
<div
|
||||
data-testid="welcome-banner-container"
|
||||
style="width: 320px;"
|
||||
>
|
||||
<i
|
||||
class="Icon logo svg focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
/>
|
||||
</i>
|
||||
<div
|
||||
class="flex justify-center"
|
||||
>
|
||||
<div
|
||||
data-testid="welcome-text-container"
|
||||
style="width: 320px;"
|
||||
>
|
||||
<h2>
|
||||
Welcome to OpenLens 5!
|
||||
</h2>
|
||||
<p>
|
||||
To get you started we have auto-detected your clusters in your
|
||||
|
||||
kubeconfig file and added them to the catalog, your centralized
|
||||
|
||||
view for managing all your cloud-native resources.
|
||||
<br />
|
||||
<br />
|
||||
If you have any questions or feedback, please join our
|
||||
<a
|
||||
class="link"
|
||||
href="https://join.slack.com/t/k8slens/shared_invite/zt-wcl8jq3k-68R5Wcmk1o95MLBE5igUDQ"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Lens Community slack channel
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<ul
|
||||
class="block"
|
||||
data-testid="welcome-menu-container"
|
||||
style="width: 320px;"
|
||||
>
|
||||
<li
|
||||
class="flex grid-12"
|
||||
>
|
||||
<i
|
||||
class="Icon box col-1 material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="view_list"
|
||||
>
|
||||
view_list
|
||||
</span>
|
||||
</i>
|
||||
<a
|
||||
class="box col-10"
|
||||
>
|
||||
Browse Clusters in Catalog
|
||||
</a>
|
||||
<i
|
||||
class="Icon box col-1 material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="navigate_next"
|
||||
>
|
||||
navigate_next
|
||||
</span>
|
||||
</i>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<div
|
||||
class="HotbarMenu flex column"
|
||||
>
|
||||
<div
|
||||
class="HotbarItems flex column gaps"
|
||||
/>
|
||||
<div
|
||||
class="HotbarSelector"
|
||||
>
|
||||
<i
|
||||
class="Icon Icon previous material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_left"
|
||||
>
|
||||
arrow_left
|
||||
</span>
|
||||
</i>
|
||||
<div
|
||||
class="HotbarIndex"
|
||||
>
|
||||
<div
|
||||
class="badge Badge small clickable"
|
||||
id="hotbarIndex"
|
||||
>
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_right"
|
||||
>
|
||||
arrow_right
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="StatusBar"
|
||||
>
|
||||
<div
|
||||
class="leftSide"
|
||||
data-testid="status-bar-left"
|
||||
/>
|
||||
<div
|
||||
class="rightSide"
|
||||
data-testid="status-bar-right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="Notifications flex column align-flex-end"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="Animate opacity-scale Dialog flex center modal pinned enter"
|
||||
style="--enter-duration: 100ms; --leave-duration: 100ms;"
|
||||
>
|
||||
<div
|
||||
class="box"
|
||||
>
|
||||
<div
|
||||
class="ForceUpdateModal"
|
||||
data-testid="must-update-immediately"
|
||||
>
|
||||
<div
|
||||
class="header"
|
||||
>
|
||||
<h2>
|
||||
Please update
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
<p>
|
||||
An update to Lens Desktop is required to continue using the application.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="footer"
|
||||
>
|
||||
<button
|
||||
class="Button primary"
|
||||
data-testid="update-now-from-must-update-immediately-modal"
|
||||
type="button"
|
||||
>
|
||||
Update
|
||||
|
||||
(
|
||||
<span
|
||||
data-testid="countdown-to-automatic-update"
|
||||
>
|
||||
5
|
||||
</span>
|
||||
)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`force user to update when too long since update was downloaded when application is started given checking for updates and it resolves, when update was downloaded when not enough time passes to consider that update must be installed renders 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="ClusterManager"
|
||||
>
|
||||
<div
|
||||
class="topBar"
|
||||
>
|
||||
<div
|
||||
class="items"
|
||||
>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
data-testid="home-button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="home"
|
||||
>
|
||||
home
|
||||
</span>
|
||||
</i>
|
||||
<i
|
||||
class="Icon material interactive disabled focusable"
|
||||
data-testid="history-back"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_back"
|
||||
>
|
||||
arrow_back
|
||||
</span>
|
||||
</i>
|
||||
<i
|
||||
class="Icon material interactive disabled focusable"
|
||||
data-testid="history-forward"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_forward"
|
||||
>
|
||||
arrow_forward
|
||||
</span>
|
||||
</i>
|
||||
<button
|
||||
class="updateButton"
|
||||
data-testid="update-button"
|
||||
data-warning-level="light"
|
||||
id="update-lens-button"
|
||||
>
|
||||
Update
|
||||
<i
|
||||
class="Icon icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="items"
|
||||
/>
|
||||
</div>
|
||||
<main>
|
||||
<div
|
||||
id="lens-views"
|
||||
/>
|
||||
<div
|
||||
class="flex justify-center Welcome align-center"
|
||||
data-testid="welcome-page"
|
||||
>
|
||||
<div
|
||||
data-testid="welcome-banner-container"
|
||||
style="width: 320px;"
|
||||
>
|
||||
<i
|
||||
class="Icon logo svg focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
/>
|
||||
</i>
|
||||
<div
|
||||
class="flex justify-center"
|
||||
>
|
||||
<div
|
||||
data-testid="welcome-text-container"
|
||||
style="width: 320px;"
|
||||
>
|
||||
<h2>
|
||||
Welcome to OpenLens 5!
|
||||
</h2>
|
||||
<p>
|
||||
To get you started we have auto-detected your clusters in your
|
||||
|
||||
kubeconfig file and added them to the catalog, your centralized
|
||||
|
||||
view for managing all your cloud-native resources.
|
||||
<br />
|
||||
<br />
|
||||
If you have any questions or feedback, please join our
|
||||
<a
|
||||
class="link"
|
||||
href="https://join.slack.com/t/k8slens/shared_invite/zt-wcl8jq3k-68R5Wcmk1o95MLBE5igUDQ"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Lens Community slack channel
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<ul
|
||||
class="block"
|
||||
data-testid="welcome-menu-container"
|
||||
style="width: 320px;"
|
||||
>
|
||||
<li
|
||||
class="flex grid-12"
|
||||
>
|
||||
<i
|
||||
class="Icon box col-1 material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="view_list"
|
||||
>
|
||||
view_list
|
||||
</span>
|
||||
</i>
|
||||
<a
|
||||
class="box col-10"
|
||||
>
|
||||
Browse Clusters in Catalog
|
||||
</a>
|
||||
<i
|
||||
class="Icon box col-1 material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="navigate_next"
|
||||
>
|
||||
navigate_next
|
||||
</span>
|
||||
</i>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<div
|
||||
class="HotbarMenu flex column"
|
||||
>
|
||||
<div
|
||||
class="HotbarItems flex column gaps"
|
||||
/>
|
||||
<div
|
||||
class="HotbarSelector"
|
||||
>
|
||||
<i
|
||||
class="Icon Icon previous material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_left"
|
||||
>
|
||||
arrow_left
|
||||
</span>
|
||||
</i>
|
||||
<div
|
||||
class="HotbarIndex"
|
||||
>
|
||||
<div
|
||||
class="badge Badge small clickable"
|
||||
id="hotbarIndex"
|
||||
>
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_right"
|
||||
>
|
||||
arrow_right
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="StatusBar"
|
||||
>
|
||||
<div
|
||||
class="leftSide"
|
||||
data-testid="status-bar-left"
|
||||
/>
|
||||
<div
|
||||
class="rightSide"
|
||||
data-testid="status-bar-right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="Notifications flex column align-flex-end"
|
||||
/>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import type { CheckForPlatformUpdates } from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
|
||||
import checkForPlatformUpdatesInjectable from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable";
|
||||
import type { DownloadPlatformUpdate } from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
||||
import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
||||
import quitAndInstallUpdateInjectable from "../../main/application-update/quit-and-install-update.injectable";
|
||||
import timeAfterUpdateMustBeInstalledInjectable from "../../renderer/application-update/force-update-modal/time-after-update-must-be-installed.injectable";
|
||||
import secondsAfterInstallStartsInjectable from "../../renderer/application-update/force-update-modal/seconds-after-install-starts.injectable";
|
||||
|
||||
const TIME_AFTER_UPDATE_MUST_BE_INSTALLED = 1000;
|
||||
const TIME_AFTER_INSTALL_STARTS = 5 * 1000;
|
||||
|
||||
describe("force user to update when too long since update was downloaded", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
let checkForPlatformUpdatesMock: AsyncFnMock<CheckForPlatformUpdates>;
|
||||
let downloadPlatformUpdateMock: AsyncFnMock<DownloadPlatformUpdate>;
|
||||
let mainDi: DiContainer;
|
||||
let quitAndInstallUpdateMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
useFakeTime("2015-10-21T07:28:00Z");
|
||||
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
|
||||
applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => {
|
||||
checkForPlatformUpdatesMock = asyncFn();
|
||||
|
||||
mainDi.override(checkForPlatformUpdatesInjectable, () => checkForPlatformUpdatesMock);
|
||||
|
||||
downloadPlatformUpdateMock = asyncFn();
|
||||
|
||||
mainDi.override(downloadPlatformUpdateInjectable, () => downloadPlatformUpdateMock);
|
||||
|
||||
quitAndInstallUpdateMock = jest.fn();
|
||||
|
||||
mainDi.override(quitAndInstallUpdateInjectable, () => quitAndInstallUpdateMock);
|
||||
|
||||
rendererDi.override(timeAfterUpdateMustBeInstalledInjectable, () => TIME_AFTER_UPDATE_MUST_BE_INSTALLED);
|
||||
|
||||
rendererDi.override(secondsAfterInstallStartsInjectable, () => TIME_AFTER_INSTALL_STARTS / 1000);
|
||||
});
|
||||
|
||||
mainDi = applicationBuilder.dis.mainDi;
|
||||
});
|
||||
|
||||
describe("when application is started", () => {
|
||||
let rendered: RenderResult;
|
||||
|
||||
beforeEach(async () => {
|
||||
rendered = await applicationBuilder.render();
|
||||
});
|
||||
|
||||
describe("given checking for updates and it resolves, when update was downloaded", () => {
|
||||
beforeEach(async () => {
|
||||
const processCheckingForUpdates = mainDi.inject(
|
||||
processCheckingForUpdatesInjectable,
|
||||
);
|
||||
|
||||
processCheckingForUpdates("irrelevant");
|
||||
|
||||
await checkForPlatformUpdatesMock.resolve({
|
||||
updateWasDiscovered: true,
|
||||
version: "42.0.0",
|
||||
});
|
||||
|
||||
await downloadPlatformUpdateMock.resolve({
|
||||
downloadWasSuccessful: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not show modal yet", () => {
|
||||
expect(rendered.queryByTestId("must-update-immediately")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("when not enough time passes to consider that update must be installed", () => {
|
||||
beforeEach(() => {
|
||||
advanceFakeTime(TIME_AFTER_UPDATE_MUST_BE_INSTALLED - 1);
|
||||
});
|
||||
|
||||
it("does not show modal yet", () => {
|
||||
expect(rendered.queryByTestId("must-update-immediately")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when enough time passes to consider that update must be installed", () => {
|
||||
beforeEach(() => {
|
||||
advanceFakeTime(TIME_AFTER_UPDATE_MUST_BE_INSTALLED);
|
||||
});
|
||||
|
||||
it("shows modal to inform about forced update", () => {
|
||||
expect(rendered.getByTestId("must-update-immediately")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("when selected to update now, restarts the application to update", () => {
|
||||
fireEvent.click(rendered.getByTestId("update-now-from-must-update-immediately-modal"));
|
||||
|
||||
expect(quitAndInstallUpdateMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows countdown for automatic update", () => {
|
||||
expect(rendered.getByTestId("countdown-to-automatic-update")).toHaveTextContent("5");
|
||||
});
|
||||
|
||||
it("when some time passes, updates the countdown for automatic update", () => {
|
||||
advanceFakeTime(1000);
|
||||
|
||||
expect(rendered.getByTestId("countdown-to-automatic-update")).toHaveTextContent("4");
|
||||
});
|
||||
|
||||
it("when not enough time passes for automatic update, does not restart the application yet", () => {
|
||||
advanceFakeTime(TIME_AFTER_INSTALL_STARTS - 1);
|
||||
|
||||
expect(quitAndInstallUpdateMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("when enough time passes for automatically update, restarts the application to update", () => {
|
||||
advanceFakeTime(TIME_AFTER_INSTALL_STARTS);
|
||||
|
||||
expect(quitAndInstallUpdateMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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 { computed } from "mobx";
|
||||
import { rootFrameChildComponentInjectionToken } from "../../frames/root-frame/root-frame-child-component-injection-token";
|
||||
import { ForceUpdateModal } from "./force-update-modal";
|
||||
import timeSinceUpdateWasDownloadedInjectable from "./time-since-update-was-downloaded.injectable";
|
||||
import updateDownloadedDateTimeInjectable from "../../../common/application-update/update-downloaded-date-time/update-downloaded-date-time.injectable";
|
||||
import timeAfterUpdateMustBeInstalledInjectable from "./time-after-update-must-be-installed.injectable";
|
||||
|
||||
const forceUpdateModalRootFrameComponentInjectable = getInjectable({
|
||||
id: "force-update-modal-root-frame-component",
|
||||
|
||||
instantiate: (di) => {
|
||||
const timeSinceUpdateWasDownloaded = di.inject(timeSinceUpdateWasDownloadedInjectable);
|
||||
const updateDownloadedDateTime = di.inject(updateDownloadedDateTimeInjectable);
|
||||
const timeWhenUpdateMustBeInstalled = di.inject(timeAfterUpdateMustBeInstalledInjectable);
|
||||
|
||||
return {
|
||||
id: "force-update-modal",
|
||||
Component: ForceUpdateModal,
|
||||
|
||||
shouldRender: computed(
|
||||
() =>
|
||||
!!updateDownloadedDateTime.value.get() &&
|
||||
timeSinceUpdateWasDownloaded.get() >= timeWhenUpdateMustBeInstalled,
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: rootFrameChildComponentInjectionToken,
|
||||
});
|
||||
|
||||
export default forceUpdateModalRootFrameComponentInjectable;
|
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
$baseline: 8px;
|
||||
|
||||
.ForceUpdateModal {
|
||||
background-color: white;
|
||||
border-radius: 1 * $baseline;
|
||||
padding: 3 * $baseline;
|
||||
width: 50 * $baseline;
|
||||
|
||||
.header {
|
||||
margin-bottom: 2 * $baseline;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-bottom: 2 * $baseline;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import React from "react";
|
||||
import restartAndInstallUpdateInjectable from "../../components/update-button/restart-and-install-update.injectable";
|
||||
import { Countdown } from "../../components/countdown/countdown";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import installUpdateCountdownInjectable from "./install-update-countdown.injectable";
|
||||
import { Dialog } from "../../components/dialog";
|
||||
import { Button } from "../../components/button";
|
||||
import styles from "./force-update-modal.module.scss";
|
||||
|
||||
interface Dependencies {
|
||||
restartAndInstallUpdate: () => void;
|
||||
secondsTill: IComputedValue<number>;
|
||||
}
|
||||
|
||||
const NonInjectedForceUpdateModal = observer(
|
||||
({ restartAndInstallUpdate, secondsTill }: Dependencies) => (
|
||||
<Dialog isOpen={true} pinned>
|
||||
<div
|
||||
data-testid="must-update-immediately"
|
||||
className={styles.ForceUpdateModal}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<h2>Please update</h2>
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<p>
|
||||
An update to Lens Desktop is required to continue using the application.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
primary
|
||||
data-testid="update-now-from-must-update-immediately-modal"
|
||||
onClick={restartAndInstallUpdate}
|
||||
label="Update"
|
||||
>
|
||||
{" "}
|
||||
(
|
||||
<Countdown
|
||||
secondsTill={secondsTill}
|
||||
data-testid="countdown-to-automatic-update"
|
||||
/>
|
||||
)
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
),
|
||||
);
|
||||
|
||||
export const ForceUpdateModal = withInjectables<Dependencies>(
|
||||
NonInjectedForceUpdateModal,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
restartAndInstallUpdate: di.inject(restartAndInstallUpdateInjectable),
|
||||
secondsTill: di.inject(installUpdateCountdownInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 countdownStateInjectable from "../../components/countdown/countdown-state.injectable";
|
||||
import secondsAfterInstallStartsInjectable from "./seconds-after-install-starts.injectable";
|
||||
import restartAndInstallUpdateInjectable from "../../components/update-button/restart-and-install-update.injectable";
|
||||
|
||||
const installUpdateCountdownInjectable = getInjectable({
|
||||
id: "install-update-countdown",
|
||||
|
||||
instantiate: (di) => {
|
||||
const secondsAfterInstallStarts = di.inject(secondsAfterInstallStartsInjectable);
|
||||
const restartAndInstallUpdate = di.inject(restartAndInstallUpdateInjectable);
|
||||
|
||||
return di.inject(countdownStateInjectable, {
|
||||
startFrom: secondsAfterInstallStarts,
|
||||
|
||||
onZero: () => {
|
||||
restartAndInstallUpdate();
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default installUpdateCountdownInjectable;
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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";
|
||||
|
||||
const secondsAfterInstallStartsInjectable = getInjectable({
|
||||
id: "seconds-after-install-starts",
|
||||
instantiate: () => 90,
|
||||
});
|
||||
|
||||
export default secondsAfterInstallStartsInjectable;
|
||||
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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";
|
||||
|
||||
const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const timeAfterUpdateMustBeInstalledInjectable = getInjectable({
|
||||
id: "time-after-update-must-be-installed",
|
||||
instantiate: () => THIRTY_DAYS,
|
||||
});
|
||||
|
||||
export default timeAfterUpdateMustBeInstalledInjectable;
|
||||
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 assert from "assert";
|
||||
import { computed } from "mobx";
|
||||
import moment from "moment";
|
||||
import updateDownloadedDateTimeInjectable from "../../../common/application-update/update-downloaded-date-time/update-downloaded-date-time.injectable";
|
||||
import { reactiveNow } from "../../../common/utils/reactive-now/reactive-now";
|
||||
|
||||
const timeSinceUpdateWasDownloadedInjectable = getInjectable({
|
||||
id: "time-since-update-was-downloaded",
|
||||
|
||||
instantiate: (di) => {
|
||||
const updateDownloadedDateTime = di.inject(updateDownloadedDateTimeInjectable);
|
||||
|
||||
return computed(() => {
|
||||
const currentTimestamp = reactiveNow();
|
||||
|
||||
const downloadedAt = updateDownloadedDateTime.value.get();
|
||||
|
||||
assert(downloadedAt);
|
||||
|
||||
return currentTimestamp - (moment(downloadedAt).unix() * 1000);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default timeSinceUpdateWasDownloadedInjectable;
|
||||
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import {
|
||||
computed,
|
||||
observable,
|
||||
onBecomeObserved,
|
||||
onBecomeUnobserved,
|
||||
runInAction,
|
||||
} from "mobx";
|
||||
|
||||
const countdownStateInjectable = getInjectable({
|
||||
id: "countdown-state",
|
||||
|
||||
instantiate: (
|
||||
di,
|
||||
{ startFrom, onZero }: { startFrom: number; onZero: () => void },
|
||||
) => {
|
||||
const state = observable.box(startFrom);
|
||||
|
||||
let intervalId: NodeJS.Timer | undefined;
|
||||
|
||||
const stop = () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
|
||||
const start = () => {
|
||||
intervalId = setInterval(() => {
|
||||
const secondsLeft = state.get() - 1;
|
||||
|
||||
runInAction(() => {
|
||||
state.set(secondsLeft);
|
||||
});
|
||||
|
||||
if (secondsLeft === 0) {
|
||||
stop();
|
||||
onZero();
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
onBecomeObserved(state, start);
|
||||
onBecomeUnobserved(state, stop);
|
||||
|
||||
return computed(() => state.get());
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
export default countdownStateInjectable;
|
||||
142
src/renderer/components/countdown/countdown.test.tsx
Normal file
142
src/renderer/components/countdown/countdown.test.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
import countdownStateInjectable from "./countdown-state.injectable";
|
||||
import type { DiRender } from "../test-utils/renderFor";
|
||||
import { renderFor } from "../test-utils/renderFor";
|
||||
import { Countdown } from "./countdown";
|
||||
import React from "react";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { advanceFakeTime, useFakeTime } from "../../../common/test-utils/use-fake-time";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observe } from "mobx";
|
||||
import { noop } from "../../../common/utils";
|
||||
|
||||
describe("countdown", () => {
|
||||
let di: DiContainer;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(() => {
|
||||
useFakeTime("2015-10-21T07:28:00Z");
|
||||
|
||||
di = createContainer("irrelevant");
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
di.register(countdownStateInjectable);
|
||||
});
|
||||
|
||||
describe("when rendering countdown", () => {
|
||||
let rendered: RenderResult;
|
||||
let onZeroMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
onZeroMock = jest.fn();
|
||||
|
||||
const secondsTill = di.inject(countdownStateInjectable, {
|
||||
startFrom: 42,
|
||||
onZero: onZeroMock,
|
||||
});
|
||||
|
||||
rendered = render(
|
||||
<Countdown secondsTill={secondsTill} />,
|
||||
);
|
||||
});
|
||||
|
||||
it("renders with initial seconds", () => {
|
||||
expect(rendered.container).toHaveTextContent("42");
|
||||
});
|
||||
|
||||
describe("when time passes", () => {
|
||||
beforeEach(() => {
|
||||
advanceFakeTime(1000);
|
||||
});
|
||||
|
||||
it("updates the seconds", () => {
|
||||
expect(rendered.container).toHaveTextContent("41");
|
||||
});
|
||||
|
||||
it("does not call callback yet", () => {
|
||||
expect(onZeroMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("when just not enough time passes to fulfill the countdown, does not call the callback yet", () => {
|
||||
advanceFakeTime(41 * 1000);
|
||||
|
||||
expect(onZeroMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when time passes enough to fulfill the countdown", () => {
|
||||
beforeEach(() => {
|
||||
advanceFakeTime(42 * 1000);
|
||||
});
|
||||
|
||||
it("shows zero as seconds", () => {
|
||||
expect(rendered.container).toHaveTextContent("0");
|
||||
});
|
||||
|
||||
it("calls the callback", () => {
|
||||
expect(onZeroMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when time passes even more", () => {
|
||||
beforeEach(() => {
|
||||
onZeroMock.mockClear();
|
||||
|
||||
advanceFakeTime(1000);
|
||||
});
|
||||
|
||||
it("does not update the countdown anymore", () => {
|
||||
expect(rendered.container).toHaveTextContent("0");
|
||||
});
|
||||
|
||||
it("does not call the callback", () => {
|
||||
expect(onZeroMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("given observed", () => {
|
||||
let onZeroMock: jest.Mock;
|
||||
let unobserve: () => void;
|
||||
let secondsTill: IComputedValue<number>;
|
||||
|
||||
beforeEach(() => {
|
||||
onZeroMock = jest.fn();
|
||||
|
||||
secondsTill = di.inject(countdownStateInjectable, {
|
||||
startFrom: 1,
|
||||
onZero: onZeroMock,
|
||||
});
|
||||
|
||||
unobserve = observe(secondsTill, noop);
|
||||
});
|
||||
|
||||
describe("given unobserved, when enough time passes so that it would fulfill the countdown", () => {
|
||||
beforeEach(() => {
|
||||
onZeroMock.mockClear();
|
||||
|
||||
unobserve();
|
||||
|
||||
advanceFakeTime(1000);
|
||||
});
|
||||
|
||||
it("does not call callback yet", () => {
|
||||
expect(onZeroMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("given observed again, when time passes to fulfill the countdown, calls the callback", () => {
|
||||
observe(secondsTill, noop);
|
||||
|
||||
advanceFakeTime(1000);
|
||||
|
||||
expect(onZeroMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
16
src/renderer/components/countdown/countdown.tsx
Normal file
16
src/renderer/components/countdown/countdown.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import type { HTMLAttributes } from "react";
|
||||
import React from "react";
|
||||
|
||||
interface CountdownProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
secondsTill: IComputedValue<number>;
|
||||
}
|
||||
|
||||
export const Countdown = observer(({ secondsTill, ...props }: CountdownProps) => (
|
||||
<span {...props}>{secondsTill.get()}</span>
|
||||
));
|
||||
Loading…
Reference in New Issue
Block a user