| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | /* eslint-disable @typescript-eslint/no-non-null-assertion */ | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | /* globals document, Event  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import MathUI from '../src/mathui'; | 
					
						
							|  |  |  | import MainFormView from '../src/ui/mainformview'; | 
					
						
							| 
									
										
										
										
											2025-05-09 22:28:35 +03:00
										 |  |  | import { ClassicEditor, ContextualBalloon, ButtonView, View, Paragraph, ClickObserver, keyCodes, _setModelData as setModelData } from 'ckeditor5'; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | import { describe, beforeEach, it, afterEach, vi, expect, MockInstance, expectTypeOf } from "vitest"; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe( 'MathUI', () => { | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 	let editorElement: HTMLDivElement; | 
					
						
							|  |  |  | 	let editor: ClassicEditor; | 
					
						
							|  |  |  | 	let mathUIFeature: MathUI; | 
					
						
							|  |  |  | 	let mathButton: ButtonView; | 
					
						
							|  |  |  | 	let balloon: ContextualBalloon; | 
					
						
							|  |  |  | 	let formView: MainFormView | null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	beforeEach( async () => { | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 		editorElement = document.createElement( 'div' ); | 
					
						
							|  |  |  | 		document.body.appendChild( editorElement ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-06 19:35:50 +01:00
										 |  |  | 		return ClassicEditor | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			.create( editorElement, { | 
					
						
							| 
									
										
										
										
											2019-10-11 19:22:03 +03:00
										 |  |  | 				plugins: [ MathUI, Paragraph ], | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				math: { | 
					
						
							| 
									
										
										
										
											2019-10-11 19:22:03 +03:00
										 |  |  | 					engine: ( equation, element, display ) => { | 
					
						
							|  |  |  | 						if ( display ) { | 
					
						
							|  |  |  | 							element.innerHTML = '\\[' + equation + '\\]'; | 
					
						
							|  |  |  | 						} else { | 
					
						
							|  |  |  | 							element.innerHTML = '\\(' + equation + '\\)'; | 
					
						
							|  |  |  | 						} | 
					
						
							| 
									
										
										
										
											2025-05-09 22:16:12 +03:00
										 |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				licenseKey: "GPL" | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ) | 
					
						
							|  |  |  | 			.then( newEditor => { | 
					
						
							|  |  |  | 				editor = newEditor; | 
					
						
							|  |  |  | 				mathUIFeature = editor.plugins.get( MathUI ); | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				mathButton = editor.ui.componentFactory.create( 'math' ) as ButtonView; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				balloon = editor.plugins.get( ContextualBalloon ); | 
					
						
							|  |  |  | 				formView = mathUIFeature.formView; | 
					
						
							| 
									
										
										
										
											2019-10-11 19:22:03 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// There is no point to execute BalloonPanelView attachTo and pin methods so lets override it.
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				vi.spyOn( balloon.view, 'attachTo' ).mockReturnValue( false ); | 
					
						
							|  |  |  | 				vi.spyOn( balloon.view, 'pin' ).mockReturnValue(); | 
					
						
							| 
									
										
										
										
											2019-10-11 19:22:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				formView?.render(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 	} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	afterEach( () => { | 
					
						
							|  |  |  | 		editorElement.remove(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return editor.destroy(); | 
					
						
							|  |  |  | 	} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	describe( 'init', () => { | 
					
						
							|  |  |  | 		it( 'should register click observer', () => { | 
					
						
							|  |  |  | 			expect( editor.editing.view.getObserver( ClickObserver ) ).to.be.instanceOf( ClickObserver ); | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it( 'should create #formView', () => { | 
					
						
							|  |  |  | 			expect( formView ).to.be.instanceOf( MainFormView ); | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		describe( 'math toolbar button', () => { | 
					
						
							|  |  |  | 			it( 'should be registered', () => { | 
					
						
							|  |  |  | 				expect( mathButton ).to.be.instanceOf( ButtonView ); | 
					
						
							|  |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should be toggleable button', () => { | 
					
						
							|  |  |  | 				expect( mathButton.isToggleable ).to.be.true; | 
					
						
							|  |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should be bound to the math command', () => { | 
					
						
							|  |  |  | 				const command = editor.commands.get( 'math' ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				if ( !command ) { | 
					
						
							|  |  |  | 					throw new Error( 'Missing math command' ); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				command.isEnabled = true; | 
					
						
							|  |  |  | 				command.value = '\\sqrt{x^2}'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				expect( mathButton.isEnabled ).to.be.true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				command.isEnabled = false; | 
					
						
							|  |  |  | 				command.value = undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				expect( mathButton.isEnabled ).to.be.false; | 
					
						
							|  |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should call #_showUI upon #execute', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				const spy = vi.spyOn( mathUIFeature, '_showUI' ).mockReturnValue(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				mathButton.fire( 'execute' ); | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(spy).toHaveBeenCalledOnce(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 	} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	describe( '_showUI()', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 		let balloonAddSpy: MockInstance; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		beforeEach( () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 			balloonAddSpy = vi.spyOn( balloon, 'add' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			editor.editing.view.document.isFocused = true; | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it( 'should not work if the math command is disabled', () => { | 
					
						
							|  |  |  | 			setModelData( editor.model, '<paragraph>f[o]o</paragraph>' ); | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			const command = editor.commands.get( 'math' )!; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			command.isEnabled = false; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			mathUIFeature._showUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			expect( balloon.visibleView ).to.be.null; | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it( 'should not throw if the UI is already visible', () => { | 
					
						
							|  |  |  | 			setModelData( editor.model, '<paragraph>f[o]o</paragraph>' ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			mathUIFeature._showUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			expect( () => { | 
					
						
							|  |  |  | 				mathUIFeature._showUI(); | 
					
						
							|  |  |  | 			} ).to.not.throw(); | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it( 'should add #mainFormView to the balloon and attach the balloon to the selection when text fragment is selected', () => { | 
					
						
							|  |  |  | 			setModelData( editor.model, '<paragraph>f[o]o</paragraph>' ); | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 			const selectedRange = editorElement.ownerDocument.getSelection()?.getRangeAt( 0 ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			mathUIFeature._showUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			expect( balloon.visibleView ).to.equal( formView ); | 
					
						
							| 
									
										
										
										
											2025-05-10 00:53:56 +03:00
										 |  |  | 			expect(balloonAddSpy.mock.lastCall?.[0]).toMatchObject({ | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				view: formView, | 
					
						
							|  |  |  | 				position: { | 
					
						
							|  |  |  | 					target: selectedRange | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it( 'should add #mainFormView to the balloon and attach the balloon to the selection when selection is collapsed', () => { | 
					
						
							|  |  |  | 			setModelData( editor.model, '<paragraph>f[]oo</paragraph>' ); | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 			const selectedRange = editorElement.ownerDocument.getSelection()?.getRangeAt( 0 ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			mathUIFeature._showUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			expect( balloon.visibleView ).to.equal( formView ); | 
					
						
							| 
									
										
										
										
											2025-05-10 00:53:56 +03:00
										 |  |  | 			expect(balloonAddSpy.mock.lastCall?.[0]).toMatchObject({ | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				view: formView, | 
					
						
							|  |  |  | 				position: { | 
					
						
							|  |  |  | 					target: selectedRange | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} ); | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it( 'should disable #mainFormView element when math command is disabled', () => { | 
					
						
							|  |  |  | 			setModelData( editor.model, '<paragraph>f[o]o</paragraph>' ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			mathUIFeature._showUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 			const command = editor.commands.get( 'math' )!; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 			command.isEnabled = true; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 			expect( formView!.mathInputView.isReadOnly ).to.be.false; | 
					
						
							| 
									
										
										
										
											2025-05-10 00:53:56 +03:00
										 |  |  | 			expect( formView!.saveButtonView.isEnabled ).to.be.false; | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 			expect( formView!.cancelButtonView.isEnabled ).to.be.true; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 			command.isEnabled = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			expect( formView!.mathInputView.isReadOnly ).to.be.true; | 
					
						
							|  |  |  | 			expect( formView!.saveButtonView.isEnabled ).to.be.false; | 
					
						
							|  |  |  | 			expect( formView!.cancelButtonView.isEnabled ).to.be.true; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		describe( '_hideUI()', () => { | 
					
						
							|  |  |  | 			beforeEach( () => { | 
					
						
							|  |  |  | 				mathUIFeature._showUI(); | 
					
						
							|  |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should remove the UI from the balloon', () => { | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				expect( balloon.hasView( formView! ) ).to.be.true; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				mathUIFeature._hideUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				expect( balloon.hasView( formView! ) ).to.be.false; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should focus the `editable` by default', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				const spy = vi.spyOn( editor.editing.view, 'focus' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				mathUIFeature._hideUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// First call is from _removeFormView.
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(spy).toHaveBeenCalledTimes(2); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should focus the `editable` before before removing elements from the balloon', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				const focusSpy = vi.spyOn( editor.editing.view, 'focus' ); | 
					
						
							|  |  |  | 				const removeSpy = vi.spyOn( balloon, 'remove' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				mathUIFeature._hideUI(); | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(focusSpy).toHaveBeenCalledBefore(removeSpy); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should not throw an error when views are not in the `balloon`', () => { | 
					
						
							|  |  |  | 				mathUIFeature._hideUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				expect( () => { | 
					
						
							|  |  |  | 					mathUIFeature._hideUI(); | 
					
						
							|  |  |  | 				} ).to.not.throw(); | 
					
						
							|  |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should clear ui#update listener from the ViewDocument', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				const spy = vi.fn(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				mathUIFeature.listenTo( editor.ui, 'update', spy ); | 
					
						
							|  |  |  | 				mathUIFeature._hideUI(); | 
					
						
							|  |  |  | 				editor.ui.fire( 'update' ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(spy).not.toHaveBeenCalled(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		describe( 'keyboard support', () => { | 
					
						
							| 
									
										
										
										
											2019-10-11 19:22:03 +03:00
										 |  |  | 			it( 'should show the UI on Ctrl+M keystroke', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				const spy = vi.spyOn( mathUIFeature, '_showUI' ).mockReturnValue( ); | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				const command = editor.commands.get( 'math' )!; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				command.isEnabled = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				const keydata = { | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 					keyCode: keyCodes.m, | 
					
						
							|  |  |  | 					ctrlKey: true, | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					altKey: false, | 
					
						
							|  |  |  | 					shiftKey: false, | 
					
						
							|  |  |  | 					metaKey: false, | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 					preventDefault: vi.fn(), | 
					
						
							|  |  |  | 					stopPropagation: vi.fn() | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				editor.keystrokes.press( keydata ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(spy).not.toHaveBeenCalled(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				command.isEnabled = true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				editor.keystrokes.press( keydata ); | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(spy).toHaveBeenCalled(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should prevent default action on Ctrl+M keystroke', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				const preventDefaultSpy = vi.fn(); | 
					
						
							|  |  |  | 				const stopPropagationSpy = vi.fn(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				const keyEvtData = { | 
					
						
							|  |  |  | 					altKey: false, | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 					ctrlKey: true, | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					shiftKey: false, | 
					
						
							|  |  |  | 					metaKey: false, | 
					
						
							|  |  |  | 					keyCode: keyCodes.m, | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 					preventDefault: preventDefaultSpy, | 
					
						
							|  |  |  | 					stopPropagation: stopPropagationSpy | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				editor.keystrokes.press( keyEvtData ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(preventDefaultSpy).toHaveBeenCalledOnce(); | 
					
						
							|  |  |  | 				expect(stopPropagationSpy).toHaveBeenCalledOnce(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should make stack with math visible on Ctrl+M keystroke - no math', () => { | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				const command = editor.commands.get( 'math' )!; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				command.isEnabled = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				balloon.add( { | 
					
						
							|  |  |  | 					view: new View(), | 
					
						
							|  |  |  | 					stackId: 'custom' | 
					
						
							|  |  |  | 				} ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				const keyEvtData = { | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 					keyCode: keyCodes.m, | 
					
						
							|  |  |  | 					ctrlKey: true, | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					altKey: false, | 
					
						
							|  |  |  | 					shiftKey: false, | 
					
						
							|  |  |  | 					metaKey: false, | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 					preventDefault: vi.fn(), | 
					
						
							|  |  |  | 					stopPropagation: vi.fn() | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				editor.keystrokes.press( keyEvtData ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				expect( balloon.visibleView ).to.equal( formView ); | 
					
						
							|  |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should make stack with math visible on Ctrl+M keystroke - math', () => { | 
					
						
							|  |  |  | 				setModelData( editor.model, '<paragraph><$text equation="x^2">f[]oo</$text></paragraph>' ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const customView = new View(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				balloon.add( { | 
					
						
							|  |  |  | 					view: customView, | 
					
						
							|  |  |  | 					stackId: 'custom' | 
					
						
							|  |  |  | 				} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				expect( balloon.visibleView ).to.equal( customView ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				editor.keystrokes.press( { | 
					
						
							|  |  |  | 					keyCode: keyCodes.m, | 
					
						
							|  |  |  | 					ctrlKey: true, | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					altKey: false, | 
					
						
							|  |  |  | 					shiftKey: false, | 
					
						
							|  |  |  | 					metaKey: false, | 
					
						
							|  |  |  | 					// @ts-expect-error - preventDefault
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 					preventDefault: vi.fn(), | 
					
						
							|  |  |  | 					stopPropagation: vi.fn() | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				expect( balloon.visibleView ).to.equal( formView ); | 
					
						
							|  |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should hide the UI after Esc key press (from editor) and not focus the editable', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				const spy = vi.spyOn( mathUIFeature, '_hideUI' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				const keyEvtData = { | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					altKey: false, | 
					
						
							|  |  |  | 					ctrlKey: false, | 
					
						
							|  |  |  | 					shiftKey: false, | 
					
						
							|  |  |  | 					metaKey: false, | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 					keyCode: keyCodes.esc, | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 					preventDefault: vi.fn(), | 
					
						
							|  |  |  | 					stopPropagation: vi.fn() | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// Balloon is visible.
 | 
					
						
							|  |  |  | 				mathUIFeature._showUI(); | 
					
						
							|  |  |  | 				editor.keystrokes.press( keyEvtData ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(spy).toHaveBeenCalledExactlyOnceWith(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should not hide the UI after Esc key press (from editor) when UI is open but is not visible', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				const spy = vi.spyOn( mathUIFeature, '_hideUI' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				const keyEvtData = { | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					altKey: false, | 
					
						
							|  |  |  | 					shiftKey: false, | 
					
						
							|  |  |  | 					ctrlKey: false, | 
					
						
							|  |  |  | 					metaKey: false, | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 					keyCode: keyCodes.esc, | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 					preventDefault: vi.fn(), | 
					
						
							|  |  |  | 					stopPropagation: vi.fn() | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				const viewMock = new View(); | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				vi.spyOn( viewMock, 'render' ); | 
					
						
							|  |  |  | 				vi.spyOn( viewMock, 'destroy' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				mathUIFeature._showUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// Some view precedes the math UI in the balloon.
 | 
					
						
							|  |  |  | 				balloon.add( { view: viewMock } ); | 
					
						
							|  |  |  | 				editor.keystrokes.press( keyEvtData ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(spy).not.toHaveBeenCalled(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		describe( 'mouse support', () => { | 
					
						
							|  |  |  | 			it( 'should hide the UI and not focus editable upon clicking outside the UI', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				const spy = vi.spyOn( mathUIFeature, '_hideUI' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				mathUIFeature._showUI(); | 
					
						
							|  |  |  | 				document.body.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(spy).toHaveBeenCalledExactlyOnceWith(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it( 'should not hide the UI upon clicking inside the the UI', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				const spy = vi.spyOn( mathUIFeature, '_hideUI' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				mathUIFeature._showUI(); | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				balloon.view.element!.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 				expect(spy).not.toHaveBeenCalled(); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 			} ); | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		describe( 'math form view', () => { | 
					
						
							|  |  |  | 			it( 'should mark the editor UI as focused when the #formView is focused', () => { | 
					
						
							|  |  |  | 				mathUIFeature._showUI(); | 
					
						
							|  |  |  | 				expect( balloon.visibleView ).to.equal( formView ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				editor.ui.focusTracker.isFocused = false; | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 				formView!.element!.dispatchEvent( new Event( 'focus' ) ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				expect( editor.ui.focusTracker.isFocused ).to.be.true; | 
					
						
							|  |  |  | 			} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			describe( 'binding', () => { | 
					
						
							|  |  |  | 				beforeEach( () => { | 
					
						
							|  |  |  | 					setModelData( editor.model, '<paragraph>f[o]o</paragraph>' ); | 
					
						
							|  |  |  | 				} ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-11 19:22:03 +03:00
										 |  |  | 				it( 'should bind mainFormView.mathInputView#value to math command value', () => { | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 					const command = editor.commands.get( 'math' ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					expect( formView!.mathInputView.value ).to.null; | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					command!.value = 'x^2'; | 
					
						
							|  |  |  | 					expect( formView!.mathInputView.value ).to.equal( 'x^2' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				it( 'should execute math command on mainFormView#submit event', () => { | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 					const executeSpy = vi.spyOn( editor, 'execute' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					formView!.mathInputView.fieldView.element!.value = 'x^2'; | 
					
						
							|  |  |  | 					formView!.fire( 'submit' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-10 00:53:56 +03:00
										 |  |  | 					expect(executeSpy.mock.lastCall?.slice(0, 2)).toMatchObject(['math', 'x^2']); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				it( 'should hide the balloon on mainFormView#cancel if math command does not have a value', () => { | 
					
						
							|  |  |  | 					mathUIFeature._showUI(); | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					formView!.fire( 'cancel' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					expect( balloon.visibleView ).to.be.null; | 
					
						
							|  |  |  | 				} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				it( 'should hide the balloon after Esc key press if math command does not have a value', () => { | 
					
						
							|  |  |  | 					const keyEvtData = { | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 						altKey: false, | 
					
						
							|  |  |  | 						shiftKey: false, | 
					
						
							|  |  |  | 						metaKey: false, | 
					
						
							|  |  |  | 						ctrlKey: false, | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 						keyCode: keyCodes.esc, | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 						preventDefault: vi.fn(), | 
					
						
							|  |  |  | 						stopPropagation: vi.fn() | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 					}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					mathUIFeature._showUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					formView!.keystrokes.press( keyEvtData ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					expect( balloon.visibleView ).to.be.null; | 
					
						
							|  |  |  | 				} ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				it( 'should blur math input element before hiding the view', () => { | 
					
						
							|  |  |  | 					mathUIFeature._showUI(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 					const focusSpy = vi.spyOn( formView!.saveButtonView, 'focus' ); | 
					
						
							|  |  |  | 					const removeSpy = vi.spyOn( balloon, 'remove' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 20:55:51 -03:00
										 |  |  | 					formView!.fire( 'cancel' ); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 23:56:09 +03:00
										 |  |  | 					expect(focusSpy).toHaveBeenCalledBefore(removeSpy); | 
					
						
							| 
									
										
										
										
											2019-10-09 13:38:30 +03:00
										 |  |  | 				} ); | 
					
						
							|  |  |  | 			} ); | 
					
						
							|  |  |  | 		} ); | 
					
						
							|  |  |  | 	} ); | 
					
						
							|  |  |  | } ); |