@wordpress/block-library
Version:
Block library for the WordPress editor.
438 lines (385 loc) • 16.9 kB
JavaScript
/**
* External dependencies
*/
import {
act,
fireEvent,
initializeEditor,
getEditorHtml,
render,
waitFor,
} from 'test/helpers';
import { Image } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
/**
* WordPress dependencies
*/
import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks';
import {
requestMediaPicker,
setFeaturedImage,
sendMediaUpload,
subscribeMediaUpload,
} from '@wordpress/react-native-bridge';
import { select } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { store as coreStore } from '@wordpress/core-data';
import '@wordpress/jest-console';
/**
* Internal dependencies
*/
import { registerCoreBlocks } from '../..';
import ImageEdit from '../edit';
let uploadCallBack;
subscribeMediaUpload.mockImplementation( ( callback ) => {
uploadCallBack = callback;
} );
sendMediaUpload.mockImplementation( ( payload ) => {
uploadCallBack( payload );
} );
function mockGetMedia( media ) {
jest.spyOn( select( coreStore ), 'getMedia' ).mockReturnValue( media );
}
const apiFetchPromise = Promise.resolve( {} );
const clipboardPromise = Promise.resolve( '' );
Clipboard.getString.mockImplementation( () => clipboardPromise );
beforeAll( () => {
registerCoreBlocks();
// Mock Image.getSize to avoid failed attempt to size non-existant image
const getSizeSpy = jest.spyOn( Image, 'getSize' );
getSizeSpy.mockImplementation( ( _url, callback ) => callback( 300, 200 ) );
} );
afterAll( () => {
getBlockTypes().forEach( ( { name } ) => {
unregisterBlockType( name );
} );
// Restore mocks.
Image.getSize.mockRestore();
} );
describe( 'Image Block', () => {
it( 'sets link to None', async () => {
const initialHtml = `
<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"media","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default">
<a href="https://cldup.com/cXyG__fTLN.jpg">
<img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/>
</a>
<figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
const screen = await initializeEditor( { initialHtml } );
// We must await the image fetch via `getMedia`
await act( () => apiFetchPromise );
fireEvent.press( screen.getByA11yLabel( /Image Block/ ) );
// Awaiting navigation event seemingly required due to React Navigation bug
// https://github.com/react-navigation/react-navigation/issues/9701
await act( () =>
fireEvent.press( screen.getByA11yLabel( 'Open Settings' ) )
);
fireEvent.press( screen.getByText( 'Media File' ) );
fireEvent.press( screen.getByText( 'None' ) );
const expectedHtml = `<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"none","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default"><img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/><figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
expect( getEditorHtml() ).toBe( expectedHtml );
} );
it( 'sets link to Media File', async () => {
const initialHtml = `
<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"none","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default">
<img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/>
<figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
const screen = await initializeEditor( { initialHtml } );
// We must await the image fetch via `getMedia`
await act( () => apiFetchPromise );
fireEvent.press( screen.getByA11yLabel( /Image Block/ ) );
// Awaiting navigation event seemingly required due to React Navigation bug
// https://github.com/react-navigation/react-navigation/issues/9701
await act( () =>
fireEvent.press( screen.getByA11yLabel( 'Open Settings' ) )
);
fireEvent.press( screen.getByText( 'None' ) );
fireEvent.press( screen.getByText( 'Media File' ) );
const expectedHtml = `<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"media","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default"><a href="https://cldup.com/cXyG__fTLN.jpg"><img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/></a><figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
expect( getEditorHtml() ).toBe( expectedHtml );
} );
it( 'sets link to Custom URL', async () => {
const initialHtml = `
<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"none","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default">
<img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/>
<figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
const screen = await initializeEditor( { initialHtml } );
// We must await the image fetch via `getMedia`
await act( () => apiFetchPromise );
fireEvent.press( screen.getByA11yLabel( /Image Block/ ) );
// Awaiting navigation event seemingly required due to React Navigation bug
// https://github.com/react-navigation/react-navigation/issues/9701
await act( () =>
fireEvent.press( screen.getByA11yLabel( 'Open Settings' ) )
);
fireEvent.press( screen.getByText( 'None' ) );
fireEvent.press( screen.getByText( 'Custom URL' ) );
// Await asynchronous fetch of clipboard
await act( () => clipboardPromise );
fireEvent.changeText(
screen.getByPlaceholderText( 'Search or type URL' ),
'wordpress.org'
);
fireEvent.press( screen.getByA11yLabel( 'Apply' ) );
await waitFor(
() => new Promise( ( resolve ) => setTimeout( resolve, 100 ) )
);
const expectedHtml = `<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"custom","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default"><a href="http://wordpress.org"><img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/></a><figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
expect( getEditorHtml() ).toBe( expectedHtml );
} );
it( 'swaps the link between destinations', async () => {
const initialHtml = `
<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"none","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default">
<img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/>
<figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
const screen = await initializeEditor( { initialHtml } );
// We must await the image fetch via `getMedia`
await act( () => apiFetchPromise );
fireEvent.press( screen.getByA11yLabel( /Image Block/ ) );
// Awaiting navigation event seemingly required due to React Navigation bug
// https://github.com/react-navigation/react-navigation/issues/9701
await act( () =>
fireEvent.press( screen.getByA11yLabel( 'Open Settings' ) )
);
fireEvent.press( screen.getByText( 'None' ) );
fireEvent.press( screen.getByText( 'Media File' ) );
await waitFor( () => screen.getByText( 'Custom URL' ) );
fireEvent.press( screen.getByText( 'Custom URL' ) );
// Await asynchronous fetch of clipboard
await act( () => clipboardPromise );
fireEvent.changeText(
screen.getByPlaceholderText( 'Search or type URL' ),
'wordpress.org'
);
fireEvent.press( screen.getByA11yLabel( 'Apply' ) );
await waitFor( () => screen.getByText( 'Custom URL' ) );
fireEvent.press( screen.getByText( 'Custom URL' ) );
// Await asynchronous fetch of clipboard
await act( () => clipboardPromise );
fireEvent.press( screen.getByText( 'Media File' ) );
const expectedHtml = `<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"media","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default"><a href="https://cldup.com/cXyG__fTLN.jpg"><img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/></a><figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
expect( getEditorHtml() ).toBe( expectedHtml );
} );
it( 'does not display the Link To URL within the Custom URL input when set to Media File and query parameters are present', async () => {
const initialHtml = `
<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"media","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default">
<a href="https://cldup.com/cXyG__fTLN.jpg">
<img src="https://cldup.com/cXyG__fTLN.jpg?w=683" alt="" class="wp-image-1"/>
</a>
<figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
const screen = await initializeEditor( { initialHtml } );
// We must await the image fetch via `getMedia`
await act( () => apiFetchPromise );
fireEvent.press( screen.getByA11yLabel( /Image Block/ ) );
// Awaiting navigation event seemingly required due to React Navigation bug
// https://github.com/react-navigation/react-navigation/issues/9701
await act( () =>
fireEvent.press( screen.getByA11yLabel( 'Open Settings' ) )
);
fireEvent.press( screen.getByText( 'Media File' ) );
expect( screen.queryByA11yLabel( /https:\/\/cldup\.com/ ) ).toBeNull();
} );
it( 'sets link target', async () => {
const initialHtml = `
<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"custom","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default">
<a href="https://wordpress.org">
<img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/>
</a>
<figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
const screen = await initializeEditor( { initialHtml } );
// We must await the image fetch via `getMedia`
await act( () => apiFetchPromise );
const imageBlock = screen.getByA11yLabel( /Image Block/ );
fireEvent.press( imageBlock );
const settingsButton = screen.getByA11yLabel( 'Open Settings' );
// Awaiting navigation event seemingly required due to React Navigation bug
// https://github.com/react-navigation/react-navigation/issues/9701
await act( () => fireEvent.press( settingsButton ) );
const linkTargetButton = screen.getByText( 'Open in new tab' );
fireEvent.press( linkTargetButton );
const expectedHtml = `<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"custom","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default"><a href="https://wordpress.org" target="_blank" rel="noreferrer noopener"><img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/></a><figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
expect( getEditorHtml() ).toBe( expectedHtml );
} );
it( 'unset link target', async () => {
const initialHtml = `
<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"custom","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default">
<a href="https://wordpress.org" target="_blank" rel="noreferrer noopener">
<img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/>
</a>
<figcaption class="wp-element-caption">Mountain</figcaption>
</figure>
<!-- /wp:image -->`;
const screen = await initializeEditor( { initialHtml } );
// We must await the image fetch via `getMedia`
await act( () => apiFetchPromise );
const imageBlock = screen.getByA11yLabel( /Image Block/ );
fireEvent.press( imageBlock );
const settingsButton = screen.getByA11yLabel( 'Open Settings' );
// Awaiting navigation event seemingly required due to React Navigation bug
// https://github.com/react-navigation/react-navigation/issues/9701
await act( () => fireEvent.press( settingsButton ) );
const linkTargetButton = screen.getByText( 'Open in new tab' );
fireEvent.press( linkTargetButton );
const expectedHtml = `<!-- wp:image {"id":1,"sizeSlug":"large","linkDestination":"custom","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-style-default"><a href="https://wordpress.org"><img src="https://cldup.com/cXyG__fTLN.jpg" alt="" class="wp-image-1"/></a><figcaption class="wp-element-caption">Mountain</figcaption></figure>
<!-- /wp:image -->`;
expect( getEditorHtml() ).toBe( expectedHtml );
} );
describe( "when replacing media for an image set as the post's featured image", () => {
function mockFeaturedMedia( featuredImageId ) {
jest.spyOn(
select( editorStore ),
'getEditedPostAttribute'
).mockImplementation( ( attributeName ) =>
attributeName === 'featured_media' ? featuredImageId : undefined
);
}
it( 'does not prompt to replace featured image during a new image upload', () => {
// Arrange
const INITIAL_IMAGE = { id: 1, url: 'mock-url-1' };
const NEW_IMAGE_PENDING = { id: 2, url: 'mock-url-2' };
mockFeaturedMedia( INITIAL_IMAGE.id );
const screen = render( <ImageEdit attributes={ INITIAL_IMAGE } /> );
// Act
screen.update( <ImageEdit attributes={ NEW_IMAGE_PENDING } /> );
const MEDIA_UPLOAD_STATE_UPLOADING = 1;
sendMediaUpload( {
state: MEDIA_UPLOAD_STATE_UPLOADING,
mediaId: NEW_IMAGE_PENDING.id,
progress: 0.1,
} );
// Assert
expect( setFeaturedImage ).not.toHaveBeenCalled();
} );
it( 'does not prompt to replace featured image after a new image upload fails', () => {
// Arrange
const INITIAL_IMAGE = { id: 1, url: 'mock-url-1' };
const NEW_IMAGE_PENDING = { id: 2, url: 'mock-url-2' };
mockFeaturedMedia( INITIAL_IMAGE.id );
const screen = render(
<ImageEdit
attributes={ INITIAL_IMAGE }
setAttributes={ () => {} }
/>
);
// Act
screen.update(
<ImageEdit
attributes={ NEW_IMAGE_PENDING }
setAttributes={ () => {} }
/>
);
const MEDIA_UPLOAD_STATE_UPLOADING = 1;
sendMediaUpload( {
state: MEDIA_UPLOAD_STATE_UPLOADING,
mediaId: NEW_IMAGE_PENDING.id,
progress: 0.1,
} );
const MEDIA_UPLOAD_STATE_FAILED = 3;
sendMediaUpload( {
state: MEDIA_UPLOAD_STATE_FAILED,
mediaId: NEW_IMAGE_PENDING.id,
} );
// Assert
expect( setFeaturedImage ).not.toHaveBeenCalled();
} );
it( 'prompts to replace featured image after a new image upload succeeds', () => {
// Arrange
const INITIAL_IMAGE = { id: 1, url: 'mock-url-1' };
const NEW_IMAGE_PENDING = { id: 2, url: 'mock-url-2' };
const NEW_IMAGE_RESOLVED = { id: 3, url: 'mock-url-2' };
mockFeaturedMedia( INITIAL_IMAGE.id );
const screen = render( <ImageEdit attributes={ INITIAL_IMAGE } /> );
// Act
screen.update( <ImageEdit attributes={ NEW_IMAGE_PENDING } /> );
mockGetMedia( { id: NEW_IMAGE_RESOLVED.id } );
screen.update(
<ImageEdit
attributes={ NEW_IMAGE_RESOLVED }
setAttributes={ () => {} }
/>
);
// Assert
expect( setFeaturedImage ).toHaveBeenCalledTimes( 1 );
expect( setFeaturedImage ).toHaveBeenCalledWith(
NEW_IMAGE_RESOLVED.id
);
} );
it( 'prompts to replace featured image for a cached image', () => {
// Arrange
const INITIAL_IMAGE = { id: 1, url: 'mock-url-1' };
const NEW_IMAGE_RESOLVED = { id: 3, url: 'mock-url-2' };
mockFeaturedMedia( INITIAL_IMAGE.id );
const screen = render(
<ImageEdit
attributes={ INITIAL_IMAGE }
setAttributes={ () => {} }
/>
);
// Act
mockGetMedia( { id: NEW_IMAGE_RESOLVED.id } );
screen.update(
<ImageEdit
attributes={ NEW_IMAGE_RESOLVED }
setAttributes={ () => {} }
/>
);
// Assert
expect( setFeaturedImage ).toHaveBeenCalledTimes( 1 );
expect( setFeaturedImage ).toHaveBeenCalledWith(
NEW_IMAGE_RESOLVED.id
);
} );
} );
it( 'sets src and alt attributes when selecting media', async () => {
const IMAGE = { id: 1, url: 'mock-image', alt: 'A beautiful mountain' };
requestMediaPicker.mockImplementationOnce(
( source, filter, multiple, callback ) => {
callback( {
id: IMAGE.id,
url: IMAGE.url,
alt: IMAGE.alt,
} );
}
);
mockGetMedia( {
id: IMAGE.id,
source_url: IMAGE.url,
} );
const initialHtml = `
<!-- wp:image -->
<figure class="wp-block-image">
<img alt="" />
</figure>
<!-- /wp:image -->`;
const screen = await initializeEditor( { initialHtml } );
fireEvent.press( screen.getByText( 'ADD IMAGE' ) );
fireEvent.press( screen.getByText( 'WordPress Media Library' ) );
const expectedHtml = `<!-- wp:image {"id":${ IMAGE.id },"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="${ IMAGE.url }" alt="${ IMAGE.alt }" class="wp-image-${ IMAGE.id }"/></figure>
<!-- /wp:image -->`;
expect( getEditorHtml() ).toBe( expectedHtml );
} );
} );