Replace splitter e2e test with unit test

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-04-03 16:45:30 +02:00
parent 8a12755164
commit 3a74ce9267
10 changed files with 449 additions and 97 deletions

View file

@ -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')
})
})

View file

@ -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)
})
})
})

View file

@ -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<ScrollProps> = ({ scrollState, onScroll, onMak
<div
className={`d-flex flex-column h-100 position-relative`}
onTouchStart={onMakeScrollSource}
onMouseEnter={onMakeScrollSource}>
onMouseEnter={onMakeScrollSource}
{...cypressId('editor-pane')}>
<MaxLengthWarning />
<ToolBar />
<ReactCodeMirror

View file

@ -0,0 +1,304 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Splitter can render both panes 1`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left "
style="width: calc(50% - 5px);"
>
left
</div>
<div
class="splitter separator"
>
<div
class="split-divider"
data-testid="splitter-divider"
/>
</div>
<div
class="splitter right "
style="width: calc(100% - 50%);"
>
right
</div>
</div>
</div>
`;
exports[`Splitter can render only the left pane 1`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left "
style="width: calc(100% - 5px);"
>
left
</div>
<div
class="splitter right d-none"
style="width: calc(100% - 100%);"
>
right
</div>
</div>
</div>
`;
exports[`Splitter can render only the right pane 1`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left d-none"
style="width: calc(0% - 5px);"
>
left
</div>
<div
class="splitter right "
style="width: calc(100% - 0%);"
>
right
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with mouse 1`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left "
style="width: calc(50% - 5px);"
>
left
</div>
<div
class="splitter separator"
>
<div
class="split-divider"
data-testid="splitter-divider"
/>
</div>
<div
class="splitter right "
style="width: calc(100% - 50%);"
>
right
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with mouse 2`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left "
style="width: calc(100% - 5px);"
>
left
</div>
<div
class="splitter separator"
>
<div
class="split-divider"
data-testid="splitter-divider"
/>
</div>
<div
class="splitter right "
style="width: calc(100% - 100%);"
>
right
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with mouse 3`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left "
style="width: calc(0% - 5px);"
>
left
</div>
<div
class="splitter separator"
>
<div
class="split-divider"
data-testid="splitter-divider"
/>
</div>
<div
class="splitter right "
style="width: calc(100% - 0%);"
>
right
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with mouse 4`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left "
style="width: calc(0% - 5px);"
>
left
</div>
<div
class="splitter separator"
>
<div
class="split-divider"
data-testid="splitter-divider"
/>
</div>
<div
class="splitter right "
style="width: calc(100% - 0%);"
>
right
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with touch 1`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left "
style="width: calc(50% - 5px);"
>
left
</div>
<div
class="splitter separator"
>
<div
class="split-divider"
data-testid="splitter-divider"
/>
</div>
<div
class="splitter right "
style="width: calc(100% - 50%);"
>
right
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with touch 2`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left "
style="width: calc(NaN% - 5px);"
>
left
</div>
<div
class="splitter separator"
>
<div
class="split-divider"
data-testid="splitter-divider"
/>
</div>
<div
class="splitter right "
style="width: calc(100% - NaN%);"
>
right
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with touch 3`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left "
style="width: calc(NaN% - 5px);"
>
left
</div>
<div
class="splitter separator"
>
<div
class="split-divider"
data-testid="splitter-divider"
/>
</div>
<div
class="splitter right "
style="width: calc(100% - NaN%);"
>
right
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with touch 4`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="splitter left "
style="width: calc(NaN% - 5px);"
>
left
</div>
<div
class="splitter separator"
>
<div
class="split-divider"
data-testid="splitter-divider"
/>
</div>
<div
class="splitter right "
style="width: calc(100% - NaN%);"
>
right
</div>
</div>
</div>
`;

View file

@ -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<SplitDividerProps> = ({ onGrab }) => {
onMouseDown={onGrab}
onTouchStart={onGrab}
className={styles['split-divider']}
{...cypressId('split-divider')}
{...testId('splitter-divider')}
/>
)
}

View file

@ -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(<Splitter showLeft={true} showRight={false} left={<>left</>} right={<>right</>} />)
expect(view.container).toMatchSnapshot()
})
it('can render only the right pane', () => {
const view = render(<Splitter showLeft={false} showRight={true} left={<>left</>} right={<>right</>} />)
expect(view.container).toMatchSnapshot()
})
it('can render both panes', () => {
const view = render(<Splitter showLeft={true} showRight={true} left={<>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(<Splitter showLeft={true} showRight={true} left={<>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(<Splitter showLeft={true} showRight={true} left={<>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()
})
})
})

View file

@ -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<SplitterProps> = ({
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 (
<div ref={splitContainer} className={`flex-fill flex-row d-flex ${additionalContainerClassName || ''}`}>
<div
{...cypressId('splitter-left')}
className={`${styles['splitter']} ${styles['left']} ${!showLeft ? 'd-none' : ''}`}
style={{ width: `calc(${adjustedRelativeSplitValue}% - 5px)` }}>
{left}
</div>
<ShowIf condition={showLeft && showRight}>
<div className={`${styles['splitter']} ${styles['separator']}`} {...cypressId('splitter-separator')}>
<div className={`${styles['splitter']} ${styles['separator']}`}>
<SplitDivider onGrab={onStartResizing} />
</div>
</ShowIf>
<div
{...cypressId('splitter-right')}
className={`${styles['splitter']} ${styles['right']} ${!showRight ? 'd-none' : ''}`}
style={{ width: `calc(100% - ${adjustedRelativeSplitValue}%)` }}>
{right}

View file

@ -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]
}

View file

@ -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])
}

19
src/utils/test-id.ts Normal file
View file

@ -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 }
}
}