htmx/test/tests/unit/__issueRequest.js
2025-11-08 19:54:13 -07:00

225 lines
7.5 KiB
JavaScript

describe('__issueRequest unit tests', function() {
beforeEach(function() {
setupTest();
});
afterEach(function() {
cleanupTest();
});
it('triggers htmx:before:request event', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let ctx = htmx.__createRequestContext(div, new Event('click'))
let mockFetch = async () => ({
status: 200,
headers: new Headers(),
text: async () => ''
})
ctx.fetch = mockFetch
let beforeRequestFired = false
div.addEventListener('htmx:before:request', () => beforeRequestFired = true)
await htmx.__issueRequest(ctx)
assert.isTrue(beforeRequestFired)
})
it('triggers htmx:after:request event', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let ctx = htmx.__createRequestContext(div, new Event('click'))
let mockFetch = async () => ({
status: 200,
headers: new Headers(),
text: async () => ''
})
ctx.fetch = mockFetch
let afterRequestFired = false
div.addEventListener('htmx:after:request', () => afterRequestFired = true)
await htmx.__issueRequest(ctx)
assert.isTrue(afterRequestFired)
})
it('calls custom fetch implementation', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let ctx = htmx.__createRequestContext(div, new Event('click'))
let fetchCalled = false
let fetchAction = null
ctx.fetch = async (action, request) => {
fetchCalled = true
fetchAction = action
return {
status: 200,
headers: new Headers(),
text: async () => ''
}
}
await htmx.__issueRequest(ctx)
assert.isTrue(fetchCalled)
assert.equal(fetchAction, '/test')
})
it('does not execute when queue blocks request', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none" hx-sync="drop"></div>')
// Issue first request
let ctx1 = htmx.__createRequestContext(div, new Event('click'))
ctx1.fetch = async () => new Promise(() => {}) // never resolves
htmx.__issueRequest(ctx1) // don't await
// Try to issue second request
let ctx2 = htmx.__createRequestContext(div, new Event('click'))
let fetchCalled = false
ctx2.fetch = async () => {
fetchCalled = true
return { status: 200, headers: new Headers(), text: async () => '' }
}
await htmx.__issueRequest(ctx2)
assert.isFalse(fetchCalled)
})
it('returns early if htmx:before:request is cancelled', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let ctx = htmx.__createRequestContext(div, new Event('click'))
div.addEventListener('htmx:before:request', (e) => e.preventDefault())
let fetchCalled = false
ctx.fetch = async () => {
fetchCalled = true
return { status: 200, headers: new Headers(), text: async () => '' }
}
await htmx.__issueRequest(ctx)
assert.isFalse(fetchCalled)
})
it('returns early if confirm returns false', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none" hx-confirm="Are you sure?"></div>')
let ctx = htmx.__createRequestContext(div, new Event('click'))
let originalConfirm = window.confirm
window.confirm = () => false
let fetchCalled = false
ctx.fetch = async () => {
fetchCalled = true
return { status: 200, headers: new Headers(), text: async () => '' }
}
await htmx.__issueRequest(ctx)
assert.isFalse(fetchCalled)
window.confirm = originalConfirm
})
it('creates response object with correct structure', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let ctx = htmx.__createRequestContext(div, new Event('click'))
let mockHeaders = new Headers()
ctx.fetch = async () => ({
status: 201,
headers: mockHeaders,
text: async () => 'response text'
})
await htmx.__issueRequest(ctx)
assert.equal(ctx.response.status, 201)
assert.equal(ctx.response.headers, mockHeaders)
assert.isDefined(ctx.response.raw)
})
it('catches errors and triggers htmx:error event', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let ctx = htmx.__createRequestContext(div, new Event('click'))
let errorFired = false
let capturedError = null
div.addEventListener('htmx:error', (e) => {
errorFired = true
capturedError = e.detail.error
})
let testError = new Error('fetch failed')
ctx.fetch = async () => { throw testError }
await htmx.__issueRequest(ctx)
assert.isTrue(errorFired)
assert.equal(capturedError, testError)
})
it('always triggers htmx:finally:request', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let ctx = htmx.__createRequestContext(div, new Event('click'))
let finallyFired = false
div.addEventListener('htmx:finally:request', () => finallyFired = true)
ctx.fetch = async () => { throw new Error('fail') }
await htmx.__issueRequest(ctx)
assert.isTrue(finallyFired)
})
it('updates ctx.status through request lifecycle', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none"></div>')
let ctx = htmx.__createRequestContext(div, new Event('click'))
let statuses = []
div.addEventListener('htmx:before:request', () => statuses.push(ctx.status))
ctx.fetch = async () => {
statuses.push(ctx.status)
return { status: 200, headers: new Headers(), text: async () => '' }
}
await htmx.__issueRequest(ctx)
statuses.push(ctx.status)
assert.include(statuses, 'issuing')
assert.include(statuses, 'swapped')
})
it('processes next queued request after completion', async function () {
let div = createProcessedHTML('<div hx-get="/test" hx-swap="none" hx-sync="queue all"></div>')
let request1Complete = false
let request2Started = false
// First request
let ctx1 = htmx.__createRequestContext(div, new Event('click'))
ctx1.fetch = async () => {
await new Promise(r => setTimeout(r, 10))
request1Complete = true
return { status: 200, headers: new Headers(), text: async () => '' }
}
// Second request (should be queued)
let ctx2 = htmx.__createRequestContext(div, new Event('click'))
ctx2.fetch = async () => {
request2Started = true
return { status: 200, headers: new Headers(), text: async () => '' }
}
let p1 = htmx.__issueRequest(ctx1)
await new Promise(r => setTimeout(r, 5)) // let first request start
let p2 = htmx.__issueRequest(ctx2)
await Promise.all([p1, p2])
assert.isTrue(request1Complete)
assert.isTrue(request2Started)
})
});