@@ -22,42 +22,39 @@ export class SharedDirectoryOracle {
2222 private readonly model = new Map < string , unknown > ( ) ;
2323
2424 public constructor ( private readonly sharedDir : ISharedDirectory ) {
25+ // Capture initial state BEFORE attaching event listeners
26+ // to avoid double-counting any events that might fire during initialization
27+ this . captureInitialSnapshot ( sharedDir ) ;
28+
29+ // Now attach event listeners for future changes
2530 this . sharedDir . on ( "valueChanged" , this . onValueChanged ) ;
2631 this . sharedDir . on ( "clear" , this . onClear ) ;
2732 this . sharedDir . on ( "subDirectoryCreated" , this . onSubDirCreated ) ;
2833 this . sharedDir . on ( "subDirectoryDeleted" , this . onSubDirDeleted ) ;
2934 this . sharedDir . on ( "containedValueChanged" , this . onContainedValueChanged ) ;
30-
31- this . captureInitialSnapshot ( sharedDir ) ;
3235 }
3336
3437 private captureInitialSnapshot ( dir : IDirectory ) : void {
38+ const { absolutePath } = dir ;
39+
3540 // Capture keys
3641 for ( const [ key , value ] of dir . entries ( ) ) {
37- const pathKey = dir . absolutePath === "/" ? `/${ key } ` : `${ dir . absolutePath } /${ key } ` ;
38-
42+ const pathKey = absolutePath === "/" ? `/${ key } ` : `${ absolutePath } /${ key } ` ;
3943 this . model . set ( pathKey , value ) ;
4044 }
4145
46+ // Recurse into subdirectories to capture their keys
4247 for ( const [ , subDir ] of dir . subdirectories ( ) ) {
43- // Just recurse to capture keys inside the subdir
4448 this . captureInitialSnapshot ( subDir ) ;
4549 }
4650 }
4751
4852 private readonly onValueChanged = ( change : IDirectoryValueChanged ) => {
49- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
50- const { key, previousValue } = change ;
53+ const { key } = change ;
5154 const path = change . path ?? "" ;
5255
5356 const pathKey = path === "/" ? `/${ key } ` : `${ path } /${ key } ` ;
5457
55- assert . strictEqual (
56- previousValue ,
57- this . model . get ( pathKey ) ,
58- `Mismatch on previous value for key="${ key } "` ,
59- ) ;
60-
6158 const fuzzDir = this . sharedDir . getWorkingDirectory ( path ) ;
6259 if ( ! fuzzDir ) return ;
6360
@@ -69,28 +66,36 @@ export class SharedDirectoryOracle {
6966 } ;
7067
7168 private readonly onClear = ( local : boolean ) => {
72- this . model . clear ( ) ;
69+ // Clear only root-level keys, not subdirectories or their contents
70+ for ( const key of [ ...this . model . keys ( ) ] ) {
71+ const parts = key . split ( "/" ) . filter ( ( p ) => p . length > 0 ) ;
72+ if ( parts . length === 1 ) {
73+ // Root-level key like "/key1"
74+ this . model . delete ( key ) ;
75+ }
76+ }
7377 } ;
7478
7579 private readonly onSubDirCreated = (
76- subdirName : string ,
80+ path : string ,
7781 local : boolean ,
7882 target : ISharedDirectory ,
7983 ) => {
80- const { absolutePath } = target ;
81- if ( ! this . model . has ( `${ absolutePath } ${ subdirName } ` ) ) {
82- this . model . set ( `${ absolutePath } ${ subdirName } ` , undefined ) ;
84+ // path is relative from root, e.g., "dir1" or "dir1/dir2"
85+ const subdirPath = path . startsWith ( "/" ) ? path : `/${ path } ` ;
86+ if ( ! this . model . has ( subdirPath ) ) {
87+ this . model . set ( subdirPath , undefined ) ;
8388 }
8489 } ;
8590
8691 private readonly onSubDirDeleted = ( path : string ) => {
8792 const absPath = path . startsWith ( "/" ) ? path : `/${ path } ` ;
93+ const prefix = `${ absPath } /` ;
94+
8895 for ( const key of [ ...this . model . keys ( ) ] ) {
89- if ( key . startsWith ( absPath ) ) {
90- const deleted = this . model . delete ( key ) ;
91- if ( ! deleted ) {
92- assert ( "not deleted" ) ;
93- }
96+ // Delete all keys under the deleted subdirectory
97+ if ( key . startsWith ( prefix ) ) {
98+ this . model . delete ( key ) ;
9499 }
95100 }
96101 } ;
@@ -100,18 +105,11 @@ export class SharedDirectoryOracle {
100105 local : boolean ,
101106 target : IDirectory ,
102107 ) => {
103- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
104- const { key, previousValue } = change ;
108+ const { key } = change ;
105109 const { absolutePath } = target ;
106110
107111 const pathKey = absolutePath === "/" ? `/${ key } ` : `${ absolutePath } /${ key } ` ;
108112
109- assert . strictEqual (
110- previousValue ,
111- this . model . get ( pathKey ) ,
112- `Mismatch on previous value for key="${ key } "` ,
113- ) ;
114-
115113 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
116114 const newValue = target . get ( key ) ;
117115
@@ -123,29 +121,33 @@ export class SharedDirectoryOracle {
123121 } ;
124122
125123 public validate ( ) : void {
126- for ( const [ pathKey , value ] of this . model . entries ( ) ) {
127- const parts = pathKey . split ( "/" ) . filter ( ( p ) => p . length > 0 ) ;
128- assert ( parts . length > 0 , "Invalid path, cannot extract key" ) ;
124+ this . validateDirectory ( this . sharedDir ) ;
125+ }
129126
130- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
131- const leafKey = parts . pop ( ) ! ; // The actual key
132- let dir : IDirectory | undefined = this . sharedDir ;
127+ private validateDirectory ( dir : IDirectory ) : void {
128+ const { absolutePath } = dir ;
133129
134- for ( const part of parts ) {
135- dir = dir . getSubDirectory ( part ) ;
136- if ( ! dir ) break ;
130+ // Check all keys in this directory
131+ for ( const [ key , value ] of dir . entries ( ) ) {
132+ const pathKey = absolutePath === "/" ? `/${ key } ` : `${ absolutePath } /${ key } ` ;
133+
134+ // Only validate keys that the oracle is tracking
135+ // (keys loaded from snapshots may not fire events)
136+ if ( this . model . has ( pathKey ) ) {
137+ // Verify oracle has the correct value
138+ assert . deepStrictEqual (
139+ this . model . get ( pathKey ) ,
140+ value ,
141+ `Value mismatch for key "${ pathKey } ": oracle=${ this . model . get ( pathKey ) } , actual=${ value } ` ,
142+ ) ;
137143 }
144+ }
138145
139- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
140- const actual = dir ?. get ( leafKey ) ;
141- assert . deepStrictEqual (
142- actual ,
143- value ,
144- `SharedDirectoryOracle mismatch at path="${ pathKey } " with actual value = ${ actual } and oracle value = ${ value } with model entries = ${ JSON . stringify ( this . model . entries ( ) ) } }` ,
145- ) ;
146+ // Recursively validate subdirectories
147+ for ( const [ , subdir ] of dir . subdirectories ( ) ) {
148+ this . validateDirectory ( subdir ) ;
146149 }
147150 }
148-
149151 public dispose ( ) : void {
150152 this . sharedDir . off ( "valueChanged" , this . onValueChanged ) ;
151153 this . sharedDir . off ( "clear" , this . onClear ) ;
0 commit comments