Skip to content

Commit d5a7f74

Browse files
committed
Normalize "empty" string values to a canonical form for comparison in the config watcher
1 parent 31a55b9 commit d5a7f74

File tree

2 files changed

+55
-2
lines changed

2 files changed

+55
-2
lines changed

src/configWatcher.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,28 @@ 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);
31-
appliedValues.set(setting, newValue);
3231
}
32+
appliedValues.set(setting, newValue);
3333
}
3434

3535
if (changedSettings.length > 0) {
3636
onChange(changedSettings);
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" string values to a canonical form for comparison.
47+
*/
48+
function normalizeEmptyValue(value: unknown): unknown {
49+
if (value === undefined || value === null || value === "") {
50+
return undefined;
51+
}
52+
return value;
53+
}

test/unit/configWatcher.test.ts

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

0 commit comments

Comments
 (0)