From 815c23e26188f0995f43172784505a43999168ec Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 29 Mar 2019 07:50:09 -0700 Subject: [PATCH] fix(context): clear binding cache upon scope or value getter changes --- .../src/__tests__/unit/binding.unit.ts | 64 ++++++++++++++++++- packages/context/src/binding.ts | 14 +++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/packages/context/src/__tests__/unit/binding.unit.ts b/packages/context/src/__tests__/unit/binding.unit.ts index 28cb6837a4d0..4055de5f7b3b 100644 --- a/packages/context/src/__tests__/unit/binding.unit.ts +++ b/packages/context/src/__tests__/unit/binding.unit.ts @@ -3,7 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {expect} from '@loopback/testlab'; +import {expect, sinon, SinonSpy} from '@loopback/testlab'; import { Binding, BindingScope, @@ -214,6 +214,68 @@ describe('Binding', () => { }); }); + describe('cache', () => { + let spy: SinonSpy; + beforeEach(() => { + spy = sinon.spy(); + }); + + it('clears cache if scope changes', () => { + const indexBinding = ctx + .bind('index') + .toDynamicValue(spy) + .inScope(BindingScope.SINGLETON); + + ctx.getSync(indexBinding.key); + sinon.assert.calledOnce(spy); + spy.resetHistory(); + + // Singleton + ctx.getSync(indexBinding.key); + sinon.assert.notCalled(spy); + spy.resetHistory(); + + indexBinding.inScope(BindingScope.CONTEXT); + ctx.getSync(indexBinding.key); + sinon.assert.calledOnce(spy); + }); + + it('clears cache if _getValue changes', () => { + const providerSpy = sinon.spy(); + class IndexProvider implements Provider { + value() { + return providerSpy(); + } + } + const indexBinding = ctx + .bind('index') + .toDynamicValue(spy) + .inScope(BindingScope.SINGLETON); + + ctx.getSync(indexBinding.key); + sinon.assert.calledOnce(spy); + spy.resetHistory(); + + // Singleton + ctx.getSync(indexBinding.key); + sinon.assert.notCalled(spy); + spy.resetHistory(); + + // Now change the value getter + indexBinding.toProvider(IndexProvider); + ctx.getSync(indexBinding.key); + sinon.assert.notCalled(spy); + sinon.assert.calledOnce(providerSpy); + spy.resetHistory(); + providerSpy.resetHistory(); + + // Singleton + ctx.getSync(indexBinding.key); + sinon.assert.notCalled(spy); + sinon.assert.notCalled(providerSpy); + }); + }); + describe('toJSON()', () => { it('converts a keyed binding to plain JSON object', () => { const json = binding.toJSON(); diff --git a/packages/context/src/binding.ts b/packages/context/src/binding.ts index ca38eded5bf9..accbbab2a7bf 100644 --- a/packages/context/src/binding.ts +++ b/packages/context/src/binding.ts @@ -211,6 +211,15 @@ export class Binding { }); } + /** + * Clear the cache + */ + private _clearCache() { + if (!this._cache) return; + // WeakMap does not have a `clear` method + this._cache = new WeakMap(); + } + /** * This is an internal function optimized for performance. * Users should use `@inject(key)` or `ctx.get(key)` instead. @@ -346,6 +355,7 @@ export class Binding { * @param scope Binding scope */ inScope(scope: BindingScope): this { + if (this._scope !== scope) this._clearCache(); this._scope = scope; return this; } @@ -357,7 +367,7 @@ export class Binding { */ applyDefaultScope(scope: BindingScope): this { if (!this._scope) { - this._scope = scope; + this.inScope(scope); } return this; } @@ -367,6 +377,8 @@ export class Binding { * @param getValue getValue function */ private _setValueGetter(getValue: ValueGetter) { + // Clear the cache + this._clearCache(); this._getValue = getValue; }