test(ai-chat): cover the conditional assistant-name signature (#108)
Extract the shared assistant-name predicate (resolveAssistantName: trimmed name or null) used by typing-indicator + message-item, and unit-test the branches (name shown; whitespace-only -> 'AI agent' fallback; undefined -> fallback). Behavior-identical (|| -> ?? since the helper returns null). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import type { UIMessage } from "@ai-sdk/react";
|
||||
import ToolCallCard from "@/features/ai-chat/components/tool-call-card.tsx";
|
||||
import { ToolUiPart, isToolPart } from "@/features/ai-chat/utils/tool-parts.tsx";
|
||||
import { renderChatMarkdown } from "@/features/ai-chat/utils/markdown.ts";
|
||||
import { resolveAssistantName } from "@/features/ai-chat/utils/assistant-name.ts";
|
||||
import { describeChatError } from "@/features/ai-chat/utils/error-message.ts";
|
||||
import classes from "@/features/ai-chat/components/ai-chat.module.css";
|
||||
|
||||
@@ -67,7 +68,7 @@ export default function MessageItem({
|
||||
return (
|
||||
<Box className={classes.messageRow}>
|
||||
<Text size="xs" c="dimmed" mb={4}>
|
||||
{assistantName?.trim() || t("AI agent")}
|
||||
{resolveAssistantName(assistantName) ?? t("AI agent")}
|
||||
</Text>
|
||||
{message.parts.map((part, index) => {
|
||||
if (part.type === "text") {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Box, Group, Text } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { resolveAssistantName } from "@/features/ai-chat/utils/assistant-name.ts";
|
||||
import classes from "@/features/ai-chat/components/ai-chat.module.css";
|
||||
|
||||
interface TypingIndicatorProps {
|
||||
@@ -23,12 +24,12 @@ interface TypingIndicatorProps {
|
||||
*/
|
||||
export default function TypingIndicator({ assistantName }: TypingIndicatorProps) {
|
||||
const { t } = useTranslation();
|
||||
const name = assistantName?.trim();
|
||||
const name = resolveAssistantName(assistantName);
|
||||
|
||||
return (
|
||||
<Box className={classes.messageRow}>
|
||||
<Text size="xs" c="dimmed" mb={4}>
|
||||
{name || t("AI agent")}
|
||||
{name ?? t("AI agent")}
|
||||
</Text>
|
||||
<Group gap={8} align="center">
|
||||
<span className={classes.typingDots} aria-hidden="true">
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { resolveAssistantName } from "./assistant-name";
|
||||
|
||||
describe("resolveAssistantName", () => {
|
||||
it("returns a real name unchanged", () => {
|
||||
expect(resolveAssistantName("Ada")).toBe("Ada");
|
||||
});
|
||||
|
||||
it("trims surrounding whitespace from a real name", () => {
|
||||
expect(resolveAssistantName(" Ada ")).toBe("Ada");
|
||||
});
|
||||
|
||||
it("returns null for a whitespace-only name (the reason for .trim())", () => {
|
||||
expect(resolveAssistantName(" ")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when the name is undefined", () => {
|
||||
expect(resolveAssistantName(undefined)).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for an empty string", () => {
|
||||
expect(resolveAssistantName("")).toBeNull();
|
||||
});
|
||||
});
|
||||
16
apps/client/src/features/ai-chat/utils/assistant-name.ts
Normal file
16
apps/client/src/features/ai-chat/utils/assistant-name.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// Pure helper for resolving the assistant's display name. Kept free of React so
|
||||
// it can be unit-tested in isolation (see assistant-name.test.ts) and shared by
|
||||
// the components that render the assistant identity (TypingIndicator, MessageItem).
|
||||
|
||||
/**
|
||||
* Resolve the assistant's display name from the optional configured identity.
|
||||
*
|
||||
* Returns the trimmed name when it has visible (non-whitespace) characters, or
|
||||
* `null` when the name is absent or whitespace-only. Callers fall back to a
|
||||
* generic "AI agent" label on `null`. The `.trim()` is why a name of " " must
|
||||
* resolve to `null` rather than rendering an empty label.
|
||||
*/
|
||||
export function resolveAssistantName(assistantName?: string): string | null {
|
||||
const name = assistantName?.trim();
|
||||
return name ? name : null;
|
||||
}
|
||||
Reference in New Issue
Block a user