Files
5chan/src/hooks/__tests__/use-fresh-replies.test.tsx
T

210 lines
5.4 KiB
TypeScript

import * as React from 'react';
import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import useFreshReplies from '../use-fresh-replies';
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
type TestComment = {
cid?: string;
content?: string;
index?: number;
number?: number;
pendingApproval?: boolean;
communityAddress?: string;
};
const testState = vi.hoisted(() => ({
accountComments: [] as TestComment[],
accountCommentsCalls: [] as Array<{ commentIndices?: number[] } | undefined>,
replies: [] as TestComment[],
}));
vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
useAccountComments: (options?: { commentIndices?: number[] }) => {
testState.accountCommentsCalls.push(options);
if (!options?.commentIndices?.length) {
return {
accountComments: testState.accountComments,
};
}
const normalizedCommentIndices = options.commentIndices.filter((commentIndex) => Number.isInteger(commentIndex) && commentIndex >= 0);
return {
accountComments: normalizedCommentIndices
.map((commentIndex) => testState.accountComments.find((accountComment) => accountComment.index === commentIndex))
.filter(Boolean),
};
},
}));
let container: HTMLDivElement;
let latestValue: ReturnType<typeof useFreshReplies>;
let root: Root;
const HookHarness = () => {
latestValue = useFreshReplies(testState.replies as never);
return null;
};
const renderHook = () => {
act(() => {
root.render(createElement(HookHarness));
});
};
describe('useFreshReplies', () => {
beforeEach(() => {
testState.accountComments = [];
testState.accountCommentsCalls = [];
testState.replies = [];
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
container.remove();
});
it('replaces stale indexed replies with the latest account comment objects', () => {
testState.replies = [
{
cid: 'reply-cid',
content: 'stale reply',
index: 3,
number: undefined,
communityAddress: 'music.eth',
},
{
cid: 'network-reply-cid',
content: 'network reply',
communityAddress: 'music.eth',
},
];
testState.accountComments = [
{
cid: 'reply-cid',
content: 'fresh reply',
index: 3,
number: 27,
communityAddress: 'music.eth',
},
];
renderHook();
expect(latestValue[0]).toBe(testState.accountComments[0] as never);
expect(latestValue[0]?.number).toBe(27);
expect(latestValue[1]).toBe(testState.replies[1] as never);
expect(testState.accountCommentsCalls).toContainEqual({ commentIndices: [3] });
testState.accountComments = [
{
...testState.accountComments[0],
number: 28,
},
];
renderHook();
expect(latestValue[0]?.number).toBe(28);
});
it('dedupes retried local replies when a stale reply and fresh reply share index 0', () => {
testState.replies = [
{
content: 'stale failed reply',
index: 0,
communityAddress: 'music.eth',
},
{
content: 'duplicate stale failed reply',
index: 0,
communityAddress: 'music.eth',
},
];
testState.accountComments = [
{
content: 'retried pending reply',
index: 0,
number: 99,
communityAddress: 'music.eth',
},
];
renderHook();
expect(latestValue).toHaveLength(1);
expect(latestValue[0]).toBe(testState.accountComments[0] as never);
expect(testState.accountCommentsCalls).toContainEqual({ commentIndices: [0] });
});
it('orders replies by final post number after a pending reply is approved', () => {
testState.replies = [
{
cid: 'reply-8',
index: 8,
number: 8,
communityAddress: 'music.eth',
},
{
cid: 'reply-12',
index: 12,
number: 12,
communityAddress: 'music.eth',
},
{
cid: 'pending-reply',
index: 11,
number: undefined,
pendingApproval: true,
communityAddress: 'music.eth',
},
];
testState.accountComments = [
{
cid: 'reply-11',
index: 11,
number: 11,
communityAddress: 'music.eth',
},
];
renderHook();
expect(latestValue.map((reply) => reply.cid)).toEqual(['reply-8', 'reply-11', 'reply-12']);
expect(testState.accountCommentsCalls).toContainEqual({ commentIndices: [8, 12, 11] });
});
it('orders already-numbered replies when an approved queued reply was appended', () => {
testState.replies = [
{
cid: 'reply-8',
number: 8,
communityAddress: 'music.eth',
},
{
cid: 'reply-12',
number: 12,
communityAddress: 'music.eth',
},
{
cid: 'reply-11',
number: 11,
communityAddress: 'music.eth',
},
];
renderHook();
expect(latestValue.map((reply) => reply.cid)).toEqual(['reply-8', 'reply-11', 'reply-12']);
});
});