openpencil/tests/e2e/chat/diagnostics.spec.ts
Danila Poyarkov b4e479d9d0
feat: persist AI conversations and add chat history (#661)
* feat: persist AI conversations and add chat history

Store transcripts and attachment previews in IndexedDB, separate conversation history from transport lifetime, and add document-aware switching with shared Storybook coverage. Preserve interrupted activity and guard stale writes and async switches.

* test: group chat history tests by domain

* feat: simplify chat history navigation and diagnostics

Use a compact header with searchable document-scoped history and a correctly anchored conversation menu. Move diagnostic copying into the menu with copy-result feedback, and separate Storybook fixtures from composition.

* fix: address chat history review findings

* test: cover harness shutdown and restored chat scrolling

* fix: preserve Portless proxy port in MCP routes

* docs: clarify UI animation conventions

* feat: configure reasoning display and animate disclosure

* fix: separate transcript following from reasoning disclosure

* fix: restore selected chat after document recovery

* refactor: separate chat history persistence and sessions

* style: format reasoning story imports
2026-09-08 11:19:07 +03:00

67 lines
2.5 KiB
TypeScript

import { test, expect } from '#tests/helpers/chat/fixture'
test('diagnostic menu copies the conversation to the clipboard', async ({
configuredChat: chat,
page,
context
}) => {
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
await chat.submit('Clipboard regression conversation')
await expect(chat.assistantMessage()).toBeVisible()
await page.getByRole('button', { name: 'Conversation actions' }).click()
await page.getByRole('menuitem', { name: 'Copy diagnostic log' }).click()
await expect(page.getByRole('status')).toContainText('Diagnostic log copied.')
const contents = await page.evaluate(() => navigator.clipboard.readText())
expect(contents).toContain('OPEN PENCIL AI DEBUG LOG')
expect(contents).toContain('Clipboard regression conversation')
})
test('clipboard rejection shows failure feedback rather than success', async ({
configuredChat: chat,
page
}) => {
await chat.submit('Clipboard failure conversation')
await expect(chat.assistantMessage()).toBeVisible()
await page.evaluate(() => {
Object.defineProperty(navigator.clipboard, 'writeText', {
configurable: true,
value: async () => {
throw new DOMException('Permission denied', 'NotAllowedError')
}
})
})
await page.getByRole('button', { name: 'Conversation actions' }).click()
await page.getByRole('menuitem', { name: 'Copy diagnostic log' }).click()
await expect(page.getByRole('status')).toContainText('Could not copy the diagnostic log.')
await expect(page.getByText('Diagnostic log copied.', { exact: true })).toHaveCount(0)
})
test('mobile conversation menu stays attached to its trigger and inside the viewport', async ({
configuredChat: chat,
page
}) => {
await chat.submit('Mobile menu conversation')
await expect(chat.assistantMessage()).toBeVisible()
await page.setViewportSize({ width: 390, height: 844 })
await page.getByTestId('mobile-ribbon-ai').click()
const trigger = page.getByRole('button', { name: 'Conversation actions' })
await expect(trigger).toBeVisible()
const triggerBox = await trigger.boundingBox()
await trigger.click()
const menu = page.getByRole('menu')
await expect(menu).toBeVisible()
await expect
.poll(async () => {
const box = await menu.boundingBox()
if (!box || !triggerBox) return false
return (
box.x >= 0 &&
box.x + box.width <= 390 &&
box.y >= 0 &&
box.y + box.height <= 844 &&
Math.abs(box.x + box.width - triggerBox.x - triggerBox.width) < 2
)
})
.toBe(true)
})