Skip to content

Commit

Permalink
refactor: remove dependency on isDeepStrictEqual (#7584)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone authored Jan 30, 2025
1 parent 2440cdd commit 4b0ff81
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 10 deletions.
12 changes: 12 additions & 0 deletions packages/core/src/definitions/CommandClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,15 @@ export interface CommandClassInfo {
/** The maximum version of the CC that is supported or controlled */
version: number;
}

export function isCCInfoEqual(
a: CommandClassInfo,
b: CommandClassInfo,
): boolean {
return (
a.isSupported === b.isSupported
&& a.isControlled === b.isControlled
&& a.secure === b.secure
&& a.version === b.version
);
}
101 changes: 100 additions & 1 deletion packages/core/src/values/ValueDB.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CommandClasses } from "../definitions/CommandClasses.js";
import { ZWaveErrorCodes } from "../error/ZWaveError.js";
import { assertZWaveError } from "../test/assertZWaveError.js";
import { ValueMetadata } from "./Metadata.js";
import { ValueDB, dbKeyToValueIdFast } from "./ValueDB.js";
import { ValueDB, dbKeyToValueIdFast, valueEquals } from "./ValueDB.js";
import type { ValueID } from "./_Types.js";

function setup(): {
Expand Down Expand Up @@ -992,3 +992,102 @@ test("keys that are invalid JSON should not cause a crash", (t) => {
t.expect(valueDB.getAllMetadata(44)).toStrictEqual([]);
t.expect(valueDB["_index"].size).toBe(0);
});

test("valueEquals() -> should return true for primitive types with equal values", (t) => {
t.expect(valueEquals(42, 42)).toBe(true);
t.expect(valueEquals("hello", "hello")).toBe(true);
t.expect(valueEquals(true, true)).toBe(true);
t.expect(valueEquals(undefined, undefined)).toBe(true);
t.expect(valueEquals(null, null)).toBe(true);
});

test("valueEquals() -> should return false for primitive types with different values", (t) => {
t.expect(valueEquals(42, 43)).toBe(false);
t.expect(valueEquals("hello", "world")).toBe(false);
t.expect(valueEquals(true, false)).toBe(false);
t.expect(valueEquals(undefined, null)).toBe(false);
});

test("valueEquals() -> should return true for equal Uint8Array values", (t) => {
const a = new Uint8Array([1, 2, 3]);
const b = new Uint8Array([1, 2, 3]);
t.expect(valueEquals(a, b)).toBe(true);
});

test("valueEquals() -> should return false for different Uint8Array values", (t) => {
const a = new Uint8Array([1, 2, 3]);
const b = new Uint8Array([4, 5, 6]);
t.expect(valueEquals(a, b)).toBe(false);
});

test("valueEquals() -> should return true for equal arrays", (t) => {
t.expect(valueEquals([1, 2, 3], [1, 2, 3])).toBe(true);
t.expect(valueEquals(["a", "b", "c"], ["a", "b", "c"])).toBe(true);
});

test("valueEquals() -> should return false for different arrays", (t) => {
t.expect(valueEquals([1, 2, 3], [4, 5, 6])).toBe(false);
t.expect(valueEquals(["a", "b", "c"], ["d", "e", "f"])).toBe(false);
t.expect(valueEquals([1, 2, 3], [1, 2])).toBe(false);
});

test("valueEquals() -> should return true for equal objects", (t) => {
t.expect(valueEquals({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true);
t.expect(valueEquals({ foo: "bar" }, { foo: "bar" })).toBe(true);
});

test("valueEquals() -> should return false for different objects", (t) => {
t.expect(valueEquals({ a: 1, b: 2 }, { a: 1, b: 3 })).toBe(false);
t.expect(valueEquals({ foo: "bar" }, { foo: "baz" })).toBe(false);
t.expect(valueEquals({ a: 1, b: 2 }, { a: 1 })).toBe(false);
});

test("valueEquals() -> should return false for functions and symbols", (t) => {
t.expect(valueEquals(() => {}, () => {})).toBe(false);
t.expect(valueEquals(Symbol("a"), Symbol("a"))).toBe(false);
});

test("valueEquals() -> should return true for equal nested arrays", (t) => {
t.expect(valueEquals([1, [2, 3], [4, [5, 6]]], [1, [2, 3], [4, [5, 6]]]))
.toBe(true);
});

test("valueEquals() -> should return false for different nested arrays", (t) => {
t.expect(valueEquals([1, [2, 3], [4, [5, 6]]], [1, [2, 3], [4, [5, 7]]]))
.toBe(false);
t.expect(valueEquals([1, [2, 3]], [1, [2, 3, 4]])).toBe(false);
});

// Tests für verschachtelte Objekte
test("valueEquals() -> should return true for equal nested objects", (t) => {
t.expect(
valueEquals({ a: 1, b: { c: 2, d: { e: 3 } } }, {
a: 1,
b: { c: 2, d: { e: 3 } },
}),
).toBe(true);
});

test("valueEquals() -> should return false for different nested objects", (t) => {
t.expect(
valueEquals(
{ a: 1, b: { c: 2, d: { e: 3 } } },
{ a: 1, b: { c: 2, d: { e: 4 } } },
),
).toBe(false);
t.expect(
valueEquals(
{ a: 1, b: { c: 2 } },
{ a: 1, b: { c: 2, d: 3 } },
),
).toBe(false);
});

// Tests für den Vergleich von Werten unterschiedlicher Typen
test("valueEquals() -> should return false for values of different types", (t) => {
t.expect(valueEquals(42, "42")).toBe(false);
t.expect(valueEquals(true, 1)).toBe(false);
t.expect(valueEquals(null, undefined)).toBe(false);
t.expect(valueEquals({ a: 1 }, [1])).toBe(false);
t.expect(valueEquals(new Uint8Array([1, 2, 3]), [1, 2, 3])).toBe(false);
});
50 changes: 49 additions & 1 deletion packages/core/src/values/ValueDB.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { type Database } from "@zwave-js/shared/bindings";
import { TypedEventTarget } from "@zwave-js/shared/safe";
import {
TypedEventTarget,
areUint8ArraysEqual,
isUint8Array,
} from "@zwave-js/shared/safe";
import { isArray, isObject } from "alcalzone-shared/typeguards";
import type { CommandClasses } from "../definitions/CommandClasses.js";
import {
ZWaveError,
Expand Down Expand Up @@ -92,6 +97,49 @@ export function valueIdToString(valueID: ValueID): string {
return JSON.stringify(normalizeValueID(valueID));
}

/**
* Compares two values and checks if they are equal.
* This is a portable, but weak version of isDeepStrictEqual, limited to the types of values
* that can be stored in the Value DB.
*/
export function valueEquals(a: unknown, b: unknown): boolean {
switch (typeof a) {
case "bigint":
case "boolean":
case "number":
case "string":
case "undefined":
return a === b;

case "function":
case "symbol":
// These cannot be stored in the value DB
return false;
}

if (a === null) return b === null;

if (isUint8Array(a)) {
return isUint8Array(b) && areUint8ArraysEqual(a, b);
}

if (isArray(a)) {
return isArray(b)
&& a.length === b.length
&& a.every((v, i) => valueEquals(v, b[i]));
}

if (isObject(a)) {
if (!isObject(b)) return false;
const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]);
return [...allKeys].every((k) =>
valueEquals((a as any)[k], (b as any)[k])
);
}

return false;
}

/**
* The value store for a single node
*/
Expand Down
6 changes: 3 additions & 3 deletions packages/testing/src/MockNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
NOT_KNOWN,
SecurityClass,
type SecurityManagers,
isCCInfoEqual,
securityClassOrder,
} from "@zwave-js/core";
import { TimedExpectation } from "@zwave-js/shared";
import { isDeepStrictEqual } from "node:util";
import type { CCIdToCapabilities } from "./CCSpecificCapabilities.js";
import type { MockController } from "./MockController.js";
import {
Expand Down Expand Up @@ -94,7 +94,7 @@ export class MockEndpoint {
public addCC(cc: CommandClasses, info: Partial<CommandClassInfo>): void {
const original = this.implementedCCs.get(cc);
const updated = Object.assign({}, original ?? defaultCCInfo, info);
if (!isDeepStrictEqual(original, updated)) {
if (original == undefined || !isCCInfoEqual(original, updated)) {
this.implementedCCs.set(cc, updated);
}
}
Expand Down Expand Up @@ -387,7 +387,7 @@ export class MockNode {
public addCC(cc: CommandClasses, info: Partial<CommandClassInfo>): void {
const original = this.implementedCCs.get(cc);
const updated = Object.assign({}, original ?? defaultCCInfo, info);
if (!isDeepStrictEqual(original, updated)) {
if (original == undefined || !isCCInfoEqual(original, updated)) {
this.implementedCCs.set(cc, updated);
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/zwave-js/src/lib/node/Endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ import {
ZWaveErrorCodes,
actuatorCCs,
getCCName,
isCCInfoEqual,
} from "@zwave-js/core";
import { getEnumMemberName, num2hex } from "@zwave-js/shared";
import { isDeepStrictEqual } from "node:util";
import type { Driver } from "../driver/Driver.js";
import { cacheKeys } from "../driver/NetworkCache.js";
import type { DeviceClass } from "./DeviceClass.js";
Expand Down Expand Up @@ -162,7 +162,7 @@ export class Endpoint
},
info,
);
if (!isDeepStrictEqual(original, updated)) {
if (original == undefined || !isCCInfoEqual(original, updated)) {
this._implementedCommandClasses.set(cc, updated);
}
}
Expand Down
5 changes: 2 additions & 3 deletions packages/zwave-js/src/lib/node/mixins/60_ScheduledPoll.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type CCAPI, type SchedulePollOptions } from "@zwave-js/cc";
import { type ValueDB, normalizeValueID } from "@zwave-js/core";
import { type ValueDB, normalizeValueID, valueEquals } from "@zwave-js/core";
import {
type CommandClasses,
MessagePriority,
Expand All @@ -8,7 +8,6 @@ import {
type ValueUpdatedArgs,
} from "@zwave-js/core/safe";
import { ObjectKeyMap } from "@zwave-js/shared";
import { isDeepStrictEqual } from "node:util";
import { type Driver } from "../../driver/Driver.js";
import { type DeviceClass } from "../DeviceClass.js";
import { EndpointsMixin } from "./50_Endpoints.js";
Expand Down Expand Up @@ -168,7 +167,7 @@ export abstract class SchedulePollMixin extends EndpointsMixin
if (
actualValue !== undefined
&& poll.expectedValue !== undefined
&& !isDeepStrictEqual(poll.expectedValue, actualValue)
&& !valueEquals(poll.expectedValue, actualValue)
) {
return false;
}
Expand Down

0 comments on commit 4b0ff81

Please sign in to comment.