import { createContainer, DiContainer, getInjectable, Injectable } from "@ogre-tools/injectable"; import type { Feature } from "./feature"; import { registerFeature } from "./register-feature"; import { deregisterFeature } from "./deregister-feature"; import { getFeature } from "./feature"; describe("feature-dependencies", () => { describe("given a parent Feature with another Features as dependency", () => { let di: DiContainer; let someInjectable: Injectable; let someInjectableInDependencyFeature: Injectable; let someParentFeature: Feature; let someDependencyFeature: Feature; beforeEach(() => { di = createContainer("irrelevant"); someInjectable = getInjectable({ id: "some-injectable-2", instantiate: () => "some-instance", }); someInjectableInDependencyFeature = getInjectable({ id: "some-injectable", instantiate: () => "some-instance-2", }); someDependencyFeature = getFeature({ id: "some-dependency-feature", register: (di) => di.register(someInjectableInDependencyFeature), }); someParentFeature = getFeature({ id: "some-feature", register: (di) => di.register(someInjectable), dependencies: [someDependencyFeature], }); registerFeature(di, someParentFeature); }); it("when an injectable from the dependency Feature is injected, does so", () => { const actual = di.inject(someInjectableInDependencyFeature); expect(actual).toBe("some-instance-2"); }); it("when the dependency Feature is deregistered, throws", () => { expect(() => { deregisterFeature(di, someDependencyFeature); }).toThrow( 'Tried to deregister Feature "some-dependency-feature", but it is the dependency of Features "some-feature"', ); }); it("given the parent Feature is already deregistered, when also the dependency Feature is deregistered, throws", () => { deregisterFeature(di, someParentFeature); expect(() => { deregisterFeature(di, someDependencyFeature); }).toThrow('Tried to deregister feature "some-dependency-feature", but it was not registered.'); }); it("given the parent Feature is deregistered, when injecting an injectable from the dependency Feature, throws", () => { deregisterFeature(di, someParentFeature); expect(() => { di.inject(someInjectableInDependencyFeature); }).toThrow('Tried to inject non-registered injectable "irrelevant" -> "some-injectable".'); }); }); describe("given a first Feature is registered, when second Feature using the first Feature as dependency gets registered", () => { let di: DiContainer; let someInjectable: Injectable; let someFeature2: Feature; let someFeature1: Feature; beforeEach(() => { di = createContainer("irrelevant"); someInjectable = getInjectable({ id: "some-injectable", instantiate: () => "some-instance", }); someFeature1 = getFeature({ id: "some-feature-1", register: (di) => di.register(someInjectable), }); someFeature2 = getFeature({ id: "some-feature-2", register: () => {}, dependencies: [someFeature1], }); registerFeature(di, someFeature1, someFeature2); }); it("when the first Feature is deregistered, throws", () => { expect(() => { deregisterFeature(di, someFeature1); }).toThrow('Tried to deregister Feature "some-feature-1", but it is the dependency of Features "some-feature-2"'); }); it("given the second Feature is deregistered, when injecting an injectable from the first Feature, still does so", () => { deregisterFeature(di, someFeature2); const actual = di.inject(someInjectable); expect(actual).toBe("some-instance"); }); }); describe("given parent Features with a shared Feature as dependency", () => { let di: DiContainer; let someInjectableInDependencyFeature: Injectable; let someFeature1: Feature; let someFeature2: Feature; let someSharedDependencyFeature: Feature; beforeEach(() => { di = createContainer("irrelevant"); someInjectableInDependencyFeature = getInjectable({ id: "some-injectable-in-dependency-feature", instantiate: () => "some-instance", }); someSharedDependencyFeature = getFeature({ id: "some-dependency-feature", register: (di) => di.register(someInjectableInDependencyFeature), }); const someFeatureForAdditionalHierarchy = getFeature({ id: "some-feature-for-additional-hierarchy", register: () => {}, dependencies: [someSharedDependencyFeature], }); someFeature1 = getFeature({ id: "some-feature-1", register: () => {}, dependencies: [someFeatureForAdditionalHierarchy], }); someFeature2 = getFeature({ id: "some-feature-2", register: () => {}, dependencies: [someFeatureForAdditionalHierarchy], }); registerFeature(di, someFeature1, someFeature2); }); it("when the shared Feature is deregistered, throws", () => { expect(() => { deregisterFeature(di, someSharedDependencyFeature); }).toThrow( 'Tried to deregister Feature "some-dependency-feature", but it is the dependency of Features "some-feature-1, some-feature-2"', ); }); it("given only part of the parent Features get deregistered, when injecting an injectable from the shared Feature, does so", () => { deregisterFeature(di, someFeature1); const actual = di.inject(someInjectableInDependencyFeature); expect(actual).toBe("some-instance"); }); it("given all of the parent Features get deregistered, when injecting an injectable from the shared Feature, throws", () => { deregisterFeature(di, someFeature1, someFeature2); expect(() => { di.inject(someInjectableInDependencyFeature); }).toThrow('Tried to inject non-registered injectable "irrelevant" -> "some-injectable-in-dependency-feature".'); }); }); describe("given parent Features with a shared Feature as dependency and registered, when the shared Feature gets registered again", () => { let di: DiContainer; let someInjectableInDependencyFeature: Injectable; let someFeature1: Feature; let someFeature2: Feature; let someSharedDependencyFeature: Feature; beforeEach(() => { di = createContainer("irrelevant"); someInjectableInDependencyFeature = getInjectable({ id: "some-injectable-in-dependency-feature", instantiate: () => "some-instance", }); someSharedDependencyFeature = getFeature({ id: "some-dependency-feature", register: (di) => di.register(someInjectableInDependencyFeature), }); const someFeatureForAdditionalHierarchy = getFeature({ id: "some-feature-for-additional-hierarchy", register: () => {}, dependencies: [someSharedDependencyFeature], }); someFeature1 = getFeature({ id: "some-feature-1", register: () => {}, dependencies: [someFeatureForAdditionalHierarchy], }); someFeature2 = getFeature({ id: "some-feature-2", register: () => {}, dependencies: [someFeatureForAdditionalHierarchy], }); registerFeature(di, someFeature1, someFeature2, someSharedDependencyFeature); }); it("when the shared Feature is deregistered, throws", () => { expect(() => { deregisterFeature(di, someSharedDependencyFeature); }).toThrow( 'Tried to deregister Feature "some-dependency-feature", but it is the dependency of Features "some-feature-1, some-feature-2"', ); }); it("given only part of the parent Features get deregistered, when injecting an injectable from the shared Feature, does so", () => { deregisterFeature(di, someFeature1); const actual = di.inject(someInjectableInDependencyFeature); expect(actual).toBe("some-instance"); }); it("given all of the parent Features get deregistered, when injecting an injectable from the shared Feature, still does so", () => { deregisterFeature(di, someFeature1, someFeature2); const actual = di.inject(someInjectableInDependencyFeature); expect(actual).toBe("some-instance"); }); it("given all of the Features get deregistered, when injecting an injectable from the shared Feature, throws", () => { deregisterFeature(di, someFeature1, someFeature2, someSharedDependencyFeature); expect(() => { di.inject(someInjectableInDependencyFeature); }).toThrow('Tried to inject non-registered injectable "irrelevant" -> "some-injectable-in-dependency-feature".'); }); }); });