import { expect, test } from '@playwright/test' import { authenticate, mockHermesApi, TEST_ACCESS_KEY } from './fixtures' // The fixture has one category and one node; calculate its initial projection // so the test exercises the actual canvas hit testing and pointer events. function nodePosition(id: string, kind: 'memory' | 'skill', width: number, height: number) { let hash = 2166136261 for (const char of `${id}:0`) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619) const seed = hash >>> 0 const a = (seed % 6283) / 1000 const b = ((seed >> 8) % 3141) / 1000 const spread = kind === 'memory' ? 125 : 165 const x = Math.cos(-0.84) * 360 + Math.cos(a) * Math.sin(b) * spread const y = Math.sin(-0.84) * 360 * 0.72 + Math.cos(b) * spread * 0.85 const z = Math.sin(a) * Math.sin(b) * spread const x1 = x * Math.cos(0.55) - z * Math.sin(0.55) const z1 = x * Math.sin(0.55) + z * Math.cos(0.55) const y1 = y * Math.cos(-0.25) - z1 * Math.sin(-0.25) const z2 = y * Math.sin(-0.25) + z1 * Math.cos(-0.25) const perspective = 860 / (860 + z2) return { x: width / 2 + x1 * perspective, y: height / 2 + y1 * perspective } } for (const kind of ['memory', 'skill'] as const) { test(`previews a ${kind} node on hover and keeps click details`, async ({ page }) => { const id = kind === 'memory' ? 'memory:0' : 'skill:example' const label = kind === 'memory' ? 'Memory label' : 'Example skill title' await page.setViewportSize({ width: 1280, height: 900 }) await authenticate(page, TEST_ACCESS_KEY, 'research') await mockHermesApi(page) await page.route(/^https?:\/\/[^/]+\/api\/hermes\/journey(?:\?.*)?$/, route => route.fulfill({ json: { profile: 'research', source: 'cli', graph: { nodes: [{ id, label, kind, category: 'general', useCount: 1 }], edges: [], clusters: [], memory: [{ title: 'Memory label', body: 'Remember this useful detail.' }], }, } })) await page.goto('/#/hermes/journey') const canvas = page.locator('.galaxy-canvas') await expect(page.locator('.galaxy-hud')).toContainText('1') const box = (await canvas.boundingBox())! const point = nodePosition(id, kind, box.width, box.height) await page.mouse.move(box.x + point.x, box.y + point.y) const preview = page.locator('.node-preview') await expect(preview).toBeVisible() await expect(preview).toContainText(kind === 'memory' ? 'Remember this useful detail.' : label) const previewBox = (await preview.boundingBox())! expect(previewBox.x).toBeGreaterThanOrEqual(box.x) expect(previewBox.x + previewBox.width).toBeLessThanOrEqual(box.x + box.width + 1) await expect(page.locator('.journey-detail-drawer')).toBeHidden() await page.mouse.click(box.x + point.x, box.y + point.y) await expect(page.locator('.journey-detail-drawer')).toBeVisible() await page.keyboard.press('Escape') await page.mouse.move(box.x + 5, box.y + box.height - 5) await expect(preview).toHaveCount(0) }) }