Skip to content

Commit

Permalink
Merge branch 'master' into rpenido/fal-3876-add-library-content-to-a-…
Browse files Browse the repository at this point in the history
…course
  • Loading branch information
rpenido authored Oct 8, 2024
2 parents d20dc73 + 434fea3 commit d602803
Show file tree
Hide file tree
Showing 85 changed files with 1,893 additions and 1,546 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest
needs: tests
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download code coverage results
uses: actions/download-artifact@v4
with:
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions src/content-tags-drawer/ContentTagsDrawer.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ describe('<ContentTagsDrawer />', () => {
initializeMocks();
});

afterEach(() => {
jest.clearAllMocks();
});

it('should render page and page title correctly', () => {
renderDrawer(stagedTagsId);
expect(screen.getByText('Manage tags')).toBeInTheDocument();
Expand Down
27 changes: 16 additions & 11 deletions src/course-outline/CourseOutline.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ describe('<CourseOutline />', () => {
});

it('check video sharing option shows error on failure', async () => {
const { findByLabelText, queryByRole } = render(<RootWrapper />);
render(<RootWrapper />);

axiosMock
.onPost(getCourseBlockApiUrl(courseId), {
Expand All @@ -235,7 +235,7 @@ describe('<CourseOutline />', () => {
},
})
.reply(500);
const optionDropdown = await findByLabelText(statusBarMessages.videoSharingTitle.defaultMessage);
const optionDropdown = await screen.findByLabelText(statusBarMessages.videoSharingTitle.defaultMessage);
await act(
async () => fireEvent.change(optionDropdown, { target: { value: VIDEO_SHARING_OPTIONS.allOff } }),
);
Expand All @@ -247,8 +247,10 @@ describe('<CourseOutline />', () => {
},
}));

const alertElement = queryByRole('alert');
expect(alertElement).toHaveTextContent(
const alertElements = screen.queryAllByRole('alert');
expect(alertElements.find(
(el) => el.classList.contains('alert-content'),
)).toHaveTextContent(
pageAlertMessages.alertFailedGeneric.defaultMessage,
);
});
Expand Down Expand Up @@ -511,9 +513,10 @@ describe('<CourseOutline />', () => {
notificationDismissUrl: '/some/url',
});

const { findByRole } = render(<RootWrapper />);
expect(await findByRole('alert')).toBeInTheDocument();
const dismissBtn = await findByRole('button', { name: 'Dismiss' });
render(<RootWrapper />);
const alert = await screen.findByText(pageAlertMessages.configurationErrorTitle.defaultMessage);
expect(alert).toBeInTheDocument();
const dismissBtn = await screen.findByRole('button', { name: 'Dismiss' });
axiosMock
.onDelete('/some/url')
.reply(204);
Expand Down Expand Up @@ -2160,10 +2163,10 @@ describe('<CourseOutline />', () => {
});

it('check whether unit copy & paste option works correctly', async () => {
const { findAllByTestId, queryByTestId, findAllByRole } = render(<RootWrapper />);
render(<RootWrapper />);
// get first section -> first subsection -> first unit element
const [section] = courseOutlineIndexMock.courseStructure.childInfo.children;
const [sectionElement] = await findAllByTestId('section-card');
const [sectionElement] = await screen.findAllByTestId('section-card');
const [subsection] = section.childInfo.children;
axiosMock
.onGet(getXBlockApiUrl(section.id))
Expand Down Expand Up @@ -2202,7 +2205,7 @@ describe('<CourseOutline />', () => {
await act(async () => fireEvent.mouseOver(clipboardLabel));

// find clipboard content popover link
const popoverContent = queryByTestId('popover-content');
const popoverContent = screen.queryByTestId('popover-content');
expect(popoverContent.tagName).toBe('A');
expect(popoverContent).toHaveAttribute('href', `${getConfig().STUDIO_BASE_URL}${unit.studioUrl}`);

Expand Down Expand Up @@ -2233,8 +2236,10 @@ describe('<CourseOutline />', () => {
errorFiles: ['error.css'],
});

let alerts = await screen.findAllByRole('alert');
// Exclude processing notification toast
alerts = alerts.filter((el) => !el.classList.contains('toast-container'));
// 3 alerts should be present
const alerts = await findAllByRole('alert');
expect(alerts.length).toEqual(3);

// check alerts for errorFiles
Expand Down
16 changes: 10 additions & 6 deletions src/course-unit/CourseUnit.test.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import {
act, render, waitFor, fireEvent, within,
act, render, waitFor, fireEvent, within, screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';
Expand Down Expand Up @@ -525,17 +525,19 @@ describe('<CourseUnit />', () => {
});

it('should display a warning alert for unpublished course unit version', async () => {
const { getByRole } = render(<RootWrapper />);
render(<RootWrapper />);

await waitFor(() => {
const unpublishedAlert = getByRole('alert', { class: 'course-unit-unpublished-alert' });
const unpublishedAlert = screen.getAllByRole('alert').find(
(el) => el.classList.contains('alert-content'),
);
expect(unpublishedAlert).toHaveTextContent(messages.alertUnpublishedVersion.defaultMessage);
expect(unpublishedAlert).toHaveClass('alert-warning');
});
});

it('should not display an unpublished alert for a course unit with explicit staff lock and unpublished status', async () => {
const { queryByRole } = render(<RootWrapper />);
render(<RootWrapper />);

axiosMock
.onGet(getCourseUnitApiUrl(courseId))
Expand All @@ -547,8 +549,10 @@ describe('<CourseUnit />', () => {
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);

await waitFor(() => {
const unpublishedAlert = queryByRole('alert', { class: 'course-unit-unpublished-alert' });
expect(unpublishedAlert).toBeNull();
const alert = screen.queryAllByRole('alert').find(
(el) => el.classList.contains('alert-content'),
);
expect(alert).toBeUndefined();
});
});

Expand Down
55 changes: 0 additions & 55 deletions src/editors/Editor.test.jsx

This file was deleted.

34 changes: 22 additions & 12 deletions src/editors/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Note: there is no Editor.test.tsx. This component only works together with
// <EditorPage> as its parent, so they are tested together in EditorPage.test.tsx
import React from 'react';
import { useDispatch } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
Expand All @@ -7,13 +9,15 @@ import * as hooks from './hooks';

import supportedEditors from './supportedEditors';
import type { EditorComponent } from './EditorComponent';
import { useEditorContext } from './EditorContext';

export interface Props extends EditorComponent {
blockType: string;
blockId: string | null;
learningContextId: string | null;
lmsEndpointUrl: string | null;
studioEndpointUrl: string | null;
fullScreen?: boolean;
}

const Editor: React.FC<Props> = ({
Expand All @@ -36,23 +40,29 @@ const Editor: React.FC<Props> = ({
studioEndpointUrl,
},
});
const { fullScreen } = useEditorContext();

const EditorComponent = supportedEditors[blockType];
return (
<div
className="d-flex flex-column"
>
const innerEditor = (EditorComponent !== undefined)
? <EditorComponent {...{ onClose, returnFunction }} />
: <FormattedMessage {...messages.couldNotFindEditor} />;

if (fullScreen) {
return (
<div
className="pgn__modal-fullscreen h-100"
role="dialog"
aria-label={blockType}
className="d-flex flex-column"
>
{(EditorComponent !== undefined)
? <EditorComponent {...{ onClose, returnFunction }} />
: <FormattedMessage {...messages.couldNotFindEditor} />}
<div
className="pgn__modal-fullscreen h-100"
role="dialog"
aria-label={blockType}
>
{innerEditor}
</div>
</div>
</div>
);
);
}
return innerEditor;
};

export default Editor;
39 changes: 39 additions & 0 deletions src/editors/EditorContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';

/**
* Shared context that's used by all our editors.
*
* Note: we're in the process of moving things from redux into this.
*/
export interface EditorContext {
learningContextId: string;
/**
* When editing components in the libraries part of the Authoring MFE, we show
* the editors in a modal (fullScreen = false). This is the preferred approach
* so that authors can see context behind the modal.
* However, when making edits from the legacy course view, we display the
* editors in a fullscreen view. This approach is deprecated.
*/
fullScreen: boolean;
}

const context = React.createContext<EditorContext | undefined>(undefined);

/** Hook to get the editor context (shared context) */
export function useEditorContext() {
const ctx = React.useContext(context);
if (ctx === undefined) {
/* istanbul ignore next */
throw new Error('This component needs to be wrapped in <EditorContextProvider>');
}
return ctx;
}

export const EditorContextProvider: React.FC<{
children: React.ReactNode,
learningContextId: string;
fullScreen: boolean;
}> = ({ children, ...contextData }) => {
const ctx: EditorContext = React.useMemo(() => ({ ...contextData }), []);
return <context.Provider value={ctx}>{children}</context.Provider>;
};
58 changes: 0 additions & 58 deletions src/editors/EditorPage.jsx

This file was deleted.

Loading

0 comments on commit d602803

Please sign in to comment.