diff --git a/packages/extension/src/companion/CompanionEngagements.spec.tsx b/packages/extension/src/companion/CompanionEngagements.spec.tsx deleted file mode 100644 index 7c0ab2df51..0000000000 --- a/packages/extension/src/companion/CompanionEngagements.spec.tsx +++ /dev/null @@ -1,60 +0,0 @@ -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(); - - expect(screen.queryByText('New')).not.toBeInTheDocument(); - }); -}); diff --git a/packages/extension/src/companion/CompanionEngagements.tsx b/packages/extension/src/companion/CompanionEngagements.tsx index b9b6b8a953..07a35873b3 100644 --- a/packages/extension/src/companion/CompanionEngagements.tsx +++ b/packages/extension/src/companion/CompanionEngagements.tsx @@ -1,15 +1,10 @@ import type { ReactElement } from 'react'; import React from 'react'; import type { PostBootData } from '@dailydotdev/shared/src/lib/boot'; -import { UserVote } from '@dailydotdev/shared/src/graphql/posts'; import { ClickableText } from '@dailydotdev/shared/src/components/buttons/ClickableText'; -import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext'; import { useQueryClient } from '@tanstack/react-query'; import { useRawBackgroundRequest } from '@dailydotdev/shared/src/hooks/companion'; -import { useConditionalFeature } from '@dailydotdev/shared/src/hooks/useConditionalFeature'; import { largeNumberFormat } from '@dailydotdev/shared/src/lib'; -import { featureUpvoteCountThreshold } from '@dailydotdev/shared/src/lib/featureManagement'; -import { getUpvoteCountDisplay } from '@dailydotdev/shared/src/lib/post'; interface CompanionEngagementsProps { post: PostBootData; @@ -20,8 +15,6 @@ export function CompanionEngagements({ post, onUpvotesClick, }: CompanionEngagementsProps): ReactElement | null { - const { user } = useAuthContext(); - const isLoggedIn = !!user; const client = useQueryClient(); useRawBackgroundRequest(({ res, key }) => { if (!Array.isArray(key)) { @@ -35,26 +28,12 @@ export function CompanionEngagements({ client.setQueryData(key, res); }); - const { value: upvoteThresholdConfig } = useConditionalFeature({ - feature: featureUpvoteCountThreshold, - shouldEvaluate: isLoggedIn, - }); - if (!post) { return null; } const upvotes = post.numUpvotes ?? 0; const comments = post.numComments ?? 0; - const userHasUpvoted = post.userState?.vote === UserVote.Up; - const { showCount } = getUpvoteCountDisplay( - upvotes, - upvoteThresholdConfig.threshold, - upvoteThresholdConfig.belowThresholdLabel, - userHasUpvoted, - post.createdAt, - upvoteThresholdConfig.newWindowHours, - ); return (
{upvotes <= 0 && Be the first to upvote} - {showCount && ( + {upvotes > 0 && ( {largeNumberFormat(upvotes)} Upvote {upvotes > 1 ? 's' : ''} diff --git a/packages/shared/src/components/cards/SimilarPosts/PostEngagementCounts.tsx b/packages/shared/src/components/cards/SimilarPosts/PostEngagementCounts.tsx index 2cc6e57bb5..15804df459 100644 --- a/packages/shared/src/components/cards/SimilarPosts/PostEngagementCounts.tsx +++ b/packages/shared/src/components/cards/SimilarPosts/PostEngagementCounts.tsx @@ -3,43 +3,19 @@ import React from 'react'; import classNames from 'classnames'; import { separatorCharacter } from '../common/common'; import { largeNumberFormat } from '../../../lib'; -import { useConditionalFeature } from '../../../hooks/useConditionalFeature'; -import { featureUpvoteCountThreshold } from '../../../lib/featureManagement'; -import { getUpvoteCountDisplay } from '../../../lib/post'; interface PostEngagementCountsProps { upvotes: number; comments: number; - createdAt?: string; className?: string; - userHasUpvoted?: boolean; - shouldEvaluateFeature?: boolean; } export function PostEngagementCounts({ upvotes, comments, - createdAt, className, - userHasUpvoted = false, - shouldEvaluateFeature = false, }: PostEngagementCountsProps): ReactElement { - const { value: upvoteThresholdConfig } = useConditionalFeature({ - feature: featureUpvoteCountThreshold, - shouldEvaluate: shouldEvaluateFeature, - }); - const { showCount, belowThresholdLabel } = getUpvoteCountDisplay( - upvotes, - upvoteThresholdConfig.threshold, - upvoteThresholdConfig.belowThresholdLabel, - userHasUpvoted, - createdAt, - upvoteThresholdConfig.newWindowHours, - ); - - const upvoteText = showCount - ? `${largeNumberFormat(upvotes)} Upvotes` - : belowThresholdLabel || ''; + const upvoteText = upvotes > 0 ? `${largeNumberFormat(upvotes)} Upvotes` : ''; const hasUpvoteText = !!upvoteText; return ( diff --git a/packages/shared/src/components/cards/common/ActionButtons.tsx b/packages/shared/src/components/cards/common/ActionButtons.tsx index b0c3aa1e12..f4b515461f 100644 --- a/packages/shared/src/components/cards/common/ActionButtons.tsx +++ b/packages/shared/src/components/cards/common/ActionButtons.tsx @@ -11,9 +11,6 @@ import { } from '../../icons'; import { ButtonColor, ButtonSize, ButtonVariant } from '../../buttons/Button'; import { useFeedPreviewMode } from '../../../hooks'; -import { useConditionalFeature } from '../../../hooks/useConditionalFeature'; -import { featureUpvoteCountThreshold } from '../../../lib/featureManagement'; -import { getUpvoteCountDisplay } from '../../../lib/post'; import { UpvoteButtonIcon } from './UpvoteButtonIcon'; import { BookmarkButton } from '../../buttons'; import { IconSize } from '../../Icon'; @@ -22,7 +19,6 @@ import PostAwardAction from '../../post/PostAwardAction'; import ConditionalWrapper from '../../ConditionalWrapper'; import { PostTagsPanel } from '../../post/block/PostTagsPanel'; import { LinkWithTooltip } from '../../tooltips/LinkWithTooltip'; -import { useAuthContext } from '../../../contexts/AuthContext'; import { useCardActions } from '../../../hooks/cards/useCardActions'; export type ActionButtonsVariant = 'grid' | 'list' | 'signal'; @@ -79,8 +75,6 @@ const ActionButtons = ({ }: ActionButtonsProps): ReactElement | null => { const config = variantConfig[variant]; const isFeedPreview = useFeedPreviewMode(); - const { user } = useAuthContext(); - const isLoggedIn = !!user; const { isUpvoteActive, @@ -99,11 +93,6 @@ const ActionButtons = ({ closeTagsPanelOnUpvote: variant === 'list', }); - const { value: upvoteThresholdConfig } = useConditionalFeature({ - feature: featureUpvoteCountThreshold, - shouldEvaluate: isLoggedIn, - }); - if (isFeedPreview) { return null; } @@ -111,16 +100,6 @@ const ActionButtons = ({ const commentCount = post.numComments ?? 0; const upvoteCount = post.numUpvotes ?? 0; - const { showCount: showUpvoteCount, belowThresholdLabel: upvoteLabel } = - getUpvoteCountDisplay( - upvoteCount, - upvoteThresholdConfig.threshold, - upvoteThresholdConfig.belowThresholdLabel, - isUpvoteActive, - post.createdAt, - upvoteThresholdConfig.newWindowHours, - ); - const commentButton = config.useCommentLink ? ( } > - {showUpvoteCount ? ( + {upvoteCount > 0 && ( - ) : ( - !!upvoteLabel && ( - - {upvoteLabel} - - ) )} diff --git a/packages/shared/src/components/cards/common/PostMetadata.spec.tsx b/packages/shared/src/components/cards/common/PostMetadata.spec.tsx deleted file mode 100644 index 781a8fa4e2..0000000000 --- a/packages/shared/src/components/cards/common/PostMetadata.spec.tsx +++ /dev/null @@ -1,59 +0,0 @@ -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( - , - ); - - expect(screen.getByText('New')).toBeInTheDocument(); - }); - - it('does not render below-threshold label when disabled for non-card surfaces', () => { - render( - , - ); - - expect(screen.queryByText('New')).not.toBeInTheDocument(); - }); - - it('does not render below-threshold label when upvote count is not provided', () => { - render(); - - expect(screen.queryByText('New')).not.toBeInTheDocument(); - }); -}); diff --git a/packages/shared/src/components/cards/common/PostMetadata.tsx b/packages/shared/src/components/cards/common/PostMetadata.tsx index 5713783315..137e5e314b 100644 --- a/packages/shared/src/components/cards/common/PostMetadata.tsx +++ b/packages/shared/src/components/cards/common/PostMetadata.tsx @@ -6,10 +6,6 @@ import { Separator } from './common'; import type { Post } from '../../../graphql/posts'; import { formatReadTime, DateFormat } from '../../utilities'; import { largeNumberFormat } from '../../../lib'; -import { useAuthContext } from '../../../contexts/AuthContext'; -import { useConditionalFeature } from '../../../hooks/useConditionalFeature'; -import { featureUpvoteCountThreshold } from '../../../lib/featureManagement'; -import { getUpvoteCountDisplay } from '../../../lib/post'; import { useFeedCardContext } from '../../../features/posts/FeedCardContext'; import { Tooltip } from '../../tooltip/Tooltip'; import type { PollMetadataProps } from './PollMetadata'; @@ -24,8 +20,6 @@ interface PostMetadataProps isVideoType?: boolean; domain?: ReactNode; pollMetadata?: PollMetadataProps; - userHasUpvoted?: boolean; - showBelowThresholdLabel?: boolean; } export default function PostMetadata({ @@ -38,8 +32,6 @@ export default function PostMetadata({ isVideoType, domain, pollMetadata, - userHasUpvoted = false, - showBelowThresholdLabel = true, }: PostMetadataProps): ReactElement { const hasUpvoteCount = typeof numUpvotes === 'number'; const upvoteCount = numUpvotes ?? 0; @@ -47,22 +39,6 @@ export default function PostMetadata({ const timeActionContent = isVideoType ? 'watch' : 'read'; const showReadTime = isVideoType ? Number.isInteger(readTime) : !!readTime; const { boostedBy } = useFeedCardContext(); - const { user } = useAuthContext(); - const isLoggedIn = !!user; - - const { value: upvoteThresholdConfig } = useConditionalFeature({ - feature: featureUpvoteCountThreshold, - shouldEvaluate: isLoggedIn, - }); - const { showCount: showUpvoteCount, belowThresholdLabel: upvoteLabel } = - getUpvoteCountDisplay( - upvoteCount, - upvoteThresholdConfig.threshold, - upvoteThresholdConfig.belowThresholdLabel, - userHasUpvoted, - createdAt, - upvoteThresholdConfig.newWindowHours, - ); const promotedText = useScrambler('Promoted'); const promotedByTooltip = useScrambler( @@ -98,7 +74,7 @@ export default function PostMetadata({ }, !!showReadTime && domain && { key: 'domain', node: domain }, hasUpvoteCount && - showUpvoteCount && { + upvoteCount > 0 && { key: 'upvotes', node: ( @@ -106,13 +82,6 @@ export default function PostMetadata({ ), }, - hasUpvoteCount && - !showUpvoteCount && - !!upvoteLabel && - showBelowThresholdLabel && { - key: 'upvotes', - node: {upvoteLabel}, - }, ].filter(Boolean) as { key: string; node: ReactNode }[]; return ( diff --git a/packages/shared/src/components/modals/RepostListItem.spec.tsx b/packages/shared/src/components/modals/RepostListItem.spec.tsx deleted file mode 100644 index 4ff7b41dda..0000000000 --- a/packages/shared/src/components/modals/RepostListItem.spec.tsx +++ /dev/null @@ -1,48 +0,0 @@ -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(); - - expect(screen.queryByText('New')).not.toBeInTheDocument(); - expect(screen.queryByTestId('repost-upvotes')).not.toBeInTheDocument(); - }); -}); diff --git a/packages/shared/src/components/modals/RepostListItem.tsx b/packages/shared/src/components/modals/RepostListItem.tsx index 76bbc6f4df..6c0d027b2e 100644 --- a/packages/shared/src/components/modals/RepostListItem.tsx +++ b/packages/shared/src/components/modals/RepostListItem.tsx @@ -3,15 +3,10 @@ import React from 'react'; import { ProfileImageSize } from '../ProfilePicture'; import { SourceAvatar } from '../profile/source/SourceAvatar'; import Link from '../utilities/Link'; -import { UserVote } from '../../graphql/posts'; import type { Post } from '../../graphql/posts'; import { isSourceUserSource } from '../../graphql/sources'; import { DiscussIcon, LockIcon, UpvoteIcon } from '../icons'; import { largeNumberFormat } from '../../lib/numberFormat'; -import { useAuthContext } from '../../contexts/AuthContext'; -import { useConditionalFeature } from '../../hooks/useConditionalFeature'; -import { featureUpvoteCountThreshold } from '../../lib/featureManagement'; -import { getUpvoteCountDisplay } from '../../lib/post'; import { UserShortInfo } from '../profile/UserShortInfo'; import { TimeFormatType, formatDate } from '../../lib/dateFormat'; @@ -26,25 +21,10 @@ export function RepostListItem({ scrollingContainer, appendTooltipTo, }: RepostListItemProps): ReactElement { - const { user } = useAuthContext(); - const isLoggedIn = !!user; - const { value: upvoteThresholdConfig } = useConditionalFeature({ - feature: featureUpvoteCountThreshold, - shouldEvaluate: isLoggedIn, - }); const { source } = post; const isUserSource = source ? isSourceUserSource(source) : false; const upvotes = post.numUpvotes ?? 0; const comments = post.numComments ?? 0; - const userHasUpvoted = post.userState?.vote === UserVote.Up; - 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; @@ -141,7 +121,7 @@ export function RepostListItem({ {/* Upvotes and comments */}
- {showUpvotes && ( + {upvotes > 0 && (
diff --git a/packages/shared/src/components/post/PostUpvotesCommentsCount.spec.tsx b/packages/shared/src/components/post/PostUpvotesCommentsCount.spec.tsx deleted file mode 100644 index e26177ac5c..0000000000 --- a/packages/shared/src/components/post/PostUpvotesCommentsCount.spec.tsx +++ /dev/null @@ -1,61 +0,0 @@ -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(); - - expect(screen.queryByText('New')).not.toBeInTheDocument(); - }); -}); diff --git a/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx b/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx index 0a0be12118..8e5599d401 100644 --- a/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx +++ b/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx @@ -1,15 +1,8 @@ import type { ReactElement } from 'react'; import React from 'react'; -import { - POST_REPOSTS_BY_ID_QUERY, - UserVote, - type Post, -} from '../../graphql/posts'; +import { POST_REPOSTS_BY_ID_QUERY, type Post } from '../../graphql/posts'; import { ClickableText } from '../buttons/ClickableText'; import { largeNumberFormat } from '../../lib'; -import { useConditionalFeature } from '../../hooks/useConditionalFeature'; -import { featureUpvoteCountThreshold } from '../../lib/featureManagement'; -import { getUpvoteCountDisplay } from '../../lib/post'; import { Image } from '../image/Image'; import { useLazyModal } from '../../hooks/useLazyModal'; import { LazyModal } from '../modals/common/types'; @@ -34,25 +27,11 @@ export function PostUpvotesCommentsCount({ }: PostUpvotesCommentsCountProps): ReactElement { const { openModal } = useLazyModal(); const { user } = useAuthContext(); - const isLoggedIn = !!user; - const { value: upvoteThresholdConfig } = useConditionalFeature({ - feature: featureUpvoteCountThreshold, - shouldEvaluate: isLoggedIn, - }); const upvotes = post.numUpvotes || 0; const comments = post.numComments || 0; const awards = post.numAwards || 0; const reposts = post.numReposts || 0; const hasAccessToCores = useHasAccessToCores(); - const userHasUpvoted = post.userState?.vote === UserVote.Up; - const { showCount: showUpvotes } = getUpvoteCountDisplay( - upvotes, - upvoteThresholdConfig.threshold, - upvoteThresholdConfig.belowThresholdLabel, - userHasUpvoted, - post.createdAt, - upvoteThresholdConfig.newWindowHours, - ); const onRepostsClick = () => openModal({ type: LazyModal.RepostsPopup, @@ -80,7 +59,7 @@ export function PostUpvotesCommentsCount({ {post.analytics.impressions > 1 ? 's' : ''} )} - {showUpvotes && ( + {upvotes > 0 && ( onUpvotesClick?.(upvotes)}> {largeNumberFormat(upvotes)} Upvote{upvotes > 1 ? 's' : ''} diff --git a/packages/shared/src/components/post/SocialTwitterPostContent.tsx b/packages/shared/src/components/post/SocialTwitterPostContent.tsx index 64bb8c7b7a..6d6f8a9191 100644 --- a/packages/shared/src/components/post/SocialTwitterPostContent.tsx +++ b/packages/shared/src/components/post/SocialTwitterPostContent.tsx @@ -163,7 +163,6 @@ function SocialTwitterPostContentRaw({ {!!post.createdAt && } diff --git a/packages/shared/src/components/widgets/SimilarPosts.tsx b/packages/shared/src/components/widgets/SimilarPosts.tsx index 96099ddc93..126ed33388 100644 --- a/packages/shared/src/components/widgets/SimilarPosts.tsx +++ b/packages/shared/src/components/widgets/SimilarPosts.tsx @@ -3,7 +3,6 @@ import React, { useContext } from 'react'; import classNames from 'classnames'; import Link from '../utilities/Link'; import { ArrowIcon } from '../icons'; -import { UserVote } from '../../graphql/posts'; import type { Post } from '../../graphql/posts'; import styles from '../cards/common/Card.module.css'; import { LazyImage } from '../LazyImage'; @@ -82,9 +81,6 @@ const DefaultListItem = ({ post, onLinkClick }: PostProps): ReactElement => ( )} diff --git a/packages/shared/src/lib/featureManagement.ts b/packages/shared/src/lib/featureManagement.ts index db3a5cf33c..acc5a652c4 100644 --- a/packages/shared/src/lib/featureManagement.ts +++ b/packages/shared/src/lib/featureManagement.ts @@ -151,13 +151,3 @@ export const sharedPostPreviewFeature = new Feature( 'shared_post_preview', false, ); - -export const featureUpvoteCountThreshold = new Feature<{ - threshold: number; - belowThresholdLabel: string; - newWindowHours: number; -}>('upvote_count_threshold', { - threshold: 0, - belowThresholdLabel: '', - newWindowHours: 24, -}); diff --git a/packages/shared/src/lib/post.spec.ts b/packages/shared/src/lib/post.spec.ts deleted file mode 100644 index 49eacdfede..0000000000 --- a/packages/shared/src/lib/post.spec.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { getUpvoteCountDisplay } from './post'; - -describe('getUpvoteCountDisplay', () => { - const now = new Date('2026-03-30T12:00:00.000Z'); - - beforeEach(() => { - jest.spyOn(Date, 'now').mockReturnValue(now.getTime()); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('shows label for recent posts below threshold with zero upvotes', () => { - const result = getUpvoteCountDisplay( - 0, - 3, - 'New', - false, - '2026-03-30T11:00:00.000Z', - 24, - ); - - expect(result).toEqual({ showCount: false, belowThresholdLabel: 'New' }); - }); - - it('shows label for recent posts below threshold with positive upvotes', () => { - const result = getUpvoteCountDisplay( - 2, - 3, - 'New', - false, - '2026-03-30T11:00:00.000Z', - 24, - ); - - expect(result).toEqual({ showCount: false, belowThresholdLabel: 'New' }); - }); - - it('supports empty configured label', () => { - const result = getUpvoteCountDisplay( - 2, - 3, - '', - false, - '2026-03-30T11:00:00.000Z', - 24, - ); - - expect(result).toEqual({ showCount: false, belowThresholdLabel: '' }); - }); - - it('hides label for old posts below threshold', () => { - const result = getUpvoteCountDisplay( - 2, - 3, - 'New', - false, - '2026-03-28T11:00:00.000Z', - 24, - ); - - expect(result).toEqual({ showCount: false, belowThresholdLabel: '' }); - }); - - it('hides label for yesterday posts even within 24-hour window', () => { - const result = getUpvoteCountDisplay( - 1, - 3, - 'New', - false, - '2026-03-29T23:30:00.000Z', - 24, - ); - - expect(result).toEqual({ showCount: false, belowThresholdLabel: '' }); - }); - - it('hides label when createdAt is undefined', () => { - const result = getUpvoteCountDisplay(1, 3, 'New', false, undefined, 24); - - expect(result).toEqual({ showCount: false, belowThresholdLabel: '' }); - }); - - it('shows numeric count at or above threshold', () => { - const result = getUpvoteCountDisplay( - 3, - 3, - 'New', - false, - '2026-03-30T11:00:00.000Z', - 24, - ); - - expect(result).toEqual({ showCount: true, belowThresholdLabel: '' }); - }); - - it('shows numeric count for old posts at threshold', () => { - const result = getUpvoteCountDisplay( - 3, - 3, - 'New', - false, - '2026-03-28T11:00:00.000Z', - 24, - ); - - expect(result).toEqual({ showCount: true, belowThresholdLabel: '' }); - }); - - it('shows numeric count when user already upvoted', () => { - const result = getUpvoteCountDisplay( - 0, - 3, - 'New', - true, - '2026-03-30T11:00:00.000Z', - 24, - ); - - expect(result).toEqual({ showCount: true, belowThresholdLabel: '' }); - }); - - it('keeps control behavior when threshold is disabled', () => { - const result = getUpvoteCountDisplay( - 1, - 0, - 'New', - false, - '2026-03-30T11:00:00.000Z', - 24, - ); - - expect(result).toEqual({ showCount: true, belowThresholdLabel: '' }); - }); - - it('hides count for zero upvotes when threshold is disabled', () => { - const result = getUpvoteCountDisplay( - 0, - 0, - 'New', - false, - '2026-03-30T11:00:00.000Z', - 24, - ); - - expect(result).toEqual({ showCount: false, belowThresholdLabel: '' }); - }); -}); diff --git a/packages/shared/src/lib/post.ts b/packages/shared/src/lib/post.ts deleted file mode 100644 index 789f2a3025..0000000000 --- a/packages/shared/src/lib/post.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { isSameDay } from 'date-fns'; - -export interface UpvoteCountDisplay { - showCount: boolean; - belowThresholdLabel: string; -} - -function isTodayPost( - createdAt: string | Date | undefined, - newWindowHours: number, -): boolean { - // Missing/invalid publish dates are treated as non-recent to avoid - // showing a misleading below-threshold freshness label. - if (!createdAt) { - return false; - } - - const createdAtMs = new Date(createdAt).getTime(); - if (Number.isNaN(createdAtMs)) { - return false; - } - - const nowMs = Date.now(); - const createdAtDate = new Date(createdAtMs); - const nowDate = new Date(nowMs); - if (!isSameDay(createdAtDate, nowDate)) { - return false; - } - - const windowMs = Math.max(0, newWindowHours) * 60 * 60 * 1000; - // Inclusive boundary is intentional: content exactly at the window limit - // is still considered within the "Today" window. - return nowMs - createdAtMs <= windowMs; -} - -export function getUpvoteCountDisplay( - numUpvotes: number, - threshold: number, - belowThresholdLabel: string, - userHasUpvoted: boolean, - createdAt?: string | Date, - newWindowHours = 24, -): UpvoteCountDisplay { - if (userHasUpvoted) { - return { showCount: true, belowThresholdLabel: '' }; - } - - if (threshold <= 0) { - if (numUpvotes <= 0) { - return { showCount: false, belowThresholdLabel: '' }; - } - - return { showCount: true, belowThresholdLabel: '' }; - } - - if (numUpvotes >= threshold) { - return { showCount: true, belowThresholdLabel: '' }; - } - - if (!isTodayPost(createdAt, newWindowHours)) { - return { showCount: false, belowThresholdLabel: '' }; - } - - return { showCount: false, belowThresholdLabel }; -}