This commit is contained in:
Andras Schmelczer 2026-09-27 09:04:05 +01:00
commit c869f0a880

View file

@ -1,29 +1,35 @@
/* eslint-disable @typescript-eslint/no-unsafe-type-assertion */
import assert from "node:assert/strict";
import { it } from "node:test";
import type {
import {
Database,
RelativePath,
VaultUpdateId
emptyState,
type RelativePath,
type VaultUpdateId
} from "../persistence/database";
import type { FileOperations } from "../file-operations/file-operations";
import type { FileSystemOperations } from "../file-operations/filesystem-operations";
import type { WebSocketManager } from "../services/websocket-manager";
import type { ClientCursors } from "../services/types/ClientCursors";
import { EventListeners } from "../utils/data-structures/event-listeners";
import { hash } from "../utils/hash";
import { CursorTracker } from "./cursor-tracker";
function createDatabase(digest: string): Database {
const state = emptyState("test");
state.local.document = "note.md";
state.documents.document = { base: { vaultUpdateId: 1, hash: digest } };
return new Database(
state,
async () => undefined,
"test",
async () => undefined
);
}
it("keeps the last usable cursor while retaining only the latest future update", async (context) => {
const content = new TextEncoder().encode("current");
const contentHash = await hash(content);
let version = 1;
const database = {
getDocumentByDocumentId: () => ({
documentId: "document",
relativePath: "note.md",
metadata: { parentVersionId: version, hash: contentHash }
})
} as unknown as Database;
const database = createDatabase(contentHash);
const websocket = {
onWebSocketStatusChanged: new EventListeners(),
onRemoteCursorsUpdateReceived: new EventListeners<
@ -35,7 +41,7 @@ it("keeps the last usable cursor while retaining only the latest future update",
const tracker = new CursorTracker(
database,
websocket,
{ read: async () => content } as unknown as FileOperations,
{ read: async () => ({ content }) } as unknown as FileSystemOperations,
notifier
);
context.after(() => tracker.reset());
@ -61,14 +67,20 @@ it("keeps the last usable cursor while retaining only the latest future update",
]);
assert.equal(received[0].documentsWithCursors[0].vault_update_id, 1);
}
version = 2;
database.state.documents.document.base = {
vaultUpdateId: 2,
hash: contentHash
};
await notifier.triggerAsync("note.md");
assert.equal(
received[0].documentsWithCursors[0].vault_update_id,
1,
"superseded future cursors must not reappear"
);
version = 3;
database.state.documents.document.base = {
vaultUpdateId: 3,
hash: contentHash
};
await notifier.triggerAsync("note.md");
assert.equal(received[0].documentsWithCursors[0].vault_update_id, 3);
assert.equal(received[0].documentsWithCursors[0].relative_path, "note.md");
@ -80,17 +92,7 @@ it("refreshes unchanged cursors on reconnect and heartbeat, and follows the docu
context.mock.timers.enable({ apis: ["setInterval"] });
const content = new TextEncoder().encode("hello");
let path = "note.md";
const record = () => ({
documentId: "document",
relativePath: path,
metadata: { parentVersionId: 1, hash: contentHash }
});
const contentHash = await hash(content);
const database = {
getLatestDocumentByRelativePath: (candidate: RelativePath) =>
candidate === path ? record() : undefined,
getDocumentByDocumentId: () => record()
} as unknown as Database;
const database = createDatabase(await hash(content));
const sent: {
documentsWithCursors: {
relative_path: RelativePath;
@ -110,7 +112,7 @@ it("refreshes unchanged cursors on reconnect and heartbeat, and follows the docu
const tracker = new CursorTracker(
database,
websocket,
{ read: async () => content } as unknown as FileOperations,
{ read: async () => ({ content }) } as unknown as FileSystemOperations,
notifier
);
const drain = async () => {
@ -133,6 +135,7 @@ it("refreshes unchanged cursors on reconnect and heartbeat, and follows the docu
await drain();
assert.equal(sent.length, 3);
path = "renamed.md";
database.state.local.document = path;
await notifier.triggerAsync(path);
assert.equal(sent.at(-1)!.documentsWithCursors[0].relative_path, path);
websocket.onWebSocketStatusChanged.trigger(false);
@ -155,13 +158,7 @@ for (const interruption of ["disconnect", "reset"] as const) {
const contentHash = await hash(content);
const entered = Promise.withResolvers<void>();
const release = Promise.withResolvers<void>();
const database = {
getDocumentByDocumentId: () => ({
documentId: "document",
relativePath: "note.md",
metadata: { parentVersionId: 1, hash: contentHash }
})
} as unknown as Database;
const database = createDatabase(contentHash);
const websocket = {
onWebSocketStatusChanged: new EventListeners<
(connected: boolean) => unknown
@ -178,9 +175,9 @@ for (const interruption of ["disconnect", "reset"] as const) {
read: async () => {
entered.resolve();
await release.promise;
return content;
return { content };
}
} as unknown as FileOperations,
} as unknown as FileSystemOperations,
new EventListeners<(path: RelativePath) => unknown>()
);
let received: ClientCursors[] = [];
@ -223,7 +220,7 @@ for (const interruption of ["disconnect", "reset"] as const) {
it("a remote cursor becomes outdated if the document advances during its snapshot read", async () => {
const content = new TextEncoder().encode("old");
let metadata = { parentVersionId: 1, hash: await hash(content) };
const database = createDatabase(await hash(content));
const entered = Promise.withResolvers<void>(),
release = Promise.withResolvers<void>();
const websocket = {
@ -236,21 +233,15 @@ it("a remote cursor becomes outdated if the document advances during its snapsho
updateLocalCursors: () => {}
} as unknown as WebSocketManager;
const tracker = new CursorTracker(
{
getDocumentByDocumentId: () => ({
documentId: "document",
relativePath: "note.md",
metadata
})
} as unknown as Database,
database,
websocket,
{
read: async () => {
entered.resolve();
await release.promise;
return content;
return { content };
}
} as unknown as FileOperations,
} as unknown as FileSystemOperations,
new EventListeners<(path: RelativePath) => unknown>()
);
let outdated: boolean | undefined;
@ -272,8 +263,8 @@ it("a remote cursor becomes outdated if the document advances during its snapsho
}
]);
await entered.promise;
metadata = {
parentVersionId: 2,
database.state.documents.document.base = {
vaultUpdateId: 2,
hash: await hash(new TextEncoder().encode("new content"))
};
release.resolve();