From 8fd96bf10c5dffb8c73386ac17eccf32d69fb816 Mon Sep 17 00:00:00 2001 From: Aral Roca Gomez Date: Wed, 16 Oct 2024 22:30:40 +0200 Subject: [PATCH] feat: add data-cid as key (#16) --- package.json | 2 +- src/index.test.ts | 529 +++++++++++++++++++++++++++++++++++++++++++++- src/index.ts | 7 +- 3 files changed, 532 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9345222..b57a743 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff-dom-streaming", - "version": "0.6.1", + "version": "0.6.2", "bugs": "https://github.com/aralroca/diff-dom-streaming/issues", "description": "Diff DOM algorithm with streaming. Gets all modifications, insertions and removals between a DOM fragment and a stream HTML reader.", "keywords": [ diff --git a/src/index.test.ts b/src/index.test.ts index 3970fc9..186b50f 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -27,8 +27,8 @@ const diffCode = await transpiler.transform( const normalize = (t: string) => t.replace(/\s*\n\s*/g, "").replaceAll("'", '"'); -describe("Diff test", () => { - let browser: Browser; +describe("Diff test", async () => { + let browser = await engine.chrome.launch(); let page: Page; beforeEach(async () => { @@ -36,11 +36,11 @@ describe("Diff test", () => { }); afterEach(async () => { - await page.close(); + await page?.close(); }); afterAll(async () => { - await browser.close(); + await browser?.close(); }); describe("Chrome View Transitions API", () => { @@ -481,6 +481,527 @@ describe("Diff test", () => { ]); }); + it("should diff children (data-cid) move by deleting", async () => { + const [newHTML, mutations] = await testDiff({ + oldHTMLString: ` +
+ hello + text + text2 +
+ `, + newHTMLStringChunks: [ + "
", + 'hello2', + 'text1', + "
", + ], + }); + expect(newHTML).toBe( + normalize(` + + + +
+ hello2 + text1 +
+ + + `), + ); + + expect(mutations).toEqual([ + { + addedNodes: [], + attributeName: null, + oldValue: "hello", + outerHTML: undefined, + removedNodes: [], + tagName: undefined, + type: "characterData", + }, + { + addedNodes: [], + attributeName: "href", + oldValue: "link", + outerHTML: 'hello2', + removedNodes: [], + tagName: "A", + type: "attributes", + }, + { + addedNodes: [], + attributeName: null, + oldValue: null, + outerHTML: + '
hello2text2text
', + removedNodes: [ + { + nodeName: "I", + nodeValue: null, + }, + ], + tagName: "DIV", + type: "childList", + }, + { + addedNodes: [ + { + nodeName: "I", + nodeValue: null, + keepsExistingNodeReference: true, + }, + ], + attributeName: null, + oldValue: null, + outerHTML: + '
hello2text2text
', + removedNodes: [], + tagName: "DIV", + type: "childList", + }, + { + addedNodes: [], + attributeName: null, + oldValue: "text2", + outerHTML: undefined, + removedNodes: [], + tagName: undefined, + type: "characterData", + }, + { + addedNodes: [], + attributeName: null, + oldValue: null, + outerHTML: + '
hello2text1
', + removedNodes: [ + { + nodeName: "B", + nodeValue: null, + }, + ], + tagName: "DIV", + type: "childList", + }, + ]); + }); + + it("should diff children (data-cid) move by shuffling", async () => { + const [newHTML, mutations] = await testDiff({ + oldHTMLString: ` +
+ hello + text + text2 +
+ `, + newHTMLStringChunks: [ + "
", + 'hello', + 'text2', + 'text', + "
", + ], + }); + expect(newHTML).toBe( + normalize(` + + + +
+ hello + text2 + text +
+ + + `), + ); + + expect(mutations).toEqual([ + { + addedNodes: [], + attributeName: null, + oldValue: null, + outerHTML: + '
hellotext2text
', + removedNodes: [ + { + nodeName: "I", + nodeValue: null, + }, + ], + tagName: "DIV", + type: "childList", + }, + { + addedNodes: [ + { + nodeName: "I", + nodeValue: null, + keepsExistingNodeReference: true, + }, + ], + attributeName: null, + oldValue: null, + outerHTML: + '
hellotext2text
', + removedNodes: [], + tagName: "DIV", + type: "childList", + }, + ]); + }); + + it("should diff children (data-cid) remove", async () => { + const [newHTML, mutations] = await testDiff({ + oldHTMLString: ` +
+ hello + text + text2 +
+ `, + newHTMLStringChunks: ["
", 'hello2', "
"], + }); + expect(newHTML).toBe( + normalize(` + + + +
+ hello2 +
+ + + `), + ); + expect(mutations).toEqual([ + { + addedNodes: [], + attributeName: null, + oldValue: "hello", + outerHTML: undefined, + removedNodes: [], + tagName: undefined, + type: "characterData", + }, + { + addedNodes: [], + attributeName: "href", + oldValue: "link", + outerHTML: 'hello2', + removedNodes: [], + tagName: "A", + type: "attributes", + }, + { + addedNodes: [], + attributeName: null, + oldValue: null, + outerHTML: '
hello2
', + removedNodes: [ + { + nodeName: "I", + nodeValue: null, + }, + ], + tagName: "DIV", + type: "childList", + }, + { + addedNodes: [], + attributeName: null, + oldValue: null, + outerHTML: '
hello2
', + removedNodes: [ + { + nodeName: "B", + nodeValue: null, + }, + ], + tagName: "DIV", + type: "childList", + }, + ]); + }); + + it("should diff children (data-cid) insert new node", async () => { + const [newHTML, mutations] = await testDiff({ + oldHTMLString: ` +
+ hello + text2 +
+ `, + newHTMLStringChunks: [ + "
", + 'hello2', + "test", + 'text2', + "
", + ], + }); + expect(newHTML).toBe( + normalize(` + + + +
+ hello2 + test + text2 +
+ + + `), + ); + + expect(mutations).toEqual([ + { + addedNodes: [], + attributeName: null, + oldValue: "hello", + outerHTML: undefined, + removedNodes: [], + tagName: undefined, + type: "characterData", + }, + { + addedNodes: [], + attributeName: "href", + oldValue: "link", + outerHTML: 'hello2', + removedNodes: [], + tagName: "A", + type: "attributes", + }, + { + addedNodes: [ + { + keepsExistingNodeReference: false, + nodeName: "B", + nodeValue: null, + }, + ], + attributeName: null, + oldValue: null, + outerHTML: + '
hello2testtext2
', + removedNodes: [], + tagName: "DIV", + type: "childList", + }, + { + addedNodes: [], + attributeName: null, + oldValue: null, + outerHTML: + '
hello2testtext2
', + removedNodes: [ + { + nodeName: "I", + nodeValue: null, + }, + ], + tagName: "DIV", + type: "childList", + }, + { + addedNodes: [ + { + keepsExistingNodeReference: true, + nodeName: "I", + nodeValue: null, + }, + ], + attributeName: null, + oldValue: null, + outerHTML: + '
hello2testtext2
', + removedNodes: [], + tagName: "DIV", + type: "childList", + }, + ]); + }); + + it("should diff children (data-cid) with xhtml namespaceURI", async () => { + const [newHTML, mutations] = await testDiff({ + oldHTMLString: ` +
+ hello + text + text2 +
+ `, + newHTMLStringChunks: [ + '
', + 'hello2', + 'text1', + "
", + ], + }); + expect(newHTML).toBe( + normalize(` + + + +
+ hello2 + text1 +
+ + + `), + ); + + expect(mutations).toEqual([ + { + addedNodes: [], + attributeName: null, + oldValue: "hello", + outerHTML: undefined, + removedNodes: [], + tagName: undefined, + type: "characterData", + }, + { + addedNodes: [], + attributeName: "href", + oldValue: "link", + outerHTML: 'hello2', + removedNodes: [], + tagName: "A", + type: "attributes", + }, + { + addedNodes: [], + attributeName: null, + oldValue: null, + outerHTML: + '
hello2text2text
', + removedNodes: [ + { + nodeName: "I", + nodeValue: null, + }, + ], + tagName: "DIV", + type: "childList", + }, + { + addedNodes: [ + { + keepsExistingNodeReference: true, + nodeName: "I", + nodeValue: null, + }, + ], + attributeName: null, + oldValue: null, + outerHTML: + '
hello2text2text
', + removedNodes: [], + tagName: "DIV", + type: "childList", + }, + { + addedNodes: [], + attributeName: null, + oldValue: "text2", + outerHTML: undefined, + removedNodes: [], + tagName: undefined, + type: "characterData", + }, + { + addedNodes: [], + attributeName: null, + oldValue: null, + outerHTML: + '
hello2text1
', + removedNodes: [ + { + nodeName: "B", + nodeValue: null, + }, + ], + tagName: "DIV", + type: "childList", + }, + ]); + }); + + it("should diff children (data-cid) move (custom attribute)", async () => { + const [newHTML, mutations] = await testDiff({ + oldHTMLString: ` +
+ hello + text + text2 +
+ `, + newHTMLStringChunks: [ + "
", + 'hello', + 'text2', + 'text', + "
", + ], + }); + expect(newHTML).toBe( + normalize(` + + + +
+ hello + text2 + text +
+ + + `), + ); + + expect(mutations).toEqual([ + { + addedNodes: [], + attributeName: null, + oldValue: null, + outerHTML: + '
hellotext2text
', + removedNodes: [ + { + nodeName: "I", + nodeValue: null, + }, + ], + tagName: "DIV", + type: "childList", + }, + { + addedNodes: [ + { + nodeName: "I", + nodeValue: null, + keepsExistingNodeReference: true, + }, + ], + attributeName: null, + oldValue: null, + outerHTML: + '
hellotext2text
', + removedNodes: [], + tagName: "DIV", + type: "childList", + }, + ]); + }); + it("should diff children (key) move by deleting", async () => { const [newHTML, mutations] = await testDiff({ oldHTMLString: ` diff --git a/src/index.ts b/src/index.ts index 37523cb..e5bd19e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -207,7 +207,12 @@ async function setChildNodes(oldParent: Node, newParent: Node, walker: Walker) { } function getKey(node: Node) { - return (node as Element)?.getAttribute?.("key") || (node as Element).id; + // data-cid fixes navigating with elements with registered server actions in Brisa: + // https://github.com/brisa-build/brisa/issues/558 + for (const attr of ["key", "id", "data-cid"]) { + const value = (node as Element)?.getAttribute?.(attr); + if (value) return value; + } } /**