test(crm): 完善高停留雷达Push通知与收件箱单测覆盖
这个提交包含在:
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
CrmRadarPushService,
|
||||
RadarPushPayload,
|
||||
isHotRadarPush,
|
||||
} from '../../../src/modules/crm/service/push';
|
||||
import { RadarTrack } from '../../../src/comm/types/omni';
|
||||
|
||||
@@ -91,6 +92,16 @@ describe('Task 35: 雷达 Push 通知内存渠道 (CrmRadarPushService)', () =>
|
||||
expect(cardB[0].visitorId).toBe('vis_b');
|
||||
});
|
||||
|
||||
it('停留满 3 分钟或浏览 3 个模块视为高意向推送', () => {
|
||||
expect(isHotRadarPush({ stayDurationMs: 179999, viewedBlocks: ['hero'] })).toBe(
|
||||
false
|
||||
);
|
||||
expect(isHotRadarPush({ stayDurationMs: 180000, viewedBlocks: [] })).toBe(true);
|
||||
expect(
|
||||
isHotRadarPush({ stayDurationMs: 1000, viewedBlocks: ['hero', 'case', 'article'] })
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('unsubscribe 后不再收到后续 publish', () => {
|
||||
const received: RadarPushPayload[] = [];
|
||||
const listener = (payload: RadarPushPayload) => {
|
||||
|
||||
@@ -262,6 +262,50 @@ describe('Task 11: 后端访客雷达模块 (modules/crm) 单元测试', () => {
|
||||
expect(result.country).toBe('Singapore');
|
||||
expect(result.city).toBe('Singapore');
|
||||
});
|
||||
|
||||
it('高停留足迹会走雷达 Push,普通足迹不推', async () => {
|
||||
const published: any[] = [];
|
||||
radarService.crmRadarPushService = {
|
||||
publish: (track: any) => {
|
||||
published.push(track);
|
||||
return track;
|
||||
},
|
||||
} as any;
|
||||
|
||||
await radarService.recordTrack({
|
||||
cardId: 101,
|
||||
stayDurationMs: 5000,
|
||||
viewedBlocks: ['hero'],
|
||||
});
|
||||
await radarService.recordTrack({
|
||||
cardId: 101,
|
||||
stayDurationMs: 180000,
|
||||
viewedBlocks: ['hero'],
|
||||
});
|
||||
|
||||
expect(published).toHaveLength(1);
|
||||
expect(published[0].stayDurationMs).toBe(180000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('P2 高意向雷达收件箱', () => {
|
||||
it('hotInbox 只统计当前用户名片的高意向足迹', async () => {
|
||||
const radarService = new CrmRadarService();
|
||||
radarService.cardInfoEntity = {
|
||||
find: jest.fn(async () => [{ id: 101, userId: 11 }]),
|
||||
} as any;
|
||||
radarService.crmTrackEntity = {
|
||||
find: jest.fn(async () => [
|
||||
{ id: 1, cardId: 101, stayDurationMs: 180000, viewedBlocks: [] },
|
||||
{ id: 2, cardId: 101, stayDurationMs: 1000, viewedBlocks: ['hero'] },
|
||||
{ id: 3, cardId: 202, stayDurationMs: 200000, viewedBlocks: [] },
|
||||
]),
|
||||
} as any;
|
||||
|
||||
const inbox = await radarService.hotInbox(11);
|
||||
expect(inbox.unread).toBe(1);
|
||||
expect(inbox.list[0].cardId).toBe(101);
|
||||
});
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
|
||||
@@ -87,7 +87,37 @@ export type LeadStatus = 'new' | 'contacted' | 'hot' | 'won' | 'archived';
|
||||
|
||||
export type LeadSource = 'nfc' | 'qr' | 'link' | 'ai_chat' | 'exchange';
|
||||
|
||||
export type CardContactSource = 'exchange' | 'collect' | 'scan';
|
||||
export type CardContactSource = 'exchange' | 'collect' | 'scan' | 'event';
|
||||
|
||||
export type CardEventStatus = 'active' | 'ended';
|
||||
|
||||
export interface CardEvent {
|
||||
id?: string | number;
|
||||
code: string;
|
||||
name: string;
|
||||
status: CardEventStatus;
|
||||
}
|
||||
|
||||
export interface CardEventMember {
|
||||
id?: string | number;
|
||||
eventId: number;
|
||||
userId: number;
|
||||
cardId: number;
|
||||
}
|
||||
|
||||
export interface CardEventJoinResult {
|
||||
event: CardEvent;
|
||||
member: CardEventMember;
|
||||
}
|
||||
|
||||
export interface TeamRankItem {
|
||||
memberId: number;
|
||||
memberName: string;
|
||||
leads: number;
|
||||
touches: number;
|
||||
score: number;
|
||||
rank: number;
|
||||
}
|
||||
|
||||
export interface CardContact {
|
||||
id?: string | number;
|
||||
@@ -147,6 +177,11 @@ export interface RadarTrack {
|
||||
createdAt?: string | Date;
|
||||
}
|
||||
|
||||
export interface RadarHotInbox {
|
||||
unread: number;
|
||||
list: RadarTrack[];
|
||||
}
|
||||
|
||||
export interface RecordRadarTrackParams {
|
||||
cardId: string | number;
|
||||
visitorId: string;
|
||||
|
||||
在新工单中引用
屏蔽一个用户