diff --git a/src/editors/EditorContainer.tsx b/src/editors/EditorContainer.tsx index aaa8680b61..8d26f24901 100644 --- a/src/editors/EditorContainer.tsx +++ b/src/editors/EditorContainer.tsx @@ -21,7 +21,7 @@ const EditorContainer: React.FC = ({ }) => { const { blockType, blockId } = useParams(); if (blockType === undefined || blockId === undefined) { - // This shouldn't be possible; it's just here to satisfy the type checker. + // istanbul ignore next - This shouldn't be possible; it's just here to satisfy the type checker. return
Error: missing URL parameters
; } return ( diff --git a/src/generic/toast-context/index.tsx b/src/generic/toast-context/index.tsx index f4fd2aa332..40145068fa 100644 --- a/src/generic/toast-context/index.tsx +++ b/src/generic/toast-context/index.tsx @@ -16,11 +16,11 @@ export interface ToastProviderProps { * 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({ +export const ToastContext = React.createContext({ toastMessage: null, showToast: () => {}, closeToast: () => {}, -} as ToastContextData); +}); /** * React component to provide `ToastContext` to the app diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx index 9c5d85c629..6e5bbc647e 100644 --- a/src/library-authoring/LibraryAuthoringPage.tsx +++ b/src/library-authoring/LibraryAuthoringPage.tsx @@ -120,6 +120,7 @@ const LibraryAuthoringPage = () => { const { libraryId } = useParams(); if (!libraryId) { + // istanbul ignore next - This shouldn't be possible; it's just here to satisfy the type checker. throw new Error('Rendered without libraryId URL parameter'); } const { data: libraryData, isLoading } = useContentLibrary(libraryId); diff --git a/src/library-authoring/LibraryLayout.tsx b/src/library-authoring/LibraryLayout.tsx index 65ad125b92..f956f96533 100644 --- a/src/library-authoring/LibraryLayout.tsx +++ b/src/library-authoring/LibraryLayout.tsx @@ -18,7 +18,8 @@ const LibraryLayout = () => { const queryClient = useQueryClient(); if (libraryId === undefined) { - throw new Error('Error: route is missing libraryId.'); // Should never happen + // istanbul ignore next - This shouldn't be possible; it's just here to satisfy the type checker. + throw new Error('Error: route is missing libraryId.'); } const navigate = useNavigate(); diff --git a/src/library-authoring/add-content/AddContentWorkflow.test.tsx b/src/library-authoring/add-content/AddContentWorkflow.test.tsx index 555174e2fd..5a77c92953 100644 --- a/src/library-authoring/add-content/AddContentWorkflow.test.tsx +++ b/src/library-authoring/add-content/AddContentWorkflow.test.tsx @@ -47,11 +47,8 @@ const renderOpts = { }; describe('AddContentWorkflow test', () => { - beforeEach(() => { - initializeMocks(); - }); - it('can create an HTML component', async () => { + initializeMocks(); render(, renderOpts); // Click "New [Component]" @@ -86,4 +83,22 @@ describe('AddContentWorkflow test', () => { fireEvent.click(saveButton); expect(saveSpy).toHaveBeenCalledTimes(1); }); + + it('can create a Problem component', async () => { + const { mockShowToast } = initializeMocks(); + render(, renderOpts); + + // Click "New [Component]" + const newComponentButton = await screen.findByRole('button', { name: /New/ }); + fireEvent.click(newComponentButton); + + // Pre-condition - this is NOT shown yet: + expect(screen.queryByText('Content created successfully.')).not.toBeInTheDocument(); + + // Click "Problem" to create a capa problem component + fireEvent.click(await screen.findByRole('button', { name: /Problem/ })); + + // We haven't yet implemented the problem editor, so we expect only a toast to appear + await waitFor(() => expect(mockShowToast).toHaveBeenCalledWith('Content created successfully.')); + }); }); diff --git a/src/library-authoring/component-info/ComponentDeveloperInfo.tsx b/src/library-authoring/component-info/ComponentDeveloperInfo.tsx index 983b2034ea..430d9a7636 100644 --- a/src/library-authoring/component-info/ComponentDeveloperInfo.tsx +++ b/src/library-authoring/component-info/ComponentDeveloperInfo.tsx @@ -9,6 +9,7 @@ interface Props { usageKey: string; } +/* istanbul ignore next */ export const ComponentDeveloperInfo: React.FC = ({ usageKey }) => { const { data: olx, isLoading: isOLXLoading } = useXBlockOLX(usageKey); return ( diff --git a/src/library-authoring/data/api.mocks.ts b/src/library-authoring/data/api.mocks.ts index cb744173b8..6b35bac8b5 100644 --- a/src/library-authoring/data/api.mocks.ts +++ b/src/library-authoring/data/api.mocks.ts @@ -115,6 +115,9 @@ export async function mockCreateLibraryBlock( if (args.blockType === 'html' && args.libraryId === mockContentLibrary.libraryId) { return mockCreateLibraryBlock.newHtmlData; } + if (args.blockType === 'problem' && args.libraryId === mockContentLibrary.libraryId) { + return mockCreateLibraryBlock.newProblemData; + } throw new Error(`mockCreateLibraryBlock doesn't know how to mock ${JSON.stringify(args)}`); } mockCreateLibraryBlock.newHtmlData = { @@ -125,6 +128,14 @@ mockCreateLibraryBlock.newHtmlData = { hasUnpublishedChanges: true, tagsCount: 0, } satisfies api.CreateBlockDataResponse; +mockCreateLibraryBlock.newProblemData = { + id: 'lb:Axim:TEST:problem:prob1', + defKey: 'prob1', + blockType: 'problem', + displayName: 'New Problem', + hasUnpublishedChanges: true, + tagsCount: 0, +} satisfies api.CreateBlockDataResponse; /** Apply this mock. Returns a spy object that can tell you if it's been called. */ mockCreateLibraryBlock.applyMock = () => ( jest.spyOn(api, 'createLibraryBlock').mockImplementation(mockCreateLibraryBlock) diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts index 1ffe488774..983a197819 100644 --- a/src/library-authoring/data/apiHooks.ts +++ b/src/library-authoring/data/apiHooks.ts @@ -242,6 +242,7 @@ export const useUpdateXBlockFields = (usageKey: string) => { }); }; +/* istanbul ignore next */ // This is only used in developer builds, and the associated UI doesn't work in test or prod export const useXBlockOLX = (usageKey: string) => ( useQuery({ queryKey: libraryAuthoringQueryKeys.xblockOLX(usageKey), diff --git a/src/testUtils.tsx b/src/testUtils.tsx index d306dfb1a7..283e341f48 100644 --- a/src/testUtils.tsx +++ b/src/testUtils.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; import { AxiosError } from 'axios'; +import { jest } from '@jest/globals'; import { initializeMockApp } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { IntlProvider } from '@edx/frontend-platform/i18n'; @@ -20,6 +21,7 @@ import { Routes, } from 'react-router-dom'; +import { ToastContext, type ToastContextData } from './generic/toast-context'; import initializeReduxStore from './store'; /** @deprecated Use React Query and/or regular React Context instead of redux */ @@ -27,6 +29,13 @@ let reduxStore; let queryClient; let axiosMock: MockAdapter; +/** To use this: `const { mockShowToast } = initializeMocks()` and `expect(mockShowToast).toHaveBeenCalled()` */ +let mockToastContext: ToastContextData = { + showToast: jest.fn(), + closeToast: jest.fn(), + toastMessage: null, +}; + export interface RouteOptions { /** The URL path, like '/libraries/:libraryId' */ path?: string; @@ -106,9 +115,11 @@ function makeWrapper({ ...routeArgs }: RouteOptions) { - - {children} - + + + {children} + + @@ -152,9 +163,17 @@ export function initializeMocks({ user = defaultUser } = {}) { }); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + // Reset `mockToastContext` for this current test + mockToastContext = { + showToast: jest.fn(), + closeToast: jest.fn(), + toastMessage: null, + }; + return { reduxStore, axiosMock, + mockShowToast: mockToastContext.showToast, }; }