diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index 5dd81a7e6..b35bf4c80 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -186,14 +186,20 @@ const inject = { const $el = $(e.currentTarget); let cfgs = $el.data("pat-inject"); if ($el[0].tagName === "FORM" && e.type === "submit") { - if ($el[0].matches(":invalid")) { - // Do not submit invalid forms. - // Works with native form validation and with pat-validation. + const form = $el[0]; + const submitter = e.submitter; + + // Do not submit invalid forms, if validation is active. + // Works with native form validation and with pat-validation. + if ( + !submitter?.matches("[formnovalidate]") && + !form.matches("[novalidate]") && + form.matches(":invalid") + ) { log.debug("Form is invalid, aborting"); return; } - const submitter = e.submitter; const formaction = submitter?.getAttribute("formaction"); if (formaction) { const opts = { diff --git a/src/pat/inject/inject.test.js b/src/pat/inject/inject.test.js index 78debe099..82a04b161 100644 --- a/src/pat/inject/inject.test.js +++ b/src/pat/inject/inject.test.js @@ -1439,6 +1439,128 @@ describe("pat-inject", function () { expect(pattern.onTrigger).toHaveBeenCalledTimes(1); }); }); + + describe("9.2.4.6 - Validation on submit", () => { + it("9.2.4.6.1 - Submit valid forms.", async function () { + document.body.innerHTML = ` +
+ `; + jest.spyOn(pattern, "execute"); + + const form = document.querySelector("form"); + const button = form.querySelector("button"); + + // add submit listener + let catched = false; + form.addEventListener("submit", () => { + catched = true; + }); + + pattern.init($(form)); + await utils.timeout(1); // wait a tick for async to settle. + + button.click(); + + expect(catched).toBe(true); + expect(pattern.execute).toHaveBeenCalled(); + }); + + it("9.2.4.6.2 - Do not submit invalid forms.", async function () { + document.body.innerHTML = ` + + `; + jest.spyOn(pattern, "execute"); + + const form = document.querySelector("form"); + const button = form.querySelector("button"); + + // add submit listener + let catched = false; + form.addEventListener("submit", () => { + catched = true; + }); + + pattern.init($(form)); + await utils.timeout(1); // wait a tick for async to settle. + + button.click(); + + expect(catched).toBe(false); + expect(pattern.execute).not.toHaveBeenCalled(); + }); + + it("9.2.4.6.3 - Respect a form novalidate attribute and allow submission.", async function () { + document.body.innerHTML = ` + + `; + jest.spyOn(pattern, "execute"); + + const form = document.querySelector("form"); + const button = form.querySelector("button"); + + // add submit listener + let catched = false; + form.addEventListener("submit", () => { + catched = true; + }); + + pattern.init($(form)); + await utils.timeout(1); // wait a tick for async to settle. + + button.click(); + + expect(catched).toBe(true); + expect(pattern.execute).toHaveBeenCalled(); + }); + + // The following test does not work with jsDOM 25.0.1. + // jsDOM supports `form[novalidate]` but not `button[formnovalidate]` + // Ref: https://github.com/jsdom/jsdom/pull/3249 + it.skip("9.2.4.6.4 - Respect a formnovalidate attribute on buttons and allow submission.", async function () { + document.body.innerHTML = ` + + `; + jest.spyOn(pattern, "execute"); + + const form = document.querySelector("form"); + const button_cancel = form.querySelector("button.cancel"); + const button_submit = form.querySelector("button.submit"); + + // add submit listener + let catched = false; + form.addEventListener("submit", () => { + catched = true; + }); + + pattern.init($(form)); + await utils.timeout(1); // wait a tick for async to settle. + + button_submit.click(); + + expect(catched).toBe(false); + expect(pattern.execute).not.toHaveBeenCalled(); + + button_cancel.click(); + + expect(catched).toBe(true); + expect(pattern.execute).toHaveBeenCalled(); + + }); + + }); }); }); });