// import { slateType } from '../src/SlateType'; import { Operation, Transforms, Path, Node, Text, Ancestor } from 'slate'; import * as fuzzer from 'ot-fuzzer'; import * as _ from 'lodash'; const BLOCKS = ['block1', 'block2', 'block3', 'block4', 'block5']; export const getAllTextPaths = (node: Node): Path[] => { let array: Path[] = []; for (let [, p] of Node.texts(node)) { array.push(p); } return array; }; interface TextWithPath extends Text { path: Path; } export const getRandomLeafWithPath = (snapshot: Node): TextWithPath | null => { const paths = getAllTextPaths(snapshot); const path = paths[fuzzer.randomInt(paths.length)]; if (!path || !path.length) { return null; } const t = Node.leaf(snapshot, path); return { ...t, path }; }; export const getRandomPathFrom = (snapshot: Node): Path => { if (Text.isText(snapshot) || snapshot.children.length === 0) { return []; } const path: Path = []; // let currentNode = snapshot; while (1) { // stop when you get to a leaf if (Text.isText(snapshot)) { return path; } if (snapshot.children.length === 0) { return path; } if (fuzzer.randomInt(3) === 0 && path.length > 0) { return path; } // continue const index = <number>fuzzer.randomInt(snapshot.children.length); path.push(index); snapshot = snapshot.children[index]; } return path; }; export const getRandomPathTo = (snapshot: Node): Path => { const path: Path = []; // let currentNode = snapshot; while (1) { // stop when you get to a leaf if (Text.isText(snapshot)) { return path; } if (snapshot.children.length === 0) { return [...path, 0]; } // randomly stop at the next level if (fuzzer.randomInt(3) === 0) { const index = <number>fuzzer.randomInt(snapshot.children.length + 1); return [...path, index]; } // continue const index = <number>fuzzer.randomInt(snapshot.children.length); path.push(index); snapshot = snapshot.children[index]; } return path; }; export const generateAndApplyRandomOp = function (snapshot) { const result = _.cloneDeep(snapshot); let op: Operation | null = null; while (!op) { let index = fuzzer.randomInt(genRandOp.length); op = genRandOp[index](snapshot); } Transforms.transform(result, op); return [[op], result]; }; // insert_text: ['path', 'offset', 'text', 'marks', 'data'], export const generateRandomInsertTextOp = (snapshot): Operation | null => { const randomLeaf = getRandomLeafWithPath(snapshot); return randomLeaf ? { type: 'insert_text', path: randomLeaf.path, offset: fuzzer.randomInt(randomLeaf.text.length), text: fuzzer.randomWord(), } : null; }; // remove_text: ['path', 'offset', 'text', 'marks', 'data'], export const generateRandomRemoveTextOp = (snapshot): Operation | null => { const randomLeaf = getRandomLeafWithPath(snapshot); if (!randomLeaf) return null; const offset = fuzzer.randomInt(randomLeaf.text.length); const textLength = fuzzer.randomInt(randomLeaf.text.length - offset); return { type: 'remove_text', path: randomLeaf.path, offset, text: randomLeaf.text.slice(offset, offset + textLength), }; }; // insert_node: ['path', 'node', 'data'] export const generateRandomInsertNodeOp = (snapshot): Operation => { const randomPath = getRandomPathTo(snapshot); const parent = <Ancestor>Node.get(snapshot, Path.parent(randomPath)); let node; if (parent.children[0] && Text.isText(parent.children[0])) { node = { text: fuzzer.randomWord() }; } else if (!parent.children[0] && fuzzer.randomInt(3) === 0) { node = { text: fuzzer.randomWord() }; } else if (fuzzer.randomInt(2) === 0) { node = { type: BLOCKS[fuzzer.randomInt(BLOCKS.length)], children: [{ text: fuzzer.randomWord() }, { text: fuzzer.randomWord() }], }; } else { node = { type: BLOCKS[fuzzer.randomInt(BLOCKS.length)], children: [ { type: BLOCKS[fuzzer.randomInt(BLOCKS.length)], children: [ { text: fuzzer.randomWord() }, { text: fuzzer.randomWord() }, ], }, ], }; } return { type: 'insert_node', path: randomPath, node, }; }; export const generateRandomRemoveNodeOp = (snapshot): Operation | null => { const randomPath = getRandomPathFrom(snapshot); return randomPath.length ? { type: 'remove_node', path: randomPath, node: Node.get(snapshot, randomPath), } : null; }; export const generateRandomSplitNodeOp = (snapshot): Operation | null => { const randomPath = getRandomPathFrom(snapshot); const node = Node.get(snapshot, randomPath); const position = Text.isText(node) ? <number>fuzzer.randomInt(node.text.length + 1) : <number>fuzzer.randomInt(node.children.length + 1); return randomPath.length ? { type: 'split_node', path: randomPath, position, target: null, properties: {}, } : null; }; export const generateRandomMergeNodeOp = (snapshot): Operation | null => { const randomPath = getRandomPathFrom(snapshot); if (randomPath.length == 0 || randomPath[randomPath.length - 1] == 0) { return null; } const prev = Node.get(snapshot, Path.previous(randomPath)); const node = Node.get(snapshot, randomPath); const properties = {}; // Object.keys(prev).forEach((key) => { // if (key !== 'text' && key !== 'children') { // properties[key] = null; // } // }); // Object.keys(node).forEach((key) => { // if (key !== 'text' && key !== 'children') { // properties[key] = node[key]; // } // }); if (Text.isText(prev) && Text.isText(node)) { return { type: 'merge_node', path: randomPath, position: prev.text.length, target: null, properties, }; } if (!Text.isText(prev) && !Text.isText(node)) { return { type: 'merge_node', path: randomPath, position: prev.children.length, target: null, properties, }; } return null; }; export const generateRandomMoveNodeOp = (snapshot): Operation | null => { let count = 0; while (count < 10) { count++; const path = getRandomPathFrom(snapshot); const newPath = getRandomPathTo(snapshot); if (Path.isSibling(path, newPath)) { const parent = <Ancestor>Node.get(snapshot, Path.parent(newPath)); if (newPath[newPath.length - 1] == parent.children.length) { newPath[newPath.length - 1]--; } } if (!Path.isAncestor(path, newPath)) { return { type: 'move_node', path, newPath, }; } } return null; }; const KEYS = ['key1', 'key2', 'key3', 'key4', 'key5']; const VALUES = [null, true, false, 1, 'alpha']; export const generateRandomSetNodeOp = (snapshot): Operation | null => { const path = getRandomPathFrom(snapshot); if (path.length === 0) return null; const newProperties = {}; KEYS.forEach((key) => { if (fuzzer.randomInt(2) === 0) { newProperties[key] = VALUES[fuzzer.randomInt(VALUES.length)]; } }); return { type: 'set_node', path, properties: {}, newProperties, }; }; const genRandOp = [ generateRandomInsertTextOp, generateRandomRemoveTextOp, generateRandomInsertNodeOp, generateRandomRemoveNodeOp, generateRandomSplitNodeOp, generateRandomMergeNodeOp, generateRandomMoveNodeOp, generateRandomSetNodeOp, ];