From 7b4d46eccea969218efabd338972a1cb54f05d24 Mon Sep 17 00:00:00 2001 From: Abhishek-Punhani Date: Tue, 19 May 2026 22:38:22 +0530 Subject: [PATCH 01/11] feat(AssessmentEditor): enhance question card display Signed-off-by: Abhishek-Punhani --- .../AssessmentEditor/AssessmentEditor.spec.js | 39 ++- .../AssessmentEditor/AssessmentEditor.vue | 262 +++++++++------ .../AssessmentItemEditor.spec.js | 20 +- .../AssessmentItemEditor.vue | 90 ++++-- .../AssessmentItemPreview.spec.js | 7 + .../AssessmentItemPreview.vue | 303 +++++++++++------- .../channelEdit/components/edit/EditView.vue | 10 + 7 files changed, 472 insertions(+), 259 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.spec.js index 3f2b2f42d3..9afd337ef2 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.spec.js @@ -94,7 +94,7 @@ const clickClose = async assessmentItemWrapper => { const clickEdit = async assessmentItemWrapper => { await assessmentItemWrapper - .findComponent(`[data-test="toolbarIcon-${AssessmentItemToolbarActions.EDIT_ITEM}"]`) + .findComponent(`[data-test="toolbarMenuItem-${AssessmentItemToolbarActions.EDIT_ITEM}"]`) .trigger('click'); }; @@ -210,6 +210,15 @@ describe('AssessmentEditor', () => { expect(wrapper.findComponent('[data-test="showAnswersCheckbox"]').exists()).toBe(true); }); + it("wraps 'Show answers' checkbox in a page container", () => { + expect(wrapper.find('.show-answers-container').exists()).toBe(true); + }); + + it('renders question card headers', () => { + expect(wrapper.html()).toContain('Question 1 of 4 — Numeric input'); + expect(wrapper.html()).toContain('Question 2 of 4 — Single choice'); + }); + it("doesn't render answers preview by default", () => { const items = getItems(wrapper); @@ -233,32 +242,36 @@ describe('AssessmentEditor', () => { it('opens an item on item click', async () => { const items = getItems(wrapper); await items.at(1).trigger('click'); + const updatedItems = getItems(wrapper); - expect(isItemOpen(items.at(0))).toBe(false); - expect(isItemOpen(items.at(1))).toBe(true); - expect(isItemOpen(items.at(2))).toBe(false); - expect(isItemOpen(items.at(3))).toBe(false); + expect(isItemOpen(updatedItems.at(0))).toBe(false); + expect(isItemOpen(updatedItems.at(1))).toBe(true); + expect(isItemOpen(updatedItems.at(2))).toBe(false); + expect(isItemOpen(updatedItems.at(3))).toBe(false); }); - it('opens an item on toolbar edit icon click', async () => { + it('opens an item on toolbar edit menu click', async () => { const items = getItems(wrapper); await clickEdit(items.at(1)); + const updatedItems = getItems(wrapper); - expect(isItemOpen(items.at(0))).toBe(false); - expect(isItemOpen(items.at(1))).toBe(true); - expect(isItemOpen(items.at(2))).toBe(false); - expect(isItemOpen(items.at(3))).toBe(false); + expect(isItemOpen(updatedItems.at(0))).toBe(false); + expect(isItemOpen(updatedItems.at(1))).toBe(true); + expect(isItemOpen(updatedItems.at(2))).toBe(false); + expect(isItemOpen(updatedItems.at(3))).toBe(false); }); it('closes an item on close button click', async () => { // open an item at first const items = getItems(wrapper); await items.at(1).trigger('click'); - expect(isItemOpen(items.at(1))).toBe(true); + let updatedItems = getItems(wrapper); + expect(isItemOpen(updatedItems.at(1))).toBe(true); // now close it - await clickClose(items.at(1)); - expect(isItemOpen(items.at(1))).toBe(false); + await clickClose(updatedItems.at(1)); + updatedItems = getItems(wrapper); + expect(isItemOpen(updatedItems.at(1))).toBe(false); }); describe('on "Delete" click', () => { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue index c1bd5257e0..9bfc38f433 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue @@ -2,72 +2,43 @@ @@ -204,11 +215,6 @@ return { activeItem: null, displayAnswersPreview: false, - itemToolbarMenuActions: [ - AssessmentItemToolbarActions.ADD_ITEM_ABOVE, - AssessmentItemToolbarActions.ADD_ITEM_BELOW, - AssessmentItemToolbarActions.DELETE_ITEM, - ], }; }, computed: { @@ -247,6 +253,12 @@ itemIdx(item) { return this.sortedItems.findIndex(i => areItemsEqual(i, item)); }, + questionNumberLabel(idx) { + return this.$tr('questionNumberLabel', { + number: idx + 1, + total: this.sortedItems.length, + }); + }, openItem(item) { if (!this.isPerseusItem(item)) { this.closeActiveItem(); @@ -293,14 +305,21 @@ return classes; }, - itemToolbarIconActions(item) { - const actions = [ + itemToolbarIconActions() { + return [ [AssessmentItemToolbarActions.MOVE_ITEM_UP, { collapse: true }], [AssessmentItemToolbarActions.MOVE_ITEM_DOWN, { collapse: true }], ]; + }, + itemToolbarMenuActions(item) { + const actions = [ + AssessmentItemToolbarActions.ADD_ITEM_ABOVE, + AssessmentItemToolbarActions.ADD_ITEM_BELOW, + AssessmentItemToolbarActions.DELETE_ITEM, + ]; - if (!this.isItemActive(item)) { - actions.unshift([AssessmentItemToolbarActions.EDIT_ITEM, { collapse: false }]); + if (!this.isItemActive(item) && !this.isPerseusItem(item)) { + actions.unshift(AssessmentItemToolbarActions.EDIT_ITEM); } return actions; @@ -459,6 +478,7 @@ closeBtnLabel: 'Close', newQuestionBtnLabel: 'New question', showAnswers: 'Show answers', + questionNumberLabel: 'Question {number} of {total}', }, }; @@ -467,21 +487,53 @@ diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.spec.js index 157e6a4fc6..bd7a4ac282 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.spec.js @@ -38,16 +38,15 @@ const updateQuestion = async (wrapper, newQuestionText) => { }; const selectKind = async (wrapper, kind) => { - const input = wrapper.findComponent('[data-test="kindSelect"]'); - await input.setValue(kind); - await input.trigger('input', kind); + wrapper.vm.onKindUpdate(kind); + await wrapper.vm.$nextTick(); }; describe('AssessmentItemEditor', () => { let wrapper; it('smoke test', () => { - const wrapper = shallowMount(AssessmentItemEditor); + const wrapper = shallowMount(AssessmentItemEditor, { store }); expect(wrapper.exists()).toBe(true); }); @@ -73,6 +72,19 @@ describe('AssessmentItemEditor', () => { expect(wrapper.findComponent({ name: 'HintsEditor' }).exists()).toBe(true); }); + it('renders the type label and hints divider', () => { + wrapper = mount(AssessmentItemEditor, { + store, + propsData: { + item: ITEM, + }, + listeners, + }); + + expect(wrapper.find('.field-label').text()).toBe('Type'); + expect(wrapper.find('.hints-divider').exists()).toBe(true); + }); + describe('on question text update', () => { beforeEach(async () => { wrapper = mount(AssessmentItemEditor, { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue index 3bf4d09799..51d66c2e92 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue @@ -2,25 +2,23 @@
- - - +
+ {{ $tr('typeLabel') }} +
+ +
@@ -41,6 +39,7 @@ v-if="isQuestionOpen" v-model="question" mode="edit" + class="question-editor" :autofocus="shouldAutofocusQuestion" :imageProcessor="EditorImageProcessor" @update="onQuestionUpdate" @@ -104,8 +103,9 @@ @close="closeAnswer" /> + + option.value === this.kind) || + this.kindSelectItems[0] + ); + }, + set(option) { + const newKind = option && option.value ? option.value : option; + if (!newKind) { + return; + } + this.onKindUpdate(newKind); + }, + }, answers() { if (!this.item || !this.item.answers) { return []; @@ -344,7 +357,7 @@ answers: newAnswers, }); }, - // question type VSelect needs to be rerended when confirmation dialog + // question type KSelect needs to be rerendered when confirmation dialog // cancelled to display a correct, previous, value that has changed // in the select but has not been changed in data storage actually // because of cancel action @@ -460,6 +473,7 @@ }, }, $trs: { + typeLabel: 'Type', questionTypeLabel: 'Response type', questionLabel: 'Question', dialogTitle: 'Changing question type', @@ -480,6 +494,32 @@ diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue index 7a6ec7a506..6049d07c17 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.vue @@ -7,7 +7,10 @@ lg5 class="kind-select-container" > -
+
{{ $tr('typeLabel') }}
@@ -268,7 +273,7 @@ ); }, set(option) { - const newKind = option && option.value ? option.value : option; + const newKind = option?.value; if (!newKind) { return; } @@ -500,30 +505,22 @@ .field-label { margin-bottom: 8px; - font-size: 16px; - font-weight: 700; + font-size: 14px; + font-weight: 600; } .kind-select { max-width: 440px; } - .question-editor { - min-height: 112px; - } - - ::v-deep .question-editor.editor-container { - min-height: 112px; - } - .hints-divider { - // The -28px matches the 28px horizontal padding of .question-card-body + max-width: none !important; + // The -20px matches the 20px horizontal padding of .question-card-body // in AssessmentEditor.vue. If that padding changes, update this value too. - margin: 16px -28px 0; + margin: 16px -20px 0; } .question-text { - border: 1px solid var(--v-grey-lighten4); transition: 0.7s; &:hover { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemPreview/AssessmentItemPreview.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemPreview/AssessmentItemPreview.vue index 885813b991..9198eb2911 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemPreview/AssessmentItemPreview.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemPreview/AssessmentItemPreview.vue @@ -8,8 +8,12 @@
-

+

{{ questionNumberAndTypeLabel }}

@@ -322,13 +326,12 @@ justify-content: space-between; min-height: 76px; padding: 16px 28px; - border-bottom: 1px solid var(--v-grey-lighten4); } .question-card-title { margin: 0; - font-size: 16px; - font-weight: 700; + font-size: 14px; + font-weight: 600; } .question-card-body { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditView.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditView.vue index f875739535..398e2c2031 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditView.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/EditView.vue @@ -114,7 +114,10 @@ - + diff --git a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue index 70a2b6b77c..e97352648d 100644 --- a/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue +++ b/contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue @@ -4,6 +4,7 @@ ref="editorContainer" class="editor-container" :class="{ 'view-mode': editorMode === 'view' }" + :style="minHeight && editorMode !== 'view' ? { minHeight } : {}" :tabindex="tabindex" role="textbox" :aria-label="editorMode === 'edit' ? TipTapEditorLabel$() : TipTapViewerLabel$()" @@ -303,6 +304,10 @@ type: Object, default: () => ({}), }, + minHeight: { + type: String, + default: null, + }, }, emits: ['update', 'minimize', 'open-editor'], }); From 18ec00df63ee032e8b2f8a73ed4bc08605657965 Mon Sep 17 00:00:00 2001 From: Abhishek-Punhani Date: Fri, 22 May 2026 00:43:35 +0530 Subject: [PATCH 05/11] refactor: simplify assessment item preview by removing card rendering and centralizing header logic in AssessmentEditor Signed-off-by: Abhishek-Punhani --- .../AssessmentEditor/AssessmentEditor.vue | 100 +++--- .../AssessmentItemPreview.vue | 312 +++++++----------- 2 files changed, 165 insertions(+), 247 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue index 85107491d4..b428ddc8a2 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue @@ -26,20 +26,32 @@ tag="div" >