Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions packages/extension/src/companion/CompanionEngagements.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { useQueryClient } from '@tanstack/react-query';
import { UserVote } from '@dailydotdev/shared/src/graphql/posts';
import type { PostBootData } from '@dailydotdev/shared/src/lib/boot';
import { CompanionEngagements } from './CompanionEngagements';

const mockUseConditionalFeature = jest.fn();
const mockUseAuthContext = jest.fn();
const mockUseQueryClient = useQueryClient as jest.Mock;

jest.mock('@dailydotdev/shared/src/hooks/useConditionalFeature', () => ({
useConditionalFeature: () => mockUseConditionalFeature(),
}));

jest.mock('@dailydotdev/shared/src/contexts/AuthContext', () => ({
useAuthContext: () => mockUseAuthContext(),
}));

jest.mock('@dailydotdev/shared/src/hooks/companion', () => ({
useRawBackgroundRequest: jest.fn(),
}));

jest.mock('@tanstack/react-query', () => ({
...jest.requireActual('@tanstack/react-query'),
useQueryClient: jest.fn(),
}));

describe('CompanionEngagements', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseAuthContext.mockReturnValue({ user: { id: 'user-id' } });
mockUseConditionalFeature.mockReturnValue({
value: {
threshold: 3,
belowThresholdLabel: 'New',
newWindowHours: 24,
},
});
mockUseQueryClient.mockReturnValue({
setQueryData: jest.fn(),
});
});

