From 5af5916b9ef108bd1a30886e3bdde0fbe2706579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Tue, 28 Apr 2026 15:59:33 -0300 Subject: [PATCH 1/2] feat: make new unit outline design default and fix sidebar for Content Experiments --- .env | 3 +- .env.development | 1 - .env.test | 3 +- src/course-unit/CourseUnit.test.tsx | 71 +++++++++++++++++-- .../HeaderNavigations.test.tsx | 5 -- .../header-title/HeaderTitle.test.tsx | 13 ++++ .../unit-sidebar/UnitSidebarContext.tsx | 8 ++- .../unit-sidebar/UnitSidebarPagesContext.tsx | 16 +++-- src/course-unit/utils.ts | 2 +- src/index.jsx | 3 +- 10 files changed, 101 insertions(+), 24 deletions(-) diff --git a/.env b/.env index 7b9229b9a3..9ed2d75cca 100644 --- a/.env +++ b/.env @@ -37,8 +37,7 @@ ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=false ENABLE_TAGGING_TAXONOMY_PAGES=true ENABLE_CERTIFICATE_PAGE=true ENABLE_COURSE_IMPORT_IN_LIBRARY=false -ENABLE_UNIT_PAGE_NEW_DESIGN=false -ENABLE_COURSE_OUTLINE_NEW_DESIGN=false +ENABLE_UNIT_PAGE_NEW_DESIGN=true BBB_LEARN_MORE_URL='' HOTJAR_APP_ID='' HOTJAR_VERSION=6 diff --git a/.env.development b/.env.development index 9e25a53b35..d2d4e86357 100644 --- a/.env.development +++ b/.env.development @@ -38,7 +38,6 @@ ENABLE_ASSETS_PAGE=false ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=true ENABLE_CERTIFICATE_PAGE=true ENABLE_COURSE_IMPORT_IN_LIBRARY=true -ENABLE_COURSE_OUTLINE_NEW_DESIGN=true ENABLE_UNIT_PAGE_NEW_DESIGN=true ENABLE_NEW_VIDEO_UPLOAD_PAGE=true ENABLE_TAGGING_TAXONOMY_PAGES=true diff --git a/.env.test b/.env.test index 617eacb32f..eef08000c5 100644 --- a/.env.test +++ b/.env.test @@ -34,8 +34,7 @@ ENABLE_ASSETS_PAGE=false ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=true ENABLE_CERTIFICATE_PAGE=true ENABLE_COURSE_IMPORT_IN_LIBRARY=true -ENABLE_COURSE_OUTLINE_NEW_DESIGN=false -ENABLE_UNIT_PAGE_NEW_DESIGN=false +ENABLE_UNIT_PAGE_NEW_DESIGN=true ENABLE_TAGGING_TAXONOMY_PAGES=true BBB_LEARN_MORE_URL='' INVITE_STUDENTS_EMAIL_TO="someone@domain.com" diff --git a/src/course-unit/CourseUnit.test.tsx b/src/course-unit/CourseUnit.test.tsx index 67060ecdf9..3f54223112 100644 --- a/src/course-unit/CourseUnit.test.tsx +++ b/src/course-unit/CourseUnit.test.tsx @@ -149,6 +149,7 @@ const RootWrapper = () => ( describe('', () => { beforeEach(async () => { const mocks = initializeMocks(); + window.scrollTo = jest.fn(); global.localStorage.clear(); store = mocks.reduxStore; @@ -180,6 +181,10 @@ describe('', () => { }); it('render CourseUnit component correctly', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); const currentSectionName = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[1].display_name; const currentSubSectionName = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[1].display_name; @@ -271,6 +276,10 @@ describe('', () => { }); it('closes legacy edit modal and updates course unit sidebar after saveEditedXBlockData message', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); const xblocksIframe = await screen.findByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); @@ -308,6 +317,10 @@ describe('', () => { }); it('updates course unit sidebar after receiving refreshPositions message', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); const xblocksIframe = await screen.findByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); @@ -341,6 +354,10 @@ describe('', () => { it('checks whether xblock is removed when the corresponding delete button is clicked and the sidebar is the updated', async () => { const user = userEvent.setup(); + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); const iframe = await screen.findByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); @@ -513,6 +530,10 @@ describe('', () => { }); it('checks if xblock is a duplicate when the corresponding duplicate button is clicked and if the sidebar status is updated', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); axiosMock .onGet(getCourseSectionVerticalApiUrl(blockId)) .reply(200, { @@ -1130,6 +1151,10 @@ describe('', () => { }); it('renders course unit details for a draft with unpublished changes', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); await waitFor(() => { @@ -1206,6 +1231,10 @@ describe('', () => { it('should toggle visibility from sidebar and update course unit state accordingly', async () => { const user = userEvent.setup(); + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); const courseUnitSidebar = await screen.findByTestId('course-unit-sidebar'); @@ -1298,6 +1327,10 @@ describe('', () => { it('should publish course unit after click on the "Publish" button', async () => { const user = userEvent.setup(); + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); let courseUnitSidebar; let publishBtn; @@ -1350,6 +1383,10 @@ describe('', () => { it('should discard changes after click on the Discard changes button', async () => { const user = userEvent.setup(); + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); let courseUnitSidebar; let discardChangesBtn; @@ -1425,6 +1462,10 @@ describe('', () => { }); it('should toggle visibility from header configure modal and update course unit state accordingly', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); const user = userEvent.setup(); render(); expect( @@ -1524,6 +1565,13 @@ describe('', () => { }); describe('Copy paste functionality', () => { + beforeEach(() => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); + }); + it('should copy a unit, paste it as a new unit, and update the course section vertical data', async () => { const user = userEvent.setup(); render(); @@ -2320,17 +2368,31 @@ describe('', () => { }); }); - it('should display visibility modal correctly', async () => ( - checkRenderVisibilityModal('libraryContentAccess') - )); + it('should display visibility modal correctly', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); + await checkRenderVisibilityModal('libraryContentAccess'); + }); - it('opens legacy edit modal on edit button click', checkLegacyEditModalOnEditMessage); + it('opens legacy edit modal on edit button click', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); + await checkLegacyEditModalOnEditMessage(); + }); }); describe('Split Test Content page', () => { const newUnitId = '12345'; beforeEach(async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); axiosMock .onGet(getCourseSectionVerticalApiUrl(blockId)) .reply(200, { @@ -2524,6 +2586,7 @@ describe('', () => { setConfig({ ...getConfig(), ENABLE_TAGGING_TAXONOMY_PAGES: 'true', + ENABLE_UNIT_PAGE_NEW_DESIGN: false, }); render(); diff --git a/src/course-unit/header-navigations/HeaderNavigations.test.tsx b/src/course-unit/header-navigations/HeaderNavigations.test.tsx index 00ddcb71bd..12394aa3fb 100644 --- a/src/course-unit/header-navigations/HeaderNavigations.test.tsx +++ b/src/course-unit/header-navigations/HeaderNavigations.test.tsx @@ -108,11 +108,6 @@ describe('', () => { }); it('click Add button should open add sidebar', async () => { - setConfig({ - ...getConfig(), - ENABLE_UNIT_PAGE_NEW_DESIGN: 'true', - }); - const user = userEvent.setup(); renderComponent({ unitCategory: COURSE_BLOCK_NAMES.vertical.id }); diff --git a/src/course-unit/header-title/HeaderTitle.test.tsx b/src/course-unit/header-title/HeaderTitle.test.tsx index 36281e5c8e..ef7511e3bf 100644 --- a/src/course-unit/header-title/HeaderTitle.test.tsx +++ b/src/course-unit/header-title/HeaderTitle.test.tsx @@ -1,5 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { getConfig, setConfig } from '@edx/frontend-platform'; import { initializeMocks, render, screen } from '@src/testUtils'; import userEvent from '@testing-library/user-event'; import { executeThunk } from '@src/utils'; @@ -47,6 +48,10 @@ describe('', () => { }); it('render HeaderTitle component correctly', () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); renderComponent(); expect(screen.getByText(unitTitle)).toBeInTheDocument(); @@ -55,6 +60,10 @@ describe('', () => { }); it('render HeaderTitle with open edit form', () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); renderComponent({ isTitleEditFormOpen: true, }); @@ -66,6 +75,10 @@ describe('', () => { }); it('Units sourced from upstream show a enabled edit button', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); // Override mock unit with one sourced from an upstream library axiosMock = new MockAdapter(getAuthenticatedHttpClient()); axiosMock diff --git a/src/course-unit/unit-sidebar/UnitSidebarContext.tsx b/src/course-unit/unit-sidebar/UnitSidebarContext.tsx index e8e76c4b7f..34517a0327 100644 --- a/src/course-unit/unit-sidebar/UnitSidebarContext.tsx +++ b/src/course-unit/unit-sidebar/UnitSidebarContext.tsx @@ -29,9 +29,10 @@ interface UnitSidebarContextData { readOnly: boolean; /* * There are other blocks that use the same unit screen and sidebars. - * For example: Conditional block. + * For example: Conditional block, Content Experiments block. */ isVertical: boolean; + currentItemCategory?: string; } const UnitSidebarContext = createContext(undefined); @@ -55,7 +56,8 @@ export const UnitSidebarProvider = ({ const [isOpen, open, , toggle] = useToggle(true); const currentItemData = useSelector(getCourseUnitData); - const isVertical = currentItemData?.category === 'vertical'; + const currentItemCategory = currentItemData?.category; + const isVertical = currentItemCategory === 'vertical'; const setCurrentPageKey = useCallback(/* istanbul ignore next */ ( pageKey?: UnitSidebarPageKeys, @@ -92,6 +94,7 @@ export const UnitSidebarProvider = ({ toggle, readOnly, isVertical, + currentItemCategory, }), [ currentPageKey, @@ -105,6 +108,7 @@ export const UnitSidebarProvider = ({ toggle, readOnly, isVertical, + currentItemCategory, ], ); diff --git a/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx b/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx index 3054f622c0..5014af611f 100644 --- a/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx +++ b/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx @@ -16,7 +16,7 @@ export type UnitSidebarPages = { align?: SidebarPage; }; -const getUnitSidebarPages = (readOnly: boolean, hasComponentSelected: boolean) => { +const getUnitSidebarPages = (readOnly: boolean, disableAdd: boolean) => { const showAlignSidebar = getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true'; return { @@ -30,8 +30,8 @@ const getUnitSidebarPages = (readOnly: boolean, hasComponentSelected: boolean) = component: AddSidebar, icon: Plus, title: messages.sidebarButtonAdd, - disabled: hasComponentSelected, - tooltip: hasComponentSelected ? messages.sidebarDisabledAddTooltip : undefined, + disabled: disableAdd, + tooltip: disableAdd ? messages.sidebarDisabledAddTooltip : undefined, }, }), ...(showAlignSidebar && { @@ -81,12 +81,18 @@ type UnitSidebarPagesProviderProps = { }; export const UnitSidebarPagesProvider = ({ children }: UnitSidebarPagesProviderProps) => { - const { readOnly, selectedComponentId } = useUnitSidebarContext(); + const { + readOnly, + selectedComponentId, + currentItemCategory, + } = useUnitSidebarContext(); const hasComponentSelected = selectedComponentId !== undefined; + const isSplitTest = currentItemCategory === 'split_test'; + const disableAdd = hasComponentSelected || isSplitTest; const sidebarPages = useMemo( - () => getUnitSidebarPages(readOnly, hasComponentSelected), + () => getUnitSidebarPages(readOnly, disableAdd), [readOnly, hasComponentSelected], ); diff --git a/src/course-unit/utils.ts b/src/course-unit/utils.ts index 973f053686..cb15e6a87f 100644 --- a/src/course-unit/utils.ts +++ b/src/course-unit/utils.ts @@ -47,5 +47,5 @@ export const subsectionFirstUnitEditUrl = ( }; export const isUnitPageNewDesignEnabled = () => ( - getConfig().ENABLE_UNIT_PAGE_NEW_DESIGN?.toString().toLowerCase() === 'true' + (getConfig().ENABLE_UNIT_PAGE_NEW_DESIGN?.toString().toLowerCase() ?? 'true') === 'true' ); diff --git a/src/index.jsx b/src/index.jsx index b62a9e58cf..4d4d6d2c73 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -184,8 +184,7 @@ initialize({ process.env.ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN || 'false', ENABLE_CERTIFICATE_PAGE: process.env.ENABLE_CERTIFICATE_PAGE || 'false', ENABLE_COURSE_IMPORT_IN_LIBRARY: process.env.ENABLE_COURSE_IMPORT_IN_LIBRARY || 'false', - ENABLE_UNIT_PAGE_NEW_DESIGN: process.env.ENABLE_UNIT_PAGE_NEW_DESIGN || 'false', - ENABLE_COURSE_OUTLINE_NEW_DESIGN: process.env.ENABLE_COURSE_OUTLINE_NEW_DESIGN || 'false', + ENABLE_UNIT_PAGE_NEW_DESIGN: process.env.ENABLE_UNIT_PAGE_NEW_DESIGN || 'true', ENABLE_TAGGING_TAXONOMY_PAGES: process.env.ENABLE_TAGGING_TAXONOMY_PAGES || 'false', ENABLE_CHECKLIST_QUALITY: process.env.ENABLE_CHECKLIST_QUALITY || 'true', ENABLE_GRADING_METHOD_IN_PROBLEMS: process.env.ENABLE_GRADING_METHOD_IN_PROBLEMS === 'true', From fe9bfa6a0ad468c42c44a487c1c5fe8bdebbcd13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Wed, 29 Apr 2026 17:24:11 -0300 Subject: [PATCH 2/2] fix: fixes from review Co-authored-by: Navin Karkera --- src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx b/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx index 5014af611f..18b821a841 100644 --- a/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx +++ b/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx @@ -93,7 +93,7 @@ export const UnitSidebarPagesProvider = ({ children }: UnitSidebarPagesProviderP const sidebarPages = useMemo( () => getUnitSidebarPages(readOnly, disableAdd), - [readOnly, hasComponentSelected], + [readOnly, disableAdd], ); return (