diff --git a/src/features/Common/data/_test_/api.test.js b/src/features/Common/data/_test_/api.test.js index 2774140f..7c11fdbb 100644 --- a/src/features/Common/data/_test_/api.test.js +++ b/src/features/Common/data/_test_/api.test.js @@ -19,17 +19,19 @@ describe('getCoursesByInstitution', () => { }; const institutionId = 1; + const page = 1; getAuthenticatedHttpClient.mockReturnValue(httpClientMock); - getCoursesByInstitution(institutionId); + getCoursesByInstitution(institutionId, true, page); expect(getAuthenticatedHttpClient).toHaveBeenCalledTimes(1); expect(getAuthenticatedHttpClient).toHaveBeenCalledWith(); expect(httpClientMock.get).toHaveBeenCalledTimes(1); expect(httpClientMock.get).toHaveBeenCalledWith( - 'http://localhost:18000/pearson_course_operation/api/v2/courses/?limit=false&institution_id=1', + 'http://localhost:18000/pearson_course_operation/api/v2/courses/?limit=true&institution_id=1', + { params: { page } }, ); }); }); diff --git a/src/features/Common/data/api.js b/src/features/Common/data/api.js index 20311c13..b2938082 100644 --- a/src/features/Common/data/api.js +++ b/src/features/Common/data/api.js @@ -1,9 +1,14 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { getConfig } from '@edx/frontend-platform'; -function getCoursesByInstitution(institutionId) { +function getCoursesByInstitution(institutionId, limit, page, filters) { + const params = { + page, + ...filters, + }; return getAuthenticatedHttpClient().get( - `${getConfig().COURSE_OPERATIONS_API_V2_BASE_URL}/courses/?limit=false&institution_id=${institutionId}`, + `${getConfig().COURSE_OPERATIONS_API_V2_BASE_URL}/courses/?limit=${limit}&institution_id=${institutionId}`, + { params }, ); } diff --git a/src/features/Courses/CoursesFilters/_test_/index.test.jsx b/src/features/Courses/CoursesFilters/_test_/index.test.jsx new file mode 100644 index 00000000..2b4ec2bc --- /dev/null +++ b/src/features/Courses/CoursesFilters/_test_/index.test.jsx @@ -0,0 +1,102 @@ +/* eslint-disable react/prop-types */ +import React from 'react'; +import { render, fireEvent, act } from '@testing-library/react'; +import CoursesFilters from 'features/Courses/CoursesFilters'; +import '@testing-library/jest-dom/extend-expect'; + +jest.mock('react-select', () => function reactSelect({ options, valueR, onChange }) { + function handleChange(event) { + const option = options.find( + (optionR) => optionR.value === event.currentTarget.value, + ); + onChange(option); + } + + return ( + + ); +}); + +describe('InstructorsFilters Component', () => { + const mockSetFilters = jest.fn(); + + beforeEach(() => { + mockSetFilters.mockClear(); + }); + + const dataCourses = [ + { + masterCourseName: 'Demo Course 1', + numberOfClasses: 1, + missingClassesForInstructor: null, + numberOfStudents: 1, + numberOfPendingStudents: 1, + }, + { + masterCourseName: 'Demo Course 2', + numberOfClasses: 1, + missingClassesForInstructor: 1, + numberOfStudents: 16, + numberOfPendingStudents: 0, + }, + ]; + + test('call service when apply filters', async () => { + const fetchData = jest.fn(); + const resetPagination = jest.fn(); + const { getByText, getByTestId } = render( + , + ); + + const courseSelect = getByTestId('select'); + const buttonApplyFilters = getByText('Apply'); + + expect(courseSelect).toBeInTheDocument(); + fireEvent.change(courseSelect, { + target: { value: 'Demo Course 1' }, + }); + + expect(getByText('Demo Course 1')).toBeInTheDocument(); + await act(async () => { + fireEvent.click(buttonApplyFilters); + }); + expect(fetchData).toHaveBeenCalledTimes(1); + }); + + test('clear filters', async () => { + const fetchData = jest.fn(); + const resetPagination = jest.fn(); + const { getByText, getByTestId } = render( + , + ); + + const courseSelect = getByTestId('select'); + const buttonClearFilters = getByText('Reset'); + + expect(courseSelect).toBeInTheDocument(); + expect(courseSelect).toBeInTheDocument(); + fireEvent.change(courseSelect, { + target: { value: 'Demo Course 1' }, + }); + await act(async () => { + fireEvent.click(buttonClearFilters); + }); + expect(resetPagination).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/features/Courses/CoursesFilters/index.jsx b/src/features/Courses/CoursesFilters/index.jsx new file mode 100644 index 00000000..f085c127 --- /dev/null +++ b/src/features/Courses/CoursesFilters/index.jsx @@ -0,0 +1,79 @@ +import React, { useState, useEffect } from 'react'; + +import { Col, Form } from '@edx/paragon'; +import { Select, Button } from 'react-paragon-topaz'; +import { logError } from '@edx/frontend-platform/logging'; +import PropTypes from 'prop-types'; + +const CoursesFilters = ({ + fetchData, resetPagination, dataCourses, setFilters, +}) => { + const [courseOptions, setCourseOptions] = useState([]); + const [courseSelected, setCourseSelected] = useState(null); + + const handleCoursesFilter = async (e) => { + e.preventDefault(); + const form = e.target; + const formData = new FormData(form); + const formJson = Object.fromEntries(formData.entries()); + setFilters(formJson); + try { + fetchData(formJson); + } catch (error) { + logError(error); + } + }; + + const handleCleanFilters = () => { + fetchData(); + resetPagination(); + setCourseSelected(null); + setFilters({}); + }; + + useEffect(() => { + if (dataCourses.length > 0) { + const options = dataCourses.map(course => ({ + ...course, + label: course.masterCourseName, + value: course.masterCourseName, + })); + setCourseOptions(options); + } + }, [dataCourses]); + + return ( +
+
+

Find a course

+
+ + +