From 80eaa701d46dda603c2ed2b09d8062786cb963a8 Mon Sep 17 00:00:00 2001 From: Megaman Date: Sat, 26 Oct 2024 13:51:07 +0800 Subject: [PATCH 1/7] fix: optimize text node handling for incremental updates --- src/morphdom.js | 33 +++++--- test/browser/test.js | 78 +++++++++++++++++++ .../autotest/incremental-text/from.html | 5 ++ .../autotest/incremental-text/to.html | 5 ++ 4 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/autotest/incremental-text/from.html create mode 100644 test/fixtures/autotest/incremental-text/to.html diff --git a/src/morphdom.js b/src/morphdom.js index f978372..3d9ade8 100644 --- a/src/morphdom.js +++ b/src/morphdom.js @@ -236,6 +236,28 @@ export default function morphdomFactory(morphAttrs) { } } + function morphText(morphedNode, toNode) { + var morphedText = morphedNode.nodeValue; + var toText = toNode.nodeValue; + + if (morphedText === toText) { + return; + } + + // Handle incremental update case + if (toText.startsWith(morphedText)) { + var appendedText = toText.substring(morphedText.length); + morphedNode.after(appendedText); + morphedNode.parentNode.normalize(); + return; + } + + // Simply update nodeValue on the original node to + // change the text value + morphedNode.nodeValue = toText; + return; + } + function morphChildren(fromEl, toEl) { var skipFrom = skipFromChildren(fromEl, toEl); var curToNodeChild = toEl.firstChild; @@ -336,12 +358,7 @@ export default function morphdomFactory(morphAttrs) { } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) { // Both nodes being compared are Text or Comment nodes isCompatible = true; - // Simply update nodeValue on the original node to - // change the text value - if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) { - curFromNodeChild.nodeValue = curToNodeChild.nodeValue; - } - + morphText(curFromNodeChild, curToNodeChild); } } @@ -426,9 +443,7 @@ export default function morphdomFactory(morphAttrs) { } } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node if (toNodeType === morphedNodeType) { - if (morphedNode.nodeValue !== toNode.nodeValue) { - morphedNode.nodeValue = toNode.nodeValue; - } + morphText(morphedNode, toNode); return morphedNode; } else { diff --git a/test/browser/test.js b/test/browser/test.js index 146d4be..a4fcf4e 100644 --- a/test/browser/test.js +++ b/test/browser/test.js @@ -1657,6 +1657,84 @@ describe('morphdom' , function() { expect(noUpdateParentBefore.isSameNode(noUpdateBefore.parentNode)).to.equal(false); }); + it('should preserve text selection during incremental updates', () => { + // Initial setup + var fromEl = document.createElement('div'); + var initialText = 'Hello world'; + fromEl.textContent = initialText; + document.body.appendChild(fromEl); + + // Create and set selection + var selection = window.getSelection(); + var range = document.createRange(); + range.setStart(fromEl.firstChild, 0); + range.setEnd(fromEl.firstChild, 5); // Select "Hello" + selection.removeAllRanges(); + selection.addRange(range); + + // Create target node with incremental update + var toEl = document.createElement('div'); + toEl.textContent = 'Hello world, how are you?'; + + // Apply morphdom with incremental update option + morphdom(fromEl, toEl); + + // Verify selection is preserved + var updatedSelection = window.getSelection(); + expect(updatedSelection.toString()).to.equal('Hello'); + expect(fromEl.textContent).to.equal('Hello world, how are you?'); + }); + + it('should handle multiple incremental updates correctly', () => { + var fromEl = document.createElement('div'); + fromEl.textContent = 'Start'; + document.body.appendChild(fromEl); + + // Create and set selection + var selection = window.getSelection(); + var range = document.createRange(); + range.setStart(fromEl.firstChild, 0); + range.setEnd(fromEl.firstChild, 5); + selection.removeAllRanges(); + selection.addRange(range); + + // Perform multiple updates + var updates = [ + 'Start with', + 'Start with more', + 'Start with more text', + 'Start with more text!!!', + ]; + + updates.forEach(text => { + var toEl = document.createElement('div'); + toEl.textContent = text; + + morphdom(fromEl, toEl); + + // Verify text content after each update + expect(fromEl.textContent).to.equal(text); + }); + + // Verify final selection + expect(window.getSelection().toString()).to.equal('Start'); + }); + + it('should properly normalize DOM after incremental updates', () => { + var fromEl = document.createElement('div'); + fromEl.textContent = 'Initial'; + + var toEl = document.createElement('div'); + toEl.textContent = 'Initial text with more content'; + + morphdom(fromEl, toEl); + + // Verify that we have a single text node after normalization + expect(fromEl.childNodes.length).to.equal(1); + expect(fromEl.childNodes[0].nodeType).to.equal(3); // TEXT_NODE + expect(fromEl.textContent).to.equal('Initial text with more content'); + }); + xit('should reuse DOM element with matching ID and class name (2)', function() { // NOTE: This test is currently failing. We need to improve the special case code // for handling incompatible root nodes. diff --git a/test/fixtures/autotest/incremental-text/from.html b/test/fixtures/autotest/incremental-text/from.html new file mode 100644 index 0000000..bc090af --- /dev/null +++ b/test/fixtures/autotest/incremental-text/from.html @@ -0,0 +1,5 @@ +test1 +test2 +test3 + +test4 \ No newline at end of file diff --git a/test/fixtures/autotest/incremental-text/to.html b/test/fixtures/autotest/incremental-text/to.html new file mode 100644 index 0000000..76c068d --- /dev/null +++ b/test/fixtures/autotest/incremental-text/to.html @@ -0,0 +1,5 @@ +test1 FOO +test2 BAR +test3 FOO +BAR +test4 FOO From 3b3a08d900c9930ec7bae84be6449035b71c313b Mon Sep 17 00:00:00 2001 From: Megaman Date: Sat, 26 Oct 2024 14:27:46 +0800 Subject: [PATCH 2/7] fix: remove redundant return --- src/morphdom.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/morphdom.js b/src/morphdom.js index 3d9ade8..b9c2e57 100644 --- a/src/morphdom.js +++ b/src/morphdom.js @@ -255,7 +255,6 @@ export default function morphdomFactory(morphAttrs) { // Simply update nodeValue on the original node to // change the text value morphedNode.nodeValue = toText; - return; } function morphChildren(fromEl, toEl) { From f801beb4e08fab4b30425729194c18a91578abfb Mon Sep 17 00:00:00 2001 From: Megaman Date: Sat, 26 Oct 2024 14:36:36 +0800 Subject: [PATCH 3/7] fix: rename variable names --- src/morphdom.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/morphdom.js b/src/morphdom.js index b9c2e57..5c9255b 100644 --- a/src/morphdom.js +++ b/src/morphdom.js @@ -236,25 +236,25 @@ export default function morphdomFactory(morphAttrs) { } } - function morphText(morphedNode, toNode) { - var morphedText = morphedNode.nodeValue; - var toText = toNode.nodeValue; + function morphText(fromEl, toEl) { + var fromText = fromEl.nodeValue; + var toText = toEl.nodeValue; - if (morphedText === toText) { + if (fromText === toText) { return; } // Handle incremental update case - if (toText.startsWith(morphedText)) { - var appendedText = toText.substring(morphedText.length); - morphedNode.after(appendedText); - morphedNode.parentNode.normalize(); + if (toText.startsWith(fromText)) { + var appendedText = toText.substring(fromText.length); + fromEl.after(appendedText); + fromEl.parentNode.normalize(); return; } // Simply update nodeValue on the original node to // change the text value - morphedNode.nodeValue = toText; + fromEl.nodeValue = toText; } function morphChildren(fromEl, toEl) { From 9a5e05d9d6e4a688537caf91cc7e13170402d2cf Mon Sep 17 00:00:00 2001 From: Megaman Date: Sat, 26 Oct 2024 14:38:43 +0800 Subject: [PATCH 4/7] fix: unify EOF of test fixtures file --- test/fixtures/autotest/incremental-text/to.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/autotest/incremental-text/to.html b/test/fixtures/autotest/incremental-text/to.html index 76c068d..5415a79 100644 --- a/test/fixtures/autotest/incremental-text/to.html +++ b/test/fixtures/autotest/incremental-text/to.html @@ -2,4 +2,4 @@ test2 BAR test3 FOO BAR -test4 FOO +test4 FOO \ No newline at end of file From 6ec9dce3b184e6f78077195b4eda751c04d9c787 Mon Sep 17 00:00:00 2001 From: Megaman Date: Sat, 26 Oct 2024 14:46:56 +0800 Subject: [PATCH 5/7] fix: correct variable name of nodes --- .vscode/settings.json | 3 +++ src/morphdom.js | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9792498 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": false +} \ No newline at end of file diff --git a/src/morphdom.js b/src/morphdom.js index 5c9255b..7a17031 100644 --- a/src/morphdom.js +++ b/src/morphdom.js @@ -236,9 +236,9 @@ export default function morphdomFactory(morphAttrs) { } } - function morphText(fromEl, toEl) { - var fromText = fromEl.nodeValue; - var toText = toEl.nodeValue; + function morphText(fromNode, toNode) { + var fromText = fromNode.nodeValue; + var toText = toNode.nodeValue; if (fromText === toText) { return; @@ -247,14 +247,14 @@ export default function morphdomFactory(morphAttrs) { // Handle incremental update case if (toText.startsWith(fromText)) { var appendedText = toText.substring(fromText.length); - fromEl.after(appendedText); - fromEl.parentNode.normalize(); + fromNode.after(appendedText); + fromNode.parentNode.normalize(); return; } // Simply update nodeValue on the original node to // change the text value - fromEl.nodeValue = toText; + fromNode.nodeValue = toText; } function morphChildren(fromEl, toEl) { From 8b610b78b7bd5e180675099bf87e8720c94fa8d1 Mon Sep 17 00:00:00 2001 From: Megaman Date: Sat, 26 Oct 2024 14:48:31 +0800 Subject: [PATCH 6/7] fix: remove file accidentally committed --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 9792498..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "editor.formatOnSave": false -} \ No newline at end of file From f55c77447c792d26c8a35e45c9d6e08f07b7d0dc Mon Sep 17 00:00:00 2001 From: Megaman Date: Sat, 26 Oct 2024 14:58:22 +0800 Subject: [PATCH 7/7] fix: handle incremental update for text node only --- src/morphdom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/morphdom.js b/src/morphdom.js index 7a17031..64cd697 100644 --- a/src/morphdom.js +++ b/src/morphdom.js @@ -245,7 +245,7 @@ export default function morphdomFactory(morphAttrs) { } // Handle incremental update case - if (toText.startsWith(fromText)) { + if (fromNode.nodeType === TEXT_NODE && toText.startsWith(fromText)) { var appendedText = toText.substring(fromText.length); fromNode.after(appendedText); fromNode.parentNode.normalize();