it('does not render below-threshold label in companion', () => {
const post = {
id: 'post-id',
createdAt: new Date().toISOString(),
numUpvotes: 1,
numComments: 2,
userState: {
vote: UserVote.None,
},
} as PostBootData;

render(<CompanionEngagements post={post} />);

expect(screen.queryByText('New')).not.toBeInTheDocument();
});
});
5 changes: 1 addition & 4 deletions packages/extension/src/companion/CompanionEngagements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function CompanionEngagements({
const upvotes = post.numUpvotes ?? 0;
const comments = post.numComments ?? 0;
const userHasUpvoted = post.userState?.vote === UserVote.Up;
const { showCount, belowThresholdLabel } = getUpvoteCountDisplay(
const { showCount } = getUpvoteCountDisplay(
upvotes,
upvoteThresholdConfig.threshold,
upvoteThresholdConfig.belowThresholdLabel,
Expand All @@ -68,9 +68,6 @@ export function CompanionEngagements({
{upvotes > 1 ? 's' : ''}
</ClickableText>
)}
{!showCount && !!belowThresholdLabel && (
<span>{belowThresholdLabel}</span>
)}
{comments > 0 && (
<span>
{largeNumberFormat(comments)}
Expand Down
59 changes: 59 additions & 0 deletions packages/shared/src/components/cards/common/PostMetadata.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import PostMetadata from './PostMetadata';

const mockUseConditionalFeature = jest.fn();
const mockUseAuthContext = jest.fn();

jest.mock('../../../hooks/useConditionalFeature', () => ({
useConditionalFeature: () => mockUseConditionalFeature(),
}));

jest.mock('../../../contexts/AuthContext', () => ({
useAuthContext: () => mockUseAuthContext(),
}));

describe('PostMetadata upvote label visibility', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseAuthContext.mockReturnValue({ user: { id: 'user-id' } });
mockUseConditionalFeature.mockReturnValue({
value: {
threshold: 3,
belowThresholdLabel: 'New',
newWindowHours: 24,
},
});
});

it('renders below-threshold label by default', () => {
render(
<PostMetadata
createdAt={new Date().toISOString()}
numUpvotes={1}
readTime={1}
/>,
);

expect(screen.getByText('New')).toBeInTheDocument();
});

it('does not render below-threshold label when disabled for non-card surfaces', () => {
render(
<PostMetadata
createdAt={new Date().toISOString()}
numUpvotes={1}
readTime={1}
showBelowThresholdLabel={false}
/>,
);

expect(screen.queryByText('New')).not.toBeInTheDocument();
});

it('does not render below-threshold label when upvote count is not provided', () => {
render(<PostMetadata createdAt={new Date().toISOString()} readTime={1} />);

expect(screen.queryByText('New')).not.toBeInTheDocument();
});
});
26 changes: 16 additions & 10 deletions packages/shared/src/components/cards/common/PostMetadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface PostMetadataProps
domain?: ReactNode;
pollMetadata?: PollMetadataProps;
userHasUpvoted?: boolean;
showBelowThresholdLabel?: boolean;
}

export default function PostMetadata({
Expand All @@ -38,7 +39,9 @@ export default function PostMetadata({
domain,
pollMetadata,
userHasUpvoted = false,
showBelowThresholdLabel = true,
}: PostMetadataProps): ReactElement {
const hasUpvoteCount = typeof numUpvotes === 'number';
const upvoteCount = numUpvotes ?? 0;
const readTimeValue = readTime ?? 0;
const timeActionContent = isVideoType ? 'watch' : 'read';
Expand Down Expand Up @@ -94,16 +97,19 @@ export default function PostMetadata({
),
},
!!showReadTime && domain && { key: 'domain', node: domain },
showUpvoteCount && {
key: 'upvotes',
node: (
<span data-testid="numUpvotes">
{largeNumberFormat(upvoteCount)} upvote{upvoteCount > 1 ? 's' : ''}
</span>
),
},
!showUpvoteCount &&
!!upvoteLabel && {
hasUpvoteCount &&
showUpvoteCount && {
key: 'upvotes',
node: (
<span data-testid="numUpvotes">
{largeNumberFormat(upvoteCount)} upvote{upvoteCount > 1 ? 's' : ''}
</span>
),
},
hasUpvoteCount &&
!showUpvoteCount &&
!!upvoteLabel &&
showBelowThresholdLabel && {
key: 'upvotes',
node: <span data-testid="numUpvotes">{upvoteLabel}</span>,
},
Expand Down
48 changes: 48 additions & 0 deletions packages/shared/src/components/modals/RepostListItem.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import type { Post } from '../../graphql/posts';
import { UserVote } from '../../graphql/posts';
import { RepostListItem } from './RepostListItem';

const mockUseConditionalFeature = jest.fn();
const mockUseAuthContext = jest.fn();

jest.mock('../../hooks/useConditionalFeature', () => ({
useConditionalFeature: () => mockUseConditionalFeature(),
}));

jest.mock('../../contexts/AuthContext', () => ({
useAuthContext: () => mockUseAuthContext(),
}));

describe('RepostListItem', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseAuthContext.mockReturnValue({ user: { id: 'user-id' } });
mockUseConditionalFeature.mockReturnValue({
value: {
threshold: 3,
belowThresholdLabel: 'New',
newWindowHours: 24,
},
});
});

it('does not render below-threshold label in repost modal items', () => {
const post = {
id: 'post-id',
title: '',
createdAt: new Date().toISOString(),
numUpvotes: 1,
numComments: 2,
userState: {
vote: UserVote.None,
},
} as Post;

render(<RepostListItem post={post} />);

expect(screen.queryByText('New')).not.toBeInTheDocument();
expect(screen.queryByTestId('repost-upvotes')).not.toBeInTheDocument();
});
});
30 changes: 17 additions & 13 deletions packages/shared/src/components/modals/RepostListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ export function RepostListItem({
const upvotes = post.numUpvotes ?? 0;
const comments = post.numComments ?? 0;
const userHasUpvoted = post.userState?.vote === UserVote.Up;
const { showCount: showUpvotes, belowThresholdLabel: upvoteLabel } =
getUpvoteCountDisplay(
upvotes,
upvoteThresholdConfig.threshold,
upvoteThresholdConfig.belowThresholdLabel,
userHasUpvoted,
post.createdAt,
upvoteThresholdConfig.newWindowHours,
);
const { showCount: showUpvotes } = getUpvoteCountDisplay(
upvotes,
upvoteThresholdConfig.threshold,
upvoteThresholdConfig.belowThresholdLabel,
userHasUpvoted,
post.createdAt,
upvoteThresholdConfig.newWindowHours,
);
const { author } = post;
const showSquadPreview = !isUserSource && !!source;
const isPrivateSquad = !!source && !source.public;
Expand Down Expand Up @@ -142,10 +141,15 @@ export function RepostListItem({

{/* Upvotes and comments */}
<div className="mt-3 flex items-center gap-4 text-text-quaternary typo-callout">
<span className="flex items-center gap-1.5">
<UpvoteIcon className="size-4" />
{showUpvotes ? largeNumberFormat(upvotes) : upvoteLabel}
</span>
{showUpvotes && (
<span
className="flex items-center gap-1.5"
data-testid="repost-upvotes"
>
<UpvoteIcon className="size-4" />
{largeNumberFormat(upvotes)}
</span>
)}
<span className="flex items-center gap-1.5">
<DiscussIcon className="size-4" />
{largeNumberFormat(comments)}
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/components/post/PostContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ export function PostContentRaw({
createdAt={post.createdAt}
readTime={post.readTime}
isVideoType={isVideoType}
showBelowThresholdLabel={false}
className={metadataClassName}
domain={
!isVideoType &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import type { Post } from '../../graphql/posts';
import { UserVote } from '../../graphql/posts';
import { PostUpvotesCommentsCount } from './PostUpvotesCommentsCount';

const mockUseConditionalFeature = jest.fn();
const mockUseAuthContext = jest.fn();
const mockOpenModal = jest.fn();

jest.mock('../../hooks/useConditionalFeature', () => ({
useConditionalFeature: () => mockUseConditionalFeature(),
}));

jest.mock('../../contexts/AuthContext', () => ({
useAuthContext: () => mockUseAuthContext(),
}));

jest.mock('../../hooks/useLazyModal', () => ({
useLazyModal: () => ({ openModal: mockOpenModal }),
}));

jest.mock('../../hooks/useCoresFeature', () => ({
useHasAccessToCores: () => false,
}));

jest.mock('../../lib/user', () => ({
canViewPostAnalytics: () => false,
}));

describe('PostUpvotesCommentsCount', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseAuthContext.mockReturnValue({ user: { id: 'user-id' } });
mockUseConditionalFeature.mockReturnValue({
value: {
threshold: 3,
belowThresholdLabel: 'New',
newWindowHours: 24,
},
});
});

it('does not render below-threshold label on post page', () => {
const post = {
id: 'post-id',
createdAt: new Date().toISOString(),
numUpvotes: 1,
numComments: 0,
numAwards: 0,
numReposts: 0,
userState: {
vote: UserVote.None,
},
} as Post;

render(<PostUpvotesCommentsCount post={post} />);

expect(screen.queryByText('New')).not.toBeInTheDocument();
});
});
18 changes: 8 additions & 10 deletions packages/shared/src/components/post/PostUpvotesCommentsCount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,14 @@ export function PostUpvotesCommentsCount({
const reposts = post.numReposts || 0;
const hasAccessToCores = useHasAccessToCores();
const userHasUpvoted = post.userState?.vote === UserVote.Up;
const { showCount: showUpvotes, belowThresholdLabel: upvoteLabel } =
getUpvoteCountDisplay(
upvotes,
upvoteThresholdConfig.threshold,
upvoteThresholdConfig.belowThresholdLabel,
userHasUpvoted,
post.createdAt,
upvoteThresholdConfig.newWindowHours,
);
const { showCount: showUpvotes } = getUpvoteCountDisplay(
upvotes,
upvoteThresholdConfig.threshold,
upvoteThresholdConfig.belowThresholdLabel,
userHasUpvoted,
post.createdAt,
upvoteThresholdConfig.newWindowHours,
);
const onRepostsClick = () =>
openModal({
type: LazyModal.RepostsPopup,
Expand Down Expand Up @@ -86,7 +85,6 @@ export function PostUpvotesCommentsCount({
{largeNumberFormat(upvotes)} Upvote{upvotes > 1 ? 's' : ''}
</ClickableText>
)}
{!showUpvotes && !!upvoteLabel && <span>{upvoteLabel}</span>}
{comments > 0 && (
<span>
{largeNumberFormat(comments)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ function SocialTwitterPostContentRaw({
<PostMetadata
createdAt={post.createdAt}
readTime={post.readTime}
showBelowThresholdLabel={false}
className={classNames('mt-4 !typo-callout', 'mb-4')}
>
{!!post.createdAt && <Separator className="mx-0" />}
Expand Down
Loading