|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +import { test, expect } from '@playwright/test'; |
| 3 | +import { sendPromptAndWait } from './test-helpers'; |
| 4 | + |
| 5 | +// Matches fixtures/citations.json. The graph runs the REAL search_documents |
| 6 | +// tool; its no-match fallback returns the first 3 corpus docs, so the citation |
| 7 | +// set is deterministic: ng-signals-overview, ng-signals-rxjs, ng-control-flow. |
| 8 | +const PROMPT = 'cite your sources on angular signals'; |
| 9 | + |
| 10 | +test('inline citation markers render as resolved pills', async ({ page }) => { |
| 11 | + const bubble = await sendPromptAndWait(page, PROMPT); |
| 12 | + const markers = bubble.locator('.chat-citation-marker'); |
| 13 | + await expect(markers.first()).toBeVisible(); |
| 14 | + // 4 inline references across 3 distinct sources. |
| 15 | + expect(await markers.count()).toBeGreaterThanOrEqual(3); |
| 16 | + // Resolved markers with a URL render as anchors linking out; none unresolved. |
| 17 | + await expect(bubble.locator('a.chat-citation-marker').first()).toHaveAttribute('href', /angular\.dev/); |
| 18 | + await expect(bubble.locator('.chat-citation-marker--unresolved')).toHaveCount(0); |
| 19 | +}); |
| 20 | + |
| 21 | +test('sources panel is collapsed by default and expands to the cited sources', async ({ page }) => { |
| 22 | + const bubble = await sendPromptAndWait(page, PROMPT); |
| 23 | + |
| 24 | + await expect(bubble.locator('.chat-citations')).toBeVisible(); |
| 25 | + await expect(bubble.locator('.chat-citations__count')).toHaveText('3'); |
| 26 | + |
| 27 | + // Collapsed by default: header shows, list is absent. |
| 28 | + await expect(bubble.locator('.chat-citations__header')).toBeVisible(); |
| 29 | + await expect(bubble.locator('.chat-citations__list')).toHaveCount(0); |
| 30 | + |
| 31 | + // Expand → three detail cards in citation order. |
| 32 | + await bubble.locator('.chat-citations__header').click(); |
| 33 | + const cards = bubble.locator('.chat-citations-card'); |
| 34 | + await expect(cards).toHaveCount(3); |
| 35 | + await expect(cards.nth(0)).toContainText('Signals'); |
| 36 | + await expect(cards.nth(1)).toContainText('RxJS interop'); |
| 37 | + await expect(cards.nth(2)).toContainText('control flow'); |
| 38 | + // Cards are links to the source (the card element itself is the <a>). |
| 39 | + await expect(cards.nth(0)).toHaveAttribute('href', /angular\.dev\/guide\/signals/); |
| 40 | +}); |
| 41 | + |
| 42 | +test('focusing a marker opens the portaled provenance preview card', async ({ page }) => { |
| 43 | + const bubble = await sendPromptAndWait(page, PROMPT); |
| 44 | + |
| 45 | + // Focus (deterministic across pointer types) opens the preview. |
| 46 | + await bubble.locator('a.chat-citation-marker').first().focus(); |
| 47 | + |
| 48 | + // The preview is portaled to the body-level overlay container, not the bubble. |
| 49 | + const preview = page.locator('.chat-citation-preview'); |
| 50 | + await expect(preview).toBeVisible(); |
| 51 | + await expect(preview.locator('.chat-citation-preview__domain')).toContainText('angular.dev'); |
| 52 | + await expect(preview.locator('.chat-citation-preview__open')).toBeVisible(); |
| 53 | +}); |
0 commit comments