1/* 2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import DeepTypeUtils from './DeepTypeUtils'; 17function assertDeepEquals(actualValue, expected) { 18 let result = eq(actualValue, expected[0]); 19 let msg = logMsg(actualValue, expected[0]); 20 return { 21 pass: result, 22 message: msg 23 }; 24} 25 26function getMapLog(item) { 27 let valueStr = ''; 28 let keyValue = ''; 29 if (item.length > 1) { 30 let key = item[0]; 31 let value = item[1]; 32 if (value !== value) { 33 valueStr = value; 34 } else { 35 valueStr = JSON.stringify(value); 36 } 37 keyValue = JSON.stringify(key); 38 } 39 return '[' + keyValue + ',' + valueStr + ']'; 40} 41 42function getArrayLog(item) { 43 // NAN 44 if (item !== item) { 45 return item; 46 } 47 if (item === undefined) { 48 return 'undefined'; 49 } 50 return JSON.stringify(item); 51} 52 53 54function getCollectionLog(data) { 55 // 获取a的对象名称 56 let finallyResult = ''; 57 const aClassName = Object.prototype.toString.call(data); 58 if (aClassName === '[object Map]') { 59 let result = Array.from(data); 60 finallyResult = result.flatMap((item) => { 61 return getMapLog(item); 62 }); 63 } 64 if (aClassName === '[object Set]') { 65 let setArray = Array.from(data); 66 finallyResult = setArray.flatMap((item) => { 67 return getArrayLog(item); 68 }); 69 } 70 if (aClassName === '[object Array]') { 71 finallyResult = data.flatMap((item) => { 72 return getArrayLog(item); 73 }); 74 } 75 return finallyResult; 76} 77 78function getActualValueLog(actualValue) { 79 // 获取a的对象名称 80 const aClassName = Object.prototype.toString.call(actualValue); 81 let actualMsg = ''; 82 if (aClassName === '[object Function]') { 83 actualMsg = 'actualValue Function'; 84 } else if (aClassName === '[object Promise]') { 85 actualMsg = 'actualValue Promise'; 86 } else if (aClassName === '[object Map]') { 87 let finallyResult = getCollectionLog(actualValue); 88 actualMsg = '[' + finallyResult + ']'; 89 } else if (aClassName === '[object Set]') { 90 let flatMapResult = getCollectionLog(actualValue); 91 actualMsg = '[' + flatMapResult + ']'; 92 } else if (aClassName === '[object Array]') { 93 let flatMapResult = getCollectionLog(actualValue); 94 actualMsg = '[' + flatMapResult + ']'; 95 } else if (aClassName === '[object RegExp]') { 96 actualMsg = JSON.stringify(actualValue.source.replace('\\', '')); 97 } else if (aClassName === '[object BigInt]') { 98 actualMsg = actualValue; 99 } else if (aClassName === '[object Error]') { 100 actualMsg = actualValue.message; 101 } else if (aClassName === '[object ArrayBuffer]') { 102 actualMsg = actualValue.byteLength; 103 } 104 else { 105 // NAN 106 if (actualValue !== actualValue) { 107 actualMsg = actualValue.toString(); 108 } else { 109 actualMsg = JSON.stringify(actualValue); 110 } 111 } 112 return actualMsg; 113} 114 115function getExpectedLog(expected) { 116 const bClassName = Object.prototype.toString.call(expected); 117 let expectMsg = ''; 118 if (bClassName === '[object Function]') { 119 expectMsg = 'expected Function'; 120 } else if (bClassName === '[object Promise]') { 121 expectMsg = 'expected Promise'; 122 } else if (bClassName === '[object Map]') { 123 let finallyResult = getCollectionLog(expected); 124 expectMsg = '[' + finallyResult + ']'; 125 } else if (bClassName === '[object Set]') { 126 let flatMapResult = getCollectionLog(expected); 127 expectMsg = '[' + flatMapResult + ']'; 128 } else if (bClassName === '[object Array]') { 129 let flatMapResult = getCollectionLog(expected); 130 expectMsg = '[' + flatMapResult + ']'; 131 } else if (bClassName === '[object RegExp]') { 132 expectMsg = JSON.stringify(expected.source.replace('\\', '')); 133 } else if (bClassName === '[object BigInt]') { 134 expectMsg = expected; 135 } else if (bClassName === '[object Error]') { 136 expectMsg = expected.message; 137 } else if (bClassName === '[object ArrayBuffer]') { 138 expectMsg = expected.byteLength; 139 } 140 else { 141 // NAN 142 if (expected !== expected) { 143 expectMsg = expected.toString(); 144 } else { 145 expectMsg = JSON.stringify(expected); 146 } 147 } 148 return expectMsg; 149} 150 151/** 152 * 获取失败显示日志 153 * @param actualValue 实际对象 154 * @param expected 期待比较对象 155 */ 156function logMsg(actualValue, expected) { 157 // 获取a的对象名称 158 let actualMsg = getActualValueLog(actualValue); 159 let expectMsg = getExpectedLog(expected); 160 161 return 'expect ' + actualMsg + ' deep equals ' + expectMsg; 162} 163 164function eq(a, b) { 165 let result = true; 166 167 if (a === b) { 168 result = a !== 0 || 1 / a === 1 / b; 169 return result; 170 } 171 172 if (a === null || b === null) { 173 result = a === b; 174 return result; 175 } 176 // 获取a的对象名称 177 const aClassName = Object.prototype.toString.call(a); 178 const bClassName = Object.prototype.toString.call(b); 179 // 不同类型不同对象 180 if (aClassName !== bClassName) { 181 return false; 182 } 183 if (aClassName === '[object String]' || aClassName === '[object Number]' || aClassName === '[object Date]' || 184 aClassName === '[object Boolean]' || aClassName === '[object ArrayBuffer]' || 185 aClassName === '[object RegExp]' || aClassName === '[object Error]') { 186 result = isEqualSampleObj(a, b); 187 return result; 188 } 189 190 if (typeof a !== 'object' || typeof b !== 'object') { 191 return false; 192 } 193 194 if (DeepTypeUtils.isDomNode(a) || DeepTypeUtils.isPromise(a) || DeepTypeUtils.isFunction(a)) { 195 result = isEqualNodeOrPromiseOrFunction(a, b); 196 return result; 197 } 198 199 if (aClassName === '[object Array]' || aClassName === '[object Map]' || aClassName === '[object Set]') { 200 result = isEqualCollection(a, b); 201 return result; 202 } 203 204 result = isEqualObj(a, b); 205 return result; 206} 207 208function isEqualNodeOrPromiseOrFunction(a, b) { 209 let equalNodeOrPromiseOrFunction = true; 210 if (DeepTypeUtils.isDomNode(a) && DeepTypeUtils.isDomNode(b)) { 211 const aIsDomNode = DeepTypeUtils.isDomNode(a); 212 const bIsDomNode = DeepTypeUtils.isDomNode(b); 213 if (aIsDomNode && bIsDomNode) { 214 // At first try to use DOM3 method isEqualNode 215 equalNodeOrPromiseOrFunction = a.isEqualNode(b); 216 return equalNodeOrPromiseOrFunction; 217 } 218 if (aIsDomNode || bIsDomNode) { 219 equalNodeOrPromiseOrFunction = false; 220 return false; 221 } 222 } 223 224 if (DeepTypeUtils.isPromise(a) && DeepTypeUtils.isPromise(b)) { 225 const aIsPromise = DeepTypeUtils.isPromise(a); 226 const bIsPromise = DeepTypeUtils.isPromise(b); 227 // 俩个Promise对象 228 if (aIsPromise && bIsPromise) { 229 equalNodeOrPromiseOrFunction = a === b; 230 return a === b; 231 } 232 } 233 if (DeepTypeUtils.isFunction(a) && DeepTypeUtils.isFunction(b)) { 234 // 俩个函数对象 235 const aCtor = a.constructor, 236 bCtor = b.constructor; 237 if ( 238 aCtor !== bCtor && 239 DeepTypeUtils.isFunction(aCtor) && 240 DeepTypeUtils.isFunction(bCtor) && 241 a instanceof aCtor && 242 b instanceof bCtor && 243 !(aCtor instanceof aCtor && bCtor instanceof bCtor) 244 ) { 245 equalNodeOrPromiseOrFunction = false; 246 return false; 247 } 248 } 249 return equalNodeOrPromiseOrFunction; 250} 251 252function isEqualCollection(a, b) { 253 let equalCollection = true; 254 // 获取a的对象名称 255 const aClassName = Object.prototype.toString.call(a); 256 const bClassName = Object.prototype.toString.call(b); 257 // 都是数组 258 if (aClassName === '[object Array]') { 259 equalCollection = isEqualArray(a, b); 260 return equalCollection; 261 } 262 263 // 都是Map 264 if (DeepTypeUtils.isMap(a) && DeepTypeUtils.isMap(b)) { 265 equalCollection = isEqualMap(a, b); 266 return equalCollection; 267 } 268 269 // 都是Set 270 if (DeepTypeUtils.isSet(a) && DeepTypeUtils.isSet(b)) { 271 equalCollection = isEqualSet(a, b); 272 return equalCollection; 273 } 274 275 return true; 276} 277 278function isEqualSampleObj(a, b) { 279 let equalSampleObj = true; 280 const aClassName = Object.prototype.toString.call(a); 281 const bClassName = Object.prototype.toString.call(b); 282 // 俩个string对象 283 if (aClassName === '[object String]') { 284 equalSampleObj = a === String(b); 285 return equalSampleObj; 286 } 287 // 俩个Number对象 288 if (aClassName === '[object Number]') { 289 // NAN 290 if (a !== a && b !== b) { 291 return a === b; 292 } 293 equalSampleObj = a !== +a ? b !== +b : a === 0 && b === 0 ? 1 / a === 1 / b : a === +b; 294 return equalSampleObj; 295 } 296 297 // 俩个Date对象/ boolean对象 298 if (aClassName === '[object Date]' || aClassName === '[object Boolean]') { 299 equalSampleObj = +a === +b; 300 return equalSampleObj; 301 } 302 303 // 俩个ArrayBuffer 304 if (aClassName === '[object ArrayBuffer]') { 305 equalSampleObj = eq(new Uint8Array(a), new Uint8Array(b)); 306 return equalSampleObj; 307 } 308 309 // 正则表达式 310 if (aClassName === '[object RegExp]') { 311 return ( 312 a.source === b.source && 313 a.global === b.global && 314 a.multiline === b.multiline && 315 a.ignoreCase === b.ignoreCase 316 ); 317 } 318 319 if (a instanceof Error && b instanceof Error) { 320 equalSampleObj = a.message === b.message; 321 return equalSampleObj; 322 } 323 324 return equalSampleObj; 325} 326 327function isEqualObj(a, b) { 328 let equalObj = true; 329 const aClassName = Object.prototype.toString.call(a); 330 const bClassName = Object.prototype.toString.call(b); 331 const aKeys = DeepTypeUtils.keys(a, aClassName === '[object Array]'); 332 let size = aKeys.length; 333 334 // 俩个对象属性长度不一致, 俩对象不相同 335 if (DeepTypeUtils.keys(b, bClassName === '[object Array]').length !== size) { 336 return false; 337 } 338 339 // 俩对象属性数量相同, 递归比较每个属性值得值 340 for (const key of aKeys) { 341 // b 没有 key 属性 342 if (!DeepTypeUtils.has(b, key)) { 343 equalObj = false; 344 continue; 345 } 346 if (!eq(a[key], b[key])) { 347 equalObj = false; 348 } 349 } 350 return equalObj; 351} 352 353function isEqualArray(a, b) { 354 let equalArray = true; 355 const aLength = a.length; 356 const bLength = b.length; 357 if (aLength !== bLength) { 358 // 数组长度不同,不是同一个对象 359 return false; 360 } 361 for (let i = 0; i < aLength || i < bLength; i++) { 362 // 递归每一个元素是否相同 363 equalArray = eq(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0) && equalArray; 364 } 365 return equalArray; 366} 367 368function isEqualMap(a, b) { 369 let equalMap = true; 370 if (a.size !== b.size) { 371 return false; 372 } 373 const keysA = []; 374 const keysB = []; 375 a.forEach(function(valueA, keyA) { 376 keysA.push(keyA); 377 }); 378 b.forEach(function(valueB, keyB) { 379 keysB.push(keyB); 380 }); 381 const mapKeys = [keysA, keysB]; 382 const cmpKeys = [keysB, keysA]; 383 for (let i = 0; equalMap && i < mapKeys.length; i++) { 384 const mapIter = mapKeys[i]; 385 const cmpIter = cmpKeys[i]; 386 387 for (let j = 0; equalMap && j < mapIter.length; j++) { 388 const mapKey = mapIter[j]; 389 const cmpKey = cmpIter[j]; 390 const mapValueA = a.get(mapKey); 391 let mapValueB; 392 if (eq(mapKey, cmpKey)) { 393 mapValueB = b.get(cmpKey); 394 } else { 395 mapValueB = b.get(mapKey); 396 } 397 equalMap = eq(mapValueA, mapValueB); 398 } 399 } 400 return equalMap; 401} 402 403function isEqualSet(a, b) { 404 let equalSet = true; 405 if (a.size !== b.size) { 406 return false; 407 } 408 const valuesA = []; 409 a.forEach(function(valueA) { 410 valuesA.push(valueA); 411 }); 412 const valuesB = []; 413 b.forEach(function(valueB) { 414 valuesB.push(valueB); 415 }); 416 const setPairs = [[valuesA, valuesB], [valuesB, valuesA]]; 417 for (let i = 0; equalSet && i < setPairs.length; i++) { 418 const baseValues = setPairs[i][0]; 419 const otherValues = setPairs[i][1]; 420 for (const baseValue of baseValues) { 421 let found = false; 422 for (let j = 0; !found && j < otherValues.length; j++) { 423 const otherValue = otherValues[j]; 424 // 深度比较对象 425 found = eq(baseValue, otherValue); 426 } 427 equalSet = equalSet && found; 428 } 429 } 430 return equalSet; 431} 432 433export default assertDeepEquals; 434