@schukai/monster 4.148.0 → 4.148.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -383,6 +383,21 @@ describe("DOM", function () {
383
383
  expect(u.getSubject().a).to.be.equal(1);
384
384
  });
385
385
 
386
+ it("run() should accept dictionary payload keys issue #494", async function () {
387
+ element.innerHTML = `<span data-monster-replace="path:nested.value"></span>`;
388
+ const u = new Updater(element, {
389
+ hasOwnProperty: "payload",
390
+ nested: { value: "rendered" },
391
+ });
392
+
393
+ try {
394
+ await u.run();
395
+ expect(element.textContent).to.equal("rendered");
396
+ } finally {
397
+ u.dispose();
398
+ }
399
+ });
400
+
386
401
  it("enableEventProcessing() should be chainable", function () {
387
402
  const u = new Updater(element);
388
403
  expect(u.enableEventProcessing()).to.be.instanceof(Updater);
@@ -1892,6 +1907,37 @@ describe("DOM", function () {
1892
1907
  });
1893
1908
 
1894
1909
  describe("Updater reactive updates", function () {
1910
+ [false, true].forEach((batchUpdates) => {
1911
+ it(`should preserve microtask changes with batchUpdates=${batchUpdates} issue #496`, async function () {
1912
+ let mocks = document.getElementById("mocks");
1913
+ mocks.innerHTML = `
1914
+ <span id="microtask-a" data-monster-replace="path:a"></span>
1915
+ <span id="microtask-b" data-monster-replace="path:b"></span>
1916
+ `;
1917
+
1918
+ const updater = new Updater(mocks, { a: "A1", b: "B1" });
1919
+ updater.setBatchUpdates(batchUpdates);
1920
+
1921
+ try {
1922
+ await updater.run();
1923
+ const subject = updater.getSubject();
1924
+ subject.a = "A2";
1925
+ queueMicrotask(() => {
1926
+ subject.b = "B2";
1927
+ });
1928
+
1929
+ await new Promise((resolve) => setTimeout(resolve, 20));
1930
+
1931
+ expect(subject.a).to.equal("A2");
1932
+ expect(subject.b).to.equal("B2");
1933
+ expect(document.getElementById("microtask-a").textContent).to.equal("A2");
1934
+ expect(document.getElementById("microtask-b").textContent).to.equal("B2");
1935
+ } finally {
1936
+ updater.dispose();
1937
+ }
1938
+ });
1939
+ });
1940
+
1895
1941
  it("should update the DOM when the subject changes", function (done) {
1896
1942
  let mocks = document.getElementById("mocks");
1897
1943
  mocks.innerHTML = html1;
@@ -2017,4 +2063,251 @@ describe("DOM", function () {
2017
2063
  .catch((e) => done(e));
2018
2064
  });
2019
2065
  });
2066
+
2067
+ describe("Updater event lifecycle issue #497", function () {
2068
+ const waitForBinding = () => new Promise((resolve) => setTimeout(resolve, 80));
2069
+
2070
+ it("should debounce different bound controls independently", async function () {
2071
+ const mocks = document.getElementById("mocks");
2072
+ mocks.innerHTML = `
2073
+ <div id="event-root">
2074
+ <input id="event-first" data-monster-bind="path:first">
2075
+ <input id="event-second" data-monster-bind="path:second">
2076
+ </div>
2077
+ `;
2078
+ const updater = new Updater(document.getElementById("event-root"), {});
2079
+ updater.enableEventProcessing();
2080
+
2081
+ try {
2082
+ const first = document.getElementById("event-first");
2083
+ const second = document.getElementById("event-second");
2084
+ first.value = "A";
2085
+ first.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
2086
+ second.value = "B";
2087
+ second.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
2088
+
2089
+ await waitForBinding();
2090
+
2091
+ expect(updater.getSubject().first).to.equal("A");
2092
+ expect(updater.getSubject().second).to.equal("B");
2093
+ } finally {
2094
+ updater.dispose();
2095
+ }
2096
+ });
2097
+
2098
+ it("should coalesce a soft keyboard event burst to the latest value", async function () {
2099
+ const mocks = document.getElementById("mocks");
2100
+ mocks.innerHTML = `
2101
+ <div id="keyboard-root">
2102
+ <input id="keyboard-input" data-monster-bind="path:value">
2103
+ </div>
2104
+ `;
2105
+ const updater = new Updater(document.getElementById("keyboard-root"), {});
2106
+ updater.enableEventProcessing();
2107
+
2108
+ try {
2109
+ const input = document.getElementById("keyboard-input");
2110
+ input.value = "intermediate";
2111
+ input.dispatchEvent(new InputEvent("input", {
2112
+ bubbles: true,
2113
+ composed: true,
2114
+ data: "i",
2115
+ isComposing: true,
2116
+ }));
2117
+ input.value = "final";
2118
+ input.dispatchEvent(new KeyboardEvent("keyup", { bubbles: true, composed: true }));
2119
+ input.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
2120
+
2121
+ await waitForBinding();
2122
+
2123
+ expect(updater.getSubject().value).to.equal("final");
2124
+ } finally {
2125
+ updater.dispose();
2126
+ }
2127
+ });
2128
+
2129
+ it("disableEventProcessing() should cancel pending binding work", async function () {
2130
+ const mocks = document.getElementById("mocks");
2131
+ mocks.innerHTML = `
2132
+ <div id="disable-root">
2133
+ <input id="disable-input" data-monster-bind="path:value">
2134
+ </div>
2135
+ `;
2136
+ const updater = new Updater(document.getElementById("disable-root"), {});
2137
+ updater.enableEventProcessing();
2138
+
2139
+ try {
2140
+ const input = document.getElementById("disable-input");
2141
+ input.value = "ignored";
2142
+ input.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
2143
+ updater.disableEventProcessing();
2144
+
2145
+ await waitForBinding();
2146
+
2147
+ expect(updater.getSubject()).to.not.have.property("value");
2148
+ } finally {
2149
+ updater.dispose();
2150
+ }
2151
+ });
2152
+
2153
+ it("dispose() should cancel pending binding work", async function () {
2154
+ const mocks = document.getElementById("mocks");
2155
+ mocks.innerHTML = `
2156
+ <div id="dispose-root">
2157
+ <input id="dispose-input" data-monster-bind="path:value">
2158
+ </div>
2159
+ `;
2160
+ const updater = new Updater(document.getElementById("dispose-root"), {});
2161
+ const subject = updater.getSubject();
2162
+ updater.enableEventProcessing();
2163
+
2164
+ const input = document.getElementById("dispose-input");
2165
+ input.value = "ignored";
2166
+ input.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
2167
+ updater.dispose();
2168
+
2169
+ await waitForBinding();
2170
+
2171
+ expect(subject).to.not.have.property("value");
2172
+ });
2173
+
2174
+ it("should remove previously registered event types before re-enabling", async function () {
2175
+ const mocks = document.getElementById("mocks");
2176
+ mocks.innerHTML = `
2177
+ <div id="types-root">
2178
+ <input id="types-input" data-monster-bind="path:value">
2179
+ </div>
2180
+ `;
2181
+ const updater = new Updater(document.getElementById("types-root"), {});
2182
+ updater.enableEventProcessing();
2183
+ updater.setEventTypes(["blur"]);
2184
+ updater.enableEventProcessing();
2185
+
2186
+ try {
2187
+ const input = document.getElementById("types-input");
2188
+ input.value = "stale-input-listener";
2189
+ input.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
2190
+ await waitForBinding();
2191
+ expect(updater.getSubject()).to.not.have.property("value");
2192
+
2193
+ input.value = "blur-value";
2194
+ input.dispatchEvent(new Event("blur", { bubbles: false, composed: true }));
2195
+ await waitForBinding();
2196
+ expect(updater.getSubject().value).to.equal("blur-value");
2197
+ } finally {
2198
+ updater.dispose();
2199
+ }
2200
+ });
2201
+ });
2202
+
2203
+ describe("Updater pipe failure recovery issue #498", function () {
2204
+ const waitForUpdate = () => new Promise((resolve) => setTimeout(resolve, 20));
2205
+
2206
+ it("should preserve and recover replace content", async function () {
2207
+ const mocks = document.getElementById("mocks");
2208
+ mocks.innerHTML = `<span id="failure-replace" data-monster-replace="path:value | call:unstable"></span>`;
2209
+ const updater = new Updater(mocks, { value: "first" });
2210
+ let failing = false;
2211
+ updater.setCallback("unstable", (value) => {
2212
+ if (failing) throw new Error("probe failure");
2213
+ return value;
2214
+ });
2215
+
2216
+ try {
2217
+ await updater.run();
2218
+ const target = document.getElementById("failure-replace");
2219
+ expect(target.textContent).to.equal("first");
2220
+
2221
+ failing = true;
2222
+ updater.getSubject().value = "second";
2223
+ await waitForUpdate();
2224
+ expect(target.textContent).to.equal("first");
2225
+ expect(target.getAttribute("data-monster-error")).to.equal("probe failure");
2226
+
2227
+ failing = false;
2228
+ updater.getSubject().value = "third";
2229
+ await waitForUpdate();
2230
+ expect(target.textContent).to.equal("third");
2231
+ expect(target.hasAttribute("data-monster-error")).to.be.false;
2232
+ } finally {
2233
+ updater.dispose();
2234
+ }
2235
+ });
2236
+
2237
+ it("should preserve attributes while valid sibling bindings continue", async function () {
2238
+ const mocks = document.getElementById("mocks");
2239
+ mocks.innerHTML = `
2240
+ <span
2241
+ id="failure-attribute"
2242
+ data-monster-attributes="title path:value | call:unstable, data-valid path:sibling"
2243
+ ></span>
2244
+ `;
2245
+ const updater = new Updater(mocks, { value: "first", sibling: "one" });
2246
+ let failing = false;
2247
+ updater.setCallback("unstable", (value) => {
2248
+ if (failing) throw new Error("probe failure");
2249
+ return value;
2250
+ });
2251
+
2252
+ try {
2253
+ await updater.run();
2254
+ const target = document.getElementById("failure-attribute");
2255
+ expect(target.getAttribute("title")).to.equal("first");
2256
+
2257
+ failing = true;
2258
+ updater.getSubject().value = "second";
2259
+ updater.getSubject().sibling = "two";
2260
+ await waitForUpdate();
2261
+ expect(target.getAttribute("title")).to.equal("first");
2262
+ expect(target.getAttribute("data-valid")).to.equal("two");
2263
+ expect(target.getAttribute("data-monster-error")).to.equal("probe failure");
2264
+
2265
+ failing = false;
2266
+ updater.getSubject().value = "third";
2267
+ await waitForUpdate();
2268
+ expect(target.getAttribute("title")).to.equal("third");
2269
+ expect(target.hasAttribute("data-monster-error")).to.be.false;
2270
+ } finally {
2271
+ updater.dispose();
2272
+ }
2273
+ });
2274
+
2275
+ it("should preserve properties while valid sibling bindings continue", async function () {
2276
+ const mocks = document.getElementById("mocks");
2277
+ mocks.innerHTML = `
2278
+ <input
2279
+ id="failure-property"
2280
+ data-monster-properties="value path:value | call:unstable, disabled path:disabled"
2281
+ >
2282
+ `;
2283
+ const updater = new Updater(mocks, { value: "first", disabled: false });
2284
+ let failing = false;
2285
+ updater.setCallback("unstable", (value) => {
2286
+ if (failing) throw new Error("probe failure");
2287
+ return value;
2288
+ });
2289
+
2290
+ try {
2291
+ await updater.run();
2292
+ const target = document.getElementById("failure-property");
2293
+ expect(target.value).to.equal("first");
2294
+
2295
+ failing = true;
2296
+ updater.getSubject().value = "second";
2297
+ updater.getSubject().disabled = true;
2298
+ await waitForUpdate();
2299
+ expect(target.value).to.equal("first");
2300
+ expect(target.disabled).to.be.true;
2301
+ expect(target.getAttribute("data-monster-error")).to.equal("probe failure");
2302
+
2303
+ failing = false;
2304
+ updater.getSubject().value = "third";
2305
+ await waitForUpdate();
2306
+ expect(target.value).to.equal("third");
2307
+ expect(target.hasAttribute("data-monster-error")).to.be.false;
2308
+ } finally {
2309
+ updater.dispose();
2310
+ }
2311
+ });
2312
+ });
2020
2313
  });
@@ -292,4 +292,56 @@ describe('ProxyObserver', function () {
292
292
  });
293
293
  });
