[Init] Initial commit - NetMesh terminal manager
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
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
This commit is contained in:
130
application/state/textEditorSaveCoordinator.test.ts
Normal file
130
application/state/textEditorSaveCoordinator.test.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
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"]);
|
||||
});
|
||||
Reference in New Issue
Block a user