editor.js/test/cypress/support/commands.ts
Peter Savchenko c5854eea14
fix(inline-toolbar): appearance logic improved (#2550)
* fix(inline-toolbar): appearing logic improved

* tests added

* fix tests

* debounce added

* fix test build in github action

* increase closeTo delta for ff
2023-12-09 02:05:27 +03:00

237 lines
5.9 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* This file contains custom commands for Cypress.
* Also it can override the existing commands.
*
* --------------------------------------------------
*/
import type { EditorConfig, OutputData } from './../../../types/index';
import type EditorJS from '../../../types/index';
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
*/
Cypress.Commands.add('createEditor', (editorConfig: EditorConfig = {}): Chainable<EditorJS> => {
return cy.window()
.then((window) => {
return new Promise((resolve: (instance: EditorJS) => void) => {
const editorContainer = window.document.createElement('div');
editorContainer.setAttribute('id', 'editorjs');
editorContainer.dataset.cy = 'editorjs';
editorContainer.style.border = '1px dotted #388AE5';
window.document.body.appendChild(editorContainer);
const editorInstance: EditorJS = new window.EditorJS(editorConfig);
editorInstance.isReady.then(() => {
resolve(editorInstance);
});
});
});
});
/**
* Paste command to dispatch paste event
*
* Usage
* cy.get('div').paste({'text/plain': 'Text', 'text/html': '<b>Text</b>'})
*
* @param data - map with MIME type as a key and data as value
*/
Cypress.Commands.add('paste', {
prevSubject: true,
}, (subject, data: {[type: string]: string}) => {
const pasteEvent = Object.assign(new Event('paste', {
bubbles: true,
cancelable: true,
}), {
clipboardData: {
getData: (type): string => data[type],
types: Object.keys(data),
},
});
subject[0].dispatchEvent(pasteEvent);
cy.wait(200); // wait a little since some tools (paragraph) could have async hydration
});
/**
* Copy command to dispatch copy event on subject
*
* Usage:
* cy.get('div').copy().then(data => {})
*/
Cypress.Commands.add('copy', { prevSubject: true }, (subject) => {
const clipboardData: {[type: string]: any} = {};
const copyEvent = Object.assign(new Event('copy', {
bubbles: true,
cancelable: true,
}), {
clipboardData: {
setData: (type: string, data: any): void => {
clipboardData[type] = data;
},
},
});
subject[0].dispatchEvent(copyEvent);
return cy.wrap(clipboardData);
});
/**
* Cut command to dispatch cut event on subject
*
* Usage:
* cy.get('div').cut().then(data => {})
*/
Cypress.Commands.add('cut', { prevSubject: true }, (subject) => {
const clipboardData: {[type: string]: any} = {};
const copyEvent = Object.assign(new Event('cut', {
bubbles: true,
cancelable: true,
}), {
clipboardData: {
setData: (type: string, data: any): void => {
clipboardData[type] = data;
},
},
});
subject[0].dispatchEvent(copyEvent);
return cy.wrap(clipboardData);
});
/**
* Calls EditorJS API render method
*
* @param data — data to render
*/
Cypress.Commands.add('render', { prevSubject: true }, (subject: EditorJS, data: OutputData) => {
return cy.wrap(subject.render(data))
.then(() => {
return cy.wrap(subject);
});
});
/**
* Select passed text in element
* Note. Previous subject should have 'textNode' as firstChild
*
* Usage
* cy.get('[data-cy=editorjs]')
* .find('.ce-paragraph')
* .selectText('block te')
*
* @param text - text to select
*/
Cypress.Commands.add('selectText', {
prevSubject: true,
}, (subject, text: string) => {
const el = subject[0];
const document = el.ownerDocument;
const range = document.createRange();
const textNode = el.firstChild;
const selectionPositionStart = textNode.textContent.indexOf(text);
const selectionPositionEnd = selectionPositionStart + text.length;
range.setStart(textNode, selectionPositionStart);
range.setEnd(textNode, selectionPositionEnd);
document.getSelection().removeAllRanges();
document.getSelection().addRange(range);
return cy.wrap(subject);
});
/**
* Select element's text by offset
* Note. Previous subject should have 'textNode' as firstChild
*
* Usage
* cy.get('[data-cy=editorjs]')
* .find('.ce-paragraph')
* .selectTextByOffset([0, 5])
*
* @param offset - offset to select
*/
Cypress.Commands.add('selectTextByOffset', {
prevSubject: true,
}, (subject, offset: [number, number]) => {
const el = subject[0];
const document = el.ownerDocument;
const range = document.createRange();
const textNode = el.firstChild;
const selectionPositionStart = offset[0];
const selectionPositionEnd = offset[1];
range.setStart(textNode, selectionPositionStart);
range.setEnd(textNode, selectionPositionEnd);
document.getSelection().removeAllRanges();
document.getSelection().addRange(range);
return cy.wrap(subject);
});
/**
* Returns line wrap positions for passed element
*
* Usage
* cy.get('[data-cy=editorjs]')
* .find('.ce-paragraph')
* .getLineWrapPositions()
*
* @returns number[] - array of line wrap positions
*/
Cypress.Commands.add('getLineWrapPositions', {
prevSubject: true,
}, (subject) => {
const element = subject[0];
const document = element.ownerDocument;
const text = element.textContent;
const lineWraps = [];
let currentLineY = 0;
/**
* Iterate all chars in text, create range for each char and get its position
*/
for (let i = 0; i < text.length; i++) {
const range = document.createRange();
range.setStart(element.firstChild, i);
range.setEnd(element.firstChild, i);
const rect = range.getBoundingClientRect();
if (i === 0) {
currentLineY = rect.top;
continue;
}
/**
* If current char Y position is higher than previously saved line Y, that means a line wrap
*/
if (rect.top > currentLineY) {
lineWraps.push(i);
currentLineY = rect.top;
}
}
return cy.wrap(lineWraps);
});