/* * These tests were inspired by morphdom. * Here's their license: * * The MIT License (MIT) * * Copyright (c) Patrick Steele-Idem (psteeleidem.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ import { describe, it, expect, beforeEach, afterEach } from "vitest" import { morph } from "../src/morphlex" describe("Morphdom-style fixture tests", () => { let container: HTMLElement beforeEach(() => { container = document.createElement("div") document.body.appendChild(container) }) afterEach(() => { if (container && container.parentNode) { container.parentNode.removeChild(container) } }) function parseHTML(html: string): HTMLElement { const tmp = document.createElement("div") tmp.innerHTML = html.trim() return tmp.firstChild as HTMLElement } describe("simple morphing", () => { it("should add new element before existing element", () => { const from = parseHTML("
bold
") const to = parseHTML("
italicsbold
") morph(from, to) expect(from.innerHTML).toBe("italicsbold") }) it("should handle equal elements", () => { const from = parseHTML("
test
") const to = parseHTML("
test
") morph(from, to) expect(from.innerHTML).toBe("test") }) it("should shorten list of children", () => { const from = parseHTML("
123
") const to = parseHTML("
1
") morph(from, to) expect(from.children.length).toBe(1) expect(from.innerHTML).toBe("1") }) it("should lengthen list of children", () => { const from = parseHTML("
1
") const to = parseHTML("
123
") morph(from, to) expect(from.children.length).toBe(3) expect(from.innerHTML).toBe("123") }) it("should reverse children", () => { const from = parseHTML("
abc
") const to = parseHTML("
cba
") morph(from, to) expect(from.innerHTML).toBe("cba") }) }) describe("attribute handling", () => { it("should handle empty string attribute values", () => { const from = parseHTML('
') const to = parseHTML('
') morph(from, to) expect(from.getAttribute("class")).toBe("") }) }) describe("input elements", () => { it("should morph input element", () => { const from = parseHTML('') const to = parseHTML('') morph(from, to) // Input values are updated by default when not modified expect((from as HTMLInputElement).value).toBe("World") }) it("should add disabled attribute to input", () => { const from = parseHTML('') const to = parseHTML('') morph(from, to) expect((from as HTMLInputElement).disabled).toBe(true) }) it("should remove disabled attribute from input", () => { const from = parseHTML('') const to = parseHTML('') morph(from, to) expect((from as HTMLInputElement).disabled).toBe(false) }) }) describe("select elements", () => { it("should handle select element with selected option", () => { const from = parseHTML(` `) const to = parseHTML(` `) morph(from, to) // Selected attribute is removed but not added - select defaults to first option const select = from as HTMLSelectElement expect(select.value).toBe("1") expect(select.options[0].selected).toBe(true) expect(select.options[1].selected).toBe(false) }) it("should handle select element with default selection", () => { const from = parseHTML(` `) const to = parseHTML(` `) morph(from, to) // Selected options are updated by default when not modified const select = from as HTMLSelectElement expect(select.value).toBe("2") expect(select.options[1].selected).toBe(true) }) }) describe("id-based morphing", () => { it("should handle nested elements with IDs", () => { const from = parseHTML(`
A
B
`) const to = parseHTML(`
B Updated
A Updated
`) const aEl = from.querySelector("#a") const bEl = from.querySelector("#b") morph(from, to) // Elements with IDs should be preserved expect(from.querySelector("#a")).toBe(aEl) expect(from.querySelector("#b")).toBe(bEl) expect(from.querySelector("#a")?.textContent).toBe("A Updated") expect(from.querySelector("#b")?.textContent).toBe("B Updated") }) it("should handle reversing elements with IDs", () => { const from = parseHTML(`
a
b
c
`) const to = parseHTML(`
c
b
a
`) const aEl = from.querySelector("#a") const bEl = from.querySelector("#b") const cEl = from.querySelector("#c") morph(from, to) expect(from.querySelector("#a")).toBe(aEl) expect(from.querySelector("#b")).toBe(bEl) expect(from.querySelector("#c")).toBe(cEl) }) it("should handle prepending element with ID", () => { const from = parseHTML(`
a
b
`) const to = parseHTML(`
c
a
b
`) const aEl = from.querySelector("#a") const bEl = from.querySelector("#b") morph(from, to) expect(from.querySelector("#a")).toBe(aEl) expect(from.querySelector("#b")).toBe(bEl) expect(from.children.length).toBe(3) expect(from.children[0].id).toBe("c") }) it("should handle changing tag name with ID preservation", () => { const from = parseHTML(`
A
`) const to = parseHTML(`
A
`) morph(from, to) expect(from.querySelector("#a")?.tagName).toBe("SPAN") }) }) describe("tag name changes", () => { it("should change tag name", () => { const from = parseHTML("
Hello
") const to = parseHTML("
Hello
") morph(from, to) expect(from.innerHTML).toBe("Hello") }) it("should change tag name with IDs", () => { const from = parseHTML(`
A
B
`) const to = parseHTML(`
A B
`) morph(from, to) expect(from.querySelector("#a")?.tagName).toBe("SPAN") expect(from.querySelector("#b")?.tagName).toBe("SPAN") }) }) describe("SVG elements", () => { it("should handle SVG elements", () => { const from = parseHTML(` `) const to = parseHTML(` `) morph(from, to) expect(from.children.length).toBe(2) expect(from.children[0].tagName.toLowerCase()).toBe("circle") expect(from.children[1].tagName.toLowerCase()).toBe("rect") }) it("should append new SVG elements", () => { const from = parseHTML('') const to = parseHTML(` `) morph(from, to) expect(from.children.length).toBe(2) }) }) describe("data table tests", () => { it("should handle complex data table morphing", () => { const from = parseHTML(`
AB
CD
`) const to = parseHTML(`
ABE
CDF
`) morph(from, to) const rows = from.querySelectorAll("tr") expect(rows.length).toBe(2) expect(rows[0].children.length).toBe(3) expect(rows[0].children[2].textContent).toBe("E") expect(rows[1].children[2].textContent).toBe("F") }) it("should handle data table with row modifications", () => { const from = parseHTML(`
1
2
3
`) const to = parseHTML(`
1
2 Updated
3
4
`) morph(from, to) const rows = from.querySelectorAll("tr") expect(rows.length).toBe(4) expect(rows[1].textContent).toBe("2 Updated") expect(rows[3].textContent).toBe("4") }) }) describe("nested id scenarios", () => { it("should handle deeply nested IDs - scenario 2", () => { const from = parseHTML(`
A
B
`) const to = parseHTML(`
B
A
`) const aEl = from.querySelector("#a") const bEl = from.querySelector("#b") morph(from, to) expect(from.querySelector("#a")).toBe(aEl) expect(from.querySelector("#b")).toBe(bEl) }) it("should handle deeply nested IDs - scenario 3", () => { const from = parseHTML(`
A
B
`) const to = parseHTML(`
B
A
`) const aEl = from.querySelector("#a") const bEl = from.querySelector("#b") morph(from, to) expect(from.querySelector("#a")).toBe(aEl) expect(from.querySelector("#b")).toBe(bEl) }) it("should handle deeply nested IDs - scenario 4", () => { const from = parseHTML(`
A
B
`) const to = parseHTML(`
B
A
`) const aEl = from.querySelector("#a") const bEl = from.querySelector("#b") morph(from, to) expect(from.querySelector("#a")).toBe(aEl) expect(from.querySelector("#b")).toBe(bEl) }) it("should handle deeply nested IDs - scenario 5", () => { const from = parseHTML(`
B
`) const to = parseHTML(`
A
B
`) morph(from, to) expect(from.querySelector("#a")?.textContent?.trim()).toBe("A") expect(from.querySelector("#b")?.textContent).toBe("B") }) it("should handle deeply nested IDs - scenario 6", () => { const from = parseHTML(`
A
B
`) const to = parseHTML(`
B
`) morph(from, to) expect(from.querySelector("#a #b")?.textContent).toBe("B") }) it("should handle deeply nested IDs - scenario 7", () => { const from = parseHTML(`
C
`) const to = parseHTML(`
A
B
C
`) morph(from, to) expect(from.children.length).toBe(3) expect(from.querySelector("#a")?.textContent?.trim()).toBe("A") expect(from.querySelector("#b")?.textContent?.trim()).toBe("B") expect(from.querySelector("#c")?.textContent).toBe("C") }) }) describe("large document morphing", () => { it("should handle large DOM trees efficiently", () => { let fromHTML = "
" let toHTML = "
" for (let i = 0; i < 100; i++) { fromHTML += `
Item ${i}
` toHTML += `
Item ${i} Updated
` } fromHTML += "
" toHTML += "
" const from = parseHTML(fromHTML) const to = parseHTML(toHTML) const originalElements = Array.from(from.children).map((el) => el) morph(from, to) // All elements should be preserved expect(from.children.length).toBe(100) for (let i = 0; i < 100; i++) { expect(from.children[i]).toBe(originalElements[i]) expect(from.children[i].textContent).toBe(`Item ${i} Updated`) } }) }) })