From ae270f1cb7254ff9747a9f246dbdccbb7a6752ba Mon Sep 17 00:00:00 2001 From: Tanya Fomina Date: Fri, 28 Oct 2022 19:25:33 +0300 Subject: [PATCH] Fix checklist deletion 2 --- src/components/flipper.ts | 4 ++ test/cypress/tests/utils/flipper.spec.ts | 88 ++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 test/cypress/tests/utils/flipper.spec.ts diff --git a/src/components/flipper.ts b/src/components/flipper.ts index f79a6b3c..ed2df15f 100644 --- a/src/components/flipper.ts +++ b/src/components/flipper.ts @@ -123,6 +123,10 @@ export default class Flipper { * Listening all keydowns on document and react on TAB/Enter press * TAB will leaf iterator items * ENTER will click the focused item + * + * Note: the event should be handled in capturing mode on following reasons: + * - prevents plugins inner keydown handlers from being called while keyboard navigation + * - otherwise this handler will be called at the moment it is attached which causes false flipper firing (see https://techread.me/js-addeventlistener-fires-for-past-events/) */ document.addEventListener('keydown', this.onKeyDown, true); } diff --git a/test/cypress/tests/utils/flipper.spec.ts b/test/cypress/tests/utils/flipper.spec.ts new file mode 100644 index 00000000..3bd98760 --- /dev/null +++ b/test/cypress/tests/utils/flipper.spec.ts @@ -0,0 +1,88 @@ +import { PopoverItem } from '../../../../types/index.js'; + +/** + * Mock of some Block Tool + */ +class SomePlugin { + /** + * Event handler to be spyed in test + */ + // eslint-disable-next-line @typescript-eslint/no-empty-function + public static pluginInternalKeydownHandler(): void {} + + /** + * Mocked render method + */ + public render(): HTMLElement { + const wrapper = document.createElement('div'); + + wrapper.classList.add('cdx-some-plugin'); + wrapper.contentEditable = 'true'; + wrapper.addEventListener('keydown', SomePlugin.pluginInternalKeydownHandler); + + return wrapper; + } + + /** + * Used to display our tool in the Toolboz + */ + public static get toolbox(): PopoverItem { + return { + icon: '₷', + label: 'Some tool', + // eslint-disable-next-line @typescript-eslint/no-empty-function + onActivate: (): void => {}, + }; + } +} + +describe('Flipper', () => { + beforeEach(() => { + if (this && this.editorInstance) { + this.editorInstance.destroy(); + } else { + cy.createEditor({ + tools: { + sometool: SomePlugin, + }, + }).as('editorInstance'); + } + }); + + it('should prevent plugins event handlers from being called while keyboard navigation', () => { + const TAB_KEY_CODE = 9; + const ARROW_DOWN_KEY_CODE = 40; + const ENTER_KEY_CODE = 13; + + const sampleText = 'sample text'; + + cy.spy(SomePlugin, 'pluginInternalKeydownHandler'); + + // Insert sometool block and enter sample text + cy.get('[data-cy=editorjs]') + .get('div.ce-block') + .trigger('keydown', { keyCode: TAB_KEY_CODE }); + + cy.get('[data-item-name=sometool]').click(); + + cy.get('[data-cy=editorjs]') + .get('.cdx-some-plugin') + .focus() + .type(sampleText); + + // Try to delete the block via keyboard + cy.get('[data-cy=editorjs]') + .get('.cdx-some-plugin') + // Open tunes menu + .trigger('keydown', { keyCode: TAB_KEY_CODE }) + // Navigate to delete button + .trigger('keydown', { keyCode: ARROW_DOWN_KEY_CODE }) + .trigger('keydown', { keyCode: ARROW_DOWN_KEY_CODE }) + // Click delete + .trigger('keydown', { keyCode: ENTER_KEY_CODE }) + // // Confirm delete + .trigger('keydown', { keyCode: ENTER_KEY_CODE }); + + expect(SomePlugin.pluginInternalKeydownHandler).to.have.not.been.called; + }); +});