diff --git a/src/components/ui/mark-ready-dialog.test.tsx b/src/components/ui/mark-ready-dialog.test.tsx
new file mode 100644
index 0000000..cbfbfe3
--- /dev/null
+++ b/src/components/ui/mark-ready-dialog.test.tsx
@@ -0,0 +1,126 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { cleanup, fireEvent, render, screen } from "@testing-library/react";
+
+import { MarkReadyDialog } from "./mark-ready-dialog";
+
+const PO_LABEL = "PO-2026-07-009";
+const ITEM_LABEL = "Magnetic drill";
+
+describe("MarkReadyDialog", () => {
+ beforeEach(() => {
+ class ResizeObserverMock {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+ }
+ vi.stubGlobal("ResizeObserver", ResizeObserverMock);
+ window.HTMLElement.prototype.scrollIntoView = vi.fn();
+ });
+
+ afterEach(() => {
+ cleanup();
+ });
+
+ it("renders the PO and item in the title", () => {
+ render(
+ ,
+ );
+ expect(
+ screen.getByText(`Mark ready: ${PO_LABEL} · ${ITEM_LABEL}`),
+ ).toBeInTheDocument();
+ });
+
+ it("disables Save when the textarea is empty", () => {
+ render(
+ ,
+ );
+ expect(screen.getByRole("button", { name: /save & mark ready/i })).toBeDisabled();
+ });
+
+ it("disables Save when the textarea is whitespace only", () => {
+ render(
+ ,
+ );
+ fireEvent.change(screen.getByRole("textbox"), {
+ target: { value: " \n " },
+ });
+ expect(screen.getByRole("button", { name: /save & mark ready/i })).toBeDisabled();
+ });
+
+ it("enables Save and submits the trimmed note", () => {
+ const onSubmit = vi.fn();
+ render(
+ ,
+ );
+ fireEvent.change(screen.getByRole("textbox"), {
+ target: { value: " spoke to David " },
+ });
+ const save = screen.getByRole("button", { name: /save & mark ready/i });
+ expect(save).not.toBeDisabled();
+ fireEvent.click(save);
+ expect(onSubmit).toHaveBeenCalledWith("spoke to David");
+ });
+
+ it("renders a server error without closing the modal", () => {
+ render(
+ ,
+ );
+ expect(
+ screen.getByText(/a note is required when marking ready/i),
+ ).toBeInTheDocument();
+ expect(screen.getByRole("alert")).toBeInTheDocument();
+ expect(
+ screen.getByRole("dialog", {
+ name: `Mark ready: ${PO_LABEL} · ${ITEM_LABEL}`,
+ }),
+ ).toBeInTheDocument();
+ });
+
+ it("does not call onSubmit when the dialog is closed", () => {
+ const onSubmit = vi.fn();
+ const onOpenChange = vi.fn();
+ render(
+ ,
+ );
+ const save = screen.queryByRole("button", { name: /save & mark ready/i });
+ expect(save).toBeNull();
+ expect(onSubmit).not.toHaveBeenCalled();
+ });
+});
\ No newline at end of file