mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-05-04 12:33:04 +02:00
Fix message dual render and get the jump to unread link out of the way on visible unread boundaries. Closes #57.
This commit is contained in:
@@ -7,6 +7,7 @@ import { MessageList } from '../components/MessageList';
|
||||
import type { Message } from '../types';
|
||||
|
||||
const scrollIntoViewMock = vi.fn();
|
||||
const originalGetBoundingClientRect = HTMLElement.prototype.getBoundingClientRect;
|
||||
|
||||
function createMessage(overrides: Partial<Message> = {}): Message {
|
||||
return {
|
||||
@@ -35,6 +36,11 @@ describe('MessageList channel sender rendering', () => {
|
||||
value: scrollIntoViewMock,
|
||||
writable: true,
|
||||
});
|
||||
Object.defineProperty(HTMLElement.prototype, 'getBoundingClientRect', {
|
||||
configurable: true,
|
||||
value: originalGetBoundingClientRect,
|
||||
writable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders explicit corrupt placeholder and warning avatar for unnamed corrupt channel packets', () => {
|
||||
@@ -155,4 +161,68 @@ describe('MessageList channel sender rendering', () => {
|
||||
expect(screen.getByText('Unread messages')).toBeInTheDocument();
|
||||
expect(scrollIntoViewMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('hides the jump-to-unread button when the unread marker is already visible', () => {
|
||||
Object.defineProperty(HTMLElement.prototype, 'getBoundingClientRect', {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: function () {
|
||||
const element = this as HTMLElement;
|
||||
if (element.textContent?.includes('Unread messages')) {
|
||||
return {
|
||||
top: 200,
|
||||
bottom: 240,
|
||||
left: 0,
|
||||
right: 300,
|
||||
width: 300,
|
||||
height: 40,
|
||||
x: 0,
|
||||
y: 200,
|
||||
toJSON: () => '',
|
||||
};
|
||||
}
|
||||
if (element.className.includes('overflow-y-auto')) {
|
||||
return {
|
||||
top: 100,
|
||||
bottom: 500,
|
||||
left: 0,
|
||||
right: 400,
|
||||
width: 400,
|
||||
height: 400,
|
||||
x: 0,
|
||||
y: 100,
|
||||
toJSON: () => '',
|
||||
};
|
||||
}
|
||||
return {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: () => '',
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const messages = [
|
||||
createMessage({ id: 1, received_at: 1700000001, text: 'Alice: older' }),
|
||||
createMessage({ id: 2, received_at: 1700000010, text: 'Alice: newer' }),
|
||||
];
|
||||
|
||||
render(
|
||||
<MessageList
|
||||
messages={messages}
|
||||
contacts={[]}
|
||||
loading={false}
|
||||
unreadMarkerLastReadAt={1700000005}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Unread messages')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Jump to unread' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -323,6 +323,102 @@ describe('useConversationMessages background reconcile ordering', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('useConversationMessages older-page dedup and reentry', () => {
|
||||
beforeEach(() => {
|
||||
mockGetMessages.mockReset();
|
||||
messageCache.clear();
|
||||
});
|
||||
|
||||
it('prevents duplicate overlapping older-page fetches in the same tick', async () => {
|
||||
const conv: Conversation = { type: 'contact', id: 'conv_a', name: 'Contact A' };
|
||||
|
||||
const fullPage = Array.from({ length: 200 }, (_, i) =>
|
||||
createMessage({
|
||||
id: i + 1,
|
||||
conversation_key: 'conv_a',
|
||||
text: `msg-${i + 1}`,
|
||||
sender_timestamp: 1700000000 + i,
|
||||
received_at: 1700000000 + i,
|
||||
})
|
||||
);
|
||||
mockGetMessages.mockResolvedValueOnce(fullPage);
|
||||
|
||||
const olderDeferred = createDeferred<Message[]>();
|
||||
mockGetMessages.mockReturnValueOnce(olderDeferred.promise);
|
||||
|
||||
const { result } = renderHook(() => useConversationMessages(conv));
|
||||
|
||||
await waitFor(() => expect(result.current.messagesLoading).toBe(false));
|
||||
expect(result.current.messages).toHaveLength(200);
|
||||
expect(result.current.hasOlderMessages).toBe(true);
|
||||
|
||||
act(() => {
|
||||
void result.current.fetchOlderMessages();
|
||||
void result.current.fetchOlderMessages();
|
||||
});
|
||||
|
||||
expect(mockGetMessages).toHaveBeenCalledTimes(2); // initial page + one older fetch
|
||||
|
||||
olderDeferred.resolve([
|
||||
createMessage({
|
||||
id: 0,
|
||||
conversation_key: 'conv_a',
|
||||
text: 'older-msg',
|
||||
sender_timestamp: 1699999999,
|
||||
received_at: 1699999999,
|
||||
}),
|
||||
]);
|
||||
|
||||
await waitFor(() => expect(result.current.loadingOlder).toBe(false));
|
||||
expect(result.current.messages).toHaveLength(201);
|
||||
expect(result.current.messages.filter((msg) => msg.id === 0)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not append duplicate messages from an overlapping older page', async () => {
|
||||
const conv: Conversation = { type: 'contact', id: 'conv_a', name: 'Contact A' };
|
||||
|
||||
const fullPage = Array.from({ length: 200 }, (_, i) =>
|
||||
createMessage({
|
||||
id: i + 1,
|
||||
conversation_key: 'conv_a',
|
||||
text: `msg-${i + 1}`,
|
||||
sender_timestamp: 1700000000 + i,
|
||||
received_at: 1700000000 + i,
|
||||
})
|
||||
);
|
||||
mockGetMessages.mockResolvedValueOnce(fullPage);
|
||||
mockGetMessages.mockResolvedValueOnce([
|
||||
createMessage({
|
||||
id: 1,
|
||||
conversation_key: 'conv_a',
|
||||
text: 'msg-1',
|
||||
sender_timestamp: 1700000000,
|
||||
received_at: 1700000000,
|
||||
}),
|
||||
createMessage({
|
||||
id: 0,
|
||||
conversation_key: 'conv_a',
|
||||
text: 'older-msg',
|
||||
sender_timestamp: 1699999999,
|
||||
received_at: 1699999999,
|
||||
}),
|
||||
]);
|
||||
|
||||
const { result } = renderHook(() => useConversationMessages(conv));
|
||||
|
||||
await waitFor(() => expect(result.current.messagesLoading).toBe(false));
|
||||
expect(result.current.messages).toHaveLength(200);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.fetchOlderMessages();
|
||||
});
|
||||
|
||||
expect(result.current.messages.filter((msg) => msg.id === 1)).toHaveLength(1);
|
||||
expect(result.current.messages.filter((msg) => msg.id === 0)).toHaveLength(1);
|
||||
expect(result.current.messages).toHaveLength(201);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useConversationMessages forward pagination', () => {
|
||||
beforeEach(() => {
|
||||
mockGetMessages.mockReset();
|
||||
|
||||
Reference in New Issue
Block a user