From 03f3d0933a240b9d32e6104091b3488d75fa8167 Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Mon, 16 May 2022 13:55:25 +0300 Subject: [PATCH] Introduce abstraction for a state that is shared between environments Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen --- .../sync-box/create-sync-box.injectable.ts | 45 +++++++ .../sync-box-channel-listener.injectable.ts | 32 +++++ .../sync-box/sync-box-channel.injectable.ts | 18 +++ .../sync-box/sync-box-state.injectable.ts | 18 +++ src/common/sync-box/sync-box.test.ts | 116 ++++++++++++++++++ 5 files changed, 229 insertions(+) create mode 100644 src/common/sync-box/create-sync-box.injectable.ts create mode 100644 src/common/sync-box/sync-box-channel-listener.injectable.ts create mode 100644 src/common/sync-box/sync-box-channel.injectable.ts create mode 100644 src/common/sync-box/sync-box-state.injectable.ts create mode 100644 src/common/sync-box/sync-box.test.ts diff --git a/src/common/sync-box/create-sync-box.injectable.ts b/src/common/sync-box/create-sync-box.injectable.ts new file mode 100644 index 0000000000..7e4b44bb9f --- /dev/null +++ b/src/common/sync-box/create-sync-box.injectable.ts @@ -0,0 +1,45 @@ +/** + * 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 type { IComputedValue } from "mobx"; +import { computed } from "mobx"; +import syncBoxChannelInjectable from "./sync-box-channel.injectable"; +import { sendToAgnosticChannelInjectionToken } from "./channel/send-to-agnostic-channel-injection-token"; +import syncBoxStateInjectable from "./sync-box-state.injectable"; + +const createSyncBoxInjectable = getInjectable({ + id: "create-sync-box", + + instantiate: (di) => { + const syncBoxChannel = di.inject(syncBoxChannelInjectable); + const sendToAgnosticChannel = di.inject(sendToAgnosticChannelInjectionToken); + const getSyncBoxState = (id: string) => di.inject(syncBoxStateInjectable, id); + + return (id: string): SyncBox => { + const state = getSyncBoxState(id); + + return { + id, + + value: computed(() => state.get()), + + set: (value) => { + state.set(value); + + sendToAgnosticChannel(syncBoxChannel, { id, value }); + }, + }; + }; + }, +}); + +export default createSyncBoxInjectable; + + +export interface SyncBox { + id: string; + value: IComputedValue; + set: (value: TValue) => void; +} diff --git a/src/common/sync-box/sync-box-channel-listener.injectable.ts b/src/common/sync-box/sync-box-channel-listener.injectable.ts new file mode 100644 index 0000000000..a9ed8fd98e --- /dev/null +++ b/src/common/sync-box/sync-box-channel-listener.injectable.ts @@ -0,0 +1,32 @@ +/** + * 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 syncBoxChannelInjectable from "./sync-box-channel.injectable"; +import { channelListenerInjectionToken } from "./channel/channel-listener-injection-token"; +import syncBoxStateInjectable from "./sync-box-state.injectable"; + +const syncBoxChannelListenerInjectable = getInjectable({ + id: "sync-box-channel-listener", + + instantiate: (di) => { + const getSyncBoxState = (id: string) => di.inject(syncBoxStateInjectable, id); + + return { + channel: di.inject(syncBoxChannelInjectable), + + handler: ({ id, value }) => { + const target = getSyncBoxState(id); + + if (target) { + target.set(value); + } + }, + }; + }, + + injectionToken: channelListenerInjectionToken, +}); + +export default syncBoxChannelListenerInjectable; diff --git a/src/common/sync-box/sync-box-channel.injectable.ts b/src/common/sync-box/sync-box-channel.injectable.ts new file mode 100644 index 0000000000..4aaa3b1252 --- /dev/null +++ b/src/common/sync-box/sync-box-channel.injectable.ts @@ -0,0 +1,18 @@ +/** + * 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 { channelInjectionToken } from "./channel/channel-injection-token"; + +const syncBoxChannelInjectable = getInjectable({ + id: "sync-box-channel", + + instantiate: () => ({ + id: "sync-box-channel", + }), + + injectionToken: channelInjectionToken, +}); + +export default syncBoxChannelInjectable; diff --git a/src/common/sync-box/sync-box-state.injectable.ts b/src/common/sync-box/sync-box-state.injectable.ts new file mode 100644 index 0000000000..e695833da4 --- /dev/null +++ b/src/common/sync-box/sync-box-state.injectable.ts @@ -0,0 +1,18 @@ +/** + * 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 { observable } from "mobx"; + +const syncBoxStateInjectable = getInjectable({ + id: "sync-box-state", + + instantiate: () => observable.box(), + + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, id: string) => id, + }), +}); + +export default syncBoxStateInjectable; diff --git a/src/common/sync-box/sync-box.test.ts b/src/common/sync-box/sync-box.test.ts new file mode 100644 index 0000000000..14f1473efc --- /dev/null +++ b/src/common/sync-box/sync-box.test.ts @@ -0,0 +1,116 @@ +/** + * 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 { observe, runInAction } from "mobx"; +import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import type { SyncBox } from "./create-sync-box.injectable"; +import createSyncBoxInjectable from "./create-sync-box.injectable"; +import applicationWindowInjectable from "../../main/start-main-application/lens-window/application-window/application-window.injectable"; + +describe("sync-box", () => { + let syncBoxInMain: SyncBox; + let syncBoxInRenderer: SyncBox; + let applicationBuilder: ApplicationBuilder; + + beforeEach(() => { + applicationBuilder = getApplicationBuilder(); + + const someInjectable = getInjectable({ + id: "some-injectable", + + instantiate: (di) => { + const createSyncBox = di.inject(createSyncBoxInjectable); + + return createSyncBox("some-state"); + }, + }); + + applicationBuilder.dis.mainDi.register(someInjectable); + applicationBuilder.dis.rendererDi.register(someInjectable); + + syncBoxInMain = applicationBuilder.dis.mainDi.inject(someInjectable); + syncBoxInRenderer = applicationBuilder.dis.rendererDi.inject(someInjectable); + }); + + describe("when application starts", () => { + beforeEach(() => { + + }); + + it("", () => { + + }); + + describe("when window starts", () => { + it("", () => { + + }); + }); + }); + + describe("when application starts with a window", () => { + let valueInRenderer: string; + let valueInMain: string; + + beforeEach(async () => { + await applicationBuilder.render(); + + const applicationWindow = applicationBuilder.dis.mainDi.inject( + applicationWindowInjectable, + ); + + await applicationWindow.show(); + + observe(syncBoxInRenderer.value, ({ newValue }) => { + valueInRenderer = newValue as string; + }, true); + + observe(syncBoxInMain.value, ({ newValue }) => { + valueInMain = newValue as string; + }, true); + }); + + it("does not know default value in main", () => { + expect(valueInMain).toBeUndefined(); + }); + + it("does not know default value in renderer", () => { + expect(valueInRenderer).toBeUndefined(); + }); + + describe("when value is set from main", () => { + beforeEach(() => { + runInAction(() => { + syncBoxInMain.set("some-value-from-main"); + }); + }); + + it("has value in main", () => { + expect(valueInMain).toBe("some-value-from-main"); + }); + + it("has value in renderer", () => { + expect(valueInRenderer).toBe("some-value-from-main"); + }); + + describe("when value is set from renderer", () => { + beforeEach(() => { + runInAction(() => { + syncBoxInRenderer.set("some-value-from-renderer"); + }); + }); + + it("has value in main", () => { + expect(valueInMain).toBe("some-value-from-renderer"); + }); + + it("has value in renderer", () => { + expect(valueInRenderer).toBe("some-value-from-renderer"); + }); + }); + }); + }); +});