1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Introduce Countdown component

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-07-01 11:19:16 +03:00
parent 4d99a46dfe
commit 130b5a7e8b
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
3 changed files with 211 additions and 0 deletions

View File

@ -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;

View 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();
});
});
});
});

View 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>
));