MichaelWest22 0bb59973f8
fix hx-validate to respect noValidate and validate inputs outside of … (#3562)
fix hx-validate to respect noValidate and validate inputs outside of just forms

Co-authored-by: MichaelWest22 <michael.west@docuvera.com>
2025-12-04 11:45:04 -07:00

301 lines
12 KiB
JavaScript

describe('Basic Functionality', () => {
afterEach(() => {
cleanupTest();
})
it('Button click triggers fetch and swaps content', async ()=> {
// Set up mock response
mockResponse('GET', '/demo', '<div id="result">Success!</div>');
// Create test button
createProcessedHTML('<button id="test-btn" hx-action="/demo" hx-target="#target">Click</button><div id="target">Original</div>');
// Click the button
find("#test-btn").click()
await forRequest();
// Verify the swap occurred
assertTextContentIs("#target", "Success!");
})
it('validation errors prevent submission of a form', async function() {
// Set up mock response
mockResponse('POST', '/demo', '<div id="result">Success!</div>');
createProcessedHTML('<form><input id="i1" required/><button id="b1" hx-post="/demo" hx-validate="true">Demo</button></form>');
find('#b1').click();
assert.equal(fetchMock.pendingRequests.length, 0);
// fill in the required value
find("#i1").value = "foo"
find('#b1').click()
await forRequestWithDelay();
assert.isUndefined(find('#target'));
assertTextContentIs("#result", "Success!");
})
it('validation errors do not prevent submission of an element within a form marked as hx-validate=false', async function() {
// Set up mock response
mockResponse('POST', '/demo', new MockResponse('<div id="result">Success!</div>'));
// Create test button
createProcessedHTML('<form><input id="i1" required/><button id="b1" hx-post="/demo" hx-validate="false">Demo</button></form>');
// Click the button
find('#b1').click()
await forRequestWithDelay();
assert.isUndefined(find('#target'));
assertTextContentIs("#result", "Success!");
})
it('validation errors prevent submission of a single input with hx-validate=true', async function() {
mockResponse('POST', '/demo', '<div id="result">Success!</div>');
createProcessedHTML('<input id="i1" required hx-post="/demo" hx-validate="true" name="test" hx-trigger="click"/>');
find('#i1').click();
assert.equal(fetchMock.pendingRequests.length, 0);
find("#i1").value = "foo"
find('#i1').click()
await forRequestWithDelay();
assertTextContentIs("#result", "Success!");
})
it('validation errors prevent submission of hx-included inputs', async function() {
mockResponse('POST', '/demo', '<div id="result">Success!</div>');
createProcessedHTML('<input id="i1" required name="test1"/><button id="b1" hx-post="/demo" hx-validate="true" hx-include="#i1">Submit</button>');
find('#b1').click();
assert.equal(fetchMock.pendingRequests.length, 0);
find("#i1").value = "foo"
find('#b1').click()
await forRequestWithDelay();
assertTextContentIs("#result", "Success!");
})
it('form with noValidate does not validate by default', async function() {
mockResponse('POST', '/demo', '<div id="result">Success!</div>');
createProcessedHTML('<form novalidate><input id="i1" required name="test"/><button id="b1" hx-post="/demo">Submit</button></form>');
find('#b1').click();
await forRequestWithDelay();
assertTextContentIs("#result", "Success!");
})
it('form with noValidate can be overridden with hx-validate=true', async function() {
mockResponse('POST', '/demo', '<div id="result">Success!</div>');
createProcessedHTML('<form novalidate><input id="i1" required name="test"/><button id="b1" hx-post="/demo" hx-validate="true">Submit</button></form>');
find('#b1').click();
assert.equal(fetchMock.pendingRequests.length, 0);
find("#i1").value = "foo"
find('#b1').click()
await forRequestWithDelay();
assertTextContentIs("#result", "Success!");
})
it('submit button with formNoValidate skips validation', async function() {
mockResponse('POST', '/demo', '<div id="result">Success!</div>');
createProcessedHTML('<form><input id="i1" required name="test"/><button id="b1" hx-post="/demo" formnovalidate>Submit</button></form>');
find('#b1').click();
await forRequestWithDelay();
assertTextContentIs("#result", "Success!");
})
it('form validates by default without hx-validate attribute', async function() {
mockResponse('POST', '/demo', '<div id="result">Success!</div>');
createProcessedHTML('<form hx-post="/demo"><input id="i1" required name="test"/><button id="b1" type="submit">Submit</button></form>');
find('#b1').click();
assert.equal(fetchMock.pendingRequests.length, 0);
find("#i1").value = "foo"
find('#b1').click()
await forRequestWithDelay();
assertTextContentIs("#result", "Success!");
})
// it('Button added dynamically still triggers fetch and swaps', async function() {
// // Set up mock response
// fetchMock.mockResponse('/demo', new MockResponse('<div id="d1">Foo</div>'));
// // Add button dynamically
// const playground = find('#test-playground');
// playground.insertAdjacentHTML('beforeend', `<button hx-action="/demo">Button 1</button>`);
// // Wait for htmx to initialize the new element
// await htmx.forEvent("mx:init", 2000);
// // Click the button
// find('button').click();
// // Wait for the swap to complete
// await htmx.forEvent("htmx:after:swap", 2000);
// // Verify the swap occurred
// const result = find('#d1');
// assertExists(result);
// assertEquals("Foo", result.textContent);
// })
//
// it('Button with hx-target swaps into target element', async function() {
// // Set up mock response
// fetchMock.mockResponse('/demo', new MockResponse('<div id="d1">Foo</div>'));
// // Create test button with target
// await initLiveContent(`
// <button hx-action="/demo" hx-target="#output1">Button 1</button>
// <output id="output1">Bar</output>
// `);
// // Click the button
// find('button').click();
// // Wait for the swap to complete
// await htmx.forEvent("htmx:after:swap", 2000);
// // Verify the swap occurred in the target
// const result = find('#d1');
// assertExists(result);
// assertEquals("Foo", result.textContent);
// })
//
// it('Button with swap afterend swaps after the end of the target', async function() {
// // Set up mock response
// fetchMock.mockResponse('/demo', new MockResponse('<div id="d1">Foo</div>'));
// // Create test button with target
// await initLiveContent(`
// <button id="b1" hx-action="/demo" hx-swap="afterend">Button 1</button>`);
// // Click the button
// find('button').click();
// // Wait for the swap to complete
// await htmx.forEvent("htmx:after:swap", 2000);
//
// // Verify the swap occurred after the target
// const btn = find('#b1');
// assertExists(btn);
// assertEquals("Button 1", btn.textContent);
//
// // Verify the swap occurred after the target
// const result = btn.nextElementSibling;
// assertExists(result);
// assertEquals("Foo", result.textContent);
// })
//
// it('Test attributes can be explicity inherited to children', async function() {
// // Set up mock response
// fetchMock.mockResponse('/demo1', new MockResponse('<div>Foo</div>'));
// fetchMock.mockResponse('/demo2', new MockResponse('<div>Bar</div>'));
// // Create test button with target
// await initLiveContent(`
// <div hx-swap:inherited="beforeend" hx-target:inherited="#output">
// <button id="b1" hx-action="/demo1">Button 1</button>
// <button id="b2" hx-action="/demo2">Button 2</button>
// </div>
// <div id="output"></div>
// `);
// // Click the button
// find('#b1').click();
// await htmx.forEvent("htmx:after:swap", 2000);
//
// // Verify the swap occurred after the target
// const output = find('#output');
// assertExists(output);
// assertEquals("Foo", output.textContent);
//
// find('#b2').click();
// await htmx.forEvent("htmx:after:swap", 2000);
//
// assertEquals("FooBar", output.textContent);
// })
// })
//
// describe('Advanced Triggers', function() {
// afterEach(function() {
// cleanup()
// })
//
// it('Test multiple events can trigger', async function() {
// // Set up mock response
// fetchMock.mockResponse('/demo', new MockResponse('<div>Foo</div>'));
// // Create test button with target
// await initLiveContent(`
// <button id="b1" hx-action="/demo" hx-target="#output" hx-swap="beforeend" hx-trigger="foo, bar">Button 1</button>
// <output id="output"></output>
// `);
// // Click the button
// let btn = find('#b1');
// htmx.trigger(btn, 'foo');
// await htmx.forEvent("htmx:after:swap", 2000);
//
// // Verify the swap occurred after the target
// const output = find('#output');
// assertExists(output);
// assertEquals("Foo", output.textContent);
//
// htmx.trigger(btn, 'bar');
// await htmx.forEvent("htmx:after:swap", 2000);
//
// assertEquals("FooFoo", output.textContent);
// })
//
// it('Test delay can delay a trigger', async function() {
// fetchMock.mockResponse('/demo', new MockResponse('<div>Foo</div>'));
// await initLiveContent(`
// <div id="d1" hx-action="/demo" hx-swap="innerHTML" hx-trigger="foo delay:1s">Div 1</div>
// `);
//
// let div = find('#d1');
// htmx.trigger(div, 'foo');
// // should still be Div 1
// await htmx.timeout(300);
// assertEquals("Div 1", div.textContent);
//
// // delay should trigger eventually
// await htmx.forEvent("htmx:after:swap", 2000);
// assertEquals("Foo", div.textContent);
// })
//
// it('Test delay debounces a trigger', async function() {
// fetchMock.mockResponse('/demo', new MockResponse('<div>Foo</div>'));
// await initLiveContent(`
// <div id="d1" hx-action="/demo" hx-swap="innerHTML" hx-trigger="foo delay:1s">Div 1</div>
// `);
//
// let div = find('#d1');
// htmx.trigger(div, 'foo');
// // should still be Div 1
// await htmx.timeout(600);
// assertEquals("Div 1", div.textContent);
//
// htmx.trigger(div, 'foo');
// // should still be Div 1 due to retrigger
// await htmx.timeout(600);
// assertEquals("Div 1", div.textContent);
//
// // delay should trigger eventually
// await htmx.forEvent("htmx:after:swap", 2000);
// assertEquals("Foo", div.textContent);
// })
//
// it('Test \'every\' polls properly', async function() {
// fetchMock.mockResponse('/demo', new MockResponse('<div>Foo</div>'));
// await initLiveContent(`
// <div id="d1" hx-action="/demo" hx-swap="innerHTML" hx-trigger="every 1s">Div 1</div>
// `);
//
// let div = find('#d1');
//
// // should still be Div 1
// await htmx.timeout(600);
// assertEquals("Div 1", div.textContent);
//
// // delay should trigger eventually
// await htmx.forEvent("htmx:after:swap", 2000);
// assertEquals("Foo", div.textContent);
// })
})