feat(doc): F4.1 the shared document — a project in a Y.Doc and back, the janitor
packages/doc holds the editor's model (ImportedProject) losslessly in a Yjs document (schema 1): ports with their settings, each node's settings as maps per field, programs, files as a flat path map in creation order (text as Y.Text), own Java applications, links, documentation items, archive entries. The core's snapshot and the .fls export go through the project. The janitor repairs dangling links, two links at one port and files without their folder, deterministically and idempotently. All 23 corpus files come back through a Yjs update with the same snapshot and export. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
// A project in its document and back, for every file of the corpus: written
|
||||
// into a document, sent as Yjs sends it (an update into a second document)
|
||||
// and read back, it is the same project — the same snapshot for the core,
|
||||
// the same .fls on export, each folder's files in the same order.
|
||||
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
import type { VirtualFileSystem } from '@mdg-filius/core';
|
||||
import { openProjectArchive, projectSnapshot, saveProjectArchive, type ImportedProject } from '@mdg-filius/fls';
|
||||
import { docArchive, docSnapshot, readProject, ROOTS, SCHEMA_VERSION, writeProject } from '../src/index.js';
|
||||
|
||||
const corpus = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
||||
const files = ['java-examples', 'java-tests', 'ipad-fixtures'].flatMap((folder) =>
|
||||
readdirSync(join(corpus, folder))
|
||||
.filter((name) => name.endsWith('.fls'))
|
||||
.map((name) => join(folder, name)),
|
||||
);
|
||||
|
||||
/** Ids that fall in the file's reverse order: the document must keep the order itself, not by its ids. */
|
||||
function opened(file: string): ImportedProject {
|
||||
let counter = 0;
|
||||
return openProjectArchive(new Uint8Array(readFileSync(join(corpus, file))), () => `00000000-0000-0000-0000-${(0xffff_ffff - ++counter).toString(16).padStart(12, '0')}`);
|
||||
}
|
||||
|
||||
/** The document as another client gets it: its whole state as one update. */
|
||||
function sent(doc: Y.Doc): Y.Doc {
|
||||
const other = new Y.Doc();
|
||||
Y.applyUpdate(other, Y.encodeStateAsUpdate(doc));
|
||||
return other;
|
||||
}
|
||||
|
||||
/** Every folder's entries in the order they were made. */
|
||||
function creationOrder(files: VirtualFileSystem): string[] {
|
||||
const paths: string[] = [];
|
||||
const walk = (folder: string): void => {
|
||||
for (const entry of files.entriesInCreationOrder(folder)) {
|
||||
paths.push(entry.path);
|
||||
if (entry.content.kind === 'directory') walk(entry.path);
|
||||
}
|
||||
};
|
||||
walk('/');
|
||||
return paths;
|
||||
}
|
||||
|
||||
describe('a project in its document and back', () => {
|
||||
it('reads the whole corpus', () => {
|
||||
expect(files.length).toBe(23);
|
||||
});
|
||||
|
||||
for (const file of files) {
|
||||
it(file, () => {
|
||||
const project = opened(file);
|
||||
const doc = new Y.Doc();
|
||||
writeProject(doc, project);
|
||||
expect(doc.getMap(ROOTS.meta).get('schema')).toBe(SCHEMA_VERSION);
|
||||
const received = sent(doc);
|
||||
const back = readProject(received);
|
||||
|
||||
expect({ ...back, warnings: [] }).toEqual({ ...project, warnings: [] });
|
||||
for (const [nodeId, fileSystem] of project.virtualFileSystems) {
|
||||
expect(creationOrder(back.virtualFileSystems.get(nodeId)!), nodeId).toEqual(creationOrder(fileSystem));
|
||||
}
|
||||
expect(docSnapshot(received)).toEqual(projectSnapshot(project));
|
||||
expect(docArchive(received).archive).toEqual(saveProjectArchive(project).archive);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user