11import { readFileSync } from "node:fs" ;
22import { describe , expect , it } from "vitest" ;
3- import {
4- markChatListRead ,
5- nextVisibleChat ,
6- unreadWorkCount ,
7- unreadWorkForDot ,
8- unreadWorkOnPanelClose ,
9- } from "./unread-counts" ;
3+ import { markChatListRead , nextVisibleChat , unreadWorkCount } from "./unread-counts" ;
104
115const list = ( ) => [
126 { id : "chat_a" , hasUnreadWake : true , hasUnreadWork : true } ,
@@ -59,45 +53,21 @@ describe("nextVisibleChat", () => {
5953} ) ;
6054
6155/**
62- * The chat on screen is being read, so it isn't work waiting for anyone — but only while the
63- * panel is actually open .
56+ * A turn landing in the chat on screen must not light the dot — and the chat is left out of the
57+ * count rather than subtracted off it afterwards, so a chat holding nothing is never subtracted .
6458 */
65- describe ( "unreadWorkForDot " , ( ) => {
66- it ( "subtracts the chat the panel is showing " , ( ) => {
67- expect ( unreadWorkForDot ( { reported : 3 , panelOpen : true , visibleChatId : "chat_a" } ) ) . toBe ( 2 ) ;
59+ describe ( "the chat on screen " , ( ) => {
60+ it ( "is left out, however much work lands in it " , ( ) => {
61+ expect ( unreadWorkCount ( list ( ) , "chat_a" ) ) . toBe ( 1 ) ;
6862 } ) ;
6963
70- it ( "counts every chat when the panel is closed" , ( ) => {
71- expect ( unreadWorkForDot ( { reported : 3 , panelOpen : false , visibleChatId : "chat_a" } ) ) . toBe ( 3 ) ;
72- expect ( unreadWorkForDot ( { reported : 3 , panelOpen : true , visibleChatId : null } ) ) . toBe ( 3 ) ;
64+ it ( "takes nothing off the count when it holds no unseen work" , ( ) => {
65+ expect ( unreadWorkCount ( list ( ) , "chat_c" ) ) . toBe ( 2 ) ;
7366 } ) ;
7467
75- it ( "never reports a negative count, or one the poll didn't give" , ( ) => {
76- expect ( unreadWorkForDot ( { reported : 0 , panelOpen : true , visibleChatId : "chat_a" } ) ) . toBe ( 0 ) ;
77- expect ( unreadWorkForDot ( { reported : undefined , panelOpen : false , visibleChatId : null } ) ) . toBe (
78- 0
79- ) ;
80- } ) ;
81- } ) ;
82-
83- /**
84- * Closing the panel stops the chat on screen being read, so the one the open panel took off the
85- * count comes straight back rather than a poll later.
86- */
87- describe ( "unreadWorkOnPanelClose" , ( ) => {
88- it ( "settles the dot on the panel's own count" , ( ) => {
89- const open = unreadWorkForDot ( { reported : 1 , panelOpen : true , visibleChatId : "chat_a" } ) ;
90- expect ( open ) . toBe ( 0 ) ;
91- // Work landed in another chat while chat_a was on screen: the panel's list still counts it.
92- expect ( unreadWorkOnPanelClose ( { shown : open , panelCount : 1 } ) ) . toBe ( 1 ) ;
93- } ) ;
94-
95- it ( "leaves the shown count alone when the panel closed before its list loaded" , ( ) => {
96- expect ( unreadWorkOnPanelClose ( { shown : 2 , panelCount : null } ) ) . toBe ( 2 ) ;
97- } ) ;
98-
99- it ( "takes the panel's zero too, so reading the last chat darkens the dot at once" , ( ) => {
100- expect ( unreadWorkOnPanelClose ( { shown : 1 , panelCount : 0 } ) ) . toBe ( 0 ) ;
68+ it ( "counts every chat when there is none on screen" , ( ) => {
69+ expect ( unreadWorkCount ( list ( ) , null ) ) . toBe ( 2 ) ;
70+ expect ( unreadWorkCount ( list ( ) , undefined ) ) . toBe ( 2 ) ;
10171 } ) ;
10272} ) ;
10373
@@ -106,7 +76,7 @@ describe("what the panel and the layout actually do with it", () => {
10676 const layout = readFileSync ( new URL ( "./DashboardAgent.tsx" , import . meta. url ) , "utf8" ) ;
10777
10878 it ( "reports the count from the list, and only from the list" , ( ) => {
109- expect ( panel ) . toContain ( "onUnreadWorkChange?.(unreadWorkCount(chats));" ) ;
79+ expect ( panel ) . toContain ( "onUnreadWorkChange?.(unreadWorkCount(chats, active?.chatId ));" ) ;
11080 expect ( panel ) . not . toContain ( "settled.filter((chat) => chat.hasUnreadWork).length" ) ;
11181 expect ( layout ) . not . toContain ( "setUnreadWork((count) => Math.max(0, count - 1))" ) ;
11282 } ) ;
@@ -118,22 +88,14 @@ describe("what the panel and the layout actually do with it", () => {
11888 } ) ;
11989
12090 /**
121- * The poll runs for as long as this tab is watching, so anything it reads about the panel has
122- * to come from a ref. `open` is state: the callback would keep the value it had when polling
123- * started, which is `false`, and the dot would go on counting the chat on screen .
91+ * Structural: there is no DOM here to open a panel in. The poll runs for as long as this tab
92+ * is watching, so the chat on screen has to be read from a ref at request time — `open` in the
93+ * callback's closure is whatever it was when polling started .
12494 */
125- it ( "reads the panel's state at poll time, not from the closure" , ( ) => {
126- expect ( layout ) . toContain ( "panelOpen: panelOpen.current," ) ;
127- expect ( layout ) . not . toContain ( "open && visibleChat.current" ) ;
128- } ) ;
129-
130- /** Structural: there is no DOM here to close a real panel in. */
131- it ( "settles the count on the closing edge instead of waiting for the poll" , ( ) => {
132- expect ( panel ) . toContain ( "onUnreadWorkChange?.(unreadWorkCount(chats));" ) ;
133- expect ( layout ) . toContain ( "panelWorkCount.current = count;" ) ;
134- expect ( layout ) . toContain (
135- "unreadWorkOnPanelClose({ shown, panelCount: panelWorkCount.current })"
136- ) ;
95+ it ( "names the chat on screen to the poll instead of correcting the count it gets back" , ( ) => {
96+ expect ( layout ) . toContain ( "const onScreen = visibleChat.current;" ) ;
97+ expect ( layout ) . toContain ( "setUnreadWork(Math.max(0, data.unreadWork ?? 0));" ) ;
98+ expect ( layout ) . not . toContain ( "panelOpen.current" ) ;
13799 } ) ;
138100
139101 it ( "re-seeds both counts when the environment changes under the layout" , ( ) => {
0 commit comments