htmx/test/attributes/hx-swap.js
Thomas Cowart 078d5da5b4
Update parseInterval to handle "0" correctly (#1835)
* Update parseInterval to handle "0" correctly

When a parameter like "0ms" is passed in to parseInterval it gets parsed to 0.
Previously this would result in a return value of "undefined" because 0 is falsy
and thus the `return 0 || undefined` statements return undefined.

The purpose of the form `parseFloat(str) || undefined` was to return "undefined" if
parseFloat failed (parseFloat returns NaN, a falsy value, if it can't parse its
argument). Unfortunately, as mentioned, parseFloat can also succeed and return a
falsy value -- when the argument is "0" (or "0.0", etc.). So the new code, rather
than depending on the falsiness of the result of parseFloat, explicitly checks for
a NaN.

* Adds some semicolons

Adds some semicolons to parseInterval (and tests) for consistency.

* Add one more parseInterval test for "0"

Adds test test to make sure parseInterval works on "0".

* Adds functional tests for every, swap, settle, throttle, and delay

* Explcitly check that setTimeout values are > 0

These values come from user settings that are read from parseInterval,
so they could be a number or undefined.

If the value being checked is > 0 setTimeout will be called with some
associated function. If the value is 0 or 'undefined' the associated function
will be called immediately ('undefined' is not greater than 0).

* Change '!== undefined' to '> 0'

`pollInterval !== undefined` is a subtly different conditional than just `pollInterval` or `pollInterval > 0` (which are equivalent). Changes the conditional to `pollInterval > 0` so as to not change the behavior but also be more explicit in the test.
2023-12-20 15:46:04 -07:00

375 lines
14 KiB
JavaScript

describe("hx-swap attribute", function(){
beforeEach(function() {
this.server = makeServer();
clearWorkArea();
});
afterEach(function() {
this.server.restore();
clearWorkArea();
});
it('swap innerHTML properly', function()
{
this.server.respondWith("GET", "/test", '<a hx-get="/test2">Click Me</a>');
this.server.respondWith("GET", "/test2", "Clicked!");
var div = make('<div hx-get="/test"></div>')
div.click();
this.server.respond();
div.innerHTML.should.equal('<a hx-get="/test2">Click Me</a>');
var a = div.querySelector('a');
a.click();
this.server.respond();
a.innerHTML.should.equal('Clicked!');
});
it('swap outerHTML properly', function()
{
this.server.respondWith("GET", "/test", '<a id="a1" hx-get="/test2">Click Me</a>');
this.server.respondWith("GET", "/test2", "Clicked!");
var div = make('<div id="d1" hx-get="/test" hx-swap="outerHTML"></div>')
div.click();
should.equal(byId("d1"), div);
this.server.respond();
should.equal(byId("d1"), null);
byId("a1").click();
this.server.respond();
byId("a1").innerHTML.should.equal('Clicked!');
});
it('swap beforebegin properly', function()
{
var i = 0;
this.server.respondWith("GET", "/test", function(xhr){
i++;
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
});
this.server.respondWith("GET", "/test2", "*");
var div = make('<div hx-get="/test" hx-swap="beforebegin">*</div>')
var parent = div.parentElement;
div.click();
this.server.respond();
div.innerText.should.equal("*");
removeWhiteSpace(parent.innerText).should.equal("1*");
byId("a1").click();
this.server.respond();
removeWhiteSpace(parent.innerText).should.equal("**");
div.click();
this.server.respond();
div.innerText.should.equal("*");
removeWhiteSpace(parent.innerText).should.equal("*2*");
byId("a2").click();
this.server.respond();
removeWhiteSpace(parent.innerText).should.equal("***");
});
it('swap afterbegin properly', function()
{
var i = 0;
this.server.respondWith("GET", "/test", function(xhr){
i++;
xhr.respond(200, {}, "" + i);
});
var div = make('<div hx-get="/test" hx-swap="afterbegin">*</div>')
div.click();
this.server.respond();
div.innerText.should.equal("1*");
div.click();
this.server.respond();
div.innerText.should.equal("21*");
div.click();
this.server.respond();
div.innerText.should.equal("321*");
});
it('swap afterbegin properly with no initial content', function()
{
var i = 0;
this.server.respondWith("GET", "/test", function(xhr){
i++;
xhr.respond(200, {}, "" + i);
});
var div = make('<div hx-get="/test" hx-swap="afterbegin"></div>')
div.click();
this.server.respond();
div.innerText.should.equal("1");
div.click();
this.server.respond();
div.innerText.should.equal("21");
div.click();
this.server.respond();
div.innerText.should.equal("321");
});
it('swap afterend properly', function()
{
var i = 0;
this.server.respondWith("GET", "/test", function(xhr){
i++;
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
});
this.server.respondWith("GET", "/test2", "*");
var div = make('<div hx-get="/test" hx-swap="afterend">*</div>')
var parent = div.parentElement;
div.click();
this.server.respond();
div.innerText.should.equal("*");
removeWhiteSpace(parent.innerText).should.equal("*1");
byId("a1").click();
this.server.respond();
removeWhiteSpace(parent.innerText).should.equal("**");
div.click();
this.server.respond();
div.innerText.should.equal("*");
removeWhiteSpace(parent.innerText).should.equal("*2*");
byId("a2").click();
this.server.respond();
removeWhiteSpace(parent.innerText).should.equal("***");
});
it('handles beforeend properly', function()
{
var i = 0;
this.server.respondWith("GET", "/test", function(xhr){
i++;
xhr.respond(200, {}, "" + i);
});
var div = make('<div hx-get="/test" hx-swap="beforeend">*</div>')
div.click();
this.server.respond();
div.innerText.should.equal("*1");
div.click();
this.server.respond();
div.innerText.should.equal("*12");
div.click();
this.server.respond();
div.innerText.should.equal("*123");
});
it('handles beforeend properly with no initial content', function()
{
var i = 0;
this.server.respondWith("GET", "/test", function(xhr){
i++;
xhr.respond(200, {}, "" + i);
});
var div = make('<div hx-get="/test" hx-swap="beforeend"></div>')
div.click();
this.server.respond();
div.innerText.should.equal("1");
div.click();
this.server.respond();
div.innerText.should.equal("12");
div.click();
this.server.respond();
div.innerText.should.equal("123");
});
it('properly parses various swap specifications', function(){
var swapSpec = htmx._("getSwapSpecification"); // internal function for swap spec
swapSpec(make("<div/>")).swapStyle.should.equal("innerHTML")
swapSpec(make("<div hx-swap='innerHTML'/>")).swapStyle.should.equal("innerHTML")
swapSpec(make("<div hx-swap='innerHTML'/>")).swapDelay.should.equal(0)
swapSpec(make("<div hx-swap='innerHTML'/>")).settleDelay.should.equal(0) // set to 0 in tests
swapSpec(make("<div hx-swap='innerHTML swap:10'/>")).swapDelay.should.equal(10)
swapSpec(make("<div hx-swap='innerHTML swap:0'/>")).swapDelay.should.equal(0)
swapSpec(make("<div hx-swap='innerHTML swap:0ms'/>")).swapDelay.should.equal(0)
swapSpec(make("<div hx-swap='innerHTML settle:10'/>")).settleDelay.should.equal(10)
swapSpec(make("<div hx-swap='innerHTML settle:0'/>")).settleDelay.should.equal(0)
swapSpec(make("<div hx-swap='innerHTML settle:0s'/>")).settleDelay.should.equal(0)
swapSpec(make("<div hx-swap='innerHTML swap:10 settle:11'/>")).swapDelay.should.equal(10)
swapSpec(make("<div hx-swap='innerHTML swap:10 settle:11'/>")).settleDelay.should.equal(11)
swapSpec(make("<div hx-swap='innerHTML settle:11 swap:10'/>")).swapDelay.should.equal(10)
swapSpec(make("<div hx-swap='innerHTML settle:11 swap:10'/>")).settleDelay.should.equal(11)
swapSpec(make("<div hx-swap='innerHTML settle:0 swap:0'/>")).settleDelay.should.equal(0)
swapSpec(make("<div hx-swap='innerHTML settle:0 swap:0'/>")).settleDelay.should.equal(0)
swapSpec(make("<div hx-swap='innerHTML settle:0s swap:0ms'/>")).settleDelay.should.equal(0)
swapSpec(make("<div hx-swap='innerHTML settle:0s swap:0ms'/>")).settleDelay.should.equal(0)
swapSpec(make("<div hx-swap='innerHTML nonsense settle:11 swap:10'/>")).settleDelay.should.equal(11)
swapSpec(make("<div hx-swap='innerHTML nonsense settle:11 swap:10 '/>")).settleDelay.should.equal(11)
swapSpec(make("<div hx-swap='swap:10'/>")).swapStyle.should.equal("innerHTML")
swapSpec(make("<div hx-swap='swap:10'/>")).swapDelay.should.equal(10)
swapSpec(make("<div hx-swap='swap:0'/>")).swapDelay.should.equal(0);
swapSpec(make("<div hx-swap='swap:0s'/>")).swapDelay.should.equal(0);
swapSpec(make("<div hx-swap='settle:10'/>")).swapStyle.should.equal("innerHTML")
swapSpec(make("<div hx-swap='settle:10'/>")).settleDelay.should.equal(10)
swapSpec(make("<div hx-swap='settle:0'/>")).settleDelay.should.equal(0)
swapSpec(make("<div hx-swap='settle:0s'/>")).settleDelay.should.equal(0)
swapSpec(make("<div hx-swap='swap:10 settle:11'/>")).swapStyle.should.equal("innerHTML")
swapSpec(make("<div hx-swap='swap:10 settle:11'/>")).swapDelay.should.equal(10)
swapSpec(make("<div hx-swap='swap:10 settle:11'/>")).settleDelay.should.equal(11)
swapSpec(make("<div hx-swap='swap:0s settle:0'/>")).swapDelay.should.equal(0)
swapSpec(make("<div hx-swap='swap:0s settle:0'/>")).settleDelay.should.equal(0)
swapSpec(make("<div hx-swap='settle:11 swap:10'/>")).swapStyle.should.equal("innerHTML")
swapSpec(make("<div hx-swap='settle:11 swap:10'/>")).swapDelay.should.equal(10)
swapSpec(make("<div hx-swap='settle:11 swap:10'/>")).settleDelay.should.equal(11)
swapSpec(make("<div hx-swap='settle:0s swap:10'/>")).swapDelay.should.equal(10)
swapSpec(make("<div hx-swap='settle:0s swap:10'/>")).settleDelay.should.equal(0)
swapSpec(make("<div hx-swap='customstyle settle:11 swap:10'/>")).swapStyle.should.equal("customstyle")
})
it('works with a swap delay', function(done) {
this.server.respondWith("GET", "/test", "Clicked!");
var div = make("<div hx-get='/test' hx-swap='innerHTML swap:10ms'></div>");
div.click();
this.server.respond();
div.innerText.should.equal("");
setTimeout(function () {
div.innerText.should.equal("Clicked!");
done();
}, 30);
});
it("works immediately with no swap delay", function (done) {
this.server.respondWith("GET", "/test", "Clicked!");
var div = make(
"<div hx-get='/test' hx-swap='innerHTML swap:0ms'></div>"
);
div.click();
this.server.respond();
div.innerText.should.equal("Clicked!");
done();
});
it('works with a settle delay', function(done) {
this.server.respondWith("GET", "/test", "<div id='d1' class='foo' hx-get='/test' hx-swap='outerHTML settle:10ms'></div>");
var div = make("<div id='d1' hx-get='/test' hx-swap='outerHTML settle:10ms'></div>");
div.click();
this.server.respond();
div.classList.contains('foo').should.equal(false);
setTimeout(function () {
byId('d1').classList.contains('foo').should.equal(true);
done();
}, 30);
});
it("works with no settle delay", function (done) {
this.server.respondWith(
"GET",
"/test",
"<div id='d1' class='foo' hx-get='/test' hx-swap='outerHTML settle:0ms'></div>"
);
var div = make(
"<div id='d1' hx-get='/test' hx-swap='outerHTML settle:0ms'></div>"
);
div.click();
this.server.respond();
div.classList.contains("foo").should.equal(false);
setTimeout(function () {
byId("d1").classList.contains("foo").should.equal(true);
done();
}, 30);
});
it('swap outerHTML properly w/ data-* prefix', function()
{
this.server.respondWith("GET", "/test", '<a id="a1" data-hx-get="/test2">Click Me</a>');
this.server.respondWith("GET", "/test2", "Clicked!");
var div = make('<div id="d1" data-hx-get="/test" data-hx-swap="outerHTML"></div>')
div.click();
should.equal(byId("d1"), div);
this.server.respond();
should.equal(byId("d1"), null);
byId("a1").click();
this.server.respond();
byId("a1").innerHTML.should.equal('Clicked!');
});
it('swap none works properly', function()
{
this.server.respondWith("GET", "/test", 'Ooops, swapped');
var div = make('<div hx-swap="none" hx-get="/test">Foo</div>')
div.click();
this.server.respond();
div.innerHTML.should.equal('Foo');
});
it('swap outerHTML does not trigger htmx:afterSwap on original element', function()
{
this.server.respondWith("GET", "/test", 'Clicked!');
var div = make('<div id="d1" hx-get="/test" hx-swap="outerHTML"></div>')
div.addEventListener("htmx:afterSwap", function(){
count++;
})
div.click();
var count = 0;
should.equal(byId("d1"), div);
this.server.respond();
should.equal(byId("d1"), null);
count.should.equal(0);
});
it('swap delete works properly', function()
{
this.server.respondWith("GET", "/test", 'Oops, deleted!');
var div = make('<div id="d1" hx-swap="delete" hx-get="/test">Foo</div>')
div.click();
this.server.respond();
should.equal(byId("d1"), null);
});
it('in presence of bad swap spec, it uses the default swap strategy', function()
{
var initialSwapStyle = htmx.config.defaultSwapStyle;
htmx.config.defaultSwapStyle = "outerHTML";
try {
this.server.respondWith("GET", "/test", "Clicked!");
var div = make('<div><button id="b1" hx-swap="foo" hx-get="/test">Initial</button></div>')
var b1 = byId("b1");
b1.click();
this.server.respond();
div.innerHTML.should.equal('Clicked!');
} finally {
htmx.config.defaultSwapStyle = initialSwapStyle;
}
});
it('hx-swap ignoreTitle works', function()
{
window.document.title = "Test Title";
this.server.respondWith("GET", "/test", function (xhr) {
xhr.respond(200, {}, "<title class=''>htmx rocks!</title>Clicked!");
});
var btn = make('<button hx-get="/test" hx-swap="innerHTML ignoreTitle:true">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerText.should.equal("Clicked!");
window.document.title.should.equal("Test Title");
});
})