11import { act , renderHook , waitFor } from "@testing-library/react" ;
2- import { afterEach , beforeEach , describe , expect , it , vi } from "vitest" ;
2+ import {
3+ afterAll ,
4+ afterEach ,
5+ beforeEach ,
6+ describe ,
7+ expect ,
8+ it ,
9+ vi ,
10+ } from "vitest" ;
311import type { Mock } from "vitest" ;
412import { useCopilotKit } from "../../context" ;
5- import { CopilotKitCoreRuntimeConnectionStatus } from "@copilotkit/core" ;
13+ import { ɵcreateMemoryStore } from "@copilotkit/core" ;
14+ import type { ɵMemoryStore } from "@copilotkit/core" ;
615import { useMemories } from "../use-memories" ;
716
817vi . mock ( "../../context" , ( ) => ( {
@@ -11,7 +20,6 @@ vi.mock("../../context", () => ({
1120
1221const mockUseCopilotKit = useCopilotKit as ReturnType < typeof vi . fn > ;
1322
14- const AGENT_ID = "agent-1" ;
1523const RUNTIME_URL = "https://runtime.example.com" ;
1624
1725type WireMemory = {
@@ -72,42 +80,74 @@ function makeFetchMock(
7280 } ) ;
7381}
7482
75- let registeredStore : unknown ;
76- let fetchMock : Mock ;
83+ // Stub the global fetch once for the entire module — RxJS's fromFetch always
84+ // calls globalThis.fetch, so injected fetch in ɵcreateMemoryStore is a
85+ // pass-through. vi.stubGlobal restores the original automatically on afterAll.
86+ const fetchMock = vi . fn ( ) ;
87+ vi . stubGlobal ( "fetch" , fetchMock ) ;
7788
78- function setupCopilotKit ( ) : void {
79- registeredStore = undefined ;
89+ let store : ɵMemoryStore ;
90+
91+ function setupCopilotKit ( mock : Mock ) : void {
92+ store = ɵcreateMemoryStore ( { fetch : mock } ) ;
93+ store . start ( ) ;
8094
8195 mockUseCopilotKit . mockReturnValue ( {
8296 copilotkit : {
83- runtimeUrl : RUNTIME_URL ,
84- headers : { } ,
85- intelligence : { wsUrl : "wss://gw.example.com/client" } ,
86- runtimeConnectionStatus : CopilotKitCoreRuntimeConnectionStatus . Connected ,
87- registerMemoryStore : vi . fn ( ( _agentId : string , store : unknown ) => {
88- registeredStore = store ;
89- } ) ,
90- unregisterMemoryStore : vi . fn ( ) ,
97+ getMemoryStore : ( ) => store ,
9198 } ,
9299 executingToolCallIds : new Set ( ) ,
93100 } ) ;
94101}
95102
103+ /**
104+ * Triggers the store's first context dispatch so the snapshot fetch fires.
105+ * Must be called inside `act(...)` after `renderHook` so the hook is already
106+ * subscribed when the async response arrives.
107+ */
108+ function activateStore ( ) : void {
109+ store . setContext ( {
110+ runtimeUrl : RUNTIME_URL ,
111+ wsUrl : "wss://gw.example.com/client" ,
112+ headers : { } ,
113+ } ) ;
114+ }
115+
96116describe ( "useMemories" , ( ) => {
97117 beforeEach ( ( ) => {
98- setupCopilotKit ( ) ;
118+ fetchMock . mockReset ( ) ;
119+ mockUseCopilotKit . mockReset ( ) ;
120+ // Default: empty snapshot with no mutations expected.
121+ fetchMock . mockImplementation ( makeFetchMock ( [ ] ) ) ;
122+ setupCopilotKit ( fetchMock ) ;
99123 } ) ;
100124
101125 afterEach ( ( ) => {
102- vi . unstubAllGlobals ( ) ;
103126 vi . clearAllMocks ( ) ;
104127 } ) ;
105128
129+ afterAll ( ( ) => {
130+ vi . unstubAllGlobals ( ) ;
131+ } ) ;
132+
133+ it ( "exposes empty memories and isAvailable true on initial render" , ( ) => {
134+ const { result } = renderHook ( ( ) => useMemories ( ) ) ;
135+
136+ expect ( result . current . memories ) . toEqual ( [ ] ) ;
137+ expect ( result . current . isAvailable ) . toBe ( true ) ;
138+ } ) ;
139+
106140 it ( "loads the snapshot on mount" , async ( ) => {
107- fetchMock = makeFetchMock ( [ wireMemory ( "m1" ) , wireMemory ( "m2" ) ] ) ;
108- vi . stubGlobal ( "fetch" , fetchMock ) ;
141+ fetchMock . mockImplementation (
142+ makeFetchMock ( [ wireMemory ( "m1" ) , wireMemory ( "m2" ) ] ) ,
143+ ) ;
144+ setupCopilotKit ( fetchMock ) ;
109145
110- const { result } = renderHook ( ( ) => useMemories ( { agentId : AGENT_ID } ) ) ;
146+ const { result } = renderHook ( ( ) => useMemories ( ) ) ;
147+
148+ act ( ( ) => {
149+ activateStore ( ) ;
150+ } ) ;
111151
112152 await waitFor ( ( ) => {
113153 expect ( result . current . memories . map ( ( m ) => m . id ) ) . toEqual ( [ "m1" , "m2" ] ) ;
@@ -118,17 +158,23 @@ describe("useMemories", () => {
118158 `${ RUNTIME_URL } /memories` ,
119159 expect . objectContaining ( { method : "GET" } ) ,
120160 ) ;
121- expect ( registeredStore ) . toBeDefined ( ) ;
122161 } ) ;
123162
124163 it ( "addMemory POSTs and resolves to the created memory, adding it to the list" , async ( ) => {
125- fetchMock = makeFetchMock ( [ ] , ( ) => ( {
126- ...wireMemory ( "m1" , "hi" ) ,
127- absorbed : false ,
128- } ) ) ;
129- vi . stubGlobal ( "fetch" , fetchMock ) ;
164+ fetchMock . mockImplementation (
165+ makeFetchMock ( [ ] , ( ) => ( {
166+ ...wireMemory ( "m1" , "hi" ) ,
167+ absorbed : false ,
168+ } ) ) ,
169+ ) ;
170+ setupCopilotKit ( fetchMock ) ;
171+
172+ const { result } = renderHook ( ( ) => useMemories ( ) ) ;
173+
174+ act ( ( ) => {
175+ activateStore ( ) ;
176+ } ) ;
130177
131- const { result } = renderHook ( ( ) => useMemories ( { agentId : AGENT_ID } ) ) ;
132178 await waitFor ( ( ) => expect ( result . current . isLoading ) . toBe ( false ) ) ;
133179
134180 let created : { id : string } | undefined ;
@@ -150,13 +196,20 @@ describe("useMemories", () => {
150196 } ) ;
151197
152198 it ( "updateMemory supersedes: resolves to the new id and the list shows it (old id gone)" , async ( ) => {
153- fetchMock = makeFetchMock ( [ wireMemory ( "m1" , "old" ) ] , ( ) => ( {
154- ...wireMemory ( "m2" , "new" ) ,
155- retiredId : "m1" ,
156- } ) ) ;
157- vi . stubGlobal ( "fetch" , fetchMock ) ;
199+ fetchMock . mockImplementation (
200+ makeFetchMock ( [ wireMemory ( "m1" , "old" ) ] , ( ) => ( {
201+ ...wireMemory ( "m2" , "new" ) ,
202+ retiredId : "m1" ,
203+ } ) ) ,
204+ ) ;
205+ setupCopilotKit ( fetchMock ) ;
206+
207+ const { result } = renderHook ( ( ) => useMemories ( ) ) ;
208+
209+ act ( ( ) => {
210+ activateStore ( ) ;
211+ } ) ;
158212
159- const { result } = renderHook ( ( ) => useMemories ( { agentId : AGENT_ID } ) ) ;
160213 await waitFor ( ( ) =>
161214 expect ( result . current . memories . map ( ( m ) => m . id ) ) . toEqual ( [ "m1" ] ) ,
162215 ) ;
@@ -180,10 +233,15 @@ describe("useMemories", () => {
180233 } ) ;
181234
182235 it ( "removeMemory DELETEs and removes the memory from the list" , async ( ) => {
183- fetchMock = makeFetchMock ( [ wireMemory ( "m1" ) ] ) ;
184- vi . stubGlobal ( "fetch" , fetchMock ) ;
236+ fetchMock . mockImplementation ( makeFetchMock ( [ wireMemory ( "m1" ) ] ) ) ;
237+ setupCopilotKit ( fetchMock ) ;
238+
239+ const { result } = renderHook ( ( ) => useMemories ( ) ) ;
240+
241+ act ( ( ) => {
242+ activateStore ( ) ;
243+ } ) ;
185244
186- const { result } = renderHook ( ( ) => useMemories ( { agentId : AGENT_ID } ) ) ;
187245 await waitFor ( ( ) =>
188246 expect ( result . current . memories . map ( ( m ) => m . id ) ) . toEqual ( [ "m1" ] ) ,
189247 ) ;
0 commit comments