Skip to content

Commit

Permalink
Merge pull request #43 from Pearson-Advance/vue/PADV-979
Browse files Browse the repository at this point in the history
feat: Classes table in courses view
  • Loading branch information
01001110J authored Feb 29, 2024
2 parents f9448e8 + ca43061 commit 3a4c215
Show file tree
Hide file tree
Showing 24 changed files with 785 additions and 59 deletions.
26 changes: 17 additions & 9 deletions src/assets/colors.scss
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
// Color variables
// Primary
$primary: #007394;
$primary-dark: #003057;

// Basic Colors
$color-black: #020917;
$black-80: #333;
$color-white: #fefefe;
$color-gray: #60646d;
$gray-70: #666666;
$gray-60: #808080;
$gray-30: #d3d3d3;
$gray-20: #dfe1e1;
$color-pink: #ffecf0;
$color-green: #e7f7eb;
$color-yellow: #fafbe5;
$color-purple: #f4e3ee;
$blue-20: #e4faff;

$hyperlink-color: #17897c;
$color-active-button: #989ba3;
$hyperlink-color: #17897c;
$bg-main-color: #f3f3f3;

// Gray
$gray-20: #dfe1e1;
$gray-30: #d3d3d3;
$gray-60: #808080;
$gray-70: #666666;

// Blue
$blue-20: #e4faff;

// Black
$black: #000;
$black-80: #333;
11 changes: 9 additions & 2 deletions src/assets/global.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
@import "assets/colors.scss";
@import "assets/variables.scss";

.page-content-container {
border: 1px solid $gray-20;
border-radius: 0.375rem;
border-radius: $border-radius-1;
padding: 20px 0;
box-shadow: 0px 3px 12px 0px $gray-30;
background-color: $color-white;
Expand All @@ -17,7 +18,7 @@

.filter-container .filters {
border: 1px solid $gray-20;
border-radius: 0.375rem;
border-radius: $border-radius-1;
padding: 1.5rem 1rem;
}

Expand All @@ -34,3 +35,9 @@
list-style-type: none;
padding-left: 0;
}

.link {
&:-webkit-any-link {
color: $primary;
}
}
2 changes: 2 additions & 0 deletions src/assets/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@

$font-family: "Open Sans", sans-serif;
$account-menu-box-shadow: 2px 3px 8px 0 rgba(0, 0, 0, 0.5);

$border-radius-1: 0.375rem;
4 changes: 2 additions & 2 deletions src/features/Classes/data/thunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import {
} from 'features/Classes/data/slice';
import { getClassesByInstitution } from 'features/Common/data/api';

