2aa2893973
InboxAdapter gains uploadAttachment + attachment params on mailReply/ composeInternal/composeExternal. MailReader reply and ComposeModal grow an attach affordance (paperclip + pending chips); mail history renders sent attachments. MockInboxAdapter implements upload. 67 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
4.2 KiB
TypeScript
98 lines
4.2 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import { InboxProvider } from './provider';
|
|
import { Inbox } from './inbox';
|
|
import { MockInboxAdapter } from '../adapters/mock-inbox';
|
|
|
|
function mount() {
|
|
return render(
|
|
<InboxProvider adapter={new MockInboxAdapter()}>
|
|
<Inbox />
|
|
</InboxProvider>,
|
|
);
|
|
}
|
|
|
|
describe('mock inbox adapter', () => {
|
|
it('unifies work items + mail in the Open view; other states drop mail', async () => {
|
|
const a = new MockInboxAdapter();
|
|
const open = await a.listInbox('OPEN');
|
|
expect(open.some((i) => i.kind === 'MAIL')).toBe(true);
|
|
expect(open.some((i) => i.kind === 'MENTION')).toBe(true);
|
|
const done = await a.listInbox('DONE');
|
|
expect(done.some((i) => i.kind === 'MAIL')).toBe(false);
|
|
});
|
|
|
|
it('transition moves a work item out of Open', async () => {
|
|
const a = new MockInboxAdapter();
|
|
await a.transition('in_3', 'DONE');
|
|
expect((await a.listInbox('OPEN')).some((i) => i.id === 'in_3')).toBe(false);
|
|
expect((await a.listInbox('DONE')).some((i) => i.id === 'in_3')).toBe(true);
|
|
});
|
|
|
|
it('mail reply appends to the thread', async () => {
|
|
const a = new MockInboxAdapter();
|
|
await a.mailReply('mt_welcome', 'thanks!');
|
|
expect((await a.mailHistory('mt_welcome')).some((m) => m.text === 'thanks!')).toBe(true);
|
|
});
|
|
|
|
it('reply carries an uploaded attachment', async () => {
|
|
const a = new MockInboxAdapter();
|
|
const ref = await a.uploadAttachment(new File(['x'], 'plan.pdf', { type: 'application/pdf' }));
|
|
expect(ref).toMatchObject({ filename: 'plan.pdf', mimeType: 'application/pdf' });
|
|
await a.mailReply('mt_welcome', '', ref);
|
|
const last = (await a.mailHistory('mt_welcome')).at(-1)!;
|
|
expect(last.attachment?.filename).toBe('plan.pdf');
|
|
});
|
|
|
|
it('composeInternal carries a first attachment onto the new thread', async () => {
|
|
const a = new MockInboxAdapter();
|
|
const ref = await a.uploadAttachment(new File(['x'], 'quote.png', { type: 'image/png' }));
|
|
await a.composeInternal('pp_sofia', 'Quote', 'see attached', [ref]);
|
|
const open = await a.listInbox('OPEN');
|
|
const row = open.find((i) => i.title === 'Quote')!;
|
|
expect((await a.mailHistory(row.threadId!)).at(-1)?.attachment?.filename).toBe('quote.png');
|
|
});
|
|
});
|
|
|
|
describe('<Inbox /> (rendered)', () => {
|
|
it('lists items and opens a mail thread on click', async () => {
|
|
mount();
|
|
// A folded mail row is present.
|
|
const welcome = await screen.findByText('Welcome to the Founders Club');
|
|
fireEvent.click(welcome);
|
|
// The reader opens with a reply box.
|
|
expect(await screen.findByLabelText('Reply')).toBeTruthy();
|
|
});
|
|
|
|
it('replies into a mail thread', async () => {
|
|
mount();
|
|
fireEvent.click(await screen.findByText('Welcome to the Founders Club'));
|
|
const input = (await screen.findByLabelText('Reply')) as HTMLInputElement;
|
|
fireEvent.change(input, { target: { value: 'got it' } });
|
|
fireEvent.click(screen.getByText('Reply'));
|
|
await waitFor(() => expect(screen.getByText('got it')).toBeTruthy());
|
|
});
|
|
|
|
it('attaches a file into a mail reply', async () => {
|
|
mount();
|
|
fireEvent.click(await screen.findByText('Welcome to the Founders Club'));
|
|
const file = new File(['data'], 'roof.pdf', { type: 'application/pdf' });
|
|
fireEvent.change(await screen.findByLabelText('Attach file'), { target: { files: [file] } });
|
|
// The pending chip shows the file, then Reply sends it.
|
|
await screen.findByText(/roof\.pdf/);
|
|
fireEvent.click(screen.getByText('Reply'));
|
|
await waitFor(() => expect(screen.getAllByText(/roof\.pdf/).length).toBeGreaterThan(0));
|
|
});
|
|
|
|
it('composes an in-app message and it shows in the inbox', async () => {
|
|
mount();
|
|
fireEvent.click(await screen.findByText('New message'));
|
|
// pick a recipient
|
|
fireEvent.click(await screen.findByText('Sofia Ramirez'));
|
|
fireEvent.change(screen.getByLabelText('Subject'), { target: { value: 'Quick q' } });
|
|
fireEvent.change(screen.getByLabelText('Message body'), { target: { value: 'ping' } });
|
|
fireEvent.click(screen.getByText('Send'));
|
|
await waitFor(() => expect(screen.getByText('Quick q')).toBeTruthy());
|
|
});
|
|
});
|