mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: keep the open message when / focuses Mountain View's search field in the top bar
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Email, Mailbox } from '@/lib/jmap/types';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
@@ -22,6 +22,7 @@ vi.mock('@/components/layout/mv-top-bar', () => ({
|
||||
<>
|
||||
<button onClick={() => search.onSubmit('invoice')}>stub search</button>
|
||||
<button onClick={() => search.onSelectContact({ email: 'bob@example.org' }, 'from')}>stub contact</button>
|
||||
<input aria-label="stub search field" data-search-input />
|
||||
</>
|
||||
),
|
||||
}));
|
||||
@@ -129,4 +130,14 @@ describe('searching in Mountain View while a message is open', () => {
|
||||
await waitFor(() => expect(advancedSearch).toHaveBeenCalled());
|
||||
expect(selectedId()).toBeNull();
|
||||
});
|
||||
|
||||
it('leaves the message open for the / shortcut, whose field is in the top bar', async () => {
|
||||
render(<MailApp />);
|
||||
await screen.findByText('message viewer');
|
||||
act(() => { fireEvent.keyDown(window, { key: '/' }); });
|
||||
await waitFor(() => expect(document.activeElement).toBe(screen.getByLabelText('stub search field')));
|
||||
// Long enough for a go-back that runs after the field is focused.
|
||||
await act(async () => { await new Promise((resolve) => setTimeout(resolve, 50)); });
|
||||
expect(selectedId()).toBe('m1');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ vi.mock('@/components/layout/mobile-header', () => ({ MobileHeader: () => null }
|
||||
vi.mock('@/components/layout/mv-top-bar', () => ({ MvTopBar: () => null }));
|
||||
vi.mock('@/components/layout/mv-side-panel', () => ({ MvSidePanel: () => null }));
|
||||
vi.mock('@/components/email/message-list-tabs', () => ({ useMessageListPanelProps: (id: string) => ({ id }), MessageListTabs: () => null }));
|
||||
vi.mock('@/components/search/search-box', () => ({ SearchBox: () => null }));
|
||||
vi.mock('@/components/search/search-box', () => ({ SearchBox: () => <input aria-label="stub search field" data-search-input /> }));
|
||||
vi.mock('@/components/tour/tour-provider', () => ({ useTour: () => ({ activeTour: null, registerStep: vi.fn() }) }));
|
||||
vi.mock('@/components/plugins/plugin-slot', () => ({ PluginSlot: () => null }));
|
||||
vi.mock('@/hooks/use-plugin-slot-offers', () => ({ usePluginSlotOffers: () => [] }));
|
||||
@@ -288,6 +288,17 @@ describe('A list collapsed under an open message (focused layout)', () => {
|
||||
act(() => { fireEvent.keyDown(window, { key: '/' }); });
|
||||
await waitFor(() => expect(selectedId()).toBeNull());
|
||||
await waitFor(() => expect(list()).not.toHaveAttribute('inert'));
|
||||
await waitFor(() => expect(document.activeElement).toBe(screen.getByLabelText('stub search field')));
|
||||
});
|
||||
|
||||
it('keeps the selection for the / shortcut while a linked message is still arriving', async () => {
|
||||
// The mailboxes never arrive, so the link stays unapplied and the list collapsed for it.
|
||||
useEmailStore.setState({ fetchMailboxes: vi.fn(() => new Promise(() => {})) } as never);
|
||||
render(<MailApp linkSegments={['message', 'm2']} />);
|
||||
expect(list()).toHaveAttribute('inert');
|
||||
act(() => { fireEvent.keyDown(window, { key: '/' }); });
|
||||
await act(async () => { await new Promise((resolve) => setTimeout(resolve, 50)); });
|
||||
expect(selectedId()).toBe('m1');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1125,15 +1125,20 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
},
|
||||
onFocusSearch: () => {
|
||||
if (isScheduledView) return;
|
||||
const findSearchInput = () => document.querySelector<HTMLInputElement>('[data-search-input]');
|
||||
const focusSearch = () => {
|
||||
const searchInput = document.querySelector('[data-search-input]') as HTMLInputElement;
|
||||
const searchInput = findSearchInput();
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
searchInput.select();
|
||||
}
|
||||
};
|
||||
// The field is in the list, which an open message can have collapsed.
|
||||
if (shouldCollapseListPane) {
|
||||
// The field can be in the list, which an open message has collapsed, and
|
||||
// that message has to close first. Mountain View's is in the top bar.
|
||||
if (findSearchInput()?.closest('[inert]')) {
|
||||
// A linked message still arriving holds the list collapsed: nothing to
|
||||
// go back to yet.
|
||||
if (deepLinkPane !== null) return;
|
||||
handleMobileBack();
|
||||
requestAnimationFrame(() => requestAnimationFrame(focusSearch));
|
||||
} else {
|
||||
@@ -1220,7 +1225,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}), [activeEmails, selectedEmail, client, selectedMailbox, isMobile, isTablet, selectedEmailKeys, mailboxes, isScheduledView, mvShell, cursorOnly, showCrossStarred, isUnifiedView, unifiedRole, archiveActionEnabled]);
|
||||
}), [activeEmails, selectedEmail, client, selectedMailbox, isMobile, isTablet, selectedEmailKeys, mailboxes, isScheduledView, mvShell, cursorOnly, showCrossStarred, isUnifiedView, unifiedRole, archiveActionEnabled, deepLinkPane]);
|
||||
|
||||
// Initialize keyboard shortcuts
|
||||
// Mountain View keeps the ticked rows while a message is read (#1231). The
|
||||
|
||||
Reference in New Issue
Block a user