294
294
 
295
+ describe('native object compatibility issue #495', function () {
296
+ it('should preserve Date, Map, and Set identity and native methods', function () {
297
+ const date = new Date('2026-01-01T00:00:00Z');
298
+ const map = new Map([['key', 'value']]);
299
+ const set = new Set(['value']);
300
+ const proxy = new ProxyObserver({date, map, set});
301
+ const subject = proxy.getSubject();
302
+
303
+ expect(subject.date).to.equal(date);
304
+ expect(subject.date.getTime()).to.equal(date.getTime());
305
+ expect(subject.map).to.equal(map);
306
+ expect(subject.map.get('key')).to.equal('value');
307
+ expect(subject.set).to.equal(set);
308
+ expect(subject.set.has('value')).to.be.true;
309
+ });
310
+
311
+ it('should not notify for internal Map and Set mutations', async function () {
312
+ const proxy = new ProxyObserver({map: new Map(), set: new Set()});
313
+ let notifications = 0;
314
+ proxy.attachObserver(new Observer(function () {
315
+ notifications++;
316
+ }));
317
+
318
+ proxy.getSubject().map.set('key', 'value');
319
+ proxy.getSubject().set.add('value');
320
+ await new Promise((resolve) => setTimeout(resolve, 0));
321
+
322
+ expect(notifications).to.equal(0);
323
+ });
324
+
325
+ it('should notify when a complete native object property is replaced', async function () {
326
+ const proxy = new ProxyObserver({map: new Map()});
327
+ const replacement = new Map([['key', 'value']]);
328
+ let notifications = 0;
329
+ let resolveNotification;
330
+ const notified = new Promise((resolve) => {
331
+ resolveNotification = resolve;
332
+ });
333
+ proxy.attachObserver(new Observer(function () {
334
+ notifications++;
335
+ resolveNotification();
336
+ }));
337
+
338
+ proxy.getSubject().map = replacement;
339
+ await notified;
340
+
341
+ expect(proxy.getRealSubject().map).to.equal(replacement);
342
+ expect(proxy.getSubject().map).to.equal(replacement);
343
+ expect(notifications).to.equal(1);
344
+ });
345
+ });
346
+
295
347
  })
@@ -107,6 +107,30 @@ describe('Clone', function () {
107
107
  let b = clone(a);
108
108
  expect(a.x).is.equal(b.x)
109
109
  });
110
+
111
+ it('should clone objects with an own hasOwnProperty key issue #494', function () {
112
+ const source = {
113
+ hasOwnProperty: 'payload',
114
+ nested: {value: 1},
115
+ };
116
+
117
+ const result = clone(source);
118
+
119
+ expect(result).to.deep.equal(source);
120
+ expect(result).not.to.equal(source);
121
+ expect(result.nested).not.to.equal(source.nested);
122
+ });
123
+
124
+ it('should deeply clone null-prototype dictionaries issue #494', function () {
125
+ const source = Object.create(null);
126
+ source.nested = {value: 1};
127
+
128
+ const result = clone(source);
129
+
130
+ expect(Object.getPrototypeOf(result)).to.equal(null);
131
+ expect(result.nested).to.deep.equal({value: 1});
132
+ expect(result.nested).not.to.equal(source.nested);
133
+ });
110
134
  })
111
135
  describe('.clone(function)', function () {
112
136