mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-05 08:16:22 +00:00
test: discard drafts without a question and once Undo is over, and take onSend's second argument, since the bin and Undo send changed
This commit is contained in:
@@ -3,6 +3,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import React from 'react';
|
||||
import { EmailComposer } from '../email-composer';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { UNDO_TOAST_DURATION } from '@/lib/email-action-toast';
|
||||
|
||||
// ─── Heavy component mocks (mirrors composer-close-guard.test.tsx) ────────────
|
||||
|
||||
@@ -129,6 +130,7 @@ vi.mock('@/stores/template-store', () => {
|
||||
|
||||
vi.mock('@/stores/toast-store', () => ({
|
||||
toast: { info: () => {}, error: () => {}, success: () => {} },
|
||||
useToastStore: { getState: () => ({ toasts: [] }) },
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/plugin-hooks', () => ({
|
||||
@@ -352,7 +354,7 @@ describe('a re-opened draft that lives in a group account', () => {
|
||||
fireEvent.click(screen.getAllByTestId('composer-send')[0]);
|
||||
await act(async () => { await vi.advanceTimersByTimeAsync(0); });
|
||||
|
||||
expect(onSend).toHaveBeenCalledWith(expect.objectContaining({ draftId: 'eaaaaab', draftAccountId: 'c' }));
|
||||
expect(onSend).toHaveBeenCalledWith(expect.objectContaining({ draftId: 'eaaaaab', draftAccountId: 'c' }), expect.anything());
|
||||
});
|
||||
|
||||
it('sends a version saved on the way without the group account', async () => {
|
||||
@@ -366,7 +368,7 @@ describe('a re-opened draft that lives in a group account', () => {
|
||||
await act(async () => { await vi.advanceTimersByTimeAsync(0); });
|
||||
|
||||
expect(createDraft.mock.calls[0][13]?.previousDraftAccountId).toBe('c');
|
||||
expect(onSend).toHaveBeenCalledWith(expect.objectContaining({ draftId: 'draft-v2', draftAccountId: undefined }));
|
||||
expect(onSend).toHaveBeenCalledWith(expect.objectContaining({ draftId: 'draft-v2', draftAccountId: undefined }), expect.anything());
|
||||
});
|
||||
|
||||
it('tells the host where the draft lives when it is discarded', async () => {
|
||||
@@ -376,9 +378,11 @@ describe('a re-opened draft that lives in a group account', () => {
|
||||
render(<EmailComposer initialData={GROUP_DRAFT} onDiscardDraft={onDiscardDraft} requestCloseRef={requestCloseRef} onClose={vi.fn()} />);
|
||||
|
||||
fireEvent.change(screen.getByDisplayValue('Quarterly numbers'), { target: { value: 'Quarterly numbers v2' } });
|
||||
// Closing keeps the draft now; Discard is the way to delete it.
|
||||
// Closing keeps the draft now; Discard is the way to delete it, once its Undo is over.
|
||||
fireEvent.click(screen.getAllByTestId('composer-discard')[0]);
|
||||
fireEvent.click(within(screen.getByRole('alertdialog')).getByText('discard'));
|
||||
expect(screen.queryByRole('alertdialog')).toBeNull();
|
||||
expect(onDiscardDraft).not.toHaveBeenCalled();
|
||||
await act(async () => { await vi.advanceTimersByTimeAsync(UNDO_TOAST_DURATION); });
|
||||
|
||||
expect(onDiscardDraft).toHaveBeenCalledWith('eaaaaab', { accountId: 'c', localAccountId: undefined });
|
||||
});
|
||||
@@ -394,9 +398,10 @@ describe('a re-opened draft that lives in a group account', () => {
|
||||
expect(createDraft.mock.calls[0][13]?.previousDraftAccountId).toBeUndefined();
|
||||
|
||||
fireEvent.change(screen.getByDisplayValue('Quarterly numbers v2'), { target: { value: 'Quarterly numbers v3' } });
|
||||
// Closing keeps the draft now; Discard is the way to delete it.
|
||||
// Closing keeps the draft now; Discard is the way to delete it, once its Undo is over.
|
||||
fireEvent.click(screen.getAllByTestId('composer-discard')[0]);
|
||||
fireEvent.click(within(screen.getByRole('alertdialog')).getByText('discard'));
|
||||
expect(onDiscardDraft).not.toHaveBeenCalled();
|
||||
await act(async () => { await vi.advanceTimersByTimeAsync(UNDO_TOAST_DURATION); });
|
||||
expect(onDiscardDraft).toHaveBeenCalledWith('draft-v2', { accountId: undefined, localAccountId: undefined });
|
||||
});
|
||||
});
|
||||
@@ -450,6 +455,6 @@ describe('send while a draft save is in flight', () => {
|
||||
expect(createDraft).toHaveBeenCalledTimes(2);
|
||||
expect(createDraft.mock.calls[1][7]).toBe('draft-v2');
|
||||
expect(onSend).toHaveBeenCalledTimes(1);
|
||||
expect(onSend).toHaveBeenCalledWith(expect.objectContaining({ draftId: 'draft-v3', subject: 'Quarterly numbers v3' }));
|
||||
expect(onSend).toHaveBeenCalledWith(expect.objectContaining({ draftId: 'draft-v3', subject: 'Quarterly numbers v3' }), expect.anything());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { render, screen, fireEvent, act, within } from '@testing-library/react';
|
||||
import { render, screen, fireEvent, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import React from 'react';
|
||||
import { EmailComposer, type ComposerDraftData } from '../email-composer';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { UNDO_TOAST_DURATION } from '@/lib/email-action-toast';
|
||||
|
||||
const offlineQueue = vi.hoisted(() => ({ noteDraftReplacedElsewhere: vi.fn(async () => {}) }));
|
||||
vi.mock('@/lib/offline/queue', async (importOriginal) => ({
|
||||
@@ -143,6 +144,7 @@ vi.mock('@/stores/template-store', () => {
|
||||
|
||||
vi.mock('@/stores/toast-store', () => ({
|
||||
toast: { info: () => {}, error: () => {}, success: () => {} },
|
||||
useToastStore: { getState: () => ({ toasts: [] }) },
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/plugin-hooks', () => ({
|
||||
@@ -254,10 +256,12 @@ describe('a draft and the login it was saved with', () => {
|
||||
await act(async () => { await vi.advanceTimersByTimeAsync(2500); });
|
||||
},
|
||||
switchFrom: (identityId: string) => fireEvent.change(screen.getByTestId('composer-from'), { target: { value: identityId } }),
|
||||
discard: () => {
|
||||
// The Discard button asks even when the draft is saved.
|
||||
discard: async (onDiscardDraft: ReturnType<typeof vi.fn>) => {
|
||||
// The bin asks nothing; the draft goes once its Undo is over.
|
||||
fireEvent.click(screen.getByText('discard'));
|
||||
fireEvent.click(within(screen.getByRole('alertdialog')).getByText('discard'));
|
||||
expect(screen.queryByRole('alertdialog')).toBeNull();
|
||||
expect(onDiscardDraft).not.toHaveBeenCalled();
|
||||
await act(async () => { await vi.advanceTimersByTimeAsync(UNDO_TOAST_DURATION); });
|
||||
},
|
||||
send: async () => {
|
||||
fireEvent.click(screen.getAllByTestId('composer-send')[0]);
|
||||
@@ -274,7 +278,7 @@ describe('a draft and the login it was saved with', () => {
|
||||
expect(clientA.createDraft).toHaveBeenCalledTimes(1);
|
||||
composer.switchFrom('login-b::id-b');
|
||||
await act(async () => { await vi.advanceTimersByTimeAsync(2500); });
|
||||
composer.discard();
|
||||
await composer.discard(onDiscardDraft);
|
||||
|
||||
expect(onDiscardDraft).toHaveBeenCalledWith('draft-a2', { accountId: undefined, localAccountId: 'login-a' });
|
||||
});
|
||||
@@ -295,7 +299,7 @@ describe('a draft and the login it was saved with', () => {
|
||||
expect(clientA.deleteEmail).toHaveBeenCalledWith('draft-a2', undefined);
|
||||
expect(clientB.deleteEmail).not.toHaveBeenCalled();
|
||||
|
||||
composer.discard();
|
||||
await composer.discard(onDiscardDraft);
|
||||
expect(onDiscardDraft).toHaveBeenCalledWith('draft-b2', { accountId: undefined, localAccountId: 'login-b' });
|
||||
});
|
||||
|
||||
@@ -330,7 +334,7 @@ describe('a draft and the login it was saved with', () => {
|
||||
composer.switchFrom('login-b::id-b');
|
||||
await composer.send();
|
||||
|
||||
expect(onSend).toHaveBeenCalledWith(expect.objectContaining({ localAccountId: 'login-b', draftId: undefined, draftAccountId: undefined }));
|
||||
expect(onSend).toHaveBeenCalledWith(expect.objectContaining({ localAccountId: 'login-b', draftId: undefined, draftAccountId: undefined }), expect.anything());
|
||||
expect(clientA.deleteEmail).toHaveBeenCalledWith('draft-a2', undefined);
|
||||
expect(clientB.deleteEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -373,16 +377,16 @@ describe('a draft and the login it was saved with', () => {
|
||||
expect(clientB.deleteEmail).toHaveBeenCalledWith('draft-b1', 'c');
|
||||
expect(clientA.deleteEmail).not.toHaveBeenCalled();
|
||||
|
||||
composer.discard();
|
||||
await composer.discard(onDiscardDraft);
|
||||
expect(onDiscardDraft).toHaveBeenCalledWith('draft-a2', { accountId: undefined, localAccountId: 'login-a' });
|
||||
});
|
||||
|
||||
it('discards an unsaved draft opened from another login through that login and account', () => {
|
||||
it('discards an unsaved draft opened from another login through that login and account', async () => {
|
||||
const onDiscardDraft = vi.fn();
|
||||
const composer = open({ ...DRAFT, draftId: 'draft-b1', draftAccountId: 'c', draftLocalAccountId: 'login-b' }, { onDiscardDraft });
|
||||
|
||||
fireEvent.change(screen.getByDisplayValue('Hello'), { target: { value: 'Hello 2' } });
|
||||
composer.discard();
|
||||
await composer.discard(onDiscardDraft);
|
||||
|
||||
expect(onDiscardDraft).toHaveBeenCalledWith('draft-b1', { accountId: 'c', localAccountId: 'login-b' });
|
||||
});
|
||||
|
||||
@@ -230,8 +230,9 @@ describe('Save and close with attachment uploads', () => {
|
||||
});
|
||||
it('can discard during the wait without a later save or close follow-up', async () => {
|
||||
const c = await openComposer(); await c.add(file()); await c.chooseSave();
|
||||
// The bin asks nothing: the composer goes at once, with Undo on a toast.
|
||||
fireEvent.click(screen.getAllByTestId('composer-discard')[0]);
|
||||
fireEvent.click(within(await screen.findByRole('alertdialog')).getByRole('button', { name: /^Discard$/ }));
|
||||
expect(screen.queryByRole('alertdialog')).toBeNull();
|
||||
await complete(0); await new Promise(resolve => setTimeout(resolve, 200));
|
||||
expect(creations).toEqual([]); expect(c.closed).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user