Fix mock client event triggering

This commit is contained in:
Andras Schmelczer 2026-04-01 21:57:42 +01:00
parent 19e4c39f44
commit 7c203bc5c9
2 changed files with 29 additions and 78 deletions

View file

@ -307,7 +307,7 @@ export class MockAgent extends MockClient {
);
if (fileContent.split(content).length > 2) {
if (this.useSlowFileEvents) {
logger.warn(
this.client.logger.warn(
`Content ${content} (of ${this.name}) found more than once in '${file}'. File content:\n${fileContent}`
);
} else {
@ -369,9 +369,8 @@ export class MockAgent extends MockClient {
`Decided to create file ${file} with content ${content}`
);
return this.create(file, new TextEncoder().encode(` ${content} `), {
ignoreSlowFileEvents: true
});
return this.write(file, new TextEncoder().encode(` ${content} `),);
}
// Binary file creation — exercises the putBinary server path (not in mergeable_file_extensions)
@ -388,12 +387,10 @@ export class MockAgent extends MockClient {
const { uuid, bytes } = this.getBinaryContent();
this.client.logger.info(
`Decided to create binary file ${file}`
`Decided to create binary file ${file}: ${uuid}`
);
return this.create(file, bytes, {
ignoreSlowFileEvents: true
});
return this.write(file, bytes,);
}
private async disableSyncAction(): Promise<void> {
@ -450,14 +447,6 @@ export class MockAgent extends MockClient {
this.client.logger.info(`Renamed file: ${file} -> ${newName}`);
await this.rename(file, newName);
this.executeFileOperation(
async () =>
this.client.syncLocallyUpdatedFile({
oldPath: file,
relativePath: newName
}),
true
);
}
private async updateFileAction(): Promise<void> {
@ -495,13 +484,6 @@ export class MockAgent extends MockClient {
})
);
this.executeFileOperation(
async () =>
this.client.syncLocallyUpdatedFile({
relativePath: file
}),
true
);
}
private async updateBinaryFileAction(): Promise<void> {
@ -530,13 +512,7 @@ export class MockAgent extends MockClient {
this.doNotTouchWhileOffline.push(file);
this.files.set(file, bytes);
this.executeFileOperation(
async () =>
this.client.syncLocallyUpdatedFile({
relativePath: file
}),
true
);
}
private async deleteFileAction(): Promise<void> {
@ -554,10 +530,7 @@ export class MockAgent extends MockClient {
`Deleting file: ${file} with:\n content '${new TextDecoder().decode(this.files.get(file))}'`
);
await this.delete(file);
this.executeFileOperation(
async () => this.client.syncLocallyDeletedFile(file),
true
);
}
private getContent(): string {

View file

@ -40,37 +40,22 @@ export class MockClient extends debugging.InMemoryFileSystem {
await this.client.start();
}
public async create(
path: RelativePath,
newContent: Uint8Array,
{ ignoreSlowFileEvents }: { ignoreSlowFileEvents: boolean } = {
ignoreSlowFileEvents: false
}
): Promise<void> {
if (this.files.has(path)) {
throw new Error(`File ${path} already exists`);
}
this.client.logger.info(
`Creating file ${path} with content ${new TextDecoder().decode(newContent)}`
);
this.files.set(path, newContent);
public override async write(
path: RelativePath,
content: Uint8Array
): Promise<void> {
this.files.set(path, content);
this.executeFileOperation(
async () => this.client.syncLocallyCreatedFile(path),
ignoreSlowFileEvents
async () => this.client.syncLocallyUpdatedFile({ relativePath: path }),
);
}
public override async atomicUpdateText(
path: RelativePath,
updater: (currentContent: TextWithCursors) => TextWithCursors
): Promise<string> {
// This method is called by BOTH the sync client (for remote text
// merges) and the test agent (for user updates). We must NOT call
// executeFileOperation here because the sync-client path would
// echo remote writes back as local modifications, creating an
// infinite sync loop. The test agent calls executeFileOperation
// separately after this method returns.
const file = this.files.get(path);
if (!file) {
throw new Error(`File ${path} does not exist`);
@ -80,38 +65,26 @@ export class MockClient extends debugging.InMemoryFileSystem {
const newContentUint8Array = new TextEncoder().encode(newContent);
this.files.set(path, newContentUint8Array);
this.executeFileOperation(
async () => this.client.syncLocallyUpdatedFile({ relativePath: path }),
);
return newContent;
}
public override async write(
path: RelativePath,
content: Uint8Array
): Promise<void> {
// This method is called by the sync client when writing files
// received from the server (remote updates). Do NOT call
// executeFileOperation here — that would echo the remote write
// back as a local modification, creating an infinite sync loop.
// User-initiated writes go through create(), atomicUpdateText(),
// or direct files.set() + executeFileOperation() in mock-agent.
this.files.set(path, content);
}
public override async delete(path: RelativePath): Promise<void> {
// Just perform the filesystem operation. The test agent calls
// executeFileOperation separately in mock-agent.ts. Not echoing
// here prevents the sync client's remote-delete writes from
// triggering spurious local-delete sync operations.
this.files.delete(path);
this.executeFileOperation(
async () => this.client.syncLocallyDeletedFile(path),
);
}
public override async rename(
oldPath: RelativePath,
newPath: RelativePath
): Promise<void> {
// Just perform the filesystem operation. The test agent calls
// executeFileOperation separately in mock-agent.ts. Not echoing
// here prevents the sync client's ensureClearPath / remote-rename
// writes from triggering spurious local-update sync operations.
const file = this.files.get(oldPath);
if (!file) {
throw new Error(`File ${oldPath} does not exist`);
@ -120,13 +93,18 @@ export class MockClient extends debugging.InMemoryFileSystem {
if (oldPath !== newPath) {
this.files.delete(oldPath);
}
this.executeFileOperation(
async () => this.client.syncLocallyUpdatedFile({
oldPath,
relativePath: newPath
}),
);
}
protected executeFileOperation(
callback: () => unknown,
ignoreSlowFileEvents = false
): void {
if (this.useSlowFileEvents && !ignoreSlowFileEvents) {
if (this.useSlowFileEvents) {
// we aren't the best client and it takes some time to notice changes
setTimeout(callback, Math.random() * 100);
} else {