From 057af833e468602b86f68fcb7e4fd1fa9acfa503 Mon Sep 17 00:00:00 2001 From: Thomas Cowart Date: Tue, 26 Sep 2023 21:44:03 -0400 Subject: [PATCH 1/6] 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. --- src/htmx.js | 18 ++++++++++-------- test/core/internals.js | 4 ++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/htmx.js b/src/htmx.js index 0d8c0cfb7..c75a73f58 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -133,16 +133,18 @@ return (function () { if (str == undefined) { return undefined } + + let interval = NaN; if (str.slice(-2) == "ms") { - return parseFloat(str.slice(0,-2)) || undefined - } - if (str.slice(-1) == "s") { - return (parseFloat(str.slice(0,-1)) * 1000) || undefined - } - if (str.slice(-1) == "m") { - return (parseFloat(str.slice(0,-1)) * 1000 * 60) || undefined + interval = parseFloat(str.slice(0, -2)); + } else if (str.slice(-1) == "s") { + interval = parseFloat(str.slice(0, -1)) * 1000; + } else if (str.slice(-1) == "m") { + interval = parseFloat(str.slice(0, -1)) * 1000 * 60; + } else { + interval = parseFloat(str); } - return parseFloat(str) || undefined + return isNaN(interval) ? undefined : interval; } /** diff --git a/test/core/internals.js b/test/core/internals.js index 1de1fb982..3ce015397 100644 --- a/test/core/internals.js +++ b/test/core/internals.js @@ -79,6 +79,10 @@ describe("Core htmx internals Tests", function() { chai.expect(htmx.parseInterval("1s")).to.be.equal(1000) chai.expect(htmx.parseInterval("1.5s")).to.be.equal(1500) chai.expect(htmx.parseInterval("2s")).to.be.equal(2000) + chai.expect(htmx.parseInterval("0ms")).to.be.equal(0); + chai.expect(htmx.parseInterval("0s")).to.be.equal(0); + chai.expect(htmx.parseInterval("0m")).to.be.equal(0); + chai.expect(htmx.parseInterval("5")).to.be.equal(5); chai.expect(htmx.parseInterval(null)).to.be.undefined chai.expect(htmx.parseInterval("")).to.be.undefined From d56d4c664a824058629198a16df23061efcc25c1 Mon Sep 17 00:00:00 2001 From: Thomas Cowart Date: Tue, 26 Sep 2023 22:01:12 -0400 Subject: [PATCH 2/6] Adds some semicolons Adds some semicolons to parseInterval (and tests) for consistency. --- src/htmx.js | 2 +- test/core/internals.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/htmx.js b/src/htmx.js index c75a73f58..a613fd169 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -131,7 +131,7 @@ return (function () { function parseInterval(str) { if (str == undefined) { - return undefined + return undefined; } let interval = NaN; diff --git a/test/core/internals.js b/test/core/internals.js index 3ce015397..e29dc0f80 100644 --- a/test/core/internals.js +++ b/test/core/internals.js @@ -76,9 +76,9 @@ describe("Core htmx internals Tests", function() { it("handles parseInterval correctly", function() { chai.expect(htmx.parseInterval("1ms")).to.be.equal(1); chai.expect(htmx.parseInterval("300ms")).to.be.equal(300); - chai.expect(htmx.parseInterval("1s")).to.be.equal(1000) - chai.expect(htmx.parseInterval("1.5s")).to.be.equal(1500) - chai.expect(htmx.parseInterval("2s")).to.be.equal(2000) + chai.expect(htmx.parseInterval("1s")).to.be.equal(1000); + chai.expect(htmx.parseInterval("1.5s")).to.be.equal(1500); + chai.expect(htmx.parseInterval("2s")).to.be.equal(2000); chai.expect(htmx.parseInterval("0ms")).to.be.equal(0); chai.expect(htmx.parseInterval("0s")).to.be.equal(0); chai.expect(htmx.parseInterval("0m")).to.be.equal(0); From 0e89164f8150748b76bd77e8ba376a792498a861 Mon Sep 17 00:00:00 2001 From: Thomas Cowart Date: Tue, 26 Sep 2023 22:04:46 -0400 Subject: [PATCH 3/6] Add one more parseInterval test for "0" Adds test test to make sure parseInterval works on "0". --- test/core/internals.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/core/internals.js b/test/core/internals.js index e29dc0f80..3d689293e 100644 --- a/test/core/internals.js +++ b/test/core/internals.js @@ -82,6 +82,7 @@ describe("Core htmx internals Tests", function() { chai.expect(htmx.parseInterval("0ms")).to.be.equal(0); chai.expect(htmx.parseInterval("0s")).to.be.equal(0); chai.expect(htmx.parseInterval("0m")).to.be.equal(0); + chai.expect(htmx.parseInterval("0")).to.be.equal(0); chai.expect(htmx.parseInterval("5")).to.be.equal(5); chai.expect(htmx.parseInterval(null)).to.be.undefined From 44477faa77ef205ae24bd531b5c481ba7abfbe90 Mon Sep 17 00:00:00 2001 From: Thomas Cowart Date: Wed, 27 Sep 2023 08:54:11 -0400 Subject: [PATCH 4/6] Adds functional tests for every, swap, settle, throttle, and delay --- src/htmx.js | 2 +- test/attributes/hx-swap.js | 49 +++++++++++++++++++- test/attributes/hx-trigger.js | 84 ++++++++++++++++++++++++++++++++++- 3 files changed, 130 insertions(+), 5 deletions(-) diff --git a/src/htmx.js b/src/htmx.js index a613fd169..1ed1adebc 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -1827,7 +1827,7 @@ return (function () { if (!maybeFilterEvent(triggerSpec, elt, makeEvent("load", {elt: elt}))) { loadImmediately(elt, handler, nodeData, triggerSpec.delay); } - } else if (triggerSpec.pollInterval) { + } else if (triggerSpec.pollInterval !== undefined) { nodeData.polling = true; processPolling(elt, handler, triggerSpec); } else { diff --git a/test/attributes/hx-swap.js b/test/attributes/hx-swap.js index dff1b0406..4ff63621f 100644 --- a/test/attributes/hx-swap.js +++ b/test/attributes/hx-swap.js @@ -197,27 +197,43 @@ describe("hx-swap attribute", function(){ swapSpec(make("
")).swapDelay.should.equal(0) swapSpec(make("
")).settleDelay.should.equal(0) // set to 0 in tests swapSpec(make("
")).swapDelay.should.equal(10) + swapSpec(make("
")).swapDelay.should.equal(0) + swapSpec(make("
")).swapDelay.should.equal(0) swapSpec(make("
")).settleDelay.should.equal(10) + swapSpec(make("
")).settleDelay.should.equal(0) + swapSpec(make("
")).settleDelay.should.equal(0) swapSpec(make("
")).swapDelay.should.equal(10) swapSpec(make("
")).settleDelay.should.equal(11) swapSpec(make("
")).swapDelay.should.equal(10) swapSpec(make("
")).settleDelay.should.equal(11) + swapSpec(make("
")).settleDelay.should.equal(0) + swapSpec(make("
")).settleDelay.should.equal(0) + swapSpec(make("
")).settleDelay.should.equal(0) + swapSpec(make("
")).settleDelay.should.equal(0) swapSpec(make("
")).settleDelay.should.equal(11) swapSpec(make("
")).settleDelay.should.equal(11) - + swapSpec(make("
")).swapStyle.should.equal("innerHTML") swapSpec(make("
")).swapDelay.should.equal(10) + swapSpec(make("
")).swapDelay.should.equal(0); + swapSpec(make("
")).swapDelay.should.equal(0); swapSpec(make("
")).swapStyle.should.equal("innerHTML") swapSpec(make("
")).settleDelay.should.equal(10) - + swapSpec(make("
")).settleDelay.should.equal(0) + swapSpec(make("
")).settleDelay.should.equal(0) + swapSpec(make("
")).swapStyle.should.equal("innerHTML") swapSpec(make("
")).swapDelay.should.equal(10) swapSpec(make("
")).settleDelay.should.equal(11) + swapSpec(make("
")).swapDelay.should.equal(0) + swapSpec(make("
")).settleDelay.should.equal(0) swapSpec(make("
")).swapStyle.should.equal("innerHTML") swapSpec(make("
")).swapDelay.should.equal(10) swapSpec(make("
")).settleDelay.should.equal(11) + swapSpec(make("
")).swapDelay.should.equal(10) + swapSpec(make("
")).settleDelay.should.equal(0) swapSpec(make("
")).swapStyle.should.equal("customstyle") }) @@ -234,6 +250,17 @@ describe("hx-swap attribute", function(){ }, 30); }); + it("works immediately with no swap delay", function (done) { + this.server.respondWith("GET", "/test", "Clicked!"); + var div = make( + "
" + ); + div.click(); + this.server.respond(); + div.innerText.should.equal("Clicked!"); + done(); + }); + it('works with a settle delay', function(done) { this.server.respondWith("GET", "/test", "
"); var div = make("
"); @@ -246,6 +273,24 @@ describe("hx-swap attribute", function(){ }, 30); }); + it("works with no settle delay", function (done) { + this.server.respondWith( + "GET", + "/test", + "
" + ); + var div = make( + "
" + ); + 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", 'Click Me'); diff --git a/test/attributes/hx-trigger.js b/test/attributes/hx-trigger.js index 77b8364ba..335c3f611 100644 --- a/test/attributes/hx-trigger.js +++ b/test/attributes/hx-trigger.js @@ -211,14 +211,20 @@ describe("hx-trigger attribute", function(){ var specExamples = { "": [{trigger: 'click'}], "every 1s": [{trigger: 'every', pollInterval: 1000}], + "every 0s": [{trigger: 'every', pollInterval: 0}], + "every 0ms": [{trigger: 'every', pollInterval: 0}], "click": [{trigger: 'click'}], "customEvent": [{trigger: 'customEvent'}], "event changed": [{trigger: 'event', changed: true}], "event once": [{trigger: 'event', once: true}], - "event delay:1s": [{trigger: 'event', delay: 1000}], "event throttle:1s": [{trigger: 'event', throttle: 1000}], - "event delay:1s, foo": [{trigger: 'event', delay: 1000}, {trigger: 'foo'}], + "event throttle:0s": [{trigger: 'event', throttle: 0}], + "event throttle:0ms": [{trigger: 'event', throttle: 0}], "event throttle:1s, foo": [{trigger: 'event', throttle: 1000}, {trigger: 'foo'}], + "event delay:1s": [{trigger: 'event', delay: 1000}], + "event delay:1s, foo": [{trigger: 'event', delay: 1000}, {trigger: 'foo'}], + "event delay:0s, foo": [{trigger: 'event', delay: 0}, {trigger: 'foo'}], + "event delay:0ms, foo": [{trigger: 'event', delay: 0}, {trigger: 'foo'}], "event changed once delay:1s": [{trigger: 'event', changed: true, once: true, delay: 1000}], "event1,event2": [{trigger: 'event1'}, {trigger: 'event2'}], "event1, event2": [{trigger: 'event1'}, {trigger: 'event2'}], @@ -398,6 +404,18 @@ describe("hx-trigger attribute", function(){ }, 100); }) + // Don't actually do this! + it("polling works every 0ms", function (done) { + this.server.respondWith("GET", "/test", "Called!"); + var div = make('
Not Called
'); + this.server.autoRespond = true; + this.server.autoRespondAfter = 0; + setTimeout(function () { + div.innerHTML.should.equal("Called!"); + done(); + }, 100); + }); + it('bad condition issues error', function(){ this.server.respondWith("GET", "/test", "Called!"); var div = make('
Not Called
'); @@ -648,6 +666,37 @@ describe("hx-trigger attribute", function(){ }, 50); }); + it("A throttle of 0 does not multiple requests from happening", function (done) { + var requests = 0; + var server = this.server; + server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + server.respondWith("GET", "/bar", "bar"); + var div = make( + "
" + ); + + div.click(); + server.respond(); + div.innerText.should.equal("Requests: 1"); + + div.click(); + server.respond(); + div.innerText.should.equal("Requests: 2"); + + div.click(); + server.respond(); + div.innerText.should.equal("Requests: 3"); + + div.click(); + server.respond(); + div.innerText.should.equal("Requests: 4"); + + done() + }); + it('delay delays the request', function(done) { var requests = 0; @@ -684,6 +733,37 @@ describe("hx-trigger attribute", function(){ }, 50); }); + it("A 0 delay does not delay the request", function (done) { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + this.server.respondWith("GET", "/bar", "bar"); + var div = make( + "
" + ); + + div.click(); + this.server.respond(); + div.innerText.should.equal("Requests: 1"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("Requests: 2"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("Requests: 3"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("Requests: 4"); + + done(); + }); + + it('requests are queued with last one winning by default', function() { var requests = 0; From a7c9bcdfb43c58945792b29156ad1f8fab312ad9 Mon Sep 17 00:00:00 2001 From: Thomas Cowart Date: Thu, 5 Oct 2023 10:46:31 -0400 Subject: [PATCH 5/6] 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). --- src/htmx.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/htmx.js b/src/htmx.js index 1ed1adebc..339336db8 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -1484,14 +1484,14 @@ return (function () { return; } - if (triggerSpec.throttle) { + if (triggerSpec.throttle > 0) { if (!elementData.throttle) { handler(elt, evt); elementData.throttle = setTimeout(function () { elementData.throttle = null; }, triggerSpec.throttle); } - } else if (triggerSpec.delay) { + } else if (triggerSpec.delay > 0) { elementData.delayed = setTimeout(function() { handler(elt, evt) }, triggerSpec.delay); } else { triggerEvent(elt, 'htmx:trigger') @@ -1768,7 +1768,7 @@ return (function () { handler(elt); } } - if (delay) { + if (delay > 0) { setTimeout(load, delay); } else { load(); From f3d4758cd709a4672ea2aae427457dfec1b25353 Mon Sep 17 00:00:00 2001 From: Thomas Cowart Date: Fri, 13 Oct 2023 16:52:48 -0400 Subject: [PATCH 6/6] 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. --- src/htmx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/htmx.js b/src/htmx.js index 339336db8..97ae773b1 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -1827,7 +1827,7 @@ return (function () { if (!maybeFilterEvent(triggerSpec, elt, makeEvent("load", {elt: elt}))) { loadImmediately(elt, handler, nodeData, triggerSpec.delay); } - } else if (triggerSpec.pollInterval !== undefined) { + } else if (triggerSpec.pollInterval > 0) { nodeData.polling = true; processPolling(elt, handler, triggerSpec); } else {