fix(mobile): hide empty mobile reply backlink wrapper (#1060)

getRenderableMobileBacklinks() now filters mobile backlinks down to replies that can actually render, so ReplyBacklinks only mounts mobileReplyBacklinks when there is visible content. This adds a regression test for the gap where a newly published reply exists before it receives its post number.
This commit is contained in:
Tommaso Casaburi
2026-03-12 16:40:48 +08:00
committed by GitHub
parent 48fccede8d
commit ef375e3578
3 changed files with 105 additions and 38 deletions
@@ -0,0 +1,44 @@
import type { Comment } from '@bitsocialnet/bitsocial-react-hooks';
import { describe, expect, it } from 'vitest';
import { getRenderableMobileBacklinks } from '../reply-backlink-utils';
const createReply = (overrides: Partial<Comment> = {}) =>
({
cid: 'reply-cid',
parentCid: 'target-cid',
subplebbitAddress: 'music.eth',
...overrides,
}) as Comment;
describe('getRenderableMobileBacklinks', () => {
it('does not report mobile reply backlinks until a renderable backlink exists', () => {
const unpublishedReply = createReply({
number: undefined,
});
const beforePublish = getRenderableMobileBacklinks({
cid: 'target-cid',
parentCid: 'op-cid',
quotedByMap: new Map([['target-cid', [unpublishedReply]]]),
directRepliesByParentCid: new Map([['target-cid', [unpublishedReply]]]),
});
expect(beforePublish.directReplyBacklinks).toHaveLength(0);
expect(beforePublish.quotedReplyBacklinks).toHaveLength(0);
const publishedReply = createReply({
cid: 'published-reply-cid',
number: 42,
});
const afterPublish = getRenderableMobileBacklinks({
cid: 'target-cid',
parentCid: 'op-cid',
quotedByMap: new Map([['target-cid', [publishedReply]]]),
directRepliesByParentCid: new Map([['target-cid', [publishedReply]]]),
});
expect(afterPublish.directReplyBacklinks).toEqual([publishedReply]);
expect(afterPublish.quotedReplyBacklinks).toHaveLength(0);
});
});
+44
View File
@@ -0,0 +1,44 @@
import type { Comment } from '@bitsocialnet/bitsocial-react-hooks';
interface GetRenderableMobileBacklinksArgs {
cid?: string;
parentCid?: string;
quotedByMap?: Map<string, Comment[]>;
directRepliesByParentCid?: Map<string, Comment[]>;
}
interface RenderableMobileBacklinks {
opBacklinks: Comment[];
directReplyBacklinks: Comment[];
quotedReplyBacklinks: Comment[];
}
const isRenderableBacklinkReply = (reply?: Comment) => Boolean(reply?.cid && typeof reply.number === 'number' && !(reply.deleted || reply.removed));
export const getRenderableMobileBacklinks = ({ cid, parentCid, quotedByMap, directRepliesByParentCid }: GetRenderableMobileBacklinksArgs): RenderableMobileBacklinks => {
if (!cid) {
return {
opBacklinks: [],
directReplyBacklinks: [],
quotedReplyBacklinks: [],
};
}
const quotedReplies = quotedByMap?.get(cid) ?? [];
if (!parentCid) {
return {
opBacklinks: quotedReplies.filter(isRenderableBacklinkReply),
directReplyBacklinks: [],
quotedReplyBacklinks: [],
};
}
const directReplies = directRepliesByParentCid?.get(cid) ?? [];
return {
opBacklinks: [],
directReplyBacklinks: directReplies.filter((reply) => reply?.parentCid === cid && isRenderableBacklinkReply(reply)),
quotedReplyBacklinks: quotedReplies.filter((reply) => reply?.parentCid !== cid && isRenderableBacklinkReply(reply)),
};
};