Skip to content

Commit 3a3170e

Browse files
authored
Converted "Rename Group" modal to React component (#7673)
1 parent 62d1155 commit 3a3170e

File tree

7 files changed

+184
-35
lines changed

7 files changed

+184
-35
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- Added tests to improve coverage for `AnnotationCategory`'s `self.to_json` method
2121
- Added tests to the Criteria Controller class to achieve full test coverage
2222
- Refactored Criterion subclasses to remove redundant code
23+
- Converted "Rename Group" functionality to React modal (#7673)
2324
- Fixed Rack deprecation warnings by updating HTTP status code symbols (#7675)
2425

2526
## [v2.8.1]

app/assets/javascripts/Groups/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ var modalCreate,
44

55
(function () {
66
const domContentLoadedCB = function () {
7-
window.modal_rename = new ModalMarkus("#rename_group_dialog");
87
modalNotesGroup = new ModalMarkus("#notes_dialog");
98
modalAssignmentGroupReUse = new ModalMarkus("#assignment_group_use_dialog");
109
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from "react";
2+
import Modal from "react-modal";
3+
4+
export default class RenameGroupModal extends React.Component {
5+
constructor(props) {
6+
super(props);
7+
this.state = {
8+
groupName: props.initialGroupName || "",
9+
};
10+
}
11+
12+
componentDidMount() {
13+
Modal.setAppElement("body");
14+
}
15+
16+
componentDidUpdate(prevProps) {
17+
if (this.props.isOpen && !prevProps.isOpen && this.props.initialGroupName) {
18+
this.setState({groupName: this.props.initialGroupName});
19+
}
20+
}
21+
22+
handleChange = event => {
23+
this.setState({groupName: event.target.value});
24+
};
25+
26+
handleSubmit = event => {
27+
event.preventDefault();
28+
if (!this.state.groupName) {
29+
return;
30+
}
31+
this.props.onSubmit(this.state.groupName);
32+
this.setState({groupName: ""});
33+
};
34+
35+
render() {
36+
return (
37+
<Modal
38+
className="react-modal markus-dialog"
39+
isOpen={this.props.isOpen}
40+
onRequestClose={this.props.onRequestClose}
41+
id="rename_group_dialog"
42+
>
43+
<h2>{I18n.t("groups.rename_group")}</h2>
44+
<form onSubmit={this.handleSubmit}>
45+
<label htmlFor="groupName">{I18n.t("activerecord.attributes.group.group_name")}</label>
46+
<input
47+
id="groupName"
48+
type="text"
49+
value={this.state.groupName}
50+
onChange={event => this.handleChange(event)}
51+
autoFocus
52+
/>
53+
<div className={"dialog-actions"}>
54+
<button
55+
className="button"
56+
type="submit"
57+
disabled={!this.state.groupName}
58+
data-testid="rename-submit-button"
59+
>
60+
{I18n.t("groups.rename_group")}
61+
</button>
62+
<button className="button" type="reset" onClick={this.props.onRequestClose}>
63+
{I18n.t("cancel")}
64+
</button>
65+
</div>
66+
</form>
67+
</Modal>
68+
);
69+
}
70+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React from "react";
2+
import {render, screen, fireEvent, waitFor} from "@testing-library/react";
3+
import RenameGroupModal from "../Modals/rename_group_modal";
4+
import Modal from "react-modal";
5+
import {expect} from "@jest/globals";
6+
7+
describe("RenameGroupModal", () => {
8+
let props;
9+
10+
beforeEach(() => {
11+
props = {
12+
isOpen: true,
13+
onRequestClose: jest.fn(),
14+
onSubmit: jest.fn(),
15+
};
16+
17+
Modal.setAppElement("body");
18+
render(<RenameGroupModal {...props} />);
19+
});
20+
21+
it("should rename the group on successful submit", async () => {
22+
const groupName = "new_group";
23+
fireEvent.change(screen.getByLabelText(I18n.t("activerecord.attributes.group.group_name")), {
24+
target: {value: groupName},
25+
});
26+
27+
const renameGroupButton = screen.getByTestId("rename-submit-button");
28+
fireEvent.click(renameGroupButton);
29+
30+
await waitFor(() => {
31+
expect(props.onSubmit).toHaveBeenCalledTimes(1);
32+
expect(props.onSubmit).toHaveBeenCalledWith(groupName);
33+
});
34+
});
35+
36+
it("should call onRequestClose on successful call", async () => {
37+
fireEvent.click(screen.getByText(I18n.t("cancel")));
38+
await waitFor(() => {
39+
expect(props.onRequestClose).toHaveBeenCalledTimes(1);
40+
});
41+
});
42+
43+
it("should not add the new group when the inputted group name is empty", async () => {
44+
const renameGroupButton = screen.getByTestId("rename-submit-button");
45+
fireEvent.click(renameGroupButton);
46+
47+
await waitFor(() => {
48+
expect(props.onSubmit).not.toHaveBeenCalled();
49+
});
50+
});
51+
52+
it("should populate input with initialGroupName when modal opens", async () => {
53+
const propsWithInitialName = {
54+
isOpen: true,
55+
onRequestClose: jest.fn(),
56+
onSubmit: jest.fn(),
57+
initialGroupName: "Original Group Name",
58+
};
59+
60+
const {rerender} = render(<RenameGroupModal {...propsWithInitialName} isOpen={false} />);
61+
rerender(<RenameGroupModal {...propsWithInitialName} isOpen={true} />);
62+
63+
expect(screen.getByDisplayValue("Original Group Name")).toBeInTheDocument();
64+
});
65+
66+
it("should not submit when group name is empty", async () => {
67+
fireEvent.change(screen.getByLabelText(I18n.t("activerecord.attributes.group.group_name")), {
68+
target: {value: ""},
69+
});
70+
71+
const form = document.querySelector("form");
72+
fireEvent.submit(form);
73+
74+
expect(props.onSubmit).not.toHaveBeenCalled();
75+
});
76+
});

app/javascript/Components/groups_manager.jsx

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import ExtensionModal from "./Modals/extension_modal";
77
import {durationSort, selectFilter} from "./Helpers/table_helpers";
88
import AutoMatchModal from "./Modals/auto_match_modal";
99
import CreateGroupModal from "./Modals/create_group_modal";
10+
import RenameGroupModal from "./Modals/rename_group_modal";
1011

1112
class GroupsManager extends React.Component {
1213
constructor(props) {
@@ -17,33 +18,23 @@ class GroupsManager extends React.Component {
1718
show_hidden: false,
1819
hidden_students_count: 0,
1920
inactive_groups_count: 0,
21+
renameGroupingId: null,
22+
renameGroupName: "",
2023
show_modal: false,
2124
selected_extension_data: {},
2225
updating_extension: false,
2326
isAutoMatchModalOpen: false,
2427
isCreateGroupModalOpen: false,
28+
isRenameGroupDialogOpen: false,
2529
examTemplates: [],
2630
loading: true,
2731
};
2832
}
2933

3034
componentDidMount() {
3135
this.fetchData();
32-
// TODO: Remove reliance on global modal
33-
if (document.readyState === "loading") {
34-
document.addEventListener("DOMContentLoaded", this.componentDidMountCB);
35-
} else {
36-
this.componentDidMountCB();
37-
}
3836
}
3937

40-
componentDidMountCB = () => {
41-
$("#rename_group_dialog form").on("ajax:success", () => {
42-
modal_rename.close();
43-
this.fetchData();
44-
});
45-
};
46-
4738
fetchData = () => {
4839
fetch(Routes.course_assignment_groups_path(this.props.course_id, this.props.assignment_id), {
4940
headers: {
@@ -131,13 +122,32 @@ class GroupsManager extends React.Component {
131122
).then(this.fetchData);
132123
};
133124

134-
renameGroup = grouping_id => {
135-
$("#new_groupname").val("");
136-
$("#rename_group_dialog form").attr(
137-
"action",
138-
Routes.rename_group_course_group_path(this.props.course_id, grouping_id)
139-
);
140-
modal_rename.open();
125+
renameGroup = (grouping_id, group_name) => {
126+
this.setState({
127+
isRenameGroupDialogOpen: true,
128+
renameGroupingId: grouping_id,
129+
renameGroupName: group_name,
130+
});
131+
};
132+
133+
handleRenameGroupDialog = newGroupName => {
134+
$.post({
135+
url: Routes.rename_group_course_group_path(this.props.course_id, this.state.renameGroupingId),
136+
data: {
137+
new_groupname: newGroupName,
138+
},
139+
}).then(() => {
140+
this.setState({isRenameGroupDialogOpen: false});
141+
this.fetchData();
142+
});
143+
};
144+
145+
handleCloseRenameGroupDialog = () => {
146+
this.setState({
147+
isRenameGroupDialogOpen: false,
148+
renameGroupingId: null,
149+
renameGroupName: "",
150+
});
141151
};
142152

143153
unassign = (grouping_id, student_user_name) => {
@@ -359,6 +369,12 @@ class GroupsManager extends React.Component {
359369
onRequestClose={this.handleCloseCreateGroupModal}
360370
onSubmit={this.handleSubmitCreateGroup}
361371
/>
372+
<RenameGroupModal
373+
isOpen={this.state.isRenameGroupDialogOpen}
374+
onRequestClose={this.handleCloseRenameGroupDialog}
375+
onSubmit={this.handleRenameGroupDialog}
376+
initialGroupName={this.state.renameGroupName}
377+
/>
362378
</div>
363379
);
364380
}
@@ -396,7 +412,7 @@ class RawGroupsTable extends React.Component {
396412
<span>{row.value}</span>
397413
<a
398414
href="#"
399-
onClick={() => this.props.renameGroup(row.original._id)}
415+
onClick={() => this.props.renameGroup(row.original._id, row.value)}
400416
title={I18n.t("groups.rename_group")}
401417
>
402418
<FontAwesomeIcon icon="fa-solid fa-pen" className="icon-right" />

app/views/groups/_rename_group_modal.html.erb

Lines changed: 0 additions & 12 deletions
This file was deleted.

app/views/groups/index.html.erb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,4 @@
6464
<%= render partial: 'download_modal', layout: 'layouts/modal_dialog' %>
6565
<%= render partial: 'upload_modal', layout: 'layouts/modal_dialog' %>
6666
<%= render partial: 'assignment_group_use_modal', layout: 'layouts/modal_dialog' %>
67-
<%= render partial: 'rename_group_modal', layout: 'layouts/modal_dialog' %>
6867
<aside class='markus-dialog' id='notes_dialog'></aside>

0 commit comments

Comments
 (0)