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));
+ });
});