Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | 2x 2x 20x 2x 20x 20x 20x 7709x 7709x 7709x 7709x 7709x 5369x 5369x 2x 1953x 1953x 4297x 4297x 5369x 5369x 5369x 2344x 1953x 1953x 2x 2x 6441x 2x 156x 156x 156x 452x 156x 2x 2x 2x 10x 10x 10x 10x 268x 10x 20x 20x 20x 20x 20x 1917x 2953x 1953x 5369x 1953x 1953x 1953x 1953x 1953x 1953x 1268x 1268x 1268x 1268x 1268x 1268x 1268x 1268x 1268x 156x 156x 197x 266x 220x 220x 221x 221x 208x 208x 208x 697x 1268x | import {createEditor, Editor, Element, Transforms} from 'slate';
import type {
SlateDocument,
SlateDescendantNode,
SlateElementNode,
SlateTextNode,
SlateRange,
SlatePoint,
} from '../../../types';
import {type SlateTrace, SlateTraceRecorder} from './traces';
export type Rng = () => number;
export const createEmptyDocument = (): SlateDocument => [{type: 'paragraph', children: [{text: ''}]}];
export const createRng = (seed: number): Rng => {
let m_w = seed;
let m_z = 987654321 ^ seed;
return () => {
m_z = (36969 * (m_z & 65535) + (m_z >> 16)) | 0;
m_w = (18000 * (m_w & 65535) + (m_w >> 16)) | 0;
let result = ((m_z << 16) + (m_w & 65535)) >>> 0;
result /= 4294967296;
return result;
};
};
class FlatNode {
constructor(
public readonly path: number[],
public readonly node: SlateDescendantNode,
) {}
}
export const flatten = (doc: SlateDocument): FlatNode[] => {
const nodes: FlatNode[] = [];
const walk = (elements: SlateDescendantNode[], path: number[] = []) => {
const length = elements.length;
for (let i = 0; i < length; i++) {
const node = elements[i];
nodes.push(new FlatNode([...path, i], node));
if (!('text' in node) && (node as SlateElementNode).children)
walk((node as SlateElementNode).children, [...path, i]);
}
};
walk(doc);
return nodes;
};
export const pickRandomNode = (rng: Rng, doc: SlateDocument): FlatNode | undefined => {
const nodes = flatten(doc);
return nodes[Math.floor(rng() * nodes.length)];
};
/** Pick random integer between min (inclusive) and max (inclusive). */
const randomInt = (rng: Rng, min: number, max: number): number => {
return Math.floor(rng() * (max - min + 1)) + min;
};
const randomText = (rng: Rng, length: number): string => {
const chars = 'abcdefghijklmnopqrstuvwxyz ';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(randomInt(rng, 0, chars.length - 1));
}
return result;
};
const blockElementTypes: string[] = ['paragraph', 'h1', 'list-item', 'bulleted-list', 'numbered-list'];
const inlineFormattingMarks: string[] = ['bold', 'italic', 'underline', 'code'];
export class SlateFuzzer {
public static genTrace(
seed: number = Math.floor(Math.random() * 1000000),
minOps: number = 10,
maxOps: number = 50,
): SlateTrace {
const fuzzer = new SlateFuzzer(seed);
const recorder = new SlateTraceRecorder(fuzzer.editor);
const totalOps = randomInt(fuzzer.rng, minOps, maxOps);
for (let i = 0; i < totalOps; i++) {
fuzzer.applyRandomHighLevelOperation();
}
return recorder.getTrace();
}
public readonly rng: Rng;
public readonly editor: Editor;
constructor(
public readonly seed: number = 123456789,
editor?: Editor,
) {
this.rng = createRng(seed);
Iif (editor) {
this.editor = editor;
} else {
this.editor = createEditor();
(this.editor.children as unknown) = createEmptyDocument();
}
}
private pick<T>(selection: T[]): T {
return selection[randomInt(this.rng, 0, selection.length - 1)];
}
public getDocument(): SlateDocument {
return this.editor.children as SlateDocument;
}
public generateRandomPoint(): SlatePoint | undefined {
const doc = this.getDocument();
const flatNodes = flatten(doc).filter((n) => 'text' in n.node);
const index = randomInt(this.rng, 0, flatNodes.length - 1);
const node = flatNodes[index] as FlatNode | undefined;
Iif (!node) return;
const maxOffset =
'text' in node.node
? (node.node as SlateTextNode).text.length
: 'children' in node.node
? (node.node as SlateElementNode).children.length
: 0;
const offset = randomInt(this.rng, 0, maxOffset);
return {path: node.path, offset};
}
public generateRandomRange(): SlateRange | undefined {
const anchor = this.generateRandomPoint();
Iif (!anchor) return;
const focus = this.rng() > 0.5 ? (this.generateRandomPoint() ?? anchor) : anchor;
return {anchor, focus};
}
public setRandomSelection(): void {
const range = this.generateRandomRange();
Iif (!range) return;
Transforms.select(this.editor, range);
}
public applyRandomHighLevelOperation() {
this.setRandomSelection();
const op = this.pick([
() => {
const text = randomText(this.rng, randomInt(this.rng, 1, 5));
// console.log('insertText', text, this.editor.selection);
this.editor.insertText(text);
},
() => {
// console.log('delete', this.editor.selection);
this.editor.delete();
},
() => {
// console.log('splitNodes');
this.editor.splitNodes();
},
() => {
const mark = this.pick(inlineFormattingMarks);
// console.log('addMark', mark);
this.editor.addMark(mark, true);
},
() => {
const mark = this.pick(inlineFormattingMarks);
// console.log('removeMark', mark);
this.editor.removeMark(mark);
},
() => {
const type = this.pick(blockElementTypes);
// console.log('setNodes - block type', type);
const editor = this.editor;
Transforms.setNodes(this.editor, {type} as any, {
// Ensure we only target block-level elements, not text nodes
match: (n) => Element.isElement(n) && Editor.isBlock(editor, n),
});
},
]);
op();
}
}
|