mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 21:19:01 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			291 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { ClassicEditor, Essentials, Paragraph, Heading, CodeBlockEditing, _setModelData as setModelData, _getModelData as getModelData, _getViewData as getViewData } from 'ckeditor5';
 | 
						|
import MermaidEditing from '../src/mermaidediting.js';
 | 
						|
import { afterEach, beforeEach, describe, it } from 'vitest';
 | 
						|
import { expect } from 'vitest';
 | 
						|
import { vi } from 'vitest';
 | 
						|
 | 
						|
/* global document */
 | 
						|
 | 
						|
describe( 'MermaidEditing', () => {
 | 
						|
	it( 'should be named', () => {
 | 
						|
		expect( MermaidEditing.pluginName ).to.equal( 'MermaidEditing' );
 | 
						|
	} );
 | 
						|
 | 
						|
	describe( 'conversion', () => {
 | 
						|
		let domElement, editor, model;
 | 
						|
 | 
						|
		beforeEach( async () => {
 | 
						|
			domElement = document.createElement( 'div' );
 | 
						|
			document.body.appendChild( domElement );
 | 
						|
 | 
						|
			editor = await ClassicEditor.create( domElement, {
 | 
						|
				licenseKey: "GPL",
 | 
						|
				plugins: [
 | 
						|
					Paragraph,
 | 
						|
					Heading,
 | 
						|
					Essentials,
 | 
						|
					CodeBlockEditing,
 | 
						|
					MermaidEditing
 | 
						|
				]
 | 
						|
			} );
 | 
						|
 | 
						|
			model = editor.model;
 | 
						|
		} );
 | 
						|
 | 
						|
		afterEach( () => {
 | 
						|
			domElement.remove();
 | 
						|
			return editor.destroy();
 | 
						|
		} );
 | 
						|
 | 
						|
		describe( 'conversion', () => {
 | 
						|
			describe( 'upcast', () => {
 | 
						|
				it( 'works correctly', () => {
 | 
						|
					editor.setData(
 | 
						|
						'<pre spellcheck="false">' +
 | 
						|
							'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
						|
						'</pre>'
 | 
						|
					);
 | 
						|
 | 
						|
					expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
 | 
						|
						'<mermaid displayMode="split" source="flowchart TB\nA --> B\nB --> C">' +
 | 
						|
						'</mermaid>'
 | 
						|
					);
 | 
						|
				} );
 | 
						|
 | 
						|
				it( 'works correctly when empty', () => {
 | 
						|
					editor.setData(
 | 
						|
						'<pre spellcheck="false">' +
 | 
						|
							'<code class="language-mermaid"></code>' +
 | 
						|
						'</pre>'
 | 
						|
					);
 | 
						|
 | 
						|
					expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
 | 
						|
						'<mermaid displayMode="split" source=""></mermaid>'
 | 
						|
					);
 | 
						|
				} );
 | 
						|
			} );
 | 
						|
 | 
						|
			describe( 'data downcast', () => {
 | 
						|
				it( 'works correctly', () => {
 | 
						|
					// Using editor.setData() instead of setModelData helper because of #11365.
 | 
						|
					editor.setData(
 | 
						|
						'<pre spellcheck="false">' +
 | 
						|
							'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
						|
						'</pre>'
 | 
						|
					);
 | 
						|
 | 
						|
					expect( editor.getData() ).to.equal(
 | 
						|
						'<pre spellcheck="false">' +
 | 
						|
							'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
						|
						'</pre>'
 | 
						|
					);
 | 
						|
				} );
 | 
						|
 | 
						|
				it( 'works correctly when empty ', () => {
 | 
						|
					// Using editor.setData() instead of setModelData helper because of #11365.
 | 
						|
					editor.setData(
 | 
						|
						'<pre spellcheck="false">' +
 | 
						|
							'<code class="language-mermaid"></code>' +
 | 
						|
						'</pre>'
 | 
						|
					);
 | 
						|
 | 
						|
					expect( editor.getData() ).to.equal(
 | 
						|
						'<pre spellcheck="false">' +
 | 
						|
							'<code class="language-mermaid"></code>' +
 | 
						|
						'</pre>'
 | 
						|
					);
 | 
						|
				} );
 | 
						|
			} );
 | 
						|
 | 
						|
			describe( 'editing downcast', () => {
 | 
						|
				it( 'works correctly without displayMode attribute', () => {
 | 
						|
					// Using editor.setData() instead of setModelData helper because of #11365.
 | 
						|
					editor.setData(
 | 
						|
						'<pre spellcheck="false">' +
 | 
						|
							'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
						|
						'</pre>'
 | 
						|
					);
 | 
						|
 | 
						|
					expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
 | 
						|
						'<div class="ck-mermaid__split-mode ck-mermaid__wrapper ck-widget ck-widget_selected' +
 | 
						|
							' ck-widget_with-selection-handle" contenteditable="false">' +
 | 
						|
							'<div class="ck ck-widget__selection-handle"></div>' +
 | 
						|
							// New lines replaced with space, same issue in getViewData as in #11365.
 | 
						|
							'<textarea class="ck-mermaid__editing-view" data-cke-ignore-events="true"' +
 | 
						|
								' placeholder="Insert mermaid source code"></textarea>' +
 | 
						|
							'<div class="ck-mermaid__preview"></div>' +
 | 
						|
							'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
 | 
						|
						'</div>'
 | 
						|
					);
 | 
						|
				} );
 | 
						|
 | 
						|
				it( 'works correctly with displayMode attribute', () => {
 | 
						|
					setModelData( editor.model,
 | 
						|
						'<mermaid source="foo" displayMode="preview"></mermaid>'
 | 
						|
					);
 | 
						|
 | 
						|
					expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
 | 
						|
						'<div class="ck-mermaid__preview-mode ck-mermaid__wrapper ck-widget ck-widget_selected ' +
 | 
						|
							'ck-widget_with-selection-handle" contenteditable="false">' +
 | 
						|
							'<div class="ck ck-widget__selection-handle"></div>' +
 | 
						|
							'<textarea class="ck-mermaid__editing-view" data-cke-ignore-events="true"' +
 | 
						|
								' placeholder="Insert mermaid source code"></textarea>' +
 | 
						|
							'<div class="ck-mermaid__preview"></div>' +
 | 
						|
							'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
 | 
						|
						'</div>'
 | 
						|
					);
 | 
						|
				} );
 | 
						|
 | 
						|
				it( 'works correctly with empty source', () => {
 | 
						|
					setModelData( editor.model,
 | 
						|
						'<mermaid source="" displayMode="preview"></mermaid>'
 | 
						|
					);
 | 
						|
 | 
						|
					expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal(
 | 
						|
						'<div class="ck-mermaid__preview-mode ck-mermaid__wrapper ck-widget ck-widget_selected ' +
 | 
						|
							'ck-widget_with-selection-handle" contenteditable="false">' +
 | 
						|
							'<div class="ck ck-widget__selection-handle"></div>' +
 | 
						|
							'<textarea class="ck-mermaid__editing-view" data-cke-ignore-events="true"' +
 | 
						|
								' placeholder="Insert mermaid source code"></textarea>' +
 | 
						|
							'<div class="ck-mermaid__preview"></div>' +
 | 
						|
							'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
 | 
						|
						'</div>'
 | 
						|
					);
 | 
						|
				} );
 | 
						|
 | 
						|
				describe( 'textarea value', () => {
 | 
						|
					let domTextarea = null;
 | 
						|
 | 
						|
					beforeEach( () => {
 | 
						|
						// Using editor.setData() instead of setModelData helper because of #11365.
 | 
						|
						editor.setData(
 | 
						|
							'<pre spellcheck="false">' +
 | 
						|
							'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
						|
							'</pre>'
 | 
						|
						);
 | 
						|
 | 
						|
						const textareaView = editor.editing.view.document.getRoot().getChild( 0 ).getChild( 1 );
 | 
						|
						domTextarea = editor.editing.view.domConverter.viewToDom( textareaView );
 | 
						|
					} );
 | 
						|
 | 
						|
					it( 'is properly set during the initial conversion', () => {
 | 
						|
						expect( domTextarea.value ).to.equal( 'flowchart TB\nA --> B\nB --> C' );
 | 
						|
					} );
 | 
						|
 | 
						|
					it( 'is properly updated after model\'s attribute change', () => {
 | 
						|
						const { model } = editor;
 | 
						|
 | 
						|
						const mermaidModel = model.document.getRoot().getChild( 0 );
 | 
						|
 | 
						|
						model.change( writer => {
 | 
						|
							writer.setAttribute( 'source', 'abc', mermaidModel );
 | 
						|
						} );
 | 
						|
 | 
						|
						expect( domTextarea.value ).to.equal( 'abc' );
 | 
						|
					} );
 | 
						|
 | 
						|
					it( 'doesn\'t loop if model attribute changes to the same value', () => {
 | 
						|
						const { model } = editor;
 | 
						|
 | 
						|
						const mermaidModel = model.document.getRoot().getChild( 0 );
 | 
						|
 | 
						|
						model.change( writer => {
 | 
						|
							writer.setAttribute( 'source', 'flowchart TB\nA --> B\nB --> C', mermaidModel );
 | 
						|
						} );
 | 
						|
 | 
						|
						expect( domTextarea.value ).to.equal( 'flowchart TB\nA --> B\nB --> C' );
 | 
						|
					} );
 | 
						|
				} );
 | 
						|
 | 
						|
				describe( 'preview div', () => {
 | 
						|
					let domPreviewContainer, renderMermaidStub;
 | 
						|
 | 
						|
					beforeEach( () => {
 | 
						|
						// Using editor.setData() instead of setModelData helper because of #11365.
 | 
						|
						editor.setData(
 | 
						|
							'<pre spellcheck="false">' +
 | 
						|
							'<code class="language-mermaid">flowchart TB\nA --> B\nB --> C</code>' +
 | 
						|
							'</pre>'
 | 
						|
						);
 | 
						|
 | 
						|
						const previewContainerView = editor.editing.view.document.getRoot().getChild( 0 ).getChild( 2 );
 | 
						|
						domPreviewContainer = editor.editing.view.domConverter.viewToDom( previewContainerView );
 | 
						|
 | 
						|
						renderMermaidStub = vi.spyOn( editor.plugins.get( 'MermaidEditing' ), '_renderMermaid' );
 | 
						|
					} );
 | 
						|
 | 
						|
					afterEach( () => {
 | 
						|
						vi.clearAllMocks();
 | 
						|
					} );
 | 
						|
 | 
						|
					it( 'has proper inner text set during the initial conversion', () => {
 | 
						|
						expect( domPreviewContainer.textContent ).to.equal( 'flowchart TB\nA --> B\nB --> C' );
 | 
						|
					} );
 | 
						|
 | 
						|
					it( 'has proper inner text set after a model\'s attribute change', () => {
 | 
						|
						const { model } = editor;
 | 
						|
 | 
						|
						const mermaidModel = model.document.getRoot().getChild( 0 );
 | 
						|
 | 
						|
						model.change( writer => {
 | 
						|
							writer.setAttribute( 'source', 'abc', mermaidModel );
 | 
						|
						} );
 | 
						|
 | 
						|
						expect( domPreviewContainer.textContent ).to.equal( 'abc' );
 | 
						|
					} );
 | 
						|
 | 
						|
					it( 'calls mermaid render function after a model\'s attribute change', () => {
 | 
						|
						const { model } = editor;
 | 
						|
 | 
						|
						const mermaidModel = model.document.getRoot().getChild( 0 );
 | 
						|
 | 
						|
						model.change( writer => {
 | 
						|
							writer.setAttribute( 'source', 'abc', mermaidModel );
 | 
						|
						} );
 | 
						|
 | 
						|
						expect(renderMermaidStub).toBeCalledWith(domPreviewContainer);
 | 
						|
					} );
 | 
						|
				} );
 | 
						|
			} );
 | 
						|
 | 
						|
			it( 'adds a editing pipeline converter that has a precedence over code block', () => {
 | 
						|
				setModelData( editor.model, '<mermaid source="foo"></mermaid>' );
 | 
						|
 | 
						|
				const firstViewChild = editor.editing.view.document.getRoot().getChild( 0 );
 | 
						|
 | 
						|
				expect( firstViewChild.name ).to.equal( 'div' );
 | 
						|
				expect( firstViewChild.hasClass( 'ck-mermaid__wrapper' ), 'has ck-mermaid__wrapper class' ).to.be.true;
 | 
						|
			} );
 | 
						|
 | 
						|
			it( 'does not convert code blocks other than mermaid language', () => {
 | 
						|
				setModelData( editor.model, '<codeBlock language="javascript">foo</codeBlock>' );
 | 
						|
 | 
						|
				const firstViewChild = editor.editing.view.document.getRoot().getChild( 0 );
 | 
						|
 | 
						|
				expect( firstViewChild.name ).not.to.equal( 'div' );
 | 
						|
				expect( firstViewChild.hasClass( 'ck-mermaid__wrapper' ), 'has ck-mermaid__wrapper class' ).to.be.false;
 | 
						|
			} );
 | 
						|
 | 
						|
			it( 'adds a preview element', () => {
 | 
						|
				setModelData( editor.model, '<mermaid source="foo"></mermaid>' );
 | 
						|
 | 
						|
				const widgetChildren = [ ...editor.editing.view.document.getRoot().getChild( 0 ).getChildren() ];
 | 
						|
				const previewView = widgetChildren.filter( item => item.name === 'div' && item.hasClass( 'ck-mermaid__preview' ) );
 | 
						|
 | 
						|
				expect( previewView.length ).to.equal( 1 );
 | 
						|
			} );
 | 
						|
 | 
						|
			it( 'adds an editing element', () => {
 | 
						|
				setModelData( editor.model, '<mermaid source="foo"></mermaid>' );
 | 
						|
 | 
						|
				const widgetChildren = [ ...editor.editing.view.document.getRoot().getChild( 0 ).getChildren() ];
 | 
						|
				const previewView = widgetChildren.filter(
 | 
						|
					item => item.name === 'textarea' && item.hasClass( 'ck-mermaid__editing-view' )
 | 
						|
				);
 | 
						|
 | 
						|
				expect( previewView.length ).to.equal( 1 );
 | 
						|
			} );
 | 
						|
		} );
 | 
						|
	} );
 | 
						|
} );
 |