Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [FC-0070] rendering split test content in unit page #1492

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const COURSE_BLOCK_NAMES = ({
sequential: { id: 'sequential', name: 'Subsection' },
vertical: { id: 'vertical', name: 'Unit' },
libraryContent: { id: 'library_content', name: 'Library content' },
splitTest: { id: 'split_test', name: 'Split Test' },
component: { id: 'component', name: 'Component' },
});

Expand Down
15 changes: 13 additions & 2 deletions src/course-unit/CourseUnit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import Breadcrumbs from './breadcrumbs/Breadcrumbs';
import HeaderNavigations from './header-navigations/HeaderNavigations';
import Sequence from './course-sequence';
import Sidebar from './sidebar';
import SplitTestSidebarInfo from './sidebar/SplitTestSidebarInfo';
import { useCourseUnit, useLayoutGrid } from './hooks';
import messages from './messages';
import PublishControls from './sidebar/PublishControls';
Expand All @@ -52,6 +53,7 @@ const CourseUnit = ({ courseId }) => {
isTitleEditFormOpen,
isUnitVerticalType,
isUnitLibraryType,
isSplitTestType,
staticFileNotices,
currentlyVisibleToStudents,
unitXBlockActions,
Expand All @@ -72,6 +74,7 @@ const CourseUnit = ({ courseId }) => {
handleRollbackMovedXBlock,
handleCloseXBlockMovedAlert,
handleNavigateToTargetUnit,
addComponentTemplateData,
} = useCourseUnit({ courseId, blockId });
const layoutGrid = useLayoutGrid(unitCategory, isUnitLibraryType);

Expand Down Expand Up @@ -153,7 +156,7 @@ const CourseUnit = ({ courseId }) => {
)}
headerActions={(
<HeaderNavigations
unitCategory={unitCategory}
category={unitCategory}
headerNavigationsActions={headerNavigationsActions}
/>
)}
Expand Down Expand Up @@ -186,14 +189,17 @@ const CourseUnit = ({ courseId }) => {
<XBlockContainerIframe
courseId={courseId}
blockId={blockId}
isUnitVerticalType={isUnitVerticalType}
unitXBlockActions={unitXBlockActions}
courseVerticalChildren={courseVerticalChildren.children}
handleConfigureSubmit={handleConfigureSubmit}
/>
{isUnitVerticalType && (
<AddComponent
blockId={blockId}
parentLocator={blockId}
isUnitVerticalType={isUnitVerticalType}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
addComponentTemplateData={addComponentTemplateData}
/>
)}
{showPasteXBlock && canPasteComponent && isUnitVerticalType && (
Expand Down Expand Up @@ -228,6 +234,11 @@ const CourseUnit = ({ courseId }) => {
</Sidebar>
</>
)}
{isSplitTestType && (
<Sidebar data-testid="course-split-test-sidebar">
<SplitTestSidebarInfo />
</Sidebar>
)}
</Stack>
</Layout.Element>
</Layout>
Expand Down
1 change: 1 addition & 0 deletions src/course-unit/CourseUnit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
@import "./header-title/HeaderTitle";
@import "./move-modal";
@import "./preview-changes";
@import "./xblock-container-iframe";

.course-unit {
min-width: 900px;
Expand Down
190 changes: 185 additions & 5 deletions src/course-unit/CourseUnit.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,8 @@ import { executeThunk } from '../utils';
import { IFRAME_FEATURE_POLICY } from '../constants';
import pasteComponentMessages from '../generic/clipboard/paste-component/messages';
import pasteNotificationsMessages from './clipboard/paste-notification/messages';
import headerNavigationsMessages from './header-navigations/messages';
import headerTitleMessages from './header-title/messages';
import courseSequenceMessages from './course-sequence/messages';
import sidebarMessages from './sidebar/messages';
import { extractCourseUnitId } from './sidebar/utils';
import CourseUnit from './CourseUnit';

Expand All @@ -62,6 +60,8 @@ import { messageTypes, PUBLISH_TYPES, UNIT_VISIBILITY_STATES } from './constants
import { IframeProvider } from './context/iFrameContext';
import moveModalMessages from './move-modal/messages';
import xblockContainerIframeMessages from './xblock-container-iframe/messages';
import headerNavigationsMessages from './header-navigations/messages';
import sidebarMessages from './sidebar/messages';
import messages from './messages';

let axiosMock;
Expand Down Expand Up @@ -212,7 +212,7 @@ describe('<CourseUnit />', () => {
const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage);
expect(iframe).toHaveAttribute('src', `${getConfig().STUDIO_BASE_URL}/container_embed/${blockId}`);
expect(iframe).toHaveAttribute('allow', IFRAME_FEATURE_POLICY);
expect(iframe).toHaveAttribute('style', 'width: 100%; height: 0px;');
expect(iframe).toHaveAttribute('style', 'height: 0px;');
expect(iframe).toHaveAttribute('scrolling', 'no');
expect(iframe).toHaveAttribute('referrerpolicy', 'origin');
expect(iframe).toHaveAttribute('loading', 'lazy');
Expand All @@ -225,11 +225,11 @@ describe('<CourseUnit />', () => {

await waitFor(() => {
const iframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage);
expect(iframe).toHaveAttribute('style', 'width: 100%; height: 0px;');
expect(iframe).toHaveAttribute('style', 'height: 0px;');
simulatePostMessageEvent(messageTypes.toggleCourseXBlockDropdown, {
courseXBlockDropdownHeight: 200,
});
expect(iframe).toHaveAttribute('style', 'width: 100%; height: 200px;');
expect(iframe).toHaveAttribute('style', 'height: 200px;');
});
});

Expand Down Expand Up @@ -1959,6 +1959,46 @@ describe('<CourseUnit />', () => {
});
});

const checkLegacyEditModalOnEditMessage = async () => {
const { getByTitle, getByTestId } = render(<RootWrapper />);

await waitFor(() => {
const editButton = getByTestId('header-edit-button');
expect(editButton).toBeInTheDocument();
const xblocksIframe = getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage);
expect(xblocksIframe).toBeInTheDocument();
userEvent.click(editButton);
});
};

