mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Merge branch 'codex/fix/pending-escape-board-redirect'
# Conflicts: # src/components/post-form/post-form.tsx
This commit is contained in:
@@ -906,7 +906,7 @@ describe('PostForm', () => {
|
|||||||
await flushEffects();
|
await flushEffects();
|
||||||
|
|
||||||
expect(testState.resetPublishPostOptionsMock).toHaveBeenCalledTimes(1);
|
expect(testState.resetPublishPostOptionsMock).toHaveBeenCalledTimes(1);
|
||||||
expect(testState.navigateMock).toHaveBeenCalledWith('/pending/7');
|
expect(testState.navigateMock).toHaveBeenCalledWith('/pending/7', { state: { boardPath: 'mu' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('redirects new posts to the board index when nonoko is used', async () => {
|
it('redirects new posts to the board index when nonoko is used', async () => {
|
||||||
|
|||||||
@@ -431,6 +431,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
|||||||
const subscriptions = account?.subscriptions || [];
|
const subscriptions = account?.subscriptions || [];
|
||||||
const directories = useDirectories();
|
const directories = useDirectories();
|
||||||
const directoryEntry = useDirectoryByAddress(effectiveBoardAddress);
|
const directoryEntry = useDirectoryByAddress(effectiveBoardAddress);
|
||||||
|
const pendingPostBoardPath = effectiveBoardAddress ? getBoardPath(effectiveBoardAddress, directories) : undefined;
|
||||||
const rulesPath = effectiveBoardAddress ? `/rules/${getBoardPath(effectiveBoardAddress, directories)}` : '/rules';
|
const rulesPath = effectiveBoardAddress ? `/rules/${getBoardPath(effectiveBoardAddress, directories)}` : '/rules';
|
||||||
const showSpoilerForPost = directoryEntry?.features?.noSpoilers !== true;
|
const showSpoilerForPost = directoryEntry?.features?.noSpoilers !== true;
|
||||||
const showSpoilerForReply = directoryEntry?.features?.noSpoilerReplies !== true;
|
const showSpoilerForReply = directoryEntry?.features?.noSpoilerReplies !== true;
|
||||||
@@ -556,10 +557,10 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
|
|||||||
if (nonokoRedirectPath) {
|
if (nonokoRedirectPath) {
|
||||||
navigate(nonokoRedirectPath, { state: getNonokoPendingRouteState(postIndex) });
|
navigate(nonokoRedirectPath, { state: getNonokoPendingRouteState(postIndex) });
|
||||||
} else {
|
} else {
|
||||||
navigate(`/pending/${postIndex}`);
|
navigate(`/pending/${postIndex}`, pendingPostBoardPath ? { state: { boardPath: pendingPostBoardPath } } : undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [postIndex, resetPublishPostOptions, navigate]);
|
}, [postIndex, pendingPostBoardPath, resetPublishPostOptions, navigate]);
|
||||||
|
|
||||||
// in post page, publish a reply to the post
|
// in post page, publish a reply to the post
|
||||||
const isInPostView = isPostPageView(location.pathname, params);
|
const isInPostView = isPostPageView(location.pathname, params);
|
||||||
|
|||||||
@@ -65,6 +65,13 @@ vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
|
|||||||
},
|
},
|
||||||
state: 'succeeded',
|
state: 'succeeded',
|
||||||
}),
|
}),
|
||||||
|
useCommunities: ({ communities }: { communities?: Array<{ name: string }> } = {}) => ({
|
||||||
|
communities: (communities ?? []).map(() => ({
|
||||||
|
roles: {
|
||||||
|
'0x123': { role: 'moderator' },
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}),
|
||||||
useEditedComment: ({ comment }: { comment?: TestComment }) => ({
|
useEditedComment: ({ comment }: { comment?: TestComment }) => ({
|
||||||
editedComment: comment,
|
editedComment: comment,
|
||||||
failedEdits: {},
|
failedEdits: {},
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ type TestComment = {
|
|||||||
const testState = vi.hoisted(() => ({
|
const testState = vi.hoisted(() => ({
|
||||||
accountCommentIndex: undefined as string | undefined,
|
accountCommentIndex: undefined as string | undefined,
|
||||||
accountComments: [] as TestComment[],
|
accountComments: [] as TestComment[],
|
||||||
|
challengeCount: 0,
|
||||||
directories: [] as Array<{ address: string; title?: string }>,
|
directories: [] as Array<{ address: string; title?: string }>,
|
||||||
getBoardPathMock: vi.fn<(address: string) => string>(),
|
getBoardPathMock: vi.fn<(address: string) => string>(),
|
||||||
|
locationState: null as { boardPath?: string } | null,
|
||||||
navigateMock: vi.fn(),
|
navigateMock: vi.fn(),
|
||||||
post: undefined as TestComment | undefined,
|
post: undefined as TestComment | undefined,
|
||||||
}));
|
}));
|
||||||
@@ -26,6 +28,9 @@ vi.mock('react-router-dom', async () => {
|
|||||||
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
|
useLocation: () => ({
|
||||||
|
state: testState.locationState,
|
||||||
|
}),
|
||||||
useNavigate: () => testState.navigateMock,
|
useNavigate: () => testState.navigateMock,
|
||||||
useParams: () => ({
|
useParams: () => ({
|
||||||
accountCommentIndex: testState.accountCommentIndex,
|
accountCommentIndex: testState.accountCommentIndex,
|
||||||
@@ -49,6 +54,10 @@ vi.mock('../../../lib/utils/route-utils', () => ({
|
|||||||
getBoardPath: (address: string) => testState.getBoardPathMock(address),
|
getBoardPath: (address: string) => testState.getBoardPathMock(address),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../stores/use-challenges-store', () => ({
|
||||||
|
default: (selector: (state: { challenges: unknown[] }) => unknown) => selector({ challenges: Array.from({ length: testState.challengeCount }) }),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../../post', () => ({
|
vi.mock('../../post', () => ({
|
||||||
Post: ({ post }: { post?: TestComment }) => createElement('div', { 'data-testid': 'post-view' }, post?.cid ?? 'no-post'),
|
Post: ({ post }: { post?: TestComment }) => createElement('div', { 'data-testid': 'post-view' }, post?.cid ?? 'no-post'),
|
||||||
}));
|
}));
|
||||||
@@ -78,8 +87,10 @@ describe('PendingPost', () => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
testState.accountCommentIndex = undefined;
|
testState.accountCommentIndex = undefined;
|
||||||
testState.accountComments = [];
|
testState.accountComments = [];
|
||||||
|
testState.challengeCount = 0;
|
||||||
testState.directories = [];
|
testState.directories = [];
|
||||||
testState.getBoardPathMock.mockReset();
|
testState.getBoardPathMock.mockReset();
|
||||||
|
testState.locationState = null;
|
||||||
testState.navigateMock.mockReset();
|
testState.navigateMock.mockReset();
|
||||||
testState.post = undefined;
|
testState.post = undefined;
|
||||||
|
|
||||||
@@ -151,6 +162,25 @@ describe('PendingPost', () => {
|
|||||||
expect(testState.navigateMock).not.toHaveBeenCalledWith('/not-found', { replace: true });
|
expect(testState.navigateMock).not.toHaveBeenCalledWith('/not-found', { replace: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('redirects abandoned pending posts back to their board after the challenge closes', async () => {
|
||||||
|
testState.accountCommentIndex = '0';
|
||||||
|
testState.accountComments = [];
|
||||||
|
testState.challengeCount = 1;
|
||||||
|
testState.locationState = { boardPath: 'mu' };
|
||||||
|
testState.post = { index: 0 };
|
||||||
|
|
||||||
|
await renderPendingPost();
|
||||||
|
|
||||||
|
expect(testState.navigateMock).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
testState.challengeCount = 0;
|
||||||
|
testState.navigateMock.mockClear();
|
||||||
|
|
||||||
|
await renderPendingPost();
|
||||||
|
|
||||||
|
expect(testState.navigateMock).toHaveBeenCalledWith('/mu', { replace: true });
|
||||||
|
});
|
||||||
|
|
||||||
it('redirects missing sparse pending account comment indices to not found', async () => {
|
it('redirects missing sparse pending account comment indices to not found', async () => {
|
||||||
testState.accountCommentIndex = '0';
|
testState.accountCommentIndex = '0';
|
||||||
testState.accountComments = [{ index: 1 }];
|
testState.accountComments = [{ index: 1 }];
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useAccountComments } from '@bitsocial/bitsocial-react-hooks';
|
import { useAccountComments } from '@bitsocial/bitsocial-react-hooks';
|
||||||
import { useDirectories } from '../../hooks/use-directories';
|
import { useDirectories } from '../../hooks/use-directories';
|
||||||
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
|
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
|
||||||
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
|
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
|
||||||
import { getBoardPath } from '../../lib/utils/route-utils';
|
import { getBoardPath } from '../../lib/utils/route-utils';
|
||||||
|
import useChallengesStore from '../../stores/use-challenges-store';
|
||||||
import { Post } from '../post';
|
import { Post } from '../post';
|
||||||
|
|
||||||
type PendingAccountComment = {
|
type PendingAccountComment = {
|
||||||
@@ -31,17 +32,40 @@ const hasPendingAccountCommentIndex = (accountComments: PendingAccountComment[]
|
|||||||
return hasExplicitIndices ? false : accountCommentIndex < accountComments.length;
|
return hasExplicitIndices ? false : accountCommentIndex < accountComments.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPendingRouteBoardPath = (state: unknown): string | undefined => {
|
||||||
|
if (!state || typeof state !== 'object') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const boardPath = (state as { boardPath?: unknown }).boardPath;
|
||||||
|
return typeof boardPath === 'string' && boardPath ? boardPath : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const PendingPost = () => {
|
const PendingPost = () => {
|
||||||
const { accountComments } = useAccountComments();
|
const { accountComments } = useAccountComments();
|
||||||
const { accountCommentIndex } = useParams<{ accountCommentIndex?: string }>();
|
const { accountCommentIndex } = useParams<{ accountCommentIndex?: string }>();
|
||||||
|
const location = useLocation();
|
||||||
const normalizedAccountCommentIndex = accountCommentIndex === undefined ? undefined : Number(accountCommentIndex);
|
const normalizedAccountCommentIndex = accountCommentIndex === undefined ? undefined : Number(accountCommentIndex);
|
||||||
const hasNormalizedAccountCommentIndex = normalizedAccountCommentIndex !== undefined && !Number.isNaN(normalizedAccountCommentIndex);
|
const hasNormalizedAccountCommentIndex = normalizedAccountCommentIndex !== undefined && !Number.isNaN(normalizedAccountCommentIndex);
|
||||||
const post = useSafeAccountComment({ commentIndex: accountCommentIndex });
|
const post = useSafeAccountComment({ commentIndex: accountCommentIndex });
|
||||||
|
const postCommunityAddress = getCommentCommunityAddress(post);
|
||||||
|
const hasAddressablePost = Boolean(post?.cid || postCommunityAddress);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const directories = useDirectories();
|
const directories = useDirectories();
|
||||||
|
const routeBoardPath = getPendingRouteBoardPath(location.state);
|
||||||
|
const postBoardPath = postCommunityAddress ? getBoardPath(postCommunityAddress, directories) : undefined;
|
||||||
|
const pendingBoardPath = postBoardPath || routeBoardPath;
|
||||||
|
const hasActiveChallenge = useChallengesStore((state) => state.challenges.length > 0);
|
||||||
|
const lastPendingBoardRef = useRef<{ accountCommentIndex: number; boardPath: string } | null>(null);
|
||||||
|
|
||||||
useEffect(() => window.scrollTo(0, 0), []);
|
useEffect(() => window.scrollTo(0, 0), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof normalizedAccountCommentIndex === 'number' && pendingBoardPath) {
|
||||||
|
lastPendingBoardRef.current = { accountCommentIndex: normalizedAccountCommentIndex, boardPath: pendingBoardPath };
|
||||||
|
}
|
||||||
|
}, [normalizedAccountCommentIndex, pendingBoardPath]);
|
||||||
|
|
||||||
const isValidAccountCommentIndex =
|
const isValidAccountCommentIndex =
|
||||||
!accountCommentIndex ||
|
!accountCommentIndex ||
|
||||||
(hasNormalizedAccountCommentIndex &&
|
(hasNormalizedAccountCommentIndex &&
|
||||||
@@ -51,17 +75,31 @@ const PendingPost = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isValidAccountCommentIndex) {
|
if (!isValidAccountCommentIndex) {
|
||||||
navigate('/not-found', { replace: true });
|
const lastPendingBoard = lastPendingBoardRef.current;
|
||||||
|
const abandonedBoardPath =
|
||||||
|
!hasActiveChallenge && lastPendingBoard && lastPendingBoard.accountCommentIndex === normalizedAccountCommentIndex ? lastPendingBoard.boardPath : undefined;
|
||||||
|
navigate(abandonedBoardPath ? `/${abandonedBoardPath}` : '/not-found', { replace: true });
|
||||||
}
|
}
|
||||||
}, [isValidAccountCommentIndex, navigate]);
|
}, [hasActiveChallenge, isValidAccountCommentIndex, navigate, normalizedAccountCommentIndex]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const postCommunityAddress = getCommentCommunityAddress(post);
|
if (post?.cid && postBoardPath) {
|
||||||
if (post?.cid && postCommunityAddress) {
|
navigate(`/${postBoardPath}/thread/${post.cid}`, { replace: true });
|
||||||
const boardPath = getBoardPath(postCommunityAddress, directories);
|
|
||||||
navigate(`/${boardPath}/thread/${post.cid}`, { replace: true });
|
|
||||||
}
|
}
|
||||||
}, [post, navigate, directories]);
|
}, [post?.cid, postBoardPath, navigate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasAddressablePost || !isValidAccountCommentIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastPendingBoard = lastPendingBoardRef.current;
|
||||||
|
const abandonedBoardPath =
|
||||||
|
!hasActiveChallenge && lastPendingBoard && lastPendingBoard.accountCommentIndex === normalizedAccountCommentIndex ? lastPendingBoard.boardPath : undefined;
|
||||||
|
if (abandonedBoardPath) {
|
||||||
|
navigate(`/${abandonedBoardPath}`, { replace: true });
|
||||||
|
}
|
||||||
|
}, [hasActiveChallenge, hasAddressablePost, isValidAccountCommentIndex, navigate, normalizedAccountCommentIndex]);
|
||||||
|
|
||||||
return <Post post={post} />;
|
return <Post post={post} />;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user