1886da342Sopenharmony_ci/* 2886da342Sopenharmony_ci * Copyright (c) 2023 Huawei Device Co., Ltd. 3886da342Sopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License"); 4886da342Sopenharmony_ci * you may not use this file except in compliance with the License. 5886da342Sopenharmony_ci * You may obtain a copy of the License at 6886da342Sopenharmony_ci * 7886da342Sopenharmony_ci * http://www.apache.org/licenses/LICENSE-2.0 8886da342Sopenharmony_ci * 9886da342Sopenharmony_ci * Unless required by applicable law or agreed to in writing, software 10886da342Sopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS, 11886da342Sopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12886da342Sopenharmony_ci * See the License for the specific language governing permissions and 13886da342Sopenharmony_ci * limitations under the License. 14886da342Sopenharmony_ci */ 15886da342Sopenharmony_ci 16886da342Sopenharmony_ciimport ExtendInterface from "./ExtendInterface.js"; 17886da342Sopenharmony_ciimport VerificationMode from "./VerificationMode.js"; 18886da342Sopenharmony_ciimport { ArgumentMatchers } from "./ArgumentMatchers.js"; 19886da342Sopenharmony_ci 20886da342Sopenharmony_ciinterface IFunction extends Function { 21886da342Sopenharmony_ci container: any; 22886da342Sopenharmony_ci original: any; 23886da342Sopenharmony_ci propName: string; 24886da342Sopenharmony_ci originalFromPrototype: boolean 25886da342Sopenharmony_ci mocker: MockKit 26886da342Sopenharmony_ci} 27886da342Sopenharmony_ci 28886da342Sopenharmony_ciclass MockKit { 29886da342Sopenharmony_ci 30886da342Sopenharmony_ci private mFunctions:Array<any> = []; 31886da342Sopenharmony_ci private stubs = new Map(); 32886da342Sopenharmony_ci private recordCalls = new Map(); 33886da342Sopenharmony_ci private currentSetKey = new Map(); 34886da342Sopenharmony_ci private mockObj = null; 35886da342Sopenharmony_ci private recordMockedMethod = new Map(); 36886da342Sopenharmony_ci private originalMethod: any; 37886da342Sopenharmony_ci 38886da342Sopenharmony_ci constructor() { 39886da342Sopenharmony_ci this.mFunctions = []; 40886da342Sopenharmony_ci this.stubs = new Map(); 41886da342Sopenharmony_ci this.recordCalls = new Map(); 42886da342Sopenharmony_ci this.currentSetKey = new Map(); 43886da342Sopenharmony_ci this.mockObj = null; 44886da342Sopenharmony_ci this.recordMockedMethod = new Map(); 45886da342Sopenharmony_ci } 46886da342Sopenharmony_ci 47886da342Sopenharmony_ci init() { 48886da342Sopenharmony_ci this.reset(); 49886da342Sopenharmony_ci } 50886da342Sopenharmony_ci 51886da342Sopenharmony_ci reset() { 52886da342Sopenharmony_ci this.mFunctions = []; 53886da342Sopenharmony_ci this.stubs = new Map() 54886da342Sopenharmony_ci this.recordCalls = new Map(); 55886da342Sopenharmony_ci this.currentSetKey = new Map(); 56886da342Sopenharmony_ci this.mockObj = null; 57886da342Sopenharmony_ci this.recordMockedMethod = new Map(); 58886da342Sopenharmony_ci } 59886da342Sopenharmony_ci 60886da342Sopenharmony_ci clearAll() { 61886da342Sopenharmony_ci this.reset(); 62886da342Sopenharmony_ci } 63886da342Sopenharmony_ci 64886da342Sopenharmony_ci clear(obj: any) { 65886da342Sopenharmony_ci if (!obj) throw Error("Please enter an object to be cleaned"); 66886da342Sopenharmony_ci if (typeof (obj) != 'object') throw new Error('Not a object'); 67886da342Sopenharmony_ci this.recordMockedMethod.forEach(function (value, key, map) { 68886da342Sopenharmony_ci if (key) { 69886da342Sopenharmony_ci obj[key] = value; 70886da342Sopenharmony_ci } 71886da342Sopenharmony_ci }); 72886da342Sopenharmony_ci } 73886da342Sopenharmony_ci 74886da342Sopenharmony_ci ignoreMock(obj:any, method: any) { 75886da342Sopenharmony_ci if (typeof (obj) != 'object') throw new Error('Not a object'); 76886da342Sopenharmony_ci if (typeof (method) != 'function') throw new Error('Not a function'); 77886da342Sopenharmony_ci let og = this.recordMockedMethod.get(method.propName); 78886da342Sopenharmony_ci if (og) { 79886da342Sopenharmony_ci obj[method.propName] = og; 80886da342Sopenharmony_ci this.recordMockedMethod.set(method.propName, undefined); 81886da342Sopenharmony_ci } 82886da342Sopenharmony_ci } 83886da342Sopenharmony_ci 84886da342Sopenharmony_ci extend(dest: any, source:any) { 85886da342Sopenharmony_ci dest["stub"] = source["stub"]; 86886da342Sopenharmony_ci dest["afterReturn"] = source["afterReturn"]; 87886da342Sopenharmony_ci dest["afterReturnNothing"] = source["afterReturnNothing"]; 88886da342Sopenharmony_ci dest["afterAction"] = source["afterAction"]; 89886da342Sopenharmony_ci dest["afterThrow"] = source["afterThrow"]; 90886da342Sopenharmony_ci dest["stubMockedCall"] = source["stubMockedCall"]; 91886da342Sopenharmony_ci dest["clear"] = source["clear"]; 92886da342Sopenharmony_ci return dest; 93886da342Sopenharmony_ci } 94886da342Sopenharmony_ci 95886da342Sopenharmony_ci stubApply(f: any, params:any, returnInfo:any) { 96886da342Sopenharmony_ci let values = this.stubs.get(f); 97886da342Sopenharmony_ci if (!values) { 98886da342Sopenharmony_ci values = new Map(); 99886da342Sopenharmony_ci } 100886da342Sopenharmony_ci let key = params[0]; 101886da342Sopenharmony_ci if (typeof key == "undefined") { 102886da342Sopenharmony_ci key = "anonymous-mock-" + f.propName; 103886da342Sopenharmony_ci } 104886da342Sopenharmony_ci let matcher = new ArgumentMatchers(); 105886da342Sopenharmony_ci if (matcher.matcheStubKey(key)) { 106886da342Sopenharmony_ci key = matcher.matcheStubKey(key); 107886da342Sopenharmony_ci if (key) { 108886da342Sopenharmony_ci this.currentSetKey.set(f, key); 109886da342Sopenharmony_ci } 110886da342Sopenharmony_ci } 111886da342Sopenharmony_ci values.set(key, returnInfo); 112886da342Sopenharmony_ci this.stubs.set(f, values); 113886da342Sopenharmony_ci } 114886da342Sopenharmony_ci 115886da342Sopenharmony_ci getReturnInfo(f: any, params:any) { 116886da342Sopenharmony_ci let values = this.stubs.get(f); 117886da342Sopenharmony_ci if (!values) { 118886da342Sopenharmony_ci return undefined; 119886da342Sopenharmony_ci } 120886da342Sopenharmony_ci let retrunKet = params[0]; 121886da342Sopenharmony_ci if (typeof retrunKet == "undefined") { 122886da342Sopenharmony_ci retrunKet = "anonymous-mock-" + f.propName; 123886da342Sopenharmony_ci } 124886da342Sopenharmony_ci let stubSetKey = this.currentSetKey.get(f); 125886da342Sopenharmony_ci 126886da342Sopenharmony_ci if (stubSetKey && (typeof (retrunKet) != "undefined")) { 127886da342Sopenharmony_ci retrunKet = stubSetKey; 128886da342Sopenharmony_ci } 129886da342Sopenharmony_ci let matcher = new ArgumentMatchers(); 130886da342Sopenharmony_ci if (matcher.matcheReturnKey(params[0], undefined, stubSetKey) && matcher.matcheReturnKey(params[0], undefined, stubSetKey) != stubSetKey) { 131886da342Sopenharmony_ci retrunKet = params[0]; 132886da342Sopenharmony_ci } 133886da342Sopenharmony_ci 134886da342Sopenharmony_ci values.forEach(function (value: any, key: any, map: any) { 135886da342Sopenharmony_ci if (ArgumentMatchers.isRegExp(key) && matcher.matcheReturnKey(params[0], key)) { 136886da342Sopenharmony_ci retrunKet = key; 137886da342Sopenharmony_ci } 138886da342Sopenharmony_ci }); 139886da342Sopenharmony_ci 140886da342Sopenharmony_ci return values.get(retrunKet); 141886da342Sopenharmony_ci } 142886da342Sopenharmony_ci 143886da342Sopenharmony_ci findName(obj: any, value: any) { 144886da342Sopenharmony_ci let properties = this.findProperties(obj); 145886da342Sopenharmony_ci let name = ''; 146886da342Sopenharmony_ci properties.filter((item:any) => (item !== 'caller' && item !== 'arguments')).forEach( 147886da342Sopenharmony_ci function (va1:any, idx:any, array:any) { 148886da342Sopenharmony_ci if (obj[va1] === value) { 149886da342Sopenharmony_ci name = va1; 150886da342Sopenharmony_ci } 151886da342Sopenharmony_ci } 152886da342Sopenharmony_ci ); 153886da342Sopenharmony_ci return name; 154886da342Sopenharmony_ci } 155886da342Sopenharmony_ci 156886da342Sopenharmony_ci isFunctionFromPrototype(f: Function, container:Function, propName: string) { 157886da342Sopenharmony_ci if (container.constructor != Object && container.constructor.prototype !== container) { 158886da342Sopenharmony_ci return container.constructor.prototype[propName] === f; 159886da342Sopenharmony_ci } 160886da342Sopenharmony_ci return false; 161886da342Sopenharmony_ci } 162886da342Sopenharmony_ci 163886da342Sopenharmony_ci findProperties(obj: any, ...arg: Array<any>) { 164886da342Sopenharmony_ci function getProperty(new_obj:any): Array<any> { 165886da342Sopenharmony_ci if (new_obj.__proto__ === null) { 166886da342Sopenharmony_ci return []; 167886da342Sopenharmony_ci } 168886da342Sopenharmony_ci let properties = Object.getOwnPropertyNames(new_obj); 169886da342Sopenharmony_ci return [...properties, ...getProperty(new_obj.__proto__)]; 170886da342Sopenharmony_ci } 171886da342Sopenharmony_ci return getProperty(obj); 172886da342Sopenharmony_ci } 173886da342Sopenharmony_ci 174886da342Sopenharmony_ci recordMethodCall(originalMethod: any, args: any) { 175886da342Sopenharmony_ci originalMethod['getName'] = function () { 176886da342Sopenharmony_ci return this.name || this.toString().match(/function\s*([^(]*)\(/)[1]; 177886da342Sopenharmony_ci } 178886da342Sopenharmony_ci let name = originalMethod.getName(); 179886da342Sopenharmony_ci let arglistString = name + '(' + Array.from(args).toString() + ')'; 180886da342Sopenharmony_ci let records = this.recordCalls.get(arglistString); 181886da342Sopenharmony_ci if (!records) { 182886da342Sopenharmony_ci records = 0; 183886da342Sopenharmony_ci } 184886da342Sopenharmony_ci records++; 185886da342Sopenharmony_ci this.recordCalls.set(arglistString, records); 186886da342Sopenharmony_ci } 187886da342Sopenharmony_ci 188886da342Sopenharmony_ci mockFunc(originalObject:any, originalMethod:any) { 189886da342Sopenharmony_ci let tmp = this; 190886da342Sopenharmony_ci this.originalMethod = originalMethod; 191886da342Sopenharmony_ci const _this = this; 192886da342Sopenharmony_ci let f:any = function () { 193886da342Sopenharmony_ci let args = arguments; 194886da342Sopenharmony_ci let action = tmp.getReturnInfo(f, args); 195886da342Sopenharmony_ci if (originalMethod) { 196886da342Sopenharmony_ci tmp.recordMethodCall(originalMethod, args); 197886da342Sopenharmony_ci } 198886da342Sopenharmony_ci if (action) { 199886da342Sopenharmony_ci return <IFunction> action.apply(_this, args); 200886da342Sopenharmony_ci } 201886da342Sopenharmony_ci }; 202886da342Sopenharmony_ci 203886da342Sopenharmony_ci f.container = null || originalObject; 204886da342Sopenharmony_ci f.original = originalMethod || null; 205886da342Sopenharmony_ci 206886da342Sopenharmony_ci if (originalObject && originalMethod) { 207886da342Sopenharmony_ci if (typeof (originalMethod) != 'function') throw new Error('Not a function'); 208886da342Sopenharmony_ci var name = this.findName(originalObject, originalMethod); 209886da342Sopenharmony_ci originalObject[name] = f; 210886da342Sopenharmony_ci this.recordMockedMethod.set(name, originalMethod); 211886da342Sopenharmony_ci f.propName = name; 212886da342Sopenharmony_ci f.originalFromPrototype = this.isFunctionFromPrototype(f.original, originalObject, f.propName); 213886da342Sopenharmony_ci } 214886da342Sopenharmony_ci f.mocker = this; 215886da342Sopenharmony_ci this.mFunctions.push(f); 216886da342Sopenharmony_ci this.extend(f, new ExtendInterface(this)); 217886da342Sopenharmony_ci return f; 218886da342Sopenharmony_ci } 219886da342Sopenharmony_ci 220886da342Sopenharmony_ci verify(methodName:any, argsArray:any) { 221886da342Sopenharmony_ci if (!methodName) { 222886da342Sopenharmony_ci throw Error("not a function name"); 223886da342Sopenharmony_ci } 224886da342Sopenharmony_ci let a = this.recordCalls.get(methodName + '(' + argsArray.toString() + ')'); 225886da342Sopenharmony_ci return new VerificationMode(a ? a : 0); 226886da342Sopenharmony_ci } 227886da342Sopenharmony_ci 228886da342Sopenharmony_ci mockObject(object: any) { 229886da342Sopenharmony_ci if (!object || typeof object === "string") { 230886da342Sopenharmony_ci throw Error(`this ${object} cannot be mocked`); 231886da342Sopenharmony_ci } 232886da342Sopenharmony_ci const _this = this; 233886da342Sopenharmony_ci let mockedObject:any = {}; 234886da342Sopenharmony_ci let keys = Reflect.ownKeys(object); 235886da342Sopenharmony_ci keys.filter(key => (typeof Reflect.get(object, key)) === 'function') 236886da342Sopenharmony_ci .forEach((key:any) => { 237886da342Sopenharmony_ci mockedObject[key] = object[key]; 238886da342Sopenharmony_ci mockedObject[key] = _this.mockFunc(mockedObject, mockedObject[key]); 239886da342Sopenharmony_ci }); 240886da342Sopenharmony_ci return mockedObject; 241886da342Sopenharmony_ci } 242886da342Sopenharmony_ci} 243886da342Sopenharmony_ci 244886da342Sopenharmony_cifunction ifMockedFunction(f: any) { 245886da342Sopenharmony_ci if (Object.prototype.toString.call(f) != "[object Function]" && 246886da342Sopenharmony_ci Object.prototype.toString.call(f) != "[object AsyncFunction]") { 247886da342Sopenharmony_ci throw Error("not a function"); 248886da342Sopenharmony_ci } 249886da342Sopenharmony_ci if (!f.stub) { 250886da342Sopenharmony_ci throw Error("not a mock function"); 251886da342Sopenharmony_ci } 252886da342Sopenharmony_ci return true; 253886da342Sopenharmony_ci} 254886da342Sopenharmony_ci 255886da342Sopenharmony_cifunction when(f: any) { 256886da342Sopenharmony_ci if (ifMockedFunction(f)) { 257886da342Sopenharmony_ci return f.stub.bind(f); 258886da342Sopenharmony_ci } 259886da342Sopenharmony_ci} 260886da342Sopenharmony_ci 261886da342Sopenharmony_cifunction MockSetup(target: Object, propertyName: string | Symbol, descriptor: TypedPropertyDescriptor<() => void>): void { 262886da342Sopenharmony_ci const aboutToAppearOrigin = target.aboutToAppear; 263886da342Sopenharmony_ci const setup = descriptor.value; 264886da342Sopenharmony_ci target.aboutToAppear = function (...args: any[]) { 265886da342Sopenharmony_ci if (target.__Param) { // copy attributes and params of the original context 266886da342Sopenharmony_ci try { 267886da342Sopenharmony_ci const map = target.__Param as Map<string, unknown>; 268886da342Sopenharmony_ci for (const [key, val] of map) { 269886da342Sopenharmony_ci this[key] = val; // 'this' refers to context of current function 270886da342Sopenharmony_ci } 271886da342Sopenharmony_ci } catch (e) { 272886da342Sopenharmony_ci throw new Error(`Mock setup param error: ${e}`); 273886da342Sopenharmony_ci } 274886da342Sopenharmony_ci } 275886da342Sopenharmony_ci 276886da342Sopenharmony_ci if (setup) { // apply the mock content 277886da342Sopenharmony_ci try { 278886da342Sopenharmony_ci setup.apply(this); 279886da342Sopenharmony_ci } catch (e) { 280886da342Sopenharmony_ci throw new Error(`Mock setup apply error: ${e}`); 281886da342Sopenharmony_ci } 282886da342Sopenharmony_ci } 283886da342Sopenharmony_ci 284886da342Sopenharmony_ci if (aboutToAppearOrigin) { // append to aboutToAppear function of the original context 285886da342Sopenharmony_ci aboutToAppearOrigin.apply(this, args); 286886da342Sopenharmony_ci } 287886da342Sopenharmony_ci } 288886da342Sopenharmony_ci} 289886da342Sopenharmony_ci 290886da342Sopenharmony_ciexport { 291886da342Sopenharmony_ci MockSetup, 292886da342Sopenharmony_ci MockKit, 293886da342Sopenharmony_ci when 294886da342Sopenharmony_ci};