fix
This commit is contained in:
parent
c869f0a880
commit
25f64ec690
1 changed files with 81 additions and 61 deletions
|
|
@ -88,7 +88,10 @@ it("keeps the last usable cursor while retaining only the latest future update",
|
||||||
assert.deepEqual(received, []);
|
assert.deepEqual(received, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("refreshes unchanged cursors on reconnect and heartbeat, and follows the document through renames", async (context) => {
|
it(
|
||||||
|
"refreshes unchanged cursors on reconnect and heartbeat, and follows the document through renames",
|
||||||
|
{ timeout: 5_000 },
|
||||||
|
async (context) => {
|
||||||
context.mock.timers.enable({ apis: ["setInterval"] });
|
context.mock.timers.enable({ apis: ["setInterval"] });
|
||||||
const content = new TextEncoder().encode("hello");
|
const content = new TextEncoder().encode("hello");
|
||||||
let path = "note.md";
|
let path = "note.md";
|
||||||
|
|
@ -99,6 +102,7 @@ it("refreshes unchanged cursors on reconnect and heartbeat, and follows the docu
|
||||||
vault_update_id: VaultUpdateId | null;
|
vault_update_id: VaultUpdateId | null;
|
||||||
}[];
|
}[];
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
let published = Promise.withResolvers<undefined>();
|
||||||
const websocket = {
|
const websocket = {
|
||||||
onWebSocketStatusChanged: new EventListeners<
|
onWebSocketStatusChanged: new EventListeners<
|
||||||
(connected: boolean) => unknown
|
(connected: boolean) => unknown
|
||||||
|
|
@ -106,18 +110,23 @@ it("refreshes unchanged cursors on reconnect and heartbeat, and follows the docu
|
||||||
onRemoteCursorsUpdateReceived: new EventListeners(),
|
onRemoteCursorsUpdateReceived: new EventListeners(),
|
||||||
updateLocalCursors: (value: (typeof sent)[number]) => {
|
updateLocalCursors: (value: (typeof sent)[number]) => {
|
||||||
sent.push(value);
|
sent.push(value);
|
||||||
|
published.resolve(undefined);
|
||||||
}
|
}
|
||||||
} as unknown as WebSocketManager;
|
} as unknown as WebSocketManager;
|
||||||
const notifier = new EventListeners<(path: RelativePath) => unknown>();
|
const notifier = new EventListeners<(path: RelativePath) => unknown>();
|
||||||
|
const read = context.mock.fn(async () => ({ content }));
|
||||||
const tracker = new CursorTracker(
|
const tracker = new CursorTracker(
|
||||||
database,
|
database,
|
||||||
websocket,
|
websocket,
|
||||||
{ read: async () => ({ content }) } as unknown as FileSystemOperations,
|
{ read } as unknown as FileSystemOperations,
|
||||||
notifier
|
notifier
|
||||||
);
|
);
|
||||||
const drain = async () => {
|
const waitForSend = async (trigger: () => void): Promise<void> => {
|
||||||
for (let i = 0; i < 20; i++)
|
published = Promise.withResolvers<undefined>();
|
||||||
await new Promise((resolve) => setImmediate(resolve));
|
trigger();
|
||||||
|
// Hashing finishes outside the event loop; a fixed number of turns
|
||||||
|
// cannot guarantee that the cursor snapshot has been published.
|
||||||
|
await published.promise;
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await tracker.sendLocalCursorsToServer({
|
await tracker.sendLocalCursorsToServer({
|
||||||
|
|
@ -128,29 +137,40 @@ it("refreshes unchanged cursors on reconnect and heartbeat, and follows the docu
|
||||||
"note.md": [{ start: 1, end: 2 }]
|
"note.md": [{ start: 1, end: 2 }]
|
||||||
});
|
});
|
||||||
assert.equal(sent.length, 1);
|
assert.equal(sent.length, 1);
|
||||||
|
await waitForSend(() => {
|
||||||
websocket.onWebSocketStatusChanged.trigger(true);
|
websocket.onWebSocketStatusChanged.trigger(true);
|
||||||
await drain();
|
});
|
||||||
assert.equal(sent.length, 2);
|
assert.equal(sent.length, 2);
|
||||||
|
await waitForSend(() => {
|
||||||
context.mock.timers.tick(15_000);
|
context.mock.timers.tick(15_000);
|
||||||
await drain();
|
});
|
||||||
assert.equal(sent.length, 3);
|
assert.equal(sent.length, 3);
|
||||||
path = "renamed.md";
|
path = "renamed.md";
|
||||||
database.state.local.document = path;
|
database.state.local.document = path;
|
||||||
await notifier.triggerAsync(path);
|
await notifier.triggerAsync(path);
|
||||||
assert.equal(sent.at(-1)!.documentsWithCursors[0].relative_path, path);
|
assert.equal(
|
||||||
|
sent.at(-1)!.documentsWithCursors[0].relative_path,
|
||||||
|
path
|
||||||
|
);
|
||||||
websocket.onWebSocketStatusChanged.trigger(false);
|
websocket.onWebSocketStatusChanged.trigger(false);
|
||||||
const count = sent.length;
|
const count = sent.length;
|
||||||
|
const readCount = read.mock.callCount();
|
||||||
context.mock.timers.tick(30_000);
|
context.mock.timers.tick(30_000);
|
||||||
await drain();
|
// A surviving heartbeat would start a snapshot read in the send lock's
|
||||||
|
// next microtask, even if hashing that snapshot has not finished yet.
|
||||||
|
await Promise.resolve();
|
||||||
|
assert.equal(read.mock.callCount(), readCount);
|
||||||
assert.equal(sent.length, count);
|
assert.equal(sent.length, count);
|
||||||
|
await waitForSend(() => {
|
||||||
websocket.onWebSocketStatusChanged.trigger(true);
|
websocket.onWebSocketStatusChanged.trigger(true);
|
||||||
await drain();
|
});
|
||||||
assert.equal(sent.length, count + 1);
|
assert.equal(sent.length, count + 1);
|
||||||
} finally {
|
} finally {
|
||||||
tracker.reset();
|
tracker.reset();
|
||||||
context.mock.timers.reset();
|
context.mock.timers.reset();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
for (const interruption of ["disconnect", "reset"] as const) {
|
for (const interruption of ["disconnect", "reset"] as const) {
|
||||||
it(`an in-flight cursor read cannot resurrect presence after ${interruption}`, async () => {
|
it(`an in-flight cursor read cannot resurrect presence after ${interruption}`, async () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue