@schukai/monster 4.148.7 → 4.148.9

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.
@@ -1439,7 +1439,7 @@ describe("ControlBar", function () {
1439
1439
  }
1440
1440
  });
1441
1441
 
1442
- it("should keep the overflow switch stable across the switch-width threshold", async function () {
1442
+ it("should produce the same overflow layout across repeated switch-width threshold crossings", async function () {
1443
1443
  const OriginalResizeObserver = window.ResizeObserver;
1444
1444
  const originalGlobalResizeObserver = globalThis.ResizeObserver;
1445
1445
  const originalRequestAnimationFrame = window.requestAnimationFrame;
@@ -1524,7 +1524,11 @@ describe("ControlBar", function () {
1524
1524
  );
1525
1525
  Object.defineProperty(switchButton, "offsetWidth", {
1526
1526
  configurable: true,
1527
- value: 20,
1527
+ get: () =>
1528
+ switchButton.hasAttribute("hidden") ||
1529
+ switchButton.classList.contains("hidden")
1530
+ ? 0
1531
+ : 20,
1528
1532
  });
1529
1533
 
1530
1534
  await flushFrames();
@@ -1538,25 +1542,25 @@ describe("ControlBar", function () {
1538
1542
  wrapperWidth = 155;
1539
1543
  resizeObserver.triggerResize([{ target: wrapper }]);
1540
1544
  await flushFrames();
1541
- expect(switchButton.hasAttribute("hidden")).to.be.false;
1542
- expect(document.getElementById("threshold-three").getAttribute("slot")).to
1543
- .equal("popper");
1545
+ expect(switchButton.hasAttribute("hidden")).to.be.true;
1546
+ expect(document.getElementById("threshold-three").hasAttribute("slot")).to
1547
+ .be.false;
1544
1548
 
1545
- wrapperWidth = 175;
1549
+ wrapperWidth = 145;
1546
1550
  resizeObserver.triggerResize([{ target: wrapper }]);
1547
1551
  await flushFrames();
1548
1552
  expect(switchButton.hasAttribute("hidden")).to.be.false;
1549
1553
  expect(document.getElementById("threshold-three").getAttribute("slot")).to
1550
1554
  .equal("popper");
1551
1555
 
1552
- wrapperWidth = 190;
1556
+ wrapperWidth = 155;
1553
1557
  resizeObserver.triggerResize([{ target: wrapper }]);
1554
1558
  await flushFrames();
1555
1559
  expect(switchButton.hasAttribute("hidden")).to.be.true;
1556
1560
  expect(document.getElementById("threshold-three").hasAttribute("slot")).to
1557
1561
  .be.false;
1558
1562
 
1559
- wrapperWidth = 160;
1563
+ wrapperWidth = 145;
1560
1564
  resizeObserver.triggerResize([{ target: wrapper }]);
1561
1565
  await flushFrames();
1562
1566
  expect(switchButton.hasAttribute("hidden")).to.be.false;
@@ -1570,6 +1574,201 @@ describe("ControlBar", function () {
1570
1574
  }
1571
1575
  });
1572
1576
 
1577
+ it("should not count rendered padding and borders twice when controls fit", async function () {
1578
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
1579
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
1580
+ const scheduledCallbacks = [];
1581
+ const flushFrames = async () => {
1582
+ while (scheduledCallbacks.length > 0) {
1583
+ scheduledCallbacks.shift()();
1584
+ await new Promise((resolve) => setTimeout(resolve, 0));
1585
+ }
1586
+ };
1587
+
1588
+ try {
1589
+ window.requestAnimationFrame = (callback) => {
1590
+ scheduledCallbacks.push(callback);
1591
+ return scheduledCallbacks.length;
1592
+ };
1593
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
1594
+
1595
+ const mocks = document.getElementById("mocks");
1596
+ mocks.innerHTML = `
1597
+ <div id="border-box-wrapper">
1598
+ <monster-control-bar id="border-box-bar">
1599
+ <button id="border-box-one" style="padding: 10px; border: 2px solid">One</button>
1600
+ <button id="border-box-two" style="padding: 10px; border: 2px solid">Two</button>
1601
+ </monster-control-bar>
1602
+ </div>
1603
+ `;
1604
+
1605
+ const wrapper = document.getElementById("border-box-wrapper");
1606
+ const bar = document.getElementById("border-box-bar");
1607
+ const buttons = [
1608
+ document.getElementById("border-box-one"),
1609
+ document.getElementById("border-box-two"),
1610
+ ];
1611
+
1612
+ wrapper.style.boxSizing = "border-box";
1613
+ Object.defineProperty(wrapper, "clientWidth", {
1614
+ configurable: true,
1615
+ value: 130,
1616
+ });
1617
+
1618
+ for (const button of buttons) {
1619
+ Object.defineProperty(button, "offsetWidth", {
1620
+ configurable: true,
1621
+ value: 60,
1622
+ });
1623
+ Object.defineProperty(button, "offsetHeight", {
1624
+ configurable: true,
1625
+ value: 30,
1626
+ });
1627
+ button.getBoundingClientRect = () => ({
1628
+ width: 60,
1629
+ height: 30,
1630
+ top: 0,
1631
+ right: 60,
1632
+ bottom: 30,
1633
+ left: 0,
1634
+ x: 0,
1635
+ y: 0,
1636
+ toJSON: () => {},
1637
+ });
1638
+ }
1639
+
1640
+ const switchButton = bar.shadowRoot.querySelector(
1641
+ '[data-monster-role="switch"]',
1642
+ );
1643
+ Object.defineProperty(switchButton, "offsetWidth", {
1644
+ configurable: true,
1645
+ get: () =>
1646
+ switchButton.hasAttribute("hidden") ||
1647
+ switchButton.classList.contains("hidden")
1648
+ ? 0
1649
+ : 20,
1650
+ });
1651
+
1652
+ await flushFrames();
1653
+
1654
+ expect(switchButton.hasAttribute("hidden")).to.be.true;
1655
+ expect(buttons.every((button) => !button.hasAttribute("slot"))).to.be.true;
1656
+ } finally {
1657
+ window.requestAnimationFrame = originalRequestAnimationFrame;
1658
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
1659
+ }
1660
+ });
1661
+
1662
+ it("should recalculate overflow when a visible control width changes", async function () {
1663
+ const OriginalResizeObserver = window.ResizeObserver;
1664
+ const originalGlobalResizeObserver = globalThis.ResizeObserver;
1665
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
1666
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
1667
+
1668
+ class TrackingResizeObserver extends ResizeObserverMock {
1669
+ static instances = [];
1670
+
1671
+ constructor(callback) {
1672
+ super(callback);
1673
+ TrackingResizeObserver.instances.push(this);
1674
+ }
1675
+ }
1676
+
1677
+ const scheduledCallbacks = [];
1678
+ const flushFrames = async () => {
1679
+ while (scheduledCallbacks.length > 0) {
1680
+ scheduledCallbacks.shift()();
1681
+ await new Promise((resolve) => setTimeout(resolve, 0));
1682
+ }
1683
+ };
1684
+
1685
+ try {
1686
+ window.ResizeObserver = TrackingResizeObserver;
1687
+ globalThis.ResizeObserver = TrackingResizeObserver;
1688
+ window.requestAnimationFrame = (callback) => {
1689
+ scheduledCallbacks.push(callback);
1690
+ return scheduledCallbacks.length;
1691
+ };
1692
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
1693
+
1694
+ const mocks = document.getElementById("mocks");
1695
+ mocks.innerHTML = `
1696
+ <div id="item-resize-wrapper">
1697
+ <monster-control-bar id="item-resize-bar">
1698
+ <button id="item-resize-one">One</button>
1699
+ <button id="item-resize-two">Two</button>
1700
+ </monster-control-bar>
1701
+ </div>
1702
+ `;
1703
+
1704
+ const wrapper = document.getElementById("item-resize-wrapper");
1705
+ const bar = document.getElementById("item-resize-bar");
1706
+ const buttons = [
1707
+ document.getElementById("item-resize-one"),
1708
+ document.getElementById("item-resize-two"),
1709
+ ];
1710
+ const widths = [60, 60];
1711
+
1712
+ wrapper.style.boxSizing = "border-box";
1713
+ Object.defineProperty(wrapper, "clientWidth", {
1714
+ configurable: true,
1715
+ value: 160,
1716
+ });
1717
+
1718
+ buttons.forEach((button, index) => {
1719
+ Object.defineProperty(button, "offsetWidth", {
1720
+ configurable: true,
1721
+ get: () => widths[index],
1722
+ });
1723
+ Object.defineProperty(button, "offsetHeight", {
1724
+ configurable: true,
1725
+ value: 30,
1726
+ });
1727
+ button.getBoundingClientRect = () => ({
1728
+ width: widths[index],
1729
+ height: 30,
1730
+ top: 0,
1731
+ right: widths[index],
1732
+ bottom: 30,
1733
+ left: 0,
1734
+ x: 0,
1735
+ y: 0,
1736
+ toJSON: () => {},
1737
+ });
1738
+ });
1739
+
1740
+ const switchButton = bar.shadowRoot.querySelector(
1741
+ '[data-monster-role="switch"]',
1742
+ );
1743
+ Object.defineProperty(switchButton, "offsetWidth", {
1744
+ configurable: true,
1745
+ value: 20,
1746
+ });
1747
+
1748
+ await flushFrames();
1749
+ expect(buttons.every((button) => !button.hasAttribute("slot"))).to.be.true;
1750
+
1751
+ const resizeObserver = TrackingResizeObserver.instances.find((observer) =>
1752
+ observer.elements.includes(wrapper),
1753
+ );
1754
+ expect(resizeObserver).to.exist;
1755
+
1756
+ widths[0] = 110;
1757
+ resizeObserver.triggerResize([{ target: buttons[0] }]);
1758
+ expect(scheduledCallbacks.length).to.equal(1);
1759
+ await flushFrames();
1760
+
1761
+ expect(buttons[0].hasAttribute("slot")).to.be.false;
1762
+ expect(buttons[1].getAttribute("slot")).to.equal("popper");
1763
+ expect(switchButton.hasAttribute("hidden")).to.be.false;
1764
+ } finally {
1765
+ window.ResizeObserver = OriginalResizeObserver;
1766
+ globalThis.ResizeObserver = originalGlobalResizeObserver;
1767
+ window.requestAnimationFrame = originalRequestAnimationFrame;
1768
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
1769
+ }
1770
+ });
1771
+
1573
1772
  it("should move spacers with adjacent overflow controls", async function () {
1574
1773
  const OriginalResizeObserver = window.ResizeObserver;
1575
1774
  const originalGlobalResizeObserver = globalThis.ResizeObserver;
@@ -1787,7 +1986,7 @@ describe("ControlBar", function () {
1787
1986
  }
1788
1987
  });
1789
1988
 
1790
- it("should keep the previous overflow boundary while the switch remains visible", async function () {
1989
+ it("should restore the maximal fitting prefix regardless of resize direction", async function () {
1791
1990
  const OriginalResizeObserver = window.ResizeObserver;
1792
1991
  const originalGlobalResizeObserver = globalThis.ResizeObserver;
1793
1992
  const originalRequestAnimationFrame = window.requestAnimationFrame;
@@ -1890,13 +2089,22 @@ describe("ControlBar", function () {
1890
2089
  wrapperWidth = 190;
1891
2090
  resizeObserver.triggerResize([{ target: wrapper }]);
1892
2091
  await flushFrames();
2092
+ expect(switchButton.hasAttribute("hidden")).to.be.true;
2093
+ expect(document.getElementById("sticky-overflow-wide").hasAttribute("slot"))
2094
+ .to.be.false;
2095
+ expect(document.getElementById("sticky-overflow-small").hasAttribute("slot"))
2096
+ .to.be.false;
2097
+
2098
+ wrapperWidth = 90;
2099
+ resizeObserver.triggerResize([{ target: wrapper }]);
2100
+ await flushFrames();
1893
2101
  expect(switchButton.hasAttribute("hidden")).to.be.false;
1894
2102
  expect(document.getElementById("sticky-overflow-wide").getAttribute("slot"))
1895
2103
  .to.equal("popper");
1896
2104
  expect(document.getElementById("sticky-overflow-small").getAttribute("slot"))
1897
2105
  .to.equal("popper");
1898
2106
 
1899
- wrapperWidth = 220;
2107
+ wrapperWidth = 190;
1900
2108
  resizeObserver.triggerResize([{ target: wrapper }]);
1901
2109
  await flushFrames();
1902
2110
  expect(switchButton.hasAttribute("hidden")).to.be.true;
@@ -4,6 +4,7 @@ import * as chai from 'chai';
4
4
  import {internalSymbol} from "../../../source/constants.mjs";
5
5
  import {customElementUpdaterLinkSymbol} from "../../../source/dom/constants.mjs";
6
6
  import {getDocument} from "../../../source/dom/util.mjs";
7
+ import {Observer} from "../../../source/types/observer.mjs";
7
8
  import {ProxyObserver} from "../../../source/types/proxyobserver.mjs";
8
9
  import {chaiDom} from "../../util/chai-dom.mjs";
9
10
  import {initJSDOM} from "../../util/jsdom.mjs";
@@ -28,7 +29,7 @@ const updaterSymbolSymbol = Symbol.for(updaterSymbolKey);
28
29
 
29
30
  describe('DOM', function () {
30
31
 
31
- let CustomElement, registerCustomElement, TestComponent, document, TestComponent2, TestStateComponent, TestMutationComponent, HiddenStateClass, assignUpdaterToElement,
32
+ let CustomElement, registerCustomElement, updaterTransformerMethodsSymbol, TestComponent, document, TestComponent2, TestStateComponent, TestMutationComponent, HiddenStateClass, assignUpdaterToElement,
32
33
  addObjectWithUpdaterToElement;
33
34
 
34
35
  describe("assignUpdaterToElement", function () {
@@ -154,6 +155,7 @@ describe('DOM', function () {
154
155
  try {
155
156
  CustomElement = m['CustomElement'];
156
157
  registerCustomElement = m['registerCustomElement'];
158
+ updaterTransformerMethodsSymbol = m['updaterTransformerMethodsSymbol'];
157
159
  TestComponent = class extends CustomElement {
158
160
  static getTag() {
159
161
  return "monster-testclass"
@@ -510,6 +512,81 @@ describe('DOM', function () {
510
512
  }, 20);
511
513
  }, 10);
512
514
  });
515
+
516
+ it('should synchronize option arrays without unchanged proxy churn issue #517', async function () {
517
+ const htmlTAG = 'monster-testclass-array-sync-517';
518
+
519
+ if (!customElements.get(htmlTAG)) {
520
+ class ArraySyncComponent extends CustomElement {
521
+ static getTag() {
522
+ return htmlTAG;
523
+ }
524
+
525
+ get defaults() {
526
+ return Object.assign({}, super.defaults, {
527
+ items: Array.from({length: 100}, (_, index) => `item-${index}`),
528
+ unrelated: 0,
529
+ templates: {
530
+ main: '<span id="array-sync-value" data-monster-replace="path:items | call:joinItems"></span><span id="array-sync-value-second" data-monster-replace="path:items | call:joinItems"></span>',
531
+ },
532
+ });
533
+ }
534
+
535
+ [updaterTransformerMethodsSymbol]() {
536
+ return {
537
+ joinItems: (items) => items.join(','),
538
+ };
539
+ }
540
+ }
541
+
542
+ registerCustomElement(ArraySyncComponent);
543
+ }
544
+
545
+ const element = document.createElement(htmlTAG);
546
+ document.getElementById('test1').appendChild(element);
547
+ await new Promise((resolve) => setTimeout(resolve, 20));
548
+
549
+ const updaterGroups = element[customElementUpdaterLinkSymbol];
550
+ let linkedUpdater = null;
551
+ for (const group of updaterGroups) {
552
+ for (const updater of group) {
553
+ linkedUpdater = updater;
554
+ break;
555
+ }
556
+ if (linkedUpdater) break;
557
+ }
558
+ expect(linkedUpdater).to.exist;
559
+
560
+ let notificationCount = 0;
561
+ class CountingObserver extends Observer {
562
+ update(subject) {
563
+ notificationCount++;
564
+ return super.update(subject);
565
+ }
566
+ }
567
+ const observer = new CountingObserver(() => {});
568
+ linkedUpdater[internalSymbol].subject.attachObserver(observer);
569
+
570
+ try {
571
+ element.setOption('unrelated', 1);
572
+ await new Promise((resolve) => setTimeout(resolve, 30));
573
+ expect(notificationCount).to.be.at.most(3);
574
+
575
+ notificationCount = 0;
576
+ element.setOption('items.50', 'changed');
577
+ await new Promise((resolve) => setTimeout(resolve, 30));
578
+
579
+ expect(notificationCount).to.be.at.most(3);
580
+ expect(
581
+ element.shadowRoot.querySelector('#array-sync-value').textContent,
582
+ ).to.contain('changed');
583
+ expect(
584
+ element.shadowRoot.querySelector('#array-sync-value-second').textContent,
585
+ ).to.contain('changed');
586
+ } finally {
587
+ linkedUpdater[internalSymbol].subject.detachObserver(observer);
588
+ }
589
+ });
513
590
  })
514
591
 
515
592
  describe('setOptions()', function () {
@@ -860,6 +937,64 @@ describe('DOM', function () {
860
937
  })
861
938
 
862
939
  describe('mutation observer lifecycle', function () {
940
+ it('should coalesce child mutations without a refresh loop issue #517', async function () {
941
+ const htmlTAG = 'monster-testclass-mutation-loop-517';
942
+
943
+ if (!customElements.get(htmlTAG)) {
944
+ class MutationLoopComponent extends CustomElement {
945
+ static getTag() {
946
+ return htmlTAG;
947
+ }
948
+
949
+ get defaults() {
950
+ return Object.assign({}, super.defaults, {
951
+ shadowMode: false,
952
+ features: {
953
+ mutationObserver: true,
954
+ },
955
+ templates: {
956
+ main: '<span data-monster-replace="static:stable"></span>',
957
+ },
958
+ });
959
+ }
960
+ }
961
+
962
+ registerCustomElement(MutationLoopComponent);
963
+ }
964
+
965
+ const element = document.createElement(htmlTAG);
966
+ document.getElementById('test1').appendChild(element);
967
+ await new Promise((resolve) => setTimeout(resolve, 120));
968
+
969
+ const updaterGroups = element[customElementUpdaterLinkSymbol];
970
+ let linkedUpdater = null;
971
+ for (const group of updaterGroups) {
972
+ for (const updater of group) {
973
+ linkedUpdater = updater;
974
+ break;
975
+ }
976
+ if (linkedUpdater) break;
977
+ }
978
+ expect(linkedUpdater).to.exist;
979
+
980
+ let runCount = 0;
981
+ const originalRun = linkedUpdater.run.bind(linkedUpdater);
982
+ linkedUpdater.run = function () {
983
+ runCount++;
984
+ return originalRun();
985
+ };
986
+
987
+ element.appendChild(document.createElement('i'));
988
+ element.appendChild(document.createElement('b'));
989
+ element.appendChild(document.createElement('em'));
990
+ await new Promise((resolve) => setTimeout(resolve, 160));
991
+
992
+ expect(runCount).to.equal(1);
993
+ expect(element.querySelector('[data-monster-replace]').textContent).to.equal(
994
+ 'stable',
995
+ );
996
+ });
997
+
863
998
  it('should not rerun linked updaters after disconnect', function (done) {
864
999
  let mocks = document.getElementById('mocks');
865
1000
  mocks.innerHTML = `<monster-testclass-mutation id="mutation-case"></monster-testclass-mutation>`;
@@ -170,6 +170,7 @@ let htmlPatchVsReplace = `
170
170
 
171
171
  describe("DOM", function () {
172
172
  let Updater = null;
173
+ let addObjectWithUpdaterToElement = null;
173
174
 
174
175
  before(function (done) {
175
176
  const options = {};
@@ -177,6 +178,7 @@ describe("DOM", function () {
177
178
  import("../../../source/dom/updater.mjs")
178
179
  .then((m) => {
179
180
  Updater = m.Updater;
181
+ addObjectWithUpdaterToElement = m.addObjectWithUpdaterToElement;
180
182
 
181
183
  if (!customElements.get("monster-test-property")) {
182
184
  class MonsterTestProperty extends HTMLElement {
@@ -1907,6 +1909,50 @@ describe("DOM", function () {
1907
1909
  });
1908
1910
 
1909
1911
  describe("Updater reactive updates", function () {
1912
+ it("should enable configured batching before the initial run issue #517", async function () {
1913
+ const originalRun = Updater.prototype.run;
1914
+ const originalSetBatchUpdates = Updater.prototype.setBatchUpdates;
1915
+ const batchedUpdaters = new WeakSet();
1916
+ const batchStateAtRun = [];
1917
+ const symbol = Symbol("initial-batch-test");
1918
+ const element = document.createElement("div");
1919
+ element.setAttribute("data-monster-replace", "path:value");
1920
+ document.getElementById("mocks").appendChild(element);
1921
+
1922
+ Updater.prototype.setBatchUpdates = function (enabled) {
1923
+ if (enabled === true) {
1924
+ batchedUpdaters.add(this);
1925
+ }
1926
+ return originalSetBatchUpdates.call(this, enabled);
1927
+ };
1928
+ Updater.prototype.run = function () {
1929
+ batchStateAtRun.push(batchedUpdaters.has(this));
1930
+ return originalRun.call(this);
1931
+ };
1932
+
1933
+ let updaters = [];
1934
+ try {
1935
+ updaters = await Promise.all(
1936
+ addObjectWithUpdaterToElement.call(
1937
+ element,
1938
+ element,
1939
+ symbol,
1940
+ { value: "initial" },
1941
+ { batchUpdates: true },
1942
+ ),
1943
+ );
1944
+
1945
+ expect(batchStateAtRun).to.deep.equal([true]);
1946
+ expect(element.textContent).to.equal("initial");
1947
+ } finally {
1948
+ Updater.prototype.run = originalRun;
1949
+ Updater.prototype.setBatchUpdates = originalSetBatchUpdates;
1950
+ for (const updater of updaters) {
1951
+ updater.dispose();
1952
+ }
1953
+ }
1954
+ });
1955
+
1910
1956
  [false, true].forEach((batchUpdates) => {
1911
1957
  it(`should preserve microtask changes with batchUpdates=${batchUpdates} issue #496`, async function () {
1912
1958
  let mocks = document.getElementById("mocks");
@@ -1938,6 +1984,134 @@ describe("DOM", function () {
1938
1984
  });
1939
1985
  });
1940
1986
 
1987
+ [false, true].forEach((batchUpdates) => {
1988
+ it(`should render each binding once per snapshot with batchUpdates=${batchUpdates} issue #517`, async function () {
1989
+ const mocks = document.getElementById("mocks");
1990
+ const staticBindings = Array.from(
1991
+ { length: 20 },
1992
+ (_, index) =>
1993
+ `<span data-index="${index}" data-monster-replace="static:marker | call:trackStatic"></span>`,
1994
+ ).join("");
1995
+ mocks.innerHTML = `
1996
+ <span id="snapshot-items" data-monster-replace="path:items | call:lastItem"></span>
1997
+ ${staticBindings}
1998
+ <span id="snapshot-attribute" data-monster-attributes="title path:items | call:trackAttribute"></span>
1999
+ <input id="snapshot-property" data-monster-properties="value path:items | call:trackProperty">
2000
+ `;
2001
+
2002
+ const updater = new Updater(mocks, { items: [] });
2003
+ updater.setBatchUpdates(batchUpdates);
2004
+ let staticRenderCount = 0;
2005
+ let attributeRenderCount = 0;
2006
+ let propertyRenderCount = 0;
2007
+ updater.setCallback("trackStatic", (value) => {
2008
+ staticRenderCount++;
2009
+ return value;
2010
+ });
2011
+ updater.setCallback("lastItem", (value) => value.at(-1) ?? "");
2012
+ updater.setCallback("trackAttribute", (value) => {
2013
+ attributeRenderCount++;
2014
+ return value;
2015
+ });
2016
+ updater.setCallback("trackProperty", (value) => {
2017
+ propertyRenderCount++;
2018
+ return value;
2019
+ });
2020
+
2021
+ let innerHTMLDescriptor = null;
2022
+ let replacementWrites = 0;
2023
+ try {
2024
+ await updater.run();
2025
+ staticRenderCount = 0;
2026
+ attributeRenderCount = 0;
2027
+ propertyRenderCount = 0;
2028
+
2029
+ innerHTMLDescriptor = Object.getOwnPropertyDescriptor(
2030
+ Element.prototype,
2031
+ "innerHTML",
2032
+ );
2033
+ Object.defineProperty(Element.prototype, "innerHTML", {
2034
+ configurable: innerHTMLDescriptor.configurable,
2035
+ enumerable: innerHTMLDescriptor.enumerable,
2036
+ get: innerHTMLDescriptor.get,
2037
+ set(value) {
2038
+ if (
2039
+ mocks.contains(this) &&
2040
+ this.hasAttribute("data-monster-replace")
2041
+ ) {
2042
+ replacementWrites++;
2043
+ }
2044
+ return innerHTMLDescriptor.set.call(this, value);
2045
+ },
2046
+ });
2047
+
2048
+ const items = updater.getSubject().items;
2049
+ for (let index = 0; index < 500; index++) {
2050
+ items.push(`new-${index}`);
2051
+ }
2052
+
2053
+ await new Promise((resolve) => setTimeout(resolve, 30));
2054
+
2055
+ expect(document.getElementById("snapshot-items").textContent).to.equal(
2056
+ "new-499",
2057
+ );
2058
+ expect(staticRenderCount).to.equal(20);
2059
+ expect(attributeRenderCount).to.equal(1);
2060
+ expect(propertyRenderCount).to.equal(1);
2061
+ expect(replacementWrites).to.equal(1);
2062
+ } finally {
2063
+ updater.dispose();
2064
+ if (innerHTMLDescriptor) {
2065
+ Object.defineProperty(
2066
+ Element.prototype,
2067
+ "innerHTML",
2068
+ innerHTMLDescriptor,
2069
+ );
2070
+ }
2071
+ }
2072
+ });
2073
+ });
2074
+
2075
+ it("should not rewrite unchanged replacement content issue #517", async function () {
2076
+ const mocks = document.getElementById("mocks");
2077
+ mocks.innerHTML = `
2078
+ <span id="unchanged-replacement" data-monster-replace="static:same"></span>
2079
+ `;
2080
+
2081
+ const element = document.getElementById("unchanged-replacement");
2082
+ const descriptor = Object.getOwnPropertyDescriptor(
2083
+ Element.prototype,
2084
+ "innerHTML",
2085
+ );
2086
+ let writes = 0;
2087
+
2088
+ Object.defineProperty(Element.prototype, "innerHTML", {
2089
+ configurable: descriptor.configurable,
2090
+ enumerable: descriptor.enumerable,
2091
+ get: descriptor.get,
2092
+ set(value) {
2093
+ if (this === element) {
2094
+ writes++;
2095
+ }
2096
+ return descriptor.set.call(this, value);
2097
+ },
2098
+ });
2099
+
2100
+ const updater = new Updater(mocks, { trigger: 0 });
2101
+ try {
2102
+ await updater.run();
2103
+ writes = 0;
2104
+ updater.getSubject().trigger = 1;
2105
+ await new Promise((resolve) => setTimeout(resolve, 20));
2106
+
2107
+ expect(element.textContent).to.equal("same");
2108
+ expect(writes).to.equal(0);
2109
+ } finally {
2110
+ updater.dispose();
2111
+ Object.defineProperty(Element.prototype, "innerHTML", descriptor);
2112
+ }
2113
+ });
2114
+
1941
2115
  it("should update the DOM when the subject changes", function (done) {
1942
2116
  let mocks = document.getElementById("mocks");
1943
2117
  mocks.innerHTML = html1;