Skip to content

Commit 3b2f46a

Browse files
committed
test(cli): add unit tests for toggleAgentMessageCollapse in use-chat-messages
Adds 14 meaningful tests for the collapse toggle state transformation logic: - Expanding collapsed messages (isCollapsed, userOpened) - Collapsing expanded messages - Default state handling - Non-agent message passthrough - Immutability guarantees - Toggle cycle behavior Removed trivial tests for pagination/array slicing per code review feedback.
1 parent 8b7566b commit 3b2f46a

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import { describe, test, expect } from 'bun:test'
2+
3+
import type { ChatMessage } from '../../types/chat'
4+
5+
/**
6+
* Tests for useChatMessages hook logic.
7+
*
8+
* These tests focus on the non-trivial state transformation logic:
9+
* - Collapse toggle behavior for agent messages (isCollapsed, userOpened)
10+
*
11+
* Note: Pagination logic (slice, hidden count, load more) is trivial
12+
* JavaScript operations and doesn't warrant unit testing.
13+
*/
14+
15+
// ============================================================================
16+
// Extracted pure functions (mirror the hook's logic)
17+
// ============================================================================
18+
19+
/**
20+
* Toggles the collapsed state of an agent message.
21+
* Mirrors the handleCollapseToggle logic for agent variant messages.
22+
*/
23+
const toggleAgentMessageCollapse = (message: ChatMessage): ChatMessage => {
24+
if (message.variant !== 'agent') return message
25+
26+
const wasCollapsed = message.metadata?.isCollapsed ?? false
27+
return {
28+
...message,
29+
metadata: {
30+
...message.metadata,
31+
isCollapsed: !wasCollapsed,
32+
// Mark as user-opened if expanding (wasCollapsed was true)
33+
userOpened: wasCollapsed,
34+
},
35+
}
36+
}
37+
38+
/**
39+
* Creates a minimal agent message for testing.
40+
*/
41+
const createAgentMessage = (
42+
id: string,
43+
options: {
44+
isCollapsed?: boolean
45+
userOpened?: boolean
46+
parentId?: string
47+
} = {},
48+
): ChatMessage => ({
49+
id,
50+
variant: 'agent',
51+
content: '',
52+
timestamp: new Date().toISOString(),
53+
parentId: options.parentId,
54+
metadata: {
55+
isCollapsed: options.isCollapsed,
56+
userOpened: options.userOpened,
57+
},
58+
})
59+
60+
/**
61+
* Creates a minimal user message for testing.
62+
*/
63+
const createUserMessage = (
64+
id: string,
65+
options: { parentId?: string } = {},
66+
): ChatMessage => ({
67+
id,
68+
variant: 'user',
69+
timestamp: new Date().toISOString(),
70+
content: 'test message',
71+
parentId: options.parentId,
72+
})
73+
74+
// ============================================================================
75+
// Tests
76+
// ============================================================================
77+
78+
describe('useChatMessages - toggleAgentMessageCollapse', () => {
79+
describe('expanding collapsed messages', () => {
80+
test('sets isCollapsed to false when was true', () => {
81+
const message = createAgentMessage('agent-1', { isCollapsed: true })
82+
const result = toggleAgentMessageCollapse(message)
83+
84+
expect(result.metadata?.isCollapsed).toBe(false)
85+
})
86+
87+
test('sets userOpened to true when expanding', () => {
88+
const message = createAgentMessage('agent-1', { isCollapsed: true })
89+
const result = toggleAgentMessageCollapse(message)
90+
91+
expect(result.metadata?.userOpened).toBe(true)
92+
})
93+
94+
test('preserves other metadata when expanding', () => {
95+
const message: ChatMessage = {
96+
...createAgentMessage('agent-1', { isCollapsed: true }),
97+
metadata: {
98+
isCollapsed: true,
99+
customField: 'preserved',
100+
} as ChatMessage['metadata'],
101+
}
102+
const result = toggleAgentMessageCollapse(message)
103+
104+
expect((result.metadata as Record<string, unknown>)?.customField).toBe('preserved')
105+
})
106+
})
107+
108+
describe('collapsing expanded messages', () => {
109+
test('sets isCollapsed to true when was false', () => {
110+
const message = createAgentMessage('agent-1', { isCollapsed: false })
111+
const result = toggleAgentMessageCollapse(message)
112+
113+
expect(result.metadata?.isCollapsed).toBe(true)
114+
})
115+
116+
test('sets userOpened to false when collapsing', () => {
117+
const message = createAgentMessage('agent-1', { isCollapsed: false })
118+
const result = toggleAgentMessageCollapse(message)
119+
120+
// When collapsing (wasCollapsed=false), userOpened is set to wasCollapsed (false)
121+
expect(result.metadata?.userOpened).toBe(false)
122+
})
123+
})
124+
125+
describe('default state handling', () => {
126+
test('treats undefined isCollapsed as false (expanded)', () => {
127+
const message = createAgentMessage('agent-1') // No isCollapsed set
128+
const result = toggleAgentMessageCollapse(message)
129+
130+
// Should collapse (toggle from default false to true)
131+
expect(result.metadata?.isCollapsed).toBe(true)
132+
// userOpened should be false (wasCollapsed was false by default)
133+
expect(result.metadata?.userOpened).toBe(false)
134+
})
135+
136+
test('handles message with no metadata', () => {
137+
const message: ChatMessage = {
138+
id: 'agent-1',
139+
variant: 'agent',
140+
content: '',
141+
timestamp: new Date().toISOString(),
142+
}
143+
const result = toggleAgentMessageCollapse(message)
144+
145+
expect(result.metadata?.isCollapsed).toBe(true)
146+
})
147+
})
148+
149+
describe('non-agent messages', () => {
150+
test('returns user message unchanged', () => {
151+
const message = createUserMessage('user-1')
152+
const result = toggleAgentMessageCollapse(message)
153+
154+
expect(result).toBe(message) // Same reference
155+
})
156+
157+
test('does not modify user message metadata', () => {
158+
const message = createUserMessage('user-1')
159+
const result = toggleAgentMessageCollapse(message)
160+
161+
expect(result.metadata).toEqual(message.metadata)
162+
})
163+
})
164+
165+
describe('immutability', () => {
166+
test('does not mutate original message', () => {
167+
const message = createAgentMessage('agent-1', { isCollapsed: true })
168+
const originalCollapsed = message.metadata?.isCollapsed
169+
170+
toggleAgentMessageCollapse(message)
171+
172+
expect(message.metadata?.isCollapsed).toBe(originalCollapsed)
173+
})
174+
175+
test('creates new message object', () => {
176+
const message = createAgentMessage('agent-1', { isCollapsed: true })
177+
const result = toggleAgentMessageCollapse(message)
178+
179+
expect(result).not.toBe(message)
180+
})
181+
182+
test('creates new metadata object', () => {
183+
const message = createAgentMessage('agent-1', { isCollapsed: true })
184+
const result = toggleAgentMessageCollapse(message)
185+
186+
expect(result.metadata).not.toBe(message.metadata)
187+
})
188+
})
189+
190+
describe('toggle cycle', () => {
191+
test('collapse then expand preserves correct state transitions', () => {
192+
// Start with expanded message
193+
let message = createAgentMessage('agent-1', { isCollapsed: false })
194+
195+
// Collapse it
196+
message = toggleAgentMessageCollapse(message)
197+
expect(message.metadata?.isCollapsed).toBe(true)
198+
expect(message.metadata?.userOpened).toBe(false)
199+
200+
// Expand it again
201+
message = toggleAgentMessageCollapse(message)
202+
expect(message.metadata?.isCollapsed).toBe(false)
203+
expect(message.metadata?.userOpened).toBe(true) // Marked as user-opened
204+
})
205+
206+
test('rapid toggle preserves correct state', () => {
207+
let message = createAgentMessage('agent-1', { isCollapsed: false })
208+
209+
// Toggle many times
210+
for (let i = 0; i < 10; i++) {
211+
message = toggleAgentMessageCollapse(message)
212+
}
213+
214+
// Even number of toggles = back to original state
215+
expect(message.metadata?.isCollapsed).toBe(false)
216+
})
217+
})
218+
})

0 commit comments

Comments
 (0)