diff --git a/.github/workflows/deploytest.yml b/.github/workflows/deploytest.yml index b5c3fef9a3..cb4e4a2346 100644 --- a/.github/workflows/deploytest.yml +++ b/.github/workflows/deploytest.yml @@ -61,7 +61,9 @@ jobs: ${{ runner.os }}-pyprod- - name: Install pip-tools and python dependencies run: | - python -m pip install --upgrade pip + # Pin pip to 25.2 to avoid incompatibility with pip-tools and 25.3 + # see https://github.com/jazzband/pip-tools/issues/2252 + python -m pip install pip==25.2 pip install pip-tools pip-sync requirements.txt - name: Use pnpm diff --git a/.github/workflows/pythontest.yml b/.github/workflows/pythontest.yml index ec99bec269..5467f8a47c 100644 --- a/.github/workflows/pythontest.yml +++ b/.github/workflows/pythontest.yml @@ -82,7 +82,9 @@ jobs: ${{ runner.os }}-pytest- - name: Install pip-tools and python dependencies run: | - python -m pip install --upgrade pip + # Pin pip to 25.2 to avoid incompatibility with pip-tools and 25.3 + # see https://github.com/jazzband/pip-tools/issues/2252 + python -m pip install pip==25.2 pip install pip-tools pip-sync requirements.txt requirements-dev.txt - name: Test pytest diff --git a/Makefile b/Makefile index a4160f6b99..002d337323 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ migrate: # 4) Remove the management command from this `deploy-migrate` recipe # 5) Repeat! deploy-migrate: - python contentcuration/manage.py set_file_duration + echo "Nothing to do here!" contentnodegc: python contentcuration/manage.py garbage_collect 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/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)'); + }); });