Commit 3de45054 authored by Illya Klymov's avatar Illya Klymov

Merge branch '214370-utc-support-date-time-picker' into 'master'

Add UTC setting for the date time picker

See merge request gitlab-org/gitlab!33039
parents 3102e946 edf6f2d2
<script> <script>
import { GlDeprecatedButton, GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui'; import { GlIcon, GlDeprecatedButton, GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import { convertToFixedRange, isEqualTimeRanges, findTimeRange } from '~/lib/utils/datetime_range'; import { convertToFixedRange, isEqualTimeRanges, findTimeRange } from '~/lib/utils/datetime_range';
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import DateTimePickerInput from './date_time_picker_input.vue'; import DateTimePickerInput from './date_time_picker_input.vue';
import { import {
defaultTimeRanges, defaultTimeRanges,
defaultTimeRange, defaultTimeRange,
isValidDate, isValidInputString,
stringToISODate, inputStringToIsoDate,
ISODateToString, isoDateToInputString,
truncateZerosInDateTime,
isDateTimePickerInputValid,
} from './date_time_picker_lib'; } from './date_time_picker_lib';
const events = { const events = {
...@@ -24,13 +21,13 @@ const events = { ...@@ -24,13 +21,13 @@ const events = {
export default { export default {
components: { components: {
Icon, GlIcon,
TooltipOnTruncate,
DateTimePickerInput,
GlFormGroup,
GlDeprecatedButton, GlDeprecatedButton,
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlFormGroup,
TooltipOnTruncate,
DateTimePickerInput,
}, },
props: { props: {
value: { value: {
...@@ -48,20 +45,41 @@ export default { ...@@ -48,20 +45,41 @@ export default {
required: false, required: false,
default: true, default: true,
}, },
utc: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
timeRange: this.value, timeRange: this.value,
startDate: '',
endDate: '', /**
* Valid start iso date string, null if not valid value
*/
startDate: null,
/**
* Invalid start date string as input by the user
*/
startFallbackVal: '',
/**
* Valid end iso date string, null if not valid value
*/
endDate: null,
/**
* Invalid end date string as input by the user
*/
endFallbackVal: '',
}; };
}, },
computed: { computed: {
startInputValid() { startInputValid() {
return isValidDate(this.startDate); return isValidInputString(this.startDate);
}, },
endInputValid() { endInputValid() {
return isValidDate(this.endDate); return isValidInputString(this.endDate);
}, },
isValid() { isValid() {
return this.startInputValid && this.endInputValid; return this.startInputValid && this.endInputValid;
...@@ -69,21 +87,31 @@ export default { ...@@ -69,21 +87,31 @@ export default {
startInput: { startInput: {
get() { get() {
return this.startInputValid ? this.formatDate(this.startDate) : this.startDate; return this.dateToInput(this.startDate) || this.startFallbackVal;
}, },
set(val) { set(val) {
// Attempt to set a formatted date if possible try {
this.startDate = isDateTimePickerInputValid(val) ? stringToISODate(val) : val; this.startDate = this.inputToDate(val);
this.startFallbackVal = null;
} catch (e) {
this.startDate = null;
this.startFallbackVal = val;
}
this.timeRange = null; this.timeRange = null;
}, },
}, },
endInput: { endInput: {
get() { get() {
return this.endInputValid ? this.formatDate(this.endDate) : this.endDate; return this.dateToInput(this.endDate) || this.endFallbackVal;
}, },
set(val) { set(val) {
// Attempt to set a formatted date if possible try {
this.endDate = isDateTimePickerInputValid(val) ? stringToISODate(val) : val; this.endDate = this.inputToDate(val);
this.endFallbackVal = null;
} catch (e) {
this.endDate = null;
this.endFallbackVal = val;
}
this.timeRange = null; this.timeRange = null;
}, },
}, },
...@@ -96,10 +124,10 @@ export default { ...@@ -96,10 +124,10 @@ export default {
} }
const { start, end } = convertToFixedRange(this.value); const { start, end } = convertToFixedRange(this.value);
if (isValidDate(start) && isValidDate(end)) { if (isValidInputString(start) && isValidInputString(end)) {
return sprintf(__('%{start} to %{end}'), { return sprintf(__('%{start} to %{end}'), {
start: this.formatDate(start), start: this.stripZerosInDateTime(this.dateToInput(start)),
end: this.formatDate(end), end: this.stripZerosInDateTime(this.dateToInput(end)),
}); });
} }
} catch { } catch {
...@@ -107,6 +135,13 @@ export default { ...@@ -107,6 +135,13 @@ export default {
} }
return ''; return '';
}, },
customLabel() {
if (this.utc) {
return __('Custom range (UTC)');
}
return __('Custom range');
},
}, },
watch: { watch: {
value(newValue) { value(newValue) {
...@@ -132,8 +167,17 @@ export default { ...@@ -132,8 +167,17 @@ export default {
} }
}, },
methods: { methods: {
formatDate(date) { dateToInput(date) {
return truncateZerosInDateTime(ISODateToString(date)); if (date === null) {
return null;
}
return isoDateToInputString(date, this.utc);
},
inputToDate(value) {
return inputStringToIsoDate(value, this.utc);
},
stripZerosInDateTime(str = '') {
return str.replace(' 00:00:00', '');
}, },
closeDropdown() { closeDropdown() {
this.$refs.dropdown.hide(); this.$refs.dropdown.hide();
...@@ -169,10 +213,16 @@ export default { ...@@ -169,10 +213,16 @@ export default {
menu-class="date-time-picker-menu" menu-class="date-time-picker-menu"
toggle-class="date-time-picker-toggle text-truncate" toggle-class="date-time-picker-toggle text-truncate"
> >
<template #button-content>
<span class="gl-flex-grow-1 text-truncate">{{ timeWindowText }}</span>
<span v-if="utc" class="text-muted gl-font-weight-bold gl-font-sm">{{ __('UTC') }}</span>
<gl-icon class="gl-dropdown-caret" name="chevron-down" aria-hidden="true" />
</template>
<div class="d-flex justify-content-between gl-p-2-deprecated-no-really-do-not-use-me"> <div class="d-flex justify-content-between gl-p-2-deprecated-no-really-do-not-use-me">
<gl-form-group <gl-form-group
v-if="customEnabled" v-if="customEnabled"
:label="__('Custom range')" :label="customLabel"
label-for="custom-from-time" label-for="custom-from-time"
label-class="gl-pb-1-deprecated-no-really-do-not-use-me" label-class="gl-pb-1-deprecated-no-really-do-not-use-me"
class="custom-time-range-form-group col-md-7 gl-pl-1-deprecated-no-really-do-not-use-me gl-pr-0 m-0" class="custom-time-range-form-group col-md-7 gl-pl-1-deprecated-no-really-do-not-use-me gl-pr-0 m-0"
...@@ -214,7 +264,7 @@ export default { ...@@ -214,7 +264,7 @@ export default {
active-class="active" active-class="active"
@click="setQuickRange(option)" @click="setQuickRange(option)"
> >
<icon <gl-icon
name="mobile-issue-close" name="mobile-issue-close"
class="align-bottom" class="align-bottom"
:class="{ invisible: !isOptionActive(option) }" :class="{ invisible: !isOptionActive(option) }"
......
...@@ -6,9 +6,9 @@ import { dateFormats } from './date_time_picker_lib'; ...@@ -6,9 +6,9 @@ import { dateFormats } from './date_time_picker_lib';
const inputGroupText = { const inputGroupText = {
invalidFeedback: sprintf(__('Format: %{dateFormat}'), { invalidFeedback: sprintf(__('Format: %{dateFormat}'), {
dateFormat: dateFormats.stringDate, dateFormat: dateFormats.inputFormat,
}), }),
placeholder: dateFormats.stringDate, placeholder: dateFormats.inputFormat,
}; };
export default { export default {
......
import dateformat from 'dateformat'; import dateformat from 'dateformat';
import { __ } from '~/locale'; import { __ } from '~/locale';
/**
* Valid strings for this regex are
* 2019-10-01 and 2019-10-01 01:02:03
*/
const dateTimePickerRegex = /^(\d{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])(?: (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))?$/;
/** /**
* Default time ranges for the date picker. * Default time ranges for the date picker.
* @see app/assets/javascripts/lib/utils/datetime_range.js * @see app/assets/javascripts/lib/utils/datetime_range.js
...@@ -34,23 +28,33 @@ export const defaultTimeRanges = [ ...@@ -34,23 +28,33 @@ export const defaultTimeRanges = [
export const defaultTimeRange = defaultTimeRanges.find(tr => tr.default); export const defaultTimeRange = defaultTimeRanges.find(tr => tr.default);
export const dateFormats = { export const dateFormats = {
ISODate: "yyyy-mm-dd'T'HH:MM:ss'Z'", /**
stringDate: 'yyyy-mm-dd HH:MM:ss', * Format used by users to input dates
*
* Note: Should be a format that can be parsed by Date.parse.
*/
inputFormat: 'yyyy-mm-dd HH:MM:ss',
/**
* Format used to strip timezone from inputs
*/
stripTimezoneFormat: "yyyy-mm-dd'T'HH:MM:ss'Z'",
}; };
/** /**
* The URL params start and end need to be validated * Returns true if the date can be parsed succesfully after
* before passing them down to other components. * being typed by a user.
* *
* @param {string} dateString * It allows some ambiguity so validation is not strict.
* @returns true if the string is a valid date, false otherwise *
* @param {string} value - Value as typed by the user
* @returns true if the value can be parsed as a valid date, false otherwise
*/ */
export const isValidDate = dateString => { export const isValidInputString = value => {
try { try {
// dateformat throws error that can be caught. // dateformat throws error that can be caught.
// This is better than using `new Date()` // This is better than using `new Date()`
if (dateString && dateString.trim()) { if (value && value.trim()) {
dateformat(dateString, 'isoDateTime'); dateformat(value, 'isoDateTime');
return true; return true;
} }
return false; return false;
...@@ -60,25 +64,30 @@ export const isValidDate = dateString => { ...@@ -60,25 +64,30 @@ export const isValidDate = dateString => {
}; };
/** /**
* Convert the input in Time picker component to ISO date. * Convert the input in time picker component to an ISO date.
* *
* @param {string} val * @param {string} value
* @returns {string} * @param {Boolean} utc - If true, it forces the date to by
* formatted using UTC format, ignoring the local time.
* @returns {Date}
*/ */
export const stringToISODate = val => export const inputStringToIsoDate = (value, utc = false) => {
dateformat(new Date(val.replace(/-/g, '/')), dateFormats.ISODate, true); let date = new Date(value);
if (utc) {
// Forces date to be interpreted as UTC by stripping the timezone
// by formatting to a string with 'Z' and skipping timezone
date = dateformat(date, dateFormats.stripTimezoneFormat);
}
return dateformat(date, 'isoUtcDateTime');
};
/** /**
* Convert the ISO date received from the URL to string * Converts a iso date string to a formatted string for the Time picker component.
* for the Time picker component.
* *
* @param {Date} date * @param {String} ISO Formatted date
* @returns {string} * @returns {string}
*/ */
export const ISODateToString = date => dateformat(date, dateFormats.stringDate); export const isoDateToInputString = (date, utc = false) =>
dateformat(date, dateFormats.inputFormat, utc);
export const truncateZerosInDateTime = datetime => datetime.replace(' 00:00:00', '');
export const isDateTimePickerInputValid = val => dateTimePickerRegex.test(val);
export default {}; export default {};
...@@ -6665,6 +6665,9 @@ msgstr "" ...@@ -6665,6 +6665,9 @@ msgstr ""
msgid "Custom range" msgid "Custom range"
msgstr "" msgstr ""
msgid "Custom range (UTC)"
msgstr ""
msgid "CustomCycleAnalytics|Add a stage" msgid "CustomCycleAnalytics|Add a stage"
msgstr "" msgstr ""
...@@ -23477,6 +23480,9 @@ msgstr "" ...@@ -23477,6 +23480,9 @@ msgstr ""
msgid "URL or request ID" msgid "URL or request ID"
msgstr "" msgstr ""
msgid "UTC"
msgstr ""
msgid "Unable to apply suggestions to a deleted line." msgid "Unable to apply suggestions to a deleted line."
msgstr "" msgstr ""
......
import * as dateTimePickerLib from '~/vue_shared/components/date_time_picker/date_time_picker_lib'; import timezoneMock from 'timezone-mock';
import {
isValidInputString,
inputStringToIsoDate,
isoDateToInputString,
} from '~/vue_shared/components/date_time_picker/date_time_picker_lib';
describe('date time picker lib', () => { describe('date time picker lib', () => {
describe('isValidDate', () => { describe('isValidInputString', () => {
[ [
{ {
input: '2019-09-09T00:00:00.000Z', input: '2019-09-09T00:00:00.000Z',
...@@ -48,121 +54,137 @@ describe('date time picker lib', () => { ...@@ -48,121 +54,137 @@ describe('date time picker lib', () => {
output: false, output: false,
}, },
].forEach(({ input, output }) => { ].forEach(({ input, output }) => {
it(`isValidDate return ${output} for ${input}`, () => { it(`isValidInputString return ${output} for ${input}`, () => {
expect(dateTimePickerLib.isValidDate(input)).toBe(output); expect(isValidInputString(input)).toBe(output);
}); });
}); });
}); });
describe('stringToISODate', () => { describe('inputStringToIsoDate', () => {
['', 'null', undefined, 'abc'].forEach(input => { [
'',
'null',
undefined,
'abc',
'xxxx-xx-xx',
'9999-99-19',
'2019-19-23',
'2019-09-23 x',
'2019-09-29 24:24:24',
].forEach(input => {
it(`throws error for invalid input like ${input}`, () => { it(`throws error for invalid input like ${input}`, () => {
expect(() => dateTimePickerLib.stringToISODate(input)).toThrow(); expect(() => inputStringToIsoDate(input)).toThrow();
}); });
}); });
[ [
{ {
input: '2019-09-09 01:01:01', input: '2019-09-08 01:01:01',
output: '2019-09-09T01:01:01Z', output: '2019-09-08T01:01:01Z',
}, },
{ {
input: '2019-09-09 00:00:00', input: '2019-09-08 00:00:00',
output: '2019-09-09T00:00:00Z', output: '2019-09-08T00:00:00Z',
}, },
{ {
input: '2019-09-09 23:59:59', input: '2019-09-08 23:59:59',
output: '2019-09-09T23:59:59Z', output: '2019-09-08T23:59:59Z',
}, },
{ {
input: '2019-09-09', input: '2019-09-08',
output: '2019-09-09T00:00:00Z', output: '2019-09-08T00:00:00Z',
}, },
].forEach(({ input, output }) => {
it(`returns ${output} from ${input}`, () => {
expect(dateTimePickerLib.stringToISODate(input)).toBe(output);
});
});
});
describe('truncateZerosInDateTime', () => {
[
{ {
input: '', input: '2019-09-08',
output: '', output: '2019-09-08T00:00:00Z',
}, },
{ {
input: '2019-10-10', input: '2019-09-08 00:00:00',
output: '2019-10-10', output: '2019-09-08T00:00:00Z',
}, },
{ {
input: '2019-10-10 00:00:01', input: '2019-09-08 23:24:24',
output: '2019-10-10 00:00:01', output: '2019-09-08T23:24:24Z',
}, },
{ {
input: '2019-10-10 00:00:00', input: '2019-09-08 0:0:0',
output: '2019-10-10', output: '2019-09-08T00:00:00Z',
}, },
].forEach(({ input, output }) => { ].forEach(({ input, output }) => {
it(`truncateZerosInDateTime return ${output} for ${input}`, () => { it(`returns ${output} from ${input}`, () => {
expect(dateTimePickerLib.truncateZerosInDateTime(input)).toBe(output); expect(inputStringToIsoDate(input)).toBe(output);
}); });
}); });
describe('timezone formatting', () => {
const value = '2019-09-08 01:01:01';
const utcResult = '2019-09-08T01:01:01Z';
const localResult = '2019-09-08T08:01:01Z';
test.each`
val | locatTimezone | utc | result
${value} | ${'UTC'} | ${undefined} | ${utcResult}
${value} | ${'UTC'} | ${false} | ${utcResult}
${value} | ${'UTC'} | ${true} | ${utcResult}
${value} | ${'US/Pacific'} | ${undefined} | ${localResult}
${value} | ${'US/Pacific'} | ${false} | ${localResult}
${value} | ${'US/Pacific'} | ${true} | ${utcResult}
`(
'when timezone is $locatTimezone, formats $result for utc = $utc',
({ val, locatTimezone, utc, result }) => {
timezoneMock.register(locatTimezone);
expect(inputStringToIsoDate(val, utc)).toBe(result);
timezoneMock.unregister();
},
);
});
}); });
describe('isDateTimePickerInputValid', () => { describe('isoDateToInputString', () => {
[ [
{ {
input: null, input: '2019-09-08T01:01:01Z',
output: false, output: '2019-09-08 01:01:01',
},
{
input: '',
output: false,
}, },
{ {
input: 'xxxx-xx-xx', input: '2019-09-08T01:01:01.999Z',
output: false, output: '2019-09-08 01:01:01',
}, },
{ {
input: '9999-99-19', input: '2019-09-08T00:00:00Z',
output: false, output: '2019-09-08 00:00:00',
},
{
input: '2019-19-23',
output: false,
},
{
input: '2019-09-23',
output: true,
},
{
input: '2019-09-23 x',
output: false,
},
{
input: '2019-09-29 0:0:0',
output: false,
},
{
input: '2019-09-29 00:00:00',
output: true,
},
{
input: '2019-09-29 24:24:24',
output: false,
},
{
input: '2019-09-29 23:24:24',
output: true,
},
{
input: '2019-09-29 23:24:24 ',
output: false,
}, },
].forEach(({ input, output }) => { ].forEach(({ input, output }) => {
it(`returns ${output} for ${input}`, () => { it(`returns ${output} for ${input}`, () => {
expect(dateTimePickerLib.isDateTimePickerInputValid(input)).toBe(output); expect(isoDateToInputString(input)).toBe(output);
});
}); });
describe('timezone formatting', () => {
const value = '2019-09-08T08:01:01Z';
const utcResult = '2019-09-08 08:01:01';
const localResult = '2019-09-08 01:01:01';
test.each`
val | locatTimezone | utc | result
${value} | ${'UTC'} | ${undefined} | ${utcResult}
${value} | ${'UTC'} | ${false} | ${utcResult}
${value} | ${'UTC'} | ${true} | ${utcResult}
${value} | ${'US/Pacific'} | ${undefined} | ${localResult}
${value} | ${'US/Pacific'} | ${false} | ${localResult}
${value} | ${'US/Pacific'} | ${true} | ${utcResult}
`(
'when timezone is $locatTimezone, formats $result for utc = $utc',
({ val, locatTimezone, utc, result }) => {
timezoneMock.register(locatTimezone);
expect(isoDateToInputString(val, utc)).toBe(result);
timezoneMock.unregister();
},
);
}); });
}); });
}); });
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import timezoneMock from 'timezone-mock';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import { import {
defaultTimeRanges, defaultTimeRanges,
...@@ -8,16 +9,16 @@ import { ...@@ -8,16 +9,16 @@ import {
const optionsCount = defaultTimeRanges.length; const optionsCount = defaultTimeRanges.length;
describe('DateTimePicker', () => { describe('DateTimePicker', () => {
let dateTimePicker; let wrapper;
const dropdownToggle = () => dateTimePicker.find('.dropdown-toggle'); const dropdownToggle = () => wrapper.find('.dropdown-toggle');
const dropdownMenu = () => dateTimePicker.find('.dropdown-menu'); const dropdownMenu = () => wrapper.find('.dropdown-menu');
const applyButtonElement = () => dateTimePicker.find('button.btn-success').element; const applyButtonElement = () => wrapper.find('button.btn-success').element;
const findQuickRangeItems = () => dateTimePicker.findAll('.dropdown-item'); const findQuickRangeItems = () => wrapper.findAll('.dropdown-item');
const cancelButtonElement = () => dateTimePicker.find('button.btn-secondary').element; const cancelButtonElement = () => wrapper.find('button.btn-secondary').element;
const createComponent = props => { const createComponent = props => {
dateTimePicker = mount(DateTimePicker, { wrapper = mount(DateTimePicker, {
propsData: { propsData: {
...props, ...props,
}, },
...@@ -25,54 +26,86 @@ describe('DateTimePicker', () => { ...@@ -25,54 +26,86 @@ describe('DateTimePicker', () => {
}; };
afterEach(() => { afterEach(() => {
dateTimePicker.destroy(); wrapper.destroy();
}); });
it('renders dropdown toggle button with selected text', done => { it('renders dropdown toggle button with selected text', () => {
createComponent(); createComponent();
dateTimePicker.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
expect(dropdownToggle().text()).toBe(defaultTimeRange.label); expect(dropdownToggle().text()).toBe(defaultTimeRange.label);
done(); });
});
it('renders dropdown toggle button with selected text and utc label', () => {
createComponent({ utc: true });
return wrapper.vm.$nextTick(() => {
expect(dropdownToggle().text()).toContain(defaultTimeRange.label);
expect(dropdownToggle().text()).toContain('UTC');
}); });
}); });
it('renders dropdown with 2 custom time range inputs', () => { it('renders dropdown with 2 custom time range inputs', () => {
createComponent(); createComponent();
dateTimePicker.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
expect(dateTimePicker.findAll('input').length).toBe(2); expect(wrapper.findAll('input').length).toBe(2);
}); });
}); });
it('renders inputs with h/m/s truncated if its all 0s', done => { describe('renders label with h/m/s truncated if possible', () => {
createComponent({ [
value: { {
start: '2019-10-10T00:00:00.000Z',
end: '2019-10-10T00:00:00.000Z',
label: '2019-10-10 to 2019-10-10',
},
{
start: '2019-10-10T00:00:00.000Z', start: '2019-10-10T00:00:00.000Z',
end: '2019-10-14T00:10:00.000Z', end: '2019-10-14T00:10:00.000Z',
label: '2019-10-10 to 2019-10-14 00:10:00',
}, },
{
start: '2019-10-10T00:00:00.000Z',
end: '2019-10-10T00:00:01.000Z',
label: '2019-10-10 to 2019-10-10 00:00:01',
},
{
start: '2019-10-10T00:00:01.000Z',
end: '2019-10-10T00:00:01.000Z',
label: '2019-10-10 00:00:01 to 2019-10-10 00:00:01',
},
{
start: '2019-10-10T00:00:01.000Z',
end: '2019-10-10T00:00:01.000Z',
utc: true,
label: '2019-10-10 00:00:01 to 2019-10-10 00:00:01 UTC',
},
].forEach(({ start, end, utc, label }) => {
it(`for start ${start}, end ${end}, and utc ${utc}, label is ${label}`, () => {
createComponent({
value: { start, end },
utc,
});
return wrapper.vm.$nextTick(() => {
expect(dropdownToggle().text()).toBe(label);
});
}); });
dateTimePicker.vm.$nextTick(() => {
expect(dateTimePicker.find('#custom-time-from').element.value).toBe('2019-10-10');
expect(dateTimePicker.find('#custom-time-to').element.value).toBe('2019-10-14 00:10:00');
done();
}); });
}); });
it(`renders dropdown with ${optionsCount} (default) items in quick range`, done => { it(`renders dropdown with ${optionsCount} (default) items in quick range`, () => {
createComponent(); createComponent();
dropdownToggle().trigger('click'); dropdownToggle().trigger('click');
dateTimePicker.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
expect(findQuickRangeItems().length).toBe(optionsCount); expect(findQuickRangeItems().length).toBe(optionsCount);
done();
}); });
}); });
it('renders dropdown with a default quick range item selected', done => { it('renders dropdown with a default quick range item selected', () => {
createComponent(); createComponent();
dropdownToggle().trigger('click'); dropdownToggle().trigger('click');
dateTimePicker.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
expect(dateTimePicker.find('.dropdown-item.active').exists()).toBe(true); expect(wrapper.find('.dropdown-item.active').exists()).toBe(true);
expect(dateTimePicker.find('.dropdown-item.active').text()).toBe(defaultTimeRange.label); expect(wrapper.find('.dropdown-item.active').text()).toBe(defaultTimeRange.label);
done();
}); });
}); });
...@@ -86,78 +119,128 @@ describe('DateTimePicker', () => { ...@@ -86,78 +119,128 @@ describe('DateTimePicker', () => {
describe('user input', () => { describe('user input', () => {
const fillInputAndBlur = (input, val) => { const fillInputAndBlur = (input, val) => {
dateTimePicker.find(input).setValue(val); wrapper.find(input).setValue(val);
return dateTimePicker.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
dateTimePicker.find(input).trigger('blur'); wrapper.find(input).trigger('blur');
return dateTimePicker.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
}; };
beforeEach(done => { beforeEach(() => {
createComponent(); createComponent();
dateTimePicker.vm.$nextTick(done); return wrapper.vm.$nextTick();
}); });
it('displays inline error message if custom time range inputs are invalid', done => { it('displays inline error message if custom time range inputs are invalid', () => {
fillInputAndBlur('#custom-time-from', '2019-10-01abc') return fillInputAndBlur('#custom-time-from', '2019-10-01abc')
.then(() => fillInputAndBlur('#custom-time-to', '2019-10-10abc')) .then(() => fillInputAndBlur('#custom-time-to', '2019-10-10abc'))
.then(() => { .then(() => {
expect(dateTimePicker.findAll('.invalid-feedback').length).toBe(2); expect(wrapper.findAll('.invalid-feedback').length).toBe(2);
done(); });
})
.catch(done);
}); });
it('keeps apply button disabled with invalid custom time range inputs', done => { it('keeps apply button disabled with invalid custom time range inputs', () => {
fillInputAndBlur('#custom-time-from', '2019-10-01abc') return fillInputAndBlur('#custom-time-from', '2019-10-01abc')
.then(() => fillInputAndBlur('#custom-time-to', '2019-09-19')) .then(() => fillInputAndBlur('#custom-time-to', '2019-09-19'))
.then(() => { .then(() => {
expect(applyButtonElement().getAttribute('disabled')).toBe('disabled'); expect(applyButtonElement().getAttribute('disabled')).toBe('disabled');
done(); });
})
.catch(done);
}); });
it('enables apply button with valid custom time range inputs', done => { it('enables apply button with valid custom time range inputs', () => {
fillInputAndBlur('#custom-time-from', '2019-10-01') return fillInputAndBlur('#custom-time-from', '2019-10-01')
.then(() => fillInputAndBlur('#custom-time-to', '2019-10-19')) .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19'))
.then(() => { .then(() => {
expect(applyButtonElement().getAttribute('disabled')).toBeNull(); expect(applyButtonElement().getAttribute('disabled')).toBeNull();
done(); });
}) });
.catch(done.fail);
describe('when "apply" is clicked', () => {
it('emits iso dates', () => {
return fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00')
.then(() => fillInputAndBlur('#custom-time-to', '2019-10-19 00:00:00'))
.then(() => {
applyButtonElement().click();
expect(wrapper.emitted().input).toHaveLength(1);
expect(wrapper.emitted().input[0]).toEqual([
{
end: '2019-10-19T00:00:00Z',
start: '2019-10-01T00:00:00Z',
},
]);
});
}); });
it('emits dates in an object when apply is clicked', done => { it('emits iso dates, for dates without time of day', () => {
fillInputAndBlur('#custom-time-from', '2019-10-01') return fillInputAndBlur('#custom-time-from', '2019-10-01')
.then(() => fillInputAndBlur('#custom-time-to', '2019-10-19')) .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19'))
.then(() => { .then(() => {
applyButtonElement().click(); applyButtonElement().click();
expect(dateTimePicker.emitted().input).toHaveLength(1); expect(wrapper.emitted().input).toHaveLength(1);
expect(dateTimePicker.emitted().input[0]).toEqual([ expect(wrapper.emitted().input[0]).toEqual([
{ {
end: '2019-10-19T00:00:00Z', end: '2019-10-19T00:00:00Z',
start: '2019-10-01T00:00:00Z', start: '2019-10-01T00:00:00Z',
}, },
]); ]);
done(); });
})
.catch(done.fail);
}); });
it('unchecks quick range when text is input is clicked', done => { describe('when timezone is different', () => {
beforeAll(() => {
timezoneMock.register('US/Pacific');
});
afterAll(() => {
timezoneMock.unregister();
});
it('emits iso dates', () => {
return fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00')
.then(() => fillInputAndBlur('#custom-time-to', '2019-10-19 12:00:00'))
.then(() => {
applyButtonElement().click();
expect(wrapper.emitted().input).toHaveLength(1);
expect(wrapper.emitted().input[0]).toEqual([
{
start: '2019-10-01T07:00:00Z',
end: '2019-10-19T19:00:00Z',
},
]);
});
});
it('emits iso dates with utc format', () => {
wrapper.setProps({ utc: true });
return wrapper.vm
.$nextTick()
.then(() => fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00'))
.then(() => fillInputAndBlur('#custom-time-to', '2019-10-19 12:00:00'))
.then(() => {
applyButtonElement().click();
expect(wrapper.emitted().input).toHaveLength(1);
expect(wrapper.emitted().input[0]).toEqual([
{
start: '2019-10-01T00:00:00Z',
end: '2019-10-19T12:00:00Z',
},
]);
});
});
});
});
it('unchecks quick range when text is input is clicked', () => {
const findActiveItems = () => findQuickRangeItems().filter(w => w.is('.active')); const findActiveItems = () => findQuickRangeItems().filter(w => w.is('.active'));
expect(findActiveItems().length).toBe(1); expect(findActiveItems().length).toBe(1);
fillInputAndBlur('#custom-time-from', '2019-10-01') return fillInputAndBlur('#custom-time-from', '2019-10-01').then(() => {
.then(() => {
expect(findActiveItems().length).toBe(0); expect(findActiveItems().length).toBe(0);
});
done();
})
.catch(done.fail);
}); });
it('emits dates in an object when a is clicked', () => { it('emits dates in an object when a is clicked', () => {
...@@ -165,23 +248,22 @@ describe('DateTimePicker', () => { ...@@ -165,23 +248,22 @@ describe('DateTimePicker', () => {
.at(3) // any item .at(3) // any item
.trigger('click'); .trigger('click');
expect(dateTimePicker.emitted().input).toHaveLength(1); expect(wrapper.emitted().input).toHaveLength(1);
expect(dateTimePicker.emitted().input[0][0]).toMatchObject({ expect(wrapper.emitted().input[0][0]).toMatchObject({
duration: { duration: {
seconds: expect.any(Number), seconds: expect.any(Number),
}, },
}); });
}); });
it('hides the popover with cancel button', done => { it('hides the popover with cancel button', () => {
dropdownToggle().trigger('click'); dropdownToggle().trigger('click');
dateTimePicker.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
cancelButtonElement().click(); cancelButtonElement().click();
dateTimePicker.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
expect(dropdownMenu().classes('show')).toBe(false); expect(dropdownMenu().classes('show')).toBe(false);
done();
}); });
}); });
}); });
...@@ -210,7 +292,7 @@ describe('DateTimePicker', () => { ...@@ -210,7 +292,7 @@ describe('DateTimePicker', () => {
jest.spyOn(Date, 'now').mockImplementation(() => MOCK_NOW); jest.spyOn(Date, 'now').mockImplementation(() => MOCK_NOW);
}); });
it('renders dropdown with a label in the quick range', done => { it('renders dropdown with a label in the quick range', () => {
createComponent({ createComponent({
value: { value: {
duration: { seconds: 60 * 5 }, duration: { seconds: 60 * 5 },
...@@ -218,14 +300,26 @@ describe('DateTimePicker', () => { ...@@ -218,14 +300,26 @@ describe('DateTimePicker', () => {
options: otherTimeRanges, options: otherTimeRanges,
}); });
dropdownToggle().trigger('click'); dropdownToggle().trigger('click');
dateTimePicker.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
expect(dropdownToggle().text()).toBe('5 minutes'); expect(dropdownToggle().text()).toBe('5 minutes');
});
});
done(); it('renders dropdown with a label in the quick range and utc label', () => {
createComponent({
value: {
duration: { seconds: 60 * 5 },
},
utc: true,
options: otherTimeRanges,
});
dropdownToggle().trigger('click');
return wrapper.vm.$nextTick(() => {
expect(dropdownToggle().text()).toBe('5 minutes UTC');
}); });
}); });
it('renders dropdown with quick range items', done => { it('renders dropdown with quick range items', () => {
createComponent({ createComponent({
value: { value: {
duration: { seconds: 60 * 2 }, duration: { seconds: 60 * 2 },
...@@ -233,7 +327,7 @@ describe('DateTimePicker', () => { ...@@ -233,7 +327,7 @@ describe('DateTimePicker', () => {
options: otherTimeRanges, options: otherTimeRanges,
}); });
dropdownToggle().trigger('click'); dropdownToggle().trigger('click');
dateTimePicker.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
const items = findQuickRangeItems(); const items = findQuickRangeItems();
expect(items.length).toBe(Object.keys(otherTimeRanges).length); expect(items.length).toBe(Object.keys(otherTimeRanges).length);
...@@ -245,22 +339,18 @@ describe('DateTimePicker', () => { ...@@ -245,22 +339,18 @@ describe('DateTimePicker', () => {
expect(items.at(2).text()).toBe('5 minutes'); expect(items.at(2).text()).toBe('5 minutes');
expect(items.at(2).is('.active')).toBe(false); expect(items.at(2).is('.active')).toBe(false);
done();
}); });
}); });
it('renders dropdown with a label not in the quick range', done => { it('renders dropdown with a label not in the quick range', () => {
createComponent({ createComponent({
value: { value: {
duration: { seconds: 60 * 4 }, duration: { seconds: 60 * 4 },
}, },
}); });
dropdownToggle().trigger('click'); dropdownToggle().trigger('click');
dateTimePicker.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
expect(dropdownToggle().text()).toBe('2020-01-23 19:56:00 to 2020-01-23 20:00:00'); expect(dropdownToggle().text()).toBe('2020-01-23 19:56:00 to 2020-01-23 20:00:00');
done();
}); });
}); });
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment