Skip to content

Commit f188ad9

Browse files
authored
Merge pull request #1493 from openedx/eahmadjaved/ENT-10164
feat: migrate deprecated Table to DataTable for PastWeekPassedLearner
2 parents efb6399 + b713777 commit f188ad9

File tree

9 files changed

+532
-402
lines changed

9 files changed

+532
-402
lines changed

src/components/Admin/Admin.test.jsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -484,10 +484,6 @@ describe('<Admin />', () => {
484484
csvFetchMethod: 'fetchEnrolledLearners',
485485
csvFetchParams: [enterpriseId, {}, { csv: true }],
486486
},
487-
'completed-learners-week': {
488-
csvFetchMethod: 'fetchCourseEnrollments',
489-
csvFetchParams: [enterpriseId, { passedDate: 'last_week' }, { csv: true }],
490-
},
491487
};
492488

493489
afterEach(() => {

src/components/Admin/DownloadButtonWrapper.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const DownloadButtonWrapper = ({
1818
'registered-unenrolled-learners',
1919
'enrolled-learners-inactive-courses',
2020
'completed-learners',
21+
'completed-learners-week',
2122
].includes(actionSlug);
2223

2324
return (

src/components/Admin/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ class Admin extends React.Component {
238238
defaultMessage: 'Past Week',
239239
description: 'Report title for number of courses completed by learners in past week',
240240
}),
241-
component: <PastWeekPassedLearnersTable />,
241+
component: <PastWeekPassedLearnersTable id="completed-learners-week" />,
242242
csvFetchMethod: () => (
243243
EnterpriseDataApiService.fetchCourseEnrollments(
244244
enterpriseId,
Lines changed: 239 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,265 @@
1-
import React from 'react';
2-
import { MemoryRouter } from 'react-router-dom';
3-
import renderer from 'react-test-renderer';
1+
import { IntlProvider } from '@edx/frontend-platform/i18n';
42
import configureMockStore from 'redux-mock-store';
53
import thunk from 'redux-thunk';
64
import { Provider } from 'react-redux';
7-
import { IntlProvider } from '@edx/frontend-platform/i18n';
5+
import { createMemoryHistory } from 'history';
6+
import { Router } from 'react-router';
7+
import { act } from '@testing-library/react';
88
import { mount } from 'enzyme';
9-
109
import PastWeekPassedLearnersTable from '.';
10+
import usePastWeekPassedLearners from './data/hooks/usePastWeekPassedLearners';
11+
import { PAGE_SIZE } from '../../data/constants/table';
12+
13+
// Mock the hooks
14+
jest.mock('./data/hooks/usePastWeekPassedLearners', () => jest.fn());
15+
jest.mock('../Admin/TableDataContext', () => ({
16+
useTableData: () => ({
17+
setTableHasData: jest.fn(),
18+
}),
19+
}));
1120

12-
const enterpriseId = 'test-enterprise';
1321
const mockStore = configureMockStore([thunk]);
14-
const store = mockStore({
15-
portalConfiguration: {
16-
enterpriseId,
17-
},
18-
table: {
19-
'completed-learners-week': {
22+
23+
// Mock implementations
24+
const mockFetchData = jest.fn().mockResolvedValue({});
25+
const mockFetchDataImmediate = jest.fn();
26+
27+
describe('PastWeekPassedLearnersTable', () => {
28+
const enterpriseId = 'test-enterprise-id';
29+
const tableId = 'completed-learners-week';
30+
31+
const store = mockStore({
32+
portalConfiguration: {
33+
enterpriseId,
34+
},
35+
});
36+
37+
const defaultProps = {
38+
id: tableId,
39+
};
40+
41+
beforeEach(() => {
42+
// Setup default mock implementation
43+
usePastWeekPassedLearners.mockReturnValue({
44+
isLoading: false,
2045
data: {
21-
count: 2,
22-
num_pages: 1,
23-
current_page: 1,
2446
results: [
2547
{
26-
id: 1,
27-
passed_date: '2018-09-23T16:27:34.690065Z',
28-
course_title: 'Dive into ReactJS',
29-
course_key: 'edX/ReactJS',
30-
user_email: '[email protected]',
48+
userEmail: '[email protected]',
49+
courseTitle: 'React Basics',
50+
passedDate: '2025-04-20T12:00:00Z',
3151
},
3252
{
33-
id: 5,
34-
passed_date: '2018-09-22T16:27:34.690065Z',
35-
course_title: 'Redux with ReactJS',
36-
course_key: 'edX/Redux_ReactJS',
37-
user_email: '[email protected]',
38-
53+
userEmail: '[email protected]',
54+
courseTitle: 'Advanced React',
55+
passedDate: '2025-04-19T12:00:00Z',
3956
},
4057
],
41-
next: null,
42-
start: 0,
43-
previous: null,
58+
itemCount: 2,
59+
pageCount: 1,
4460
},
45-
ordering: null,
46-
loading: false,
47-
error: null,
48-
},
49-
},
50-
});
61+
fetchData: mockFetchData,
62+
fetchDataImmediate: mockFetchDataImmediate,
63+
hasData: true,
64+
});
65+
});
5166

52-
const PastWeekPassedLearnersWrapper = props => (
53-
<MemoryRouter>
54-
<IntlProvider locale="en">
55-
<Provider store={store}>
56-
<PastWeekPassedLearnersTable
57-
{...props}
58-
/>
59-
</Provider>
60-
</IntlProvider>
61-
</MemoryRouter>
62-
);
67+
afterEach(() => {
68+
jest.clearAllMocks();
69+
});
6370

64-
describe('PastWeekPassedLearnersTable', () => {
65-
let wrapper;
66-
67-
it('renders table correctly', () => {
68-
const tree = renderer
69-
.create((
70-
<PastWeekPassedLearnersWrapper />
71-
))
72-
.toJSON();
73-
expect(tree).toMatchSnapshot();
71+
const PastWeekPassedLearnersTableWrapper = (props = {}) => {
72+
const history = createMemoryHistory();
73+
return (
74+
<Router location={history.location} navigator={history}>
75+
<IntlProvider locale="en">
76+
<Provider store={store}>
77+
<PastWeekPassedLearnersTable {...defaultProps} {...props} />
78+
</Provider>
79+
</IntlProvider>
80+
</Router>
81+
);
82+
};
83+
84+
it('renders the table with learner data', () => {
85+
const wrapper = mount(<PastWeekPassedLearnersTableWrapper />);
86+
87+
// Check if table exists
88+
const table = wrapper.find('DataTable');
89+
expect(table.exists()).toBe(true);
90+
91+
// Verify DataTable props
92+
expect(table.prop('id')).toBe(tableId);
93+
expect(table.prop('data')).toEqual([
94+
{
95+
userEmail: '[email protected]',
96+
courseTitle: 'React Basics',
97+
passedDate: '2025-04-20T12:00:00Z',
98+
},
99+
{
100+
userEmail: '[email protected]',
101+
courseTitle: 'Advanced React',
102+
passedDate: '2025-04-19T12:00:00Z',
103+
},
104+
]);
105+
expect(table.prop('itemCount')).toBe(2);
106+
expect(table.prop('pageCount')).toBe(1);
107+
expect(table.prop('isLoading')).toBe(false);
108+
109+
// Verify columns are correctly configured
110+
expect(table.prop('columns').length).toBe(3);
111+
expect(table.prop('columns')[0].accessor).toBe('userEmail');
112+
expect(table.prop('columns')[1].accessor).toBe('courseTitle');
113+
expect(table.prop('columns')[2].accessor).toBe('passedDate');
114+
});
115+
116+
it('renders empty table when no data is available', () => {
117+
usePastWeekPassedLearners.mockReturnValue({
118+
isLoading: false,
119+
data: {
120+
results: [],
121+
itemCount: 0,
122+
pageCount: 0,
123+
},
124+
fetchData: mockFetchData,
125+
fetchDataImmediate: mockFetchDataImmediate,
126+
hasData: false,
127+
});
128+
129+
const wrapper = mount(<PastWeekPassedLearnersTableWrapper />);
130+
131+
const table = wrapper.find('DataTable');
132+
expect(table.exists()).toBe(true);
133+
expect(table.prop('data')).toEqual([]);
134+
expect(table.prop('itemCount')).toBe(0);
135+
expect(table.prop('pageCount')).toBe(0);
74136
});
75137

76-
it('renders table with correct data', () => {
77-
const tableId = 'completed-learners-week';
78-
const columnTitles = ['Email', 'Course Title', 'Passed Date'];
79-
const rowsData = [
80-
[
81-
82-
'Dive into ReactJS',
83-
'September 23, 2018',
84-
],
85-
[
86-
87-
'Redux with ReactJS',
88-
'September 22, 2018',
89-
],
90-
];
91-
92-
wrapper = mount((
93-
<PastWeekPassedLearnersWrapper />
94-
));
95-
96-
// Verify that table has correct number of columns
97-
expect(wrapper.find(`.${tableId} thead th`).length).toEqual(3);
98-
99-
// Verify only expected columns are shown
100-
wrapper.find(`.${tableId} thead th`).forEach((column, index) => {
101-
expect(column.text()).toContain(columnTitles[index]);
138+
it('shows loading state when data is being fetched', () => {
139+
usePastWeekPassedLearners.mockReturnValue({
140+
isLoading: true,
141+
data: {
142+
results: [],
143+
itemCount: 0,
144+
pageCount: 0,
145+
},
146+
fetchData: mockFetchData,
147+
fetchDataImmediate: mockFetchDataImmediate,
148+
hasData: false,
102149
});
103150

104-
// Verify that table has correct number of rows
105-
expect(wrapper.find(`.${tableId} tbody tr`).length).toEqual(2);
151+
const wrapper = mount(<PastWeekPassedLearnersTableWrapper />);
152+
153+
const table = wrapper.find('DataTable');
154+
expect(table.prop('isLoading')).toBe(true);
155+
});
156+
157+
it('fetches data immediately on mount', () => {
158+
mount(<PastWeekPassedLearnersTableWrapper />);
159+
160+
expect(mockFetchDataImmediate).toHaveBeenCalledTimes(1);
161+
expect(mockFetchDataImmediate).toHaveBeenCalledWith(
162+
{
163+
pageIndex: 0,
164+
pageSize: PAGE_SIZE, // PAGE_SIZE constant value
165+
sortBy: [
166+
{ id: 'passedDate', desc: true },
167+
],
168+
},
169+
true,
170+
);
171+
});
172+
173+
it('uses URL query parameters for initial page', () => {
174+
const history = createMemoryHistory();
175+
history.push(`?${tableId}-page=2`); // Set page 2 in URL
176+
177+
const wrapper = mount(
178+
<Router location={history.location} navigator={history}>
179+
<IntlProvider locale="en">
180+
<Provider store={store}>
181+
<PastWeekPassedLearnersTable {...defaultProps} />
182+
</Provider>
183+
</IntlProvider>
184+
</Router>,
185+
);
186+
187+
const table = wrapper.find('DataTable');
188+
// Check that initialState has pageIndex set to 1 (0-based index for page 2)
189+
expect(table.prop('initialState').pageIndex).toBe(1);
190+
191+
// Check that fetchDataImmediate was called with pageIndex 1
192+
expect(mockFetchDataImmediate).toHaveBeenCalledWith(
193+
expect.objectContaining({
194+
pageIndex: 1,
195+
}),
196+
true,
197+
);
198+
});
199+
200+
it('updates URL when page changes', async () => {
201+
const history = createMemoryHistory();
202+
jest.spyOn(history, 'push');
106203

107-
// Verify each row in table has correct data
108-
wrapper.find(`.${tableId} tbody tr`).forEach((row, rowIndex) => {
109-
row.find('td').forEach((cell, colIndex) => {
110-
expect(cell.text()).toEqual(rowsData[rowIndex][colIndex]);
204+
const wrapper = mount(
205+
<Router location={history.location} navigator={history}>
206+
<IntlProvider locale="en">
207+
<Provider store={store}>
208+
<PastWeekPassedLearnersTable {...defaultProps} />
209+
</Provider>
210+
</IntlProvider>
211+
</Router>,
212+
);
213+
214+
// Simulate page change by calling fetchData prop with new table state
215+
const table = wrapper.find('DataTable');
216+
await act(async () => {
217+
await table.prop('fetchData')({
218+
pageIndex: 1, // Navigate to page 2 (0-indexed)
219+
pageSize: 50,
220+
sortBy: [],
111221
});
112222
});
223+
224+
// Check that fetchData was called
225+
expect(mockFetchData).toHaveBeenCalledWith({
226+
pageIndex: 1,
227+
pageSize: 50,
228+
sortBy: [],
229+
});
230+
});
231+
232+
it('renders UserEmail component correctly', () => {
233+
const wrapper = mount(<PastWeekPassedLearnersTableWrapper />);
234+
235+
// Find columns in the DataTable props
236+
const columns = wrapper.find('DataTable').prop('columns');
237+
const emailColumn = columns.find(col => col.accessor === 'userEmail');
238+
239+
// Test the Cell renderer with a sample row
240+
const testRow = {
241+
original: {
242+
userEmail: '[email protected]',
243+
},
244+
};
245+
246+
const emailCell = mount(
247+
<IntlProvider locale="en">
248+
{emailColumn.Cell({ row: testRow })}
249+
</IntlProvider>,
250+
);
251+
252+
expect(emailCell.find('[data-hj-suppress]').text()).toBe('[email protected]');
253+
});
254+
255+
it('formats timestamps correctly in the passedDate column', () => {
256+
const wrapper = mount(<PastWeekPassedLearnersTableWrapper />);
257+
258+
// Since the actual formatting depends on a utility function i18nFormatTimestamp,
259+
// we can verify the Cell prop is properly configured
260+
const columns = wrapper.find('DataTable').prop('columns');
261+
const dateColumn = columns.find(col => col.accessor === 'passedDate');
262+
expect(dateColumn).toBeDefined();
263+
expect(typeof dateColumn.Cell).toBe('function');
113264
});
114265
});

0 commit comments

Comments
 (0)