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:
Tommaso Casaburi
2026-07-02 14:52:06 +07:00
committed by GitHub
parent 546ee99f12
commit b4a6f0a65a
55 changed files with 2688 additions and 154 deletions
+138
View File
@@ -0,0 +1,138 @@
import { describe, expect, it } from 'vitest';
import {
canTransferComment,
getTargetTransferModerationFlairs,
getTransferPublishPayload,
getTransferSourceBoardReference,
getTransferSourceBoardRulesLink,
getTransferSourceModeration,
hasTransferredCommentMarker,
TRANSFERRED_COMMENT_FLAIR,
} from '../comment-transfer';
describe('comment-transfer', () => {
const selectedFields = {
displayName: false,
title: true,
content: true,
link: true,
spoiler: true,
flairs: true,
};
it('only allows available top-level posts to transfer', () => {
expect(canTransferComment({ cid: 'source' } as never)).toBe(true);
expect(canTransferComment({ cid: 'reply', parentCid: 'source' } as never)).toBe(false);
expect(canTransferComment({ cid: '' } as never)).toBe(false);
expect(canTransferComment({ cid: 'source', deleted: true } as never)).toBe(false);
expect(canTransferComment({ cid: 'source', removed: true } as never)).toBe(false);
expect(canTransferComment({ cid: 'source', commentModeration: { removed: true } } as never)).toBe(false);
expect(canTransferComment({ cid: 'source', commentModeration: { purged: true } } as never)).toBe(false);
expect(canTransferComment({ cid: 'source', archived: true } as never)).toBe(false);
expect(canTransferComment({ cid: 'source', commentModeration: { archived: true } } as never)).toBe(false);
});
it('copies selected post fields while excluding flag and transferred flairs', () => {
const payload = getTransferPublishPayload(
{
cid: 'source',
communityAddress: 'music-posting.eth',
content: 'wrong board',
flairs: [{ text: 'flag:country:auto', type: 'country' }, { text: 'flash:loop' }, TRANSFERRED_COMMENT_FLAIR],
link: 'https://example.com/file.swf',
spoiler: true,
title: 'Flash',
} as never,
selectedFields,
'flash-posting.eth',
);
expect(payload).toMatchObject({
communityAddress: 'flash-posting.eth',
content: 'wrong board',
flairs: [{ text: 'flash:loop' }],
link: 'https://example.com/file.swf',
spoiler: true,
title: 'Flash',
});
});
it('copies selected comment body text verbatim while still ignoring blank bodies', () => {
expect(
getTransferPublishPayload(
{
cid: 'source',
communityAddress: 'music-posting.eth',
content: '\n wrong board \n',
} as never,
selectedFields,
'tech-posting.eth',
),
).toMatchObject({
communityAddress: 'tech-posting.eth',
content: '\n wrong board \n',
});
expect(
getTransferPublishPayload(
{
cid: 'source',
communityAddress: 'music-posting.eth',
content: ' ',
} as never,
selectedFields,
'tech-posting.eth',
),
).not.toHaveProperty('content');
});
it('adds the transferred marker to target moderation flairs after safe copied tags', () => {
const flairs = getTargetTransferModerationFlairs(
{
cid: 'source',
flairs: [{ text: 'flag:country:US' }, { text: 'flash:loop' }],
} as never,
selectedFields,
);
expect(flairs).toEqual([{ text: 'flash:loop' }, { text: '5chan:transferred' }]);
});
it('marks pending source comments as rejected and published source comments as removed', () => {
expect(getTransferSourceModeration({ pendingApproval: true } as never, '>>>/g/', '/mu/', '[rules](/rules#mu)')).toEqual({
approved: false,
reason: 'Moved to >>>/g/, this post did not belong to /mu/ ([rules](/rules#mu))',
});
expect(getTransferSourceModeration({ pendingApproval: false } as never, '>>>/g/', '/mu/', '[rules](/rules#mu)')).toEqual({
removed: true,
reason: 'Moved to >>>/g/, this post did not belong to /mu/ ([rules](/rules#mu))',
});
});
it('formats source board references and rules links from directory metadata', () => {
expect(getTransferSourceBoardReference({ address: 'japanese-culture.eth', directoryCode: 'jp', title: '/jp/ - Otaku Culture' }, 'fallback.eth')).toBe('/jp/');
expect(getTransferSourceBoardRulesLink({ address: 'japanese-culture.eth', directoryCode: 'jp', title: '/jp/ - Otaku Culture' })).toBe('[rules](/rules#jp)');
expect(getTransferSourceBoardReference(undefined, 'source-board.eth')).toBe('source-board.eth');
expect(getTransferSourceBoardRulesLink(undefined)).toBe('the rules');
});
it('only treats moderation/update flairs as the transferred marker', () => {
expect(hasTransferredCommentMarker({ flairs: [{ text: '5chan:transferred' }] })).toBe(false);
expect(
hasTransferredCommentMarker({
raw: {
commentUpdate: {
flairs: [{ text: '5chan:transferred' }],
},
},
}),
).toBe(true);
expect(
hasTransferredCommentMarker({
commentModeration: {
flairs: [{ text: '5chan:transferred' }],
},
}),
).toBe(true);
});
});