const checkRenderVisibilityModal = async (headingMessageId) => {
const { getByRole, getByTestId } = render(<RootWrapper />);
let configureModal;
let restrictAccessSelect;

await waitFor(() => {
const headerConfigureBtn = getByRole('button', { name: /settings/i });
expect(headerConfigureBtn).toBeInTheDocument();

userEvent.click(headerConfigureBtn);
configureModal = getByTestId('configure-modal');
restrictAccessSelect = within(configureModal)
.getByRole('combobox', { name: configureModalMessages.restrictAccessTo.defaultMessage });
expect(within(configureModal)
.getByRole('heading', { name: configureModalMessages[headingMessageId].defaultMessage })).toBeInTheDocument();
expect(within(configureModal)
.queryByText(configureModalMessages.unitVisibility.defaultMessage)).not.toBeInTheDocument();
expect(within(configureModal)
.getByText(configureModalMessages.restrictAccessTo.defaultMessage)).toBeInTheDocument();
expect(restrictAccessSelect).toBeInTheDocument();
expect(restrictAccessSelect).toHaveValue('-1');
});

const modalSaveBtn = within(configureModal)
.getByRole('button', { name: configureModalMessages.saveButton.defaultMessage });
userEvent.click(modalSaveBtn);
};

describe('Library Content page', () => {
const newUnitId = '12345';
const sequenceId = courseSectionVerticalMock.subsection_location;
Expand All @@ -1978,6 +2018,20 @@ describe('<CourseUnit />', () => {
},
});
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
axiosMock
.onGet(getCourseUnitApiUrl(courseId))
.reply(200, {
...courseUnitIndexMock,
category: 'library_content',
ancestor_info: {
...courseUnitIndexMock.ancestor_info,
child_info: {
...courseUnitIndexMock.ancestor_info.child_info,
category: 'library_content',
},
},
});
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
});

it('navigates to library content page on receive window event', () => {
Expand Down Expand Up @@ -2014,5 +2068,131 @@ describe('<CourseUnit />', () => {
expect(queryByRole('heading', { name: /unit location/i })).not.toBeInTheDocument();
});
});

it('should display visibility modal correctly', async () => (
checkRenderVisibilityModal('libraryContentAccess')
));

it('opens legacy edit modal on edit button click', checkLegacyEditModalOnEditMessage);
});

