Skip to content

Commit 52c723e

Browse files
authored
Normalize values to a canonical form in the config watcher (#757)
1 parent dfadc3e commit 52c723e

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

src/configWatcher.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function watchConfigurationChanges(
2626

2727
const newValue = getValue();
2828

29-
if (!isDeepStrictEqual(newValue, appliedValues.get(setting))) {
29+
if (!configValuesEqual(newValue, appliedValues.get(setting))) {
3030
changedSettings.push(setting);
3131
appliedValues.set(setting, newValue);
3232
}
@@ -37,3 +37,22 @@ export function watchConfigurationChanges(
3737
}
3838
});
3939
}
40+
41+
function configValuesEqual(a: unknown, b: unknown): boolean {
42+
return isDeepStrictEqual(normalizeEmptyValue(a), normalizeEmptyValue(b));
43+
}
44+
45+
/**
46+
* Normalize empty values (undefined, null, "", []) to a canonical form for comparison.
47+
*/
48+
function normalizeEmptyValue(value: unknown): unknown {
49+
if (
50+
value === undefined ||
51+
value === null ||
52+
value === "" ||
53+
(Array.isArray(value) && value.length === 0)
54+
) {
55+
return undefined;
56+
}
57+
return value;
58+
}

test/unit/configWatcher.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,44 @@ describe("watchConfigurationChanges", () => {
9494
expect(changes).toEqual([["test.setting"]]);
9595
dispose();
9696
});
97+
98+
it("treats undefined, null, empty string, and empty array as equivalent", () => {
99+
const config = new MockConfigurationProvider();
100+
config.set("test.setting", undefined);
101+
const { changes, dispose } = createWatcher("test.setting");
102+
103+
config.set("test.setting", null);
104+
config.set("test.setting", "");
105+
config.set("test.setting", []);
106+
config.set("test.setting", undefined);
107+
108+
expect(changes).toEqual([]);
109+
dispose();
110+
});
111+
112+
interface ValueChangeTestCase {
113+
name: string;
114+
from: unknown;
115+
to: unknown;
116+
}
117+
118+
it.each<ValueChangeTestCase>([
119+
{ name: "undefined to value", from: undefined, to: "value" },
120+
{ name: "value to empty string", from: "value", to: "" },
121+
{ name: "undefined to false", from: undefined, to: false },
122+
{ name: "undefined to zero", from: undefined, to: 0 },
123+
{ name: "null to value", from: null, to: "value" },
124+
{ name: "empty string to value", from: "", to: "value" },
125+
{ name: "empty array to non-empty array", from: [], to: ["item"] },
126+
{ name: "value to different value", from: "old", to: "new" },
127+
])("detects change: $name", ({ from, to }) => {
128+
const config = new MockConfigurationProvider();
129+
config.set("test.setting", from);
130+
const { changes, dispose } = createWatcher("test.setting");
131+
132+
config.set("test.setting", to);
133+
134+
expect(changes).toEqual([["test.setting"]]);
135+
dispose();
136+
});
97137
});

0 commit comments

Comments
 (0)