Skip to content

Commit

Permalink
Merge branch 'master' into TP2000-1193--upgrade-to-django-4-2
Browse files Browse the repository at this point in the history
  • Loading branch information
dalecannon committed Feb 23, 2024
2 parents 7938c80 + 06daff3 commit 83dba31
Show file tree
Hide file tree
Showing 39 changed files with 1,873 additions and 92 deletions.
1 change: 1 addition & 0 deletions common/jinja2/layouts/layout.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"html": logout_login_link,
} if not settings.SSO_ENABLED else "",
],
"useTudorCrown": true,
}) }}
{% endblock %}

Expand Down
4 changes: 3 additions & 1 deletion common/static/common/js/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import initFilterDisabledToggleForComCode from './conditionalDisablingFilters'
import initOpenCloseAccordionSection from './openCloseAccordion';
import initTapDebounce from './buttonDebounce';
import { setupQuotaOriginFormset } from './components/QuotaOriginFormset/index';
import { setupWorkbasketUserAssignment } from './components/WorkbasketUserAssignment/index';


showHideCheckboxes();
Expand All @@ -30,4 +31,5 @@ initAutocompleteProgressiveEnhancement();
initFilterDisabledToggleForComCode();
initOpenCloseAccordionSection();
initTapDebounce();
setupQuotaOriginFormset();
setupQuotaOriginFormset();
setupWorkbasketUserAssignment();
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ function QuotaOriginFormset({ data, options, errors }) {

function init() {
const originsContainer = document.getElementById("quota_origins");
if (!originsContainer)
return;
const root = createRoot(originsContainer);
const origins = [...originsData];
// originsData and geoAreasOptions come from template quotas/jinja2/includes/quotas/quota-edit-origins.jinja
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import { useEffect } from 'react';
import accessibleAutocomplete from 'accessible-autocomplete'


function AssignUserForm({ assignmentType, users }) {
const elementId = "users-select"
const elementName = "users"
const label = "Assign user"
const hint = "Select a user to assign"

useEffect(() => {
const selectElement = document.getElementById(elementId);
if (selectElement)
accessibleAutocomplete.enhanceSelectElement(
{
autoselect: false,
defaultValue: "",
minLength: 2,
showAllValues: true,
selectElement: selectElement
}
);
}, [])

return (
<>
<form action={assignUsersUrl} method="POST" data-testid={"assign-user-form"}>
<input type="hidden" value={CSRF_TOKEN} name="csrfmiddlewaretoken"/>
<div className="govuk-form-group">
<label className="govuk-label" htmlFor={elementId}>
{label}
</label>
<div id={`${elementName}-hint`} className="govuk-hint">
{hint}
</div>
<select
required={true}
id={elementId}
name={elementName}
data-testid='assign-user-select'
>
<option value="">Select a user</option>
{users.map(user =>
<option key={user.pk} value={user.pk}>{user.name}</option>
)}
</select>
</div>
<div className="govuk-form-group">
<input type="hidden" name="assignment_type" value={assignmentType}/>
</div>
<button type="submit" className="govuk-button" data-prevent-double-click="true">Save</button>
</form>
</>
)
}

export { AssignUserForm };
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { useEffect } from 'react';
import accessibleAutocomplete from 'accessible-autocomplete'


function UnassignUserForm({ users }) {
const elementId = "assignments-select"
const elementName = "assignments"
const label = "Unassign user"
const hint = "Select a user to unassign"

useEffect(() => {
const selectElement = document.getElementById(elementId);
if (selectElement)
accessibleAutocomplete.enhanceSelectElement(
{
autoselect: false,
defaultValue: "",
minLength: 2,
showAllValues: true,
selectElement: selectElement
}
);
}, [])

return (
<form action={unassignUsersUrl} method="POST" data-testid={"unassign-user-form"}>
<input type="hidden" value={CSRF_TOKEN} name="csrfmiddlewaretoken"/>
<div className="govuk-form-group">
<label className="govuk-label" htmlFor={elementId}>
{label}
</label>
<div id={`${elementName}-hint`} className="govuk-hint">
{hint}
</div>
<select
required={true}
id={elementId}
name={elementName}
data-testid='unassign-user-select'
>
<option value="">Select a user</option>
{users.map(user =>
<option key={user.pk} value={user.pk}>{user.name}</option>
)}
</select>
</div>
<button type="submit" className="govuk-button" data-prevent-double-click="true">Save</button>
</form>
)
}

export { UnassignUserForm };
151 changes: 151 additions & 0 deletions common/static/common/js/components/WorkbasketUserAssignment/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React from 'react';
import { useState } from 'react';
import { createPortal } from 'react-dom';
import { createRoot } from 'react-dom/client';
import { AssignUserForm } from './AssignUserForm'
import { UnassignUserForm } from './UnassignUserForm'


/**
* Renders a button that opens/closes a form to assign/unassign users.
* @param action The form's action (Assign or Unassign)
* @param assignment The form's assignment type (workers or reviewers)
* @param users The form's selectable users
* @param buttonId The button's element ID
* @param formId The form's element ID
*/
function WorkbasketUserAssignment({ action, assignment, users, buttonId, formId }) {
const [showForm, setShowForm] = useState(null);
const assignmentType = assignment == "workers" ? "WORKBASKET_WORKER" : "WORKBASKET_REVIEWER";

const removeFormDiv = () => {
const possibleFormDivs = [
document.getElementById("assign-workers-form"),
document.getElementById("unassign-workers-form"),
document.getElementById("assign-reviewers-form"),
document.getElementById("unassign-reviewers-form"),
];
possibleFormDivs.forEach(form => {
if (form) {
const assignmentRow = form.previousSibling;
assignmentRow.classList.remove("govuk-summary-list__row--no-border");
form.remove();
}
});
}

const createFormDiv = () => {
const formDiv = document.createElement("div");
formDiv.id = formId;
formDiv.className = "govuk-!-margin-top-4";
const assignmentButton = document.getElementById(buttonId);
const assignmentRow = assignmentButton.closest(".govuk-summary-list__row");
assignmentRow.classList.add("govuk-summary-list__row--no-border");
assignmentRow.after(formDiv);
setShowForm(formDiv);
}

const handleClick = (e) => {
e.preventDefault();
const isShown = document.getElementById(formId);
removeFormDiv();
if(showForm && isShown) {
setShowForm(null);
return;
}
createFormDiv();
}

function UserForm({ action }) {
if (action === "Assign")
return <AssignUserForm assignmentType={assignmentType} users={users}/>;
else
return <UnassignUserForm users={users}/>;
}

return (
<>
<button
type="button"
onClick={handleClick}
id={buttonId}
data-testid={buttonId}
className="govuk-link fake-link">
{action}<span className="govuk-visually-hidden"> {assignment}</span>
</button>

{showForm !== null && createPortal(
<UserForm action={action}/>,
showForm
)}
</>
)
}

/**
* Creates React roots in which to render WorkBasketUserAssignment components
* for each assign/unassign link on the workbasket summary view.
*/
function init() {
const assignWorkersLink = document.getElementById("assign-workers");
if (assignWorkersLink) {
const assignWorkers = createRoot(assignWorkersLink)
assignWorkers.render(
<WorkbasketUserAssignment
action="Assign"
assignment="workers"
users={assignableUsers}
buttonId="assign-workers"
formId="assign-workers-form"
/>
);
}

const assignReviewersLink = document.getElementById("assign-reviewers");
if (assignReviewersLink) {
const assignReviewers = createRoot(assignReviewersLink)
assignReviewers.render(
<WorkbasketUserAssignment
action="Assign"
assignment="reviewers"
users={assignableUsers}
buttonId="assign-reviewers"
formId="assign-reviewers-form"
/>
);
}

const unassignWorkersLink = document.getElementById("unassign-workers");
if (unassignWorkersLink) {
const unassignWorkers = createRoot(unassignWorkersLink)
unassignWorkers.render(
<WorkbasketUserAssignment
action="Unassign"
assignment="workers"
users={assignedWorkers}
buttonId="unassign-workers"
formId="unassign-workers-form"
/>
);
}

const unassignReviewersLink = document.getElementById("unassign-reviewers");
if (unassignReviewersLink) {
const unassignReviewers = createRoot(unassignReviewersLink)
unassignReviewers.render(
<WorkbasketUserAssignment
action="Unassign"
assignment="reviewers"
users={assignedReviewers}
buttonId="unassign-reviewers"
formId="unassign-reviewers-form"
/>
);
}
}

function setupWorkbasketUserAssignment() {
document.addEventListener('DOMContentLoaded', init());
}

export { setupWorkbasketUserAssignment, WorkbasketUserAssignment };
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import renderer from 'react-test-renderer';
import { fireEvent, render, screen } from "@testing-library/react";
import { AssignUserForm } from '../AssignUserForm';


const mockUsers = [
{
"pk": 1,
"name": "User One",
},
{
"pk": 2,
"name": "User Two",
},
{
"pk": 3,
"name": "User Three",
},
]

describe(AssignUserForm, () => {
const csrfToken = "abc123";
const assignUsersUrl = "/workbaskets/1/assign-users/";

beforeAll(() => {
window.CSRF_TOKEN = csrfToken;
window.assignUsersUrl = assignUsersUrl;
});

afterAll(() => {
delete window.CSRF_TOKEN;
delete window.assignUsersUrl;
});

it('renders form', () => {
const component = renderer.create(
<AssignUserForm
assignmentType={"WORKBASKET_WORKER"}
users={mockUsers}
/>
);

let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});

it('has assign users form action url', () => {
render(
<AssignUserForm
assignmentType={"WORKBASKET_WORKER"}
users={mockUsers}
/>
);

expect(screen.getByTestId("assign-user-form")).toHaveAttribute('action', assignUsersUrl);
});

it('does not submit when form is empty', () => {
render(
<AssignUserForm
assignmentType={"WORKBASKET_WORKER"}
users={mockUsers}
/>
);

const mockSubmit = jest.fn(e => e.preventDefault());
screen.getByTestId("assign-user-form").onsubmit = mockSubmit

fireEvent.click(screen.getByRole("button", { name: "Save" }));

expect(mockSubmit).not.toHaveBeenCalled();
})

it('submits with selected user', () => {
render(
<AssignUserForm
assignmentType={"WORKBASKET_WORKER"}
users={mockUsers}
/>
);

const mockSubmit = jest.fn(e => e.preventDefault());
screen.getByTestId("assign-user-form").onsubmit = mockSubmit
const input = screen.getByTestId('assign-user-select');

fireEvent.change(input, {target: {value: mockUsers[0].pk}});
fireEvent.click(screen.getByRole("button", { name: "Save" }));

expect(mockSubmit).toHaveBeenCalled();
})
})
Loading

0 comments on commit 83dba31

Please sign in to comment.