Files
Remote-Terminal-for-MeshCore/frontend/src/test/useAppShellProps.test.ts

226 lines
6.4 KiB
TypeScript

import { act, renderHook } from '@testing-library/react';
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { useAppShellProps } from '../hooks/useAppShellProps';
import type {
AppSettings,
Channel,
Contact,
Conversation,
Favorite,
HealthStatus,
Message,
RadioConfig,
RawPacket,
} from '../types';
const mocks = vi.hoisted(() => ({
api: {
createChannel: vi.fn(),
getChannels: vi.fn(),
decryptHistoricalPackets: vi.fn(),
},
}));
vi.mock('../api', () => ({
api: mocks.api,
}));
const publicChannel: Channel = {
key: '8B3387E9C5CDEA6AC9E5EDBAA115CD72',
name: 'Public',
is_hashtag: false,
on_radio: false,
last_read_at: null,
};
const config: RadioConfig = {
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,
};
const health: HealthStatus = {
status: 'connected',
radio_connected: true,
radio_initializing: false,
connection_info: null,
database_size_mb: 1,
oldest_undecrypted_timestamp: null,
fanout_statuses: {},
bots_disabled: false,
};
const appSettings: AppSettings = {
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,
flood_scope: '',
blocked_keys: [],
blocked_names: [],
};
function createArgs(overrides: Partial<Parameters<typeof useAppShellProps>[0]> = {}) {
const activeConversation: Conversation = {
type: 'channel',
id: publicChannel.key,
name: publicChannel.name,
};
const contacts: Contact[] = [];
const channels: Channel[] = [publicChannel];
const rawPackets: RawPacket[] = [];
const favorites: Favorite[] = [];
const messages: Message[] = [];
return {
contacts,
channels,
rawPackets,
undecryptedCount: 0,
activeConversation,
config,
health,
favorites,
appSettings,
unreadCounts: {},
mentions: {},
lastMessageTimes: {},
showCracker: false,
crackerRunning: false,
messageInputRef: { current: null },
targetMessageId: null,
infoPaneContactKey: null,
infoPaneFromChannel: false,
infoPaneChannelKey: null,
messages,
messagesLoading: false,
loadingOlder: false,
hasOlderMessages: false,
hasNewerMessages: false,
loadingNewer: false,
handleOpenNewMessage: vi.fn(),
handleToggleCracker: vi.fn(),
markAllRead: vi.fn(async () => {}),
handleSortOrderChange: vi.fn(async () => {}),
handleSelectConversationWithTargetReset: vi.fn(),
handleNavigateToMessage: vi.fn(),
handleSaveConfig: vi.fn(async () => {}),
handleSaveAppSettings: vi.fn(async () => {}),
handleSetPrivateKey: vi.fn(async () => {}),
handleReboot: vi.fn(async () => {}),
handleAdvertise: vi.fn(async () => {}),
handleHealthRefresh: vi.fn(async () => {}),
fetchAppSettings: vi.fn(async () => {}),
setChannels: vi.fn(),
fetchUndecryptedCount: vi.fn(async () => {}),
handleCreateContact: vi.fn(async () => {}),
handleCreateChannel: vi.fn(async () => {}),
handleCreateHashtagChannel: vi.fn(async () => {}),
handleDeleteContact: vi.fn(async () => {}),
handleDeleteChannel: vi.fn(async () => {}),
handleToggleFavorite: vi.fn(async () => {}),
handleSetChannelFloodScopeOverride: vi.fn(async () => {}),
handleOpenContactInfo: vi.fn(),
handleOpenChannelInfo: vi.fn(),
handleCloseContactInfo: vi.fn(),
handleCloseChannelInfo: vi.fn(),
handleSenderClick: vi.fn(),
handleResendChannelMessage: vi.fn(async () => {}),
handleTrace: vi.fn(async () => {}),
handleSendMessage: vi.fn(async () => {}),
fetchOlderMessages: vi.fn(async () => {}),
fetchNewerMessages: vi.fn(async () => {}),
jumpToBottom: vi.fn(),
setTargetMessageId: vi.fn(),
handleNavigateToChannel: vi.fn(),
handleBlockKey: vi.fn(async () => {}),
handleBlockName: vi.fn(async () => {}),
...overrides,
};
}
describe('useAppShellProps', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('creates a cracked channel, refreshes channels, decrypts history, and refreshes undecrypted count', async () => {
mocks.api.createChannel.mockResolvedValue({
key: '11'.repeat(16),
name: 'Found',
is_hashtag: false,
});
mocks.api.getChannels.mockResolvedValue([
publicChannel,
{ ...publicChannel, key: '11'.repeat(16), name: 'Found' },
]);
mocks.api.decryptHistoricalPackets.mockResolvedValue({ decrypted_count: 4 });
const args = createArgs();
const { result } = renderHook(() => useAppShellProps(args));
await act(async () => {
await result.current.crackerProps.onChannelCreate('Found', '11'.repeat(16));
});
expect(mocks.api.createChannel).toHaveBeenCalledWith('Found', '11'.repeat(16));
expect(mocks.api.getChannels).toHaveBeenCalledTimes(1);
expect(args.setChannels).toHaveBeenCalledWith([
publicChannel,
{ ...publicChannel, key: '11'.repeat(16), name: 'Found' },
]);
expect(mocks.api.decryptHistoricalPackets).toHaveBeenCalledWith({
key_type: 'channel',
channel_key: '11'.repeat(16),
});
expect(args.fetchUndecryptedCount).toHaveBeenCalledTimes(1);
});
it('does not fail cracked channel creation when undecrypted count refresh rejects', async () => {
mocks.api.createChannel.mockResolvedValue({
key: '22'.repeat(16),
name: 'Found',
is_hashtag: false,
});
mocks.api.getChannels.mockResolvedValue([
publicChannel,
{ ...publicChannel, key: '22'.repeat(16), name: 'Found' },
]);
mocks.api.decryptHistoricalPackets.mockResolvedValue({ decrypted_count: 4 });
const args = createArgs({
fetchUndecryptedCount: vi.fn(async () => {
throw new Error('refresh failed');
}),
});
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
const { result } = renderHook(() => useAppShellProps(args));
await act(async () => {
await result.current.crackerProps.onChannelCreate('Found', '22'.repeat(16));
});
expect(mocks.api.decryptHistoricalPackets).toHaveBeenCalledWith({
key_type: 'channel',
channel_key: '22'.repeat(16),
});
expect(consoleError).toHaveBeenCalledWith(
'Failed to refresh undecrypted count after cracked channel create:',
expect.any(Error)
);
consoleError.mockRestore();
});
});