Address non-blocking review items on the AI-chat stream-perf PR: - Drop the unused `metadata` param from the `msg` test factory in message-item.test.ts; no caller passed it. - Add a per-part-kind coupling guard to message-signature.test.ts that, for each part kind rendered today (text, reasoning, tool-*) plus the metadata banners, asserts that mutating a field the MessageItem render body DRAWS flips messageSignature — an executable lock for the load-bearing memo invariant documented in message-signature.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.5 KiB
TypeScript
74 lines
2.5 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import type { UIMessage } from "@ai-sdk/react";
|
|
|
|
// Stub react-i18next: importing the component module pulls in `useTranslation`,
|
|
// and we only exercise the pure `arePropsEqual` comparator (no rendering), so a
|
|
// minimal `t` that echoes the key is enough. Mirrors the stub in
|
|
// reasoning-block.test.tsx.
|
|
vi.mock("react-i18next", () => ({
|
|
useTranslation: () => ({ t: (key: string) => key }),
|
|
}));
|
|
|
|
import { arePropsEqual } from "./message-item";
|
|
|
|
/**
|
|
* Tests for `arePropsEqual`, the `React.memo` comparator for MessageItem. It must
|
|
* return false on any visible prop/content change (so the row re-renders) and
|
|
* true when nothing visible changed (so a finalized row is skipped). A FIXED
|
|
* message id is used so a content-identical clone yields an equal signature.
|
|
*/
|
|
const msg = (parts: UIMessage["parts"]): UIMessage =>
|
|
({ id: "m1", role: "assistant", parts }) as UIMessage;
|
|
|
|
const props = (
|
|
message: UIMessage,
|
|
over: Record<string, unknown> = {},
|
|
) => ({
|
|
message,
|
|
showCitations: true,
|
|
neutralizeInternalLinks: false,
|
|
assistantName: "AI",
|
|
...over,
|
|
});
|
|
|
|
describe("arePropsEqual", () => {
|
|
it("returns false when showCitations differs", () => {
|
|
const m = msg([{ type: "text", text: "answer" }]);
|
|
expect(
|
|
arePropsEqual(props(m), props(m, { showCitations: false })),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("returns false when neutralizeInternalLinks differs", () => {
|
|
const m = msg([{ type: "text", text: "answer" }]);
|
|
expect(
|
|
arePropsEqual(props(m), props(m, { neutralizeInternalLinks: true })),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("returns false when assistantName differs", () => {
|
|
const m = msg([{ type: "text", text: "answer" }]);
|
|
expect(
|
|
arePropsEqual(props(m), props(m, { assistantName: "Other" })),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("returns true on the identity fast path (same message object, equal props)", () => {
|
|
const m = msg([{ type: "text", text: "answer" }]);
|
|
expect(arePropsEqual(props(m), props(m))).toBe(true);
|
|
});
|
|
|
|
it("returns true for the same content in a different message object", () => {
|
|
const a = msg([{ type: "text", text: "answer" }]);
|
|
const b = msg([{ type: "text", text: "answer" }]);
|
|
expect(a).not.toBe(b);
|
|
expect(arePropsEqual(props(a), props(b))).toBe(true);
|
|
});
|
|
|
|
it("returns false when content changed in a different message object", () => {
|
|
const a = msg([{ type: "text", text: "answer" }]);
|
|
const b = msg([{ type: "text", text: "answer grown" }]);
|
|
expect(arePropsEqual(props(a), props(b))).toBe(false);
|
|
});
|
|
});
|