Skip to content

Commit

Permalink
Implement basic in memory cache for rich link previews data (#32741)
Browse files Browse the repository at this point in the history
* Implement closer to API layer using Map

* Only cache successful responses.

* Adds automated test coverage

* Directly return promise chain

Co-authored-by: Kai Hao <kevin830726@gmail.com>

* Rename default import for clarity

Co-authored-by: Kai Hao <kevin830726@gmail.com>
  • Loading branch information
getdave and kevin940726 authored Jun 28, 2021
1 parent 8d3db37 commit d2111e0
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs, prependHTTP } from '@wordpress/url';

/**
* A simple in-memory cache for requests.
* This avoids repeat HTTP requests which may be beneficial
* for those wishing to preserve low-bandwidth.
*/
const CACHE = new Map();

/**
* @typedef WPRemoteUrlData
*
Expand Down Expand Up @@ -38,9 +45,16 @@ const fetchRemoteUrlData = async ( url, options = {} ) => {
url: prependHTTP( url ),
};

if ( CACHE.has( url ) ) {
return CACHE.get( url );
}

return apiFetch( {
path: addQueryArgs( endpoint, args ),
...options,
} ).then( ( res ) => {
CACHE.set( url, res );
return res;
} );
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* Internal dependencies
*/
import fetchRemoteUrlData from '../__experimental-fetch-remote-url-data';
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';

jest.mock( '@wordpress/api-fetch' );

describe( 'fetchRemoteUrlData', () => {
afterEach( () => {
apiFetch.mockReset();
} );

describe( 'return value settles as expected', () => {
it( 'resolves with response data upon fetch success', async () => {
const data = {
title: 'Lorem ipsum dolor',
icon: '',
image: '',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
};
apiFetch.mockReturnValueOnce( Promise.resolve( data ) );

await expect(
fetchRemoteUrlData( 'https://www.wordpress.org' )
).resolves.toEqual( data );

expect( apiFetch ).toBeCalledTimes( 1 );
} );

it( 'rejects with error upon fetch failure', async () => {
apiFetch.mockReturnValueOnce( Promise.reject( 'fetch failed' ) );

await expect(
fetchRemoteUrlData( 'https://www.wordpress.org/1' )
).rejects.toEqual( 'fetch failed' );
} );
} );

describe( 'interaction with underlying fetch API', () => {
it( 'passes options argument through to fetch API', async () => {
apiFetch.mockReturnValueOnce( Promise.resolve() );

await fetchRemoteUrlData( 'https://www.wordpress.org/2', {
method: 'POST',
} );

expect( apiFetch ).toBeCalledTimes( 1 );

const argsPassedToFetchApi = apiFetch.mock.calls[ 0 ][ 0 ];

expect( argsPassedToFetchApi ).toEqual(
expect.objectContaining( {
method: 'POST',
} )
);
} );
} );

describe( 'client side caching', () => {
it( 'caches repeat requests to same url', async () => {
const targetUrl = 'https://www.wordpress.org/3';
const data = {
title: 'Lorem ipsum dolor',
icon: '',
image: '',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
};
apiFetch.mockReturnValueOnce( Promise.resolve( data ) );

await expect( fetchRemoteUrlData( targetUrl ) ).resolves.toEqual(
data
);
expect( apiFetch ).toBeCalledTimes( 1 );

// Allow us to reassert on calls without it being polluted by first fetch
// but retains the mock implementation from earlier.
apiFetch.mockClear();

// Fetch the same URL again...should be cached.
await expect( fetchRemoteUrlData( targetUrl ) ).resolves.toEqual(
data
);

// Should now be in cache so no need to refetch from API.
expect( apiFetch ).toBeCalledTimes( 0 );
} );

it( 'does not cache failed requests', async () => {
const targetUrl = 'https://www.wordpress.org/4';
const data = {
title: 'Lorem ipsum dolor',
icon: '',
image: '',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
};

apiFetch
.mockReturnValueOnce( Promise.reject( 'fetch failed' ) )
.mockReturnValueOnce( Promise.resolve( data ) );

await expect( fetchRemoteUrlData( targetUrl ) ).rejects.toEqual(
'fetch failed'
);

// Cache should not store the previous failed fetch and should retry
// with a new fetch.
await expect( fetchRemoteUrlData( targetUrl ) ).resolves.toEqual(
data
);

expect( apiFetch ).toBeCalledTimes( 2 );
} );
} );
} );

0 comments on commit d2111e0

Please sign in to comment.