@@ -11,6 +11,22 @@ import { IDeepnoteNotebookManager } from '../types';
1111import { DeepnoteFileChangeWatcher } from './deepnoteFileChangeWatcher' ;
1212import { SnapshotService } from './snapshots/snapshotService' ;
1313
14+ const validProject = {
15+ version : '1.0.0' ,
16+ metadata : { createdAt : '2025-01-01T00:00:00Z' } ,
17+ project : {
18+ id : 'project-1' ,
19+ name : 'Test Project' ,
20+ notebooks : [
21+ {
22+ id : 'notebook-1' ,
23+ name : 'Notebook 1' ,
24+ blocks : [ { id : 'block-1' , type : 'code' , sortingKey : 'a0' , blockGroup : '1' , content : 'print("hello")' } ]
25+ }
26+ ]
27+ }
28+ } as DeepnoteProject ;
29+
1430const waitForTimeoutMs = 5000 ;
1531const waitForIntervalMs = 50 ;
1632const debounceWaitMs = 800 ;
@@ -37,6 +53,7 @@ async function waitFor(
3753suite ( 'DeepnoteFileChangeWatcher' , ( ) => {
3854 let watcher : DeepnoteFileChangeWatcher ;
3955 let mockDisposables : IDisposableRegistry ;
56+ let mockedNotebookManager : IDeepnoteNotebookManager ;
4057 let mockNotebookManager : IDeepnoteNotebookManager ;
4158 let onDidChangeFile : EventEmitter < Uri > ;
4259 let onDidCreateFile : EventEmitter < Uri > ;
@@ -51,7 +68,11 @@ suite('DeepnoteFileChangeWatcher', () => {
5168 saveCount = 0 ;
5269
5370 mockDisposables = [ ] ;
54- mockNotebookManager = instance ( mock < IDeepnoteNotebookManager > ( ) ) ;
71+
72+ mockedNotebookManager = mock < IDeepnoteNotebookManager > ( ) ;
73+ when ( mockedNotebookManager . getOriginalProject ( anything ( ) ) ) . thenReturn ( validProject ) ;
74+ when ( mockedNotebookManager . getTheSelectedNotebookForAProject ( anything ( ) ) ) . thenReturn ( 'notebook-1' ) ;
75+ mockNotebookManager = instance ( mockedNotebookManager ) ;
5576
5677 // Set up FileSystemWatcher mock
5778 onDidChangeFile = new EventEmitter < Uri > ( ) ;
@@ -126,6 +147,7 @@ suite('DeepnoteFileChangeWatcher', () => {
126147 readFileCalls ++ ;
127148 return Promise . resolve ( new TextEncoder ( ) . encode ( yamlContent ) ) ;
128149 } ) ;
150+ when ( mockFs . writeFile ( anything ( ) , anything ( ) ) ) . thenReturn ( Promise . resolve ( ) ) ;
129151 when ( mockedVSCodeNamespaces . workspace . fs ) . thenReturn ( instance ( mockFs ) ) ;
130152
131153 return mockFs ;
@@ -325,8 +347,9 @@ project:
325347 onDidChangeFile . fire ( uri ) ;
326348 await waitFor ( ( ) => saveCount >= 1 ) ;
327349
328- // The save from the first reload set a self-write marker.
329- // Simulate the auto-save fs event being consumed (as it would in real VS Code).
350+ // The first reload sets 2 self-write markers (writeFile + save).
351+ // Consume them both with simulated fs events.
352+ onDidChangeFile . fire ( uri ) ;
330353 onDidChangeFile . fire ( uri ) ;
331354
332355 // Second real external change: use different YAML content
@@ -1053,6 +1076,42 @@ project:
10531076 } ) ;
10541077
10551078 test ( 'should not apply updates when cells have no block IDs and no fallback' , async ( ) => {
1079+ const noFallbackDisposables : IDisposableRegistry = [ ] ;
1080+ const noFallbackOnDidChange = new EventEmitter < Uri > ( ) ;
1081+ const noFallbackOnDidCreate = new EventEmitter < Uri > ( ) ;
1082+ const fsWatcherNf = mock < FileSystemWatcher > ( ) ;
1083+ when ( fsWatcherNf . onDidChange ) . thenReturn ( noFallbackOnDidChange . event ) ;
1084+ when ( fsWatcherNf . onDidCreate ) . thenReturn ( noFallbackOnDidCreate . event ) ;
1085+ when ( fsWatcherNf . dispose ( ) ) . thenReturn ( ) ;
1086+ when ( mockedVSCodeNamespaces . workspace . createFileSystemWatcher ( anything ( ) ) ) . thenReturn (
1087+ instance ( fsWatcherNf )
1088+ ) ;
1089+
1090+ let nfApplyEditCount = 0 ;
1091+ when ( mockedVSCodeNamespaces . workspace . applyEdit ( anything ( ) ) ) . thenCall ( ( ) => {
1092+ nfApplyEditCount ++ ;
1093+ return Promise . resolve ( true ) ;
1094+ } ) ;
1095+
1096+ let nfReadSnapshotCount = 0 ;
1097+ const nfSnapshotService = mock < SnapshotService > ( ) ;
1098+ when ( nfSnapshotService . isSnapshotsEnabled ( ) ) . thenReturn ( true ) ;
1099+ when ( nfSnapshotService . readSnapshot ( anything ( ) ) ) . thenCall ( ( ) => {
1100+ nfReadSnapshotCount ++ ;
1101+ return Promise . resolve ( snapshotOutputs ) ;
1102+ } ) ;
1103+ when ( nfSnapshotService . onFileWritten ( anything ( ) ) ) . thenReturn ( { dispose : ( ) => { } } as Disposable ) ;
1104+
1105+ const nfManager = mock < IDeepnoteNotebookManager > ( ) ;
1106+ when ( nfManager . getOriginalProject ( anything ( ) ) ) . thenReturn ( undefined ) ;
1107+
1108+ const nfWatcher = new DeepnoteFileChangeWatcher (
1109+ noFallbackDisposables ,
1110+ instance ( nfManager ) ,
1111+ instance ( nfSnapshotService )
1112+ ) ;
1113+ nfWatcher . activate ( ) ;
1114+
10561115 const snapshotUri = Uri . file ( '/workspace/snapshots/my-project_project-1_latest.snapshot.deepnote' ) ;
10571116 const notebook = createMockNotebook ( {
10581117 uri : Uri . file ( '/workspace/test.deepnote' ) ,
@@ -1068,16 +1127,22 @@ project:
10681127
10691128 when ( mockedVSCodeNamespaces . workspace . notebookDocuments ) . thenReturn ( [ notebook ] ) ;
10701129
1071- snapshotOnDidChange . fire ( snapshotUri ) ;
1130+ noFallbackOnDidChange . fire ( snapshotUri ) ;
10721131
1073- await waitFor ( ( ) => readSnapshotCallCount >= 1 ) ;
1132+ await waitFor ( ( ) => nfReadSnapshotCount >= 1 ) ;
10741133
1075- assert . isAtLeast ( readSnapshotCallCount , 1 , 'readSnapshot should be called' ) ;
1134+ assert . isAtLeast ( nfReadSnapshotCount , 1 , 'readSnapshot should be called' ) ;
10761135 assert . strictEqual (
1077- snapshotApplyEditCount ,
1136+ nfApplyEditCount ,
10781137 0 ,
10791138 'applyEdit should NOT be called when no block IDs can be resolved'
10801139 ) ;
1140+
1141+ for ( const d of noFallbackDisposables ) {
1142+ d . dispose ( ) ;
1143+ }
1144+ noFallbackOnDidChange . dispose ( ) ;
1145+ noFallbackOnDidCreate . dispose ( ) ;
10811146 } ) ;
10821147
10831148 test ( 'should fall back to replaceCells when no kernel is active' , async ( ) => {
0 commit comments