@@ -14,18 +14,24 @@ import SdkConfig from "../../../../../src/SdkConfig";
1414import SearchWarning , { WarningKind } from "../../../../../src/components/views/elements/SearchWarning" ;
1515import EventIndexPeg from "../../../../../src/indexing/EventIndexPeg" ;
1616import { type default as EventIndex } from "../../../../../src/indexing/EventIndex" ;
17+ import { SearchScope } from "../../../../../src/Searching" ;
18+
19+ const SEARCHED_ROOM = "!searched:example.org" ;
20+ const OTHER_ROOM = "!other:example.org" ;
21+ const PARTIAL_WARNING = "Results may be incomplete because your search index is still being built." ;
1722
1823/**
1924 * A minimal fake EventIndex exposing only the surface that SearchWarning consumes:
20- * `currentRoom()` (null once the crawl is complete) and the `changedCheckpoint` emitter.
25+ * `crawlingRooms()` (the rooms with outstanding crawler checkpoints) and the `changedCheckpoint`
26+ * emitter.
2127 */
2228class FakeEventIndex {
2329 private listeners = new Map < string , Set < ( ...args : any [ ] ) => void > > ( ) ;
2430
25- public constructor ( private current : Room | null ) { }
31+ public constructor ( private crawling : string [ ] = [ ] ) { }
2632
27- public currentRoom ( ) : Room | null {
28- return this . current ;
33+ public crawlingRooms ( ) : { crawlingRooms : Set < string > ; totalRooms : Set < string > } {
34+ return { crawlingRooms : new Set ( this . crawling ) , totalRooms : new Set ( this . crawling ) } ;
2935 }
3036
3137 public on ( event : string , listener : ( ...args : any [ ] ) => void ) : void {
@@ -37,10 +43,15 @@ class FakeEventIndex {
3743 this . listeners . get ( event ) ?. delete ( listener ) ;
3844 }
3945
40- /** Test helper: simulate the crawler advancing (or finishing, when `current` is null). */
41- public emitChangedCheckpoint ( current : Room | null ) : void {
42- this . current = current ;
43- this . listeners . get ( "changedCheckpoint" ) ?. forEach ( ( listener ) => listener ( current ) ) ;
46+ /**
47+ * Test helper: simulate the crawler advancing (or finishing, when `crawling` is empty).
48+ *
49+ * The real index emits only the globally-current room, which cannot answer a per-room
50+ * question, so `currentRoom` is passed through purely to prove consumers ignore it.
51+ */
52+ public emitChangedCheckpoint ( crawling : string [ ] , currentRoom : Room | null = null ) : void {
53+ this . crawling = crawling ;
54+ this . listeners . get ( "changedCheckpoint" ) ?. forEach ( ( listener ) => listener ( currentRoom ) ) ;
4455 }
4556}
4657
@@ -86,61 +97,137 @@ describe("<SearchWarning />", () => {
8697 EventIndexPeg . index = index as unknown as EventIndex ;
8798 } ;
8899
89- it ( "renders the partial-index warning while the crawl is still in progress " , ( ) => {
90- setIndex ( new FakeEventIndex ( { name : "Encrypted room" } as Room ) ) ;
100+ it ( "warns a room-scoped search while the searched room is still being crawled " , ( ) => {
101+ setIndex ( new FakeEventIndex ( [ SEARCHED_ROOM ] ) ) ;
91102
92103 const { queryByText, queryByRole } = render (
93- < SearchWarning isRoomEncrypted = { true } kind = { WarningKind . Search } /> ,
104+ < SearchWarning
105+ isRoomEncrypted = { true }
106+ kind = { WarningKind . Search }
107+ scope = { SearchScope . Room }
108+ roomId = { SEARCHED_ROOM }
109+ /> ,
94110 ) ;
95111
96- expect (
97- queryByText ( "Results may be incomplete because your search index is still being built." ) ,
98- ) . toBeInTheDocument ( ) ;
112+ expect ( queryByText ( PARTIAL_WARNING ) ) . toBeInTheDocument ( ) ;
99113 // The notice appears dynamically while the panel is open, so it must be a live region (#32253).
100114 expect ( queryByRole ( "status" ) ) . toBeInTheDocument ( ) ;
101115 } ) ;
102116
117+ it ( "does not warn a room-scoped search when only an unrelated room is still being crawled" , ( ) => {
118+ setIndex ( new FakeEventIndex ( [ OTHER_ROOM ] ) ) ;
119+
120+ const { container } = render (
121+ < SearchWarning
122+ isRoomEncrypted = { true }
123+ kind = { WarningKind . Search }
124+ scope = { SearchScope . Room }
125+ roomId = { SEARCHED_ROOM }
126+ /> ,
127+ ) ;
128+
129+ expect ( container ) . toBeEmptyDOMElement ( ) ;
130+ } ) ;
131+
132+ it ( "warns an all-rooms search while any room is still being crawled" , ( ) => {
133+ setIndex ( new FakeEventIndex ( [ OTHER_ROOM ] ) ) ;
134+
135+ const { queryByText } = render (
136+ < SearchWarning isRoomEncrypted = { true } kind = { WarningKind . Search } scope = { SearchScope . All } /> ,
137+ ) ;
138+
139+ expect ( queryByText ( PARTIAL_WARNING ) ) . toBeInTheDocument ( ) ;
140+ } ) ;
141+
142+ it ( "falls back to the global check when the searched room is not yet known" , ( ) => {
143+ // RoomView can render before a room alias has resolved to an id.
144+ setIndex ( new FakeEventIndex ( [ OTHER_ROOM ] ) ) ;
145+
146+ const { queryByText } = render (
147+ < SearchWarning
148+ isRoomEncrypted = { true }
149+ kind = { WarningKind . Search }
150+ scope = { SearchScope . Room }
151+ roomId = { undefined }
152+ /> ,
153+ ) ;
154+
155+ expect ( queryByText ( PARTIAL_WARNING ) ) . toBeInTheDocument ( ) ;
156+ } ) ;
157+
103158 it ( "renders nothing once the index has finished crawling" , ( ) => {
104- // currentRoom() returns null when the crawl is complete (fully indexed).
105- setIndex ( new FakeEventIndex ( null ) ) ;
159+ setIndex ( new FakeEventIndex ( [ ] ) ) ;
106160
107- const { container } = render ( < SearchWarning isRoomEncrypted = { true } kind = { WarningKind . Search } /> ) ;
161+ const { container } = render (
162+ < SearchWarning isRoomEncrypted = { true } kind = { WarningKind . Search } scope = { SearchScope . All } /> ,
163+ ) ;
108164
109165 expect ( container ) . toBeEmptyDOMElement ( ) ;
110166 } ) ;
111167
112168 it ( "does not warn for the Files kind even while crawling" , ( ) => {
113- setIndex ( new FakeEventIndex ( { name : "Encrypted room" } as Room ) ) ;
169+ setIndex ( new FakeEventIndex ( [ SEARCHED_ROOM ] ) ) ;
114170
115171 const { container } = render ( < SearchWarning isRoomEncrypted = { true } kind = { WarningKind . Files } /> ) ;
116172
117173 expect ( container ) . toBeEmptyDOMElement ( ) ;
118174 } ) ;
119175
120- it ( "clears the warning when a changedCheckpoint event reports completion " , ( ) => {
121- const index = new FakeEventIndex ( { name : "Encrypted room" } as Room ) ;
176+ it ( "clears the warning when the searched room's checkpoint drains " , ( ) => {
177+ const index = new FakeEventIndex ( [ SEARCHED_ROOM ] ) ;
122178 setIndex ( index ) ;
123179
124- const { queryByText } = render ( < SearchWarning isRoomEncrypted = { true } kind = { WarningKind . Search } /> ) ;
180+ const { queryByText } = render (
181+ < SearchWarning
182+ isRoomEncrypted = { true }
183+ kind = { WarningKind . Search }
184+ scope = { SearchScope . Room }
185+ roomId = { SEARCHED_ROOM }
186+ /> ,
187+ ) ;
125188
126- expect (
127- queryByText ( "Results may be incomplete because your search index is still being built." ) ,
128- ) . toBeInTheDocument ( ) ;
189+ expect ( queryByText ( PARTIAL_WARNING ) ) . toBeInTheDocument ( ) ;
129190
130- // The crawler drains its last checkpoint: currentRoom() becomes null.
131191 act ( ( ) => {
132- index . emitChangedCheckpoint ( null ) ;
192+ index . emitChangedCheckpoint ( [ ] ) ;
133193 } ) ;
134194
135- expect (
136- queryByText ( "Results may be incomplete because your search index is still being built." ) ,
137- ) . not . toBeInTheDocument ( ) ;
195+ expect ( queryByText ( PARTIAL_WARNING ) ) . not . toBeInTheDocument ( ) ;
138196 } ) ;
139197
140- it ( "renders nothing when the room is not encrypted even while crawling" , ( ) => {
141- setIndex ( new FakeEventIndex ( { name : "Encrypted room" } as Room ) ) ;
198+ it ( "keeps warning when the crawler moves to another room but the searched room is still queued" , ( ) => {
199+ const index = new FakeEventIndex ( [ SEARCHED_ROOM , OTHER_ROOM ] ) ;
200+ setIndex ( index ) ;
201+
202+ const { queryByText } = render (
203+ < SearchWarning
204+ isRoomEncrypted = { true }
205+ kind = { WarningKind . Search }
206+ scope = { SearchScope . Room }
207+ roomId = { SEARCHED_ROOM }
208+ /> ,
209+ ) ;
142210
143- const { container } = render ( < SearchWarning isRoomEncrypted = { false } kind = { WarningKind . Search } /> ) ;
211+ // The event payload names a different room; the searched room is still outstanding, so
212+ // the warning must survive. This fails if the handler trusts the payload.
213+ act ( ( ) => {
214+ index . emitChangedCheckpoint ( [ SEARCHED_ROOM ] , { roomId : OTHER_ROOM } as Room ) ;
215+ } ) ;
216+
217+ expect ( queryByText ( PARTIAL_WARNING ) ) . toBeInTheDocument ( ) ;
218+ } ) ;
219+
220+ it ( "renders nothing when the room is not encrypted even while crawling" , ( ) => {
221+ setIndex ( new FakeEventIndex ( [ SEARCHED_ROOM ] ) ) ;
222+
223+ const { container } = render (
224+ < SearchWarning
225+ isRoomEncrypted = { false }
226+ kind = { WarningKind . Search }
227+ scope = { SearchScope . Room }
228+ roomId = { SEARCHED_ROOM }
229+ /> ,
230+ ) ;
144231
145232 expect ( container ) . toBeEmptyDOMElement ( ) ;
146233 } ) ;
0 commit comments