import { assertArrayContains , assertEquals , assertThrows , test , } from "./test_deps.ts" ; import { TypeDependencyGraphNode } from "./di-container.ts" ; import { Deferred , DiContainer , Inject , Injectable } from "./mod.ts" ; import { Atom , Electron , Molecule , Neutron , Person , Proton , Quark , ThingOne , } from "./shared-test-types.ts" ; test ( "DiContainer.buildDependencyGraph() - builds dependency graph" , ( ) => { const container = DiContainer . root ( ) ; const root = container . getDependencyGraph ( Molecule ) ; assertArrayContains ( ( root as TypeDependencyGraphNode ) . params . map ( ( param ) => ( param . node . kind === "type" ? param . node : undefined ) ) . find ( ( node ) => node ?. ctor === Atom ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . find ( ( node ) => node ?. ctor === Proton ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . map ( ( node ) => node ?. ctor ) ?? [ ] , [ Quark ] ) ; assertArrayContains ( ( root as TypeDependencyGraphNode ) . params . map ( ( param ) => ( param . node . kind === "type" ? param . node : undefined ) ) . find ( ( node ) => node ?. ctor === Atom ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . find ( ( node ) => node ?. ctor === Neutron ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . map ( ( node ) => node ?. ctor ) ?? [ ] , [ Quark ] ) ; assertEquals ( ( root as TypeDependencyGraphNode ) . params . map ( ( param ) => ( param . node . kind === "type" ? param . node : undefined ) ) . find ( ( node ) => node ?. ctor === Atom ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . find ( ( node ) => node ?. ctor === Electron ) ?. params ?? [ ] , [ ] ) ; assertEquals ( ( root as TypeDependencyGraphNode ) . params . map ( ( param ) => ( param . node . kind === "type" ? param . node : undefined ) ) . find ( ( node ) => node ?. ctor === Atom ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . find ( ( node ) => node ?. ctor === Proton ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . find ( ( node ) => node ?. ctor === Quark ) ?. params ?? [ ] , [ ] ) ; assertEquals ( ( root as TypeDependencyGraphNode ) . params . map ( ( param ) => ( param . node . kind === "type" ? param . node : undefined ) ) . find ( ( node ) => node ?. ctor === Atom ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . find ( ( node ) => node ?. ctor === Neutron ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . find ( ( node ) => node ?. ctor === Quark ) ?. params ?? [ ] , [ ] ) ; } ) ; test ( "DiContainer.buildDependencyGraph() - fails on unknown dependency" , ( ) => { class Piston { } @ Injectable ( ) class Engine { constructor ( public pistons ) { } } @ Injectable ( ) class Car { constructor ( public engine ) { } } assertThrows ( ( ) => { DiContainer . root ( ) . getDependencyGraph ( Car ) ; } , undefined , "Error composing Car < Engine < Piston. Piston is not registered" ) ; } ) ; test ( "DiContainer.buildDependencyGraph() - allows optional dependency" , ( ) => { const container = DiContainer . root ( ) ; container . getDependencyGraph ( Person ) ; } ) ; test ( "DiContainer.buildDependencyGraph() - fails on circular dependency" , ( ) => { @ Injectable ( ) class Money { constructor ( @ Inject ( "JOB" ) _job ) { } } @ Injectable ( ) class College { constructor ( _money ) { } } @ Injectable ( "JOB" ) class Job { constructor ( _college ) { } } assertThrows ( ( ) => { DiContainer . root ( ) . getDependencyGraph ( Money ) ; } , undefined , "Circular dependency detected: Money > JOB > College > Money" ) ; } ) ; test ( "DiContainer.buildDependencyGraph() - allows circular property dependencies" , ( ) => { const thingOneGraph = DiContainer . root ( ) . getDependencyGraph ( ThingOne ) ; const thingTwoGraph = DiContainer . root ( ) . getDependencyGraph ( "THING_TWO" ) ; assertEquals ( ( thingOneGraph as TypeDependencyGraphNode ) . props [ "otherThing" ] . node . identifier , "THING_TWO" ) ; assertEquals ( ( thingTwoGraph as TypeDependencyGraphNode ) . props [ "otherThing" ] . node . identifier , ThingOne ) ; } ) ; test ( "DiContainer.buildDependencyGraph() - child container can override parent" , ( ) => { const global = DiContainer . root ( ) ; const child = global . createChild ( "test" ) ; child . registerValue ( "PANTS" , "Jeans" ) ; const globalPersonGraph = global . getDependencyGraph ( Person ) ; const childPersonGraph = child . getDependencyGraph ( Person ) ; assertEquals ( ( childPersonGraph as TypeDependencyGraphNode ) . params [ 0 ] . node . kind , "value" ) ; assertEquals ( ( globalPersonGraph as TypeDependencyGraphNode ) . params [ 0 ] . node . kind , "null" ) ; } ) ; test ( "DiContainer.buildDependencyGraph() - can defer dependencies" , ( ) => { @ Injectable ( "FOO" ) class Foo { constructor ( @ Inject ( "BAR" ) _bar ) { } } @ Injectable ( "BAR" ) class Bar { constructor ( @ Inject ( "FOO" , { defer : true } ) _foo ) { } } DiContainer . root ( ) . getDependencyGraph ( "FOO" ) ; } ) ; test ( "DiContainer.import() - imports from another container" , ( ) => { const container1 = new DiContainer ( "test1" ) ; const container2 = new DiContainer ( "test2" ) ; const container3 = new DiContainer ( "test3" ) ; container1 . registerType ( Quark , Quark ) ; container1 . registerType ( Electron , Electron ) ; container2 . registerType ( Proton , Proton , [ { identifier : Quark } ] ) ; container2 . registerType ( Neutron , Neutron , [ { identifier : Quark } ] ) ; container2 . registerType ( Atom , Atom , [ { identifier : Proton } , { identifier : Neutron } , { identifier : Electron } , ] ) ; container2 . import ( Quark , container1 ) ; container2 . import ( Electron , container1 ) ; container3 . import ( Atom , container2 ) ; const atomGraph = container3 . getDependencyGraph ( Atom ) ; assertArrayContains ( ( atomGraph as TypeDependencyGraphNode ) . params . map ( ( param ) => ( param . node . kind === "type" ? param . node : undefined ) ) . find ( ( node ) => node ?. ctor === Proton ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . map ( ( node ) => node ?. ctor ) ?? [ ] , [ Quark ] ) ; assertArrayContains ( ( atomGraph as TypeDependencyGraphNode ) . params . map ( ( param ) => ( param . node . kind === "type" ? param . node : undefined ) ) . find ( ( node ) => node ?. ctor === Neutron ) ?. params . map ( ( param ) => param . node . kind === "type" ? param . node : undefined ) . map ( ( node ) => node ?. ctor ) ?? [ ] , [ Quark ] ) ; assertArrayContains ( ( atomGraph as TypeDependencyGraphNode ) . params . map ( ( param ) => ( param . node . kind === "type" ? param . node : undefined ) ) . map ( ( node ) => node ?. ctor ) ?? [ ] , [ Electron ] ) ; assertThrows ( ( ) => container3 . getDependencyGraph ( Neutron ) , undefined , "Error composing Neutron. Neutron is not registered" ) ; } ) ; test ( "DiContainer.import() - does not import non-imported definitions" , ( ) => { const container1 = new DiContainer ( "test1" ) ; const container2 = new DiContainer ( "test2" ) ; const container3 = new DiContainer ( "test3" ) ; container1 . registerType ( Quark , Quark ) ; container1 . registerType ( Electron , Electron ) ; container2 . registerType ( Proton , Proton , [ { identifier : Quark } ] ) ; container2 . registerType ( Neutron , Neutron , [ { identifier : Quark } ] ) ; container2 . registerType ( Atom , Atom , [ { identifier : Proton } , { identifier : Neutron } , { identifier : Electron } , ] ) ; container2 . import ( Quark , container1 ) ; container2 . import ( Electron , container1 ) ; container3 . import ( Atom , container2 ) ; assertThrows ( ( ) => { container3 . getDependencyGraph ( Neutron ) ; } , undefined , "Error composing Neutron. Neutron is not registered" ) ; } ) ;