2021-11-24 19:14:24 +01:00
|
|
|
import Header from '@editorjs/header';
|
|
|
|
import Image from '@editorjs/simple-image';
|
2021-04-08 21:19:49 +02:00
|
|
|
import * as _ from '../../../src/components/utils';
|
deps(TypeScript) - upgrade to v5, upgrade ts-loader, fix types error, fix pasteConfig getter wrapper (#2322)
* deps: upgrade typescript to v5, upgrade ts-loader to support newest TS
* Fix (??) type of `pasteConfig`
TypeScript 4.9 found something is wrong with this code, but it's unclear (to me) which line is wrong. This PR is a guess, do with it what you will.
In paste.ts there's a check to see if `pasteConfig === false`:
https://github.com/codex-team/editor.js/blob/next/src/components/modules/paste.ts#L287
However, this getter never returns false because if the LHS of the `||` is `false`, `{ }` is returned instead.
It seems like this meant to be `??` instead so that if `this.constructable[InternalBlockToolSettings.PasteConfig]` was `undefined` (missing), then `{}` would be returned instead. But maybe you meant `false` here - I don't know.
* feat: create alias for PasteConfig, fix lint
* fix: problems with types
* test: add case for disabling preventing default behavior of paste event handler, add cases for pasteConfig getter in BlockTool wrapper
* chore: upgrade CHANGELOG.md
* fix: interface naming convention
* chore: apply CHANGELOG.md suggestion
* refactor: create custom Editor instance inside test case
* fix: remove editor instance destroy after PR feedback
---------
Co-authored-by: Ryan Cavanaugh <RyanCavanaugh@users.noreply.github.com>
2023-04-02 17:52:42 +02:00
|
|
|
import EditorJS, { BlockTool, BlockToolData } from '../../../types';
|
|
|
|
import $ from '../../../src/components/dom';
|
2021-04-08 21:19:49 +02:00
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
describe('Copy pasting from Editor', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
cy.createEditor({
|
|
|
|
tools: {
|
|
|
|
header: Header,
|
|
|
|
image: Image,
|
|
|
|
},
|
|
|
|
}).as('editorInstance');
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(function () {
|
deps(TypeScript) - upgrade to v5, upgrade ts-loader, fix types error, fix pasteConfig getter wrapper (#2322)
* deps: upgrade typescript to v5, upgrade ts-loader to support newest TS
* Fix (??) type of `pasteConfig`
TypeScript 4.9 found something is wrong with this code, but it's unclear (to me) which line is wrong. This PR is a guess, do with it what you will.
In paste.ts there's a check to see if `pasteConfig === false`:
https://github.com/codex-team/editor.js/blob/next/src/components/modules/paste.ts#L287
However, this getter never returns false because if the LHS of the `||` is `false`, `{ }` is returned instead.
It seems like this meant to be `??` instead so that if `this.constructable[InternalBlockToolSettings.PasteConfig]` was `undefined` (missing), then `{}` would be returned instead. But maybe you meant `false` here - I don't know.
* feat: create alias for PasteConfig, fix lint
* fix: problems with types
* test: add case for disabling preventing default behavior of paste event handler, add cases for pasteConfig getter in BlockTool wrapper
* chore: upgrade CHANGELOG.md
* fix: interface naming convention
* chore: apply CHANGELOG.md suggestion
* refactor: create custom Editor instance inside test case
* fix: remove editor instance destroy after PR feedback
---------
Co-authored-by: Ryan Cavanaugh <RyanCavanaugh@users.noreply.github.com>
2023-04-02 17:52:42 +02:00
|
|
|
if (this.editorInstance && this.editorInstance.destroy) {
|
2021-04-08 21:19:49 +02:00
|
|
|
this.editorInstance.destroy();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
context('pasting', function () {
|
|
|
|
it('should paste plain text', function () {
|
2021-04-08 22:15:42 +02:00
|
|
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.paste({
|
2022-11-25 18:56:50 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
2021-04-08 21:19:49 +02:00
|
|
|
'text/plain': 'Some plain text',
|
|
|
|
})
|
2021-04-08 22:15:42 +02:00
|
|
|
.wait(0)
|
2021-04-08 21:19:49 +02:00
|
|
|
.should('contain', 'Some plain text');
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
it('should paste inline html data', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.paste({
|
2022-11-25 18:56:50 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
2021-04-08 21:19:49 +02:00
|
|
|
'text/html': '<p><b>Some text</b></p>',
|
|
|
|
})
|
|
|
|
.should('contain.html', '<b>Some text</b>');
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
it('should paste several blocks if plain text contains new lines', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.paste({
|
2022-11-25 18:56:50 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
2021-04-08 21:19:49 +02:00
|
|
|
'text/plain': 'First block\n\nSecond block',
|
|
|
|
});
|
|
|
|
|
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.then(blocks => {
|
|
|
|
expect(blocks[0].textContent).to.eq('First block');
|
|
|
|
expect(blocks[1].textContent).to.eq('Second block');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
it('should paste several blocks if html contains several paragraphs', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.paste({
|
2022-11-25 18:56:50 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
2021-04-08 21:19:49 +02:00
|
|
|
'text/html': '<p>First block</p><p>Second block</p>',
|
|
|
|
});
|
|
|
|
|
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.then(blocks => {
|
|
|
|
expect(blocks[0].textContent).to.eq('First block');
|
|
|
|
expect(blocks[1].textContent).to.eq('Second block');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
it('should paste using custom data type', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.paste({
|
2022-11-25 18:56:50 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
2021-04-08 21:19:49 +02:00
|
|
|
'application/x-editor-js': JSON.stringify([
|
|
|
|
{
|
|
|
|
tool: 'paragraph',
|
|
|
|
data: {
|
|
|
|
text: 'First block',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
tool: 'paragraph',
|
|
|
|
data: {
|
|
|
|
text: 'Second block',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
]),
|
|
|
|
});
|
|
|
|
|
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.then(blocks => {
|
|
|
|
expect(blocks[0].textContent).to.eq('First block');
|
|
|
|
expect(blocks[1].textContent).to.eq('Second block');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
it('should parse block tags', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.paste({
|
2022-11-25 18:56:50 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
2021-04-08 21:19:49 +02:00
|
|
|
'text/html': '<h2>First block</h2><p>Second block</p>',
|
|
|
|
});
|
|
|
|
|
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('h2.ce-header')
|
|
|
|
.should('contain', 'First block');
|
|
|
|
|
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-paragraph')
|
|
|
|
.should('contain', 'Second block');
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
it('should parse pattern', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.paste({
|
2022-11-25 18:56:50 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
2021-04-08 21:19:49 +02:00
|
|
|
'text/plain': 'https://codex.so/public/app/img/external/codex2x.png',
|
|
|
|
});
|
|
|
|
|
|
|
|
cy.get('[data-cy=editorjs]')
|
2021-04-27 15:33:00 +02:00
|
|
|
// In Edge test are performed slower, so we need to increase timeout to wait until image is loaded on the page
|
|
|
|
.get('img', { timeout: 10000 })
|
2021-04-08 21:19:49 +02:00
|
|
|
.should('have.attr', 'src', 'https://codex.so/public/app/img/external/codex2x.png');
|
|
|
|
});
|
deps(TypeScript) - upgrade to v5, upgrade ts-loader, fix types error, fix pasteConfig getter wrapper (#2322)
* deps: upgrade typescript to v5, upgrade ts-loader to support newest TS
* Fix (??) type of `pasteConfig`
TypeScript 4.9 found something is wrong with this code, but it's unclear (to me) which line is wrong. This PR is a guess, do with it what you will.
In paste.ts there's a check to see if `pasteConfig === false`:
https://github.com/codex-team/editor.js/blob/next/src/components/modules/paste.ts#L287
However, this getter never returns false because if the LHS of the `||` is `false`, `{ }` is returned instead.
It seems like this meant to be `??` instead so that if `this.constructable[InternalBlockToolSettings.PasteConfig]` was `undefined` (missing), then `{}` would be returned instead. But maybe you meant `false` here - I don't know.
* feat: create alias for PasteConfig, fix lint
* fix: problems with types
* test: add case for disabling preventing default behavior of paste event handler, add cases for pasteConfig getter in BlockTool wrapper
* chore: upgrade CHANGELOG.md
* fix: interface naming convention
* chore: apply CHANGELOG.md suggestion
* refactor: create custom Editor instance inside test case
* fix: remove editor instance destroy after PR feedback
---------
Co-authored-by: Ryan Cavanaugh <RyanCavanaugh@users.noreply.github.com>
2023-04-02 17:52:42 +02:00
|
|
|
|
|
|
|
it('should not prevent default behaviour if block\'s paste config equals false', function () {
|
|
|
|
/**
|
|
|
|
* Destroy default Editor to render custom one with different tools
|
|
|
|
*/
|
|
|
|
cy.get('@editorInstance')
|
|
|
|
.then((editorInstance: unknown) => (editorInstance as EditorJS).destroy());
|
|
|
|
|
|
|
|
const onPasteStub = cy.stub().as('onPaste');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tool with disabled preventing default behavior of onPaste event
|
|
|
|
*/
|
|
|
|
class BlockToolWithPasteHandler implements BlockTool {
|
|
|
|
public static pasteConfig = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render block
|
|
|
|
*/
|
|
|
|
public render(): HTMLElement {
|
|
|
|
const block = $.make('div', 'ce-block-with-disabled-prevent-default', {
|
|
|
|
contentEditable: 'true',
|
|
|
|
});
|
|
|
|
|
|
|
|
block.addEventListener('paste', onPasteStub);
|
|
|
|
|
|
|
|
return block;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save data method
|
|
|
|
*/
|
|
|
|
public save(): BlockToolData {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cy.createEditor({
|
|
|
|
tools: {
|
|
|
|
blockToolWithPasteHandler: BlockToolWithPasteHandler,
|
|
|
|
},
|
|
|
|
}).as('editorInstanceWithBlockToolWithPasteHandler');
|
|
|
|
|
|
|
|
cy.get('@editorInstanceWithBlockToolWithPasteHandler')
|
|
|
|
.render({
|
|
|
|
blocks: [
|
|
|
|
{
|
|
|
|
type: 'blockToolWithPasteHandler',
|
|
|
|
data: {},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
cy.get('@editorInstanceWithBlockToolWithPasteHandler')
|
|
|
|
.get('div.ce-block-with-disabled-prevent-default')
|
|
|
|
.click()
|
|
|
|
.paste({
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
|
|
'text/plain': 'Hello',
|
|
|
|
});
|
|
|
|
|
|
|
|
cy.get('@onPaste')
|
|
|
|
.should('have.been.calledWithMatch', {
|
|
|
|
defaultPrevented: false,
|
|
|
|
});
|
|
|
|
});
|
2021-04-08 21:19:49 +02:00
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
context('copying', function () {
|
|
|
|
it('should copy inline fragment', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.type('Some text{selectall}')
|
|
|
|
.copy()
|
|
|
|
.then(clipboardData => {
|
|
|
|
/**
|
|
|
|
* As no blocks selected, clipboard data will be empty as will be handled by browser
|
|
|
|
*/
|
|
|
|
expect(clipboardData).to.be.empty;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
it('should copy several blocks', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.type('First block{enter}');
|
|
|
|
|
|
|
|
cy.get('[data-cy=editorjs')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.next()
|
|
|
|
.type('Second block')
|
|
|
|
.type('{movetostart}')
|
|
|
|
.trigger('keydown', {
|
|
|
|
shiftKey: true,
|
|
|
|
keyCode: _.keyCodes.UP,
|
|
|
|
})
|
|
|
|
.copy()
|
|
|
|
.then(clipboardData => {
|
2021-04-08 22:15:42 +02:00
|
|
|
expect(clipboardData['text/html']).to.match(/<p>First block(<br>)?<\/p><p>Second block(<br>)?<\/p>/);
|
2021-04-08 21:19:49 +02:00
|
|
|
expect(clipboardData['text/plain']).to.eq(`First block\n\nSecond block`);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Need to wait for custom data as it is set asynchronously
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
2022-11-25 18:56:50 +01:00
|
|
|
cy.wait(0).then(function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
expect(clipboardData['application/x-editor-js']).not.to.be.undefined;
|
|
|
|
|
|
|
|
const data = JSON.parse(clipboardData['application/x-editor-js']);
|
|
|
|
|
|
|
|
expect(data[0].tool).to.eq('paragraph');
|
2021-04-08 22:15:42 +02:00
|
|
|
expect(data[0].data.text).to.match(/First block(<br>)?/);
|
2021-04-08 21:19:49 +02:00
|
|
|
expect(data[1].tool).to.eq('paragraph');
|
2021-04-08 22:15:42 +02:00
|
|
|
expect(data[1].data.text).to.match(/Second block(<br>)?/);
|
2021-04-08 21:19:49 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
context('cutting', function () {
|
|
|
|
it('should cut inline fragment', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.type('Some text{selectall}')
|
|
|
|
.cut()
|
|
|
|
.then(clipboardData => {
|
|
|
|
/**
|
|
|
|
* As no blocks selected, clipboard data will be empty as will be handled by browser
|
|
|
|
*/
|
|
|
|
expect(clipboardData).to.be.empty;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
it('should cut several blocks', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.click()
|
|
|
|
.type('First block{enter}');
|
|
|
|
|
|
|
|
cy.get('[data-cy=editorjs')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.next()
|
|
|
|
.type('Second block')
|
|
|
|
.type('{movetostart}')
|
|
|
|
.trigger('keydown', {
|
|
|
|
shiftKey: true,
|
|
|
|
keyCode: _.keyCodes.UP,
|
|
|
|
})
|
|
|
|
.cut()
|
|
|
|
.then(clipboardData => {
|
2021-04-08 22:15:42 +02:00
|
|
|
expect(clipboardData['text/html']).to.match(/<p>First block(<br>)?<\/p><p>Second block(<br>)?<\/p>/);
|
2021-04-08 21:19:49 +02:00
|
|
|
expect(clipboardData['text/plain']).to.eq(`First block\n\nSecond block`);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Need to wait for custom data as it is set asynchronously
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
2022-11-25 18:56:50 +01:00
|
|
|
cy.wait(0).then(function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
expect(clipboardData['application/x-editor-js']).not.to.be.undefined;
|
|
|
|
|
|
|
|
const data = JSON.parse(clipboardData['application/x-editor-js']);
|
|
|
|
|
|
|
|
expect(data[0].tool).to.eq('paragraph');
|
2021-04-08 22:15:42 +02:00
|
|
|
expect(data[0].data.text).to.match(/First block(<br>)?/);
|
2021-04-08 21:19:49 +02:00
|
|
|
expect(data[1].tool).to.eq('paragraph');
|
2021-04-08 22:15:42 +02:00
|
|
|
expect(data[1].data.text).to.match(/Second block(<br>)?/);
|
2021-04-08 21:19:49 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.should('not.contain', 'First block')
|
|
|
|
.should('not.contain', 'Second block');
|
|
|
|
});
|
|
|
|
|
2022-11-25 18:56:50 +01:00
|
|
|
it('should cut lots of blocks', function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
const numberOfBlocks = 50;
|
|
|
|
|
|
|
|
for (let i = 0; i < numberOfBlocks; i++) {
|
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.last()
|
|
|
|
.click()
|
|
|
|
.type(`Block ${i}{enter}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
cy.get('[data-cy=editorjs]')
|
|
|
|
.get('div.ce-block')
|
|
|
|
.first()
|
|
|
|
.click()
|
|
|
|
.type('{ctrl+A}')
|
|
|
|
.type('{ctrl+A}')
|
|
|
|
.cut()
|
|
|
|
.then((clipboardData) => {
|
|
|
|
/**
|
|
|
|
* Need to wait for custom data as it is set asynchronously
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
2022-11-25 18:56:50 +01:00
|
|
|
cy.wait(0).then(function () {
|
2021-04-08 21:19:49 +02:00
|
|
|
expect(clipboardData['application/x-editor-js']).not.to.be.undefined;
|
|
|
|
|
|
|
|
const data = JSON.parse(clipboardData['application/x-editor-js']);
|
|
|
|
|
|
|
|
expect(data.length).to.eq(numberOfBlocks + 1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|