Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RNMobile] Add integration tests to cover Drag & Drop functionality #41364

Merged
merged 11 commits into from
May 31, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default function BlockDraggableChip( { icon } ) {
);

return (
<View style={ [ containerStyle, shadowStyle ] }>
<View style={ [ containerStyle, shadowStyle ] } testID="draggable-chip">
<BlockIcon icon={ dragHandle } />
{ icon && <BlockIcon icon={ icon } /> }
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ const BlockDraggableWrapper = ( { children, isRTL } ) => {
onDragStart={ startDragging }
onDragOver={ updateDragging }
onDragEnd={ stopDragging }
testID="block-draggable-wrapper"
>
{ children( { onScroll: scrollHandler } ) }
</Draggable>
Expand Down Expand Up @@ -302,6 +303,7 @@ const BlockDraggableWrapper = ( { children, isRTL } ) => {
* @param {string} props.clientId Client id of the block.
* @param {string} [props.draggingClientId] Client id to use for dragging. If not defined, the value from `clientId` will be used.
* @param {boolean} [props.enabled] Enables the draggable trigger.
* @param {string} [props.testID] Id used for querying the long-press gesture handler in tests.
*
* @return {Function} Render function which includes the parameter `isDraggable` to determine if the block can be dragged.
*/
Expand All @@ -310,6 +312,7 @@ const BlockDraggable = ( {
children,
draggingClientId,
enabled = true,
testID,
} ) => {
const wasBeingDragged = useRef( false );
const [ isEditingText, setIsEditingText ] = useState( false );
Expand Down Expand Up @@ -446,6 +449,7 @@ const BlockDraggable = ( {
android: DEFAULT_LONG_PRESS_MIN_DURATION,
} ) }
onLongPress={ onLongPressDraggable }
testID={ testID }
>
<Animated.View style={ wrapperStyles }>
{ children( { isDraggable: true } ) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`BlockDraggable moves blocks: Initial order 1`] = `
"<!-- wp:paragraph -->
<p>This is a paragraph.</p>
<!-- /wp:paragraph -->
<!-- wp:image {\\"sizeSlug\\":\\"large\\"} -->
<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure>
<!-- /wp:image -->
<!-- wp:spacer -->
<div style=\\"height:100px\\" aria-hidden=\\"true\\" class=\\"wp-block-spacer\\"></div>
<!-- /wp:spacer -->
<!-- wp:gallery {\\"linkTo\\":\\"none\\"} -->
<figure class=\\"wp-block-gallery has-nested-images columns-default is-cropped\\"><!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} -->
<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure>
<!-- /wp:image -->
<!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} -->
<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure>
<!-- /wp:image --></figure>
<!-- /wp:gallery -->"
`;
exports[`BlockDraggable moves blocks: Paragraph block moved from first to second position 1`] = `
"<!-- wp:image {\\"sizeSlug\\":\\"large\\"} -->
<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure>
<!-- /wp:image -->
<!-- wp:paragraph -->
<p>This is a paragraph.</p>
<!-- /wp:paragraph -->
<!-- wp:spacer -->
<div style=\\"height:100px\\" aria-hidden=\\"true\\" class=\\"wp-block-spacer\\"></div>
<!-- /wp:spacer -->
<!-- wp:gallery {\\"linkTo\\":\\"none\\"} -->
<figure class=\\"wp-block-gallery has-nested-images columns-default is-cropped\\"><!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} -->
<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure>
<!-- /wp:image -->
<!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} -->
<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure>
<!-- /wp:image --></figure>
<!-- /wp:gallery -->"
`;
exports[`BlockDraggable moves blocks: Spacer block moved from third to first position 1`] = `
"<!-- wp:spacer -->
<div style=\\"height:100px\\" aria-hidden=\\"true\\" class=\\"wp-block-spacer\\"></div>
<!-- /wp:spacer -->
<!-- wp:image {\\"sizeSlug\\":\\"large\\"} -->
<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure>
<!-- /wp:image -->
<!-- wp:paragraph -->
<p>This is a paragraph.</p>
<!-- /wp:paragraph -->
<!-- wp:gallery {\\"linkTo\\":\\"none\\"} -->
<figure class=\\"wp-block-gallery has-nested-images columns-default is-cropped\\"><!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} -->
<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure>
<!-- /wp:image -->
<!-- wp:image {\\"sizeSlug\\":\\"large\\",\\"linkDestination\\":\\"none\\"} -->
<figure class=\\"wp-block-image size-large\\"><img src=\\"https://cldup.com/cXyG__fTLN.jpg\\" alt=\\"\\"/></figure>
<!-- /wp:image --></figure>
<!-- /wp:gallery -->"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/**
* External dependencies
*/
import {
act,
fireEvent,
initializeEditor,
waitForStoreResolvers,
within,
advanceAnimationByFrame,
} from 'test/helpers';
import { fireGestureHandler } from 'react-native-gesture-handler/jest-utils';
import { State } from 'react-native-gesture-handler';

// Touch event type constants have been extracted from original source code, as they are not exported in the package.
// Reference: https://github.com/software-mansion/react-native-gesture-handler/blob/90895e5f38616a6be256fceec6c6a391cd9ad7c7/src/TouchEventType.ts
export const TouchEventType = {
UNDETERMINED: 0,
TOUCHES_DOWN: 1,
TOUCHES_MOVE: 2,
TOUCHES_UP: 3,
TOUCHES_CANCELLED: 4,
};

const DEFAULT_TOUCH_EVENTS = [
{
id: 1,
eventType: TouchEventType.TOUCHES_DOWN,
x: 0,
y: 0,
},
];

/**
* @typedef {Object} WPBlockWithLayout
* @property {string} name Name of the block (e.g. Paragraph).
* @property {string} html HTML content.
* @property {Object} layout Layout data.
* @property {Object} layout.x X position.
* @property {Object} layout.y Y position.
* @property {Object} layout.width Width.
* @property {Object} layout.height Height.
*/

/**
* Initialize the editor with an array of blocks that include their HTML and layout.
*
* @param {WPBlockWithLayout[]} blocks Initial blocks.
*
* @return {import('@testing-library/react-native').RenderAPI} The Testing Library screen.
*/
export const initializeWithBlocksLayouts = async ( blocks ) => {
const initialHtml = blocks.map( ( block ) => block.html ).join( '\n' );

const screen = await initializeEditor( { initialHtml } );
const { getByA11yLabel } = screen;

const waitPromises = [];
blocks.forEach( ( block, index ) => {
const a11yLabel = new RegExp(
`${ block.name } Block\\. Row ${ index + 1 }`
);
const element = getByA11yLabel( a11yLabel );
// "onLayout" event will populate the blocks layouts data.
fireEvent( element, 'layout', {
nativeEvent: { layout: block.layout },
} );
if ( block.nestedBlocks ) {
// Nested blocks are rendered via the FlatList of the inner block list.
// In order to render the items of a FlatList, it's required to trigger the
// "onLayout" event. Additionally, the call is wrapped over "waitForStoreResolvers"
// because the nested blocks might make API requests (e.g. the Gallery block).
waitPromises.push(
waitForStoreResolvers( () =>
fireEvent(
within( element ).getByTestId( 'block-list-wrapper' ),
'layout',
{
nativeEvent: {
layout: {
width: block.layout.width,
height: block.layout.height,
},
},
}
)
)
);

block.nestedBlocks.forEach( ( nestedBlock, nestedIndex ) => {
const nestedA11yLabel = new RegExp(
`${ nestedBlock.name } Block\\. Row ${ nestedIndex + 1 }`
);
fireEvent(
within( element ).getByA11yLabel( nestedA11yLabel ),
'layout',
{
nativeEvent: { layout: nestedBlock.layout },
}
);
} );
}
} );
await Promise.all( waitPromises );

return screen;
};

/**
* Fires long-press gesture event on a block.
*
* @param {import('react-test-renderer').ReactTestInstance} block Block test instance.
* @param {string} testID Id for querying the draggable trigger element.
* @param {Object} [options] Configuration options for the gesture event.
* @param {boolean} [options.failed] Determines if the gesture should fail.
* @param {number} [options.triggerIndex] In case there are multiple draggable triggers, this specifies the index to use.
*/
export const fireLongPress = (
block,
testID,
{ failed = false, triggerIndex } = {}
) => {
const draggableTrigger =
typeof triggerIndex !== 'undefined'
? within( block ).getAllByTestId( testID )[ triggerIndex ]
: within( block ).getByTestId( testID );
if ( failed ) {
fireGestureHandler( draggableTrigger, [ { state: State.FAILED } ] );
} else {
fireGestureHandler( draggableTrigger, [
{ oldState: State.BEGAN, state: State.ACTIVE },
{ state: State.ACTIVE },
{ state: State.END },
] );
}
// Advance timers one frame to ensure that shared values
// are updated and trigger animation reactions.
act( () => advanceAnimationByFrame( 1 ) );
};

/**
* Fires pan gesture event on a BlockDraggable component.
*
* @param {import('react-test-renderer').ReactTestInstance} blockDraggable BlockDraggable test instance.
* @param {Object} [touchEvents] Array of touch events to dispatch on the pan gesture.
*/
export const firePanGesture = (
blockDraggable,
touchEvents = DEFAULT_TOUCH_EVENTS
) => {
const gestureTouchEvents = touchEvents.map(
( { eventType, ...touchEvent } ) => ( {
allTouches: [ touchEvent ],
eventType,
} )
);
fireGestureHandler( blockDraggable, [
// TOUCHES_DOWN event is only received on ACTIVE state, so we have to fire it manually.
{ oldState: State.BEGAN, state: State.ACTIVE },
...gestureTouchEvents,
{ state: State.END },
] );
// Advance timers one frame to ensure that shared values
// are updated and trigger animation reactions.
act( () => advanceAnimationByFrame( 1 ) );
};

/**
* Gets the draggable chip element.
*
* @param {import('@testing-library/react-native').RenderAPI} screen The Testing Library screen.
*
* @return {import('react-test-renderer').ReactTestInstance} Draggable chip test instance.
*/
export const getDraggableChip = ( { getByTestId } ) => {
let draggableChip;
try {
draggableChip = getByTestId( 'draggable-chip' );
} catch ( e ) {
// NOOP.
}
return draggableChip;
};
Loading