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