19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -1079,6 +1079,29 @@ describe("waitFor...", () => {
await click(".test");
expect.verifySteps(["before", "in catch", "updatecontent"]);
});
test("waitFor support promise is 'undefined'", async () => {
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
_root: { "t-on-click": this.onClick },
};
async onClick() {
await this.waitFor(undefined);
expect.step("clicked");
}
updateContent() {
expect.step("updatecontent");
super.updateContent();
}
}
await startInteraction(Test, TemplateTest);
expect.verifySteps([]);
await click(".test");
expect.verifySteps(["clicked", "updatecontent"]);
});
});
describe("waitForTimeout", () => {
@ -1903,6 +1926,41 @@ describe("t-att and t-out", () => {
expect("span").not.toHaveAttribute("animal");
expect("span").toHaveAttribute("egg", "mysterious");
});
test("t-out-... resets at stop", async () => {
class Test extends Interaction {
static selector = "span";
dynamicContent = {
_root: { "t-out": () => "colibri" },
};
}
const { core } = await startInteraction(Test, TemplateTest);
expect("span").toHaveText("colibri");
core.stopInteractions();
expect("span").toHaveText("coucou");
});
test("t-out-... restores all values on stop", async () => {
class Test extends Interaction {
static selector = "div";
dynamicContent = {
span: { "t-out": () => "colibri" },
};
}
const { core } = await startInteraction(
Test,
`
<div>
<span>penguin</span>
<span>ostrich</span>
</div>
`
);
expect("span").toHaveText("colibri");
core.stopInteractions();
expect("span:first").toHaveText("penguin");
expect("span:last").toHaveText("ostrich");
});
});
describe("components", () => {
@ -2436,23 +2494,64 @@ describe("locked", () => {
expect(queryFirst("span")).toBe(null);
});
test("locked add a loading icon when the execution takes more than 400ms", async () => {
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
button: {
"t-on-click": this.locked(this.onClickLong, true),
},
};
async onClickLong() {
await new Promise((resolve) => setTimeout(resolve, 5000));
describe("loading effect", () => {
let handlerDuration = 5000;
beforeEach(async () => {
class Test extends Interaction {
static selector = ".test";
dynamicContent = {
button: {
"t-on-click": this.locked(this.onClickLong, true),
},
};
async onClickLong() {
await new Promise((resolve) => setTimeout(resolve, handlerDuration));
expect.step("handler done");
}
}
}
await startInteraction(Test, TemplateTestDoubleButton);
expect(queryFirst("span")).toBe(null);
await click("button");
await advanceTime(500);
expect(queryFirst("span")).not.toBe(null);
await startInteraction(Test, TemplateTestDoubleButton, {
waitForStart: false,
});
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
if ([...m.addedNodes].some((node) => node.tagName === "SPAN")) {
expect.step("loading added");
}
}
});
observer.observe(queryFirst("button"), { childList: true });
});
test("should be added when the handler takes more than 400ms", async () => {
expect("span.fa-spin").toHaveCount(0);
await click("button");
// Advance time more than the debounce delay of makeButtonHandler
// (400ms) but less than the handler duration.
await advanceTime(500);
expect("span.fa-spin").toHaveCount(1);
expect.verifySteps(["loading added"]);
await advanceTime(handlerDuration);
expect.verifySteps(["handler done"]);
});
test("should never be added when the handler takes less than 400ms", async () => {
handlerDuration = 100;
expect("span.fa-spin").toHaveCount(0);
await click("button");
// Advance time more than the handler duration but less than the
// debounce delay of makeButtonHandler (400ms).
await advanceTime(200);
expect.verifySteps(["handler done"]);
await advanceTime(1000);
expect("span.fa-spin").toHaveCount(0);
expect.verifySteps([], {
message:
"Loading effect should never be added in the DOM for handlers shorter than 400ms",
});
});
});
test("locked automatically binds functions", async () => {

View file

@ -160,6 +160,33 @@ test("start interactions with selectorHas", async () => {
expect(core.interactions[0].interaction.el).toBe(queryOne(".test:has(.inner)"));
});
test("stop interactions with selectorHas", async () => {
class Test extends Interaction {
static selector = ".test";
static selectorHas = ".inner";
start() {
expect.step("start");
}
destroy() {
expect.step("destroy");
}
}
const { core } = await startInteraction(
Test,
`
<div class="test"><div class="inner"></div><div class="other"></div></div>
`
);
expect.verifySteps(["start"]);
queryOne(".inner").remove();
core.stopInteractions(queryOne(".other")); // on sub-element of the Interaction root
expect.verifySteps(["destroy"]);
});
test("start interactions even if there is a crash when evaluating selectorNotHas", async () => {
class Boom extends Interaction {
static selector = ".test";
@ -216,6 +243,30 @@ test("start interactions with selectorNotHas", async () => {
expect(core.interactions[0].interaction.el).toBe(queryOne(".test:not(:has(.inner))"));
});
test("stop interactions with selectorNotHas", async () => {
class Test extends Interaction {
static selector = ".test";
static selectorNotHas = ".inner";
start() {
expect.step("start");
}
destroy() {
expect.step("destroy");
}
}
const { core } = await startInteraction(Test, `<div class="test"></div>`);
expect.verifySteps(["start"]);
const div = document.createElement("div");
div.className = "inner";
queryOne(".test").appendChild(div);
core.stopInteractions(div); // on sub-element of the Interaction root (added node)
expect.verifySteps(["destroy"]);
});
test("recover from error as much as possible when applying dynamiccontent", async () => {
let a = "a";
let b = "b";