feat(FN-2660): refine TodoView empty state and edit flow

- Reset list/item draft state when switching lists or entering a conflicting edit mode
- Hide the empty-state prompt while creating a new list and improve list accessibility semantics with role and aria-current attributes
- Add Escape handling to clear pending new-item text and improve mobile checkbox/scrollbar styling in TodoView CSS
- Expand TodoView tests to cover trimming behavior, draft clearing, empty-state transitions, and selected-list accessibility
This commit is contained in:
Fusion
2026-04-27 01:12:33 -07:00
committed by gsxdsm
parent 809a76a3c2
commit fca0fce605
3 changed files with 179 additions and 8 deletions

View File

@@ -192,6 +192,20 @@ describe("TodoView", () => {
expect(state.createItem).toHaveBeenCalledWith("Pack bags");
});
it("pressing Escape in add-item input clears the pending draft", () => {
const state = createMockTodoLists();
mockUseTodoLists.mockReturnValue(state);
render(<TodoView addToast={addToast} />);
const input = screen.getByTestId("new-item-input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "Pack bags" } });
fireEvent.keyDown(input, { key: "Escape" });
expect(state.createItem).not.toHaveBeenCalled();
expect(input.value).toBe("");
});
it("clicking checkbox calls toggleItem with item ID", () => {
const state = createMockTodoLists();
mockUseTodoLists.mockReturnValue(state);
@@ -258,13 +272,102 @@ describe("TodoView", () => {
expect(state.deleteItem).toHaveBeenCalledWith("item-1");
});
it("shows new list input when empty state create button is clicked", () => {
it("trims surrounding whitespace before creating a list", () => {
const state = createMockTodoLists();
mockUseTodoLists.mockReturnValue(state);
render(<TodoView addToast={addToast} />);
fireEvent.click(screen.getByTestId("add-list-button"));
const input = screen.getByTestId("new-list-input");
fireEvent.change(input, { target: { value: " Weekend " } });
fireEvent.keyDown(input, { key: "Enter" });
expect(state.createList).toHaveBeenCalledWith("Weekend");
});
it("trims surrounding whitespace before creating an item", () => {
const state = createMockTodoLists();
mockUseTodoLists.mockReturnValue(state);
render(<TodoView addToast={addToast} />);
const input = screen.getByTestId("new-item-input");
fireEvent.change(input, { target: { value: " Pack bags " } });
fireEvent.keyDown(input, { key: "Enter" });
expect(state.createItem).toHaveBeenCalledWith("Pack bags");
});
it("trims surrounding whitespace before renaming a list", () => {
const state = createMockTodoLists();
mockUseTodoLists.mockReturnValue(state);
render(<TodoView addToast={addToast} />);
fireEvent.click(screen.getByTestId("rename-list-button-list-1"));
const input = screen.getByTestId("rename-list-input-list-1");
fireEvent.change(input, { target: { value: " Renamed List " } });
fireEvent.keyDown(input, { key: "Enter" });
expect(state.renameList).toHaveBeenCalledWith("list-1", "Renamed List");
});
it("trims surrounding whitespace before saving an item edit", () => {
const state = createMockTodoLists();
mockUseTodoLists.mockReturnValue(state);
render(<TodoView addToast={addToast} />);
fireEvent.click(screen.getByText("Buy groceries"));
const input = screen.getByTestId("edit-item-input-item-1");
fireEvent.change(input, { target: { value: " Buy vegetables " } });
fireEvent.keyDown(input, { key: "Enter" });
expect(state.updateItem).toHaveBeenCalledWith("item-1", { text: "Buy vegetables" });
});
it("clicking another list cancels an in-progress list rename", () => {
mockUseTodoLists.mockReturnValue(createMockTodoLists());
render(<TodoView addToast={addToast} />);
fireEvent.click(screen.getByTestId("rename-list-button-list-1"));
expect(screen.getByTestId("rename-list-input-list-1")).toBeInTheDocument();
fireEvent.click(screen.getByTestId("todo-list-list-2"));
expect(screen.queryByTestId("rename-list-input-list-1")).not.toBeInTheDocument();
});
it("clears inline item edit when selected list changes", () => {
const initialState = createMockTodoLists({ selectedListId: "list-1" });
mockUseTodoLists.mockReturnValue(initialState);
const { rerender } = render(<TodoView addToast={addToast} />);
fireEvent.click(screen.getByText("Buy groceries"));
expect(screen.getByTestId("edit-item-input-item-1")).toBeInTheDocument();
mockUseTodoLists.mockReturnValue(createMockTodoLists({ selectedListId: "list-2" }));
rerender(<TodoView addToast={addToast} />);
expect(screen.queryByTestId("edit-item-input-item-1")).not.toBeInTheDocument();
});
it("shows new list input and hides empty-state prompt when create list is clicked", () => {
mockUseTodoLists.mockReturnValue(createMockTodoLists({ lists: [], items: [], selectedListId: null }));
render(<TodoView addToast={addToast} />);
fireEvent.click(screen.getByRole("button", { name: "Create List" }));
expect(screen.getByTestId("new-list-input")).toBeInTheDocument();
expect(screen.queryByText("No todo lists yet. Create one to get started.")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Create List" })).not.toBeInTheDocument();
});
it("marks the selected list with aria-current", () => {
render(<TodoView addToast={addToast} />);
expect(screen.getByTestId("todo-list-list-1")).toHaveAttribute("aria-current", "true");
expect(screen.getByTestId("todo-list-list-2")).not.toHaveAttribute("aria-current", "true");
});
it("applies active class to selected list", () => {