From 91b21878ef3dad423f0efff4c786ca4453899a27 Mon Sep 17 00:00:00 2001
From: Tobias Kuhnert <tobias.kuhnert@ufz.de>
Date: Fri, 4 Jun 2021 07:37:27 +0000
Subject: [PATCH] Implemented DateTimePicker component and tests

---
 components/DateTimePicker.vue | 296 ++++++++++++++++++++
 test/DateTimePicker.test.ts   | 496 ++++++++++++++++++++++++++++++++++
 2 files changed, 792 insertions(+)
 create mode 100644 components/DateTimePicker.vue
 create mode 100644 test/DateTimePicker.test.ts

diff --git a/components/DateTimePicker.vue b/components/DateTimePicker.vue
new file mode 100644
index 000000000..c9a1c7515
--- /dev/null
+++ b/components/DateTimePicker.vue
@@ -0,0 +1,296 @@
+<!--
+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="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>
diff --git a/test/DateTimePicker.test.ts b/test/DateTimePicker.test.ts
new file mode 100644
index 000000000..ed72053f2
--- /dev/null
+++ b/test/DateTimePicker.test.ts
@@ -0,0 +1,496 @@
+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('')
+  })
+})
-- 
GitLab