memos/web/tests/task-list-item-check-all.test.tsx
boojack 5192732cee fix(web): keep task checkboxes controlled so check-all updates render
remark-gfm passes checked as undefined for unchecked task items, so the
Base UI Checkbox mounted uncontrolled and permanently ignored the
checked=true arriving after "Check all tasks" rewrote the memo content.
Coerce the prop to a boolean so the checkbox is always controlled.

Also drop the `${updateTime}` suffixes from memo React keys and the
comment editor cache key: a protobuf-es Timestamp stringifies to
"[object Object]", so the suffix was a constant — the keys were
effectively name-only already, and now say so honestly.

Fixes usememos/memos#6143
2026-08-06 09:09:37 +08:00

59 lines
2.3 KiB
TypeScript

import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { TaskListItem } from "@/components/MemoContent/TaskListItem";
// Regression test for usememos/memos#6143 ("Check all tasks not working").
//
// remark-gfm passes `checked: undefined` for unchecked task items (`- [ ]`).
// Base UI's Checkbox locks controlled/uncontrolled mode at mount, so a checkbox
// that mounts with `checked === undefined` becomes uncontrolled and silently
// ignores `checked=true` arriving later — exactly what happens after
// "Check all tasks" rewrites the memo content and the markdown re-renders.
// TaskListItem must coerce `checked` to a boolean so the checkbox is always
// controlled.
const mockUpdateMemo = vi.hoisted(() => vi.fn());
vi.mock("@/hooks/useMemoQueries", () => ({
useUpdateMemo: () => ({ mutate: mockUpdateMemo }),
}));
vi.mock("@/components/MemoView/MemoViewContext", () => ({
useMemoViewContext: () => ({
memo: {
name: "memos/1",
content: "- [ ] task one",
relations: [],
attachments: [],
reactions: [],
},
}),
useMemoViewDerived: () => ({
readonly: false,
}),
}));
describe("<TaskListItem /> check-all regression (#6143)", () => {
it("reflects checked=true arriving after mounting unchecked (Check all tasks)", () => {
// remark-gfm renders `- [ ]` with `checked` undefined.
const { rerender } = render(<TaskListItem checked={undefined} node={undefined} />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toHaveAttribute("aria-checked", "false");
// "Check all tasks" updates memo content; the markdown re-renders with checked=true.
rerender(<TaskListItem checked={true} node={undefined} />);
expect(checkbox).toHaveAttribute("aria-checked", "true");
});
it("reflects checked reverting to undefined (Uncheck all tasks)", () => {
const { rerender } = render(<TaskListItem checked={true} node={undefined} />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toHaveAttribute("aria-checked", "true");
// remark-gfm passes undefined (not false) for `- [ ]` items.
rerender(<TaskListItem checked={undefined} node={undefined} />);
expect(checkbox).toHaveAttribute("aria-checked", "false");
});
});