Ensured BoR post actions matches those in webapp (#9362)

* Ensured VBoR post actions matches those in webapp

* Updated tests

* Fixed a value

* Review fixes
This commit is contained in:
Harshil Sharma 2026-02-04 12:18:35 +05:30 committed by GitHub
parent 8732cb9430
commit a9258543bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 63 additions and 22 deletions

View file

@ -102,7 +102,7 @@ const Header = (props: HeaderProps) => {
const usernameOverride = ensureString(post.props?.override_username);
const isUnrevealedPost = useMemo(() => isUnrevealedBoRPost(post), [post, post.metadata?.expire_at]);
const ownBoRPost = useMemo(() => isOwnBoRPost(post, currentUser), [currentUser, post]);
const ownBoRPost = useMemo(() => isOwnBoRPost(post, currentUser?.id), [currentUser?.id, post]);
const showBoRIcon = isUnrevealedPost || ownBoRPost;
const borExpireAt = post.metadata?.expire_at;
const serverUrl = useServerUrl();

View file

@ -28,7 +28,38 @@ describe('PostOptions', () => {
database = server.database;
});
it('should show limited options for unrevealed BoR post', async () => {
it('should show limited options for own BoR post', async () => {
const unrevealedBoRPost = TestHelper.fakePostModel({
type: PostTypes.BURN_ON_READ,
channelId: TestHelper.basicChannel!.id,
userId: TestHelper.basicUser!.id,
});
renderWithEverything(
<PostOptions
post={unrevealedBoRPost}
serverUrl={serverUrl}
showAddReaction={true}
sourceScreen={'DraftScheduledPostOptions'}
componentId={'DraftScheduledPostOptions'}
/>,
{database},
);
await waitFor(() => {
expect(screen.queryByText('Save')).toBeVisible();
});
expect(screen.queryByText('Copy Link')).toBeVisible();
expect(screen.queryByText('Pin to Channel')).not.toBeVisible();
expect(screen.queryByText('Copy Text')).not.toBeVisible();
expect(screen.queryByText('Edit')).not.toBeVisible();
expect(screen.queryByText('Reply')).not.toBeVisible();
expect(screen.queryByText('Follow Message')).not.toBeVisible();
});
it('should show limited options for received unrevealed BoR post', async () => {
const unrevealedBoRPost = TestHelper.fakePostModel({
type: PostTypes.BURN_ON_READ,
channelId: TestHelper.basicChannel!.id,
@ -54,9 +85,8 @@ describe('PostOptions', () => {
expect(screen.getByText('Mark as Unread')).toBeVisible();
});
expect(screen.queryByText('Copy Link')).toBeVisible();
expect(screen.queryByText('Save')).toBeVisible();
expect(screen.queryByText('Copy Link')).not.toBeVisible();
expect(screen.queryByText('Save')).not.toBeVisible();
expect(screen.queryByText('Pin to Channel')).not.toBeVisible();
expect(screen.queryByText('Copy Text')).not.toBeVisible();
expect(screen.queryByText('Edit')).not.toBeVisible();
@ -64,7 +94,7 @@ describe('PostOptions', () => {
expect(screen.queryByText('Follow Message')).not.toBeVisible();
});
it('should show limited options for revealed BoR post', async () => {
it('should show limited options for someone else\'s revealed BoR post', async () => {
const unrevealedBoRPost = TestHelper.fakePostModel({
type: PostTypes.BURN_ON_READ,
channelId: TestHelper.basicChannel!.id,
@ -91,11 +121,10 @@ describe('PostOptions', () => {
await waitFor(() => {
expect(screen.getByText('Mark as Unread')).toBeVisible();
expect(screen.queryByText('Save')).toBeVisible();
});
expect(screen.queryByText('Copy Link')).toBeVisible();
expect(screen.queryByText('Save')).toBeVisible();
expect(screen.queryByText('Copy Link')).not.toBeVisible();
expect(screen.queryByText('Copy Text')).not.toBeVisible();
expect(screen.queryByText('Pin to Channel')).not.toBeVisible();
expect(screen.queryByText('Edit')).not.toBeVisible();

View file

@ -3,7 +3,7 @@
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
import {combineLatest, of as of$, Observable} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
import {Permissions, Post, Screens} from '@constants';
import {AppBindingLocations} from '@constants/apps';
@ -146,14 +146,20 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, sour
}),
);
const canDelete = combineLatest([canDeletePostPermission, channelIsArchived, channelIsReadOnly, canPostPermission]).pipe(switchMap(([permission, isArchived, isReadOnly, canPost]) => {
return of$(permission && !isArchived && !isReadOnly && canPost);
const canDelete = combineLatest([canDeletePostPermission, channelIsArchived, channelIsReadOnly, canPostPermission, currentUser]).pipe(switchMap(([permission, isArchived, isReadOnly, canPost, user]) => {
const canDeleteBoRPost = borPost ? post.userId === user?.id : true;
return of$(permission && !isArchived && !isReadOnly && canPost && canDeleteBoRPost);
}));
const thread = observeIsCRTEnabled(database).pipe(
switchMap((enabled) => (enabled ? observeThreadById(database, post.id) : of$(undefined))),
);
const currentUserId = currentUser.pipe(
switchMap((u) => of$(u?.id)),
distinctUntilChanged(),
);
return {
canMarkAsUnread,
canAddReaction,
@ -167,6 +173,7 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, sour
thread,
bindings,
isBoRPost: of$(borPost),
currentUserId,
};
});

View file

@ -16,6 +16,7 @@ import {useIsTablet} from '@hooks/device';
import useNavButtonPressed from '@hooks/navigation_button_pressed';
import BottomSheet from '@screens/bottom_sheet';
import {dismissBottomSheet} from '@screens/navigation';
import {isOwnBoRPost, isUnrevealedBoRPost} from '@utils/bor';
import {bottomSheetSnapPoint} from '@utils/helpers';
import {isSystemMessage} from '@utils/post';
@ -48,13 +49,14 @@ type PostOptionsProps = {
bindings: AppBinding[];
serverUrl: string;
isBoRPost?: boolean;
currentUserId?: string;
};
const PostOptions = ({
canAddReaction, canDelete, canEdit,
canMarkAsUnread, canPin, canReply,
combinedPost, componentId, isSaved,
sourceScreen, post, thread, bindings, serverUrl,
isBoRPost,
isBoRPost, currentUserId,
}: PostOptionsProps) => {
const managedConfig = useManagedConfig<ManagedConfig>();
const isTablet = useIsTablet();
@ -69,9 +71,12 @@ const PostOptions = ({
const isSystemPost = isSystemMessage(post);
const canCopyPermalink = !isSystemPost && managedConfig?.copyAndPasteProtection !== 'true';
const canCopyBoRPostPermalink = isBoRPost ? post.userId === currentUserId : true;
const canCopyPermalink = !isSystemPost && managedConfig?.copyAndPasteProtection !== 'true' && canCopyBoRPostPermalink;
const canCopyText = canCopyPermalink && post.message && !isBoRPost;
const canSavePost = !isSystemPost && (!isUnrevealedBoRPost(post) || isOwnBoRPost(post, currentUserId));
const shouldRenderFollow = !(sourceScreen !== Screens.CHANNEL || !thread);
const shouldShowBindings = bindings.length > 0 && !isSystemPost;
@ -79,7 +84,7 @@ const PostOptions = ({
const items: Array<string | number> = [1];
const optionsCount = [
canCopyPermalink, canCopyText, canDelete, canEdit,
canMarkAsUnread, canPin, canReply, !isSystemPost, shouldRenderFollow,
canMarkAsUnread, canPin, canReply, canSavePost, shouldRenderFollow,
].reduce((acc, v) => {
return v ? acc + 1 : acc;
}, 0) + (shouldShowBindings ? 0.5 : 0);
@ -94,7 +99,7 @@ const PostOptions = ({
}, [
canAddReaction, canCopyPermalink, canCopyText,
canDelete, canEdit, shouldRenderFollow, shouldShowBindings,
canMarkAsUnread, canPin, canReply, isSystemPost,
canMarkAsUnread, canPin, canReply, canSavePost,
]);
const renderContent = () => {
@ -136,7 +141,7 @@ const PostOptions = ({
sourceScreen={sourceScreen}
/>
}
{!isSystemPost &&
{canSavePost &&
<SaveOption
bottomSheetId={Screens.POST_OPTIONS}
isSaved={isSaved}

View file

@ -100,7 +100,7 @@ describe('BoR utility functions', () => {
userId: 'user123',
} as PostModel;
expect(isOwnBoRPost(ownBorPost, mockUser)).toBe(true);
expect(isOwnBoRPost(ownBorPost, mockUser.id)).toBe(true);
});
it('should return false for BoR posts not owned by current user', () => {
@ -109,7 +109,7 @@ describe('BoR utility functions', () => {
userId: 'user456',
} as PostModel;
expect(isOwnBoRPost(othersBorPost, mockUser)).toBe(false);
expect(isOwnBoRPost(othersBorPost, mockUser.id)).toBe(false);
});
it('should return false for non-BoR posts', () => {
@ -118,7 +118,7 @@ describe('BoR utility functions', () => {
userId: 'user123',
} as PostModel;
expect(isOwnBoRPost(ownRegularPost, mockUser)).toBe(false);
expect(isOwnBoRPost(ownRegularPost, mockUser.id)).toBe(false);
});
it('should return false when no current user is provided', () => {

View file

@ -17,8 +17,8 @@ export function isUnrevealedBoRPost(post: Post | PostModel): boolean {
return isBoRPost(post) && Boolean(!post.metadata?.expire_at);
}
export function isOwnBoRPost(post: PostModel, currentUser?: UserModel): boolean {
return isBoRPost(post) && Boolean(currentUser && post.userId === currentUser.id);
export function isOwnBoRPost(post: PostModel, currentUserId?: UserModel['id']): boolean {
return isBoRPost(post) && post.userId === currentUserId;
}
function isBoRPostExpiredForMe(post: PostModel): boolean {