/*
* These tests were inspired by idiomorph.
* Here's their license:
*
* Zero-Clause BSD
* =============
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
* FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
import { describe, it, expect, beforeEach, afterEach } from "vitest"
import { morph, morphInner } from "../src/morphlex"
describe("Idiomorph-style 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("basic morphing with different content types", () => {
it("should morph with single node", () => {
const initial = parseHTML("")
container.appendChild(initial)
const final = document.createElement("button")
final.textContent = "Bar"
morph(initial, final)
expect(initial.textContent).toBe("Bar")
})
it("should morph with string", () => {
const initial = parseHTML("")
container.appendChild(initial)
morph(initial, "")
expect(initial.textContent).toBe("Bar")
})
})
describe("morphInner functionality", () => {
it("should morph innerHTML with string", () => {
const div = parseHTML("
Old
")
container.appendChild(div)
morphInner(div, "
New
")
expect(div.innerHTML).toBe("New")
})
it("should morph innerHTML with element", () => {
const div = parseHTML("
Old
")
container.appendChild(div)
const newDiv = document.createElement("div")
const newSpan = document.createElement("span")
newSpan.textContent = "New"
newDiv.appendChild(newSpan)
morphInner(div, newDiv)
expect(div.innerHTML).toBe("New")
})
it("should clear children when morphing to empty", () => {
const div = parseHTML("
')
morph(initial, final)
expect(initial.textContent).toBe("New")
})
})
describe("complex scenarios", () => {
it("should not build ID in new content parent into persistent id set", () => {
const initial = parseHTML('
"
const from = parseHTML(fromHTML)
const to = parseHTML(toHTML)
container.appendChild(from)
const originalElements = Array.from(from.children).map((el) => el)
morph(from, to)
// All elements should be preserved
expect(from.children.length).toBe(50)
for (let i = 0; i < 50; i++) {
expect(from.children[i]).toBe(originalElements[i])
expect(from.children[i].textContent).toBe(`Item ${i} Updated`)
}
})
it("should handle reordering large lists", () => {
let fromHTML = "
"
for (let i = 0; i < 20; i++) {
fromHTML += `
Item ${i}
`
}
fromHTML += "
"
let toHTML = "
"
for (let i = 19; i >= 0; i--) {
toHTML += `
Item ${i}
`
}
toHTML += "
"
const from = parseHTML(fromHTML)
const to = parseHTML(toHTML)
container.appendChild(from)
const elementMap = new Map()
for (let i = 0; i < 20; i++) {
elementMap.set(`item-${i}`, from.querySelector(`#item-${i}`))
}
morph(from, to)
// All elements should be preserved
for (let i = 0; i < 20; i++) {
expect(from.querySelector(`#item-${i}`)).toBe(elementMap.get(`item-${i}`))
}
})
})
})