Merge pull request #8945 from aakhter/SPAP-37-unreviewed-count-signal

refactor(sync): migrate conflict-journal unreviewedCount to a Signal
This commit is contained in:
Johannes Millan 2026-07-13 10:24:23 +02:00 committed by GitHub
commit 88c62567cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 11 additions and 15 deletions

View file

@ -104,9 +104,7 @@ export class MainHeaderComponent implements OnDestroy {
// SPAP-15: persistent badge on the sync icon — count of unreviewed
// auto-resolved sync conflicts awaiting review.
readonly unreviewedConflictCount = toSignal(this._conflictJournal.unreviewedCount$, {
initialValue: 0,
});
readonly unreviewedConflictCount = this._conflictJournal.unreviewedCount;
T: typeof T = T;
isShowSimpleCounterBtnsDropdown = signal(false);

View file

@ -1,5 +1,4 @@
import { TestBed } from '@angular/core/testing';
import { firstValueFrom } from 'rxjs';
import { ConflictJournalService } from './conflict-journal.service';
import {
ConflictJournalEntry,
@ -64,14 +63,14 @@ describe('ConflictJournalService (store)', () => {
expect(unreviewed.map((e) => e.id)).toEqual(['new', 'old']);
});
it('unreviewedCount$ reflects the number of unreviewed entries', async () => {
expect(await firstValueFrom(service.unreviewedCount$)).toBe(0);
it('unreviewedCount reflects the number of unreviewed entries', async () => {
expect(service.unreviewedCount()).toBe(0);
await service.record(makeEntry({ id: 'a', status: 'unreviewed' }));
await service.record(makeEntry({ id: 'b', status: 'unreviewed' }));
await service.record(makeEntry({ id: 'c', status: 'info' }));
expect(await firstValueFrom(service.unreviewedCount$)).toBe(2);
expect(service.unreviewedCount()).toBe(2);
});
it('markKept / markFlipped update status and the unreviewed count', async () => {
@ -83,20 +82,20 @@ describe('ConflictJournalService (store)', () => {
expect((await service.getEntry('a'))?.status).toBe('kept');
expect((await service.getEntry('b'))?.status).toBe('flipped');
expect(await firstValueFrom(service.unreviewedCount$)).toBe(0);
expect(service.unreviewedCount()).toBe(0);
});
it('clearAll() removes every entry and resets the unreviewed count (profile switch)', async () => {
await service.record(makeEntry({ id: 'a', status: 'unreviewed' }));
await service.record(makeEntry({ id: 'b', status: 'kept' }));
expect(await firstValueFrom(service.unreviewedCount$)).toBe(1);
expect(service.unreviewedCount()).toBe(1);
await service.clearAll();
expect(await service.getEntry('a')).toBeUndefined();
expect(await service.getEntry('b')).toBeUndefined();
expect((await service.list('history')).length).toBe(0);
expect(await firstValueFrom(service.unreviewedCount$)).toBe(0);
expect(service.unreviewedCount()).toBe(0);
});
describe('retention (pruneOnStart)', () => {

View file

@ -19,9 +19,8 @@
* Journal entries are DEVICE-LOCAL and are NEVER uploaded to the sync server.
*/
import { Injectable } from '@angular/core';
import { Injectable, signal } from '@angular/core';
import { DBSchema, IDBPDatabase, openDB } from 'idb';
import { BehaviorSubject, Observable } from 'rxjs';
import { OpLog } from '../../core/log';
import {
CONFLICT_JOURNAL_DB_NAME,
@ -56,9 +55,9 @@ export class ConflictJournalService {
private _db?: IDBPDatabase<ConflictJournalDB>;
private _initPromise?: Promise<IDBPDatabase<ConflictJournalDB>>;
private readonly _unreviewedCount$ = new BehaviorSubject<number>(0);
private readonly _unreviewedCount = signal(0);
/** Number of entries still awaiting review (status === 'unreviewed'). */
readonly unreviewedCount$: Observable<number> = this._unreviewedCount$.asObservable();
readonly unreviewedCount = this._unreviewedCount.asReadonly();
private _openDb(): Promise<IDBPDatabase<ConflictJournalDB>> {
return openDB<ConflictJournalDB>(
@ -270,6 +269,6 @@ export class ConflictJournalService {
CONFLICT_JOURNAL_INDEX_STATUS,
IDBKeyRange.only('unreviewed'),
);
this._unreviewedCount$.next(count);
this._unreviewedCount.set(count);
}
}