Skip to content

Commit 27c0d19

Browse files
authored
Merge pull request #1002 from Patternslib/fix-pat-validation-chrome
Fix pat validation chrome
2 parents 07f9133 + 39249b2 commit 27c0d19

File tree

4 files changed

+116
-30
lines changed

4 files changed

+116
-30
lines changed

src/core/utils.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ const ensureArray = (it, force_array) => {
576576

577577
const localized_isodate = (date) => {
578578
// Return a iso date (date only) in the current timezone instead of a
579-
// UTC ISO 8602 date+time component which toISOString returns.
579+
// UTC ISO 8601 date+time component which toISOString returns.
580580

581581
const day = date.getDate().toString().padStart(2, "0");
582582
const month = (date.getMonth() + 1).toString().padStart(2, "0");
@@ -628,6 +628,20 @@ const unescape_html = (escaped_html) => {
628628
.replace(/"/g, '"');
629629
};
630630

631+
/**
632+
* Return true, if the given value is a valid ISO 8601 date/time string with or without an optional time component.
633+
*
634+
* @param {String} value - The date/time value to be checked.
635+
* @param {Boolean} [optional_time=false] - True, if time component is optional.
636+
* @return {Boolean} - True, if the given value is a valid Date string. False if not.
637+
*/
638+
const is_iso_date_time = (value, optional_time = false) => {
639+
const re_date_time = optional_time
640+
? /^\d{4}-[01]\d-[0-3]\d(T[0-2]\d:[0-5]\d)?$/
641+
: /^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d$/;
642+
return re_date_time.test(value);
643+
};
644+
631645
var utils = {
632646
// pattern pimping - own module?
633647
jqueryPlugin: jqueryPlugin,
@@ -658,6 +672,7 @@ var utils = {
658672
localized_isodate: localized_isodate,
659673
escape_html: escape_html,
660674
unescape_html: unescape_html,
675+
is_iso_date_time: is_iso_date_time,
661676
getCSSValue: dom.get_css_value, // BBB: moved to dom. TODO: Remove in upcoming version.
662677
};
663678

src/core/utils.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,3 +721,31 @@ describe("escape/unescape html ...", function () {
721721
expect(utils.unescape_html(undefined)).toBe("");
722722
});
723723
});
724+
725+
describe("is_iso_date_time ...", function () {
726+
it("detects valid date/time objects", () => {
727+
expect(utils.is_iso_date_time("2022-05-04T21:00")).toBe(true);
728+
729+
// We actually do not strictly check for a valid datetime, just if the
730+
// format is correct.
731+
expect(utils.is_iso_date_time("2222-19-39T29:59")).toBe(true);
732+
733+
// But some basic constraints are in place
734+
expect(utils.is_iso_date_time("2222-20-40T30:60")).toBe(false);
735+
736+
// And this is for sure no valid date/time
737+
expect(utils.is_iso_date_time("not2-ok-40T30:60")).toBe(false);
738+
739+
// Also, the time component cannot be left out
740+
expect(utils.is_iso_date_time("2022-05-04")).toBe(false);
741+
742+
// Not even partially.
743+
expect(utils.is_iso_date_time("2022-05-04T21")).toBe(false);
744+
745+
// Unless we set optional_time to true.
746+
expect(utils.is_iso_date_time("2022-05-04", true)).toBe(true);
747+
748+
// But still, partial time component does not pass.
749+
expect(utils.is_iso_date_time("2022-05-04T21", true)).toBe(false);
750+
});
751+
});

src/pat/validation/validation.js

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -138,53 +138,54 @@ export default Base.extend({
138138
const msg = input_options.message.date || input_options.message.datetime;
139139

140140
let not_after;
141-
let not_before;
142141
let not_after_el;
143-
let not_before_el;
144-
const date = new Date(input.value);
145-
if (isNaN(date)) {
146-
// Should not happen or input only partially typed in.
147-
return;
148-
}
149142
if (input_options.not.after) {
150-
// Handle value as date.
151-
not_after = new Date(input_options.not.after);
152-
if (isNaN(not_after)) {
143+
if (utils.is_iso_date_time(input_options.not.after, true)) {
144+
not_after = new Date(input_options.not.after);
145+
} else {
153146
// Handle value as selector
154147
not_after_el = document.querySelector(input_options.not.after);
155-
not_after = not_after_el?.value;
156-
not_after =
157-
not_after &&
158-
new Date(
159-
document.querySelector(input_options.not.after).value
160-
);
148+
not_after = not_after_el?.value
149+
? new Date(not_after_el?.value)
150+
: undefined;
161151
}
162152

163153
// Use null if no valid date.
164154
not_after = isNaN(not_after) ? null : not_after;
165155
}
156+
157+
let not_before;
158+
let not_before_el;
166159
if (input_options.not.before) {
167-
// Handle value as date.
168-
not_before = new Date(input_options.not.before);
169-
if (isNaN(not_before)) {
160+
if (utils.is_iso_date_time(input_options.not.before, true)) {
161+
not_before = new Date(input_options.not.before);
162+
} else {
170163
// Handle value as selector
171164
not_before_el = document.querySelector(input_options.not.before);
172-
not_before = not_before_el?.value;
173-
not_before =
174-
not_before &&
175-
new Date(
176-
document.querySelector(input_options.not.before).value
177-
);
165+
not_before = not_before_el?.value
166+
? new Date(not_before_el?.value)
167+
: undefined;
178168
}
179169

180170
// Use null if no valid date.
181171
not_before = isNaN(not_before) ? null : not_before;
182172
}
183-
if (not_after && date > not_after) {
184-
this.set_validity({ input: input, msg: msg });
185-
} else if (not_before && date < not_before) {
186-
this.set_validity({ input: input, msg: msg });
173+
174+
if (
175+
input.value &&
176+
utils.is_iso_date_time(input.value, true) &&
177+
!isNaN(new Date(input.value))
178+
) {
179+
// That's 1 valid date!
180+
const date = new Date(input.value);
181+
182+
if (not_after && date > not_after) {
183+
this.set_validity({ input: input, msg: msg });
184+
} else if (not_before && date < not_before) {
185+
this.set_validity({ input: input, msg: msg });
186+
}
187187
}
188+
188189
// always check the other input to clear/set errors
189190
!stop && // do not re-check when stop is set to avoid infinite loops
190191
not_after_el &&

src/pat/validation/validation.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,20 @@ describe("pat-validation", function () {
864864
inp_start.dispatchEvent(events.change_event());
865865
await utils.timeout(1); // wait a tick for async to settle.
866866
expect(el.querySelectorAll("em.warning").length).toBe(0);
867+
868+
// Violate the constraint again...
869+
inp_start.value = "2020-10-11";
870+
inp_start.dispatchEvent(events.change_event());
871+
inp_end.value = "2020-10-10";
872+
inp_end.dispatchEvent(events.change_event());
873+
await utils.timeout(1); // wait a tick for async to settle.
874+
expect(el.querySelectorAll("em.warning").length).toBe(2);
875+
876+
// Clearing one of the optional values should clear all errors.
877+
inp_start.value = "";
878+
inp_start.dispatchEvent(events.change_event());
879+
await utils.timeout(1); // wait a tick for async to settle.
880+
expect(el.querySelectorAll("em.warning").length).toBe(0);
867881
});
868882

869883
it("5.6 - doesn't validate empty optional dates", async function () {
@@ -992,6 +1006,34 @@ describe("pat-validation", function () {
9921006
expect(el.querySelectorAll("em.warning").length).toBe(0);
9931007
});
9941008

1009+
it("5.9 - Do not interpret ``ok-1`` as a valid date.", async function () {
1010+
// This issue popped up in Chrome but not in Firefox.
1011+
// A date like ``ok-1`` was interpreted as ``2000-12-31T23:00:00.000Z``.
1012+
// Explicitly checking for a valid ISO 8601 date fixes this.
1013+
1014+
document.body.innerHTML = `
1015+
<form class="pat-validation">
1016+
<input
1017+
type="date"
1018+
name="date"
1019+
data-pat-validation="message-date: Wong date!; not-after: ok-1"
1020+
/>
1021+
</form>
1022+
`;
1023+
1024+
const el = document.querySelector(".pat-validation");
1025+
const inp = el.querySelector("[name=date]");
1026+
1027+
new Pattern(el);
1028+
await utils.timeout(1); // wait a tick for async to settle.
1029+
1030+
inp.value = "2022-01-01";
1031+
inp.dispatchEvent(events.change_event());
1032+
await utils.timeout(1); // wait a tick for async to settle.
1033+
1034+
expect(el.querySelectorAll("em.warning").length).toBe(0);
1035+
});
1036+
9951037
it("6.1 - validates radio buttons", async function () {
9961038
document.body.innerHTML = `
9971039
<form class="pat-validation"

0 commit comments

Comments
 (0)