Skip to content

Commit

Permalink
feat: Add instructors component
Browse files Browse the repository at this point in the history
  • Loading branch information
AuraAlba committed Oct 2, 2023
1 parent 309c0cf commit d01f951
Show file tree
Hide file tree
Showing 15 changed files with 484 additions and 8 deletions.
57 changes: 57 additions & 0 deletions src/features/Instructors/InstructorsPage/_test_/index.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import axios from 'axios';
import {
render,
waitFor,
} from '@testing-library/react';
import InstructorsPage from 'features/Instructors/InstructorsPage';
import '@testing-library/jest-dom/extend-expect';

jest.mock('axios');

jest.mock('@edx/frontend-platform/logging', () => ({
logError: jest.fn(),
}));

const mockResponse = {
data: {
results: [
{
instructorUsername: 'Instructor1',
instructorName: 'Instructor 1',
instructorEmail: '[email protected]',
ccxId: 'CCX1',
ccxName: 'CCX 1',
},
{
instructorUsername: 'Instructor2',
instructorName: 'Instructor 2',
instructorEmail: '[email protected]',
ccxId: 'CCX2',
ccxName: 'CCX 2',
},
],
count: 2,
num_pages: 1,
current_page: 1,
},
};

describe('InstructorPage', () => {
test('render instructor page', () => {
axios.get.mockResolvedValue(mockResponse);

const component = render(<InstructorsPage />);

waitFor(() => {
expect(component.container).toHaveTextContent('Instructor1');
expect(component.container).toHaveTextContent('Instructor2');
expect(component.container).toHaveTextContent('Instructor 1');
expect(component.container).toHaveTextContent('Instructor 2');
expect(component.container).toHaveTextContent('[email protected]');
expect(component.container).toHaveTextContent('[email protected]');
expect(component.container).toHaveTextContent('CCX1');
expect(component.container).toHaveTextContent('CCX 2');
});
});
});
70 changes: 70 additions & 0 deletions src/features/Instructors/InstructorsPage/_test_/reducer.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
FETCH_INSTRUCTOR_DATA_REQUEST,
FETCH_INSTRUCTOR_DATA_SUCCESS,
FETCH_INSTRUCTOR_DATA_FAILURE,
UPDATE_CURRENT_PAGE,
} from 'features/Instructors/actionTypes';
import { RequestStatus } from 'features/constants';
import reducer from 'features/Instructors/InstructorsPage/reducer';

describe('Instructor page reducers', () => {
const initialState = {
data: [],
error: null,
currentPage: 1,
numPages: 0,
};

test('should handle FETCH_INSTRUCTOR_DATA_REQUEST', () => {
const state = {
...initialState,
status: RequestStatus.LOADING,
};
const action = {
type: FETCH_INSTRUCTOR_DATA_REQUEST,
};
expect(reducer(state, action)).toEqual(state);
});

test('should handle FFETCH_INSTRUCTOR_DATA_SUCCESS', () => {
const state = {
...initialState,
status: RequestStatus.SUCCESS,
count: 0,
};
const action = {
type: FETCH_INSTRUCTOR_DATA_SUCCESS,
payload: {
results: [],
count: 0,
numPages: 0,
},
};
expect(reducer(state, action)).toEqual(state);
});

test('should handle FETCH_INSTRUCTOR_DATA_FAILURE', () => {
const state = {
...initialState,
status: RequestStatus.ERROR,
error: '',
};
const action = {
type: FETCH_INSTRUCTOR_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);
});
});
75 changes: 75 additions & 0 deletions src/features/Instructors/InstructorsPage/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useEffect, useState, useReducer } from 'react';
import { camelCaseObject } from '@edx/frontend-platform';

import { logError } from '@edx/frontend-platform/logging';
import Container from '@edx/paragon/dist/Container';
import {
Pagination,
} from '@edx/paragon';
import InstructorsTable from 'features/Instructors/InstructorsTable';

import { getInstructorData } from 'features/Instructors/data/api';
import {
FETCH_INSTRUCTOR_DATA_REQUEST,
FETCH_INSTRUCTOR_DATA_SUCCESS,
FETCH_INSTRUCTOR_DATA_FAILURE,
UPDATE_CURRENT_PAGE,
} from 'features/Instructors/actionTypes';
import { RequestStatus } from 'features/constants';
import reducer from 'features/Instructors/InstructorsPage/reducer';

const initialState = {
data: [],
status: RequestStatus.SUCCESS,
error: null,
currentPage: 1,
numPages: 0,
};

const InstructorsPage = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const [currentPage, setCurrentPage] = useState(1);

const fetchData = async () => {
dispatch({ type: FETCH_INSTRUCTOR_DATA_REQUEST });

try {
const response = camelCaseObject(await getInstructorData(currentPage));
dispatch({ type: FETCH_INSTRUCTOR_DATA_SUCCESS, payload: response.data });
} catch (error) {
dispatch({ type: FETCH_INSTRUCTOR_DATA_FAILURE, payload: error });
logError(error);
}
};

useEffect(() => {
fetchData(); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage]);

