import {
  DI,
  inject,
  Registration,
  resolve,
  singleton,
  transient,
} from '@aurelia/kernel';
import { _, assert, createSpy, ISpy } from '@aurelia/testing';

function decorator() { return (target: any, context: ClassDecoratorContext) => target; }

describe('1-kernel/di.spec.ts', function () {
  describe(`The DI object`, function () {
    describe(`createContainer()`, function () {
      // it(`returns an instance of Container`, function () {
      //   const actual = DI.createContainer();
      //   assert.instanceOf(actual, Container, `actual`);
      // });

      it(`returns a new container every time`, function () {
        assert.notStrictEqual(DI.createContainer(), DI.createContainer(), `DI.createContainer()`);
      });
    });

    // describe(`getDesignParamtypes()`, function () {
    //   it(`returns PLATFORM.emptyArray if the class has no constructor or decorators`, function () {
    //     class Foo {}
    //     const actual = DI.getDesignParamtypes(Foo);
    //     assert.strictEqual(actual, PLATFORM.emptyArray, `actual`);
    //   });
    //   it(`returns PLATFORM.emptyArray if the class has a decorator but no constructor`, function () {
    //     @decorator()
    //     class Foo {}
    //     const actual = DI.getDesignParamtypes(Foo);
    //     assert.strictEqual(actual, PLATFORM.emptyArray, `actual`);
    //   });

    //   it(`returns PLATFORM.emptyArray if the class has no constructor args or decorators`, function () {
    //     class Foo { constructor() { return; } }
    //     const actual = DI.getDesignParamtypes(Foo);
    //     assert.strictEqual(actual, PLATFORM.emptyArray, `actual`);
    //   });

    //   it(`returns PLATFORM.emptyArray if the class has constructor args but no decorators`, function () {
    //     class Bar {}
    //     class Foo { constructor(public bar: Bar) {} }
    //     const actual = DI.getDesignParamtypes(Foo);
    //     assert.strictEqual(actual, PLATFORM.emptyArray, `actual`);
    //   });

    //   it(`returns PLATFORM.emptyArray if the class has constructor args and the decorator is applied via a function call`, function () {
    //     class Bar {}
    //     class Foo { constructor(public bar: Bar) {} }
    //     decorator()(Foo);
    //     const actual = DI.getDesignParamtypes(Foo);
    //     assert.strictEqual(actual, PLATFORM.emptyArray, `actual`);
    //   });

    //   it(`returns PLATFORM.emptyArray if the class is declared as an anonymous variable, even if it has ctor args and decorator is applied properly`, function () {
    //     class Bar {}
    //     @decorator()
    //     const FooInline = class { constructor(public bar: Bar) {} };
    //     const actual = DI.getDesignParamtypes(FooInline);
    //     assert.strictEqual(actual, PLATFORM.emptyArray, `actual`);
    //   });

    //   it(`returns PLATFORM.emptyArray if the class is declared as a named variable, even if it has ctor args and decorator is applied properly`, function () {
    //     class Bar {}
    //     @decorator()
    //     const FooInline = class Foo { constructor(public bar: Bar) {} };
    //     const actual = DI.getDesignParamtypes(FooInline);
    //     assert.strictEqual(actual, PLATFORM.emptyArray, `actual`);
    //   });

    //   describe(`returns an empty array if the class has a decorator but no constructor args`, function () {
    //     @decorator()
    //     class Foo { constructor() { return; } }

    //     it(_`${Foo}`, function () {
    //       const actual = DI.getDesignParamtypes(Foo);
    //       assertIsMutableArray(actual, 0);
    //     });

    //     it(_`${class {}}`, function () {
    //       let cls;
    //       function anonDecorator(): ClassDecorator { return (target: any) => cls = target; }
    //       @anonDecorator()
    //       class { constructor() { return; } }
    //       const actual = DI.getDesignParamtypes(cls);
    //       assertIsMutableArray(actual, 0);
    //     });
    //   });

    //   describe(`falls back to Object for declarations that cannot be statically analyzed`, function () {
    //     interface ArgCtor {}
    //     for (const argCtor of [
    //       class Bar {},
    //       function () { return; },
    //       () => { return; },
    //       class {},
    //       {},
    //       Error,
    //       Array,
    //       (class Bar {}).prototype,
    //       (class Bar {}).prototype.constructor
    //     ] as any[]) {
    //       @decorator()
    //       class FooDecoratorInvocation { constructor(public arg: ArgCtor) {} }

    //       it(_`${FooDecoratorInvocation} { constructor(${argCtor}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooDecoratorInvocation);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Object, `actual[0]`);
    //       });

    //       @(decorator as any)
    //       class FooDecoratorNonInvocation { constructor(public arg: ArgCtor) {} }

    //       it(_`${FooDecoratorNonInvocation} { constructor(${argCtor}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooDecoratorInvocation);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Object, `actual[0]`);
    //       });
    //     }
    //   });

    //   describe(`falls back to Object for mismatched declarations`, function () {

    //     // Technically we're testing TypeScript here, but it's still useful to have an in-place fixture to validate our assumptions
    //     // And also to have an alert mechanism for when the functionality in TypeScript changes, without having to read the changelogs

    //     // What we're verifying here is under which circumstances a function object will or won't be properly resolved as a
    //     // designParamType, and it seems like the presence of a same-name interface actually breaks this in some situations

    //     // Note: the order of declaration (interface first or other thing first) doesn't seem to matter here
    //     // But whether or not there is a direct type cast, does seem to matter in the case of AnonClass (note the negative assertion)

    //     // It's unclear whether the difference between AnonClass (which works) and AnonClassInterface (which doesn't work) is a bug in TS or not,
    //     // but it has ramifications we need to keep in mind.

    //     interface Bar {}
    //     class Bar {}

    //     interface AnonClass {}
    //     const AnonClass = class {};

    //     interface AnonClassInterface {}
    //     const AnonClassInterface: AnonClassInterface = class {};

    //     interface VarFunc {}
    //     const VarFunc = function () { return; };

    //     interface VarFuncInterface {}
    //     const VarFuncInterface: VarFuncInterface = function () { return; };

    //     interface Func {}
    //     // eslint-disable-next-line no-empty
    //     function Func() {}

    //     interface Arrow {}
    //     const Arrow = () => { return; };

    //     interface ArrowInterface {}
    //     const ArrowInterface: ArrowInterface = () => { return; };

    //     describe(`decorator invocation`, function () {
    //       @decorator()
    //       class FooBar { constructor(public arg: Bar) {} }

    //       // Note: this is a negative assertion meant to make it easier to compare this describe with the one below
    //       it(_`NOT ${FooBar} { constructor(public ${Bar}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooBar);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Bar, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooAnonClass { constructor(public arg: AnonClass) {} }

    //       // Note: this is a negative assertion meant to make it easier to compare this describe with the one below
    //       it(_`NOT ${FooAnonClass} { constructor(public ${AnonClass}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooAnonClass);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], AnonClass, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooAnonClassInterface { constructor(public arg: AnonClassInterface) {} }

    //       // this one is particularly interesting..
    //       it(_`${FooAnonClassInterface} { constructor(public ${AnonClassInterface}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooAnonClassInterface);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Object, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooVarFunc { constructor(public arg: VarFunc) {} }

    //       it(_`${FooVarFunc} { constructor(public ${VarFunc}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooVarFunc);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Object, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooVarFuncInterface { constructor(public arg: VarFuncInterface) {} }

    //       it(_`${FooVarFuncInterface} { constructor(public ${VarFuncInterface}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooVarFuncInterface);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Object, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooFunc { constructor(public arg: Func) {} }

    //       it(_`${FooFunc} { constructor(public ${Func}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooFunc);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Object, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooArrow { constructor(public arg: Arrow) {} }

    //       it(_`${FooArrow} { constructor(public ${Arrow}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooArrow);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Object, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooArrowInterface { constructor(public arg: ArrowInterface) {} }

    //       it(_`${FooArrowInterface} { constructor(public ${ArrowInterface}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooArrowInterface);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Object, `actual[0]`);
    //       });
    //     });
    //   });

    //   describe(`returns the correct types for valid declarations`, function () {
    //     class Bar {}

    //     const AnonClass = class {};

    //     const VarFunc = function () { return; };

    //     // eslint-disable-next-line no-empty
    //     function Func() {}

    //     const Arrow = () => { return; };

    //     describe(`decorator invocation`, function () {
    //       @decorator()
    //       class FooBar { constructor(public arg: Bar) {} }

    //       it(_`${FooBar} { constructor(public ${Bar}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooBar);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Bar, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooAnonClass { constructor(public arg: AnonClass) {} }

    //       it(_`${FooAnonClass} { constructor(public ${AnonClass}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooAnonClass);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], AnonClass, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooVarFunc { constructor(public arg: VarFunc) {} }

    //       it(_`${FooVarFunc} { constructor(public ${VarFunc}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooVarFunc);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], VarFunc, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooFunc { constructor(public arg: Func) {} }

    //       it(_`${FooFunc} { constructor(public ${Func}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooFunc);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Func, `actual[0]`);
    //       });

    //       @decorator()
    //       class FooArrow { constructor(public arg: Arrow) {} }

    //       it(_`${FooArrow} { constructor(public ${Arrow}) }`, function () {
    //         const actual = DI.getDesignParamtypes(FooArrow);
    //         assertIsMutableArray(actual, 1);
    //         assert.strictEqual(actual[0], Arrow, `actual[0]`);
    //       });
    //     });
    //   });
    // });

    describe(`getDependencies()`, function () {
      let getDesignParamtypes: ISpy<typeof DI.getDesignParamtypes>;

      beforeEach(function () {
        getDesignParamtypes = createSpy(DI, 'getDesignParamtypes', true);
      });

      afterEach(function () {
        getDesignParamtypes.restore();
      });

      // TODO: Enable those tests once the decorator metadata is emitted by TS.
      // The tests are disabled because TS with TC39 decorators (non-legacy), does not emit the decorator metadata as of now.
      // The following tests are dependent on that and hence cannot be successfully run.
      // Refer: https://github.com/microsoft/TypeScript/issues/55788
      it.skip(`uses getDesignParamtypes() if the static inject property does not exist`, function () {
        class Bar {}
        @decorator()
        // eslint-disable-next-line @typescript-eslint/no-useless-constructor
        class Foo { public constructor(_bar: Bar) { /* empty */ } }
        DI.getDependencies(Foo);
      });

      // TODO: Enable those tests once the decorator metadata is emitted by TS.
      // The tests are disabled because TS with TC39 decorators (non-legacy), does not emit the decorator metadata as of now.
      // The following tests are dependent on that and hence cannot be successfully run.
      // Refer: https://github.com/microsoft/TypeScript/issues/55788
      it.skip(`uses getDesignParamtypes() if the static inject property is undefined`, function () {
        class Bar {}
        @decorator()
        // eslint-disable-next-line @typescript-eslint/no-useless-constructor
        class Foo { public static inject; public constructor(_bar: Bar) { /* empty */ } }
        DI.getDependencies(Foo);
      });

      it(`throws when inject is not an array`, function () {
        class Bar {}
        class Foo { public static inject = Bar; }

        assert.throws(() => DI.getDependencies(Foo), void 0, `() => DI.getDependencies(Foo)`);
      });

      for (const deps of [
        [class Bar {}],
        [class Bar {}, class Bar {}],
        [undefined],
        [null],
        [42]
      ]) {
        it(_`returns a copy of the inject array ${deps}`, function () {
          class Foo { public static inject = deps.slice(); }
          const actual = DI.getDependencies(Foo);

          assert.deepStrictEqual(actual, deps, `actual`);
          assert.notStrictEqual(actual, Foo.inject, `actual`);
        });
      }

      for (const deps of [
        [class Bar {}],
        [class Bar {}, class Bar {}],
        [undefined],
        [null],
        [42]
      ]) {
        it(_`does not traverse the 2-layer prototype chain for inject array ${deps}`, function () {
          class Foo { public static inject = deps.slice(); }
          class Bar extends Foo { public static inject = deps.slice(); }
          const actual = DI.getDependencies(Bar);

          assert.deepStrictEqual(actual, deps, `actual`);
        });

        it(_`does not traverse the 3-layer prototype chain for inject array ${deps}`, function () {
          class Foo { public static inject = deps.slice(); }
          class Bar extends Foo { public static inject = deps.slice(); }
          class Baz extends Bar { public static inject = deps.slice(); }
          const actual = DI.getDependencies(Baz);

          assert.deepStrictEqual(actual, deps, `actual`);
        });

        it(_`does not traverse the 1-layer + 2-layer prototype chain (with gap) for inject array ${deps}`, function () {
          class Foo { public static inject = deps.slice(); }
          class Bar extends Foo { }
          class Baz extends Bar { public static inject = deps.slice(); }
          class Qux extends Baz { public static inject = deps.slice(); }
          const actual = DI.getDependencies(Qux);

          assert.deepStrictEqual(actual, deps, `actual`);
        });
      }

    });

    describe(`createInterface()`, function () {
      it(`returns a function that stringifies its default friendly name`, function () {
        const sut = DI.createInterface();
        const expected = 'InterfaceSymbol<(anonymous)>';
        assert.strictEqual(sut.toString(), expected, `sut.toString() === '${expected}'`);
        assert.strictEqual(String(sut), expected, `String(sut) === '${expected}'`);
        assert.strictEqual(`${sut}`, expected, `\`\${sut}\` === '${expected}'`);
      });

      it(`returns a function that stringifies its configured friendly name`, function () {
        const sut = DI.createInterface('IFoo');
        const expected = 'InterfaceSymbol<IFoo>';
        assert.strictEqual(sut.toString(), expected, `sut.toString() === '${expected}'`);
        assert.strictEqual(String(sut), expected, `String(sut) === '${expected}'`);
        assert.strictEqual(`${sut}`, expected, `\`\${sut}\` === '${expected}'`);
      });
    });
  });

  describe(`The inject decorator`, function () {
    class Dep1 {}
    class Dep2 {}
    class Dep3 {}

    it(`can decorate classes with explicit dependencies`, function () {
      @inject(Dep1, Dep2, Dep3)
      class Foo {}

      assert.deepStrictEqual(DI.getDependencies(Foo), [Dep1, Dep2, Dep3], `Foo['inject']`);
    });

    // it(`can decorate classes with implicit dependencies`, function () {
    //   @inject()
    //   class Foo { constructor(dep1: Dep1, dep2: Dep2, dep3: Dep3) { return; } }

    //   assert.deepStrictEqual(Foo['inject'], [Dep1, Dep2, Dep3], `Foo['inject']`);
    // });

    // TODO: Enable those tests once the decorator metadata is emitted by TS.
    // The tests are disabled because TS with TC39 decorators (non-legacy), does not emit the decorator metadata as of now.
    // The following tests are dependent on that and hence cannot be successfully run.
    // Refer: https://github.com/microsoft/TypeScript/issues/55788
    it.skip(`can decorate constructor parameters explicitly`, function () {
      class Foo {
      // eslint-disable-next-line @typescript-eslint/no-useless-constructor
      public constructor(
        _dep1 = resolve(Dep1),
        _dep2 = resolve(Dep2),
        _dep3 = resolve(Dep3),
      ) { /* empty */ }
    }

      assert.deepStrictEqual(DI.getDependencies(Foo), [Dep1, Dep2, Dep3], `Foo['inject']`);
    });

    // it(`can decorate constructor parameters implicitly`, function () {
    //   class Foo { constructor(@inject() dep1: Dep1, @inject() dep2: Dep2, @inject() dep3: Dep3) { return; } }

    //   assert.deepStrictEqual(Foo['inject'], [Dep1, Dep2, Dep3], `Foo['inject']`);
    // });

    it(`can decorate properties explicitly`, function () {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      class Foo { @inject(Dep1)public dep1; @inject(Dep2)public dep2; @inject(Dep3)public dep3; }

      const metadata = Foo[Symbol.metadata]['au:annotation:di:paramtypes'];
      assert.strictEqual(metadata['dep1'], Dep1, `Foo['inject'].dep1`);
      assert.strictEqual(metadata['dep2'], Dep2, `Foo['inject'].dep2`);
      assert.strictEqual(metadata['dep3'], Dep3, `Foo['inject'].dep3`);
    });

    // TODO: Enable those tests once the decorator metadata is emitted by TS.
    // The tests are disabled because TS with TC39 decorators (non-legacy), does not emit the decorator metadata as of now.
    // The following tests are dependent on that and hence cannot be successfully run.
    // Refer: https://github.com/microsoft/TypeScript/issues/55788
    it.skip(`cannot decorate properties implicitly`, function () {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      class Foo { @inject()public dep1: Dep1; @inject()public dep2: Dep2; @inject()public dep3: Dep3; }

      assert.strictEqual(DI.getDependencies(Foo)['dep1'], undefined, `Foo['inject'].dep1`);
      assert.strictEqual(DI.getDependencies(Foo)['dep2'], undefined, `Foo['inject'].dep2`);
      assert.strictEqual(DI.getDependencies(Foo)['dep3'], undefined, `Foo['inject'].dep3`);
    });
  });

  describe(`The transient decorator`, function () {
    it(`works as a plain decorator`, function () {
      @transient
      class Foo {}

      assert.instanceOf(Foo['register'], Function, `Foo['register']`);

      const container = DI.createContainer();

      const foo1 = container.get(Foo);
      const foo2 = container.get(Foo);

      assert.notStrictEqual(foo1, foo2, `foo1`);
    });

    it(`works as an invocation`, function () {
      @transient()
      class Foo {}

      assert.instanceOf(Foo['register'], Function, `Foo['register']`);

      const container = DI.createContainer();

      const foo1 = container.get(Foo);
      const foo2 = container.get(Foo);

      assert.notStrictEqual(foo1, foo2, `foo1`);
    });
  });

  describe(`The singleton decorator`, function () {
    it(`works as a plain decorator`, function () {
      @singleton
      class Foo {}

      assert.instanceOf(Foo['register'], Function, `Foo['register']`);

      const container = DI.createContainer();

      const foo1 = container.get(Foo);
      const foo2 = container.get(Foo);

      assert.strictEqual(foo1, foo2, `foo1`);
    });

    it(`works as an invocation`, function () {
      @singleton()
      class Foo {}

      assert.instanceOf(Foo['register'], Function, `Foo['register']`);

      const container = DI.createContainer();

      const foo1 = container.get(Foo);
      const foo2 = container.get(Foo);

      assert.strictEqual(foo1, foo2, `foo1`);
    });
  });

  // describe(`The Resolver class`, function () {
  //   let container: IContainer;
  //   let registerResolver: ReturnType<typeof spy>;

  //   beforeEach(function () {
  //     container = DI.createContainer();
  //     registerResolver = spy(container, 'registerResolver');
  //   });

  //   afterEach(function () {
  //     registerResolver.restore();
  //   });

  //   describe(`register()`, function () {
  //     it(`registers the resolver to the container with the provided key`, function () {
  //       const sut = new Resolver('foo', 0, null);
  //       sut.register(container, 'bar');
  //       expect(registerResolver).to.have.been.calledWith('bar', sut);
  //     });

  //     it(`registers the resolver to the container with its own`, function () {
  //       const sut = new Resolver('foo', 0, null);
  //       sut.register(container);
  //       expect(registerResolver).to.have.been.calledWith('foo', sut);
  //     });
  //   });

  //   describe(`resolve()`, function () {
  //     it(`instance - returns state`, function () {
  //       const state = {};
  //       const sut = new Resolver('foo', ResolverStrategy.instance, state);
  //       const actual = sut.resolve(container, container);
  //       assert.strictEqual(actual, state, `actual`);
  //     });

  //     it(`singleton - returns an instance of the type and sets strategy to instance`, function () {
  //       class Foo {}
  //       const sut = new Resolver('foo', ResolverStrategy.singleton, Foo);
  //       const actual = sut.resolve(container, container);
  //       assert.instanceOf(actual, Foo, `actual`);

  //       const actual2 = sut.resolve(container, container);
  //       assert.strictEqual(actual2, actual, `actual2`);
  //     });

  //     it(`transient - always returns a new instance of the type`, function () {
  //       class Foo {}
  //       const sut = new Resolver('foo', ResolverStrategy.transient, Foo);
  //       const actual1 = sut.resolve(container, container);
  //       assert.instanceOf(actual1, Foo, `actual1`);

  //       const actual2 = sut.resolve(container, container);
  //       assert.instanceOf(actual2, Foo, `actual2`);
  //       assert.notStrictEqual(actual2, actual1, `actual2`);
  //     });

  //     it(`array - calls resolve() on the first item in the state array`, function () {
  //       const resolver = { resolve: spy() };
  //       const sut = new Resolver('foo', ResolverStrategy.array, [resolver]);
  //       sut.resolve(container, container);
  //       expect(resolver.resolve).to.have.been.calledWith(container, container);
  //     });

  //     it(`throws for unknown strategy`, function () {
  //       const sut = new Resolver('foo', -1, null);
  //       assert.throws(() => sut.resolve(container, container), /6/, `() => sut.resolve(container, container)`);
  //     });
  //   });

  //   describe(`getFactory()`, function () {
  //     it(`returns a new singleton Factory if it does not exist`, function () {
  //       class Foo {}
  //       const sut = new Resolver(Foo, ResolverStrategy.singleton, Foo);
  //       const actual = sut.getFactory(container);
  //       assert.instanceOf(actual, Factory, `actual`);
  //       assert.strictEqual(actual.Type, Foo, `actual.Type`);
  //     });

  //     it(`returns a new transient Factory if it does not exist`, function () {
  //       class Foo {}
  //       const sut = new Resolver(Foo, ResolverStrategy.transient, Foo);
  //       const actual = sut.getFactory(container);
  //       assert.instanceOf(actual, Factory, `actual`);
  //       assert.strictEqual(actual.Type, Foo, `actual.Type`);
  //     });

  //     it(`returns a null for instance strategy`, function () {
  //       class Foo {}
  //       const sut = new Resolver(Foo, ResolverStrategy.instance, Foo);
  //       const actual = sut.getFactory(container);
  //       assert.strictEqual(actual, null, `actual`);
  //     });

  //     it(`returns a null for array strategy`, function () {
  //       class Foo {}
  //       const sut = new Resolver(Foo, ResolverStrategy.array, Foo);
  //       const actual = sut.getFactory(container);
  //       assert.strictEqual(actual, null, `actual`);
  //     });

  //     it(`returns a null for alias strategy`, function () {
  //       class Foo {}
  //       const sut = new Resolver(Foo, ResolverStrategy.alias, Foo);
  //       const actual = sut.getFactory(container);
  //       assert.strictEqual(actual, null, `actual`);
  //     });

  //     it(`returns a null for callback strategy`, function () {
  //       class Foo {}
  //       const sut = new Resolver(Foo, ResolverStrategy.callback, Foo);
  //       const actual = sut.getFactory(container);
  //       assert.strictEqual(actual, null, `actual`);
  //     });
  //   });
  // });

  // describe(`The Factory class`, function () {

  //   describe(`create()`, function () {
  //     for (const count of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) {
  //       it(`returns a new Factory with ${count} deps`, function () {
  //         class Bar {}
  //         class Foo {public static inject = Array(count).map(c => Bar); }
  //         const actual = Factory.create(Foo);
  //         assert.instanceOf(actual, Factory, `actual`);
  //         assert.strictEqual(actual.Type, Foo, `actual.Type`);
  //         if (count < 6) {
  //           assert.strictEqual(actual['invoker'], classInvokers[count], `actual['invoker']`);
  //         } else {
  //           assert.strictEqual(actual['invoker'], fallbackInvoker, `actual['invoker']`);
  //         }
  //         assert.notStrictEqual(actual['dependencies'], Foo.inject, `actual['dependencies']`);
  //         assert.deepStrictEqual(actual['dependencies'], Foo.inject, `actual['dependencies']`);
  //       });
  //     }
  //   });

  //   describe(`construct()`, function () {
  //     for (const staticCount of [0, 1, 2, 3, 4, 5, 6, 7]) {
  //       for (const dynamicCount of [0, 1, 2]) {
  //         const container = new Container();
  //         it(`instantiates a type with ${staticCount} static deps and ${dynamicCount} dynamic deps`, function () {
  //           class Bar {}
  //           class Foo {public static inject = Array(staticCount).fill(Bar); public args: any[]; constructor(...args: any[]) {this.args = args; }}
  //           const sut = Factory.create(Foo);
  //           const dynamicDeps = dynamicCount ? Array(dynamicCount).fill({}) : undefined;

  //           const actual = sut.construct(container, dynamicDeps);

  //           for (let i = 0, ii = Foo.inject.length; i < ii; ++i) {
  //             assert.instanceOf(actual.args[i], Foo.inject[i], `actual.args[i]`);
  //           }
  //           for (let i = 0, ii = dynamicDeps ? dynamicDeps.length : 0; i < ii; ++i) {
  //             assert.strictEqual(actual.args[Foo.inject.length + i], dynamicDeps[i], `actual.args[Foo.inject.length + i]`);
  //           }
  //         });
  //       }
  //     }
  //   });

  //   describe(`registerTransformer()`, function () {
  //     it(`registers the transformer`, function () {
  //       const container = new Container();
  //       class Foo {public bar; public baz; }
  //       const sut = Factory.create(Foo);
  //       // eslint-disable-next-line prefer-object-spread
  //       sut.registerTransformer(foo2 => Object.assign(foo2, { bar: 1 }));
  //       // eslint-disable-next-line prefer-object-spread
  //       sut.registerTransformer(foo2 => Object.assign(foo2, { baz: 2 }));
  //       const foo = sut.construct(container);
  //       assert.strictEqual(foo.bar, 1, `foo.bar`);
  //       assert.strictEqual(foo.baz, 2, `foo.baz`);
  //       assert.instanceOf(foo, Foo, `foo`);
  //     });
  //   });

  // });

  describe(`The Container class`, function () {
    function createFixture() {
      const sut = DI.createContainer();
      const register = createSpy();

      return { sut, register };
    }

    describe(`register()`, function () {
      it(_`calls register() on {register}`, function () {
        const { sut, register } = createFixture();
        sut.register({register});

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on {register},{register}`, function () {
        const { sut, register } = createFixture();
        sut.register({register}, {register});

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on [{register},{register}]`, function () {
        const { sut, register } = createFixture();
        sut.register([{register}, {register}] as any);

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on {foo:{register}}`, function () {
        const { sut, register } = createFixture();
        sut.register({foo: {register}});

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on {foo:{register}},{foo:{register}}`, function () {
        const { sut, register } = createFixture();
        sut.register({foo: {register}}, {foo: {register}});

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on [{foo:{register}},{foo:{register}}]`, function () {
        const { sut, register } = createFixture();
        sut.register([{foo: {register}}, {foo: {register}}] as any);

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on {register},{foo:{register}}`, function () {
        const { sut, register } = createFixture();
        sut.register({register}, {foo: {register}});

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on [{register},{foo:{register}}]`, function () {
        const { sut, register } = createFixture();
        sut.register([{register}, {foo: {register}}] as any);

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on [{register},{}]`, function () {
        const { sut, register } = createFixture();
        sut.register([{register}, {}] as any);

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on [{},{register}]`, function () {
        const { sut, register } = createFixture();
        sut.register([{}, {register}] as any);

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on [{foo:{register}},{foo:{}}]`, function () {
        const { sut, register } = createFixture();
        sut.register([{foo: {register}}, {foo: {}}] as any);

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
          ],
          `register.calls`,
        );
      });

      it(_`calls register() on [{foo:{}},{foo:{register}}]`, function () {
        const { sut, register } = createFixture();
        sut.register([{foo: {}}, {foo: {register}}] as any);

        assert.deepStrictEqual(
          register.calls,
          [
            [sut],
          ],
          `register.calls`,
        );
      });

      describe(`does NOT throw when attempting to register primitive values`, function () {
        for (const value of [
          void 0,
          null,
          true,
          false,
          '',
          'asdf',
          NaN,
          Infinity,
          0,
          42,
          Symbol(),
          Symbol('a'),
        ]) {
          it(`{foo:${String(value)}}`, function () {
            const { sut } = createFixture();
            sut.register({foo:value});
          });

          it(`{foo:{bar:${String(value)}}}`, function () {
            const { sut } = createFixture();
            sut.register({foo:{bar:value}});
          });

          it(`[${String(value)}]`, function () {
            const { sut } = createFixture();
            sut.register([value]);
          });

          it(`${String(value)}`, function () {
            const { sut } = createFixture();
            sut.register(value);
          });
        }
      });
    });

    describe(`deregister()`, function () {
      function createFixture() {
        const sut = DI.createContainer();
        const classInstance = class {};

        return { sut, classInstance };
      }

      it(`deregisters a registered instance by key`, function () {
        const { sut, classInstance } = createFixture();
        const key = 'key';
        const instance = new classInstance();

        sut.register(Registration.instance(key, instance));
        assert.strictEqual(sut.get(key), instance, `sut.get(key) === instance`);

        sut.deregister(key);
        assert.throws(() => sut.get(key), /AUR0009/, `() => sut.get(key)`);
      });

      it(`deregisters a registered singleton by key`, function () {
        const { sut, classInstance } = createFixture();
        const key = 'key';
        const type = classInstance;

        sut.register(Registration.singleton(key, type));
        assert.instanceOf(sut.get(key), type, `sut.get(key) instanceof type`);

        sut.deregister(key);
        assert.throws(() => sut.get(key), /AUR0009/, `() => sut.get(key)`);
      });

      it(`deregisters a registered transient by key`, function () {
        const { sut, classInstance } = createFixture();
        const key = 'key';
        const type = classInstance;

        sut.register(Registration.transient(key, type));
        assert.instanceOf(sut.get(key), type, `sut.get(key) instanceof type`);

        sut.deregister(key);
        assert.throws(() => sut.get(key), /AUR0009/, `() => sut.get(key)`);
      });

      it(`deregisters a registered callback by key`, function () {
        const { sut, classInstance } = createFixture();
        const key = 'key';
        const callback = () => new classInstance();

        sut.register(Registration.callback(key, callback));
        assert.strictEqual(sut.get(key).constructor, classInstance, `sut.get(key).constructor === classInstance`);

        sut.deregister(key);
        assert.throws(() => sut.get(key), /AUR0009/, `() => sut.get(key)`);
      });

      it(`deregisters a registered alias by key`, function () {
        const { sut, classInstance } = createFixture();
        const key = 'key';
        const aliasKey = 'aliasKey';
        const instance = new classInstance();

        sut.register(Registration.instance(key, instance));
        sut.register(Registration.aliasTo(key, aliasKey));
        assert.strictEqual(sut.get(aliasKey), instance, `sut.get(aliasKey) === instance`);

        sut.deregister(aliasKey);
        assert.throws(() => sut.get(aliasKey), /AUR0009/, `() => sut.get(aliasKey)`);
      });

      it(`does not throw when deregistering a non-existent key`, function () {
        const { sut } = createFixture();
        const key = 'key';

        assert.doesNotThrow(() => sut.deregister(key), `() => sut.deregister(key)`);
      });

      it(`deregisters multiple instances registered with the same key`, function () {
        const { sut, classInstance } = createFixture();
        const key = 'key';
        const instance1 = new classInstance();
        const instance2 = new classInstance();

        sut.register(Registration.instance(key, instance1));
        sut.register(Registration.instance(key, instance2));

        const instances = sut.getAll(key);
        assert.equal(instances.length, 2, 'sut.getAll(key) should return 2 instances');
        assert.includes(instances, instance1, 'sut.getAll(key) should include instance1');
        assert.includes(instances, instance2, 'sut.getAll(key) should include instance2');

        sut.deregister(key);
        assert.throws(() => sut.get(key), /AUR0009/, `() => sut.get(key)`);
      });

      it('deregisters a callback by key', function () {
        const { sut, classInstance } = createFixture();
        const key = 'key';
        const callback = () => new classInstance();

        sut.register(Registration.callback(key, callback));
        assert.strictEqual(sut.get(key).constructor, classInstance, `sut.get(key).constructor === classInstance`);

        sut.deregister(key);

        assert.throws(() => sut.get(key), /AUR0009/, `() => sut.get(key)`);
      });

      it('deregisters an alias by key and retains the original key', function () {
        const { sut, classInstance } = createFixture();
        const key = 'key';
        const aliasKey = 'aliasKey';
        const instance = new classInstance();

        sut.register(Registration.instance(key, instance));
        sut.register(Registration.aliasTo(key, aliasKey));

        assert.strictEqual(sut.get(aliasKey), instance, `sut.get(aliasKey) === instance`);

        sut.deregister(aliasKey);

        assert.strictEqual(sut.get(key), instance, `sut.get(key) === instance`);
        assert.throws(() => sut.get(aliasKey), /AUR0009/, `() => sut.get(aliasKey)`);
      });

      it(`deregisters nested containers without affecting root container`, function () {
        const { sut, classInstance } = createFixture();
        const key = 'key';
        const instance = new classInstance();
        const child = sut.createChild();

        sut.register(Registration.instance(key, instance));
        child.register(Registration.instance(key, instance));
        assert.strictEqual(sut.get(key), instance, `sut.get(key) === instance`);
        assert.strictEqual(child.get(key), instance, `child.get(key) === instance`);

        child.deregister(key);
        assert.strictEqual(sut.get(key), instance, `sut.get(key) === instance after child.deregister`);

        assert.strictEqual(child.has(classInstance, true), false, 'child does not have classInstance');
      });
    });

    // describe(`registerResolver()`, function () {
    //   for (const key of [null, undefined, Object]) {
    //     it(_`throws on invalid key ${key}`, function () {
    //       assert.throws(() => sut.registerResolver(key, null as any), /5/, `() => sut.registerResolver(key, null as any)`);
    //     });
    //   }

    //   it(`registers the resolver if it does not exist yet`, function () {
    //     const key = {};
    //     const resolver = new Resolver(key, ResolverStrategy.instance, {});
    //     sut.registerResolver(key, resolver);
    //     const actual = sut.getResolver(key);
    //     assert.strictEqual(actual, resolver, `actual`);
    //   });

    //   it(`changes to array resolver if the key already exists`, function () {
    //     const key = {};
    //     const resolver1 = new Resolver(key, ResolverStrategy.instance, {});
    //     const resolver2 = new Resolver(key, ResolverStrategy.instance, {});
    //     sut.registerResolver(key, resolver1);
    //     const actual1 = sut.getResolver(key);
    //     assert.strictEqual(actual1, resolver1, `actual1`);
    //     sut.registerResolver(key, resolver2);
    //     const actual2 = sut.getResolver(key);
    //     assert.notStrictEqual(actual2, actual1, `actual2`);
    //     assert.notStrictEqual(actual2, resolver1, `actual2`);
    //     assert.notStrictEqual(actual2, resolver2, `actual2`);
    //     assert.strictEqual(actual2['strategy'], ResolverStrategy.array, `actual2['strategy']`);
    //     assert.strictEqual(actual2['state'][0], resolver1, `actual2['state'][0]`);
    //     assert.strictEqual(actual2['state'][1], resolver2, `actual2['state'][1]`);
    //   });

    //   it(`appends to the array resolver if the key already exists more than once`, function () {
    //     const key = {};
    //     const resolver1 = new Resolver(key, ResolverStrategy.instance, {});
    //     const resolver2 = new Resolver(key, ResolverStrategy.instance, {});
    //     const resolver3 = new Resolver(key, ResolverStrategy.instance, {});
    //     sut.registerResolver(key, resolver1);
    //     sut.registerResolver(key, resolver2);
    //     sut.registerResolver(key, resolver3);
    //     const actual1 = sut.getResolver(key);
    //     assert.strictEqual(actual1['strategy'], ResolverStrategy.array, `actual1['strategy']`);
    //     assert.strictEqual(actual1['state'][0], resolver1, `actual1['state'][0]`);
    //     assert.strictEqual(actual1['state'][1], resolver2, `actual1['state'][1]`);
    //     assert.strictEqual(actual1['state'][2], resolver3, `actual1['state'][2]`);
    //   });
    // });

    describe(`registerTransformer()`, function () {
      for (const key of [null, undefined]) {
        it(_`throws on invalid key ${key}`, function () {
          const { sut } = createFixture();
          assert.throws(() => sut.registerTransformer(key, null as any), /AUR0014/, `() => sut.registerTransformer(key, null as any)`);
          // assert.throws(() => sut.registerTransformer(key, null as any), /key\/value cannot be null or undefined/, `() => sut.registerTransformer(key, null as any)`);
        });
      }

      it(`registers the transformer if it does not exist yet`, function () {
        return;
      });

      it(`reuses the existing transformer if it exists`, function () {
        return;
      });
    });

    describe(`getResolver()`, function () {
      for (const key of [null, undefined]) {
        it(_`throws on invalid key ${key}`, function () {
          const { sut } = createFixture();
          assert.throws(() => sut.getResolver(key, null as any), /AUR0014/, `() => sut.getResolver(key, null as any)`);
          // assert.throws(() => sut.getResolver(key, null as any), /key\/value cannot be null or undefined/, `() => sut.getResolver(key, null as any)`);
        });
      }

    });

    // describe(`has()`, function () {
    //   for (const key of [null, undefined, Object]) {
    //     it(_`returns false for non-existing key ${key}`, function () {
    //       assert.strictEqual(sut.has(key as any, false), false, `sut.has(key as any, false)`);
    //     });
    //   }
    //   it(`returns true for existing key`, function () {
    //     const key = {};
    //     sut.registerResolver(key, new Resolver(key, ResolverStrategy.instance, {}));
    //     assert.strictEqual(sut.has(key as any, false), true, `sut.has(key as any, false)`);
    //   });
    // });

    describe(`get()`, function () {
      for (const key of [null, undefined]) {
        it(_`throws on invalid key ${key}`, function () {
          const { sut } = createFixture();
          assert.throws(() => sut.get(key), /AUR0014/, `() => sut.get(key)`);
          // assert.throws(() => sut.get(key), /key\/value cannot be null or undefined/, `() => sut.get(key)`);
        });
      }

    });

    describe(`getAll()`, function () {
      for (const key of [null, undefined]) {
        it(_`throws on invalid key ${key}`, function () {
          const { sut } = createFixture();
          assert.throws(() => sut.getAll(key), /AUR0014/, `() => sut.getAll(key)`);
          // assert.throws(() => sut.getAll(key), /key\/value cannot be null or undefined/, `() => sut.getAll(key)`);
        });
      }

    });

    // describe(`getFactory()`, function () {
    //   for (const count of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) {
    //     sut = new Container(); // ensure the state is reset (beforeEach doesn't know about loops)

    //     it(`returns a new Factory with ${count} deps if it does not exist`, function () {
    //       class Bar {}
    //       class Foo {public static inject = Array(count).map(c => Bar); }
    //       const actual = sut.getFactory(Foo);
    //       assert.instanceOf(actual, Factory, `actual`);
    //       assert.strictEqual(actual.Type, Foo, `actual.Type`);
    //       if (count < 6) {
    //         assert.strictEqual(actual['invoker'], classInvokers[count], `actual['invoker']`);
    //       } else {
    //         assert.strictEqual(actual['invoker'], fallbackInvoker, `actual['invoker']`);
    //       }
    //       assert.notStrictEqual(actual['dependencies'], Foo.inject, `actual['dependencies']`);
    //       assert.deepStrictEqual(actual['dependencies'], Foo.inject, `actual['dependencies']`);
    //     });
    //   }

    //   it(`reuses the existing factory if it already exists`, function () {
    //     const create = spy(Factory, 'create');
    //     class Foo {}
    //     const actual = sut.getFactory(Foo);
    //     assert.instanceOf(actual, Factory, `actual`);
    //     const actual2 = sut.getFactory(Foo);
    //     assert.strictEqual(actual, actual2, `actual`);
    //     expect(create).to.have.been.calledOnce;
    //     create.restore();
    //   });
    // });

    describe(`createChild()`, function () {
      it(`inherits non-resource obj keyed factories from root`, function () {
        const type = class {};
        const key = {} as any;

        const parent = DI.createContainer();

        parent.register(Registration.singleton(key, type));

        const factoryFromParent = parent.getFactory(key);

        const child = parent.createChild();

        const factoryFromChild = child.getFactory(key);

        assert.strictEqual(factoryFromParent, factoryFromChild);
      });

      // Irrelevant due to metadata
      // it(`inherits non-resource string keyed factories from root`, function () {
      //   const type = class {};
      //   const key = 'foo' as any;

      //   const parent = DI.createContainer();

      //   parent.register(Registration.singleton(key, type));

      //   const factoryFromParent = parent.getFactory(key);

      //   const child = parent.createChild();

      //   const factoryFromChild = child.getFactory(key);

      //   assert.strictEqual(factoryFromParent, factoryFromChild);
      // });

      // Irrelevant due to metadata
      // it(`inherits resource factories from root`, function () {
      //   const type = class {};
      //   const key = 'foo:bar' as any;

      //   const parent = DI.createContainer();

      //   parent.register(Registration.singleton(key, type));

      //   const factoryFromParent = parent.getFactory(key);

      //   const child = parent.createChild();

      //   const factoryFromChild = child.getFactory(key);

      //   assert.strictEqual(factoryFromParent, factoryFromChild);
      // });

      it(`does NOT store non-resource obj keyed resolvers in resourceResolvers`, function () {
        const type = class {};
        const key = {} as any;

        const parent = DI.createContainer();

        parent.register(Registration.singleton(key, type));

        const parentHasKey = parent['res'][key] !== void 0;

        const child = parent.createChild();

        const childHasKey = child['res'][key] !== void 0;

        assert.strictEqual(parentHasKey, false, `has@root`);
        assert.strictEqual(childHasKey, false, `has@child`);
      });

      // Irrelevant due to metadata
      // it(`does NOT store non-resource string keyed resolvers in resourceResolvers`, function () {
      //   const type = class {};
      //   const key = 'foo' as any;

      //   const parent = DI.createContainer();

      //   parent.register(Registration.singleton(key, type));

      //   const parentHasKey = parent['res'][key] !== void 0;

      //   const child = parent.createChild();

      //   const childHasKey = child['res'][key] !== void 0;

      //   assert.strictEqual(parentHasKey, false, `has@root`);
      //   assert.strictEqual(childHasKey, false, `has@child`);
      // });

      // Irrelevant due to metadata
      // it(`stores resource resolvers in resourceResolvers and inherits them from root`, function () {
      //   const type = class {};
      //   const key = 'foo:bar' as any;

      //   const parent = DI.createContainer();

      //   parent.register(Registration.singleton(key, type));

      //   const parentHasKey = parent['res'][key] !== void 0;

      //   const child = parent.createChild();

      //   const childHasKey = child['res'][key] !== void 0;

      //   assert.strictEqual(parentHasKey, true, `has@root`);
      //   assert.strictEqual(childHasKey, true, `has@child`);
      // });

      describe(`followed by another createChild()`, function () {
        it(`inherits non-resource obj keyed factories from root`, function () {
          const type = class {};
          const key = {} as any;

          const root = DI.createContainer();

          root.register(Registration.singleton(key, type));

          const factoryFromRoot = root.getFactory(key);

          const parent = root.createChild();
          const child = parent.createChild();

          const factoryFromChild = child.getFactory(key);

          assert.strictEqual(factoryFromRoot, factoryFromChild);
        });

        // Irrelevant due to metadata
        // it(`inherits non-resource string keyed factories from root`, function () {
        //   const type = class {};
        //   const key = 'foo' as any;

        //   const root = DI.createContainer();

        //   root.register(Registration.singleton(key, type));

        //   const factoryFromRoot = root.getFactory(key);

        //   const parent = root.createChild();
        //   const child = parent.createChild();

        //   const factoryFromChild = child.getFactory(key);

        //   assert.strictEqual(factoryFromRoot, factoryFromChild);
        // });

        // Irrelevant due to metadata
        // it(`inherits resource factories from root`, function () {
        //   const type = class {};
        //   const key = 'foo:bar' as any;

        //   const root = DI.createContainer();

        //   root.register(Registration.singleton(key, type));

        //   const factoryFromRoot = root.getFactory(key);

        //   const parent = root.createChild();
        //   const child = parent.createChild();

        //   const factoryFromChild = child.getFactory(key);

        //   assert.strictEqual(factoryFromRoot, factoryFromChild);
        // });

        it(`inherits non-resource obj keyed factories from parent`, function () {
          const type = class {};
          const key = {} as any;

          const root = DI.createContainer();
          const parent = root.createChild();

          parent.register(Registration.singleton(key, type));

          const factoryFromParent = parent.getFactory(key);

          const child = parent.createChild();

          const factoryFromChild = child.getFactory(key);

          assert.strictEqual(factoryFromParent, factoryFromChild);
        });

        // Irrelevant due to metadata
        // it(`inherits non-resource string keyed factories from parent`, function () {
        //   const type = class {};
        //   const key = 'foo' as any;

        //   const root = DI.createContainer();
        //   const parent = root.createChild();

        //   parent.register(Registration.singleton(key, type));

        //   const factoryFromParent = parent.getFactory(key);

        //   const child = parent.createChild();

        //   const factoryFromChild = child.getFactory(key);

        //   assert.strictEqual(factoryFromParent, factoryFromChild);
        // });

        // Irrelevant due to metadata
        // it(`inherits resource factories from parent`, function () {
        //   const type = class {};
        //   const key = 'foo:bar' as any;

        //   const root = DI.createContainer();
        //   const parent = root.createChild();

        //   parent.register(Registration.singleton(key, type));

        //   const factoryFromParent = parent.getFactory(key);

        //   const child = parent.createChild();

        //   const factoryFromChild = child.getFactory(key);

        //   assert.strictEqual(factoryFromParent, factoryFromChild);
        // });

        it(`does NOT store non-resource obj keyed resolvers in resourceResolvers`, function () {
          const type = class {};
          const key = {} as any;

          const root = DI.createContainer();
          const parent = root.createChild();

          parent.register(Registration.singleton(key, type));

          const parentHasKey = parent['res'][key] !== void 0;

          const child = parent.createChild();

          const childHasKey = child['res'][key] !== void 0;

          assert.strictEqual(parentHasKey, false, `parentHasKey`);
          assert.strictEqual(childHasKey, false, `childHasKey`);
        });

        it(`does NOT store non-resource string keyed resolvers in resourceResolvers`, function () {
          const type = class {};
          const key = 'foo' as any;

          const root = DI.createContainer();
          const parent = root.createChild();

          parent.register(Registration.singleton(key, type));

          const parentHasKey = parent['res'][key] !== void 0;

          const child = parent.createChild();

          const childHasKey = child['res'][key] !== void 0;

          assert.strictEqual(parentHasKey, false, `parentHasKey`);
          assert.strictEqual(childHasKey, false, `childHasKey`);
        });

        it(`stores resource resolvers in resourceResolvers in parent but does not inherit them from parent`, function () {
          const type = class {};
          const key = 'foo:bar' as any;

          const root = DI.createContainer();
          const parent = root.createChild();

          parent.register(Registration.singleton(key, type));

          const parentHasKey = parent['res'][key] !== void 0;

          const child = parent.createChild();

          const childHasKey = child['res'][key] !== void 0;

          assert.strictEqual(parentHasKey, true, `parentHasKey`);
          assert.strictEqual(childHasKey, false, `childHasKey`);
        });

        // container used to copy resource keys all the way down
        // it's not only wasteful, but also inappropriate
        // a change in the way resources information is carried forward resulted in this test being skipped
        // but kept as a reminder how it used to be, in case someone relying on this behavior ran into the odd behavior
        //
        // eslint-disable-next-line mocha/no-skipped-tests
        it.skip(`stores resource resolvers in resourceResolvers in parent and inherits them from root but does not from parent`, function () {
          const type = class {};
          const keyFromRoot = 'foo:bar' as any;
          const keyFromParent = 'foo:baz' as any;

          const root = DI.createContainer();

          root.register(Registration.singleton(keyFromRoot, type));

          const parent = root.createChild();

          parent.register(Registration.singleton(keyFromParent, type));

          const parentHasKeyFromRoot = parent['res'][keyFromRoot] !== void 0;
          const parentHasKeyFromParent = parent['res'][keyFromParent] !== void 0;

          const child = parent.createChild();

          const childHasKeyFromRoot = child['res'][keyFromRoot] !== void 0;
          const childHasKeyFromParent = child['res'][keyFromParent] !== void 0;

          assert.strictEqual(parentHasKeyFromRoot, false, `parentHasKeyFromRoot`);
          assert.strictEqual(parentHasKeyFromParent, true, `parentHasKeyFromParent`);

          assert.strictEqual(childHasKeyFromRoot, false, `childHasKeyFromRoot`);
          assert.strictEqual(childHasKeyFromParent, false, `childHasKeyFromParent`);
        });
      });
    });

    describe(`jitRegister()`, function () {
      return;
    });
  });

  // describe(`The Registration object`, function () {

  //   it(`instance() returns the correct resolver`, function () {
  //     const value = {};
  //     const actual = Registration.instance('key', value);
  //     assert.strictEqual(actual['key'], 'key', `actual['key']`);
  //     assert.strictEqual(actual['strategy'], ResolverStrategy.instance, `actual['strategy']`);
  //     assert.strictEqual(actual['state'], value, `actual['state']`);
  //   });

  //   it(`singleton() returns the correct resolver`, function () {
  //     class Foo {}
  //     const actual = Registration.singleton('key', Foo);
  //     assert.strictEqual(actual['key'], 'key', `actual['key']`);
  //     assert.strictEqual(actual['strategy'], ResolverStrategy.singleton, `actual['strategy']`);
  //     assert.strictEqual(actual['state'], Foo, `actual['state']`);
  //   });

  //   it(`transient() returns the correct resolver`, function () {
  //     class Foo {}
  //     const actual = Registration.transient('key', Foo);
  //     assert.strictEqual(actual['key'], 'key', `actual['key']`);
  //     assert.strictEqual(actual['strategy'], ResolverStrategy.transient, `actual['strategy']`);
  //     assert.strictEqual(actual['state'], Foo, `actual['state']`);
  //   });

  //   it(`callback() returns the correct resolver`, function () {
  //     const callback = () => { return; };
  //     const actual = Registration.callback('key', callback);
  //     assert.strictEqual(actual['key'], 'key', `actual['key']`);
  //     assert.strictEqual(actual['strategy'], ResolverStrategy.callback, `actual['strategy']`);
  //     assert.strictEqual(actual['state'], callback, `actual['state']`);
  //   });

  //   it(`alias() returns the correct resolver`, function () {
  //     const actual = Registration.aliasTo('key', 'key2');
  //     assert.strictEqual(actual['key'], 'key2', `actual['key']`);
  //     assert.strictEqual(actual['strategy'], ResolverStrategy.alias, `actual['strategy']`);
  //     assert.strictEqual(actual['state'], 'key', `actual['state']`);
  //   });
  // });

  // describe(`The classInvokers object`, function () {
  //   const container = { get(t) {
  //     return new t();
  //   } } as any as IContainer;
  //   class Foo { public args: any[]; constructor(...args: any[]) { this.args = args; } }

  //   class Dep1 {}
  //   class Dep2 {}
  //   class Dep3 {}
  //   class Dep4 {}
  //   class Dep5 {}
  //   class Dep6 {}

  //   it(`invoke() handles 0 deps`, function () {
  //     const actual = classInvokers[0].invoke(container, Foo, []) as Foo;
  //     assert.strictEqual(actual.args.length, 0, `actual.args.length`);
  //   });

  //   it(`invoke() handles 1 dep`, function () {
  //     const actual = classInvokers[1].invoke(container, Foo, [Dep1]) as Foo;
  //     assert.strictEqual(actual.args.length, 1, `actual.args.length`);
  //     assert.instanceOf(actual.args[0], Dep1, `actual.args[0]`);
  //   });

  //   it(`invoke() handles 2 deps`, function () {
  //     const actual = classInvokers[2].invoke(container, Foo, [Dep1, Dep2]) as Foo;
  //     assert.strictEqual(actual.args.length, 2, `actual.args.length`);
  //     assert.instanceOf(actual.args[0], Dep1, `actual.args[0]`);
  //     assert.instanceOf(actual.args[1], Dep2, `actual.args[1]`);
  //   });

  //   it(`invoke() handles 3 deps`, function () {
  //     const actual = classInvokers[3].invoke(container, Foo, [Dep1, Dep2, Dep3]) as Foo;
  //     assert.strictEqual(actual.args.length, 3, `actual.args.length`);
  //     assert.instanceOf(actual.args[0], Dep1, `actual.args[0]`);
  //     assert.instanceOf(actual.args[1], Dep2, `actual.args[1]`);
  //     assert.instanceOf(actual.args[2], Dep3, `actual.args[2]`);
  //   });

  //   it(`invoke() handles 4 deps`, function () {
  //     const actual = classInvokers[4].invoke(container, Foo, [Dep1, Dep2, Dep3, Dep4]) as Foo;
  //     assert.strictEqual(actual.args.length, 4, `actual.args.length`);
  //     assert.instanceOf(actual.args[0], Dep1, `actual.args[0]`);
  //     assert.instanceOf(actual.args[1], Dep2, `actual.args[1]`);
  //     assert.instanceOf(actual.args[2], Dep3, `actual.args[2]`);
  //     assert.instanceOf(actual.args[3], Dep4, `actual.args[3]`);
  //   });

  //   it(`invoke() handles 5 deps`, function () {
  //     const actual = classInvokers[5].invoke(container, Foo, [Dep1, Dep2, Dep3, Dep4, Dep5]) as Foo;
  //     assert.strictEqual(actual.args.length, 5, `actual.args.length`);
  //     assert.instanceOf(actual.args[0], Dep1, `actual.args[0]`);
  //     assert.instanceOf(actual.args[1], Dep2, `actual.args[1]`);
  //     assert.instanceOf(actual.args[2], Dep3, `actual.args[2]`);
  //     assert.instanceOf(actual.args[3], Dep4, `actual.args[3]`);
  //     assert.instanceOf(actual.args[4], Dep5, `actual.args[4]`);
  //   });

  //   it(`invoke() does not handle 6 deps`, function () {
  //     assert.throws(() => classInvokers[6].invoke(container, Foo, [Dep1, Dep2, Dep3, Dep4, Dep5, Dep6]), /undefined/, `() => classInvokers[6].invoke(container, Foo, [Dep1, Dep2, Dep3, Dep4, Dep5, Dep6])`);
  //   });

  // });

  // describe(`The invokeWithDynamicDependencies function`, function () {
  //   const container = { get(t) {
  //     return `static${t}`;
  //   } } as any as IContainer;
  //   class Foo { public args: any[]; constructor(...args: any[]) { this.args = args; } }

  //   const deps = [class Dep1 {}, class Dep2 {}, class Dep3 {}];

  //   it(_`throws when staticDeps is null`, function () {
  //     assert.throws(() => invokeWithDynamicDependencies(container, Foo, null, []), void 0, `() => invokeWithDynamicDependencies(container, Foo, null, [])`);
  //   });

  //   it(_`throws when any of the staticDeps is null`, function () {
  //     assert.throws(() => invokeWithDynamicDependencies(container, Foo, [null], []), /7/, `() => invokeWithDynamicDependencies(container, Foo, [null], [])`);
  //   });

  //   it(_`throws when any of the staticDeps is undefined`, function () {
  //     assert.throws(() => invokeWithDynamicDependencies(container, Foo, [undefined], []), /7/, `() => invokeWithDynamicDependencies(container, Foo, [undefined], [])`);
  //   });

  //   it(_`throws when staticDeps is undefined`, function () {
  //     assert.throws(() => invokeWithDynamicDependencies(container, Foo, undefined, []), void 0, `() => invokeWithDynamicDependencies(container, Foo, undefined, [])`);
  //   });

  //   it(_`handles staticDeps is ${deps}`, function () {
  //     const actual = invokeWithDynamicDependencies(container, Foo, deps, []);
  //     assert.deepStrictEqual(actual.args, deps.map(d => `static${d}`), `actual.args`);
  //   });

  //   it(`handles dynamicDeps is null`, function () {
  //     const actual = invokeWithDynamicDependencies(container, Foo, [], null);
  //     assert.strictEqual(actual.args.length, 1, `actual.args.length`);
  //     assert.strictEqual(actual.args[0], null, `actual.args[0]`);
  //   });

  //   it(`handles dynamicDeps is undefined`, function () {
  //     const actual = invokeWithDynamicDependencies(container, Foo, [], undefined);
  //     assert.strictEqual(actual.args.length, 0, `actual.args.length`);
  //   });

  //   it(_`handles dynamicDeps is ${deps}`, function () {
  //     const actual = invokeWithDynamicDependencies(container, Foo, [], deps);
  //     assert.deepStrictEqual(actual.args, deps, `actual.args`);
  //   });

  //   it(_`handles staticDeps is ${deps} and dynamicDeps is ${deps}`, function () {
  //     const actual = invokeWithDynamicDependencies(container, Foo, deps, deps);
  //     assert.strictEqual(actual.args[0], `static${deps[0]}`, `actual.args[0]`);
  //     assert.strictEqual(actual.args[1], `static${deps[1]}`, `actual.args[1]`);
  //     assert.strictEqual(actual.args[2], `static${deps[2]}`, `actual.args[2]`);
  //     assert.strictEqual(actual.args[3], deps[0], `actual.args[3]`);
  //     assert.strictEqual(actual.args[4], deps[1], `actual.args[4]`);
  //     assert.strictEqual(actual.args[5], deps[2], `actual.args[5]`);
  //   });
  // });
});
