From 767b58b752d8f31090133a303234109441679a05 Mon Sep 17 00:00:00 2001 From: ihor-romaniuk Date: Tue, 20 Feb 2024 11:59:18 +0100 Subject: [PATCH] fix: [AXIMST-481] Unit page - fix configuration modal valiation --- .../course-xblock/CourseXBlock.jsx | 2 +- .../course-xblock/CourseXBlock.test.jsx | 7 +- .../configure-modal/ConfigureModal.jsx | 10 ++- src/utils.js | 23 ++++++ src/utils.test.js | 70 ++++++++++++++++++- 5 files changed, 105 insertions(+), 7 deletions(-) diff --git a/src/course-unit/course-xblock/CourseXBlock.jsx b/src/course-unit/course-xblock/CourseXBlock.jsx index ff91aeef1b..7b4b232703 100644 --- a/src/course-unit/course-xblock/CourseXBlock.jsx +++ b/src/course-unit/course-xblock/CourseXBlock.jsx @@ -37,7 +37,7 @@ const CourseXBlock = ({ category: COURSE_BLOCK_NAMES.component.id, displayName: title, userPartitionInfo, - showCorrectness: 'never', + showCorrectness: 'always', }; const onDeleteSubmit = () => { diff --git a/src/course-unit/course-xblock/CourseXBlock.test.jsx b/src/course-unit/course-xblock/CourseXBlock.test.jsx index 19360547a3..60d86afc69 100644 --- a/src/course-unit/course-xblock/CourseXBlock.test.jsx +++ b/src/course-unit/course-xblock/CourseXBlock.test.jsx @@ -196,8 +196,7 @@ describe('', () => { expect(handleConfigureSubmitMock).not.toHaveBeenCalled(); }); - // ToDo: fix text due to changes generic/configure-modal/ConfigureModal.jsx - it.skip('handles submit restrict access data when save button is clicked', async () => { + it('handles submit restrict access data when save button is clicked', async () => { axiosMock .onPost(getXBlockBaseApiUrl(id), { publish: PUBLISH_TYPES.republish, @@ -243,7 +242,9 @@ describe('', () => { }); expect(saveModalBtnText).toBeInTheDocument(); userEvent.click(saveModalBtnText); - expect(handleConfigureSubmitMock).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(handleConfigureSubmitMock).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/src/generic/configure-modal/ConfigureModal.jsx b/src/generic/configure-modal/ConfigureModal.jsx index 0368937ac0..164c21b09e 100644 --- a/src/generic/configure-modal/ConfigureModal.jsx +++ b/src/generic/configure-modal/ConfigureModal.jsx @@ -2,6 +2,7 @@ import React from 'react'; import * as Yup from 'yup'; import PropTypes from 'prop-types'; +import { isEqual } from 'lodash'; import { useIntl } from '@edx/frontend-platform/i18n'; import { ModalDialog, @@ -15,6 +16,7 @@ import { Formik } from 'formik'; import { VisibilityTypes } from '../../data/constants'; import { COURSE_BLOCK_NAMES } from '../../constants'; +import { deepSortObject } from '../../utils'; import messages from './messages'; import BasicTab from './BasicTab'; import VisibilityTab from './VisibilityTab'; @@ -275,7 +277,7 @@ const ConfigureModal = ({ validateOnChange > {({ - values, handleSubmit, dirty, isValid, setFieldValue, + values, handleSubmit, isValid, setFieldValue, }) => ( <> @@ -288,7 +290,11 @@ const ConfigureModal = ({ {intl.formatMessage(messages.cancelButton)} - diff --git a/src/utils.js b/src/utils.js index e3529cf301..5df1578503 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,6 +2,7 @@ import { useContext, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useMediaQuery } from 'react-responsive'; import * as Yup from 'yup'; +import _ from 'lodash'; import { snakeCase } from 'lodash/string'; import moment from 'moment'; import { getConfig, getPath } from '@edx/frontend-platform'; @@ -298,3 +299,25 @@ export const objectToQueryString = (obj) => ( (key) => `${key }=${ obj[key]}`, ).join('&') ); + +/** + * Recursively deep sorts the properties of an object. + * @param {Object|Array|*} obj - The object to deep sort. + * @returns {Object|Array|*} - The deep sorted object. + */ +export function deepSortObject(obj) { + if (!_.isObject(obj)) { + return obj; + } + + if (Array.isArray(obj)) { + return _.map(obj, deepSortObject).sort(); + } + + return _.chain(obj) + .toPairs() // Convert object to key-value pairs array + .sortBy(0) // Sort by keys + .fromPairs() // Convert back to object + .mapValues(deepSortObject) // Recursively sort property values + .value(); +} diff --git a/src/utils.test.js b/src/utils.test.js index e4aada849f..4319968d03 100644 --- a/src/utils.test.js +++ b/src/utils.test.js @@ -1,6 +1,6 @@ import { getConfig, getPath } from '@edx/frontend-platform'; -import { getFileSizeToClosestByte, createCorrectInternalRoute } from './utils'; +import { getFileSizeToClosestByte, createCorrectInternalRoute, deepSortObject } from './utils'; jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn(), @@ -77,4 +77,72 @@ describe('FilesAndUploads utils', () => { expect(result).toBe('/course-authoring/some/path'); }); }); + describe('deepSortObject', () => { + it('should deep sort an object with nested properties', () => { + const unsortedObject = { + z: 1, + b: { + c: 3, + a: 2, + }, + d: [4, 1, 3, 2], + e: 'hello', + }; + + const sortedObject = deepSortObject(unsortedObject); + + const expectedSortedObject = { + b: { + a: 2, + c: 3, + }, + d: [1, 2, 3, 4], + e: 'hello', + z: 1, + }; + + expect(sortedObject).toEqual(expectedSortedObject); + }); + it('should handle arrays and other types correctly', () => { + const unsortedObject = { + z: 1, + b: { + c: 3, + a: 2, + }, + d: [4, 1, 3, 2], + e: 'hello', + f: true, + }; + + const sortedObject = deepSortObject(unsortedObject); + + const expectedSortedObject = { + b: { + a: 2, + c: 3, + }, + d: [1, 2, 3, 4], + e: 'hello', + f: true, + z: 1, + }; + + expect(sortedObject).toEqual(expectedSortedObject); + }); + it('should not modify the original object', () => { + const unsortedObject = { + z: 1, + b: { + c: 3, + a: 2, + }, + d: [4, 1, 3, 2], + e: 'hello', + }; + const sortedObject = deepSortObject(unsortedObject); + + expect(sortedObject).not.toBe(unsortedObject); + }); + }); });