eigent/test/unit/components/MemoryScopeDirectory.test.tsx

236 lines
7 KiB
TypeScript

// ========= 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<typeof import('@/store/spaceStore')>()),
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 <output data-testid="location">{location.search}</output>;
}
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(
<MemoryRouter initialEntries={['/home?section=settings&tab=memory']}>
<MemoryScopeDirectory scopeType="project" />
<CurrentLocation />
</MemoryRouter>
);
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(
<MemoryRouter>
<MemoryScopeDirectory scopeType="space" />
</MemoryRouter>
);
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(
<MemoryRouter>
<MemoryScopeDirectory scopeType="space" />
</MemoryRouter>
);
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();
});
});