jdm_javascript_dom_manipulator
Version:
framework javascript
1,087 lines (869 loc) • 37.3 kB
JavaScript
import { describe, it, expect, beforeEach, vi } from "vitest";
import { Jdm } from "../src/jdm.js";
import { _common } from "../src/_common.js";
import { _animation, AnimationOption, keyframe } from "../src/_animation.js";
import { Proto } from "../src/proto"; // aggiorna il path se necessario
// Alias globale per compatibilità
globalThis.JDM = (...args) => new Jdm(...args);
let div;
let animationMock;
beforeEach(() => {
document.body.innerHTML = "";
div = JDM("<div>Test</div>", document.body);
animationMock = {
play: vi.fn(),
cancel: vi.fn(),
onfinish: null,
};
HTMLElement.prototype.animate = vi.fn(() => animationMock);
});
describe("JDM - Class", () => {
it("aggiunge una singola classe", () => {
div.jdm_addClassList("foo");
expect(div.classList.contains("foo")).toBe(true);
});
it("aggiunge un array di classi", () => {
div.jdm_addClassList(["foo", "bar"]);
expect(div.classList.contains("foo") && div.classList.contains("bar")).toBe(true);
});
it("rimuove una singola classe", () => {
div.classList.add("foo", "bar");
div.jdm_removeClassList("foo");
expect(div.classList.contains("foo")).toBe(false);
});
it("rimuove un array di classi", () => {
div.classList.add("foo", "bar");
div.jdm_removeClassList(["foo", "bar"]);
expect(div.classList.contains("foo") && div.classList.contains("bar")).toBe(false);
});
it("toggle di una singola classe", () => {
div.jdm_toggleClassList("bar");
expect(div.classList.contains("bar")).toBe(true);
div.jdm_toggleClassList("bar");
expect(div.classList.contains("bar")).toBe(false);
});
it("toggle di un array di classi", () => {
div.jdm_toggleClassList(["bar", "foo"]);
expect(div.classList.contains("foo") && div.classList.contains("bar")).toBe(true);
div.jdm_toggleClassList(["bar", "foo"]);
expect(div.classList.contains("foo") && div.classList.contains("bar")).toBe(false);
});
it("verifico se c'è una stringa nella classe", () => {
div.jdm_addClassList(["bar", "foo"]);
const resultFindTrue = div.jdm_findClassList("bar");
const resultFindFalse = div.jdm_findClassList("test");
expect(resultFindTrue).toBe(true);
expect(resultFindFalse).toBe(false);
});
it("verifico se c'è un array di stringhe in AND nella classe", () => {
div.jdm_addClassList(["bar", "foo"]);
const resultFindTrue = div.jdm_findClassList(["bar", "foo"]);
const resultFindFalse = div.jdm_findClassList(["bar", "test"]);
const resultFindFalse2 = div.jdm_findClassList(["alt", "test"]);
expect(resultFindTrue).toBe(true);
expect(resultFindFalse).toBe(false);
expect(resultFindFalse2).toBe(false);
});
it("verifico se c'è un array di stringhe in OR nella classe", () => {
div.jdm_addClassList(["bar", "foo"]);
const resultFindTrue = div.jdm_findClassList(["bar", "foo"], true);
const resultFindFalse = div.jdm_findClassList(["bar", "test"], true);
const resultFindFalse2 = div.jdm_findClassList(["alt", "test"], true);
expect(resultFindTrue).toBe(true);
expect(resultFindFalse).toBe(true);
expect(resultFindFalse2).toBe(false);
});
});
describe("JDM - Base", () => {
it("crea un elemento partendo da un selector", () => {
const el = document.createElement("div");
el.id = "foo";
div.appendChild(el);
const newEl = JDM(document.querySelector("#foo"));
expect(newEl.tagName).toBe("DIV");
expect(document.body.contains(div)).toBe(true);
});
it("crea un elemento jdm-element", () => {
const newEl = JDM(null, div);
expect(newEl.tagName).toBe("JDM-ELEMENT");
expect(document.body.contains(div)).toBe(true);
});
it("crea un elemento e lo aggiunge al body", () => {
expect(div.tagName).toBe("DIV");
expect(document.body.contains(div)).toBe(true);
});
it("aggiunge un singolo elemento come figli", () => {
const child = document.createElement("p");
div.jdm_append(child);
expect(div.contains(child)).toBe(true);
});
it("aggiunge un array di elementi come figli", () => {
const child = document.createElement("p");
const child2 = document.createElement("p");
div.jdm_append([child, child2]);
expect(div.contains(child)).toBe(true);
expect(div.contains(child2)).toBe(true);
});
it("preprende un singolo elemento", () => {
const firstElement = document.createElement("div");
firstElement.innerHTML = "<p>primo</p>";
div.appendChild(firstElement);
const prependElement = document.createElement("div");
prependElement.innerHTML = "<p>prependElement</p>";
div.jdm_prepend(prependElement);
expect(div.children[0]).toBe(prependElement);
expect(div.children[1]).toBe(firstElement);
});
it("preprende una lista di elementi", () => {
const firstElement = document.createElement("div");
firstElement.innerHTML = "<p>primo</p>";
div.appendChild(firstElement);
const prependElement1 = document.createElement("div");
prependElement1.innerHTML = "<b>prependElement1</b>";
const prependElement2 = document.createElement("div");
prependElement2.innerHTML = "<i>prependElement2</i>";
div.jdm_prepend([prependElement1, prependElement2]);
expect(div.children[0]).toBe(prependElement2);
expect(div.children[1]).toBe(prependElement1);
expect(div.children[2]).toBe(firstElement);
});
it("inserisco prima un element", () => {
const firstElement = JDM("<div>primo</div>", div);
const secondElement = JDM("<div>second</div>", div);
const newElement = JDM("<div>new</div>");
secondElement.jdm_appendBefore(newElement);
expect(div.children[0]).toBe(firstElement);
expect(div.children[1]).toBe(newElement);
expect(div.children[2]).toBe(secondElement);
});
it("inserisco prima una lista di elementi", () => {
const firstElement = JDM("<div>primo</div>", div);
const secondElement = JDM("<div>second</div>", div);
const newElement = JDM("<div>new</div>");
const newElement2 = JDM("<div>new2</div>");
secondElement.jdm_appendBefore([newElement, newElement2]);
expect(div.children[0]).toBe(firstElement);
expect(div.children[1]).toBe(newElement);
expect(div.children[2]).toBe(newElement2);
expect(div.children[3]).toBe(secondElement);
});
it("svuota un div (o elemento)", () => {
const element = document.createElement("p");
div.appendChild(element);
expect(div.children[0]).toBe(element);
div.jdm_empty();
expect(div.children.length === 0).toBe(true);
});
it("distrugge un elemento", () => {
const el = JDM("<div>foo</div>", div);
expect(div.contains(el)).toBe(true);
expect(el.isConnected).toBe(true);
expect(el.parentNode).toBe(div);
el.jdm_destroy();
expect(div.contains(el)).toBe(false);
expect(el.isConnected).toBe(false);
expect(el.parentNode).toBe(null);
});
it("imposta uno stile inline sull'elemento", () => {
const el = JDM("<div></div>", div);
el.jdm_setStyle("color", "red");
expect(el.style.color).toBe("red");
el.jdm_setStyle("backgroundColor", "blue");
expect(el.style.backgroundColor).toBe("blue");
expect(el.getAttribute("style")).toContain("color: red");
expect(el.getAttribute("style")).toContain("background-color: blue");
});
it("estende il nodo con una proprietà personalizzata", () => {
const data = { id: 1, label: "Test" };
expect(div.myData).toEqual(undefined);
div.jdm_extendNode("myData", data);
expect(div.myData).toEqual({ id: 1, label: "Test" });
expect("myData" in div).toBe(true);
expect(div.hasOwnProperty("myData")).toBe(true);
});
it("imposta il contenuto HTML interno dell'elemento", () => {
expect(div.innerHTML).toBe("Test");
div.jdm_innerHTML("<p>foo</p>");
expect(div.innerHTML).toBe("<p>foo</p>");
expect(div.querySelector("p")?.textContent).toBe("foo");
});
it("propaga innerHTML su elementi non form", () => {
const input = JDM('<input type="text" />', div);
const target = JDM("<div></div>", div);
input.jdm_binding(target, "input", false);
input.value = "test binding";
input.dispatchEvent(new Event("input"));
expect(target.innerHTML).toBe("test binding");
});
it("aggiunge un event listener all'elemento", () => {
const el = JDM("<button>Click</button>", div);
const spy = vi.fn();
el.jdm_addEventListener("click", spy);
el.click();
expect(spy).toHaveBeenCalledTimes(1);
});
it("rimuove un event listener dall'elemento", () => {
const el = JDM("<button>Remove</button>", div);
const spy = vi.fn();
// Aggiungi → Rimuovi → Esegui evento → Verifica che NON venga chiamato
el.addEventListener("click", spy);
el.jdm_removeEventListener("click", spy);
el.click();
expect(spy).not.toHaveBeenCalled();
});
it("estende il nodo con i riferimenti presenti in jdm_childNode", () => {
const parent = JDM("<div></div>", div);
// Crea due nodi figli da associare manualmente
const child1 = document.createElement("span");
const child2 = document.createElement("p");
parent.jdm_childNode = {
titolo: child1,
descrizione: child2,
};
// Applica l'estensione
parent.jdm_extendChildNode();
// Verifica che le proprietà siano state aggiunte
expect(parent.titolo).toBe(child1);
expect(parent.descrizione).toBe(child2);
});
it("non estende nulla se jdm_childNode è assente o vuoto", () => {
const el = JDM("<div></div>", div);
// Non c'è jdm_childNode
expect(() => el.jdm_extendChildNode()).not.toThrow();
// jdm_childNode vuoto
el.jdm_childNode = {};
expect(() => el.jdm_extendChildNode()).not.toThrow();
});
});
describe("JDM - Attribute", () => {
it("imposta un attributo", () => {
div.jdm_setAttribute("data-test", "value");
expect(div.getAttribute("data-test")).toBe("value");
});
it("legge un attributo", () => {
div.setAttribute("data-test", "value");
expect(div.jdm_getAttribute("data-test")).toBe("value");
});
it("imposta un id", () => {
div.jdm_addId("bar");
expect(div.jdm_getAttribute("id")).toBe("bar");
});
it("rimuove un attributo da un elemento", () => {
const el = JDM('<div data-test="ciao"></div>', div);
expect(el.hasAttribute("data-test")).toBe(true);
el.jdm_removeAttribute("data-test");
expect(el.hasAttribute("data-test")).toBe(false);
});
});
describe("JDM - Form", () => {
beforeEach(() => {
document.body.innerHTML = "";
});
it("imposta il valore su diversi tipi di elementi, incluso un form complesso", () => {
// Input checkbox
const checkbox = JDM('<input type="checkbox">', div);
checkbox.jdm_setValue(true);
expect(checkbox.checked).toBe(true);
// Input radio
const radio = JDM('<input type="radio">', div);
radio.jdm_setValue(true);
expect(radio.checked).toBe(true);
// Input number (con moltiplicazione implicita)
const number = JDM('<input type="number">', div);
number.jdm_setValue("42");
expect(number.value).toBe("42");
// Input range (cast a number implicito)
const range = JDM('<input type="range">', div);
range.jdm_setValue("7");
expect(range.value).toBe("7");
// Input text normale
const text = JDM('<input type="text">', div);
text.jdm_setValue("ciao");
expect(text.value).toBe("ciao");
// Form complesso
const formHTML = `
<form>
<input name="name" type="text" />
<input name="age" type="number" />
<input name="active" type="checkbox" />
<input name="colors[]" type="checkbox" value="red" />
<input name="colors[]" type="checkbox" value="green" />
<input name="colors[]" type="checkbox" value="blue" />
<input name="profile[email]" type="text" />
<input name="profile[notifications]" type="checkbox" />
</form>
`;
const form = JDM(formHTML, div);
const data = {
name: "Marco",
age: 35,
active: true,
colors: ["red", "blue"],
profile: {
email: "marco@example.com",
notifications: true,
},
};
form.jdm_setValue(data);
expect(form.elements.name.value).toBe("Marco");
expect(form.elements.age.value).toBe("35");
expect(form.elements.active.checked).toBe(true);
expect(form.querySelector('[value="red"]').checked).toBe(true);
expect(form.querySelector('[value="green"]').checked).toBe(false);
expect(form.querySelector('[value="blue"]').checked).toBe(true);
expect(form.elements["profile[email]"].value).toBe("marco@example.com");
expect(form.elements["profile[notifications]"].checked).toBe(true);
});
it("restituisce correttamente il valore da input, checkbox, radio, select e form complesso", () => {
// Input checkbox
const checkbox = JDM('<input type="checkbox">', div);
checkbox.checked = true;
expect(checkbox.jdm_getValue()).toBe(true);
// Input radio
const radio = JDM('<input type="radio">', div);
radio.checked = false;
expect(radio.jdm_getValue()).toBe(false);
// Select
const select = JDM(
`
<select>
<option value="one">Uno</option>
<option value="two" selected>Due</option>
</select>
`,
div,
);
expect(select.jdm_getValue()).toBe("two");
// Input fallback (text)
const input = JDM('<input type="text">', div);
input.value = "test";
expect(input.jdm_getValue()).toBe("test");
// FORM COMPLESSO
const formHTML = `
<form>
<input name="text" type="text" value="hello" />
<input name="empty" type="text" value="" />
<input name="nullable" type="text" value="null" />
<input name="active" type="checkbox" checked />
<input name="colors[]" type="checkbox" value="red" checked />
<input name="colors[]" type="checkbox" value="green" />
<input name="colors[]" type="checkbox" value="blue" checked />
<input name="profile[email]" type="text" value="foo@example.com" />
<input name="profile[roles][]" type="text" value="admin" />
<input name="profile[roles][]" type="text" value="editor" />
</form>
`;
const form = JDM(formHTML, div);
const result = form.jdm_getValue();
expect(result).toEqual({
text: "hello",
empty: null, // "" → null
nullable: null, // "null" → null
active: "on", // checkbox → "on" (default value)
colors: ["red", "blue"], // array []
profile: {
email: "foo@example.com",
roles: ["admin", "editor"], // array annidato
},
});
});
it("gestisce array senza chiave e chiavi duplicate in jdm_getValue", () => {
const formHTML = `
<form>
<input name="list[]" type="text" value="uno" />
<input name="list[]" type="text" value="due" />
<input name="duplicated" type="text" value="first" />
<input name="duplicated" type="text" value="second" />
</form>
`;
const form = JDM(formHTML, div);
const result = form.jdm_getValue();
expect(result).toEqual({
list: ["uno", "due"],
duplicated: ["first", "second"],
});
});
it("invoca jdm_onSubmit", () => {
const form = JDM("<form><button>Submit</button></form>", document.body);
const mock = vi.fn();
form.jdm_onSubmit(e => {
e.preventDefault();
mock();
});
form.querySelector("button").click();
form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
expect(mock).toHaveBeenCalled();
});
it("svuota un input", () => {
const input = JDM("input", div);
input.value = "bar";
expect(input.value).toBe("bar");
input.jdm_empty();
expect(input.value).toBe("");
});
it("svuota una textarea", () => {
const textarea = JDM("textarea", div);
textarea.value = "bar";
expect(textarea.value).toBe("bar");
textarea.jdm_empty();
expect(textarea.value).toBe("");
});
it("svuota un checkbox", () => {
const checkbox = JDM('<input type="checkbox">', div);
checkbox.checked = true;
expect(checkbox.checked).toBe(true);
checkbox.jdm_empty();
expect(checkbox.checked).toBe(false);
});
it("svuota un radio", () => {
const radio = JDM('<input type="radio">', div);
radio.checked = true;
expect(radio.checked).toBe(true);
radio.jdm_empty();
expect(radio.checked).toBe(false);
});
it("svuota i campi del form", () => {
const formHTML = `
<form>
<input name="text" type="text" value="Marco" />
<input name="number" type="number" value="33" />
<input name="checkbox" type="checkbox" checked />
<select name="select" />
<option value="1" selected>one</option>
<option value="2">two</option>
</select>
</form>
`;
const form = JDM(formHTML, div);
// Simula modifica dei valori prima dello svuotamento
form.elements.text.value = "foo";
form.elements.number.value = 99;
form.elements.checkbox.checked = true;
form.elements.select.value = "2";
expect(form.elements.text.value).toBe("foo");
expect(form.elements.number.value).toBe("99");
expect(form.elements.checkbox.checked).toBe(true);
expect(form.elements.select.value).toBe("2");
// Esegui reset
form.jdm_empty();
expect(form.elements.text.value).toBe("");
expect(form.elements.number.value).toBe("");
expect(form.elements.checkbox.checked).toBe(false);
expect(form.elements.select.value).toBe("");
});
it("valida un input e genera evento 'validate'", () => {
const input = JDM("<input required>", div);
const spy = vi.fn();
input.addEventListener("validate", e => {
// Verifica che venga emesso l'evento
expect(e).toBeInstanceOf(CustomEvent);
expect(typeof e.detail).toBe("boolean");
spy(e.detail);
});
input.jdm_validate();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(false);
input.value = "ok";
input.jdm_validate();
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenLastCalledWith(true);
});
it("propaga il valore da un input all'altro (binding unidirezionale)", () => {
const input1 = JDM('<input type="text" />', div);
const input2 = JDM('<input type="text" />', div);
input1.jdm_binding(input2, "input", false);
input1.value = "foo";
input1.dispatchEvent(new Event("input"));
expect(input2.value).toBe("foo");
});
it("propaga il valore in entrambe le direzioni (binding bidirezionale)", () => {
const input1 = JDM('<input type="text" />', div);
const input2 = JDM('<input type="text" />', div);
input1.jdm_binding(input2); // default: twoWayDataBinding = true
input1.value = "foo";
input1.dispatchEvent(new Event("input"));
expect(input2.value).toBe("foo");
input2.value = "bar";
input2.dispatchEvent(new Event("input"));
expect(input1.value).toBe("bar");
});
});
describe("JDM - Event", () => {
it("registra un event listener per 'input'", () => {
const input = JDM('<input type="text">', div);
const spy = vi.fn();
input.jdm_onInput(spy);
input.value = "foo";
input.dispatchEvent(new Event("input"));
expect(spy).toHaveBeenCalledTimes(1);
});
it("non lancia errore se la funzione non viene fornita", () => {
const input = JDM('<input type="text">', div);
expect(() => input.jdm_onInput()).not.toThrow();
});
it("registra un event listener per 'change'", () => {
const select = JDM(
`
<select>
<option value="a">A</option>
<option value="b">B</option>
</select>
`,
div,
);
const spy = vi.fn();
select.jdm_onChange(spy);
select.value = "b";
select.dispatchEvent(new Event("change"));
expect(spy).toHaveBeenCalledTimes(1);
});
it("non lancia errore se nessuna funzione viene passata", () => {
const input = JDM('<input type="text">', div);
expect(() => input.jdm_onChange()).not.toThrow();
});
it("registra un event listener per 'select'", () => {
const input = JDM('<input type="text" value="ciao">', div);
const spy = vi.fn();
input.jdm_onSelect(spy);
// Seleziona del testo
input.focus();
input.setSelectionRange(0, 2);
input.dispatchEvent(new Event("select"));
expect(spy).toHaveBeenCalledTimes(1);
});
it("non lancia errore se nessuna funzione viene passata", () => {
const input = JDM('<input type="text">', div);
expect(() => input.jdm_onSelect()).not.toThrow();
});
it("registra un event listener debounce per 'input'", async () => {
const input = JDM('<input type="text">', div);
const spy = vi.fn();
const delay = 100;
input.jdm_onDebounce(spy, delay, "input");
input.value = "a";
input.dispatchEvent(new Event("input"));
input.value = "ab";
input.dispatchEvent(new Event("input"));
input.value = "abc";
input.dispatchEvent(new Event("input"));
await new Promise(r => setTimeout(r, delay + 20));
expect(spy).toHaveBeenCalledTimes(1);
});
it("usa il valore di timeout predefinito se non fornito", async () => {
const input = JDM('<input type="text">', div);
const spy = vi.fn();
input.jdm_onDebounce(spy);
input.dispatchEvent(new Event("input"));
await new Promise(r => setTimeout(r, 320));
expect(spy).toHaveBeenCalledTimes(1);
});
it("usa 'input' come metodo di default", async () => {
const input = JDM('<input type="text">', div);
const spy = vi.fn();
input.jdm_onDebounce(spy);
input.dispatchEvent(new Event("input"));
await new Promise(r => setTimeout(r, 320));
expect(spy).toHaveBeenCalled();
});
it("non lancia errore se nessuna funzione viene passata", async () => {
const input = JDM('<input type="text">', div);
expect(() => input.jdm_onDebounce()).not.toThrow();
input.dispatchEvent(new Event("input"));
await new Promise(r => setTimeout(r, 320));
});
it("registra un event listener per 'click'", () => {
const button = JDM("<button>Cliccami</button>", div);
const spy = vi.fn();
button.jdm_onClick(spy);
button.dispatchEvent(new MouseEvent("click"));
expect(spy).toHaveBeenCalledTimes(1);
});
it("non lancia errore se nessuna funzione viene passata", () => {
const button = JDM("<button>Cliccami</button>", div);
expect(() => button.jdm_onClick()).not.toThrow();
button.dispatchEvent(new MouseEvent("click"));
});
it("ritorna il nodo HTML", () => {
const button = JDM("<button>Cliccami</button>", div);
const returned = button.jdm_onClick();
expect(returned).toBeInstanceOf(HTMLElement);
expect(returned.tagName).toBe("BUTTON");
});
it("registra un event listener per 'contextmenu'", () => {
const el = JDM("<div>Destro</div>", div);
const spy = vi.fn();
el.jdm_onRightClick(spy);
el.dispatchEvent(new MouseEvent("contextmenu"));
expect(spy).toHaveBeenCalledTimes(1);
});
it("non lancia errore se nessuna funzione viene passata", () => {
const el = JDM("<div>Destro</div>", div);
expect(() => el.jdm_onRightClick()).not.toThrow();
el.dispatchEvent(new MouseEvent("contextmenu"));
});
it("ritorna il nodo HTML", () => {
const el = JDM("<div>Destro</div>", div);
const returned = el.jdm_onRightClick();
expect(returned).toBeInstanceOf(HTMLElement);
expect(returned.tagName).toBe("DIV");
});
it("registra un event listener per 'dblclick'", () => {
const el = JDM("<div>Doppio click</div>", div);
const spy = vi.fn();
el.jdm_onDoubleClick(spy);
el.dispatchEvent(new MouseEvent("dblclick"));
expect(spy).toHaveBeenCalledTimes(1);
});
it("non lancia errore se nessuna funzione viene passata", () => {
const el = JDM("<div>Doppio click</div>", div);
expect(() => el.jdm_onDoubleClick()).not.toThrow();
el.dispatchEvent(new MouseEvent("dblclick"));
});
it("ritorna il nodo HTML", () => {
const el = JDM("<div>Doppio click</div>", div);
const returned = el.jdm_onDoubleClick();
expect(returned).toBeInstanceOf(HTMLElement);
expect(returned.tagName).toBe("DIV");
});
it("registra un event listener per 'invalid'", () => {
const form = JDM("<form><input required></form>", div);
const input = form.querySelector("input");
const spy = vi.fn();
JDM(input).jdm_onInvalid(spy);
// L'evento 'invalid' non si attiva programmaticamente tramite .dispatchEvent
// ma possiamo forzarlo così:
input.dispatchEvent(new Event("invalid", { bubbles: true, cancelable: true }));
expect(spy).toHaveBeenCalledTimes(1);
});
it("non lancia errore se nessuna funzione viene passata", () => {
const input = JDM("<input required>", div);
expect(() => input.jdm_onInvalid()).not.toThrow();
input.dispatchEvent(new Event("invalid", { bubbles: true, cancelable: true }));
});
it("ritorna il nodo HTML", () => {
const input = JDM("<input required>", div);
const returned = input.jdm_onInvalid();
expect(returned).toBeInstanceOf(HTMLElement);
expect(returned.tagName).toBe("INPUT");
});
it("registra un event listener per 'load'", () => {
const img = JDM('<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB">', div);
const spy = vi.fn();
img.jdm_onLoad(spy);
// Simuliamo il caricamento dell'immagine
img.dispatchEvent(new Event("load"));
expect(spy).toHaveBeenCalledTimes(1);
});
it("non lancia errore se nessuna funzione viene passata", () => {
const img = JDM("<img>", div);
expect(() => img.jdm_onLoad()).not.toThrow();
img.dispatchEvent(new Event("load"));
});
it("ritorna il nodo HTML", () => {
const img = JDM("<img>", div);
const returned = img.jdm_onLoad();
expect(returned).toBeInstanceOf(HTMLElement);
expect(returned.tagName).toBe("IMG");
});
it("registra un event listener per 'error'", () => {
const img = JDM("<img>", div);
const spy = vi.fn();
img.jdm_onError(spy);
// Simuliamo un errore nel caricamento dell'immagine
img.dispatchEvent(new Event("error"));
expect(spy).toHaveBeenCalledTimes(1);
});
it("non lancia errore se nessuna funzione viene passata", () => {
const img = JDM("<img>", div);
expect(() => img.jdm_onError()).not.toThrow();
img.dispatchEvent(new Event("error"));
});
it("ritorna il nodo HTML", () => {
const img = JDM("<img>", div);
const returned = img.jdm_onError();
expect(returned).toBeInstanceOf(HTMLElement);
expect(returned.tagName).toBe("IMG");
});
});
describe("JDM - Commons", () => {
it("dovrebbe chiamare la funzione dopo il timeout", async () => {
const spy = vi.fn();
const debouncedFunc = _common.debounce(spy, 100); // timeout di 100ms
debouncedFunc();
debouncedFunc();
debouncedFunc();
// Verifica che la funzione non sia stata chiamata immediatamente
expect(spy).not.toHaveBeenCalled();
// Attendere 150ms per assicurarsi che il debouncedFunc venga chiamato solo una volta
await new Promise(resolve => setTimeout(resolve, 150));
expect(spy).toHaveBeenCalledTimes(1);
});
it("dovrebbe usare il timeout di default se non fornito", async () => {
const spy = vi.fn();
const debouncedFunc = _common.debounce(spy);
debouncedFunc();
debouncedFunc();
// Verifica che la funzione non sia stata chiamata immediatamente
expect(spy).not.toHaveBeenCalled();
// Attendere il timeout di default di 300ms
await new Promise(resolve => setTimeout(resolve, 350));
expect(spy).toHaveBeenCalledTimes(1);
});
it("dovrebbe creare un CustomEvent e dispatcharlo sul nodo", () => {
const node = document.createElement("div");
const spy = vi.fn();
node.addEventListener("custom-event", e => {
spy(e.detail);
});
const data = { key: "value" };
_common.genEvent(node, "custom-event", data);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(data);
});
it("dovrebbe far propagare l'evento ai genitori se 'propagateToParents' è true", () => {
const parent = document.createElement("div");
const child = document.createElement("div");
parent.appendChild(child);
const spy = vi.fn();
parent.addEventListener("custom-event", e => {
spy(e.detail);
});
const data = { key: "value" };
_common.genEvent(child, "custom-event", data);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(data);
});
it("non fa propagare l'evento ai genitori se 'propagateToParents' è false", () => {
const parent = document.createElement("div");
const child = document.createElement("div");
parent.appendChild(child);
const spy = vi.fn();
parent.addEventListener("custom-event", e => {
spy(e.detail);
});
const data = { key: "value" };
_common.genEvent(child, "custom-event", data, false);
expect(spy).not.toHaveBeenCalled();
});
it("dovrebbe restituire il tag name in minuscolo", () => {
const div = document.createElement("div");
const span = document.createElement("span");
expect(_common.getTag(div)).toBe("div");
expect(_common.getTag(span)).toBe("span");
});
it("dovrebbe restituire undefined per nodi senza tagName", () => {
const textNode = document.createTextNode("test");
expect(_common.getTag(textNode)).toBeUndefined();
});
});
describe("JDM - Animation", () => {
it("utilizzare i valori di default", () => {
const animationOption = new AnimationOption();
expect(animationOption.duration).toBe(250);
expect(animationOption.easing).toBe("ease-in-out");
expect(animationOption.fill).toBe("forwards");
expect(animationOption.delay).toBe(0);
expect(animationOption.composite).toBe("replace");
expect(animationOption.direction).toBe("normal");
expect(animationOption.iterations).toBe(1);
});
it("permettere di sovrascrivere i valori di default", () => {
const animationOption = new AnimationOption(500, "ease", "both", 100, "accumulate", "reverse", 3);
expect(animationOption.duration).toBe(500);
expect(animationOption.easing).toBe("ease");
expect(animationOption.fill).toBe("both");
expect(animationOption.delay).toBe(100);
expect(animationOption.composite).toBe("accumulate");
expect(animationOption.direction).toBe("reverse");
expect(animationOption.iterations).toBe(3);
});
it("utilizzare il valore di default per i parametri non specificati", () => {
const animationOption = new AnimationOption(400, "ease-out");
expect(animationOption.duration).toBe(400);
expect(animationOption.easing).toBe("ease-out");
expect(animationOption.fill).toBe("forwards"); // Valore di default
expect(animationOption.delay).toBe(0); // Valore di default
expect(animationOption.composite).toBe("replace"); // Valore di default
expect(animationOption.direction).toBe("normal"); // Valore di default
expect(animationOption.iterations).toBe(1); // Valore di default
});
it(" cancellare tutte le animazioni e resettare gli stili", () => {
const fn = vi.fn();
const node = JDM("<div>foo</div>", div).jdm_fadeIn();
const result = node.jdm_clearAnimations();
expect(node.style.animation).toBe("none");
expect(node.style.transition).toBe("none");
expect(node.style.transform).toBe("");
expect(node.style.opacity).toBe("");
expect(result).toBe(node);
});
it.each(Object.entries(keyframe))("dovrebbe chiamare animate e callback in %s", async (method, keyframeFn) => {
const callbackFn = vi.fn();
let frame;
if (method === "rotation") {
frame = keyframeFn(90);
div[`jdm_${method}`](callbackFn, 90);
} else {
frame = keyframeFn;
div[`jdm_${method}`](callbackFn);
}
expect(div.animate).toHaveBeenCalledWith(frame, expect.any(Object));
animationMock.onfinish?.();
expect(callbackFn).toHaveBeenCalledTimes(1);
});
it.each(Object.entries(keyframe))(" %s ritorna un elemento HTMLElement", async (method, keyframeFn) => {
let response = null;
let frame = null;
if (method === "rotation") {
frame = keyframeFn(90);
response = div[`jdm_${method}`](vi.fn(), 90);
} else {
frame = keyframeFn;
response = div[`jdm_${method}`](vi.fn());
}
expect(response).toBe(div);
expect(response instanceof HTMLElement).toBe(true);
});
});
describe("JDM - Proto", () => {
describe("String.prototype.toBoolean", () => {
it("restituisce true per 'true', '1', 'yes'", () => {
expect("true".toBoolean()).toBe(true);
expect("1".toBoolean()).toBe(true);
expect("yes".toBoolean()).toBe(true);
expect("TrUe".toBoolean()).toBe(true); // case-insensitive
expect(" YES ".toBoolean()).toBe(true); // con spazi
});
it("restituisce false per 'false', '0', 'no'", () => {
expect("false".toBoolean()).toBe(false);
expect("0".toBoolean()).toBe(false);
expect("no".toBoolean()).toBe(false);
expect("FaLsE".toBoolean()).toBe(false);
expect(" NO ".toBoolean()).toBe(false);
});
it("genera un errore per stringhe non valide", () => {
expect(() => "maybe".toBoolean()).toThrow("Invalid boolean string: maybe");
expect(() => "".toBoolean()).toThrow("Invalid boolean string: ");
});
});
describe("String.prototype.toCapitalize", () => {
it("mette la prima lettera in maiuscolo", () => {
expect("ciao".toCapitalize()).toBe("Ciao");
expect("Ciao".toCapitalize()).toBe("Ciao");
expect("c".toCapitalize()).toBe("C");
expect("".toCapitalize()).toBe("");
});
});
describe("Number.prototype.toBoolean", () => {
it("restituisce true per 1", () => {
expect((1).toBoolean()).toBe(true);
});
it("restituisce false per 0", () => {
expect((0).toBoolean()).toBe(false);
});
it("genera un errore per altri numeri", () => {
expect(() => (2).toBoolean()).toThrow("Invalid boolean string: 2");
expect(() => (-1).toBoolean()).toThrow("Invalid boolean string: -1");
});
});
});