mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
262 lines
6.8 KiB
TypeScript
262 lines
6.8 KiB
TypeScript
import React from 'react';
|
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const PUBLIC_CHANNEL_KEY = '8B3387E9C5CDEA6AC9E5EDBAA115CD72';
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
api: {
|
|
getRadioConfig: vi.fn(),
|
|
getSettings: vi.fn(),
|
|
getUndecryptedPacketCount: vi.fn(),
|
|
getChannels: vi.fn(),
|
|
getContacts: vi.fn(),
|
|
migratePreferences: vi.fn(),
|
|
},
|
|
useConversationMessagesCalls: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('../api', () => ({
|
|
api: mocks.api,
|
|
}));
|
|
|
|
vi.mock('../useWebSocket', () => ({
|
|
useWebSocket: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('../hooks', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('../hooks')>();
|
|
return {
|
|
...actual,
|
|
useConversationMessages: (activeConversation: unknown, targetMessageId: number | null) => {
|
|
mocks.useConversationMessagesCalls(activeConversation, targetMessageId);
|
|
return {
|
|
messages: [],
|
|
messagesLoading: false,
|
|
loadingOlder: false,
|
|
hasOlderMessages: false,
|
|
hasNewerMessages: false,
|
|
loadingNewer: false,
|
|
hasNewerMessagesRef: { current: false },
|
|
setMessages: vi.fn(),
|
|
fetchOlderMessages: vi.fn(async () => {}),
|
|
fetchNewerMessages: vi.fn(async () => {}),
|
|
jumpToBottom: vi.fn(),
|
|
addMessageIfNew: vi.fn(),
|
|
updateMessageAck: vi.fn(),
|
|
triggerReconcile: vi.fn(),
|
|
};
|
|
},
|
|
useUnreadCounts: () => ({
|
|
unreadCounts: {},
|
|
mentions: {},
|
|
lastMessageTimes: {},
|
|
incrementUnread: vi.fn(),
|
|
markAllRead: vi.fn(),
|
|
trackNewMessage: vi.fn(),
|
|
refreshUnreads: vi.fn(),
|
|
}),
|
|
getMessageContentKey: () => 'content-key',
|
|
};
|
|
});
|
|
|
|
vi.mock('../messageCache', () => ({
|
|
addMessage: vi.fn(),
|
|
updateAck: vi.fn(),
|
|
remove: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('../components/StatusBar', () => ({
|
|
StatusBar: () => <div data-testid="status-bar" />,
|
|
}));
|
|
|
|
vi.mock('../components/Sidebar', () => ({
|
|
Sidebar: ({
|
|
onSelectConversation,
|
|
activeConversation,
|
|
}: {
|
|
onSelectConversation: (conv: { type: 'search' | 'channel'; id: string; name: string }) => void;
|
|
activeConversation: { type: string; id: string } | null;
|
|
}) => (
|
|
<div>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
onSelectConversation({
|
|
type: 'search',
|
|
id: 'search',
|
|
name: 'Message Search',
|
|
})
|
|
}
|
|
>
|
|
Open Search
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
onSelectConversation({
|
|
type: 'channel',
|
|
id: PUBLIC_CHANNEL_KEY,
|
|
name: 'Public',
|
|
})
|
|
}
|
|
>
|
|
Open Public
|
|
</button>
|
|
<div data-testid="active-conversation">
|
|
{activeConversation ? `${activeConversation.type}:${activeConversation.id}` : 'none'}
|
|
</div>
|
|
</div>
|
|
),
|
|
}));
|
|
|
|
vi.mock('../components/ChatHeader', () => ({
|
|
ChatHeader: () => <div data-testid="chat-header" />,
|
|
}));
|
|
|
|
vi.mock('../components/MessageList', () => ({
|
|
MessageList: () => <div data-testid="message-list" />,
|
|
}));
|
|
|
|
vi.mock('../components/MessageInput', () => ({
|
|
MessageInput: React.forwardRef((_props, ref) => {
|
|
React.useImperativeHandle(ref, () => ({ appendText: vi.fn() }));
|
|
return <div data-testid="message-input" />;
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../components/NewMessageModal', () => ({
|
|
NewMessageModal: () => null,
|
|
}));
|
|
|
|
vi.mock('../components/SearchView', () => ({
|
|
SearchView: ({
|
|
onNavigateToMessage,
|
|
}: {
|
|
onNavigateToMessage: (target: {
|
|
id: number;
|
|
type: 'CHAN' | 'PRIV';
|
|
conversation_key: string;
|
|
conversation_name: string;
|
|
}) => void;
|
|
}) => (
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
onNavigateToMessage({
|
|
id: 321,
|
|
type: 'CHAN',
|
|
conversation_key: PUBLIC_CHANNEL_KEY,
|
|
conversation_name: 'Public',
|
|
})
|
|
}
|
|
>
|
|
Jump Result
|
|
</button>
|
|
),
|
|
}));
|
|
|
|
vi.mock('../components/SettingsModal', () => ({
|
|
SettingsModal: () => null,
|
|
}));
|
|
|
|
vi.mock('../components/RawPacketList', () => ({
|
|
RawPacketList: () => null,
|
|
}));
|
|
|
|
vi.mock('../components/ContactInfoPane', () => ({
|
|
ContactInfoPane: () => null,
|
|
}));
|
|
|
|
vi.mock('../components/ChannelInfoPane', () => ({
|
|
ChannelInfoPane: () => null,
|
|
}));
|
|
|
|
vi.mock('../components/ui/sheet', () => ({
|
|
Sheet: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
SheetContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
SheetHeader: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
SheetTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
SheetDescription: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
}));
|
|
|
|
vi.mock('../components/ui/sonner', () => ({
|
|
Toaster: () => null,
|
|
toast: {
|
|
success: vi.fn(),
|
|
error: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
import { App } from '../App';
|
|
|
|
describe('App search jump target handling', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
|
|
mocks.api.getRadioConfig.mockResolvedValue({
|
|
public_key: 'aa'.repeat(32),
|
|
name: 'TestNode',
|
|
lat: 0,
|
|
lon: 0,
|
|
tx_power: 17,
|
|
max_tx_power: 22,
|
|
radio: { freq: 910.525, bw: 62.5, sf: 7, cr: 5 },
|
|
path_hash_mode: 0,
|
|
path_hash_mode_supported: false,
|
|
});
|
|
mocks.api.getSettings.mockResolvedValue({
|
|
max_radio_contacts: 200,
|
|
favorites: [],
|
|
auto_decrypt_dm_on_advert: false,
|
|
sidebar_sort_order: 'recent',
|
|
last_message_times: {},
|
|
preferences_migrated: true,
|
|
advert_interval: 0,
|
|
last_advert_time: 0,
|
|
});
|
|
mocks.api.getUndecryptedPacketCount.mockResolvedValue({ count: 0 });
|
|
mocks.api.getChannels.mockResolvedValue([
|
|
{
|
|
key: PUBLIC_CHANNEL_KEY,
|
|
name: 'Public',
|
|
is_hashtag: false,
|
|
on_radio: false,
|
|
last_read_at: null,
|
|
},
|
|
]);
|
|
mocks.api.getContacts.mockResolvedValue([]);
|
|
});
|
|
|
|
it('clears jump target when user selects a non-search conversation', async () => {
|
|
render(<App />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getAllByText('Open Search').length).toBeGreaterThan(0);
|
|
});
|
|
|
|
fireEvent.click(screen.getAllByText('Open Search')[0]);
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Jump Result')).toBeInTheDocument();
|
|
});
|
|
|
|
fireEvent.click(screen.getByText('Jump Result'));
|
|
|
|
await waitFor(() => {
|
|
expect(mocks.useConversationMessagesCalls.mock.calls.some((call) => call[1] === 321)).toBe(
|
|
true
|
|
);
|
|
});
|
|
|
|
fireEvent.click(screen.getAllByText('Open Public')[0]);
|
|
|
|
await waitFor(() => {
|
|
const lastCall =
|
|
mocks.useConversationMessagesCalls.mock.calls[
|
|
mocks.useConversationMessagesCalls.mock.calls.length - 1
|
|
];
|
|
expect(lastCall?.[1]).toBeNull();
|
|
});
|
|
});
|
|
});
|