import { render } from "@testing-library/react"; import React from "react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; /** * Regression test: CopilotMessages children must have React keys. * * When CopilotMessages receives multiple children (e.g. memoizedChildren + * RegisteredActionsRenderer), React treats them as a dynamic list and warns * if they lack keys. This test verifies the fix by rendering a minimal * reproduction using the same pattern as CopilotKitInternal. */ // Minimal stand-in for CopilotMessages – renders children inside a provider-like wrapper. function CopilotMessages({ children }: { children: React.ReactNode }) { const memoized = React.useMemo(() => children, [children]); return
{memoized}
; } describe("CopilotMessages children keys", () => { let consoleErrorSpy: ReturnType; const keyWarnings: string[] = []; beforeEach(() => { keyWarnings.length = 0; consoleErrorSpy = vi .spyOn(console, "error") .mockImplementation((...args: any[]) => { const msg = args.map(String).join(" "); if ( msg.includes('unique "key" prop') || msg.includes("unique 'key' prop") || msg.includes("unique key") ) { keyWarnings.push(msg); } }); }); afterEach(() => { consoleErrorSpy.mockRestore(); }); it("warns about missing keys when children array lacks keys (pre-fix pattern)", () => { const ChildA = () =>
app content
; const ChildB = () => actions; // Passing an explicit array as children – this is what JSX compiles to // when you write: {memoizedChildren} // React sees: { children: [memoizedChildren, ] } render({[, ]}); expect(keyWarnings.length).toBeGreaterThan(0); }); it("does NOT warn when children array elements have keys (post-fix pattern)", () => { const ChildA = () =>
app content
; const ChildB = () => actions; // The fix: wrap in keyed elements render( {[ , , ]} , ); expect(keyWarnings).toHaveLength(0); }); it("does NOT warn with the actual fix pattern (keyed JSX children)", () => { const MemoChildren = React.memo(() =>
app content
); MemoChildren.displayName = "MemoChildren"; const RegisteredActionsRenderer = React.memo(() => null); RegisteredActionsRenderer.displayName = "RegisteredActionsRenderer"; render( , ); expect(keyWarnings).toHaveLength(0); }); });