feat(uplifts): align Reject/Revoke Cancel and required asterisk [SH-120]

This commit is contained in:
Arthur Bassi 2026-08-27 11:35:54 -03:00
parent 4a3d04ed01
commit ba2e3e37c7
12 changed files with 573 additions and 43 deletions

View file

@ -0,0 +1,3 @@
export function formatUpliftMoney(value: number): string {
return `$${value.toLocaleString("en-US", { maximumFractionDigits: 2 })}`;
}

View file

@ -0,0 +1,100 @@
import { useEffect, useState } from "react";
import { Dialog, DialogActions, DialogContent, DialogTitle } from "@mui/material";
import { formatUpliftMoney } from "@/app/(protected)/uplifts/_components/format-uplift-money";
import { UpliftReasonField } from "@/app/(protected)/uplifts/_components/uplift-reason-field";
import { Button } from "@/components/ui/button";
import { DialogCancelButton } from "@/components/ui/dialog-cancel-button";
import { dialogBlurBackdropSlotProps } from "@/components/ui/dialog-blur-backdrop";
import { Text } from "@/components/ui/text";
import { guardDialogClose } from "@/lib/dialog-guard";
export type RejectDialogProps = {
open: boolean;
amount?: number | null;
woNumber?: string | null;
onClose: () => void;
onConfirm: (reason: string) => void;
isPending?: boolean;
};
function rejectTitle(amount: number | null | undefined): string {
if (amount != null && Number.isFinite(amount)) {
return `Reject uplift of ${formatUpliftMoney(amount)}?`;
}
return "Reject uplift?";
}
export function RejectDialog({
open,
amount,
woNumber,
onClose,
onConfirm,
isPending = false,
}: RejectDialogProps) {
const [reason, setReason] = useState("");
const canConfirm = reason.trim().length > 0 && !isPending;
useEffect(() => {
if (open) {
setReason("");
}
}, [open]);
const handleClose = () => {
if (isPending) return;
setReason("");
onClose();
};
return (
<Dialog
open={open}
onClose={guardDialogClose(isPending, handleClose)}
maxWidth={false}
slotProps={{
...dialogBlurBackdropSlotProps,
paper: {
className: "w-full",
style: { borderRadius: 12, maxWidth: 480 },
},
}}
>
<DialogTitle
className="p-6 pb-0"
style={{ fontSize: 16, color: "var(--color-text-primary)" }}
>
{rejectTitle(amount)}
</DialogTitle>
<DialogContent className="px-6 pt-3">
{Boolean(woNumber) && (
<Text as="p" variant="description" className="mb-3">
The dispatcher sees this reason on WO #{woNumber}.
</Text>
)}
<UpliftReasonField
id="reject-uplift-reason"
label="Reason for rejection"
required
value={reason}
disabled={isPending}
placeholder="Explain why this uplift is not approved..."
onChange={setReason}
/>
</DialogContent>
<DialogActions className="px-6 pb-6 pt-2">
<DialogCancelButton onClick={handleClose} disabled={isPending} />
<Button
variant="destructive"
className="min-h-0 px-3 py-1 text-[12px]"
disabled={!canConfirm}
loading={isPending}
loadingText="Rejecting…"
onClick={() => onConfirm(reason.trim())}
>
Reject uplift
</Button>
</DialogActions>
</Dialog>
);
}

View file

