gongxiangp2/components/department-dashboard/performance-conversion.test.ts

171 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
BUSINESS_TREND_BRANDS,
addCalendarMonths,
buildIntentRateWindowSummary,
calculateIntentRate,
daysBetweenInclusive,
extractValidProductIds,
isValidDateKey,
toShanghaiDateKey,
type PerformanceConversionDailyMetric,
} from './performance-conversion'
describe('performance conversion helpers', () => {
it('keeps the requested six brands in the agreed order', () => {
expect(BUSINESS_TREND_BRANDS).toEqual(['亿家达', '卓禾', '欧意朗', '顾家', '索菲亚', '朴美'])
})
it('extracts and de-duplicates numeric product ids while rejecting placeholders', () => {
expect(extractValidProductIds(
'1077434823611 / 1077434823611',
'970584740986,新品暂无;0',
null,
)).toEqual(['1077434823611', '970584740986'])
})
it('adds six calendar months with month-end clamping', () => {
expect(addCalendarMonths('2026-03-31', 6)).toBe('2026-09-30')
expect(addCalendarMonths('2024-08-31', 6)).toBe('2025-02-28')
})
it('uses Shanghai dates and inclusive coverage days', () => {
expect(toShanghaiDateKey('2026-03-31T16:00:00.000Z')).toBe('2026-04-01')
expect(daysBetweenInclusive('2026-04-01', '2026-04-03')).toBe(3)
})
it('validates real calendar date keys', () => {
expect(isValidDateKey('2026-09-17')).toBe(true)
expect(isValidDateKey('2026-02-29')).toBe(false)
expect(isValidDateKey('2026-9-17')).toBe(false)
})
it('calculates intent rate from daily interest and conversion metrics', () => {
expect(calculateIntentRate({ favorites: 10, cartUsers: 5, visitors: 100, paymentConversionRate: 0.03 })).toBe(0.18)
expect(calculateIntentRate({ favorites: 10, cartUsers: 5, visitors: 0, paymentConversionRate: 0.03 })).toBeNull()
expect(calculateIntentRate({ favorites: 10, cartUsers: null, visitors: 100, paymentConversionRate: 0.03 })).toBeNull()
})
it('calculates complete 7-day and 15-day averages with intent-rate change', () => {
const daily = Array.from({ length: 15 }, (_, index) => ({
date: `2026-07-${String(index + 1).padStart(2, '0')}`,
impressions: 1000,
visitors: 100,
clickRate: 0.1,
favorites: index < 8 ? 10 : 20,
cartUsers: 10,
paymentConversionRate: 0.03,
paymentAmount: 100,
roi: 1,
}))
const summary = buildIntentRateWindowSummary(daily)
expect(summary.sevenDayIntentRate).toBeCloseTo(0.23, 12)
expect(summary.fifteenDayIntentRate).toBeCloseTo(0.27666666666666667, 12)
expect(summary.intentRateChange).toBeCloseTo(0.20289855072463767, 12)
})
function dailyMetrics(length: number): PerformanceConversionDailyMetric[] {
return Array.from({ length }, (_, index) => ({
date: `2026-10-${String(index + 7).padStart(2, '0')}`,
impressions: null,
visitors: 100,
clickRate: null,
favorites: 10,
cartUsers: 10,
paymentConversionRate: 0.03,
paymentAmount: null,
roi: null,
}))
}
it('counts missing dates as zero and divides by the full calendar window', () => {
const daily = dailyMetrics(15)
// The October 7 completion has gaps on October 10, 17 and 18.
for (const index of [3, 10, 11]) {
daily[index] = { ...daily[index], favorites: null, cartUsers: null, visitors: null, paymentConversionRate: null }
}
const summary = buildIntentRateWindowSummary(daily)
const sevenDayAverage = 0.23 * 6 / 7
const fifteenDayAverage = 0.23 * 12 / 15
expect(summary.sevenDayIntentRate).toBeCloseTo(sevenDayAverage, 12)
expect(summary.fifteenDayIntentRate).toBeCloseTo(fifteenDayAverage, 12)
expect(summary.intentRateChange).toBeCloseTo((fifteenDayAverage - sevenDayAverage) / sevenDayAverage, 12)
expect(daily[3].visitors).toBeNull()
expect(daily[10].favorites).toBeNull()
})
it('counts an unavailable daily rate as zero when fields are missing or visitors are zero', () => {
const daily = dailyMetrics(7)
daily[2].favorites = null
daily[4].visitors = 0
expect(buildIntentRateWindowSummary(daily)).toEqual({
sevenDayIntentRate: expect.closeTo(0.23 * 5 / 7, 12),
fifteenDayIntentRate: null,
intentRateChange: null,
})
})
it('keeps unavailable windows until 7 or 15 calendar days have elapsed', () => {
expect(buildIntentRateWindowSummary(dailyMetrics(6)).sevenDayIntentRate).toBeNull()
for (const length of [7, 14]) {
const summary = buildIntentRateWindowSummary(dailyMetrics(length))
expect(summary.sevenDayIntentRate).toBeCloseTo(0.23, 12)
expect(summary.fifteenDayIntentRate).toBeNull()
expect(summary.intentRateChange).toBeNull()
}
expect(buildIntentRateWindowSummary([])).toEqual({
sevenDayIntentRate: null,
fifteenDayIntentRate: null,
intentRateChange: null,
})
})
it('returns zero averages but no change when all elapsed dates are missing', () => {
const daily = dailyMetrics(15).map((day) => ({
...day, favorites: null, cartUsers: null, visitors: null, paymentConversionRate: null,
}))
expect(buildIntentRateWindowSummary(daily)).toEqual({
sevenDayIntentRate: 0,
fifteenDayIntentRate: 0,
intentRateChange: null,
})
})
it('returns unavailable values when a window is incomplete or its 7-day rate is zero', () => {
const fiveDays = Array.from({ length: 5 }, (_, index) => ({
date: `2026-07-${String(index + 1).padStart(2, '0')}`,
impressions: 1000,
visitors: 100,
clickRate: 0.1,
favorites: 10,
cartUsers: 10,
paymentConversionRate: 0.03,
paymentAmount: 100,
roi: 1,
}))
expect(buildIntentRateWindowSummary(fiveDays)).toEqual({
sevenDayIntentRate: null,
fifteenDayIntentRate: null,
intentRateChange: null,
})
const zeroSevenDayRate = Array.from({ length: 15 }, (_, index) => ({
date: `2026-07-${String(index + 1).padStart(2, '0')}`,
impressions: 1000,
visitors: 100,
clickRate: 0.1,
favorites: index < 7 ? 0 : 10,
cartUsers: index < 7 ? 0 : 10,
paymentConversionRate: index < 7 ? 0 : 0.03,
paymentAmount: 100,
roi: 1,
}))
expect(buildIntentRateWindowSummary(zeroSevenDayRate)).toMatchObject({
sevenDayIntentRate: 0,
intentRateChange: null,
})
})
})