Skip to content

Commit

Permalink
Add usePostPrePublishValue hook (#39119)
Browse files Browse the repository at this point in the history
* Create usePostPrePublishValue hook

* Extract re-usable test logic to common utilities

* Add tests for usePostPrePublishValue

* Add changelog

* Make CI happy
  • Loading branch information
manzoorwanijk authored and gogdzl committed Oct 25, 2024
1 parent 649eae9 commit fbfd26c
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: added

Added usePostPrePublishValue hook
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { useEffect, useRef, useState } from '@wordpress/element';

/**
* Preserve a value from the post pre-publish state.
*
* The value will stop updating after the post is published.
*
* @template V
* @param {V} value - The value to preserve.
*
* @return {V} The preserved value.
*/
export function usePostPrePublishValue< V >( value: V ) {
const isPublishing = useSelect(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- `@wordpress/editor` is a nightmare to work with TypeScript
select => ( select( editorStore ) as any ).isPublishingPost(),
[]
);

const [ currentValue, setCurrentValue ] = useState( value );

const valueFrozen = useRef( false );

useEffect( () => {
// Freeze the value after publishing starts.
if ( isPublishing ) {
valueFrozen.current = true;
}

// Since the value is not frozen yet, update the current value.
if ( ! valueFrozen.current ) {
setCurrentValue( value );
}
}, [ isPublishing, value ] );

return currentValue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { act, renderHook } from '@testing-library/react';
import { RegistryProvider } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { usePostPrePublishValue } from '../';
import {
connections as connectionsList,
createRegistryWithStores,
} from '../../../utils/test-utils';

const connections = connectionsList.map( connection => ( { ...connection, enabled: true } ) );

const post = {
jetpack_publicize_connections: [ connections[ 0 ] ],
};

describe( 'usePostPrePublishValue', () => {
it( 'should return the value by default', async () => {
const registry = createRegistryWithStores( post );

const { result } = renderHook( () => usePostPrePublishValue( 'test-value' ), {
wrapper: ( { children } ) => (
<RegistryProvider value={ registry }>{ children }</RegistryProvider>
),
} );

expect( result.current ).toBe( 'test-value' );
} );

it( 'should return the updated value when the post is not being published', async () => {
const registry = createRegistryWithStores( post );

const { rerender, result } = renderHook(
( initialValue = 'first-value' ) => usePostPrePublishValue( initialValue ),
{
wrapper: ( { children } ) => (
<RegistryProvider value={ registry }>{ children }</RegistryProvider>
),
}
);

rerender( 'second-value' );

await act( async () => {
await registry.dispatch( editorStore ).editPost( {
status: 'draft',
content: 'Some test content',
} );
} );

expect( result.current ).toBe( 'second-value' );
} );

it( 'should preserve the pre-publish value', async () => {
const registry = createRegistryWithStores( post );

const { rerender, result } = renderHook(
( initialValue = 'first-value' ) => usePostPrePublishValue( initialValue ),
{
wrapper: ( { children } ) => (
<RegistryProvider value={ registry }>{ children }</RegistryProvider>
),
}
);

rerender( 'second-value' );

await act( async () => {
registry.dispatch( editorStore ).editPost( {
status: 'publish',
content: 'Some test content',
} );
registry.dispatch( editorStore ).savePost();
} );

rerender( 'third-value' );

expect( result.current ).toBe( 'second-value' );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { store as socialStore } from '../../../social-store';
import {
connections as connectionsList,
createRegistryWithStores,
testPost,
postPublishFetchHandler,
} from '../../../utils/test-utils';

const connections = connectionsList.map( connection => ( { ...connection, enabled: true } ) );
Expand All @@ -16,9 +16,6 @@ const post = {
jetpack_publicize_connections: [ connections[ 0 ] ],
};

const getMethod = options =>
options.headers?.[ 'X-HTTP-Method-Override' ] || options.method || 'GET';

describe( 'useSyncPostDataToStore', () => {
it( 'should do nothing by default', async () => {
const registry = createRegistryWithStores( post );
Expand Down Expand Up @@ -67,37 +64,7 @@ describe( 'useSyncPostDataToStore', () => {
await registry.resolveSelect( socialStore ).getConnections();

// Mock apiFetch response.
apiFetch.setFetchHandler( async options => {
const method = getMethod( options );
const { path, data, parse = true } = options;

const wrapReturn = parse
? v => v
: v =>
// Ideally we'd do `new Response( JSON.stringify( v ) )` here, but jsdom deletes that. Sigh.
// See https://github.com/jsdom/jsdom/issues/1724
( {
async json() {
return v;
},
} );

if ( method === 'PUT' && path.startsWith( `/wp/v2/posts/${ testPost.id }` ) ) {
return wrapReturn( { ...post, ...data } );
} else if (
// This URL is requested by the actions dispatched in this test.
// They are safe to ignore and are only listed here to avoid triggeringan error.
method === 'GET' &&
path.startsWith( '/wp/v2/types/post' )
) {
return wrapReturn( {} );
}

throw {
code: 'unknown_path',
message: `Unknown path: ${ method } ${ path }`,
};
} );
apiFetch.setFetchHandler( postPublishFetchHandler( post ) );

const prevConnections = registry.select( socialStore ).getConnections();

Expand Down
51 changes: 51 additions & 0 deletions projects/js-packages/publicize-components/src/utils/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,54 @@ export function createActiveConnections( count ) {
},
];
}

const getMethod = options =>
options.headers?.[ 'X-HTTP-Method-Override' ] || options.method || 'GET';

/**
* Get the mocked fetch handler for post publish fetch requests.
*
* @param {Record<string, any>} postData - Data to be used in the fetch request.
*
* @return {(options: import('@wordpress/api-fetch/build-types/types').APIFetchOptions) => Promise<any>} Promise resolving to the fetch response
*/
export function postPublishFetchHandler( postData ) {
/**
* The mocked fetch handler for post publish fetch requests.
*
* @param {import('@wordpress/api-fetch/build-types/types').APIFetchOptions} options - Fetch options.
*
* @return {Promise<any>} Promise resolving to the fetch response
*/
return async function ( options ) {
const method = getMethod( options );
const { path, data, parse = true } = options;

const wrapReturn = parse
? v => v
: v =>
// Ideally we'd do `new Response( JSON.stringify( v ) )` here, but jsdom deletes that. Sigh.
// See https://github.com/jsdom/jsdom/issues/1724
( {
async json() {
return v;
},
} );

if ( method === 'PUT' && path.startsWith( `/wp/v2/posts/${ testPost.id }` ) ) {
return wrapReturn( { ...postData, ...data } );
} else if (
// This URL is requested by the actions dispatched in this test.
// They are safe to ignore and are only listed here to avoid triggeringan error.
method === 'GET' &&
path.startsWith( '/wp/v2/types/post' )
) {
return wrapReturn( {} );
}

throw {
code: 'unknown_path',
message: `Unknown path: ${ method } ${ path }`,
};
};
}

0 comments on commit fbfd26c

Please sign in to comment.