FEATURE: new date/time components (#7898)
This commit is contained in:
parent
194a2b612f
commit
95ad4f9077
|
@ -0,0 +1,101 @@
|
||||||
|
/* global Pikaday:true */
|
||||||
|
import loadScript from "discourse/lib/load-script";
|
||||||
|
import {
|
||||||
|
default as computed,
|
||||||
|
on
|
||||||
|
} from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: ["d-date-input"],
|
||||||
|
date: null,
|
||||||
|
_picker: null,
|
||||||
|
|
||||||
|
@computed("site.mobileView")
|
||||||
|
inputType(mobileView) {
|
||||||
|
return mobileView ? "date" : "text";
|
||||||
|
},
|
||||||
|
|
||||||
|
@on("didInsertElement")
|
||||||
|
_loadDatePicker() {
|
||||||
|
const container = this.element.querySelector(`#${this.containerId}`);
|
||||||
|
|
||||||
|
if (this.site.mobileView) {
|
||||||
|
this._loadNativePicker(container);
|
||||||
|
} else {
|
||||||
|
this._loadPikadayPicker(container);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
didUpdateAttrs() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
if (this._picker) {
|
||||||
|
this._picker.setDate(this.date, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_loadPikadayPicker(container) {
|
||||||
|
loadScript("/javascripts/pikaday.js").then(() => {
|
||||||
|
Ember.run.next(() => {
|
||||||
|
const default_opts = {
|
||||||
|
field: this.element.querySelector(".date-picker"),
|
||||||
|
container: container || this.element,
|
||||||
|
bound: container === null,
|
||||||
|
format: "LL",
|
||||||
|
firstDay: 1,
|
||||||
|
i18n: {
|
||||||
|
previousMonth: I18n.t("dates.previous_month"),
|
||||||
|
nextMonth: I18n.t("dates.next_month"),
|
||||||
|
months: moment.months(),
|
||||||
|
weekdays: moment.weekdays(),
|
||||||
|
weekdaysShort: moment.weekdaysShort()
|
||||||
|
},
|
||||||
|
onSelect: date => this._handleSelection(date)
|
||||||
|
};
|
||||||
|
|
||||||
|
this._picker = new Pikaday(Object.assign(default_opts, this._opts()));
|
||||||
|
this._picker.setDate(this.date, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_loadNativePicker(container) {
|
||||||
|
const wrapper = container || this.element;
|
||||||
|
const picker = wrapper.querySelector("input.date-picker");
|
||||||
|
picker.onchange = () => this._handleSelection(picker.value);
|
||||||
|
picker.hide = () => {
|
||||||
|
/* do nothing for native */
|
||||||
|
};
|
||||||
|
picker.destroy = () => {
|
||||||
|
/* do nothing for native */
|
||||||
|
};
|
||||||
|
this._picker = picker;
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleSelection(value) {
|
||||||
|
if (!this.element || this.isDestroying || this.isDestroyed) return;
|
||||||
|
|
||||||
|
this._picker && this._picker.hide();
|
||||||
|
|
||||||
|
if (this.onChange) {
|
||||||
|
this.onChange(moment(value).toDate());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@on("willDestroyElement")
|
||||||
|
_destroy() {
|
||||||
|
if (this._picker) {
|
||||||
|
this._picker.destroy();
|
||||||
|
}
|
||||||
|
this._picker = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed()
|
||||||
|
placeholder() {
|
||||||
|
return I18n.t("dates.placeholder");
|
||||||
|
},
|
||||||
|
|
||||||
|
_opts() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: ["d-date-time-input-range"],
|
||||||
|
|
||||||
|
from: null,
|
||||||
|
to: null,
|
||||||
|
onChangeTo: null,
|
||||||
|
onChangeFrom: null,
|
||||||
|
currentPanel: "from",
|
||||||
|
showFromTime: true,
|
||||||
|
showToTime: true,
|
||||||
|
error: null,
|
||||||
|
|
||||||
|
fromPanelActive: Ember.computed.equal("currentPanel", "from"),
|
||||||
|
toPanelActive: Ember.computed.equal("currentPanel", "to"),
|
||||||
|
|
||||||
|
_valid(state) {
|
||||||
|
if (state.to < state.from) {
|
||||||
|
return I18n.t("date_time_picker.errors.to_before_from");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
_onChange(options, value) {
|
||||||
|
if (this.onChange) {
|
||||||
|
const state = {
|
||||||
|
from: this.from,
|
||||||
|
to: this.to
|
||||||
|
};
|
||||||
|
|
||||||
|
const diff = {};
|
||||||
|
diff[options.prop] = value;
|
||||||
|
|
||||||
|
const newState = Object.assign(state, diff);
|
||||||
|
|
||||||
|
const validation = this._valid(newState);
|
||||||
|
if (validation === true) {
|
||||||
|
this.set("error", null);
|
||||||
|
this.onChange(newState);
|
||||||
|
} else {
|
||||||
|
this.set("error", validation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangePanel(panel) {
|
||||||
|
this.set("currentPanel", panel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: ["d-date-time-input"],
|
||||||
|
date: null,
|
||||||
|
showTime: true,
|
||||||
|
|
||||||
|
_hours: Ember.computed("date", function() {
|
||||||
|
return this.date ? this.date.getHours() : null;
|
||||||
|
}),
|
||||||
|
|
||||||
|
_minutes: Ember.computed("date", function() {
|
||||||
|
return this.date ? this.date.getMinutes() : null;
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onChangeTime(time) {
|
||||||
|
if (this.onChange) {
|
||||||
|
const year = this.date.getFullYear();
|
||||||
|
const month = this.date.getMonth();
|
||||||
|
const day = this.date.getDate();
|
||||||
|
this.onChange(new Date(year, month, day, time.hours, time.minutes));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangeDate(date) {
|
||||||
|
if (this.onChange) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth();
|
||||||
|
const day = date.getDate();
|
||||||
|
this.onChange(
|
||||||
|
new Date(year, month, day, this._hours || 0, this._minutes || 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { isNumeric } from "discourse/lib/utilities";
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: ["d-time-input"],
|
||||||
|
hours: null,
|
||||||
|
minutes: null,
|
||||||
|
_hours: Ember.computed.oneWay("hours"),
|
||||||
|
_minutes: Ember.computed.oneWay("minutes"),
|
||||||
|
isSafari: Ember.computed.oneWay("capabilities.isSafari"),
|
||||||
|
isMobile: Ember.computed.oneWay("site.mobileView"),
|
||||||
|
nativePicker: Ember.computed.or("isSafari", "isMobile"),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onInput(options, event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (this.onChange) {
|
||||||
|
let value = event.target.value;
|
||||||
|
|
||||||
|
if (!isNumeric(value)) {
|
||||||
|
value = 0;
|
||||||
|
} else {
|
||||||
|
value = parseInt(value, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.prop === "hours") {
|
||||||
|
value = Math.max(0, Math.min(value, 23))
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
this._processHoursChange(value);
|
||||||
|
} else {
|
||||||
|
value = Math.max(0, Math.min(value, 59))
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
this._processMinutesChange(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ember.run.schedule("afterRender", () => (event.target.value = value));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onFocusIn(value, event) {
|
||||||
|
if (value && event.target) {
|
||||||
|
event.target.select();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangeTime(event) {
|
||||||
|
const time = event.target.value;
|
||||||
|
|
||||||
|
if (time && this.onChange) {
|
||||||
|
this.onChange({
|
||||||
|
hours: time.split(":")[0],
|
||||||
|
minutes: time.split(":")[1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_processHoursChange(hours) {
|
||||||
|
this.onChange({
|
||||||
|
hours,
|
||||||
|
minutes: this._minutes || "00"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_processMinutesChange(minutes) {
|
||||||
|
this.onChange({
|
||||||
|
hours: this._hours || "00",
|
||||||
|
minutes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
{{input
|
||||||
|
type=inputType
|
||||||
|
class="date-picker"
|
||||||
|
placeholder=placeholder
|
||||||
|
value=value}}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<ul class="panels {{currentPanel}}">
|
||||||
|
<li>
|
||||||
|
{{d-button
|
||||||
|
label="date_time_picker.from"
|
||||||
|
class="from-panel"
|
||||||
|
action=(action "onChangePanel" "from")}}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{d-button
|
||||||
|
label="date_time_picker.to"
|
||||||
|
class="to-panel"
|
||||||
|
action=(action "onChangePanel" "to")}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{#if error}}
|
||||||
|
<div class="alert error">{{error}}</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="panel from {{if fromPanelActive 'visible'}}">
|
||||||
|
{{date-time-input
|
||||||
|
date=from
|
||||||
|
onChange=(action "_onChange" (hash prop="from"))
|
||||||
|
showTime=showFromTime
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel to {{if toPanelActive 'visible'}}">
|
||||||
|
{{date-time-input
|
||||||
|
date=to
|
||||||
|
onChange=(action "_onChange" (hash prop="to"))
|
||||||
|
showTime=showToTime
|
||||||
|
}}
|
||||||
|
</div>
|
|
@ -0,0 +1,9 @@
|
||||||
|
{{date-input date=date onChange=(action "onChangeDate")}}
|
||||||
|
|
||||||
|
{{#if showTime}}
|
||||||
|
{{time-input
|
||||||
|
hours=_hours
|
||||||
|
minutes=_minutes
|
||||||
|
onChange=(action "onChangeTime")
|
||||||
|
}}
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<div class="fields">
|
||||||
|
{{#if nativePicker}}
|
||||||
|
{{input
|
||||||
|
class="field time"
|
||||||
|
type="time"
|
||||||
|
value=(concat _hours ":" _minutes)
|
||||||
|
change=(action "onChangeTime")
|
||||||
|
}}
|
||||||
|
{{else}}
|
||||||
|
{{input
|
||||||
|
class="field hours"
|
||||||
|
type="number"
|
||||||
|
title="Hours"
|
||||||
|
minlength=2
|
||||||
|
maxlength=2
|
||||||
|
max="23"
|
||||||
|
min="0"
|
||||||
|
placeholder="00"
|
||||||
|
value=_hours
|
||||||
|
input=(action "onInput" (hash prop="hours"))
|
||||||
|
focus-in=(action "onFocusIn")
|
||||||
|
}}
|
||||||
|
|
||||||
|
<div class="separator">:</div>
|
||||||
|
|
||||||
|
{{input
|
||||||
|
class="field minutes"
|
||||||
|
title="Minutes"
|
||||||
|
type="number"
|
||||||
|
minlength=2
|
||||||
|
maxlength=2
|
||||||
|
max="59"
|
||||||
|
min="0"
|
||||||
|
placeholder="00"
|
||||||
|
value=_minutes
|
||||||
|
input=(action "onInput" (hash prop="minutes"))
|
||||||
|
focus-in=(action "onFocusIn")
|
||||||
|
}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
|
@ -189,4 +189,40 @@ if (RegExp.prototype.flags === undefined) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
|
||||||
|
if (!String.prototype.padStart) {
|
||||||
|
String.prototype.padStart = function padStart(targetLength, padString) {
|
||||||
|
targetLength = targetLength >> 0; //truncate if number, or convert non-number to 0;
|
||||||
|
padString = String(typeof padString !== "undefined" ? padString : " ");
|
||||||
|
if (this.length >= targetLength) {
|
||||||
|
return String(this);
|
||||||
|
} else {
|
||||||
|
targetLength = targetLength - this.length;
|
||||||
|
if (targetLength > padString.length) {
|
||||||
|
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
|
||||||
|
}
|
||||||
|
return padString.slice(0, targetLength) + String(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
|
||||||
|
if (!String.prototype.padEnd) {
|
||||||
|
String.prototype.padEnd = function padEnd(targetLength, padString) {
|
||||||
|
targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
|
||||||
|
padString = String(typeof padString !== "undefined" ? padString : " ");
|
||||||
|
if (this.length > targetLength) {
|
||||||
|
return String(this);
|
||||||
|
} else {
|
||||||
|
targetLength = targetLength - this.length;
|
||||||
|
if (targetLength > padString.length) {
|
||||||
|
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
|
||||||
|
}
|
||||||
|
return String(this) + padString.slice(0, targetLength);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
.d-date-input {
|
||||||
|
.date-picker {
|
||||||
|
margin: 0;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pika-single {
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
.d-date-time-input-range {
|
||||||
|
padding: 0.5em;
|
||||||
|
background: whitesmole;
|
||||||
|
border: 1px solid $primary-low;
|
||||||
|
width: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.panels {
|
||||||
|
display: inline-flex;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&.from {
|
||||||
|
.from-panel {
|
||||||
|
background: $danger;
|
||||||
|
color: $secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.to {
|
||||||
|
.to-panel {
|
||||||
|
background: $danger;
|
||||||
|
color: $secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
display: none;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
.d-date-time-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid $primary-low;
|
||||||
|
width: 258px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.date-picker,
|
||||||
|
.fields {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
.d-time-input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.fields {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid $primary-low;
|
||||||
|
|
||||||
|
.field {
|
||||||
|
text-align: center;
|
||||||
|
width: auto;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
width: 32px;
|
||||||
|
|
||||||
|
&.time {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hours,
|
||||||
|
&.minutes {
|
||||||
|
text-align: center;
|
||||||
|
width: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hours {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.minutes {
|
||||||
|
padding-left: 10px;
|
||||||
|
width: 55px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1518,6 +1518,12 @@ en:
|
||||||
one: "Select at least {{count}} item."
|
one: "Select at least {{count}} item."
|
||||||
other: "Select at least {{count}} items."
|
other: "Select at least {{count}} items."
|
||||||
|
|
||||||
|
date_time_picker:
|
||||||
|
from: From
|
||||||
|
to: To
|
||||||
|
errors:
|
||||||
|
to_before_from: "To date must be later than from date."
|
||||||
|
|
||||||
emoji_picker:
|
emoji_picker:
|
||||||
filter_placeholder: Search for emoji
|
filter_placeholder: Search for emoji
|
||||||
smileys_&_emotion: Smileys and Emotion
|
smileys_&_emotion: Smileys and Emotion
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import componentTest from "helpers/component-test";
|
||||||
|
|
||||||
|
moduleForComponent("date-input", { integration: true });
|
||||||
|
|
||||||
|
function dateInput() {
|
||||||
|
return find(".date-picker");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDate(date) {
|
||||||
|
this.set("date", date);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pika(year, month, day) {
|
||||||
|
await click(
|
||||||
|
`.pika-button.pika-day[data-pika-year="${year}"][data-pika-month="${month}"][data-pika-day="${day}"]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
const DEFAULT_DATE = new Date(2019, 0, 29);
|
||||||
|
|
||||||
|
componentTest("default", {
|
||||||
|
template: `{{date-input date=date}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ date: DEFAULT_DATE });
|
||||||
|
},
|
||||||
|
|
||||||
|
test(assert) {
|
||||||
|
assert.equal(dateInput().val(), "January 29, 2019");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("prevents mutations", {
|
||||||
|
template: `{{date-input date=date onChange=onChange}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ date: DEFAULT_DATE });
|
||||||
|
this.set("onChange", noop);
|
||||||
|
},
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
await click(dateInput());
|
||||||
|
await pika(2019, 0, 2);
|
||||||
|
|
||||||
|
assert.ok(this.date.getTime() === DEFAULT_DATE.getTime());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("allows mutations through actions", {
|
||||||
|
template: `{{date-input date=date onChange=onChange}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ date: DEFAULT_DATE });
|
||||||
|
this.set("onChange", setDate);
|
||||||
|
},
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
await click(dateInput());
|
||||||
|
await pika(2019, 0, 2);
|
||||||
|
|
||||||
|
assert.ok(this.date.getTime() === new Date(2019, 0, 2).getTime());
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,102 @@
|
||||||
|
import componentTest from "helpers/component-test";
|
||||||
|
|
||||||
|
moduleForComponent("date-time-input-range", { integration: true });
|
||||||
|
|
||||||
|
function fromDateInput() {
|
||||||
|
return find(".from .date-picker");
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromHoursInput() {
|
||||||
|
return find(".from .field.hours");
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromMinutesInput() {
|
||||||
|
return find(".from .field.minutes");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDateInput() {
|
||||||
|
return find(".to .date-picker");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toHoursInput() {
|
||||||
|
return find(".to .field.hours");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toMinutesInput() {
|
||||||
|
return find(".to .field.minutes");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDates(dates) {
|
||||||
|
this.setProperties(dates);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pika(year, month, day) {
|
||||||
|
await click(
|
||||||
|
`.pika-button.pika-day[data-pika-year="${year}"][data-pika-month="${month}"][data-pika-day="${day}"]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DATE_TIME = new Date(2019, 0, 29, 14, 45);
|
||||||
|
|
||||||
|
componentTest("default", {
|
||||||
|
template: `{{date-time-input-range from=date to=to}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ date: DEFAULT_DATE_TIME, to: null });
|
||||||
|
},
|
||||||
|
|
||||||
|
test(assert) {
|
||||||
|
assert.equal(fromDateInput().val(), "January 29, 2019");
|
||||||
|
assert.equal(fromHoursInput().val(), "14");
|
||||||
|
assert.equal(fromMinutesInput().val(), "45");
|
||||||
|
|
||||||
|
assert.equal(toDateInput().val(), "");
|
||||||
|
assert.equal(toHoursInput().val(), "");
|
||||||
|
assert.equal(toMinutesInput().val(), "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("can switch panels", {
|
||||||
|
template: `{{date-time-input-range}}`,
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
assert.ok(exists(".panel.from.visible"));
|
||||||
|
assert.notOk(exists(".panel.to.visible"));
|
||||||
|
|
||||||
|
await click(".panels .to-panel");
|
||||||
|
|
||||||
|
assert.ok(exists(".panel.to.visible"));
|
||||||
|
assert.notOk(exists(".panel.from.visible"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("prevents toDate to be before fromDate", {
|
||||||
|
template: `{{date-time-input-range from=from to=to onChange=onChange}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({
|
||||||
|
from: DEFAULT_DATE_TIME,
|
||||||
|
to: DEFAULT_DATE_TIME,
|
||||||
|
onChange: setDates
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
assert.notOk(exists(".error"));
|
||||||
|
|
||||||
|
await click(toDateInput());
|
||||||
|
await pika(2019, 0, 1);
|
||||||
|
|
||||||
|
assert.ok(exists(".error"));
|
||||||
|
assert.ok(
|
||||||
|
this.to.getTime() === DEFAULT_DATE_TIME.getTime(),
|
||||||
|
"it didnt trigger a mutation"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click(toDateInput());
|
||||||
|
await pika(2019, 0, 30);
|
||||||
|
|
||||||
|
assert.notOk(exists(".error"));
|
||||||
|
assert.ok(this.to.getTime() === new Date(2019, 0, 30, 14, 45).getTime());
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,84 @@
|
||||||
|
import componentTest from "helpers/component-test";
|
||||||
|
|
||||||
|
moduleForComponent("date-time-input", { integration: true });
|
||||||
|
|
||||||
|
function dateInput() {
|
||||||
|
return find(".date-picker");
|
||||||
|
}
|
||||||
|
|
||||||
|
function hoursInput() {
|
||||||
|
return find(".field.hours");
|
||||||
|
}
|
||||||
|
|
||||||
|
function minutesInput() {
|
||||||
|
return find(".field.minutes");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDate(date) {
|
||||||
|
this.set("date", date);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pika(year, month, day) {
|
||||||
|
await click(
|
||||||
|
`.pika-button.pika-day[data-pika-year="${year}"][data-pika-month="${month}"][data-pika-day="${day}"]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DATE_TIME = new Date(2019, 0, 29, 14, 45);
|
||||||
|
|
||||||
|
componentTest("default", {
|
||||||
|
template: `{{date-time-input date=date}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ date: DEFAULT_DATE_TIME });
|
||||||
|
},
|
||||||
|
|
||||||
|
test(assert) {
|
||||||
|
assert.equal(dateInput().val(), "January 29, 2019");
|
||||||
|
assert.equal(hoursInput().val(), "14");
|
||||||
|
assert.equal(minutesInput().val(), "45");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("prevents mutations", {
|
||||||
|
template: `{{date-time-input date=date}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ date: DEFAULT_DATE_TIME });
|
||||||
|
},
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
await click(dateInput());
|
||||||
|
await pika(2019, 0, 2);
|
||||||
|
|
||||||
|
assert.ok(this.date.getTime() === DEFAULT_DATE_TIME.getTime());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("allows mutations through actions", {
|
||||||
|
template: `{{date-time-input date=date onChange=onChange}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ date: DEFAULT_DATE_TIME });
|
||||||
|
this.set("onChange", setDate);
|
||||||
|
},
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
await click(dateInput());
|
||||||
|
await pika(2019, 0, 2);
|
||||||
|
|
||||||
|
assert.ok(this.date.getTime() === new Date(2019, 0, 2, 14, 45).getTime());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("can hide time", {
|
||||||
|
template: `{{date-time-input date=date showTime=false}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ date: DEFAULT_DATE_TIME });
|
||||||
|
},
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
assert.notOk(exists(hoursInput()));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,97 @@
|
||||||
|
import componentTest from "helpers/component-test";
|
||||||
|
|
||||||
|
moduleForComponent("time-input", { integration: true });
|
||||||
|
|
||||||
|
function hoursInput() {
|
||||||
|
return find(".field.hours");
|
||||||
|
}
|
||||||
|
|
||||||
|
function minutesInput() {
|
||||||
|
return find(".field.minutes");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTime(time) {
|
||||||
|
this.setProperties(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
componentTest("default", {
|
||||||
|
template: `{{time-input hours=hours minutes=minutes}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ hours: "14", minutes: "58" });
|
||||||
|
},
|
||||||
|
|
||||||
|
test(assert) {
|
||||||
|
assert.equal(hoursInput().val(), "14");
|
||||||
|
assert.equal(minutesInput().val(), "58");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("prevents mutations", {
|
||||||
|
template: `{{time-input hours=hours minutes=minutes}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ hours: "14", minutes: "58" });
|
||||||
|
},
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
await fillIn(hoursInput(), "12");
|
||||||
|
assert.ok(this.hours === "14");
|
||||||
|
|
||||||
|
await fillIn(minutesInput(), "36");
|
||||||
|
assert.ok(this.minutes === "58");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("allows mutations through actions", {
|
||||||
|
template: `{{time-input hours=hours minutes=minutes onChange=onChange}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.setProperties({ hours: "14", minutes: "58" });
|
||||||
|
this.set("onChange", setTime);
|
||||||
|
},
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
await fillIn(hoursInput(), "12");
|
||||||
|
assert.ok(this.hours === "12");
|
||||||
|
|
||||||
|
await fillIn(minutesInput(), "36");
|
||||||
|
assert.ok(this.minutes === "36");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("hours and minutes have boundaries", {
|
||||||
|
template: `{{time-input hours=14 minutes=58 onChange=onChange}}`,
|
||||||
|
|
||||||
|
beforeEach() {
|
||||||
|
this.set("onChange", noop);
|
||||||
|
},
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
await fillIn(hoursInput(), "2");
|
||||||
|
assert.equal(hoursInput().val(), "02");
|
||||||
|
|
||||||
|
await fillIn(hoursInput(), "@");
|
||||||
|
assert.equal(hoursInput().val(), "00");
|
||||||
|
|
||||||
|
await fillIn(hoursInput(), "24");
|
||||||
|
assert.equal(hoursInput().val(), "23");
|
||||||
|
|
||||||
|
await fillIn(hoursInput(), "-1");
|
||||||
|
assert.equal(hoursInput().val(), "00");
|
||||||
|
|
||||||
|
await fillIn(minutesInput(), "@");
|
||||||
|
assert.equal(minutesInput().val(), "00");
|
||||||
|
|
||||||
|
await fillIn(minutesInput(), "2");
|
||||||
|
assert.equal(minutesInput().val(), "02");
|
||||||
|
|
||||||
|
await fillIn(minutesInput(), "60");
|
||||||
|
assert.equal(minutesInput().val(), "59");
|
||||||
|
|
||||||
|
await fillIn(minutesInput(), "-1");
|
||||||
|
assert.equal(minutesInput().val(), "00");
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in New Issue