mirror of
https://github.com/bigskysoftware/htmx.git
synced 2026-01-14 04:31:45 +00:00
fix hx-validate to respect noValidate and validate inputs outside of just forms Co-authored-by: MichaelWest22 <michael.west@docuvera.com>
301 lines
12 KiB
JavaScript
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);
|
|
// })
|
|
})
|
|
|
|
|