Skip to content
Snippets Groups Projects
Verified Commit 8caaf770 authored by Marc Hanisch's avatar Marc Hanisch
Browse files

Merge branch 'master' into actions-ui-brainstorming-nbck

parents bef0f9c4 9e3d69de
No related branches found
No related tags found
1 merge request!93Actions UI for devices
<!--
Web client of the Sensor Management System software developed within the
Helmholtz DataHub Initiative by GFZ and UFZ.
Copyright (C) 2020, 2021
- Nils Brinckmann (GFZ, nils.brinckmann@gfz-potsdam.de)
- Marc Hanisch (GFZ, marc.hanisch@gfz-potsdam.de)
- Tobias Kuhnert (UFZ, tobias.kuhnert@ufz.de)
- Helmholtz Centre Potsdam - GFZ German Research Centre for
Geosciences (GFZ, https://www.gfz-potsdam.de)
Parts of this program were developed within the context of the
following publicly funded projects or measures:
- Helmholtz Earth and Environment DataHub
(https://www.helmholtz.de/en/research/earth_and_environment/initiatives/#h51095)
Licensed under the HEESIL, Version 1.0 or - as soon they will be
approved by the "Community" - subsequent versions of the HEESIL
(the "Licence").
You may not use this work except in compliance with the Licence.
You may obtain a copy of the Licence at:
https://gitext.gfz-potsdam.de/software/heesil
Unless required by applicable law or agreed to in writing, software
distributed under the Licence is distributed on an "AS IS" basis,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the Licence for the specific language governing
permissions and limitations under the Licence.
-->
<template>
<v-text-field
:value="valueAsDateTimeString"
:label="label"
:rules="textInputRules"
@input="updateByTextfield"
>
<template #append-outer>
<v-btn icon @click.stop="initPicker">
<v-icon>mdi-calendar-range</v-icon>
</v-btn>
<v-dialog
v-model="dialog"
:width="dialogWidth"
@click:outside="closePicker"
>
<v-card>
<v-card-text class="text-center">
<v-tabs v-model="activeTab" fixed-tabs>
<v-tab v-if="isDateUsed" key="calendar">
<slot name="dateIcon">
<v-icon>mdi-calendar-outline</v-icon>
</slot>
</v-tab>
<v-tab v-if="isTimeUsed" key="time">
<slot name="timeIcon">
<v-icon>mdi-clock-outline</v-icon>
</slot>
</v-tab>
<v-tab-item v-if="isDateUsed" key="calendar">
<v-date-picker
:value="datePickerValue"
class="height-adjustment"
@input="setDatePickerValue"
/>
</v-tab-item>
<v-tab-item v-if="isTimeUsed" key="time">
<v-time-picker
:value="timePickerValue"
format="24hr"
class="height-adjustment"
@input="setTimePickerValue"
/>
</v-tab-item>
</v-tabs>
</v-card-text>
<v-card-actions>
<v-btn
small
text
@click="resetPicker"
>
Cancel
</v-btn>
<v-spacer />
<v-btn
color="green"
small
@click="applyPickerValue"
>
Apply
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
</v-text-field>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator'
import { DateTime } from 'luxon'
const DEFAULT_DIALOG_WIDTH = 340
const DEFAULT_DATETIME_FORMAT = 'yyyy-MM-dd HH:mm'
const DEFAULT_DATE_FORMAT = 'yyyy-MM-dd'
const DEFAULT_TIME_FORMAT = 'HH:mm'
@Component
// @ts-ignore
export default class DateTimePicker extends Vue {
@Prop({ type: Object, default: null }) value!: DateTime | null;
@Prop({ type: String, required: true }) label!: string
@Prop({ type: Number, default: DEFAULT_DIALOG_WIDTH }) dialogWidth?: number;
@Prop({ type: Boolean, default: false }) useDate!: boolean;
@Prop({ type: Boolean, default: false }) useTime!: boolean;
@Prop({ default: () => [], type: Array }) readonly rules!: [];
private isDatetimeUsed: boolean = true;
private usesDate: boolean = false;
private usesTime: boolean = false;
private dialog: boolean = false;
private activeTab: number = 0;
private optsZone = { zone: 'UTC' };
private textInput: string = '';
private currentFormat: string = DEFAULT_DATETIME_FORMAT;
private datePickerValue: string = '';
private timePickerValue: string = '';
created () {
this.usesDate = this.useDate
this.usesTime = this.useTime
if (this.usesDate || this.usesTime) {
this.isDatetimeUsed = false
}
if (this.usesDate && this.usesTime) {
this.isDatetimeUsed = true
this.usesDate = false
this.usesTime = false
}
if (this.usesDate) {
this.currentFormat = DEFAULT_DATE_FORMAT
}
if (this.usesTime) {
this.currentFormat = DEFAULT_TIME_FORMAT
}
}
get isTimeUsed () {
return this.isDatetimeUsed || this.usesTime
}
get isDateUsed () {
return this.isDatetimeUsed || this.usesDate
}
get valueAsDateTimeString (): string {
if (this.value) {
this.setTextInputByValue(this.value)
}
return this.textInput
}
get datePart (): string {
if (this.isValueValidByCurrentFormat(this.textInput)) {
return this.parseToCurrentFormat().toFormat(DEFAULT_DATE_FORMAT)
}
return new Date().toISOString().substr(0, 10)
}
get timePart (): string {
if (this.isValueValidByCurrentFormat(this.textInput)) {
return this.parseToCurrentFormat().toFormat(DEFAULT_TIME_FORMAT)
}
return '00:00'
}
setTextInputByValue (datetimeValue: DateTime | null) {
if (datetimeValue) {
this.textInput = datetimeValue.setZone('UTC').toFormat(this.currentFormat)
} else {
this.textInput = ''
}
}
setDatePickerValue (value: string) {
this.datePickerValue = value
}
setTimePickerValue (value: string) {
this.timePickerValue = value
}
resetPickerValues () {
this.setTimePickerValue('')
this.setDatePickerValue('')
}
initPicker () {
this.dialog = true
this.initDateAndTimePickerValues()
}
initDateAndTimePickerValues () {
this.datePickerValue = this.datePart
this.timePickerValue = this.timePart
}
resetPicker () {
this.resetPickerValues()
this.closePicker()
}
closePicker () {
this.dialog = false
this.activeTab = 0
}
getPickerValue ():string {
if (this.isDatetimeUsed) {
return this.datePickerValue + ' ' + this.timePickerValue
}
if (this.usesDate) {
return this.datePickerValue
}
if (this.usesTime) {
return this.timePickerValue
}
return ''
}
applyPickerValue () {
const value = this.getPickerValue()
this.updateByTextfield(value)
this.closePicker()
this.resetPickerValues()
}
parseToCurrentFormat () {
return DateTime.fromFormat(this.textInput, this.currentFormat, this.optsZone)
}
updateByTextfield (newTextValue: string) {
this.textInput = newTextValue
if (this.isValueValidByCurrentFormat(this.textInput)) {
this.emitDateTimeObject()
} else {
this.emitValue(null)
}
}
emitDateTimeObject () {
const newValue = this.parseToCurrentFormat()
this.emitValue(newValue)
}
emitValue (newValue: DateTime | null) {
this.$emit('input', newValue)
}
isValueValidByCurrentFormat (value: string): boolean {
return DateTime.fromFormat(value, this.currentFormat).isValid
}
get textInputRules () {
let rulesList: ((value: string) => string | boolean)[] = []
if (this.rules.length > 0) {
rulesList = rulesList.concat(this.rules)
}
const textInputRule = (v: string) => {
return this.isValueValidByCurrentFormat(v) || `Please use the format: ${this.currentFormat}`
}
rulesList.push(textInputRule)
return rulesList
}
}
</script>
<style>
.height-adjustment {
min-height: 392px;
}
</style>
import Vue from 'vue'
import { mount } from '@vue/test-utils'
// @ts-ignore
import DateTimePicker from '@/components/DateTimePicker'
import { DateTime } from 'luxon'
import Vuetify from 'vuetify'
Vue.use(Vuetify)
const factory = (options = {}) => {
const vuetify = new Vuetify()
return mount(DateTimePicker, {
vuetify,
...options
})
}
describe('DatetimePicker.vue', () => {
let wrapper: any
it('renders a vue instance', () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
expect(wrapper).toBeTruthy()
})
it('textfield displays the correct label', () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
const textfieldLabel = wrapper.find('.v-text-field__slot > label')
expect(textfieldLabel.text()).toBe('Testlabel')
})
it('textfield displays nothing when value is null', () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
expect(wrapper.find('input[type="text"]').element.value).toBe('')
})
it('textfield displays the correct date and time passed as DateTime-Object', () => {
const testDateTime = DateTime.fromISO('2021-01-20T20:12:00.000Z', { zone: 'UTC' })
wrapper = factory({
propsData: {
label: 'Display date and time',
value: testDateTime
}
})
expect(wrapper.find('input[type="text"]').element.value).toBe('2021-01-20 20:12')
})
it('textfield displays the correct date passed as DateTime-Object', () => {
const testDateTime = DateTime.fromISO('2021-01-20T20:12:00.000Z', { zone: 'UTC' })
wrapper = factory({
propsData: {
label: 'Display date and time',
value: testDateTime,
'use-date': true
}
})
expect(wrapper.find('input[type="text"]').element.value).toBe('2021-01-20')
})
it('textfield displays the correct time passed as DateTime-Object', () => {
const testDateTime = DateTime.fromISO('2021-01-20T20:12:00.000Z', { zone: 'UTC' })
wrapper = factory({
propsData: {
label: 'Display date and time',
value: testDateTime,
'use-time': true
}
})
expect(wrapper.find('input[type="text"]').element.value).toBe('20:12')
})
it('displays correct error message for datetime format, when textfield contains value with wrong format', async () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
const textFieldInput = wrapper.find('input[type="text"]')
await textFieldInput.setValue('2021-05-01')
expect(wrapper.find('input[type="text"]').element.value).toBe('2021-05-01')
const validationMessage = wrapper.find('div.v-messages__message')
expect(validationMessage.text()).toBe('Please use the format: yyyy-MM-dd HH:mm')
})
it('displays correct error message for date format, when textfield contains value with wrong format', async () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null,
'use-date': true
}
})
const textFieldInput = wrapper.find('input[type="text"]')
await textFieldInput.setValue('2021-05-011')
expect(wrapper.find('input[type="text"]').element.value).toBe('2021-05-011')
const validationMessage = wrapper.find('div.v-messages__message')
expect(validationMessage.text()).toBe('Please use the format: yyyy-MM-dd')
})
it('displays correct error message for time format, when textfield contains value with wrong format', async () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null,
'use-time': true
}
})
const textFieldInput = wrapper.find('input[type="text"]')
await textFieldInput.setValue('2021-05-01')
expect(wrapper.find('input[type="text"]').element.value).toBe('2021-05-01')
const validationMessage = wrapper.find('div.v-messages__message')
expect(validationMessage.text()).toBe('Please use the format: HH:mm')
})
it('uses datetime when use-date and use-datime are both WRONGLY passed as props', () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null,
'use-time': true,
'use-date': true
}
})
expect(wrapper.vm.isDatetimeUsed).toBeTruthy()
expect(wrapper.vm.usesDate).toBeFalsy()
expect(wrapper.vm.usesTime).toBeFalsy()
})
it('emits correct dateTime object when textInput is updated', async () => {
const expectedDateTime = DateTime.fromFormat('2020-05-01 12:12', 'yyyy-MM-dd HH:mm', { zone: 'UTC' })
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
wrapper.vm.updateByTextfield('2020-05-01 12:12')
expect(wrapper.emitted('input')).toBeTruthy()
await wrapper.vm.$nextTick()
expect(wrapper.emitted().input[0]).toEqual([expectedDateTime])
})
it('emits correct dateTime object when updated by date picker when value was null', async () => {
const expectedDateTime = DateTime.fromFormat('2020-05-01 00:00', 'yyyy-MM-dd HH:mm', { zone: 'UTC' })
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
wrapper.vm.initDateAndTimePickerValues()
wrapper.vm.setDatePickerValue('2020-05-01')
wrapper.vm.applyPickerValue()
expect(wrapper.emitted('input')).toBeTruthy()
await wrapper.vm.$nextTick()
expect(wrapper.emitted().input[0]).toEqual([expectedDateTime])
})
it('emits correct dateTime object when updated by date picker when value was passed', async () => {
const testDateTime = DateTime.fromISO('2021-01-20T20:12:00.000Z', { zone: 'UTC' })
const expectedDateTime = DateTime.fromFormat('2020-05-01 20:12', 'yyyy-MM-dd HH:mm', { zone: 'UTC' })
wrapper = factory({
propsData: {
label: 'Testlabel',
value: testDateTime
}
})
wrapper.vm.initDateAndTimePickerValues()
wrapper.vm.setDatePickerValue('2020-05-01')
wrapper.vm.applyPickerValue()
expect(wrapper.emitted('input')).toBeTruthy()
await wrapper.vm.$nextTick()
expect(wrapper.emitted().input[0]).toEqual([expectedDateTime])
})
it('emits null when updated by date picker and the date is not valid', async () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
wrapper.vm.initDateAndTimePickerValues()
wrapper.vm.setDatePickerValue('2020-05-88')
wrapper.vm.applyPickerValue()
expect(wrapper.emitted('input')).toBeTruthy()
await wrapper.vm.$nextTick()
expect(wrapper.emitted().input[0]).toEqual([null])
})
it('emits correct dateTime object when updated by time picker when value was null', async () => {
const expectedTime = '12:13'
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
wrapper.vm.initDateAndTimePickerValues()
wrapper.vm.setTimePickerValue(expectedTime)
wrapper.vm.applyPickerValue()
expect(wrapper.emitted('input')).toBeTruthy()
await wrapper.vm.$nextTick()
const actual = wrapper.emitted().input[0][0]
expect(actual.hour + ':' + actual.minute).toBe(expectedTime)
})
it('emits correct dateTime object when updated by time picker when value was passed', async () => {
const testDateTime = DateTime.fromISO('2021-01-20T20:12:00.000Z', { zone: 'UTC' })
const expectedTime = '12:13'
wrapper = factory({
propsData: {
label: 'Testlabel',
value: testDateTime
}
})
wrapper.vm.initDateAndTimePickerValues()
wrapper.vm.setTimePickerValue(expectedTime)
wrapper.vm.applyPickerValue()
expect(wrapper.emitted('input')).toBeTruthy()
await wrapper.vm.$nextTick()
const actual = wrapper.emitted().input[0][0]
expect(actual.hour + ':' + actual.minute).toBe(expectedTime)
})
it('emits null when updated by time picker and the time is not valid', async () => {
const invalidTime = '12:88'
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
wrapper.vm.initDateAndTimePickerValues()
wrapper.vm.setTimePickerValue(invalidTime)
wrapper.vm.applyPickerValue()
expect(wrapper.emitted('input')).toBeTruthy()
await wrapper.vm.$nextTick()
expect(wrapper.emitted().input[0]).toEqual([null])
})
it('setTextInputByValue sets textInput to empty string when null is passed', () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
wrapper.vm.setTextInputByValue(null)
expect(wrapper.vm.textInput).toBe('')
})
it('sets the correct values when picker is closed', () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
wrapper.vm.activeTab = 1
wrapper.vm.display = true
wrapper.vm.closePicker()
expect(wrapper.vm.activeTab).toBe(0)
expect(wrapper.vm.dialog).toBe(false)
})
it('includes the provided rules', () => {
const testRuleNameRequired = (v:any) => !!v || 'Name is required'
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null,
rules: [
testRuleNameRequired
]
}
})
expect(wrapper.vm.textInputRules).toContain(testRuleNameRequired)
})
it('picker initializes correct when value was null', () => {
// Fix to avoid following warning--------------------------
// console.warn
// [Vuetify] Unable to locate target [data-app]
const app = document.createElement('div')
app.setAttribute('data-app', 'true')
document.body.append(app)
// --------------------------------------------------------
const expectedDate = new Date().toISOString().substr(0, 10)
const expectedTime = '00:00'
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
wrapper.vm.initPicker()
expect(wrapper.vm.dialog).toBeTruthy()
expect(wrapper.vm.datePickerValue).toBe(expectedDate)
expect(wrapper.vm.timePickerValue).toBe(expectedTime)
})
it('picker initializes correct when value was passed', () => {
// Fix to avoid following warning--------------------------
// console.warn
// [Vuetify] Unable to locate target [data-app]
const app = document.createElement('div')
app.setAttribute('data-app', 'true')
document.body.append(app)
// --------------------------------------------------------
const testDateTime = DateTime.fromISO('2021-01-20T20:12:00.000Z', { zone: 'UTC' })
const expectedDate = '2021-01-20'
const expectedTime = '20:12'
wrapper = factory({
propsData: {
label: 'Testlabel',
value: testDateTime
}
})
wrapper.vm.initPicker()
expect(wrapper.vm.dialog).toBeTruthy()
expect(wrapper.vm.datePickerValue).toBe(expectedDate)
expect(wrapper.vm.timePickerValue).toBe(expectedTime)
})
it('resetPicker sets the correct values', () => {
// Fix to avoid following warning--------------------------
// console.warn
// [Vuetify] Unable to locate target [data-app]
const app = document.createElement('div')
app.setAttribute('data-app', 'true')
document.body.append(app)
// --------------------------------------------------------
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null
}
})
wrapper.vm.dialog = true
wrapper.vm.datePickerValue = '2021-05-05'
wrapper.vm.timePickerValue = '10:10'
wrapper.vm.activeTab = 1
expect(wrapper.vm.dialog).toBeTruthy()
expect(wrapper.vm.datePickerValue).toBe('2021-05-05')
expect(wrapper.vm.timePickerValue).toBe('10:10')
expect(wrapper.vm.activeTab).toBe(1)
wrapper.vm.resetPicker()
expect(wrapper.vm.dialog).toBeFalsy()
expect(wrapper.vm.datePickerValue).toBe('')
expect(wrapper.vm.timePickerValue).toBe('')
expect(wrapper.vm.activeTab).toBe(0)
})
it('emits correct dateTime object when using use-date and updated by date picker when value was null', async () => {
const expectedDateTime = DateTime.fromFormat('2020-05-01 00:00', 'yyyy-MM-dd HH:mm', { zone: 'UTC' })
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null,
'use-date': true
}
})
wrapper.vm.initDateAndTimePickerValues()
wrapper.vm.setDatePickerValue('2020-05-01')
wrapper.vm.applyPickerValue()
expect(wrapper.emitted('input')).toBeTruthy()
await wrapper.vm.$nextTick()
expect(wrapper.emitted().input[0]).toEqual([expectedDateTime])
})
it('emits correct dateTime object when using use-time and updated by time picker when value was null', async () => {
const expectedTime = '12:13'
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null,
'use-time': true
}
})
wrapper.vm.initDateAndTimePickerValues()
wrapper.vm.setTimePickerValue(expectedTime)
wrapper.vm.applyPickerValue()
expect(wrapper.emitted('input')).toBeTruthy()
await wrapper.vm.$nextTick()
const actual = wrapper.emitted().input[0][0]
expect(actual.hour + ':' + actual.minute).toBe(expectedTime)
})
it('getPickerValue returns empty string when neither date, time nor datetime are true', () => {
wrapper = factory({
propsData: {
label: 'Testlabel',
value: null,
'use-time': true
}
})
wrapper.vm.isUseDatetime = false
wrapper.vm.isUseTime = false
wrapper.vm.isUseDateTime = false
expect(wrapper.vm.getPickerValue()).toBe('')
})
})
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment