From a77e3ed287b805589458360c60cd9acaf7c75d8a Mon Sep 17 00:00:00 2001 From: Samson Akol Date: Fri, 10 Oct 2025 16:42:01 +0300 Subject: [PATCH 1/2] Adds defensive check when validating node title --- .../contentcuration/frontend/shared/utils/validation.js | 3 ++- .../contentcuration/frontend/shared/utils/validation.spec.js | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/contentcuration/contentcuration/frontend/shared/utils/validation.js b/contentcuration/contentcuration/frontend/shared/utils/validation.js index e576d678e5..bb50c05799 100644 --- a/contentcuration/contentcuration/frontend/shared/utils/validation.js +++ b/contentcuration/contentcuration/frontend/shared/utils/validation.js @@ -268,8 +268,9 @@ export function getInvalidText(validators, value) { // Node validation // These functions return an array of error codes export function getNodeTitleErrors(node) { + const title = node.title || ''; return getTitleValidators() - .map(validator => validator(node.title)) + .map(validator => validator(title)) .filter(value => value !== true); } diff --git a/contentcuration/contentcuration/frontend/shared/utils/validation.spec.js b/contentcuration/contentcuration/frontend/shared/utils/validation.spec.js index a1868b32f9..52917d3ae8 100644 --- a/contentcuration/contentcuration/frontend/shared/utils/validation.spec.js +++ b/contentcuration/contentcuration/frontend/shared/utils/validation.spec.js @@ -62,6 +62,11 @@ describe('channelEdit utils', () => { const node = { title: ' ' }; expect(getNodeTitleErrors(node)).toEqual([ValidationErrors.TITLE_REQUIRED]); }); + + it('returns an error for a undefined title', () => { + const node = {}; + expect(getNodeTitleErrors(node)).toEqual([ValidationErrors.TITLE_REQUIRED]); + }); }); describe('getNodeLicenseErrors', () => { From 4390e78effd0a718b0e5ad54159d00b0bfd8dac7 Mon Sep 17 00:00:00 2001 From: Samson Akol Date: Tue, 21 Oct 2025 10:37:37 +0300 Subject: [PATCH 2/2] Streamline getNodeDetailsErrorsList and getContentNodeDetailsAreValid getters --- .../channelEdit/vuex/contentNode/getters.js | 14 +++++++++++--- .../contentcuration/frontend/shared/constants.js | 1 + .../frontend/shared/utils/validation.js | 3 +-- .../frontend/shared/utils/validation.spec.js | 5 ----- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js index 2927514b80..3e31ce6ed3 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js +++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js @@ -7,7 +7,7 @@ import messages from '../../translator'; import { parseNode } from './utils'; import { getNodeDetailsErrors, getNodeFilesErrors } from 'shared/utils/validation'; import { ContentKindsNames } from 'shared/leUtils/ContentKinds'; -import { NEW_OBJECT } from 'shared/constants'; +import { NEW_OBJECT, ValidationErrors } from 'shared/constants'; import { COPYING_STATUS, COPYING_STATUS_VALUES } from 'shared/data/constants'; function sorted(nodes) { @@ -158,14 +158,22 @@ export function getContentNodeIsValid(state, getters, rootState, rootGetters) { export function getContentNodeDetailsAreValid(state) { return function (contentNodeId) { - const contentNode = state.contentNodesMap[contentNodeId]; - return contentNode && (contentNode[NEW_OBJECT] || !getNodeDetailsErrors(contentNode).length); + return !getNodeDetailsErrorsList(state)(contentNodeId).length; }; } export function getNodeDetailsErrorsList(state) { return function (contentNodeId) { const contentNode = state.contentNodesMap[contentNodeId]; + + if (!contentNode) { + return [ValidationErrors.MISSING_NODE]; + } + + if (contentNode[NEW_OBJECT]) { + return []; + } + return getNodeDetailsErrors(contentNode); }; } diff --git a/contentcuration/contentcuration/frontend/shared/constants.js b/contentcuration/contentcuration/frontend/shared/constants.js index 77ddaa9b90..f17081da03 100644 --- a/contentcuration/contentcuration/frontend/shared/constants.js +++ b/contentcuration/contentcuration/frontend/shared/constants.js @@ -190,6 +190,7 @@ export const ValidationErrors = { ACTIVITY_DURATION_MAX_FOR_LONG_ACTIVITY: 'ACTIVITY_DURATION_MAX_FOR_LONG_ACTIVITY', ACTIVITY_DURATION_MIN_REQUIREMENT: 'ACTIVITY_DURATION_MIN_REQUIREMENT', ACTIVITY_DURATION_TOO_LONG: 'ACTIVITY_DURATION_TOO_LONG', + MISSING_NODE: 'MISSING_NODE', ...fileErrors, }; diff --git a/contentcuration/contentcuration/frontend/shared/utils/validation.js b/contentcuration/contentcuration/frontend/shared/utils/validation.js index bb50c05799..e576d678e5 100644 --- a/contentcuration/contentcuration/frontend/shared/utils/validation.js +++ b/contentcuration/contentcuration/frontend/shared/utils/validation.js @@ -268,9 +268,8 @@ export function getInvalidText(validators, value) { // Node validation // These functions return an array of error codes export function getNodeTitleErrors(node) { - const title = node.title || ''; return getTitleValidators() - .map(validator => validator(title)) + .map(validator => validator(node.title)) .filter(value => value !== true); } diff --git a/contentcuration/contentcuration/frontend/shared/utils/validation.spec.js b/contentcuration/contentcuration/frontend/shared/utils/validation.spec.js index 52917d3ae8..a1868b32f9 100644 --- a/contentcuration/contentcuration/frontend/shared/utils/validation.spec.js +++ b/contentcuration/contentcuration/frontend/shared/utils/validation.spec.js @@ -62,11 +62,6 @@ describe('channelEdit utils', () => { const node = { title: ' ' }; expect(getNodeTitleErrors(node)).toEqual([ValidationErrors.TITLE_REQUIRED]); }); - - it('returns an error for a undefined title', () => { - const node = {}; - expect(getNodeTitleErrors(node)).toEqual([ValidationErrors.TITLE_REQUIRED]); - }); }); describe('getNodeLicenseErrors', () => {