Skip to content

Commit

Permalink
chore: Fix merge conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisChV committed Jul 19, 2024
2 parents 166ab04 + 3a14141 commit 1ebe818
Show file tree
Hide file tree
Showing 21 changed files with 801 additions and 93 deletions.
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.

69 changes: 69 additions & 0 deletions src/generic/toast-context/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { initializeMockApp } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { ToastContext, ToastProvider } from '.';
import initializeStore from '../../store';

export interface WraperProps {
children: React.ReactNode;
}

const TestComponentToShow = () => {
const { showToast } = React.useContext(ToastContext);

React.useEffect(() => {
showToast('This is the toast!');
}, [showToast]);

return <div>Content</div>;
};

const TestComponentToClose = () => {
const { showToast, closeToast } = React.useContext(ToastContext);

React.useEffect(() => {
showToast('This is the toast!');
closeToast();
}, [showToast]);

return <div>Content</div>;
};

let store;
const RootWrapper = ({ children }: WraperProps) => (
<AppProvider store={store}>
<ToastProvider>
{children}
</ToastProvider>
</AppProvider>
);

describe('<ToastProvider />', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
});

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

it('should show toast', async () => {
render(<RootWrapper><TestComponentToShow /></RootWrapper>);
expect(await screen.findByText('This is the toast!')).toBeInTheDocument();
});

it('should close toast', async () => {
render(<RootWrapper><TestComponentToClose /></RootWrapper>);
expect(await screen.findByText('Content')).toBeInTheDocument();
expect(screen.queryByText('This is the toast!')).not.toBeInTheDocument();
});
});
57 changes: 57 additions & 0 deletions src/generic/toast-context/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import { Toast } from '@openedx/paragon';

export interface ToastContextData {
toastMessage: string | null;
showToast: (message: string) => void;
closeToast: () => void;
}

export interface ToastProviderProps {
children: React.ReactNode;
}

/**
* Global context to keep track of popup message(s) that appears to user after
* they take an action like creating or deleting something.
*/
export const ToastContext = React.createContext({
toastMessage: null,
showToast: () => {},
closeToast: () => {},
} as ToastContextData);

/**
* React component to provide `ToastContext` to the app
*/
export const ToastProvider = (props: ToastProviderProps) => {
// TODO, We can convert this to a queue of messages,
// see: https://github.com/open-craft/frontend-app-course-authoring/pull/38#discussion_r1638990647

const [toastMessage, setToastMessage] = React.useState<string | null>(null);

React.useEffect(() => () => {
// Cleanup function to avoid updating state on unmounted component
setToastMessage(null);
}, []);

const showToast = React.useCallback((message) => setToastMessage(message), [setToastMessage]);
const closeToast = React.useCallback(() => setToastMessage(null), [setToastMessage]);

const context = React.useMemo(() => ({
toastMessage,
showToast,
closeToast,
}), [toastMessage, showToast, closeToast]);

return (
<ToastContext.Provider value={context}>
{props.children}
{ toastMessage && (
<Toast show={toastMessage !== null} onClose={closeToast}>
{toastMessage}
</Toast>
)}
</ToastContext.Provider>
);
};
15 changes: 9 additions & 6 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { initializeHotjar } from '@edx/frontend-enterprise-hotjar';
import { logError } from '@edx/frontend-platform/logging';
import messages from './i18n';

import { CreateLibrary, LibraryAuthoringPage } from './library-authoring';
import { CreateLibrary, LibraryLayout } from './library-authoring';
import initializeStore from './store';
import CourseAuthoringRoutes from './CourseAuthoringRoutes';
import Head from './head/Head';
Expand All @@ -25,6 +25,7 @@ import CourseRerun from './course-rerun';
import { TaxonomyLayout, TaxonomyDetailPage, TaxonomyListPage } from './taxonomy';
import { ContentTagsDrawer } from './content-tags-drawer';
import AccessibilityPage from './accessibility-page';
import { ToastProvider } from './generic/toast-context';

