diff --git a/src/pat/validation/documentation.md b/src/pat/validation/documentation.md index 394e82c65..a3dc74fdd 100644 --- a/src/pat/validation/documentation.md +++ b/src/pat/validation/documentation.md @@ -1,7 +1,9 @@ ## Description -This pattern provides form validation based on the HTML standard and offers extended functionality like custom error messages and extra validation rules. - +This pattern provides form validation with standard HTML validation attributes, +extra validation rules, custom error messages and a custom error template. It +is based HTML form validation framework and therefor supports form state +checking via CSS peudo classes like `:valid` and `:invalid`. ## Documentation @@ -10,13 +12,23 @@ The rest is handled mostly with standard HTML validation attributes. This patterns offers: -- extra validation rules like checking for equality or checking is one date it after another. -- custom error messages. +- Custom error messages for the standard HTML validation attributes. +- Custom error template to display error messages. +- Extra validation rules where the standard HTML validation attributes are not enough. + +These extra validation rules are: + +- Equality checking between two fields (e.g. password confirmation). +- Date and datetime validation for before and after a given date or another input field. -Since it is based on the HTML standard you can still use the `:valid`, `:invalid` and `:out-of-range` CSS pseudo classes. -You can use any HTML form validation attributes but here are some examples: +### HTML form validation framework integration. +This pattern uses the [JavaScript Constraint Validation API](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation). +Valid formns or inputs can be selected with the `:valid` pseudo class, invalid ones with the `:invalid` pseudo class. + + +### Validation rules | Name | Syntax | Description | | ------------- | -------------------------- | ------------------------------------------------------------ | @@ -31,64 +43,94 @@ You can use any HTML form validation attributes but here are some examples: > **_NOTE:_** The form inputs must have a `name` attribute, otherwise the validation would not happen. -### Error messages +### Custom error messages -Error messages are inserted into the DOM as `em` elements with a `message warning` class. -For most input elements error messages are inserted immediately after the input element. -In addition both the input element and its label will get an `warning` class. +Error messages are unique per type of validation (e.g. `required`, `email` or `number`) and can be overridden: - +```html +
+ +
+``` -Checkboxes and radio buttons are treated differently: if they are contained in a fieldset with class `checklist` error messages are added at the end of the fieldset. +Error messages can also be overridden on a per-field basis, for example: -
- - - - Please make a choice -
+```html + +``` -#### Overriding error messages +For a list of all available error messages see the [Options reference](#options-reference). -Error messages are unique per type of validation (e.g. `required`, `email` or `number`) and can be overridden: -
+### Error message rendering - +Error messages are inserted into the DOM as `em` elements with a `message warning` class. +For most input elements error messages are inserted immediately after the input element. +In addition both the input element and its label will get an `warning` class. -
+```html + +``` -Error messages can also be overridden on a per-field basis, for example: +Checkboxes and radio buttons are treated differently: if they are contained in a fieldset with class `checklist` error messages are added at the end of the fieldset. + +```html +
+ + + + Please make a choice +
+``` - +The error message template is be overridden via JavaScript by customizing the error_template method of the Pattern API. +This is an [example taken from Mockup](https://github.com/plone/mockup/blob/6c93b810b2c07b5bd58eec80cd03f700c9447d8c/src/patterns.js#L67): + +```javascript +import { Pattern as ValidationPattern } from "@patternslib/patternslib/src/pat/validation/validation"; + +ValidationPattern.prototype.error_template = (message) => + `${message}`; +``` ### Options reference -> **_NOTE:_** The form inputs must have a `name` attribute, otherwise the validation would not happen. +> **_NOTE:_** The form inputs must have a `name` attribute, otherwise the +> validation would not happen. -> **_NOTE:_** If you need to exclude a submit button from form validation - like a cancel button which actually submits - add the `formnovalidate` attribute to the button. +> **_NOTE:_** If you need to exclude a submit button from form validation - +> like a cancel button which actually submits - add the `formnovalidate` +> attribute to the button. | Property | Description | Default | Type | | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -------------------------------------- | | disable-selector | A selector for elements that should be disabled when there are errors in the form. | | CSS Selector | -| equality | Field-specific. The name of another input this input should equal to (useful for password confirmation). | | String | | message-date | The error message for date fields. | This value must be a valid date | String | | message-datetime | The error message for datetime fields. | This value must be a valid date and time | String | | message-email | The error message for email fields. | This value must be a valid email address | String | -| message-equality | The error message for fields required to be equal | is not equal to %{attribute} | String | -| message-max | The error message for max number values. | This value must be less than or equal to %{count} | String | -| message-min | The error message for min number values. | This value must be greater than or equal to %{count} | String | +| message-max | The error message for number values which are higher than max. | This value must be less than or equal to %{count} | String | +| message-min | The error message for number values which are lower than min. | This value must be greater than or equal to %{count} | String | | message-number | The error message for numbers. | This value must be a number. | String | | message-required | The error message for required fields. | This field is required. | String | -| not-after | Field-specific. A lower time limit restriction for date and datetime fields. | | CSS Selector or a ISO8601 date string. | -| not-before | Field-specific. An upper time limit restriction for date and datetime fields. | | CSS Selector or a ISO8601 date string. | +| message-equality | The error message for fields required to be equal | is not equal to %{attribute} | String | +| equality | Field-specific extra rule. The name of another input this input should equal to (useful for password confirmation). | | String | +| not-after | Field-specific extra rule. A lower time limit restriction for date and datetime fields. | | CSS Selector or a ISO8601 date string. | +| not-before | Field-specific extra rule. An upper time limit restriction for date and datetime fields. | | CSS Selector or a ISO8601 date string. | +| delay | Time in milliseconds before validation starts to avoid validating while typing. | 100 | Integer | diff --git a/src/pat/validation/validation.js b/src/pat/validation/validation.js index 53421d41d..2070a7652 100644 --- a/src/pat/validation/validation.js +++ b/src/pat/validation/validation.js @@ -10,27 +10,25 @@ import utils from "../../core/utils"; import registry from "../../core/registry"; const logger = logging.getLogger("pat-validation"); -//logger.setLevel(logging.Level.DEBUG); + export const parser = new Parser("validation"); parser.addArgument("disable-selector", "[type=submit], button:not([type=button])"); // Elements which must be disabled if there are errors -parser.addArgument("message-date", ""); // "This value must be a valid date"); -parser.addArgument("message-datetime", ""); // "This value must be a valid date and time"); -parser.addArgument("message-email", ""); // "This value must be a valid email address"); -parser.addArgument("message-max", ""); // "This value must be less than or equal to %{count}"); -parser.addArgument("message-min", ""); // "This value must be greater than or equal to %{count}"); // prettier-ignore -parser.addArgument("message-number", ""); // "This value must be a number"); -parser.addArgument("message-required", ""); // "This field is required"); +parser.addArgument("message-date", ""); // "This value must be a valid date" +parser.addArgument("message-datetime", ""); // "This value must be a valid date and time" +parser.addArgument("message-email", ""); // "This value must be a valid email address" +parser.addArgument("message-max", ""); // "This value must be less than or equal to %{count}" +parser.addArgument("message-min", ""); // "This value must be greater than or equal to %{count}" +parser.addArgument("message-number", ""); // "This value must be a number" +parser.addArgument("message-required", ""); // "This field is required" parser.addArgument("message-equality", "is not equal to %{attribute}."); parser.addArgument("not-after", null); parser.addArgument("not-before", null); parser.addArgument("equality", null); parser.addArgument("delay", 100); // Delay before validation is done to avoid validating while typing. -// BBB -// TODO: deprecated. Will be removed with next major version. +// Aliases parser.addAlias("message-integer", "message-number"); -parser.addArgument("error-template"); const KEY_ERROR_EL = "__patternslib__input__error__el"; const KEY_ERROR_MSG = "__patternslib__input__error__msg"; @@ -121,19 +119,7 @@ class Pattern extends BasePattern { return; } - logger.debug(` - validity_state.badInput ${validity_state.badInput} - validity_state.customError ${validity_state.customError} - validity_state.patternMismatch ${validity_state.patternMismatch} - validity_state.rangeOverflow ${validity_state.rangeOverflow} - validity_state.rangeUnderflow ${validity_state.rangeUnderflow} - validity_state.stepMismatch ${validity_state.stepMismatch} - validity_state.tooLong ${validity_state.tooLong} - validity_state.tooShort ${validity_state.tooShort} - validity_state.typeMismatch ${validity_state.typeMismatch} - validity_state.valid ${validity_state.valid} - validity_state.valueMissing ${validity_state.valueMissing} - `); + logger.debug(`validity_state: `, validity_state); const input_options = parser.parse(input); diff --git a/src/pat/validation/validation.test.js b/src/pat/validation/validation.test.js index b50f5e15c..f9a0eaa62 100644 --- a/src/pat/validation/validation.test.js +++ b/src/pat/validation/validation.test.js @@ -18,1481 +18,1495 @@ describe("pat-validation", function () { parser.parameters.delay.value = orig_delay; }); - it("1.1 - validates with no constraints", async function () { - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=name]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + describe("1 - general tests", function () { + it("1.1 - validates with no constraints", async function () { + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=name]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("1.2 - adds an error when validation fails", async function () { - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=name]"); + it("1.2 - adds an error when validation fails", async function () { + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=name]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - }); + expect(el.querySelectorAll("em.warning").length).toBe(1); + }); - it("1.3 - removes the error when the field becomes valid.", async function () { - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=name]"); + it("1.3 - removes the error when the field becomes valid.", async function () { + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=name]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning").length).toBe(1); - inp.value = "abc"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = "abc"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("1.4 - multiple error messages on multiple errors.", async function () { - document.body.innerHTML = ` -
- - -
- `; - const el = document.querySelector(".pat-validation"); - const inp1 = el.querySelector("[name=name1]"); - const inp2 = el.querySelector("[name=name2]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - inp1.value = ""; - inp1.dispatchEvent(events.change_event()); - inp2.value = ""; - inp2.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(2); - }); + it("1.4 - multiple error messages on multiple errors.", async function () { + document.body.innerHTML = ` +
+ + +
+ `; + const el = document.querySelector(".pat-validation"); + const inp1 = el.querySelector("[name=name1]"); + const inp2 = el.querySelector("[name=name2]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp1.value = ""; + inp1.dispatchEvent(events.change_event()); + inp2.value = ""; + inp2.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(2); + }); - it("1.5 - removes one error message but keeps the other", async function () { - document.body.innerHTML = ` -
- - -
- `; - const el = document.querySelector(".pat-validation"); - const inp1 = el.querySelector("[name=name1]"); - const inp2 = el.querySelector("[name=name2]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - inp1.value = ""; - inp1.dispatchEvent(events.change_event()); - inp2.value = ""; - inp2.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(2); - - inp2.value = "abc"; - inp2.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - - inp1.value = "abc"; - inp1.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + it("1.5 - removes one error message but keeps the other", async function () { + document.body.innerHTML = ` +
+ + +
+ `; + const el = document.querySelector(".pat-validation"); + const inp1 = el.querySelector("[name=name1]"); + const inp2 = el.querySelector("[name=name2]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp1.value = ""; + inp1.dispatchEvent(events.change_event()); + inp2.value = ""; + inp2.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(2); + + inp2.value = "abc"; + inp2.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + + inp1.value = "abc"; + inp1.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("1.6 - can use a globally configured error message and overwrite on a per field basis.", async function () { - document.body.innerHTML = ` -
- - -
- `; - const el = document.querySelector(".pat-validation"); - const inp1 = el.querySelector("[name=name1]"); - const inp2 = el.querySelector("[name=name2]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - inp1.value = ""; - inp1.dispatchEvent(events.change_event()); - inp2.value = ""; - inp2.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(2); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe("need this 1"); - expect(el.querySelectorAll("em.warning")[1].textContent).toBe("need this 2"); - }); + it("1.6 - can use a globally configured error message and overwrite on a per field basis.", async function () { + document.body.innerHTML = ` +
+ + +
+ `; + const el = document.querySelector(".pat-validation"); + const inp1 = el.querySelector("[name=name1]"); + const inp2 = el.querySelector("[name=name2]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp1.value = ""; + inp1.dispatchEvent(events.change_event()); + inp2.value = ""; + inp2.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(2); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe("need this 1"); + expect(el.querySelectorAll("em.warning")[1].textContent).toBe("need this 2"); + }); - it("1.7 - doesn't validate disabled elements", async function () { - document.body.innerHTML = ` -
- -
- `; + it("1.7 - doesn't validate disabled elements", async function () { + document.body.innerHTML = ` +
+ +
+ `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=input]"); + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=input]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("1.8 - can disable certain form elements when validation fails", async function () { - // Tests the disable-selector argument - document.body.innerHTML = ` -
- - -
- `; - - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=input]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - inp.value = 4.5; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "This value must be an integer." - ); - expect(el.querySelector("#form-buttons-create").disabled).toBe(true); - - inp.value = 5; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - expect(el.querySelector("#form-buttons-create").disabled).toBe(false); - }); + it("1.8 - can disable certain form elements when validation fails", async function () { + // Tests the disable-selector argument + document.body.innerHTML = ` +
+ + +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=input]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp.value = 4.5; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "This value must be an integer." + ); + expect(el.querySelector("#form-buttons-create").disabled).toBe(true); + + inp.value = 5; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + expect(el.querySelector("#form-buttons-create").disabled).toBe(false); + }); - it("1.9 - can define a custom error message template by subclassing.", async function () { - class CustomValidation extends Pattern { - error_template(message) { - return `
${message}
`; + it("1.9 - can define a custom error message template by subclassing.", async function () { + class CustomValidation extends Pattern { + error_template(message) { + return `
${message}
`; + } } - } - - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=name]"); - - const instance = new CustomValidation(el); - await events.await_pattern_init(instance); - - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("div.validation-error").length).toBe(1); - expect(el.querySelectorAll("div.validation-error")[0].textContent).toBe( - "need this" - ); - }); - it("1.10 - Adds a novalidate attribute to not show the browsers validation bubbles.", async function () { - // See: https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#a_more_detailed_example - // The ``novalidate`` attribute does not deactivate the validation API - // but prevents the browser from showing validation messages by itself. - document.body.innerHTML = ` -
-
- `; - const el = document.querySelector(".pat-validation"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - expect(el.hasAttribute("novalidate")).toBe(true); - }); + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=name]"); + + const instance = new CustomValidation(el); + await events.await_pattern_init(instance); + + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("div.validation-error").length).toBe(1); + expect(el.querySelectorAll("div.validation-error")[0].textContent).toBe( + "need this" + ); + }); - it("1.11 - Prevents submit when invalid.", async function () { - document.body.innerHTML = ` -
- - -
- `; - const el = document.querySelector(".pat-validation"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - el.querySelector("button").click(); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - }); + it("1.10 - Adds a novalidate attribute to not show the browsers validation bubbles.", async function () { + // See: https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#a_more_detailed_example + // The ``novalidate`` attribute does not deactivate the validation API + // but prevents the browser from showing validation messages by itself. + document.body.innerHTML = ` +
+
+ `; + const el = document.querySelector(".pat-validation"); + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + expect(el.hasAttribute("novalidate")).toBe(true); + }); - it("1.12 - Allows submit with ``formnovalidate``.", async function () { - // Buttons with ``formnovalidate`` should prevent validation. + it("1.11 - Prevents submit when invalid.", async function () { + document.body.innerHTML = ` +
+ + +
+ `; + const el = document.querySelector(".pat-validation"); + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + el.querySelector("button").click(); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + }); - document.body.innerHTML = ` -
- - -
- `; - const el = document.querySelector(".pat-validation"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + it("1.12 - Allows submit with ``formnovalidate``.", async function () { + // Buttons with ``formnovalidate`` should prevent validation. - el.querySelector("button").click(); - await utils.timeout(1); // wait a tick for async to settle. + document.body.innerHTML = ` +
+ + +
+ `; + const el = document.querySelector(".pat-validation"); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + el.querySelector("button").click(); + await utils.timeout(1); // wait a tick for async to settle. - it("1.13 - Prevents other event handlers when invalid.", async function () { - document.body.innerHTML = ` -
- - -
- `; - const el = document.querySelector(".pat-validation"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - let submit_called = false; - let click_called = false; - - // Note: the handlers must be registered after Pattern initialization. - // Otherwise the pattern will not be able to prevent the event. - // In case of other patterns, the validation pattern will be reordered - // first and submit prevention does work. - el.addEventListener("submit", () => (submit_called = true)); - el.addEventListener("click", () => (click_called = true)); - - el.querySelector("button").click(); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(submit_called).toBe(false); - expect(click_called).toBe(true); - }); + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("1.14 - Prevents pat-inject form submission when invalid.", async function () { - const pat_inject = (await import("../inject/inject")).default; - const registry = (await import("../../core/registry")).default; + it("1.13 - Prevents other event handlers when invalid.", async function () { + document.body.innerHTML = ` +
+ + +
+ `; + const el = document.querySelector(".pat-validation"); + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + let submit_called = false; + let click_called = false; + + // Note: the handlers must be registered after Pattern initialization. + // Otherwise the pattern will not be able to prevent the event. + // In case of other patterns, the validation pattern will be reordered + // first and submit prevention does work. + el.addEventListener("submit", () => (submit_called = true)); + el.addEventListener("click", () => (click_called = true)); + + el.querySelector("button").click(); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(submit_called).toBe(false); + expect(click_called).toBe(true); + }); - document.body.innerHTML = ` -
- - -
- `; - const el = document.querySelector(".pat-validation"); + it("1.14 - Prevents pat-inject form submission when invalid.", async function () { + const pat_inject = (await import("../inject/inject")).default; + const registry = (await import("../../core/registry")).default; - const spy_inject_submit = jest.spyOn(pat_inject, "onTrigger"); + document.body.innerHTML = ` +
+ + +
+ `; + const el = document.querySelector(".pat-validation"); - registry.scan(document.body); - await utils.timeout(1); // wait a tick for async to settle. + const spy_inject_submit = jest.spyOn(pat_inject, "onTrigger"); - el.querySelector("button").click(); - await utils.timeout(1); // wait a tick for async to settle. + registry.scan(document.body); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(spy_inject_submit).not.toHaveBeenCalled(); - }); + el.querySelector("button").click(); + await utils.timeout(1); // wait a tick for async to settle. - it("1.15 - Prevents pat-modal closing with a pat-inject when invalid.", async function () { - await import("../close-panel/close-panel"); - const pat_inject = (await import("../inject/inject")).default; - const pat_modal = (await import("../modal/modal")).default; - const registry = (await import("../../core/registry")).default; - - document.body.innerHTML = ` -
-
- - -
-
- `; - const el = document.querySelector("form"); - - const spy_inject_submit = jest.spyOn(pat_inject, "onTrigger"); - const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy"); - - registry.scan(document.body); - await utils.timeout(1); // wait a tick for async to settle. - - el.querySelector("button").click(); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(spy_inject_submit).not.toHaveBeenCalled(); - expect(spy_destroy_modal).not.toHaveBeenCalled(); - }); + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(spy_inject_submit).not.toHaveBeenCalled(); + }); - it("1.16 - Prevents pat-modal closing when invalid.", async function () { - await import("../close-panel/close-panel"); - const pat_modal = (await import("../modal/modal")).default; - const registry = (await import("../../core/registry")).default; + it("1.15 - Prevents pat-modal closing with a pat-inject when invalid.", async function () { + await import("../close-panel/close-panel"); + const pat_inject = (await import("../inject/inject")).default; + const pat_modal = (await import("../modal/modal")).default; + const registry = (await import("../../core/registry")).default; + + document.body.innerHTML = ` +
+
+ + +
+
+ `; + const el = document.querySelector("form"); + + const spy_inject_submit = jest.spyOn(pat_inject, "onTrigger"); + const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy"); + + registry.scan(document.body); + await utils.timeout(1); // wait a tick for async to settle. + + el.querySelector("button").click(); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(spy_inject_submit).not.toHaveBeenCalled(); + expect(spy_destroy_modal).not.toHaveBeenCalled(); + }); - document.body.innerHTML = ` -
-
- - -
-
- `; - const el = document.querySelector("form"); + it("1.16 - Prevents pat-modal closing when invalid.", async function () { + await import("../close-panel/close-panel"); + const pat_modal = (await import("../modal/modal")).default; + const registry = (await import("../../core/registry")).default; - const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy"); + document.body.innerHTML = ` +
+
+ + +
+
+ `; + const el = document.querySelector("form"); - registry.scan(document.body); - await utils.timeout(1); // wait a tick for async to settle. - await utils.timeout(1); // wait another tick + const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy"); - el.querySelector("button").click(); - await utils.timeout(1); // wait a tick for async to settle. + registry.scan(document.body); + await utils.timeout(1); // wait a tick for async to settle. + await utils.timeout(1); // wait another tick - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(spy_destroy_modal).not.toHaveBeenCalled(); - }); + el.querySelector("button").click(); + await utils.timeout(1); // wait a tick for async to settle. - it("1.17 - Prevents pat-modal closing when invalid with custom validation rule.", async function () { - await import("../close-panel/close-panel"); - const pat_modal = (await import("../modal/modal")).default; - const registry = (await import("../../core/registry")).default; - - const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy"); - - document.body.innerHTML = ` -
-
- - - - -
-
- `; - const el = document.querySelector("form"); - const inp_ok = document.querySelector("input[name=ok]"); - inp_ok.value = "foo"; - - registry.scan(document.body); - await utils.timeout(1); // wait a tick for async to settle. - await utils.timeout(1); // wait another tick - - el.querySelector("button.submit").click(); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(spy_destroy_modal).not.toHaveBeenCalled(); - - // A non-submit close-panel button does not check for validity. - el.querySelector("button.cancel").click(); - await utils.timeout(1); // wait a tick for async to settle. - - expect(spy_destroy_modal).toHaveBeenCalled(); - }); + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(spy_destroy_modal).not.toHaveBeenCalled(); + }); - it("1.18 - validates all inputs after submit", async function () { - document.body.innerHTML = ` -
- - - -
- `; - const el = document.querySelector(".pat-validation"); + it("1.17 - Prevents pat-modal closing when invalid with custom validation rule.", async function () { + await import("../close-panel/close-panel"); + const pat_modal = (await import("../modal/modal")).default; + const registry = (await import("../../core/registry")).default; + + const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy"); + + document.body.innerHTML = ` +
+
+ + + + +
+
+ `; + const el = document.querySelector("form"); + const inp_ok = document.querySelector("input[name=ok]"); + inp_ok.value = "foo"; + + registry.scan(document.body); + await utils.timeout(1); // wait a tick for async to settle. + await utils.timeout(1); // wait another tick + + el.querySelector("button.submit").click(); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(spy_destroy_modal).not.toHaveBeenCalled(); + + // A non-submit close-panel button does not check for validity. + el.querySelector("button.cancel").click(); + await utils.timeout(1); // wait a tick for async to settle. + + expect(spy_destroy_modal).toHaveBeenCalled(); + }); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + it("1.18 - validates all inputs after submit", async function () { + document.body.innerHTML = ` +
+ + + +
+ `; + const el = document.querySelector(".pat-validation"); - document.querySelector("button").click(); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - expect(el.querySelectorAll("em.warning").length).toBe(2); - }); + document.querySelector("button").click(); - it("1.19 - validates all inputs after one failed check and disabled button", async function () { - document.body.innerHTML = ` -
- - - - - -
- `; - const el = document.querySelector(".pat-validation"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - document.querySelector("[name=i1]").dispatchEvent(events.blur_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(2); - expect(el.querySelector("[name=b1]").disabled).toBe(true); - expect(el.querySelector("[name=b2]").disabled).toBe(false); - expect(el.querySelector("[name=b3]").disabled).toBe(true); - }); + expect(el.querySelectorAll("em.warning").length).toBe(2); + }); - it("1.20 - does not validate all inputs after one failed check and no disabled button", async function () { - document.body.innerHTML = ` -
- - -
- `; - const el = document.querySelector(".pat-validation"); + it("1.19 - validates all inputs after one failed check and disabled button", async function () { + document.body.innerHTML = ` +
+ + + + + +
+ `; + const el = document.querySelector(".pat-validation"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + document.querySelector("[name=i1]").dispatchEvent(events.blur_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(2); + expect(el.querySelector("[name=b1]").disabled).toBe(true); + expect(el.querySelector("[name=b2]").disabled).toBe(false); + expect(el.querySelector("[name=b3]").disabled).toBe(true); + }); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + it("1.20 - does not validate all inputs after one failed check and no disabled button", async function () { + document.body.innerHTML = ` +
+ + +
+ `; + const el = document.querySelector(".pat-validation"); - document.querySelector("[name=i1]").dispatchEvent(events.blur_event()); - await utils.timeout(1); // wait a tick for async to settle. + const instance = new Pattern(el); + await events.await_pattern_init(instance); - expect(el.querySelectorAll("em.warning").length).toBe(1); - }); + document.querySelector("[name=i1]").dispatchEvent(events.blur_event()); + await utils.timeout(1); // wait a tick for async to settle. - it("1.21 - Emits an update event when the validation state changes", async function () { - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=name]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - let event; - el.addEventListener("pat-update", (e) => { - event = e; + expect(el.querySelectorAll("em.warning").length).toBe(1); }); - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(event.detail.pattern).toBe("validation"); - expect(event.detail.dom).toBe(el); - expect(event.detail.action).toBe("invalid"); - - inp.value = "okay"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(event.detail.pattern).toBe("validation"); - expect(event.detail.dom).toBe(el); - expect(event.detail.action).toBe("valid"); + it("1.21 - Emits an update event when the validation state changes", async function () { + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=name]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + let event; + el.addEventListener("pat-update", (e) => { + event = e; + }); + + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(event.detail.pattern).toBe("validation"); + expect(event.detail.dom).toBe(el); + expect(event.detail.action).toBe("invalid"); + + inp.value = "okay"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(event.detail.pattern).toBe("validation"); + expect(event.detail.dom).toBe(el); + expect(event.detail.action).toBe("valid"); + }); }); - it("2.1 - validates required inputs", async function () { - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=name]"); + describe("2 - required inputs", function () { + it("2.1 - validates required inputs", async function () { + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=name]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning").length).toBe(1); - // Check validation passes with some input. - inp.value = "123"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + // Check validation passes with some input. + inp.value = "123"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("2.2 - validates required pat-autosuggest inputs on form submit", async function () { - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=name]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - el.dispatchEvent(events.submit_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - - // Check validation passes with some input. - inp.value = "one"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + it("2.2 - validates required pat-autosuggest inputs on form submit", async function () { + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=name]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + el.dispatchEvent(events.submit_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + + // Check validation passes with some input. + inp.value = "one"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("2.3 - validates required inputs with HTML5 required attribute style", async function () { - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=name]"); + it("2.3 - validates required inputs with HTML5 required attribute style", async function () { + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=name]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - }); + expect(el.querySelectorAll("em.warning").length).toBe(1); + }); - it("2.4 - can show custom validation messages", async function () { - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=name]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelector("em.warning").textContent).toBe( - "I'm sorry Dave, I can't let you do that." - ); - }); + it("2.4 - can show custom validation messages", async function () { + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=name]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelector("em.warning").textContent).toBe( + "I'm sorry Dave, I can't let you do that." + ); + }); - it("2.5 - can show custom per-field validation messages", async function () { - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=name]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelector("em.warning").textContent).toBe("Computer says no."); + it("2.5 - can show custom per-field validation messages", async function () { + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=name]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelector("em.warning").textContent).toBe("Computer says no."); + }); }); - it("3.1 - validates email inputs", async function () { - document.body.innerHTML = ` -
- -
- `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=email]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - inp.value = "invalid email"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelector("em.warning").textContent).toBe( - "This value must be a valid email address." - ); - - inp.value = "person@mail.com"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); + describe("3 - email inputs", function () { + it("3.1 - validates email inputs", async function () { + document.body.innerHTML = ` +
+ +
+ `; + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=email]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp.value = "invalid email"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelector("em.warning").textContent).toBe( + "This value must be a valid email address." + ); + + inp.value = "person@mail.com"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); }); - it("4.1 - validates number limits", async function () { - document.body.innerHTML = ` -
- - -
- `; - const el = document.querySelector(".pat-validation"); - const inp_min = el.querySelector("[name=min]"); - const inp_max = el.querySelector("[name=max]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - // min - - inp_min.value = 4; - inp_min.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelector("em.warning").textContent).toBe( - "This value must be greater than or equal to 5" - ); - - inp_min.value = 6; - inp_min.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(0); - - // max - - inp_max.value = 6; - inp_max.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelector("em.warning").textContent).toBe( - "This value must be less than or equal to 5" - ); - - inp_max.value = 5; - inp_max.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + describe("4 - number inputs", function () { + it("4.1 - validates number limits", async function () { + document.body.innerHTML = ` +
+ + +
+ `; + const el = document.querySelector(".pat-validation"); + const inp_min = el.querySelector("[name=min]"); + const inp_max = el.querySelector("[name=max]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + // min + + inp_min.value = 4; + inp_min.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelector("em.warning").textContent).toBe( + "This value must be greater than or equal to 5" + ); + + inp_min.value = 6; + inp_min.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(0); + + // max + + inp_max.value = 6; + inp_max.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelector("em.warning").textContent).toBe( + "This value must be less than or equal to 5" + ); + + inp_max.value = 5; + inp_max.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("4.2 - validates integers", async function () { - document.body.innerHTML = ` -
- -
- `; + it("4.2 - validates integers", async function () { + document.body.innerHTML = ` +
+ +
+ `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=testing]"); + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=testing]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.value = 4.5; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = 4.5; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning").length).toBe(1); - inp.value = 4; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = 4; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("4.3 - validates real numbers within steps", async function () { - document.body.innerHTML = ` -
- -
- `; + it("4.3 - validates real numbers within steps", async function () { + document.body.innerHTML = ` +
+ +
+ `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=testing]"); + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=testing]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.value = 4.55; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = 4.55; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning").length).toBe(1); - inp.value = 4.5; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = 4.5; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); + expect(el.querySelectorAll("em.warning").length).toBe(0); - inp.value = 4; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = 4; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("4.4 - validates real number with any number of decimal places.", async function () { - document.body.innerHTML = ` -
- -
- `; - - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=testing]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - // Let's provoke an error first. - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - - inp.value = 3.14159265359; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - inp.value = 3.14; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - inp.value = 3; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + it("4.4 - validates real number with any number of decimal places.", async function () { + document.body.innerHTML = ` +
+ +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=testing]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + // Let's provoke an error first. + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + + inp.value = 3.14159265359; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + inp.value = 3.14; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + inp.value = 3; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("4.5 - Number: Checks for correct usage of error messages.", async function () { - document.body.innerHTML = ` -
- -
- `; + it("4.5 - Number: Checks for correct usage of error messages.", async function () { + document.body.innerHTML = ` +
+ +
+ `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=testing]"); + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=testing]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.value = 4.5; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = 4.5; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "The value must be an integer." - ); + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "The value must be an integer." + ); - inp.value = 5; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = 5; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("4.6 - Number: Checks for the message-integer message alias for message-number.", async function () { - document.body.innerHTML = ` -
- -
- `; + it("4.6 - Number: Checks for the message-integer message alias for message-number.", async function () { + document.body.innerHTML = ` +
+ +
+ `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=testing]"); + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=testing]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.value = 4.5; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = 4.5; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "The value must be an integer." - ); + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "The value must be an integer." + ); - inp.value = 5; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = 5; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); }); - // Using ``type="text"`` for date validation as Constraints API - // ``ValidityState`` information is not updated after programmatically - // setting values. - // See: https://twitter.com/thetetet/status/1285239806205755393 - it.skip("5.1 - validates dates with before/after constraints", async function () { - document.body.innerHTML = ` -
- -
- `; - - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=date]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); + describe("5 - date inputs", function () { + // Using ``type="text"`` for date validation as Constraints API + // ``ValidityState`` information is not updated after programmatically + // setting values. + // See: https://twitter.com/thetetet/status/1285239806205755393 + it.skip("5.1 - validates dates with before/after constraints", async function () { + document.body.innerHTML = ` +
+ +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=date]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp.value = "2020-02-30"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe("Wong date!"); + }); - inp.value = "2020-02-30"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe("Wong date!"); - }); + it("5.2 - validates dates with before/after as min/max attributes with default error message.", async function () { + document.body.innerHTML = ` +
+ +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=date]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + // No error when left empty and not required. + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + // No error when left empty and not required. + inp.value = "2010-10-10"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + }); - it("5.2 - validates dates with before/after as min/max attributes with default error message.", async function () { - document.body.innerHTML = ` -
- -
- `; - - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=date]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - // No error when left empty and not required. - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - // No error when left empty and not required. - inp.value = "2010-10-10"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - }); + it("5.3 - validates dates with before/after as min/max attributes with custom error message.", async function () { + document.body.innerHTML = ` +
+ +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=date]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + // No error when left empty and not required. + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + inp.value = "2010-10-10"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe("Wong date!"); + + inp.value = "2023-02-23"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe("Wong date!"); + + inp.value = "2022-01-01"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("5.3 - validates dates with before/after as min/max attributes with custom error message.", async function () { - document.body.innerHTML = ` -
- -
- `; - - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=date]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - // No error when left empty and not required. - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - inp.value = "2010-10-10"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe("Wong date!"); - - inp.value = "2023-02-23"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe("Wong date!"); - - inp.value = "2022-01-01"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + it("5.4.1 - validates dates with before/after as pattern config attributes with custom error message.", async function () { + document.body.innerHTML = ` +
+ +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=date]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + // No error when left empty and not required. + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + inp.value = "2010-10-10"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe("Wong date!"); + + inp.value = "2023-02-23"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe("Wong date!"); + + inp.value = "2022-01-01"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("5.4.1 - validates dates with before/after as pattern config attributes with custom error message.", async function () { - document.body.innerHTML = ` -
- -
- `; - - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=date]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - // No error when left empty and not required. - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - inp.value = "2010-10-10"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe("Wong date!"); - - inp.value = "2023-02-23"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe("Wong date!"); - - inp.value = "2022-01-01"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + it("5.4.2 - validates dates with before/after as pattern config attributes with NO custom error message, using fixed dates.", async function () { + document.body.innerHTML = ` +
+ +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=date]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + // No error when left empty and not required. + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + inp.value = "2010-10-10"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "The date must be after 2011-11-11" + ); + + inp.value = "2023-02-23"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "The date must be before 2022-02-22" + ); + + inp.value = "2022-01-01"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("5.4.2 - validates dates with before/after as pattern config attributes with NO custom error message, using fixed dates.", async function () { - document.body.innerHTML = ` -
- -
- `; - - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=date]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - // No error when left empty and not required. - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - inp.value = "2010-10-10"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "The date must be after 2011-11-11" - ); - - inp.value = "2023-02-23"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "The date must be before 2022-02-22" - ); - - inp.value = "2022-01-01"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + it("5.4.3 - validates dates with before/after as pattern config attributes with NO custom error message, using labels.", async function () { + document.body.innerHTML = ` +
+ + +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp1 = el.querySelector("[name=date1]"); + const inp2 = el.querySelector("[name=date2]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp1.value = "2010-10-10"; + inp2.value = "2001-01-01"; + + inp1.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(2); + + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "The date must be before woo date" + ); + + expect(el.querySelectorAll("em.warning")[1].textContent).toBe( + "The date must be after ye date" + ); + }); - it("5.4.3 - validates dates with before/after as pattern config attributes with NO custom error message, using labels.", async function () { - document.body.innerHTML = ` -
- - -
- `; + + `; - const el = document.querySelector(".pat-validation"); - const inp1 = el.querySelector("[name=date1]"); - const inp2 = el.querySelector("[name=date2]"); + const el = document.querySelector(".pat-validation"); + const inp1 = el.querySelector("[name=date1]"); + const inp2 = el.querySelector("[name=date2]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp1.value = "2010-10-10"; - inp2.value = "2001-01-01"; + inp1.value = "2010-10-10"; + inp2.value = "2001-01-01"; - inp1.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(2); + inp1.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(2); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "The date must be before woo date" - ); - - expect(el.querySelectorAll("em.warning")[1].textContent).toBe( - "The date must be after ye date" - ); - }); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "The date must be before date2" + ); - it("5.4.4 - validates dates with before/after as pattern config attributes with NO custom error message, using input names.", async function () { - document.body.innerHTML = ` -
- - -
- `; - - const el = document.querySelector(".pat-validation"); - const inp1 = el.querySelector("[name=date1]"); - const inp2 = el.querySelector("[name=date2]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - inp1.value = "2010-10-10"; - inp2.value = "2001-01-01"; - - inp1.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(2); - - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "The date must be before date2" - ); - - expect(el.querySelectorAll("em.warning")[1].textContent).toBe( - "The date must be after date1" - ); - }); + expect(el.querySelectorAll("em.warning")[1].textContent).toBe( + "The date must be after date1" + ); + }); - it("5.5 - validates dates with before/after constraints", async function () { - document.body.innerHTML = ` -
- - -
- `; - - const el = document.querySelector(".pat-validation"); - const inp_start = el.querySelector("[name=start]"); - const inp_end = el.querySelector("[name=end]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - // Before/after without required allows for empty dates of the - // relation. - inp_start.value = "2020-10-10"; - inp_start.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - // Violate the before/after constraint - inp_end.value = "2020-10-05"; - inp_end.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(2); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "The start date must on or before the end date." - ); - expect(el.querySelectorAll("em.warning")[1].textContent).toBe( - "The end date must on or before the start date." - ); - - // Fulfill the before/after constraint - same date - inp_start.value = "2020-10-10"; - inp_start.dispatchEvent(events.change_event()); - inp_end.value = "2020-10-10"; - inp_end.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - // Violate the before/after constraint - inp_start.value = "2020-10-11"; - inp_start.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(2); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "The start date must on or before the end date." - ); - expect(el.querySelectorAll("em.warning")[1].textContent).toBe( - "The end date must on or before the start date." - ); - - // Fulfill the before/after constraint - start before end - inp_start.value = "2020-10-01"; - inp_start.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - // Before/after without required allows for empty dates of the - // relation. - inp_start.value = ""; - inp_start.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - // Violate the constraint again... - inp_start.value = "2020-10-11"; - inp_start.dispatchEvent(events.change_event()); - inp_end.value = "2020-10-10"; - inp_end.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(2); - - // Clearing one of the optional values should clear all errors. - inp_start.value = ""; - inp_start.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + it("5.5 - validates dates with before/after constraints", async function () { + document.body.innerHTML = ` +
+ + +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp_start = el.querySelector("[name=start]"); + const inp_end = el.querySelector("[name=end]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + // Before/after without required allows for empty dates of the + // relation. + inp_start.value = "2020-10-10"; + inp_start.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + // Violate the before/after constraint + inp_end.value = "2020-10-05"; + inp_end.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(2); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "The start date must on or before the end date." + ); + expect(el.querySelectorAll("em.warning")[1].textContent).toBe( + "The end date must on or before the start date." + ); + + // Fulfill the before/after constraint - same date + inp_start.value = "2020-10-10"; + inp_start.dispatchEvent(events.change_event()); + inp_end.value = "2020-10-10"; + inp_end.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + // Violate the before/after constraint + inp_start.value = "2020-10-11"; + inp_start.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(2); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "The start date must on or before the end date." + ); + expect(el.querySelectorAll("em.warning")[1].textContent).toBe( + "The end date must on or before the start date." + ); + + // Fulfill the before/after constraint - start before end + inp_start.value = "2020-10-01"; + inp_start.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + // Before/after without required allows for empty dates of the + // relation. + inp_start.value = ""; + inp_start.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + // Violate the constraint again... + inp_start.value = "2020-10-11"; + inp_start.dispatchEvent(events.change_event()); + inp_end.value = "2020-10-10"; + inp_end.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(2); + + // Clearing one of the optional values should clear all errors. + inp_start.value = ""; + inp_start.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("5.6 - doesn't validate empty optional dates", async function () { - document.body.innerHTML = ` -
- -
- `; + it("5.6 - doesn't validate empty optional dates", async function () { + document.body.innerHTML = ` +
+ +
+ `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=date]"); + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=date]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("5.7 - do require-validate non-empty required dates", async function () { - document.body.innerHTML = ` -
- -
- `; + it("5.7 - do require-validate non-empty required dates", async function () { + document.body.innerHTML = ` +
+ +
+ `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=input]"); + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=input]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.value = ""; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = ""; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "This field is required" - ); - }); + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "This field is required" + ); + }); - it("5.8 - validates datetime-local with before/after constraints", async function () { - document.body.innerHTML = ` -
- - -
- `; - - const el = document.querySelector(".pat-validation"); - const inp_start = el.querySelector("[name=start]"); - const inp_end = el.querySelector("[name=end]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - // Before/after without required allows for empty dates of the - // relation. - inp_start.value = "2022-01-05T10:00"; - inp_start.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - // Violate the before/after constraint - inp_end.value = "2022-01-05T09:00"; - inp_end.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(2); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "The start date/time must on or before the end date/time." - ); - expect(el.querySelectorAll("em.warning")[1].textContent).toBe( - "The end date/time must on or before the start date/time." - ); - - // Fulfill the before/after constraint - same date - inp_start.value = "2022-01-05T10:00"; - inp_start.dispatchEvent(events.change_event()); - inp_end.value = "2022-01-05T10:00"; - inp_end.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - // Violate the before/after constraint - inp_start.value = "2022-01-05T11:00"; - inp_start.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(2); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "The start date/time must on or before the end date/time." - ); - expect(el.querySelectorAll("em.warning")[1].textContent).toBe( - "The end date/time must on or before the start date/time." - ); - - // Fulfill the before/after constraint - start before end - inp_start.value = "2022-01-04T10:00"; - inp_start.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - - // Before/after without required allows for empty dates of the - // relation. - inp_start.value = ""; - inp_start.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - }); + it("5.8 - validates datetime-local with before/after constraints", async function () { + document.body.innerHTML = ` +
+ + +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp_start = el.querySelector("[name=start]"); + const inp_end = el.querySelector("[name=end]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + // Before/after without required allows for empty dates of the + // relation. + inp_start.value = "2022-01-05T10:00"; + inp_start.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + // Violate the before/after constraint + inp_end.value = "2022-01-05T09:00"; + inp_end.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(2); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "The start date/time must on or before the end date/time." + ); + expect(el.querySelectorAll("em.warning")[1].textContent).toBe( + "The end date/time must on or before the start date/time." + ); + + // Fulfill the before/after constraint - same date + inp_start.value = "2022-01-05T10:00"; + inp_start.dispatchEvent(events.change_event()); + inp_end.value = "2022-01-05T10:00"; + inp_end.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + // Violate the before/after constraint + inp_start.value = "2022-01-05T11:00"; + inp_start.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(2); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "The start date/time must on or before the end date/time." + ); + expect(el.querySelectorAll("em.warning")[1].textContent).toBe( + "The end date/time must on or before the start date/time." + ); + + // Fulfill the before/after constraint - start before end + inp_start.value = "2022-01-04T10:00"; + inp_start.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + + // Before/after without required allows for empty dates of the + // relation. + inp_start.value = ""; + inp_start.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); - it("5.9 - Do not interpret ``ok-1`` as a valid date.", async function () { - // This issue popped up in Chrome but not in Firefox. - // A date like ``ok-1`` was interpreted as ``2000-12-31T23:00:00.000Z``. - // Explicitly checking for a valid ISO 8601 date fixes this. + it("5.9 - Do not interpret ``ok-1`` as a valid date.", async function () { + // This issue popped up in Chrome but not in Firefox. + // A date like ``ok-1`` was interpreted as ``2000-12-31T23:00:00.000Z``. + // Explicitly checking for a valid ISO 8601 date fixes this. - document.body.innerHTML = ` -
- -
- `; + document.body.innerHTML = ` +
+ +
+ `; - const el = document.querySelector(".pat-validation"); - const inp = el.querySelector("[name=date]"); + const el = document.querySelector(".pat-validation"); + const inp = el.querySelector("[name=date]"); - const instance = new Pattern(el); - await events.await_pattern_init(instance); + const instance = new Pattern(el); + await events.await_pattern_init(instance); - inp.value = "2022-01-01"; - inp.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. + inp.value = "2022-01-01"; + inp.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); + expect(el.querySelectorAll("em.warning").length).toBe(0); + }); }); - it("6.1 - validates radio buttons", async function () { - document.body.innerHTML = ` -
- - - - -
- `; - - const el = document.querySelector(".pat-validation"); - const inps = el.querySelectorAll("[name=colour]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - el.dispatchEvent(events.submit_event()); - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "This field is required" - ); - - inps[0].checked = true; - el.addEventListener("submit", (e) => { - e.preventDefault(); - e.stopPropagation(); + describe("6 - radio inputs", function () { + it("6.1 - validates radio buttons", async function () { + document.body.innerHTML = ` +
+ + + + +
+ `; + + const el = document.querySelector(".pat-validation"); + const inps = el.querySelectorAll("[name=colour]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + el.dispatchEvent(events.submit_event()); + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "This field is required" + ); + + inps[0].checked = true; + el.addEventListener("submit", (e) => { + e.preventDefault(); + e.stopPropagation(); + }); + el.dispatchEvent(events.submit_event()); + + await utils.timeout(1); // wait a tick for async to settle. + + expect(el.querySelectorAll("em.warning").length).toBe(0); }); - el.dispatchEvent(events.submit_event()); - - await utils.timeout(1); // wait a tick for async to settle. - - expect(el.querySelectorAll("em.warning").length).toBe(0); }); - it("7.1 - can check for password confirmation", async function () { - document.body.innerHTML = ` -
- - - -
- `; - - const el = document.querySelector(".pat-validation"); - const inp_p = el.querySelector("[name=password]"); - const inp_c = el.querySelector("[name=password-confirmation]"); - - const instance = new Pattern(el); - await events.await_pattern_init(instance); - - inp_p.value = "foo"; - inp_c.value = "bar"; - inp_c.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(1); - expect(el.querySelectorAll("em.warning")[0].textContent).toBe( - "I would like this to be equal to password" - ); - expect(el.querySelector("#form-buttons-create").disabled).toBe(true); - - inp_c.value = "foo"; - inp_c.dispatchEvent(events.change_event()); - await utils.timeout(1); // wait a tick for async to settle. - expect(el.querySelectorAll("em.warning").length).toBe(0); - expect(el.querySelector("#form-buttons-create").disabled).toBe(false); + describe("7 - password inputs", function () { + it("7.1 - can check for password confirmation", async function () { + document.body.innerHTML = ` +
+ + + +
+ `; + + const el = document.querySelector(".pat-validation"); + const inp_p = el.querySelector("[name=password]"); + const inp_c = el.querySelector("[name=password-confirmation]"); + + const instance = new Pattern(el); + await events.await_pattern_init(instance); + + inp_p.value = "foo"; + inp_c.value = "bar"; + inp_c.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(1); + expect(el.querySelectorAll("em.warning")[0].textContent).toBe( + "I would like this to be equal to password" + ); + expect(el.querySelector("#form-buttons-create").disabled).toBe(true); + + inp_c.value = "foo"; + inp_c.dispatchEvent(events.change_event()); + await utils.timeout(1); // wait a tick for async to settle. + expect(el.querySelectorAll("em.warning").length).toBe(0); + expect(el.querySelector("#form-buttons-create").disabled).toBe(false); + }); }); });