feat(FN-2046): add accessible delete button to chat session items

- Add an inline delete button to each chat session item with an aria-label and Trash icon
- Prevent session selection when delete is clicked by stopping event propagation and opening the confirmation dialog
- Style the delete control to appear on hover/focus and remain visible on mobile for touch accessibility
- Add ChatView tests covering render behavior, confirmation flow, selection guard, delete action, and CSS visibility rules
This commit is contained in:
Fusion
2026-04-18 00:02:09 -07:00
committed by gsxdsm
parent b3cc6047c3
commit d58825cbd2
3 changed files with 158 additions and 0 deletions

View File

@@ -1169,6 +1169,113 @@ describe("formatModelTag helper function", () => {
});
});
describe("Chat Session Delete Button", () => {
it("renders delete button on each session item", () => {
setupMockChat({
sessions: [
{ id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat 1", updatedAt: "2026-04-08T00:00:00.000Z" },
{ id: "session-002", agentId: "agent-002", status: "active", title: "Test Chat 2", updatedAt: "2026-04-08T00:00:00.000Z" },
],
filteredSessions: [
{ id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat 1", updatedAt: "2026-04-08T00:00:00.000Z" },
{ id: "session-002", agentId: "agent-002", status: "active", title: "Test Chat 2", updatedAt: "2026-04-08T00:00:00.000Z" },
],
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const deleteButtons = screen.getAllByTestId("chat-session-delete-btn");
expect(deleteButtons.length).toBe(2);
});
it("clicking delete button shows confirmation dialog", async () => {
setupMockChat({
sessions: [{ id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", updatedAt: "2026-04-08T00:00:00.000Z" }],
filteredSessions: [{ id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", updatedAt: "2026-04-08T00:00:00.000Z" }],
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const deleteButton = screen.getByTestId("chat-session-delete-btn");
await userEvent.click(deleteButton);
// Dialog should be open
const dialog = document.querySelector(".chat-new-dialog");
expect(dialog).toBeInTheDocument();
expect(within(dialog!).getByText("Delete Conversation?")).toBeInTheDocument();
});
it("clicking delete button does not select the session", async () => {
const selectSession = vi.fn();
setupMockChat({
sessions: [{ id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", updatedAt: "2026-04-08T00:00:00.000Z" }],
filteredSessions: [{ id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", updatedAt: "2026-04-08T00:00:00.000Z" }],
selectSession,
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const deleteButton = screen.getByTestId("chat-session-delete-btn");
await userEvent.click(deleteButton);
expect(selectSession).not.toHaveBeenCalled();
});
it("confirming delete calls deleteSession", async () => {
const deleteSession = vi.fn();
setupMockChat({
sessions: [{ id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", updatedAt: "2026-04-08T00:00:00.000Z" }],
filteredSessions: [{ id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", updatedAt: "2026-04-08T00:00:00.000Z" }],
deleteSession,
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const deleteButton = screen.getByTestId("chat-session-delete-btn");
await userEvent.click(deleteButton);
// Click confirm in dialog
const dialog = document.querySelector(".chat-new-dialog");
await userEvent.click(within(dialog!).getByText("Delete"));
expect(deleteSession).toHaveBeenCalledWith("session-001");
});
});
describe("Chat Session Delete Button CSS", () => {
const css = fs.readFileSync(stylesPath, "utf-8");
it(".chat-session-delete-btn exists with opacity: 0", () => {
const match = css.match(/\.chat-session-delete-btn\s*\{([^}]*)\}/);
expect(match).toBeTruthy();
expect(match![1]).toContain("opacity: 0");
});
it(".chat-session-item:hover .chat-session-delete-btn has opacity: 1", () => {
const match = css.match(/\.chat-session-item:hover\s*\.chat-session-delete-btn\s*\{([^}]*)\}/);
expect(match).toBeTruthy();
expect(match![1]).toContain("opacity: 1");
});
it("mobile override makes delete button always visible", () => {
// Find all mobile media query blocks and check if any has chat-session-delete-btn with opacity: 1
const mobileRegex = /@media\s*\(max-width:\s*768px\)\s*\{([\s\S]*?)\n\}/g;
let match;
let foundMobileDeleteBtn = false;
while ((match = mobileRegex.exec(css)) !== null) {
const mediaContent = match[1];
if (mediaContent.includes(".chat-session-delete-btn")) {
const deleteBtnMatch = mediaContent.match(/\.chat-session-delete-btn\s*\{([^}]*)\}/);
if (deleteBtnMatch && deleteBtnMatch[1].includes("opacity: 1")) {
foundMobileDeleteBtn = true;
break;
}
}
}
expect(foundMobileDeleteBtn).toBe(true);
});
});
describe("ChatView CSS — nested flexbox scrolling fix", () => {
const css = fs.readFileSync(stylesPath, "utf-8");