You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
7.8 KiB
245 lines
7.8 KiB
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import {beforeEach, describe, expect, it} from 'vitest';
|
|
import {createTestWrapper, mockData, mockServer} from '../../utils/msw-helpers';
|
|
import {renderHook, waitFor} from '@testing-library/react';
|
|
import {usePostNewsletterStats} from '@src/hooks/usePostNewsletterStats';
|
|
|
|
describe('usePostNewsletterStats', () => {
|
|
const testPostId = 'test-post-id';
|
|
|
|
beforeEach(() => {
|
|
mockServer.setup(); // Basic setup with defaults
|
|
});
|
|
|
|
it('calculates stats correctly from post email data', async () => {
|
|
const postWithEmailStats = mockData.post({
|
|
id: testPostId,
|
|
email: {
|
|
email_count: 1000,
|
|
opened_count: 300
|
|
},
|
|
count: {
|
|
clicks: 50
|
|
}
|
|
});
|
|
|
|
mockServer.setup({
|
|
posts: [postWithEmailStats]
|
|
});
|
|
|
|
const {result} = renderHook(() => usePostNewsletterStats(testPostId), {
|
|
wrapper: createTestWrapper()
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.stats).toEqual({
|
|
sent: 1000,
|
|
opened: 300,
|
|
clicked: 50,
|
|
openedRate: 0.3, // 300/1000
|
|
clickedRate: 0.05 // 50/1000
|
|
});
|
|
});
|
|
});
|
|
|
|
it('returns zero stats when post has no email data', async () => {
|
|
const postWithoutEmail = mockData.post({
|
|
id: testPostId
|
|
// No email or count data
|
|
});
|
|
|
|
mockServer.setup({
|
|
posts: [postWithoutEmail]
|
|
});
|
|
|
|
const {result} = renderHook(() => usePostNewsletterStats(testPostId), {
|
|
wrapper: createTestWrapper()
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.stats).toEqual({
|
|
sent: 0,
|
|
opened: 0,
|
|
clicked: 0,
|
|
openedRate: 0,
|
|
clickedRate: 0
|
|
});
|
|
});
|
|
});
|
|
|
|
it('calculates average newsletter performance correctly', async () => {
|
|
const newsletterStats = [
|
|
{post_id: 'post1', open_rate: 0.25, click_rate: 0.03},
|
|
{post_id: 'post2', open_rate: 0.35, click_rate: 0.07},
|
|
{post_id: 'post3', open_rate: 0.30, click_rate: 0.05}
|
|
];
|
|
|
|
mockServer.setup({
|
|
posts: [mockData.post({id: testPostId})],
|
|
newsletterBasicStats: newsletterStats,
|
|
newsletterClickStats: newsletterStats
|
|
});
|
|
|
|
const {result} = renderHook(() => usePostNewsletterStats(testPostId), {
|
|
wrapper: createTestWrapper()
|
|
});
|
|
|
|
await waitFor(() => {
|
|
// Average: (0.25 + 0.35 + 0.30) / 3 = 0.30
|
|
// Average: (0.03 + 0.07 + 0.05) / 3 = 0.05
|
|
expect(result.current.averageStats).toEqual({
|
|
openedRate: 0.30,
|
|
clickedRate: 0.05
|
|
});
|
|
});
|
|
});
|
|
|
|
it('prevents division by zero in rate calculations', async () => {
|
|
const postWithClicksButNoEmails = mockData.post({
|
|
id: testPostId,
|
|
email: {
|
|
email_count: 0,
|
|
opened_count: 5 // Impossible but testing edge case
|
|
},
|
|
count: {
|
|
clicks: 10
|
|
}
|
|
});
|
|
|
|
mockServer.setup({
|
|
posts: [postWithClicksButNoEmails]
|
|
});
|
|
|
|
const {result} = renderHook(() => usePostNewsletterStats(testPostId), {
|
|
wrapper: createTestWrapper()
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.stats.openedRate).toBe(0);
|
|
expect(result.current.stats.clickedRate).toBe(0);
|
|
expect(Number.isNaN(result.current.stats.openedRate)).toBe(false);
|
|
expect(Number.isNaN(result.current.stats.clickedRate)).toBe(false);
|
|
});
|
|
});
|
|
|
|
it('handles missing newsletter comparison data gracefully', async () => {
|
|
mockServer.setup({
|
|
posts: [mockData.post({id: testPostId})],
|
|
newsletterBasicStats: [],
|
|
newsletterClickStats: []
|
|
});
|
|
|
|
const {result} = renderHook(() => usePostNewsletterStats(testPostId), {
|
|
wrapper: createTestWrapper()
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.averageStats).toEqual({
|
|
openedRate: 0,
|
|
clickedRate: 0
|
|
});
|
|
});
|
|
});
|
|
|
|
it('provides top performing links sorted by click count', async () => {
|
|
const linksData = [
|
|
{
|
|
post_id: testPostId,
|
|
link: {link_id: 'link1', to: 'https://popular.com', from: 'post', edited: false},
|
|
count: {clicks: 25}
|
|
},
|
|
{
|
|
post_id: testPostId,
|
|
link: {link_id: 'link2', to: 'https://www.another.com', from: 'post', edited: false},
|
|
count: {clicks: 15}
|
|
}
|
|
];
|
|
|
|
mockServer.setup({
|
|
posts: [mockData.post({id: testPostId})],
|
|
links: linksData
|
|
});
|
|
|
|
const {result} = renderHook(() => usePostNewsletterStats(testPostId), {
|
|
wrapper: createTestWrapper()
|
|
});
|
|
|
|
await waitFor(() => {
|
|
// Should be sorted by click count (highest first) and URLs cleaned
|
|
expect(result.current.topLinks).toHaveLength(2);
|
|
expect(result.current.topLinks[0].count).toBe(25);
|
|
expect(result.current.topLinks[1].count).toBe(15);
|
|
|
|
// Verify URL cleaning and display formatting happens
|
|
expect(result.current.topLinks[0].link.title).toBe('popular.com');
|
|
expect(result.current.topLinks[1].link.title).toBe('another.com');
|
|
});
|
|
});
|
|
|
|
it('calculates precise rates with fractional results', async () => {
|
|
const postWithPrecisionChallenge = mockData.post({
|
|
id: testPostId,
|
|
email: {
|
|
email_count: 7,
|
|
opened_count: 2
|
|
},
|
|
count: {
|
|
clicks: 1
|
|
}
|
|
});
|
|
|
|
mockServer.setup({
|
|
posts: [postWithPrecisionChallenge]
|
|
});
|
|
|
|
const {result} = renderHook(() => usePostNewsletterStats(testPostId), {
|
|
wrapper: createTestWrapper()
|
|
});
|
|
|
|
await waitFor(() => {
|
|
// 2/7 = 0.2857142857142857... (JavaScript precision)
|
|
expect(result.current.stats.openedRate).toBeCloseTo(2 / 7, 10);
|
|
// 1/7 = 0.14285714285714285... (JavaScript precision)
|
|
expect(result.current.stats.clickedRate).toBeCloseTo(1 / 7, 10);
|
|
|
|
// Ensure calculations return valid numbers (not NaN or Infinity)
|
|
expect(Number.isFinite(result.current.stats.openedRate)).toBe(true);
|
|
expect(Number.isFinite(result.current.stats.clickedRate)).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('handles enterprise scale numbers correctly', async () => {
|
|
const enterprisePost = mockData.post({
|
|
id: testPostId,
|
|
email: {
|
|
email_count: 1000000,
|
|
opened_count: 250000
|
|
},
|
|
count: {
|
|
clicks: 12500
|
|
}
|
|
});
|
|
|
|
mockServer.setup({
|
|
posts: [enterprisePost]
|
|
});
|
|
|
|
const {result} = renderHook(() => usePostNewsletterStats(testPostId), {
|
|
wrapper: createTestWrapper()
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.stats).toEqual({
|
|
sent: 1000000,
|
|
opened: 250000,
|
|
clicked: 12500,
|
|
openedRate: 0.25, // 250000/1000000
|
|
clickedRate: 0.0125 // 12500/1000000
|
|
});
|
|
|
|
// Ensure calculations maintain precision at scale
|
|
expect(Number.isFinite(result.current.stats.openedRate)).toBe(true);
|
|
expect(Number.isFinite(result.current.stats.clickedRate)).toBe(true);
|
|
});
|
|
});
|
|
}); |