feat: add conversation image previews and sharing

This commit is contained in:
jjbrunton committed 2026-08-09 22:28:41 +01:00
1 parent 488145340d
commit b573b88b08
20 files changed
+634 -26

No files matched your search

+69 -4
View File
@@ -59,10 +59,15 @@ function detailFixture(chat = alice) {
};
}
const server = setupServer(http.get('/api/accounts', () => HttpResponse.json([{
accountId: '@t2_bot:reddit.com', redditUsername: 'bot-account', matrixUserId: '@t2_bot:reddit.com',
isRunning: true, status: 'authenticated', createdAt: 1, updatedAt: 2, lastImportedAt: 2,
}])));
const server = setupServer(
http.get('/api/accounts', () => HttpResponse.json([{
accountId: '@t2_bot:reddit.com', redditUsername: 'bot-account', matrixUserId: '@t2_bot:reddit.com',
isRunning: true, status: 'authenticated', createdAt: 1, updatedAt: 2, lastImportedAt: 2,
}])),
http.get('/api/conversations/:id/shareable-media', () => HttpResponse.json({
requestKind: null, assets: [], reason: 'The latest inbound message does not ask for an image.',
})),
);
function installConversationList() {
server.use(
@@ -147,8 +152,20 @@ describe('ConversationsPage', () => {
);
expect(screen.getByText('2h ago')).toBeInTheDocument();
expect(screen.getByRole('img', { name: 'Source post: Looking for a chat' })).toHaveClass('is-blurred');
expect(screen.getByRole('button', { name: 'View source image full screen: Looking for a chat' })).toBeDisabled();
await user.click(screen.getByRole('button', { name: 'Reveal original image' }));
expect(screen.getByRole('img', { name: 'Source post: Looking for a chat' })).not.toHaveClass('is-blurred');
const previewTrigger = screen.getByRole('button', { name: 'View source image full screen: Looking for a chat' });
await user.click(previewTrigger);
const preview = screen.getByRole('dialog', { name: 'Source image: Looking for a chat' });
expect(within(preview).getByRole('img', { name: 'Source post: Looking for a chat' })).toHaveAttribute(
'src',
'https://i.redd.it/source-post.jpg',
);
expect(within(preview).getByRole('button', { name: 'Close source image preview' })).toHaveFocus();
await user.keyboard('{Escape}');
expect(screen.queryByRole('dialog', { name: 'Source image: Looking for a chat' })).not.toBeInTheDocument();
expect(previewTrigger).toHaveFocus();
});
it('hides detected sellers by default and reveals marked conversations on request', async () => {
@@ -313,6 +330,54 @@ describe('ConversationsPage', () => {
expect(await screen.findByRole('textbox', { name: 'Reply to u/alice' })).toHaveValue('Would you like to share a photo?');
});
it('shares an eligible saved image through an explicit confirmation', async () => {
const asset = {
id: '11111111-1111-4111-8111-111111111111', tags: ['selfie'], safety: 'sfw',
classificationStatus: 'ready', description: 'A selfie', confidence: 0.95,
fileName: 'selfie.jpg', mimeType: 'image/jpeg', sizeBytes: 1024, width: 800, height: 1000,
enabled: true, consentConfirmedAt: 1, createdAt: 1, updatedAt: 1,
sendCount: 0, lastSentAt: null, urlPath: '/library-media/selfie.jpg',
};
let sharedAssetId: string | null = null;
server.use(
http.get('/api/conversations', () => HttpResponse.json([alice])),
http.get('/api/conversations/room-alice', () => HttpResponse.json({
...detailFixture(),
messages: [
{ sender: 'me', text: 'Hello Alice', timestamp: 1, isSelf: true },
{ eventId: '$request', sender: 'alice', text: 'Can you send a selfie?', timestamp: 2, isSelf: false },
],
})),
http.get('/api/conversations/room-alice/shareable-media', () => HttpResponse.json({
requestKind: 'selfie', assets: [asset], reason: null,
})),
http.post('/api/conversations/room-alice/share-media', async ({ request }) => {
const body = await request.json() as { assetId: string };
sharedAssetId = body.assetId;
return HttpResponse.json({ success: true });
}),
);
const user = userEvent.setup();
renderConversations();
await user.click(await screen.findByRole('button', { name: /u\/alice/ }));
await user.click(await screen.findByText('Share a saved image'));
const image = await screen.findByRole('img', { name: 'Saved image tagged selfie' });
expect(image).toHaveClass('is-blurred');
await user.click(screen.getByRole('button', { name: 'Reveal saved images' }));
expect(image).not.toHaveClass('is-blurred');
await user.click(screen.getByRole('radio', { name: /Saved image tagged selfie/ }));
const reviewButton = screen.getByRole('button', { name: 'Review image share' });
await user.click(reviewButton);
const dialog = screen.getByRole('dialog', { name: 'Share this saved image?' });
expect(within(dialog).getByRole('img', { name: 'Saved image tagged selfie' })).toHaveAttribute('src', asset.urlPath);
await user.click(within(dialog).getByRole('button', { name: 'Confirm image share' }));
expect(await screen.findByText(/Saved image shared with u\/alice/)).toBeInTheDocument();
expect(sharedAssetId).toBe(asset.id);
expect(screen.getByText('Share a saved image')).toHaveFocus();
});
it('shows unused turns separately from a safety handoff', async () => {
server.use(
http.get('/api/conversations', () => HttpResponse.json([alice])),