Files
frontend-app-authoring/src/library-authoring/components/LibraryComponents.test.tsx

179 lines
6.4 KiB
TypeScript

import fetchMock from 'fetch-mock-jest';
import {
fireEvent,
render,
screen,
initializeMocks,
} from '../../testUtils';
import { getContentSearchConfigUrl } from '../../search-manager/data/api';
import { mockLibraryBlockTypes, mockContentLibrary } from '../data/api.mocks';
import mockEmptyResult from '../../search-modal/__mocks__/empty-search-result.json';
import { LibraryProvider } from '../common/context';
import { libraryComponentsMock } from '../__mocks__';
import LibraryComponents from './LibraryComponents';
const searchEndpoint = 'http://mock.meilisearch.local/multi-search';
mockLibraryBlockTypes.applyMock();
mockContentLibrary.applyMock();
const mockFetchNextPage = jest.fn();
const mockUseSearchContext = jest.fn();
const data = {
totalHits: 1,
hits: [],
isFetchingNextPage: false,
hasNextPage: false,
fetchNextPage: mockFetchNextPage,
searchKeywords: '',
isFiltered: false,
isLoading: false,
};
const returnEmptyResult = (_url: string, req) => {
const requestData = JSON.parse(req.body?.toString() ?? '');
const query = requestData?.queries[0]?.q ?? '';
// We have to replace the query (search keywords) in the mock results with the actual query,
// because otherwise we may have an inconsistent state that causes more queries and unexpected results.
mockEmptyResult.results[0].query = query;
// And fake the required '_formatted' fields; it contains the highlighting <mark>...</mark> around matched words
// eslint-disable-next-line no-underscore-dangle, no-param-reassign
mockEmptyResult.results[0]?.hits.forEach((hit: any) => { hit._formatted = { ...hit }; });
return mockEmptyResult;
};
jest.mock('../../search-manager', () => ({
...jest.requireActual('../../search-manager'),
useSearchContext: () => mockUseSearchContext(),
}));
const clipboardBroadcastChannelMock = {
postMessage: jest.fn(),
close: jest.fn(),
};
(global as any).BroadcastChannel = jest.fn(() => clipboardBroadcastChannelMock);
const withLibraryId = (libraryId: string) => ({
extraWrapper: ({ children }: { children: React.ReactNode }) => (
<LibraryProvider libraryId={libraryId}>{children}</LibraryProvider>
),
});
describe('<LibraryComponents />', () => {
beforeEach(() => {
const { axiosMock } = initializeMocks();
fetchMock.post(searchEndpoint, returnEmptyResult, { overwriteRoutes: true });
// The API method to get the Meilisearch connection details uses Axios:
axiosMock.onGet(getContentSearchConfigUrl()).reply(200, {
url: 'http://mock.meilisearch.local',
index_name: 'studio',
api_key: 'test-key',
});
});
afterEach(() => {
fetchMock.reset();
mockFetchNextPage.mockReset();
});
it('should render empty state', async () => {
mockUseSearchContext.mockReturnValue({
...data,
totalHits: 0,
});
render(<LibraryComponents variant="full" />, withLibraryId(mockContentLibrary.libraryId));
expect(await screen.findByText(/you have not added any content to this library yet\./i));
expect(await screen.findByRole('button', { name: /add component/i })).toBeInTheDocument();
});
it('should render empty state without add content button', async () => {
mockUseSearchContext.mockReturnValue({
...data,
totalHits: 0,
});
render(<LibraryComponents variant="full" />, withLibraryId(mockContentLibrary.libraryIdReadOnly));
expect(await screen.findByText(/you have not added any content to this library yet\./i));
expect(screen.queryByRole('button', { name: /add component/i })).not.toBeInTheDocument();
});
it('should render a spinner while loading', async () => {
mockUseSearchContext.mockReturnValue({
...data,
isLoading: true,
});
render(<LibraryComponents variant="full" />, withLibraryId(mockContentLibrary.libraryId));
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('should render components in full variant', async () => {
mockUseSearchContext.mockReturnValue({
...data,
hits: libraryComponentsMock,
});
render(<LibraryComponents variant="full" />, withLibraryId(mockContentLibrary.libraryId));
expect(await screen.findByText('This is a text: ID=1')).toBeInTheDocument();
expect(screen.getByText('This is a text: ID=2')).toBeInTheDocument();
expect(screen.getByText('Video Component 3')).toBeInTheDocument();
expect(screen.getByText('Video Component 4')).toBeInTheDocument();
expect(screen.getByText('This is a problem: ID=5')).toBeInTheDocument();
expect(screen.getByText('This is a problem: ID=6')).toBeInTheDocument();
});
it('should render components in preview variant', async () => {
mockUseSearchContext.mockReturnValue({
...data,
hits: libraryComponentsMock,
});
render(<LibraryComponents variant="preview" />, withLibraryId(mockContentLibrary.libraryId));
expect(await screen.findByText('This is a text: ID=1')).toBeInTheDocument();
expect(screen.getByText('This is a text: ID=2')).toBeInTheDocument();
expect(screen.getByText('Video Component 3')).toBeInTheDocument();
expect(screen.getByText('Video Component 4')).toBeInTheDocument();
expect(screen.queryByText('This is a problem: ID=5')).not.toBeInTheDocument();
expect(screen.queryByText('This is a problem: ID=6')).not.toBeInTheDocument();
});
it('should call `fetchNextPage` on scroll to bottom in full variant', async () => {
mockUseSearchContext.mockReturnValue({
...data,
hits: libraryComponentsMock,
hasNextPage: true,
});
render(<LibraryComponents variant="full" />, withLibraryId(mockContentLibrary.libraryId));
Object.defineProperty(window, 'innerHeight', { value: 800 });
Object.defineProperty(document.body, 'scrollHeight', { value: 1600 });
fireEvent.scroll(window, { target: { scrollY: 1000 } });
expect(mockFetchNextPage).toHaveBeenCalled();
});
it('should not call `fetchNextPage` on scroll to bottom in preview variant', async () => {
mockUseSearchContext.mockReturnValue({
...data,
hits: libraryComponentsMock,
hasNextPage: true,
});
render(<LibraryComponents variant="preview" />, withLibraryId(mockContentLibrary.libraryId));
Object.defineProperty(window, 'innerHeight', { value: 800 });
Object.defineProperty(document.body, 'scrollHeight', { value: 1600 });
fireEvent.scroll(window, { target: { scrollY: 1000 } });
expect(mockFetchNextPage).not.toHaveBeenCalled();
});
});