const handlePagination = (targetPage) => {
setCurrentPage(targetPage);
dispatch({ type: UPDATE_CURRENT_PAGE, payload: targetPage });
fetchData();
};

return (
<Container size="xl" className="px-3">
<h2>Instructors</h2>
<InstructorsTable
data={state.data}
count={state.count}
/>
<Pagination
paginationLabel="paginationNavigation"
pageCount={state.numPages}
currentPage={currentPage}
onPageSelect={handlePagination}
variant="reduced"
className="mx-auto"
size="small"
/>
</Container>
);
};

export default InstructorsPage;
39 changes: 39 additions & 0 deletions src/features/Instructors/InstructorsPage/reducer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
FETCH_INSTRUCTOR_DATA_REQUEST,
FETCH_INSTRUCTOR_DATA_SUCCESS,
FETCH_INSTRUCTOR_DATA_FAILURE,
UPDATE_CURRENT_PAGE,
} from 'features/Instructors/actionTypes';
import { RequestStatus } from 'features/constants';

const reducer = (state, action) => {
switch (action.type) {
case FETCH_INSTRUCTOR_DATA_REQUEST:
return { ...state, status: RequestStatus.LOADING };
case FETCH_INSTRUCTOR_DATA_SUCCESS: {
const { results, count, numPages } = action.payload;
return {
...state,
status: RequestStatus.SUCCESS,
data: results,
numPages,
count,
};
}
case FETCH_INSTRUCTOR_DATA_FAILURE:
return {
...state,
status: RequestStatus.ERROR,
error: action.payload,
};
case UPDATE_CURRENT_PAGE:
return {
...state,
currentPage: action.payload,
};
default:
return state;
}
};

export default reducer;
31 changes: 31 additions & 0 deletions src/features/Instructors/InstructorsTable/_test_/columns.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { columns } from 'features/Instructors/InstructorsTable/columns';

describe('columns', () => {
test('returns an array of columns with correct properties', () => {
expect(columns).toBeInstanceOf(Array);
expect(columns).toHaveLength(5);

const [
usernameColumn,
nameColumn,
emailColumn,
courseNameColumn,
courseKeyColumn,
] = columns;

expect(usernameColumn).toHaveProperty('Header', 'User Name');
expect(usernameColumn).toHaveProperty('accessor', 'instructorUsername');

expect(nameColumn).toHaveProperty('Header', 'Name');
expect(nameColumn).toHaveProperty('accessor', 'instructorName');

expect(emailColumn).toHaveProperty('Header', 'Email');
expect(emailColumn).toHaveProperty('accessor', 'instructorEmail');

expect(courseNameColumn).toHaveProperty('Header', 'Course key');
expect(courseNameColumn).toHaveProperty('accessor', 'ccxId');

expect(courseKeyColumn).toHaveProperty('Header', 'Course name');
expect(courseKeyColumn).toHaveProperty('accessor', 'ccxName');
});
});
49 changes: 49 additions & 0 deletions src/features/Instructors/InstructorsTable/_test_/index.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';

import InstructorsTable from 'features/Instructors/InstructorsTable';
import { columns } from 'features/Instructors/InstructorsTable/columns';

describe('Instructor Table', () => {
test('renders InstructorsTable without data', () => {
render(<InstructorsTable data={[]} count={0} columns={[]} />);
const emptyTableText = screen.getByText('No instructors found.');
expect(emptyTableText).toBeInTheDocument();
});

test('renders InstructorsTable with data', () => {
const data = [
{
instructorUsername: 'Instructor1',
instructorName: 'Instructor 1',
instructorEmail: '[email protected]',
ccxId: 'CCX1',
ccxName: 'CCX 1',
},
{
instructorUsername: 'Instructor2',
instructorName: 'Instructor 2',
instructorEmail: '[email protected]',
ccxId: 'CCX2',
ccxName: 'CCX 2',
},
];

const component = render(
<InstructorsTable data={data} count={data.length} columns={columns} />,
);

// Check if the table rows are present
const tableRows = screen.getAllByRole('row');
expect(tableRows).toHaveLength(data.length + 1); // Data rows + 1 header row
expect(component.container).toHaveTextContent('Instructor1');
expect(component.container).toHaveTextContent('Instructor2');
expect(component.container).toHaveTextContent('Instructor 1');
expect(component.container).toHaveTextContent('Instructor 2');
expect(component.container).toHaveTextContent('[email protected]');
expect(component.container).toHaveTextContent('[email protected]');
expect(component.container).toHaveTextContent('CCX 1');
expect(component.container).toHaveTextContent('CCX 2');
});
});
26 changes: 26 additions & 0 deletions src/features/Instructors/InstructorsTable/columns.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const columns = [
{
Header: 'User Name',
accessor: 'instructorUsername',
},
{
Header: 'Name',
accessor: 'instructorName',
},
{
Header: 'Email',
accessor: 'instructorEmail',
},
{
Header: 'Course key',
accessor: 'ccxId',
},
{
Header: 'Course name',
accessor: 'ccxName',
},
];

const hideColumns = { hiddenColumns: ['ccxId'] };

export { hideColumns, columns };
Loading

0 comments on commit d01f951

Please sign in to comment.