diff --git a/frontend/src/components/form/api/ApiColorField.vue b/frontend/src/components/form/api/ApiColorField.vue
new file mode 100644
index 0000000000..99adae9d08
--- /dev/null
+++ b/frontend/src/components/form/api/ApiColorField.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/form/api/__tests__/ApiColorField.spec.js b/frontend/src/components/form/api/__tests__/ApiColorField.spec.js
new file mode 100644
index 0000000000..e95f15325b
--- /dev/null
+++ b/frontend/src/components/form/api/__tests__/ApiColorField.spec.js
@@ -0,0 +1,92 @@
+import ApiColorField from '../ApiColorField.vue'
+import { fireEvent, 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 { regex } from 'vee-validate/dist/rules'
+import { ColorSpace, sRGB } from 'colorjs.io/fn'
+
+extend('regex', regex)
+
+ColorSpace.register(sRGB)
+describe('An ApiColorField', () => {
+ let apiMock
+
+ const FIELD_PATH = 'test-field/123'
+ const FIELD_LABEL = 'Test field'
+ const COLOR_1 = '#FF0000'
+ const COLOR_2 = '#FAFFAF'
+
+ beforeEach(() => {
+ apiMock = ApiMock.create()
+ })
+
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
+
+ test('triggers api.patch and status update if input changes', async () => {
+ // given
+ apiMock.get().thenReturn(ApiMock.success(COLOR_1).forPath(FIELD_PATH))
+ apiMock.patch().thenReturn(ApiMock.success(COLOR_2))
+ render(ApiColorField, {
+ props: {
+ autoSave: false,
+ path: FIELD_PATH,
+ uri: 'test-field/123',
+ label: FIELD_LABEL,
+ required: true,
+ },
+ mocks: {
+ api: apiMock.getMocks(),
+ },
+ })
+
+ // when
+ const inputField = await screen.findByLabelText(FIELD_LABEL)
+ inputField.value = COLOR_2
+ await fireEvent.input(inputField)
+ // click the button to open the picker
+ // click the save button
+ await waitFor(async () => {
+ 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 () => {
+ // given
+ apiMock.get().thenReturn(ApiMock.networkError().forPath(FIELD_PATH))
+ render(ApiColorField, {
+ props: {
+ autoSave: false,
+ path: FIELD_PATH,
+ 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).forPath(FIELD_PATH))
+
+ // when
+ await user.click(retryButton)
+
+ // then
+ await waitFor(async () => {
+ expect((await screen.findByLabelText(FIELD_LABEL)).value).toBe(COLOR_1)
+ })
+ })
+})
diff --git a/frontend/src/components/form/api/__tests__/ApiColorPicker.spec.js b/frontend/src/components/form/api/__tests__/ApiColorPicker.spec.js
index 77e958f948..52d430a272 100644
--- a/frontend/src/components/form/api/__tests__/ApiColorPicker.spec.js
+++ b/frontend/src/components/form/api/__tests__/ApiColorPicker.spec.js
@@ -5,9 +5,11 @@ import user from '@testing-library/user-event'
import { ApiMock } from '@/components/form/api/__tests__/ApiMock'
import { extend } from 'vee-validate'
import { regex } from 'vee-validate/dist/rules'
+import { ColorSpace, sRGB } from 'colorjs.io/fn'
extend('regex', regex)
+ColorSpace.register(sRGB)
describe('An ApiColorPicker', () => {
let apiMock
@@ -49,12 +51,16 @@ describe('An ApiColorPicker', () => {
const canvas = container.querySelector('canvas')
await user.click(canvas, { clientX: 10, clientY: 10 })
// click the save button
- await user.click(screen.getByLabelText('Speichern'))
+ await waitFor(async () => {
+ await user.click(screen.getByLabelText('Speichern'))
+ })
// then
- const inputField = await screen.findByLabelText(FIELD_LABEL)
- expect(inputField.value).toBe(COLOR_2)
- expect(apiMock.getMocks().patch).toBeCalledTimes(1)
+ 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 () => {
diff --git a/frontend/src/components/form/base/ColorPicker/ColorSwatch.vue b/frontend/src/components/form/base/ColorPicker/ColorSwatch.vue
index 7e53e4adb8..6959e6245d 100644
--- a/frontend/src/components/form/base/ColorPicker/ColorSwatch.vue
+++ b/frontend/src/components/form/base/ColorPicker/ColorSwatch.vue
@@ -1,12 +1,14 @@
@@ -17,7 +19,7 @@ import { contrastColor } from '@/common/helpers/colors.js'
export default {
name: 'ColorSwatch',
props: {
- color: { type: String, required: true },
+ color: { type: String, default: null },
},
computed: {
contrast() {
@@ -28,6 +30,19 @@ export default {
}
diff --git a/frontend/src/components/form/base/EColorField.vue b/frontend/src/components/form/base/EColorField.vue
new file mode 100644
index 0000000000..143776a19e
--- /dev/null
+++ b/frontend/src/components/form/base/EColorField.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/form/base/EColorPicker.vue b/frontend/src/components/form/base/EColorPicker.vue
index 861ffd0c69..bdb649070d 100644
--- a/frontend/src/components/form/base/EColorPicker.vue
+++ b/frontend/src/components/form/base/EColorPicker.vue
@@ -2,69 +2,131 @@
Displays a field as a color picker (can be used with v-model)
-->
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+