Skip to content

Commit 6dc77a0

Browse files
committed
Add unit tests for DeepnoteFileChangeWatcher to handle scenarios without block IDs and implement a valid project structure for testing
1 parent 77c0347 commit 6dc77a0

1 file changed

Lines changed: 72 additions & 7 deletions

File tree

src/notebooks/deepnote/deepnoteFileChangeWatcher.unit.test.ts

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,22 @@ import { IDeepnoteNotebookManager } from '../types';
1111
import { DeepnoteFileChangeWatcher } from './deepnoteFileChangeWatcher';
1212
import { 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+
1430
const waitForTimeoutMs = 5000;
1531
const waitForIntervalMs = 50;
1632
const debounceWaitMs = 800;
@@ -37,6 +53,7 @@ async function waitFor(
3753
suite('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

Comments
 (0)