/** * Util useful to create mocked versions of classes with each of their methods * and properties replaced, while being properly checked by TypeScript. * @param {Object} methods - Each of this class' methods (key is the method * name, value is the implementation). * @param {Object} properties - Each of this class' property (key is the * property name, value is the implementation). * @returns {*} - The mocked class, which should respect its public API. */ export function makeMockedClass( methods: MethodsMap, properties: PropertiesMap, ): { new (opts?: Partial): T; prototype: IPrototype } { // eslint-disable-next-line @typescript-eslint/no-extraneous-class class Dummy { constructor(opts: Partial = {}) { // Apply default property values for (const key in properties) { if (Object.prototype.hasOwnProperty.call(properties, key)) { // @ts-expect-error dynamic assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this[key] = properties[key]; } } // Override with any provided opts for (const key in opts) { if (Object.prototype.hasOwnProperty.call(opts, key)) { // @ts-expect-error dynamic assignment this[key] = opts[key]; } } } } for (const [name, fn] of Object.entries(methods)) { // @ts-expect-error dynamic assignment Dummy.prototype[name as keyof typeof Dummy.prototype] = fn; } return Dummy as unknown as { new (opts?: Partial): T; prototype: IPrototype }; } /* eslint-disable @typescript-eslint/no-unsafe-function-type, @typescript-eslint/no-restricted-types */ type IPrototype = { [K in keyof T as T[K] extends Function ? K : never]: T[K] }; // Splits T's keys into method keys and property keys type MethodKeys = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T]; type PropertyKeys = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]; /* eslint-enable @typescript-eslint/no-unsafe-function-type, @typescript-eslint/no-restricted-types */ type MethodsMap = { [K in MethodKeys]: T[K] }; type PropertiesMap = { [K in PropertyKeys]: T[K] };