diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditBooleanMapModal.vue b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditBooleanMapModal.vue index a8b621bd13..93f34bc587 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditBooleanMapModal.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditBooleanMapModal.vue @@ -95,7 +95,7 @@ }, data() { return { - updateDescendants: false, + updateDescendants: true, error: '', /** * selectedValues is an object with the following structure: diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditLanguageModal.vue b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditLanguageModal.vue index 6287b14dd5..381afcf60f 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditLanguageModal.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditLanguageModal.vue @@ -92,7 +92,7 @@ return { selectedLanguage: '', searchQuery: '', - updateDescendants: false, + updateDescendants: true, isMultipleNodeLanguages: false, changed: false, }; diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditTitleDescriptionModal.vue b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditTitleDescriptionModal.vue index 3acdde0ad2..15b271664e 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditTitleDescriptionModal.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/EditTitleDescriptionModal.vue @@ -98,6 +98,7 @@ id: nodeId, title: title.trim(), description: description.trim(), + checkComplete: true, }); /* eslint-disable-next-line kolibri/vue-no-undefined-string-uses */ this.$store.dispatch('showSnackbarSimple', commonStrings.$tr('changesSaved')); diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js index 2d4306846b..aa96754a67 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js @@ -275,10 +275,11 @@ describe('EditBooleanMapModal', () => { expect(wrapper.find('[data-test="update-descendants-checkbox"]').exists()).toBeFalsy(); }); - test('should call updateContentNode on success submit if the user does not check the update descendants checkbox', async () => { + test('should call updateContentNode on success submit if the user uncheck the update descendants checkbox', async () => { nodes['node1'].kind = ContentKindsNames.TOPIC; const wrapper = makeWrapper({ nodeIds: ['node1'], isDescendantsUpdatable: true }); + wrapper.find('[data-test="update-descendants-checkbox"]').element.click(); await wrapper.vm.handleSave(); expect(contentNodeActions.updateContentNode).toHaveBeenCalledWith(expect.anything(), { @@ -287,11 +288,10 @@ describe('EditBooleanMapModal', () => { }); }); - test('should call updateContentNodeDescendants on success submit if the user checks the descendants checkbox', async () => { + test('should call updateContentNodeDescendants on success submit if the user does not uncheck the update descendants checkbox', async () => { nodes['node1'].kind = ContentKindsNames.TOPIC; const wrapper = makeWrapper({ nodeIds: ['node1'], isDescendantsUpdatable: true }); - wrapper.find('[data-test="update-descendants-checkbox"]').element.click(); await wrapper.vm.handleSave(); expect(contentNodeActions.updateContentNodeDescendants).toHaveBeenCalledWith( diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditLanguageModal.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditLanguageModal.spec.js index c9716d55da..2c2cb791ff 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditLanguageModal.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditLanguageModal.spec.js @@ -220,8 +220,8 @@ describe('EditLanguageModal', () => { }); describe('topic nodes present', () => { - it('should display the checkbox to apply change to descendants if a topic is present', () => { - [wrapper] = makeWrapper(['test-en-topic', 'test-en-res']); + test('should display a selected checkbox to apply change to descendants if a topic is present', () => { + const [wrapper] = makeWrapper(['test-en-topic', 'test-en-res']); expect( wrapper.findComponent('[data-test="update-descendants-checkbox"]').exists(), @@ -236,30 +236,33 @@ describe('EditLanguageModal', () => { ).toBeFalsy(); }); - it('should call updateContentNode with the right language on success submit if the user does not check the checkbox', async () => { - [wrapper, mocks] = makeWrapper(['test-en-topic', 'test-en-res']); + test('should call updateContentNodeDescendants with the right language on success submit by default', async () => { + const [wrapper, mocks] = makeWrapper(['test-en-topic', 'test-en-res']); await chooseLanguage(wrapper, 'es'); await wrapper.vm.handleSave(); - await wrapper.vm.$nextTick(); - expect(mocks.updateContentNode).toHaveBeenCalledWith({ + expect(mocks.updateContentNodeDescendants).toHaveBeenCalledWith({ id: 'test-en-topic', language: 'es', }); }); - it('should call updateContentNodeDescendants with the right language on success submit if the user checks the checkbox', async () => { - [wrapper, mocks] = makeWrapper(['test-en-topic', 'test-en-res']); + test('should call updateContentNode with the right language on success submit if the user unchecks check the checkbox', async () => { + const [wrapper, mocks] = makeWrapper(['test-en-topic', 'test-en-res']); await chooseLanguage(wrapper, 'es'); - wrapper.findComponent('[data-test="update-descendants-checkbox"]').vm.$emit('change', true); + + // Uncheck the descendants checkbox + const descendantsCheckbox = wrapper.findComponent( + '[data-test="update-descendants-checkbox"]', + ); + descendantsCheckbox.vm.$emit('change', false); await wrapper.vm.$nextTick(); - expect(wrapper.vm.updateDescendants).toBe(true); + await wrapper.vm.handleSave(); - await wrapper.vm.$nextTick(); - expect(mocks.updateContentNodeDescendants).toHaveBeenCalledWith({ + expect(mocks.updateContentNode).toHaveBeenCalledWith({ id: 'test-en-topic', language: 'es', }); diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditTitleDescriptionModal.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditTitleDescriptionModal.spec.js index 464a8e9026..632f364fe1 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditTitleDescriptionModal.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditTitleDescriptionModal.spec.js @@ -33,9 +33,7 @@ describe('EditTitleDescriptionModal', () => { }, }, }), - propsData: { - nodeId, - }, + propsData: { nodeId }, }); updateContentNode = jest.spyOn(wrapper.vm, 'updateContentNode').mockImplementation(() => {}); @@ -70,7 +68,8 @@ describe('EditTitleDescriptionModal', () => { expect(updateContentNode).toHaveBeenCalledWith({ id: nodeId, title: newTitle, - description: newDescription, + description: newDescription ?? '', + checkComplete: true, }); }); @@ -80,11 +79,11 @@ describe('EditTitleDescriptionModal', () => { descriptionInput.vm.$emit('input', ''); modal.vm.$emit('submit'); - expect(updateContentNode).toHaveBeenCalledWith({ id: nodeId, title: newTitle, description: '', + checkComplete: true, }); }); diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/ResourcePanel.vue b/contentcuration/contentcuration/frontend/channelEdit/components/ResourcePanel.vue index 674bcfa160..e8616b3c1e 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/ResourcePanel.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/ResourcePanel.vue @@ -74,7 +74,8 @@ slider-color="primary" > @@ -86,7 +87,8 @@ /> diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/DetailsTabView.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/DetailsTabView.vue index 3002797c94..d7e6021ee9 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/DetailsTabView.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/DetailsTabView.vue @@ -499,6 +499,7 @@ } from 'shared/constants'; import { constantsTranslationMixin, metadataTranslationMixin } from 'shared/mixins'; import { crossComponentTranslator } from 'shared/i18n'; + import { LanguagesNames } from 'shared/leUtils/Languages'; function getValueFromResults(results) { if (results.length === 0) { @@ -715,7 +716,17 @@ }, }, role: generateGetterSetter('role_visibility'), - language: generateGetterSetter('language'), + language: { + get() { + const value = this.getValueFromNodes('language'); + return value === nonUniqueValue ? LanguagesNames.MUL : value; + }, + set(value) { + if (!(value === LanguagesNames.MUL && this.language === LanguagesNames.MUL)) { + this.update({ language: value }); + } + }, + }, accessibility: generateNestedNodesGetterSetter('accessibility_labels'), contentLevel: generateNestedNodesGetterSetterObject('grade_levels'), resourcesNeeded: generateNestedNodesGetterSetterObject('learner_needs'), diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/SearchResultsList.vue b/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/SearchResultsList.vue index 4f95c2cd99..2862ff2146 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/SearchResultsList.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/SearchResultsList.vue @@ -3,13 +3,22 @@
- - - - - - - + + + + +

@@ -105,8 +114,8 @@

- - + + diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/trash/TrashModal.vue b/contentcuration/contentcuration/frontend/channelEdit/views/trash/TrashModal.vue index 9052cf5720..271d12a8d0 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/views/trash/TrashModal.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/views/trash/TrashModal.vue @@ -281,19 +281,24 @@ 'moveContentNodes', 'loadContentNodes', 'loadAncestors', + 'removeContentNodes', ]), loadNodes() { this.loading = true; + this.more = null; + this.moreLoading = false; if (!this.trashId) { this.loading = false; return; } - this.loadChildren({ parent: this.trashId, ordering: '-modified' }).then( - childrenResponse => { - this.loading = false; - this.more = childrenResponse.more || null; - }, - ); + this.removeContentNodes({ parentId: this.trashId }).then(() => { + this.loadChildren({ parent: this.trashId, ordering: '-modified' }).then( + childrenResponse => { + this.loading = false; + this.more = childrenResponse.more || null; + }, + ); + }); }, moveNodes(target) { return this.moveContentNodes({ diff --git a/contentcuration/contentcuration/frontend/shared/data/__tests__/ContentNodeResource.spec.js b/contentcuration/contentcuration/frontend/shared/data/__tests__/ContentNodeResource.spec.js index 3f16719252..31bcb53bb4 100644 --- a/contentcuration/contentcuration/frontend/shared/data/__tests__/ContentNodeResource.spec.js +++ b/contentcuration/contentcuration/frontend/shared/data/__tests__/ContentNodeResource.spec.js @@ -574,8 +574,12 @@ describe('ContentNode methods', () => { it('should update the node with the payload', async () => { node.parent = parent.id; - await expect(ContentNode.tableMove({ node, parent, payload, change })).resolves.toBe(payload); - expect(table.update).toHaveBeenCalledWith(node.id, payload); + const result = await ContentNode.tableMove({ node, parent, payload, change }); + expect(result).toMatchObject({ ...payload, modified: expect.any(String) }); + expect(table.update).toHaveBeenCalledTimes(1); + const [updateId, updatePayload] = table.update.mock.calls[0]; + expect(updateId).toBe(node.id); + expect(updatePayload).toBe(result); expect(table.put).not.toBeCalled(); expect(table.update).not.toHaveBeenCalledWith(node.parent, { changed: true }); }); @@ -584,19 +588,23 @@ describe('ContentNode methods', () => { node.parent = parent.id; updated = false; const newPayload = { ...payload, root_id: parent.root_id }; - await expect(ContentNode.tableMove({ node, parent, payload, change })).resolves.toMatchObject( - newPayload, + const result = await ContentNode.tableMove({ node, parent, payload, change }); + expect(result).toMatchObject({ ...newPayload, modified: expect.any(String) }); + expect(table.update).toHaveBeenCalledWith( + node.id, + expect.objectContaining({ ...payload, modified: expect.any(String) }), ); - expect(table.update).toHaveBeenCalledWith(node.id, payload); - expect(table.put).toHaveBeenCalledWith(newPayload); + expect(table.put).toHaveBeenCalledWith(result); expect(table.update).not.toHaveBeenCalledWith(node.parent, { changed: true }); }); it('should mark the old parent as changed', async () => { - await expect(ContentNode.tableMove({ node, parent, payload, change })).resolves.toMatchObject( - payload, + const result = await ContentNode.tableMove({ node, parent, payload, change }); + expect(result).toMatchObject({ ...payload, modified: expect.any(String) }); + expect(table.update).toHaveBeenCalledWith( + node.id, + expect.objectContaining({ ...payload, modified: expect.any(String) }), ); - expect(table.update).toHaveBeenCalledWith(node.id, payload); expect(table.put).not.toBeCalled(); expect(table.update).toHaveBeenCalledWith(node.parent, { changed: true }); }); diff --git a/contentcuration/contentcuration/frontend/shared/data/resources.js b/contentcuration/contentcuration/frontend/shared/data/resources.js index 37c307d850..1ddc0cdbfe 100644 --- a/contentcuration/contentcuration/frontend/shared/data/resources.js +++ b/contentcuration/contentcuration/frontend/shared/data/resources.js @@ -1687,6 +1687,10 @@ export const ContentNode = new TreeResource({ async tableMove({ node, parent, payload }) { // Do direct table writes here rather than using add/update methods to avoid // creating unnecessary additional change events. + payload = { + ...payload, + modified: new Date().toISOString(), + }; const updated = await this.table.update(node.id, payload); // Update didn't succeed, this node probably doesn't exist, do a put instead, // but need to add in other parent info. diff --git a/contentcuration/contentcuration/frontend/shared/views/__tests__/languageDropdown.spec.js b/contentcuration/contentcuration/frontend/shared/views/__tests__/languageDropdown.spec.js index f478ebcd9b..8bdcd165fd 100644 --- a/contentcuration/contentcuration/frontend/shared/views/__tests__/languageDropdown.spec.js +++ b/contentcuration/contentcuration/frontend/shared/views/__tests__/languageDropdown.spec.js @@ -1,4 +1,4 @@ -import { mount } from '@vue/test-utils'; +import { mount, shallowMount } from '@vue/test-utils'; import LanguageDropdown from '../LanguageDropdown.vue'; import TestForm from './TestForm.vue'; import { LanguagesList } from 'shared/leUtils/Languages'; @@ -61,4 +61,24 @@ describe('languageDropdown', () => { await wrapper.vm.$nextTick(); expect(wrapper.find('.error--text').exists()).toBe(true); }); + + it('returns formatted language text when native_name is present', () => { + const wrapper = shallowMount(LanguageDropdown, { + mocks: { + $tr: (key, params) => `${params.language} (${params.code})`, + }, + }); + const item = { native_name: 'Español,Spanish', id: 'es' }; + expect(wrapper.vm.languageText(item)).toBe('Español (es)'); + }); + + it('returns formatted language text when native_name is an empty string', () => { + const wrapper = shallowMount(LanguageDropdown, { + mocks: { + $tr: (key, params) => `${params.language} (${params.code})`, + }, + }); + const item = { native_name: '', id: 'de' }; + expect(wrapper.vm.languageText(item)).toBe(' (de)'); + }); });