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.
- Loading branch information
Showing
19 changed files
with
646 additions
and
24 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,28 @@ | ||
// Color variables | ||
// Primary | ||
$primary: #007394; | ||
$primary-dark: #003057; | ||
|
||
// General | ||
$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; |
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
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
148 changes: 148 additions & 0 deletions
148
src/features/Courses/CourseDetailTable/__test__/columns.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,148 @@ | ||
import { Provider } from 'react-redux'; | ||
import { initializeStore } from 'store'; | ||
import { | ||
fireEvent, | ||
render, | ||
waitFor, | ||
} from '@testing-library/react'; | ||
import '@testing-library/jest-dom/extend-expect'; | ||
import { MemoryRouter, Route } from 'react-router-dom'; | ||
|
||
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, ' / ', 4] }); | ||
}); | ||
|
||
test('Should render the instructors', () => { | ||
const values = { | ||
row: { | ||
values: { instructors: ['Sam Sepiol'] }, | ||
original: { | ||
classId: 'Demo Course 1', | ||
}, | ||
}, | ||
}; | ||
|
||
const Component = () => columns[1].Cell(values); | ||
|
||
const store = initializeStore(); | ||
const { container } = render( | ||
<Provider store={store}> | ||
<MemoryRouter initialEntries={['/courses/Demo%20Course%201']}> | ||
<Route path="/courses/:classId"> | ||
<Component /> | ||
</Route> | ||
</MemoryRouter> | ||
</Provider>, | ||
); | ||
|
||
expect(container.querySelector('li').textContent).toBe('Sam Sepiol'); | ||
}); | ||
|
||
test('Should render the assign button if instructor is not present', async () => { | ||
const store = initializeStore(); | ||
const values = { | ||
row: { | ||
values: { instructors: [] }, | ||
original: { | ||
classId: 'Demo Course 1', | ||
}, | ||
}, | ||
}; | ||
|
||
const ComponentNoInstructor = () => columns[1].Cell(values); | ||
const { container, getAllByText } = render( | ||
<Provider store={store}> | ||
<MemoryRouter initialEntries={['/courses/Demo%20Course%201']}> | ||
<Route path="/courses/:classId"> | ||
<ComponentNoInstructor /> | ||
</Route> | ||
</MemoryRouter> | ||
</Provider>, | ||
); | ||
|
||
const modalButton = container.querySelector('button'); | ||
expect(modalButton).toBeInTheDocument(); | ||
|
||
fireEvent.click(modalButton); | ||
|
||
await waitFor(() => { | ||
const title = getAllByText('Assign instructor')[0]; | ||
expect(title).toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
69 changes: 69 additions & 0 deletions
69
src/features/Courses/CourseDetailTable/__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,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'); | ||
}); | ||
}); |
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,90 @@ | ||
/* eslint-disable react/prop-types */ | ||
import React, { useState } from 'react'; | ||
import { Badge, Button } from 'react-paragon-topaz'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { useParams } from 'react-router-dom'; | ||
import { format } from 'date-fns'; | ||
|
||
import { fetchClassesData } from 'features/Classes/data/thunks'; | ||
import { updateClassSelected } from 'features/Instructors/data/slice'; | ||
import AssignInstructors from 'features/Instructors/AssignInstructors'; | ||
|
||
const columns = [ | ||
{ | ||
Header: 'Class', | ||
accessor: 'className', | ||
Cell: ({ row }) => (<span className="text-truncate">{row.values.className}</span>), | ||
}, | ||
{ | ||
Header: 'Instructor', | ||
accessor: 'instructors', | ||
Cell: ({ row }) => { | ||
const { classId } = useParams(); | ||
const [isModalOpen, setIsModalOpen] = useState(false); | ||
|
||
const dispatch = useDispatch(); | ||
const institution = useSelector((state) => state.main.selectedInstitution); | ||
|
||
const handleOpenModal = () => { | ||
dispatch(updateClassSelected(row.original.classId)); | ||
setIsModalOpen(true); | ||
}; | ||
|
||
const handleCloseModal = () => { | ||
dispatch(fetchClassesData(institution.id, 1, classId)); | ||
setIsModalOpen(false); | ||
}; | ||
|
||
if (row.values.instructors?.length > 0) { | ||
return ( | ||
<ul className="instructors-list mb-0"> | ||
{row.values.instructors.map(instructor => <li key={instructor} className="text-truncate">{`${instructor}`}</li>)} | ||
</ul> | ||
); | ||
} | ||
|
||
return ( | ||
<> | ||
<Button onClick={handleOpenModal} className="button"> | ||
<i className="fa fa-plus mr-2" /> | ||
Assign | ||
</Button> | ||
<AssignInstructors | ||
isOpen={isModalOpen} | ||
close={handleCloseModal} | ||
/> | ||
</> | ||
); | ||
}, | ||
}, | ||
{ | ||
Header: 'Enrollment status', | ||
accessor: 'numberOfPendingStudents', | ||
Cell: ({ row }) => { | ||
const isEnrollmentComplete = row.values.numberOfPendingStudents === 0; | ||
|
||
return isEnrollmentComplete ? <Badge variant="success" light>Complete</Badge> : <Badge variant="warning" light>Pending ({row.values.numberOfPendingStudents})</Badge>; | ||
}, | ||
}, | ||
{ | ||
Header: 'Students Enrolled', | ||
accessor: 'numberOfStudents', | ||
Cell: ({ row }) => (<span>{row.values.numberOfStudents}{' / '}{row.values.numberOfStudents + row.values.numberOfPendingStudents}</span>), | ||
}, | ||
{ | ||
Header: 'Max', | ||
accessor: 'maxStudents', | ||
}, | ||
{ | ||
Header: 'Start date', | ||
accessor: 'startDate', | ||
Cell: ({ row }) => (row.values.startDate ? format(row.values.startDate, 'MM/dd/yy') : '-'), | ||
}, | ||
{ | ||
Header: 'End date', | ||
accessor: 'endDate', | ||
Cell: ({ row }) => (row.values.endDate ? format(row.values.endDate, 'MM/dd/yy') : '-'), | ||
}, | ||
]; | ||
|
||
export { columns }; |
Oops, something went wrong.