@@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing';
22import type { DevframeRpcClient } from 'devframe/client' ;
33import { afterEach , describe , expect , it } from 'vitest' ;
44import { StoreInspector } from '../pages/store-inspector' ;
5- import type { NgrxPage , NgrxState } from '../pages/store-types' ;
5+ import type { NgrxLogEntry , NgrxPage , NgrxState } from '../pages/store-types' ;
66
77afterEach ( ( ) => {
88 TestBed . resetTestingModule ( ) ;
@@ -69,6 +69,106 @@ async function restoreNewest(result: { ok: boolean; paused?: boolean; message: s
6969 return fixture . nativeElement as HTMLElement ;
7070}
7171
72+ function eventPage ( events : NgrxLogEntry [ ] ) : NgrxPage {
73+ return {
74+ pageId : 'p1' ,
75+ url : 'http://localhost/' ,
76+ title : 'Shop' ,
77+ stores : [ ] ,
78+ classic : null ,
79+ log : events ,
80+ reportedAt : 1 ,
81+ } ;
82+ }
83+
84+ function eventEntry ( seq : number , type = 'bookingCancelled' ) : NgrxLogEntry {
85+ return {
86+ seq,
87+ source : 'event' ,
88+ storeId : 'event' ,
89+ type,
90+ eventType : type ,
91+ payload : { id : seq } ,
92+ timestamp : seq ,
93+ diff : [ ] ,
94+ restorable : false ,
95+ } ;
96+ }
97+
98+ function fakeEventClient ( events : NgrxLogEntry [ ] ) : DevframeRpcClient {
99+ const ep = eventPage ( events ) ;
100+ return {
101+ connectionMeta : { } ,
102+ scope : ( ) => ( {
103+ rpc : {
104+ call : async ( name : string ) => ( name === 'request-ngrx-action' ? { ok : true } : [ ] ) ,
105+ sharedState : async ( ) => ( {
106+ value : ( ) => ( { pages : [ ep ] } ) as NgrxState ,
107+ on : ( ) => ( ) => undefined ,
108+ } ) ,
109+ } ,
110+ } ) ,
111+ } as unknown as DevframeRpcClient ;
112+ }
113+
114+ describe ( 'StoreInspector events' , ( ) => {
115+ it ( 'shows events in a dedicated section and selects one on click' , async ( ) => {
116+ const fixture = TestBed . createComponent ( StoreInspector ) ;
117+ document . body . appendChild ( fixture . nativeElement ) ;
118+ fixture . componentRef . setInput ( 'rpc' , fakeEventClient ( [ eventEntry ( 1 ) , eventEntry ( 2 ) ] ) ) ;
119+ await fixture . whenStable ( ) ;
120+ fixture . detectChanges ( ) ;
121+
122+ const host = fixture . nativeElement as HTMLElement ;
123+ const evtButtons = host . querySelectorAll < HTMLButtonElement > ( '.event-item' ) ;
124+ expect ( evtButtons . length ) . toBe ( 2 ) ;
125+ expect ( host . querySelector ( '.event-detail-focus' ) ) . toBeNull ( ) ;
126+
127+ evtButtons [ 0 ] . click ( ) ;
128+ await fixture . whenStable ( ) ;
129+ fixture . detectChanges ( ) ;
130+ expect ( host . querySelector ( '.event-detail-focus' ) ) . not . toBeNull ( ) ;
131+ expect ( evtButtons [ 0 ] . getAttribute ( 'aria-pressed' ) ) . toBe ( 'true' ) ;
132+ expect ( evtButtons [ 1 ] . getAttribute ( 'aria-pressed' ) ) . toBe ( 'false' ) ;
133+ } ) ;
134+
135+ it ( 'moves focus to the event detail panel when an event is clicked' , async ( ) => {
136+ const fixture = TestBed . createComponent ( StoreInspector ) ;
137+ document . body . appendChild ( fixture . nativeElement ) ;
138+ fixture . componentRef . setInput ( 'rpc' , fakeEventClient ( [ eventEntry ( 1 ) ] ) ) ;
139+ await fixture . whenStable ( ) ;
140+ fixture . detectChanges ( ) ;
141+
142+ const host = fixture . nativeElement as HTMLElement ;
143+ host . querySelector < HTMLButtonElement > ( '.event-item' ) ! . click ( ) ;
144+ await fixture . whenStable ( ) ;
145+ fixture . detectChanges ( ) ;
146+ expect ( document . activeElement ) . toBe ( host . querySelector ( '.event-detail-focus' ) ) ;
147+ } ) ;
148+
149+ it ( 'does not steal focus from the filter when typing after an event is selected' , async ( ) => {
150+ const fixture = TestBed . createComponent ( StoreInspector ) ;
151+ document . body . appendChild ( fixture . nativeElement ) ;
152+ fixture . componentRef . setInput ( 'rpc' , fakeEventClient ( [ eventEntry ( 1 ) ] ) ) ;
153+ await fixture . whenStable ( ) ;
154+ fixture . detectChanges ( ) ;
155+
156+ const host = fixture . nativeElement as HTMLElement ;
157+ host . querySelector < HTMLButtonElement > ( '.event-item' ) ! . click ( ) ;
158+ await fixture . whenStable ( ) ;
159+ fixture . detectChanges ( ) ;
160+
161+ // Now type in the filter — this causes a re-render but must not steal focus
162+ const input = host . querySelector < HTMLInputElement > ( 'input[type="search"]' ) ! ;
163+ input . focus ( ) ;
164+ input . value = 'book' ;
165+ input . dispatchEvent ( new Event ( 'input' ) ) ;
166+ await fixture . whenStable ( ) ;
167+ fixture . detectChanges ( ) ;
168+ expect ( document . activeElement ) . toBe ( input ) ;
169+ } ) ;
170+ } ) ;
171+
72172describe ( 'StoreInspector restore focus' , ( ) => {
73173 it ( 'focuses the state when restoring the newest action does not pause the store' , async ( ) => {
74174 const host = await restoreNewest ( { ok : true , paused : false , message : 'Jumped.' } ) ;
0 commit comments