feat(FN-4611): add themed quick chat session dropdown
- Replace the quick chat header mode toggle with a session picker dropdown styled from dashboard theme tokens - Surface chat room entries alongside sessions and keep active room/session switching in the same menu - Add QuickChatFAB and CSS regression coverage for the new dropdown behavior and token-based styling - Add a changeset note for the quick chat session picker update Fusion-Task-Id: FN-4611 Fusion-Task-Id: FN-4634 Fusion-Task-Lineage: 87cc75e7-ba50-4cb5-bb9f-6d86f49268e0
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { loadAllAppCss } from "../test/cssFixture";
|
||||
|
||||
describe("QuickChat session dropdown CSS", () => {
|
||||
it("defines themed dropdown selectors using tokens", async () => {
|
||||
const css = await loadAllAppCss();
|
||||
|
||||
const triggerBlock = css.match(/\.quick-chat-session-trigger\s*\{[^}]*\}/)?.[0] ?? "";
|
||||
const dropdownBlock = css.match(/\.quick-chat-session-dropdown\s*\{[^}]*\}/)?.[0] ?? "";
|
||||
|
||||
expect(triggerBlock).toContain(".quick-chat-session-trigger");
|
||||
expect(dropdownBlock).toContain(".quick-chat-session-dropdown");
|
||||
|
||||
const combined = `${triggerBlock}\n${dropdownBlock}`;
|
||||
expect(combined).not.toMatch(/#[0-9a-fA-F]{3,8}\b/);
|
||||
expect(combined).not.toMatch(/rgba?\(/);
|
||||
expect(combined).not.toMatch(/(?<![\w-])(?:[1-9]\d*|\d+\.\d+)px\b/);
|
||||
});
|
||||
});
|
||||
@@ -381,6 +381,91 @@
|
||||
padding: calc(var(--space-sm) - (var(--space-xs) / 4)) calc(var(--space-md) - (var(--space-xs) / 4));
|
||||
}
|
||||
|
||||
.quick-chat-session-menu {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.quick-chat-session-trigger {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
gap: var(--space-sm);
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
border-color: var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-sm);
|
||||
transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.quick-chat-session-trigger > span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.quick-chat-session-trigger > svg:last-child {
|
||||
margin-left: auto;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.quick-chat-session-trigger:hover {
|
||||
background: var(--card-hover);
|
||||
}
|
||||
|
||||
.quick-chat-session-trigger:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring-strong);
|
||||
}
|
||||
|
||||
.quick-chat-session-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + var(--space-xs));
|
||||
left: 0;
|
||||
width: 100%;
|
||||
max-height: calc(var(--space-xl) * 10);
|
||||
overflow-y: auto;
|
||||
background: var(--surface);
|
||||
border: var(--btn-border-width) solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
padding: var(--space-xs);
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.quick-chat-session-dropdown-group-label {
|
||||
margin: var(--space-xs) var(--space-sm);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--space-sm);
|
||||
font-weight: 600;
|
||||
letter-spacing: calc(var(--space-xs) / 8);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.quick-chat-session-option {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: var(--space-sm);
|
||||
text-align: left;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.quick-chat-session-option:hover {
|
||||
background: var(--card-hover);
|
||||
}
|
||||
|
||||
.quick-chat-session-option:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring-strong);
|
||||
}
|
||||
|
||||
.quick-chat-session-option--active {
|
||||
background: color-mix(in srgb, var(--todo) 12%, transparent);
|
||||
}
|
||||
|
||||
.quick-chat-panel-messages {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -791,6 +876,13 @@
|
||||
z-index: 1100;
|
||||
}
|
||||
|
||||
.quick-chat-session-dropdown {
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: auto;
|
||||
max-height: calc(100vh - var(--header-height) - var(--mobile-nav-height));
|
||||
}
|
||||
|
||||
.quick-chat-resize-handle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import { FileMentionPopup } from "./FileMentionPopup";
|
||||
import { useFileMention } from "../hooks/useFileMention";
|
||||
import { useMobileKeyboard } from "../hooks/useMobileKeyboard";
|
||||
import { useViewportMode } from "../hooks/useViewportMode";
|
||||
import { useAppSettings } from "../hooks/useAppSettings";
|
||||
import { useChatRooms } from "../hooks/useChatRooms";
|
||||
|
||||
interface PendingAttachment {
|
||||
file: File;
|
||||
@@ -893,6 +895,7 @@ export function QuickChatFAB({
|
||||
const [chatMode, setChatMode] = useState<"agent" | "model">("agent");
|
||||
const [selectedAgentId, setSelectedAgentId] = useState<string>("");
|
||||
const [newSessionChooserOpen, setNewSessionChooserOpen] = useState(false);
|
||||
const [sessionMenuOpen, setSessionMenuOpen] = useState(false);
|
||||
const [newSessionMode, setNewSessionMode] = useState<"agent" | "model">("model");
|
||||
const [newSessionAgentId, setNewSessionAgentId] = useState<string>("");
|
||||
const [newSessionModel, setNewSessionModel] = useState<string>("");
|
||||
@@ -985,11 +988,15 @@ export function QuickChatFAB({
|
||||
refreshSessions,
|
||||
skipNextSessionInitRef,
|
||||
} = useQuickChat(projectId, addToast);
|
||||
const { experimentalFeatures } = useAppSettings();
|
||||
const chatRoomsEnabled = experimentalFeatures?.chatRooms === true;
|
||||
const roomsState = useChatRooms(projectId, addToast);
|
||||
|
||||
const panelRef = useRef<HTMLDivElement | null>(null);
|
||||
const fabRef = useRef<HTMLButtonElement | null>(null);
|
||||
const messagesRef = useRef<HTMLDivElement | null>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const sessionMenuRef = useRef<HTMLDivElement | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const pendingAttachmentsRef = useRef<PendingAttachment[]>([]);
|
||||
const shouldAutoFocusComposerRef = useRef(false);
|
||||
@@ -1648,6 +1655,60 @@ export function QuickChatFAB({
|
||||
});
|
||||
}, [agents, models, sessions]);
|
||||
|
||||
const roomOptions = useMemo(
|
||||
() => (chatRoomsEnabled ? roomsState.rooms : []),
|
||||
[chatRoomsEnabled, roomsState.rooms],
|
||||
);
|
||||
|
||||
const showRoomGroups = chatRoomsEnabled && roomOptions.length > 0;
|
||||
|
||||
const activeSessionLabel = useMemo(() => {
|
||||
if (showRoomGroups && roomsState.activeRoom) {
|
||||
return `#${roomsState.activeRoom.name}`;
|
||||
}
|
||||
const activeOption = sessionOptions.find((option) => option.id === activeSession?.id);
|
||||
if (activeOption) {
|
||||
return activeOption.label;
|
||||
}
|
||||
if (sessionsLoading) {
|
||||
return "Loading sessions…";
|
||||
}
|
||||
return "Select a session";
|
||||
}, [activeSession?.id, roomsState.activeRoom, sessionOptions, sessionsLoading, showRoomGroups]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSessionMenuOpen(false);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionMenuOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleSessionMenuOutsideClick = (event: MouseEvent) => {
|
||||
const target = event.target as Node;
|
||||
if (sessionMenuRef.current?.contains(target)) {
|
||||
return;
|
||||
}
|
||||
setSessionMenuOpen(false);
|
||||
};
|
||||
|
||||
const handleSessionMenuEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
setSessionMenuOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleSessionMenuOutsideClick);
|
||||
document.addEventListener("keydown", handleSessionMenuEscape);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleSessionMenuOutsideClick);
|
||||
document.removeEventListener("keydown", handleSessionMenuEscape);
|
||||
};
|
||||
}, [sessionMenuOpen]);
|
||||
|
||||
const inputPlaceholder = useMemo(() => {
|
||||
if (chatMode === "agent") {
|
||||
if (selectedAgent) {
|
||||
@@ -1679,8 +1740,14 @@ export function QuickChatFAB({
|
||||
}
|
||||
|
||||
void selectSession(selectedSession);
|
||||
setSessionMenuOpen(false);
|
||||
}, [selectSession, sessions]);
|
||||
|
||||
const handleRoomSwitch = useCallback((roomId: string) => {
|
||||
roomsState.selectRoom(roomId);
|
||||
setSessionMenuOpen(false);
|
||||
}, [roomsState]);
|
||||
|
||||
const handleCreateFreshSession = useCallback(async () => {
|
||||
if (sessionsLoading) return;
|
||||
|
||||
@@ -2405,18 +2472,65 @@ export function QuickChatFAB({
|
||||
</div>
|
||||
|
||||
<div className="quick-chat-panel-agent-select" data-testid="quick-chat-session-select">
|
||||
<label htmlFor="quick-chat-session-select" className="visually-hidden">Select session</label>
|
||||
<select
|
||||
id="quick-chat-session-select"
|
||||
value={activeSession?.id ?? ""}
|
||||
onChange={(event) => handleSessionSwitch(event.target.value)}
|
||||
data-testid="quick-chat-session-dropdown"
|
||||
>
|
||||
<option value="" disabled>{sessionsLoading ? "Loading sessions…" : "Select a session"}</option>
|
||||
{sessionOptions.map((sessionOption) => (
|
||||
<option key={sessionOption.id} value={sessionOption.id}>{sessionOption.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="quick-chat-session-menu" ref={sessionMenuRef}>
|
||||
<label htmlFor="quick-chat-session-dropdown-trigger" className="visually-hidden">Select session</label>
|
||||
<input type="hidden" data-testid="quick-chat-session-dropdown" value={activeSession?.id ?? ""} readOnly />
|
||||
<button
|
||||
id="quick-chat-session-dropdown-trigger"
|
||||
type="button"
|
||||
className="btn quick-chat-session-trigger"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={sessionMenuOpen}
|
||||
data-testid="quick-chat-session-dropdown-trigger"
|
||||
onClick={() => setSessionMenuOpen((current) => !current)}
|
||||
>
|
||||
{activeSession?.modelProvider ? (
|
||||
<ProviderIcon provider={activeSession.modelProvider} size="sm" />
|
||||
) : (
|
||||
<MessageSquare size={16} aria-hidden="true" />
|
||||
)}
|
||||
<span>{activeSessionLabel}</span>
|
||||
<ChevronDown size={16} aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
{sessionMenuOpen && (
|
||||
<div className="quick-chat-session-dropdown" role="menu" data-testid="quick-chat-session-dropdown-menu">
|
||||
{showRoomGroups && (
|
||||
<div className="quick-chat-session-dropdown-group-label">Sessions</div>
|
||||
)}
|
||||
{sessionOptions.map((sessionOption) => (
|
||||
<button
|
||||
key={sessionOption.id}
|
||||
type="button"
|
||||
role="menuitem"
|
||||
data-testid={`quick-chat-session-option-${sessionOption.id}`}
|
||||
className={`quick-chat-session-option${roomsState.activeRoom === null && activeSession?.id === sessionOption.id ? " quick-chat-session-option--active" : ""}`}
|
||||
onClick={() => handleSessionSwitch(sessionOption.id)}
|
||||
>
|
||||
{sessionOption.label}
|
||||
</button>
|
||||
))}
|
||||
|
||||
{showRoomGroups && (
|
||||
<>
|
||||
<div className="quick-chat-session-dropdown-group-label">Rooms</div>
|
||||
{roomOptions.map((room) => (
|
||||
<button
|
||||
key={room.id}
|
||||
type="button"
|
||||
role="menuitem"
|
||||
data-testid={`quick-chat-session-option-room-${room.slug}`}
|
||||
className={`quick-chat-session-option${roomsState.activeRoom?.id === room.id ? " quick-chat-session-option--active" : ""}`}
|
||||
onClick={() => handleRoomSwitch(room.id)}
|
||||
>
|
||||
#{room.name}
|
||||
</button>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{newSessionChooserOpen && (
|
||||
|
||||
@@ -6,6 +6,8 @@ import * as apiModule from "../../api";
|
||||
import { useAgents } from "../../hooks/useAgents";
|
||||
import { useViewportMode } from "../../hooks/useViewportMode";
|
||||
import { useMobileKeyboard } from "../../hooks/useMobileKeyboard";
|
||||
import { useAppSettings } from "../../hooks/useAppSettings";
|
||||
import { useChatRooms } from "../../hooks/useChatRooms";
|
||||
import { QuickChatFAB } from "../QuickChatFAB";
|
||||
|
||||
vi.mock("../../api", () => ({
|
||||
@@ -24,6 +26,8 @@ vi.mock("../../api", () => ({
|
||||
vi.mock("../../hooks/useAgents", () => ({ useAgents: vi.fn() }));
|
||||
vi.mock("../../hooks/useViewportMode", () => ({ useViewportMode: vi.fn() }));
|
||||
vi.mock("../../hooks/useMobileKeyboard", () => ({ useMobileKeyboard: vi.fn() }));
|
||||
vi.mock("../../hooks/useAppSettings", () => ({ useAppSettings: vi.fn() }));
|
||||
vi.mock("../../hooks/useChatRooms", () => ({ useChatRooms: vi.fn() }));
|
||||
|
||||
const mockFetchResumeChatSession = vi.mocked(apiModule.fetchResumeChatSession);
|
||||
const mockFetchChatSessions = vi.mocked(apiModule.fetchChatSessions);
|
||||
@@ -36,6 +40,8 @@ const mockCancelChatResponse = vi.mocked(apiModule.cancelChatResponse);
|
||||
const mockUseAgents = vi.mocked(useAgents);
|
||||
const mockUseViewportMode = vi.mocked(useViewportMode);
|
||||
const mockUseMobileKeyboard = vi.mocked(useMobileKeyboard);
|
||||
const mockUseAppSettings = vi.mocked(useAppSettings);
|
||||
const mockUseChatRooms = vi.mocked(useChatRooms);
|
||||
|
||||
const agents: Agent[] = [
|
||||
{ id: "agent-001", name: "Agent One", role: "executor", state: "active", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {} },
|
||||
@@ -117,6 +123,23 @@ describe("QuickChatFAB session-first UX", () => {
|
||||
viewportOffsetTop: 0,
|
||||
keyboardOpen: false,
|
||||
});
|
||||
mockUseAppSettings.mockReturnValue({
|
||||
experimentalFeatures: {},
|
||||
} as ReturnType<typeof useAppSettings>);
|
||||
mockUseChatRooms.mockReturnValue({
|
||||
rooms: [],
|
||||
roomsLoading: false,
|
||||
roomsError: null,
|
||||
activeRoom: null,
|
||||
activeRoomMembers: [],
|
||||
messages: [],
|
||||
messagesLoading: false,
|
||||
selectRoom: vi.fn(),
|
||||
createRoom: vi.fn(),
|
||||
deleteRoom: vi.fn(),
|
||||
sendRoomMessage: vi.fn(),
|
||||
refreshRooms: vi.fn(),
|
||||
});
|
||||
mockFetchResumeChatSession.mockImplementation(async ({ agentId, modelProvider, modelId }) => ({
|
||||
session: resolveResumeSession(agentId, modelProvider, modelId),
|
||||
}));
|
||||
@@ -150,8 +173,64 @@ describe("QuickChatFAB session-first UX", () => {
|
||||
|
||||
expect(await screen.findByTestId("quick-chat-session-dropdown")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("quick-chat-mode-toggle")).toBeNull();
|
||||
expect(screen.getByRole("option", { name: "Model thread — GPT-4o [openai/gpt-4o]" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("option", { name: "Session 2 — Agent One" })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
|
||||
expect(screen.getByTestId("quick-chat-session-option-session-model")).toHaveClass("quick-chat-session-option--active");
|
||||
expect(screen.getByTestId("quick-chat-session-option-session-agent")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render room options when chat rooms are disabled", async () => {
|
||||
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
|
||||
fireEvent.click(screen.getByTestId("quick-chat-fab"));
|
||||
fireEvent.click(await screen.findByTestId("quick-chat-session-dropdown-trigger"));
|
||||
|
||||
expect(screen.queryByTestId("quick-chat-session-option-room-engineering")).toBeNull();
|
||||
});
|
||||
|
||||
it("opens/closes session menu and handles rooms when enabled", async () => {
|
||||
const selectRoom = vi.fn();
|
||||
mockUseAppSettings.mockReturnValue({ experimentalFeatures: { chatRooms: true } } as ReturnType<typeof useAppSettings>);
|
||||
mockUseChatRooms.mockReturnValue({
|
||||
rooms: [{ id: "room-1", name: "engineering", slug: "engineering", memberCount: 2, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }],
|
||||
roomsLoading: false,
|
||||
roomsError: null,
|
||||
activeRoom: { id: "room-1", name: "engineering", slug: "engineering", memberCount: 2, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() },
|
||||
activeRoomMembers: [],
|
||||
messages: [],
|
||||
messagesLoading: false,
|
||||
selectRoom,
|
||||
createRoom: vi.fn(),
|
||||
deleteRoom: vi.fn(),
|
||||
sendRoomMessage: vi.fn(),
|
||||
refreshRooms: vi.fn(),
|
||||
});
|
||||
|
||||
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
|
||||
fireEvent.click(screen.getByTestId("quick-chat-fab"));
|
||||
|
||||
const trigger = await screen.findByTestId("quick-chat-session-dropdown-trigger");
|
||||
expect(trigger).toHaveTextContent("#engineering");
|
||||
fireEvent.click(trigger);
|
||||
|
||||
expect(screen.getByTestId("quick-chat-session-dropdown-menu")).toBeInTheDocument();
|
||||
expect(screen.getByText("Sessions")).toBeInTheDocument();
|
||||
expect(screen.getByText("Rooms")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("quick-chat-session-option-session-model")).toHaveClass("quick-chat-session-option");
|
||||
expect(screen.getByTestId("quick-chat-session-option-room-engineering")).toHaveClass("quick-chat-session-option--active");
|
||||
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-option-room-engineering"));
|
||||
expect(selectRoom).toHaveBeenCalledWith("room-1");
|
||||
|
||||
fireEvent.click(trigger);
|
||||
fireEvent.mouseDown(screen.getByTestId("quick-chat-new-thread"));
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("quick-chat-session-dropdown-menu")).toBeNull();
|
||||
});
|
||||
|
||||
fireEvent.click(trigger);
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-option-session-model"));
|
||||
await waitFor(() => {
|
||||
expect(mockFetchChatMessages).toHaveBeenCalledWith("session-model", { limit: 50 }, "proj-1");
|
||||
});
|
||||
});
|
||||
|
||||
it("opens inline chooser from new button defaulting to model", async () => {
|
||||
@@ -218,7 +297,8 @@ describe("QuickChatFAB session-first UX", () => {
|
||||
expect(screen.getByTestId("quick-chat-input")).toHaveAttribute("placeholder", "Message Agent Two");
|
||||
expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("active-latest");
|
||||
});
|
||||
expect(screen.getByRole("option", { name: /Claude thread/i })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
|
||||
expect(screen.getByTestId("quick-chat-session-option-archived-newest")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("reopen restores the newest active session by max(lastMessageAt, updatedAt)", async () => {
|
||||
@@ -247,7 +327,8 @@ describe("QuickChatFAB session-first UX", () => {
|
||||
expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("newer-last-message");
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByTestId("quick-chat-session-dropdown"), { target: { value: "older-updated" } });
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-option-older-updated"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("older-updated");
|
||||
});
|
||||
@@ -293,7 +374,8 @@ describe("QuickChatFAB session-first UX", () => {
|
||||
expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("active-latest");
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByTestId("quick-chat-session-dropdown"), { target: { value: "active-older" } });
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-option-active-older"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("quick-chat-session-dropdown")).toHaveValue("active-older");
|
||||
});
|
||||
@@ -372,15 +454,17 @@ describe("QuickChatFAB session-first UX", () => {
|
||||
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
|
||||
fireEvent.click(screen.getByTestId("quick-chat-fab"));
|
||||
|
||||
expect(await screen.findByRole("option", { name: "Session 1 — GPT-4o [openai/gpt-4o]" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("option", { name: "Session 2 — Claude 3.7 Sonnet [anthropic/claude-3-7-sonnet]" })).toBeInTheDocument();
|
||||
fireEvent.click(await screen.findByTestId("quick-chat-session-dropdown-trigger"));
|
||||
expect(screen.getByTestId("quick-chat-session-option-session-openai")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("quick-chat-session-option-session-anthropic")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("includes both title and model descriptor in session label", async () => {
|
||||
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
|
||||
fireEvent.click(screen.getByTestId("quick-chat-fab"));
|
||||
|
||||
expect(await screen.findByRole("option", { name: "Model thread — GPT-4o [openai/gpt-4o]" })).toBeInTheDocument();
|
||||
fireEvent.click(await screen.findByTestId("quick-chat-session-dropdown-trigger"));
|
||||
expect(screen.getByTestId("quick-chat-session-option-session-model")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("uses icon-only model tag without pill styling when mobile header fallback is active", async () => {
|
||||
@@ -577,9 +661,9 @@ describe("QuickChatFAB session-first UX", () => {
|
||||
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
|
||||
fireEvent.click(screen.getByTestId("quick-chat-fab"));
|
||||
|
||||
const select = await screen.findByTestId("quick-chat-session-dropdown");
|
||||
await screen.findByRole("option", { name: "Session 2 — Agent One" });
|
||||
fireEvent.change(select, { target: { value: "session-agent" } });
|
||||
await screen.findByTestId("quick-chat-session-dropdown");
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-option-session-agent"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchChatMessages).toHaveBeenCalledWith("session-agent", { limit: 50 }, "proj-1");
|
||||
@@ -970,7 +1054,9 @@ describe("QuickChatFAB session-first UX", () => {
|
||||
},
|
||||
});
|
||||
|
||||
fireEvent.change(await screen.findByTestId("quick-chat-session-dropdown"), { target: { value: "session-agent" } });
|
||||
await screen.findByTestId("quick-chat-session-dropdown");
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-dropdown-trigger"));
|
||||
fireEvent.click(screen.getByTestId("quick-chat-session-option-session-agent"));
|
||||
scrollHeightValue = 1700;
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
Reference in New Issue
Block a user