feat: F4.6f2 the text editor on the document's Y.Text, files both ways while the simulation runs
Oracle TextEditorWindowRun uses Java's text editor window and its file dialog as a person does. The open file's text is its Y.Text in the document (plan 7.3): typed is in the file at once, for everyone and for the running engine. Core openText/saveTextAs without Java's mistakes (lost saves after save-as onto a file or a removed file, files named 'ordner/d.txt' or ''); doc files.ts (changeFiles, fileText, nodeFiles, readFilesInto); the room takes a person's files into the engine at once and writes the engine's files back at the tick they changed; the editor's window, Java's dialog, the text area following others' changes with the caret in place, its own undo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
// A device's files in the document (`files`: path → { order, kind, text |
|
||||
// data, mediaType? }): read as the core's file system, changed through the
|
||||
// core's operations (what the text editor's, and later the explorer's,
|
||||
// buttons do), and the two ways between the document and a running engine —
|
||||
// what a run wrote goes into the document (writeFiles, for writeRunState),
|
||||
// what a person wrote meanwhile into the engine (readFilesInto). Only what
|
||||
// differs is written either way, a text where it changed, so that what two
|
||||
// write at once is kept (plan §7.3: the text editor's content is the
|
||||
// document's Y.Text).
|
||||
|
||||
import * as Y from 'yjs';
|
||||
import type { Id, VirtualFileContent, VirtualFileSystem } from '@mdg-filius/core';
|
||||
import { VirtualFileSystem as FileSystem } from '@mdg-filius/core';
|
||||
import { fileEntry, fileSystemOf, ROOTS } from './projectDoc.js';
|
||||
|
||||
export function sameBytes(left: Uint8Array, right: Uint8Array): boolean {
|
||||
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
||||
}
|
||||
|
||||
/** A text changed where it changed: the common start and end stay (and with them what another person typed there). */
|
||||
export function replaceText(text: Y.Text, next: string): void {
|
||||
const current = text.toString();
|
||||
if (current === next) return;
|
||||
let start = 0;
|
||||
while (start < current.length && start < next.length && current[start] === next[start]) start += 1;
|
||||
let end = 0;
|
||||
while (end < current.length - start && end < next.length - start && current[current.length - 1 - end] === next[next.length - 1 - end]) end += 1;
|
||||
if (current.length - start - end > 0) text.delete(start, current.length - start - end);
|
||||
if (next.length - start - end > 0) text.insert(start, next.slice(start, next.length - end));
|
||||
}
|
||||
|
||||
/** Whether a document entry holds the content. */
|
||||
function holds(entry: Y.Map<unknown>, content: VirtualFileContent): boolean {
|
||||
if (entry.get('kind') !== content.kind) return false;
|
||||
if (content.kind === 'directory' || content.kind === 'text') return true;
|
||||
return sameBytes(entry.get('data') as Uint8Array, content.data) && entry.get('mediaType') === content.mediaType;
|
||||
}
|
||||
|
||||
/** A node's files as the file system has them: new ones after the others, changed ones in their place, gone ones removed. */
|
||||
export function writeFiles(node: Y.Map<unknown>, fileSystem: VirtualFileSystem): void {
|
||||
let files = node.get('files') as Y.Map<Y.Map<unknown>> | undefined;
|
||||
const wanted: [string, VirtualFileContent][] = [];
|
||||
const walk = (folder: string): void => {
|
||||
for (const entry of fileSystem.entriesInCreationOrder(folder)) {
|
||||
wanted.push([entry.path, entry.content]);
|
||||
if (entry.content.kind === 'directory') walk(entry.path);
|
||||
}
|
||||
};
|
||||
walk('/');
|
||||
if (files === undefined) {
|
||||
if (wanted.length === 0) return;
|
||||
files = new Y.Map<Y.Map<unknown>>();
|
||||
node.set('files', files);
|
||||
}
|
||||
const paths = new Set(wanted.map(([path]) => path));
|
||||
for (const path of [...files.keys()]) if (!paths.has(path)) files.delete(path);
|
||||
let order = Math.max(-1, ...[...files.values()].map((entry) => entry.get('order') as number)) + 1;
|
||||
for (const [path, content] of wanted) {
|
||||
const entry = files.get(path);
|
||||
if (entry === undefined) {
|
||||
files.set(path, fileEntry(order++, content));
|
||||
} else if (!holds(entry, content)) {
|
||||
files.set(path, fileEntry(entry.get('order') as number, content));
|
||||
} else if (content.kind === 'text') {
|
||||
replaceText(entry.get('text') as Y.Text, content.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nodeMap(doc: Y.Doc, id: Id): Y.Map<unknown> | undefined {
|
||||
return doc.getMap<Y.Map<unknown>>(ROOTS.nodes).get(id);
|
||||
}
|
||||
|
||||
/** A device's files as the core's file system; none for a device the document does not have. */
|
||||
export function nodeFiles(doc: Y.Doc, id: Id): VirtualFileSystem | undefined {
|
||||
const node = nodeMap(doc, id);
|
||||
if (node === undefined) return undefined;
|
||||
const files = node.get('files') as Y.Map<Y.Map<unknown>> | undefined;
|
||||
return files === undefined ? new FileSystem() : fileSystemOf(files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a device's files through the core's operations (filePrograms): the
|
||||
* change gets them as a file system, and what it changed is written into
|
||||
* the document, in one transaction with the origin. What the change
|
||||
* returned; undefined for a device the document does not have.
|
||||
*/
|
||||
export function changeFiles<T>(doc: Y.Doc, id: Id, origin: unknown, change: (files: VirtualFileSystem) => T): T | undefined {
|
||||
const node = nodeMap(doc, id);
|
||||
const files = nodeFiles(doc, id);
|
||||
if (node === undefined || files === undefined) return undefined;
|
||||
const result = change(files);
|
||||
// What did not change writes nothing (writeFiles writes what differs only).
|
||||
doc.transact(() => writeFiles(node, files), origin);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** A text file's text in the document, the entry found in any case; none when there is no text file at the path. */
|
||||
export function fileText(doc: Y.Doc, id: Id, path: string): Y.Text | undefined {
|
||||
const files = nodeMap(doc, id)?.get('files') as Y.Map<Y.Map<unknown>> | undefined;
|
||||
if (files === undefined) return undefined;
|
||||
const lower = path.toLowerCase();
|
||||
const entry = files.get(path) ?? [...files.entries()].find(([key]) => key.toLowerCase() === lower)?.[1];
|
||||
// Only a text file's entry has a text.
|
||||
return entry?.get('text') as Y.Text | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The document's files of a device into a file system (a running engine's):
|
||||
* what differs only, an entry the document no longer has removed, a new one
|
||||
* after the others; when a file turned into a folder or back, all anew in the
|
||||
* document's order (the command line lists them so). Read tolerantly, as fileSystemOf: an entry the file
|
||||
* system refuses is left out. Whether anything changed.
|
||||
*/
|
||||
export function readFilesInto(doc: Y.Doc, id: Id, fileSystem: VirtualFileSystem): boolean {
|
||||
const node = nodeMap(doc, id);
|
||||
if (node === undefined) return false;
|
||||
const revision = fileSystem.revision;
|
||||
const wanted = fileSystemOf((node.get('files') as Y.Map<Y.Map<unknown>> | undefined) ?? new Y.Map());
|
||||
// By their spelling: a name changed only in case is the entry anew.
|
||||
const kept = new Set(wanted.allEntries().map((entry) => entry.path));
|
||||
// Deepest first, so that a folder's entries go before it.
|
||||
for (const entry of fileSystem.allEntries().reverse()) {
|
||||
if (entry.path !== '/' && !kept.has(entry.path)) fileSystem.deleteItem(entry.path, true);
|
||||
}
|
||||
const walk = (folder: string): void => {
|
||||
for (const { path, content } of wanted.entriesInCreationOrder(folder)) {
|
||||
const have = fileSystem.contains(path) ? fileSystem.entry(path).content : undefined;
|
||||
if (have === undefined || !sameContent(have, content)) {
|
||||
try {
|
||||
// A folder for a file, or a file for a folder: the old one goes first.
|
||||
if (have !== undefined && (have.kind === 'directory' || content.kind === 'directory')) fileSystem.deleteItem(path, true);
|
||||
write(fileSystem, path, content);
|
||||
} catch {
|
||||
// Left out (see above).
|
||||
}
|
||||
}
|
||||
if (content.kind === 'directory') walk(path);
|
||||
}
|
||||
};
|
||||
walk('/');
|
||||
// A file turned into a folder, or a folder into a file, came in after the others: built anew in the document's order.
|
||||
if (preorder(fileSystem).join('\n') !== preorder(wanted).join('\n')) {
|
||||
for (const entry of fileSystem.entriesInCreationOrder('/')) fileSystem.deleteItem(entry.path, true);
|
||||
for (const path of preorder(wanted)) write(fileSystem, path, wanted.entry(path).content);
|
||||
}
|
||||
return fileSystem.revision !== revision;
|
||||
}
|
||||
|
||||
/** Every entry's path, each folder's entries in the order they were made after the folder. */
|
||||
function preorder(fileSystem: VirtualFileSystem): string[] {
|
||||
return fileSystem.entriesInCreationOrder('/').flatMap(function below(entry): string[] {
|
||||
return [entry.path, ...(entry.content.kind === 'directory' ? fileSystem.entriesInCreationOrder(entry.path).flatMap(below) : [])];
|
||||
});
|
||||
}
|
||||
|
||||
function write(fileSystem: VirtualFileSystem, path: string, content: VirtualFileContent): void {
|
||||
if (content.kind === 'directory') fileSystem.createDirectory(path);
|
||||
else if (content.kind === 'text') fileSystem.writeTextFile(path, content.text);
|
||||
else if (content.kind === 'image') fileSystem.writeImageFile(path, content.data, content.mediaType);
|
||||
else fileSystem.writeBinaryFile(path, content.data, content.mediaType);
|
||||
}
|
||||
|
||||
function sameContent(left: VirtualFileContent, right: VirtualFileContent): boolean {
|
||||
if (left.kind !== right.kind) return false;
|
||||
if (left.kind === 'directory') return true;
|
||||
if (left.kind === 'text') return left.text === (right as typeof left).text;
|
||||
const other = right as typeof left;
|
||||
return sameBytes(left.data, other.data) && left.mediaType === other.mediaType;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Public surface of the shared document.
|
||||
export * from './commands.js';
|
||||
export * from './fields.js';
|
||||
export * from './files.js';
|
||||
export * from './janitor.js';
|
||||
export * from './projectDoc.js';
|
||||
export * from './settings.js';
|
||||
|
||||
@@ -224,7 +224,7 @@ export function inOrder(map: Y.Map<Y.Map<unknown>>): [string, Y.Map<unknown>][]
|
||||
* system refuses (its folder missing before the janitor ran, or a name two
|
||||
* people gave at once in different case) is left out, not the project.
|
||||
*/
|
||||
function fileSystemOf(files: Y.Map<Y.Map<unknown>>): VirtualFileSystem {
|
||||
export function fileSystemOf(files: Y.Map<Y.Map<unknown>>): VirtualFileSystem {
|
||||
const fileSystem = new VirtualFileSystem();
|
||||
for (const [path, entry] of inOrder(files)) {
|
||||
const kind = entry.get('kind') as VirtualFileContent['kind'];
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
// the second time and a text edited meanwhile keeps what it did not touch.
|
||||
|
||||
import * as Y from 'yjs';
|
||||
import type { Id, InstallableProgram, VirtualFileContent, VirtualFileSystem } from '@mdg-filius/core';
|
||||
import { fileEntry, NODE_SETTINGS, ROOTS } from './projectDoc.js';
|
||||
import type { Id, InstallableProgram, VirtualFileSystem } from '@mdg-filius/core';
|
||||
import { sameBytes, writeFiles } from './files.js';
|
||||
import { NODE_SETTINGS, ROOTS } from './projectDoc.js';
|
||||
|
||||
export { emailServerAfterRun } from '@mdg-filius/fls';
|
||||
import { fromY, toY, type PlainValue } from './values.js';
|
||||
@@ -43,60 +44,6 @@ function same(left: unknown, right: unknown): boolean {
|
||||
return left === right;
|
||||
}
|
||||
|
||||
function sameBytes(left: Uint8Array, right: Uint8Array): boolean {
|
||||
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
||||
}
|
||||
|
||||
/** A text changed where it changed: the common start and end stay (and with them what another person typed there). */
|
||||
function replaceText(text: Y.Text, next: string): void {
|
||||
const current = text.toString();
|
||||
if (current === next) return;
|
||||
let start = 0;
|
||||
while (start < current.length && start < next.length && current[start] === next[start]) start += 1;
|
||||
let end = 0;
|
||||
while (end < current.length - start && end < next.length - start && current[current.length - 1 - end] === next[next.length - 1 - end]) end += 1;
|
||||
if (current.length - start - end > 0) text.delete(start, current.length - start - end);
|
||||
if (next.length - start - end > 0) text.insert(start, next.slice(start, next.length - end));
|
||||
}
|
||||
|
||||
/** Whether a document entry holds the content. */
|
||||
function holds(entry: Y.Map<unknown>, content: VirtualFileContent): boolean {
|
||||
if (entry.get('kind') !== content.kind) return false;
|
||||
if (content.kind === 'directory' || content.kind === 'text') return true;
|
||||
return sameBytes(entry.get('data') as Uint8Array, content.data) && entry.get('mediaType') === content.mediaType;
|
||||
}
|
||||
|
||||
/** A node's files as the run left them: new ones after the others, changed ones in their place, gone ones removed. */
|
||||
function writeFiles(node: Y.Map<unknown>, fileSystem: VirtualFileSystem): void {
|
||||
let files = node.get('files') as Y.Map<Y.Map<unknown>> | undefined;
|
||||
const wanted: [string, VirtualFileContent][] = [];
|
||||
const walk = (folder: string): void => {
|
||||
for (const entry of fileSystem.entriesInCreationOrder(folder)) {
|
||||
wanted.push([entry.path, entry.content]);
|
||||
if (entry.content.kind === 'directory') walk(entry.path);
|
||||
}
|
||||
};
|
||||
walk('/');
|
||||
if (files === undefined) {
|
||||
if (wanted.length === 0) return;
|
||||
files = new Y.Map<Y.Map<unknown>>();
|
||||
node.set('files', files);
|
||||
}
|
||||
const paths = new Set(wanted.map(([path]) => path));
|
||||
for (const path of [...files.keys()]) if (!paths.has(path)) files.delete(path);
|
||||
let order = Math.max(-1, ...[...files.values()].map((entry) => entry.get('order') as number)) + 1;
|
||||
for (const [path, content] of wanted) {
|
||||
const entry = files.get(path);
|
||||
if (entry === undefined) {
|
||||
files.set(path, fileEntry(order++, content));
|
||||
} else if (!holds(entry, content)) {
|
||||
files.set(path, fileEntry(entry.get('order') as number, content));
|
||||
} else if (content.kind === 'text') {
|
||||
replaceText(entry.get('text') as Y.Text, content.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Writes what the runs left of the nodes, what differs only, in one transaction; nodes the document no longer has are left out. */
|
||||
export function writeRunState(doc: Y.Doc, states: ReadonlyMap<Id, NodeRunState>, origin: unknown = SIM_ORIGIN): void {
|
||||
const nodes = doc.getMap<Y.Map<unknown>>(ROOTS.nodes);
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
// A device's files in the document: read as a file system, changed through
|
||||
// the core's operations, a text file's Y.Text found in any case, and the
|
||||
// document's files into a running engine's — what differs only.
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
import { saveTextAs, VirtualFileSystem } from '@mdg-filius/core';
|
||||
import { addDevice, changeFiles, emptyProject, fileText, nodeFiles, readFilesInto, ROOTS, writeProject } from '../src/index.js';
|
||||
|
||||
function setUp() {
|
||||
const doc = new Y.Doc();
|
||||
writeProject(doc, emptyProject());
|
||||
let counter = 0;
|
||||
addDevice(doc, { kind: 'pc', id: 'pc', name: 'PC', x: 0, y: 0, newId: () => `port-${++counter}` }, 'me');
|
||||
const files = () => doc.getMap<Y.Map<unknown>>(ROOTS.nodes).get('pc')!.get('files') as Y.Map<Y.Map<unknown>> | undefined;
|
||||
return { doc, files };
|
||||
}
|
||||
|
||||
const paths = (fileSystem: VirtualFileSystem) => fileSystem.allEntries().map((entry) => [entry.path, entry.content.kind === 'text' ? entry.content.text : entry.content.kind]);
|
||||
|
||||
describe("a device's files in the document", () => {
|
||||
it('are changed through the core, written with the origin, what changed only', () => {
|
||||
const { doc, files } = setUp();
|
||||
expect(paths(nodeFiles(doc, 'pc')!)).toEqual([['/', 'directory']]);
|
||||
expect(nodeFiles(doc, 'none')).toBeUndefined();
|
||||
const origins: unknown[] = [];
|
||||
doc.on('update', (_update: Uint8Array, origin: unknown) => origins.push(origin));
|
||||
expect(changeFiles(doc, 'pc', 'me', (fileSystem) => saveTextAs(fileSystem, '/', 'a.txt', 'Anfang Ende'))).toBe('saved');
|
||||
expect(origins).toEqual(['me']);
|
||||
expect((files()!.get('/a.txt')!.get('text') as Y.Text).toString()).toBe('Anfang Ende');
|
||||
// Nothing changed: nothing written.
|
||||
expect(changeFiles(doc, 'pc', 'me', (fileSystem) => fileSystem.contains('/a.txt'))).toBe(true);
|
||||
expect(origins).toHaveLength(1);
|
||||
expect(changeFiles(doc, 'none', 'me', () => 'ran')).toBeUndefined();
|
||||
});
|
||||
|
||||
it("finds a text file's Y.Text in any case, and none for a folder, an image or what is not there", () => {
|
||||
const { doc } = setUp();
|
||||
changeFiles(doc, 'pc', 'me', (fileSystem) => {
|
||||
fileSystem.writeTextFile('/Notiz.txt', 'x');
|
||||
fileSystem.createDirectory('/ordner');
|
||||
fileSystem.writeImageFile('/bild.png', new Uint8Array([1]), 'image/png');
|
||||
});
|
||||
const text = fileText(doc, 'pc', '/Notiz.txt');
|
||||
expect(text?.toString()).toBe('x');
|
||||
expect(fileText(doc, 'pc', '/NOTIZ.TXT')).toBe(text);
|
||||
expect([fileText(doc, 'pc', '/ordner'), fileText(doc, 'pc', '/bild.png'), fileText(doc, 'pc', '/fehlt.txt'), fileText(doc, 'none', '/Notiz.txt')]).toEqual([
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
]);
|
||||
const empty = new Y.Doc();
|
||||
writeProject(empty, emptyProject());
|
||||
addDevice(empty, { kind: 'pc', id: 'pc', name: 'PC', x: 0, y: 0, newId: () => 'port' }, 'me');
|
||||
expect(fileText(empty, 'pc', '/Notiz.txt')).toBeUndefined();
|
||||
});
|
||||
|
||||
it("go into a running engine's file system, what differs only: new ones after the others, changed ones in their place, gone ones removed", () => {
|
||||
const { doc } = setUp();
|
||||
changeFiles(doc, 'pc', 'me', (fileSystem) => {
|
||||
fileSystem.createDirectory('/ordner');
|
||||
fileSystem.writeTextFile('/ordner/a.txt', 'a');
|
||||
fileSystem.writeTextFile('/b.txt', 'b');
|
||||
fileSystem.writeTextFile('/Gross.txt', 'G');
|
||||
fileSystem.writeImageFile('/bild.png', new Uint8Array([1]), 'image/png');
|
||||
fileSystem.writeBinaryFile('/daten.bin', new Uint8Array([2]));
|
||||
});
|
||||
const engine = nodeFiles(doc, 'pc')!;
|
||||
engine.writeTextFile('/eigen.txt', 'the engine wrote it');
|
||||
// Alike but for the engine's own file: it goes, the others stay as they are.
|
||||
expect(readFilesInto(doc, 'pc', engine)).toBe(true);
|
||||
expect(paths(engine)).toEqual(paths(nodeFiles(doc, 'pc')!));
|
||||
const revision = engine.revision;
|
||||
expect(readFilesInto(doc, 'pc', engine)).toBe(false);
|
||||
expect(engine.revision).toBe(revision);
|
||||
// A text changed, an image, a binary file, a folder turned into a file and a file into a folder, a new one.
|
||||
changeFiles(doc, 'pc', 'me', (fileSystem) => {
|
||||
fileSystem.writeTextFile('/b.txt', 'b2');
|
||||
fileSystem.writeImageFile('/bild.png', new Uint8Array([3]), 'image/png');
|
||||
fileSystem.writeBinaryFile('/daten.bin', new Uint8Array([2]), 'application/octet-stream');
|
||||
fileSystem.deleteItem('/ordner', true);
|
||||
fileSystem.writeTextFile('/ordner', 'now a file');
|
||||
fileSystem.deleteItem('/b.txt');
|
||||
fileSystem.createDirectory('/b.txt');
|
||||
fileSystem.writeTextFile('/neu.txt', 'neu');
|
||||
});
|
||||
expect(readFilesInto(doc, 'pc', engine)).toBe(true);
|
||||
expect(paths(engine)).toEqual(paths(nodeFiles(doc, 'pc')!));
|
||||
expect(
|
||||
engine
|
||||
.entriesInCreationOrder('/')
|
||||
.map((entry) => entry.path)
|
||||
.at(-1),
|
||||
).toBe('/neu.txt');
|
||||
expect(engine.imageFile('/bild.png').data).toEqual(new Uint8Array([3]));
|
||||
expect(engine.entry('/daten.bin').content).toEqual({ kind: 'binary', data: new Uint8Array([2]), mediaType: 'application/octet-stream' });
|
||||
expect(readFilesInto(doc, 'none', engine)).toBe(false);
|
||||
// A file in a new folder; then a folder with all in it removed; a name changed only in case comes with its spelling.
|
||||
changeFiles(doc, 'pc', 'me', (fileSystem) => {
|
||||
fileSystem.createDirectory('/neu');
|
||||
fileSystem.createDirectory('/neu/tiefer');
|
||||
fileSystem.writeTextFile('/neu/tiefer/c.txt', 'c');
|
||||
});
|
||||
expect(readFilesInto(doc, 'pc', engine)).toBe(true);
|
||||
expect(engine.textFile('/neu/tiefer/c.txt')).toBe('c');
|
||||
changeFiles(doc, 'pc', 'me', (fileSystem) => {
|
||||
fileSystem.deleteItem('/neu', true);
|
||||
fileSystem.renameInPlace('/neu.txt', 'NEU.txt');
|
||||
});
|
||||
expect(readFilesInto(doc, 'pc', engine)).toBe(true);
|
||||
expect(paths(engine)).toEqual(paths(nodeFiles(doc, 'pc')!));
|
||||
expect(engine.contains('/neu')).toBe(false);
|
||||
expect(engine.entry('/neu.txt').path).toBe('/NEU.txt');
|
||||
// What stayed keeps its place, a name in capitals too.
|
||||
const order = (fileSystem: VirtualFileSystem) => fileSystem.entriesInCreationOrder('/').map((entry) => entry.path);
|
||||
expect(order(engine)).toEqual(order(nodeFiles(doc, 'pc')!));
|
||||
expect(order(engine).indexOf('/Gross.txt')).toBeLessThan(order(engine).indexOf('/NEU.txt'));
|
||||
// So also in a folder.
|
||||
changeFiles(doc, 'pc', 'me', (fileSystem) => {
|
||||
fileSystem.createDirectory('/tief');
|
||||
fileSystem.writeTextFile('/tief/x', 'x');
|
||||
fileSystem.writeTextFile('/tief/y.txt', 'y');
|
||||
});
|
||||
readFilesInto(doc, 'pc', engine);
|
||||
changeFiles(doc, 'pc', 'me', (fileSystem) => {
|
||||
fileSystem.deleteItem('/tief/x');
|
||||
fileSystem.createDirectory('/tief/x');
|
||||
});
|
||||
readFilesInto(doc, 'pc', engine);
|
||||
expect(engine.entriesInCreationOrder('/tief').map((entry) => entry.path)).toEqual(['/tief/x', '/tief/y.txt']);
|
||||
});
|
||||
|
||||
it('leave out what the file system refuses, as the editor draws it', () => {
|
||||
const { doc, files } = setUp();
|
||||
changeFiles(doc, 'pc', 'me', (fileSystem) => fileSystem.writeTextFile('/a.txt', 'a'));
|
||||
// Two people gave a name at once in different case: one file, the first name, the later text (as fileSystemOf reads it); one in a folder not there is left out.
|
||||
doc.transact(() => {
|
||||
const entry = new Y.Map<unknown>();
|
||||
entry.set('order', 5);
|
||||
entry.set('kind', 'text');
|
||||
entry.set('text', new Y.Text('A'));
|
||||
files()!.set('/A.TXT', entry);
|
||||
const lost = new Y.Map<unknown>();
|
||||
lost.set('order', 6);
|
||||
lost.set('kind', 'text');
|
||||
lost.set('text', new Y.Text('lost'));
|
||||
files()!.set('/fehlt/x.txt', lost);
|
||||
});
|
||||
const engine = new VirtualFileSystem();
|
||||
expect(readFilesInto(doc, 'pc', engine)).toBe(true);
|
||||
expect(paths(engine)).toEqual([
|
||||
['/', 'directory'],
|
||||
['/a.txt', 'A'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user