mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
extract app shell prop assembly
This commit is contained in:
@@ -42,6 +42,7 @@ frontend/src/
|
||||
│ ├── useUnreadCounts.ts # Unread counters, mentions, recent-sort timestamps
|
||||
│ ├── useRealtimeAppState.ts # WebSocket event application and reconnect recovery
|
||||
│ ├── useAppShell.ts # App-shell view state (settings/sidebar/modals/cracker)
|
||||
│ ├── useAppShellProps.ts # AppShell child prop assembly + cracker create/decrypt flow
|
||||
│ ├── useRepeaterDashboard.ts # Repeater dashboard state (login, panes, console, retries)
|
||||
│ ├── useRadioControl.ts # Radio health/config state, reconnection
|
||||
│ ├── useAppSettings.ts # Settings, favorites, preferences migration
|
||||
@@ -147,6 +148,7 @@ frontend/src/
|
||||
├── useConversationMessages.test.ts
|
||||
├── useConversationMessages.race.test.ts
|
||||
├── useConversationNavigation.test.ts
|
||||
├── useAppShellProps.test.ts
|
||||
├── useAppShell.test.ts
|
||||
├── useRepeaterDashboard.test.ts
|
||||
├── useContactsAndChannels.test.ts
|
||||
@@ -178,6 +180,7 @@ High-level state is delegated to hooks:
|
||||
- `useConversationRouter`: URL hash → active conversation routing
|
||||
- `useConversationNavigation`: search target, conversation selection reset, and info-pane state
|
||||
- `useConversationActions`: send/resend/trace/block handlers and channel override updates
|
||||
- `useAppShellProps`: assembles the prop bundles passed into `AppShell` children, including the cracker-created-channel historical decrypt flow
|
||||
- `useConversationMessages`: dedup/update helpers and pending ACK buffering
|
||||
- `useConversationTimeline`: conversation switch loading, cache restore, jump-target loading, pagination, reconcile
|
||||
- `useUnreadCounts`: unread counters, mention tracking, recent-sort timestamps
|
||||
|
||||
@@ -4,6 +4,7 @@ import { takePrefetchOrFetch } from './prefetch';
|
||||
import { useWebSocket } from './useWebSocket';
|
||||
import {
|
||||
useAppShell,
|
||||
useAppShellProps,
|
||||
useUnreadCounts,
|
||||
useConversationMessages,
|
||||
useRadioControl,
|
||||
@@ -222,6 +223,81 @@ export function App() {
|
||||
messageInputRef,
|
||||
});
|
||||
|
||||
const {
|
||||
statusProps,
|
||||
sidebarProps,
|
||||
conversationPaneProps,
|
||||
searchProps,
|
||||
settingsProps,
|
||||
crackerProps,
|
||||
newMessageModalProps,
|
||||
contactInfoPaneProps,
|
||||
channelInfoPaneProps,
|
||||
} = useAppShellProps({
|
||||
contacts,
|
||||
channels,
|
||||
rawPackets,
|
||||
undecryptedCount,
|
||||
activeConversation,
|
||||
config,
|
||||
health,
|
||||
favorites,
|
||||
appSettings,
|
||||
unreadCounts,
|
||||
mentions,
|
||||
lastMessageTimes,
|
||||
showCracker,
|
||||
crackerRunning,
|
||||
messageInputRef,
|
||||
targetMessageId,
|
||||
infoPaneContactKey,
|
||||
infoPaneFromChannel,
|
||||
infoPaneChannelKey,
|
||||
messages,
|
||||
messagesLoading,
|
||||
loadingOlder,
|
||||
hasOlderMessages,
|
||||
hasNewerMessages,
|
||||
loadingNewer,
|
||||
handleOpenNewMessage,
|
||||
handleToggleCracker,
|
||||
markAllRead,
|
||||
handleSortOrderChange,
|
||||
handleSelectConversationWithTargetReset,
|
||||
handleNavigateToMessage,
|
||||
handleSaveConfig,
|
||||
handleSaveAppSettings,
|
||||
handleSetPrivateKey,
|
||||
handleReboot,
|
||||
handleAdvertise,
|
||||
handleHealthRefresh,
|
||||
fetchAppSettings,
|
||||
setChannels,
|
||||
fetchUndecryptedCount,
|
||||
handleCreateContact,
|
||||
handleCreateChannel,
|
||||
handleCreateHashtagChannel,
|
||||
handleDeleteContact,
|
||||
handleDeleteChannel,
|
||||
handleToggleFavorite,
|
||||
handleSetChannelFloodScopeOverride,
|
||||
handleOpenContactInfo,
|
||||
handleOpenChannelInfo,
|
||||
handleCloseContactInfo,
|
||||
handleCloseChannelInfo,
|
||||
handleSenderClick,
|
||||
handleResendChannelMessage,
|
||||
handleTrace,
|
||||
handleSendMessage,
|
||||
fetchOlderMessages,
|
||||
fetchNewerMessages,
|
||||
jumpToBottom,
|
||||
setTargetMessageId,
|
||||
handleNavigateToChannel,
|
||||
handleBlockKey,
|
||||
handleBlockName,
|
||||
});
|
||||
|
||||
// Connect to WebSocket
|
||||
useWebSocket(wsHandlers);
|
||||
|
||||
@@ -266,119 +342,15 @@ export function App() {
|
||||
onCloseSettingsView={handleCloseSettingsView}
|
||||
onCloseNewMessage={handleCloseNewMessage}
|
||||
onLocalLabelChange={setLocalLabel}
|
||||
statusProps={{ health, config }}
|
||||
sidebarProps={{
|
||||
contacts,
|
||||
channels,
|
||||
activeConversation,
|
||||
onSelectConversation: handleSelectConversationWithTargetReset,
|
||||
onNewMessage: handleOpenNewMessage,
|
||||
lastMessageTimes,
|
||||
unreadCounts,
|
||||
mentions,
|
||||
showCracker,
|
||||
crackerRunning,
|
||||
onToggleCracker: handleToggleCracker,
|
||||
onMarkAllRead: markAllRead,
|
||||
favorites,
|
||||
sortOrder: appSettings?.sidebar_sort_order ?? 'recent',
|
||||
onSortOrderChange: handleSortOrderChange,
|
||||
}}
|
||||
conversationPaneProps={{
|
||||
activeConversation,
|
||||
contacts,
|
||||
channels,
|
||||
rawPackets,
|
||||
config,
|
||||
health,
|
||||
favorites,
|
||||
messages,
|
||||
messagesLoading,
|
||||
loadingOlder,
|
||||
hasOlderMessages,
|
||||
targetMessageId,
|
||||
hasNewerMessages,
|
||||
loadingNewer,
|
||||
messageInputRef,
|
||||
onTrace: handleTrace,
|
||||
onToggleFavorite: handleToggleFavorite,
|
||||
onDeleteContact: handleDeleteContact,
|
||||
onDeleteChannel: handleDeleteChannel,
|
||||
onSetChannelFloodScopeOverride: handleSetChannelFloodScopeOverride,
|
||||
onOpenContactInfo: handleOpenContactInfo,
|
||||
onOpenChannelInfo: handleOpenChannelInfo,
|
||||
onSenderClick: handleSenderClick,
|
||||
onLoadOlder: fetchOlderMessages,
|
||||
onResendChannelMessage: handleResendChannelMessage,
|
||||
onTargetReached: () => setTargetMessageId(null),
|
||||
onLoadNewer: fetchNewerMessages,
|
||||
onJumpToBottom: jumpToBottom,
|
||||
onSendMessage: handleSendMessage,
|
||||
}}
|
||||
searchProps={{
|
||||
contacts,
|
||||
channels,
|
||||
onNavigateToMessage: handleNavigateToMessage,
|
||||
}}
|
||||
settingsProps={{
|
||||
config,
|
||||
health,
|
||||
appSettings,
|
||||
onSave: handleSaveConfig,
|
||||
onSaveAppSettings: handleSaveAppSettings,
|
||||
onSetPrivateKey: handleSetPrivateKey,
|
||||
onReboot: handleReboot,
|
||||
onAdvertise: handleAdvertise,
|
||||
onHealthRefresh: handleHealthRefresh,
|
||||
onRefreshAppSettings: fetchAppSettings,
|
||||
blockedKeys: appSettings?.blocked_keys,
|
||||
blockedNames: appSettings?.blocked_names,
|
||||
onToggleBlockedKey: handleBlockKey,
|
||||
onToggleBlockedName: handleBlockName,
|
||||
}}
|
||||
crackerProps={{
|
||||
packets: rawPackets,
|
||||
channels,
|
||||
onChannelCreate: async (name, key) => {
|
||||
const created = await api.createChannel(name, key);
|
||||
const data = await api.getChannels();
|
||||
setChannels(data);
|
||||
await api.decryptHistoricalPackets({
|
||||
key_type: 'channel',
|
||||
channel_key: created.key,
|
||||
});
|
||||
fetchUndecryptedCount();
|
||||
},
|
||||
}}
|
||||
newMessageModalProps={{
|
||||
contacts,
|
||||
undecryptedCount,
|
||||
onSelectConversation: handleSelectConversationWithTargetReset,
|
||||
onCreateContact: handleCreateContact,
|
||||
onCreateChannel: handleCreateChannel,
|
||||
onCreateHashtagChannel: handleCreateHashtagChannel,
|
||||
}}
|
||||
contactInfoPaneProps={{
|
||||
contactKey: infoPaneContactKey,
|
||||
fromChannel: infoPaneFromChannel,
|
||||
onClose: handleCloseContactInfo,
|
||||
contacts,
|
||||
config,
|
||||
favorites,
|
||||
onToggleFavorite: handleToggleFavorite,
|
||||
onNavigateToChannel: handleNavigateToChannel,
|
||||
blockedKeys: appSettings?.blocked_keys,
|
||||
blockedNames: appSettings?.blocked_names,
|
||||
onToggleBlockedKey: handleBlockKey,
|
||||
onToggleBlockedName: handleBlockName,
|
||||
}}
|
||||
channelInfoPaneProps={{
|
||||
channelKey: infoPaneChannelKey,
|
||||
onClose: handleCloseChannelInfo,
|
||||
channels,
|
||||
favorites,
|
||||
onToggleFavorite: handleToggleFavorite,
|
||||
}}
|
||||
statusProps={statusProps}
|
||||
sidebarProps={sidebarProps}
|
||||
conversationPaneProps={conversationPaneProps}
|
||||
searchProps={searchProps}
|
||||
settingsProps={settingsProps}
|
||||
crackerProps={crackerProps}
|
||||
newMessageModalProps={newMessageModalProps}
|
||||
contactInfoPaneProps={contactInfoPaneProps}
|
||||
channelInfoPaneProps={channelInfoPaneProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,3 +9,4 @@ export { useContactsAndChannels } from './useContactsAndChannels';
|
||||
export { useRealtimeAppState } from './useRealtimeAppState';
|
||||
export { useConversationActions } from './useConversationActions';
|
||||
export { useConversationNavigation } from './useConversationNavigation';
|
||||
export { useAppShellProps } from './useAppShellProps';
|
||||
|
||||
306
frontend/src/hooks/useAppShellProps.ts
Normal file
306
frontend/src/hooks/useAppShellProps.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
import { useCallback, type ComponentProps, type Dispatch, type SetStateAction } from 'react';
|
||||
|
||||
import { api } from '../api';
|
||||
import { ChannelInfoPane } from '../components/ChannelInfoPane';
|
||||
import { ContactInfoPane } from '../components/ContactInfoPane';
|
||||
import { ConversationPane } from '../components/ConversationPane';
|
||||
import { NewMessageModal } from '../components/NewMessageModal';
|
||||
import { SearchView } from '../components/SearchView';
|
||||
import { SettingsModal } from '../components/SettingsModal';
|
||||
import { Sidebar } from '../components/Sidebar';
|
||||
import { StatusBar } from '../components/StatusBar';
|
||||
import { CrackerPanel } from '../components/CrackerPanel';
|
||||
import type {
|
||||
AppSettings,
|
||||
Channel,
|
||||
Contact,
|
||||
Conversation,
|
||||
Favorite,
|
||||
HealthStatus,
|
||||
Message,
|
||||
RadioConfig,
|
||||
RawPacket,
|
||||
} from '../types';
|
||||
|
||||
type StatusProps = Pick<ComponentProps<typeof StatusBar>, 'health' | 'config'>;
|
||||
type SidebarProps = ComponentProps<typeof Sidebar>;
|
||||
type ConversationPaneProps = ComponentProps<typeof ConversationPane>;
|
||||
type SearchProps = ComponentProps<typeof SearchView>;
|
||||
type SettingsProps = Omit<
|
||||
ComponentProps<typeof SettingsModal>,
|
||||
'open' | 'pageMode' | 'externalSidebarNav' | 'desktopSection' | 'onClose' | 'onLocalLabelChange'
|
||||
>;
|
||||
type CrackerProps = Omit<ComponentProps<typeof CrackerPanel>, 'visible' | 'onRunningChange'>;
|
||||
type NewMessageModalProps = Omit<ComponentProps<typeof NewMessageModal>, 'open' | 'onClose'>;
|
||||
type ContactInfoPaneProps = ComponentProps<typeof ContactInfoPane>;
|
||||
type ChannelInfoPaneProps = ComponentProps<typeof ChannelInfoPane>;
|
||||
|
||||
interface UseAppShellPropsArgs {
|
||||
contacts: Contact[];
|
||||
channels: Channel[];
|
||||
rawPackets: RawPacket[];
|
||||
undecryptedCount: number;
|
||||
activeConversation: Conversation | null;
|
||||
config: RadioConfig | null;
|
||||
health: HealthStatus | null;
|
||||
favorites: Favorite[];
|
||||
appSettings: AppSettings | null;
|
||||
unreadCounts: Record<string, number>;
|
||||
mentions: Record<string, boolean>;
|
||||
lastMessageTimes: Record<string, number>;
|
||||
showCracker: boolean;
|
||||
crackerRunning: boolean;
|
||||
messageInputRef: ConversationPaneProps['messageInputRef'];
|
||||
targetMessageId: number | null;
|
||||
infoPaneContactKey: string | null;
|
||||
infoPaneFromChannel: boolean;
|
||||
infoPaneChannelKey: string | null;
|
||||
messages: Message[];
|
||||
messagesLoading: boolean;
|
||||
loadingOlder: boolean;
|
||||
hasOlderMessages: boolean;
|
||||
hasNewerMessages: boolean;
|
||||
loadingNewer: boolean;
|
||||
handleOpenNewMessage: () => void;
|
||||
handleToggleCracker: () => void;
|
||||
markAllRead: () => void;
|
||||
handleSortOrderChange: (sortOrder: 'recent' | 'alpha') => Promise<void>;
|
||||
handleSelectConversationWithTargetReset: (
|
||||
conv: Conversation,
|
||||
options?: { preserveTarget?: boolean }
|
||||
) => void;
|
||||
handleNavigateToMessage: SearchProps['onNavigateToMessage'];
|
||||
handleSaveConfig: SettingsProps['onSave'];
|
||||
handleSaveAppSettings: SettingsProps['onSaveAppSettings'];
|
||||
handleSetPrivateKey: SettingsProps['onSetPrivateKey'];
|
||||
handleReboot: SettingsProps['onReboot'];
|
||||
handleAdvertise: SettingsProps['onAdvertise'];
|
||||
handleHealthRefresh: SettingsProps['onHealthRefresh'];
|
||||
fetchAppSettings: () => Promise<void>;
|
||||
setChannels: Dispatch<SetStateAction<Channel[]>>;
|
||||
fetchUndecryptedCount: () => Promise<void>;
|
||||
handleCreateContact: NewMessageModalProps['onCreateContact'];
|
||||
handleCreateChannel: NewMessageModalProps['onCreateChannel'];
|
||||
handleCreateHashtagChannel: NewMessageModalProps['onCreateHashtagChannel'];
|
||||
handleDeleteContact: ConversationPaneProps['onDeleteContact'];
|
||||
handleDeleteChannel: ConversationPaneProps['onDeleteChannel'];
|
||||
handleToggleFavorite: (type: 'channel' | 'contact', id: string) => Promise<void>;
|
||||
handleSetChannelFloodScopeOverride: ConversationPaneProps['onSetChannelFloodScopeOverride'];
|
||||
handleOpenContactInfo: ConversationPaneProps['onOpenContactInfo'];
|
||||
handleOpenChannelInfo: ConversationPaneProps['onOpenChannelInfo'];
|
||||
handleCloseContactInfo: () => void;
|
||||
handleCloseChannelInfo: () => void;
|
||||
handleSenderClick: NonNullable<ConversationPaneProps['onSenderClick']>;
|
||||
handleResendChannelMessage: NonNullable<ConversationPaneProps['onResendChannelMessage']>;
|
||||
handleTrace: ConversationPaneProps['onTrace'];
|
||||
handleSendMessage: ConversationPaneProps['onSendMessage'];
|
||||
fetchOlderMessages: ConversationPaneProps['onLoadOlder'];
|
||||
fetchNewerMessages: ConversationPaneProps['onLoadNewer'];
|
||||
jumpToBottom: ConversationPaneProps['onJumpToBottom'];
|
||||
setTargetMessageId: Dispatch<SetStateAction<number | null>>;
|
||||
handleNavigateToChannel: ContactInfoPaneProps['onNavigateToChannel'];
|
||||
handleBlockKey: NonNullable<ContactInfoPaneProps['onToggleBlockedKey']>;
|
||||
handleBlockName: NonNullable<ContactInfoPaneProps['onToggleBlockedName']>;
|
||||
}
|
||||
|
||||
interface UseAppShellPropsResult {
|
||||
statusProps: StatusProps;
|
||||
sidebarProps: SidebarProps;
|
||||
conversationPaneProps: ConversationPaneProps;
|
||||
searchProps: SearchProps;
|
||||
settingsProps: SettingsProps;
|
||||
crackerProps: CrackerProps;
|
||||
newMessageModalProps: NewMessageModalProps;
|
||||
contactInfoPaneProps: ContactInfoPaneProps;
|
||||
channelInfoPaneProps: ChannelInfoPaneProps;
|
||||
}
|
||||
|
||||
export function useAppShellProps({
|
||||
contacts,
|
||||
channels,
|
||||
rawPackets,
|
||||
undecryptedCount,
|
||||
activeConversation,
|
||||
config,
|
||||
health,
|
||||
favorites,
|
||||
appSettings,
|
||||
unreadCounts,
|
||||
mentions,
|
||||
lastMessageTimes,
|
||||
showCracker,
|
||||
crackerRunning,
|
||||
messageInputRef,
|
||||
targetMessageId,
|
||||
infoPaneContactKey,
|
||||
infoPaneFromChannel,
|
||||
infoPaneChannelKey,
|
||||
messages,
|
||||
messagesLoading,
|
||||
loadingOlder,
|
||||
hasOlderMessages,
|
||||
hasNewerMessages,
|
||||
loadingNewer,
|
||||
handleOpenNewMessage,
|
||||
handleToggleCracker,
|
||||
markAllRead,
|
||||
handleSortOrderChange,
|
||||
handleSelectConversationWithTargetReset,
|
||||
handleNavigateToMessage,
|
||||
handleSaveConfig,
|
||||
handleSaveAppSettings,
|
||||
handleSetPrivateKey,
|
||||
handleReboot,
|
||||
handleAdvertise,
|
||||
handleHealthRefresh,
|
||||
fetchAppSettings,
|
||||
setChannels,
|
||||
fetchUndecryptedCount,
|
||||
handleCreateContact,
|
||||
handleCreateChannel,
|
||||
handleCreateHashtagChannel,
|
||||
handleDeleteContact,
|
||||
handleDeleteChannel,
|
||||
handleToggleFavorite,
|
||||
handleSetChannelFloodScopeOverride,
|
||||
handleOpenContactInfo,
|
||||
handleOpenChannelInfo,
|
||||
handleCloseContactInfo,
|
||||
handleCloseChannelInfo,
|
||||
handleSenderClick,
|
||||
handleResendChannelMessage,
|
||||
handleTrace,
|
||||
handleSendMessage,
|
||||
fetchOlderMessages,
|
||||
fetchNewerMessages,
|
||||
jumpToBottom,
|
||||
setTargetMessageId,
|
||||
handleNavigateToChannel,
|
||||
handleBlockKey,
|
||||
handleBlockName,
|
||||
}: UseAppShellPropsArgs): UseAppShellPropsResult {
|
||||
const handleCreateCrackedChannel = useCallback<CrackerProps['onChannelCreate']>(
|
||||
async (name, key) => {
|
||||
const created = await api.createChannel(name, key);
|
||||
const updatedChannels = await api.getChannels();
|
||||
setChannels(updatedChannels);
|
||||
await api.decryptHistoricalPackets({
|
||||
key_type: 'channel',
|
||||
channel_key: created.key,
|
||||
});
|
||||
await fetchUndecryptedCount();
|
||||
},
|
||||
[fetchUndecryptedCount, setChannels]
|
||||
);
|
||||
|
||||
return {
|
||||
statusProps: { health, config },
|
||||
sidebarProps: {
|
||||
contacts,
|
||||
channels,
|
||||
activeConversation,
|
||||
onSelectConversation: handleSelectConversationWithTargetReset,
|
||||
onNewMessage: handleOpenNewMessage,
|
||||
lastMessageTimes,
|
||||
unreadCounts,
|
||||
mentions,
|
||||
showCracker,
|
||||
crackerRunning,
|
||||
onToggleCracker: handleToggleCracker,
|
||||
onMarkAllRead: () => {
|
||||
void markAllRead();
|
||||
},
|
||||
favorites,
|
||||
sortOrder: appSettings?.sidebar_sort_order ?? 'recent',
|
||||
onSortOrderChange: (sortOrder) => {
|
||||
void handleSortOrderChange(sortOrder);
|
||||
},
|
||||
},
|
||||
conversationPaneProps: {
|
||||
activeConversation,
|
||||
contacts,
|
||||
channels,
|
||||
rawPackets,
|
||||
config,
|
||||
health,
|
||||
favorites,
|
||||
messages,
|
||||
messagesLoading,
|
||||
loadingOlder,
|
||||
hasOlderMessages,
|
||||
targetMessageId,
|
||||
hasNewerMessages,
|
||||
loadingNewer,
|
||||
messageInputRef,
|
||||
onTrace: handleTrace,
|
||||
onToggleFavorite: handleToggleFavorite,
|
||||
onDeleteContact: handleDeleteContact,
|
||||
onDeleteChannel: handleDeleteChannel,
|
||||
onSetChannelFloodScopeOverride: handleSetChannelFloodScopeOverride,
|
||||
onOpenContactInfo: handleOpenContactInfo,
|
||||
onOpenChannelInfo: handleOpenChannelInfo,
|
||||
onSenderClick: handleSenderClick,
|
||||
onLoadOlder: fetchOlderMessages,
|
||||
onResendChannelMessage: handleResendChannelMessage,
|
||||
onTargetReached: () => setTargetMessageId(null),
|
||||
onLoadNewer: fetchNewerMessages,
|
||||
onJumpToBottom: jumpToBottom,
|
||||
onSendMessage: handleSendMessage,
|
||||
},
|
||||
searchProps: {
|
||||
contacts,
|
||||
channels,
|
||||
onNavigateToMessage: handleNavigateToMessage,
|
||||
},
|
||||
settingsProps: {
|
||||
config,
|
||||
health,
|
||||
appSettings,
|
||||
onSave: handleSaveConfig,
|
||||
onSaveAppSettings: handleSaveAppSettings,
|
||||
onSetPrivateKey: handleSetPrivateKey,
|
||||
onReboot: handleReboot,
|
||||
onAdvertise: handleAdvertise,
|
||||
onHealthRefresh: handleHealthRefresh,
|
||||
onRefreshAppSettings: fetchAppSettings,
|
||||
blockedKeys: appSettings?.blocked_keys,
|
||||
blockedNames: appSettings?.blocked_names,
|
||||
onToggleBlockedKey: handleBlockKey,
|
||||
onToggleBlockedName: handleBlockName,
|
||||
},
|
||||
crackerProps: {
|
||||
packets: rawPackets,
|
||||
channels,
|
||||
onChannelCreate: handleCreateCrackedChannel,
|
||||
},
|
||||
newMessageModalProps: {
|
||||
contacts,
|
||||
undecryptedCount,
|
||||
onSelectConversation: handleSelectConversationWithTargetReset,
|
||||
onCreateContact: handleCreateContact,
|
||||
onCreateChannel: handleCreateChannel,
|
||||
onCreateHashtagChannel: handleCreateHashtagChannel,
|
||||
},
|
||||
contactInfoPaneProps: {
|
||||
contactKey: infoPaneContactKey,
|
||||
fromChannel: infoPaneFromChannel,
|
||||
onClose: handleCloseContactInfo,
|
||||
contacts,
|
||||
config,
|
||||
favorites,
|
||||
onToggleFavorite: handleToggleFavorite,
|
||||
onNavigateToChannel: handleNavigateToChannel,
|
||||
blockedKeys: appSettings?.blocked_keys,
|
||||
blockedNames: appSettings?.blocked_names,
|
||||
onToggleBlockedKey: handleBlockKey,
|
||||
onToggleBlockedName: handleBlockName,
|
||||
},
|
||||
channelInfoPaneProps: {
|
||||
channelKey: infoPaneChannelKey,
|
||||
onClose: handleCloseChannelInfo,
|
||||
channels,
|
||||
favorites,
|
||||
onToggleFavorite: handleToggleFavorite,
|
||||
},
|
||||
};
|
||||
}
|
||||
189
frontend/src/test/useAppShellProps.test.ts
Normal file
189
frontend/src/test/useAppShellProps.test.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user