From 7874ad6b5f9d838e436237a3f10a7422e48b14bf Mon Sep 17 00:00:00 2001 From: Aqil Aziz Date: Sat, 16 May 2026 09:33:46 +0700 Subject: [PATCH] test: cover memory detail rendering (#1878) --- .../__tests__/MemoryTextAndDetail.test.tsx | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 app/src/components/intelligence/__tests__/MemoryTextAndDetail.test.tsx diff --git a/app/src/components/intelligence/__tests__/MemoryTextAndDetail.test.tsx b/app/src/components/intelligence/__tests__/MemoryTextAndDetail.test.tsx new file mode 100644 index 000000000..242362d01 --- /dev/null +++ b/app/src/components/intelligence/__tests__/MemoryTextAndDetail.test.tsx @@ -0,0 +1,141 @@ +import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import type { + Chunk, + EntityRef, + MemoryRetrievalEntity, + ScoreBreakdown, +} from '../../../utils/tauriCommands'; +import { MemoryChunkDetail } from '../MemoryChunkDetail'; +import { MemoryTextWithEntities } from '../MemoryTextWithEntities'; + +const rpcMocks = vi.hoisted(() => ({ + memoryTreeEntityIndexFor: vi.fn(), + memoryTreeChunkScore: vi.fn(), +})); + +vi.mock('../../../utils/tauriCommands', async () => { + const actual = await vi.importActual( + '../../../utils/tauriCommands' + ); + return { + ...actual, + memoryTreeEntityIndexFor: rpcMocks.memoryTreeEntityIndexFor, + memoryTreeChunkScore: rpcMocks.memoryTreeChunkScore, + }; +}); + +const BASE_CHUNK: Chunk = { + id: 'chunk-1234567890abcdef', + source_kind: 'email', + source_id: 'gmail:alice@example.com|bob@example.com', + source_ref: 'gmail://msg/abc', + owner: 'bob@example.com', + timestamp_ms: Date.UTC(2026, 4, 16, 9, 30, 0), + token_count: 220, + lifecycle_status: 'admitted', + content_preview: + 'Project Atlas kickoff. Alice (PERSON) owns Atlas (PROJECT). Follow up tomorrow.', + has_embedding: true, + tags: ['person/Alice'], +}; + +const ENTITIES: EntityRef[] = [ + { entity_id: 'person:Alice', kind: 'person', surface: 'Alice', count: 2 }, + { entity_id: 'project:Atlas', kind: 'project', surface: 'Atlas', count: 1 }, +]; + +const SCORE: ScoreBreakdown = { + signals: [ + { name: 'recency', weight: 0.4, value: 0.8 }, + { name: 'importance', weight: 0.6, value: 0.6 }, + ], + total: 0.68, + threshold: 0.5, + kept: true, + llm_consulted: false, +}; + +describe('MemoryTextWithEntities', () => { + it('renders nothing when there is no text or entity chip data', () => { + const { container } = render(); + + expect(container).toBeEmptyDOMElement(); + }); + + it('renders structured entity chips with type titles', () => { + const entities: MemoryRetrievalEntity[] = [ + { id: 'e1', name: 'Alice', entity_type: 'PERSON' }, + { id: 'e2', name: 'Atlas', entity_type: 'PROJECT' }, + ]; + + render(); + + expect(screen.getByTitle('Alice (PERSON)')).toBeInTheDocument(); + expect(screen.getByTitle('Atlas (PROJECT)')).toBeInTheDocument(); + expect(screen.getByText('Relevant context')).toBeInTheDocument(); + }); + + it('turns inline entity annotations into accessible badges', () => { + render(); + + expect(screen.getByTitle('Entity type: PERSON')).toHaveTextContent('PERSON'); + expect(screen.getByTitle('Entity type: PROJECT')).toHaveTextContent('PROJECT'); + expect(screen.getByText(/Alice/)).toBeInTheDocument(); + expect(screen.getByText(/owns Atlas/)).toBeInTheDocument(); + }); +}); + +describe('MemoryChunkDetail', () => { + beforeEach(() => { + vi.clearAllMocks(); + rpcMocks.memoryTreeEntityIndexFor.mockResolvedValue(ENTITIES); + rpcMocks.memoryTreeChunkScore.mockResolvedValue(SCORE); + }); + + it('loads chunk entities and score details for the letter view', async () => { + const onSelectEntity = vi.fn(); + render(); + + expect(screen.getByTestId('memory-chunk-detail')).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Project Atlas kickoff' })).toBeInTheDocument(); + expect(screen.getByTitle('Entity type: PERSON')).toHaveTextContent('PERSON'); + expect(screen.getByTitle('Entity type: PROJECT')).toHaveTextContent('PROJECT'); + + await waitFor(() => { + expect(rpcMocks.memoryTreeEntityIndexFor).toHaveBeenCalledWith(BASE_CHUNK.id); + expect(rpcMocks.memoryTreeChunkScore).toHaveBeenCalledWith(BASE_CHUNK.id); + }); + + const mentioned = await screen.findByTestId('memory-chunk-mentioned'); + expect(within(mentioned).getByText('Alice')).toBeInTheDocument(); + expect(within(mentioned).getByText('2 chunks')).toBeInTheDocument(); + expect(within(mentioned).getByText('Atlas')).toBeInTheDocument(); + expect(within(mentioned).getByText('1 chunk')).toBeInTheDocument(); + + fireEvent.click(within(mentioned).getByText('Alice')); + expect(onSelectEntity).toHaveBeenCalledWith(ENTITIES[0]); + + expect(screen.getByTestId('memory-chunk-scorebars')).toBeInTheDocument(); + expect(screen.getByText('recency')).toBeInTheDocument(); + expect(screen.getByLabelText('recency score 80 percent')).toBeInTheDocument(); + expect(screen.getByText('gmail://msg/abc')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /chunk 12345678/i })).toBeInTheDocument(); + expect(screen.getByText('bge-m3 1024dim')).toBeInTheDocument(); + }); + + it('copies the full chunk id from the footer button', async () => { + const writeText = vi.fn().mockResolvedValue(undefined); + Object.defineProperty(navigator, 'clipboard', { value: { writeText }, configurable: true }); + + render(); + + fireEvent.click(screen.getByTitle('Copy chunk id')); + + await waitFor(() => { + expect(writeText).toHaveBeenCalledWith(BASE_CHUNK.id); + }); + expect(await screen.findByText('copied')).toBeInTheDocument(); + }); +});