From 6c45432ef67d55a9a309ded3a22f8553b261b338 Mon Sep 17 00:00:00 2001 From: Martin Jesper Low Madsen Date: Sat, 9 May 2020 13:43:57 +0200 Subject: [PATCH 1/2] enhancement(repository): Add hidden __factory property to mocks to differentiate them in conditional typing --- src/repository/repository.ts | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/repository/repository.ts b/src/repository/repository.ts index 7cca95d4e..5f3f4ac02 100644 --- a/src/repository/repository.ts +++ b/src/repository/repository.ts @@ -1,4 +1,5 @@ -type Factory = Function; +// eslint-disable-next-line +type Factory = (...args: any[]) => any; export class Repository { private readonly _repository: { [key: string]: Factory }; @@ -15,7 +16,36 @@ export class Repository { } public registerFactory(key: string, factory: Factory): void { - this._repository[key] = factory; + const proxy: Factory = new Proxy( + factory, + { + apply(target: Factory, _this: unknown, args: Parameters): ReturnType { + const mock: ReturnType = target(...args); + + if (typeof mock === 'undefined') { + return; + } + + if (!(mock instanceof Object)) { + return mock; + } + + if (typeof mock.__factory !== 'undefined') { + return mock; + } + + Object.defineProperty(mock, '__factory', { + enumerable: false, + writable: false, + value: key, + }); + + return mock; + }, + }, + ); + + this._repository[key] = proxy; } public getFactory(key: string): Factory { From acfffabdf6281c323adcb854380b9234569854a4 Mon Sep 17 00:00:00 2001 From: Martin Jesper Low Madsen Date: Sat, 16 May 2020 15:29:31 +0200 Subject: [PATCH 2/2] chore(*): Rename __factory identifier to __ident and apply it to mocked methods as well --- src/extension/method/provider/provider.ts | 7 ++++- src/repository/repository.ts | 33 ++-------------------- src/utils/applyIdentityProperty.ts | 34 +++++++++++++++++++++++ 3 files changed, 43 insertions(+), 31 deletions(-) create mode 100644 src/utils/applyIdentityProperty.ts diff --git a/src/extension/method/provider/provider.ts b/src/extension/method/provider/provider.ts index e6b50dd72..e20c931a0 100644 --- a/src/extension/method/provider/provider.ts +++ b/src/extension/method/provider/provider.ts @@ -1,3 +1,4 @@ +import { applyIdentityProperty } from '../../../utils/applyIdentityProperty'; import { functionMethod } from './functionMethod'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type Method = (name: string, value: any) => () => any; @@ -45,6 +46,10 @@ export class Provider { return this._method(name, value()); } - return this._method(name, value); + // FIXME: Do this smarter, it's a bit counter intuitive to return a new + // proxy every single time this function is called. It should probably mock + // based on name if that ends up being a string representing the type + // signature. + return applyIdentityProperty(this._method, name)(name, value); } } diff --git a/src/repository/repository.ts b/src/repository/repository.ts index 5f3f4ac02..8a5706a52 100644 --- a/src/repository/repository.ts +++ b/src/repository/repository.ts @@ -1,3 +1,5 @@ +import { applyIdentityProperty } from '../utils/applyIdentityProperty'; + // eslint-disable-next-line type Factory = (...args: any[]) => any; @@ -16,36 +18,7 @@ export class Repository { } public registerFactory(key: string, factory: Factory): void { - const proxy: Factory = new Proxy( - factory, - { - apply(target: Factory, _this: unknown, args: Parameters): ReturnType { - const mock: ReturnType = target(...args); - - if (typeof mock === 'undefined') { - return; - } - - if (!(mock instanceof Object)) { - return mock; - } - - if (typeof mock.__factory !== 'undefined') { - return mock; - } - - Object.defineProperty(mock, '__factory', { - enumerable: false, - writable: false, - value: key, - }); - - return mock; - }, - }, - ); - - this._repository[key] = proxy; + this._repository[key] = applyIdentityProperty(factory, key); } public getFactory(key: string): Factory { diff --git a/src/utils/applyIdentityProperty.ts b/src/utils/applyIdentityProperty.ts new file mode 100644 index 000000000..1b8ce0181 --- /dev/null +++ b/src/utils/applyIdentityProperty.ts @@ -0,0 +1,34 @@ +// eslint-disable-next-line +type Function = (...args: any[]) => K; +type IdentityFlavored = K & { __ident?: string }; + +export function applyIdentityProperty>(target: T, identity: string): T { + return new Proxy( + target, + { + apply(func: T, _this: unknown, args: Parameters): IdentityFlavored | undefined { + const t: IdentityFlavored = func(...args); + + if (typeof t === 'undefined') { + return; + } + + if (!(t instanceof Object)) { + return t; + } + + if (typeof t.__ident !== 'undefined') { + return t; + } + + Object.defineProperty(t, '__ident', { + enumerable: false, + writable: false, + value: identity, + }); + + return t; + }, + }, + ); +}