Skip to content

Commit 065e37f

Browse files
authored
Merge pull request #1071 from Patternslib/fix-validation
Fix validation
2 parents 4a4cd49 + bc27e41 commit 065e37f

File tree

10 files changed

+309
-44
lines changed

10 files changed

+309
-44
lines changed

src/core/dom.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ const show = (el) => {
7979
const val = el[DATA_STYLE_DISPLAY] || null;
8080
el.style.display = val;
8181
delete el[DATA_STYLE_DISPLAY];
82-
el.removeAttribute("hidden", "");
82+
el.removeAttribute("hidden");
8383
};
8484

8585
/**

src/core/events.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ const await_pattern_init = (pattern) => {
9797
* Event factories
9898
*/
9999

100+
const blur_event = () => {
101+
return new Event("blur", {
102+
bubbles: false,
103+
cancelable: false,
104+
});
105+
};
106+
100107
const click_event = () => {
101108
return new Event("click", {
102109
bubbles: true,
@@ -111,6 +118,13 @@ const change_event = () => {
111118
});
112119
};
113120

121+
const focus_event = () => {
122+
return new Event("focus", {
123+
bubbles: false,
124+
cancelable: false,
125+
});
126+
};
127+
114128
const input_event = () => {
115129
return new Event("input", {
116130
bubbles: true,
@@ -151,8 +165,10 @@ export default {
151165
remove_event_listener: remove_event_listener,
152166
await_event: await_event,
153167
await_pattern_init: await_pattern_init,
168+
blur_event: blur_event,
154169
click_event: click_event,
155170
change_event: change_event,
171+
focus_event: focus_event,
156172
input_event: input_event,
157173
mousedown_event: mousedown_event,
158174
mouseup_event: mouseup_event,

src/core/events.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ describe("core.events tests", () => {
9696
inner = el.querySelector("#inner");
9797
});
9898

99+
it("blur event", async () => {
100+
outer.addEventListener("blur", () => {
101+
catched = "outer";
102+
});
103+
inner.addEventListener("blur", () => {
104+
catched = "inner";
105+
});
106+
inner.dispatchEvent(events.blur_event());
107+
await utils.timeout(1);
108+
expect(catched).toBe("inner");
109+
});
110+
99111
it("click event", async () => {
100112
outer.addEventListener("click", () => {
101113
catched = "outer";
@@ -114,6 +126,18 @@ describe("core.events tests", () => {
114126
expect(catched).toBe("outer");
115127
});
116128

129+
it("focus event", async () => {
130+
outer.addEventListener("focus", () => {
131+
catched = "outer";
132+
});
133+
inner.addEventListener("focus", () => {
134+
catched = "inner";
135+
});
136+
inner.dispatchEvent(events.focus_event());
137+
await utils.timeout(1);
138+
expect(catched).toBe("inner");
139+
});
140+
117141
it("input event", async () => {
118142
outer.addEventListener("input", () => {
119143
catched = "outer";

src/pat/auto-suggest/auto-suggest.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ export default Base.extend({
8888
if (this.el.tagName === "INPUT") {
8989
config = this.create_input_config(config);
9090
}
91-
this.$el.select2(config);
91+
const $sel2 = this.$el.select2(config);
92+
9293
dom.hide(this.el); // hide input, but keep active (e.g. for validation)
9394

9495
this.$el.on("pat-update", (e, data) => {
@@ -101,6 +102,20 @@ export default Base.extend({
101102
}
102103
});
103104

105+
// Allow pat-validate to check for validity when select2 was interacted
106+
// with but no value selected.
107+
const initiate_empty_check = () => {
108+
const val = $sel2.select2("val");
109+
if (val?.length === 0) {
110+
// catches "" and []
111+
// blur the input field so that pat-validate can kick in when
112+
// nothing was selected.
113+
this.el.dispatchEvent(events.blur_event());
114+
}
115+
};
116+
this.$el.on("select2-close", initiate_empty_check.bind(this));
117+
this.$el.on("select2-blur", initiate_empty_check.bind(this));
118+
104119
this.$el.on("change", () => {
105120
// The jQuery "change" event isn't caught by normal JavaScript
106121
// event listeners, e.g. in pat-validation.

src/pat/auto-suggest/auto-suggest.test.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,5 +300,66 @@ describe("pat-autosuggest", function () {
300300

301301
expect(spy).toHaveBeenCalled();
302302
});
303+
304+
it("4.2 - Works with pat-validate on an empty selection.", async function () {
305+
const pattern_validation = (await import("../validation/validation")).default; // prettier-ignore
306+
307+
document.body.innerHTML = `
308+
<form class="pat-validation" data-pat-validation="delay: 0">
309+
<input
310+
name="words"
311+
class="pat-autosuggest"
312+
required
313+
data-pat-autosuggest="words: apple, orange, pear" />
314+
</form>
315+
`;
316+
317+
const form = document.querySelector("form");
318+
const input = document.querySelector("input");
319+
320+
new pattern_validation(form);
321+
new pattern(input);
322+
await utils.timeout(1); // wait a tick for async to settle.
323+
324+
// Open the select2 dropdown
325+
$(".select2-input").click();
326+
await utils.timeout(1); // wait a tick for async to settle.
327+
328+
// Close it without selecting something.
329+
$(input).select2("close");
330+
331+
await utils.timeout(1); // wait a tick for async to settle.
332+
333+
// There should be a error message from pat-validation.
334+
expect(form.querySelectorAll("em.warning").length).toBe(1);
335+
});
336+
337+
it("4.3 - Works with pat-validate when empty and focus moves away.", async function () {
338+
const pattern_validation = (await import("../validation/validation")).default; // prettier-ignore
339+
340+
document.body.innerHTML = `
341+
<form class="pat-validation" data-pat-validation="delay: 0">
342+
<input
343+
name="words"
344+
class="pat-autosuggest"
345+
required
346+
data-pat-autosuggest="words: apple, orange, pear" />
347+
</form>
348+
`;
349+
350+
const form = document.querySelector("form");
351+
const input = document.querySelector("input");
352+
353+
new pattern_validation(form);
354+
new pattern(input);
355+
await utils.timeout(1); // wait a tick for async to settle.
356+
357+
// Move focus away from select2
358+
$(".select2-input")[0].dispatchEvent(events.blur_event());
359+
await utils.timeout(1); // wait a tick for async to settle.
360+
361+
// There should be a error message from pat-validation.
362+
expect(form.querySelectorAll("em.warning").length).toBe(1);
363+
});
303364
});
304365
});

src/pat/date-picker/date-picker.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
/* pat-date-picker - Polyfill for input type=date */
22
import $ from "jquery";
33
import Base from "../../core/base";
4+
import logging from "../../core/logging";
45
import Parser from "../../core/parser";
56
import dom from "../../core/dom";
67
import events from "../../core/events";
78
import utils from "../../core/utils";
89

10+
const log = logging.getLogger("date-picker");
11+
912
export const parser = new Parser("date-picker");
1013
parser.addArgument("behavior", "styled", ["native", "styled"]);
1114
parser.addArgument("week-numbers", [], ["show", "hide"]);
@@ -148,6 +151,13 @@ export default Base.extend({
148151
firstDay: this.options.firstDay,
149152
showWeekNumber: this.options.weekNumbers === "show",
150153
onSelect: () => this.dispatch_change_event(),
154+
onClose: () => {
155+
if (this.options.behavior === "styled") {
156+
// blur the input field so that pat-validate can kick in when
157+
// nothing was selected.
158+
el.dispatchEvent(events.blur_event());
159+
}
160+
},
151161
};
152162

153163
if (el.getAttribute("min")) {
@@ -162,9 +172,7 @@ export default Base.extend({
162172
const response = await fetch(this.options.i18n);
163173
config.i18n = await response.json();
164174
} catch {
165-
console.error(
166-
`date-picker could not load i18n for ${this.options.i18n}`
167-
);
175+
log.error(`date-picker could not load i18n for ${this.options.i18n}`);
168176
}
169177
}
170178
this.pikaday = new Pikaday(config);

src/pat/date-picker/date-picker.test.js

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe("pat-date-picker", function () {
4343
jest.restoreAllMocks();
4444
});
4545

46-
it("Default date picker.", async () => {
46+
it("1 - Default date picker.", async () => {
4747
document.body.innerHTML = '<input type="date" class="pat-date-picker"/>';
4848
const el = document.querySelector("input[type=date]");
4949

@@ -85,7 +85,7 @@ describe("pat-date-picker", function () {
8585
expect(el.value).toBe(isodate);
8686
});
8787

88-
it("Date picker starts at Monday.", async () => {
88+
it("2 - Date picker starts at Monday.", async () => {
8989
document.body.innerHTML =
9090
'<input type="date" class="pat-date-picker" data-pat-date-picker="first-day: 1" />';
9191
const el = document.querySelector("input[type=date]");
@@ -98,7 +98,7 @@ describe("pat-date-picker", function () {
9898
expect(first_day.textContent).toBe("Mon");
9999
});
100100

101-
it("Date picker with pre-set value.", async () => {
101+
it("3 - Date picker with pre-set value.", async () => {
102102
document.body.innerHTML =
103103
'<input type="date" class="pat-date-picker" value="1900-01-01"/>';
104104
const el = document.querySelector("input[type=date]");
@@ -120,7 +120,7 @@ describe("pat-date-picker", function () {
120120
expect(display_el.textContent).toBe("1900-01-01");
121121
});
122122

123-
it("Date picker with week numbers.", async () => {
123+
it("4 - Date picker with week numbers.", async () => {
124124
document.body.innerHTML =
125125
'<input type="date" class="pat-date-picker" data-pat-date-picker="week-numbers: show" value="2017-09-18"/>';
126126
const el = document.querySelector("input[type=date]");
@@ -133,7 +133,7 @@ describe("pat-date-picker", function () {
133133
expect(week_num.textContent).toBe("35");
134134
});
135135

136-
describe("Date picker with i18n", function () {
136+
describe("5 - Date picker with i18n", function () {
137137
describe("with proper json URL", function () {
138138
it("properly localizes the months and weekdays", async () => {
139139
global.fetch = jest.fn().mockImplementation(mock_fetch_i18n);
@@ -177,7 +177,7 @@ describe("pat-date-picker", function () {
177177
});
178178
});
179179

180-
describe("Update one input depending on the other.", function () {
180+
describe("6 - Update one input depending on the other.", function () {
181181
it("Updates with default offset-days", async () => {
182182
const wrapper = document.createElement("div");
183183
wrapper.innerHTML = `
@@ -324,7 +324,7 @@ describe("pat-date-picker", function () {
324324
});
325325
});
326326

327-
it("Formatted date.", async () => {
327+
it("7 - Formatted date.", async () => {
328328
document.body.innerHTML =
329329
'<input type="date" class="pat-date-picker" value="2021-03-09" data-pat-date-picker="output-format: Do MMMM YYYY; locale: de"/>';
330330
const el = document.querySelector("input[type=date]");
@@ -346,7 +346,7 @@ describe("pat-date-picker", function () {
346346
expect(el.value).toBe("2021-03-12");
347347
});
348348

349-
it("Styled behavior with clear button.", async () => {
349+
it("8 - Styled behavior with clear button.", async () => {
350350
document.body.innerHTML =
351351
'<input type="date" class="pat-date-picker" value="2021-03-09"/>';
352352
const el = document.querySelector("input[type=date]");
@@ -400,7 +400,7 @@ describe("pat-date-picker", function () {
400400
expect(clear_button).toBeFalsy();
401401
});
402402

403-
it("Formatted date with clear button.", async () => {
403+
it("9 - Formatted date with clear button.", async () => {
404404
document.body.innerHTML =
405405
'<input type="date" class="pat-date-picker" value="2021-03-09" data-pat-date-picker="output-format: DD.MM.YYYY"/>';
406406
const el = document.querySelector("input[type=date]");
@@ -454,7 +454,7 @@ describe("pat-date-picker", function () {
454454
expect(clear_button).toBeFalsy();
455455
});
456456

457-
it("Required formatted date - no clear button available.", async () => {
457+
it("10 - Required formatted date - no clear button available.", async () => {
458458
document.body.innerHTML =
459459
'<input type="date" class="pat-date-picker" value="2021-03-01" required/>';
460460
const el = document.querySelector("input[type=date]");
@@ -486,7 +486,7 @@ describe("pat-date-picker", function () {
486486
expect(clear_button).toBeFalsy();
487487
});
488488

489-
it("Native behavior with fallback to pika", async () => {
489+
it("11 - Native behavior with fallback to pika", async () => {
490490
// We mocking as if we're not supporting input type date.
491491
jest.spyOn(utils, "checkInputSupport").mockImplementation(() => false);
492492

@@ -528,7 +528,7 @@ describe("pat-date-picker", function () {
528528
expect(el.value).toBe(isodate);
529529
});
530530

531-
it("does not initialize the date picker in styled behavior when disabled", async () => {
531+
it("12 - does not initialize the date picker in styled behavior when disabled", async () => {
532532
document.body.innerHTML = `
533533
<input
534534
type="date"
@@ -559,7 +559,7 @@ describe("pat-date-picker", function () {
559559
expect(document.querySelector(".pika-lendar")).toBeFalsy();
560560
});
561561

562-
it("works with pat-autosubmit", async () => {
562+
it("13 - works with pat-autosubmit", async () => {
563563
document.body.innerHTML = `
564564
<form class="pat-autosubmit" onsubmit="return false;">
565565
<input name="date" type="date" class="pat-date-picker"/>
@@ -581,4 +581,41 @@ describe("pat-date-picker", function () {
581581
await utils.timeout(500); // wait for delay
582582
expect(handle_submit).toHaveBeenCalled();
583583
});
584+
585+
it("14 - Selecting nothing in styled behavior triggers pat-validation.", async () => {
586+
document.body.innerHTML = `
587+
<form class="pat-validation" data-pat-validation="delay: 0">
588+
<input
589+
type="date"
590+
name="date"
591+
class="pat-date-picker"
592+
required
593+
/>
594+
</form>
595+
`;
596+
const form = document.querySelector("form");
597+
const el = document.querySelector("input[type=date]");
598+
599+
const pattern_validation = (await import("../validation/validation")).default;
600+
601+
new pattern_validation(form);
602+
new pattern(el);
603+
await utils.timeout(1); // wait a tick for async to settle.
604+
605+
// Open the date picker
606+
document.querySelector("time").click();
607+
await utils.timeout(1); // wait a tick for async to settle.
608+
609+
// Date picker is opened
610+
expect(document.querySelector(".pika-lendar")).toBeTruthy();
611+
612+
// Close the date picker without selecting a date.
613+
document.body.click();
614+
615+
// Wait for validation to run.
616+
await utils.timeout(1);
617+
618+
// There should be a error message from pat-validation.
619+
expect(form.querySelectorAll("em.warning").length).toBe(1);
620+
});
584621
});

0 commit comments

Comments
 (0)