From f046dc00b9b149558d3ed6376f3ae6107bbdb9ad Mon Sep 17 00:00:00 2001 From: Brian Love Date: Thu, 11 Jun 2026 20:45:48 -0700 Subject: [PATCH] test(examples/ag-ui): e2e for move_stop panel mutation + day_card view render Co-Authored-By: Claude Fable 5 --- .../ag-ui/angular/e2e/fixtures/itinerary.json | 16 ++++++ .../e2e/itinerary-client-tools.spec.ts | 53 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/examples/ag-ui/angular/e2e/fixtures/itinerary.json b/examples/ag-ui/angular/e2e/fixtures/itinerary.json index 186b41ebf..dfde2f5e1 100644 --- a/examples/ag-ui/angular/e2e/fixtures/itinerary.json +++ b/examples/ag-ui/angular/e2e/fixtures/itinerary.json @@ -17,6 +17,22 @@ { "match": { "userMessage": "Clear my day 2 plans" }, "response": { "toolCalls": [ { "name": "clear_day", "arguments": { "day": 2 } } ] } + }, + { + "match": { "userMessage": "Move the Eiffel Tower to day 2", "hasToolResult": true }, + "response": { "content": "Done — the Eiffel Tower is now on day 2." } + }, + { + "match": { "userMessage": "Move the Eiffel Tower to day 2" }, + "response": { "toolCalls": [ { "name": "move_stop", "arguments": { "place": "Eiffel Tower", "toDay": 2 } } ] } + }, + { + "match": { "userMessage": "Show me a recap card for day 1", "hasToolResult": true }, + "response": { "content": "Here's your day 1 recap." } + }, + { + "match": { "userMessage": "Show me a recap card for day 1" }, + "response": { "toolCalls": [ { "name": "day_card", "arguments": { "day": 1, "places": ["Louvre", "Eiffel Tower"] } } ] } } ] } diff --git a/examples/ag-ui/angular/e2e/itinerary-client-tools.spec.ts b/examples/ag-ui/angular/e2e/itinerary-client-tools.spec.ts index af548df56..2c843e3de 100644 --- a/examples/ag-ui/angular/e2e/itinerary-client-tools.spec.ts +++ b/examples/ag-ui/angular/e2e/itinerary-client-tools.spec.ts @@ -75,3 +75,56 @@ test('ask chain: clear_day confirm mutates the panel and resumes the run', async await expect(confirm.getByRole('button', { name: 'Clear' })).toHaveCount(0); await expect(confirm.getByRole('button', { name: 'Cancel' })).toHaveCount(0); }); + +test('move_stop action: panel mutates — Eiffel Tower moves from day 1 to day 2', async ({ + page, +}) => { + await openDemo(page); + + // Seed has Eiffel Tower under Day 1 — verify it's there before sending. + const panel = page.getByRole('region', { name: 'Trip itinerary' }); + await expect(panel).toBeVisible(); + + // Day sections are
each containing an

with + // "Day N" text and a
    with the stop rows. + const day1Section = panel.locator('section.itin__day').filter({ hasText: 'Day 1' }); + const day2Section = panel.locator('section.itin__day').filter({ hasText: 'Day 2' }); + + await expect(day1Section).toContainText('Eiffel Tower'); + await expect(day2Section).not.toContainText('Eiffel Tower'); + + await messageInput(page).fill('Move the Eiffel Tower to day 2'); + await sendButton(page).click(); + + // move_stop is an action tool: the browser executes it immediately (no + // user interaction required). The store update is synchronous, so the panel + // re-renders before the continuation streams back. Wait on the continuation + // text to confirm both the tool execution AND the second run have finished. + await expect(page.getByText('Done — the Eiffel Tower is now on day 2.')).toBeVisible({ + timeout: 30_000, + }); + + // Panel must reflect the mutation: Eiffel Tower is now under Day 2, gone from Day 1. + await expect(day2Section).toContainText('Eiffel Tower'); + await expect(day1Section).not.toContainText('Eiffel Tower'); +}); + +test('day_card view: component renders with model-filled props and run auto-continues', async ({ + page, +}) => { + await openDemo(page); + + await messageInput(page).fill('Show me a recap card for day 1'); + await sendButton(page).click(); + + // day_card is a `view` tool: it auto-acks without user interaction, so the + // run continues immediately (two runs total, same pattern as the read test). + // The card must be visible and contain the model-supplied day number and places. + const card = page.locator('app-day-card'); + await expect(card).toBeVisible({ timeout: 30_000 }); + await expect(card).toContainText('Day 1'); + await expect(card).toContainText('Louvre'); + + // Continuation text confirms the second run finished. + await expect(page.getByText("Here's your day 1 recap.")).toBeVisible({ timeout: 30_000 }); +});