// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= import { MemoryScopeDirectory } from '@/components/Settings/Memory/MemoryScopeNotice'; import type { MemoryScopeState } from '@/service/memoryApi'; import { render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter, useLocation } from 'react-router-dom'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const directoryMocks = vi.hoisted(() => ({ listMemoryEntries: vi.fn(), listMemoryScopeSummaries: vi.fn(), })); const spaceState = { spaces: { 'space-1': { id: 'space-1', name: 'Design Space', sourceType: 'folder' as const, rootPath: '/design', status: 'active' as const, schemaVersion: 2, createdAt: 1, updatedAt: 10, }, 'space-2': { id: 'space-2', name: 'Research Space', sourceType: 'folder' as const, rootPath: '/research', status: 'active' as const, schemaVersion: 2, createdAt: 2, updatedAt: 20, }, }, projectsBySpaceId: { 'space-1': { 'project-1': { id: 'project-1', spaceId: 'space-1', name: 'Brand refresh', status: 'active' as const, createdAt: 1, updatedAt: 11, }, }, 'space-2': { 'project-2': { id: 'project-2', spaceId: 'space-2', name: 'Customer interviews', status: 'active' as const, createdAt: 2, updatedAt: 22, }, }, }, }; vi.mock('@/service/memoryApi', () => ({ listMemoryEntries: directoryMocks.listMemoryEntries, listMemoryScopeSummaries: directoryMocks.listMemoryScopeSummaries, })); vi.mock('@/store/spaceStore', async (importOriginal) => ({ ...(await importOriginal()), useSpaceStore: (selector: (state: typeof spaceState) => unknown) => selector(spaceState), })); function memoryState( scopeType: 'space' | 'project', scopeId: string, tokens: number ): MemoryScopeState { return { scope_type: scopeType, scope_id: scopeId, owner_kind: 'desktop', revision: 1, capture_enabled: scopeType === 'project', use_enabled: true, sync_scope: 'full_memory', token_limit: 640, current_token_count: tokens, consolidate_threshold: 0.75, processed_through_watermark: null, extractor_version: 'test', last_consolidated_at: null, last_error: null, updated_at: 1, }; } function CurrentLocation() { const location = useLocation(); return {location.search}; } describe('Memory scope directory', () => { beforeEach(() => { directoryMocks.listMemoryScopeSummaries.mockReset(); }); it('shows which Projects have Memory and sorts them first', async () => { directoryMocks.listMemoryScopeSummaries.mockResolvedValue({ items: [ { scope_type: 'project', scope_id: 'project-1', entry_count: 0, scope_state: memoryState('project', 'project-1', 0), }, { scope_type: 'project', scope_id: 'project-2', entry_count: 3, scope_state: memoryState('project', 'project-2', 120), }, ], }); render( ); expect(await screen.findByText('1 Project with Memory')).toBeVisible(); expect(screen.getByText('3 saved notes total')).toBeVisible(); const rows = document.querySelectorAll('article'); expect(rows).toHaveLength(2); expect( within(rows[0] as HTMLElement).getByText('Customer interviews') ).toBeVisible(); expect( within(rows[0] as HTMLElement).getByText('Space: Research Space') ).toBeVisible(); expect(within(rows[0] as HTMLElement).getByText('3 notes')).toBeVisible(); expect( within(rows[1] as HTMLElement).getByText('Brand refresh') ).toBeVisible(); expect(within(rows[1] as HTMLElement).getByText('Empty')).toBeVisible(); const user = userEvent.setup(); await user.click( screen.getByRole('button', { name: 'Manage Memory for Customer interviews', }) ); expect(screen.getByTestId('location')).toHaveTextContent( '?section=spaces&spaceId=space-2&spaceTab=memory&memoryScope=project&projectId=project-2' ); }); it('uses the same searchable directory and Manage action for Spaces', async () => { directoryMocks.listMemoryScopeSummaries.mockResolvedValue({ items: [ { scope_type: 'space', scope_id: 'space-1', entry_count: 2, scope_state: memoryState('space', 'space-1', 80), }, { scope_type: 'space', scope_id: 'space-2', entry_count: 0, scope_state: memoryState('space', 'space-2', 0), }, ], }); render( ); expect(await screen.findByText('1 Space with Memory')).toBeVisible(); expect(screen.getByLabelText('Search Space Memory')).toBeVisible(); expect( screen.queryByRole('button', { name: 'Open Space Memory' }) ).not.toBeInTheDocument(); expect( screen.getByRole('button', { name: 'Manage Memory for Design Space' }) ).toBeVisible(); }); it('falls back to existing per-scope reads while an older Brain is still running', async () => { directoryMocks.listMemoryScopeSummaries.mockRejectedValue( Object.assign(new Error('Not Found'), { status: 404 }) ); directoryMocks.listMemoryEntries.mockImplementation( (_scopeType: string, scopeId: string) => Promise.resolve({ items: scopeId === 'space-2' ? [{ memory_id: 'memory-1' }] : [], scope_state: memoryState( 'space', scopeId, scopeId === 'space-2' ? 30 : 0 ), }) ); render( ); expect(await screen.findByText('1 Space with Memory')).toBeVisible(); expect(directoryMocks.listMemoryEntries).toHaveBeenCalledTimes(2); expect( screen.getByRole('button', { name: 'Manage Memory for Research Space', }) ).toBeVisible(); }); });