diff --git a/packages/ioc/src/container.ts b/packages/ioc/src/container.ts index 18a26f5..05d8c3b 100644 --- a/packages/ioc/src/container.ts +++ b/packages/ioc/src/container.ts @@ -11,13 +11,14 @@ function hasCallableMethod(obj: object, name: PropertyKey) { */ export class Container { private __singletons = new Map(); - private hooks= new Map(); + //hooks are Maps of string -> object, where object is a reference to an object in __singletons + private hooks= new Map(); private finished_init = false; constructor(options: { autowire: boolean; path?: string }) { if(options.autowire) { /* noop */ } } - addHook(name: string, callback: Function) { + addHook(name: string, callback: object) { if (!this.hooks.has(name)) { this.hooks.set(name, []); } @@ -27,7 +28,7 @@ export class Container { if(hasCallableMethod(insert, hookname)) { //@ts-ignore - this.addHook(hookname, () => insert[hookname]()) + this.addHook(hookname, insert) } } @@ -68,10 +69,11 @@ export class Container { return Object.fromEntries(this.__singletons) as T } - async executeHooks(name: string) { + private async executeHooks(name: string) { const hookFunctions = this.hooks.get(name) || []; - for (const hookFunction of hookFunctions) { - await hookFunction(); + for (const hookObject of hookFunctions) { + //@ts-ignore .registerHooks verifies the hookObject hasCallableMethod + await hookObject[name](); } } @@ -88,7 +90,7 @@ export class Container { if (hasCallableMethod(existing, 'dispose')) { existing.dispose(); // get the index of the existing singleton, now delete the dispose hook at that index - const hookIndex = this.hooks.get('dispose')!.indexOf(existing.dispose); + const hookIndex = this.hooks.get('dispose')!.indexOf(existing); if (hookIndex > -1) { this.hooks.get('dispose')!.splice(hookIndex, 1); } diff --git a/packages/ioc/test/index.test.ts b/packages/ioc/test/index.test.ts index 6ab8a99..4cc8f1b 100644 --- a/packages/ioc/test/index.test.ts +++ b/packages/ioc/test/index.test.ts @@ -1,23 +1,30 @@ import { Container } from '../src/container'; -import { describe, it, expect, beforeEach, vi, Mock } from 'vitest'; +import { describe, it, expect, beforeEach, vi, Mock, afterEach } from 'vitest'; +class SingletonCheese { + dispose() { + return this.value + } + constructor(public value: string){} +} describe('CoreContainer Tests', () => { let coreContainer: Container; let singletonWInit: { init: Mock; value: string } - let singletonWDispose: { dispose: Mock; value: string } + let singletonWDispose: SingletonCheese beforeEach(() => { coreContainer = new Container({ autowire: false }); singletonWInit = { - value: 'singletonWithInit', - init: vi.fn() + value: 'singletonWithInit', + init: vi.fn() }; - singletonWDispose = { - value: 'singletonWithDispose', - dispose: vi.fn() - } + singletonWDispose = new SingletonCheese('singletonWithDispose') }); + afterEach(() => { + vi.clearAllMocks() + }) + it('Adding and getting singletons', () => { coreContainer.addSingleton('singletonKey', { value: 'singletonValue' }); const singleton = coreContainer.get('singletonKey'); @@ -125,11 +132,19 @@ describe('CoreContainer Tests', () => { expect(singletonWInit.init).toHaveBeenCalledOnce(); }); - it('should be false because not swapping anything', () => { + it('should return false because not swapping anything', () => { const swap = coreContainer.swap('singletonKeyWithInit', singletonWInit); expect(swap).toBe(false); }) - + it('should return true because not swapping anything', () => { + coreContainer.addSingleton('singletonKeyWithInit', singletonWInit); + const singletonWithInit2 = { + value: 'singletonValueWithInit2', + init: vi.fn() + }; + const swap = coreContainer.swap('singletonKeyWithInit', singletonWithInit2); + expect(swap).toBe(true); + }) it('should swap object with another', () => { coreContainer.addSingleton('singleton', singletonWInit) const singletonWithInit2 = { @@ -153,12 +168,24 @@ describe('CoreContainer Tests', () => { }; coreContainer.addSingleton('singletonWithDispose3', singletonWithDispose3); - const swapped = coreContainer.swap('singleton', singletonWithDispose2); + vi.spyOn(singletonWDispose, 'dispose') + coreContainer.swap('singleton', singletonWithDispose2); expect(singletonWDispose.dispose).toHaveBeenCalledOnce(); expect(coreContainer.get>('singleton')).toBe(singletonWithDispose2); expect(singletonWithDispose2.dispose).not.toHaveBeenCalledOnce(); expect(singletonWithDispose3.dispose).not.toHaveBeenCalledOnce(); - expect(swapped).toBe(true); + }) + it('should swap object, maintaining reference to `this`', () => { + coreContainer.addSingleton('singleton', singletonWDispose); + const singletonWithDispose2 = { + value: 'singletonValueWithDispose2', + dispose: vi.fn() + }; + const spiedDispose = vi.spyOn(singletonWDispose, 'dispose') + const swapped = coreContainer.swap('singleton', singletonWithDispose2); + expect(spiedDispose.mock.results[0].value).toEqual('singletonWithDispose'); + expect(coreContainer.get>('singleton')).toBe(singletonWithDispose2); + expect(singletonWithDispose2.dispose).not.toHaveBeenCalledOnce(); }) })