@ -0,0 +1,110 @@
import { useEffect, useState } from "react";
import { Dialog, DialogActions, DialogContent, DialogTitle } from "@mui/material";
import { formatUpliftMoney } from "@/app/(protected)/uplifts/_components/format-uplift-money";
import { UpliftReasonField } from "@/app/(protected)/uplifts/_components/uplift-reason-field";
import { Button } from "@/components/ui/button";
import { DialogCancelButton } from "@/components/ui/dialog-cancel-button";
import { dialogBlurBackdropSlotProps } from "@/components/ui/dialog-blur-backdrop";
import { Text } from "@/components/ui/text";
import { guardDialogClose } from "@/lib/dialog-guard";
export type RevokeDialogProps = {
open: boolean;
amount?: number | null;
woNumber?: string | null;
onClose: () => void;
onConfirm: (reason: string) => void;
isPending?: boolean;
};
function revokeAmountLabel(amount: number | null | undefined): string {
if (amount != null && Number.isFinite(amount)) {
return formatUpliftMoney(amount);
}
return "this uplift";
}
function revokeConfirmLabel(amount: number | null | undefined): string {
if (amount != null && Number.isFinite(amount)) {
return `Revoke ${formatUpliftMoney(amount)}`;
}
return "Revoke";
}
export function RevokeDialog({
open,
amount,
woNumber,
onClose,
onConfirm,
isPending = false,
}: RevokeDialogProps) {
const [reason, setReason] = useState("");
const canConfirm = reason.trim().length > 0 && !isPending;
useEffect(() => {
if (open) {
setReason("");
}
}, [open]);
const handleClose = () => {
if (isPending) return;
setReason("");
onClose();
};
const amountLabel = revokeAmountLabel(amount);
const woSuffix = woNumber ? ` on WO ${woNumber}` : "";
return (
<Dialog
open={open}
onClose={guardDialogClose(isPending, handleClose)}
maxWidth={false}
slotProps={{
...dialogBlurBackdropSlotProps,
paper: {
className: "w-full",
style: { borderRadius: 12, maxWidth: 480 },
},
}}
>
<DialogTitle
className="p-6 pb-0"
style={{ fontSize: 16, color: "var(--color-text-primary)" }}
>
Revoke this approval?
</DialogTitle>
<DialogContent className="px-6 pt-3">
<Text as="p" variant="description" className="mb-3">
The approved uplift of {amountLabel}
{woSuffix} will be withdrawn. This does not recover money already spent — it records that
the authorization was a mistake.
</Text>
<UpliftReasonField
id="revoke-uplift-reason"
label="Reason for revoking"
required
value={reason}
disabled={isPending}
placeholder="Explain why this approval is being withdrawn..."
onChange={setReason}
/>
</DialogContent>
<DialogActions className="px-6 pb-6 pt-2">
<DialogCancelButton onClick={handleClose} disabled={isPending} />
<Button
variant="destructive"
className="min-h-0 px-3 py-1 text-[12px]"
disabled={!canConfirm}
loading={isPending}
loadingText="Revoking…"
onClick={() => onConfirm(reason.trim())}
>
{revokeConfirmLabel(amount)}
</Button>
</DialogActions>
</Dialog>
);
}

View file

@ -0,0 +1,44 @@
import {
DLG_INPUT_CLS,
DLG_INPUT_STYLE,
} from "@/app/(protected)/workorders/_components/wizard/wizard-input-styles";
import { FieldLabel } from "@/app/(protected)/workorders/_components/list/table/cells/dialog-field-label";
type UpliftReasonFieldProps = {
id: string;
label: string;
value: string;
placeholder: string;
required?: boolean;
disabled?: boolean;
onChange: (value: string) => void;
};
export function UpliftReasonField({
id,
label,
value,
placeholder,
required = false,
disabled = false,
onChange,
}: UpliftReasonFieldProps) {
return (
<div>
<FieldLabel htmlFor={id} required={required}>
{label}
</FieldLabel>
<textarea
id={id}
value={value}
disabled={disabled}
placeholder={placeholder}
rows={3}
autoFocus
className={`${DLG_INPUT_CLS} resize-none`}
style={{ ...DLG_INPUT_STYLE, fontFamily: "inherit" }}
onChange={(event) => onChange(event.target.value)}
/>
</div>
);
}

View file

