Compare commits
11 Commits
test_hyper
...
open-relea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
292931f2cb | ||
|
|
a580b8980a | ||
|
|
4592c0d06a | ||
|
|
91922874fc | ||
|
|
0020597d97 | ||
|
|
74ce163bec | ||
|
|
2b8edfd761 | ||
|
|
d0c4765698 | ||
|
|
d48d6ffa5d | ||
|
|
14f035435c | ||
|
|
fa25150e3d |
@@ -4,7 +4,7 @@
|
|||||||
<title>Course Authoring | <%= process.env.SITE_NAME %></title>
|
<title>Course Authoring | <%= process.env.SITE_NAME %></title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="shortcut icon" href="<%= process.env.FAVICON_URL %>" type="image/x-icon" />
|
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ const SettingCard = ({
|
|||||||
iconAs={Icon}
|
iconAs={Icon}
|
||||||
alt={intl.formatMessage(messages.helpButtonText)}
|
alt={intl.formatMessage(messages.helpButtonText)}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className=" ml-1 mr-2"
|
className="flex-shrink-0 ml-1 mr-2"
|
||||||
/>
|
/>
|
||||||
<ModalPopup
|
<ModalPopup
|
||||||
hasArrow
|
hasArrow
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import messages from './messages';
|
|||||||
import CustomPagesProvider from './CustomPagesProvider';
|
import CustomPagesProvider from './CustomPagesProvider';
|
||||||
import EditModal from './EditModal';
|
import EditModal from './EditModal';
|
||||||
import getPageHeadTitle from '../generic/utils';
|
import getPageHeadTitle from '../generic/utils';
|
||||||
|
import { getPagePath } from '../utils';
|
||||||
|
|
||||||
const CustomPages = ({
|
const CustomPages = ({
|
||||||
courseId,
|
courseId,
|
||||||
@@ -121,7 +122,7 @@ const CustomPages = ({
|
|||||||
ariaLabel="Custom Page breadcrumbs"
|
ariaLabel="Custom Page breadcrumbs"
|
||||||
links={[
|
links={[
|
||||||
{ label: 'Content', href: `${config.STUDIO_BASE_URL}/course/${courseId}` },
|
{ label: 'Content', href: `${config.STUDIO_BASE_URL}/course/${courseId}` },
|
||||||
{ label: 'Pages and Resources', href: `/course/${courseId}/pages-and-resources` },
|
{ label: 'Pages and Resources', href: getPagePath(courseId, 'true', 'tabs') },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
@import "./export-stepper/ExportStepper";
|
|
||||||
@import "./export-footer/ExportFooter";
|
@import "./export-footer/ExportFooter";
|
||||||
|
|
||||||
.export {
|
.export {
|
||||||
|
|||||||
@@ -22,6 +22,21 @@ import {
|
|||||||
updateSavingStatus,
|
updateSavingStatus,
|
||||||
} from './slice';
|
} from './slice';
|
||||||
|
|
||||||
|
function setExportDate({
|
||||||
|
date, exportStatus, exportOutput, dispatch,
|
||||||
|
}) {
|
||||||
|
// If there is no cookie for the last export date, set it now.
|
||||||
|
const cookies = new Cookies();
|
||||||
|
const cookieData = cookies.get(LAST_EXPORT_COOKIE_NAME);
|
||||||
|
if (!cookieData?.completed) {
|
||||||
|
setExportCookie(date, exportStatus === EXPORT_STAGES.SUCCESS);
|
||||||
|
}
|
||||||
|
// If we don't have export date set yet via cookie, set success date to current date.
|
||||||
|
if (exportOutput && !cookieData?.completed) {
|
||||||
|
dispatch(updateSuccessDate(date));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function startExportingCourse(courseId) {
|
export function startExportingCourse(courseId) {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
|
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
|
||||||
@@ -45,29 +60,30 @@ export function fetchExportStatus(courseId) {
|
|||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
dispatch(updateLoadingStatus({ status: RequestStatus.IN_PROGRESS }));
|
dispatch(updateLoadingStatus({ status: RequestStatus.IN_PROGRESS }));
|
||||||
try {
|
try {
|
||||||
const { exportStatus, exportOutput, exportError } = await getExportStatus(courseId);
|
const {
|
||||||
|
exportStatus, exportOutput, exportError,
|
||||||
|
} = await getExportStatus(courseId);
|
||||||
dispatch(updateCurrentStage(Math.abs(exportStatus)));
|
dispatch(updateCurrentStage(Math.abs(exportStatus)));
|
||||||
|
|
||||||
|
const date = moment().valueOf();
|
||||||
|
|
||||||
|
setExportDate({
|
||||||
|
date, exportStatus, exportOutput, dispatch,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exportError) {
|
||||||
|
const errorMessage = exportError.rawErrorMsg || exportError;
|
||||||
|
const errorUnitUrl = exportError.editUnitUrl || null;
|
||||||
|
dispatch(updateError({ msg: errorMessage, unitUrl: errorUnitUrl }));
|
||||||
|
dispatch(updateIsErrorModalOpen(true));
|
||||||
|
}
|
||||||
|
|
||||||
if (exportOutput) {
|
if (exportOutput) {
|
||||||
if (exportOutput.startsWith('/')) {
|
if (exportOutput.startsWith('/')) {
|
||||||
dispatch(updateDownloadPath(`${getConfig().STUDIO_BASE_URL}${exportOutput}`));
|
dispatch(updateDownloadPath(`${getConfig().STUDIO_BASE_URL}${exportOutput}`));
|
||||||
} else {
|
} else {
|
||||||
dispatch(updateDownloadPath(exportOutput));
|
dispatch(updateDownloadPath(exportOutput));
|
||||||
}
|
}
|
||||||
dispatch(updateSuccessDate(moment().valueOf()));
|
|
||||||
}
|
|
||||||
|
|
||||||
const cookies = new Cookies();
|
|
||||||
const cookieData = cookies.get(LAST_EXPORT_COOKIE_NAME);
|
|
||||||
if (!cookieData?.completed) {
|
|
||||||
setExportCookie(moment().valueOf(), exportStatus === EXPORT_STAGES.SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exportError) {
|
|
||||||
const errorMessage = exportError.rawErrorMsg || exportError;
|
|
||||||
const errorUnitUrl = exportError.editUnitUrl || null;
|
|
||||||
dispatch(updateError({ msg: errorMessage, unitUrl: errorUnitUrl }));
|
|
||||||
dispatch(updateIsErrorModalOpen(true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL }));
|
dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||||
|
|||||||
146
src/export-page/data/thunks.test.js
Normal file
146
src/export-page/data/thunks.test.js
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import Cookies from 'universal-cookie';
|
||||||
|
import { fetchExportStatus } from './thunks';
|
||||||
|
import * as api from './api';
|
||||||
|
import { EXPORT_STAGES } from './constants';
|
||||||
|
|
||||||
|
jest.mock('universal-cookie', () => jest.fn().mockImplementation(() => ({
|
||||||
|
get: jest.fn().mockImplementation(() => ({ completed: false })),
|
||||||
|
})));
|
||||||
|
|
||||||
|
jest.mock('../utils', () => ({
|
||||||
|
setExportCookie: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('fetchExportStatus thunk', () => {
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
const getState = jest.fn();
|
||||||
|
const courseId = 'course-123';
|
||||||
|
const exportStatus = EXPORT_STAGES.COMPRESSING;
|
||||||
|
const exportOutput = 'export output';
|
||||||
|
const exportError = 'export error';
|
||||||
|
let mockGetExportStatus;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
mockGetExportStatus = jest.spyOn(api, 'getExportStatus').mockResolvedValue({
|
||||||
|
exportStatus,
|
||||||
|
exportOutput,
|
||||||
|
exportError,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch updateCurrentStage with export status', async () => {
|
||||||
|
mockGetExportStatus.mockResolvedValue({
|
||||||
|
exportStatus,
|
||||||
|
exportOutput,
|
||||||
|
exportError,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchExportStatus(courseId)(dispatch, getState);
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
payload: exportStatus,
|
||||||
|
type: 'exportPage/updateCurrentStage',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch updateError on export error', async () => {
|
||||||
|
mockGetExportStatus.mockResolvedValue({
|
||||||
|
exportStatus,
|
||||||
|
exportOutput,
|
||||||
|
exportError,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchExportStatus(courseId)(dispatch, getState);
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
payload: {
|
||||||
|
msg: exportError,
|
||||||
|
unitUrl: null,
|
||||||
|
},
|
||||||
|
type: 'exportPage/updateError',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch updateIsErrorModalOpen with true if export error', async () => {
|
||||||
|
mockGetExportStatus.mockResolvedValue({
|
||||||
|
exportStatus,
|
||||||
|
exportOutput,
|
||||||
|
exportError,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchExportStatus(courseId)(dispatch, getState);
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
payload: true,
|
||||||
|
type: 'exportPage/updateIsErrorModalOpen',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not dispatch updateIsErrorModalOpen if no export error', async () => {
|
||||||
|
mockGetExportStatus.mockResolvedValue({
|
||||||
|
exportStatus,
|
||||||
|
exportOutput,
|
||||||
|
exportError: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchExportStatus(courseId)(dispatch, getState);
|
||||||
|
|
||||||
|
expect(dispatch).not.toHaveBeenCalledWith({
|
||||||
|
payload: false,
|
||||||
|
type: 'exportPage/updateIsErrorModalOpen',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch updateDownloadPath if there's export output", async () => {
|
||||||
|
mockGetExportStatus.mockResolvedValue({
|
||||||
|
exportStatus,
|
||||||
|
exportOutput,
|
||||||
|
exportError,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchExportStatus(courseId)(dispatch, getState);
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
payload: exportOutput,
|
||||||
|
type: 'exportPage/updateDownloadPath',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch updateSuccessDate with current date if export status is success', async () => {
|
||||||
|
mockGetExportStatus.mockResolvedValue({
|
||||||
|
exportStatus:
|
||||||
|
EXPORT_STAGES.SUCCESS,
|
||||||
|
exportOutput,
|
||||||
|
exportError,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchExportStatus(courseId)(dispatch, getState);
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
payload: expect.any(Number),
|
||||||
|
type: 'exportPage/updateSuccessDate',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not dispatch updateSuccessDate with current date if last-export cookie is already set', async () => {
|
||||||
|
mockGetExportStatus.mockResolvedValue({
|
||||||
|
exportStatus:
|
||||||
|
EXPORT_STAGES.SUCCESS,
|
||||||
|
exportOutput,
|
||||||
|
exportError,
|
||||||
|
});
|
||||||
|
|
||||||
|
Cookies.mockImplementation(() => ({
|
||||||
|
get: jest.fn().mockReturnValueOnce({ completed: true }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
await fetchExportStatus(courseId)(dispatch, getState);
|
||||||
|
|
||||||
|
expect(dispatch).not.toHaveBeenCalledWith({
|
||||||
|
payload: expect.any,
|
||||||
|
type: 'exportPage/updateSuccessDate',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
.pgn__stepper-header-step-list {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
@@ -153,7 +153,7 @@ describe('<CreateOrRerunCourseForm />', () => {
|
|||||||
userEvent.type(runInput, '1');
|
userEvent.type(runInput, '1');
|
||||||
userEvent.click(createBtn);
|
userEvent.click(createBtn);
|
||||||
});
|
});
|
||||||
await axiosMock.onPost(getCreateOrRerunCourseUrl).reply(200, { url });
|
await axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, { url });
|
||||||
await executeThunk(updateCreateOrRerunCourseQuery({ org: 'testX', run: 'some' }), store.dispatch);
|
await executeThunk(updateCreateOrRerunCourseQuery({ org: 'testX', run: 'some' }), store.dispatch);
|
||||||
|
|
||||||
expect(window.location.assign).toHaveBeenCalledWith(`${process.env.STUDIO_BASE_URL}${url}`);
|
expect(window.location.assign).toHaveBeenCalledWith(`${process.env.STUDIO_BASE_URL}${url}`);
|
||||||
@@ -168,7 +168,7 @@ describe('<CreateOrRerunCourseForm />', () => {
|
|||||||
const numberInput = screen.getByPlaceholderText(messages.courseNumberPlaceholder.defaultMessage);
|
const numberInput = screen.getByPlaceholderText(messages.courseNumberPlaceholder.defaultMessage);
|
||||||
const runInput = screen.getByPlaceholderText(messages.courseRunPlaceholder.defaultMessage);
|
const runInput = screen.getByPlaceholderText(messages.courseRunPlaceholder.defaultMessage);
|
||||||
const createBtn = screen.getByRole('button', { name: messages.createButton.defaultMessage });
|
const createBtn = screen.getByRole('button', { name: messages.createButton.defaultMessage });
|
||||||
await axiosMock.onPost(getCreateOrRerunCourseUrl).reply(200, { url, destinationCourseKey });
|
await axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, { url, destinationCourseKey });
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
userEvent.type(displayNameInput, 'foo course name');
|
userEvent.type(displayNameInput, 'foo course name');
|
||||||
@@ -250,7 +250,7 @@ describe('<CreateOrRerunCourseForm />', () => {
|
|||||||
it('shows alert error if postErrors presents', async () => {
|
it('shows alert error if postErrors presents', async () => {
|
||||||
render(<RootWrapper {...props} />);
|
render(<RootWrapper {...props} />);
|
||||||
await mockStore();
|
await mockStore();
|
||||||
await axiosMock.onPost(getCreateOrRerunCourseUrl).reply(200, { errMsg: 'aaa' });
|
await axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, { errMsg: 'aaa' });
|
||||||
await executeThunk(updateCreateOrRerunCourseQuery({ org: 'testX', run: 'some' }), store.dispatch);
|
await executeThunk(updateCreateOrRerunCourseQuery({ org: 'testX', run: 'some' }), store.dispatch);
|
||||||
|
|
||||||
expect(screen.getByText('aaa')).toBeInTheDocument();
|
expect(screen.getByText('aaa')).toBeInTheDocument();
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|||||||
import { convertObjectToSnakeCase } from '../../utils';
|
import { convertObjectToSnakeCase } from '../../utils';
|
||||||
|
|
||||||
export const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
|
export const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
|
||||||
export const getCreateOrRerunCourseUrl = new URL('course/', getApiBaseUrl()).href;
|
export const getCreateOrRerunCourseUrl = () => new URL('course/', getApiBaseUrl()).href;
|
||||||
export const getCourseRerunUrl = (courseId) => new URL(`/api/contentstore/v1/course_rerun/${courseId}`, getApiBaseUrl()).href;
|
export const getCourseRerunUrl = (courseId) => new URL(`/api/contentstore/v1/course_rerun/${courseId}`, getApiBaseUrl()).href;
|
||||||
export const getOrganizationsUrl = new URL('organizations', getApiBaseUrl()).href;
|
export const getOrganizationsUrl = () => new URL('organizations', getApiBaseUrl()).href;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get's organizations data.
|
* Get's organizations data.
|
||||||
@@ -14,7 +14,7 @@ export const getOrganizationsUrl = new URL('organizations', getApiBaseUrl()).hre
|
|||||||
*/
|
*/
|
||||||
export async function getOrganizations() {
|
export async function getOrganizations() {
|
||||||
const { data } = await getAuthenticatedHttpClient().get(
|
const { data } = await getAuthenticatedHttpClient().get(
|
||||||
getOrganizationsUrl,
|
getOrganizationsUrl(),
|
||||||
);
|
);
|
||||||
return camelCaseObject(data);
|
return camelCaseObject(data);
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ export async function getCourseRerun(courseId) {
|
|||||||
*/
|
*/
|
||||||
export async function createOrRerunCourse(courseData) {
|
export async function createOrRerunCourse(courseData) {
|
||||||
const { data } = await getAuthenticatedHttpClient().post(
|
const { data } = await getAuthenticatedHttpClient().post(
|
||||||
getCreateOrRerunCourseUrl,
|
getCreateOrRerunCourseUrl(),
|
||||||
convertObjectToSnakeCase(courseData, true),
|
convertObjectToSnakeCase(courseData, true),
|
||||||
);
|
);
|
||||||
return camelCaseObject(data);
|
return camelCaseObject(data);
|
||||||
|
|||||||
@@ -66,10 +66,10 @@ describe('generic api calls', () => {
|
|||||||
org: 'edX',
|
org: 'edX',
|
||||||
run: 'Demo_Course',
|
run: 'Demo_Course',
|
||||||
};
|
};
|
||||||
axiosMock.onPost(getCreateOrRerunCourseUrl).reply(200, courseRerunData);
|
axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, courseRerunData);
|
||||||
const result = await createOrRerunCourse(courseRerunData);
|
const result = await createOrRerunCourse(courseRerunData);
|
||||||
|
|
||||||
expect(axiosMock.history.post[0].url).toEqual(getCreateOrRerunCourseUrl);
|
expect(axiosMock.history.post[0].url).toEqual(getCreateOrRerunCourseUrl());
|
||||||
expect(result).toEqual(courseRerunData);
|
expect(result).toEqual(courseRerunData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ describe('<AssignmentSection />', () => {
|
|||||||
it('checking correct assignment weight of total grade value', async () => {
|
it('checking correct assignment weight of total grade value', async () => {
|
||||||
const { getByTestId } = render(<RootWrapper setGradingData={setGradingData} />);
|
const { getByTestId } = render(<RootWrapper setGradingData={setGradingData} />);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const assignmentShortLabelInput = getByTestId('assignment-weight-input');
|
const assignmentWeightInput = getByTestId('assignment-weight-input');
|
||||||
expect(assignmentShortLabelInput.value).toBe('100');
|
expect(assignmentWeightInput.value).toBe('100');
|
||||||
fireEvent.change(assignmentShortLabelInput, { target: { value: '123' } });
|
fireEvent.change(assignmentWeightInput, { target: { value: '123' } });
|
||||||
expect(testObj.graders[0].weight).toBe('123');
|
expect(testObj.graders[0].weight).toBe(123);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('checking correct assignment total number value', async () => {
|
it('checking correct assignment total number value', async () => {
|
||||||
@@ -74,7 +74,7 @@ describe('<AssignmentSection />', () => {
|
|||||||
const assignmentTotalNumberInput = getByTestId('assignment-minCount-input');
|
const assignmentTotalNumberInput = getByTestId('assignment-minCount-input');
|
||||||
expect(assignmentTotalNumberInput.value).toBe('1');
|
expect(assignmentTotalNumberInput.value).toBe('1');
|
||||||
fireEvent.change(assignmentTotalNumberInput, { target: { value: '123' } });
|
fireEvent.change(assignmentTotalNumberInput, { target: { value: '123' } });
|
||||||
expect(testObj.graders[0].minCount).toBe('123');
|
expect(testObj.graders[0].minCount).toBe(123);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('checking correct assignment number of droppable value', async () => {
|
it('checking correct assignment number of droppable value', async () => {
|
||||||
@@ -83,7 +83,7 @@ describe('<AssignmentSection />', () => {
|
|||||||
const assignmentNumberOfDroppableInput = getByTestId('assignment-dropCount-input');
|
const assignmentNumberOfDroppableInput = getByTestId('assignment-dropCount-input');
|
||||||
expect(assignmentNumberOfDroppableInput.value).toBe('1');
|
expect(assignmentNumberOfDroppableInput.value).toBe('1');
|
||||||
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '2' } });
|
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '2' } });
|
||||||
expect(testObj.graders[0].dropCount).toBe('2');
|
expect(testObj.graders[0].dropCount).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('checking correct error msg if dropCount have negative number or empty string', async () => {
|
it('checking correct error msg if dropCount have negative number or empty string', async () => {
|
||||||
@@ -100,20 +100,20 @@ describe('<AssignmentSection />', () => {
|
|||||||
it('checking correct error msg if minCount have negative number or empty string', async () => {
|
it('checking correct error msg if minCount have negative number or empty string', async () => {
|
||||||
const { getByText, getByTestId } = render(<RootWrapper />);
|
const { getByText, getByTestId } = render(<RootWrapper />);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const assignmentNumberOfDroppableInput = getByTestId('assignment-minCount-input');
|
const assignmentMinCountInput = getByTestId('assignment-minCount-input');
|
||||||
expect(assignmentNumberOfDroppableInput.value).toBe('1');
|
expect(assignmentMinCountInput.value).toBe('1');
|
||||||
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '-2' } });
|
fireEvent.change(assignmentMinCountInput, { target: { value: '-2' } });
|
||||||
expect(getByText(messages.totalNumberErrorMessage.defaultMessage)).toBeInTheDocument();
|
expect(getByText(messages.totalNumberErrorMessage.defaultMessage)).toBeInTheDocument();
|
||||||
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '' } });
|
fireEvent.change(assignmentMinCountInput, { target: { value: '' } });
|
||||||
expect(getByText(messages.totalNumberErrorMessage.defaultMessage)).toBeInTheDocument();
|
expect(getByText(messages.totalNumberErrorMessage.defaultMessage)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('checking correct error msg if total weight have negative number', async () => {
|
it('checking correct error msg if total weight have negative number', async () => {
|
||||||
const { getByText, getByTestId } = render(<RootWrapper />);
|
const { getByText, getByTestId } = render(<RootWrapper />);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const assignmentNumberOfDroppableInput = getByTestId('assignment-weight-input');
|
const assignmentWeightInput = getByTestId('assignment-weight-input');
|
||||||
expect(assignmentNumberOfDroppableInput.value).toBe('100');
|
expect(assignmentWeightInput.value).toBe('100');
|
||||||
fireEvent.change(assignmentNumberOfDroppableInput, { target: { value: '-100' } });
|
fireEvent.change(assignmentWeightInput, { target: { value: '-100' } });
|
||||||
expect(getByText(messages.weightOfTotalGradeErrorMessage.defaultMessage)).toBeInTheDocument();
|
expect(getByText(messages.weightOfTotalGradeErrorMessage.defaultMessage)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ const AssignmentSection = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAssignmentChange = (e, assignmentId) => {
|
const handleAssignmentChange = (e, assignmentId) => {
|
||||||
const { name, value } = e.target;
|
const { name, value, type: inputType } = e.target;
|
||||||
|
|
||||||
|
let inputValue = value;
|
||||||
|
if (inputType === 'number') {
|
||||||
|
inputValue = parseInt(value, 10);
|
||||||
|
}
|
||||||
|
|
||||||
setShowSavePrompt(true);
|
setShowSavePrompt(true);
|
||||||
|
|
||||||
@@ -42,7 +47,7 @@ const AssignmentSection = ({
|
|||||||
...prevState,
|
...prevState,
|
||||||
graders: graders.map(grader => {
|
graders: graders.map(grader => {
|
||||||
if (grader.id === assignmentId) {
|
if (grader.id === assignmentId) {
|
||||||
return { ...grader, [name]: value };
|
return { ...grader, [name]: inputValue };
|
||||||
}
|
}
|
||||||
return grader;
|
return grader;
|
||||||
}),
|
}),
|
||||||
@@ -66,7 +71,7 @@ const AssignmentSection = ({
|
|||||||
return (
|
return (
|
||||||
<div className="assignment-items">
|
<div className="assignment-items">
|
||||||
{graders?.map((gradeField) => {
|
{graders?.map((gradeField) => {
|
||||||
const courseAssignmentUsage = courseAssignmentLists[gradeField.type.toLowerCase()];
|
const courseAssignmentUsage = courseAssignmentLists[gradeField.type];
|
||||||
const showDefinedCaseAlert = gradeField.minCount !== courseAssignmentUsage?.length
|
const showDefinedCaseAlert = gradeField.minCount !== courseAssignmentUsage?.length
|
||||||
&& Boolean(courseAssignmentUsage?.length);
|
&& Boolean(courseAssignmentUsage?.length);
|
||||||
const showNotDefinedCaseAlert = !courseAssignmentUsage?.length && Boolean(gradeField.type);
|
const showNotDefinedCaseAlert = !courseAssignmentUsage?.length && Boolean(gradeField.type);
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
// This SCSS was partly copied from edx/frontend-app-support-tools/src/support-header/index.scss.
|
|
||||||
$spacer: 1rem;
|
|
||||||
$white: #FFFFFF;
|
|
||||||
|
|
||||||
.btn-tertiary:hover {
|
|
||||||
color: white;
|
|
||||||
background-color: #00262B;
|
|
||||||
}
|
|
||||||
|
|
||||||
.course-title-lockup {
|
|
||||||
@media only screen and (max-width: 768px) {
|
|
||||||
padding-left: .5rem;
|
|
||||||
max-width: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width: 769px) {
|
|
||||||
padding: .5rem;
|
|
||||||
padding-right: $spacer;
|
|
||||||
border-right: 1px solid #E5E5E5;
|
|
||||||
min-width: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: #333333;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
line-height: 1.375rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-header-mobile,
|
|
||||||
.site-header-desktop {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-header-mobile {
|
|
||||||
img {
|
|
||||||
height: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-header-desktop {
|
|
||||||
height: 3.75rem;
|
|
||||||
box-shadow: 0 1px 0 0 rgb(0 0 0 / .1);
|
|
||||||
background: $white;
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
display: block;
|
|
||||||
box-sizing: content-box;
|
|
||||||
position: relative;
|
|
||||||
top: -.05em;
|
|
||||||
height: 1.75rem;
|
|
||||||
padding: $spacer 0;
|
|
||||||
margin-right: $spacer;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -44,7 +44,7 @@ export const getSettingMenuItems = ({ studioBaseUrl, courseId, intl }) => ([
|
|||||||
title: intl.formatMessage(messages['header.links.courseTeam']),
|
title: intl.formatMessage(messages['header.links.courseTeam']),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: `${studioBaseUrl}/group_configurations/course-v1:${courseId}`,
|
href: `${studioBaseUrl}/group_configurations/${courseId}`,
|
||||||
title: intl.formatMessage(messages['header.links.groupConfigurations']),
|
title: intl.formatMessage(messages['header.links.groupConfigurations']),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@import "~@edx/brand/paragon/variables";
|
@import "~@edx/brand/paragon/variables";
|
||||||
@import "~@edx/paragon/scss/core/core";
|
@import "~@edx/paragon/scss/core/core";
|
||||||
@import "~@edx/brand/paragon/overrides";
|
@import "~@edx/brand/paragon/overrides";
|
||||||
@import "header/header";
|
@import "~@edx/frontend-component-header/dist/index";
|
||||||
@import "assets/scss/variables";
|
@import "assets/scss/variables";
|
||||||
@import "assets/scss/form";
|
@import "assets/scss/form";
|
||||||
@import "assets/scss/utilities";
|
@import "assets/scss/utilities";
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ const StudioHome = ({ intl }) => {
|
|||||||
studioRequestEmail,
|
studioRequestEmail,
|
||||||
libraryAuthoringMfeUrl,
|
libraryAuthoringMfeUrl,
|
||||||
redirectToLibraryAuthoringMfe,
|
redirectToLibraryAuthoringMfe,
|
||||||
splitStudioHome,
|
|
||||||
} = studioHomeData;
|
} = studioHomeData;
|
||||||
|
|
||||||
function getHeaderButtons() {
|
function getHeaderButtons() {
|
||||||
@@ -68,26 +67,24 @@ const StudioHome = ({ intl }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let libraryHref = `${getConfig().STUDIO_BASE_URL}/home#libraries-tab`;
|
let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`;
|
||||||
if (splitStudioHome) {
|
|
||||||
libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`;
|
|
||||||
}
|
|
||||||
if (redirectToLibraryAuthoringMfe) {
|
if (redirectToLibraryAuthoringMfe) {
|
||||||
libraryHref = `${libraryAuthoringMfeUrl}/create`;
|
libraryHref = `${libraryAuthoringMfeUrl}/create`;
|
||||||
headerButtons.push(
|
|
||||||
<Button
|
|
||||||
variant="outline-primary"
|
|
||||||
iconBefore={AddIcon}
|
|
||||||
size="sm"
|
|
||||||
disabled={showNewCourseContainer}
|
|
||||||
href={libraryHref}
|
|
||||||
data-testid="new-library-button"
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.addNewLibraryBtnText)}
|
|
||||||
</Button>,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headerButtons.push(
|
||||||
|
<Button
|
||||||
|
variant="outline-primary"
|
||||||
|
iconBefore={AddIcon}
|
||||||
|
size="sm"
|
||||||
|
disabled={showNewCourseContainer}
|
||||||
|
href={libraryHref}
|
||||||
|
data-testid="new-library-button"
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.addNewLibraryBtnText)}
|
||||||
|
</Button>,
|
||||||
|
);
|
||||||
|
|
||||||
return headerButtons;
|
return headerButtons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,15 +116,16 @@ describe('<StudioHome />', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('render new library button', () => {
|
describe('render new library button', () => {
|
||||||
it('should not show button', async () => {
|
it('href should include home_library', async () => {
|
||||||
useSelector.mockReturnValue({
|
useSelector.mockReturnValue({
|
||||||
...studioHomeMock,
|
...studioHomeMock,
|
||||||
courseCreatorStatus: COURSE_CREATOR_STATES.granted,
|
courseCreatorStatus: COURSE_CREATOR_STATES.granted,
|
||||||
});
|
});
|
||||||
|
const studioBaseUrl = 'http://localhost:18010';
|
||||||
|
|
||||||
const { queryByTestId } = render(<RootWrapper />);
|
const { getByTestId } = render(<RootWrapper />);
|
||||||
const createNewLibraryButton = queryByTestId('new-library-button');
|
const createNewLibraryButton = getByTestId('new-library-button');
|
||||||
expect(createNewLibraryButton).toBeNull();
|
expect(createNewLibraryButton.getAttribute('href')).toBe(`${studioBaseUrl}/home_library`);
|
||||||
});
|
});
|
||||||
it('href should include create', async () => {
|
it('href should include create', async () => {
|
||||||
useSelector.mockReturnValue({
|
useSelector.mockReturnValue({
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe('<OrganizationSection />', async () => {
|
|||||||
});
|
});
|
||||||
store = initializeStore();
|
store = initializeStore();
|
||||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||||
axiosMock.onDelete(getOrganizationsUrl).reply(200);
|
axiosMock.onDelete(getOrganizationsUrl()).reply(200);
|
||||||
await executeThunk(fetchOrganizationsQuery(), store.dispatch);
|
await executeThunk(fetchOrganizationsQuery(), store.dispatch);
|
||||||
useSelector.mockReturnValue(['edX', 'org']);
|
useSelector.mockReturnValue(['edX', 'org']);
|
||||||
});
|
});
|
||||||
|
|||||||
23
src/utils.js
23
src/utils.js
@@ -95,12 +95,31 @@ export function parseArrayOrObjectValues(obj) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a correct inner path depend on config PUBLIC_PATH.
|
||||||
|
* @param {string} checkPath - the internal route path that is validated
|
||||||
|
* @returns {string} - the correct internal route path
|
||||||
|
*/
|
||||||
|
export const createCorrectInternalRoute = (checkPath) => {
|
||||||
|
let basePath = getConfig().PUBLIC_PATH;
|
||||||
|
|
||||||
|
if (basePath.endsWith('/')) {
|
||||||
|
basePath = basePath.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkPath.startsWith(basePath)) {
|
||||||
|
return `${basePath}${checkPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkPath;
|
||||||
|
};
|
||||||
|
|
||||||
export function getPagePath(courseId, isMfePageEnabled, urlParameter) {
|
export function getPagePath(courseId, isMfePageEnabled, urlParameter) {
|
||||||
if (isMfePageEnabled === 'true') {
|
if (isMfePageEnabled === 'true') {
|
||||||
if (urlParameter === 'tabs') {
|
if (urlParameter === 'tabs') {
|
||||||
return `${getConfig().BASE_URL}/course/${courseId}/pages-and-resources`;
|
return createCorrectInternalRoute(`/course/${courseId}/pages-and-resources`);
|
||||||
}
|
}
|
||||||
return `${getConfig().BASE_URL}/course/${courseId}/${urlParameter}`;
|
return createCorrectInternalRoute(`/course/${courseId}/${urlParameter}`);
|
||||||
}
|
}
|
||||||
return `${getConfig().STUDIO_BASE_URL}/${urlParameter}/${courseId}`;
|
return `${getConfig().STUDIO_BASE_URL}/${urlParameter}/${courseId}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user