From 993d27db6ba7e3d9c2f2713b46e64c2e82599632 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 24 Oct 2024 23:29:27 +0200 Subject: [PATCH] fix(pat-inject): Allow to submit invalid forms when form/novalidate. Respect the form[novalidate] and button[formnovalidate] attributes and allow the submission of invalid forms. This is necessary if a cancel button is a submit button and needs to be allowed to submit even if the form input is invalid. --- src/pat/inject/inject.js | 14 ++-- src/pat/inject/inject.test.js | 122 ++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 4 deletions(-) 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(); + + }); + + }); }); }); });