import { fireEvent, render, waitFor } from '@testing-library/svelte' import { describe, expect, it, vi } from 'vitest' import { APP_SERVICES } from '$lib/app-services' import { account, session, status, testServices } from '$test/fixtures' import Composer from './Composer.svelte' describe('Composer', () => { it('posts through an in-memory endpoint double', async () => { const postStatus = vi.fn().mockResolvedValue(status()) const services = testServices({ session: session({ token: 'token', me: account(), signedIn: true }), endpoints: { postStatus }, }) const view = render(Composer, { props: { initialText: 'A backend-free component test' }, context: new Map([[APP_SERVICES, services]]), }) await fireEvent.click(view.getByRole('button', { name: 'Post Entry' })) await waitFor(() => expect(postStatus).toHaveBeenCalledOnce()) expect(postStatus.mock.calls[0][1]).toMatchObject({ status: 'A backend-free component test', visibility: 'public', }) }) it('can submit a structured quote without commentary', async () => { const quoted = status({ id: 'quoted-entry', content: '

The original thought

', }) const postStatus = vi.fn().mockResolvedValue(status({ id: 'new-quote' })) const services = testServices({ session: session({ token: 'token', me: account(), signedIn: true }), endpoints: { postStatus }, }) const view = render(Composer, { props: { quote: quoted, submitLabel: 'Post Quote' }, context: new Map([[APP_SERVICES, services]]), }) expect(view.getByText('The original thought')).toBeInTheDocument() await fireEvent.click(view.getByRole('button', { name: 'Post Quote' })) await waitFor(() => expect(postStatus).toHaveBeenCalledOnce()) expect(postStatus.mock.calls[0][1]).toMatchObject({ status: '', quoted_status_id: 'quoted-entry', }) }) it('composes a multiple-choice poll without a backend', async () => { const postStatus = vi.fn().mockResolvedValue(status()) const services = testServices({ session: session({ token: 'token', me: account(), signedIn: true, instance: { title: 'Pleroma', version: '2.9.0', configuration: { polls: { max_options: 4, max_characters_per_option: 30, min_expiration: 300, max_expiration: 604_800, }, }, }, }), endpoints: { postStatus }, }) const view = render(Composer, { props: { initialText: 'Which old web feature should return?' }, context: new Map([[APP_SERVICES, services]]), }) await fireEvent.click(view.getByRole('button', { name: 'Poll' })) await fireEvent.input(view.getByRole('textbox', { name: 'Poll option 1' }), { target: { value: 'Guestbooks' }, }) await fireEvent.input(view.getByRole('textbox', { name: 'Poll option 2' }), { target: { value: 'Webrings' }, }) await fireEvent.click(view.getByRole('button', { name: 'Add option' })) await fireEvent.input(view.getByRole('textbox', { name: 'Poll option 3' }), { target: { value: 'Blink tags' }, }) await fireEvent.click(view.getByRole('checkbox', { name: 'Allow multiple choices' })) await fireEvent.change(view.getByLabelText('Keep open for'), { target: { value: '3600' }, }) await fireEvent.click(view.getByRole('button', { name: 'Post Entry' })) await waitFor(() => expect(postStatus).toHaveBeenCalledOnce()) expect(postStatus.mock.calls[0][1]).toMatchObject({ status: 'Which old web feature should return?', poll: { options: ['Guestbooks', 'Webrings', 'Blink tags'], expires_in: 3600, multiple: true, }, }) }) it.each([ ['Command+Enter', { metaKey: true }], ['Ctrl+Enter', { ctrlKey: true }], ])('submits with %s from the focused compose field', async (_label, modifier) => { const postStatus = vi.fn().mockResolvedValue(status()) const services = testServices({ session: session({ token: 'token', me: account(), signedIn: true }), endpoints: { postStatus }, }) const view = render(Composer, { props: { initialText: 'Keyboard-posted entry' }, context: new Map([[APP_SERVICES, services]]), }) await fireEvent.keyDown(view.getByRole('textbox', { name: 'Entry text' }), { key: 'Enter', ...modifier, }) await waitFor(() => expect(postStatus).toHaveBeenCalledOnce()) expect(postStatus.mock.calls[0][1].status).toBe('Keyboard-posted entry') }) it('uploads a pasted clipboard image and attaches it to the post', async () => { const pastedImage = new File(['image bytes'], 'pasted-image.png', { type: 'image/png' }) const uploadMedia = vi.fn().mockResolvedValue({ id: 'pasted-media', type: 'image', url: 'https://media.example/pasted-image.png', preview_url: 'https://media.example/pasted-image-preview.png', description: null, }) const postStatus = vi.fn().mockResolvedValue(status()) const services = testServices({ session: session({ token: 'token', me: account(), signedIn: true, instance: { title: 'Test server', version: '1.0.0', configuration: { statuses: { max_media_attachments: 4 } }, }, }), endpoints: { uploadMedia, postStatus }, }) const view = render(Composer, { props: { initialText: 'A pasted picture' }, context: new Map([[APP_SERVICES, services]]), }) const textarea = view.getByRole('textbox', { name: 'Entry text' }) await fireEvent.paste(textarea, { clipboardData: { items: [ { kind: 'file', type: 'image/png', getAsFile: () => pastedImage, }, ], files: [pastedImage], }, }) await waitFor(() => expect(uploadMedia).toHaveBeenCalledOnce()) expect(uploadMedia.mock.calls[0][1]).toBe(pastedImage) expect(view.container.querySelector('.composer-attachment img')).toHaveAttribute( 'src', 'https://media.example/pasted-image-preview.png', ) await fireEvent.click(view.getByRole('button', { name: 'Post Entry' })) await waitFor(() => expect(postStatus).toHaveBeenCalledOnce()) expect(postStatus.mock.calls[0][1].media_ids).toEqual(['pasted-media']) }) it('leaves ordinary text-only paste alone', async () => { const uploadMedia = vi.fn() const services = testServices({ session: session({ token: 'token', me: account(), signedIn: true }), endpoints: { uploadMedia }, }) const view = render(Composer, { context: new Map([[APP_SERVICES, services]]), }) const textarea = view.getByRole('textbox', { name: 'Entry text' }) const wasNotCancelled = await fireEvent.paste(textarea, { clipboardData: { items: [{ kind: 'string', type: 'text/plain', getAsFile: () => null }], files: [], }, }) expect(wasNotCancelled).toBe(true) expect(uploadMedia).not.toHaveBeenCalled() }) })