function fetchClassesData(id, currentPage) {
function fetchClassesData(id, currentPage, courseName = '') {
return async (dispatch) => {
dispatch(fetchClassesDataRequest);

try {
const response = camelCaseObject(await getClassesByInstitution(id, '', true, '', currentPage));
const response = camelCaseObject(await getClassesByInstitution(id, courseName, true, '', currentPage));
dispatch(fetchClassesDataSuccess(response.data));
} catch (error) {
dispatch(fetchClassesDataFailed());
Expand Down
1 change: 0 additions & 1 deletion src/features/Common/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ function getLicensesByInstitution(institutionId, limit, page = 1, urlParamsFilte

function getClassesByInstitution(institutionId, courseName, limit = false, instructorsList = '', page = '') {
const encodedCourseName = encodeURIComponent(courseName);

return getAuthenticatedHttpClient().get(
`${getConfig().COURSE_OPERATIONS_API_V2_BASE_URL}/classes`
+ `/?limit=${limit}&institution_id=${institutionId}&course_name=${encodedCourseName}&instructors=${instructorsList}&page=${page}`,
Expand Down
202 changes: 202 additions & 0 deletions src/features/Courses/CourseDetailTable/__test__/columns.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import {
fireEvent,
waitFor,
} from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { MemoryRouter, Route } from 'react-router-dom';

import { renderWithProviders } from 'test-utils';
import { columns } from 'features/Courses/CourseDetailTable/columns';

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

const [
className,
instructor,
enrollmentStatus,
studentsEnrolled,
maxStudents,
startDate,
endDate,
] = columns;

expect(className).toHaveProperty('Header', 'Class');
expect(className).toHaveProperty('accessor', 'className');

expect(instructor).toHaveProperty('Header', 'Instructor');
expect(instructor).toHaveProperty('accessor', 'instructors');

expect(enrollmentStatus).toHaveProperty('Header', 'Enrollment status');
expect(enrollmentStatus).toHaveProperty('accessor', 'numberOfPendingStudents');

expect(studentsEnrolled).toHaveProperty('Header', 'Students Enrolled');
expect(studentsEnrolled).toHaveProperty('accessor', 'numberOfStudents');

expect(maxStudents).toHaveProperty('Header', 'Max');
expect(maxStudents).toHaveProperty('accessor', 'maxStudents');

expect(startDate).toHaveProperty('Header', 'Start date');
expect(startDate).toHaveProperty('accessor', 'startDate');

expect(endDate).toHaveProperty('Header', 'End date');
expect(endDate).toHaveProperty('accessor', 'endDate');
});

test('Should render the title into a span tag', () => {
const title = columns[0].Cell({ row: { values: { className: 'Class example' } } });
expect(title).toHaveProperty('type', 'span');
expect(title.props).toEqual({ className: 'text-truncate', children: 'Class example' });
});

test('Should render the dates', () => {
const startDate = columns[5].Cell({ row: { values: { startDate: '2024-02-13T17:42:22Z' } } });
expect(startDate).toBe('02/13/24');

const endDate = columns[5].Cell({ row: { values: { startDate: '2024-04-13T17:42:22Z' } } });
expect(endDate).toBe('04/13/24');

const nullDate = columns[5].Cell({ row: { values: { startDate: null } } });
expect(nullDate).toBe('-');

const nullDate2 = columns[6].Cell({ row: { values: { startDate: null } } });
expect(nullDate2).toBe('-');
});

test('Should render the enrollment status', () => {
const pendingStudents = { row: { values: { numberOfStudents: 3, numberOfPendingStudents: 1 } } };

const enrollmentStatus = columns[2].Cell(pendingStudents);
expect(enrollmentStatus.props).toEqual({ children: ['Pending (', 1, ')'], light: true, variant: 'warning' });

const completeStudents = { row: { values: { numberOfStudents: 3, numberOfPendingStudents: 0 } } };

const enrollmentStatusComplete = columns[2].Cell(completeStudents);
expect(enrollmentStatusComplete.props).toEqual({ children: 'Complete', light: true, variant: 'success' });
});

test('Should render the students enrolled', () => {
const values = { row: { values: { numberOfStudents: 3, numberOfPendingStudents: 1 } } };

const studentsEnrolled = columns[3].Cell(values);
expect(studentsEnrolled).toHaveProperty('type', 'span');
expect(studentsEnrolled.props).toEqual({ children: 3 });
});

test('Should render the instructors', () => {
const values = {
row: {
values: { instructors: ['Sam Sepiol'] },
original: {
classId: 'Demo Course 1',
},
},
};

const Component = () => columns[1].Cell(values);
const mockStore = {
classes: {
table: {
data: [
{
masterCourseName: 'Demo MasterCourse 1',
className: 'Demo Class 1',
startDate: '09/21/24',
endDate: null,
numberOfStudents: 1,
maxStudents: 100,
instructors: ['Sam Sepiol'],
},
{
masterCourseName: 'Demo MasterCourse 2',
className: 'Demo Class 2',
startDate: '09/21/25',
endDate: null,
numberOfStudents: 2,
maxStudents: 200,
instructors: [],
},
],
count: 2,
num_pages: 1,
current_page: 1,
},
},
};

const component = renderWithProviders(
<MemoryRouter initialEntries={['/courses/Demo%20Course%201']}>
<Route path="/courses/:classId">
<Component />
</Route>
</MemoryRouter>,
{ preloadedState: mockStore },
);

expect(component.getByText('Sam Sepiol')).toBeInTheDocument();
});

test('Should render the assign button if instructor is not present', async () => {
const values = {
row: {
values: { instructors: [] },
original: {
classId: 'Demo Course 1',
},
},
};

const ComponentNoInstructor = () => columns[1].Cell(values);

const mockStore = {
classes: {
table: {
data: [
{
masterCourseName: 'Demo MasterCourse 1',
className: 'Demo Class 1',
startDate: '09/21/24',
endDate: null,
numberOfStudents: 1,
maxStudents: 100,
instructors: ['Sam Sepiol'],
},
{
masterCourseName: 'Demo MasterCourse 2',
className: 'Demo Class 2',
startDate: '09/21/25',
endDate: null,
numberOfStudents: 2,
maxStudents: 200,
instructors: [],
},
],
count: 2,
num_pages: 1,
current_page: 1,
},
},
};

const component = renderWithProviders(
<MemoryRouter initialEntries={['/courses/Demo%20Course%201']}>
<Route path="/courses/:classId">
<ComponentNoInstructor />
</Route>
</MemoryRouter>,
{ preloadedState: mockStore },
);

const modalButton = component.getByRole('button');
expect(modalButton).toBeInTheDocument();

fireEvent.click(modalButton);

await waitFor(() => {
const title = component.getAllByText('Assign instructor')[0];
expect(title).toBeInTheDocument();
});
});
});
69 changes: 69 additions & 0 deletions src/features/Courses/CourseDetailTable/__test__/index.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react';
import '@testing-library/jest-dom';
import { MemoryRouter, Route } from 'react-router-dom';
import { render, screen } from '@testing-library/react';

import { renderWithProviders } from 'test-utils';
import CourseDetailTable from 'features/Courses/CourseDetailTable';
import { columns } from 'features/Courses/CourseDetailTable/columns';

describe('Course Details Table', () => {
test('Should render the table without data', () => {
render(<CourseDetailTable data={[]} count={0} columns={[]} />);
const emptyTableText = screen.getByText('No classes were found.');
expect(emptyTableText).toBeInTheDocument();
});

test('Should render the table with data', () => {
const mockStore = {
classes: {
table: {
data: [
{
masterCourseName: 'Demo MasterCourse 1',
className: 'Demo Class 1',
startDate: '09/21/24',
endDate: null,
numberOfStudents: 1,
maxStudents: 100,
instructors: ['instructor_1'],
},
{
masterCourseName: 'Demo MasterCourse 2',
className: 'Demo Class 2',
startDate: '09/21/25',
endDate: null,
numberOfStudents: 2,
maxStudents: 200,
instructors: ['instructor_2'],
},
],
count: 2,
num_pages: 1,
current_page: 1,
},
},
};

const component = renderWithProviders(
<MemoryRouter initialEntries={['/courses']}>
<Route path="/courses">
<CourseDetailTable
data={mockStore.classes.table.data}
count={mockStore.classes.table.data.length}
columns={columns}
/>
</Route>
</MemoryRouter>,
{ preloadedState: mockStore },
);

expect(component.container).toHaveTextContent('Class');
expect(component.container).toHaveTextContent('Instructor');
expect(component.container).toHaveTextContent('Enrollment status');
expect(component.container).toHaveTextContent('Students Enrolled');
expect(component.container).toHaveTextContent('Max');
expect(component.container).toHaveTextContent('Start date');
expect(component.container).toHaveTextContent('End date');
});
});
Loading

0 comments on commit 3a4c215

Please sign in to comment.