From 3a74ce92675bf7be530a6ff532d116c111259153 Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Sun, 3 Apr 2022 16:45:30 +0200 Subject: [PATCH] Replace splitter e2e test with unit test Signed-off-by: Tilman Vatteroth --- cypress/integration/editorMode.spec.ts | 13 +- cypress/integration/splitter.spec.ts | 42 --- .../editor-page/editor-pane/editor-pane.tsx | 4 +- .../__snapshots__/splitter.test.tsx.snap | 304 ++++++++++++++++++ .../splitter/split-divider/split-divider.tsx | 4 +- .../editor-page/splitter/splitter.test.tsx | 70 ++++ .../editor-page/splitter/splitter.tsx | 33 +- .../gist/use-resize-gist-frame.ts | 21 +- ...e-bind-pointer-movement-event-on-window.ts | 36 +++ src/utils/test-id.ts | 19 ++ 10 files changed, 449 insertions(+), 97 deletions(-) delete mode 100644 cypress/integration/splitter.spec.ts create mode 100644 src/components/editor-page/splitter/__snapshots__/splitter.test.tsx.snap create mode 100644 src/components/editor-page/splitter/splitter.test.tsx create mode 100644 src/hooks/common/use-bind-pointer-movement-event-on-window.ts create mode 100644 src/utils/test-id.ts diff --git a/cypress/integration/editorMode.spec.ts b/cypress/integration/editorMode.spec.ts index e318d2c7e..d27937759 100644 --- a/cypress/integration/editorMode.spec.ts +++ b/cypress/integration/editorMode.spec.ts @@ -9,18 +9,17 @@ import { PAGE_MODE } from '../support/visit' describe('Editor mode from URL parameter is used', () => { it('mode view', () => { cy.visitTestNote(PAGE_MODE.EDITOR, 'view') - cy.getByCypressId('splitter-left').should('not.be.visible') - cy.getByCypressId('splitter-right').should('be.visible') + cy.getByCypressId('editor-pane').should('not.be.visible') + cy.getByCypressId('documentIframe').should('be.visible') }) it('mode both', () => { cy.visitTestNote(PAGE_MODE.EDITOR, 'both') - cy.getByCypressId('splitter-left').should('be.visible') - cy.getByCypressId('splitter-separator').should('exist') - cy.getByCypressId('splitter-right').should('be.visible') + cy.getByCypressId('editor-pane').should('be.visible') + cy.getByCypressId('documentIframe').should('be.visible') }) it('mode edit', () => { cy.visitTestNote(PAGE_MODE.EDITOR, 'edit') - cy.getByCypressId('splitter-left').should('be.visible') - cy.getByCypressId('splitter-right').should('not.be.visible') + cy.getByCypressId('editor-pane').should('be.visible') + cy.getByCypressId('documentIframe').should('not.be.visible') }) }) diff --git a/cypress/integration/splitter.spec.ts b/cypress/integration/splitter.spec.ts deleted file mode 100644 index ca1f943a6..000000000 --- a/cypress/integration/splitter.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -describe('Split view', () => { - beforeEach(() => { - cy.visitTestNote() - }) - - it('can show both panes', () => { - cy.getByCypressId('view-mode-both').click() - cy.getByCypressId('splitter-left').should('be.visible') - cy.getByCypressId('splitter-right').should('be.visible') - }) - - it('can show only preview pane', () => { - cy.getByCypressId('view-mode-preview').click() - cy.getByCypressId('splitter-left').should('be.not.visible') - cy.getByCypressId('splitter-right').should('be.visible') - }) - - it('can show only editor pane', () => { - cy.getByCypressId('view-mode-editor').click() - cy.getByCypressId('splitter-left').should('be.visible') - cy.getByCypressId('splitter-right').should('be.not.visible') - }) - - it('can change the split by dragging', () => { - cy.getByCypressId('splitter-left').then((leftPanebefore) => { - const widthBefore = leftPanebefore.outerWidth() - - cy.getByCypressId('view-mode-both').click() - cy.getByCypressId('split-divider').should('be.visible').trigger('mousedown', { buttons: 1 }) - cy.document().trigger('mousemove', { buttons: 1, pageX: 0, pageY: 0 }) - cy.getByCypressId('split-divider').trigger('mouseup') - - cy.getByCypressId('splitter-left').should('not.eq', widthBefore) - }) - }) -}) diff --git a/src/components/editor-page/editor-pane/editor-pane.tsx b/src/components/editor-page/editor-pane/editor-pane.tsx index 2a70d6ee3..5a7734945 100644 --- a/src/components/editor-page/editor-pane/editor-pane.tsx +++ b/src/components/editor-page/editor-pane/editor-pane.tsx @@ -30,6 +30,7 @@ import { EditorView } from '@codemirror/view' import { autocompletion } from '@codemirror/autocomplete' import { useCodeMirrorFocusReference } from './hooks/use-code-mirror-focus-reference' import { useOffScreenScrollProtection } from './hooks/use-off-screen-scroll-protection' +import { cypressId } from '../../../utils/cypress-attribute' const logger = new Logger('EditorPane') @@ -94,7 +95,8 @@ export const EditorPane: React.FC = ({ scrollState, onScroll, onMak
+ onMouseEnter={onMakeScrollSource} + {...cypressId('editor-pane')}> +
+
+ left +
+
+
+
+
+ right +
+
+
+`; + +exports[`Splitter can render only the left pane 1`] = ` +
+
+
+ left +
+
+ right +
+
+
+`; + +exports[`Splitter can render only the right pane 1`] = ` +
+
+
+ left +
+
+ right +
+
+
+`; + +exports[`Splitter resize can change size with mouse 1`] = ` +
+
+
+ left +
+
+
+
+
+ right +
+
+
+`; + +exports[`Splitter resize can change size with mouse 2`] = ` +
+
+
+ left +
+
+
+
+
+ right +
+
+
+`; + +exports[`Splitter resize can change size with mouse 3`] = ` +
+
+
+ left +
+
+
+
+
+ right +
+
+
+`; + +exports[`Splitter resize can change size with mouse 4`] = ` +
+
+
+ left +
+
+
+
+
+ right +
+
+
+`; + +exports[`Splitter resize can change size with touch 1`] = ` +
+
+
+ left +
+
+
+
+
+ right +
+
+
+`; + +exports[`Splitter resize can change size with touch 2`] = ` +
+
+
+ left +
+
+
+
+
+ right +
+
+
+`; + +exports[`Splitter resize can change size with touch 3`] = ` +
+
+
+ left +
+
+
+
+
+ right +
+
+
+`; + +exports[`Splitter resize can change size with touch 4`] = ` +
+
+
+ left +
+
+
+
+
+ right +
+
+
+`; diff --git a/src/components/editor-page/splitter/split-divider/split-divider.tsx b/src/components/editor-page/splitter/split-divider/split-divider.tsx index 04706e8de..a603ddaa3 100644 --- a/src/components/editor-page/splitter/split-divider/split-divider.tsx +++ b/src/components/editor-page/splitter/split-divider/split-divider.tsx @@ -6,7 +6,7 @@ import React from 'react' import styles from './split-divider.module.scss' -import { cypressId } from '../../../../utils/cypress-attribute' +import { testId } from '../../../../utils/test-id' export interface SplitDividerProps { onGrab: () => void @@ -18,7 +18,7 @@ export const SplitDivider: React.FC = ({ onGrab }) => { onMouseDown={onGrab} onTouchStart={onGrab} className={styles['split-divider']} - {...cypressId('split-divider')} + {...testId('splitter-divider')} /> ) } diff --git a/src/components/editor-page/splitter/splitter.test.tsx b/src/components/editor-page/splitter/splitter.test.tsx new file mode 100644 index 000000000..6e0981217 --- /dev/null +++ b/src/components/editor-page/splitter/splitter.test.tsx @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { fireEvent, render, screen } from '@testing-library/react' +import { Splitter } from './splitter' + +describe('Splitter', () => { + it('can render only the left pane', () => { + const view = render(left} right={<>right} />) + expect(view.container).toMatchSnapshot() + }) + + it('can render only the right pane', () => { + const view = render(left} right={<>right} />) + expect(view.container).toMatchSnapshot() + }) + + it('can render both panes', () => { + const view = render(left} right={<>right} />) + expect(view.container).toMatchSnapshot() + }) + + describe('resize', () => { + beforeEach(() => { + Object.defineProperty(window.HTMLDivElement.prototype, 'clientWidth', { value: 1920 }) + Object.defineProperty(window.HTMLDivElement.prototype, 'offsetLeft', { value: 0 }) + }) + + it('can change size with mouse', async () => { + const view = render(left} right={<>right} />) + expect(view.container).toMatchSnapshot() + const divider = await screen.findByTestId('splitter-divider') + + fireEvent.mouseDown(divider, {}) + fireEvent.mouseMove(window, { buttons: 1, clientX: 1920 }) + fireEvent.mouseUp(window) + expect(view.container).toMatchSnapshot() + + fireEvent.mouseDown(divider, {}) + fireEvent.mouseMove(window, { buttons: 1, clientX: 0 }) + fireEvent.mouseUp(window) + expect(view.container).toMatchSnapshot() + + fireEvent.mouseMove(window, { buttons: 1, clientX: 1920 }) + expect(view.container).toMatchSnapshot() + }) + + it('can change size with touch', async () => { + const view = render(left} right={<>right} />) + expect(view.container).toMatchSnapshot() + const divider = await screen.findByTestId('splitter-divider') + + fireEvent.touchStart(divider, {}) + fireEvent.touchMove(window, { buttons: 1, clientX: 1920 }) + fireEvent.touchEnd(window) + expect(view.container).toMatchSnapshot() + + fireEvent.touchStart(divider, {}) + fireEvent.touchMove(window, { buttons: 1, clientX: 0 }) + fireEvent.touchCancel(window) + expect(view.container).toMatchSnapshot() + + fireEvent.touchMove(window, { buttons: 1, clientX: 1920 }) + expect(view.container).toMatchSnapshot() + }) + }) +}) diff --git a/src/components/editor-page/splitter/splitter.tsx b/src/components/editor-page/splitter/splitter.tsx index 20db3cc38..4ff8af7b6 100644 --- a/src/components/editor-page/splitter/splitter.tsx +++ b/src/components/editor-page/splitter/splitter.tsx @@ -5,12 +5,12 @@ */ import type { ReactElement } from 'react' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import React, { useCallback, useRef, useState } from 'react' import { ShowIf } from '../../common/show-if/show-if' import { SplitDivider } from './split-divider/split-divider' import styles from './splitter.module.scss' import { useAdjustedRelativeSplitValue } from './hooks/use-adjusted-relative-split-value' -import { cypressId } from '../../../utils/cypress-attribute' +import { useBindPointerMovementEventOnWindow } from '../../../hooks/common/use-bind-pointer-movement-event-on-window' export interface SplitterProps { left?: ReactElement @@ -41,9 +41,9 @@ const isLeftMouseButtonClicked = (mouseEvent: MouseEvent): boolean => { */ const extractHorizontalPosition = (moveEvent: MouseEvent | TouchEvent): number => { if (isMouseEvent(moveEvent)) { - return moveEvent.pageX + return moveEvent.clientX } else { - return moveEvent.touches[0]?.pageX + return moveEvent.touches[0]?.clientX } } @@ -107,42 +107,21 @@ export const Splitter: React.FC = ({ moveEvent.preventDefault() }, []) - /** - * Registers and unregisters necessary event listeners on the body so you can use the split even if the mouse isn't moving over it. - */ - useEffect(() => { - const moveHandler = onMove - const stopResizeHandler = onStopResizing - window.addEventListener('touchmove', moveHandler) - window.addEventListener('mousemove', moveHandler) - window.addEventListener('touchcancel', stopResizeHandler) - window.addEventListener('touchend', stopResizeHandler) - window.addEventListener('mouseup', stopResizeHandler) - - return () => { - window.removeEventListener('touchmove', moveHandler) - window.removeEventListener('mousemove', moveHandler) - window.removeEventListener('touchcancel', stopResizeHandler) - window.removeEventListener('touchend', stopResizeHandler) - window.removeEventListener('mouseup', stopResizeHandler) - } - }, [resizingInProgress, onMove, onStopResizing]) + useBindPointerMovementEventOnWindow(onMove, onStopResizing) return (
{left}
-
+
{right} diff --git a/src/components/markdown-renderer/markdown-extension/gist/use-resize-gist-frame.ts b/src/components/markdown-renderer/markdown-extension/gist/use-resize-gist-frame.ts index 659c44165..0742daf5d 100644 --- a/src/components/markdown-renderer/markdown-extension/gist/use-resize-gist-frame.ts +++ b/src/components/markdown-renderer/markdown-extension/gist/use-resize-gist-frame.ts @@ -5,7 +5,8 @@ */ import type React from 'react' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useRef, useState } from 'react' +import { useBindPointerMovementEventOnWindow } from '../../../../hooks/common/use-bind-pointer-movement-event-on-window' /** * Determines if the left mouse button is pressed in the given event @@ -82,23 +83,7 @@ export const useResizeGistFrame = (initialFrameHeight: number): [number, Pointer } }, []) - useEffect(() => { - const moveHandler = onMove - const stopResizeHandler = onStopResizing - window.addEventListener('touchmove', moveHandler) - window.addEventListener('mousemove', moveHandler) - window.addEventListener('touchcancel', stopResizeHandler) - window.addEventListener('touchend', stopResizeHandler) - window.addEventListener('mouseup', stopResizeHandler) - - return () => { - window.removeEventListener('touchmove', moveHandler) - window.removeEventListener('mousemove', moveHandler) - window.removeEventListener('touchcancel', stopResizeHandler) - window.removeEventListener('touchend', stopResizeHandler) - window.removeEventListener('mouseup', stopResizeHandler) - } - }, [onMove, onStopResizing]) + useBindPointerMovementEventOnWindow(onMove, onStopResizing) return [frameHeight, onStartResizing] } diff --git a/src/hooks/common/use-bind-pointer-movement-event-on-window.ts b/src/hooks/common/use-bind-pointer-movement-event-on-window.ts new file mode 100644 index 000000000..c455a0bf3 --- /dev/null +++ b/src/hooks/common/use-bind-pointer-movement-event-on-window.ts @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useEffect } from 'react' + +/** + * Registers event listener for pointer movement and pointer release on the window object. + * + * @param onPointerMovement is triggered if the user moves the pointer over the window + * @param onPointerRelease is triggered if the pointer is released (touch end or mouse up) + */ +export const useBindPointerMovementEventOnWindow = ( + onPointerMovement: (event: MouseEvent | TouchEvent) => void, + onPointerRelease: () => void +) => { + useEffect(() => { + const pointerMovement = onPointerMovement + const pointerRelease = onPointerRelease + window.addEventListener('touchmove', pointerMovement) + window.addEventListener('mousemove', pointerMovement) + window.addEventListener('touchcancel', pointerRelease) + window.addEventListener('touchend', pointerRelease) + window.addEventListener('mouseup', pointerRelease) + + return () => { + window.removeEventListener('touchmove', pointerMovement) + window.removeEventListener('mousemove', pointerMovement) + window.removeEventListener('touchcancel', pointerRelease) + window.removeEventListener('touchend', pointerRelease) + window.removeEventListener('mouseup', pointerRelease) + } + }, [onPointerMovement, onPointerRelease]) +} diff --git a/src/utils/test-id.ts b/src/utils/test-id.ts new file mode 100644 index 000000000..cf99ba0b5 --- /dev/null +++ b/src/utils/test-id.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * Returns an object with the "data-testid" attribute that is used to find + * elements in unit tests. + * This works only if the runtime is built in test mode. + * + * @param identifier The identifier that is used to find the element + * @return An object if in test mode, undefined otherwise. + */ +export const testId = (identifier: string): Record<'data-testid', string> | undefined => { + if (process.env.NODE_ENV === 'test') { + return { 'data-testid': identifier } + } +}