import { mount } from "$core/binder";
import { signal } from "$core/signal";
import { describe, expect, it } from "vitest";
describe("data-volt-for binding", () => {
it("renders a list from array signal", () => {
const container = document.createElement("div");
container.innerHTML = `
`;
const items = signal(["apple", "banana", "cherry"]);
mount(container, { items });
const ul = container.querySelector("ul")!;
const listItems = ul.querySelectorAll("li");
expect(listItems.length).toBe(3);
expect(listItems[0]?.textContent).toBe("apple");
expect(listItems[1]?.textContent).toBe("banana");
expect(listItems[2]?.textContent).toBe("cherry");
});
it("updates list when signal changes", () => {
const container = document.createElement("div");
container.innerHTML = ``;
const items = signal(["one", "two"]);
mount(container, { items });
const ul = container.querySelector("ul")!;
let listItems = ul.querySelectorAll("li");
expect(listItems.length).toBe(2);
expect(listItems[0]?.textContent).toBe("one");
expect(listItems[1]?.textContent).toBe("two");
items.set(["one", "two", "three", "four"]);
listItems = ul.querySelectorAll("li");
expect(listItems.length).toBe(4);
expect(listItems[2]?.textContent).toBe("three");
expect(listItems[3]?.textContent).toBe("four");
items.set(["solo"]);
listItems = ul.querySelectorAll("li");
expect(listItems.length).toBe(1);
expect(listItems[0]?.textContent).toBe("solo");
});
it("renders list with object properties", () => {
const container = document.createElement("div");
container.innerHTML = `
`;
const users = signal([{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }]);
mount(container, { users });
const spans = container.querySelectorAll("span");
expect(spans.length).toBe(2);
expect(spans[0]?.textContent).toBe("Alice");
expect(spans[1]?.textContent).toBe("Bob");
});
it("supports index access with (item, index) syntax", () => {
const container = document.createElement("div");
container.innerHTML = `
`;
const items = signal(["first", "second", "third"]);
mount(container, { items });
const listItems = container.querySelectorAll("li");
expect(listItems.length).toBe(3);
const firstItem = listItems[0]?.querySelectorAll("span");
expect(firstItem?.[0]?.textContent).toBe("0");
expect(firstItem?.[1]?.textContent).toBe("first");
const secondItem = listItems[1]?.querySelectorAll("span");
expect(secondItem?.[0]?.textContent).toBe("1");
expect(secondItem?.[1]?.textContent).toBe("second");
});
it("handles empty arrays", () => {
const container = document.createElement("div");
container.innerHTML = ``;
const items = signal([]);
mount(container, { items });
const listItems = container.querySelectorAll("li");
expect(listItems.length).toBe(0);
items.set(["now", "there", "are", "items"]);
const updatedItems = container.querySelectorAll("li");
expect(updatedItems.length).toBe(4);
});
it("handles static arrays (non-signal)", () => {
const container = document.createElement("div");
container.innerHTML = ``;
mount(container, { items: ["static", "array"] });
const listItems = container.querySelectorAll("li");
expect(listItems.length).toBe(2);
expect(listItems[0]?.textContent).toBe("static");
expect(listItems[1]?.textContent).toBe("array");
});
it("supports event handlers in list items", () => {
const container = document.createElement("div");
container.innerHTML = `
`;
let clickedItem = "";
const handleClick = (event: Event) => {
const button = event.target as HTMLButtonElement;
clickedItem = button.textContent || "";
};
const items = signal([{ text: "Click Me" }, { text: "Or Me" }]);
mount(container, { items, handleClick });
const buttons = container.querySelectorAll("button");
expect(buttons.length).toBe(2);
buttons[0]?.click();
expect(clickedItem).toBe("Click Me");
buttons[1]?.click();
expect(clickedItem).toBe("Or Me");
});
it("supports nested loops", () => {
const container = document.createElement("div");
container.innerHTML = `
`;
const groups = signal([{ items: ["a", "b"] }, { items: ["c", "d", "e"] }]);
mount(container, { groups });
const divs = container.querySelectorAll("div");
expect(divs.length).toBe(2);
const firstGroupItems = divs[0]?.querySelectorAll("li");
expect(firstGroupItems?.length).toBe(2);
expect(firstGroupItems?.[0]?.textContent).toBe("a");
const secondGroupItems = divs[1]?.querySelectorAll("li");
expect(secondGroupItems?.length).toBe(3);
expect(secondGroupItems?.[2]?.textContent).toBe("e");
});
it("properly cleans up when unmounting", () => {
const container = document.createElement("div");
container.innerHTML = ``;
const items = signal([{ value: signal("A") }, { value: signal("B") }]);
const cleanup = mount(container, { items });
expect(container.querySelectorAll("li").length).toBe(2);
const listItemsBefore = container.querySelectorAll("li");
const textBefore = [listItemsBefore[0]?.textContent, listItemsBefore[1]?.textContent];
cleanup();
const itemA = items.get()[0];
const itemB = items.get()[1];
itemA.value.set("Changed A");
itemB.value.set("Changed B");
const listItems = container.querySelectorAll("li");
expect(listItems[0]?.textContent).toBe(textBefore[0]);
expect(listItems[1]?.textContent).toBe(textBefore[1]);
});
it("handles non-array values gracefully", () => {
const container = document.createElement("div");
container.innerHTML = ``;
mount(container, { notAnArray: "not an array" });
const listItems = container.querySelectorAll("li");
expect(listItems.length).toBe(0);
});
it("supports reactive properties within list items", () => {
const container = document.createElement("div");
container.innerHTML = `
`;
const todos = signal([{ title: signal("Buy milk"), completed: signal({ done: false }) }, {
title: signal("Walk dog"),
completed: signal({ done: true }),
}]);
mount(container, { todos });
const listItems = container.querySelectorAll("li");
expect(listItems.length).toBe(2);
const firstTodo = listItems[0];
const firstTitle = firstTodo?.querySelector("span:first-child");
const firstStatus = firstTodo?.querySelector("span:last-child");
expect(firstTitle?.textContent).toBe("Buy milk");
expect(firstStatus?.classList.contains("done")).toBe(false);
todos.get()[0].title.set("Buy eggs");
todos.get()[0].completed.set({ done: true });
expect(firstTitle?.textContent).toBe("Buy eggs");
expect(firstStatus?.classList.contains("done")).toBe(true);
});
});