import Quill from 'quill' import { mountFlowText } from '../../src/renderer/src/magic-canvas/canvas-flow.mjs' import { createRoot } from 'react-dom/client' import { mountCanvasNote } from '../../src/renderer/src/magic-canvas/canvas-note.mjs' import { MagicCanvasThumbnail } from '../../src/renderer/src/MagicCanvasThumbnail' import { fromCoreContent, toCoreContent } from '../../src/renderer/src/magic-canvas/model' import '../../src/renderer/src/i18n' import 'quill/dist/quill.snow.css' import '../../src/renderer/src/styles.css' import '../../src/renderer/src/magic-canvas.css' async function canvasRegression() { const errors: string[] = [] const host = document.createElement('div') host.className = 'magic-canvas-editor' document.body.append(host) let editor = mountCanvasNote(host, { version: 2, pages: [{ id: 'first', width: 794, height: 1123, background: { type: 'template', template: 'blank' }, objects: [{ type: 'Path', canvasKind: 'pen', path: [['M', 20, 20], ['L', 150, 80]], stroke: '#ff0000', strokeWidth: 4, fill: null }] }], flow: { version: 1, ops: [{ insert: 'Flow line\n'.repeat(50) }] } }, { onError: (error) => errors.push(error) }) const initial = await editor.flush() const ink = async () => { await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))) const canvas = host.querySelector('.lower-canvas')! const pixels = canvas.getContext('2d')!.getImageData(0, 0, canvas.width, canvas.height).data let count = 0 for (let i = 3; i < pixels.length; i += 4) if (pixels[i]) count++ return count } const visibleInkBefore = await ink() host.querySelector('[data-tool="flow-text"]')!.click() host.querySelector('.canvas-note-next-page')!.click() await editor.flush() const beforeShrink = host.querySelector('.canvas-note-page-counter')!.textContent const quill = Quill.find(host.querySelector('.canvas-flow-quill')!) as Quill quill.setText('Short flow\n', 'user') const saved = JSON.parse(JSON.stringify(await editor.flush())) const afterShrink = host.querySelector('.canvas-note-page-counter')!.textContent const visibleInkAfter = await ink() const flowTransform = host.querySelector('.canvas-flow-surface')!.style.transform await editor.destroy() editor = mountCanvasNote(host, saved, { onError: (error) => errors.push(error) }) const reopened = await editor.flush() const reopenedInk = await ink() const styles = (surface: Element) => { const style = getComputedStyle(surface) return { fontSize: style.fontSize, lineHeight: style.lineHeight, columnFill: style.columnFill, padding: style.padding } } const editorStyles = styles(host.querySelector('.canvas-flow-surface')!) const [expectedThumbnail] = await editor.capturePages({ firstPageOnly: true, thumbnailWidth: 240 }) await editor.destroy() const thumbnailHost = document.createElement('div') document.body.append(thumbnailHost) let thumbnailStyles: ReturnType | undefined const observer = new MutationObserver(() => { const surface = document.querySelector('.magic-note-thumbnail-renderer .canvas-flow-surface') if (surface) thumbnailStyles = styles(surface) }) observer.observe(document.body, { childList: true, subtree: true }) const root = createRoot(thumbnailHost) root.render() for (let i = 0; i < 400 && !thumbnailHost.querySelector('img'); i++) await new Promise(resolve => setTimeout(resolve, 25)) const thumbnailMatchesEditor = thumbnailHost.querySelector('img')?.src === expectedThumbnail.dataUrl root.unmount() observer.disconnect() thumbnailHost.remove() const imageImports = [] const pixel = document.createElement('canvas') pixel.width = pixel.height = 1 const png = await new Promise((resolve) => pixel.toBlob(blob => resolve(blob!), 'image/png')) for (const size of [2 * 1024 * 1024 + 1, 20 * 1024 * 1024, 20 * 1024 * 1024 + 1]) { const importErrors: string[] = [] editor = mountCanvasNote(host, undefined, { onError: error => importErrors.push(error) }) await editor.flush() const input = host.querySelector('.canvas-note-image-input')! const transfer = new DataTransfer() transfer.items.add(new File([png, new Uint8Array(size - png.size)], 'boundary.png', { type: 'image/png' })) input.files = transfer.files input.dispatchEvent(new Event('change')) const imported = await editor.flush() const persisted = fromCoreContent(imported, []) await editor.destroy() editor = mountCanvasNote(host, toCoreContent(persisted), { onError: error => importErrors.push(error) }) const restored = await editor.flush() imageImports.push({ size, imported: imported.pages[0].objects.length, restored: restored.pages[0].objects.length, errors: importErrors }) await editor.destroy() } host.remove() const layer = document.createElement('div') const toolbar = document.createElement('div') document.body.append(layer, toolbar) let flow = mountFlowText(layer, toolbar, { version: 1, ops: [ { insert: 'Task' }, { insert: '\n', attributes: { list: 'unchecked' } }, { insert: '\n' } ] }) let flowQuill = Quill.find(layer.firstElementChild!) as Quill const checklistBefore = flow.content() flowQuill.setSelection(5, 0, 'api') flow.insertPageBreak('page-2') flow.removePageBreak('page-2') const checklistAfter = await flow.flush() flow.destroy() const overflowErrors: string[] = [] flow = mountFlowText(layer, toolbar, { version: 1, ops: [{ insert: 'x'.repeat(19995) + '\n' }] }, { onError: (error: string) => overflowErrors.push(error) }) flowQuill = Quill.find(layer.firstElementChild!) as Quill flowQuill.setSelection(19995, 0, 'api') flowQuill.insertText(19995, 'HELLO', 'user') flowQuill.setSelection(20000, 0, 'api') flowQuill.history.cutoff() flowQuill.insertText(20000, '!', 'user') const selectionAfterRejection = flowQuill.getSelection() const acceptedLength = flow.text().length flow.undo() const undoneLength = flow.text().length flowQuill.setSelection(3, 4, 'api') flowQuill.insertText(3, 'REJECTED', 'user') const selectionWithRedo = flowQuill.getSelection() const retainedRedo = flow.canRedo() flow.redo() const redoneLength = flow.text().length flow.destroy() layer.remove() toolbar.remove() return { errors, initialPages: initial.pages.length, initialObjects: initial.pages[0].objects, beforeShrink, afterShrink, savedPages: saved.pages.length, savedObjects: saved.pages[0].objects, reopenedObjects: reopened.pages[0].objects, visibleInkBefore, visibleInkAfter, reopenedInk, flowTransform, editorStyles, thumbnailStyles, thumbnailMatchesEditor, imageImports, checklistBefore, checklistAfter, overflowErrors, selectionAfterRejection, selectionWithRedo, acceptedLength, undoneLength, retainedRedo, redoneLength } } Object.assign(window, { canvasRegression })