editor.js/test/cypress/tests/modules/BlockEvents/Tab.cy.ts
Peter Savchenko cd29c52e51
feat(ui): native-like tab behaviour, slash for toolbox (#2569)
* slash to open toolbox, tab for navigation

* tab, focus improvements

- remove "focused" block state
- tab navigation respects inputs
- allow to focus contentless blocks

* fix tests

* tests for Slash

* tab tests

* test for tabbing out of editor

* tests fixed

* review fixes
2023-12-22 23:15:35 +03:00

371 lines
8.4 KiB
TypeScript

import ToolMock from '../../../fixtures/tools/ToolMock';
/**
* Mock of tool that contains two inputs
*/
class ToolWithTwoInputs extends ToolMock {
/**
* Create element with two inputs
*/
public render(): HTMLElement {
const wrapper = document.createElement('div');
const input1 = document.createElement('div');
const input2 = document.createElement('div');
input1.contentEditable = 'true';
input2.contentEditable = 'true';
wrapper.setAttribute('data-cy', 'tool-with-two-inputs');
wrapper.appendChild(input1);
wrapper.appendChild(input2);
return wrapper;
}
}
/**
* Mock of tool without inputs
*/
class ContentlessTool extends ToolMock {
public static contentless = true;
/**
* Create element without inputs
*/
public render(): HTMLElement {
const wrapper = document.createElement('div');
wrapper.setAttribute('data-cy', 'contentless-tool');
wrapper.textContent = '***';
return wrapper;
}
}
/**
* Time to wait for caret to finish moving
*/
const CARET_MOVE_TIME = 100;
describe('Tab keydown', function () {
it('should focus next Block if Block contains only one input', () => {
cy.createEditor({
data: {
blocks: [
{
type: 'paragraph',
data: {
text: 'first paragraph',
},
},
{
type: 'paragraph',
data: {
text: 'second paragraph',
},
},
],
},
});
cy.get('[data-cy=editorjs]')
.find('.ce-paragraph')
.first()
.click()
.trigger('keydown', { keyCode: 9 })
.wait(CARET_MOVE_TIME);
cy.get('[data-cy=editorjs]')
.find('.ce-paragraph')
.last()
.then(($secondBlock) => {
const editorWindow = $secondBlock.get(0).ownerDocument.defaultView;
const selection = editorWindow.getSelection();
const range = selection.getRangeAt(0);
/**
* Check that second block contains range
*/
expect(range.startContainer.parentElement).to.equal($secondBlock.get(0));
});
});
it('should focus next input if Block contains several inputs', () => {
cy.createEditor({
tools: {
toolWithTwoInputs: {
class: ToolWithTwoInputs,
},
},
data: {
blocks: [
{
type: 'toolWithTwoInputs',
data: {},
},
{
type: 'paragraph',
data: {
text: 'second paragraph',
},
},
],
},
});
cy.get('[data-cy=tool-with-two-inputs]')
.find('[contenteditable=true]')
.first()
.click()
.trigger('keydown', { keyCode: 9 })
.wait(CARET_MOVE_TIME);
cy.get('[data-cy=tool-with-two-inputs]')
.find('[contenteditable=true]')
.last()
.then(($secondInput) => {
const editorWindow = $secondInput.get(0).ownerDocument.defaultView;
const selection = editorWindow.getSelection();
const range = selection.getRangeAt(0);
/**
* Check that second block contains range
*/
expect(range.startContainer).to.equal($secondInput.get(0));
});
});
it('should highlight next Block if it does not contain any inputs (contentless Block)', () => {
cy.createEditor({
tools: {
contentlessTool: {
class: ContentlessTool,
},
},
data: {
blocks: [
{
type: 'paragraph',
data: {
text: 'second paragraph',
},
},
{
type: 'contentlessTool',
data: {},
},
{
type: 'paragraph',
data: {
text: 'third paragraph',
},
},
],
},
});
cy.get('[data-cy=editorjs]')
.find('.ce-paragraph')
.first()
.click()
.trigger('keydown', { keyCode: 9 })
.wait(CARET_MOVE_TIME);
cy.get('[data-cy=contentless-tool]')
.parents('.ce-block')
.should('have.class', 'ce-block--selected');
});
it('should focus next input after Editor when pressed in last Block', () => {
cy.createEditor({});
/**
* Add regular input after Editor
*/
cy.window()
.then((window) => {
const input = window.document.createElement('input');
input.setAttribute('data-cy', 'regular-input');
window.document.body.appendChild(input);
});
cy.get('[data-cy=editorjs]')
.find('.ce-paragraph')
.click()
.tab();
cy.get('[data-cy=regular-input]')
.should('have.focus');
});
});
describe('Shift+Tab keydown', function () {
it('should focus previous Block if Block contains only one input', () => {
cy.createEditor({
data: {
blocks: [
{
type: 'paragraph',
data: {
text: 'first paragraph',
},
},
{
type: 'paragraph',
data: {
text: 'second paragraph',
},
},
],
},
});
cy.get('[data-cy=editorjs]')
.find('.ce-paragraph')
.last()
.click()
.trigger('keydown', {
keyCode: 9,
shiftKey: true,
})
.wait(CARET_MOVE_TIME);
cy.get('[data-cy=editorjs]')
.find('.ce-paragraph')
.first()
.then(($firstBlock) => {
const editorWindow = $firstBlock.get(0).ownerDocument.defaultView;
const selection = editorWindow.getSelection();
const range = selection.getRangeAt(0);
/**
* Check that second block contains range
*/
expect(range.startContainer.parentElement).to.equal($firstBlock.get(0));
});
});
it('should focus previous input if Block contains several inputs', () => {
cy.createEditor({
tools: {
toolWithTwoInputs: {
class: ToolWithTwoInputs,
},
},
data: {
blocks: [
{
type: 'paragraph',
data: {
text: 'second paragraph',
},
},
{
type: 'toolWithTwoInputs',
data: {},
},
],
},
});
cy.get('[data-cy=tool-with-two-inputs]')
.find('[contenteditable=true]')
.last()
.click()
.trigger('keydown', {
keyCode: 9,
shiftKey: true,
})
.wait(CARET_MOVE_TIME);
cy.get('[data-cy=tool-with-two-inputs]')
.find('[contenteditable=true]')
.first()
.then(($firstInput) => {
const editorWindow = $firstInput.get(0).ownerDocument.defaultView;
const selection = editorWindow.getSelection();
const range = selection.getRangeAt(0);
/**
* Check that second block contains range
*/
expect(range.startContainer).to.equal($firstInput.get(0));
});
});
it('should highlight previous Block if it does not contain any inputs (contentless Block)', () => {
cy.createEditor({
tools: {
contentlessTool: {
class: ContentlessTool,
},
},
data: {
blocks: [
{
type: 'paragraph',
data: {
text: 'second paragraph',
},
},
{
type: 'contentlessTool',
data: {},
},
{
type: 'paragraph',
data: {
text: 'third paragraph',
},
},
],
},
});
cy.get('[data-cy=editorjs]')
.find('.ce-paragraph')
.last()
.click()
.trigger('keydown', {
keyCode: 9,
shiftKey: true,
})
.wait(CARET_MOVE_TIME);
cy.get('[data-cy=contentless-tool]')
.parents('.ce-block')
.should('have.class', 'ce-block--selected');
});
it('should focus previous input before Editor when pressed in first Block', () => {
cy.createEditor({});
/**
* Add regular input before Editor
*/
cy.window()
.then((window) => {
const input = window.document.createElement('input');
input.setAttribute('data-cy', 'regular-input');
window.document.body.insertBefore(input, window.document.body.firstChild);
});
cy.get('[data-cy=editorjs]')
.find('.ce-paragraph')
.click()
.tab({ shift: true });
cy.get('[data-cy=regular-input]')
.should('have.focus');
});
});