From 3e1008ee96cfe273cb82eb2376798e40606db50b Mon Sep 17 00:00:00 2001 From: Federico Date: Wed, 20 Mar 2024 20:55:51 -0300 Subject: [PATCH] Migrate tests to Typescript --- package.json | 3 + src/mathui.ts | 5 +- tests/{automath.js => automath.ts} | 15 +-- tests/{index.js => index.ts} | 1 + tests/{math.js => math.ts} | 5 +- tests/{mathui.js => mathui.ts} | 156 ++++++++++++++++++----------- tests/typings-external.ts | 9 ++ yarn.lock | 22 ++++ 8 files changed, 150 insertions(+), 66 deletions(-) rename tests/{automath.js => automath.ts} (93%) rename tests/{index.js => index.ts} (92%) rename tests/{math.js => math.ts} (92%) rename tests/{mathui.js => mathui.ts} (77%) create mode 100644 tests/typings-external.ts diff --git a/package.json b/package.json index fd8c88f0b..5290c896f 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,9 @@ "@ckeditor/ckeditor5-table": "41.4.2", "@ckeditor/ckeditor5-theme-lark": "41.4.2", "@ckeditor/ckeditor5-upload": "41.4.2", + "@types/chai": "^4.3.13", + "@types/mocha": "^10.0.6", + "@types/sinon": "^17.0.3", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "eslint": "^8.57.0", diff --git a/src/mathui.ts b/src/mathui.ts index 12a9b2776..7a588ead3 100644 --- a/src/mathui.ts +++ b/src/mathui.ts @@ -159,7 +159,10 @@ export default class MathUI extends Plugin { this.formView.displayButtonView.isOn = mathCommand.display || false; } - private _hideUI() { + /** + * @private + */ + public _hideUI(): void { if ( !this._isFormInPanel ) { return; } diff --git a/tests/automath.js b/tests/automath.ts similarity index 93% rename from tests/automath.js rename to tests/automath.ts index 66eddf81c..3e856bfae 100644 --- a/tests/automath.js +++ b/tests/automath.ts @@ -7,11 +7,13 @@ import Undo from '@ckeditor/ckeditor5-undo/src/undo'; import Typing from '@ckeditor/ckeditor5-typing/src/typing'; import global from '@ckeditor/ckeditor5-utils/src/dom/global'; import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import { expect } from 'chai'; +import type { SinonFakeTimers } from 'sinon'; describe( 'AutoMath - integration', () => { - let editorElement, editor; + let editorElement: HTMLDivElement, editor: ClassicEditor; - beforeEach( () => { + beforeEach( async () => { editorElement = global.document.createElement( 'div' ); global.document.body.appendChild( editorElement ); @@ -52,7 +54,7 @@ describe( 'AutoMath - integration', () => { } ); describe( 'use fake timers', () => { - let clock; + let clock: SinonFakeTimers; beforeEach( () => { clock = sinon.useFakeTimers(); @@ -169,17 +171,18 @@ describe( 'AutoMath - integration', () => { } ); } ); - function pasteHtml( editor, html ) { + function pasteHtml( editor: ClassicEditor, html: string ) { editor.editing.view.document.fire( 'paste', { dataTransfer: createDataTransfer( { 'text/html': html } ), preventDefault() { + return undefined; } } ); } - function createDataTransfer( data ) { + function createDataTransfer( data: Record ) { return { - getData( type ) { + getData( type: string ) { return data[ type ]; } }; diff --git a/tests/index.js b/tests/index.ts similarity index 92% rename from tests/index.js rename to tests/index.ts index a3fba740b..6b6192445 100644 --- a/tests/index.js +++ b/tests/index.ts @@ -1,6 +1,7 @@ import { Math as MathDll, AutoformatMath as AutoformatMathDll } from '../src'; import Math from '../src/math'; import AutoformatMath from '../src/autoformatmath'; +import { expect } from 'chai'; describe( 'CKEditor5 Math DLL', () => { it( 'exports Math', () => { diff --git a/tests/math.js b/tests/math.ts similarity index 92% rename from tests/math.js rename to tests/math.ts index 144f5012e..3d3308f5e 100644 --- a/tests/math.js +++ b/tests/math.ts @@ -5,11 +5,12 @@ import MathUI from '../src/mathui'; import AutoMath from '../src/automath'; import Widget from '@ckeditor/ckeditor5-widget/src/widget'; import global from '@ckeditor/ckeditor5-utils/src/dom/global'; +import { expect } from 'chai'; describe( 'Math', () => { - let editorElement, editor; + let editorElement: HTMLDivElement, editor: ClassicEditor; - beforeEach( () => { + beforeEach( async () => { editorElement = global.document.createElement( 'div' ); global.document.body.appendChild( editorElement ); diff --git a/tests/mathui.js b/tests/mathui.ts similarity index 77% rename from tests/mathui.js rename to tests/mathui.ts index cd4149067..e5de67202 100644 --- a/tests/mathui.js +++ b/tests/mathui.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* globals document, Event */ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; @@ -12,11 +13,18 @@ import View from '@ckeditor/ckeditor5-ui/src/view'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver'; +import { expect } from 'chai'; +import type { SinonSpy } from 'sinon'; describe( 'MathUI', () => { - let editorElement, editor, mathUIFeature, mathButton, balloon, formView; + let editorElement: HTMLDivElement; + let editor: ClassicEditor; + let mathUIFeature: MathUI; + let mathButton: ButtonView; + let balloon: ContextualBalloon; + let formView: MainFormView | null; - beforeEach( () => { + beforeEach( async () => { editorElement = document.createElement( 'div' ); document.body.appendChild( editorElement ); @@ -36,15 +44,15 @@ describe( 'MathUI', () => { .then( newEditor => { editor = newEditor; mathUIFeature = editor.plugins.get( MathUI ); - mathButton = editor.ui.componentFactory.create( 'math' ); + mathButton = editor.ui.componentFactory.create( 'math' ) as ButtonView; balloon = editor.plugins.get( ContextualBalloon ); formView = mathUIFeature.formView; // There is no point to execute BalloonPanelView attachTo and pin methods so lets override it. - sinon.stub( balloon.view, 'attachTo' ).returns( {} ); - sinon.stub( balloon.view, 'pin' ).returns( {} ); + sinon.stub( balloon.view, 'attachTo' ).returns( ); + sinon.stub( balloon.view, 'pin' ).returns( ); - formView.render(); + formView?.render(); } ); } ); @@ -75,6 +83,10 @@ describe( 'MathUI', () => { it( 'should be bound to the math command', () => { const command = editor.commands.get( 'math' ); + if ( !command ) { + throw new Error( 'Missing math command' ); + } + command.isEnabled = true; command.value = '\\sqrt{x^2}'; @@ -87,7 +99,7 @@ describe( 'MathUI', () => { } ); it( 'should call #_showUI upon #execute', () => { - const spy = sinon.stub( mathUIFeature, '_showUI' ).returns( {} ); + const spy = sinon.stub( mathUIFeature, '_showUI' ).returns( ); mathButton.fire( 'execute' ); sinon.assert.calledOnce( spy ); @@ -96,7 +108,7 @@ describe( 'MathUI', () => { } ); describe( '_showUI()', () => { - let balloonAddSpy; + let balloonAddSpy: SinonSpy; beforeEach( () => { balloonAddSpy = sinon.spy( balloon, 'add' ); @@ -105,7 +117,10 @@ describe( 'MathUI', () => { it( 'should not work if the math command is disabled', () => { setModelData( editor.model, 'f[o]o' ); - editor.commands.get( 'math' ).isEnabled = false; + + const command = editor.commands.get( 'math' )!; + + command.isEnabled = false; mathUIFeature._showUI(); @@ -124,7 +139,7 @@ describe( 'MathUI', () => { it( 'should add #mainFormView to the balloon and attach the balloon to the selection when text fragment is selected', () => { setModelData( editor.model, 'f[o]o' ); - const selectedRange = editorElement.ownerDocument.getSelection().getRangeAt( 0 ); + const selectedRange = editorElement.ownerDocument.getSelection()?.getRangeAt( 0 ); mathUIFeature._showUI(); @@ -139,7 +154,7 @@ describe( 'MathUI', () => { it( 'should add #mainFormView to the balloon and attach the balloon to the selection when selection is collapsed', () => { setModelData( editor.model, 'f[]oo' ); - const selectedRange = editorElement.ownerDocument.getSelection().getRangeAt( 0 ); + const selectedRange = editorElement.ownerDocument.getSelection()?.getRangeAt( 0 ); mathUIFeature._showUI(); @@ -157,17 +172,19 @@ describe( 'MathUI', () => { mathUIFeature._showUI(); - editor.commands.get( 'math' ).isEnabled = true; + const command = editor.commands.get( 'math' )!; - expect( formView.mathInputView.isReadOnly ).to.be.false; - expect( formView.saveButtonView.isEnabled ).to.be.true; - expect( formView.cancelButtonView.isEnabled ).to.be.true; + command.isEnabled = true; - editor.commands.get( 'math' ).isEnabled = false; + expect( formView!.mathInputView.isReadOnly ).to.be.false; + expect( formView!.saveButtonView.isEnabled ).to.be.true; + expect( formView!.cancelButtonView.isEnabled ).to.be.true; - expect( formView.mathInputView.isReadOnly ).to.be.true; - expect( formView.saveButtonView.isEnabled ).to.be.false; - expect( formView.cancelButtonView.isEnabled ).to.be.true; + command.isEnabled = false; + + expect( formView!.mathInputView.isReadOnly ).to.be.true; + expect( formView!.saveButtonView.isEnabled ).to.be.false; + expect( formView!.cancelButtonView.isEnabled ).to.be.true; } ); describe( '_hideUI()', () => { @@ -176,11 +193,11 @@ describe( 'MathUI', () => { } ); it( 'should remove the UI from the balloon', () => { - expect( balloon.hasView( formView ) ).to.be.true; + expect( balloon.hasView( formView! ) ).to.be.true; mathUIFeature._hideUI(); - expect( balloon.hasView( formView ) ).to.be.false; + expect( balloon.hasView( formView! ) ).to.be.false; } ); it( 'should focus the `editable` by default', () => { @@ -222,27 +239,28 @@ describe( 'MathUI', () => { describe( 'keyboard support', () => { it( 'should show the UI on Ctrl+M keystroke', () => { - const spy = sinon.stub( mathUIFeature, '_showUI' ).returns( {} ); - const command = editor.commands.get( 'math' ); + const spy = sinon.stub( mathUIFeature, '_showUI' ).returns( ); + const command = editor.commands.get( 'math' )!; command.isEnabled = false; - editor.keystrokes.press( { + const keydata = { keyCode: keyCodes.m, ctrlKey: true, + altKey: false, + shiftKey: false, + metaKey: false, preventDefault: sinon.spy(), stopPropagation: sinon.spy() - } ); + }; + + editor.keystrokes.press( keydata ); + sinon.assert.notCalled( spy ); command.isEnabled = true; - editor.keystrokes.press( { - keyCode: keyCodes.m, - ctrlKey: true, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - } ); + editor.keystrokes.press( keydata ); sinon.assert.calledOnce( spy ); } ); @@ -250,19 +268,24 @@ describe( 'MathUI', () => { const preventDefaultSpy = sinon.spy(); const stopPropagationSpy = sinon.spy(); - editor.keystrokes.press( { - keyCode: keyCodes.m, + const keyEvtData = { + altKey: false, ctrlKey: true, + shiftKey: false, + metaKey: false, + keyCode: keyCodes.m, preventDefault: preventDefaultSpy, stopPropagation: stopPropagationSpy - } ); + }; + + editor.keystrokes.press( keyEvtData ); sinon.assert.calledOnce( preventDefaultSpy ); sinon.assert.calledOnce( stopPropagationSpy ); } ); it( 'should make stack with math visible on Ctrl+M keystroke - no math', () => { - const command = editor.commands.get( 'math' ); + const command = editor.commands.get( 'math' )!; command.isEnabled = true; @@ -271,12 +294,17 @@ describe( 'MathUI', () => { stackId: 'custom' } ); - editor.keystrokes.press( { + const keyEvtData = { keyCode: keyCodes.m, ctrlKey: true, + altKey: false, + shiftKey: false, + metaKey: false, preventDefault: sinon.spy(), stopPropagation: sinon.spy() - } ); + }; + + editor.keystrokes.press( keyEvtData ); expect( balloon.visibleView ).to.equal( formView ); } ); @@ -296,6 +324,10 @@ describe( 'MathUI', () => { editor.keystrokes.press( { keyCode: keyCodes.m, ctrlKey: true, + altKey: false, + shiftKey: false, + metaKey: false, + // @ts-expect-error - preventDefault preventDefault: sinon.spy(), stopPropagation: sinon.spy() } ); @@ -306,6 +338,10 @@ describe( 'MathUI', () => { it( 'should hide the UI after Esc key press (from editor) and not focus the editable', () => { const spy = sinon.spy( mathUIFeature, '_hideUI' ); const keyEvtData = { + altKey: false, + ctrlKey: false, + shiftKey: false, + metaKey: false, keyCode: keyCodes.esc, preventDefault: sinon.spy(), stopPropagation: sinon.spy() @@ -321,16 +357,18 @@ describe( 'MathUI', () => { it( 'should not hide the UI after Esc key press (from editor) when UI is open but is not visible', () => { const spy = sinon.spy( mathUIFeature, '_hideUI' ); const keyEvtData = { + altKey: false, + shiftKey: false, + ctrlKey: false, + metaKey: false, keyCode: keyCodes.esc, - preventDefault: () => {}, - stopPropagation: () => {} + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() }; - const viewMock = { - ready: true, - render: () => {}, - destroy: () => {} - }; + const viewMock = new View(); + sinon.stub( viewMock, 'render' ); + sinon.stub( viewMock, 'destroy' ); mathUIFeature._showUI(); @@ -356,7 +394,7 @@ describe( 'MathUI', () => { const spy = sinon.spy( mathUIFeature, '_hideUI' ); mathUIFeature._showUI(); - balloon.view.element.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) ); + balloon.view.element!.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) ); sinon.assert.notCalled( spy ); } ); @@ -368,7 +406,7 @@ describe( 'MathUI', () => { expect( balloon.visibleView ).to.equal( formView ); editor.ui.focusTracker.isFocused = false; - formView.element.dispatchEvent( new Event( 'focus' ) ); + formView!.element!.dispatchEvent( new Event( 'focus' ) ); expect( editor.ui.focusTracker.isFocused ).to.be.true; } ); @@ -381,20 +419,20 @@ describe( 'MathUI', () => { it( 'should bind mainFormView.mathInputView#value to math command value', () => { const command = editor.commands.get( 'math' ); - expect( formView.mathInputView.value ).to.null; + expect( formView!.mathInputView.value ).to.null; - command.value = 'x^2'; - expect( formView.mathInputView.value ).to.equal( 'x^2' ); + command!.value = 'x^2'; + expect( formView!.mathInputView.value ).to.equal( 'x^2' ); } ); it( 'should execute math command on mainFormView#submit event', () => { const executeSpy = sinon.spy( editor, 'execute' ); - formView.mathInputView.value = 'x^2'; - expect( formView.mathInputView.fieldView.element.value ).to.equal( 'x^2' ); + formView!.mathInputView.value = 'x^2'; + expect( formView!.mathInputView.fieldView.element!.value ).to.equal( 'x^2' ); - formView.mathInputView.fieldView.element.value = 'x^2'; - formView.fire( 'submit' ); + formView!.mathInputView.fieldView.element!.value = 'x^2'; + formView!.fire( 'submit' ); expect( executeSpy.calledOnce ).to.be.true; expect( executeSpy.calledWith( 'math', 'x^2' ) ).to.be.true; @@ -402,13 +440,17 @@ describe( 'MathUI', () => { it( 'should hide the balloon on mainFormView#cancel if math command does not have a value', () => { mathUIFeature._showUI(); - formView.fire( 'cancel' ); + formView!.fire( 'cancel' ); 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 = { + altKey: false, + shiftKey: false, + metaKey: false, + ctrlKey: false, keyCode: keyCodes.esc, preventDefault: sinon.spy(), stopPropagation: sinon.spy() @@ -416,7 +458,7 @@ describe( 'MathUI', () => { mathUIFeature._showUI(); - formView.keystrokes.press( keyEvtData ); + formView!.keystrokes.press( keyEvtData ); expect( balloon.visibleView ).to.be.null; } ); @@ -424,10 +466,10 @@ describe( 'MathUI', () => { it( 'should blur math input element before hiding the view', () => { mathUIFeature._showUI(); - const focusSpy = sinon.spy( formView.saveButtonView, 'focus' ); + const focusSpy = sinon.spy( formView!.saveButtonView, 'focus' ); const removeSpy = sinon.spy( balloon, 'remove' ); - formView.fire( 'cancel' ); + formView!.fire( 'cancel' ); expect( focusSpy.calledBefore( removeSpy ) ).to.equal( true ); } ); diff --git a/tests/typings-external.ts b/tests/typings-external.ts new file mode 100644 index 000000000..ef09d847b --- /dev/null +++ b/tests/typings-external.ts @@ -0,0 +1,9 @@ +import type { SinonStatic } from 'sinon'; + +declare global { + // eslint-disable-next-line no-var + var sinon: SinonStatic; +} + +export default {}; + diff --git a/yarn.lock b/yarn.lock index 251b7f30a..4f0cb5255 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1302,6 +1302,11 @@ dependencies: "@types/node" "*" +"@types/chai@^4.3.13": + version "4.3.16" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.16.tgz#b1572967f0b8b60bf3f87fe1d854a5604ea70c82" + integrity sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ== + "@types/connect-history-api-fallback@^1.3.5": version "1.5.1" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.1.tgz#6e5e3602d93bda975cebc3449e1a318340af9e20" @@ -1427,6 +1432,11 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.3.tgz#dd249cef80c6fff2ba6a0d4e5beca913e04e25f8" integrity sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A== +"@types/mocha@^10.0.6": + version "10.0.6" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.6.tgz#818551d39113081048bdddbef96701b4e8bb9d1b" + integrity sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg== + "@types/node@*", "@types/node@>=10.0.0": version "20.8.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.5.tgz#13352ae1f80032171616910e8aba2e3e52e57d96" @@ -1493,6 +1503,18 @@ "@types/mime" "*" "@types/node" "*" +"@types/sinon@^17.0.3": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.3.tgz#9aa7e62f0a323b9ead177ed23a36ea757141a5fa" + integrity sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz#5fd3592ff10c1e9695d377020c033116cc2889f2" + integrity sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ== + "@types/sockjs@^0.3.33": version "0.3.34" resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.34.tgz#43e10e549b36d2ba2589278f00f81b5d7ccda167"