list-tracking-metadata.test.ts3.0 KBView on GitHub /**
* listThreads-borne open/click tracking reaching the thread store.
*
* The "opened" badge reads `threadData[id].trackingData`, which used to be populated
* only by `mail.get` — so it rendered late, after a thread was clicked or idle-
* prefetched, instead of with the list row. `listThreads` now carries the same records
* in its preview payload; these lock in that they survive the metadata-population path,
* including the change-detection guard that short-circuits an otherwise-identical page.
*/
import { useCedarStore } from '@/modules/store';
import type { ThreadPreviewMetadata } from '@/modules/threads/threadList/store/threadSlice';
const trackingRecord = (trackingId: string, openCount: number) => ({
trackingId,
gmailMessageId: `msg-${trackingId}`,
openCount,
clickCount: 0,
firstOpenedAt: null,
lastOpenedAt: null,
firstClickedAt: null,
openTrackingEnabled: true,
linkTrackingEnabled: true,
});
const metadata = (over: Partial<ThreadPreviewMetadata> = {}): ThreadPreviewMetadata => ({
hasUnread: false,
totalReplies: 1,
labels: [{ id: 'SENT', name: 'SENT' }],
preview: { sender: { email: '<email>' }, subject: 'hi', snippet: '' },
...over,
});
const trackingFor = (threadId: string) =>
useCedarStore.getState().threadData[threadId]?.trackingData;
describe('listThreads tracking -> thread store', () => {
it('populates tracking for a thread the store has never seen', () => {
useCedarStore.getState().batchPopulateThreadMetadata({
't-new': metadata({ trackingData: [trackingRecord('trk-a', 2)] }),
});
expect(trackingFor('t-new')?.map((t) => t.openCount)).toEqual([2]);
});
it('refreshes tracking on a page whose other metadata is unchanged', () => {
// Opens move independently of labels and reply count, so the batch path's
// "nothing changed, skip this thread" guard must not swallow the new counts.
const base = metadata({ trackingData: [trackingRecord('trk-a', 1)] });
useCedarStore.getState().batchPopulateThreadMetadata({ 't-1': base });
expect(trackingFor('t-1')?.[0]?.openCount).toBe(1);
useCedarStore.getState().batchPopulateThreadMetadata({
't-1': metadata({ trackingData: [trackingRecord('trk-a', 4)] }),
});
expect(trackingFor('t-1')?.[0]?.openCount).toBe(4);
});
it('leaves existing tracking alone when a page carries none', () => {
// A page with no tracking records for the thread (e.g. the lookup degraded) must
// not wipe what mail.get already loaded.
useCedarStore.getState().batchPopulateThreadMetadata({
't-2': metadata({ trackingData: [trackingRecord('trk-a', 3)] }),
});
useCedarStore.getState().batchPopulateThreadMetadata({ 't-2': metadata() });
expect(trackingFor('t-2')?.[0]?.openCount).toBe(3);
});
it('populates tracking through the single-thread path too', () => {
useCedarStore
.getState()
.populateThreadMetadata('t-3', metadata({ trackingData: [trackingRecord('trk-b', 7)] }));
expect(trackingFor('t-3')?.map((t) => t.trackingId)).toEqual(['trk-b']);
});
});