Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
131 lines
4.0 KiB
TypeScript
131 lines
4.0 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { createTextEditorSaveCoordinator } from "./textEditorSaveCoordinator.ts";
|
|
|
|
const deferred = <T = void>() => {
|
|
let resolve!: (value: T | PromiseLike<T>) => void;
|
|
let reject!: (reason?: unknown) => void;
|
|
const promise = new Promise<T>((res, rej) => {
|
|
resolve = res;
|
|
reject = rej;
|
|
});
|
|
return { promise, resolve, reject };
|
|
};
|
|
|
|
test("text editor save coordinator joins duplicate saves already in flight", async () => {
|
|
const pending = deferred();
|
|
const saved: string[] = [];
|
|
const savingStates: boolean[] = [];
|
|
const coordinator = createTextEditorSaveCoordinator({
|
|
onSave: async (content) => {
|
|
saved.push(content);
|
|
await pending.promise;
|
|
},
|
|
onSavingChange: (saving) => savingStates.push(saving),
|
|
});
|
|
|
|
const first = coordinator.save("remote text");
|
|
const second = coordinator.save("remote text");
|
|
|
|
assert.deepEqual(saved, ["remote text"]);
|
|
pending.resolve();
|
|
|
|
assert.equal(await first, true);
|
|
assert.equal(await second, true);
|
|
assert.deepEqual(saved, ["remote text"]);
|
|
assert.deepEqual(savingStates, [true, false]);
|
|
});
|
|
|
|
test("text editor save coordinator saves newer content after an in-flight save finishes", async () => {
|
|
const firstSave = deferred();
|
|
const secondSave = deferred();
|
|
const saved: string[] = [];
|
|
const coordinator = createTextEditorSaveCoordinator({
|
|
onSave: async (content) => {
|
|
saved.push(content);
|
|
await (content === "v1" ? firstSave.promise : secondSave.promise);
|
|
},
|
|
});
|
|
|
|
const first = coordinator.save("v1");
|
|
const second = coordinator.save("v2");
|
|
|
|
assert.deepEqual(saved, ["v1"]);
|
|
firstSave.resolve();
|
|
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
assert.deepEqual(saved, ["v1", "v2"]);
|
|
secondSave.resolve();
|
|
|
|
assert.equal(await first, true);
|
|
assert.equal(await second, true);
|
|
});
|
|
|
|
test("text editor save coordinator returns false to duplicate callers when the in-flight save fails", async () => {
|
|
const pending = deferred();
|
|
const errors: string[] = [];
|
|
const coordinator = createTextEditorSaveCoordinator({
|
|
onSave: async () => {
|
|
await pending.promise;
|
|
throw new Error("denied");
|
|
},
|
|
onSaveError: (error) => {
|
|
errors.push(error instanceof Error ? error.message : String(error));
|
|
},
|
|
});
|
|
|
|
const first = coordinator.save("content");
|
|
const second = coordinator.save("content");
|
|
|
|
pending.resolve();
|
|
|
|
assert.equal(await first, false);
|
|
assert.equal(await second, false);
|
|
assert.deepEqual(errors, ["denied"]);
|
|
});
|
|
|
|
test("text editor save coordinator reset prevents an old in-flight save from updating the next file", async () => {
|
|
const pending = deferred();
|
|
const successes: string[] = [];
|
|
const errors: string[] = [];
|
|
const savingStates: boolean[] = [];
|
|
const coordinator = createTextEditorSaveCoordinator({
|
|
onSave: async () => {
|
|
await pending.promise;
|
|
},
|
|
onSaveSuccess: (content) => successes.push(content),
|
|
onSaveError: (error) => errors.push(error instanceof Error ? error.message : String(error)),
|
|
onSavingChange: (saving) => savingStates.push(saving),
|
|
});
|
|
|
|
const save = coordinator.save("old file");
|
|
coordinator.reset();
|
|
pending.resolve();
|
|
|
|
assert.equal(await save, false);
|
|
assert.deepEqual(successes, []);
|
|
assert.deepEqual(errors, []);
|
|
assert.deepEqual(savingStates, [true, false]);
|
|
});
|
|
|
|
test("text editor save coordinator reset cancels queued stale saves", async () => {
|
|
const firstSave = deferred();
|
|
const saved: string[] = [];
|
|
const coordinator = createTextEditorSaveCoordinator({
|
|
onSave: async (content) => {
|
|
saved.push(content);
|
|
await firstSave.promise;
|
|
},
|
|
});
|
|
|
|
const first = coordinator.save("old v1");
|
|
const queued = coordinator.save("old v2");
|
|
coordinator.reset();
|
|
firstSave.resolve();
|
|
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
|
|
assert.equal(await first, false);
|
|
assert.equal(await queued, false);
|
|
assert.deepEqual(saved, ["old v1"]);
|
|
});
|