asch/fix-everything #188

Open
andras wants to merge 114 commits from asch/fix-everything into main
2 changed files with 28 additions and 77 deletions
Showing only changes of commit 7c203bc5c9 - Show all commits

Fix mock client event triggering

Andras Schmelczer 2026-04-01 21:57:42 +01:00

View file

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

View file

@ -40,37 +40,22 @@ export class MockClient extends debugging.InMemoryFileSystem {
await this.client.start(); 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( this.executeFileOperation(
async () => this.client.syncLocallyCreatedFile(path), async () => this.client.syncLocallyUpdatedFile({ relativePath: path }),
ignoreSlowFileEvents
); );
} }
public override async atomicUpdateText( public override async atomicUpdateText(
path: RelativePath, path: RelativePath,
updater: (currentContent: TextWithCursors) => TextWithCursors updater: (currentContent: TextWithCursors) => TextWithCursors
): Promise<string> { ): 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); const file = this.files.get(path);
if (!file) { if (!file) {
throw new Error(`File ${path} does not exist`); throw new Error(`File ${path} does not exist`);
@ -80,38 +65,26 @@ export class MockClient extends debugging.InMemoryFileSystem {
const newContentUint8Array = new TextEncoder().encode(newContent); const newContentUint8Array = new TextEncoder().encode(newContent);
this.files.set(path, newContentUint8Array); this.files.set(path, newContentUint8Array);
this.executeFileOperation(
async () => this.client.syncLocallyUpdatedFile({ relativePath: path }),
);
return newContent; 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> { 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.files.delete(path);
this.executeFileOperation(
async () => this.client.syncLocallyDeletedFile(path),
);
} }
public override async rename( public override async rename(
oldPath: RelativePath, oldPath: RelativePath,
newPath: RelativePath newPath: RelativePath
): Promise<void> { ): 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); const file = this.files.get(oldPath);
if (!file) { if (!file) {
throw new Error(`File ${oldPath} does not exist`); throw new Error(`File ${oldPath} does not exist`);
@ -120,13 +93,18 @@ export class MockClient extends debugging.InMemoryFileSystem {
if (oldPath !== newPath) { if (oldPath !== newPath) {
this.files.delete(oldPath); this.files.delete(oldPath);
} }
this.executeFileOperation(
async () => this.client.syncLocallyUpdatedFile({
oldPath,
relativePath: newPath
}),
);
} }
protected executeFileOperation( protected executeFileOperation(
callback: () => unknown, callback: () => unknown,
ignoreSlowFileEvents = false
): void { ): void {
if (this.useSlowFileEvents && !ignoreSlowFileEvents) { if (this.useSlowFileEvents) {
// we aren't the best client and it takes some time to notice changes // we aren't the best client and it takes some time to notice changes
setTimeout(callback, Math.random() * 100); setTimeout(callback, Math.random() * 100);
} else { } else {