diff --git a/CHANGELOG.md b/CHANGELOG.md index 554378f..5ebab36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to @livetemplate/client will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- `lvt-preserve` attribute: morphdom escape hatch that skips an element and its subtree entirely during diff (equivalent to Phoenix LiveView's `phx-update="ignore"`). Checked on `toEl` so the server retains authority to remove it. +- `lvt-preserve-attrs` attribute: morphdom escape hatch that preserves user-managed attributes (e.g. `open` on `
`) while still diffing children. Protects attributes the server template does **not** set. Checked on `toEl` for consistent server authority. +- In-band `__navigate__` SPA navigation: same-pathname link clicks send `{action:"__navigate__", data:}` over the existing WebSocket instead of fetching new HTML. Requires server-side support (livetemplate/livetemplate#344). +- DOMParser fallback in `updateDOM`: HTML containing `', + '
', + "
", + ], + 0: "", // between chat and script (slot 0) + 1: "", // between script and after-script (slot 1) + 2: "unique-content-that-should-appear-once", // inside after-script + }; + + client.updateDOM(wrapper, tree); + + // Count: the div.after-script should appear exactly ONCE. + const afterScriptDivs = wrapper.querySelectorAll(".after-script"); + expect(afterScriptDivs.length).toBe(1); + + // The unique content should appear once. + const textOccurrences = (wrapper.textContent || "").split( + "unique-content-that-should-appear-once" + ).length - 1; + expect(textOccurrences).toBe(1); + }); + + it("uppercase ', + '
after
', + ], + 0: "", + 1: "", + }; + + client.updateDOM(wrapper, tree); + + expect(wrapper.querySelectorAll(".after").length).toBe(1); + expect(wrapper.querySelectorAll(".before").length).toBe(1); + }); + + it("a second updateDOM does not cause further duplication", () => { + const tree = { + s: [ + '
', + '
', + "
", + ], + 0: "initial", + 1: "initial-after", + }; + + client.updateDOM(wrapper, tree); + expect(wrapper.querySelectorAll(".after").length).toBe(1); + + // Apply an update that changes the dynamic slot but keeps the same structure. + const update = { 0: "updated", 1: "updated-after" }; + client.updateDOM(wrapper, update); + + expect(wrapper.querySelectorAll(".after").length).toBe(1); + expect(wrapper.textContent).toContain("updated-after"); + }); +});