/**
* Unit tests for utility functions
*/
import {
formatNumber,
formatDate,
calculateTotalEngagement,
getTopVideos,
calculateAverageWatchPercent,
truncateText,
validateVideoData
} from '../utils';
import type { TikTokVideoData } from '../types';
describe('formatNumber', () => {
test('should format millions', () => {
expect(formatNumber(1000000)).toBe('1.0M');
expect(formatNumber(2500000)).toBe('2.5M');
expect(formatNumber(10000000)).toBe('10.0M');
});
test('should format thousands', () => {
expect(formatNumber(1000)).toBe('1.0K');
expect(formatNumber(5500)).toBe('5.5K');
expect(formatNumber(999999)).toBe('1000.0K');
});
test('should not format small numbers', () => {
expect(formatNumber(0)).toBe('0');
expect(formatNumber(100)).toBe('100');
expect(formatNumber(999)).toBe('999');
});
});
describe('formatDate', () => {
test('should format date string correctly', () => {
const result = formatDate('2025-01-15');
expect(result).toMatch(/Jan 15/);
});
test('should handle ISO date strings', () => {
const result = formatDate('2025-01-15T00:00:00Z');
expect(result).toMatch(/Jan 15/);
});
test('should handle different months', () => {
expect(formatDate('2025-06-15')).toMatch(/Jun 15/);
expect(formatDate('2025-12-31')).toMatch(/Dec 31/);
});
});
describe('calculateTotalEngagement', () => {
const mockData: TikTokVideoData[] = [
{
videoId: '1',
title: 'Video 1',
views: 1000,
likes: 100,
comments: 10,
shares: 5,
watchPercent: 75,
datePublished: '2025-01-15',
duration: 45
},
{
videoId: '2',
title: 'Video 2',
views: 2000,
likes: 200,
comments: 20,
shares: 10,
watchPercent: 80,
datePublished: '2025-01-16',
duration: 60
}
];
test('should calculate total engagement correctly', () => {
const result = calculateTotalEngagement(mockData);
expect(result.totalLikes).toBe(300);
expect(result.totalComments).toBe(30);
expect(result.totalShares).toBe(15);
expect(result.totalViews).toBe(3000);
});
test('should handle empty array', () => {
const result = calculateTotalEngagement([]);
expect(result.totalLikes).toBe(0);
expect(result.totalComments).toBe(0);
expect(result.totalShares).toBe(0);
expect(result.totalViews).toBe(0);
});
test('should handle single video', () => {
const result = calculateTotalEngagement([mockData[0]]);
expect(result.totalLikes).toBe(100);
expect(result.totalComments).toBe(10);
expect(result.totalShares).toBe(5);
expect(result.totalViews).toBe(1000);
});
});
describe('getTopVideos', () => {
const mockData: TikTokVideoData[] = Array.from({ length: 20 }, (_, i) => ({
videoId: `${i}`,
title: `Video ${i}`,
views: (i + 1) * 1000,
likes: (i + 1) * 100,
comments: (i + 1) * 10,
shares: (i + 1) * 5,
watchPercent: 75,
datePublished: '2025-01-15',
duration: 45
}));
test('should return top 10 videos by default', () => {
const result = getTopVideos(mockData, 'views');
expect(result.length).toBe(10);
});
test('should sort by views correctly', () => {
const result = getTopVideos(mockData, 'views', 5);
expect(result[0].views).toBeLessThan(result[4].views);
});
test('should sort by likes correctly', () => {
const result = getTopVideos(mockData, 'likes', 5);
expect(result[0].likes).toBeLessThan(result[4].likes);
});
test('should respect custom limit', () => {
const result = getTopVideos(mockData, 'views', 3);
expect(result.length).toBe(3);
});
test('should handle array smaller than limit', () => {
const smallData = mockData.slice(0, 5);
const result = getTopVideos(smallData, 'views', 10);
expect(result.length).toBe(5);
});
});
describe('calculateAverageWatchPercent', () => {
test('should calculate average correctly', () => {
const data: TikTokVideoData[] = [
{ watchPercent: 75 } as TikTokVideoData,
{ watchPercent: 80 } as TikTokVideoData,
{ watchPercent: 85 } as TikTokVideoData
];
const result = calculateAverageWatchPercent(data);
expect(result).toBe(80);
});
test('should return 0 for empty array', () => {
const result = calculateAverageWatchPercent([]);
expect(result).toBe(0);
});
test('should handle single video', () => {
const data = [{ watchPercent: 75 } as TikTokVideoData];
const result = calculateAverageWatchPercent(data);
expect(result).toBe(75);
});
});
describe('truncateText', () => {
test('should truncate long text', () => {
const text = 'This is a very long text that needs to be truncated';
const result = truncateText(text, 20);
expect(result.length).toBeLessThanOrEqual(23); // 20 + '...'
expect(result).toContain('...');
});
test('should not truncate short text', () => {
const text = 'Short text';
const result = truncateText(text, 20);
expect(result).toBe(text);
expect(result).not.toContain('...');
});
test('should handle exact length', () => {
const text = 'Exact length text!!!';
const result = truncateText(text, 20);
expect(result).toBe(text);
});
test('should handle empty string', () => {
const result = truncateText('', 10);
expect(result).toBe('');
});
});
describe('validateVideoData', () => {
const validData: TikTokVideoData = {
videoId: '123',
title: 'Test Video',
views: 1000,
likes: 100,
comments: 10,
shares: 5,
watchPercent: 75,
datePublished: '2025-01-15',
duration: 45
};
test('should validate correct video data', () => {
expect(validateVideoData(validData)).toBe(true);
});
test('should reject missing videoId', () => {
const invalid = { ...validData, videoId: undefined };
expect(validateVideoData(invalid)).toBe(false);
});
test('should reject missing title', () => {
const invalid = { ...validData, title: undefined };
expect(validateVideoData(invalid)).toBe(false);
});
test('should reject non-numeric views', () => {
const invalid = { ...validData, views: '1000' };
expect(validateVideoData(invalid)).toBe(false);
});
test('should reject null data', () => {
expect(validateVideoData(null)).toBe(false);
});
test('should reject undefined data', () => {
expect(validateVideoData(undefined)).toBe(false);
});
test('should reject non-object data', () => {
expect(validateVideoData('string')).toBe(false);
expect(validateVideoData(123)).toBe(false);
expect(validateVideoData([])).toBe(false);
});
});