import { charge } from "$core/charge"; import type { Signal } from "$types/volt"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; describe("charge", () => { let container: HTMLDivElement; beforeEach(() => { container = document.createElement("div"); document.body.append(container); }); afterEach(() => { container.remove(); }); describe("basic charging", () => { it("discovers and mounts elements with data-volt attribute", () => { container.innerHTML = `
`; const result = charge(); expect(result.roots).toHaveLength(1); expect(result.roots[0].element).toBe(container.querySelector("#root1")); expect(typeof result.cleanup).toBe("function"); result.cleanup(); }); it("mounts multiple roots", () => { container.innerHTML = `
`; const result = charge(); expect(result.roots).toHaveLength(3); result.cleanup(); }); it("accepts custom selector", () => { container.innerHTML = `
`; const result = charge(".custom"); expect(result.roots).toHaveLength(1); expect(result.roots[0].element.id).toBe("root2"); result.cleanup(); }); it("returns empty array when no roots found", () => { container.innerHTML = `
`; const result = charge(); expect(result.roots).toHaveLength(0); result.cleanup(); }); }); describe("data-volt-state parsing", () => { it("creates signals from data-volt-state JSON", () => { container.innerHTML = `
`; const result = charge(); const root = container.querySelector("div[data-volt]")!; const spans = root.querySelectorAll("span"); expect(spans[0].textContent).toBe("0"); expect(spans[1].textContent).toBe("hello"); result.cleanup(); }); it("creates signals for nested objects", () => { container.innerHTML = `
`; const result = charge(); const spans = container.querySelectorAll("span"); expect(spans[0].textContent).toBe("Alice"); expect(spans[1].textContent).toBe("30"); result.cleanup(); }); it("creates reactive signals that can be updated", () => { container.innerHTML = `
`; const result = charge(); const span = container.querySelector("span")!; const scope = result.roots[0].scope; expect(span.textContent).toBe("0"); (scope.count as Signal).set(5); expect(span.textContent).toBe("5"); (scope.count as Signal).set(10); expect(span.textContent).toBe("10"); result.cleanup(); }); it("handles arrays in state", () => { container.innerHTML = `
`; const result = charge(); const items = container.querySelectorAll("li"); expect(items).toHaveLength(3); expect(items[0].textContent).toBe("a"); expect(items[1].textContent).toBe("b"); expect(items[2].textContent).toBe("c"); result.cleanup(); }); it("handles empty state object", () => { container.innerHTML = `
Content
`; const result = charge(); expect(result.roots).toHaveLength(1); const scope = result.roots[0].scope; expect(scope.$store).toBeDefined(); expect(scope.$arc).toBeDefined(); expect(scope.$origin).toBeDefined(); expect(scope.$pins).toBeDefined(); expect(scope.$probe).toBeDefined(); expect(scope.$pulse).toBeDefined(); expect(scope.$scope).toBe(scope); expect(scope.$uid).toBeDefined(); result.cleanup(); }); it("handles missing data-volt-state gracefully", () => { container.innerHTML = `
`; const result = charge(); const span = container.querySelector("span")!; expect(span.textContent).toBe("static"); result.cleanup(); }); it("logs error for invalid JSON", () => { const consoleError = console.error; const errors: unknown[] = []; console.error = (...args: unknown[]) => errors.push(args); container.innerHTML = `
Content
`; const result = charge(); expect(errors.length).toBeGreaterThan(0); console.error = consoleError; result.cleanup(); }); it("logs error for non-object state", () => { const consoleError = console.error; const errors: unknown[] = []; console.error = (...args: unknown[]) => errors.push(args); container.innerHTML = `
Content
`; const result = charge(); expect(errors.length).toBeGreaterThan(0); console.error = consoleError; result.cleanup(); }); }); describe("data-volt-computed", () => { it("creates computed values from expressions", () => { container.innerHTML = `
`; const result = charge(); const spans = container.querySelectorAll("span"); expect(spans[0].textContent).toBe("5"); expect(spans[1].textContent).toBe("10"); result.cleanup(); }); it("updates computed values when dependencies change", () => { container.innerHTML = `
`; const result = charge(); const scope = result.roots[0].scope; const double = container.querySelector("#double")!; const triple = container.querySelector("#triple")!; expect(double.textContent).toBe("6"); expect(triple.textContent).toBe("9"); (scope.count as Signal).set(5); expect(double.textContent).toBe("10"); expect(triple.textContent).toBe("15"); result.cleanup(); }); it("supports computed values with multiple dependencies", () => { container.innerHTML = `
`; const result = charge(); const scope = result.roots[0].scope; expect(container.querySelector("#sum")!.textContent).toBe("8"); expect(container.querySelector("#product")!.textContent).toBe("15"); (scope.a as Signal).set(10); expect(container.querySelector("#sum")!.textContent).toBe("13"); expect(container.querySelector("#product")!.textContent).toBe("30"); result.cleanup(); }); it("supports complex expressions in computed", () => { container.innerHTML = `
`; const result = charge(); const span = container.querySelector("span")!; expect(span.textContent).toBe("true"); result.cleanup(); }); it("handles computed with no dependencies", () => { container.innerHTML = `
`; const result = charge(); const span = container.querySelector("span")!; expect(span.textContent).toBe("84"); result.cleanup(); }); it("supports computed accessing nested properties", () => { container.innerHTML = `
`; const result = charge(); const span = container.querySelector("span")!; expect(span.textContent).toBe("John"); result.cleanup(); }); }); describe("isolated scopes", () => { it("creates isolated scopes for each root", () => { container.innerHTML = `
`; const result = charge(); expect(container.querySelector("#root1")!.textContent).toBe("1"); expect(container.querySelector("#root2")!.textContent).toBe("2"); const scope = result.roots[0].scope; (scope.count as Signal).set(10); expect(container.querySelector("#root1")!.textContent).toBe("10"); expect(container.querySelector("#root2")!.textContent).toBe("2"); result.cleanup(); }); it("does not share state between roots", () => { container.innerHTML = `
`; const result = charge(); expect(container.querySelector("#s1")!.textContent).toBe("root1"); expect(container.querySelector("#s2")!.textContent).toBe("root2"); result.cleanup(); }); }); describe("cleanup", () => { it("cleans up all roots when calling global cleanup", () => { container.innerHTML = `
`; const result = charge(); const span = container.querySelector("span")!; const scope = result.roots[0].scope; (scope.count as Signal).set(5); expect(span.textContent).toBe("5"); result.cleanup(); (scope.count as Signal).set(10); expect(span.textContent).toBe("5"); }); it("cleans up individual roots", () => { container.innerHTML = `
`; const result = charge(); const span = container.querySelector("span")!; const scope = result.roots[0].scope; (scope.count as Signal).set(5); expect(span.textContent).toBe("5"); result.roots[0].cleanup(); (scope.count as Signal).set(10); expect(span.textContent).toBe("5"); }); }); describe("integration with bindings", () => { it("works with all binding types", () => { container.innerHTML = `
`; const result = charge(); const span = container.querySelector("span")!; expect(span.textContent).toBe("Hello"); expect(span.classList.contains("true")).toBe(true); result.cleanup(); }); it("works with conditional rendering", () => { container.innerHTML = `

Visible

`; const result = charge(); expect(container.querySelector("p")).toBeTruthy(); const scope = result.roots[0].scope; (scope.show as Signal).set(false); expect(container.querySelector("p")).toBeNull(); result.cleanup(); }); it("works with list rendering", () => { container.innerHTML = `
`; const result = charge(); expect(container.querySelectorAll("li")).toHaveLength(2); result.cleanup(); }); }); });