a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
at main 4.1 kB view raw
1import { mount, signal } from "$volt"; 2import { describe, expect, it } from "vitest"; 3 4describe("integration: mount", () => { 5 it("creates a reactive counter", () => { 6 const container = document.createElement("div"); 7 container.innerHTML = ` 8 <div> 9 <p data-volt-text="count">0</p> 10 <p data-volt-class="countClass">Classes</p> 11 </div> 12 `; 13 14 const count = signal(0); 15 const countClass = signal({ positive: false, zero: true }); 16 17 mount(container, { count, countClass }); 18 19 const textElement = container.querySelector("p:first-child"); 20 const classElement = container.querySelector("p:last-child"); 21 22 expect(textElement?.textContent).toBe("0"); 23 expect(classElement?.classList.contains("zero")).toBe(true); 24 expect(classElement?.classList.contains("positive")).toBe(false); 25 26 count.set(5); 27 countClass.set({ positive: true, zero: false }); 28 29 expect(textElement?.textContent).toBe("5"); 30 expect(classElement?.classList.contains("zero")).toBe(false); 31 expect(classElement?.classList.contains("positive")).toBe(true); 32 }); 33 34 it("handles complex nested structures", () => { 35 const container = document.createElement("div"); 36 container.innerHTML = ` 37 <div data-volt-text="title">Title</div> 38 <ul> 39 <li data-volt-text="items.first">First</li> 40 <li data-volt-text="items.second">Second</li> 41 </ul> 42 <footer data-volt-html="footer">Footer</footer> 43 `; 44 45 const title = signal("My App"); 46 const items = { first: "Item 1", second: "Item 2" }; 47 const footer = signal("<strong>© 2025</strong>"); 48 49 mount(container, { title, items, footer }); 50 51 expect(container.querySelector("[data-volt-text='title']")?.textContent).toBe("My App"); 52 expect(container.querySelector("li:first-child")?.textContent).toBe("Item 1"); 53 expect(container.querySelector("li:last-child")?.textContent).toBe("Item 2"); 54 expect(container.querySelector("footer")?.innerHTML).toBe("<strong>© 2025</strong>"); 55 56 title.set("Updated App"); 57 footer.set("<em>New Footer</em>"); 58 59 expect(container.querySelector("[data-volt-text='title']")?.textContent).toBe("Updated App"); 60 expect(container.querySelector("footer")?.innerHTML).toBe("<em>New Footer</em>"); 61 }); 62 63 it("properly cleans up all bindings", () => { 64 const container = document.createElement("div"); 65 container.innerHTML = ` 66 <div data-volt-text="a">A</div> 67 <div data-volt-text="b">B</div> 68 <div data-volt-text="c">C</div> 69 `; 70 71 const a = signal("A"); 72 const b = signal("B"); 73 const c = signal("C"); 74 75 const cleanup = mount(container, { a, b, c }); 76 77 const divs = [...container.querySelectorAll("div")]; 78 expect(divs[0]?.textContent).toBe("A"); 79 expect(divs[1]?.textContent).toBe("B"); 80 expect(divs[2]?.textContent).toBe("C"); 81 82 a.set("A1"); 83 b.set("B1"); 84 c.set("C1"); 85 86 expect(divs[0]?.textContent).toBe("A1"); 87 expect(divs[1]?.textContent).toBe("B1"); 88 expect(divs[2]?.textContent).toBe("C1"); 89 90 cleanup(); 91 92 a.set("A2"); 93 b.set("B2"); 94 c.set("C2"); 95 96 expect(divs[0]?.textContent).toBe("A1"); 97 expect(divs[1]?.textContent).toBe("B1"); 98 expect(divs[2]?.textContent).toBe("C1"); 99 }); 100 101 it("supports mixed static and reactive values", () => { 102 const container = document.createElement("div"); 103 container.innerHTML = ` 104 <h1 data-volt-text="staticTitle">Title</h1> 105 <p data-volt-text="dynamicContent">Content</p> 106 <span data-volt-class="'always-visible'">Visible</span> 107 `; 108 109 const staticTitle = "Welcome"; 110 const dynamicContent = signal("Loading..."); 111 112 mount(container, { staticTitle, dynamicContent }); 113 114 expect(container.querySelector("h1")?.textContent).toBe("Welcome"); 115 expect(container.querySelector("p")?.textContent).toBe("Loading..."); 116 expect(container.querySelector("span")?.classList.contains("always-visible")).toBe(true); 117 118 dynamicContent.set("Content loaded!"); 119 expect(container.querySelector("p")?.textContent).toBe("Content loaded!"); 120 }); 121});