| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- import { TestBed } from '@angular/core/testing';
- import { UnreadService } from './unread.service';
- import { SessionService } from './session.service';
- /**
- * 未读计数单测 —— **直接守护本次开发抓出的两个 bug**
- *
- * ① 「刚连接就有 39 条未读」:初次 `/sync` 的历史回填被算成未读
- * → 用 `catchingUp` 分界覆盖
- * ② `data.liveEvent` 不能区分历史与实时(实测初次同步历史也为 `true`)
- * → 改用「初次同步完成分界 + 已读时间戳」双判据
- *
- * 覆盖 SPEC FR-04 的未读口径与 FR-14(每域唯一 owner)。
- */
- describe('UnreadService', () => {
- let svc: UnreadService;
- let session: SessionService;
- beforeEach(() => {
- localStorage.clear();
- TestBed.configureTestingModule({});
- session = TestBed.inject(SessionService);
- svc = TestBed.inject(UnreadService);
- });
- afterEach(() => localStorage.clear());
- describe('catchingUp 分界(bug①回归)', () => {
- it('初次同步回填阶段:历史消息一律不计未读', () => {
- // 模拟 5 条历史按时间升序回填 —— 全部不应计数
- const base = 1_700_000_000_000;
- for (let i = 0; i < 5; i++) {
- const counted = svc.onIncoming('agent-a', base + i * 1000, /*catchingUp*/ true);
- expect(counted).toBe(false);
- }
- expect(svc.countOf('agent-a')).toBe(0);
- });
- it('分界之后:晚于基线的消息计入', () => {
- const base = 1_700_000_000_000;
- svc.onIncoming('agent-a', base, true); // 回填阶段建立基线
- const counted = svc.onIncoming('agent-a', base + 5000, false);
- expect(counted).toBe(true);
- expect(svc.countOf('agent-a')).toBe(1);
- });
- });
- describe('时间戳口径(bug②回归)', () => {
- it('早于已读基线的消息不计(已读 / 重复投递)', () => {
- const base = 1_700_000_000_000;
- svc.onIncoming('a', base, true);
- expect(svc.onIncoming('a', base - 1000, false)).toBe(false);
- expect(svc.onIncoming('a', base, false)).toBe(false); // 等于基线也不计
- expect(svc.countOf('a')).toBe(0);
- });
- it('首次见到房间时只推进基线,不计未读', () => {
- expect(svc.onIncoming('new-agent', 1_700_000_000_000, false)).toBe(false);
- expect(svc.countOf('new-agent')).toBe(0);
- // 基线已建立,后续更新的消息才计
- expect(svc.onIncoming('new-agent', 1_700_000_001_000, false)).toBe(true);
- expect(svc.countOf('new-agent')).toBe(1);
- });
- it('离线期间积累的消息(晚于基线)全部计入', () => {
- const base = 1_700_000_000_000;
- svc.onIncoming('a', base, true);
- for (let i = 1; i <= 7; i++) svc.onIncoming('a', base + i * 1000, false);
- expect(svc.countOf('a')).toBe(7);
- });
- });
- describe('markRead', () => {
- it('清零该 agent 的未读并推进基线', () => {
- const base = 1_700_000_000_000;
- svc.onIncoming('a', base, true);
- svc.onIncoming('a', base + 1000, false);
- svc.onIncoming('a', base + 2000, false);
- expect(svc.countOf('a')).toBe(2);
- svc.markRead('a', base + 2000);
- expect(svc.countOf('a')).toBe(0);
- // 基线已推进 → 早于它的消息不再计
- expect(svc.onIncoming('a', base + 1500, false)).toBe(false);
- });
- it('只影响目标 agent,不误伤其它', () => {
- const t = 1_700_000_000_000;
- svc.onIncoming('a', t, true);
- svc.onIncoming('a', t + 1, false);
- svc.onIncoming('b', t, true);
- svc.onIncoming('b', t + 1, false);
- svc.markRead('a', t + 1);
- expect(svc.countOf('a')).toBe(0);
- expect(svc.countOf('b')).toBe(1);
- });
- });
- describe('total 与多 agent', () => {
- it('total 为各 agent 之和', () => {
- const t = 1_700_000_000_000;
- for (const id of ['a', 'b', 'c']) {
- svc.onIncoming(id, t, true);
- svc.onIncoming(id, t + 1, false);
- svc.onIncoming(id, t + 2, false);
- }
- expect(svc.countOf('a')).toBe(2);
- expect(svc.total()).toBe(6);
- });
- });
- describe('持久化(退出浏览器不丢)', () => {
- it('按用户隔离:不同用户互不串号', () => {
- // 用户1
- session.setSession('t1', { id: 'u1', name: 'A' });
- svc.reload();
- const t = 1_700_000_000_000;
- svc.onIncoming('a', t, true);
- svc.onIncoming('a', t + 1, false);
- expect(svc.countOf('a')).toBe(1);
- // 换用户2 → 重新载入应为空
- session.setSession('t2', { id: 'u2', name: 'B' });
- svc.reload();
- expect(svc.countOf('a')).toBe(0);
- // 切回用户1 → 计数恢复
- session.setSession('t1', { id: 'u1', name: 'A' });
- svc.reload();
- expect(svc.countOf('a')).toBe(1);
- });
- it('reset 清空并移除存储', () => {
- session.setSession('t1', { id: 'u1', name: 'A' });
- const t = 1_700_000_000_000;
- svc.onIncoming('a', t, true);
- svc.onIncoming('a', t + 1, false);
- svc.reset();
- expect(svc.countOf('a')).toBe(0);
- expect(svc.total()).toBe(0);
- });
- });
- describe('边界', () => {
- it('空 agentId 不计数', () => {
- expect(svc.onIncoming('', 1_700_000_000_000, false)).toBe(false);
- expect(svc.total()).toBe(0);
- });
- });
- });
|