((resolve) => { respond = resolve; }));
+ const clearError = vi.fn();
+ const reload = vi.fn();
+ const openConversation = vi.fn();
+ const chat: OrchestratorChat = {
+ orchestratorId: null,
+ messages: [],
+ loading: false,
+ sending: false,
+ error: null,
+ disabled: false,
+ send,
+ clearError,
+ reload,
+ };
+ const { rerender } = render(
+ ,
+ );
+ const input = screen.getByRole("textbox", { name: "Aufgabe an den Orchestrator" });
+ const submit = screen.getByRole("button", { name: "Senden" });
+ expect(input).toHaveAttribute("name", "command-chat-input");
+ expect(input).toHaveAttribute("rows", "2");
+ expect(submit).toBeDisabled();
+ expect(send).not.toHaveBeenCalled();
+ fireEvent.change(input, { target: { value: " \n " } });
+ expect(submit).toBeDisabled();
+ fireEvent.click(submit);
+ expect(send).not.toHaveBeenCalled();
+
+ const draft = " Prüfe die Aufgabe\nmit Kontext ";
+ fireEvent.change(input, { target: { value: draft } });
+ fireEvent.keyDown(input, { key: "Enter", shiftKey: true });
+ expect(send).not.toHaveBeenCalled();
+ expect(input).toHaveValue(draft);
+ fireEvent.keyDown(input, { key: "Enter" });
+ expect(send).toHaveBeenCalledExactlyOnceWith(draft);
+ rerender(
+ ,
+ );
+ expect.soft(screen.queryByRole("button", { name: "Senden" })).toBe(submit);
+ expect(submit).toBeDisabled();
+ expect(input).toBeDisabled();
+ expect(input).toHaveValue(draft);
+ fireEvent.click(submit);
+ expect(send).toHaveBeenCalledTimes(1);
+ rerender(
+ ,
+ );
+ await act(async () => { respond(false); });
+ expect.soft(screen.queryByRole("alert")).toHaveTextContent("Aufgabe wurde nicht angenommen.");
+ expect(input).toHaveValue(draft);
+ expect(input).toHaveFocus();
+ const history = screen.getByRole("button", { name: /Verlauf/ });
+ expect(history).toHaveAttribute("aria-expanded", "false");
+ fireEvent.click(history);
+ expect(history).toHaveAttribute("aria-expanded", "true");
+ expect(screen.getByText("Noch keine Nachrichten. Schick dem Orchestrator eine Aufgabe.")).toBeVisible();
+ expect.soft(screen.queryByRole("alert")).toHaveTextContent("Aufgabe wurde nicht angenommen.");
+ expect(clearError).not.toHaveBeenCalled();
+ expect(reload).not.toHaveBeenCalled();
+ fireEvent.click(screen.getByRole("button", { name: "Im Dialog öffnen" }));
+ expect(openConversation).toHaveBeenCalledTimes(1);
+
+ fireEvent.click(submit);
+ expect(send).toHaveBeenNthCalledWith(2, draft);
+ await act(async () => { respond(true); });
+ expect(input).toHaveValue("");
+ expect(input).toHaveFocus();
+ rerender();
+ fireEvent.change(input, { target: { value: "Gleicher Entwurf" } });
+ fireEvent.click(submit);
+ expect(send).toHaveBeenNthCalledWith(3, "Gleicher Entwurf");
+ rerender();
+ expect(input).toHaveValue("");
+ expect(send).toHaveBeenCalledTimes(3);
+ fireEvent.change(input, { target: { value: "Gleicher Entwurf" } });
+ await act(async () => { respond(true); });
+ expect(input).toHaveValue("Gleicher Entwurf");
+
+ rerender(
+ ,
+ );
+ expect(input).toHaveValue("");
+ expect(input).toBeDisabled();
+ expect(submit).toBeDisabled();
+ expect(history).toBeDisabled();
+ fireEvent.click(submit);
+ expect(send).toHaveBeenCalledTimes(3);
+});
diff --git a/src/components/CommandChat.tsx b/src/components/CommandChat.tsx
index 634649cd..02509eea 100644
--- a/src/components/CommandChat.tsx
+++ b/src/components/CommandChat.tsx
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState, type KeyboardEvent } from "react";
import { formatMessageTime, roleLabel, type OrchestratorChat } from "../lib/orchestratorChat";
+import { commandChatTexts as texts } from "./commandchat/texts";
interface CommandChatProps {
chat: OrchestratorChat;
@@ -21,7 +22,7 @@ interface CommandChatProps {
function EmptyChat() {
return (
-
Noch keine Nachrichten. Schick dem Orchestrator eine Aufgabe.
+
{texts.empty}
);
}
@@ -124,9 +125,11 @@ export default function CommandChat({ chat, projectId, onOpenConversation }: Com
)}
) : null}
- {chat.error ? {chat.error}
: null}
+ {chat.error ? (
+ {chat.error}
+ ) : null}
{disabled ? (
- Kein Projekt aktiv — wähle links ein Projekt.
+ {texts.noProject}
) : null}
diff --git a/src/components/commandchat/texts.ts b/src/components/commandchat/texts.ts
new file mode 100644
index 00000000..a5358655
--- /dev/null
+++ b/src/components/commandchat/texts.ts
@@ -0,0 +1,11 @@
+export const commandChatTexts = {
+ empty: "Noch keine Nachrichten. Schick dem Orchestrator eine Aufgabe.",
+ noProject: "Kein Projekt aktiv — wähle links ein Projekt.",
+ history: "Verlauf",
+ historyTitle: "Verlauf des Orchestrators",
+ input: "Aufgabe an den Orchestrator",
+ placeholder: "Aufgabe an den Orchestrator … (Enter sendet, Shift+Enter neue Zeile)",
+ openConversation: "Im Dialog öffnen",
+ send: "Senden",
+ sending: "Sendet …",
+} as const;