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-0044] Unit page - Manage access modal (unit & xblocks) #901

Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ export const DECODED_ROUTES = {
'/container/:blockId',
],
};

export const COURSE_BLOCK_NAMES = ({
chapter: { id: 'chapter', name: 'Section' },
sequential: { id: 'sequential', name: 'Subsection' },
vertical: { id: 'vertical', name: 'Unit' },
component: { id: 'component', name: 'Component' },
});
12 changes: 8 additions & 4 deletions src/course-outline/CourseOutline.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ import SubHeader from '../generic/sub-header/SubHeader';
import ProcessingNotification from '../generic/processing-notification';
import InternetConnectionAlert from '../generic/internet-connection-alert';
import DeleteModal from '../generic/delete-modal/DeleteModal';
import ConfigureModal from '../generic/configure-modal/ConfigureModal';
import AlertMessage from '../generic/alert-message';
import getPageHeadTitle from '../generic/utils';
import { getCurrentItem } from './data/selectors';
import { getCurrentItem, getProctoredExamsFlag } from './data/selectors';
import { COURSE_BLOCK_NAMES } from './constants';
import HeaderNavigations from './header-navigations/HeaderNavigations';
import OutlineSideBar from './outline-sidebar/OutlineSidebar';
Expand All @@ -43,7 +44,6 @@ import UnitCard from './unit-card/UnitCard';
import HighlightsModal from './highlights-modal/HighlightsModal';
import EmptyPlaceholder from './empty-placeholder/EmptyPlaceholder';
import PublishModal from './publish-modal/PublishModal';
import ConfigureModal from './configure-modal/ConfigureModal';
import PageAlerts from './page-alerts/PageAlerts';
import DraggableList from './drag-helper/DraggableList';
import {
Expand Down Expand Up @@ -129,8 +129,10 @@ const CourseOutline = ({ courseId }) => {
title: processingNotificationTitle,
} = useSelector(getProcessingNotification);

const { category } = useSelector(getCurrentItem);
const deleteCategory = COURSE_BLOCK_NAMES[category]?.name.toLowerCase();
const currentItemData = useSelector(getCurrentItem);
const deleteCategory = COURSE_BLOCK_NAMES[currentItemData.category]?.name.toLowerCase();

const enableProctoredExams = useSelector(getProctoredExamsFlag);

/**
* Move section to new index
Expand Down Expand Up @@ -431,6 +433,8 @@ const CourseOutline = ({ courseId }) => {
isOpen={isConfigureModalOpen}
onClose={handleConfigureModalClose}
onConfigureSubmit={handleConfigureItemSubmit}
currentItemData={currentItemData}
enableProctoredExams={enableProctoredExams}
/>
<DeleteModal
category={deleteCategory}
Expand Down
1 change: 0 additions & 1 deletion src/course-outline/CourseOutline.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
@import "./empty-placeholder/EmptyPlaceholder";
@import "./highlights-modal/HighlightsModal";
@import "./publish-modal/PublishModal";
@import "./configure-modal/ConfigureModal";
@import "./drag-helper/SortableItem";
@import "./xblock-status/XBlockStatus";
@import "./paste-button/PasteButton";
5 changes: 3 additions & 2 deletions src/course-outline/CourseOutline.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ import {
import { executeThunk } from '../utils';
import { COURSE_BLOCK_NAMES, VIDEO_SHARING_OPTIONS } from './constants';
import CourseOutline from './CourseOutline';
import messages from './messages';

import configureModalMessages from '../generic/configure-modal/messages';
import headerMessages from './header-navigations/messages';
import cardHeaderMessages from './card-header/messages';
import enableHighlightsModalMessages from './enable-highlights-modal/messages';
import statusBarMessages from './status-bar/messages';
import configureModalMessages from './configure-modal/messages';
import pasteButtonMessages from './paste-button/messages';
import subsectionMessages from './subsection-card/messages';
import pageAlertMessages from './page-alerts/messages';
Expand All @@ -55,6 +55,7 @@ import {
moveSubsection,
moveUnit,
} from './drag-helper/utils';
import messages from './messages';

let axiosMock;
let store;
Expand Down
8 changes: 7 additions & 1 deletion src/course-unit/CourseUnit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const CourseUnit = ({ courseId }) => {
handleTitleEdit,
handleInternetConnectionFailed,
handleCreateNewCourseXBlock,
handleConfigureSubmit,
courseVerticalChildren,
} = useCourseUnit({ courseId, blockId });

Expand Down Expand Up @@ -85,6 +86,7 @@ const CourseUnit = ({ courseId }) => {
isTitleEditFormOpen={isTitleEditFormOpen}
handleTitleEdit={handleTitleEdit}
handleTitleEditSubmit={handleTitleEditSubmit}
handleConfigureSubmit={handleConfigureSubmit}
/>
)}
breadcrumbs={(
Expand Down Expand Up @@ -119,16 +121,20 @@ const CourseUnit = ({ courseId }) => {
)}
<Stack gap={4} className="mb-4">
{courseVerticalChildren.children.map(({
name, blockId: id, blockType: type, shouldScroll,
name, blockId: id, blockType: type, shouldScroll, userPartitionInfo, validationMessages,
}) => (
<CourseXBlock
id={id}
key={id}
title={name}
type={type}
blockId={blockId}
validationMessages={validationMessages}
shouldScroll={shouldScroll}
unitXBlockActions={unitXBlockActions}
handleConfigureSubmit={handleConfigureSubmit}
data-testid="course-xblock"
userPartitionInfo={userPartitionInfo}
/>
))}
</Stack>
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 @@ -3,3 +3,4 @@
@import "./add-component/AddComponent";
@import "./course-xblock/CourseXBlock";
@import "./sidebar/Sidebar";
@import "./header-title/HeaderTitle";
79 changes: 75 additions & 4 deletions src/course-unit/CourseUnit.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ import courseSequenceMessages from './course-sequence/messages';
import sidebarMessages from './sidebar/messages';
import { extractCourseUnitId } from './sidebar/utils';
import CourseUnit from './CourseUnit';
import messages from './messages';

import deleteModalMessages from '../generic/delete-modal/messages';
import configureModalMessages from '../generic/configure-modal/messages';
import courseXBlockMessages from './course-xblock/messages';
import addComponentMessages from './add-component/messages';
import { PUBLISH_TYPES, UNIT_VISIBILITY_STATES } from './constants';
import { getContentTaxonomyTagsApiUrl, getContentTaxonomyTagsCountApiUrl } from '../content-tags-drawer/data/api';
import messages from './messages';

let axiosMock;
let store;
Expand Down Expand Up @@ -571,6 +572,7 @@ describe('<CourseUnit />', () => {
name: 'New Cloned XBlock',
block_id: '1234567890',
block_type: 'drag-and-drop-v2',
user_partition_info: {},
},
],
});
Expand All @@ -594,7 +596,7 @@ describe('<CourseUnit />', () => {
});
});

it('should toggle visibility and update course unit state accordingly', async () => {
it('should toggle visibility from sidebar and update course unit state accordingly', async () => {
const { getByRole, getByTestId } = render(<RootWrapper />);
let courseUnitSidebar;
let draftUnpublishedChangesHeading;
Expand All @@ -617,7 +619,7 @@ describe('<CourseUnit />', () => {
axiosMock
.onPost(getXBlockBaseApiUrl(blockId), {
publish: PUBLISH_TYPES.republish,
metadata: { visible_to_staff_only: true },
metadata: { visible_to_staff_only: true, group_access: null },
})
.reply(200, { dummy: 'value' });
axiosMock
Expand Down Expand Up @@ -654,7 +656,7 @@ describe('<CourseUnit />', () => {
axiosMock
.onPost(getXBlockBaseApiUrl(blockId), {
publish: PUBLISH_TYPES.republish,
metadata: { visible_to_staff_only: null },
metadata: { visible_to_staff_only: null, group_access: null },
})
.reply(200, { dummy: 'value' });
axiosMock
Expand Down Expand Up @@ -942,4 +944,73 @@ describe('<CourseUnit />', () => {
.replace('{sectionName}', courseUnitIndexMock.release_date_from),
)).toBeInTheDocument();
});

it('should toggle visibility from header configure modal and update course unit state accordingly', async () => {
const { getByRole, getByTestId } = render(<RootWrapper />);
let courseUnitSidebar;
let sidebarVisibilityCheckbox;
let modalVisibilityCheckbox;
let configureModal;
let restrictAccessSelect;

await waitFor(() => {
courseUnitSidebar = getByTestId('course-unit-sidebar');
sidebarVisibilityCheckbox = within(courseUnitSidebar)
.getByLabelText(sidebarMessages.visibilityCheckboxTitle.defaultMessage);
expect(sidebarVisibilityCheckbox).not.toBeChecked();

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)
.getByText(configureModalMessages.unitVisibility.defaultMessage)).toBeInTheDocument();
expect(within(configureModal)
.getByText(configureModalMessages.restrictAccessTo.defaultMessage)).toBeInTheDocument();
expect(restrictAccessSelect).toBeInTheDocument();
expect(restrictAccessSelect).toHaveValue('-1');

modalVisibilityCheckbox = within(configureModal)
.getByRole('checkbox', { name: configureModalMessages.hideFromLearners.defaultMessage });
expect(modalVisibilityCheckbox).not.toBeChecked();

userEvent.click(modalVisibilityCheckbox);
expect(modalVisibilityCheckbox).toBeChecked();

userEvent.selectOptions(restrictAccessSelect, '0');
const [, group1Checkbox] = within(configureModal).getAllByRole('checkbox');

userEvent.click(group1Checkbox);
expect(group1Checkbox).toBeChecked();
});

axiosMock
.onPost(getXBlockBaseApiUrl(courseUnitIndexMock.id), {
publish: null,
metadata: { visible_to_staff_only: true, group_access: { 50: [2] } },
})
.reply(200, { dummy: 'value' });
axiosMock
.onGet(getCourseUnitApiUrl(blockId))
.replyOnce(200, {
...courseUnitIndexMock,
visibility_state: UNIT_VISIBILITY_STATES.staffOnly,
has_explicit_staff_lock: true,
});

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

await waitFor(() => {
expect(sidebarVisibilityCheckbox).toBeChecked();
expect(within(courseUnitSidebar)
.getByText(sidebarMessages.sidebarTitleVisibleToStaffOnly.defaultMessage)).toBeInTheDocument();
expect(within(courseUnitSidebar)
.getByText(sidebarMessages.visibilityStaffOnlyTitle.defaultMessage)).toBeInTheDocument();
});
});
});
Loading
Loading