diff --git a/package-lock.json b/package-lock.json index 7593c1590c..69089ad681 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4706,6 +4706,17 @@ "react-dom": "^17 || ^18" } }, + "node_modules/@ogre-tools/injectable-utils": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@ogre-tools/injectable-utils/-/injectable-utils-15.1.1.tgz", + "integrity": "sha512-y6PiFaQKMQdRWeDrbz32tMVeLidaJSNm/b8y1yQEabeGliejUCVyd8e4Coh+dD6c9b1Jq+a+uNi7eHTgzITbAQ==", + "peer": true, + "peerDependencies": { + "@ogre-tools/fp": "*", + "@ogre-tools/injectable": "*", + "lodash": "^4.17.21" + } + }, "node_modules/@parcel/watcher": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", @@ -34801,9 +34812,15 @@ "name": "@k8slens/application", "version": "6.4.0-beta.13", "license": "MIT", + "devDependencies": { + "@async-fn/jest": "^1.6.4" + }, "peerDependencies": { + "@k8slens/feature-core": "^0.0.1", "@ogre-tools/fp": "^15.1.1", "@ogre-tools/injectable": "^15.1.1", + "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.1", + "@ogre-tools/injectable-utils": "^15.1.1", "lodash": "^4.17.15" } }, diff --git a/packages/technical-features/application/index.ts b/packages/technical-features/application/index.ts index 8686f83dd1..5f482865d5 100644 --- a/packages/technical-features/application/index.ts +++ b/packages/technical-features/application/index.ts @@ -1,3 +1,23 @@ +export { feature } from "./src/feature"; + +export { onLoadOfApplicationInjectionToken } from "./src/start-application/timeslots/on-load-of-application-injection-token"; +export { beforeApplicationIsLoadingInjectionToken } from "./src/start-application/timeslots/before-application-is-loading-injection-token"; +export { beforeAnythingInjectionToken } from "./src/start-application/timeslots/before-anything-injection-token"; + +export { afterBeforeAnythingInjectionToken } from "./src/start-application/timeslots/after-before-anything-injection-token"; +export { afterApplicationIsLoadedInjectionToken } from "./src/start-application/timeslots/after-application-is-loaded-injection-token"; + +export { untilReadyToStartInjectionToken } from "./src/start-application/triggers/until-ready-to-start-injection-token"; +export type { UntilReadyToStart } from "./src/start-application/triggers/until-ready-to-start-injection-token"; + +export { untilApplicationIsShownInjectionToken } from "./src/start-application/triggers/until-application-is-shown-injection-token"; +export type { UntilApplicationIsShown } from "./src/start-application/triggers/until-application-is-shown-injection-token"; + +export { untilApplicationIsReadyToLoadInjectionToken } from "./src/start-application/triggers/until-application-is-ready-to-load-injection-token"; +export type { UntilApplicationIsReadyToLoad } from "./src/start-application/triggers/until-application-is-ready-to-load-injection-token"; + +export type { StartApplication } from "./src/start-application/start-application.injectable"; +export { startApplicationInjectionToken } from "./src/start-application/start-application.injectable"; + export { applicationInformationToken } from "./src/application-information-token"; export type { ApplicationInformation } from "./src/application-information-token"; - diff --git a/packages/technical-features/application/package.json b/packages/technical-features/application/package.json index 44f922ec47..f5a101d032 100644 --- a/packages/technical-features/application/package.json +++ b/packages/technical-features/application/package.json @@ -29,8 +29,15 @@ "test": "jest --coverage --runInBand" }, "peerDependencies": { + "@k8slens/feature-core": "^0.0.1", "@ogre-tools/fp": "^15.1.1", "@ogre-tools/injectable": "^15.1.1", + "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.1", + "@ogre-tools/injectable-utils": "^15.1.1", "lodash": "^4.17.15" + }, + + "devDependencies": { + "@async-fn/jest": "^1.6.4" } } diff --git a/packages/technical-features/application/src/feature.ts b/packages/technical-features/application/src/feature.ts new file mode 100644 index 0000000000..d2ea3df020 --- /dev/null +++ b/packages/technical-features/application/src/feature.ts @@ -0,0 +1,17 @@ +import { getFeature } from "@k8slens/feature-core"; +import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration"; + +export const feature = getFeature({ + id: "application", + + register: (di) => { + autoRegister({ + di, + targetModule: module, + + getRequireContexts: () => [ + require.context("./", true, /\.injectable\.(ts|tsx)$/), + ], + }); + }, +}); diff --git a/packages/technical-features/application/src/start-application/start-application.injectable.ts b/packages/technical-features/application/src/start-application/start-application.injectable.ts new file mode 100644 index 0000000000..9c78280676 --- /dev/null +++ b/packages/technical-features/application/src/start-application/start-application.injectable.ts @@ -0,0 +1,75 @@ +import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; +import { runManySyncFor, runManyFor } from "@ogre-tools/injectable-utils"; +import { beforeAnythingInjectionToken } from "./timeslots/before-anything-injection-token"; +import { + afterBeforeAnythingInjectionToken +} from "./timeslots/after-before-anything-injection-token"; +import { untilReadyToStartInjectionToken } from "./triggers/until-ready-to-start-injection-token"; +import { + beforeApplicationIsLoadingInjectionToken +} from "./timeslots/before-application-is-loading-injection-token"; +import { untilApplicationIsReadyToLoadInjectionToken } from "./triggers/until-application-is-ready-to-load-injection-token"; +import { + onLoadOfApplicationInjectionToken +} from "./timeslots/on-load-of-application-injection-token"; +import { + afterApplicationIsLoadedInjectionToken +} from "./timeslots/after-application-is-loaded-injection-token"; +import { untilApplicationIsShownInjectionToken } from "./triggers/until-application-is-shown-injection-token"; + +export type StartApplication = () => void; + +export const startApplicationInjectionToken = + getInjectionToken({ + id: "start-application-injection-token", + }); + +const startApplicationInjectable = getInjectable({ + id: "start-application", + + instantiate: (di): StartApplication => { + const untilReadyToStart = di.inject(untilReadyToStartInjectionToken); + const untilApplicationIsReadyToLoad = di.inject(untilApplicationIsReadyToLoadInjectionToken); + const untilApplicationIsShown = di.inject(untilApplicationIsShownInjectionToken); + + const runManyAsync = runManyFor(di) + const runManySync = runManySyncFor(di) + + const beforeAnything = runManySync( + beforeAnythingInjectionToken + ); + + const afterBeforeAnything = runManySync( + afterBeforeAnythingInjectionToken + ); + + const beforeApplicationIsLoading = runManyAsync( + beforeApplicationIsLoadingInjectionToken + ); + + const onLoadOfApplication = runManyAsync(onLoadOfApplicationInjectionToken); + + const afterApplicationIsLoaded = runManyAsync( + afterApplicationIsLoadedInjectionToken + ); + + return async () => { + beforeAnything(); + afterBeforeAnything(); + + await untilReadyToStart() + + await beforeApplicationIsLoading(); + await untilApplicationIsReadyToLoad(); + + await onLoadOfApplication(); + await untilApplicationIsShown(); + + await afterApplicationIsLoaded(); + }; + }, + + injectionToken: startApplicationInjectionToken, +}); + +export default startApplicationInjectable; diff --git a/packages/technical-features/application/src/start-application/timeslots/after-application-is-loaded-injection-token.ts b/packages/technical-features/application/src/start-application/timeslots/after-application-is-loaded-injection-token.ts new file mode 100644 index 0000000000..6e04840f6e --- /dev/null +++ b/packages/technical-features/application/src/start-application/timeslots/after-application-is-loaded-injection-token.ts @@ -0,0 +1,7 @@ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Runnable } from "@ogre-tools/injectable-utils"; + +export const afterApplicationIsLoadedInjectionToken = + getInjectionToken({ + id: "after-application-is-loaded-injection-token", + }); diff --git a/packages/technical-features/application/src/start-application/timeslots/after-before-anything-injection-token.ts b/packages/technical-features/application/src/start-application/timeslots/after-before-anything-injection-token.ts new file mode 100644 index 0000000000..b8529424cc --- /dev/null +++ b/packages/technical-features/application/src/start-application/timeslots/after-before-anything-injection-token.ts @@ -0,0 +1,6 @@ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Runnable } from "@ogre-tools/injectable-utils"; + +export const afterBeforeAnythingInjectionToken = getInjectionToken({ + id: "after-before-anything", +}); diff --git a/packages/technical-features/application/src/start-application/timeslots/before-anything-injection-token.ts b/packages/technical-features/application/src/start-application/timeslots/before-anything-injection-token.ts new file mode 100644 index 0000000000..f8d2ef9203 --- /dev/null +++ b/packages/technical-features/application/src/start-application/timeslots/before-anything-injection-token.ts @@ -0,0 +1,6 @@ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Runnable } from "@ogre-tools/injectable-utils"; + +export const beforeAnythingInjectionToken = getInjectionToken({ + id: "before-anything", +}); diff --git a/packages/technical-features/application/src/start-application/timeslots/before-application-is-loading-injection-token.ts b/packages/technical-features/application/src/start-application/timeslots/before-application-is-loading-injection-token.ts new file mode 100644 index 0000000000..54bff82be5 --- /dev/null +++ b/packages/technical-features/application/src/start-application/timeslots/before-application-is-loading-injection-token.ts @@ -0,0 +1,7 @@ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Runnable } from "@ogre-tools/injectable-utils"; + +export const beforeApplicationIsLoadingInjectionToken = + getInjectionToken({ + id: "before-application-is-loading-injection-token", + }); diff --git a/packages/technical-features/application/src/start-application/timeslots/on-load-of-application-injection-token.ts b/packages/technical-features/application/src/start-application/timeslots/on-load-of-application-injection-token.ts new file mode 100644 index 0000000000..c732329e29 --- /dev/null +++ b/packages/technical-features/application/src/start-application/timeslots/on-load-of-application-injection-token.ts @@ -0,0 +1,6 @@ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Runnable } from "@ogre-tools/injectable-utils"; + +export const onLoadOfApplicationInjectionToken = getInjectionToken({ + id: "on-load-of-application", +}); diff --git a/packages/technical-features/application/src/start-application/triggers/until-application-is-ready-to-load-injection-token.ts b/packages/technical-features/application/src/start-application/triggers/until-application-is-ready-to-load-injection-token.ts new file mode 100644 index 0000000000..2ec5eea302 --- /dev/null +++ b/packages/technical-features/application/src/start-application/triggers/until-application-is-ready-to-load-injection-token.ts @@ -0,0 +1,8 @@ +import { getInjectionToken } from "@ogre-tools/injectable"; + +export type UntilApplicationIsReadyToLoad = () => Promise; + +export const untilApplicationIsReadyToLoadInjectionToken = + getInjectionToken({ + id: "until-application-is-ready-to-load-injection-token", + }); diff --git a/packages/technical-features/application/src/start-application/triggers/until-application-is-shown-injection-token.ts b/packages/technical-features/application/src/start-application/triggers/until-application-is-shown-injection-token.ts new file mode 100644 index 0000000000..f3a4198626 --- /dev/null +++ b/packages/technical-features/application/src/start-application/triggers/until-application-is-shown-injection-token.ts @@ -0,0 +1,8 @@ +import { getInjectionToken } from "@ogre-tools/injectable"; + +export type UntilApplicationIsShown = () => Promise; + +export const untilApplicationIsShownInjectionToken = + getInjectionToken({ + id: "until-application-is-shown-injection-token", + }); diff --git a/packages/technical-features/application/src/start-application/triggers/until-ready-to-start-injection-token.ts b/packages/technical-features/application/src/start-application/triggers/until-ready-to-start-injection-token.ts new file mode 100644 index 0000000000..34569aac24 --- /dev/null +++ b/packages/technical-features/application/src/start-application/triggers/until-ready-to-start-injection-token.ts @@ -0,0 +1,8 @@ +import { getInjectionToken } from "@ogre-tools/injectable"; + +export type UntilReadyToStart = () => Promise; + +export const untilReadyToStartInjectionToken = + getInjectionToken({ + id: "until-ready-to-start-injection-token", + }); diff --git a/packages/technical-features/application/src/starting-application.test.ts b/packages/technical-features/application/src/starting-application.test.ts new file mode 100644 index 0000000000..03ad38ffdb --- /dev/null +++ b/packages/technical-features/application/src/starting-application.test.ts @@ -0,0 +1,192 @@ +import { + createContainer, + DiContainer, + getInjectable, +} from "@ogre-tools/injectable"; +import { registerFeature } from "@k8slens/feature-core"; +import { feature } from "./feature"; +import { startApplicationInjectionToken } from "./start-application/start-application.injectable"; +import { beforeAnythingInjectionToken } from "./start-application/timeslots/before-anything-injection-token"; +import { afterBeforeAnythingInjectionToken } from "./start-application/timeslots/after-before-anything-injection-token"; +import { beforeApplicationIsLoadingInjectionToken } from "./start-application/timeslots/before-application-is-loading-injection-token"; +import { untilReadyToStartInjectionToken } from "./start-application/triggers/until-ready-to-start-injection-token"; +import asyncFn, { AsyncFnMock } from "@async-fn/jest"; +import { untilApplicationIsReadyToLoadInjectionToken } from "./start-application/triggers/until-application-is-ready-to-load-injection-token"; +import { onLoadOfApplicationInjectionToken } from "./start-application/timeslots/on-load-of-application-injection-token"; +import { untilApplicationIsShownInjectionToken } from "./start-application/triggers/until-application-is-shown-injection-token"; +import { afterApplicationIsLoadedInjectionToken } from "./start-application/timeslots/after-application-is-loaded-injection-token"; + +describe("starting-application", () => { + let di: DiContainer; + let untilReadyToStartMock: AsyncFnMock<() => Promise>; + let untilApplicationIsReadyToLoadMock: AsyncFnMock<() => Promise>; + let untilApplicationIsShownMock: AsyncFnMock<() => Promise>; + + let beforeAnythingMock: jest.Mock; + let afterBeforeAnythingMock: jest.Mock; + let beforeApplicationIsLoadingMock: AsyncFnMock<() => Promise>; + let onLoadOfApplicationMock: AsyncFnMock<() => Promise>; + let afterApplicationIsLoadedMock: AsyncFnMock<() => Promise>; + + beforeEach(() => { + di = createContainer("irrelevant"); + + registerFeature(di, feature); + + untilReadyToStartMock = asyncFn(); + untilApplicationIsReadyToLoadMock = asyncFn(); + untilApplicationIsShownMock = asyncFn(); + + beforeAnythingMock = jest.fn(); + afterBeforeAnythingMock = jest.fn(); + beforeApplicationIsLoadingMock = asyncFn(); + onLoadOfApplicationMock = asyncFn(); + afterApplicationIsLoadedMock = asyncFn(); + + const beforeAnythingInjectable = getInjectable({ + id: "before-anything", + instantiate: () => ({ run: beforeAnythingMock }), + injectionToken: beforeAnythingInjectionToken, + }); + + const afterBeforeAnythingInjectable = getInjectable({ + id: "after-before-anything", + instantiate: () => ({ run: afterBeforeAnythingMock }), + injectionToken: afterBeforeAnythingInjectionToken, + }); + + const beforeApplicationIsLoadingInjectable = getInjectable({ + id: "before-application-is-loading", + instantiate: () => ({ run: beforeApplicationIsLoadingMock }), + injectionToken: beforeApplicationIsLoadingInjectionToken, + }); + + const onLoadOfApplicationInjectable = getInjectable({ + id: "on-load-of-application", + instantiate: () => ({ run: onLoadOfApplicationMock }), + injectionToken: onLoadOfApplicationInjectionToken, + }); + + const afterApplicationIsLoadedInjectable = getInjectable({ + id: "after-application-is-loaded", + instantiate: () => ({ run: afterApplicationIsLoadedMock }), + injectionToken: afterApplicationIsLoadedInjectionToken, + }); + + const untilReadyToStartInjectable = getInjectable({ + id: "until-ready-to-start", + instantiate: () => untilReadyToStartMock, + injectionToken: untilReadyToStartInjectionToken, + }); + + const untilApplicationIsReadyToLoadInjectable = getInjectable({ + id: "until-application-is-ready-to-load", + instantiate: () => untilApplicationIsReadyToLoadMock, + injectionToken: untilApplicationIsReadyToLoadInjectionToken, + }); + + const untilApplicationIsShownInjectable = getInjectable({ + id: "until-application-is-shown", + instantiate: () => untilApplicationIsShownMock, + injectionToken: untilApplicationIsShownInjectionToken, + }); + + di.register( + untilReadyToStartInjectable, + untilApplicationIsReadyToLoadInjectable, + untilApplicationIsShownInjectable, + + beforeAnythingInjectable, + afterBeforeAnythingInjectable, + beforeApplicationIsLoadingInjectable, + onLoadOfApplicationInjectable, + afterApplicationIsLoadedInjectable + ); + }); + + describe("when application is started", () => { + beforeEach(() => { + const startApplication = di.inject(startApplicationInjectionToken); + + startApplication(); + }); + + it("calls the runnable registered in the before anything timeslot", () => { + expect(beforeAnythingMock).toHaveBeenCalled(); + }); + + it("calls the runnable registered in the after before anything timeslot", () => { + expect(afterBeforeAnythingMock).toHaveBeenCalled(); + }); + + it("does not call runnables registered in before application is loading yet", () => { + expect(beforeApplicationIsLoadingMock).not.toHaveBeenCalled(); + }); + + it("calls the trigger for when application is ready to start", () => { + expect(untilReadyToStartMock).toHaveBeenCalled(); + }); + + describe("when application is ready to be started", () => { + beforeEach(async () => { + await untilReadyToStartMock.resolve(); + }); + + it("calls runnables registered in before application is loading", () => { + expect(beforeApplicationIsLoadingMock).toHaveBeenCalled(); + }); + + it("does not call the trigger for until application is ready to load yet", () => { + expect(untilApplicationIsReadyToLoadMock).not.toHaveBeenCalled(); + }); + + describe("when runnables in before application is loading resolve", () => { + beforeEach(async () => { + await beforeApplicationIsLoadingMock.resolve(); + }); + + it("calls the trigger for until application is ready to load", () => { + expect(untilApplicationIsReadyToLoadMock).toHaveBeenCalled(); + }); + + it("does not call runnables registered in on load of application yet", () => { + expect(onLoadOfApplicationMock).not.toHaveBeenCalled(); + }); + + describe("when until application is ready to load resolves", () => { + beforeEach(async () => { + await untilApplicationIsReadyToLoadMock.resolve(); + }); + + it("calls runnables registered in on load of application", () => { + expect(onLoadOfApplicationMock).toHaveBeenCalled(); + }); + + it("does not call the trigger for until application is shown yet", () => { + expect(untilApplicationIsShownMock).not.toHaveBeenCalled(); + }); + + describe("when runnables in before application is loading resolve", () => { + beforeEach(async () => { + await onLoadOfApplicationMock.resolve(); + }); + + it("calls the trigger for until application is shown", () => { + expect(untilApplicationIsShownMock).toHaveBeenCalled(); + }); + + it("does not call runnables registered in after load of application yet", () => { + expect(afterApplicationIsLoadedMock).not.toHaveBeenCalled(); + }); + + it('when until application is shown resolves, calls runnables registered in after load of application', async () => { + await untilApplicationIsShownMock.resolve(); + + expect(afterApplicationIsLoadedMock).toHaveBeenCalled(); + }); + }); + }); + }); + }); + }); +});