Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions web/src/components/community/BodyComposer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Write, Preview, Post: the comment went up and the field came back as an
// empty preview box, the Preview tab still picked and greyed out, no textarea
// anywhere. Writing a second comment meant finding "Write" first. A preview
// of nothing is what the disabled tab is there to prevent, so the field goes
// back to Write when its text goes.

import { fireEvent, render, screen } from "@testing-library/react";
import { useState } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import BodyComposer from "./BodyComposer";
import CommentSection from "./CommentSection";

const refresh = vi.fn();
vi.mock("next/navigation", () => ({ useRouter: () => ({ refresh, push: vi.fn() }) }));
vi.mock("@/lib/community/auth-client", () => ({
authClient: {
useSession: () => ({ data: { user: { id: "u1", username: "ada" } }, isPending: false }),
},
}));

function Field({ initial }: { initial: string }) {
const [value, setValue] = useState(initial);
return (
<>
<BodyComposer value={value} onChange={setValue} maxLength={2000} label="Comment" grow="comment" />
<button type="button" onClick={() => setValue("")}>
clear
</button>
</>
);
}

const tab = (name: string) => screen.getByRole("tab", { name });

describe("BodyComposer", () => {
it("goes back to Write when its text is cleared while previewing", () => {
render(<Field initial="**hello**" />);
fireEvent.click(tab("Preview"));
expect(screen.queryByRole("textbox", { name: "Comment" })).toBeNull();
expect(tab("Preview").getAttribute("aria-selected")).toBe("true");

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

expect((screen.getByRole("textbox", { name: "Comment" }) as HTMLTextAreaElement).value).toBe("");
expect(tab("Write").getAttribute("aria-selected")).toBe("true");
expect(tab("Preview").getAttribute("aria-selected")).toBe("false");
expect((tab("Preview") as HTMLButtonElement).disabled).toBe(true);
});

it("stays on Write as the next text is typed", () => {
render(<Field initial="first" />);
fireEvent.click(tab("Preview"));
fireEvent.click(screen.getByRole("button", { name: "clear" }));

fireEvent.change(screen.getByRole("textbox", { name: "Comment" }), { target: { value: "s" } });

// One letter in, the field is still a textarea; the old preview does not
// come back on its own.
expect((screen.getByRole("textbox", { name: "Comment" }) as HTMLTextAreaElement).value).toBe("s");
expect(tab("Write").getAttribute("aria-selected")).toBe("true");
expect((tab("Preview") as HTMLButtonElement).disabled).toBe(false);
});

it("keeps the preview while the text is there", () => {
render(<Field initial="still here" />);
fireEvent.click(tab("Preview"));
expect(screen.getByText("still here")).toBeTruthy();
expect(screen.queryByRole("textbox", { name: "Comment" })).toBeNull();
});
});

describe("posting from Preview", () => {
beforeEach(() => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json({ ok: true }, { status: 201 })));
});
afterEach(() => {
vi.unstubAllGlobals();
refresh.mockReset();
});

it("brings the comment field back empty, on Write", async () => {
render(<CommentSection patternId="p1" comments={[]} />);
fireEvent.change(screen.getByRole("textbox", { name: "Comment" }), { target: { value: "nice *knobs*" } });
fireEvent.click(tab("Preview"));
fireEvent.click(screen.getByRole("button", { name: "Post" }));

await vi.waitFor(() => expect(refresh).toHaveBeenCalled());
expect((screen.getByRole("textbox", { name: "Comment" }) as HTMLTextAreaElement).value).toBe("");
expect(tab("Write").getAttribute("aria-selected")).toBe("true");
});
});
7 changes: 6 additions & 1 deletion web/src/components/community/BodyComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ export default function BodyComposer({
autoFocus?: boolean;
}) {
const [previewing, setPreviewing] = useState(false);
// Posting from Preview clears the text under it. Left there, the field was
// a preview of nothing with its tab greyed out and no textarea to write the
// next comment in. Adjusted during render, guarded, like AuthStatus's latch.
const hasText = value.trim().length > 0;
if (previewing && !hasText) setPreviewing(false);
const ref = useAutoGrow<HTMLTextAreaElement>(previewing ? "" : value);

return (
Expand All @@ -53,7 +58,7 @@ export default function BodyComposer({
data-active={previewing}
// Nothing to preview is worth saying out loud rather than showing an
// empty box that looks like a broken renderer.
disabled={value.trim().length === 0}
disabled={!hasText}
onClick={() => setPreviewing(true)}
>
Preview
Expand Down
Loading