@schukai/monster 4.150.6 → 4.150.7
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.
- package/package.json +1 -1
- package/source/dom/updater.mjs +65 -40
- package/test/cases/dom/updater.mjs +246 -0
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.150.
|
|
1
|
+
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.150.7"}
|
package/source/dom/updater.mjs
CHANGED
|
@@ -854,7 +854,10 @@ function removeElement(_change, renderContext) {
|
|
|
854
854
|
*/
|
|
855
855
|
function insertElement(change, renderContext) {
|
|
856
856
|
const subject = this[internalSymbol].subject.getRealSubject();
|
|
857
|
-
|
|
857
|
+
const changePath = clone(change?.["path"]);
|
|
858
|
+
if (!isArray(changePath)) {
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
858
861
|
const mem = new WeakSet();
|
|
859
862
|
let wd = 0;
|
|
860
863
|
let changed = false;
|
|
@@ -873,36 +876,41 @@ function insertElement(change, renderContext) {
|
|
|
873
876
|
let found = false;
|
|
874
877
|
wd++;
|
|
875
878
|
|
|
876
|
-
const p = clone(
|
|
877
|
-
if (!isArray(p)) return changed;
|
|
879
|
+
const p = clone(changePath);
|
|
878
880
|
|
|
879
881
|
while (p.length > 0) {
|
|
880
882
|
const current = p.join(".");
|
|
881
883
|
|
|
882
884
|
let iterator = new Set();
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
885
|
+
const candidates = getBindingCandidates(
|
|
886
|
+
container,
|
|
887
|
+
ATTRIBUTE_UPDATER_INSERT,
|
|
888
|
+
renderContext?.candidates.insert ?? new WeakMap(),
|
|
889
|
+
);
|
|
890
|
+
for (const candidate of candidates) {
|
|
891
|
+
const definition = candidate.getAttribute(ATTRIBUTE_UPDATER_INSERT);
|
|
892
|
+
if (definition?.includes(`path:${current}`)) {
|
|
893
|
+
iterator.add(candidate);
|
|
894
|
+
}
|
|
893
895
|
}
|
|
894
896
|
|
|
895
897
|
for (const [, containerElement] of iterator.entries()) {
|
|
896
|
-
if (mem.has(containerElement)) continue;
|
|
897
|
-
mem.add(containerElement);
|
|
898
|
-
|
|
899
|
-
found = true;
|
|
900
|
-
|
|
901
898
|
const attributes = containerElement.getAttribute(
|
|
902
899
|
ATTRIBUTE_UPDATER_INSERT,
|
|
903
900
|
);
|
|
904
901
|
if (attributes === null) continue;
|
|
905
902
|
|
|
903
|
+
if (
|
|
904
|
+
mem.has(containerElement) ||
|
|
905
|
+
renderContext?.insert.get(containerElement) === attributes
|
|
906
|
+
) {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
mem.add(containerElement);
|
|
910
|
+
renderContext?.insert.set(containerElement, attributes);
|
|
911
|
+
|
|
912
|
+
found = true;
|
|
913
|
+
|
|
906
914
|
const def = trimSpaces(attributes);
|
|
907
915
|
const i = def.indexOf(" ");
|
|
908
916
|
const key = trimSpaces(def.substr(0, i));
|
|
@@ -929,40 +937,53 @@ function insertElement(change, renderContext) {
|
|
|
929
937
|
|
|
930
938
|
const dataPath = cmd.split(":").pop();
|
|
931
939
|
|
|
932
|
-
let insertPoint;
|
|
933
|
-
if (containerElement.hasChildNodes()) {
|
|
934
|
-
insertPoint = containerElement.lastChild;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
940
|
if (!isIterable(value)) {
|
|
938
941
|
throw new Error("the value is not iterable");
|
|
939
942
|
}
|
|
940
943
|
|
|
941
944
|
const available = new Set();
|
|
945
|
+
const existing = new Map();
|
|
946
|
+
const managedNodes = [];
|
|
947
|
+
for (const node of containerElement.querySelectorAll(
|
|
948
|
+
`[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}]`,
|
|
949
|
+
)) {
|
|
950
|
+
const ref = node.getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE);
|
|
951
|
+
if (!existing.has(ref)) {
|
|
952
|
+
existing.set(ref, node);
|
|
953
|
+
}
|
|
954
|
+
if (ref.includes(refPrefix)) {
|
|
955
|
+
managedNodes.push(node);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
let template;
|
|
960
|
+
const pending = containerElement.ownerDocument.createDocumentFragment();
|
|
961
|
+
let structureChanged = false;
|
|
942
962
|
|
|
943
963
|
for (const [i] of Object.entries(value)) {
|
|
944
964
|
const ref = refPrefix + i;
|
|
945
965
|
const currentPath = `${dataPath}.${i}`;
|
|
946
966
|
|
|
947
967
|
available.add(ref);
|
|
948
|
-
const refElement =
|
|
949
|
-
`[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}="${ref}"]`,
|
|
950
|
-
);
|
|
968
|
+
const refElement = existing.get(ref);
|
|
951
969
|
|
|
952
970
|
if (refElement instanceof HTMLElement) {
|
|
953
|
-
insertPoint = refElement;
|
|
954
971
|
continue;
|
|
955
972
|
}
|
|
956
973
|
|
|
957
|
-
|
|
974
|
+
template ??= findDocumentTemplate(key, containerElement);
|
|
975
|
+
pending.appendChild(
|
|
976
|
+
createNewDocumentFragment(template, key, ref, currentPath),
|
|
977
|
+
);
|
|
958
978
|
changed = true;
|
|
959
979
|
}
|
|
960
980
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
981
|
+
if (pending.hasChildNodes()) {
|
|
982
|
+
containerElement.appendChild(pending);
|
|
983
|
+
structureChanged = true;
|
|
984
|
+
}
|
|
964
985
|
|
|
965
|
-
for (const
|
|
986
|
+
for (const node of managedNodes) {
|
|
966
987
|
if (
|
|
967
988
|
!available.has(
|
|
968
989
|
node.getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE),
|
|
@@ -972,11 +993,16 @@ function insertElement(change, renderContext) {
|
|
|
972
993
|
teardownManagedSubtree(node);
|
|
973
994
|
containerElement.removeChild(node);
|
|
974
995
|
changed = true;
|
|
996
|
+
structureChanged = true;
|
|
975
997
|
} catch (e) {
|
|
976
998
|
addErrorAttribute(containerElement, e);
|
|
977
999
|
}
|
|
978
1000
|
}
|
|
979
1001
|
}
|
|
1002
|
+
|
|
1003
|
+
if (structureChanged && renderContext) {
|
|
1004
|
+
renderContext.candidates.insert = new WeakMap();
|
|
1005
|
+
}
|
|
980
1006
|
}
|
|
981
1007
|
|
|
982
1008
|
p.pop();
|
|
@@ -987,7 +1013,6 @@ function insertElement(change, renderContext) {
|
|
|
987
1013
|
throw new Error("the maximum depth for the recursion is reached.");
|
|
988
1014
|
}
|
|
989
1015
|
}
|
|
990
|
-
|
|
991
1016
|
return changed;
|
|
992
1017
|
}
|
|
993
1018
|
|
|
@@ -996,24 +1021,21 @@ function insertElement(change, renderContext) {
|
|
|
996
1021
|
* @private
|
|
997
1022
|
* @license AGPLv3
|
|
998
1023
|
* @since 1.8.0
|
|
999
|
-
* @param {
|
|
1024
|
+
* @param {Template} template
|
|
1000
1025
|
* @param {string} key
|
|
1001
1026
|
* @param {string} ref
|
|
1002
1027
|
* @param {string} path
|
|
1003
1028
|
* @throws {Error} no template was found with the specified key.
|
|
1004
1029
|
*/
|
|
1005
|
-
function
|
|
1006
|
-
const template = findDocumentTemplate(key, container);
|
|
1007
|
-
|
|
1030
|
+
function createNewDocumentFragment(template, key, ref, path) {
|
|
1008
1031
|
const nodes = template.createDocumentFragment();
|
|
1009
|
-
for (const
|
|
1032
|
+
for (const node of nodes.childNodes) {
|
|
1010
1033
|
if (node instanceof HTMLElement) {
|
|
1011
1034
|
applyRecursive(node, key, path);
|
|
1012
1035
|
node.setAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE, ref);
|
|
1013
1036
|
}
|
|
1014
|
-
|
|
1015
|
-
container.appendChild(node);
|
|
1016
1037
|
}
|
|
1038
|
+
return nodes;
|
|
1017
1039
|
}
|
|
1018
1040
|
|
|
1019
1041
|
/**
|
|
@@ -1113,9 +1135,11 @@ function createRenderContext() {
|
|
|
1113
1135
|
patch: new WeakSet(),
|
|
1114
1136
|
attributes: new WeakSet(),
|
|
1115
1137
|
properties: new WeakSet(),
|
|
1138
|
+
insert: new WeakMap(),
|
|
1116
1139
|
removeProcessed: false,
|
|
1117
1140
|
insertBindingsPresent: undefined,
|
|
1118
1141
|
candidates: {
|
|
1142
|
+
insert: new WeakMap(),
|
|
1119
1143
|
replace: new WeakMap(),
|
|
1120
1144
|
patch: new WeakMap(),
|
|
1121
1145
|
attributes: new WeakMap(),
|
|
@@ -1134,6 +1158,7 @@ function createRenderContext() {
|
|
|
1134
1158
|
function invalidateRenderCandidates(renderContext) {
|
|
1135
1159
|
renderContext.removeProcessed = false;
|
|
1136
1160
|
renderContext.insertBindingsPresent = undefined;
|
|
1161
|
+
renderContext.candidates.insert = new WeakMap();
|
|
1137
1162
|
renderContext.candidates.replace = new WeakMap();
|
|
1138
1163
|
renderContext.candidates.patch = new WeakMap();
|
|
1139
1164
|
renderContext.candidates.attributes = new WeakMap();
|
|
@@ -725,6 +725,14 @@ describe("DOM", function () {
|
|
|
725
725
|
|
|
726
726
|
describe("Updater()", function () {
|
|
727
727
|
describe("Repeat", function () {
|
|
728
|
+
const waitUntil = async (predicate, message, timeout = 5000) => {
|
|
729
|
+
const deadline = Date.now() + timeout;
|
|
730
|
+
while (!predicate() && Date.now() < deadline) {
|
|
731
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
732
|
+
}
|
|
733
|
+
expect(predicate(), message).to.equal(true);
|
|
734
|
+
};
|
|
735
|
+
|
|
728
736
|
it("should build 6 li elements from an array", function (done) {
|
|
729
737
|
let element = document.getElementById("test1");
|
|
730
738
|
const testData = {
|
|
@@ -769,6 +777,244 @@ describe("DOM", function () {
|
|
|
769
777
|
})
|
|
770
778
|
.catch(done);
|
|
771
779
|
});
|
|
780
|
+
|
|
781
|
+
[false, true].forEach((batchUpdates) => {
|
|
782
|
+
it(`should bulk insert and reuse indexed entries with batchUpdates=${batchUpdates} issue #548`, async function () {
|
|
783
|
+
this.timeout(5000);
|
|
784
|
+
const mocks = document.getElementById("mocks");
|
|
785
|
+
mocks.innerHTML = `
|
|
786
|
+
<template id="bulk-row">
|
|
787
|
+
<li data-monster-replace="path:bulk-row.label"></li>
|
|
788
|
+
</template>
|
|
789
|
+
<ul id="bulk-rows" data-monster-insert="bulk-row path:items"></ul>
|
|
790
|
+
`;
|
|
791
|
+
|
|
792
|
+
const rows = document.getElementById("bulk-rows");
|
|
793
|
+
const updater = new Updater(rows, { items: [] });
|
|
794
|
+
updater.setBatchUpdates(batchUpdates);
|
|
795
|
+
|
|
796
|
+
const originalAppendChild = rows.appendChild;
|
|
797
|
+
const originalQuerySelector = rows.querySelector;
|
|
798
|
+
let appendCalls = 0;
|
|
799
|
+
let appendedFragments = 0;
|
|
800
|
+
let exactReferenceLookups = 0;
|
|
801
|
+
|
|
802
|
+
rows.appendChild = function (node) {
|
|
803
|
+
appendCalls++;
|
|
804
|
+
if (node instanceof DocumentFragment) {
|
|
805
|
+
appendedFragments++;
|
|
806
|
+
}
|
|
807
|
+
return originalAppendChild.call(this, node);
|
|
808
|
+
};
|
|
809
|
+
rows.querySelector = function (selector) {
|
|
810
|
+
if (selector.startsWith('[data-monster-insert-reference="')) {
|
|
811
|
+
exactReferenceLookups++;
|
|
812
|
+
}
|
|
813
|
+
return originalQuerySelector.call(this, selector);
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
const waitForLength = (length) =>
|
|
817
|
+
waitUntil(
|
|
818
|
+
() => rows.children.length === length,
|
|
819
|
+
`expected ${length} bulk rows`,
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
try {
|
|
823
|
+
await updater.run();
|
|
824
|
+
const items = updater.getSubject().items;
|
|
825
|
+
for (let index = 0; index < 872; index++) {
|
|
826
|
+
items.push({ label: `Row ${index}` });
|
|
827
|
+
}
|
|
828
|
+
await waitForLength(872);
|
|
829
|
+
|
|
830
|
+
expect(appendCalls).to.equal(1);
|
|
831
|
+
expect(appendedFragments).to.equal(1);
|
|
832
|
+
expect(exactReferenceLookups).to.equal(0);
|
|
833
|
+
expect(rows.firstElementChild.textContent).to.equal("Row 0");
|
|
834
|
+
expect(rows.lastElementChild.textContent).to.equal("Row 871");
|
|
835
|
+
|
|
836
|
+
const firstRow = rows.firstElementChild;
|
|
837
|
+
items.splice(400, 0, { label: "Inserted middle" });
|
|
838
|
+
await waitForLength(873);
|
|
839
|
+
expect(rows.children[400].textContent).to.equal("Inserted middle");
|
|
840
|
+
expect(rows.lastElementChild.textContent).to.equal("Row 871");
|
|
841
|
+
|
|
842
|
+
items.splice(400, 1);
|
|
843
|
+
await waitForLength(872);
|
|
844
|
+
expect(rows.children[400].textContent).to.equal("Row 400");
|
|
845
|
+
|
|
846
|
+
items[0].label = "Updated row";
|
|
847
|
+
items.splice(870, 2);
|
|
848
|
+
await waitForLength(870);
|
|
849
|
+
|
|
850
|
+
expect(rows.firstElementChild).to.equal(firstRow);
|
|
851
|
+
expect(rows.firstElementChild.textContent).to.equal("Updated row");
|
|
852
|
+
expect(rows.lastElementChild.textContent).to.equal("Row 869");
|
|
853
|
+
expect(appendCalls).to.equal(2);
|
|
854
|
+
expect(appendedFragments).to.equal(2);
|
|
855
|
+
} finally {
|
|
856
|
+
updater.dispose();
|
|
857
|
+
rows.appendChild = originalAppendChild;
|
|
858
|
+
rows.querySelector = originalQuerySelector;
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
it("should process two insert containers with the same data path issue #548", async function () {
|
|
864
|
+
const mocks = document.getElementById("mocks");
|
|
865
|
+
mocks.innerHTML = `
|
|
866
|
+
<template id="first-row"><li data-monster-replace="path:first-row"></li></template>
|
|
867
|
+
<template id="second-row"><li data-monster-replace="path:second-row"></li></template>
|
|
868
|
+
<div id="bulk-groups">
|
|
869
|
+
<ul id="first-rows" data-monster-insert="first-row path:items"></ul>
|
|
870
|
+
<ul id="second-rows" data-monster-insert="second-row path:items"></ul>
|
|
871
|
+
</div>
|
|
872
|
+
`;
|
|
873
|
+
|
|
874
|
+
const updater = new Updater(document.getElementById("bulk-groups"), {
|
|
875
|
+
items: [],
|
|
876
|
+
});
|
|
877
|
+
updater.setBatchUpdates(true);
|
|
878
|
+
|
|
879
|
+
try {
|
|
880
|
+
await updater.run();
|
|
881
|
+
updater.getSubject().items.push("Shared");
|
|
882
|
+
await waitUntil(
|
|
883
|
+
() =>
|
|
884
|
+
document.getElementById("first-rows").textContent === "Shared" &&
|
|
885
|
+
document.getElementById("second-rows").textContent === "Shared",
|
|
886
|
+
"expected both insert containers to render",
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
expect(document.getElementById("first-rows").textContent).to.equal(
|
|
890
|
+
"Shared",
|
|
891
|
+
);
|
|
892
|
+
expect(document.getElementById("second-rows").textContent).to.equal(
|
|
893
|
+
"Shared",
|
|
894
|
+
);
|
|
895
|
+
} finally {
|
|
896
|
+
updater.dispose();
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
it("should discover an insert binding created by replace in non-batch mode issue #548", async function () {
|
|
901
|
+
const mocks = document.getElementById("mocks");
|
|
902
|
+
mocks.innerHTML = `
|
|
903
|
+
<template id="dynamic-row">
|
|
904
|
+
<article data-monster-replace="path:dynamic-row.html"></article>
|
|
905
|
+
</template>
|
|
906
|
+
<template id="dynamic-child">
|
|
907
|
+
<span data-monster-replace="path:dynamic-child.label"></span>
|
|
908
|
+
</template>
|
|
909
|
+
<div id="dynamic-root" data-monster-insert="dynamic-row path:items"></div>
|
|
910
|
+
`;
|
|
911
|
+
|
|
912
|
+
const updater = new Updater(document.getElementById("dynamic-root"), {
|
|
913
|
+
items: [{ html: "", children: [] }],
|
|
914
|
+
});
|
|
915
|
+
updater.setBatchUpdates(false);
|
|
916
|
+
|
|
917
|
+
try {
|
|
918
|
+
await updater.run();
|
|
919
|
+
const item = updater.getSubject().items[0];
|
|
920
|
+
item.html =
|
|
921
|
+
'<div id="dynamic-children" data-monster-insert="dynamic-child path:items.0.children"></div>';
|
|
922
|
+
item.children.push({ label: "Child" });
|
|
923
|
+
|
|
924
|
+
await waitUntil(
|
|
925
|
+
() =>
|
|
926
|
+
document.getElementById("dynamic-children")?.textContent.trim() ===
|
|
927
|
+
"Child",
|
|
928
|
+
"expected the dynamically created insert binding to render",
|
|
929
|
+
);
|
|
930
|
+
} finally {
|
|
931
|
+
updater.dispose();
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
it("should process a changed insert definition in the same snapshot issue #548", async function () {
|
|
936
|
+
this.timeout(6000);
|
|
937
|
+
const mocks = document.getElementById("mocks");
|
|
938
|
+
mocks.innerHTML = `
|
|
939
|
+
<template id="old-row">
|
|
940
|
+
<span data-monster-replace="path:old-row"></span>
|
|
941
|
+
</template>
|
|
942
|
+
<template id="new-row">
|
|
943
|
+
<span data-monster-replace="path:new-row"></span>
|
|
944
|
+
</template>
|
|
945
|
+
<div id="rebound-insert"
|
|
946
|
+
data-monster-insert="old-row path:oldItems"
|
|
947
|
+
data-monster-attributes="data-monster-insert path:insertDefinition"></div>
|
|
948
|
+
`;
|
|
949
|
+
|
|
950
|
+
const updater = new Updater(mocks, {
|
|
951
|
+
oldItems: [],
|
|
952
|
+
insertDefinition: "old-row path:oldItems",
|
|
953
|
+
newItems: [],
|
|
954
|
+
});
|
|
955
|
+
updater.setBatchUpdates(false);
|
|
956
|
+
|
|
957
|
+
try {
|
|
958
|
+
await updater.run();
|
|
959
|
+
const container = document.getElementById("rebound-insert");
|
|
960
|
+
const subject = updater.getSubject();
|
|
961
|
+
subject.oldItems.push("Old");
|
|
962
|
+
subject.insertDefinition = "new-row path:newItems";
|
|
963
|
+
subject.newItems.push("New");
|
|
964
|
+
|
|
965
|
+
await waitUntil(
|
|
966
|
+
() =>
|
|
967
|
+
Array.from(container.children, (node) => node.textContent).join("") ===
|
|
968
|
+
"OldNew",
|
|
969
|
+
"expected the changed insert definition to render",
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
expect(container.getAttribute("data-monster-insert")).to.equal(
|
|
973
|
+
"new-row path:newItems",
|
|
974
|
+
);
|
|
975
|
+
expect(container.children.length).to.equal(2);
|
|
976
|
+
} finally {
|
|
977
|
+
updater.dispose();
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
it("should preserve multi-root template entries issue #548", async function () {
|
|
982
|
+
const mocks = document.getElementById("mocks");
|
|
983
|
+
mocks.innerHTML = `
|
|
984
|
+
<template id="multi-row">
|
|
985
|
+
<strong data-monster-replace="path:multi-row.label"></strong>
|
|
986
|
+
<em data-monster-replace="path:multi-row.label"></em>
|
|
987
|
+
</template>
|
|
988
|
+
<div id="multi-rows" data-monster-insert="multi-row path:items"></div>
|
|
989
|
+
`;
|
|
990
|
+
|
|
991
|
+
const rows = document.getElementById("multi-rows");
|
|
992
|
+
const updater = new Updater(rows, {
|
|
993
|
+
items: [{ label: "First" }, { label: "Second" }],
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
try {
|
|
997
|
+
await updater.run();
|
|
998
|
+
expect(Array.from(rows.children, (node) => node.textContent)).to.deep.equal([
|
|
999
|
+
"First",
|
|
1000
|
+
"First",
|
|
1001
|
+
"Second",
|
|
1002
|
+
"Second",
|
|
1003
|
+
]);
|
|
1004
|
+
|
|
1005
|
+
updater.getSubject().items.splice(0, 1);
|
|
1006
|
+
await waitUntil(
|
|
1007
|
+
() => rows.children.length === 2,
|
|
1008
|
+
"expected both top-level nodes of the removed entry to be removed",
|
|
1009
|
+
);
|
|
1010
|
+
expect(Array.from(rows.children, (node) => node.textContent)).to.deep.equal([
|
|
1011
|
+
"Second",
|
|
1012
|
+
"Second",
|
|
1013
|
+
]);
|
|
1014
|
+
} finally {
|
|
1015
|
+
updater.dispose();
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
772
1018
|
});
|
|
773
1019
|
});
|
|
774
1020
|
|