import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { describe, expect, it, vi } from 'vitest'; import { ChangeEvent } from '../../src/product/ChangeEvent.tsx'; import { CheckEvent } from '../../src/product/CheckEvent.tsx'; import { Composer } from '../../src/product/Composer.tsx'; import { ConversationPanel } from '../../src/product/ConversationPanel.tsx'; import { Message } from '../../src/product/Message.tsx'; import { Milestone } from '../../src/product/Milestone.tsx'; import { StepGroup } from '../../src/product/StepGroup.tsx'; import { StepLine } from '../../src/product/StepLine.tsx'; import { Thought } from '../../src/product/Thought.tsx'; describe('Message', () => { it('puts what the owner said in a soft box', () => { render(Make the phone number tappable.); expect(screen.getByText('Make the phone number tappable.').closest('.p-message')).toHaveClass('p-message-user'); }); it('shows Wursor replies beside the Wursor avatar', () => { render(On it.); expect(screen.getByRole('img', { name: 'Wursor' })).toBeInTheDocument(); expect(screen.getByText('On it.')).toBeInTheDocument(); }); }); describe('StepGroup', () => { it('is collapsed by default and opens on press', async () => { render(Home, Menu, Contact, About); const toggle = screen.getByRole('button', { name: /Looked at 4 pages/ }); expect(toggle).toHaveAttribute('aria-expanded', 'false'); expect(screen.queryByText('Home, Menu, Contact, About')).not.toBeInTheDocument(); await userEvent.click(toggle); expect(toggle).toHaveAttribute('aria-expanded', 'true'); expect(screen.getByText('Home, Menu, Contact, About')).toBeInTheDocument(); }); it('shows the count', () => { render(); expect(screen.getByText('4')).toHaveClass('p-counter'); }); }); describe('Thought', () => { it('is collapsed by default, with the reasoning hidden', () => { render(The number is plain text.); expect(screen.getByRole('button', { name: 'Thought for 6 seconds' })).toHaveAttribute('aria-expanded', 'false'); expect(screen.queryByText('The number is plain text.')).not.toBeInTheDocument(); }); it('shows the reasoning in plain words when expanded', async () => { render(The number is plain text.); await userEvent.click(screen.getByRole('button', { name: 'Thought for 6 seconds' })); expect(screen.getByText('The number is plain text.')).toBeInTheDocument(); }); it('can start expanded', () => { render(The number is plain text.); expect(screen.getByText('The number is plain text.')).toBeInTheDocument(); }); }); describe('StepLine', () => { it('shows what Wursor did and how long it took', () => { render(Made the Contact page number a tap-to-call link); expect(screen.getByText('Made the Contact page number a tap-to-call link')).toBeInTheDocument(); expect(screen.getByText('4s')).toBeInTheDocument(); }); it('marks a running step so it is announced', () => { render(Taking a photo of the page on a phone); expect(screen.getByRole('status', { name: 'In progress' })).toBeInTheDocument(); }); }); describe('ChangeEvent', () => { const props = { title: 'Changed the Contact page', meta: '1 section · 2 places', risk: 'everyday', added: 8, removed: 2, } as const; it('shows the change, its risk and how much changed', () => { render(); expect(screen.getByText('Changed the Contact page')).toBeInTheDocument(); expect(screen.getByText('Everyday')).toBeInTheDocument(); expect(screen.getByText('+8 lines')).toBeInTheDocument(); expect(screen.getByText('−2 lines')).toBeInTheDocument(); }); it('offers the full comparison', async () => { const onSeeChanges = vi.fn(); render(); await userEvent.click(screen.getByRole('button', { name: 'See what changed' })); expect(onSeeChanges).toHaveBeenCalledOnce(); }); }); describe('CheckEvent', () => { it('reports a passed check quietly', () => { render(); expect(screen.getByText('Checked 9 things. All passed.')).toBeInTheDocument(); expect(screen.getByText('Phone link works, 3 screen sizes.')).toBeInTheDocument(); }); }); describe('Milestone', () => { it('marks a stage in the life of a change', () => { render(Plan ready); expect(screen.getByRole('separator', { name: 'Plan ready' })).toBeInTheDocument(); }); }); describe('Composer', () => { it('invites a note while Wursor is working, and offers to stop', async () => { const onStop = vi.fn(); render(); expect(screen.getByPlaceholderText('Wursor is working. Add a note, or stop.')).toBeInTheDocument(); await userEvent.click(screen.getByRole('button', { name: 'Stop' })); expect(onStop).toHaveBeenCalledOnce(); }); it('shows the autonomy level and the toolbar actions', () => { render(); expect(screen.getByRole('button', { name: 'Ask first' })).toBeInTheDocument(); for (const name of ['Attach a file', 'Point at something on the page', 'Dictate']) { expect(screen.getByRole('button', { name })).toBeInTheDocument(); } }); it('lets the owner keep typing while it works', async () => { render(); await userEvent.type(screen.getByRole('textbox', { name: 'Message to Wursor' }), 'Also the header'); expect(screen.getByRole('textbox', { name: 'Message to Wursor' })).toHaveValue('Also the header'); }); }); describe('ConversationPanel', () => { it('has a header with the three detail levels, and a stream', () => { render( {}}> Hello , ); expect(screen.getByRole('heading', { name: 'Conversation' })).toBeInTheDocument(); expect(screen.getByRole('radio', { name: 'Steps' })).toBeChecked(); expect(screen.getByText('Hello')).toBeInTheDocument(); }); it('reports a change of detail level', async () => { const onDensityChange = vi.fn(); render(); await userEvent.click(screen.getByRole('radio', { name: 'Everything' })); expect(onDensityChange).toHaveBeenCalledWith('everything'); }); });