Skip to content

Commit 356dde5

Browse files
authored
Migrated Course Summary Table to @tanstack/react-table v8 (#7732)
1 parent 2f88e25 commit 356dde5

File tree

4 files changed

+359
-95
lines changed

4 files changed

+359
-95
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
- Updated pre-commit `rubocop-rails` version to 2.33.4 (#7691)
3939
- Refactored MarksPanel child components and converted the components into hook-based function components
4040
- Refactored jQuery active marks panel component tracking logic into React
41+
- Updated the course summary table to use `@tanstack/react-table` v8 (#7732)
4142
- Refactored `test_run_table.jsx` by extracting nested components into separate files (#7739)
4243

4344
## [v2.8.2]

app/javascript/Components/__tests__/course_summaries.test.jsx

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,234 @@ describe("For each CourseSummaries' loading status", () => {
1919
expect(spinner).toBeInTheDocument();
2020
});
2121
});
22+
23+
describe("CourseSummaryTable show/hide inactive students", () => {
24+
it("toggles the checkbox UI state when clicked", async () => {
25+
render(<CourseSummaryTable />);
26+
27+
const checkbox = screen.getByLabelText(/display inactive students/i);
28+
29+
expect(checkbox).not.toBeChecked(); // Initial state: not checked
30+
31+
await userEvent.click(checkbox); // Toggle to showHidden = true
32+
expect(checkbox).toBeChecked();
33+
34+
await userEvent.click(checkbox); // Toggle to showHidden = false
35+
expect(checkbox).not.toBeChecked();
36+
});
37+
38+
it("updateShowHidden correctly updates state and columnFilters", () => {
39+
const table = new CourseSummaryTable({});
40+
41+
// Spy on setState
42+
const setStateSpy = jest.spyOn(table, "setState");
43+
44+
// Initial state
45+
expect(table.state.showHidden).toBe(false);
46+
expect(table.state.columnFilters).toEqual([{id: "inactive", value: false}]);
47+
48+
// Check the checkbox (show hidden = true)
49+
const event1 = {target: {checked: true}};
50+
table.updateShowHidden(event1);
51+
52+
// Check setState called with correct values
53+
expect(setStateSpy).toHaveBeenCalledWith({
54+
showHidden: true,
55+
columnFilters: [],
56+
});
57+
58+
table.state.showHidden = true;
59+
table.state.columnFilters = [];
60+
61+
// Uncheck the checkbox (show hidden = false)
62+
const event2 = {target: {checked: false}};
63+
table.updateShowHidden(event2);
64+
65+
expect(setStateSpy).toHaveBeenCalledWith({
66+
showHidden: false,
67+
columnFilters: [{id: "inactive", value: false}], // Hidden filter added back
68+
});
69+
});
70+
71+
it("updateShowHidden preserves other column filters", () => {
72+
const table = new CourseSummaryTable({});
73+
const setStateSpy = jest.spyOn(table, "setState");
74+
75+
// Set up state with multiple filters
76+
table.state.columnFilters = [
77+
{id: "inactive", value: false},
78+
{id: "user_name", value: "test"},
79+
];
80+
81+
// Toggle show hidden to true
82+
const event = {target: {checked: true}};
83+
table.updateShowHidden(event);
84+
85+
// Should preserve user_name filter & remove hidden filter
86+
expect(setStateSpy).toHaveBeenCalledWith({
87+
showHidden: true,
88+
columnFilters: [{id: "user_name", value: "test"}],
89+
});
90+
91+
// Manually update state
92+
table.state.showHidden = true;
93+
table.state.columnFilters = [{id: "user_name", value: "test"}];
94+
95+
// Toggle back to false
96+
const event2 = {target: {checked: false}};
97+
table.updateShowHidden(event2);
98+
99+
// Should have both filters again
100+
expect(setStateSpy).toHaveBeenCalledWith({
101+
showHidden: false,
102+
columnFilters: [
103+
{id: "user_name", value: "test"},
104+
{id: "inactive", value: false},
105+
],
106+
});
107+
});
108+
});
109+
110+
describe("For CourseSummaryTable nameColumns,", () => {
111+
it("filterFn correctly hides rows when hidden=true", () => {
112+
const table = new CourseSummaryTable({});
113+
const [hiddenColumn] = table.nameColumns();
114+
115+
const filterFn = hiddenColumn.filterFn;
116+
117+
const visibleRow = {original: {hidden: false}};
118+
expect(filterFn(visibleRow, "hidden", false)).toBe(true);
119+
120+
const hiddenRow = {original: {hidden: true}};
121+
expect(filterFn(hiddenRow, "hidden", false)).toBe(false);
122+
123+
expect(filterFn(hiddenRow, "hidden", true)).toBe(true); // filterValue = true, show all rows
124+
});
125+
126+
it("filterFn shows all rows when filterValue is true", () => {
127+
const table = new CourseSummaryTable({});
128+
const [hiddenColumn] = table.nameColumns();
129+
const filterFn = hiddenColumn.filterFn;
130+
131+
const hiddenRow = {original: {hidden: true}};
132+
const visibleRow = {original: {hidden: false}};
133+
134+
// When filterValue is true (show hidden checkbox is checked), show all rows
135+
expect(filterFn(hiddenRow, "hidden", true)).toBe(true);
136+
expect(filterFn(visibleRow, "hidden", true)).toBe(true);
137+
138+
// When filterValue is false (show hidden checkbox is unchecked), filter out hidden rows
139+
expect(filterFn(hiddenRow, "hidden", false)).toBe(false);
140+
expect(filterFn(visibleRow, "hidden", false)).toBe(true);
141+
});
142+
143+
it("does not render the hidden column in the table", async () => {
144+
render(<CourseSummaryTable />);
145+
146+
expect(screen.queryByText("hidden")).not.toBeInTheDocument();
147+
});
148+
149+
it("does not render the hidden column in the table", async () => {
150+
render(<CourseSummaryTable />);
151+
152+
expect(screen.queryByText("hidden")).not.toBeInTheDocument();
153+
});
154+
});
155+
156+
describe("CourseSummaryTable dataColumns", () => {
157+
it("creates columns for assessments and marking schemes", () => {
158+
const assessments = [{id: 1, name: "A1"}];
159+
const marking_schemes = [{id: 2, name: "Scheme1"}];
160+
161+
const table = new CourseSummaryTable({assessments, marking_schemes});
162+
const columns = table.dataColumns();
163+
164+
expect(columns.length).toBe(2);
165+
});
166+
});
167+
168+
describe("CourseSummaryTable student view", () => {
169+
it("does not include name columns when student=true", () => {
170+
const assessments = [{id: 1, name: "A1"}];
171+
render(<CourseSummaryTable student={true} assessments={assessments} />);
172+
173+
// Name columns should not appear
174+
expect(screen.queryByText(I18n.t("activerecord.attributes.user.user_name"))).toBeNull();
175+
176+
// Data column should appear
177+
expect(screen.getByText("A1")).toBeInTheDocument();
178+
});
179+
});
180+
181+
describe("CourseSummaryTable manual filtering", () => {
182+
it("filterFn correctly filters marks", () => {
183+
const assessments = [{id: 1, name: "A1"}];
184+
const marking_schemes = [];
185+
const table = new CourseSummaryTable({assessments, marking_schemes});
186+
const columns = table.dataColumns();
187+
188+
const assessmentColumn = columns[0];
189+
const filterFn = assessmentColumn.filterFn;
190+
191+
// Mock row with getValue returning a number
192+
const mockRow = {
193+
getValue: () => 15,
194+
};
195+
196+
// Should only match when filter value equals mark
197+
expect(filterFn(mockRow, "assessment_marks.1.mark", "15")).toBe(true);
198+
199+
expect(filterFn(mockRow, "assessment_marks.1.mark", "1")).toBe(false);
200+
expect(filterFn(mockRow, "assessment_marks.1.mark", "5")).toBe(false);
201+
expect(filterFn(mockRow, "assessment_marks.1.mark", "7")).toBe(false);
202+
expect(filterFn(mockRow, "assessment_marks.1.mark", "20")).toBe(false);
203+
});
204+
205+
it("filterFn works for marking schemes columns", () => {
206+
const assessments = [];
207+
const marking_schemes = [{id: 1, name: "Scheme1"}];
208+
const table = new CourseSummaryTable({assessments, marking_schemes});
209+
const columns = table.dataColumns();
210+
211+
const schemeColumn = columns[0];
212+
const filterFn = schemeColumn.filterFn;
213+
214+
const mockRow = {
215+
getValue: () => 85,
216+
};
217+
218+
// Should only match when filter value equals mark
219+
expect(filterFn(mockRow, "weighted_marks.1.mark", "85")).toBe(true);
220+
221+
expect(filterFn(mockRow, "weighted_marks.1.mark", "8")).toBe(false);
222+
expect(filterFn(mockRow, "weighted_marks.1.mark", "5")).toBe(false);
223+
expect(filterFn(mockRow, "weighted_marks.1.mark", "7")).toBe(false);
224+
225+
// Null handling
226+
const mockRowWithNull = {
227+
getValue: () => null,
228+
};
229+
expect(filterFn(mockRowWithNull, "weighted_marks.1.mark", "5")).toBe(false);
230+
});
231+
232+
it("filterFn returns false for null marks", () => {
233+
const assessments = [{id: 1, name: "A1"}];
234+
const marking_schemes = [];
235+
const table = new CourseSummaryTable({assessments, marking_schemes});
236+
const columns = table.dataColumns();
237+
238+
const assessmentColumn = columns[0];
239+
const filterFn = assessmentColumn.filterFn;
240+
241+
const mockRowWithNull = {
242+
getValue: () => null,
243+
};
244+
245+
// Should return false & not show rows with null
246+
expect(() => filterFn(mockRowWithNull, "assessment_marks.1.mark", "5")).not.toThrow();
247+
expect(filterFn(mockRowWithNull, "assessment_marks.1.mark", "5")).toBe(false);
248+
249+
// Also check empty string filter returns false
250+
expect(filterFn(mockRowWithNull, "assessment_marks.1.mark", "")).toBe(false);
251+
});
252+
});

0 commit comments

Comments
 (0)