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 26/** 27 * 获取失败显示日志 28 * @param actualValue 实际对象 29 * @param expected 期待比较对象 30 */ 31function logMsg(actualValue, expected) { 32 // 获取a的对象名称 33 const aClassName = Object.prototype.toString.call(actualValue); 34 const bClassName = Object.prototype.toString.call(expected); 35 let actualMsg; 36 let expectMsg; 37 if (aClassName == '[object Function]') { 38 actualMsg = 'actualValue Function'; 39 } else if (aClassName == '[object Promise]') { 40 actualMsg = 'actualValue Promise'; 41 } else if (aClassName == '[object Set]' || aClassName == '[object Map]') { 42 actualMsg = JSON.stringify(Array.from(actualValue)); 43 } else if (aClassName == '[object RegExp]') { 44 actualMsg = JSON.stringify(actualValue.source.replace('\\', '')); 45 } else if (aClassName == '[object BigInt]') { 46 actualMsg = actualValue; 47 } else if (aClassName == '[object Error]') { 48 actualMsg = actualValue.message; 49 } else if (aClassName == '[object ArrayBuffer]') { 50 actualMsg = actualValue.byteLength; 51 } 52 else { 53 actualMsg = JSON.stringify(actualValue); 54 } 55 if (bClassName == '[object Function]') { 56 expectMsg = 'expected Function'; 57 } else if (bClassName == '[object Promise]') { 58 expectMsg = 'expected Promise'; 59 } else if (bClassName == '[object Set]' || bClassName == '[object Map]') { 60 expectMsg = JSON.stringify(Array.from(expected)); 61 } else if (bClassName == '[object RegExp]') { 62 expectMsg = JSON.stringify(expected.source.replace('\\', '')); 63 } else if (bClassName == '[object BigInt]') { 64 expectMsg = expected; 65 } else if (bClassName == '[object Error]') { 66 expectMsg = expected.message; 67 } else if (bClassName == '[object ArrayBuffer]') { 68 expectMsg = expected.byteLength; 69 } 70 else { 71 expectMsg = JSON.stringify(expected); 72 } 73 return actualMsg + ' is not deep equal ' + expectMsg; 74} 75 76function eq(a, b) { 77 let result = true; 78 79 if (a === b) { 80 result = a !== 0 || 1 / a === 1 / b; 81 return result; 82 } 83 84 if (a === null || b === null) { 85 result = a === b; 86 return result; 87 } 88 // 获取a的对象名称 89 const aClassName = Object.prototype.toString.call(a); 90 const bClassName = Object.prototype.toString.call(b); 91 // 不同类型不同对象 92 if (aClassName !== bClassName) { 93 return false; 94 } 95 if (aClassName === '[object String]' || aClassName === '[object Number]' || aClassName === '[object Date]' || 96 aClassName === '[object Boolean]' || aClassName === '[object ArrayBuffer]' || 97 aClassName === '[object RegExp]' || aClassName === '[object Error]') { 98 result = isEqualSampleObj(a, b); 99 return result; 100 } 101 102 if (typeof a !== 'object' || typeof b !== 'object') { 103 return false; 104 } 105 106 if (DeepTypeUtils.isDomNode(a) || DeepTypeUtils.isPromise(a) || DeepTypeUtils.isFunction(a)) { 107 result = isEqualNodeOrPromiseOrFunction(a, b); 108 return result; 109 } 110 111 if (aClassName === '[object Array]' || aClassName === '[object Map]' || aClassName === '[object Set]') { 112 result = isEqualCollection(a, b); 113 return result; 114 } 115 116 result = isEqualObj(a, b); 117 return result; 118} 119 120function isEqualNodeOrPromiseOrFunction(a, b) { 121 let equalNodeOrPromiseOrFunction = true; 122 if (DeepTypeUtils.isDomNode(a) && DeepTypeUtils.isDomNode(b)) { 123 const aIsDomNode = DeepTypeUtils.isDomNode(a); 124 const bIsDomNode = DeepTypeUtils.isDomNode(b); 125 if (aIsDomNode && bIsDomNode) { 126 // At first try to use DOM3 method isEqualNode 127 equalNodeOrPromiseOrFunction = a.isEqualNode(b); 128 return equalNodeOrPromiseOrFunction; 129 } 130 if (aIsDomNode || bIsDomNode) { 131 equalNodeOrPromiseOrFunction = false; 132 return false; 133 } 134 } 135 136 if (DeepTypeUtils.isPromise(a) && DeepTypeUtils.isPromise(b)) { 137 const aIsPromise = DeepTypeUtils.isPromise(a); 138 const bIsPromise = DeepTypeUtils.isPromise(b); 139 // 俩个Promise对象 140 if (aIsPromise && bIsPromise) { 141 equalNodeOrPromiseOrFunction = a === b; 142 return a === b; 143 } 144 } 145 if (DeepTypeUtils.isFunction(a) && DeepTypeUtils.isFunction(b)) { 146 // 俩个函数对象 147 const aCtor = a.constructor, 148 bCtor = b.constructor; 149 if ( 150 aCtor !== bCtor && 151 DeepTypeUtils.isFunction(aCtor) && 152 DeepTypeUtils.isFunction(bCtor) && 153 a instanceof aCtor && 154 b instanceof bCtor && 155 !(aCtor instanceof aCtor && bCtor instanceof bCtor) 156 ) { 157 equalNodeOrPromiseOrFunction = false; 158 return false; 159 } 160 } 161 return equalNodeOrPromiseOrFunction; 162} 163 164function isEqualCollection(a, b) { 165 let equalCollection = true; 166 // 获取a的对象名称 167 const aClassName = Object.prototype.toString.call(a); 168 const bClassName = Object.prototype.toString.call(b); 169 // 都是数组 170 if (aClassName === '[object Array]') { 171 equalCollection = isEqualArray(a, b); 172 return equalCollection; 173 } 174 175 // 都是Map 176 if (DeepTypeUtils.isMap(a) && DeepTypeUtils.isMap(b)) { 177 equalCollection = isEqualMap(a, b); 178 return equalCollection; 179 } 180 181 // 都是Set 182 if (DeepTypeUtils.isSet(a) && DeepTypeUtils.isSet(b)) { 183 equalCollection = isEqualSet(a, b); 184 return equalCollection; 185 } 186 187 return true; 188} 189 190function isEqualSampleObj(a, b) { 191 let equalSampleObj = true; 192 const aClassName = Object.prototype.toString.call(a); 193 const bClassName = Object.prototype.toString.call(b); 194 // 俩个string对象 195 if (aClassName === '[object String]') { 196 equalSampleObj = a === String(b); 197 return equalSampleObj; 198 } 199 // 俩个Number对象 200 if (aClassName === '[object Number]') { 201 equalSampleObj = a !== +a ? b !== +b : a === 0 && b === 0 ? 1 / a === 1 / b : a === +b; 202 return equalSampleObj; 203 } 204 205 // 俩个Date对象/ boolean对象 206 if (aClassName === '[object Date]' || aClassName === '[object Boolean]') { 207 equalSampleObj = +a === +b; 208 return equalSampleObj; 209 } 210 211 // 俩个ArrayBuffer 212 if (aClassName === '[object ArrayBuffer]') { 213 equalSampleObj = eq(new Uint8Array(a), new Uint8Array(b)); 214 return equalSampleObj; 215 } 216 217 // 正则表达式 218 if (aClassName === '[object RegExp]') { 219 return ( 220 a.source === b.source && 221 a.global === b.global && 222 a.multiline === b.multiline && 223 a.ignoreCase === b.ignoreCase 224 ); 225 } 226 227 if (a instanceof Error && b instanceof Error) { 228 equalSampleObj = a.message === b.message; 229 return equalSampleObj; 230 } 231 232 return equalSampleObj; 233} 234 235function isEqualObj(a, b) { 236 let equalObj = true; 237 const aClassName = Object.prototype.toString.call(a); 238 const bClassName = Object.prototype.toString.call(b); 239 const aKeys = DeepTypeUtils.keys(a, aClassName === '[object Array]'); 240 let size = aKeys.length; 241 242 // 俩个对象属性长度不一致, 俩对象不相同 243 if (DeepTypeUtils.keys(b, bClassName === '[object Array]').length !== size) { 244 return false; 245 } 246 247 // 俩对象属性数量相同, 递归比较每个属性值得值 248 for (const key of aKeys) { 249 // b 没有 key 属性 250 if (!DeepTypeUtils.has(b, key)) { 251 equalObj = false; 252 continue; 253 } 254 if (!eq(a[key], b[key])) { 255 equalObj = false; 256 } 257 } 258 return equalObj; 259} 260 261function isEqualArray(a, b) { 262 let equalArray = true; 263 const aLength = a.length; 264 const bLength = b.length; 265 if (aLength !== bLength) { 266 // 数组长度不同,不是同一个对象 267 return false; 268 } 269 for (let i = 0; i < aLength || i < bLength; i++) { 270 // 递归每一个元素是否相同 271 equalArray = eq(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0) && equalArray; 272 } 273 return equalArray; 274} 275 276function isEqualMap(a, b) { 277 let equalMap = true; 278 if (a.size !== b.size) { 279 return false; 280 } 281 const keysA = []; 282 const keysB = []; 283 a.forEach(function(valueA, keyA) { 284 keysA.push(keyA); 285 }); 286 b.forEach(function(valueB, keyB) { 287 keysB.push(keyB); 288 }); 289 const mapKeys = [keysA, keysB]; 290 const cmpKeys = [keysB, keysA]; 291 for (let i = 0; equalMap && i < mapKeys.length; i++) { 292 const mapIter = mapKeys[i]; 293 const cmpIter = cmpKeys[i]; 294 295 for (let j = 0; equalMap && j < mapIter.length; j++) { 296 const mapKey = mapIter[j]; 297 const cmpKey = cmpIter[j]; 298 const mapValueA = a.get(mapKey); 299 let mapValueB; 300 if (eq(mapKey, cmpKey)) { 301 mapValueB = b.get(cmpKey); 302 } else { 303 mapValueB = b.get(mapKey); 304 } 305 equalMap = eq(mapValueA, mapValueB); 306 } 307 } 308 return equalMap; 309} 310 311function isEqualSet(a, b) { 312 let equalSet = true; 313 if (a.size !== b.size) { 314 return false; 315 } 316 const valuesA = []; 317 a.forEach(function(valueA) { 318 valuesA.push(valueA); 319 }); 320 const valuesB = []; 321 b.forEach(function(valueB) { 322 valuesB.push(valueB); 323 }); 324 const setPairs = [[valuesA, valuesB], [valuesB, valuesA]]; 325 for (let i = 0; equalSet && i < setPairs.length; i++) { 326 const baseValues = setPairs[i][0]; 327 const otherValues = setPairs[i][1]; 328 for (const baseValue of baseValues) { 329 let found = false; 330 for (let j = 0; !found && j < otherValues.length; j++) { 331 const otherValue = otherValues[j]; 332 // 深度比较对象 333 found = eq(baseValue, otherValue); 334 } 335 equalSet = equalSet && found; 336 } 337 } 338 return equalSet; 339} 340 341export default assertDeepEquals; 342