diff --git a/common/helpers/dayjs.js b/common/helpers/dayjs.js
index 65d084fc57..9a6e5fde55 100644
--- a/common/helpers/dayjs.js
+++ b/common/helpers/dayjs.js
@@ -9,10 +9,12 @@ import utc from 'dayjs/plugin/utc'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import localizedFormat from 'dayjs/plugin/localizedFormat'
import isBetween from 'dayjs/plugin/isBetween'
+import duration from 'dayjs/plugin/duration'
dayjs.extend(utc)
dayjs.extend(customParseFormat)
dayjs.extend(localizedFormat)
dayjs.extend(isBetween)
+dayjs.extend(duration)
export default dayjs
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index bdf33108c8..7821c6f5d9 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -69,6 +69,7 @@
"devDependencies": {
"@babel/eslint-parser": "7.19.1",
"@testing-library/jest-dom": "5.16.5",
+ "@testing-library/user-event": "14.4.3",
"@testing-library/vue": "5.8.3",
"@vitejs/plugin-vue2": "2.2.0",
"@vue/cli-plugin-babel": "5.0.8",
@@ -3953,6 +3954,19 @@
"node": ">=8"
}
},
+ "node_modules/@testing-library/user-event": {
+ "version": "14.4.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz",
+ "integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
"node_modules/@testing-library/vue": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/@testing-library/vue/-/vue-5.8.3.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 335eed9da6..292f0dc050 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -83,6 +83,7 @@
"devDependencies": {
"@babel/eslint-parser": "7.19.1",
"@testing-library/jest-dom": "5.16.5",
+ "@testing-library/user-event": "14.4.3",
"@testing-library/vue": "5.8.3",
"@vitejs/plugin-vue2": "2.2.0",
"@vue/cli-plugin-babel": "5.0.8",
diff --git a/frontend/src/components/buttons/ButtonBack.vue b/frontend/src/components/buttons/ButtonBack.vue
index e913ea1b7c..eb008efb3b 100644
--- a/frontend/src/components/buttons/ButtonBack.vue
+++ b/frontend/src/components/buttons/ButtonBack.vue
@@ -1,5 +1,10 @@
-
+
mdi-arrow-left
diff --git a/frontend/src/components/buttons/ButtonDelete.vue b/frontend/src/components/buttons/ButtonDelete.vue
index f165e7fa58..ce00c29038 100644
--- a/frontend/src/components/buttons/ButtonDelete.vue
+++ b/frontend/src/components/buttons/ButtonDelete.vue
@@ -14,6 +14,9 @@
{{ $tc('global.button.delete') }}
+
+ {{ $tc('global.button.delete') }}
+
diff --git a/frontend/src/components/buttons/ButtonEdit.vue b/frontend/src/components/buttons/ButtonEdit.vue
index 0c689a1d9f..b2ab39b962 100644
--- a/frontend/src/components/buttons/ButtonEdit.vue
+++ b/frontend/src/components/buttons/ButtonEdit.vue
@@ -4,6 +4,9 @@
{{ $tc('global.button.edit') }}
+
+ {{ $tc('global.button.edit') }}
+
diff --git a/frontend/src/components/campAdmin/CreateCampPeriods.vue b/frontend/src/components/campAdmin/CreateCampPeriods.vue
index 424f515389..6895ddc1e9 100644
--- a/frontend/src/components/campAdmin/CreateCampPeriods.vue
+++ b/frontend/src/components/campAdmin/CreateCampPeriods.vue
@@ -48,6 +48,7 @@
:name="$tc('entity.period.fields.start')"
vee-id="start"
vee-rules="required"
+ :max="period.end"
:my="2"
:filled="false"
required
@@ -59,6 +60,7 @@
input-class="ml-2"
:name="$tc('entity.period.fields.end')"
vee-rules="required|greaterThanOrEqual_date:@start"
+ :min="period.start"
:my="2"
:filled="false"
required
diff --git a/frontend/src/components/campAdmin/DialogPeriodForm.vue b/frontend/src/components/campAdmin/DialogPeriodForm.vue
index c034b7365c..262335705a 100644
--- a/frontend/src/components/campAdmin/DialogPeriodForm.vue
+++ b/frontend/src/components/campAdmin/DialogPeriodForm.vue
@@ -11,12 +11,14 @@
:name="$tc('entity.period.fields.start')"
vee-id="start"
vee-rules="required"
+ :max="localPeriod.end"
/>
diff --git a/frontend/src/components/form/api/ApiWrapperAppend.vue b/frontend/src/components/form/api/ApiWrapperAppend.vue
index 125f0d892f..17ee97cc07 100644
--- a/frontend/src/components/form/api/ApiWrapperAppend.vue
+++ b/frontend/src/components/form/api/ApiWrapperAppend.vue
@@ -5,18 +5,6 @@
mdi-content-save
-
@@ -31,6 +19,7 @@
color="error"
type="submit"
class="mr-1"
+ :aria-label="$tc('global.button.tryagain')"
v-on="on"
@click="wrapper.on.save"
>
@@ -47,6 +36,7 @@
depressed
x-small
color="grey"
+ :aria-label="$tc('global.button.cancel')"
v-on="on"
@click="wrapper.on.reset"
>
@@ -68,8 +58,8 @@
color="success"
type="submit"
class="mr-1"
+ :aria-label="$tc('global.button.save')"
v-on="on"
- @click="wrapper.on.save"
>
mdi-check
@@ -84,6 +74,7 @@
depressed
x-small
color="grey"
+ :aria-label="$tc('global.button.cancel')"
v-on="on"
@click="wrapper.on.reset"
>
@@ -95,12 +86,7 @@
-
+
diff --git a/frontend/src/components/form/api/__tests__/ApiColorPicker.spec.js b/frontend/src/components/form/api/__tests__/ApiColorPicker.spec.js
index 16c039a80a..62c2288394 100644
--- a/frontend/src/components/form/api/__tests__/ApiColorPicker.spec.js
+++ b/frontend/src/components/form/api/__tests__/ApiColorPicker.spec.js
@@ -1,101 +1,90 @@
import ApiColorPicker from '../ApiColorPicker'
-import ApiWrapper from '@/components/form/api/ApiWrapper'
-import Vue from 'vue'
-import Vuetify from 'vuetify'
-import flushPromises from 'flush-promises'
-import formBaseComponents from '@/plugins/formBaseComponents'
-import merge from 'lodash/merge'
+import { screen, waitFor } from '@testing-library/vue'
+import { render } from '@/test/renderWithVuetify.js'
+import user from '@testing-library/user-event'
import { ApiMock } from '@/components/form/api/__tests__/ApiMock'
import { extend } from 'vee-validate'
-import { i18n } from '@/plugins'
-import { mount as mountComponent } from '@vue/test-utils'
import { regex } from 'vee-validate/dist/rules'
-import { waitForDebounce } from '@/test/util'
-
-Vue.use(Vuetify)
-Vue.use(formBaseComponents)
extend('regex', regex)
describe('An ApiColorPicker', () => {
- let vuetify
- let wrapper
let apiMock
- const fieldName = 'test-field/123'
- const COLOR_1 = '#ff0000'
- const COLOR_2 = '#ff00ff'
+ const FIELD_NAME = 'test-field/123'
+ const FIELD_LABEL = 'Test field'
+ const COLOR_1 = '#FF0000'
+ const COLOR_2 = '#FAFFAF'
+ const PICKER_BUTTON_LABEL_TEXT = 'Dialog öffnen um eine Farbe für Test field zu wählen'
beforeEach(() => {
- vuetify = new Vuetify()
apiMock = ApiMock.create()
})
afterEach(() => {
jest.restoreAllMocks()
- wrapper.destroy()
})
- const mount = (options) => {
- const app = Vue.component('App', {
- components: { ApiColorPicker },
+ test('triggers api.patch and status update if input changes', async () => {
+ // given
+ apiMock.get().thenReturn(ApiMock.success(COLOR_1).forFieldName(FIELD_NAME))
+ apiMock.patch().thenReturn(ApiMock.success(COLOR_2))
+ const { container } = render(ApiColorPicker, {
props: {
- fieldName: { type: String, default: fieldName },
+ autoSave: false,
+ fieldname: FIELD_NAME,
+ uri: 'test-field/123',
+ label: FIELD_LABEL,
+ required: true,
},
- template: `
-
- `,
- })
- apiMock.get().thenReturn(ApiMock.success(COLOR_1).forFieldName(fieldName))
- const defaultOptions = {
mocks: {
- $tc: () => {},
api: apiMock.getMocks(),
},
- }
- return mountComponent(app, {
- vuetify,
- i18n,
- attachTo: document.body,
- ...merge(defaultOptions, options),
})
- }
-
- test('triggers api.patch and status update if input changes', async () => {
- apiMock.patch().thenReturn(ApiMock.success(COLOR_2))
- wrapper = mount()
-
- await flushPromises()
-
- const input = wrapper.find('input')
- await input.setValue(COLOR_2)
- await input.trigger('submit')
-
- await waitForDebounce()
- await flushPromises()
- expect(apiMock.getMocks().patch).toBeCalledTimes(1)
- expect(wrapper.findComponent(ApiWrapper).vm.localValue).toBe(COLOR_2)
+ // when
+ // click the button to open the picker
+ await user.click(screen.getByLabelText(PICKER_BUTTON_LABEL_TEXT))
+ // click inside the color picker canvas to select a different color
+ const canvas = container.querySelector('canvas')
+ await user.click(canvas, { clientX: 10, clientY: 10 })
+ // click the save button
+ await user.click(screen.getByLabelText('Speichern'))
+
+ // then
+ await waitFor(async () => {
+ const inputField = await screen.findByLabelText(FIELD_LABEL)
+ expect(inputField.value).toBe(COLOR_2)
+ expect(apiMock.getMocks().patch).toBeCalledTimes(1)
+ })
})
test('updates state if value in store is refreshed and has new value', async () => {
- wrapper = mount()
- apiMock.get().thenReturn(ApiMock.success(COLOR_2).forFieldName(fieldName))
-
- wrapper.findComponent(ApiWrapper).vm.reload()
+ // given
+ apiMock.get().thenReturn(ApiMock.networkError().forFieldName(FIELD_NAME))
+ render(ApiColorPicker, {
+ props: {
+ autoSave: false,
+ fieldname: FIELD_NAME,
+ uri: 'test-field/123',
+ label: FIELD_LABEL,
+ required: true,
+ },
+ mocks: {
+ api: apiMock.getMocks(),
+ },
+ })
+ await screen.findByText('A network error occurred.')
+ expect((await screen.findByLabelText(FIELD_LABEL)).value).not.toBe(COLOR_1)
+ const retryButton = await screen.findByText('Erneut versuchen')
+ apiMock.get().thenReturn(ApiMock.success(COLOR_1).forFieldName(FIELD_NAME))
- await waitForDebounce()
- await flushPromises()
+ // when
+ await user.click(retryButton)
- expect(wrapper.findComponent(ApiWrapper).vm.localValue).toBe(COLOR_2)
- expect(wrapper.find('input[type=text]').element.value).toBe(COLOR_2)
+ // then
+ await waitFor(async () => {
+ expect((await screen.findByLabelText(FIELD_LABEL)).value).toBe(COLOR_1)
+ })
})
})
diff --git a/frontend/src/components/form/api/__tests__/ApiDatePicker.spec.js b/frontend/src/components/form/api/__tests__/ApiDatePicker.spec.js
index 0843c5f7ae..b5216c1676 100644
--- a/frontend/src/components/form/api/__tests__/ApiDatePicker.spec.js
+++ b/frontend/src/components/form/api/__tests__/ApiDatePicker.spec.js
@@ -1,114 +1,86 @@
import ApiDatePicker from '../ApiDatePicker'
-import ApiWrapper from '@/components/form/api/ApiWrapper'
-import Vue from 'vue'
-import Vuetify from 'vuetify'
-import dayjs from '@/plugins/dayjs'
-import flushPromises from 'flush-promises'
-import formBaseComponents from '@/plugins/formBaseComponents'
-import merge from 'lodash/merge'
+import { screen, waitFor } from '@testing-library/vue'
+import { render, setTestLocale } from '@/test/renderWithVuetify.js'
+import user from '@testing-library/user-event'
import { ApiMock } from '@/components/form/api/__tests__/ApiMock'
-import { i18n } from '@/plugins'
-import { mount as mountComponent } from '@vue/test-utils'
-import { waitForDebounce } from '@/test/util'
-import { HTML5_FMT } from '@/common/helpers/dateFormat.js'
-
-Vue.use(Vuetify)
-Vue.use(formBaseComponents)
-Vue.use(dayjs)
describe('An ApiDatePicker', () => {
- let vuetify
- let wrapper
let apiMock
- const fieldName = 'test-field/123'
+ const FIELD_NAME = 'test-field/123'
+ const FIELD_LABEL = 'Test field'
const DATE_1 = '2020-03-01'
- const DATE_2 = '2020-03-24'
-
- const format = (date) => Vue.dayjs.utc(date, HTML5_FMT.DATE).format('DD.MM.YYYY')
+ const DATE_2 = '2020-03-19'
+ const PICKER_BUTTON_LABEL_TEXT = 'Dialog öffnen um ein Datum für Test field zu wählen'
beforeEach(() => {
- i18n.locale = 'de'
- Vue.dayjs.locale(i18n.locale)
- vuetify = new Vuetify()
+ setTestLocale('de')
apiMock = ApiMock.create()
})
afterEach(() => {
jest.restoreAllMocks()
- wrapper.destroy()
})
- const mount = (options) => {
- const app = Vue.component('App', {
- components: { ApiDatePicker },
+ it('triggers api.patch and status update if input changes', async () => {
+ // given
+ apiMock.get().thenReturn(ApiMock.success(DATE_1).forFieldName(FIELD_NAME))
+ apiMock.patch().thenReturn(ApiMock.success(DATE_2))
+ render(ApiDatePicker, {
props: {
- fieldName: { type: String, default: fieldName },
+ autoSave: false,
+ fieldname: FIELD_NAME,
+ uri: 'test-field/123',
+ label: FIELD_LABEL,
+ required: true,
},
- template: `
-
- `,
- })
- apiMock.get().thenReturn(ApiMock.success(DATE_1).forFieldName(fieldName))
- const defaultOptions = {
mocks: {
- $tc: () => {},
api: apiMock.getMocks(),
},
- }
- return mountComponent(app, {
- vuetify,
- i18n,
- attachTo: document.body,
- ...merge(defaultOptions, options),
})
- }
- test('triggers api.patch and status update if input changes', async () => {
- apiMock.patch().thenReturn(ApiMock.success(DATE_2))
- wrapper = mount()
-
- await flushPromises()
-
- // open the date picker
- const openPicker = wrapper.find('button')
- await openPicker.trigger('click')
- // click on day 24 of the month
- await wrapper
- .findAll('button')
- .filter((node) => node.text() === '24')
- .at(0)
- .trigger('click')
+ // when
+ // click the button to open the picker
+ await user.click(screen.getByLabelText(PICKER_BUTTON_LABEL_TEXT))
+ // click the 19th day of the month
+ await user.click(screen.getByText('19'))
// click the save button
- const closeButton = wrapper.find('[data-testid="action-ok"]')
- await closeButton.trigger('click')
- await wrapper.find('input').trigger('submit')
+ await user.click(screen.getByLabelText('Speichern'))
- await waitForDebounce()
- await flushPromises()
-
- expect(apiMock.getMocks().patch).toBeCalledTimes(1)
- expect(wrapper.findComponent(ApiWrapper).vm.localValue).toBe(DATE_2)
+ // then
+ await waitFor(async () => {
+ const inputField = await screen.findByLabelText(FIELD_LABEL)
+ expect(inputField.value).toBe('19.03.2020')
+ expect(apiMock.getMocks().patch).toBeCalledTimes(1)
+ })
})
test('updates state if value in store is refreshed and has new value', async () => {
- wrapper = mount()
- apiMock.get().thenReturn(ApiMock.success(DATE_2).forFieldName(fieldName))
-
- wrapper.findComponent(ApiWrapper).vm.reload()
+ // given
+ apiMock.get().thenReturn(ApiMock.networkError().forFieldName(FIELD_NAME))
+ render(ApiDatePicker, {
+ props: {
+ autoSave: false,
+ fieldname: FIELD_NAME,
+ uri: 'test-field/123',
+ label: FIELD_LABEL,
+ required: true,
+ },
+ mocks: {
+ api: apiMock.getMocks(),
+ },
+ })
+ await screen.findByText('A network error occurred.')
+ expect((await screen.findByLabelText(FIELD_LABEL)).value).not.toBe('01.03.2020')
+ const retryButton = await screen.findByText('Erneut versuchen')
+ apiMock.get().thenReturn(ApiMock.success(DATE_1).forFieldName(FIELD_NAME))
- await waitForDebounce()
- await flushPromises()
+ // when
+ await user.click(retryButton)
- expect(wrapper.findComponent(ApiWrapper).vm.localValue).toBe(DATE_2)
- expect(wrapper.find('input[type=text]').element.value).toBe(format(DATE_2))
+ // then
+ await waitFor(async () => {
+ expect((await screen.findByLabelText(FIELD_LABEL)).value).toBe('01.03.2020')
+ })
})
})
diff --git a/frontend/src/components/form/api/__tests__/ApiMock.js b/frontend/src/components/form/api/__tests__/ApiMock.js
index be87b4436e..9e3467ea62 100644
--- a/frontend/src/components/form/api/__tests__/ApiMock.js
+++ b/frontend/src/components/form/api/__tests__/ApiMock.js
@@ -27,6 +27,12 @@ class MockStubbing {
}
}
+class NetworkErrorMockStubbing extends MockStubbing {
+ constructor() {
+ super()
+ }
+}
+
class ApiMockState {
constructor() {
this._get = jest.fn()
@@ -47,6 +53,22 @@ class ApiMockState {
if (!(mockStubbing instanceof MockStubbing)) {
throw new Error('apiMock must be instance of MockStubbing')
}
+ if (mockStubbing instanceof NetworkErrorMockStubbing) {
+ if (mockStubbing.fieldName === undefined || mockStubbing.value !== undefined) {
+ throw new Error('fieldName must be defined and value must be undefined')
+ }
+ const result = {
+ _meta: {
+ load: Promise.reject({
+ name: 'Network error',
+ message: 'A network error occurred.',
+ }),
+ },
+ }
+ result[mockStubbing.fieldName] = () => result
+ apiMock._get.mockReturnValue(result)
+ return this
+ }
if (mockStubbing.fieldName === undefined || mockStubbing.value === undefined) {
throw new Error('fieldName and value must be defined')
}
@@ -56,6 +78,7 @@ class ApiMockState {
load: Promise.resolve(mockStubbing.value),
},
})
+ return this
},
}
}
@@ -67,10 +90,20 @@ class ApiMockState {
if (!(mockStubbing instanceof MockStubbing)) {
throw new Error('apiMock must be instance of MockStubbing')
}
+ if (mockStubbing instanceof NetworkErrorMockStubbing) {
+ apiMock._patch.mockImplementation(() => {
+ throw {
+ name: 'NetworkError',
+ message: 'A network error occurred.',
+ }
+ })
+ return this
+ }
if (mockStubbing.fieldName !== undefined || mockStubbing.value === undefined) {
throw new Error('fieldName must be undefined and value must be defined')
}
apiMock._patch.mockReturnValue(mockPromiseResolving(mockStubbing.value))
+ return this
},
}
}
@@ -84,4 +117,7 @@ export class ApiMock {
static success(value) {
return new MockStubbing(undefined, value)
}
+ static networkError() {
+ return new NetworkErrorMockStubbing()
+ }
}
diff --git a/frontend/src/components/form/api/__tests__/ApiTimePicker.spec.js b/frontend/src/components/form/api/__tests__/ApiTimePicker.spec.js
index e6294db5e6..013e287be5 100644
--- a/frontend/src/components/form/api/__tests__/ApiTimePicker.spec.js
+++ b/frontend/src/components/form/api/__tests__/ApiTimePicker.spec.js
@@ -1,122 +1,88 @@
import ApiTimePicker from '../ApiTimePicker'
-import ApiWrapper from '@/components/form/api/ApiWrapper'
-import Vue from 'vue'
-import Vuetify from 'vuetify'
-import dayjs from '@/plugins/dayjs'
-import flushPromises from 'flush-promises'
-import formBaseComponents from '@/plugins/formBaseComponents'
-import merge from 'lodash/merge'
+import { screen, waitFor } from '@testing-library/vue'
+import { render, setTestLocale } from '@/test/renderWithVuetify.js'
+import user from '@testing-library/user-event'
import { ApiMock } from '@/components/form/api/__tests__/ApiMock'
-import { i18n } from '@/plugins'
-import { mount as mountComponent } from '@vue/test-utils'
-import { waitForDebounce } from '@/test/util'
-import { HTML5_FMT } from '@/common/helpers/dateFormat.js'
-
-Vue.use(Vuetify)
-Vue.use(formBaseComponents)
-Vue.use(dayjs)
describe('An ApiTimePicker', () => {
- let vuetify
- let wrapper
let apiMock
- const fieldName = 'test-field/123'
+ const FIELD_NAME = 'test-field/123'
+ const FIELD_LABEL = 'Test field'
const TIME_1 = '2037-07-18T09:52:00+00:00'
- const TIME_2_HOUR = 19
- const TIME_2_MINUTE = 15
- const TIME_2 = `2037-07-18T${TIME_2_HOUR}:${TIME_2_MINUTE}:00+00:00`
-
- const format = (date) =>
- Vue.dayjs.utc(date, HTML5_FMT.DATETIME_LOCAL_SECONDS).format('LT')
+ const TIME_2 = '2037-07-18T00:52:00+00:00'
+ const PICKER_BUTTON_LABEL_TEXT = 'Dialog öffnen um eine Zeit für Test field zu wählen'
beforeEach(() => {
- i18n.locale = 'de'
- Vue.dayjs.locale(i18n.locale)
- vuetify = new Vuetify()
+ setTestLocale('de')
apiMock = ApiMock.create()
})
afterEach(() => {
jest.restoreAllMocks()
- wrapper.destroy()
})
- const mount = (options) => {
- const app = Vue.component('App', {
- components: { ApiTimePicker },
+ it('triggers api.patch and status update if input changes', async () => {
+ // given
+ apiMock.get().thenReturn(ApiMock.success(TIME_1).forFieldName(FIELD_NAME))
+ apiMock.patch().thenReturn(ApiMock.success(TIME_2))
+ render(ApiTimePicker, {
props: {
- fieldName: { type: String, default: fieldName },
+ autoSave: false,
+ fieldname: FIELD_NAME,
+ uri: 'test-field/123',
+ label: FIELD_LABEL,
+ required: true,
},
- template: `
-
- `,
- })
- apiMock.get().thenReturn(ApiMock.success(TIME_1).forFieldName(fieldName))
- const defaultOptions = {
mocks: {
- $tc: () => {},
api: apiMock.getMocks(),
},
- }
- return mountComponent(app, {
- vuetify,
- i18n,
- attachTo: document.body,
- ...merge(defaultOptions, options),
})
- }
- test('triggers api.patch and status update if input changes', async () => {
- apiMock.patch().thenReturn(ApiMock.success(TIME_2))
- wrapper = mount()
-
- await flushPromises()
-
- // open the time picker
- const openPicker = wrapper.find('button')
- await openPicker.trigger('click')
- // select hour
- const clockHour = wrapper.findComponent({ name: 'v-time-picker-clock' })
- clockHour.vm.update(TIME_2_HOUR)
- clockHour.vm.valueOnMouseUp = TIME_2_HOUR
- await clockHour.trigger('mouseup')
- // select minute
- const clockMinute = wrapper.findComponent({ name: 'v-time-picker-clock' })
- clockMinute.vm.update(TIME_2_MINUTE)
- clockMinute.vm.valueOnMouseUp = TIME_2_MINUTE
- await clockMinute.trigger('mouseup')
+ // when
+ // click the button to open the picker
+ await user.click(screen.getByLabelText(PICKER_BUTTON_LABEL_TEXT))
+ // Click the 0th hour. We can only click this one, because
+ // testing library is missing the vuetify styles, and all the
+ // number elements overlap
+ await user.click(await screen.findByText('0'))
// click the save button
- const closeButton = wrapper.find('[data-testid="action-ok"]')
- await closeButton.trigger('click')
- await wrapper.find('input').trigger('submit')
- await waitForDebounce()
+ await user.click(screen.getByLabelText('Speichern'))
- await waitForDebounce()
- await flushPromises()
-
- expect(apiMock.getMocks().patch).toBeCalledTimes(1)
- expect(wrapper.findComponent(ApiWrapper).vm.localValue).toBe(TIME_2)
+ // then
+ await waitFor(async () => {
+ const inputField = await screen.findByLabelText(FIELD_LABEL)
+ expect(inputField.value).toBe('00:52')
+ expect(apiMock.getMocks().patch).toBeCalledTimes(1)
+ })
})
- test('updates state if value in store is refreshed and has new value', async () => {
- wrapper = mount()
- apiMock.get().thenReturn(ApiMock.success(TIME_2).forFieldName(fieldName))
-
- wrapper.findComponent(ApiWrapper).vm.reload()
+ it('updates state if value in store is refreshed and has new value', async () => {
+ // given
+ apiMock.get().thenReturn(ApiMock.networkError().forFieldName(FIELD_NAME))
+ render(ApiTimePicker, {
+ props: {
+ autoSave: false,
+ fieldname: FIELD_NAME,
+ uri: 'test-field/123',
+ label: FIELD_LABEL,
+ required: true,
+ },
+ mocks: {
+ api: apiMock.getMocks(),
+ },
+ })
+ await screen.findByText('A network error occurred.')
+ expect((await screen.findByLabelText(FIELD_LABEL)).value).not.toBe('09:52')
+ const retryButton = await screen.findByText('Erneut versuchen')
+ apiMock.get().thenReturn(ApiMock.success(TIME_1).forFieldName(FIELD_NAME))
- await waitForDebounce()
- await flushPromises()
+ // when
+ await user.click(retryButton)
- expect(wrapper.findComponent(ApiWrapper).vm.localValue).toBe(TIME_2)
- expect(wrapper.find('input[type=text]').element.value).toBe(format(TIME_2))
+ // then
+ await waitFor(async () => {
+ expect((await screen.findByLabelText(FIELD_LABEL)).value).toBe('09:52')
+ })
})
})
diff --git a/frontend/src/components/form/base/BasePicker.vue b/frontend/src/components/form/base/BasePicker.vue
index 709174803b..0825ba8d6d 100644
--- a/frontend/src/components/form/base/BasePicker.vue
+++ b/frontend/src/components/form/base/BasePicker.vue
@@ -5,7 +5,6 @@ Displays a field as a picker (can be used with v-model)
diff --git a/frontend/src/components/form/base/EDatePicker.vue b/frontend/src/components/form/base/EDatePicker.vue
index 1bbf24c4d4..8186d1fda7 100644
--- a/frontend/src/components/form/base/EDatePicker.vue
+++ b/frontend/src/components/form/base/EDatePicker.vue
@@ -13,25 +13,28 @@ Displays a field as a date picker (can be used with v-model)
:required="required"
:vee-id="veeId"
:vee-rules="veeRules"
+ button-aria-label-i18n-key="components.form.base.eDatePicker.openPicker"
+ close-on-picker-input
v-bind="$attrs"
@input="$emit('input', $event)"
>
-
+
-
- {{ $tc('global.button.cancel') }}
-
-
- {{ $tc('global.button.ok') }}
+
+ {{ $tc('global.button.close') }}
@@ -59,8 +62,41 @@ export default {
// format in which the `value` property is being provided & input events are triggered
valueFormat: { type: [String, Array], default: 'YYYY-MM-DD' },
- // v-date-picker allowedDates
+ // v-date-picker props
allowedDates: { type: Function, default: null },
+ min: { type: String, default: null },
+ max: { type: String, default: null },
+ },
+ data: () => ({
+ pickerMonth: undefined,
+ }),
+ watch: {
+ min: {
+ handler(newMin) {
+ if (this.value) return
+ if (!newMin) return
+ const currentPickerMonth = this.$date(this.pickerMonth)
+ const newMinPickerMonth = this.$date(newMin)
+ if (currentPickerMonth.unix() < newMinPickerMonth.unix()) {
+ // Update the month displayed in the picker
+ this.pickerMonth = newMinPickerMonth.format('YYYY-MM')
+ }
+ },
+ immediate: true,
+ },
+ max: {
+ handler(newMax) {
+ if (this.value) return
+ if (!newMax) return
+ const currentPickerMonth = this.$date(this.pickerMonth)
+ const newMaxPickerMonth = this.$date(newMax)
+ if (currentPickerMonth.unix() > newMaxPickerMonth.unix()) {
+ // Update the month displayed in the picker
+ this.pickerMonth = newMaxPickerMonth.format('YYYY-MM')
+ }
+ },
+ immediate: true,
+ },
},
methods: {
/**
@@ -95,7 +131,7 @@ export default {
* Format internal value for display in the UI
*/
format(val) {
- if (val !== '') {
+ if (val !== '' && val !== null) {
return this.getValueAsDateTime(val).format('L')
}
return ''
@@ -116,12 +152,17 @@ export default {
*/
parse(val) {
if (val) {
- const parsedDate = this.$date.utc(val, 'L')
- if (parsedDate.isValid() && parsedDate.format('L') === val) {
+ const parsedDate = this.$date(val, ['L', 'l'])
+ if (
+ parsedDate.isValid() &&
+ (parsedDate.format('L') === val || parsedDate.format('l') === val)
+ ) {
const newValue = this.setDateOnValue(parsedDate)
return Promise.resolve(newValue)
} else {
- return Promise.reject(new Error('invalid format'))
+ return Promise.reject(
+ new Error(this.$tc('components.form.base.eDatePicker.invalidFormat'))
+ )
}
} else {
return Promise.resolve('')
diff --git a/frontend/src/components/form/base/ETimePicker.vue b/frontend/src/components/form/base/ETimePicker.vue
index f44999c75b..6a1fad5937 100644
--- a/frontend/src/components/form/base/ETimePicker.vue
+++ b/frontend/src/components/form/base/ETimePicker.vue
@@ -14,22 +14,22 @@ Allows 15min steps only
v-bind="$attrs"
:vee-id="veeId"
:vee-rules="veeRules"
+ button-aria-label-i18n-key="components.form.base.eTimePicker.openPicker"
@input="$emit('input', $event)"
>
-
+
-
- {{ $tc('global.button.cancel') }}
-
-
- {{ $tc('global.button.ok') }}
+
+ {{ $tc('global.button.close') }}
@@ -56,6 +56,10 @@ export default {
// format in which the `value` property is being provided & input events are triggered
valueFormat: { type: [String, Array], default: 'YYYY-MM-DDTHH:mm:ssZ' },
+
+ // v-time-picker props
+ min: { type: String, default: null },
+ max: { type: String, default: null },
},
methods: {
allowedStep: (m) => m % 15 === 0,
@@ -93,7 +97,7 @@ export default {
* Format internal value for display in the UI
*/
format(val) {
- if (val !== '') {
+ if (val !== '' && val !== null) {
return this.getValueAsDateTime(val).format('LT')
}
return ''
@@ -114,12 +118,16 @@ export default {
*/
parse(val) {
if (val) {
- const parsedDateTime = this.$date.utc(val, 'LT')
- if (parsedDateTime.isValid() && parsedDateTime.format('LT') === val) {
+ const parsedDateTime = this.$date(val, 'LT')
+ const formatted = parsedDateTime.format('LT')
+ const valIgnoringLeadingZero = val.replace(/^0([1-9].+)/, '$1')
+ if (parsedDateTime.isValid() && formatted === valIgnoringLeadingZero) {
const newValue = this.setTimeOnValue(parsedDateTime)
return Promise.resolve(newValue)
} else {
- return Promise.reject(new Error('invalid format'))
+ return Promise.reject(
+ new Error(this.$tc('components.form.base.eTimePicker.invalidFormat'))
+ )
}
} else {
return Promise.resolve('')
diff --git a/frontend/src/components/form/base/__tests__/EColorPicker.spec.js b/frontend/src/components/form/base/__tests__/EColorPicker.spec.js
index 51a66b091f..c7f08f178d 100644
--- a/frontend/src/components/form/base/__tests__/EColorPicker.spec.js
+++ b/frontend/src/components/form/base/__tests__/EColorPicker.spec.js
@@ -1,171 +1,254 @@
-import Vue from 'vue'
-import Vuetify from 'vuetify'
+import { screen, waitFor } from '@testing-library/vue'
+import { render, setTestLocale, snapshotOf } from '@/test/renderWithVuetify.js'
+import user from '@testing-library/user-event'
+import EColorPicker from '../EColorPicker'
+
import { regex } from 'vee-validate/dist/rules'
import { extend } from 'vee-validate'
+extend('regex', regex)
-import i18n from '@/plugins/i18n'
-import formBaseComponents from '@/plugins/formBaseComponents'
+describe('An EColorPicker', () => {
+ const COLOR1 = '#ff0000'
+ const COLOR2 = '#ff00ff'
+ const COLOR3 = '#FAFFAF'
+ const INVALID_COLOR = 'some new color'
+ const PICKER_BUTTON_LABEL_TEXT = 'Dialog öffnen um eine Farbe für test zu wählen'
+ const VALIDATION_MESSAGE = 'test is not valid'
-import { mount as mountComponent } from '@vue/test-utils'
-import EColorPicker from '../EColorPicker'
-import { waitForDebounce } from '@/test/util'
-import flushPromises from 'flush-promises'
+ beforeEach(() => {
+ setTestLocale('de')
+ })
-Vue.use(Vuetify)
-Vue.use(formBaseComponents)
+ it('renders the component', async () => {
+ // given
-extend('regex', regex)
+ // when
+ render(EColorPicker, {
+ props: {
+ value: COLOR1,
+ name: 'test',
+ },
+ })
-describe('An EColorPicker', () => {
- let vuetify
+ // then
+ await screen.findByDisplayValue(COLOR1)
+ screen.getByLabelText(PICKER_BUTTON_LABEL_TEXT)
+ })
- const COLOR_1 = '#ff0000'
- const COLOR_2 = '#ff00ff'
- const INVALID_COLOR = 'some new color'
+ it('looks like a color picker', async () => {
+ // given
- const createMouseEvent = (x, y) => {
- return {
- preventDefault: () => {},
- clientX: x,
- clientY: y,
- }
- }
-
- const rectMock = {
- bottom: 0,
- height: 100,
- width: 100,
- left: 0,
- right: 0,
- top: 0,
- x: 0,
- y: 0,
- }
-
- const mount = (options) => mountComponent(EColorPicker, { vuetify, i18n, ...options })
+ // when
+ const { container } = render(EColorPicker, {
+ props: { value: COLOR1, name: 'test' },
+ })
- beforeEach(() => {
- vuetify = new Vuetify()
+ // then
+ expect(snapshotOf(container)).toMatchSnapshot('pickerclosed')
+
+ // when
+ await user.click(screen.getByLabelText(PICKER_BUTTON_LABEL_TEXT))
+
+ // then
+ await screen.findByText('Schliessen')
+ expect(snapshotOf(container)).toMatchSnapshot('pickeropen')
})
- test('renders', async () => {
- const wrapper = mount({
- propsData: {
- value: COLOR_1,
- },
+ it('opens the picker when the text field is clicked', async () => {
+ // given
+ render(EColorPicker, {
+ props: { value: COLOR1, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(COLOR1)
+
+ // when
+ await user.click(inputField)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.findByText('Schliessen')).toBeVisible()
})
- await flushPromises()
- expect(wrapper.find('input[type=text]').element.value).toBe(COLOR_1)
})
- test('looks like a color picker', async () => {
- const wrapper = mountComponent(
- {
- data: () => ({ color: COLOR_1 }),
- template: '
',
- components: { 'e-color-picker': EColorPicker },
- },
- {
- vuetify,
- attachTo: document.body,
- i18n,
- }
- )
- await waitForDebounce()
- expect(wrapper).toMatchSnapshot('pickerclosed')
- await wrapper.find('button').trigger('click')
- expect(wrapper).toMatchSnapshot('pickeropen')
- wrapper.destroy()
+ it('closes the picker when clicking the close button', async () => {
+ // given
+ render(EColorPicker, {
+ props: { value: COLOR1, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(COLOR1)
+ await user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText('Schliessen')).toBeVisible()
+ })
+ const closeButton = screen.getByText('Schliessen')
+
+ // when
+ await user.click(closeButton)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.queryByText('Schliessen')).not.toBeVisible()
+ })
})
- test('updates v-model when the value changes', async () => {
- const wrapper = mountComponent(
- {
- data: () => ({ color: COLOR_1 }),
- template: '
',
- components: { 'e-color-picker': EColorPicker },
- },
- {
- vuetify,
- i18n,
- }
- )
- expect(wrapper.vm.color).toBe(COLOR_1)
- const inputSpy = jest.fn()
- wrapper.findComponent(EColorPicker).vm.$on('input', (event) => inputSpy(event))
- const input = wrapper.find('input[type=text]')
- await input.setValue(COLOR_2)
- await waitForDebounce()
- expect(inputSpy).toBeCalledTimes(1)
- expect(inputSpy).toBeCalledWith(COLOR_2)
- expect(wrapper.vm.color).toBe(COLOR_2)
+ it('closes the picker when clicking outside', async () => {
+ // given
+ render(EColorPicker, {
+ props: { value: COLOR1, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(COLOR1)
+ await user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText('Schliessen')).toBeVisible()
+ })
+
+ // when
+ await user.click(document.body)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.queryByText('Schliessen')).not.toBeVisible()
+ })
})
- test('validates the input', async () => {
- const wrapper = mount({
- propsData: {
- value: COLOR_1,
- name: 'Color',
- },
+ it('closes the picker when pressing escape', async () => {
+ // given
+ render(EColorPicker, {
+ props: { value: COLOR1, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(COLOR1)
+ await user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText('Schliessen')).toBeVisible()
+ })
+
+ // when
+ await user.keyboard('{Escape}')
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.queryByText('Schliessen')).not.toBeVisible()
})
- const input = wrapper.find('input[type=text]')
- await input.setValue(INVALID_COLOR)
- await waitForDebounce()
- expect(wrapper.text()).toContain('Color is not valid.')
})
- test('updates its value when a color is picked', async () => {
- const wrapper = mountComponent(
- {
- data: () => ({ color: COLOR_2 }),
- template: '
',
- components: { 'e-color-picker': EColorPicker },
- },
- {
- vuetify,
- attachTo: document.body,
- i18n,
- }
- )
- await waitForDebounce()
- // open the color picker
- const openPicker = wrapper.find('button')
- await openPicker.trigger('click')
+ it('does not close the picker when selecting a color', async () => {
+ // given
+ const { container } = render(EColorPicker, {
+ props: { value: COLOR1, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(COLOR1)
+ await user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText('Schliessen')).toBeVisible()
+ })
+
+ // when
// click inside the color picker canvas to select a different color
- const canvas = wrapper.findComponent({ name: 'v-color-picker-canvas' })
- canvas.vm.$el.getBoundingClientRect = () => rectMock
- canvas.vm.handleClick(createMouseEvent(10, 10))
- await flushPromises()
- // click the save button
- const closeButton = wrapper.find('[data-testid="action-ok"]')
- await closeButton.trigger('click')
- await waitForDebounce()
- expect(wrapper.find('input[type=text]').element.value).toBe('#E6CFE6')
- wrapper.destroy()
+ const canvas = container.querySelector('canvas')
+ await user.click(canvas, { clientX: 10, clientY: 10 })
+
+ // then
+ // close button should stay visible
+ await expect(
+ waitFor(() => {
+ expect(screen.queryByText('Schliessen')).not.toBeVisible()
+ })
+ ).rejects.toThrow(/Received element is visible/)
})
- test('accepts 3-digit hex color codes', async () => {
- const wrapper = mountComponent(
- {
- data: () => ({ color: '#abc' }),
- template: '
',
- components: { 'e-color-picker': EColorPicker },
- },
- {
- vuetify,
- attachTo: document.body,
- i18n,
- }
- )
- await waitForDebounce()
- // open the color picker
- const openPicker = wrapper.find('button')
- await openPicker.trigger('click')
- // click the save button
- const closeButton = wrapper.find('[data-testid="action-ok"]')
- await closeButton.trigger('click')
- await waitForDebounce()
- expect(wrapper.find('input[type=text]').element.value).toBe('#AABBCC')
- wrapper.destroy()
+ it('updates v-model when the value changes', async () => {
+ // given
+ const { emitted } = render(EColorPicker, {
+ props: { value: COLOR1, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(COLOR1)
+
+ // when
+ await user.clear(inputField)
+ await user.keyboard(COLOR2)
+
+ // then
+ await waitFor(async () => {
+ const events = emitted().input
+ // some input events were fired
+ expect(events.length).toBeGreaterThan(0)
+ // the last one included the parsed version of our entered time
+ expect(events[events.length - 1]).toEqual([COLOR2])
+ })
+ // Our entered time should be visible...
+ screen.getByDisplayValue(COLOR2)
+ // ...and stay visible
+ await expect(
+ waitFor(() => {
+ expect(screen.getByDisplayValue(COLOR2)).not.toBeVisible()
+ })
+ ).rejects.toThrow(/Received element is visible/)
+ })
+
+ it('updates v-model when a new color is selected in the picker', async () => {
+ // given
+ const { emitted, container } = render(EColorPicker, {
+ props: { value: COLOR1, name: 'test' },
+ })
+ await screen.findByDisplayValue(COLOR1)
+
+ // when
+ // click the button to open the picker
+ await user.click(screen.getByLabelText(PICKER_BUTTON_LABEL_TEXT))
+ // click inside the color picker canvas to select a different color
+ const canvas = container.querySelector('canvas')
+ await user.click(canvas, { clientX: 10, clientY: 10 })
+ // click the close button
+ await user.click(screen.getByText('Schliessen'))
+
+ // then
+ await waitFor(async () => {
+ const events = emitted().input
+ // some input events were fired
+ expect(events.length).toBeGreaterThan(0)
+ // the last one included the parsed version of our entered time
+ expect(events[events.length - 1]).toEqual([COLOR3])
+ })
+ // Our entered time should be visible...
+ screen.getByDisplayValue(COLOR3)
+ // ...and stay visible
+ await expect(
+ waitFor(() => {
+ expect(screen.getByDisplayValue(COLOR3)).not.toBeVisible()
+ })
+ ).rejects.toThrow(/Received element is visible/)
+ })
+
+ it('validates the input', async () => {
+ // given
+ render(EColorPicker, {
+ props: { value: COLOR1, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(COLOR1)
+
+ // when
+ await user.clear(inputField)
+ await user.keyboard(INVALID_COLOR)
+
+ // then
+ await screen.findByText(VALIDATION_MESSAGE)
+ })
+
+ it('accepts 3-digit hex color codes, after picker has been shown', async () => {
+ render(EColorPicker, {
+ props: { value: COLOR1, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(COLOR1)
+ // click the button to open the picker
+ await user.click(screen.getByLabelText(PICKER_BUTTON_LABEL_TEXT))
+
+ // when
+ await user.clear(inputField)
+ await user.keyboard('#abc')
+
+ // then
+ await waitFor(() => {
+ screen.getByDisplayValue('#AABBCC')
+ })
})
})
diff --git a/frontend/src/components/form/base/__tests__/EDatePicker.spec.js b/frontend/src/components/form/base/__tests__/EDatePicker.spec.js
index a4830b4de0..f9498bf9b6 100644
--- a/frontend/src/components/form/base/__tests__/EDatePicker.spec.js
+++ b/frontend/src/components/form/base/__tests__/EDatePicker.spec.js
@@ -1,152 +1,381 @@
-import Vue from 'vue'
-import Vuetify from 'vuetify'
-
-import i18n from '@/plugins/i18n'
-import formBaseComponents from '@/plugins/formBaseComponents'
-import dayjs from '@/plugins/dayjs'
-
-import { mount as mountComponent } from '@vue/test-utils'
+import { screen, waitFor } from '@testing-library/vue'
+import { render, setTestLocale, snapshotOf } from '@/test/renderWithVuetify.js'
+import user from '@testing-library/user-event'
import EDatePicker from '../EDatePicker'
-import { waitForDebounce } from '@/test/util'
-import flushPromises from 'flush-promises'
-
-Vue.use(Vuetify)
-Vue.use(formBaseComponents)
-Vue.use(dayjs)
describe('An EDatePicker', () => {
- let vuetify
-
- const DATE_1 = '2020-03-01'
- const DATE_2 = '2015-12-31'
- const INVALID_DATE_1 = 'some date'
- const INVALID_DATE_2 = '2026-13-01'
-
- const localeData = [
- [
- 'de',
- {
- date_1: '01.03.2020',
- date_2: '31.12.2015',
- date_3: '24.03.2020',
- },
- ],
- [
- 'en',
- {
- date_1: '03/01/2020',
- date_2: '12/31/2015',
- date_3: '03/24/2020',
- },
- ],
- ]
-
- const mount = (options) => mountComponent(EDatePicker, { vuetify, i18n, ...options })
-
- describe.each(localeData)('in locale %s', (locale, data) => {
+ const DATE1_ISO = '2020-03-01'
+ const DATE2_ISO = '2020-03-19'
+
+ const localeData = {
+ de: {
+ date1: '01.03.2020',
+ date2: '19.03.2020',
+ dateShort: '2.4.2021',
+ dateInWrongLocale: '03/19/2020',
+ labelText: 'Dialog öffnen um ein Datum für test zu wählen',
+ date1Heading: 'März 2020',
+ date3Heading: 'Januar 2111',
+ date4Heading: 'Januar 1999',
+ closeButton: 'Schliessen',
+ validationMessage:
+ 'Ungültiges Format, bitte gib das Datum im Format DD.MM.YYYY ein',
+ },
+ en: {
+ date1: '03/01/2020',
+ date2: '03/19/2020',
+ dateShort: '4/2/2021',
+ dateInWrongLocale: '19.03.2020',
+ labelText: 'Open dialog to select a date for test',
+ date1Heading: 'March 2020',
+ date3Heading: 'January 2111',
+ date4Heading: 'January 1999',
+ closeButton: 'Close',
+ validationMessage: 'Invalid format, please enter the date in the format MM/DD/YYYY',
+ },
+ }
+
+ describe.each(Object.entries(localeData))('in locale %s', (locale, data) => {
beforeEach(() => {
- i18n.locale = locale
- Vue.dayjs.locale(locale)
- vuetify = new Vuetify()
- })
-
- test('renders', async () => {
- const wrapper = mount({
- propsData: {
- value: DATE_1,
- },
- })
- await flushPromises()
- expect(wrapper.find('input[type=text]').element.value).toBe(data.date_1)
- })
-
- test('looks like a date picker', async () => {
- const wrapper = mountComponent(
- {
- data: () => ({ date: DATE_1 }),
- template: '
',
- components: { 'e-date-picker': EDatePicker },
- },
- {
- vuetify,
- attachTo: document.body,
- i18n,
- }
- )
- await waitForDebounce()
- expect(wrapper).toMatchSnapshot('pickerclosed')
- await wrapper.find('button').trigger('click')
- expect(wrapper).toMatchSnapshot('pickeropen')
- wrapper.destroy()
- })
-
- test('updates v-model when the value changes', async () => {
- const wrapper = mountComponent(
- {
- data: () => ({ date: DATE_1 }),
- template: '
',
- components: { 'e-date-picker': EDatePicker },
- },
- {
- vuetify,
- i18n,
- }
- )
- expect(wrapper.vm.date).toBe(DATE_1)
- const inputSpy = jest.fn()
- wrapper.findComponent(EDatePicker).vm.$on('input', (event) => inputSpy(event))
- const input = wrapper.find('input[type=text]')
- await input.setValue(data.date_2)
- await waitForDebounce()
- expect(inputSpy).toBeCalledTimes(1)
- expect(inputSpy).toBeCalledWith(DATE_2)
- expect(wrapper.vm.date).toBe(DATE_2)
- })
-
- test('validates the input', async () => {
- const wrapper = mount({
- propsData: {
- value: DATE_1,
- },
- })
- const input = wrapper.find('input[type=text]')
- await input.setValue(INVALID_DATE_1)
- await waitForDebounce()
- expect(wrapper.text()).toContain('invalid format')
- await input.setValue(INVALID_DATE_2)
- await waitForDebounce()
- expect(wrapper.text()).toContain('invalid format')
- })
-
- test('updates its value when a date is picked', async () => {
- const wrapper = mountComponent(
- {
- data: () => ({ date: DATE_1 }),
- template: '
',
- components: { 'e-date-picker': EDatePicker },
- },
- {
- vuetify,
- attachTo: document.body,
- i18n,
- }
+ setTestLocale(locale)
+ })
+
+ it('renders the component', async () => {
+ // given
+
+ // when
+ render(EDatePicker, { props: { value: DATE1_ISO, name: 'test' } })
+
+ // then
+ await screen.findByDisplayValue(data.date1)
+ screen.getByLabelText(data.labelText)
+ })
+
+ it('looks like a date picker', async () => {
+ // given
+
+ // when
+ const { container } = render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+
+ // then
+ expect(snapshotOf(container)).toMatchSnapshot('pickerclosed')
+
+ // when
+ await user.click(screen.getByLabelText(data.labelText))
+
+ // then
+ await screen.findByText(data.closeButton)
+ await screen.findByText(data.date1Heading)
+ expect(snapshotOf(container)).toMatchSnapshot('pickeropen')
+ })
+
+ it('opens the picker when the text field is clicked', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.date1)
+
+ // when
+ await user.click(inputField)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.findByText(data.date1Heading)).toBeVisible()
+ })
+ })
+
+ it('closes the picker when clicking the close button', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.date1)
+ await user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText(data.date1Heading)).toBeVisible()
+ })
+ const closeButton = screen.getByText(data.closeButton)
+
+ // when
+ await user.click(closeButton)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.queryByText(data.date1Heading)).not.toBeVisible()
+ })
+ })
+
+ it('closes the picker when clicking outside', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.date1)
+ await user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText(data.date1Heading)).toBeVisible()
+ })
+
+ // when
+ await user.click(document.body)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.queryByText(data.date1Heading)).not.toBeVisible()
+ })
+ })
+
+ it('closes the picker when pressing escape', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.date1)
+ await user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText(data.date1Heading)).toBeVisible()
+ })
+
+ // when
+ await user.keyboard('{Escape}')
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.queryByText(data.date1Heading)).not.toBeVisible()
+ })
+ })
+
+ it('closes the picker when selecting a date', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.date1)
+ await user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText(data.date1Heading)).toBeVisible()
+ })
+
+ // when
+ // click the 19th day of the month
+ await user.click(screen.getByText('19'))
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.queryByText(data.date1Heading)).not.toBeVisible()
+ })
+ })
+
+ it('re-opens the picker when clicking the text field again after selecting a date', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.date1)
+ await user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText(data.date1Heading)).toBeVisible()
+ })
+ // click the 19th day of the month
+ await user.click(screen.getByText('19'))
+ await waitFor(async () => {
+ expect(await screen.queryByText(data.date1Heading)).not.toBeVisible()
+ })
+
+ // when
+ await user.click(inputField)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.findByText(data.date1Heading)).toBeVisible()
+ })
+ })
+
+ it('updates v-model when the input field is changed', async () => {
+ // given
+ const { emitted } = render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.date1)
+
+ // when
+ await user.clear(inputField)
+ await user.keyboard(data.date2)
+
+ // then
+ await waitFor(async () => {
+ const events = emitted().input
+ // some input events were fired
+ expect(events.length).toBeGreaterThan(0)
+ // the last one included the parsed version of our entered date
+ expect(events[events.length - 1]).toEqual([DATE2_ISO])
+ })
+ // Our entered date should be visible...
+ screen.getByDisplayValue(data.date2)
+ // ...and stay visible
+ await expect(
+ waitFor(() => {
+ expect(screen.getByDisplayValue(data.date2)).not.toBeVisible()
+ })
+ ).rejects.toThrow(/Received element is visible/)
+ })
+
+ it('updates v-model when a new date is selected in the picker', async () => {
+ // given
+ const { emitted } = render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+ await screen.findByDisplayValue(data.date1)
+
+ // when
+ // click the button to open the picker
+ await user.click(screen.getByLabelText(data.labelText))
+ // click the 19th day of the month
+ await user.click(screen.getByText('19'))
+
+ // then
+ await waitFor(async () => {
+ const events = emitted().input
+ // some input events were fired
+ expect(events.length).toBeGreaterThan(0)
+ // the last one included the parsed version of our entered date
+ expect(events[events.length - 1]).toEqual([DATE2_ISO])
+ })
+ // Our selected date should be visible...
+ screen.getByDisplayValue(data.date2)
+ // ...and stay visible
+ await expect(
+ waitFor(() => {
+ expect(screen.getByDisplayValue(data.date2)).not.toBeVisible()
+ })
+ ).rejects.toThrow(/Received element is visible/)
+ })
+
+ it('validates the input', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.date1)
+
+ // when
+ await user.clear(inputField)
+ await user.keyboard(data.dateInWrongLocale)
+
+ // then
+ await screen.findByText(data.validationMessage)
+ })
+
+ it('allows inputting a date in short format', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.date1)
+
+ // when
+ await user.clear(inputField)
+ await user.keyboard(data.dateShort)
+
+ // then
+ expect(screen.queryByText(data.validationMessage)).not.toBeInTheDocument()
+ // validation message should not appear
+ await expect(screen.findByText(data.validationMessage)).rejects.toThrow(
+ /Unable to find an element with the text/
)
- await waitForDebounce()
- // open the date picker
- const openPicker = wrapper.find('button')
- await openPicker.trigger('click')
- // click on day 24 of the month
- await wrapper
- .findAll('button')
- .filter((node) => node.text() === '24')
- .at(0)
- .trigger('click')
- // click the save button
- const closeButton = wrapper.find('[data-testid="action-ok"]')
- await closeButton.trigger('click')
- await waitForDebounce()
- expect(wrapper.find('input[type=text]').element.value).toBe(data.date_3)
- wrapper.destroy()
+ })
+
+ it('autoscrolls forward to the earliest allowable month based on min', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: '', name: 'test', min: '2111-01-01' },
+ })
+
+ // when
+ await user.click(screen.getByLabelText(data.labelText))
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.findByText(data.date3Heading)).toBeVisible()
+ })
+ })
+
+ it('does not autoscroll forward if given a value', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test', min: '2111-01-01' },
+ })
+
+ // when
+ await user.click(screen.getByLabelText(data.labelText))
+
+ // then
+ await expect(async () => {
+ await screen.findByText(data.date3Heading)
+ }).rejects.toThrow(/Unable to find an element with the text/)
+ })
+
+ it('does not autoscroll backward based on min', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: '', name: 'test', min: '1999-01-01' },
+ })
+
+ // when
+ await user.click(screen.getByLabelText(data.labelText))
+
+ // then
+ await expect(async () => {
+ await screen.findByText(data.date4Heading)
+ }).rejects.toThrow(/Unable to find an element with the text/)
+ })
+
+ it('autoscrolls back to the latest allowable month based on max', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: '', name: 'test', max: '1999-01-01' },
+ })
+
+ // when
+ await user.click(screen.getByLabelText(data.labelText))
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.findByText(data.date4Heading)).toBeVisible()
+ })
+ })
+
+ it('does not autoscroll forward based on max', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: '', name: 'test', max: '2111-01-01' },
+ })
+
+ // when
+ await user.click(screen.getByLabelText(data.labelText))
+
+ // then
+ await expect(async () => {
+ await screen.findByText(data.date3Heading)
+ }).rejects.toThrow(/Unable to find an element with the text/)
+ })
+
+ it('does not autoscroll backward if given a value', async () => {
+ // given
+ render(EDatePicker, {
+ props: { value: DATE1_ISO, name: 'test', max: '1999-01-01' },
+ })
+
+ // when
+ await user.click(screen.getByLabelText(data.labelText))
+
+ // then
+ await expect(async () => {
+ await screen.findByText(data.date4Heading)
+ }).rejects.toThrow(/Unable to find an element with the text/)
})
})
})
diff --git a/frontend/src/components/form/base/__tests__/ETimePicker.spec.js b/frontend/src/components/form/base/__tests__/ETimePicker.spec.js
index ab86e06808..e763279a50 100644
--- a/frontend/src/components/form/base/__tests__/ETimePicker.spec.js
+++ b/frontend/src/components/form/base/__tests__/ETimePicker.spec.js
@@ -1,184 +1,302 @@
-import Vue from 'vue'
-import Vuetify from 'vuetify'
-
-import i18n from '@/plugins/i18n'
-import formBaseComponents from '@/plugins/formBaseComponents'
-import dayjs from '@/plugins/dayjs'
-
-import { mount as mountComponent } from '@vue/test-utils'
+import { screen, waitFor } from '@testing-library/vue'
+import { render, setTestLocale, snapshotOf } from '@/test/renderWithVuetify.js'
+import user from '@testing-library/user-event'
import ETimePicker from '../ETimePicker'
-import { waitForDebounce } from '@/test/util'
-import flushPromises from 'flush-promises'
-
-Vue.use(Vuetify)
-Vue.use(formBaseComponents)
-Vue.use(dayjs)
describe('An ETimePicker', () => {
- let vuetify
-
- const TIME_1 = '2037-07-18T09:52:00+00:00'
- const TIME_1_HHMM = '09:52'
- const TIME_2 = '1989-10-27T18:33:00+00:00'
- const TIME_3 = '1989-10-27T19:15:00+00:00'
- const INVALID_TIME_1 = 'some time'
- const INVALID_TIME_2 = '1989-10-27T46:89:00+00:00'
-
- const localeData = [
- [
- 'de',
- {
- time_1: '09:52',
- time_2: '18:33',
- time_3: '19:15',
- },
- ],
- [
- 'en',
- {
- time_1: '9:52 AM',
- time_2: '6:33 PM',
- time_3: '7:15 PM',
- },
- ],
- ]
-
- const mount = (options) => mountComponent(ETimePicker, { vuetify, i18n, ...options })
-
- describe.each(localeData)('in locale %s', (locale, data) => {
+ const TIME1_ISO = '2037-07-18T09:52:00+00:00'
+ const TIME1_HHMM = '09:52'
+ const TIME2_ISO = '2037-07-18T18:33:00+00:00'
+ const TIME3_ISO = '2037-07-18T00:52:00+00:00'
+
+ const localeData = {
+ de: {
+ time1: '09:52',
+ time2: '18:33',
+ time3: '00:52',
+ firstHour: '0',
+ labelText: 'Dialog öffnen um eine Zeit für test zu wählen',
+ closeButton: 'Schliessen',
+ timeInWrongLocale: '9:52 AM',
+ validationMessage: 'Ungültiges Format, bitte gib die Zeit im Format HH:MM ein',
+ },
+ en: {
+ time1: '9:52 AM',
+ time2: '6:33 PM',
+ time3: '12:52 AM',
+ firstHour: '12',
+ labelText: 'Open dialog to select a time for test',
+ closeButton: 'Close',
+ timeInWrongLocale: '09:52',
+ validationMessage:
+ 'Invalid format, please enter the time in the format HH:MM AM/PM',
+ },
+ }
+
+ describe.each(Object.entries(localeData))('in locale %s', (locale, data) => {
beforeEach(() => {
- i18n.locale = locale
- Vue.dayjs.locale(locale)
- vuetify = new Vuetify()
+ setTestLocale(locale)
})
- test('renders', async () => {
- const wrapper = mount({
- propsData: {
- value: TIME_1,
+ it('renders the component', async () => {
+ // given
+
+ // when
+ render(ETimePicker, {
+ props: {
+ value: TIME1_ISO,
+ name: 'test',
},
})
- await flushPromises()
- expect(wrapper.find('input[type=text]').element.value).toBe(data.time_1)
+
+ // then
+ await screen.findByDisplayValue(data.time1)
+ screen.getByLabelText(data.labelText)
})
- test('looks like a time picker', async () => {
- const wrapper = mountComponent(
- {
- data: () => ({ time: TIME_1 }),
- template: '
',
- components: { 'e-time-picker': ETimePicker },
- },
- {
- vuetify,
- attachTo: document.body,
- i18n,
- }
- )
- await waitForDebounce()
- expect(wrapper).toMatchSnapshot('pickerclosed')
- await wrapper.find('button').trigger('click')
- expect(wrapper).toMatchSnapshot('pickeropen')
- wrapper.destroy()
+ it('looks like a time picker', async () => {
+ // given
+
+ // when
+ const { container } = render(ETimePicker, {
+ props: { value: TIME1_ISO, name: 'test' },
+ })
+
+ // then
+ expect(snapshotOf(container)).toMatchSnapshot('pickerclosed')
+
+ // when
+ await user.click(screen.getByLabelText(data.labelText))
+
+ // then
+ await screen.findByText(data.closeButton)
+ expect(snapshotOf(container)).toMatchSnapshot('pickeropen')
})
- test('allows a different valueFormat', async () => {
- const wrapper = mount({
- propsData: {
- value: TIME_1_HHMM,
+ it('allows setting a different valueFormat', async () => {
+ // given
+
+ // when
+ render(ETimePicker, {
+ props: {
+ value: TIME1_HHMM,
+ name: 'test',
valueFormat: 'HH:mm',
},
})
- await flushPromises()
- expect(wrapper.find('input[type=text]').element.value).toBe(data.time_1)
+
+ // then
+ await screen.findByDisplayValue(data.time1)
+ screen.getByLabelText(data.labelText)
})
- test('updates v-model when the value changes', async () => {
- const wrapper = mountComponent(
- {
- data: () => ({ time: TIME_2 }),
- template: '
',
- components: { 'e-time-picker': ETimePicker },
- },
- {
- vuetify,
- i18n,
- }
- )
- expect(wrapper.vm.time).toBe(TIME_2)
- const inputSpy = jest.fn()
- wrapper.findComponent(ETimePicker).vm.$on('input', (event) => inputSpy(event))
- const input = wrapper.find('input[type=text]')
- await input.setValue(data.time_3)
- await waitForDebounce()
- expect(inputSpy).toBeCalledTimes(1)
- expect(inputSpy).toBeCalledWith(TIME_3)
- expect(wrapper.vm.time).toBe(TIME_3)
+ it('opens the picker when the text field is clicked', async () => {
+ // given
+ render(ETimePicker, {
+ props: { value: TIME1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.time1)
+
+ // when
+ await user.click(inputField)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.findByText(data.closeButton)).toBeVisible()
+ })
})
- test('validates the input', async () => {
- const wrapper = mount({
- propsData: {
- value: TIME_1,
- },
+ it('closes the picker when clicking the close button', async () => {
+ // given
+ render(ETimePicker, {
+ props: { value: TIME1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.time1)
+ user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText(data.closeButton)).toBeVisible()
+ })
+ const closeButton = screen.getByText(data.closeButton)
+
+ // when
+ await user.click(closeButton)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.queryByText(data.closeButton)).not.toBeVisible()
})
- const input = wrapper.find('input[type=text]')
- await input.setValue(INVALID_TIME_1)
- await waitForDebounce()
- expect(wrapper.text()).toContain('invalid format')
- await input.setValue(INVALID_TIME_2)
- await waitForDebounce()
- expect(wrapper.text()).toContain('invalid format')
})
- test('works with invalid initialization', async () => {
- const wrapper = mount({
- propsData: {
- value: 'abc',
- },
+ it('closes the picker when clicking outside', async () => {
+ // given
+ render(ETimePicker, {
+ props: { value: TIME1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.time1)
+ user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText(data.closeButton)).toBeVisible()
+ })
+
+ // when
+ await user.click(document.body)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.queryByText(data.closeButton)).not.toBeVisible()
})
- await waitForDebounce()
- expect(wrapper.find('input[type=text]').element.value).toBe('Invalid Date')
- expect(wrapper.text()).toContain('invalid format')
- const input = wrapper.find('input[type=text]')
- await input.setValue(data.time_1)
- await waitForDebounce()
- expect(wrapper.text()).not.toContain('invalid format')
})
- test('updates its value when a time is picked', async () => {
- const wrapper = mountComponent(
- {
- data: () => ({ time: TIME_2 }),
- template: '
',
- components: { 'e-time-picker': ETimePicker },
+ it('closes the picker when pressing escape', async () => {
+ // given
+ render(ETimePicker, {
+ props: { value: TIME1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.time1)
+ user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText(data.closeButton)).toBeVisible()
+ })
+
+ // when
+ await user.click(document.body)
+
+ // then
+ await waitFor(async () => {
+ expect(await screen.queryByText(data.closeButton)).not.toBeVisible()
+ })
+ })
+
+ it('does not close the picker when selecting a time', async () => {
+ // given
+ render(ETimePicker, {
+ props: { value: TIME1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.time1)
+ user.click(inputField)
+ await waitFor(async () => {
+ expect(await screen.findByText(data.closeButton)).toBeVisible()
+ })
+
+ // when
+ // Click the 0th hour
+ await user.click(await screen.findByText(data.firstHour))
+ // Click the 45th minute
+ await user.click(await screen.findByText('45'))
+
+ // then
+ // close button should stay visible
+ await expect(
+ waitFor(() => {
+ expect(screen.queryByText(data.closeButton)).not.toBeVisible()
+ })
+ ).rejects.toThrow(/Received element is visible/)
+ })
+
+ it('updates v-model when the input field is changed', async () => {
+ // given
+ const { emitted } = render(ETimePicker, {
+ props: { value: TIME1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.time1)
+
+ // when
+ await user.clear(inputField)
+ await user.keyboard(data.time2)
+
+ // then
+ await waitFor(async () => {
+ const events = emitted().input
+ // some input events were fired
+ expect(events.length).toBeGreaterThan(0)
+ // the last one included the parsed version of our entered time
+ expect(events[events.length - 1]).toEqual([TIME2_ISO])
+ })
+ // Our entered time should be visible...
+ screen.getByDisplayValue(data.time2)
+ // ...and stay visible
+ await expect(
+ waitFor(() => {
+ expect(screen.getByDisplayValue(data.time2)).not.toBeVisible()
+ })
+ ).rejects.toThrow(/Received element is visible/)
+ })
+
+ it('updates v-model when a new time is selected in the picker', async () => {
+ // given
+ const { emitted } = render(ETimePicker, {
+ props: { value: TIME1_ISO, name: 'test' },
+ })
+ await screen.findByDisplayValue(data.time1)
+
+ // when
+ // click the button to open the picker
+ await user.click(screen.getByLabelText(data.labelText))
+ // Click the 0th hour. We can only click this one, because
+ // testing library is missing the vuetify styles, and all the
+ // number elements overlap
+ await user.click(await screen.findByText(data.firstHour))
+ // click the close button
+ await user.click(screen.getByText(data.closeButton))
+
+ // then
+ await waitFor(() => {
+ expect(screen.queryByText(data.closeButton)).not.toBeVisible()
+ })
+ await waitFor(async () => {
+ const events = emitted().input
+ // some input events were fired
+ expect(events.length).toBeGreaterThan(0)
+ // the last one included the parsed version of our entered time
+ expect(events[events.length - 1]).toEqual([TIME3_ISO])
+ })
+ // Our entered time should be visible...
+ screen.getByDisplayValue(data.time3)
+ // ...and stay visible
+ await expect(
+ waitFor(() => {
+ expect(screen.getByDisplayValue(data.time3)).not.toBeVisible()
+ })
+ ).rejects.toThrow(/Received element is visible/)
+ })
+
+ it('validates the input', async () => {
+ // given
+ render(ETimePicker, {
+ props: { value: TIME1_ISO, name: 'test' },
+ })
+ const inputField = await screen.findByDisplayValue(data.time1)
+
+ // when
+ await user.clear(inputField)
+ await user.keyboard(data.timeInWrongLocale)
+
+ // then
+ await screen.findByText(data.validationMessage)
+ })
+
+ it('works with invalid initialization', async () => {
+ // given
+
+ // when
+ render(ETimePicker, {
+ props: {
+ value: 'abc',
+ name: 'test',
},
- {
- vuetify,
- attachTo: document.body,
- i18n,
- }
- )
- await waitForDebounce()
- // open the time picker
- const openPicker = wrapper.find('button')
- await openPicker.trigger('click')
- // select hour
- const clockHour = wrapper.findComponent({ name: 'v-time-picker-clock' })
- clockHour.vm.update(19)
- clockHour.vm.valueOnMouseUp = 19
- await clockHour.trigger('mouseup')
- // select minute
- const clockMinute = wrapper.findComponent({ name: 'v-time-picker-clock' })
- clockMinute.vm.update(15)
- clockMinute.vm.valueOnMouseUp = 15
- await clockMinute.trigger('mouseup')
- // click the save button
- const closeButton = wrapper.find('[data-testid="action-ok"]')
- await closeButton.trigger('click')
- await waitForDebounce()
- expect(wrapper.find('input[type=text]').element.value).toBe(data.time_3)
- wrapper.destroy()
+ })
+
+ // then
+ const inputField = await screen.findByDisplayValue('Invalid Date')
+
+ // when
+ await user.clear(inputField)
+ await user.keyboard(data.time2)
+
+ // then
+ waitFor(() => {
+ expect(screen.queryByText('Invalid Date')).not.toBeInTheDocument()
+ })
})
})
})
diff --git a/frontend/src/components/form/base/__tests__/__snapshots__/EColorPicker.spec.js.snap b/frontend/src/components/form/base/__tests__/__snapshots__/EColorPicker.spec.js.snap
index 603bb9ca90..c42eb7045a 100644
--- a/frontend/src/components/form/base/__tests__/__snapshots__/EColorPicker.spec.js.snap
+++ b/frontend/src/components/form/base/__tests__/__snapshots__/EColorPicker.spec.js.snap
@@ -1,28 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`An EColorPicker looks like a color picker: pickerclosed 1`] = `
-