describe('Split Test Content page', () => {
const newUnitId = '12345';
const sequenceId = courseSectionVerticalMock.subsection_location;

beforeEach(async () => {
axiosMock
.onGet(getCourseSectionVerticalApiUrl(blockId))
.reply(200, {
...courseSectionVerticalMock,
xblock: {
...courseSectionVerticalMock.xblock,
category: 'split_test',
},
xblock_info: {
...courseSectionVerticalMock.xblock_info,
category: 'split_test',
},
});
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
axiosMock
.onGet(getCourseUnitApiUrl(courseId))
.reply(200, {
...courseUnitIndexMock,
category: 'split_test',
ancestor_info: {
...courseUnitIndexMock.ancestor_info,
child_info: {
...courseUnitIndexMock.ancestor_info.child_info,
category: 'split_test',
},
},
});
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
});

it('navigates to split test content page on receive window event', () => {
render(<RootWrapper />);

simulatePostMessageEvent(messageTypes.handleViewXBlockContent, { usageId: newUnitId });
expect(mockedUsedNavigate).toHaveBeenCalledWith(`/course/${courseId}/container/${newUnitId}/${sequenceId}`);
});

it('displays processing notification on receiving post message', async () => {
const { getByText, queryByText } = render(<RootWrapper />);

await waitFor(() => {
simulatePostMessageEvent(messageTypes.addNewComponent);
expect(getByText(('Adding'))).toBeInTheDocument();

simulatePostMessageEvent(messageTypes.hideProcessingNotification);
expect(queryByText(('Adding'))).not.toBeInTheDocument();

simulatePostMessageEvent(messageTypes.pasteNewComponent);
expect(getByText(('Pasting'))).toBeInTheDocument();

simulatePostMessageEvent(messageTypes.hideProcessingNotification);
expect(queryByText(('Pasting'))).not.toBeInTheDocument();
});
});

it('should render split test content page correctly', async () => {
const {
getByText,
getByRole,
queryByRole,
getByTestId,
queryByText,
} = render(<RootWrapper />);

const currentSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name;
const currentSubSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name;
const helpLinkUrl = 'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_components.html#components-that-contain-other-components';

await waitFor(() => {
const unitHeaderTitle = getByTestId('unit-header-title');
expect(getByText(unitDisplayName)).toBeInTheDocument();
expect(within(unitHeaderTitle).getByRole('button', { name: headerTitleMessages.altButtonEdit.defaultMessage })).toBeInTheDocument();
expect(within(unitHeaderTitle).getByRole('button', { name: headerTitleMessages.altButtonSettings.defaultMessage })).toBeInTheDocument();
expect(getByRole('button', { name: currentSectionName })).toBeInTheDocument();
expect(getByRole('button', { name: currentSubSectionName })).toBeInTheDocument();

expect(queryByRole('heading', { name: addComponentMessages.title.defaultMessage })).not.toBeInTheDocument();
expect(queryByRole('button', { name: headerNavigationsMessages.viewLiveButton.defaultMessage })).not.toBeInTheDocument();
expect(queryByRole('button', { name: headerNavigationsMessages.previewButton.defaultMessage })).not.toBeInTheDocument();

expect(queryByRole('heading', { name: /unit tags/i })).not.toBeInTheDocument();
expect(queryByRole('heading', { name: /unit location/i })).not.toBeInTheDocument();

// Sidebar
const sidebarContent = [
{ query: queryByRole, type: 'heading', name: sidebarMessages.sidebarSplitTestAddComponentTitle.defaultMessage },
{ query: queryByText, name: sidebarMessages.sidebarSplitTestSelectComponentType.defaultMessage.replaceAll('{bold_tag}', '') },
{ query: queryByText, name: sidebarMessages.sidebarSplitTestComponentAdded.defaultMessage },
{ query: queryByRole, type: 'heading', name: sidebarMessages.sidebarSplitTestEditComponentTitle.defaultMessage },
{ query: queryByText, name: sidebarMessages.sidebarSplitTestEditComponentInstruction.defaultMessage.replaceAll('{bold_tag}', '') },
{ query: queryByRole, type: 'heading', name: sidebarMessages.sidebarSplitTestReorganizeComponentTitle.defaultMessage },
{ query: queryByText, name: sidebarMessages.sidebarSplitTestReorganizeComponentInstruction.defaultMessage },
{ query: queryByText, name: sidebarMessages.sidebarSplitTestReorganizeGroupsInstruction.defaultMessage },
{ query: queryByRole, type: 'heading', name: sidebarMessages.sidebarSplitTestExperimentComponentTitle.defaultMessage },
{ query: queryByText, name: sidebarMessages.sidebarSplitTestExperimentComponentInstruction.defaultMessage },
{ query: queryByRole, type: 'link', name: sidebarMessages.sidebarSplitTestLearnMoreLinkLabel.defaultMessage },
];

sidebarContent.forEach(({ query, type, name }) => {
expect(type ? query(type, { name }) : query(name)).toBeInTheDocument();
});

expect(
queryByRole('link', { name: sidebarMessages.sidebarSplitTestLearnMoreLinkLabel.defaultMessage }),
).toHaveAttribute('href', helpLinkUrl);
});
});

it('should display visibility modal correctly', async () => (
checkRenderVisibilityModal('splitTestAccess')
));

it('opens legacy edit modal on edit button click', checkLegacyEditModalOnEditMessage);
});
});
Loading