From d18332e4154fa0bb23050427d0ceb6323f4daed7 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 11 Apr 2025 20:42:43 +0530 Subject: [PATCH 01/11] feat: drag overlay in unit page --- .../DraggableList/DraggableList.jsx | 23 +++++- .../units/LibraryUnitBlocks.tsx | 78 ++++++++++++------- 2 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/editors/sharedComponents/DraggableList/DraggableList.jsx b/src/editors/sharedComponents/DraggableList/DraggableList.jsx index 6946d11770..6c33351460 100644 --- a/src/editors/sharedComponents/DraggableList/DraggableList.jsx +++ b/src/editors/sharedComponents/DraggableList/DraggableList.jsx @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { act, useState } from 'react'; import PropTypes from 'prop-types'; +import {createPortal} from 'react-dom'; import { DndContext, @@ -8,6 +9,7 @@ import { PointerSensor, useSensor, useSensors, + DragOverlay, } from '@dnd-kit/core'; import { arrayMove, @@ -22,6 +24,7 @@ const DraggableList = ({ setState, updateOrder, children, + renderOverlay, }) => { const sensors = useSensors( useSensor(PointerSensor), @@ -29,8 +32,10 @@ const DraggableList = ({ coordinateGetter: sortableKeyboardCoordinates, }), ); + const [activeId, setActiveId] = useState(null); const handleDragEnd = (event) => { + setActiveId(null); const { active, over } = event; if (active.id !== over.id) { let updatedArray; @@ -46,11 +51,16 @@ const DraggableList = ({ } }; + function handleDragStart(event) { + setActiveId(event.active.id); + } + return ( {children} + {renderOverlay && createPortal( + + {renderOverlay(activeId)} + , + document.body + )} ); }; +DraggableList.defaultProps = { + overlayComponent: undefined, +} + DraggableList.propTypes = { itemList: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, @@ -70,6 +90,7 @@ DraggableList.propTypes = { setState: PropTypes.func.isRequired, updateOrder: PropTypes.func.isRequired, children: PropTypes.node.isRequired, + renderOverlay: PropTypes.func, }; export default DraggableList; diff --git a/src/library-authoring/units/LibraryUnitBlocks.tsx b/src/library-authoring/units/LibraryUnitBlocks.tsx index f1873fb08b..b2ffab97a0 100644 --- a/src/library-authoring/units/LibraryUnitBlocks.tsx +++ b/src/library-authoring/units/LibraryUnitBlocks.tsx @@ -45,6 +45,7 @@ export const LibraryUnitBlocks = ({ preview = false }: LibraryUnitBlocksProps) = const [isManageTagsDrawerOpen, openManageTagsDrawer, closeManageTagsDrawer] = useToggle(false); const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle(); + const [hidePreviewFor, setHidePreviewFor] = useState(null); const { navigateTo } = useLibraryRoutes(); const { @@ -103,35 +104,53 @@ export const LibraryUnitBlocks = ({ preview = false }: LibraryUnitBlocksProps) = return '200px'; }; + const renderOverlay = (activeId: string | null) => { + setHidePreviewFor(activeId); + if (!activeId) { + return null; + } + const block = orderedBlocks?.find((val) => val.id === activeId); + if (!block) { + return null; + } + return ( + + + + ); + } + + const BlockHeader = ({block}: {block: LibraryBlockMetadata}) => ( + <> + + + {block.displayName} + + + + {block.hasUnpublishedChanges && ( + + + + + + + )} + + + + + ); + const renderedBlocks = orderedBlocks?.map((block) => ( - - - {block.displayName} - - - - {block.hasUnpublishedChanges && ( - - - - - - - )} - - - - - )} + actions={} actionStyle={{ borderRadius: '8px 8px 0px 0px', padding: '0.5rem 1rem', @@ -141,7 +160,7 @@ export const LibraryUnitBlocks = ({ preview = false }: LibraryUnitBlocksProps) = isClickable onClick={() => handleComponentSelection(block)} > -
@@ -150,14 +169,19 @@ export const LibraryUnitBlocks = ({ preview = false }: LibraryUnitBlocksProps) = version={showOnlyPublished ? 'published' : undefined} minHeight={calculateMinHeight(block)} /> -
+ }
)); return (
- + {renderedBlocks} { !preview && ( From 681aecb95ba6183e7dad2571ba64dfe9e6a5b935 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 11 Apr 2025 20:53:42 +0530 Subject: [PATCH 02/11] feat: disable sort in preview --- .../sharedComponents/DraggableList/SortableItem.jsx | 10 ++++++++-- src/library-authoring/units/LibraryUnitBlocks.tsx | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/editors/sharedComponents/DraggableList/SortableItem.jsx b/src/editors/sharedComponents/DraggableList/SortableItem.jsx index 01816624af..7f63eaec40 100644 --- a/src/editors/sharedComponents/DraggableList/SortableItem.jsx +++ b/src/editors/sharedComponents/DraggableList/SortableItem.jsx @@ -17,6 +17,7 @@ const SortableItem = ({ children, isClickable, onClick, + disabled, // injected intl, }) => { @@ -31,6 +32,9 @@ const SortableItem = ({ } = useSortable({ id, animateLayoutChanges: () => false, + disabled: { + draggable: disabled + } }); const style = { @@ -52,7 +56,7 @@ const SortableItem = ({ > {actions} - + />} {children} @@ -76,6 +80,7 @@ SortableItem.defaultProps = { actionStyle: null, isClickable: false, onClick: null, + disabled: false, }; SortableItem.propTypes = { id: PropTypes.string.isRequired, @@ -85,6 +90,7 @@ SortableItem.propTypes = { componentStyle: PropTypes.shape({}), isClickable: PropTypes.bool, onClick: PropTypes.func, + disabled: PropTypes.bool, // injected intl: intlShape.isRequired, }; diff --git a/src/library-authoring/units/LibraryUnitBlocks.tsx b/src/library-authoring/units/LibraryUnitBlocks.tsx index b2ffab97a0..51c0404737 100644 --- a/src/library-authoring/units/LibraryUnitBlocks.tsx +++ b/src/library-authoring/units/LibraryUnitBlocks.tsx @@ -36,10 +36,13 @@ const LARGE_COMPONENTS = [ ]; interface LibraryUnitBlocksProps { + /** set to true if it is rendered as preview + * This disables drag and drop + */ preview?: boolean; } -export const LibraryUnitBlocks = ({ preview = false }: LibraryUnitBlocksProps) => { +export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { const intl = useIntl(); const [orderedBlocks, setOrderedBlocks] = useState([]); const [isManageTagsDrawerOpen, openManageTagsDrawer, closeManageTagsDrawer] = useToggle(false); @@ -120,6 +123,7 @@ export const LibraryUnitBlocks = ({ preview = false }: LibraryUnitBlocksProps) = ); } + /** Component header, split out to reuse in drag overlay */ const BlockHeader = ({block}: {block: LibraryBlockMetadata}) => ( <> @@ -159,6 +163,7 @@ export const LibraryUnitBlocks = ({ preview = false }: LibraryUnitBlocksProps) = }} isClickable onClick={() => handleComponentSelection(block)} + disabled={preview} > {hidePreviewFor !== block.id &&
Date: Fri, 11 Apr 2025 21:09:02 +0530 Subject: [PATCH 03/11] refactor: move outline specific drag helpers to outline folder --- src/course-outline/CourseOutline.jsx | 4 ++-- src/course-outline/CourseOutline.scss | 1 + src/course-outline/CourseOutline.test.jsx | 2 +- .../drag-helper/DragContextProvider.jsx | 0 src/{generic => course-outline}/drag-helper/DraggableList.jsx | 0 src/{generic => course-outline}/drag-helper/SortableItem.jsx | 0 src/{generic => course-outline}/drag-helper/SortableItem.scss | 0 src/{generic => course-outline}/drag-helper/messages.js | 0 src/{generic => course-outline}/drag-helper/utils.js | 0 src/course-outline/section-card/SectionCard.jsx | 4 ++-- src/course-outline/subsection-card/SubsectionCard.jsx | 4 ++-- src/course-outline/unit-card/UnitCard.jsx | 2 +- src/editors/sharedComponents/DraggableList/DraggableList.jsx | 2 +- src/generic/styles.scss | 1 - 14 files changed, 10 insertions(+), 10 deletions(-) rename src/{generic => course-outline}/drag-helper/DragContextProvider.jsx (100%) rename src/{generic => course-outline}/drag-helper/DraggableList.jsx (100%) rename src/{generic => course-outline}/drag-helper/SortableItem.jsx (100%) rename src/{generic => course-outline}/drag-helper/SortableItem.scss (100%) rename src/{generic => course-outline}/drag-helper/messages.js (100%) rename src/{generic => course-outline}/drag-helper/utils.js (100%) diff --git a/src/course-outline/CourseOutline.jsx b/src/course-outline/CourseOutline.jsx index b5f3f3b0a8..df52bd7361 100644 --- a/src/course-outline/CourseOutline.jsx +++ b/src/course-outline/CourseOutline.jsx @@ -45,12 +45,12 @@ import HighlightsModal from './highlights-modal/HighlightsModal'; import EmptyPlaceholder from './empty-placeholder/EmptyPlaceholder'; import PublishModal from './publish-modal/PublishModal'; import PageAlerts from './page-alerts/PageAlerts'; -import DraggableList from '../generic/drag-helper/DraggableList'; +import DraggableList from './drag-helper/DraggableList'; import { canMoveSection, possibleUnitMoves, possibleSubsectionMoves, -} from '../generic/drag-helper/utils'; +} from './drag-helper/utils'; import { useCourseOutline } from './hooks'; import messages from './messages'; import { getTagsExportFile } from './data/api'; diff --git a/src/course-outline/CourseOutline.scss b/src/course-outline/CourseOutline.scss index 056b34a8eb..101d61ff51 100644 --- a/src/course-outline/CourseOutline.scss +++ b/src/course-outline/CourseOutline.scss @@ -7,3 +7,4 @@ @import "./highlights-modal/HighlightsModal"; @import "./publish-modal/PublishModal"; @import "./xblock-status/XBlockStatus"; +@import "./drag-helper/SortableItem"; diff --git a/src/course-outline/CourseOutline.test.jsx b/src/course-outline/CourseOutline.test.jsx index 05ff962057..2ca68dc44a 100644 --- a/src/course-outline/CourseOutline.test.jsx +++ b/src/course-outline/CourseOutline.test.jsx @@ -59,7 +59,7 @@ import { moveUnitOver, moveSubsection, moveUnit, -} from '../generic/drag-helper/utils'; +} from './drag-helper/utils'; let axiosMock; let store; diff --git a/src/generic/drag-helper/DragContextProvider.jsx b/src/course-outline/drag-helper/DragContextProvider.jsx similarity index 100% rename from src/generic/drag-helper/DragContextProvider.jsx rename to src/course-outline/drag-helper/DragContextProvider.jsx diff --git a/src/generic/drag-helper/DraggableList.jsx b/src/course-outline/drag-helper/DraggableList.jsx similarity index 100% rename from src/generic/drag-helper/DraggableList.jsx rename to src/course-outline/drag-helper/DraggableList.jsx diff --git a/src/generic/drag-helper/SortableItem.jsx b/src/course-outline/drag-helper/SortableItem.jsx similarity index 100% rename from src/generic/drag-helper/SortableItem.jsx rename to src/course-outline/drag-helper/SortableItem.jsx diff --git a/src/generic/drag-helper/SortableItem.scss b/src/course-outline/drag-helper/SortableItem.scss similarity index 100% rename from src/generic/drag-helper/SortableItem.scss rename to src/course-outline/drag-helper/SortableItem.scss diff --git a/src/generic/drag-helper/messages.js b/src/course-outline/drag-helper/messages.js similarity index 100% rename from src/generic/drag-helper/messages.js rename to src/course-outline/drag-helper/messages.js diff --git a/src/generic/drag-helper/utils.js b/src/course-outline/drag-helper/utils.js similarity index 100% rename from src/generic/drag-helper/utils.js rename to src/course-outline/drag-helper/utils.js diff --git a/src/course-outline/section-card/SectionCard.jsx b/src/course-outline/section-card/SectionCard.jsx index f8d11a4cd0..5d3b5fbb5a 100644 --- a/src/course-outline/section-card/SectionCard.jsx +++ b/src/course-outline/section-card/SectionCard.jsx @@ -13,8 +13,8 @@ import classNames from 'classnames'; import { setCurrentItem, setCurrentSection } from '../data/slice'; import { RequestStatus } from '../../data/constants'; import CardHeader from '../card-header/CardHeader'; -import SortableItem from '../../generic/drag-helper/SortableItem'; -import { DragContext } from '../../generic/drag-helper/DragContextProvider'; +import SortableItem from '../drag-helper/SortableItem'; +import { DragContext } from '../drag-helper/DragContextProvider'; import TitleButton from '../card-header/TitleButton'; import XBlockStatus from '../xblock-status/XBlockStatus'; import { getItemStatus, getItemStatusBorder, scrollToElement } from '../utils'; diff --git a/src/course-outline/subsection-card/SubsectionCard.jsx b/src/course-outline/subsection-card/SubsectionCard.jsx index 1d5c7b0367..86d35231b3 100644 --- a/src/course-outline/subsection-card/SubsectionCard.jsx +++ b/src/course-outline/subsection-card/SubsectionCard.jsx @@ -15,8 +15,8 @@ import CourseOutlineSubsectionCardExtraActionsSlot from '../../plugin-slots/Cour import { setCurrentItem, setCurrentSection, setCurrentSubsection } from '../data/slice'; import { RequestStatus } from '../../data/constants'; import CardHeader from '../card-header/CardHeader'; -import SortableItem from '../../generic/drag-helper/SortableItem'; -import { DragContext } from '../../generic/drag-helper/DragContextProvider'; +import SortableItem from '../drag-helper/SortableItem'; +import { DragContext } from '../drag-helper/DragContextProvider'; import { useClipboard, PasteComponent } from '../../generic/clipboard'; import TitleButton from '../card-header/TitleButton'; import XBlockStatus from '../xblock-status/XBlockStatus'; diff --git a/src/course-outline/unit-card/UnitCard.jsx b/src/course-outline/unit-card/UnitCard.jsx index 7d17d24c53..00ac4cf50d 100644 --- a/src/course-outline/unit-card/UnitCard.jsx +++ b/src/course-outline/unit-card/UnitCard.jsx @@ -10,7 +10,7 @@ import CourseOutlineUnitCardExtraActionsSlot from '../../plugin-slots/CourseOutl import { setCurrentItem, setCurrentSection, setCurrentSubsection } from '../data/slice'; import { RequestStatus } from '../../data/constants'; import CardHeader from '../card-header/CardHeader'; -import SortableItem from '../../generic/drag-helper/SortableItem'; +import SortableItem from '../drag-helper/SortableItem'; import TitleLink from '../card-header/TitleLink'; import XBlockStatus from '../xblock-status/XBlockStatus'; import { getItemStatus, getItemStatusBorder, scrollToElement } from '../utils'; diff --git a/src/editors/sharedComponents/DraggableList/DraggableList.jsx b/src/editors/sharedComponents/DraggableList/DraggableList.jsx index 6c33351460..6e29055f2a 100644 --- a/src/editors/sharedComponents/DraggableList/DraggableList.jsx +++ b/src/editors/sharedComponents/DraggableList/DraggableList.jsx @@ -1,4 +1,4 @@ -import React, { act, useState } from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import {createPortal} from 'react-dom'; diff --git a/src/generic/styles.scss b/src/generic/styles.scss index 2ec14a72b6..00ef459221 100644 --- a/src/generic/styles.scss +++ b/src/generic/styles.scss @@ -11,6 +11,5 @@ @import "./tag-count/TagCount"; @import "./modal-dropzone/ModalDropzone"; @import "./configure-modal/ConfigureModal"; -@import "./drag-helper/SortableItem"; @import "./block-type-utils"; @import "./modal-iframe" From 07f2e45fe270178f4395b6cca623475c6b2f65c1 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Sat, 12 Apr 2025 10:45:05 +0530 Subject: [PATCH 04/11] refactor: move draggablelist to generic folder --- src/custom-pages/CustomPages.jsx | 2 +- .../DraggableList/DraggableList.jsx | 0 .../sharedComponents => generic}/DraggableList/SortableItem.jsx | 0 .../sharedComponents => generic}/DraggableList/index.jsx | 0 .../sharedComponents => generic}/DraggableList/messages.js | 0 src/library-authoring/units/LibraryUnitBlocks.tsx | 2 +- 6 files changed, 2 insertions(+), 2 deletions(-) rename src/{editors/sharedComponents => generic}/DraggableList/DraggableList.jsx (100%) rename src/{editors/sharedComponents => generic}/DraggableList/SortableItem.jsx (100%) rename src/{editors/sharedComponents => generic}/DraggableList/index.jsx (100%) rename src/{editors/sharedComponents => generic}/DraggableList/messages.js (100%) diff --git a/src/custom-pages/CustomPages.jsx b/src/custom-pages/CustomPages.jsx index ad69e670f2..d2c5e7e317 100644 --- a/src/custom-pages/CustomPages.jsx +++ b/src/custom-pages/CustomPages.jsx @@ -21,7 +21,7 @@ import { } from '@openedx/paragon'; import { Add, SpinnerSimple } from '@openedx/paragon/icons'; import Placeholder from '../editors/Placeholder'; -import DraggableList, { SortableItem } from '../editors/sharedComponents/DraggableList'; +import DraggableList, { SortableItem } from '../generic/DraggableList'; import ErrorAlert from '../editors/sharedComponents/ErrorAlerts/ErrorAlert'; import { RequestStatus } from '../data/constants'; diff --git a/src/editors/sharedComponents/DraggableList/DraggableList.jsx b/src/generic/DraggableList/DraggableList.jsx similarity index 100% rename from src/editors/sharedComponents/DraggableList/DraggableList.jsx rename to src/generic/DraggableList/DraggableList.jsx diff --git a/src/editors/sharedComponents/DraggableList/SortableItem.jsx b/src/generic/DraggableList/SortableItem.jsx similarity index 100% rename from src/editors/sharedComponents/DraggableList/SortableItem.jsx rename to src/generic/DraggableList/SortableItem.jsx diff --git a/src/editors/sharedComponents/DraggableList/index.jsx b/src/generic/DraggableList/index.jsx similarity index 100% rename from src/editors/sharedComponents/DraggableList/index.jsx rename to src/generic/DraggableList/index.jsx diff --git a/src/editors/sharedComponents/DraggableList/messages.js b/src/generic/DraggableList/messages.js similarity index 100% rename from src/editors/sharedComponents/DraggableList/messages.js rename to src/generic/DraggableList/messages.js diff --git a/src/library-authoring/units/LibraryUnitBlocks.tsx b/src/library-authoring/units/LibraryUnitBlocks.tsx index 51c0404737..b96d88aeb8 100644 --- a/src/library-authoring/units/LibraryUnitBlocks.tsx +++ b/src/library-authoring/units/LibraryUnitBlocks.tsx @@ -8,7 +8,7 @@ import classNames from 'classnames'; import { useEffect, useState } from 'react'; import { ContentTagsDrawerSheet } from '../../content-tags-drawer'; import { blockTypes } from '../../editors/data/constants/app'; -import DraggableList, { SortableItem } from '../../editors/sharedComponents/DraggableList'; +import DraggableList, { SortableItem } from '../../generic/DraggableList'; import ErrorAlert from '../../generic/alert-error'; import { getItemIcon } from '../../generic/block-type-utils'; From d1bcba4b02ebeaac7d2e11d4eb91b1f30d3221f1 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Mon, 14 Apr 2025 12:07:15 +0530 Subject: [PATCH 05/11] refactor: improve overlay function and design --- src/library-authoring/units/LibraryUnitBlocks.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/library-authoring/units/LibraryUnitBlocks.tsx b/src/library-authoring/units/LibraryUnitBlocks.tsx index b96d88aeb8..008ef5a2cb 100644 --- a/src/library-authoring/units/LibraryUnitBlocks.tsx +++ b/src/library-authoring/units/LibraryUnitBlocks.tsx @@ -1,8 +1,8 @@ import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { - ActionRow, Badge, Button, Icon, Stack, useToggle, + ActionRow, Badge, Button, Icon, IconButton, Stack, useToggle, } from '@openedx/paragon'; -import { Add, Description } from '@openedx/paragon/icons'; +import { Add, Description, DragIndicator } from '@openedx/paragon/icons'; import { useQueryClient } from '@tanstack/react-query'; import classNames from 'classnames'; import { useEffect, useState } from 'react'; @@ -119,6 +119,12 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { return ( + ); } @@ -160,6 +166,7 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { padding: '0.5rem 1rem', background: '#FBFAF9', borderBottom: 'solid 1px #E1DDDB', + outline: hidePreviewFor === block.id && '2px dashed gray' }} isClickable onClick={() => handleComponentSelection(block)} From 7ec300884664f213460f20650a6ea96fa1cc87f9 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Mon, 14 Apr 2025 16:04:11 +0530 Subject: [PATCH 06/11] feat: save component order in unit --- src/library-authoring/data/api.ts | 14 +++++++++++ src/library-authoring/data/apiHooks.ts | 23 +++++++++++++++++++ .../units/LibraryUnitBlocks.tsx | 20 ++++++++++------ src/library-authoring/units/messages.ts | 10 ++++++++ 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/library-authoring/data/api.ts b/src/library-authoring/data/api.ts index b4a7bca673..e09f796ffc 100644 --- a/src/library-authoring/data/api.ts +++ b/src/library-authoring/data/api.ts @@ -670,3 +670,17 @@ export async function updateContainerCollections(containerId: string, collection collection_keys: collectionKeys, }); } + +/** + * Update library container's children. + */ +export async function updateLibraryContainerChildren( + containerId: string, + children: string[], +): Promise { + const { data } = await getAuthenticatedHttpClient().patch( + getLibraryContainerChildrenApiUrl(containerId), + {'usage_keys': children}, + ); + return camelCaseObject(data); +} diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts index e20671a2b2..2038747ba4 100644 --- a/src/library-authoring/data/apiHooks.ts +++ b/src/library-authoring/data/apiHooks.ts @@ -56,6 +56,7 @@ import { restoreContainer, getLibraryContainerChildren, updateContainerCollections, + updateLibraryContainerChildren, } from './api'; import { VersionSpec } from '../LibraryBlock'; @@ -696,3 +697,25 @@ export const useUpdateContainerCollections = (containerId: string) => { }, }); }; + +/** + * Update container children + */ +export const useUpdateContainerChildren = (containerId?: string) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (usageKeys: string[]) => { + if (!containerId) { + return undefined; + } + return updateLibraryContainerChildren(containerId, usageKeys) + }, + onSettled: () => { + // NOTE: We invalidate the library query here because we need to update the library's + // container list. + const libraryId = getLibraryId(containerId!); + queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); + queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.container(libraryId, containerId) }); + }, + }); +}; diff --git a/src/library-authoring/units/LibraryUnitBlocks.tsx b/src/library-authoring/units/LibraryUnitBlocks.tsx index 008ef5a2cb..e31443c1e7 100644 --- a/src/library-authoring/units/LibraryUnitBlocks.tsx +++ b/src/library-authoring/units/LibraryUnitBlocks.tsx @@ -5,7 +5,7 @@ import { import { Add, Description, DragIndicator } from '@openedx/paragon/icons'; import { useQueryClient } from '@tanstack/react-query'; import classNames from 'classnames'; -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { ContentTagsDrawerSheet } from '../../content-tags-drawer'; import { blockTypes } from '../../editors/data/constants/app'; import DraggableList, { SortableItem } from '../../generic/DraggableList'; @@ -20,11 +20,12 @@ import { useLibraryContext } from '../common/context/LibraryContext'; import { PickLibraryContentModal } from '../add-content'; import ComponentMenu from '../components'; import { LibraryBlockMetadata } from '../data/api'; -import { libraryAuthoringQueryKeys, useContainerChildren } from '../data/apiHooks'; +import { libraryAuthoringQueryKeys, useContainerChildren, useUpdateContainerChildren } from '../data/apiHooks'; import { LibraryBlock } from '../LibraryBlock'; import { useLibraryRoutes } from '../routes'; import messages from './messages'; import { useSidebarContext } from '../common/context/SidebarContext'; +import { ToastContext } from '../../generic/toast-context'; /** Components that need large min height in preview */ const LARGE_COMPONENTS = [ @@ -50,6 +51,7 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { const [hidePreviewFor, setHidePreviewFor] = useState(null); const { navigateTo } = useLibraryRoutes(); + const { showToast } = useContext(ToastContext); const { unitId, @@ -64,6 +66,7 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { } = useSidebarContext(); const queryClient = useQueryClient(); + const orderMutator = useUpdateContainerChildren(unitId); const { data: blocks, isLoading, @@ -82,11 +85,14 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { return ; } - /* istanbul ignore next */ - const handleReorder = () => (newOrder: LibraryBlockMetadata[]) => { - // eslint-disable-next-line no-console - console.log('LibraryUnitBlocks newOrder: ', newOrder); - // TODO: update order of components in unit + const handleReorder = () => async (newOrder: LibraryBlockMetadata[]) => { + const usageKeys = newOrder.map((o) => o.id); + try { + await orderMutator.mutateAsync(usageKeys); + showToast(intl.formatMessage(messages.orderUpdatedMsg)); + } catch (e) { + showToast(intl.formatMessage(messages.failedOrderUpdatedMsg)); + } }; const onTagSidebarClose = () => { diff --git a/src/library-authoring/units/messages.ts b/src/library-authoring/units/messages.ts index e0e288a383..a15eeb8503 100644 --- a/src/library-authoring/units/messages.ts +++ b/src/library-authoring/units/messages.ts @@ -41,6 +41,16 @@ const messages = defineMessages({ defaultMessage: 'Failed to update container.', description: 'Message displayed when container update fails', }, + orderUpdatedMsg: { + id: 'course-authoring.library-authoring.unit-component.order-updated-msg.text', + defaultMessage: 'Order updated', + description: 'Toast message displayed when components are successfully reordered in a unit', + }, + failedOrderUpdatedMsg: { + id: 'course-authoring.library-authoring.unit-component.failed-order-updated-msg.text', + defaultMessage: 'Failed to update components order', + description: 'Toast message displayed when components are successfully reordered in a unit', + }, }); export default messages; From a2ba228716402f1777e4b0883dd361bb5a6c3bda Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Mon, 14 Apr 2025 16:14:15 +0530 Subject: [PATCH 07/11] chore: fix lint issues --- src/generic/DraggableList/DraggableList.jsx | 12 +-- src/generic/DraggableList/SortableItem.jsx | 10 ++- src/library-authoring/data/api.ts | 2 +- src/library-authoring/data/apiHooks.ts | 2 +- .../units/LibraryUnitBlocks.tsx | 75 ++++++++++--------- 5 files changed, 55 insertions(+), 46 deletions(-) diff --git a/src/generic/DraggableList/DraggableList.jsx b/src/generic/DraggableList/DraggableList.jsx index 6e29055f2a..3e45d621cb 100644 --- a/src/generic/DraggableList/DraggableList.jsx +++ b/src/generic/DraggableList/DraggableList.jsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import {createPortal} from 'react-dom'; +import { createPortal } from 'react-dom'; import { DndContext, @@ -51,9 +51,9 @@ const DraggableList = ({ } }; - function handleDragStart(event) { + const handleDragStart = (event) => { setActiveId(event.active.id); - } + }; return ( {renderOverlay(activeId)} , - document.body + document.body, )} ); }; DraggableList.defaultProps = { - overlayComponent: undefined, -} + renderOverlay: undefined, +}; DraggableList.propTypes = { itemList: PropTypes.arrayOf(PropTypes.shape({ diff --git a/src/generic/DraggableList/SortableItem.jsx b/src/generic/DraggableList/SortableItem.jsx index 7f63eaec40..b86563cb9c 100644 --- a/src/generic/DraggableList/SortableItem.jsx +++ b/src/generic/DraggableList/SortableItem.jsx @@ -33,8 +33,8 @@ const SortableItem = ({ id, animateLayoutChanges: () => false, disabled: { - draggable: disabled - } + draggable: disabled, + }, }); const style = { @@ -56,7 +56,8 @@ const SortableItem = ({ > {actions} - {!disabled && } + /> + )} {children} diff --git a/src/library-authoring/data/api.ts b/src/library-authoring/data/api.ts index e09f796ffc..1eabb381ed 100644 --- a/src/library-authoring/data/api.ts +++ b/src/library-authoring/data/api.ts @@ -680,7 +680,7 @@ export async function updateLibraryContainerChildren( ): Promise { const { data } = await getAuthenticatedHttpClient().patch( getLibraryContainerChildrenApiUrl(containerId), - {'usage_keys': children}, + { usage_keys: children }, ); return camelCaseObject(data); } diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts index 2038747ba4..3b4b59e1b4 100644 --- a/src/library-authoring/data/apiHooks.ts +++ b/src/library-authoring/data/apiHooks.ts @@ -708,7 +708,7 @@ export const useUpdateContainerChildren = (containerId?: string) => { if (!containerId) { return undefined; } - return updateLibraryContainerChildren(containerId, usageKeys) + return updateLibraryContainerChildren(containerId, usageKeys); }, onSettled: () => { // NOTE: We invalidate the library query here because we need to update the library's diff --git a/src/library-authoring/units/LibraryUnitBlocks.tsx b/src/library-authoring/units/LibraryUnitBlocks.tsx index e31443c1e7..813a47d82f 100644 --- a/src/library-authoring/units/LibraryUnitBlocks.tsx +++ b/src/library-authoring/units/LibraryUnitBlocks.tsx @@ -36,6 +36,37 @@ const LARGE_COMPONENTS = [ 'lti_consumer', ]; +interface BlockHeaderProps { + block: LibraryBlockMetadata; + onTagClick: () => void; +} + +/** Component header, split out to reuse in drag overlay */ +const BlockHeader = ({ block, onTagClick }: BlockHeaderProps) => ( + <> + + + {block.displayName} + + + + {block.hasUnpublishedChanges && ( + + + + + + + )} + + + + +); + interface LibraryUnitBlocksProps { /** set to true if it is rendered as preview * This disables drag and drop @@ -123,62 +154,37 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { return null; } return ( - - + + ); - } - - /** Component header, split out to reuse in drag overlay */ - const BlockHeader = ({block}: {block: LibraryBlockMetadata}) => ( - <> - - - {block.displayName} - - - - {block.hasUnpublishedChanges && ( - - - - - - - )} - - - - - ); + }; const renderedBlocks = orderedBlocks?.map((block) => ( } + actions={} actionStyle={{ borderRadius: '8px 8px 0px 0px', padding: '0.5rem 1rem', background: '#FBFAF9', borderBottom: 'solid 1px #E1DDDB', - outline: hidePreviewFor === block.id && '2px dashed gray' + outline: hidePreviewFor === block.id && '2px dashed gray', }} isClickable onClick={() => handleComponentSelection(block)} disabled={preview} > - {hidePreviewFor !== block.id &&
@@ -187,7 +193,8 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { version={showOnlyPublished ? 'published' : undefined} minHeight={calculateMinHeight(block)} /> -
} +
+ )} )); From ff4623e22616f23f875240840668b198d91d0dca Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Mon, 14 Apr 2025 21:55:24 +0530 Subject: [PATCH 08/11] test: drag and drop unit components --- src/generic/DraggableList/DraggableList.jsx | 21 +++++++----- src/library-authoring/data/api.test.ts | 11 ++++++ src/library-authoring/data/apiHooks.test.tsx | 13 +++++++ .../units/LibraryUnitBlocks.tsx | 3 +- .../units/LibraryUnitPage.test.tsx | 34 +++++++++++++++++++ 5 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/generic/DraggableList/DraggableList.jsx b/src/generic/DraggableList/DraggableList.jsx index 3e45d621cb..5cccd44da0 100644 --- a/src/generic/DraggableList/DraggableList.jsx +++ b/src/generic/DraggableList/DraggableList.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; import { createPortal } from 'react-dom'; @@ -25,6 +25,8 @@ const DraggableList = ({ updateOrder, children, renderOverlay, + activeId, + setActiveId, }) => { const sensors = useSensors( useSensor(PointerSensor), @@ -32,10 +34,8 @@ const DraggableList = ({ coordinateGetter: sortableKeyboardCoordinates, }), ); - const [activeId, setActiveId] = useState(null); - const handleDragEnd = (event) => { - setActiveId(null); + const handleDragEnd = useCallback((event) => { const { active, over } = event; if (active.id !== over.id) { let updatedArray; @@ -49,11 +49,12 @@ const DraggableList = ({ }); updateOrder()(updatedArray); } - }; + setActiveId?.(null); + }, [updateOrder, setActiveId]); - const handleDragStart = (event) => { - setActiveId(event.active.id); - }; + const handleDragStart = useCallback((event) => { + setActiveId?.(event.active.id); + }, [setActiveId]); return ( {}, }; DraggableList.propTypes = { @@ -91,6 +94,8 @@ DraggableList.propTypes = { updateOrder: PropTypes.func.isRequired, children: PropTypes.node.isRequired, renderOverlay: PropTypes.func, + activeId: PropTypes.string, + onDragEnd: PropTypes.func, }; export default DraggableList; diff --git a/src/library-authoring/data/api.test.ts b/src/library-authoring/data/api.test.ts index e75a2b4b1a..37ebc4105a 100644 --- a/src/library-authoring/data/api.test.ts +++ b/src/library-authoring/data/api.test.ts @@ -126,4 +126,15 @@ describe('library data API', () => { await api.addComponentsToContainer(containerId, [componentId]); expect(axiosMock.history.post[0].url).toEqual(url); }); + + it('should update container children', async () => { + const { axiosMock } = initializeMocks(); + const containerId = 'lct:org:lib1'; + const url = api.getLibraryContainerChildrenApiUrl(containerId); + + axiosMock.onPatch(url).reply(200); + + await api.updateLibraryContainerChildren(containerId, ['test']); + expect(axiosMock.history.patch[0].url).toEqual(url); + }); }); diff --git a/src/library-authoring/data/apiHooks.test.tsx b/src/library-authoring/data/apiHooks.test.tsx index 57e0a8fa73..dabd298d27 100644 --- a/src/library-authoring/data/apiHooks.test.tsx +++ b/src/library-authoring/data/apiHooks.test.tsx @@ -29,6 +29,7 @@ import { useRestoreContainer, useContainerChildren, useAddComponentsToContainer, + useUpdateContainerChildren, } from './apiHooks'; let axiosMock; @@ -266,4 +267,16 @@ describe('library api hooks', () => { expect(axiosMock.history.post[0].url).toEqual(url); }); + + it('should update container children', async () => { + const containerId = 'lct:org:lib1'; + const url = getLibraryContainerChildrenApiUrl(containerId); + + axiosMock.onPatch(url).reply(200); + const { result } = renderHook(() => useUpdateContainerChildren(containerId), { wrapper }); + await result.current.mutateAsync([]); + await waitFor(() => { + expect(axiosMock.history.patch[0].url).toEqual(url); + }); + }); }); diff --git a/src/library-authoring/units/LibraryUnitBlocks.tsx b/src/library-authoring/units/LibraryUnitBlocks.tsx index 813a47d82f..16b0e13f98 100644 --- a/src/library-authoring/units/LibraryUnitBlocks.tsx +++ b/src/library-authoring/units/LibraryUnitBlocks.tsx @@ -145,7 +145,6 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { }; const renderOverlay = (activeId: string | null) => { - setHidePreviewFor(activeId); if (!activeId) { return null; } @@ -206,6 +205,8 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => { setState={setOrderedBlocks} updateOrder={handleReorder} renderOverlay={renderOverlay} + activeId={hidePreviewFor} + setActiveId={setHidePreviewFor} > {renderedBlocks}
diff --git a/src/library-authoring/units/LibraryUnitPage.test.tsx b/src/library-authoring/units/LibraryUnitPage.test.tsx index db1df10269..2692591e7a 100644 --- a/src/library-authoring/units/LibraryUnitPage.test.tsx +++ b/src/library-authoring/units/LibraryUnitPage.test.tsx @@ -2,6 +2,7 @@ import userEvent from '@testing-library/user-event'; import type MockAdapter from 'axios-mock-adapter'; import { + fireEvent, initializeMocks, fireEvent, render, @@ -20,9 +21,16 @@ import { import { mockContentSearchConfig, mockGetBlockTypes } from '../../search-manager/data/api.mock'; import { mockClipboardEmpty } from '../../generic/data/api.mock'; import LibraryLayout from '../LibraryLayout'; +import { getLibraryContainerChildrenApiUrl } from '../data/api'; +import { RequestStatus } from '../../data/constants'; +import { closestCorners } from '@dnd-kit/core'; +import { ToastActionData } from '../../generic/toast-context'; +import { act } from 'react'; const path = '/library/:libraryId/*'; const libraryTitle = mockContentLibrary.libraryData.title; +let axiosMock: import("axios-mock-adapter/types"); +let mockShowToast: (message: string, action?: ToastActionData | undefined) => void; let axiosMock: MockAdapter; let mockShowToast: (message: string) => void; @@ -36,6 +44,17 @@ mockContentLibrary.applyMock(); mockXBlockFields.applyMock(); mockLibraryBlockMetadata.applyMock(); +jest.mock('@dnd-kit/core', () => ({ + ...jest.requireActual('@dnd-kit/core'), + // Since jsdom (used by jest) does not support getBoundingClientRect function + // which is required for drag-n-drop calculations, we mock closestCorners fn + // from dnd-kit to return collided elements as per the test. This allows us to + // test all drag-n-drop handlers. + closestCorners: jest.fn(), +})); +// eslint-disable-next-line no-promise-executor-return +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + describe('', () => { beforeEach(() => { const mocks = initializeMocks(); @@ -187,4 +206,19 @@ describe('', () => { userEvent.click(closeButton); await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument()); }); + + it('should call update order api on dragging component', async () => { + renderLibraryUnitPage(); + const firstDragHandle = (await screen.findAllByRole('button', { name: 'Drag to reorder' }))[0]; + axiosMock + .onPatch(getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId)) + .reply(200); + closestCorners.mockReturnValue([{ id: "lb:org1:Demo_course:html:text-1" }]); + await act(async () => { + fireEvent.keyDown(firstDragHandle, { code: 'Space' }); + await sleep(1); + fireEvent.keyUp(firstDragHandle, { code: 'Space' }); + }) + await waitFor(() => expect(mockShowToast).toHaveBeenLastCalledWith('test')); + }); }); From 75ae09a07cc56115aa58cb0508abe49f073d958b Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Tue, 15 Apr 2025 12:50:17 +0530 Subject: [PATCH 09/11] test: more tests --- src/generic/DraggableList/DraggableList.jsx | 2 +- .../units/LibraryUnitPage.test.tsx | 38 +++++++++++++------ .../units/LibraryUnitPage.tsx | 2 +- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/generic/DraggableList/DraggableList.jsx b/src/generic/DraggableList/DraggableList.jsx index 5cccd44da0..1515f29a06 100644 --- a/src/generic/DraggableList/DraggableList.jsx +++ b/src/generic/DraggableList/DraggableList.jsx @@ -95,7 +95,7 @@ DraggableList.propTypes = { children: PropTypes.node.isRequired, renderOverlay: PropTypes.func, activeId: PropTypes.string, - onDragEnd: PropTypes.func, + setActiveId: PropTypes.func, }; export default DraggableList; diff --git a/src/library-authoring/units/LibraryUnitPage.test.tsx b/src/library-authoring/units/LibraryUnitPage.test.tsx index 2692591e7a..916809b2ae 100644 --- a/src/library-authoring/units/LibraryUnitPage.test.tsx +++ b/src/library-authoring/units/LibraryUnitPage.test.tsx @@ -1,6 +1,7 @@ import userEvent from '@testing-library/user-event'; import type MockAdapter from 'axios-mock-adapter'; +import { act } from 'react'; import { fireEvent, initializeMocks, @@ -22,14 +23,11 @@ import { mockContentSearchConfig, mockGetBlockTypes } from '../../search-manager import { mockClipboardEmpty } from '../../generic/data/api.mock'; import LibraryLayout from '../LibraryLayout'; import { getLibraryContainerChildrenApiUrl } from '../data/api'; -import { RequestStatus } from '../../data/constants'; -import { closestCorners } from '@dnd-kit/core'; import { ToastActionData } from '../../generic/toast-context'; -import { act } from 'react'; const path = '/library/:libraryId/*'; const libraryTitle = mockContentLibrary.libraryData.title; -let axiosMock: import("axios-mock-adapter/types"); +let axiosMock: import('axios-mock-adapter/types'); let mockShowToast: (message: string, action?: ToastActionData | undefined) => void; let axiosMock: MockAdapter; @@ -44,16 +42,15 @@ mockContentLibrary.applyMock(); mockXBlockFields.applyMock(); mockLibraryBlockMetadata.applyMock(); +const closestCenter = jest.fn(); jest.mock('@dnd-kit/core', () => ({ ...jest.requireActual('@dnd-kit/core'), // Since jsdom (used by jest) does not support getBoundingClientRect function // which is required for drag-n-drop calculations, we mock closestCorners fn // from dnd-kit to return collided elements as per the test. This allows us to // test all drag-n-drop handlers. - closestCorners: jest.fn(), + closestCenter: () => closestCenter(), })); -// eslint-disable-next-line no-promise-executor-return -const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); describe('', () => { beforeEach(() => { @@ -213,12 +210,29 @@ describe('', () => { axiosMock .onPatch(getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId)) .reply(200); - closestCorners.mockReturnValue([{ id: "lb:org1:Demo_course:html:text-1" }]); + closestCenter.mockReturnValue([{ id: 'lb:org1:Demo_course:html:text-1' }]); + await act(async () => { + fireEvent.keyDown(firstDragHandle, { code: 'Space' }); + }); + await act(async () => { + fireEvent.keyDown(firstDragHandle, { code: 'Space' }); + }); + await waitFor(() => expect(mockShowToast).toHaveBeenLastCalledWith('Order updated')); + }); + + it('should show toast error message on update order failure', async () => { + renderLibraryUnitPage(); + const firstDragHandle = (await screen.findAllByRole('button', { name: 'Drag to reorder' }))[0]; + axiosMock + .onPatch(getLibraryContainerChildrenApiUrl(mockGetContainerMetadata.containerId)) + .reply(500); + closestCenter.mockReturnValue([{ id: 'lb:org1:Demo_course:html:text-1' }]); await act(async () => { fireEvent.keyDown(firstDragHandle, { code: 'Space' }); - await sleep(1); - fireEvent.keyUp(firstDragHandle, { code: 'Space' }); - }) - await waitFor(() => expect(mockShowToast).toHaveBeenLastCalledWith('test')); + }); + await act(async () => { + fireEvent.keyDown(firstDragHandle, { code: 'Space' }); + }); + await waitFor(() => expect(mockShowToast).toHaveBeenLastCalledWith('Failed to update components order')); }); }); diff --git a/src/library-authoring/units/LibraryUnitPage.tsx b/src/library-authoring/units/LibraryUnitPage.tsx index fc1eb67161..e362cdb900 100644 --- a/src/library-authoring/units/LibraryUnitPage.tsx +++ b/src/library-authoring/units/LibraryUnitPage.tsx @@ -176,8 +176,8 @@ export const LibraryUnitPage = () => { return ; } + // istanbul ignore if if (isError) { - // istanbul ignore next return ; } From f9c84693810c76364fcd2c4c63f66848c1d8ef5e Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Tue, 15 Apr 2025 13:06:08 +0530 Subject: [PATCH 10/11] test: fix coverage --- src/library-authoring/data/apiHooks.test.tsx | 8 ++++++++ src/library-authoring/data/apiHooks.ts | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/library-authoring/data/apiHooks.test.tsx b/src/library-authoring/data/apiHooks.test.tsx index dabd298d27..6a1fd3dc50 100644 --- a/src/library-authoring/data/apiHooks.test.tsx +++ b/src/library-authoring/data/apiHooks.test.tsx @@ -279,4 +279,12 @@ describe('library api hooks', () => { expect(axiosMock.history.patch[0].url).toEqual(url); }); }); + + it('should not attempt request if containerId is not defined', async () => { + const { result } = renderHook(() => useUpdateContainerChildren(), { wrapper }); + await result.current.mutateAsync([]); + await waitFor(() => { + expect(axiosMock.history.patch.length).toEqual(0); + }); + }); }); diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts index 3b4b59e1b4..d5ce87d175 100644 --- a/src/library-authoring/data/apiHooks.ts +++ b/src/library-authoring/data/apiHooks.ts @@ -711,9 +711,12 @@ export const useUpdateContainerChildren = (containerId?: string) => { return updateLibraryContainerChildren(containerId, usageKeys); }, onSettled: () => { + if (!containerId) { + return; + } // NOTE: We invalidate the library query here because we need to update the library's // container list. - const libraryId = getLibraryId(containerId!); + const libraryId = getLibraryId(containerId); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.container(libraryId, containerId) }); }, From 11bc01a0ae32dd6eb8bdc2f882f3d0a44077b8fe Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Wed, 16 Apr 2025 17:13:13 +0530 Subject: [PATCH 11/11] fix: resolve rebase conflicts --- src/library-authoring/data/apiHooks.ts | 2 +- src/library-authoring/units/LibraryUnitPage.test.tsx | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts index d5ce87d175..b52b597e35 100644 --- a/src/library-authoring/data/apiHooks.ts +++ b/src/library-authoring/data/apiHooks.ts @@ -718,7 +718,7 @@ export const useUpdateContainerChildren = (containerId?: string) => { // container list. const libraryId = getLibraryId(containerId); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); - queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.container(libraryId, containerId) }); + queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.container(containerId) }); }, }); }; diff --git a/src/library-authoring/units/LibraryUnitPage.test.tsx b/src/library-authoring/units/LibraryUnitPage.test.tsx index 916809b2ae..daa63b2301 100644 --- a/src/library-authoring/units/LibraryUnitPage.test.tsx +++ b/src/library-authoring/units/LibraryUnitPage.test.tsx @@ -3,7 +3,6 @@ import type MockAdapter from 'axios-mock-adapter'; import { act } from 'react'; import { - fireEvent, initializeMocks, fireEvent, render, @@ -11,7 +10,7 @@ import { waitFor, within, } from '../../testUtils'; -import { getLibraryContainerApiUrl } from '../data/api'; +import { getLibraryContainerApiUrl, getLibraryContainerChildrenApiUrl } from '../data/api'; import { mockContentLibrary, mockXBlockFields, @@ -22,16 +21,13 @@ import { import { mockContentSearchConfig, mockGetBlockTypes } from '../../search-manager/data/api.mock'; import { mockClipboardEmpty } from '../../generic/data/api.mock'; import LibraryLayout from '../LibraryLayout'; -import { getLibraryContainerChildrenApiUrl } from '../data/api'; import { ToastActionData } from '../../generic/toast-context'; const path = '/library/:libraryId/*'; const libraryTitle = mockContentLibrary.libraryData.title; -let axiosMock: import('axios-mock-adapter/types'); -let mockShowToast: (message: string, action?: ToastActionData | undefined) => void; let axiosMock: MockAdapter; -let mockShowToast: (message: string) => void; +let mockShowToast: (message: string, action?: ToastActionData | undefined) => void; mockClipboardEmpty.applyMock(); mockGetContainerMetadata.applyMock();