From 4edf334b411104fd6a9dc2dea6710cf95de7e488 Mon Sep 17 00:00:00 2001 From: JackUait Date: Fri, 7 Nov 2025 02:23:21 +0300 Subject: [PATCH] fix: lint issues in tests --- test/cypress/fixtures/tools/SimpleHeader.ts | 2 - test/cypress/fixtures/tools/ToolMock.ts | 2 +- test/cypress/simple-image.d.ts | 28 +++++++++++ test/cypress/support/chai-subset.d.ts | 8 ++++ test/cypress/support/commands.ts | 41 ++++++++-------- test/cypress/support/e2e.ts | 11 +++-- test/cypress/support/index.d.ts | 4 +- .../utils/createEditorWithTextBlocks.ts | 1 - .../support/utils/createParagraphMock.ts | 1 - .../support/utils/nestedEditorInstance.ts | 10 ++++ test/cypress/tests/api/block.cy.ts | 10 ++-- test/cypress/tests/api/blocks.cy.ts | 33 +++++++------ test/cypress/tests/api/caret.cy.ts | 16 +++++++ test/cypress/tests/api/toolbar.cy.ts | 2 +- test/cypress/tests/api/tools.cy.ts | 8 ++-- test/cypress/tests/i18n.cy.ts | 6 +-- test/cypress/tests/initialization.cy.ts | 6 +-- .../tests/modules/BlockEvents/ArrowLeft.cy.ts | 21 ++++++++ .../modules/BlockEvents/ArrowRight.cy.ts | 21 ++++++++ .../tests/modules/BlockEvents/Backspace.cy.ts | 25 +++++++++- .../tests/modules/BlockEvents/Delete.cy.ts | 13 ++++- .../tests/modules/BlockEvents/Enter.cy.ts | 3 ++ .../tests/modules/BlockEvents/Tab.cy.ts | 24 ++++++++++ .../cypress/tests/modules/InlineToolbar.cy.ts | 48 +++++++++++++++---- test/cypress/tests/modules/Renderer.cy.ts | 5 +- test/cypress/tests/modules/Tools.cy.ts | 41 ++++++++-------- test/cypress/tests/onchange.cy.ts | 25 +++++----- test/cypress/tests/readOnly.cy.ts | 1 - test/cypress/tests/selection.cy.ts | 2 +- test/cypress/tests/tools/BlockTool.cy.ts | 12 ++--- test/cypress/tests/tools/BlockTune.cy.ts | 6 +-- test/cypress/tests/tools/InlineTool.cy.ts | 8 ++-- .../cypress/tests/tools/ToolsCollection.cy.ts | 2 +- test/cypress/tests/tools/ToolsFactory.cy.ts | 6 ++- test/cypress/tests/ui/BlockTunes.cy.ts | 14 ++++-- test/cypress/tests/ui/InlineToolbar.cy.ts | 24 ++++++---- test/cypress/tests/ui/toolbox.cy.ts | 6 ++- test/cypress/tests/utils/popover.cy.ts | 10 ++-- types/api/blocks.d.ts | 2 +- types/block-tunes/block-tune.d.ts | 5 ++ types/tools/tool.d.ts | 5 ++ 41 files changed, 373 insertions(+), 145 deletions(-) create mode 100644 test/cypress/simple-image.d.ts create mode 100644 test/cypress/support/chai-subset.d.ts diff --git a/test/cypress/fixtures/tools/SimpleHeader.ts b/test/cypress/fixtures/tools/SimpleHeader.ts index 23421374..bfcb91b4 100644 --- a/test/cypress/fixtures/tools/SimpleHeader.ts +++ b/test/cypress/fixtures/tools/SimpleHeader.ts @@ -22,7 +22,6 @@ export class SimpleHeader implements BaseTool { /** * Return Tool's view - * * @returns {HTMLHeadingElement} * @public */ @@ -44,7 +43,6 @@ export class SimpleHeader implements BaseTool { /** * Extract Tool's data from the view - * * @param toolsContent - Text tools rendered view */ public save(toolsContent: HTMLHeadingElement): BlockToolData { diff --git a/test/cypress/fixtures/tools/ToolMock.ts b/test/cypress/fixtures/tools/ToolMock.ts index 51ea3a95..43077c05 100644 --- a/test/cypress/fixtures/tools/ToolMock.ts +++ b/test/cypress/fixtures/tools/ToolMock.ts @@ -32,7 +32,7 @@ export default class ToolMock implements BlockTool { public render(): HTMLElement { const contenteditable = document.createElement('div'); - if (this.data && this.data.text) { + if (this.data.text) { contenteditable.innerHTML = this.data.text; } diff --git a/test/cypress/simple-image.d.ts b/test/cypress/simple-image.d.ts new file mode 100644 index 00000000..1520df9c --- /dev/null +++ b/test/cypress/simple-image.d.ts @@ -0,0 +1,28 @@ +/** + * Declaration for external JS module @editorjs/simple-image + */ +declare module '@editorjs/simple-image' { + interface SimpleImageConfig { + data?: Record; + readOnly?: boolean; + api?: unknown; + block?: unknown; + } + + interface SimpleImageInstance { + render(): HTMLElement; + save(block: HTMLElement): Record; + } + + interface SimpleImageTool { + toolbox?: unknown; + pasteConfig?: unknown; + conversionConfig?: unknown; + isReadOnlySupported?: boolean; + new (config: SimpleImageConfig): SimpleImageInstance; + } + + const Image: SimpleImageTool; + export default Image; +} + diff --git a/test/cypress/support/chai-subset.d.ts b/test/cypress/support/chai-subset.d.ts new file mode 100644 index 00000000..a5938bf2 --- /dev/null +++ b/test/cypress/support/chai-subset.d.ts @@ -0,0 +1,8 @@ +/** + * Type declaration for chai-subset module + */ +declare module 'chai-subset' { + const chaiSubset: (chai: any, utils: any) => void; + export default chaiSubset; +} + diff --git a/test/cypress/support/commands.ts b/test/cypress/support/commands.ts index 35392fe2..f2cd85e8 100644 --- a/test/cypress/support/commands.ts +++ b/test/cypress/support/commands.ts @@ -13,7 +13,6 @@ import Chainable = Cypress.Chainable; /** * Create a wrapper and initialize the new instance of editor.js * Then return the instance - * * @param editorConfig - config to pass to the editor * @returns EditorJS - created instance */ @@ -43,7 +42,6 @@ Cypress.Commands.add('createEditor', (editorConfig: EditorConfig = {}): Chainabl * * Usage * cy.get('div').paste({'text/plain': 'Text', 'text/html': 'Text'}) - * * @param data - map with MIME type as a key and data as value */ Cypress.Commands.add('paste', { @@ -54,7 +52,7 @@ Cypress.Commands.add('paste', { cancelable: true, }), { clipboardData: { - getData: (type): string => data[type], + getData: (type: string): string => data[type], types: Object.keys(data), }, }); @@ -70,7 +68,9 @@ Cypress.Commands.add('paste', { * Usage: * cy.get('div').copy().then(data => {}) */ -Cypress.Commands.add('copy', { prevSubject: true }, (subject) => { +Cypress.Commands.add('copy', { + prevSubject: ['element'], +}, (subject) => { const clipboardData: {[type: string]: any} = {}; const copyEvent = Object.assign(new Event('copy', { @@ -86,7 +86,7 @@ Cypress.Commands.add('copy', { prevSubject: true }, (subject) => { subject[0].dispatchEvent(copyEvent); - return cy.wrap(clipboardData); + return cy.wrap>(clipboardData); }); /** @@ -95,7 +95,7 @@ Cypress.Commands.add('copy', { prevSubject: true }, (subject) => { * Usage: * cy.get('div').cut().then(data => {}) */ -Cypress.Commands.add('cut', { prevSubject: true }, (subject) => { +Cypress.Commands.add('cut', { prevSubject: ['element'] }, (subject) => { const clipboardData: {[type: string]: any} = {}; const copyEvent = Object.assign(new Event('cut', { @@ -111,12 +111,11 @@ Cypress.Commands.add('cut', { prevSubject: true }, (subject) => { subject[0].dispatchEvent(copyEvent); - return cy.wrap(clipboardData); + return cy.wrap>(clipboardData); }); /** * Calls EditorJS API render method - * * @param data — data to render */ Cypress.Commands.add('render', { prevSubject: true }, (subject: EditorJS, data: OutputData) => { @@ -133,9 +132,8 @@ Cypress.Commands.add('render', { prevSubject: true }, (subject: EditorJS, data: * * Usage * cy.get('[data-cy=editorjs]') - * .find('.ce-paragraph') - * .selectText('block te') - * + * .find('.ce-paragraph') + * .selectText('block te') * @param text - text to select */ Cypress.Commands.add('selectText', { @@ -162,9 +160,8 @@ Cypress.Commands.add('selectText', { * * Usage * cy.get('[data-cy=editorjs]') - * .find('.ce-paragraph') - * .selectTextByOffset([0, 5]) - * + * .find('.ce-paragraph') + * .selectTextByOffset([0, 5]) * @param offset - offset to select */ Cypress.Commands.add('selectTextByOffset', { @@ -190,9 +187,8 @@ Cypress.Commands.add('selectTextByOffset', { * * Usage * cy.get('[data-cy=editorjs]') - * .find('.ce-paragraph') - * .getLineWrapPositions() - * + * .find('.ce-paragraph') + * .getLineWrapPositions() * @returns number[] - array of line wrap positions */ Cypress.Commands.add('getLineWrapPositions', { @@ -249,7 +245,6 @@ Cypress.Commands.add('keydown', { * so real-world and Cypress behaviour were different. * * To make it work we need to trigger Cypress event with "eventConstructor: 'KeyboardEvent'", - * * @see https://github.com/cypress-io/cypress/issues/5650 * @see https://github.com/cypress-io/cypress/pull/8305/files */ @@ -264,15 +259,17 @@ Cypress.Commands.add('keydown', { /** * Extract content of pseudo element - * * @example cy.get('element').getPseudoElementContent('::before').should('eq', 'my-test-string') */ Cypress.Commands.add('getPseudoElementContent', { - prevSubject: true, -}, (subject, pseudoElement: 'string') => { + prevSubject: ['element'], +}, (subject, pseudoElement: string) => { const win = subject[0].ownerDocument.defaultView; + if (!win) { + throw new Error('defaultView is null'); + } const computedStyle = win.getComputedStyle(subject[0], pseudoElement); const content = computedStyle.getPropertyValue('content'); - return content.replace(/['"]/g, ''); // Remove quotes around the content + return cy.wrap(content.replace(/['"]/g, '')); // Remove quotes around the content }); diff --git a/test/cypress/support/e2e.ts b/test/cypress/support/e2e.ts index 0ec34b57..ea7e192b 100644 --- a/test/cypress/support/e2e.ts +++ b/test/cypress/support/e2e.ts @@ -6,20 +6,20 @@ import '@cypress/code-coverage/support'; // available to them because the supportFile is bundled and served // prior to any spec files loading +declare const chai: Chai.ChaiStatic; + import type PartialBlockMutationEvent from '../fixtures/types/PartialBlockMutationEvent'; /** * Chai plugin for checking if passed onChange method is called with an array of passed events - * * @param _chai - Chai instance */ -const beCalledWithBatchedEvents = (_chai): void => { +const beCalledWithBatchedEvents = (_chai: typeof chai): void => { /** * Check if passed onChange method is called with an array of passed events - * * @param expectedEvents - batched events to check */ - function assertToBeCalledWithBatchedEvents(expectedEvents: PartialBlockMutationEvent[]): void { + function assertToBeCalledWithBatchedEvents(this: Chai.AssertionStatic, expectedEvents: PartialBlockMutationEvent[]): void { /** * EditorJS API is passed as the first parameter of the onChange callback */ @@ -29,7 +29,8 @@ const beCalledWithBatchedEvents = (_chai): void => { this.assert( $onChange.calledOnce, 'expected #{this} to be called once', - 'expected #{this} to not be called once' + 'expected #{this} to not be called once', + true ); this.assert( diff --git a/test/cypress/support/index.d.ts b/test/cypress/support/index.d.ts index ee8a4954..32bd1dc9 100644 --- a/test/cypress/support/index.d.ts +++ b/test/cypress/support/index.d.ts @@ -31,7 +31,7 @@ declare global { * @usage * cy.get('div').copy().then(data => {}) */ - copy(): Chainable; + copy(): Chainable>; /** * Cut command to dispatch cut event on subject @@ -39,7 +39,7 @@ declare global { * @usage * cy.get('div').cut().then(data => {}) */ - cut(): Chainable; + cut(): Chainable>; /** * Calls EditorJS API render method diff --git a/test/cypress/support/utils/createEditorWithTextBlocks.ts b/test/cypress/support/utils/createEditorWithTextBlocks.ts index 08d0fc49..c9381a38 100644 --- a/test/cypress/support/utils/createEditorWithTextBlocks.ts +++ b/test/cypress/support/utils/createEditorWithTextBlocks.ts @@ -5,7 +5,6 @@ import type EditorJS from '../../../../types/index'; /** * Creates Editor instance with list of Paragraph blocks of passed texts - * * @param textBlocks - list of texts for Paragraph blocks * @param editorConfig - config to pass to the editor */ diff --git a/test/cypress/support/utils/createParagraphMock.ts b/test/cypress/support/utils/createParagraphMock.ts index 30166e87..2fca16cd 100644 --- a/test/cypress/support/utils/createParagraphMock.ts +++ b/test/cypress/support/utils/createParagraphMock.ts @@ -2,7 +2,6 @@ import { nanoid } from 'nanoid'; /** * Creates a paragraph mock - * * @param text - text for the paragraph * @returns paragraph mock */ diff --git a/test/cypress/support/utils/nestedEditorInstance.ts b/test/cypress/support/utils/nestedEditorInstance.ts index f335cbce..16ac1682 100644 --- a/test/cypress/support/utils/nestedEditorInstance.ts +++ b/test/cypress/support/utils/nestedEditorInstance.ts @@ -9,10 +9,17 @@ export const NESTED_EDITOR_ID = 'nested-editor'; export default class NestedEditor implements BlockTool { private data: { text: string }; + /** + * + * @param value - The constructor options for the block tool + */ constructor(value: BlockToolConstructorOptions) { this.data = value.data; } + /** + * + */ public render(): HTMLDivElement { const editorEl = Object.assign(document.createElement('div'), { id: NESTED_EDITOR_ID, @@ -25,6 +32,9 @@ export default class NestedEditor implements BlockTool { return editorEl; } + /** + * + */ public save(): string { return this.data.text; } diff --git a/test/cypress/tests/api/block.cy.ts b/test/cypress/tests/api/block.cy.ts index d5d731f1..cad80eef 100644 --- a/test/cypress/tests/api/block.cy.ts +++ b/test/cypress/tests/api/block.cy.ts @@ -1,4 +1,5 @@ import type EditorJS from '../../../../types'; +import type { API, BlockMutationEvent, OutputData } from '../../../../types'; import { BlockChangedMutationType } from '../../../../types/events/block/BlockChanged'; /** @@ -25,12 +26,11 @@ describe('BlockAPI', () => { /** * Creates Editor instance - * * @param [data] - data to render */ - function createEditor(data = undefined): void { + function createEditor(data?: OutputData): void { const config = { - onChange: (api, event): void => { + onChange: (_api: API, event: BlockMutationEvent | BlockMutationEvent[]): void => { console.log('something changed', event); }, data, @@ -55,6 +55,10 @@ describe('BlockAPI', () => { .then(async (editor) => { const block = editor.blocks.getById(firstBlock.id); + if (!block) { + throw new Error(`Block with id ${firstBlock.id} not found`); + } + block.dispatchChange(); cy.get('@onChange').should('be.calledWithMatch', EditorJSApiMock, Cypress.sinon.match({ diff --git a/test/cypress/tests/api/blocks.cy.ts b/test/cypress/tests/api/blocks.cy.ts index ab97b8b5..fc3e4bcc 100644 --- a/test/cypress/tests/api/blocks.cy.ts +++ b/test/cypress/tests/api/blocks.cy.ts @@ -1,5 +1,6 @@ import type EditorJS from '../../../../types/index'; -import type { ConversionConfig, ToolboxConfig, ToolConfig } from '../../../../types'; +import type { ConversionConfig, ToolboxConfig, ToolConfig, API, BlockAPI } from '../../../../types'; +import type { BlockTuneData } from '../../../../types/block-tunes/block-tune-data'; import ToolMock, { type MockToolData } from '../../fixtures/tools/ToolMock'; import { nanoid } from 'nanoid'; @@ -36,7 +37,7 @@ describe('api.blocks', () => { const block = editor.blocks.getById(firstBlock.id); expect(block).not.to.be.undefined; - expect(block.id).to.be.eq(firstBlock.id); + expect(block?.id).to.be.eq(firstBlock.id); }); }); @@ -108,18 +109,17 @@ describe('api.blocks', () => { * Example Tune Class */ class ExampleTune { - protected data: object; + protected data: BlockTuneData | null | undefined; /** * - * @param data + * @param config - Block Tune config */ - constructor({ data }) { + constructor({ data }: { api: API; config?: ToolConfig; block: BlockAPI; data: BlockTuneData }) { this.data = data; } /** * Tell editor.js that this Tool is a Block Tune - * * @returns {boolean} */ public static get isTune(): boolean { @@ -128,10 +128,9 @@ describe('api.blocks', () => { /** * Create Tunes controls wrapper that will be appended to the Block Tunes panel - * - * @returns {Element} + * @returns {HTMLElement} */ - public render(): Element { + public render(): HTMLElement { return document.createElement('div'); } @@ -144,11 +143,10 @@ describe('api.blocks', () => { /** * Returns Tune state - * - * @returns {string} + * @returns {BlockTuneData} */ - public save(): object | string { - return this.data || ''; + public save(): BlockTuneData { + return this.data ?? ''; } } @@ -178,7 +176,12 @@ describe('api.blocks', () => { // Check if it is updated cy.get('@editorInstance') .then(async (editor) => { - await editor.blocks.update(editor.blocks.getBlockByIndex(0).id, null, { + const block = editor.blocks.getBlockByIndex(0); + + if (!block) { + throw new Error('Block at index 0 not found'); + } + await editor.blocks.update(block.id, undefined, { exampleTune: 'test', }); const data = await editor.save(); @@ -481,6 +484,8 @@ describe('api.blocks', () => { export: (data) => data, /** * Passed config should be returned + * @param _content - The content string (unused in this implementation) + * @param config - The tool configuration object */ import: (_content, config) => { return { text: JSON.stringify(config) }; diff --git a/test/cypress/tests/api/caret.cy.ts b/test/cypress/tests/api/caret.cy.ts index 53a7d1fc..3eb60e49 100644 --- a/test/cypress/tests/api/caret.cy.ts +++ b/test/cypress/tests/api/caret.cy.ts @@ -40,6 +40,10 @@ describe('Caret API', () => { cy.window() .then((window) => { const selection = window.getSelection(); + + if (!selection) { + throw new Error('Selection not found'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -65,6 +69,10 @@ describe('Caret API', () => { cy.window() .then((window) => { const selection = window.getSelection(); + + if (!selection) { + throw new Error('Selection not found'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -83,6 +91,10 @@ describe('Caret API', () => { cy.get('@editorInstance') .then(async (editor) => { const block = editor.blocks.getById(paragraphDataMock.id); + + if (!block) { + throw new Error('Block not found'); + } const returnedValue = editor.caret.setToBlock(block); /** @@ -91,6 +103,10 @@ describe('Caret API', () => { cy.window() .then((window) => { const selection = window.getSelection(); + + if (!selection) { + throw new Error('Selection not found'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') diff --git a/test/cypress/tests/api/toolbar.cy.ts b/test/cypress/tests/api/toolbar.cy.ts index 41da27b1..024844a3 100644 --- a/test/cypress/tests/api/toolbar.cy.ts +++ b/test/cypress/tests/api/toolbar.cy.ts @@ -28,7 +28,7 @@ describe('api.toolbar', () => { }); afterEach(function () { - if (this.editorInstance) { + if (this.editorInstance != null) { this.editorInstance.destroy(); } }); diff --git a/test/cypress/tests/api/tools.cy.ts b/test/cypress/tests/api/tools.cy.ts index be6a52c3..09b6f62e 100644 --- a/test/cypress/tests/api/tools.cy.ts +++ b/test/cypress/tests/api/tools.cy.ts @@ -90,12 +90,12 @@ describe('Editor Tools Api', () => { cy.get('[data-cy=editorjs]') .get('.ce-popover-item[data-item-name=testTool]') .first() - .should('contain.text', TestTool.toolbox[0].title); + .should('contain.text', (TestTool.toolbox as ToolboxConfigEntry[])[0].title); cy.get('[data-cy=editorjs]') .get('.ce-popover-item[data-item-name=testTool]') .last() - .should('contain.text', TestTool.toolbox[1].title); + .should('contain.text', (TestTool.toolbox as ToolboxConfigEntry[])[1].title); }); it('should insert block with overridden data on entry click in case toolbox entry provides data overrides', () => { @@ -114,10 +114,9 @@ describe('Editor Tools Api', () => { /** * Tool constructor - * * @param data - previously saved data */ - constructor({ data }) { + constructor({ data }: { data: { testProp: string } }) { this._data = data; } @@ -147,7 +146,6 @@ describe('Editor Tools Api', () => { /** * Extracts Tool's data from the view - * * @param el - tool view */ public save(el: HTMLElement): BlockToolData { diff --git a/test/cypress/tests/i18n.cy.ts b/test/cypress/tests/i18n.cy.ts index 1bcb6b4c..b87377ed 100644 --- a/test/cypress/tests/i18n.cy.ts +++ b/test/cypress/tests/i18n.cy.ts @@ -4,7 +4,7 @@ import type { ToolboxConfig } from '../../../types'; describe('Editor i18n', () => { context('Toolbox', () => { it('should translate tool title in a toolbox', function () { - if (this && this.editorInstance) { + if (this != null && this.editorInstance != null) { this.editorInstance.destroy(); } const toolNamesDictionary = { @@ -36,7 +36,7 @@ describe('Editor i18n', () => { }); it('should translate titles of toolbox entries', function () { - if (this && this.editorInstance) { + if (this != null && this.editorInstance != null) { this.editorInstance.destroy(); } const toolNamesDictionary = { @@ -96,7 +96,7 @@ describe('Editor i18n', () => { }); it('should use capitalized tool name as translation key if toolbox title is missing', function () { - if (this && this.editorInstance) { + if (this != null && this.editorInstance != null) { this.editorInstance.destroy(); } diff --git a/test/cypress/tests/initialization.cy.ts b/test/cypress/tests/initialization.cy.ts index 2336595f..b13f0e26 100644 --- a/test/cypress/tests/initialization.cy.ts +++ b/test/cypress/tests/initialization.cy.ts @@ -13,7 +13,7 @@ describe('Editor basic initialization', () => { }); afterEach(function () { - if (this.editorInstance) { + if (this.editorInstance != null) { this.editorInstance.destroy(); } }); @@ -30,8 +30,8 @@ describe('Editor basic initialization', () => { describe('Configuration', () => { describe('readOnly', () => { - beforeEach(() => { - if (this && this.editorInstance) { + beforeEach(function () { + if (this.editorInstance != null) { this.editorInstance.destroy(); } }); diff --git a/test/cypress/tests/modules/BlockEvents/ArrowLeft.cy.ts b/test/cypress/tests/modules/BlockEvents/ArrowLeft.cy.ts index f9081a37..72a660b9 100644 --- a/test/cypress/tests/modules/BlockEvents/ArrowLeft.cy.ts +++ b/test/cypress/tests/modules/BlockEvents/ArrowLeft.cy.ts @@ -23,6 +23,9 @@ describe('Arrow Left', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -52,6 +55,9 @@ describe('Arrow Left', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -82,6 +88,9 @@ describe('Arrow Left', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -112,6 +121,9 @@ describe('Arrow Left', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -143,6 +155,9 @@ describe('Arrow Left', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -174,6 +189,9 @@ describe('Arrow Left', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -266,6 +284,9 @@ describe('Arrow Left', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') diff --git a/test/cypress/tests/modules/BlockEvents/ArrowRight.cy.ts b/test/cypress/tests/modules/BlockEvents/ArrowRight.cy.ts index ee9f8847..b6eab7ed 100644 --- a/test/cypress/tests/modules/BlockEvents/ArrowRight.cy.ts +++ b/test/cypress/tests/modules/BlockEvents/ArrowRight.cy.ts @@ -25,6 +25,9 @@ describe('Arrow Right', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -57,6 +60,9 @@ describe('Arrow Right', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -89,6 +95,9 @@ describe('Arrow Right', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -121,6 +130,9 @@ describe('Arrow Right', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -154,6 +166,9 @@ describe('Arrow Right', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -187,6 +202,9 @@ describe('Arrow Right', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -279,6 +297,9 @@ describe('Arrow Right', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') diff --git a/test/cypress/tests/modules/BlockEvents/Backspace.cy.ts b/test/cypress/tests/modules/BlockEvents/Backspace.cy.ts index bb36e650..9a986acd 100644 --- a/test/cypress/tests/modules/BlockEvents/Backspace.cy.ts +++ b/test/cypress/tests/modules/BlockEvents/Backspace.cy.ts @@ -239,6 +239,9 @@ describe('Backspace keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('@firstInput').should(($div) => { @@ -330,13 +333,16 @@ describe('Backspace keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') .find('.ce-paragraph') .should(($block) => { expect($block[0].contains(range.startContainer)).to.be.true; - expect(range.startOffset).to.be.eq($block[0].textContent.length); + expect(range.startOffset).to.be.eq($block[0].textContent?.length ?? 0); }); }); @@ -395,6 +401,9 @@ describe('Backspace keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -461,6 +470,9 @@ describe('Backspace keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -526,6 +538,9 @@ describe('Backspace keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -607,6 +622,9 @@ describe('Backspace keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('@firstBlock').should(($div) => { @@ -690,6 +708,9 @@ describe('Backspace keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('@firstBlock').should(($div) => { @@ -700,7 +721,7 @@ describe('Backspace keydown', function () { describe('at the start of the first Block', function () { it('should do nothing if Block is not empty', function () { - createEditorWithTextBlocks(['The only block. Not empty']); + createEditorWithTextBlocks([ 'The only block. Not empty' ]); cy.get('[data-cy=editorjs]') .find('.ce-paragraph') diff --git a/test/cypress/tests/modules/BlockEvents/Delete.cy.ts b/test/cypress/tests/modules/BlockEvents/Delete.cy.ts index 816649c5..deba4b6c 100644 --- a/test/cypress/tests/modules/BlockEvents/Delete.cy.ts +++ b/test/cypress/tests/modules/BlockEvents/Delete.cy.ts @@ -124,7 +124,6 @@ describe('Delete keydown', function () { * - Firefox merge blocks and with whitespace - "1 2" * * So, we have to check both variants. - * * @todo remove this check after fixing the Firefox merge behaviour */ .should(($block) => { @@ -232,6 +231,9 @@ describe('Delete keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('@secondInput').should(($div) => { @@ -323,6 +325,9 @@ describe('Delete keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -387,6 +392,9 @@ describe('Delete keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('[data-cy=editorjs]') @@ -466,6 +474,9 @@ describe('Delete keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('@secondBlock').should(($div) => { diff --git a/test/cypress/tests/modules/BlockEvents/Enter.cy.ts b/test/cypress/tests/modules/BlockEvents/Enter.cy.ts index 3dde743e..2613a0ad 100644 --- a/test/cypress/tests/modules/BlockEvents/Enter.cy.ts +++ b/test/cypress/tests/modules/BlockEvents/Enter.cy.ts @@ -63,6 +63,9 @@ describe('Enter keydown', function () { cy.window() .then((window) => { const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('Selection is null or has no ranges'); + } const range = selection.getRangeAt(0); cy.get('@lastBlock').should(($block) => { diff --git a/test/cypress/tests/modules/BlockEvents/Tab.cy.ts b/test/cypress/tests/modules/BlockEvents/Tab.cy.ts index bb2051cf..aead6f4a 100644 --- a/test/cypress/tests/modules/BlockEvents/Tab.cy.ts +++ b/test/cypress/tests/modules/BlockEvents/Tab.cy.ts @@ -81,7 +81,13 @@ describe('Tab keydown', function () { .last() .then(($secondBlock) => { const editorWindow = $secondBlock.get(0).ownerDocument.defaultView; + if (!editorWindow) { + throw new Error('Window is not available'); + } const selection = editorWindow.getSelection(); + if (!selection) { + throw new Error('Selection is not available'); + } const range = selection.getRangeAt(0); @@ -127,7 +133,13 @@ describe('Tab keydown', function () { .last() .then(($secondInput) => { const editorWindow = $secondInput.get(0).ownerDocument.defaultView; + if (!editorWindow) { + throw new Error('Window is not available'); + } const selection = editorWindow.getSelection(); + if (!selection) { + throw new Error('Selection is not available'); + } const range = selection.getRangeAt(0); @@ -240,7 +252,13 @@ describe('Shift+Tab keydown', function () { .first() .then(($firstBlock) => { const editorWindow = $firstBlock.get(0).ownerDocument.defaultView; + if (!editorWindow) { + throw new Error('Window is not available'); + } const selection = editorWindow.getSelection(); + if (!selection) { + throw new Error('Selection is not available'); + } const range = selection.getRangeAt(0); @@ -289,7 +307,13 @@ describe('Shift+Tab keydown', function () { .first() .then(($firstInput) => { const editorWindow = $firstInput.get(0).ownerDocument.defaultView; + if (!editorWindow) { + throw new Error('Window is not available'); + } const selection = editorWindow.getSelection(); + if (!selection) { + throw new Error('Selection is not available'); + } const range = selection.getRangeAt(0); diff --git a/test/cypress/tests/modules/InlineToolbar.cy.ts b/test/cypress/tests/modules/InlineToolbar.cy.ts index c68cd039..7bddc33a 100644 --- a/test/cypress/tests/modules/InlineToolbar.cy.ts +++ b/test/cypress/tests/modules/InlineToolbar.cy.ts @@ -1,6 +1,6 @@ import Header from '@editorjs/header'; import NestedEditor, { NESTED_EDITOR_ID } from '../../support/utils/nestedEditorInstance'; -import type { MenuConfig } from '@/types/tools'; +import type { MenuConfig, ToolConstructable } from '@/types/tools'; describe('Inline Toolbar', () => { it('should appear aligned with left coord of selection rect', () => { @@ -25,12 +25,21 @@ describe('Inline Toolbar', () => { .should('be.visible') .then(($toolbar) => { const editorWindow = $toolbar.get(0).ownerDocument.defaultView; + + if (!editorWindow) { + throw new Error('Unable to access window from toolbar element'); + } + const selection = editorWindow.getSelection(); + if (!selection || selection.rangeCount === 0) { + throw new Error('No selection available'); + } + const range = selection.getRangeAt(0); const rect = range.getBoundingClientRect(); - expect($toolbar.offset().left).to.be.closeTo(rect.left, 1); + expect($toolbar.offset()?.left).to.be.closeTo(rect.left, 1); }); }); @@ -70,10 +79,17 @@ describe('Inline Toolbar', () => { .then(($blockWrapper) => { const blockWrapperRect = $blockWrapper.get(0).getBoundingClientRect(); + const toolbarOffset = $toolbar.offset(); + const toolbarWidth = $toolbar.width(); + + if (!toolbarOffset || toolbarWidth === undefined) { + throw new Error('Unable to get toolbar offset or width'); + } + /** * Toolbar should be aligned with right side of text column */ - expect($toolbar.offset().left + $toolbar.width()).to.closeTo(blockWrapperRect.right, 10); + expect(toolbarOffset.left + toolbarWidth).to.closeTo(blockWrapperRect.right, 10); }); }); }); @@ -82,7 +98,7 @@ describe('Inline Toolbar', () => { cy.createEditor({ tools: { header: { - class: Header, + class: Header as unknown as ToolConstructable, inlineToolbar: ['bold', 'testTool'], }, testTool: { @@ -90,6 +106,10 @@ describe('Inline Toolbar', () => { public static isInline = true; public static isReadOnlySupported = true; // eslint-disable-next-line jsdoc/require-jsdoc + public constructor() { + // Constructor required for InlineToolConstructable + } + // eslint-disable-next-line jsdoc/require-jsdoc public render(): MenuConfig { return { title: 'Test Tool', @@ -98,7 +118,7 @@ describe('Inline Toolbar', () => { onActivate: () => {}, }; } - }, + } as unknown as ToolConstructable, }, }, readOnly: true, @@ -154,7 +174,13 @@ describe('Inline Toolbar', () => { doc.body.appendChild(form); /* Move editor to form */ - form.appendChild(doc.getElementById('editorjs')); + const editorElement = doc.getElementById('editorjs'); + + if (!editorElement) { + throw new Error('Editor element not found'); + } + + form.appendChild(editorElement); cy.get('[data-cy=editorjs]') .find('.ce-paragraph') @@ -172,7 +198,7 @@ describe('Inline Toolbar', () => { cy.createEditor({ tools: { header: { - class: Header, + class: Header as unknown as ToolConstructable, }, }, data: { @@ -207,9 +233,13 @@ describe('Inline Toolbar', () => { .then((window) => { const selection = window.getSelection(); - expect(selection.rangeCount).to.be.equal(1); + expect(selection?.rangeCount).to.be.equal(1); - const range = selection.getRangeAt(0); + const range = selection?.getRangeAt(0); + + if (!range) { + throw new Error('No range available'); + } cy.get('[data-cy=editorjs]') .find('.ce-header') diff --git a/test/cypress/tests/modules/Renderer.cy.ts b/test/cypress/tests/modules/Renderer.cy.ts index e81271e6..d9bed8ef 100644 --- a/test/cypress/tests/modules/Renderer.cy.ts +++ b/test/cypress/tests/modules/Renderer.cy.ts @@ -1,5 +1,6 @@ -import ToolMock from '../../fixtures/tools/ToolMock'; +import ToolMock, { type MockToolData } from '../../fixtures/tools/ToolMock'; import type EditorJS from '../../../../types/index'; +import type { BlockToolConstructorOptions } from '../../../../types'; describe('Renderer module', function () { it('should not cause onChange firing during initial rendering', function () { @@ -90,7 +91,7 @@ describe('Renderer module', function () { /** * @param options - tool options */ - constructor(options) { + constructor(options: BlockToolConstructorOptions) { super(options); throw new Error('Tool error'); } diff --git a/test/cypress/tests/modules/Tools.cy.ts b/test/cypress/tests/modules/Tools.cy.ts index b4d7770b..11fadcc2 100644 --- a/test/cypress/tests/modules/Tools.cy.ts +++ b/test/cypress/tests/modules/Tools.cy.ts @@ -11,7 +11,6 @@ describe('Tools module', () => { /** * Construct Tools module for testing purposes - * * @param config - Editor config */ function constructModule(config: EditorConfig = defaultConfig): Tools { @@ -238,53 +237,53 @@ describe('Tools module', () => { it('Block Tools should contain default tunes if no settings is specified', () => { const tool = module.blockTools.get('blockToolWithoutSettings'); - expect(tool.tunes.has('delete')).to.be.true; - expect(tool.tunes.has('moveUp')).to.be.true; - expect(tool.tunes.has('moveDown')).to.be.true; + expect(tool?.tunes.has('delete')).to.be.true; + expect(tool?.tunes.has('moveUp')).to.be.true; + expect(tool?.tunes.has('moveDown')).to.be.true; }); it('Block Tools should contain default tunes', () => { const tool = module.blockTools.get('blockTool'); - expect(tool.tunes.has('delete')).to.be.true; - expect(tool.tunes.has('moveUp')).to.be.true; - expect(tool.tunes.has('moveDown')).to.be.true; + expect(tool?.tunes.has('delete')).to.be.true; + expect(tool?.tunes.has('moveUp')).to.be.true; + expect(tool?.tunes.has('moveDown')).to.be.true; }); it('Block Tools should contain tunes in correct order', () => { let tool = module.blockTools.get('blockTool'); - expect(tool.tunes.has('blockTune')).to.be.true; - expect(tool.tunes.has('blockTune2')).to.be.true; - expect(Array.from(tool.tunes.keys())).to.be.deep.eq(['blockTune2', 'blockTune', 'moveUp', 'delete', 'moveDown']); + expect(tool?.tunes.has('blockTune')).to.be.true; + expect(tool?.tunes.has('blockTune2')).to.be.true; + expect(Array.from(tool?.tunes.keys() ?? [])).to.be.deep.eq(['blockTune2', 'blockTune', 'moveUp', 'delete', 'moveDown']); tool = module.blockTools.get('withSuccessfulPrepare'); - expect(tool.tunes.has('blockTune')).to.be.false; - expect(tool.tunes.has('blockTune2')).to.be.true; + expect(tool?.tunes.has('blockTune')).to.be.false; + expect(tool?.tunes.has('blockTune2')).to.be.true; tool = module.blockTools.get('withoutPrepare'); - expect(tool.tunes.has('blockTune')).to.be.false; - expect(tool.tunes.has('blockTune2')).to.be.false; + expect(tool?.tunes.has('blockTune')).to.be.false; + expect(tool?.tunes.has('blockTune2')).to.be.false; }); it('Block Tools should contain inline tools in correct order', () => { let tool = module.blockTools.get('blockTool'); - expect(tool.inlineTools.has('inlineTool')).to.be.true; - expect(tool.inlineTools.has('inlineTool2')).to.be.true; - expect(Array.from(tool.inlineTools.keys())).to.be.deep.eq(['inlineTool2', 'inlineTool']); + expect(tool?.inlineTools.has('inlineTool')).to.be.true; + expect(tool?.inlineTools.has('inlineTool2')).to.be.true; + expect(Array.from(tool?.inlineTools.keys() ?? [])).to.be.deep.eq(['inlineTool2', 'inlineTool']); tool = module.blockTools.get('withSuccessfulPrepare'); - expect(tool.inlineTools.has('inlineTool')).to.be.false; - expect(tool.inlineTools.has('inlineTool2')).to.be.true; + expect(tool?.inlineTools.has('inlineTool')).to.be.false; + expect(tool?.inlineTools.has('inlineTool2')).to.be.true; tool = module.blockTools.get('withoutPrepare'); - expect(tool.inlineTools.has('inlineTool')).to.be.false; - expect(tool.inlineTools.has('inlineTool2')).to.be.false; + expect(tool?.inlineTools.has('inlineTool')).to.be.false; + expect(tool?.inlineTools.has('inlineTool2')).to.be.false; }); }); diff --git a/test/cypress/tests/onchange.cy.ts b/test/cypress/tests/onchange.cy.ts index 6727aea8..a368ccdd 100644 --- a/test/cypress/tests/onchange.cy.ts +++ b/test/cypress/tests/onchange.cy.ts @@ -7,6 +7,7 @@ import { BlockChangedMutationType } from '../../../types/events/block/BlockChang import { BlockRemovedMutationType } from '../../../types/events/block/BlockRemoved'; import { BlockMovedMutationType } from '../../../types/events/block/BlockMoved'; import type EditorJS from '../../../types/index'; +import type { API, OutputData, BlockMutationEvent } from '../../../types/index'; import { modificationsObserverBatchTimeout } from '../../../src/components/constants'; /** @@ -22,21 +23,20 @@ const EditorJSApiMock = Cypress.sinon.match.any; describe('onChange callback', () => { /** * Creates Editor instance - * * @param blocks - list of blocks to prefill the editor */ - function createEditor(blocks = null): void { + function createEditor(blocks?: OutputData['blocks']): void { const config = { tools: { header: Header, code: Code, }, - onChange: (api, event): void => { + onChange: (api: API, event: BlockMutationEvent | BlockMutationEvent[]): void => { console.log('something changed', event); }, data: blocks ? { blocks, - } : null, + } : undefined, }; cy.spy(config, 'onChange').as('onChange'); @@ -46,23 +46,22 @@ describe('onChange callback', () => { /** * Creates Editor instance with save inside the onChange event. - * * @param blocks - list of blocks to prefill the editor */ - function createEditorWithSave(blocks = null): void { + function createEditorWithSave(blocks?: OutputData['blocks']): void { const config = { tools: { header: Header, code: Code, delimiter: Delimiter, }, - onChange: (api, event): void => { + onChange: (api: API, event: BlockMutationEvent | BlockMutationEvent[]): void => { console.log('something changed', event); api.saver.save(); }, data: blocks ? { blocks, - } : null, + } : undefined, }; cy.spy(config, 'onChange').as('onChange'); @@ -515,7 +514,7 @@ describe('onChange callback', () => { tools: { testTool: ToolWithMutationFreeAttribute, }, - onChange: (api, event): void => { + onChange: (api: API, event: BlockMutationEvent | BlockMutationEvent[]): void => { console.log('something changed', event); }, data: { @@ -582,7 +581,7 @@ describe('onChange callback', () => { tools: { testTool: ToolWithMutationFreeAttribute, }, - onChange: (api, event): void => { + onChange: (api: API, event: BlockMutationEvent | BlockMutationEvent[]): void => { console.log('something changed', event); }, data: { @@ -652,7 +651,7 @@ describe('onChange callback', () => { tools: { testTool: ToolWithMutationFreeAttribute, }, - onChange: function (api, event) { + onChange: function (api: API, event: BlockMutationEvent | BlockMutationEvent[]): void { console.log('something changed!!!!!!!!', event); }, data: { @@ -767,7 +766,7 @@ describe('onChange callback', () => { block, ], }, - onChange: (api, event): void => { + onChange: (api: API, event: BlockMutationEvent | BlockMutationEvent[]): void => { console.log('something changed', event); }, }; @@ -852,7 +851,7 @@ describe('onChange callback', () => { it('should not be called when editor is initialized with readOnly mode', () => { const config = { readOnly: true, - onChange: (api, event): void => { + onChange: (api: API, event: BlockMutationEvent | BlockMutationEvent[]): void => { console.log('something changed', event); }, data: { diff --git a/test/cypress/tests/readOnly.cy.ts b/test/cypress/tests/readOnly.cy.ts index f1f8e496..090e1cfa 100644 --- a/test/cypress/tests/readOnly.cy.ts +++ b/test/cypress/tests/readOnly.cy.ts @@ -4,7 +4,6 @@ import type EditorJS from '../../../types'; describe('ReadOnly API spec', () => { /** * Creates the new editor instance - * * @param config - Editor Config */ function createEditor(config?: EditorConfig): void { diff --git a/test/cypress/tests/selection.cy.ts b/test/cypress/tests/selection.cy.ts index c9017351..3b5f533e 100644 --- a/test/cypress/tests/selection.cy.ts +++ b/test/cypress/tests/selection.cy.ts @@ -6,7 +6,7 @@ describe('Blocks selection', () => { }); afterEach(function () { - if (this.editorInstance) { + if (this.editorInstance != null) { this.editorInstance.destroy(); } }); diff --git a/test/cypress/tests/tools/BlockTool.cy.ts b/test/cypress/tests/tools/BlockTool.cy.ts index f1ec1924..a4f54dd2 100644 --- a/test/cypress/tests/tools/BlockTool.cy.ts +++ b/test/cypress/tests/tools/BlockTool.cy.ts @@ -37,8 +37,8 @@ describe('BlockTool', () => { public static isReadOnlySupported = true; - public static reset; - public static prepare; + public static reset?: () => void | Promise; + public static prepare?: (data: {toolName: string, config: ToolSettings}) => void | Promise; public static shortcut = 'CTRL+N'; @@ -49,7 +49,7 @@ describe('BlockTool', () => { public config: ToolSettings; // eslint-disable-next-line jsdoc/require-jsdoc - constructor({ data, block, readOnly, api, config }) { + constructor({ data, block, readOnly, api, config }: { data: BlockToolData; block: object; readOnly: boolean; api: object; config: ToolSettings }) { this.data = data; this.block = block; this.readonly = readOnly; @@ -157,8 +157,8 @@ describe('BlockTool', () => { // tslint:disable-next-line:forin for (const key in expected) { - expected[key] = { - ...expected[key], + (expected as Record>)[key] = { + ...expected[key as keyof typeof expected], b: true, }; } @@ -493,7 +493,7 @@ describe('BlockTool', () => { const expected = userDefinedToolboxConfig.map((item, i) => { const toolToolboxEntry = toolboxEntries[i]; - if (toolToolboxEntry) { + if (toolToolboxEntry !== undefined) { return { ...toolToolboxEntry, ...item, diff --git a/test/cypress/tests/tools/BlockTune.cy.ts b/test/cypress/tests/tools/BlockTune.cy.ts index c0001b4a..70c49cdc 100644 --- a/test/cypress/tests/tools/BlockTune.cy.ts +++ b/test/cypress/tests/tools/BlockTune.cy.ts @@ -12,8 +12,8 @@ describe('BlockTune', () => { const options = { name: 'blockTune', constructable: class { - public static reset; - public static prepare; + public static reset?: () => void | Promise; + public static prepare?: (data: {toolName: string, config: ToolSettings}) => void | Promise; public api: object; public config: ToolSettings; @@ -21,7 +21,7 @@ describe('BlockTune', () => { public block: object; // eslint-disable-next-line jsdoc/require-jsdoc - constructor({ api, config, block, data }) { + constructor({ api, config, block, data }: { api: object; config: ToolSettings; block: object; data: BlockTuneData }) { this.api = api; this.config = config; this.block = block; diff --git a/test/cypress/tests/tools/InlineTool.cy.ts b/test/cypress/tests/tools/InlineTool.cy.ts index e220f210..303f7774 100644 --- a/test/cypress/tests/tools/InlineTool.cy.ts +++ b/test/cypress/tests/tools/InlineTool.cy.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* tslint:disable:max-classes-per-file */ -import type { ToolSettings } from '@/types'; +import type { ToolSettings, ToolConfig } from '@/types'; import { ToolType } from '@/types/tools/adapters/tool-type'; import InlineToolAdapter from '../../../../src/components/tools/inline'; @@ -17,8 +17,8 @@ describe('InlineTool', () => { public static title = 'Title'; - public static reset; - public static prepare; + public static reset?: () => void | Promise; + public static prepare?: (data: {toolName: string, config: ToolConfig}) => void | Promise; public static shortcut = 'CTRL+N'; public static isReadOnlySupported = true; @@ -31,7 +31,7 @@ describe('InlineTool', () => { * @param options.api - EditorAPI * @param options.config - tool config */ - constructor({ api, config }) { + constructor({ api, config }: { api: object; config: ToolSettings }) { this.api = api; this.config = config; } diff --git a/test/cypress/tests/tools/ToolsCollection.cy.ts b/test/cypress/tests/tools/ToolsCollection.cy.ts index b6dcb37e..48ede5f3 100644 --- a/test/cypress/tests/tools/ToolsCollection.cy.ts +++ b/test/cypress/tests/tools/ToolsCollection.cy.ts @@ -43,7 +43,7 @@ const FakeBlockTune = { * Unit tests for ToolsCollection class */ describe('ToolsCollection', (): void => { - let collection; + let collection: ToolsCollection; /** * Mock for Tools in collection diff --git a/test/cypress/tests/tools/ToolsFactory.cy.ts b/test/cypress/tests/tools/ToolsFactory.cy.ts index 6bb5aed9..c22628ab 100644 --- a/test/cypress/tests/tools/ToolsFactory.cy.ts +++ b/test/cypress/tests/tools/ToolsFactory.cy.ts @@ -6,12 +6,14 @@ import InlineToolAdapter from '../../../../src/components/tools/inline'; import BlockToolAdapter from '../../../../src/components/tools/block'; import BlockTuneAdapter from '../../../../src/components/tools/tune'; import Paragraph from '@editorjs/paragraph'; +import type { BlockToolConstructable } from '../../../../types'; describe('ToolsFactory', (): void => { - let factory; + let factory: ToolsFactory; + const paragraphClass = Paragraph as unknown as BlockToolConstructable; const config = { paragraph: { - class: Paragraph, + class: paragraphClass, }, link: { class: LinkInlineTool, diff --git a/test/cypress/tests/ui/BlockTunes.cy.ts b/test/cypress/tests/ui/BlockTunes.cy.ts index 43d7e0e5..1a90b483 100644 --- a/test/cypress/tests/ui/BlockTunes.cy.ts +++ b/test/cypress/tests/ui/BlockTunes.cy.ts @@ -33,9 +33,13 @@ describe('BlockTunes', function () { .then((window) => { const selection = window.getSelection(); - expect(selection.rangeCount).to.be.equal(1); + expect(selection?.rangeCount).to.be.equal(1); - const range = selection.getRangeAt(0); + const range = selection?.getRangeAt(0); + + if (!range) { + throw new Error('Range is undefined'); + } cy.get('[data-cy=editorjs]') .find('[data-cy="block-tunes"] .cdx-search-field') @@ -369,7 +373,11 @@ describe('BlockTunes', function () { cy.window() .then((window) => { const selection = window.getSelection(); - const range = selection.getRangeAt(0); + const range = selection?.getRangeAt(0); + + if (!range) { + throw new Error('Range is undefined'); + } cy.get('[data-cy=editorjs]') .find('.ce-header') diff --git a/test/cypress/tests/ui/InlineToolbar.cy.ts b/test/cypress/tests/ui/InlineToolbar.cy.ts index 5c337b19..f7239420 100644 --- a/test/cypress/tests/ui/InlineToolbar.cy.ts +++ b/test/cypress/tests/ui/InlineToolbar.cy.ts @@ -1,5 +1,5 @@ import Header from '@editorjs/header'; -import type { InlineTool, MenuConfig } from '../../../../types/tools'; +import type { InlineTool, InlineToolConstructorOptions, MenuConfig, ToolConstructable } from '../../../../types/tools'; import { createEditorWithTextBlocks } from '../../support/utils/createEditorWithTextBlocks'; describe('Inline Toolbar', () => { @@ -8,7 +8,7 @@ describe('Inline Toolbar', () => { cy.createEditor({ tools: { header: { - class: Header, + class: Header as unknown as ToolConstructable, }, }, data: { @@ -46,14 +46,18 @@ describe('Inline Toolbar', () => { cy.createEditor({ tools: { header: { - class: Header, + class: Header as unknown as ToolConstructable, inlineToolbar: ['bold', 'testTool', 'link'], }, testTool: { - class: class { + class: class TestTool { public static isInline = true; // eslint-disable-next-line jsdoc/require-jsdoc + public constructor(_config: InlineToolConstructorOptions) { + // Constructor required by InlineToolConstructable + } + // eslint-disable-next-line jsdoc/require-jsdoc public render(): MenuConfig { return { icon: 'n', @@ -71,7 +75,7 @@ describe('Inline Toolbar', () => { }, }; } - }, + } as unknown as ToolConstructable, }, }, data: { @@ -115,14 +119,18 @@ describe('Inline Toolbar', () => { cy.createEditor({ tools: { header: { - class: Header, + class: Header as unknown as ToolConstructable, inlineToolbar: ['bold', 'testTool'], }, testTool: { - class: class { + class: class TestTool { public static isInline = true; // eslint-disable-next-line jsdoc/require-jsdoc + public constructor(_config: InlineToolConstructorOptions) { + // Constructor required by InlineToolConstructable + } + // eslint-disable-next-line jsdoc/require-jsdoc public render(): MenuConfig { return { icon: 'n', @@ -140,7 +148,7 @@ describe('Inline Toolbar', () => { }, }; } - }, + } as unknown as ToolConstructable, }, }, data: { diff --git a/test/cypress/tests/ui/toolbox.cy.ts b/test/cypress/tests/ui/toolbox.cy.ts index 127b5090..e7a7e27b 100644 --- a/test/cypress/tests/ui/toolbox.cy.ts +++ b/test/cypress/tests/ui/toolbox.cy.ts @@ -61,7 +61,11 @@ describe('Toolbox', function () { cy.window() .then((window) => { const selection = window.getSelection(); - const range = selection.getRangeAt(0); + const range = selection?.getRangeAt(0); + + if (!range) { + throw new Error('Selection range is not available'); + } cy.get('[data-cy=editorjs]') .find(`.ce-block[data-id=${blocks[0].id}]`) diff --git a/test/cypress/tests/utils/popover.cy.ts b/test/cypress/tests/utils/popover.cy.ts index 5d42a492..6efac34d 100644 --- a/test/cypress/tests/utils/popover.cy.ts +++ b/test/cypress/tests/utils/popover.cy.ts @@ -1,6 +1,7 @@ import { PopoverDesktop as Popover, PopoverItemType } from '../../../../src/components/utils/popover'; import type { PopoverItemParams } from '@/types/utils/popover'; import type { MenuConfig } from '../../../../types/tools'; +import type { BlockToolConstructable } from '../../../../types/tools'; import Header from '@editorjs/header'; /* eslint-disable @typescript-eslint/no-empty-function */ @@ -975,7 +976,8 @@ describe('Popover', () => { cy.createEditor({ tools: { header: { - class: Header, + class: Header as unknown as BlockToolConstructable, + config: {}, }, }, data: { @@ -1020,7 +1022,8 @@ describe('Popover', () => { cy.createEditor({ tools: { header: { - class: Header, + class: Header as unknown as BlockToolConstructable, + config: {}, }, }, data: { @@ -1080,7 +1083,8 @@ describe('Popover', () => { cy.createEditor({ tools: { header: { - class: Header, + class: Header as unknown as BlockToolConstructable, + config: {}, }, }, data: { diff --git a/types/api/blocks.d.ts b/types/api/blocks.d.ts index f5957bba..06baafb6 100644 --- a/types/api/blocks.d.ts +++ b/types/api/blocks.d.ts @@ -70,7 +70,7 @@ export interface Blocks { /** * Returns the index of Block by id; */ - getBlockIndex(blockId: string): number; + getBlockIndex(blockId: string): number | undefined; /** * Get Block API object by html element diff --git a/types/block-tunes/block-tune.d.ts b/types/block-tunes/block-tune.d.ts index 2220a478..0e7b9d03 100644 --- a/types/block-tunes/block-tune.d.ts +++ b/types/block-tunes/block-tune.d.ts @@ -45,6 +45,11 @@ export interface BlockTuneConstructable { */ sanitize?: SanitizerConfig; + /** + * Shortcut for Tool + */ + shortcut?: string; + /** * @constructor * diff --git a/types/tools/tool.d.ts b/types/tools/tool.d.ts index 17aa0f2d..9cdb44e1 100644 --- a/types/tools/tool.d.ts +++ b/types/tools/tool.d.ts @@ -41,6 +41,11 @@ export interface BaseToolConstructable { */ sanitize?: SanitizerConfig; + /** + * Shortcut for Tool + */ + shortcut?: string; + /** * Title of Inline Tool. * @deprecated use {@link MenuConfig} item title instead