diff --git a/app/src/components/v-form/__snapshots__/form-field-raw-editor.test.ts.snap b/app/src/components/v-form/__snapshots__/form-field-raw-editor.test.ts.snap
new file mode 100644
index 0000000000..631c76dd30
--- /dev/null
+++ b/app/src/components/v-form/__snapshots__/form-field-raw-editor.test.ts.snap
@@ -0,0 +1,16 @@
+// Vitest Snapshot v1
+
+exports[`should render 1`] = `
+"
+
+ edit_raw_value
+
+
+
+
+ cancel
+ done
+
+
+"
+`;
diff --git a/app/src/components/v-form/form-field-raw-editor.test.ts b/app/src/components/v-form/form-field-raw-editor.test.ts
new file mode 100644
index 0000000000..56086cfba5
--- /dev/null
+++ b/app/src/components/v-form/form-field-raw-editor.test.ts
@@ -0,0 +1,76 @@
+import { it, test, expect } from 'vitest';
+import { mount } from '@vue/test-utils';
+import { createI18n } from 'vue-i18n';
+
+import formFieldRawEditor from './form-field-raw-editor.vue';
+import { GlobalMountOptions } from '@vue/test-utils/dist/types';
+
+const i18n = createI18n();
+
+const global: GlobalMountOptions = {
+ plugins: [i18n],
+};
+
+test('should render', () => {
+ expect(formFieldRawEditor).toBeTruthy();
+
+ const wrapper = mount(formFieldRawEditor, {
+ props: {
+ showModal: true,
+ field: 'object',
+ disabled: false,
+ currentValue: '["id","new_content"]',
+ },
+ global,
+ });
+ expect(wrapper.html()).toMatchSnapshot();
+});
+
+// test if there is a value
+test('submitting', async () => {
+ expect(formFieldRawEditor).toBeTruthy();
+ const wrapper = mount(formFieldRawEditor, {
+ props: {
+ showModal: true,
+ field: 'string',
+ disabled: false,
+ currentValue: 'things',
+ },
+ global,
+ });
+ const button = wrapper.findAll('v-button').at(1);
+ await button!.trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.emitted().setRawValue.length).toBe(1);
+});
+
+it('should cancel with keydown', async () => {
+ const wrapper = mount(formFieldRawEditor, {
+ props: {
+ showModal: true,
+ field: 'object',
+ disabled: false,
+ currentValue: '["id","new_content"]',
+ },
+ global,
+ });
+ await wrapper.trigger('esc');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.emitted().cancel.length).toBe(1);
+});
+
+it('should cancel with the cancel button', async () => {
+ const wrapper = mount(formFieldRawEditor, {
+ props: {
+ showModal: true,
+ field: 'object',
+ disabled: false,
+ currentValue: '["id","new_content"]',
+ },
+ global,
+ });
+ const button = wrapper.findAll('v-button').at(0);
+ await button!.trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.emitted().cancel.length).toBe(1);
+});
diff --git a/app/src/components/v-form/form-field-raw-editor.vue b/app/src/components/v-form/form-field-raw-editor.vue
new file mode 100644
index 0000000000..a33e5d3e60
--- /dev/null
+++ b/app/src/components/v-form/form-field-raw-editor.vue
@@ -0,0 +1,99 @@
+
+
+
+ {{ disabled ? t('view_raw_value') : t('edit_raw_value') }}
+
+
+
+
+
+ {{ t('cancel') }}
+ {{ t('done') }}
+
+
+
+
+
+
diff --git a/app/src/components/v-form/form-field.vue b/app/src/components/v-form/form-field.vue
index e8e66164c1..36689cd27d 100644
--- a/app/src/components/v-form/form-field.vue
+++ b/app/src/components/v-form/form-field.vue
@@ -49,17 +49,14 @@
@set-field-value="$emit('setFieldValue', $event)"
/>
-
-
- {{ isDisabled ? t('view_raw_value') : t('edit_raw_value') }}
-
-
-
-
- {{ t('done') }}
-
-
-
+
@@ -74,7 +71,6 @@