chore(mock): 🔧 Update test files in mock editor mode to reflect new validation rules

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-23 17:03:51 -08:00
parent c21765a38b
commit 8ccbb1d968
2 changed files with 86 additions and 143 deletions

View file

@ -1,38 +1,34 @@
import { test, expect } from '@playwright/test';
/**
* Draft card edit mode tests MSW mock mode.
* Extraction chip display tests MSW mock mode.
*
* DraftDiffCard has inline edit mode triggered by the "Edit value" button.
* All selectors confirmed from DraftDiffCard.tsx:
* After the assistant extracts attributes from a chat message, read-only
* ExtractionChips appear below the message thread. Each chip shows:
* - A label span (e.g. "Height", "Hair Color", "Languages")
* - A value span (e.g. "168", "blonde", "en, is")
*
* button aria-label="Edit value" toggles edit mode on
* button aria-label="Cancel edit" toggles edit mode off
* button aria-label="Confirm change" text "Confirm" (normal) / "Save" (edit mode)
* input aria-label="Edit value for {label}" text input in edit mode
* Chips update on every AI reply only the last AI message with extractions
* is shown. Sending a second message replaces the previous chip set.
*
* When edit mode is active:
* - The inline EditInput appears
* - Confirm button text changes to "Save"
* - Edit button text changes to "Cancel" (aria-label: "Cancel edit")
* MSW extraction for "I'm 5'6, have blonde hair and slim build, speak English and Icelandic":
* height_cm: 168 chip "Height" / "168"
* hair_color: blonde chip "Hair Color" / "blonde"
* languages: ['en','is'] chip "Languages" / "en, is"
*
* Clicking Confirm/Save calls onConfirm(code, editValue) which triggers
* AssistantProvider.confirmAttribute POST /confirm + GET /preview.
*
* Test message: "I'm 5'6, have blonde hair and slim build, speak English and Icelandic"
* MSW extracts: height (168cm), hair_color (blonde), languages (['en'])
* First card aria-label: "Draft change for Height"
* MSW extraction for "I speak Spanish and German":
* languages: ['es','de'] chip "Languages" / "es, de"
*/
test.describe.configure({ mode: 'serial' });
const EDITOR_URL = '/providers/valeria-reykjavik/edit';
test.describe('Profile assistant — draft card edit mode', () => {
test('Edit button toggles inline input visible', async ({ page }) => {
test.describe('Profile assistant — extraction chip display', () => {
test('extraction chips show label and value for each extracted attribute', async ({ page }) => {
await page.goto(EDITOR_URL);
await page.waitForLoadState('networkidle');
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
const fab = page.getByRole('button', { name: 'Open profile assistant' });
await fab.click();
@ -45,29 +41,18 @@ test.describe('Profile assistant — draft card edit mode', () => {
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
await input.press('Enter');
const firstDraftCard = page.locator('[aria-label*="Draft change for"]').first();
await firstDraftCard.waitFor({ state: 'visible', timeout: 15_000 });
// Click "Edit value"
const editBtn = firstDraftCard.getByRole('button', { name: 'Edit value' });
await expect(editBtn).toBeVisible({ timeout: 5_000 });
await editBtn.click();
// Inline input appears
const editInput = firstDraftCard.locator('[aria-label^="Edit value for"]');
await expect(editInput).toBeVisible({ timeout: 3_000 });
// Edit button switches to "Cancel edit"
await expect(firstDraftCard.getByRole('button', { name: 'Cancel edit' })).toBeVisible({ timeout: 3_000 });
// Confirm button text is now "Save"
await expect(firstDraftCard.getByRole('button', { name: 'Confirm change' })).toContainText('Save');
// All three extracted attributes appear as chips
await expect(dialog.locator('text=Height').first()).toBeVisible({ timeout: 15_000 });
await expect(dialog.locator('text=168').first()).toBeVisible({ timeout: 3_000 });
await expect(dialog.locator('text=Hair Color').first()).toBeVisible({ timeout: 3_000 });
await expect(dialog.locator('text=blonde').first()).toBeVisible({ timeout: 3_000 });
await expect(dialog.locator('text=Languages').first()).toBeVisible({ timeout: 3_000 });
});
test('Cancel edit reverts to normal card state', async ({ page }) => {
test('sending a second message replaces chips with latest extractions', async ({ page }) => {
await page.goto(EDITOR_URL);
await page.waitForLoadState('networkidle');
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
const fab = page.getByRole('button', { name: 'Open profile assistant' });
await fab.click();
@ -77,29 +62,28 @@ test.describe('Profile assistant — draft card edit mode', () => {
await page.waitForSelector('[role="dialog"] p', { timeout: 8_000 });
const input = page.getByRole('textbox', { name: /message input/i });
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
// First message — height and hair extracted
await input.fill("I'm 5'6, have blonde hair");
await input.press('Enter');
await expect(dialog.locator('text=Height').first()).toBeVisible({ timeout: 15_000 });
// Second message — only languages extracted, replaces previous chip set
await input.fill('I speak Spanish and German');
await input.press('Enter');
const firstDraftCard = page.locator('[aria-label*="Draft change for"]').first();
await firstDraftCard.waitFor({ state: 'visible', timeout: 15_000 });
// Languages chip with new values appears
await expect(dialog.locator('text=Languages').first()).toBeVisible({ timeout: 15_000 });
await expect(dialog.locator('text=es, de').first()).toBeVisible({ timeout: 5_000 });
// Enter edit mode
await firstDraftCard.getByRole('button', { name: 'Edit value' }).click();
await expect(firstDraftCard.locator('[aria-label^="Edit value for"]')).toBeVisible({ timeout: 3_000 });
// Cancel edit
await firstDraftCard.getByRole('button', { name: 'Cancel edit' }).click();
// Input gone, normal state restored
await expect(firstDraftCard.locator('[aria-label^="Edit value for"]')).not.toBeVisible({ timeout: 3_000 });
await expect(firstDraftCard.getByRole('button', { name: 'Edit value' })).toBeVisible({ timeout: 3_000 });
await expect(firstDraftCard.getByRole('button', { name: 'Confirm change' })).toContainText('Confirm');
// Height chip from first message is gone (chips reflect only last AI extraction)
await expect(dialog.locator('text=168')).not.toBeVisible({ timeout: 3_000 });
});
test('saving edited value confirms attribute and enables Preview Draft', async ({ page }) => {
test('Preview Draft auto-enables once extraction chips appear', async ({ page }) => {
await page.goto(EDITOR_URL);
await page.waitForLoadState('networkidle');
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
const fab = page.getByRole('button', { name: 'Open profile assistant' });
await fab.click();
@ -108,27 +92,16 @@ test.describe('Profile assistant — draft card edit mode', () => {
await dialog.waitFor({ state: 'visible', timeout: 5_000 });
await page.waitForSelector('[role="dialog"] p', { timeout: 8_000 });
const input = page.getByRole('textbox', { name: /message input/i });
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
await input.press('Enter');
const firstDraftCard = page.locator('[aria-label*="Draft change for"]').first();
await firstDraftCard.waitFor({ state: 'visible', timeout: 15_000 });
// Enter edit mode and override the extracted value
await firstDraftCard.getByRole('button', { name: 'Edit value' }).click();
const editInput = firstDraftCard.locator('[aria-label^="Edit value for"]');
await editInput.clear();
await editInput.fill('175');
// Save via Confirm button (shows "Save" text in edit mode)
await firstDraftCard.getByRole('button', { name: 'Confirm change' }).click();
// Card exits edit mode — Edit value button returns
await expect(firstDraftCard.getByRole('button', { name: 'Edit value' })).toBeVisible({ timeout: 5_000 });
// confirmAttribute + fetchDraftPreview fire → hasDrafts = true → Preview Draft enabled
// Preview Draft is disabled before any message
const previewBtn = dialog.getByRole('button', { name: /preview draft/i }).first();
await expect(previewBtn).toBeDisabled({ timeout: 3_000 });
const input = page.getByRole('textbox', { name: /message input/i });
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
await input.press('Enter');
// Chips appear → fetchDraftPreview fires → hasDrafts=true → button enabled
await expect(dialog.locator('text=Height').first()).toBeVisible({ timeout: 15_000 });
await expect(previewBtn).not.toBeDisabled({ timeout: 8_000 });
});
});

View file

@ -3,8 +3,8 @@ import { test, expect } from '@playwright/test';
/**
* Profile assistant tests on the Editor route MSW mock mode.
*
* IMPORTANT: These tests are serial each test builds on state created by
* prior interactions with the same MSW session.
* IMPORTANT: These tests are serial each test builds on state from the
* same session. Serial mode avoids MSW service-worker re-activation delays.
*
* On the editor route the AssistantProvider detects the "editor" context via
* useLocation/useParams. The session creation MSW handler returns:
@ -13,17 +13,23 @@ import { test, expect } from '@playwright/test';
* - quick replies: "Physical traits", "Services", "Use template"
*
* When the user describes physical traits, the MSW /messages handler returns
* an AI reply with extracted attributes as draft cards:
* - generic regions with accessible name "Draft change for Height", etc.
* - "Confirm change" button inside each draft card
* - "Preview Draft" button in the assistant footer (disabled until confirmed)
* an AI reply with extracted attributes. These are rendered as read-only
* ExtractionChips (label + value spans) below the message thread.
* After sendMessage, fetchDraftPreview fires automatically hasDrafts=true
* "Preview Draft" button becomes enabled without any confirmation step.
*
* Accessibility selectors confirmed from snapshot:
* Accessibility selectors:
* dialog "Profile assistant"
* textbox "Message input"
* generic "Draft change for Height"
* button "Confirm change" (text: "Confirm")
* button "Preview Draft"
* button "Preview Draft" (disabled until hasDrafts=true)
*
* Extraction chips are plain styled spans with no aria roles use text
* content selectors to assert their presence.
*
* MSW extraction for "I'm 5'6, have blonde hair and slim build, speak English and Icelandic":
* height_cm: 168 chip label "Height", chip value "168"
* hair_color: blonde chip label "Hair Color", chip value "blonde"
* languages: ['en','is'] chip label "Languages", chip value "en, is"
*/
test.describe.configure({ mode: 'serial' });
@ -36,7 +42,7 @@ test.describe('Profile assistant — editor context', () => {
await page.waitForLoadState('networkidle');
// Wait for profile editor to fully load (categories visible)
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
const fab = page.getByRole('button', { name: 'Open profile assistant' });
await expect(fab).toBeVisible({ timeout: 10_000 });
@ -54,7 +60,7 @@ test.describe('Profile assistant — editor context', () => {
await page.goto(EDITOR_URL);
await page.waitForLoadState('networkidle');
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
const fab = page.getByRole('button', { name: 'Open profile assistant' });
await fab.click();
@ -68,113 +74,77 @@ test.describe('Profile assistant — editor context', () => {
await expect(dialog.locator('button', { hasText: /use template/i })).toBeVisible({ timeout: 5_000 });
});
test('describing physical traits extracts attributes and shows draft cards', async ({ page }) => {
test('describing physical traits shows extraction chips', async ({ page }) => {
await page.goto(EDITOR_URL);
await page.waitForLoadState('networkidle');
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
const fab = page.getByRole('button', { name: 'Open profile assistant' });
await fab.click();
const dialog = page.getByRole('dialog', { name: 'Profile assistant' });
await dialog.waitFor({ state: 'visible', timeout: 5_000 });
// Wait for welcome message before interacting
await page.waitForSelector('[role="dialog"] p', { timeout: 8_000 });
// Send a description that triggers attribute extraction
const input = page.getByRole('textbox', { name: /message input/i });
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
await input.press('Enter');
// MSW returns AI reply with draft cards — each has aria-label="Draft change for X"
const draftCard = page.locator('[aria-label*="Draft change for"]').first();
await expect(draftCard).toBeVisible({ timeout: 15_000 });
// MSW extracts height_cm → chip with label "Height" and value "168"
await expect(dialog.locator('text=Height').first()).toBeVisible({ timeout: 15_000 });
await expect(dialog.locator('text=168').first()).toBeVisible({ timeout: 3_000 });
// Confirm button is present on the first draft card
const confirmBtn = draftCard.getByRole('button', { name: /confirm/i });
await expect(confirmBtn).toBeVisible({ timeout: 5_000 });
// hair_color → chip with label "Hair Color"
await expect(dialog.locator('text=Hair Color').first()).toBeVisible({ timeout: 3_000 });
});
test('confirming a draft attribute applies the change', async ({ page }) => {
test('extraction chips are read-only with no confirm or edit buttons', async ({ page }) => {
await page.goto(EDITOR_URL);
await page.waitForLoadState('networkidle');
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
const fab = page.getByRole('button', { name: 'Open profile assistant' });
await fab.click();
const dialog = page.getByRole('dialog', { name: 'Profile assistant' });
await dialog.waitFor({ state: 'visible', timeout: 5_000 });
await page.waitForSelector('[role="dialog"] p', { timeout: 8_000 });
const input = page.getByRole('textbox', { name: /message input/i });
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
await input.press('Enter');
// Wait for draft cards
const firstDraftCard = page.locator('[aria-label*="Draft change for"]').first();
await firstDraftCard.waitFor({ state: 'visible', timeout: 15_000 });
// Wait for extraction chips to appear
await expect(dialog.locator('text=Height').first()).toBeVisible({ timeout: 15_000 });
// Capture current draft card count
const initialDraftCount = await page.locator('[aria-label*="Draft change for"]').count();
// Click confirm on the first draft
const confirmBtn = firstDraftCard.getByRole('button', { name: /confirm/i });
await confirmBtn.click();
// After confirming, either the card disappears or its state changes
// We wait for any change: either count decreases or the confirm button state changes
await page.waitForFunction(
(count: number) => {
const cards = document.querySelectorAll('[role="generic"][aria-label*="Draft change for"]');
// Either fewer cards remain, or the confirm button is gone/disabled
return cards.length < count || !document.querySelector('button[aria-label*="Confirm"], button:has-text("Confirm")');
},
initialDraftCount,
{ timeout: 8_000 },
).catch(() => {
// Acceptable: button may transition to "Confirmed" state rather than disappearing
});
// Verify the confirmation was accepted — at minimum the dialog is still open
await expect(dialog).toBeVisible();
// Chips are read-only annotations — no confirm, edit, or cancel buttons
await expect(dialog.getByRole('button', { name: /confirm/i })).not.toBeVisible();
await expect(dialog.getByRole('button', { name: /edit value/i })).not.toBeVisible();
await expect(dialog.getByRole('button', { name: /cancel edit/i })).not.toBeVisible();
});
test('Preview Draft button enables after at least one confirmation', async ({ page }) => {
test('Preview Draft enables automatically after extraction', async ({ page }) => {
await page.goto(EDITOR_URL);
await page.waitForLoadState('networkidle');
await page.waitForSelector('button:has-text("Essentials")', { timeout: 15_000 });
await page.waitForSelector('button:has-text("Essentials")', { timeout: 30_000 });
const fab = page.getByRole('button', { name: 'Open profile assistant' });
await fab.click();
const dialog = page.getByRole('dialog', { name: 'Profile assistant' });
await dialog.waitFor({ state: 'visible', timeout: 5_000 });
await page.waitForSelector('[role="dialog"] p', { timeout: 8_000 });
const input = page.getByRole('textbox', { name: /message input/i });
await input.fill("I'm 5'6, have blonde hair and slim build, speak English and Icelandic");
await input.press('Enter');
// Wait for draft cards to appear
const firstDraftCard = page.locator('[aria-label*="Draft change for"]').first();
await firstDraftCard.waitFor({ state: 'visible', timeout: 15_000 });
// Confirm the first draft
const confirmBtn = firstDraftCard.getByRole('button', { name: /confirm/i });
await confirmBtn.click();
// Preview Draft button should be enabled after confirmation triggers a preview fetch.
// After hasDrafts=true, TWO "Preview draft" buttons render (header icon + footer button).
// Use first() to avoid strict-mode multiple-match violation.
// sendMessage auto-calls fetchDraftPreview → hasDrafts=true → button enabled
// No confirmation step required.
const previewBtn = dialog.getByRole('button', { name: /preview draft/i }).first();
await expect(previewBtn).toBeVisible({ timeout: 8_000 });
await expect(previewBtn).not.toBeDisabled({ timeout: 8_000 });
await expect(previewBtn).not.toBeDisabled({ timeout: 15_000 });
});
});