import 'react-datepicker/dist/react-datepicker.css';
import './index.scss';
Expand Down Expand Up @@ -53,7 +54,7 @@ const App = () => {
<Route path="/libraries" element={<StudioHome />} />
<Route path="/libraries-v1" element={<StudioHome />} />
<Route path="/library/create" element={<CreateLibrary />} />
<Route path="/library/:libraryId/*" element={<LibraryAuthoringPage />} />
<Route path="/library/:libraryId/*" element={<LibraryLayout />} />
<Route path="/course/:courseId/*" element={<CourseAuthoringRoutes />} />
<Route path="/course_rerun/:courseId" element={<CourseRerun />} />
{getConfig().ENABLE_ACCESSIBILITY_PAGE === 'true' && (
Expand Down Expand Up @@ -82,10 +83,12 @@ const App = () => {

return (
<AppProvider store={initializeStore()} wrapWithRouter={false}>
<QueryClientProvider client={queryClient}>
<Head />
<RouterProvider router={router} />
</QueryClientProvider>
<ToastProvider>
<QueryClientProvider client={queryClient}>
<Head />
<RouterProvider router={router} />
</QueryClientProvider>
</ToastProvider>
</AppProvider>
);
};
Expand Down
24 changes: 14 additions & 10 deletions src/library-authoring/EmptyStates.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import React from 'react';
import React, { useContext } from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import {
Button, Stack,
} from '@openedx/paragon';
import { Add } from '@openedx/paragon/icons';

import messages from './messages';
import { LibraryContext } from './common/context';

export const NoComponents = () => (
<Stack direction="horizontal" gap={3} className="mt-6 justify-content-center">
<FormattedMessage {...messages.noComponents} />
<Button iconBefore={Add}>
<FormattedMessage {...messages.addComponent} />
</Button>
</Stack>
);
export const NoComponents = () => {
const { openAddContentSidebar } = useContext(LibraryContext);

return (
<Stack direction="horizontal" gap={3} className="mt-6 justify-content-center">
<FormattedMessage {...messages.noComponents} />
<Button iconBefore={Add} onClick={() => openAddContentSidebar()}>
<FormattedMessage {...messages.addComponent} />
</Button>
</Stack>
);
};

export const NoSearchResults = () => (
<div className="d-flex mt-6 justify-content-center">
Expand Down
43 changes: 39 additions & 4 deletions src/library-authoring/LibraryAuthoringPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { fireEvent, render, waitFor } from '@testing-library/react';
import {
fireEvent,
render,
waitFor,
screen,
} from '@testing-library/react';
import fetchMock from 'fetch-mock-jest';

import initializeStore from '../store';
import { getContentSearchConfigUrl } from '../search-modal/data/api';
import mockResult from '../search-modal/__mocks__/search-result.json';
import mockEmptyResult from '../search-modal/__mocks__/empty-search-result.json';
import LibraryAuthoringPage from './LibraryAuthoringPage';
import { getContentLibraryApiUrl, type ContentLibrary } from './data/api';
import LibraryLayout from './LibraryLayout';

let store;
const mockUseParams = jest.fn();
Expand Down Expand Up @@ -61,15 +66,15 @@ const libraryData: ContentLibrary = {
allowPublicRead: false,
hasUnpublishedChanges: true,
hasUnpublishedDeletes: false,
canEditLibrary: true,
license: '',
canEditLibrary: false,
};

const RootWrapper = () => (
<AppProvider store={store}>
<IntlProvider locale="en" messages={{}}>
<QueryClientProvider client={queryClient}>
<LibraryAuthoringPage />
<LibraryLayout />
</QueryClientProvider>
</IntlProvider>
</AppProvider>
Expand Down Expand Up @@ -205,6 +210,16 @@ describe('<LibraryAuthoringPage />', () => {
expect(getByText('You have not added any content to this library yet.')).toBeInTheDocument();
});

it('show new content button', async () => {
mockUseParams.mockReturnValue({ libraryId: libraryData.id });
axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData);

render(<RootWrapper />);

expect(await screen.findByRole('heading')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /new/i })).toBeInTheDocument();
});

it('show library without search results', async () => {
mockUseParams.mockReturnValue({ libraryId: libraryData.id });
axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData);
Expand Down Expand Up @@ -233,4 +248,24 @@ describe('<LibraryAuthoringPage />', () => {
// This step is necessary to avoid the url change leak to other tests
fireEvent.click(getByRole('tab', { name: 'Home' }));
});

it('should open and close new content sidebar', async () => {
mockUseParams.mockReturnValue({ libraryId: libraryData.id });
axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData);

render(<RootWrapper />);

expect(await screen.findByRole('heading')).toBeInTheDocument();
expect(screen.queryByText(/add content/i)).not.toBeInTheDocument();

const newButton = screen.getByRole('button', { name: /new/i });
fireEvent.click(newButton);

expect(screen.getByText(/add content/i)).toBeInTheDocument();

const closeButton = screen.getByRole('button', { name: /close/i });
fireEvent.click(closeButton);

expect(screen.queryByText(/add content/i)).not.toBeInTheDocument();
});
});
Loading

0 comments on commit 1ebe818

Please sign in to comment.