diff --git a/src/course-outline/OutlineAddChildButtons.test.tsx b/src/course-outline/OutlineAddChildButtons.test.tsx index ba1237eba3..cf58b8680b 100644 --- a/src/course-outline/OutlineAddChildButtons.test.tsx +++ b/src/course-outline/OutlineAddChildButtons.test.tsx @@ -38,12 +38,14 @@ jest.mock('@src/course-outline/CourseOutlineContext', () => ({ })); const startCurrentFlow = jest.fn(); +const openContainerInfoSidebar = jest.fn(); let currentFlow: OutlineFlow | null = null; jest.mock('@src/course-outline/outline-sidebar/OutlineSidebarContext', () => ({ ...jest.requireActual('@src/course-outline/outline-sidebar/OutlineSidebarContext'), useOutlineSidebarContext: () => ({ ...jest.requireActual('@src/course-outline/outline-sidebar/OutlineSidebarContext').useOutlineSidebarContext(), startCurrentFlow, + openContainerInfoSidebar, currentFlow, isCurrentFlowOn: !!currentFlow, }), @@ -104,21 +106,35 @@ jest.mock('@src/course-outline/outline-sidebar/OutlineSidebarContext', () => ({ switch (containerType) { case ContainerType.Section: await waitFor(() => - expect(handleAddBlock.mutateAsync).toHaveBeenCalledWith({ - type: ContainerType.Chapter, - parentLocator: courseUsageKey, - displayName: 'Section', - }) + expect(handleAddBlock.mutateAsync).toHaveBeenCalledWith( + { + type: ContainerType.Chapter, + parentLocator: courseUsageKey, + displayName: 'Section', + }, + expect.objectContaining({ onSuccess: expect.any(Function) }), + ) ); + handleAddBlock.mutateAsync.mock.calls[0][1].onSuccess({ locator: 'new-section-id' }); + expect(openContainerInfoSidebar).toHaveBeenCalledWith('new-section-id', undefined, 'new-section-id'); break; case ContainerType.Subsection: await waitFor(() => - expect(handleAddBlock.mutateAsync).toHaveBeenCalledWith({ - type: ContainerType.Sequential, - parentLocator, - displayName: 'Subsection', - sectionId: parentLocator, - }) + expect(handleAddBlock.mutateAsync).toHaveBeenCalledWith( + { + type: ContainerType.Sequential, + parentLocator, + displayName: 'Subsection', + sectionId: parentLocator, + }, + expect.objectContaining({ onSuccess: expect.any(Function) }), + ) + ); + handleAddBlock.mutateAsync.mock.calls[0][1].onSuccess({ locator: 'new-subsection-id' }); + expect(openContainerInfoSidebar).toHaveBeenCalledWith( + 'new-subsection-id', + 'new-subsection-id', + parentLocator, ); break; case ContainerType.Unit: diff --git a/src/course-outline/OutlineAddChildButtons.tsx b/src/course-outline/OutlineAddChildButtons.tsx index 9f4134e6bb..b3d5115f8e 100644 --- a/src/course-outline/OutlineAddChildButtons.tsx +++ b/src/course-outline/OutlineAddChildButtons.tsx @@ -114,7 +114,7 @@ const NewOutlineAddChildButtons = ({ handleAddBlock, handleAddAndOpenUnit, } = useCourseOutlineContext(); - const { startCurrentFlow } = useOutlineSidebarContext(); + const { startCurrentFlow, openContainerInfoSidebar } = useOutlineSidebarContext(); let messageMap = { newButton: messages.newUnitButton, importButton: messages.useUnitFromLibraryButton, @@ -134,6 +134,10 @@ const NewOutlineAddChildButtons = ({ type: ContainerType.Chapter, parentLocator: courseUsageKey, displayName: COURSE_BLOCK_NAMES.chapter.name, + }, { + onSuccess: (data: { locator: string; }) => { + openContainerInfoSidebar(data.locator, undefined, data.locator); + }, }); flowType = ContainerType.Section; break; @@ -148,6 +152,10 @@ const NewOutlineAddChildButtons = ({ parentLocator, displayName: COURSE_BLOCK_NAMES.sequential.name, sectionId: parentLocator, + }, { + onSuccess: (data: { locator: string; }) => { + openContainerInfoSidebar(data.locator, data.locator, parentLocator); + }, }); flowType = ContainerType.Subsection; break; diff --git a/src/course-outline/card-header/CardHeader.scss b/src/course-outline/card-header/CardHeader.scss index ffa8712a7c..cf4a0c6875 100644 --- a/src/course-outline/card-header/CardHeader.scss +++ b/src/course-outline/card-header/CardHeader.scss @@ -6,7 +6,7 @@ justify-content: flex-start; padding: 0; flex: 1 1 0; - height: 1.5rem; + height: 1.75rem; margin-right: .25rem; background: transparent; color: var(--pgn-color-black); diff --git a/src/course-outline/outline-sidebar/AddSidebar.tsx b/src/course-outline/outline-sidebar/AddSidebar.tsx index 30c9f1903d..edc8f1991d 100644 --- a/src/course-outline/outline-sidebar/AddSidebar.tsx +++ b/src/course-outline/outline-sidebar/AddSidebar.tsx @@ -224,6 +224,7 @@ const ShowLibraryContent = () => { lastEditableSubsection, selectedContainerState, currentItemData, + openContainerInfoSidebar, } = useOutlineSidebarContext(); let sectionParentId = lastEditableSection?.id; @@ -237,6 +238,11 @@ const ShowLibraryContent = () => { category: ContainerType.Chapter, parentLocator: courseUsageKey, libraryContentKey: usageKey, + }, { + onSuccess: (data: { locator: string; }) => { + // istanbul ignore next + openContainerInfoSidebar(data.locator, undefined, data.locator); + }, }); break; case 'subsection': @@ -248,6 +254,11 @@ const ShowLibraryContent = () => { parentLocator: sectionParentId, libraryContentKey: usageKey, sectionId: sectionParentId, + }, { + onSuccess: (data: { locator: string; }) => { + // istanbul ignore next + openContainerInfoSidebar(data.locator, data.locator, sectionParentId); + }, }); } break; @@ -261,6 +272,11 @@ const ShowLibraryContent = () => { parentLocator: subsectionParentId, libraryContentKey: usageKey, sectionId: sectionParentId, + }, { + onSuccess: (data: { locator: string; }) => { + // istanbul ignore next + openContainerInfoSidebar(data.locator, subsectionParentId, sectionParentId); + }, }); } break; diff --git a/src/course-unit/CourseUnit.tsx b/src/course-unit/CourseUnit.tsx index 1e27ae9082..07b9d7d055 100644 --- a/src/course-unit/CourseUnit.tsx +++ b/src/course-unit/CourseUnit.tsx @@ -310,6 +310,7 @@ const CourseUnit = () => { headerNavigationsActions={headerNavigationsActions} unitTitle={unitTitle} verticalBlocks={courseVerticalChildren.children} + isPublished={courseUnit.published} /> } /> diff --git a/src/course-unit/header-navigations/HeaderNavigations.test.tsx b/src/course-unit/header-navigations/HeaderNavigations.test.tsx index 192d1c050d..00ddcb71bd 100644 --- a/src/course-unit/header-navigations/HeaderNavigations.test.tsx +++ b/src/course-unit/header-navigations/HeaderNavigations.test.tsx @@ -79,6 +79,18 @@ describe('', () => { }); }); + it('should enable the "View Live" button when isPublished is true', () => { + renderComponent({ unitCategory: COURSE_BLOCK_NAMES.vertical.id, isPublished: true }); + + expect(screen.getByRole('button', { name: messages.viewLiveButton.defaultMessage })).not.toBeDisabled(); + }); + + it('should disable the "View Live" button when isPublished is false', () => { + renderComponent({ unitCategory: COURSE_BLOCK_NAMES.vertical.id, isPublished: false }); + + expect(screen.getByRole('button', { name: messages.viewLiveButton.defaultMessage })).toBeDisabled(); + }); + it('click Info button should open info sidebar', async () => { setConfig({ ...getConfig(), diff --git a/src/course-unit/header-navigations/HeaderNavigations.tsx b/src/course-unit/header-navigations/HeaderNavigations.tsx index de7d0c997e..a8e41153a3 100644 --- a/src/course-unit/header-navigations/HeaderNavigations.tsx +++ b/src/course-unit/header-navigations/HeaderNavigations.tsx @@ -16,7 +16,7 @@ import messages from './messages'; import { isUnitPageNewDesignEnabled } from '../utils'; import { useUnitSidebarContext } from '../unit-sidebar/UnitSidebarContext'; -type HeaderNavigationActions = { +export type HeaderNavigationActions = { handleViewLive: () => void; handlePreview: () => void; handleEdit: () => void; @@ -25,6 +25,7 @@ type HeaderNavigationActions = { type HeaderNavigationsProps = { headerNavigationsActions: HeaderNavigationActions; category: string; + isPublished?: boolean; }; /** @@ -33,7 +34,11 @@ type HeaderNavigationsProps = { * - Legacy library content page * - Split test page */ -const HeaderNavigations = ({ headerNavigationsActions, category }: HeaderNavigationsProps) => { +const HeaderNavigations = ({ + headerNavigationsActions, + category, + isPublished = true, +}: HeaderNavigationsProps) => { const intl = useIntl(); const { handleViewLive, @@ -82,9 +87,11 @@ const HeaderNavigations = ({ headerNavigationsActions, category }: HeaderNavigat > {intl.formatMessage(messages.previewButton)} + {/* TODO: convert to since it navigates to a URL */} {intl.formatMessage(messages.viewLiveButton)} diff --git a/src/generic/sidebar/BlockCardButton.tsx b/src/generic/sidebar/BlockCardButton.tsx index 960e109d26..a1ce3155ab 100644 --- a/src/generic/sidebar/BlockCardButton.tsx +++ b/src/generic/sidebar/BlockCardButton.tsx @@ -51,7 +51,7 @@ export const BlockCardButton = ({ @@ -69,7 +69,7 @@ export const BlockCardButton = ({ return ( diff --git a/src/generic/sidebar/Sidebar.tsx b/src/generic/sidebar/Sidebar.tsx index 82112b91cb..ae3c0e3cc7 100644 --- a/src/generic/sidebar/Sidebar.tsx +++ b/src/generic/sidebar/Sidebar.tsx @@ -137,6 +137,7 @@ export function Sidebar({ alt={intl.formatMessage(messages.toggle)} onClick={toggle} variant="primary" + className="mb-2" /> { - const isUnitVerticalType = category === COURSE_BLOCK_NAMES.vertical.id; - return ( - - - - ); -}; - -CourseUnitHeaderActionsSlot.propTypes = { - headerNavigationsActions: PropTypes.shape({ - handleViewLive: PropTypes.func.isRequired, - handlePreview: PropTypes.func.isRequired, - handleEdit: PropTypes.func.isRequired, - }).isRequired, - category: PropTypes.string.isRequired, - unitTitle: PropTypes.string.isRequired, - verticalBlocks: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - blockId: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - blockType: PropTypes.string.isRequired, - }), - ).isRequired, -}; - -export default CourseUnitHeaderActionsSlot; diff --git a/src/plugin-slots/CourseUnitHeaderActionsSlot/index.test.tsx b/src/plugin-slots/CourseUnitHeaderActionsSlot/index.test.tsx index c55fe17da1..82f12fa1fa 100644 --- a/src/plugin-slots/CourseUnitHeaderActionsSlot/index.test.tsx +++ b/src/plugin-slots/CourseUnitHeaderActionsSlot/index.test.tsx @@ -21,6 +21,7 @@ const headerNavProps = { unitTitle: 'Mock Unit', isUnitVerticalType: false, verticalBlocks: [], + isPublished: true, }; describe('CourseUnitHeaderActionsSlot', () => { @@ -40,6 +41,7 @@ describe('CourseUnitHeaderActionsSlot', () => { verticalBlocks={[]} category="library" headerNavigationsActions={headerNavProps.headerNavigationsActions} + isPublished />, ); expect(component.toJSON().props.pluginProps.isUnitVerticalType).toEqual(false); @@ -50,6 +52,7 @@ describe('CourseUnitHeaderActionsSlot', () => { verticalBlocks={[]} category={COURSE_BLOCK_NAMES.vertical.id} headerNavigationsActions={headerNavProps.headerNavigationsActions} + isPublished />, ); expect(component.toJSON().props.pluginProps.isUnitVerticalType).toEqual(true); diff --git a/src/plugin-slots/CourseUnitHeaderActionsSlot/index.tsx b/src/plugin-slots/CourseUnitHeaderActionsSlot/index.tsx new file mode 100644 index 0000000000..e52c8ba2fc --- /dev/null +++ b/src/plugin-slots/CourseUnitHeaderActionsSlot/index.tsx @@ -0,0 +1,51 @@ +import { PluginSlot } from '@openedx/frontend-plugin-framework'; + +import HeaderNavigations, { + HeaderNavigationActions, +} from 'CourseAuthoring/course-unit/header-navigations/HeaderNavigations'; +import { COURSE_BLOCK_NAMES } from 'CourseAuthoring/constants'; + +export interface CourseUnitHeaderActionsSlotProps { + headerNavigationsActions: HeaderNavigationActions; + category: string; + unitTitle: string; + verticalBlocks: { + id: string; + blockId: string; + name: string; + blockType: string; + }[]; + isPublished: boolean; +} + +const CourseUnitHeaderActionsSlot = ({ + headerNavigationsActions, + category, + unitTitle, + verticalBlocks, + isPublished, +}: CourseUnitHeaderActionsSlotProps) => { + const isUnitVerticalType = category === COURSE_BLOCK_NAMES.vertical.id; + return ( + + + + ); +}; + +export default CourseUnitHeaderActionsSlot;