MOBILE-3720 DX: Use Proxy for magic singletons
parent
fe831d13cb
commit
55cd0f2495
|
@ -297,7 +297,7 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider<AddonCalenda
|
|||
|
||||
}
|
||||
|
||||
export const AddonCalendarSync = makeSingleton(AddonCalendarSyncProvider, ['component', 'syncInterval']);
|
||||
export const AddonCalendarSync = makeSingleton(AddonCalendarSyncProvider);
|
||||
|
||||
export type AddonCalendarSyncEvents = {
|
||||
warnings: string[];
|
||||
|
|
|
@ -398,7 +398,7 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider<AddonMessage
|
|||
|
||||
}
|
||||
|
||||
export const AddonMessagesSync = makeSingleton(AddonMessagesSyncProvider, ['component', 'syncInterval']);
|
||||
export const AddonMessagesSync = makeSingleton(AddonMessagesSyncProvider);
|
||||
|
||||
export type AddonMessagesSyncEvents = {
|
||||
warnings: string[];
|
||||
|
|
|
@ -540,7 +540,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
|
|||
}
|
||||
|
||||
}
|
||||
export const AddonModAssignSync = makeSingleton(AddonModAssignSyncProvider, ['component', 'syncInterval']);
|
||||
export const AddonModAssignSync = makeSingleton(AddonModAssignSyncProvider);
|
||||
|
||||
/**
|
||||
* Data returned by a assign sync.
|
||||
|
|
|
@ -656,7 +656,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider<AddonModForu
|
|||
|
||||
}
|
||||
|
||||
export const AddonModForumSync = makeSingleton(AddonModForumSyncProvider, ['component', 'syncInterval']);
|
||||
export const AddonModForumSync = makeSingleton(AddonModForumSyncProvider);
|
||||
|
||||
/**
|
||||
* Result of forum sync.
|
||||
|
|
|
@ -498,7 +498,7 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid
|
|||
|
||||
}
|
||||
|
||||
export const AddonModLessonSync = makeSingleton(AddonModLessonSyncProvider, ['component', 'syncInterval']);
|
||||
export const AddonModLessonSync = makeSingleton(AddonModLessonSyncProvider);
|
||||
|
||||
/**
|
||||
* Data returned by a lesson sync.
|
||||
|
|
|
@ -480,7 +480,7 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider
|
|||
|
||||
}
|
||||
|
||||
export const AddonModQuizSync = makeSingleton(AddonModQuizSyncProvider, ['component', 'syncInterval']);
|
||||
export const AddonModQuizSync = makeSingleton(AddonModQuizSyncProvider);
|
||||
|
||||
/**
|
||||
* Data returned by a quiz sync.
|
||||
|
|
|
@ -81,9 +81,9 @@ describe('CoreFormatTextDirective', () => {
|
|||
// @todo this is done because we cannot mock image being loaded, we should find an alternative...
|
||||
CoreUtils.instance.timeoutPromise = <T>() => Promise.resolve(null as unknown as T);
|
||||
|
||||
mockSingleton(CoreFilepool, { getSrcByUrl: jest.fn(() => Promise.resolve('file://local-path')) });
|
||||
mockSingleton(CoreFilepool, { getSrcByUrl: () => Promise.resolve('file://local-path') });
|
||||
mockSingleton(CoreSites, {
|
||||
getSite: jest.fn(() => Promise.resolve(site)),
|
||||
getSite: () => Promise.resolve(site),
|
||||
getCurrentSite: () => Promise.resolve(site),
|
||||
});
|
||||
mockSingleton(CoreFilter, { formatText: (text) => Promise.resolve(text) });
|
||||
|
|
|
@ -198,4 +198,4 @@ export class CoreBlockDelegateService extends CoreDelegate<CoreBlockHandler> {
|
|||
|
||||
}
|
||||
|
||||
export const CoreBlockDelegate = makeSingleton(CoreBlockDelegateService, ['blocksUpdateObservable']);
|
||||
export const CoreBlockDelegate = makeSingleton(CoreBlockDelegateService);
|
||||
|
|
|
@ -315,7 +315,7 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider<CoreCommentsS
|
|||
}
|
||||
|
||||
}
|
||||
export const CoreCommentsSync = makeSingleton(CoreCommentsSyncProvider, ['component', 'syncInterval']);
|
||||
export const CoreCommentsSync = makeSingleton(CoreCommentsSyncProvider);
|
||||
|
||||
export type CoreCommentsSyncResult = {
|
||||
warnings: string[]; // List of warnings.
|
||||
|
|
|
@ -62,4 +62,4 @@ export class CoreCourseLogCronHandlerService implements CoreCronHandler {
|
|||
|
||||
}
|
||||
|
||||
export const CoreCourseLogCronHandler = makeSingleton(CoreCourseLogCronHandlerService, ['name']);
|
||||
export const CoreCourseLogCronHandler = makeSingleton(CoreCourseLogCronHandlerService);
|
||||
|
|
|
@ -240,7 +240,7 @@ export class CoreCourseSyncProvider extends CoreSyncBaseProvider<CoreCourseSyncR
|
|||
|
||||
}
|
||||
|
||||
export const CoreCourseSync = makeSingleton(CoreCourseSyncProvider, ['component', 'syncInterval']);
|
||||
export const CoreCourseSync = makeSingleton(CoreCourseSyncProvider);
|
||||
|
||||
/**
|
||||
* Result of course sync.
|
||||
|
|
|
@ -216,14 +216,7 @@ export class CoreH5PProvider {
|
|||
|
||||
}
|
||||
|
||||
export const CoreH5P = makeSingleton(CoreH5PProvider, [
|
||||
'h5pCore',
|
||||
'h5pFramework',
|
||||
'h5pPlayer',
|
||||
'h5pStorage',
|
||||
'h5pValidator',
|
||||
'queueRunner',
|
||||
]);
|
||||
export const CoreH5P = makeSingleton(CoreH5PProvider);
|
||||
|
||||
/**
|
||||
* Params of core_h5p_get_trusted_h5p_file WS.
|
||||
|
|
|
@ -659,7 +659,7 @@ export class CoreSitePluginsProvider {
|
|||
|
||||
}
|
||||
|
||||
export const CoreSitePlugins = makeSingleton(CoreSitePluginsProvider, ['sitePluginsFinishedLoading', 'hasSitePluginsLoaded']);
|
||||
export const CoreSitePlugins = makeSingleton(CoreSitePluginsProvider);
|
||||
|
||||
/**
|
||||
* Handler of a site plugin.
|
||||
|
|
|
@ -104,4 +104,4 @@ export class CoreUserSyncProvider extends CoreSyncBaseProvider<string[]> {
|
|||
|
||||
}
|
||||
|
||||
export const CoreUserSync = makeSingleton(CoreUserSyncProvider, ['component', 'syncInterval']);
|
||||
export const CoreUserSync = makeSingleton(CoreUserSyncProvider);
|
||||
|
|
|
@ -644,7 +644,7 @@ export class CoreAppProvider {
|
|||
|
||||
}
|
||||
|
||||
export const CoreApp = makeSingleton(CoreAppProvider, ['isAndroid']);
|
||||
export const CoreApp = makeSingleton(CoreAppProvider);
|
||||
|
||||
/**
|
||||
* Data stored for a redirect to another page/site.
|
||||
|
|
|
@ -140,11 +140,4 @@ export class CoreScreenService {
|
|||
|
||||
}
|
||||
|
||||
export const CoreScreen = makeSingleton(CoreScreenService, [
|
||||
'isTablet',
|
||||
'isMobile',
|
||||
'layout',
|
||||
'layoutObservable',
|
||||
'breakpoints',
|
||||
'breakpointsObservable',
|
||||
]);
|
||||
export const CoreScreen = makeSingleton(CoreScreenService);
|
||||
|
|
|
@ -26,9 +26,9 @@ describe('CoreTextUtilsProvider', () => {
|
|||
let textUtils: CoreTextUtilsProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSingleton(CoreApp, [], { isAndroid: jest.fn(() => config.platform === 'android') });
|
||||
mockSingleton(CoreApp, [], { isAndroid: () => config.platform === 'android' });
|
||||
|
||||
sanitizer = mock<DomSanitizer>([], { bypassSecurityTrustUrl: jest.fn(url => url) });
|
||||
sanitizer = mock<DomSanitizer>([], { bypassSecurityTrustUrl: url => url });
|
||||
textUtils = new CoreTextUtilsProvider(sanitizer);
|
||||
});
|
||||
|
||||
|
|
|
@ -56,39 +56,26 @@ import { Zip as ZipService } from '@ionic-native/zip/ngx';
|
|||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
const OBJECT_PROTOTYPE = Object.getPrototypeOf(Object);
|
||||
|
||||
/**
|
||||
* Injector instance used to resolve singletons.
|
||||
*/
|
||||
let singletonsInjector: Injector | null = null;
|
||||
|
||||
/**
|
||||
* Helper to get service class properties that are methods.
|
||||
* Helper to create a method that proxies calls to the underlying singleton instance.
|
||||
*/
|
||||
type GetMethods<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never
|
||||
}[keyof T];
|
||||
|
||||
/**
|
||||
* Helper to get service class properties that are not methods.
|
||||
*/
|
||||
type GetNonMethods<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? never : K
|
||||
}[keyof T];
|
||||
// eslint-disable-next-line
|
||||
let createSingletonMethodProxy = (instance: any, method: Function, property: string | number | symbol) => method.bind(instance);
|
||||
|
||||
/**
|
||||
* Singleton proxy created using the factory method.
|
||||
*
|
||||
* @see makeSingleton
|
||||
*/
|
||||
export type CoreSingletonProxy<Service, Getters extends keyof Service = never> =
|
||||
Pick<Service, Exclude<GetMethods<Service>, GetNonMethods<Service>>> &
|
||||
Pick<Service, Getters> &
|
||||
{
|
||||
instance: Service;
|
||||
setInstance(instance: Service): void;
|
||||
};
|
||||
export type CoreSingletonProxy<Service> = Service & {
|
||||
instance: Service;
|
||||
setInstance(instance: Service): void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the injector that will be used to resolve instances in the singletons of this module.
|
||||
|
@ -99,6 +86,15 @@ export function setSingletonsInjector(injector: Injector): void {
|
|||
singletonsInjector = injector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the method to create method proxies.
|
||||
*
|
||||
* @param method Method.
|
||||
*/
|
||||
export function setCreateSingletonMethodProxy(method: typeof createSingletonMethodProxy): void {
|
||||
createSingletonMethodProxy = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a singleton proxy for the given injection token.
|
||||
*
|
||||
|
@ -112,26 +108,19 @@ export function setSingletonsInjector(injector: Injector): void {
|
|||
* @param getters Getter names to proxy.
|
||||
* @return Singleton proxy.
|
||||
*/
|
||||
export function makeSingleton<Service>(injectionToken: Type<Service> | Type<unknown> | string): CoreSingletonProxy<Service, never>;
|
||||
export function makeSingleton<Service, Getters extends keyof Service>(
|
||||
export function makeSingleton<Service extends object = object>( // eslint-disable-line @typescript-eslint/ban-types
|
||||
injectionToken: Type<Service> | Type<unknown> | string,
|
||||
getters: Getters[],
|
||||
): CoreSingletonProxy<Service, Getters>;
|
||||
export function makeSingleton<Service, Getters extends keyof Service>(
|
||||
injectionToken: Type<Service> | Type<unknown> | string,
|
||||
getters: Getters[] = [],
|
||||
): CoreSingletonProxy<Service, Getters> {
|
||||
// Define instance manipulation affordances.
|
||||
const proxy = {
|
||||
): CoreSingletonProxy<Service> {
|
||||
const singleton = {
|
||||
setInstance(instance: Service) {
|
||||
Object.defineProperty(proxy, 'instance', {
|
||||
Object.defineProperty(singleton, 'instance', {
|
||||
value: instance,
|
||||
configurable: true,
|
||||
});
|
||||
},
|
||||
} as CoreSingletonProxy<Service, Getters>;
|
||||
} as { instance: Service; setInstance(instance: Service) };
|
||||
|
||||
Object.defineProperty(proxy, 'instance', {
|
||||
Object.defineProperty(singleton, 'instance', {
|
||||
get: () => {
|
||||
if (!singletonsInjector) {
|
||||
throw new Error('Can\'t resolve a singleton instance without an injector');
|
||||
|
@ -139,70 +128,39 @@ export function makeSingleton<Service, Getters extends keyof Service>(
|
|||
|
||||
const instance = singletonsInjector.get(injectionToken);
|
||||
|
||||
proxy.setInstance(instance);
|
||||
singleton.setInstance(instance);
|
||||
|
||||
return instance;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
// Define method and getter proxies.
|
||||
if (isServiceClass(injectionToken)) {
|
||||
// Get property descriptors, going all the way up the prototype chain (for services extending other classes).
|
||||
let parentPrototype = injectionToken;
|
||||
let descriptors: Record<string, PropertyDescriptor> = {};
|
||||
|
||||
do {
|
||||
descriptors = {
|
||||
...Object.getOwnPropertyDescriptors(parentPrototype.prototype),
|
||||
...descriptors,
|
||||
};
|
||||
|
||||
parentPrototype = Object.getPrototypeOf(parentPrototype);
|
||||
} while (parentPrototype !== OBJECT_PROTOTYPE);
|
||||
|
||||
// Don't proxy constructor calls.
|
||||
delete descriptors['constructor'];
|
||||
|
||||
// Define method proxies.
|
||||
for (const [property, descriptor] of Object.entries(descriptors)) {
|
||||
// Skip getters and setters.
|
||||
if (descriptor.get || descriptor.set) {
|
||||
continue;
|
||||
return new Proxy(singleton, {
|
||||
get(target, property, receiver) {
|
||||
if (property in target) {
|
||||
return Reflect.get(target, property, receiver);
|
||||
}
|
||||
|
||||
// Define method proxy.
|
||||
Object.defineProperty(proxy, property, {
|
||||
value: (...args) => proxy.instance[property].call(proxy.instance, ...args),
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
const value = target.instance[property];
|
||||
|
||||
// Define getter proxies.
|
||||
for (const getter of getters) {
|
||||
Object.defineProperty(proxy, getter, { get: () => proxy.instance[getter] });
|
||||
}
|
||||
}
|
||||
return typeof value === 'function'
|
||||
? createSingletonMethodProxy(target.instance, value, property)
|
||||
: value;
|
||||
},
|
||||
set(target, property, value, receiver) {
|
||||
Reflect.set(target.instance, property, value, receiver);
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an injection token is a service class.
|
||||
*
|
||||
* @param injectionToken Injection token.
|
||||
* @return Whether the token is a class.
|
||||
*/
|
||||
function isServiceClass(injectionToken: Type<unknown> | string): injectionToken is Type<unknown> {
|
||||
return typeof injectionToken !== 'string';
|
||||
return true;
|
||||
},
|
||||
}) as CoreSingletonProxy<Service>;
|
||||
}
|
||||
|
||||
// Convert ionic-native services to singleton.
|
||||
export const Badge = makeSingleton(BadgeService);
|
||||
export const Chooser = makeSingleton(ChooserService);
|
||||
export const Clipboard = makeSingleton(ClipboardService);
|
||||
export const Diagnostic = makeSingleton(DiagnosticService, ['permissionStatus']);
|
||||
export const File = makeSingleton(FileService, ['documentsDirectory', 'externalApplicationStorageDirectory']);
|
||||
export const Diagnostic = makeSingleton(DiagnosticService);
|
||||
export const File = makeSingleton(FileService);
|
||||
export const FileOpener = makeSingleton(FileOpenerService);
|
||||
export const FileTransfer = makeSingleton(FileTransferService);
|
||||
export const Geolocation = makeSingleton(GeolocationService);
|
||||
|
@ -212,40 +170,24 @@ export const LocalNotifications = makeSingleton(LocalNotificationsService);
|
|||
export const Media = makeSingleton(MediaService);
|
||||
export const MediaCapture = makeSingleton(MediaCaptureService);
|
||||
export const NativeHttp = makeSingleton(HTTP);
|
||||
export const Network = makeSingleton(NetworkService, ['Connection', 'type']);
|
||||
export const Network = makeSingleton(NetworkService);
|
||||
export const Push = makeSingleton(PushService);
|
||||
export const QRScanner = makeSingleton(QRScannerService);
|
||||
export const StatusBar = makeSingleton(StatusBarService);
|
||||
export const SplashScreen = makeSingleton(SplashScreenService);
|
||||
export const SQLite = makeSingleton(SQLiteService);
|
||||
export const WebIntent = makeSingleton(WebIntentService, ['ACTION_VIEW']);
|
||||
export const WebIntent = makeSingleton(WebIntentService);
|
||||
export const WebView = makeSingleton(WebViewService);
|
||||
export const Zip = makeSingleton(ZipService);
|
||||
|
||||
export const Camera = makeSingleton(CameraService, [
|
||||
'DestinationType',
|
||||
'Direction',
|
||||
'EncodingType',
|
||||
'MediaType',
|
||||
'PictureSourceType',
|
||||
'PopoverArrowDirection',
|
||||
]);
|
||||
export const Camera = makeSingleton(CameraService);
|
||||
|
||||
export const Device = makeSingleton(DeviceService, [
|
||||
'cordova',
|
||||
'isVirtual',
|
||||
'manufacturer',
|
||||
'model',
|
||||
'platform',
|
||||
'serial',
|
||||
'uuid',
|
||||
'version',
|
||||
]);
|
||||
export const Device = makeSingleton(DeviceService);
|
||||
|
||||
// Convert some Angular and Ionic injectables to singletons.
|
||||
export const NgZone = makeSingleton(NgZoneService);
|
||||
export const Http = makeSingleton(HttpClient);
|
||||
export const Platform = makeSingleton(PlatformService, ['isRTL', 'resume']);
|
||||
export const Platform = makeSingleton(PlatformService);
|
||||
export const ActionSheetController = makeSingleton(ActionSheetControllerService);
|
||||
export const AlertController = makeSingleton(AlertControllerService);
|
||||
export const LoadingController = makeSingleton(LoadingControllerService);
|
||||
|
@ -253,10 +195,10 @@ export const ModalController = makeSingleton(ModalControllerService);
|
|||
export const PopoverController = makeSingleton(PopoverControllerService);
|
||||
export const ToastController = makeSingleton(ToastControllerService);
|
||||
export const GestureController = makeSingleton(GestureControllerService);
|
||||
export const ApplicationInit = makeSingleton(ApplicationInitStatus, ['donePromise']);
|
||||
export const ApplicationInit = makeSingleton(ApplicationInitStatus);
|
||||
export const Application = makeSingleton(ApplicationRef);
|
||||
export const NavController = makeSingleton(NavControllerService);
|
||||
export const Router = makeSingleton(RouterService, ['routerState', 'url']);
|
||||
export const Router = makeSingleton(RouterService);
|
||||
|
||||
// Convert external libraries injectables.
|
||||
export const Translate = makeSingleton(TranslateService, ['onLangChange', 'translations']);
|
||||
export const Translate = makeSingleton(TranslateService);
|
||||
|
|
|
@ -19,12 +19,12 @@ import { MilkyWayService } from './stubs';
|
|||
|
||||
describe('Singletons', () => {
|
||||
|
||||
let MilkyWay: CoreSingletonProxy<MilkyWayService, 'MEANING_OF_LIFE'>;
|
||||
let MilkyWay: CoreSingletonProxy<MilkyWayService>;
|
||||
|
||||
beforeEach(() => {
|
||||
setSingletonsInjector(mock({ get: serviceClass => new serviceClass() }));
|
||||
|
||||
MilkyWay = makeSingleton(MilkyWayService, ['MEANING_OF_LIFE']);
|
||||
MilkyWay = makeSingleton(MilkyWayService);
|
||||
});
|
||||
|
||||
it('works using the service instance', () => {
|
||||
|
@ -35,10 +35,22 @@ describe('Singletons', () => {
|
|||
expect(MilkyWay.getTheMeaningOfLife()).toBe(42);
|
||||
});
|
||||
|
||||
it('works using magic methods defined as getters', () => {
|
||||
expect(MilkyWay.reduceYears(2)).toBe(-2);
|
||||
});
|
||||
|
||||
it('works using magic getters', () => {
|
||||
expect(MilkyWay.MEANING_OF_LIFE).toBe(42);
|
||||
});
|
||||
|
||||
it('works using magic getters defined dynamically', () => {
|
||||
expect(MilkyWay.exists).toBeUndefined();
|
||||
|
||||
MilkyWay.bigBang();
|
||||
|
||||
expect(MilkyWay.exists).toBe(true);
|
||||
});
|
||||
|
||||
it('magic getters use the same instance', () => {
|
||||
expect(MilkyWay.addYears(1)).toBe(1);
|
||||
expect(MilkyWay.instance.addYears(1)).toBe(2);
|
||||
|
|
|
@ -22,10 +22,17 @@ export class Galaxy {
|
|||
|
||||
export class MilkyWayService extends Galaxy {
|
||||
|
||||
exists?: boolean;
|
||||
readonly MEANING_OF_LIFE = 42;
|
||||
|
||||
private years = 0;
|
||||
|
||||
reduceYears!: (years: number) => number;
|
||||
|
||||
bigBang(): void {
|
||||
this.exists = true;
|
||||
}
|
||||
|
||||
getTheMeaningOfLife(): number {
|
||||
return this.MEANING_OF_LIFE;
|
||||
}
|
||||
|
@ -37,3 +44,16 @@ export class MilkyWayService extends Galaxy {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
Object.defineProperty(MilkyWayService.prototype, 'reduceYears', {
|
||||
get: () => function(years: number) {
|
||||
// eslint-disable-next-line no-invalid-this
|
||||
const self = this as { years: number };
|
||||
|
||||
self.years -= years;
|
||||
|
||||
return self.years;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
|
|
@ -14,7 +14,18 @@
|
|||
|
||||
import 'jest-preset-angular';
|
||||
|
||||
import { setCreateSingletonMethodProxy } from '@singletons';
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug = () => {
|
||||
// Silence.
|
||||
};
|
||||
|
||||
// Override the method to create singleton method proxies in order to facilitate setting up
|
||||
// test expectations about method calls.
|
||||
setCreateSingletonMethodProxy(
|
||||
(instance, method, property) =>
|
||||
instance[`mock_${String(property)}`] =
|
||||
instance[`mock_${String(property)}`] ??
|
||||
jest.fn((...args) => method.call(instance, ...args)),
|
||||
);
|
||||
|
|
|
@ -40,6 +40,16 @@ export function mock<T>(
|
|||
|
||||
const methods = Array.isArray(methodsOrInstance) ? methodsOrInstance : [];
|
||||
|
||||
for (const property of Object.getOwnPropertyNames(instance)) {
|
||||
const value = instance[property];
|
||||
|
||||
if (typeof value !== 'function') {
|
||||
continue;
|
||||
}
|
||||
|
||||
instance[property] = jest.fn((...args) => value.call(instance, ...args));
|
||||
}
|
||||
|
||||
for (const method of methods) {
|
||||
instance[method] = jest.fn();
|
||||
}
|
||||
|
@ -66,17 +76,6 @@ export function mockSingleton<T>(
|
|||
|
||||
singleton.setInstance(mockInstance);
|
||||
|
||||
for (const [property, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(singleton))) {
|
||||
if (typeof descriptor.value !== 'function' || property === 'setInstance') {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object.defineProperty(singleton, property, {
|
||||
value: jest.fn(descriptor.value),
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
return mockInstance;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue