- TNL-11973: Previously Filters functionality was only working for subsections and units inside sections. Now sections are also filtered. - TNL-11974: New request, Show "no results found" if no results match the filters - TNL-11975: UI Change, Align filter menu popup to left side of filter button - TNL-11976: UI Change, Remove underline below "Course optimizer" title - TNL-11978: UI Change, Change title to "Scan my course" - TNL-11989: UI Change, Use empty space to display link, don't truncate text before the space runs out - TNL-11977: New request, Remove this stuff(scanning steps) when scan is complete, it'll disappear after 2.5 seconds - TNL-11979: UI Change, Move "This tool will scan your course..." text inside of Scan card - TNL-11980: UI Change, Move "Last scanned on..." date text below Scan button - TNL-11981: UI Change, Remove icon from "Start scanning" button - TNL-11983: UI Change, "Start scanning" button should be smaller, made it medium sized - TNL-11984: UI Change, Remove dividing line under subsection name in expanded card - TNL-11985: UI Change, Fix alignment of dividing lines, links, and icons in expanded cards to match Figma. - TNL-11986: UI Change, Match color of the broken icon with other Icons - TNL-11987: UI Change, Fix alignment of Filter chips to match Figma - Also added Beta Badge for course optimizer. - Added tests for codecov coverage
366 lines
14 KiB
JavaScript
366 lines
14 KiB
JavaScript
/* eslint-disable @typescript-eslint/no-shadow */
|
|
/* eslint-disable react/jsx-filename-extension */
|
|
import {
|
|
fireEvent, render, waitFor, screen,
|
|
} from '@testing-library/react';
|
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
|
import { AppProvider } from '@edx/frontend-platform/react';
|
|
import { initializeMockApp } from '@edx/frontend-platform';
|
|
|
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
import MockAdapter from 'axios-mock-adapter';
|
|
import initializeStore from '../store';
|
|
import messages from './messages';
|
|
import generalMessages from '../messages';
|
|
import scanResultsMessages from './scan-results/messages';
|
|
import CourseOptimizerPage, { pollLinkCheckDuringScan } from './CourseOptimizerPage';
|
|
import { postLinkCheckCourseApiUrl, getLinkCheckStatusApiUrl } from './data/api';
|
|
import { mockApiResponse, mockApiResponseForNoResultFound } from './mocks/mockApiResponse';
|
|
import * as thunks from './data/thunks';
|
|
|
|
let store;
|
|
let axiosMock;
|
|
const courseId = '123';
|
|
const courseName = 'About Node JS';
|
|
|
|
jest.mock('../generic/model-store', () => ({
|
|
useModel: jest.fn().mockReturnValue({
|
|
name: courseName,
|
|
}),
|
|
}));
|
|
|
|
const OptimizerPage = () => (
|
|
<AppProvider store={store}>
|
|
<IntlProvider locale="en" messages={{}}>
|
|
<CourseOptimizerPage courseId={courseId} />
|
|
</IntlProvider>
|
|
</AppProvider>
|
|
);
|
|
|
|
const setupOptimizerPage = async (apiResponse = mockApiResponse) => {
|
|
axiosMock.onGet(getLinkCheckStatusApiUrl(courseId)).reply(200, apiResponse);
|
|
const optimizerPage = render(<OptimizerPage />);
|
|
|
|
// Click the scan button
|
|
fireEvent.click(optimizerPage.getByText(messages.buttonTitle.defaultMessage));
|
|
|
|
// Wait for the scan results to load
|
|
await waitFor(() => {
|
|
expect(optimizerPage.getByText('Introduction to Programming')).toBeInTheDocument();
|
|
});
|
|
|
|
// Click on filters button
|
|
fireEvent.click(optimizerPage.getByText(scanResultsMessages.filterButtonLabel.defaultMessage));
|
|
|
|
return optimizerPage;
|
|
};
|
|
|
|
describe('CourseOptimizerPage', () => {
|
|
describe('pollLinkCheckDuringScan', () => {
|
|
let mockFetchLinkCheckStatus;
|
|
beforeEach(() => {
|
|
mockFetchLinkCheckStatus = jest.fn();
|
|
jest.spyOn(thunks, 'fetchLinkCheckStatus').mockImplementation(mockFetchLinkCheckStatus);
|
|
jest.useFakeTimers();
|
|
jest.spyOn(global, 'setInterval').mockImplementation((cb) => { cb(); return true; });
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllTimers();
|
|
jest.useRealTimers();
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it('should start polling if linkCheckInProgress has never been started (is null)', () => {
|
|
const linkCheckInProgress = null;
|
|
const interval = { current: null };
|
|
const dispatch = jest.fn();
|
|
const courseId = 'course-123';
|
|
pollLinkCheckDuringScan(linkCheckInProgress, interval, dispatch, courseId);
|
|
expect(interval.current).toBeTruthy();
|
|
expect(mockFetchLinkCheckStatus).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should start polling if link check is in progress', () => {
|
|
const linkCheckInProgress = true;
|
|
const interval = { current: null };
|
|
const dispatch = jest.fn();
|
|
const courseId = 'course-123';
|
|
pollLinkCheckDuringScan(linkCheckInProgress, interval, dispatch, courseId);
|
|
expect(interval.current).toBeTruthy();
|
|
});
|
|
|
|
it('should not start polling if link check is not in progress', () => {
|
|
const linkCheckInProgress = false;
|
|
const interval = { current: null };
|
|
const dispatch = jest.fn();
|
|
const courseId = 'course-123';
|
|
pollLinkCheckDuringScan(linkCheckInProgress, interval, dispatch, courseId);
|
|
expect(interval.current).toBeFalsy();
|
|
});
|
|
|
|
it('should clear the interval if link check is finished', () => {
|
|
const linkCheckInProgress = false;
|
|
const interval = { current: 1 };
|
|
const dispatch = jest.fn();
|
|
const courseId = 'course-123';
|
|
pollLinkCheckDuringScan(linkCheckInProgress, interval, dispatch, courseId);
|
|
expect(interval.current).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('CourseOptimizerPage component', () => {
|
|
beforeEach(() => {
|
|
jest.useRealTimers();
|
|
jest.clearAllMocks();
|
|
initializeMockApp({
|
|
authenticatedUser: {
|
|
userId: 3,
|
|
username: 'abc123',
|
|
administrator: true,
|
|
roles: [],
|
|
},
|
|
});
|
|
store = initializeStore();
|
|
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
|
axiosMock
|
|
.onPost(postLinkCheckCourseApiUrl(courseId))
|
|
.reply(200, { LinkCheckStatus: 'In-Progress' });
|
|
axiosMock
|
|
.onGet(getLinkCheckStatusApiUrl(courseId))
|
|
.reply(200, mockApiResponse);
|
|
});
|
|
|
|
it('should render the component', () => {
|
|
const { getByText, queryByText } = render(<OptimizerPage />);
|
|
expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
|
|
expect(getByText(messages.buttonTitle.defaultMessage)).toBeInTheDocument();
|
|
expect(queryByText(messages.preparingStepTitle)).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should start scan after clicking the scan button', async () => {
|
|
const { getByText } = render(<OptimizerPage />);
|
|
expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
|
|
fireEvent.click(getByText(messages.buttonTitle.defaultMessage));
|
|
await waitFor(() => {
|
|
expect(getByText(messages.preparingStepTitle.defaultMessage)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show no broken links found message', async () => {
|
|
axiosMock
|
|
.onGet(getLinkCheckStatusApiUrl(courseId))
|
|
.reply(200, { LinkCheckStatus: 'Succeeded' });
|
|
const { getByText } = render(<OptimizerPage />);
|
|
expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
|
|
fireEvent.click(getByText(messages.buttonTitle.defaultMessage));
|
|
await waitFor(() => {
|
|
expect(getByText(scanResultsMessages.noBrokenLinksCard.defaultMessage)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show error message if request does not go through', async () => {
|
|
axiosMock
|
|
.onPost(postLinkCheckCourseApiUrl(courseId))
|
|
.reply(500);
|
|
render(<OptimizerPage />);
|
|
expect(screen.getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
|
|
fireEvent.click(screen.getByText(messages.buttonTitle.defaultMessage));
|
|
await waitFor(() => {
|
|
expect(screen.getByText(generalMessages.supportText.defaultMessage)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show only locked links when lockedLinks filter is selected', async () => {
|
|
const {
|
|
getByText,
|
|
getByLabelText,
|
|
queryByText,
|
|
container,
|
|
} = await setupOptimizerPage();
|
|
// Check if the modal is opened
|
|
expect(getByText('Locked')).toBeInTheDocument();
|
|
// Select the broken links checkbox
|
|
fireEvent.click(getByLabelText(scanResultsMessages.lockedLabel.defaultMessage));
|
|
|
|
const collapsibleTrigger = container.querySelector('.collapsible-trigger');
|
|
expect(collapsibleTrigger).toBeInTheDocument();
|
|
fireEvent.click(collapsibleTrigger);
|
|
|
|
await waitFor(() => {
|
|
expect(getByText('Test Locked Links')).toBeInTheDocument();
|
|
expect(queryByText('Test Broken Links')).not.toBeInTheDocument();
|
|
expect(queryByText('Test Manual Links')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show only broken links when brokenLinks filter is selected', async () => {
|
|
const {
|
|
getByText,
|
|
getByLabelText,
|
|
queryByText,
|
|
container,
|
|
} = await setupOptimizerPage();
|
|
// Check if the modal is opened
|
|
expect(getByText('Broken')).toBeInTheDocument();
|
|
// Select the broken links checkbox
|
|
fireEvent.click(getByLabelText(scanResultsMessages.brokenLabel.defaultMessage));
|
|
|
|
const collapsibleTrigger = container.querySelector('.collapsible-trigger');
|
|
expect(collapsibleTrigger).toBeInTheDocument();
|
|
fireEvent.click(collapsibleTrigger);
|
|
|
|
await waitFor(() => {
|
|
expect(getByText('Test Broken Links')).toBeInTheDocument();
|
|
expect(queryByText('Test Locked Links')).not.toBeInTheDocument();
|
|
expect(queryByText('Test Manual Links')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show only manual links when manualLinks filter is selected and show all links when clicked again', async () => {
|
|
const {
|
|
getByText,
|
|
getByLabelText,
|
|
queryByText,
|
|
container,
|
|
} = await setupOptimizerPage();
|
|
// Check if the modal is opened
|
|
expect(getByText('Manual')).toBeInTheDocument();
|
|
// Select the manual links checkbox
|
|
fireEvent.click(getByLabelText(scanResultsMessages.manualLabel.defaultMessage));
|
|
|
|
const collapsibleTrigger = container.querySelector('.collapsible-trigger');
|
|
expect(collapsibleTrigger).toBeInTheDocument();
|
|
fireEvent.click(collapsibleTrigger);
|
|
|
|
await waitFor(() => {
|
|
expect(getByText('Test Manual Links')).toBeInTheDocument();
|
|
expect(queryByText('Test Broken Links')).not.toBeInTheDocument();
|
|
expect(queryByText('Test Locked Links')).not.toBeInTheDocument();
|
|
});
|
|
|
|
// Click the manual links checkbox again to clear the filter
|
|
fireEvent.click(getByLabelText(scanResultsMessages.manualLabel.defaultMessage));
|
|
|
|
// Assert that all links are displayed after clearing the filter
|
|
await waitFor(() => {
|
|
expect(getByText('Test Broken Links')).toBeInTheDocument();
|
|
expect(getByText('Test Manual Links')).toBeInTheDocument();
|
|
expect(getByText('Test Locked Links')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show only manual & locked links when manual & locked Links filters are selected, ignore broken links', async () => {
|
|
const {
|
|
getByText,
|
|
getByLabelText,
|
|
queryByText,
|
|
container,
|
|
} = await setupOptimizerPage();
|
|
// Check if the modal is opened
|
|
expect(getByText('Manual')).toBeInTheDocument();
|
|
expect(getByText('Locked')).toBeInTheDocument();
|
|
// Select the manual & locked links checkbox
|
|
fireEvent.click(getByLabelText(scanResultsMessages.manualLabel.defaultMessage));
|
|
fireEvent.click(getByLabelText(scanResultsMessages.lockedLabel.defaultMessage));
|
|
|
|
const collapsibleTrigger = container.querySelector('.collapsible-trigger');
|
|
expect(collapsibleTrigger).toBeInTheDocument();
|
|
fireEvent.click(collapsibleTrigger);
|
|
|
|
await waitFor(() => {
|
|
expect(getByText('Test Manual Links')).toBeInTheDocument();
|
|
expect(getByText('Test Locked Links')).toBeInTheDocument();
|
|
expect(queryByText('Test Broken Links')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show all links when all filters are selected', async () => {
|
|
const {
|
|
getByText,
|
|
getByLabelText,
|
|
container,
|
|
} = await setupOptimizerPage();
|
|
// Check if the modal is opened
|
|
expect(getByText('Broken')).toBeInTheDocument();
|
|
expect(getByText('Manual')).toBeInTheDocument();
|
|
expect(getByText('Locked')).toBeInTheDocument();
|
|
// Select the all checkboxes
|
|
fireEvent.click(getByLabelText(scanResultsMessages.brokenLabel.defaultMessage));
|
|
fireEvent.click(getByLabelText(scanResultsMessages.lockedLabel.defaultMessage));
|
|
fireEvent.click(getByLabelText(scanResultsMessages.manualLabel.defaultMessage));
|
|
|
|
const collapsibleTrigger = container.querySelector('.collapsible-trigger');
|
|
expect(collapsibleTrigger).toBeInTheDocument();
|
|
fireEvent.click(collapsibleTrigger);
|
|
|
|
await waitFor(() => {
|
|
expect(getByText('Test Broken Links')).toBeInTheDocument();
|
|
expect(getByText('Test Manual Links')).toBeInTheDocument();
|
|
expect(getByText('Test Locked Links')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show only manual links when the broken chip is clicked and show all links when clear filters button is clicked', async () => {
|
|
const {
|
|
getByText,
|
|
getByLabelText,
|
|
getByTestId,
|
|
queryByText,
|
|
container,
|
|
} = await setupOptimizerPage();
|
|
// Select broken & manual link checkboxes
|
|
fireEvent.click(getByLabelText(scanResultsMessages.brokenLabel.defaultMessage));
|
|
fireEvent.click(getByLabelText(scanResultsMessages.manualLabel.defaultMessage));
|
|
|
|
const collapsibleTrigger = container.querySelector('.collapsible-trigger');
|
|
expect(collapsibleTrigger).toBeInTheDocument();
|
|
fireEvent.click(collapsibleTrigger);
|
|
|
|
// Assert that all links are displayed
|
|
await waitFor(() => {
|
|
expect(getByText('Test Broken Links')).toBeInTheDocument();
|
|
expect(getByText('Test Manual Links')).toBeInTheDocument();
|
|
expect(queryByText('Test Locked Links')).not.toBeInTheDocument();
|
|
});
|
|
|
|
// Click on the "Broken" chip to filter the results
|
|
const brokenChip = getByTestId('chip-brokenLinks');
|
|
fireEvent.click(brokenChip);
|
|
|
|
// Assert that only manual links are displayed
|
|
await waitFor(() => {
|
|
expect(queryByText('Test Broken Links')).not.toBeInTheDocument();
|
|
expect(getByText('Test Manual Links')).toBeInTheDocument();
|
|
expect(queryByText('Test Locked Links')).not.toBeInTheDocument();
|
|
});
|
|
|
|
// Click the "Clear filters" button
|
|
const clearFiltersButton = getByText(scanResultsMessages.clearFilters.defaultMessage);
|
|
fireEvent.click(clearFiltersButton);
|
|
|
|
// Assert that all links are displayed after clearing filters
|
|
await waitFor(() => {
|
|
expect(getByText('Test Broken Links')).toBeInTheDocument();
|
|
expect(getByText('Test Manual Links')).toBeInTheDocument();
|
|
expect(getByText('Test Locked Links')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show no results found message when filter with no links is selected', async () => {
|
|
const {
|
|
getByText,
|
|
getByLabelText,
|
|
} = await setupOptimizerPage(mockApiResponseForNoResultFound);
|
|
// Check if the modal is opened
|
|
expect(getByText('Locked')).toBeInTheDocument();
|
|
// Select the broken links checkbox
|
|
fireEvent.click(getByLabelText(scanResultsMessages.lockedLabel.defaultMessage));
|
|
|
|
await waitFor(() => {
|
|
expect(getByText(scanResultsMessages.noResultsFound.defaultMessage)).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
});
|