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

chore: Introduce competition for InitializableState

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-04-17 15:47:57 -04:00
parent 82fdcf69c0
commit 72a01cdc17
2 changed files with 139 additions and 4 deletions

View File

@ -5,11 +5,14 @@
import type { AsyncFnMock } from "@async-fn/jest"; import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest"; import asyncFn from "@async-fn/jest";
import type { DiContainer, Injectable } from "@ogre-tools/injectable"; import type { Runnable } from "@k8slens/run-many";
import { runManyFor } from "@k8slens/run-many";
import type { DiContainer, Injectable, InjectionToken } from "@ogre-tools/injectable";
import { createContainer, getInjectionToken } from "@ogre-tools/injectable";
import { runInAction } from "mobx"; import { runInAction } from "mobx";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import type { InitializableState } from "./create"; import type { ImplInitializableInjectionTokensArgs, Initializable, InitializableState } from "./create";
import { createInitializableState } from "./create"; import { getInjectablesForInitializable, getInitializable, createInitializableState } from "./create";
describe("InitializableState tests", () => { describe("InitializableState tests", () => {
let di: DiContainer; let di: DiContainer;
@ -75,3 +78,72 @@ describe("InitializableState tests", () => {
}); });
}); });
}); });
describe("InitializableTokens technical tests", () => {
let di: DiContainer;
let initializableToken: Initializable<number>;
let phase: InjectionToken<Runnable<void>, void>;
beforeEach(() => {
di = createContainer("irrelevant");
initializableToken = getInitializable("some-root-id");
phase = getInjectionToken({ id: "some-runnable-phase" });
});
it("throws given attempting to inject the state token", () => {
expect(() => di.inject(initializableToken.stateToken)).toThrowErrorMatchingInlineSnapshot(
`"Tried to inject non-registered injectable "irrelevant" -> "some-root-id-state-token"."`,
);
});
describe("given some implementation for initializableToken is registered", () => {
let mockInit: AsyncFnMock<ImplInitializableInjectionTokensArgs<number>["init"]>;
beforeEach(() => {
mockInit = asyncFn();
const { initializationInjectable, stateInjectable } = getInjectablesForInitializable({
init: mockInit,
phase,
token: initializableToken,
});
di.register(initializationInjectable, stateInjectable);
});
it("throws given attempting to inject the state token", () => {
expect(() => di.inject(initializableToken.stateToken)).toThrowErrorMatchingInlineSnapshot(
`"Tried to inject "some-root-id" before initialization was complete"`,
);
});
describe("given the phase is started to be run", () => {
let runManyPromise: Promise<void>;
beforeEach(() => {
runManyPromise = runManyFor(di)(phase)();
});
it("throws given attempting to inject the state token", () => {
expect(() => di.inject(initializableToken.stateToken)).toThrowErrorMatchingInlineSnapshot(
`"Tried to inject "some-root-id" before initialization was complete"`,
);
});
describe("when initialization is complete", () => {
beforeEach(async () => {
await mockInit.resolve(10);
});
it("initializes the state", () => {
expect(di.inject(initializableToken.stateToken)).toBe(10);
});
it("allows the runMany to complete", async () => {
await runManyPromise;
});
});
});
});
});

View File

@ -3,8 +3,10 @@
* 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 { Runnable } from "@k8slens/run-many";
import type { DiContainerForInjection, Injectable, InjectionToken } from "@ogre-tools/injectable"; import type { DiContainerForInjection, Injectable, InjectionToken } from "@ogre-tools/injectable";
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectionToken, getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
export interface CreateInitializableStateArgs<T> { export interface CreateInitializableStateArgs<T> {
id: string; id: string;
@ -21,6 +23,67 @@ type InitializableStateValue<T> =
| { set: false } | { set: false }
| { set: true; value: T } ; | { set: true; value: T } ;
export interface Initializable<T> {
readonly rootId: string;
readonly stateToken: InjectionToken<T, void>;
}
export const getInitializable = <T>(rootId: string): Initializable<T> => ({
rootId,
stateToken: getInjectionToken<T>({
id: `${rootId}-state-token`,
}),
});
type InitState<T> = { set: true; value: T } | { set: false };
export interface ImplInitializableInjectionTokensArgs<T> {
token: Initializable<T>;
init: (di: DiContainerForInjection) => T | Promise<T>;
phase: InjectionToken<Runnable<void>, void>;
runAfter?: Runnable<void>["runAfter"];
}
export const getInjectablesForInitializable = <T>({
init,
phase,
token: {
rootId,
stateToken,
},
runAfter,
}: ImplInitializableInjectionTokensArgs<T>) => {
let state: InitState<T> = { set: false };
const stateInjectable = getInjectable({
id: `${rootId}-state`,
instantiate: () => {
assert(state.set, `Tried to inject "${rootId}" before initialization was complete`);
return state.value;
},
injectionToken: stateToken,
});
const initializationInjectable = getInjectable({
id: `${rootId}-initialization`,
instantiate: (di) => ({
run: async () => {
state = {
set: true,
value: await init(di),
};
},
runAfter,
}),
injectionToken: phase,
});
return {
stateInjectable,
initializationInjectable,
};
};
export function createInitializableState<T>(args: CreateInitializableStateArgs<T>): Injectable<InitializableState<T>, unknown, void> { export function createInitializableState<T>(args: CreateInitializableStateArgs<T>): Injectable<InitializableState<T>, unknown, void> {
const { id, init, injectionToken } = args; const { id, init, injectionToken } = args;