diff --git a/src/components/common/TransactionHistory.tsx b/src/components/common/TransactionHistory.tsx index d8d835c..c1e8f26 100644 --- a/src/components/common/TransactionHistory.tsx +++ b/src/components/common/TransactionHistory.tsx @@ -69,18 +69,7 @@ const SAMPLE_TRANSACTIONS: Transaction[] = [ }, ]; -const formatTimestamp = (timestamp: number) => { - const now = Date.now(); - const diff = now - timestamp; - const minutes = Math.floor(diff / (1000 * 60)); - const hours = Math.floor(diff / (1000 * 60 * 60)); - const days = Math.floor(diff / (1000 * 60 * 60 * 24)); - - if (minutes < 1) return 'Just now'; - if (minutes < 60) return `${minutes}m ago`; - if (hours < 24) return `${hours}h ago`; - return `${days}d ago`; -}; +import { formatRelativeTime } from '@/utils/time.utils'; const TransactionHistory: React.FC = () => { const [isCompact, setIsCompact] = useState(() => { @@ -186,7 +175,7 @@ const TransactionHistory: React.FC = () => { {tx.price} XLM - {formatTimestamp(tx.timestamp)} + {formatRelativeTime(tx.timestamp)} )} @@ -256,7 +245,7 @@ const TransactionHistory: React.FC = () => { {tx.price} XLM - {formatTimestamp(tx.timestamp)} + {formatRelativeTime(tx.timestamp)} {tx.txHash} diff --git a/src/components/common/__tests__/TransactionHistory.test.tsx b/src/components/common/__tests__/TransactionHistory.test.tsx index cc2ceec..9eb6179 100644 --- a/src/components/common/__tests__/TransactionHistory.test.tsx +++ b/src/components/common/__tests__/TransactionHistory.test.tsx @@ -51,4 +51,19 @@ describe('TransactionHistory – activity feed sign prefix (integration)', () => expect(amountEl!.textContent).toMatch(/XLM$/); }); }); + + it('renders relative time label correctly for recent versus older events (#487)', () => { + render(); + + // SAMPLE_TRANSACTIONS has one from 30 minutes ago, one from 5 days ago (120 hours). + // We expect the first one to say "30 min ago" and the latter to say "5 days ago". + // Actually, let's just check for 'min ago' and 'days ago' + expect(screen.getAllByText(/min ago/)).not.toHaveLength(0); + expect(screen.getAllByText(/days ago/)).not.toHaveLength(0); + + // Ensure raw ISO timestamp is not shown + const isoRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/; + const rawTimestamps = screen.queryAllByText(isoRegex); + expect(rawTimestamps).toHaveLength(0); + }); }); diff --git a/src/components/home/TrendingCreatorCard.tsx b/src/components/home/TrendingCreatorCard.tsx index 9da2baf..e422fad 100644 --- a/src/components/home/TrendingCreatorCard.tsx +++ b/src/components/home/TrendingCreatorCard.tsx @@ -4,6 +4,7 @@ import { Link } from 'react-router'; import type { Course } from '@/services/course.service'; type Props = { creator: Course & { walletAddress: string } }; +import { formatHolderCount } from '@/utils/numberFormat.utils'; export default function TrendingCreatorCard({ creator }: Props) { const name = creator.title || 'Unnamed creator'; @@ -49,7 +50,7 @@ export default function TrendingCreatorCard({ creator }: Props) {
- {creator.creatorShareSupply.toLocaleString()} + {formatHolderCount(creator.creatorShareSupply)}
)} diff --git a/src/components/home/__tests__/TrendingCreatorCard.integration.test.tsx b/src/components/home/__tests__/TrendingCreatorCard.integration.test.tsx new file mode 100644 index 0000000..cc770b6 --- /dev/null +++ b/src/components/home/__tests__/TrendingCreatorCard.integration.test.tsx @@ -0,0 +1,55 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { MemoryRouter } from 'react-router'; +import TrendingCreatorCard from '../TrendingCreatorCard'; +import type { Course } from '@/services/course.service'; + +const baseCreator: Course & { walletAddress: string } = { + id: 'test-1', + title: 'Test Creator', + description: 'Test Description', + price: 10, + instructorId: 'user1', + walletAddress: '0x123', + socialHandle: 'test', + category: 'Art', + level: 'Advanced', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + isVerified: false, + status: 'active', +}; + +describe('TrendingCreatorCard integration (#484)', () => { + it('formats holder count above 1000 with a K suffix', () => { + render( + + + + ); + + expect(screen.getByText('1.5K')).toBeInTheDocument(); + expect(screen.queryByText('1,500')).not.toBeInTheDocument(); + expect(screen.queryByText('1500')).not.toBeInTheDocument(); + }); + + it('shows the raw number for counts below 1000', () => { + render( + + + + ); + + expect(screen.getByText('999')).toBeInTheDocument(); + }); +}); diff --git a/src/hooks/__tests__/useIsMobile.integration.test.tsx b/src/hooks/__tests__/useIsMobile.integration.test.tsx index a1a0ae3..6c7b631 100644 --- a/src/hooks/__tests__/useIsMobile.integration.test.tsx +++ b/src/hooks/__tests__/useIsMobile.integration.test.tsx @@ -76,4 +76,11 @@ describe('useIsMobile integration (#485)', () => { }); expect(screen.getByTestId('mobile-state')).toHaveTextContent('mobile'); }); + + it('cleans up the media query listener on unmount', () => { + const removeSpy = vi.spyOn(mql, 'removeEventListener'); + const { unmount } = render(); + unmount(); + expect(removeSpy).toHaveBeenCalledWith('change', expect.any(Function)); + }); });