diff --git a/projects/plugins/jetpack/extensions/blocks/gif/edit.js b/projects/plugins/jetpack/extensions/blocks/gif/edit.js
index 2a9b6b83e57e5..a879d948b2697 100644
--- a/projects/plugins/jetpack/extensions/blocks/gif/edit.js
+++ b/projects/plugins/jetpack/extensions/blocks/gif/edit.js
@@ -3,7 +3,7 @@
*/
import classNames from 'classnames';
import { __ } from '@wordpress/i18n';
-import { createRef, useState, useEffect, useCallback } from '@wordpress/element';
+import { createRef, useState, useEffect } from '@wordpress/element';
import { Placeholder } from '@wordpress/components';
import { RichText } from '@wordpress/block-editor';
@@ -14,6 +14,7 @@ import { icon, title } from './';
import { getUrl, getPaddingTop, getEmbedUrl } from './utils';
import SearchForm from './components/search-form';
import Controls from './controls';
+import useFetchGiphyData from './hooks/use-fetch-giphy-data';
function GifEdit( {
attributes,
@@ -24,8 +25,8 @@ function GifEdit( {
const { align, caption, giphyUrl, searchText, paddingTop } = attributes;
const classes = classNames( className, `align${ align }` );
const [ captionFocus, setCaptionFocus ] = useState( false );
- const [ results, setResults ] = useState( '' );
const searchFormInputRef = createRef();
+ const { isFetching, giphyData, fetchGiphyData } = useFetchGiphyData();
const setSelectedGiphy = ( item ) => {
setAttributes( { giphyUrl: getEmbedUrl( item ), paddingTop: getPaddingTop( item ) } );
@@ -36,50 +37,20 @@ function GifEdit( {
setCaptionFocus( false );
};
- const fetchResults = async ( requestUrl ) => {
- const giphyFetch = await fetch( requestUrl )
- .then( ( response ) => {
- if ( response.ok ) {
- return response;
- }
- return false;
- } )
- .catch( () => {
- return false;
- } );
-
- if ( giphyFetch ) {
- const giphyResponse = await giphyFetch.json();
- // If there is only one result, Giphy's API does not return an array.
- // The following statement normalizes the data into an array with one member in this case.
- const giphyResults = typeof giphyResponse.data.images !== 'undefined' ? [ giphyResponse.data ] : giphyResponse.data;
-
- // Try to grab the first result. We're going to show this as the main image.
- const giphyData = giphyResults[ 0 ];
-
- // No results
- if ( ! giphyData.images ) {
- return false;
- }
-
- setResults( giphyResults );
- }
- };
-
useEffect( () => {
- if ( results && results[ 0 ] ) {
- setSelectedGiphy( results[ 0 ] );
+ if ( giphyData && giphyData[ 0 ] ) {
+ setSelectedGiphy( giphyData[ 0 ] );
}
- }, [ results ] );
+ }, [ giphyData ] );
const onSubmit = ( event ) => {
event.preventDefault();
- if ( ! attributes.searchText ) {
+ if ( ! attributes.searchText || isFetching ) {
return;
}
- fetchResults( getUrl( attributes.searchText ) );
+ fetchGiphyData( getUrl( attributes.searchText ) );
};
const onChange = ( event ) => setAttributes( { searchText: event.target.value } );
@@ -111,9 +82,9 @@ function GifEdit( {
ref={ searchFormInputRef }
/>
) }
- { isSelected && results && results.length > 1 && (
+ { isSelected && giphyData && giphyData.length > 1 && (
- { results.map( thumbnail => {
+ { giphyData.map( thumbnail => {
const thumbnailStyle = {
backgroundImage: `url(${ thumbnail.images.downsized_still.url })`,
};
diff --git a/projects/plugins/jetpack/extensions/blocks/gif/hooks/use-fetch-giphy-data.js b/projects/plugins/jetpack/extensions/blocks/gif/hooks/use-fetch-giphy-data.js
new file mode 100644
index 0000000000000..4b8a2521f8995
--- /dev/null
+++ b/projects/plugins/jetpack/extensions/blocks/gif/hooks/use-fetch-giphy-data.js
@@ -0,0 +1,57 @@
+/**
+ * External dependencies
+ */
+import { useEffect, useState } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+
+const useFetchGiphyData = ( initialValue = [] ) => {
+ const [ isFetching, setIsFetching ] = useState( false );
+ const [ giphyData, setGiphyData ] = useState( initialValue );
+ const [ fetchUrl, setFetchUrl ] = useState( '' );
+
+ useEffect( () => {
+ if ( ! fetchUrl ) {
+ return;
+ }
+
+ const fetchResults = async () => {
+ setIsFetching( true );
+
+ const giphyFetch = await fetch( fetchUrl )
+ .then( ( response ) => {
+ if ( response.ok ) {
+ return response;
+ }
+ return false;
+ } )
+ .catch( () => {
+ return false;
+ } );
+
+ if ( giphyFetch ) {
+ const giphyResponse = await giphyFetch.json();
+ // If there is only one result, Giphy's API does not return an array.
+ // The following statement normalizes the data into an array with one member in this case.
+ const giphyResults = typeof giphyResponse.data.images !== 'undefined' ? [ giphyResponse.data ] : giphyResponse.data;
+
+ // Try to grab the first result. We're going to show this as the main image.
+ const firstResult = giphyResults[ 0 ];
+
+ // Check for results.
+ if ( firstResult.images ) {
+ setGiphyData( giphyResults );
+ }
+ }
+ setIsFetching( false );
+ };
+
+ fetchResults();
+ }, [ fetchUrl ] );
+
+ return { isFetching, giphyData, fetchGiphyData: setFetchUrl };
+};
+
+export default useFetchGiphyData;
diff --git a/projects/plugins/jetpack/extensions/blocks/gif/test/edit.js b/projects/plugins/jetpack/extensions/blocks/gif/test/edit.js
index 51410cd293ca3..0323795bf4988 100644
--- a/projects/plugins/jetpack/extensions/blocks/gif/test/edit.js
+++ b/projects/plugins/jetpack/extensions/blocks/gif/test/edit.js
@@ -5,14 +5,15 @@
/**
* External dependencies
*/
-import { render, act, fireEvent } from '@testing-library/react';
+import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
/**
* Internal dependencies
*/
import GifEdit from '../edit';
-import { getUrl } from '../utils';
+import { getUrl, getPaddingTop, getEmbedUrl } from '../utils';
+import useFetchGiphyData from '../hooks/use-fetch-giphy-data';
const setAttributes = jest.fn();
@@ -31,50 +32,54 @@ const defaultProps = {
isSelected: false,
};
-const originalFetch = window.fetch;
-
-/**
- * Mock return value for a successful fetch JSON return value.
- *
- * @return {Promise} Mock return value.
- */
-const GIPHY_RESPONSE = {
- data: [
- {
- id: '9',
- embed_url: 'pony',
- images: {
- downsized_still: {
- url: 'chips',
- },
- original: {
- height: 10,
- width: 10,
- },
- }
+const GIPHY_DATA = [
+ {
+ id: '9',
+ embed_url: 'pony',
+ images: {
+ downsized_still: {
+ url: 'chips',
+ },
+ original: {
+ height: 10,
+ width: 10,
+ },
}
- ]
-};
-const RESOLVED_FETCH_PROMISE = Promise.resolve( GIPHY_RESPONSE );
-const DEFAULT_FETCH_MOCK_RETURN = Promise.resolve( {
- status: 200,
- ok: true,
- json: () => RESOLVED_FETCH_PROMISE,
-} );
+ },
+ {
+ id: '99',
+ embed_url: 'horsey',
+ images: {
+ downsized_still: {
+ url: 'fish',
+ },
+ original: {
+ height: 12,
+ width: 12,
+ },
+ }
+ }
+];
+
+const fetchGiphyData = jest.fn();
+
+jest.mock('./../hooks/use-fetch-giphy-data' );
describe( 'GifEdit', () => {
beforeEach( () => {
- window.fetch = jest.fn();
- window.fetch.mockReturnValue( DEFAULT_FETCH_MOCK_RETURN );
+ useFetchGiphyData.mockImplementation( () => {
+ return {
+ fetchGiphyData,
+ giphyData: [],
+ isFetching: false,
+ }
+ } );
} );
afterEach( async () => {
- await act( () => GIPHY_RESPONSE );
+ fetchGiphyData.mockReset();
setAttributes.mockReset();
- } );
-
- afterAll( () => {
- window.fetch = originalFetch;
+ useFetchGiphyData.mockReset();
} );
test( 'adds class names', () => {
@@ -88,16 +93,39 @@ describe( 'GifEdit', () => {
expect( container.querySelector( 'figure' ) ).not.toBeInTheDocument();
} );
-/* test( 'calls API and returns giphy images', () => {
+ test( 'calls API and returns giphy images', async () => {
+ useFetchGiphyData.mockImplementationOnce( () => {
+ return {
+ fetchGiphyData,
+ giphyData: GIPHY_DATA,
+ isFetching: false,
+ }
+ } );
const newProps = {
...defaultProps,
+ isSelected: true,
attributes: {
...defaultAttributes,
- searchText: 'sausage roll',
+ giphyUrl: 'https://itsalong.way/to/the/top/if/you/want',
+ searchText: 'a sausage roll',
},
};
- const { container } = render( );
+ const { container, screen } = render( );
+
+ expect( container.querySelector( 'form input' ).value ).toEqual( newProps.attributes.searchText );
+
fireEvent.submit( container.querySelector( 'form' ) );
- expect( window.fetch ).toHaveBeenCalledWith( getUrl( newProps.attributes.searchText ) );
- } );*/
+
+ expect( fetchGiphyData ).toHaveBeenCalledWith( getUrl( newProps.attributes.searchText ) );
+ expect( setAttributes.mock.calls[0][0] ).toStrictEqual( {
+ giphyUrl: getEmbedUrl( GIPHY_DATA[0] ),
+ paddingTop: getPaddingTop( GIPHY_DATA[0] ),
+ } );
+
+ expect( container.querySelector( 'figure' ) ).toBeInTheDocument();
+ expect( container.querySelector( 'figcaption' ) ).toBeInTheDocument();
+ expect( container.querySelector( '.wp-block-jetpack-gif-wrapper iframe' ) ).toBeInTheDocument();
+ expect( container.querySelectorAll( '.wp-block-jetpack-gif_thumbnail-container' ) ).toHaveLength( 2 );
+
+ } );
} );
diff --git a/projects/plugins/jetpack/extensions/blocks/gif/test/use-fetch-giphy-data.js b/projects/plugins/jetpack/extensions/blocks/gif/test/use-fetch-giphy-data.js
new file mode 100644
index 0000000000000..bfa919e4699cc
--- /dev/null
+++ b/projects/plugins/jetpack/extensions/blocks/gif/test/use-fetch-giphy-data.js
@@ -0,0 +1,127 @@
+/**
+ * External dependencies
+ */
+import { renderHook, act } from '@testing-library/react-hooks';
+
+/**
+ * Internal dependencies
+ */
+import useFetchGiphyData from '../hooks/use-fetch-giphy-data';
+
+const originalFetch = window.fetch;
+
+const GIPHY_SINGLE_RESPONSE = {
+ data: {
+ id: '9',
+ embed_url: 'pony',
+ images: {
+ downsized_still: {
+ url: 'chips',
+ },
+ original: {
+ height: 10,
+ width: 10,
+ },
+ }
+ },
+};
+
+export const GIPHY_MULTIPLE_RESPONSE = {
+ data: [
+ {
+ id: '9',
+ embed_url: 'pony',
+ images: {
+ downsized_still: {
+ url: 'chips',
+ },
+ original: {
+ height: 10,
+ width: 10,
+ },
+ }
+ },
+ {
+ id: '99',
+ embed_url: 'horsey',
+ images: {
+ downsized_still: {
+ url: 'fish',
+ },
+ original: {
+ height: 12,
+ width: 12,
+ },
+ }
+ }
+ ]
+};
+
+/**
+ * Mock return value for a successful fetch JSON return value.
+ *
+ * @return {Promise} Mock return value.
+ */
+function getFetchMockReturnValue( resolvedFetchPromiseResponse ) {
+ const resolvedFetchPromise = Promise.resolve( resolvedFetchPromiseResponse );
+ return Promise.resolve( {
+ ok: true,
+ json: () => resolvedFetchPromise,
+ } );
+}
+
+describe( 'useFetchGiphyData', () => {
+ beforeEach( () => {
+ window.fetch = jest.fn();
+ window.fetch.mockReturnValue( getFetchMockReturnValue( GIPHY_SINGLE_RESPONSE ) );
+ } );
+
+ afterAll( () => {
+ window.fetch = originalFetch;
+ } );
+
+ test( 'should return object response data after fetch', async () => {
+ const { result } = renderHook(() =>
+ useFetchGiphyData()
+ );
+
+ await act( async () => {
+ result.current.fetchGiphyData( 'https://icantbelieve.its/not/butter' );
+ } );
+
+ expect( result.current.giphyData ).toStrictEqual( [ GIPHY_SINGLE_RESPONSE.data ] );
+
+ expect( result.current.isFetching ).toBe( false );
+ } );
+
+ test( 'should return array data after fetch', async () => {
+ window.fetch.mockReturnValueOnce( getFetchMockReturnValue( GIPHY_MULTIPLE_RESPONSE ) )
+
+ const { result } = renderHook(() =>
+ useFetchGiphyData()
+ );
+
+ await act( async () => {
+ result.current.fetchGiphyData( 'https://icantbelieve.its/not/butter' );
+ } );
+
+ expect( result.current.giphyData ).toStrictEqual( GIPHY_MULTIPLE_RESPONSE.data );
+
+ expect( result.current.isFetching ).toBe( false );
+ } );
+
+ test( 'should not fetch if url is falsy', async () => {
+ const { result } = renderHook(() =>
+ useFetchGiphyData()
+ );
+
+ await act( async () => {
+ result.current.fetchGiphyData( null );
+ } );
+
+ expect( result.current.giphyData ).toStrictEqual( [] );
+
+ expect( result.current.isFetching ).toBe( false );
+ } );
+
+} );
diff --git a/projects/plugins/jetpack/package.json b/projects/plugins/jetpack/package.json
index 590e539cd1641..acefe0151b7d3 100644
--- a/projects/plugins/jetpack/package.json
+++ b/projects/plugins/jetpack/package.json
@@ -160,6 +160,7 @@
"@testing-library/jest-dom": "5.11.9",
"@testing-library/preact": "2.0.1",
"@testing-library/react": "11.2.3",
+ "@testing-library/react-hooks": "4.0.0",
"@testing-library/user-event": "12.8.1",
"@wordpress/components": "9.2.6",
"@wordpress/core-data": "2.12.3",
diff --git a/projects/plugins/jetpack/tests/jest-globals.js b/projects/plugins/jetpack/tests/jest-globals.js
index d4d296c92c86f..c3e9aa5732c03 100644
--- a/projects/plugins/jetpack/tests/jest-globals.js
+++ b/projects/plugins/jetpack/tests/jest-globals.js
@@ -1,3 +1,7 @@
+// Needed to use transpiled generator functions.
+// See: https://babeljs.io/docs/en/babel-polyfill for details.
+require( 'regenerator-runtime/runtime' );
+
if ( ! window.matchMedia ) {
Object.defineProperty( window, 'matchMedia', {
writable: true,
diff --git a/projects/plugins/jetpack/yarn.lock b/projects/plugins/jetpack/yarn.lock
index 10b41bd19f394..00b3df4a25b30 100644
--- a/projects/plugins/jetpack/yarn.lock
+++ b/projects/plugins/jetpack/yarn.lock
@@ -3029,6 +3029,15 @@
dependencies:
"@testing-library/dom" "^7.16.2"
+"@testing-library/react-hooks@4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-4.0.0.tgz#5bb4caa5814690cfc3e385ffaaf2ca4d54a8d08e"
+ integrity sha512-AWIR4M1Fz4dYzuKytkWtabcrwpevq7zj9dImuBOcmrpl3VkjOBDa7Q/62fwK/M30ae5XI25mDSpQ29vzC7A5Lw==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+ "@types/react" ">=16.9.0"
+ "@types/react-test-renderer" ">=16.9.0"
+
"@testing-library/react@11.2.3":
version "11.2.3"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.3.tgz#9971ede1c8465a231d7982eeca3c39fc362d5443"
@@ -3193,6 +3202,13 @@
dependencies:
"@types/react" "*"
+"@types/react-test-renderer@>=16.9.0":
+ version "17.0.1"
+ resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b"
+ integrity sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw==
+ dependencies:
+ "@types/react" "*"
+
"@types/react@*", "@types/react@^16.9.0":
version "16.9.51"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.51.tgz#f8aa51ffa9996f1387f63686696d9b59713d2b60"
@@ -3201,6 +3217,20 @@
"@types/prop-types" "*"
csstype "^3.0.2"
+"@types/react@>=16.9.0":
+ version "17.0.3"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
+ integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
+"@types/scheduler@*":
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
+ integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
+
"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"