From d241e3052ff2400fcbd9169265ebcc3894e9d44e Mon Sep 17 00:00:00 2001 From: Carson Gross Date: Sun, 2 Nov 2025 14:51:53 -0700 Subject: [PATCH] request queue tests --- src/htmx.js | 12 +- test/test.html | 1 + test/tests/unit/__getRequestQueue.js | 209 +++++++++++++++++++++++++++ test/tests/unit/__issueRequest.js | 1 - 4 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 test/tests/unit/__getRequestQueue.js diff --git a/src/htmx.js b/src/htmx.js index eb06f3ff..40926239 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -12,10 +12,9 @@ var htmx = (() => { } else { // Update ctx.status properly for replaced request contexts if (queueStrategy === "replace") { + this.#q.map(value => value.status = "dropped"); this.#q = [] if (this.#c) { - // TODO standardize on ctx.status - this.#c.cancelled = true; this.#c.abort(); } return true @@ -26,12 +25,15 @@ var htmx = (() => { // ignore the request ctx.status = "dropped"; } else if (queueStrategy === "queue last") { + this.#q.map(value => value.status = "dropped"); this.#q = [ctx] ctx.status = "queued"; } else if (this.#q.length === 0) { // default queue first this.#q.push(ctx) ctx.status = "queued"; + } else { + ctx.status = "dropped"; } return false } @@ -487,7 +489,6 @@ var htmx = (() => { raw: response, status: response.status, headers: response.headers, - cancelled: false, } if (!this.__trigger(elt, "htmx:after:request", {ctx})) return; @@ -499,9 +500,8 @@ var htmx = (() => { } else { // HTTP response ctx.text = await response.text(); - ctx.status = "response received"; - - if (!ctx.response.cancelled) { + if (ctx.status === "issuing") { + ctx.status = "response received"; this.__handleStatusCodes(ctx); this.__handleHistoryUpdate(ctx); await this.swap(ctx); diff --git a/test/test.html b/test/test.html index 833ae1ae..df1e0388 100644 --- a/test/test.html +++ b/test/test.html @@ -95,6 +95,7 @@ + diff --git a/test/tests/unit/__getRequestQueue.js b/test/tests/unit/__getRequestQueue.js new file mode 100644 index 00000000..ebb13e3e --- /dev/null +++ b/test/tests/unit/__getRequestQueue.js @@ -0,0 +1,209 @@ +describe('__getRequestQueue / RequestQueue unit tests', function() { + + beforeEach(function() { + setupTest(); + }); + + afterEach(function() { + cleanupTest(); + }); + + it('allows first request when queue is empty', function () { + let div = createProcessedHTML('
') + let ctx = htmx.__createRequestContext(div, new Event('click')) + let queue = htmx.__getRequestQueue(div) + + let result = queue.shouldIssueRequest(ctx, 'queue first') + + assert.isTrue(result) + }) + + it('queues request with "queue all" strategy', function () { + let div = createProcessedHTML('
') + let queue = htmx.__getRequestQueue(div) + + // Issue first request + let ctx1 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx1, 'queue all') + + // Queue second request + let ctx2 = htmx.__createRequestContext(div, new Event('click')) + let result = queue.shouldIssueRequest(ctx2, 'queue all') + + assert.isFalse(result) + assert.equal(ctx2.status, 'queued') + }) + + it('drops request with "drop" strategy', function () { + let div = createProcessedHTML('
') + let queue = htmx.__getRequestQueue(div) + + // Issue first request + let ctx1 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx1, 'drop') + + // Drop second request + let ctx2 = htmx.__createRequestContext(div, new Event('click')) + let result = queue.shouldIssueRequest(ctx2, 'drop') + + assert.isFalse(result) + assert.equal(ctx2.status, 'dropped') + }) + + it('queues only last with "queue last" strategy', function () { + let div = createProcessedHTML('
') + let queue = htmx.__getRequestQueue(div) + + // Issue first request + let ctx1 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx1, 'queue last') + + // Queue second request + let ctx2 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx2, 'queue last') + + // Queue third request (should drop ctx2) + let ctx3 = htmx.__createRequestContext(div, new Event('click')) + let result = queue.shouldIssueRequest(ctx3, 'queue last') + + assert.isFalse(result) + assert.equal(ctx2.status, 'dropped') + assert.equal(ctx3.status, 'queued') + }) + + it('replaces current request with "replace" strategy', function () { + let div = createProcessedHTML('
') + let queue = htmx.__getRequestQueue(div) + + // Issue first request + let ctx1 = htmx.__createRequestContext(div, new Event('click')) + ctx1.abort = () => { ctx1.aborted = true } + queue.shouldIssueRequest(ctx1, 'replace') + + // Replace with second request + let ctx2 = htmx.__createRequestContext(div, new Event('click')) + let result = queue.shouldIssueRequest(ctx2, 'replace') + + assert.isTrue(result) + assert.isTrue(ctx1.aborted) + }) + + it('defaults to "queue first" when strategy not specified', function () { + let div = createProcessedHTML('
') + let queue = htmx.__getRequestQueue(div) + + // Issue first request + let ctx1 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx1, 'queue first') + + // Queue second request + let ctx2 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx2, 'queue first') + + // Third request should be dropped (not queued) + let ctx3 = htmx.__createRequestContext(div, new Event('click')) + let result = queue.shouldIssueRequest(ctx3, 'queue first') + + assert.isFalse(result) + assert.equal(ctx2.status, 'queued') + assert.equal(ctx3.status, 'dropped') + }) + + it('hasMore returns truthy when queue has requests', function () { + let div = createProcessedHTML('
') + let queue = htmx.__getRequestQueue(div) + + let ctx1 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx1, 'queue all') + + let ctx2 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx2, 'queue all') + + assert.isOk(queue.hasMore()) + }) + + it('hasMore returns falsey when queue is empty', function () { + let div = createProcessedHTML('
') + let queue = htmx.__getRequestQueue(div) + + assert.isNotOk(queue.hasMore()) + }) + + it('nextRequest returns next queued request', function () { + let div = createProcessedHTML('
') + let queue = htmx.__getRequestQueue(div) + + let ctx1 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx1, 'queue all') + + let ctx2 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx2, 'queue all') + + let next = queue.nextRequest() + + assert.equal(next, ctx2) + }) + + it('nextRequest clears current request', function () { + let div = createProcessedHTML('
') + let queue = htmx.__getRequestQueue(div) + + let ctx1 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx1, 'queue all') + + let ctx2 = htmx.__createRequestContext(div, new Event('click')) + queue.shouldIssueRequest(ctx2, 'queue all') + + queue.nextRequest() + + // Should now allow a new request + let ctx3 = htmx.__createRequestContext(div, new Event('click')) + let result = queue.shouldIssueRequest(ctx3, 'queue first') + + assert.isTrue(result) + }) + + it('abortCurrentRequest calls abort on current request', function () { + let div = createProcessedHTML('
') + let queue = htmx.__getRequestQueue(div) + + let ctx = htmx.__createRequestContext(div, new Event('click')) + ctx.abort = () => { ctx.aborted = true } + queue.shouldIssueRequest(ctx, 'queue first') + + queue.abortCurrentRequest() + + assert.isTrue(ctx.aborted) + }) + + it('returns same queue for same element', function () { + let div = createProcessedHTML('
') + + let queue1 = htmx.__getRequestQueue(div) + let queue2 = htmx.__getRequestQueue(div) + + assert.equal(queue1, queue2) + }) + + it('returns different queue for different elements', function () { + let div1 = createProcessedHTML('
') + let div2 = createProcessedHTML('
') + + let queue1 = htmx.__getRequestQueue(div1) + let queue2 = htmx.__getRequestQueue(div2) + + assert.notEqual(queue1, queue2) + }) + + it('uses selector from hx-sync for queue', function () { + let container = createProcessedHTML('
') + let btn1 = container.querySelector('#btn1') + let btn2 = container.querySelector('#btn2') + + let queue1 = htmx.__getRequestQueue(btn1) + let queue2 = htmx.__getRequestQueue(btn2) + + assert.equal(queue1, queue2) + }) + +}); diff --git a/test/tests/unit/__issueRequest.js b/test/tests/unit/__issueRequest.js index 16cffa0f..efcbfdad 100644 --- a/test/tests/unit/__issueRequest.js +++ b/test/tests/unit/__issueRequest.js @@ -135,7 +135,6 @@ describe('__issueRequest unit tests', function() { assert.equal(ctx.response.status, 201) assert.equal(ctx.response.headers, mockHeaders) - assert.equal(ctx.response.cancelled, false) assert.isDefined(ctx.response.raw) })