test(07-02): add failing tests for fetchChatHistory async thunk

Add unit tests for fetchChatHistory extra reducers:
- Test pending state sets loading status
- Test fulfilled state adds messages and updates status
- Test message deduplication on fulfilled
- Test pagination: prepending older messages
- Test rejected state sets error
- Test null cursor handling

Tests fail as expected - async thunk not yet implemented.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Nuwan 2026-01-27 08:15:38 +05:30
parent 95483da6fe
commit b0b4ae1a53
1 changed files with 161 additions and 0 deletions

View File

@ -488,6 +488,167 @@ describe('setWindowPosition', () => {
});
});
describe('fetchChatHistory async thunk', () => {
// Import will be added in implementation phase
// This is the RED phase - tests should fail
let fetchChatHistory;
test('sets loading state on pending', () => {
// Mock the pending action type
const action = {
type: 'sessionChat/fetchChatHistory/pending',
meta: { arg: { channel: 'session-abc' } }
};
const state = {
fetchStatus: {},
fetchError: {},
messagesByChannel: {},
unreadCounts: {},
isWindowOpen: false,
activeChannel: null
};
const newState = sessionChatReducer(state, action);
expect(newState.fetchStatus['session-abc']).toBe('loading');
expect(newState.fetchError['session-abc']).toBeNull();
});
test('adds messages on fulfilled', () => {
const action = {
type: 'sessionChat/fetchChatHistory/fulfilled',
meta: { arg: { channel: 'session-abc' } },
payload: {
channel: 'session-abc',
messages: [
{ id: 'msg-1', message: 'Hello', sender_id: 'user-1', created_at: '2026-01-26T12:00:00Z' }
],
next: 20
}
};
const state = {
messagesByChannel: {},
fetchStatus: { 'session-abc': 'loading' },
fetchError: {},
nextCursors: {},
unreadCounts: {},
isWindowOpen: false,
activeChannel: null
};
const newState = sessionChatReducer(state, action);
expect(newState.messagesByChannel['session-abc']).toHaveLength(1);
expect(newState.fetchStatus['session-abc']).toBe('succeeded');
expect(newState.nextCursors['session-abc']).toBe(20);
});
test('deduplicates messages on fulfilled', () => {
// Test that fetched messages don't duplicate existing ones
const state = {
messagesByChannel: {
'session-abc': [{ id: 'msg-1', message: 'Hello', createdAt: '2026-01-26T12:00:00Z' }]
},
fetchStatus: { 'session-abc': 'loading' },
fetchError: {},
nextCursors: {},
unreadCounts: {},
isWindowOpen: false,
activeChannel: null
};
const action = {
type: 'sessionChat/fetchChatHistory/fulfilled',
meta: { arg: { channel: 'session-abc' } },
payload: {
channel: 'session-abc',
messages: [
{ id: 'msg-1', message: 'Hello', createdAt: '2026-01-26T12:00:00Z' }, // Duplicate
{ id: 'msg-2', message: 'World', createdAt: '2026-01-26T12:01:00Z' } // New
],
next: null
}
};
const newState = sessionChatReducer(state, action);
expect(newState.messagesByChannel['session-abc']).toHaveLength(2);
expect(newState.messagesByChannel['session-abc'][0].id).toBe('msg-1');
expect(newState.messagesByChannel['session-abc'][1].id).toBe('msg-2');
});
test('prepends older messages for pagination', () => {
// When fetching older messages, they should be prepended (oldest first)
const state = {
messagesByChannel: {
'session-abc': [
{ id: 'msg-3', message: 'Newest', createdAt: '2026-01-26T12:02:00Z' }
]
},
fetchStatus: { 'session-abc': 'loading' },
fetchError: {},
nextCursors: {},
unreadCounts: {},
isWindowOpen: false,
activeChannel: null
};
const action = {
type: 'sessionChat/fetchChatHistory/fulfilled',
meta: { arg: { channel: 'session-abc', before: 3 } },
payload: {
channel: 'session-abc',
messages: [
{ id: 'msg-1', message: 'Oldest', createdAt: '2026-01-26T12:00:00Z' },
{ id: 'msg-2', message: 'Middle', createdAt: '2026-01-26T12:01:00Z' }
],
next: null
}
};
const newState = sessionChatReducer(state, action);
expect(newState.messagesByChannel['session-abc']).toHaveLength(3);
expect(newState.messagesByChannel['session-abc'][0].id).toBe('msg-1');
expect(newState.messagesByChannel['session-abc'][1].id).toBe('msg-2');
expect(newState.messagesByChannel['session-abc'][2].id).toBe('msg-3');
});
test('sets error state on rejected', () => {
const action = {
type: 'sessionChat/fetchChatHistory/rejected',
meta: { arg: { channel: 'session-abc' } },
error: { message: 'Not Found' }
};
const state = {
fetchStatus: { 'session-abc': 'loading' },
fetchError: {},
messagesByChannel: {},
unreadCounts: {},
isWindowOpen: false,
activeChannel: null
};
const newState = sessionChatReducer(state, action);
expect(newState.fetchStatus['session-abc']).toBe('failed');
expect(newState.fetchError['session-abc']).toBe('Not Found');
});
test('handles null next cursor', () => {
const action = {
type: 'sessionChat/fetchChatHistory/fulfilled',
meta: { arg: { channel: 'session-abc' } },
payload: {
channel: 'session-abc',
messages: [
{ id: 'msg-1', message: 'Hello', createdAt: '2026-01-26T12:00:00Z' }
],
next: null
}
};
const state = {
messagesByChannel: {},
fetchStatus: { 'session-abc': 'loading' },
fetchError: {},
nextCursors: {},
unreadCounts: {},
isWindowOpen: false,
activeChannel: null
};
const newState = sessionChatReducer(state, action);
expect(newState.nextCursors['session-abc']).toBeNull();
});
});
describe('sessionChat integration', () => {
test('complete message flow: receive → set active → open window → mark read', () => {
const initialState = {