From 5d193823bd7b4fc6234dc2e7583cbca9f5ec25bc Mon Sep 17 00:00:00 2001 From: Himanshu Kapoor <hkapoor@gitlab.com> Date: Mon, 5 Feb 2024 15:45:05 +0800 Subject: [PATCH] Add rich text editor in vulnerabilities Allow using rich text editor when creating/editing vulnerabilities and solutions. Changelog: added --- .../new_vulnerability/section_name.vue | 41 +++++-------- .../new_vulnerability/section_solution.vue | 40 +++++-------- .../new_vulnerability/section_name_spec.js | 58 +++++++++++++------ .../section_solution_spec.js | 16 +++-- 4 files changed, 82 insertions(+), 73 deletions(-) diff --git a/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_name.vue b/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_name.vue index fc973c78036f0..f7effa5a30012 100644 --- a/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_name.vue +++ b/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_name.vue @@ -1,15 +1,14 @@ <script> -import { GlFormGroup, GlFormInput, GlFormTextarea } from '@gitlab/ui'; +import { GlFormGroup, GlFormInput } from '@gitlab/ui'; import { s__, __ } from '~/locale'; -import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import * as i18n from './i18n'; export default { components: { GlFormGroup, GlFormInput, - GlFormTextarea, - MarkdownField, + MarkdownEditor, }, inject: ['markdownDocsPath', 'markdownPreviewPath'], props: { @@ -24,6 +23,12 @@ export default { isSubmitting: false, vulnerabilityName: '', vulnerabilityDesc: '', + formFieldProps: { + id: 'form-vulnerability-desc', + name: 'form-vulnerability-desc', + 'aria-label': this.$options.i18n.vulnerabilityDesc.description, + placeholder: this.$options.i18n.vulnerabilityDesc.description, + }, }; }, methods: { @@ -49,7 +54,6 @@ export default { ), }, }, - restrictedToolBarItems: ['attach-file'], }; </script> <template> @@ -75,28 +79,15 @@ export default { label-for="form-vulnerability-desc" > <div class="gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base"> - <markdown-field - ref="markdownField" - :can-attach-file="false" - :add-spacing-classes="false" - :markdown-preview-path="markdownPreviewPath" + <markdown-editor + v-model="vulnerabilityDesc" + :disable-attachments="true" + :render-markdown-path="markdownPreviewPath" :markdown-docs-path="markdownDocsPath" - :textarea-value="vulnerabilityDesc" :is-submitting="isSubmitting" - :restricted-tool-bar-items="$options.restrictedToolBarItems" - > - <template #textarea> - <gl-form-textarea - id="form-vulnerability-desc" - v-model.trim="vulnerabilityDesc" - rows="8" - class="gl-shadow-none! gl-py-4! gl-h-auto!" - :aria-label="$options.i18n.vulnerabilityDesc.description" - :placeholder="$options.i18n.vulnerabilityDesc.description" - @input="emitChanges" - /> - </template> - </markdown-field> + :form-field-props="formFieldProps" + @input="emitChanges" + /> </div> </gl-form-group> </section> diff --git a/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_solution.vue b/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_solution.vue index 015b3f46744f7..bb866787bc814 100644 --- a/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_solution.vue +++ b/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_solution.vue @@ -1,12 +1,10 @@ <script> -import { GlFormTextarea } from '@gitlab/ui'; import { s__, __ } from '~/locale'; -import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; export default { components: { - GlFormTextarea, - MarkdownField, + MarkdownEditor, }, inject: ['markdownDocsPath', 'markdownPreviewPath'], props: { @@ -19,6 +17,12 @@ export default { data() { return { solution: '', + formFieldProps: { + id: 'form-solution', + name: 'form-solution', + 'aria-label': this.$options.i18n.description, + placeholder: this.$options.i18n.description, + }, }; }, methods: { @@ -43,29 +47,15 @@ export default { </h3> </header> <div class="gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base"> - <markdown-field - ref="markdownField" - class="gl-px-4" - :can-attach-file="false" - :add-spacing-classes="false" - :markdown-preview-path="markdownPreviewPath" + <markdown-editor + v-model="solution" + :disable-attachments="true" + :render-markdown-path="markdownPreviewPath" :markdown-docs-path="markdownDocsPath" - :restricted-tool-bar-items="$options.restrictedToolBarItems" - :textarea-value="solution" :is-submitting="isSubmitting" - > - <template #textarea> - <gl-form-textarea - id="form-solution" - v-model="solution" - rows="8" - class="gl-shadow-none! gl-px-0! gl-py-4! gl-h-auto!" - :aria-label="$options.i18n.description" - :placeholder="$options.i18n.description" - @change="emitChanges" - /> - </template> - </markdown-field> + :form-field-props="formFieldProps" + @input="emitChanges" + /> </div> </section> </template> diff --git a/ee/spec/frontend/vulnerabilities/new_vulnerability/section_name_spec.js b/ee/spec/frontend/vulnerabilities/new_vulnerability/section_name_spec.js index 2bd8ee2da38de..be7f43cebf5ad 100644 --- a/ee/spec/frontend/vulnerabilities/new_vulnerability/section_name_spec.js +++ b/ee/spec/frontend/vulnerabilities/new_vulnerability/section_name_spec.js @@ -1,9 +1,11 @@ import { nextTick } from 'vue'; -import { GlFormGroup, GlFormInput, GlFormTextarea } from '@gitlab/ui'; +import { GlFormGroup, GlFormInput } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import SectionName from 'ee/vulnerabilities/components/new_vulnerability/section_name.vue'; import { ERROR_NAME } from 'ee/vulnerabilities/components/new_vulnerability/i18n'; +import { stubComponent } from 'helpers/stub_component'; describe('New vulnerability - Section Name', () => { const markdownDocsPath = '/path/to/markdown/docs'; @@ -13,26 +15,24 @@ describe('New vulnerability - Section Name', () => { const findFormGroup = (index) => wrapper.findAllComponents(GlFormGroup).at(index); - const createWrapper = () => { - return mountExtended(SectionName, { + const createWrapper = ({ stubs = {} } = {}) => { + wrapper = mountExtended(SectionName, { provide: { markdownDocsPath, markdownPreviewPath, }, + stubs, }); }; - beforeEach(() => { - wrapper = createWrapper(); - }); - it('creates markdown editor with correct props', () => { + createWrapper(); + expect(wrapper.findComponent(MarkdownField).props()).toMatchObject({ markdownDocsPath, markdownPreviewPath, textareaValue: '', canAttachFile: false, - addSpacingClasses: false, isSubmitting: false, restrictedToolBarItems: ['attach-file'], }); @@ -43,6 +43,8 @@ describe('New vulnerability - Section Name', () => { ${'Name'} | ${'Vulnerability name or type. Ex: Cross-site scripting'} ${'Description'} | ${''} `('displays the input with the correct label: $labelText', ({ labelText, description }) => { + createWrapper(); + expect(wrapper.findByLabelText(labelText).exists()).toBe(true); if (description) { @@ -50,25 +52,41 @@ describe('New vulnerability - Section Name', () => { } }); - it.each` - field | component | fieldKey | fieldValue - ${'Name'} | ${GlFormInput} | ${'vulnerabilityName'} | ${'CVE 2021'} - ${'Description'} | ${GlFormTextarea} | ${'vulnerabilityDesc'} | ${'Password leak'} - `('emits the changes: $field', ({ component, fieldKey, fieldValue }) => { - wrapper.findComponent(component).setValue(fieldValue); - wrapper.findComponent(component).vm.$emit('change', fieldValue); + it('emits the changes: Name', () => { + const fieldValue = 'CVE 2021'; + createWrapper({ stubs: { MarkdownEditor: stubComponent(MarkdownEditor) } }); + + wrapper.findComponent(GlFormInput).setValue(fieldValue); + wrapper.findComponent(GlFormInput).vm.$emit('change', fieldValue); + expect(wrapper.emitted('change')[0][0]).toEqual({ - vulnerabilityName: '', + vulnerabilityName: fieldValue, vulnerabilityDesc: '', - [fieldKey]: fieldValue, + }); + }); + + it('emits the changes: Description', () => { + const fieldValue = 'Password leak'; + createWrapper(); + + wrapper.findComponent(MarkdownField).find('textarea').setValue(fieldValue); + wrapper.findComponent(MarkdownEditor).vm.$emit('input', fieldValue); + + expect(wrapper.emitted('change')[1][0]).toEqual({ + vulnerabilityName: '', + vulnerabilityDesc: fieldValue, }); }); it('does not display invalid state by default', () => { + createWrapper(); + expect(findFormGroup(0).attributes('aria-invalid')).toBeUndefined(); }); it('handles form validation', async () => { + createWrapper(); + wrapper.setProps({ validationState: { name: false, @@ -80,4 +98,10 @@ describe('New vulnerability - Section Name', () => { expect(findFormGroup(0).attributes('aria-invalid')).toBe('true'); expect(findFormGroup(0).text()).toContain(ERROR_NAME); }); + + it('allows switching to rich text editor', () => { + createWrapper(); + + expect(wrapper.text()).toContain('Switch to rich text editing'); + }); }); diff --git a/ee/spec/frontend/vulnerabilities/new_vulnerability/section_solution_spec.js b/ee/spec/frontend/vulnerabilities/new_vulnerability/section_solution_spec.js index 48e0706336a45..749fd4f6dfca6 100644 --- a/ee/spec/frontend/vulnerabilities/new_vulnerability/section_solution_spec.js +++ b/ee/spec/frontend/vulnerabilities/new_vulnerability/section_solution_spec.js @@ -1,6 +1,6 @@ -import { GlFormTextarea } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import SectionSolution from 'ee/vulnerabilities/components/new_vulnerability/section_solution.vue'; describe('New vulnerability - Section Solution', () => { @@ -28,7 +28,6 @@ describe('New vulnerability - Section Solution', () => { markdownPreviewPath, textareaValue: '', canAttachFile: false, - addSpacingClasses: false, isSubmitting: false, restrictedToolBarItems: ['attach-file'], }); @@ -36,9 +35,14 @@ describe('New vulnerability - Section Solution', () => { it('emits the changes for the markdown field', () => { const solution = 'The solution of the vulnerability'; - const component = wrapper.findComponent(GlFormTextarea); - component.vm.$emit('input', solution); - component.vm.$emit('change', solution); - expect(wrapper.emitted('change')[0][0]).toEqual({ solution }); + + wrapper.findComponent(MarkdownField).find('textarea').setValue(solution); + wrapper.findComponent(MarkdownEditor).vm.$emit('input', solution); + + expect(wrapper.emitted('change')[1][0]).toEqual({ solution }); + }); + + it('allows switching to rich text editor', () => { + expect(wrapper.text()).toContain('Switch to rich text editing'); }); }); -- GitLab