mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(mod queue): transfer posts across boards (#1182)
* feat(mod queue): transfer posts across boards * fix(mod queue): polish transfer modal and reason * fix(mod queue): recover abandoned transfer challenges * fix(mod queue): address transfer review feedback * fix(mod queue): hide transfer for unavailable posts * fix(mod queue): prevent repeat transfer submit * fix(mod queue): sync transfer state with queue actions * fix(mod queue): omit transfer temp index * fix(mod queue): harden transfer failure states * fix(mod queue): keep hidden transfer lock * fix(mod queue): release hidden transfer lock
This commit is contained in:
@@ -21,6 +21,8 @@ type TestComment = {
|
||||
error?: Error;
|
||||
commentModeration?: {
|
||||
archived?: boolean;
|
||||
flairs?: Array<{ text?: string }>;
|
||||
purged?: boolean;
|
||||
};
|
||||
locked?: boolean;
|
||||
number?: number;
|
||||
@@ -89,6 +91,10 @@ const enrichAccountCommentAuthor = (comment: TestComment | undefined): TestComme
|
||||
};
|
||||
};
|
||||
|
||||
function hasTransferredMarker(post?: TestComment): boolean {
|
||||
return post?.commentModeration?.flairs?.some((flair) => flair.text === '5chan:transferred') ?? false;
|
||||
}
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
@@ -194,11 +200,13 @@ vi.mock('../../../components/post-desktop/post-desktop', () => ({
|
||||
roles,
|
||||
targetReplyCid,
|
||||
replyPaginationOverride,
|
||||
onTransfer,
|
||||
}: {
|
||||
post?: TestComment;
|
||||
roles?: Record<string, unknown>;
|
||||
targetReplyCid?: string;
|
||||
replyPaginationOverride?: { replies?: TestComment[] };
|
||||
onTransfer?: () => void;
|
||||
}) =>
|
||||
createElement(
|
||||
'div',
|
||||
@@ -210,6 +218,8 @@ vi.mock('../../../components/post-desktop/post-desktop', () => ({
|
||||
'data-pending-approval': post?.pendingApproval === undefined ? '' : String(post.pendingApproval),
|
||||
'data-replies': replyPaginationOverride?.replies?.map((reply) => reply.cid).join(',') || '',
|
||||
'data-roles-present': String(roles !== undefined),
|
||||
'data-transfer-enabled': String(typeof onTransfer === 'function'),
|
||||
'data-transferred': String(hasTransferredMarker(post)),
|
||||
},
|
||||
createElement('div', { 'data-thread-container-cid': post?.cid }),
|
||||
createElement('div', { 'data-post-info-cid': post?.cid }),
|
||||
@@ -223,11 +233,13 @@ vi.mock('../../../components/post-mobile/post-mobile', () => ({
|
||||
roles,
|
||||
targetReplyCid,
|
||||
replyPaginationOverride,
|
||||
onTransfer,
|
||||
}: {
|
||||
post?: TestComment;
|
||||
roles?: Record<string, unknown>;
|
||||
targetReplyCid?: string;
|
||||
replyPaginationOverride?: { replies?: TestComment[] };
|
||||
onTransfer?: () => void;
|
||||
}) =>
|
||||
createElement(
|
||||
'div',
|
||||
@@ -239,6 +251,8 @@ vi.mock('../../../components/post-mobile/post-mobile', () => ({
|
||||
'data-pending-approval': post?.pendingApproval === undefined ? '' : String(post.pendingApproval),
|
||||
'data-replies': replyPaginationOverride?.replies?.map((reply) => reply.cid).join(',') || '',
|
||||
'data-roles-present': String(roles !== undefined),
|
||||
'data-transfer-enabled': String(typeof onTransfer === 'function'),
|
||||
'data-transferred': String(hasTransferredMarker(post)),
|
||||
},
|
||||
createElement('div', { 'data-thread-container-cid': post?.cid }),
|
||||
createElement('div', { 'data-post-info-cid': post?.cid }),
|
||||
@@ -505,6 +519,78 @@ describe('Post', () => {
|
||||
expect(container.querySelector('[data-testid="post-desktop"]')?.getAttribute('data-author-address')).toBe('account-author');
|
||||
});
|
||||
|
||||
it('rerenders posts when a transferred moderation marker appears', async () => {
|
||||
await act(async () => {
|
||||
root.render(
|
||||
createElement(Post, {
|
||||
post: {
|
||||
cid: 'post-transferred',
|
||||
communityAddress: 'music-posting.eth',
|
||||
content: 'body',
|
||||
replyCount: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(container.querySelector('[data-testid="post-desktop"]')?.getAttribute('data-transferred')).toBe('false');
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
createElement(Post, {
|
||||
post: {
|
||||
cid: 'post-transferred',
|
||||
commentModeration: {
|
||||
flairs: [{ text: '5chan:transferred' }],
|
||||
},
|
||||
communityAddress: 'music-posting.eth',
|
||||
content: 'body',
|
||||
replyCount: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(container.querySelector('[data-testid="post-desktop"]')?.getAttribute('data-transferred')).toBe('true');
|
||||
});
|
||||
|
||||
it('only forwards transfer handlers for top-level posts', async () => {
|
||||
const handleTransfer = vi.fn();
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
createElement(Post, {
|
||||
onTransfer: handleTransfer,
|
||||
post: {
|
||||
cid: 'post-transfer-handler',
|
||||
communityAddress: 'music-posting.eth',
|
||||
content: 'body',
|
||||
replyCount: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(container.querySelector('[data-testid="post-desktop"]')?.getAttribute('data-transfer-enabled')).toBe('true');
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
createElement(Post, {
|
||||
onTransfer: handleTransfer,
|
||||
post: {
|
||||
cid: 'post-transfer-handler',
|
||||
communityAddress: 'music-posting.eth',
|
||||
content: 'body',
|
||||
parentCid: 'thread-cid',
|
||||
replyCount: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(container.querySelector('[data-testid="post-desktop"]')?.getAttribute('data-transfer-enabled')).toBe('false');
|
||||
});
|
||||
|
||||
it('hydrates thread pages from cached feed data, sets the document title, and renders thread footers', async () => {
|
||||
testState.commentsByCid = {
|
||||
'cached-cid': {
|
||||
|
||||
@@ -27,6 +27,7 @@ import PostDesktop from '../../components/post-desktop/post-desktop';
|
||||
import PostMobile from '../../components/post-mobile/post-mobile';
|
||||
import { getRequestedThreadTopCid, scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
|
||||
import { evictThreadRefreshCaches } from '../../lib/utils/thread-refresh-cache-utils';
|
||||
import { hasTransferredCommentMarker } from '../../lib/comment-transfer';
|
||||
import { REPLIES_PER_PAGE } from '../../lib/constants';
|
||||
import useThreadLiveUpdatesStore from '../../stores/use-thread-live-updates-store';
|
||||
import type { QueuedCommentRouteState } from '../../lib/utils/mod-queue-utils';
|
||||
@@ -205,6 +206,7 @@ export interface PostProps {
|
||||
isPublishing?: boolean;
|
||||
onApprove?: () => void;
|
||||
onReject?: () => void;
|
||||
onTransfer?: () => void;
|
||||
onRemoveFromModQueue?: () => void;
|
||||
quotedByMap?: Map<string, Comment[]>;
|
||||
}
|
||||
@@ -221,6 +223,7 @@ export const Post = memo(
|
||||
isPublishing,
|
||||
onApprove,
|
||||
onReject,
|
||||
onTransfer,
|
||||
onRemoveFromModQueue,
|
||||
feedVirtualizationModeOverride,
|
||||
replyPaginationOverride,
|
||||
@@ -239,6 +242,7 @@ export const Post = memo(
|
||||
// handle pending mod or author edit
|
||||
const { editedComment } = useEditedComment({ comment });
|
||||
comment = mergeCommentFallback(editedComment as CommentWithRefresh | undefined, comment as CommentWithRefresh | undefined);
|
||||
const transferHandler = comment?.parentCid ? undefined : onTransfer;
|
||||
|
||||
return (
|
||||
<div className={styles.thread}>
|
||||
@@ -259,6 +263,7 @@ export const Post = memo(
|
||||
isPublishing={isPublishing}
|
||||
onApprove={onApprove}
|
||||
onReject={onReject}
|
||||
onTransfer={transferHandler}
|
||||
onRemoveFromModQueue={onRemoveFromModQueue}
|
||||
/>
|
||||
) : (
|
||||
@@ -277,6 +282,7 @@ export const Post = memo(
|
||||
isPublishing={isPublishing}
|
||||
onApprove={onApprove}
|
||||
onReject={onReject}
|
||||
onTransfer={transferHandler}
|
||||
onRemoveFromModQueue={onRemoveFromModQueue}
|
||||
/>
|
||||
)}
|
||||
@@ -290,6 +296,7 @@ export const Post = memo(
|
||||
return (
|
||||
prev?.cid === next?.cid &&
|
||||
prev?.number === next?.number &&
|
||||
prev?.parentCid === next?.parentCid &&
|
||||
prev?.postNumber === next?.postNumber &&
|
||||
prev?.replyCount === next?.replyCount &&
|
||||
prev?.updatedAt === next?.updatedAt &&
|
||||
@@ -309,6 +316,7 @@ export const Post = memo(
|
||||
prev?.deleted === next?.deleted &&
|
||||
prev?.reason === next?.reason &&
|
||||
prev?.commentModeration?.purged === next?.commentModeration?.purged &&
|
||||
hasTransferredCommentMarker(prev) === hasTransferredCommentMarker(next) &&
|
||||
prevProps.showAllReplies === nextProps.showAllReplies &&
|
||||
prevProps.showReplies === nextProps.showReplies &&
|
||||
prevProps.targetReplyCid === nextProps.targetReplyCid &&
|
||||
@@ -321,6 +329,7 @@ export const Post = memo(
|
||||
prevProps.isPublishing === nextProps.isPublishing &&
|
||||
prevProps.onApprove === nextProps.onApprove &&
|
||||
prevProps.onReject === nextProps.onReject &&
|
||||
prevProps.onTransfer === nextProps.onTransfer &&
|
||||
prevProps.onRemoveFromModQueue === nextProps.onRemoveFromModQueue
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user