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 @@ 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)" > -