@ -38,6 +38,7 @@ import {
import { useUpliftsQueue } from "@/domain/uplifts/use-cases/use-uplifts-queue";
import { formatDateTime } from "@/lib/time-utils";
import { toast } from "react-toastify";
import { RejectDialog } from "@/app/(protected)/uplifts/_components/reject-dialog";
const STATUS_OPTIONS = ["", ...UPLIFT_STATUSES];
@ -297,8 +298,6 @@ function UpliftDecisionDialog({
<DialogTitle>
{action?.type === "approve"
? `Approve uplift to ${formatMoney(action.item.requestedNTE)}?`
: action?.type === "reject"
? "Reject Uplift"
: "Request Changes"}
</DialogTitle>
<DialogContent>
@ -354,9 +353,9 @@ export default function UpliftQueuePage() {
const rows = data?.items ?? [];
const totalCount = data?.totalCount ?? 0;
const noteRequired = upliftAction?.type === "reject" || upliftAction?.type === "request-changes";
const decisionPending =
approveUplift.isPending || rejectUplift.isPending || requestChangesUplift.isPending;
const genericAction = upliftAction?.type === "reject" ? null : upliftAction;
const noteRequired = genericAction?.type === "request-changes";
const decisionPending = approveUplift.isPending || requestChangesUplift.isPending;
const handleStatusChange = (value: string) => {
setStatus(value);
@ -382,13 +381,26 @@ export default function UpliftQueuePage() {
};
if (upliftAction.type === "approve") {
approveUplift.mutate({ id, note: note || undefined }, { onSuccess: onDone });
} else if (upliftAction.type === "reject") {
rejectUplift.mutate({ id, note: note.trim() }, { onSuccess: onDone });
} else {
return;
}
if (upliftAction.type === "request-changes") {
requestChangesUplift.mutate({ id, note: note.trim() }, { onSuccess: onDone });
}
};
const handleRejectConfirm = (reason: string) => {
if (upliftAction?.type !== "reject") return;
rejectUplift.mutate(
{ id: upliftAction.item.id, note: reason },
{
onSuccess: () => {
closeDialog();
void refetch();
},
},
);
};
const downloadEvidence = async (item: UpliftQueueItem) => {
try {
await upliftsApi.downloadEvidence(item.id, item.evidenceFileName || "uplift-evidence");
@ -440,8 +452,15 @@ export default function UpliftQueuePage() {
rowsPerPage={pageSize}
rowsPerPageOptions={[25]}
/>
<RejectDialog
open={upliftAction?.type === "reject"}
amount={upliftAction?.item.requestedNTE}
onClose={closeDialog}
onConfirm={handleRejectConfirm}
isPending={rejectUplift.isPending}
/>
<UpliftDecisionDialog
action={upliftAction}
action={genericAction}
note={note}
noteRequired={noteRequired}
decisionPending={decisionPending}

View file

@ -17,6 +17,8 @@ import {
TextField,
Typography,
} from "@mui/material";
import { FieldLabel } from "@/app/(protected)/workorders/_components/list/table/cells/dialog-field-label";
import { DialogCancelButton } from "@/components/ui/dialog-cancel-button";
import type { UpliftRequest } from "@/domain/uplifts/types/uplift";
import type {
VendorPoDetail,
@ -314,19 +316,21 @@ export function UpliftDecisionDialog({
: "Request Changes"}
</DialogTitle>
<DialogContent>
<FieldLabel htmlFor="vendor-po-uplift-note" required={noteRequired}>
{noteRequired ? "Note" : "Optional note"}
</FieldLabel>
<TextField
id="vendor-po-uplift-note"
fullWidth
multiline
minRows={2}
className="mt-2"
label={noteRequired ? "Note (required)" : "Optional note"}
value={note}
required={noteRequired}
onChange={(e) => onNoteChange(e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<DialogCancelButton onClick={onClose} disabled={decisionPending} />
<Button
variant="contained"
color={action?.type === "approve" ? "success" : "primary"}

View file

@ -1,13 +1,9 @@
import { useState } from "react";
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
Typography,
} from "@mui/material";
import { Dialog, DialogActions, DialogContent, DialogTitle, TextField } from "@mui/material";
import { FieldLabel } from "@/app/(protected)/workorders/_components/list/table/cells/dialog-field-label";
import { Button } from "@/components/ui/button";
import { DialogCancelButton } from "@/components/ui/dialog-cancel-button";
import { Text } from "@/components/ui/text";
import { guardDialogClose } from "@/lib/dialog-guard";
type UpliftDecisionDialogProps = {
@ -55,27 +51,27 @@ export function UpliftDecisionDialog({
<DialogTitle>{titleFor(type, requestedNTE)}</DialogTitle>
<DialogContent>
{type === "approve" && (
<Typography variant="body2" className="mt-1 mb-3">
<Text as="p" variant="description" className="mt-1 mb-3">
${Number(currentNTE ?? 0).toFixed(2)} → ${requestedNTE.toFixed(2)} (+$
{(requestedNTE - Number(currentNTE ?? 0)).toFixed(2)})
</Typography>
</Text>
)}
<FieldLabel htmlFor="dispatch-uplift-note" required={noteRequired}>
{noteRequired ? "Note" : "Optional note"}
</FieldLabel>
<TextField
id="dispatch-uplift-note"
fullWidth
multiline
minRows={2}
className="mt-2"
label={noteRequired ? "Note (required)" : "Optional note"}
required={noteRequired}
value={note}
onChange={(event) => setNote(event.target.value)}
disabled={isPending}
/>
</DialogContent>
<DialogActions>
<Button variant="outline" onClick={handleClose} disabled={isPending}>
Cancel
</Button>
<DialogCancelButton onClick={handleClose} disabled={isPending} />
<Button
variant={type === "reject" ? "destructive" : "default"}
disabled={noteRequired && !note.trim()}

View file

@ -1,12 +1,24 @@
import type { ReactNode } from "react";
export function FieldLabel({ children }: { children: ReactNode }) {
type FieldLabelProps = {
children: ReactNode;
required?: boolean;
htmlFor?: string;
};
export function FieldLabel({ children, required = false, htmlFor }: FieldLabelProps) {
return (
<label
htmlFor={htmlFor}
className="mb-1.5 flex items-center gap-1.5 text-[12px] font-medium"
style={{ color: "var(--color-text-secondary)" }}
>
{children}
{Boolean(required) && (
<span aria-hidden style={{ color: "var(--destructive)" }}>
*
</span>
)}
</label>
);
}

View file

@ -1,13 +1,10 @@
import { useEffect, useState } from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
} from "@mui/material";
import { Dialog, DialogActions, DialogContent, DialogTitle, TextField } from "@mui/material";
import { FieldLabel } from "@/app/(protected)/workorders/_components/list/table/cells/dialog-field-label";
import { Button } from "@/components/ui/button";
import { DialogCancelButton } from "@/components/ui/dialog-cancel-button";
import { Text } from "@/components/ui/text";
import { guardDialogClose } from "@/lib/dialog-guard";
type RevokeUpliftDialogProps = {
open: boolean;
@ -38,6 +35,7 @@ export function RevokeUpliftDialog({
}, [open]);
const handleClose = () => {
if (pending) return;
onClose();
};
@ -48,27 +46,29 @@ export function RevokeUpliftDialog({
const reasonMissing = requireReason && reason.trim() === "";
return (
<Dialog open={open} onClose={handleClose} maxWidth="xs" fullWidth>
<Dialog open={open} onClose={guardDialogClose(pending, handleClose)} maxWidth="xs" fullWidth>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<Text as="p" variant="description" className="mb-3">
{description}
</Text>
<FieldLabel htmlFor="wo-revoke-uplift-reason" required={requireReason}>
Reason
</FieldLabel>
<TextField
label={requireReason ? "Reason (required)" : "Reason (optional)"}
id="wo-revoke-uplift-reason"
fullWidth
multiline
minRows={2}
required={requireReason}
value={reason}
onChange={(event) => setReason(event.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<DialogCancelButton onClick={handleClose} disabled={pending} />
<Button
color="error"
variant="contained"
variant="destructive"
className="min-h-0 px-3 py-1 text-[12px]"
disabled={pending || reasonMissing}
onClick={handleConfirm}
>

View file

@ -0,0 +1,20 @@
import { Button, type UiButtonProps } from "@/components/ui/button";
import { cn } from "@/lib/utils";
/** Same tokens as VendorDialogFooter Cancel (`ghost` + this class). */
export const VENDOR_DIALOG_CANCEL_CLASS = "min-h-0 px-2 py-1 text-[12px]";
type DialogCancelButtonProps = Omit<UiButtonProps, "variant">;
/** Cancel chrome shared with VendorDialog footer. */
export function DialogCancelButton({
className,
children = "Cancel",
...props
}: DialogCancelButtonProps) {
return (
<Button variant="ghost" className={cn(VENDOR_DIALOG_CANCEL_CLASS, className)} {...props}>
{children}
</Button>
);
}

View file

@ -0,0 +1,141 @@
import type { ComponentProps } from "react";
import { fireEvent, screen, within } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { RejectDialog } from "@/app/(protected)/uplifts/_components/reject-dialog";
import { RevokeDialog } from "@/app/(protected)/uplifts/_components/revoke-dialog";
import { FieldLabel } from "@/app/(protected)/workorders/_components/list/table/cells/dialog-field-label";
import { VendorDialogFooter } from "@/app/(protected)/workorders/_components/list/table/cells/vendor-dialog-footer";
import {
DialogCancelButton,
VENDOR_DIALOG_CANCEL_CLASS,
} from "@/components/ui/dialog-cancel-button";
import { renderWithProviders } from "@/test/test-utils";
const VENDOR_CANCEL_TOKENS = VENDOR_DIALOG_CANCEL_CLASS.split(" ");
function expectVendorCancelChrome(button: HTMLElement) {
for (const token of VENDOR_CANCEL_TOKENS) {
expect(button).toHaveClass(token);
}
expect(button).toHaveClass("text-foreground");
}
function expectDestructiveAsterisk(labelText: string) {
const label = screen.getByText(labelText).closest("label");
expect(label).toBeTruthy();
const star = within(label as HTMLElement).getByText("*");
expect(star).toHaveAttribute("aria-hidden", "true");
expect(star).toHaveStyle({ color: "var(--destructive)" });
}
function renderReject(overrides: Partial<ComponentProps<typeof RejectDialog>> = {}) {
const onClose = vi.fn();
const onConfirm = vi.fn();
renderWithProviders(
<RejectDialog open amount={250} onClose={onClose} onConfirm={onConfirm} {...overrides} />,
);
return { onClose, onConfirm };
}
function renderRevoke(overrides: Partial<ComponentProps<typeof RevokeDialog>> = {}) {
const onClose = vi.fn();
const onConfirm = vi.fn();
renderWithProviders(
<RevokeDialog open amount={500} onClose={onClose} onConfirm={onConfirm} {...overrides} />,
);
return { onClose, onConfirm };
}
describe("FieldLabel required asterisk", () => {
it("renders a destructive asterisk only when required", () => {
const { rerender } = renderWithProviders(<FieldLabel>Site</FieldLabel>);
expect(screen.getByText("Site").textContent).toBe("Site");
expect(screen.queryByText("*")).not.toBeInTheDocument();
rerender(<FieldLabel required>Site</FieldLabel>);
expectDestructiveAsterisk("Site");
});
});
describe("DialogCancelButton VendorDialog tokens", () => {
it("matches VendorDialogFooter Cancel classes (ghost + 12px compact padding)", () => {
renderWithProviders(
<>
<VendorDialogFooter isPending={false} onCancel={() => undefined} onSave={() => undefined} />
<DialogCancelButton />
</>,
);
const [vendorCancel, sharedCancel] = screen.getAllByRole("button", { name: "Cancel" });
expectVendorCancelChrome(vendorCancel);
expectVendorCancelChrome(sharedCancel);
});
});
describe("RejectDialog", () => {
it("keeps confirm disabled until a non-whitespace reason is typed", () => {
const { onConfirm } = renderReject();
const confirm = screen.getByRole("button", { name: "Reject uplift" });
expect(confirm).toBeDisabled();
fireEvent.change(screen.getByLabelText(/reason for rejection/i), {
target: { value: " " },
});
expect(confirm).toBeDisabled();
fireEvent.change(screen.getByLabelText(/reason for rejection/i), {
target: { value: "Over the NTE cap" },
});
expect(confirm).toBeEnabled();
fireEvent.click(confirm);
expect(onConfirm).toHaveBeenCalledWith("Over the NTE cap");
});
it("closes on Cancel without confirming", () => {
const { onClose, onConfirm } = renderReject();
const cancel = screen.getByRole("button", { name: "Cancel" });
expectVendorCancelChrome(cancel);
fireEvent.click(cancel);
expect(onClose).toHaveBeenCalledTimes(1);
expect(onConfirm).not.toHaveBeenCalled();
});
it("marks the reason as required with an asterisk", () => {
renderReject();
expectDestructiveAsterisk("Reason for rejection");
});
it("blocks cancel and escape while pending", () => {
const { onClose } = renderReject({ isPending: true });
expect(screen.getByRole("button", { name: "Cancel" })).toBeDisabled();
fireEvent.keyDown(document.body, { key: "Escape" });
expect(onClose).not.toHaveBeenCalled();
});
});
describe("RevokeDialog", () => {
it("keeps confirm disabled until a reason is typed", () => {
const { onConfirm } = renderRevoke();
const confirm = screen.getByRole("button", { name: "Revoke $500" });
expect(confirm).toBeDisabled();
fireEvent.change(screen.getByLabelText(/reason for revoking/i), {
target: { value: "Approved in error" },
});
fireEvent.click(confirm);
expect(onConfirm).toHaveBeenCalledWith("Approved in error");
});
it("closes on Cancel without confirming", () => {
const { onClose, onConfirm } = renderRevoke();
const cancel = screen.getByRole("button", { name: "Cancel" });
expectVendorCancelChrome(cancel);
fireEvent.click(cancel);
expect(onClose).toHaveBeenCalledTimes(1);
expect(onConfirm).not.toHaveBeenCalled();
});
it("marks the reason as required with an asterisk", () => {
renderRevoke();
expectDestructiveAsterisk("Reason for revoking");
});
});

View file

@ -0,0 +1,81 @@
import { fireEvent, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { UpliftQueueItem } from "@/domain/uplifts/types/uplift";
import { renderWithProviders } from "@/test/test-utils";
const rejectMutate = vi.fn();
const approveMutate = vi.fn();
const requestChangesMutate = vi.fn();
const refetch = vi.fn();
const pendingItem: UpliftQueueItem = {
id: 41,
status: "Pending",
currentNTE: 100,
requestedNTE: 250,
delta: 150,
vendorReason: "Extra parts",
requestedAt: "2026-01-15T10:00:00Z",
requestedByVendorName: "Gateway",
decidedAt: "",
decidedByName: "",
decisionNote: "",
requiredTier: 1,
canDecide: true,
expiresAt: "",
notificationStatus: "",
notificationError: "",
evidenceDocumentId: null,
evidenceFileName: "",
evidenceContentType: "",
evidenceSizeBytes: null,
dispatchNumber: "DSP-41",
poNumber: "PO-41",
vendorCompanyName: "Gateway Plumbing",
workOrderId: 99,
dispatchId: 7,
};
vi.mock("@/domain/uplifts/use-cases/use-uplifts-queue", () => ({
useUpliftsQueue: () => ({
data: { items: [pendingItem], totalCount: 1, page: 1, pageSize: 25 },
isLoading: false,
isFetching: false,
error: null,
refetch,
}),
}));
vi.mock("@/domain/uplifts/use-cases/use-uplift-actions", () => ({
useApproveUplift: () => ({ isPending: false, mutate: approveMutate }),
useRejectUplift: () => ({ isPending: false, mutate: rejectMutate }),
useRequestChangesUplift: () => ({ isPending: false, mutate: requestChangesMutate }),
}));
describe("Uplift queue decision dialogs", () => {
beforeEach(() => {
rejectMutate.mockReset();
approveMutate.mockReset();
requestChangesMutate.mockReset();
refetch.mockReset();
});
it("opens RejectDialog from Reject and does not reject on Cancel", async () => {
const { default: UpliftQueuePage } = await import("@/app/(protected)/uplifts/index");
renderWithProviders(<UpliftQueuePage />);
fireEvent.click(screen.getByRole("button", { name: "Reject" }));
expect(screen.getByRole("heading", { name: /reject uplift of \$250/i })).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
expect(rejectMutate).not.toHaveBeenCalled();
});
it("keeps Approve on the generic confirm dialog", async () => {
const { default: UpliftQueuePage } = await import("@/app/(protected)/uplifts/index");
renderWithProviders(<UpliftQueuePage />);
fireEvent.click(screen.getByRole("button", { name: "Approve" }));
expect(screen.getByRole("button", { name: "Confirm" })).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Reject uplift" })).not.toBeInTheDocument();
});
});