generated from openedx/frontend-template-application
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from Pearson-Advance/vue/PADV-746
PADV-746 feat: courses page
- Loading branch information
Showing
17 changed files
with
655 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
src/features/Courses/CoursesFilters/_test_/index.test.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<select data-testid="select" value={valueR} onChange={handleChange}> | ||
{options.map(({ label, value }) => ( | ||
<option key={value} value={value}> | ||
{label} | ||
</option> | ||
))} | ||
</select> | ||
); | ||
}); | ||
|
||
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( | ||
<CoursesFilters | ||
fetchData={fetchData} | ||
resetPagination={resetPagination} | ||
setFilters={mockSetFilters} | ||
dataCourses={dataCourses} | ||
/>, | ||
); | ||
|
||
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( | ||
<CoursesFilters | ||
fetchData={fetchData} | ||
resetPagination={resetPagination} | ||
setFilters={mockSetFilters} | ||
dataCourses={dataCourses} | ||
/>, | ||
); | ||
|
||
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div className="filter-container justify-content-center row"> | ||
<div className="col-11"> | ||
<h3>Find a course</h3> | ||
<Form className="row justify-content-center" onSubmit={handleCoursesFilter}> | ||
<Form.Row className="col-12"> | ||
<Form.Group as={Col}> | ||
<Select | ||
placeholder="Course" | ||
name="course_name" | ||
className="mr-2" | ||
options={courseOptions} | ||
onChange={option => setCourseSelected(option)} | ||
value={courseSelected} | ||
/> | ||
</Form.Group> | ||
<div className="d-flex col-3 justify-content-end mr-3 align-items-start"> | ||
<Button onClick={handleCleanFilters} variant="tertiary" text className="mr-2">Reset</Button> | ||
<Button type="submit">Apply</Button> | ||
</div> | ||
</Form.Row> | ||
</Form> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
CoursesFilters.propTypes = { | ||
fetchData: PropTypes.func.isRequired, | ||
resetPagination: PropTypes.func.isRequired, | ||
dataCourses: PropTypes.instanceOf(Array).isRequired, | ||
setFilters: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default CoursesFilters; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import React from 'react'; | ||
import axios from 'axios'; | ||
import CoursesPage from 'features/Courses/CoursesPage'; | ||
import { | ||
render, | ||
waitFor, | ||
} from '@testing-library/react'; | ||
import '@testing-library/jest-dom/extend-expect'; | ||
|
||
jest.mock('axios'); | ||
|
||
jest.mock('@edx/frontend-platform/logging', () => ({ | ||
logError: jest.fn(), | ||
})); | ||
|
||
const mockResponse = { | ||
data: { | ||
results: [ | ||
{ | ||
masterCourseName: 'Demo Course 1', | ||
numberOfClasses: 1, | ||
missingClassesForInstructor: null, | ||
numberOfStudents: 1, | ||
numberOfPendingStudents: 1, | ||
}, | ||
{ | ||
masterCourseName: 'Demo Course 2', | ||
numberOfClasses: 1, | ||
missingClassesForInstructor: 1, | ||
numberOfStudents: 16, | ||
numberOfPendingStudents: 0, | ||
}, | ||
], | ||
count: 2, | ||
num_pages: 1, | ||
current_page: 1, | ||
}, | ||
}; | ||
|
||
describe('CoursesPage', () => { | ||
it('renders courses data and pagination', async () => { | ||
axios.get.mockResolvedValue(mockResponse); | ||
|
||
const component = render(<CoursesPage />); | ||
|
||
waitFor(() => { | ||
expect(component.container).toHaveTextContent('Demo Course 1'); | ||
expect(component.container).toHaveTextContent('Demo Course 2'); | ||
expect(component.container).toHaveTextContent('Ready'); | ||
expect(component.container).toHaveTextContent('Missing (1)'); | ||
expect(component.container).toHaveTextContent('Pending (1)'); | ||
expect(component.container).toHaveTextContent('Complete'); | ||
expect(component.container).toHaveTextContent('1/2'); | ||
expect(component.container).toHaveTextContent('16/16'); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { | ||
FETCH_COURSES_DATA_FAILURE, | ||
FETCH_COURSES_DATA_REQUEST, | ||
FETCH_COURSES_DATA_SUCCESS, | ||
UPDATE_CURRENT_PAGE, | ||
} from 'features/Courses/actionTypes'; | ||
import { RequestStatus } from 'features/constants'; | ||
import reducer from 'features/Courses/CoursesPage/reducer'; | ||
|
||
describe('Instructor page reducers', () => { | ||
const initialState = { | ||
data: [], | ||
status: RequestStatus.SUCCESS, | ||
error: null, | ||
currentPage: 1, | ||
numPages: 0, | ||
}; | ||
|
||
test('should handle FETCH_COURSES_DATA_REQUEST', () => { | ||
const state = { | ||
...initialState, | ||
status: RequestStatus.LOADING, | ||
}; | ||
const action = { | ||
type: FETCH_COURSES_DATA_REQUEST, | ||
}; | ||
expect(reducer(state, action)).toEqual(state); | ||
}); | ||
|
||
test('should handle FETCH_COURSES_DATA_SUCCESSS', () => { | ||
const state = { | ||
...initialState, | ||
status: RequestStatus.SUCCESS, | ||
count: 0, | ||
}; | ||
const action = { | ||
type: FETCH_COURSES_DATA_SUCCESS, | ||
payload: { | ||
results: [], | ||
count: 0, | ||
numPages: 0, | ||
}, | ||
}; | ||
expect(reducer(state, action)).toEqual(state); | ||
}); | ||
|
||
test('should handle FETCH_COURSES_DATA_FAILURE', () => { | ||
const state = { | ||
...initialState, | ||
status: RequestStatus.ERROR, | ||
error: '', | ||
}; | ||
const action = { | ||
type: FETCH_COURSES_DATA_FAILURE, | ||
payload: '', | ||
}; | ||
expect(reducer(state, action)).toEqual(state); | ||
}); | ||
|
||
test('should handle UPDATE_CURRENT_PAGE', () => { | ||
const state = { | ||
...initialState, | ||
currentPage: 1, | ||
}; | ||
const action = { | ||
type: UPDATE_CURRENT_PAGE, | ||
payload: 1, | ||
}; | ||
expect(reducer(state, action)).toEqual(state); | ||
}); | ||
}); |
Oops, something went wrong.