Files
mdg-filius/packages/doc/test/projectDoc.test.ts
T
julian.kiedaischandClaude Opus 5.5 800e5de1d3 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>
2026-10-03 08:14:31 +00:00

71 lines
2.9 KiB
TypeScript

// 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);
});
}
});