Skip to content

Commit 1d41a0b

Browse files
kfaracikclaude
andcommitted
Merge branch 'main' into chore/229-migrate-swmansion-bottom-sheet
Conflict in ChatScreen.tsx: main added `rootRef` next to the sheet ref, this branch retyped that ref from @gorhom's `BottomSheetModal` to `AppBottomSheetRef`. Kept both — main's new ref and this branch's type. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 parents 48b53a6 + ec8fe09 commit 1d41a0b

48 files changed

Lines changed: 2392 additions & 198 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

__tests__/DrawerMenu.test.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ const mockChats = [
4646

4747
const mockRenameChat = jest.fn();
4848
const mockDeleteChat = jest.fn();
49+
let mockPhantomChat: { id: number } | null = null;
4950
jest.mock('../store/chatStore', () => ({
5051
useChatStore: jest.fn(() => ({
5152
chats: mockChats,
52-
phantomChat: null,
53+
phantomChat: mockPhantomChat,
54+
getChatById: (id: number) => mockChats.find((chat) => chat.id === id),
5355
renameChat: mockRenameChat,
5456
deleteChat: mockDeleteChat,
5557
})),
@@ -90,18 +92,19 @@ const renderMenu = (props: Partial<MenuProps> = {}) =>
9092
beforeEach(() => {
9193
jest.clearAllMocks();
9294
mockPathname = '/';
95+
mockPhantomChat = null;
9396
setPlatform('ios');
9497
});
9598

9699
afterEach(() => setPlatform(ORIGINAL_OS));
97100

98101
describe('DrawerMenu — collapsed', () => {
99-
it('keeps New chat, Models and App Info at the top', () => {
102+
it('keeps New chat, Models and Settings at the top', () => {
100103
renderMenu();
101104

102105
expect(screen.getByText('New chat')).toBeTruthy();
103106
expect(screen.getByText('Models')).toBeTruthy();
104-
expect(screen.getByText('App Info')).toBeTruthy();
107+
expect(screen.getByText('Settings')).toBeTruthy();
105108
});
106109

107110
it('renders the app name and a search button instead of a search field', () => {
@@ -177,6 +180,28 @@ describe('DrawerMenu — collapsed', () => {
177180
expect(onNavigate).toHaveBeenCalled();
178181
});
179182

183+
it('only closes the drawer when already on the new chat screen', () => {
184+
mockPhantomChat = { id: 4 };
185+
mockPathname = '/chat/4';
186+
const onNavigate = jest.fn();
187+
renderMenu({ onNavigate });
188+
189+
fireEvent.press(screen.getByTestId('drawer-new-chat'));
190+
191+
expect(mockStartPhantomChat).not.toHaveBeenCalled();
192+
expect(onNavigate).toHaveBeenCalled();
193+
});
194+
195+
it('starts a phantom chat when a forked chat claimed the phantom id', () => {
196+
mockPhantomChat = { id: 3 };
197+
mockPathname = '/chat/3';
198+
renderMenu();
199+
200+
fireEvent.press(screen.getByTestId('drawer-new-chat'));
201+
202+
expect(mockStartPhantomChat).toHaveBeenCalledWith({}, 'replace');
203+
});
204+
180205
it('navigates to the chat when an item is pressed', () => {
181206
renderMenu();
182207

@@ -225,15 +250,15 @@ describe('DrawerMenu — searching', () => {
225250

226251
expect(screen.getByText('New chat')).toBeTruthy();
227252
expect(screen.getByText('Models')).toBeTruthy();
228-
expect(screen.getByText('App Info')).toBeTruthy();
253+
expect(screen.getByText('Settings')).toBeTruthy();
229254
});
230255

231256
it('hides the navigation items once a query is typed, leaving only results', () => {
232257
renderMenu({ searching: true, search: 'pizza' });
233258

234259
expect(screen.queryByText('New chat')).toBeNull();
235260
expect(screen.queryByText('Models')).toBeNull();
236-
expect(screen.queryByText('App Info')).toBeNull();
261+
expect(screen.queryByText('Settings')).toBeNull();
237262
expect(screen.getByText('Pizza recipe')).toBeTruthy();
238263
});
239264

__tests__/MessageItem.test.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,32 @@ jest.mock('../components/chat-screen/AnimatedChatLoading', () => () => null);
4141
import MessageItem from '../components/chat-screen/MessageItem';
4242
import { useLLMStore } from '../store/llmStore';
4343

44-
const mockUseLLMStore = useLLMStore as jest.Mock;
44+
const mockUseLLMStore = useLLMStore as unknown as jest.Mock;
45+
46+
// The component reads the store via selectors; make the mock honor them.
47+
const mockLLMState = (state: {
48+
isGenerating?: boolean;
49+
isProcessingPrompt?: boolean;
50+
}) =>
51+
mockUseLLMStore.mockImplementation(
52+
(selector?: (s: typeof state) => unknown) =>
53+
selector ? selector(state) : state
54+
);
55+
56+
const baseMessage = {
57+
id: 1,
58+
role: 'assistant',
59+
content: 'Hello world',
60+
chatId: 1,
61+
timestamp: 0,
62+
} as React.ComponentProps<typeof MessageItem>['message'];
4563

4664
const renderItem = (
4765
props: Partial<React.ComponentProps<typeof MessageItem>> = {}
4866
) =>
4967
render(
5068
<MessageItem
69+
message={baseMessage}
5170
content="Hello world"
5271
role="assistant"
5372
isLastMessage={false}
@@ -56,7 +75,7 @@ const renderItem = (
5675
);
5776

5877
beforeEach(() => {
59-
mockUseLLMStore.mockReturnValue({ isGenerating: false });
78+
mockLLMState({ isGenerating: false });
6079
jest.spyOn(console, 'error').mockImplementation(() => {});
6180
});
6281

@@ -248,14 +267,14 @@ describe('thinking block parsing', () => {
248267
});
249268

250269
it('marks ThinkingBlock as inProgress when last message and isGenerating and thinking is incomplete', () => {
251-
mockUseLLMStore.mockReturnValue({ isGenerating: true });
270+
mockLLMState({ isGenerating: true });
252271
renderItem({ content: '<think>working...', isLastMessage: true });
253272
const block = screen.getByTestId('thinking-block');
254273
expect(block.props.accessibilityLabel).toContain('inProgress:true');
255274
});
256275

257276
it('does not mark ThinkingBlock as inProgress when not isLastMessage', () => {
258-
mockUseLLMStore.mockReturnValue({ isGenerating: true });
277+
mockLLMState({ isGenerating: true });
259278
renderItem({ content: '<think>working...', isLastMessage: false });
260279
const block = screen.getByTestId('thinking-block');
261280
expect(block.props.accessibilityLabel).toContain('inProgress:false');

0 commit comments

Comments
 (0)