@wordpress/block-library
Version:
Block library for the WordPress editor.
358 lines (315 loc) • 10.9 kB
JavaScript
import { beforeEach, describe, expect, test } from 'vitest';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createBlock, registerBlockType } from '@wordpress/blocks';
import {
store as blockEditorStore,
useBlockProps,
} from '@wordpress/block-editor';
import { useDispatch } from '@wordpress/data';
import { createElement, useEffect } from '@wordpress/element';
import {
initializeEditor,
selectBlock,
} from '@wordpress/integration-tests/helpers/integration-test-editor';
import { registerCoreBlocks } from '@wordpress/block-library';
import { unlock } from '../../lock-unlock';
globalThis.wpVitest.mockMatchMedia();
globalThis.wpVitest.mockResizeObserver();
const IMAGE_ATTRIBUTES = {
id: 1,
url: 'https://example.com/image.jpg',
alt: 'Example image',
};
const VIEWPORT_STATE_INITIALIZER = 'test/viewport-state-initializer';
const createGallery = ( attributes = {} ) =>
createBlock( 'core/gallery', attributes, [
createBlock( 'core/image', IMAGE_ATTRIBUTES ),
createBlock( 'core/image', {
...IMAGE_ATTRIBUTES,
id: 2,
} ),
] );
// The Gallery only offers its aspect ratio control when the theme or the
// defaults provide ratios to choose from.
const ASPECT_RATIO_SETTINGS = {
__experimentalFeatures: {
dimensions: {
defaultAspectRatios: true,
aspectRatios: {
default: [
{ name: 'Square - 1:1', slug: 'square', ratio: '1' },
{ name: 'Wide - 16:9', slug: '16-9', ratio: '16/9' },
],
},
},
},
};
function ViewportStateInitializer() {
const { setResponsiveEditing, setStyleStateViewport } = unlock(
useDispatch( blockEditorStore )
);
useEffect( () => {
setResponsiveEditing( true );
setStyleStateViewport( '@mobile' );
}, [ setResponsiveEditing, setStyleStateViewport ] );
return createElement( 'div', useBlockProps() );
}
/**
* Registers the core blocks up front so inner blocks can be created before the
* editor is initialized, then renders the given block.
*
* @param {Object} block Block to render, created with `createBlock`.
* @param {Object} settings Additional editor settings.
*/
async function setup( block, settings ) {
return initializeEditor( block, false, settings );
}
describe( 'Gallery block', () => {
beforeEach( () => {
registerCoreBlocks();
registerBlockType( VIEWPORT_STATE_INITIALIZER, {
apiVersion: 3,
title: 'Viewport state initializer',
category: 'text',
supports: {
html: false,
inserter: false,
},
edit: ViewportStateInitializer,
save: () => null,
} );
} );
describe( 'Block toolbar', () => {
test( 'does not offer alignment for an image inside a gallery', async () => {
await setup(
createBlock( 'core/gallery', {}, [
createBlock( 'core/image', IMAGE_ATTRIBUTES ),
] )
);
await selectBlock( 'Block: Image' );
// The gallery lays its images out with the flex layout, which
// permits no alignments, so the child image must not offer them.
expect(
screen.queryByRole( 'button', { name: 'Align block' } )
).not.toBeInTheDocument();
} );
test( 'offers alignment for an image outside a gallery', async () => {
await setup( createBlock( 'core/image', IMAGE_ATTRIBUTES ) );
await selectBlock( 'Block: Image' );
expect(
screen.getByRole( 'button', { name: 'Align block' } )
).toBeInTheDocument();
} );
} );
describe( 'Order by', () => {
test( 'offers a sort control in Settings for a static gallery that reads as a custom order until the media resolves', async () => {
await setup( createGallery() );
await selectBlock( 'Block: Gallery' );
const orderBy = await screen.findByRole( 'combobox', {
name: 'Order by',
} );
expect( orderBy ).toHaveDisplayValue( 'Custom' );
// The attachment records never resolve in this environment, so
// there's nothing to sort by yet; Random doesn't need them. Custom
// is only choosable while Random is on, since that's all it does.
expect(
screen.getByRole( 'option', { name: 'Newest to oldest' } )
).toBeDisabled();
expect(
screen.getByRole( 'option', { name: 'Custom' } )
).toBeDisabled();
expect(
screen.getByRole( 'option', { name: 'Random' } )
).toBeEnabled();
} );
test( 'stores a random order for a static gallery and clears it when choosing the custom order', async () => {
await setup( createGallery() );
await selectBlock( 'Block: Gallery' );
const orderBy = await screen.findByRole( 'combobox', {
name: 'Order by',
} );
// The select is controlled from the `randomOrder` attribute, so
// its display value reflects the stored state after each change.
await userEvent.selectOptions( orderBy, 'random' );
expect( orderBy ).toHaveDisplayValue( 'Random' );
expect(
screen.getByRole( 'option', { name: 'Custom' } )
).toBeEnabled();
await userEvent.selectOptions( orderBy, 'custom' );
expect( orderBy ).toHaveDisplayValue( 'Custom' );
expect(
screen.getByRole( 'option', { name: 'Custom' } )
).toBeDisabled();
} );
test( 'offers a random order but no custom order for a dynamic gallery', async () => {
await setup(
createBlock( 'core/gallery', {
dynamicContent: { source: 'core/attached-media' },
} )
);
await selectBlock( 'Block: Dynamic Gallery' );
const orderBy = await screen.findByRole( 'combobox', {
name: 'Order by',
} );
expect( orderBy ).toHaveDisplayValue( 'Newest to oldest' );
expect(
screen.queryByRole( 'option', { name: 'Custom' } )
).not.toBeInTheDocument();
// The order lives in Settings; the Source panel is a plain
// PanelBody with no resettable items, hence no options menu.
expect(
screen.getByRole( 'button', { name: 'Settings options' } )
).toBeInTheDocument();
expect(
screen.queryByRole( 'button', { name: 'Source options' } )
).not.toBeInTheDocument();
await userEvent.selectOptions( orderBy, 'random' );
expect( orderBy ).toHaveDisplayValue( 'Random' );
await userEvent.selectOptions( orderBy, 'title/asc' );
expect( orderBy ).toHaveDisplayValue( 'A → Z' );
} );
} );
describe( 'Layout', () => {
test( 'keeps the custom Gallery controls for the default Flex layout', async () => {
await setup( createGallery() );
await selectBlock( 'Block: Gallery' );
expect(
await screen.findByRole( 'slider', { name: 'Columns' } )
).toBeInTheDocument();
expect(
screen.getByLabelText( 'Crop images to fit' )
).toBeInTheDocument();
expect(
screen.queryByText( 'Min. column width' )
).not.toBeInTheDocument();
} );
test( 'shows only viewport-enabled Flex Gallery settings while editing a viewport state', async () => {
await setup( [
createGallery(),
createBlock( VIEWPORT_STATE_INITIALIZER ),
] );
await selectBlock( 'Block: Gallery' );
expect(
await screen.findByRole( 'slider', { name: 'Columns' } )
).toBeInTheDocument();
expect(
screen.getByLabelText( 'Crop images to fit' )
).toBeInTheDocument();
expect(
screen.queryByRole( 'combobox', { name: 'Order by' } )
).not.toBeInTheDocument();
} );
test( 'does not show a Settings tab when the Gallery has no viewport-enabled controls', async () => {
await setup( [
createGallery( { layout: { type: 'grid' } } ),
createBlock( VIEWPORT_STATE_INITIALIZER ),
] );
await selectBlock( 'Block: Gallery Grid' );
expect(
screen.queryByRole( 'tab', { name: 'Settings' } )
).not.toBeInTheDocument();
expect(
screen.queryByLabelText( 'Crop images to fit' )
).not.toBeInTheDocument();
} );
test( 'offers the aspect ratio for a viewport state in a non-Flex layout', async () => {
await setup(
[
createGallery( { layout: { type: 'grid' } } ),
createBlock( VIEWPORT_STATE_INITIALIZER ),
],
ASPECT_RATIO_SETTINGS
);
await selectBlock( 'Block: Gallery Grid' );
// Columns and crop to fit are Flex-only, but the aspect ratio
// applies in every layout, so the Grid Gallery has a viewport
// setting to show.
expect(
await screen.findByRole( 'combobox', { name: 'Aspect ratio' } )
).toBeInTheDocument();
expect(
screen.queryByLabelText( 'Crop images to fit' )
).not.toBeInTheDocument();
} );
test( 'stores a viewport aspect ratio on the Gallery instead of the images', async () => {
await setup(
[ createGallery(), createBlock( VIEWPORT_STATE_INITIALIZER ) ],
ASPECT_RATIO_SETTINGS
);
await selectBlock( 'Block: Gallery' );
await userEvent.selectOptions(
await screen.findByRole( 'combobox', { name: 'Aspect ratio' } ),
'16/9'
);
// The ratio is rendered from the Gallery's own responsive CSS for
// the viewport, so the images keep their base inline style.
screen.getAllByRole( 'img' ).forEach( ( image ) => {
// eslint-disable-next-line jest-dom/prefer-to-have-style -- toHaveStyle() is disallowed in jsdom tests.
expect( image.style.aspectRatio ).not.toBe( '16/9' );
} );
// The generated stylesheet has no accessible query to find it by.
// eslint-disable-next-line testing-library/no-node-access
const styleElements = document.querySelectorAll( 'style' );
expect(
Array.from( styleElements ).some( ( element ) =>
element.textContent.includes(
'aspect-ratio:16/9 !important'
)
)
).toBe( true );
} );
test( 'preserves layout settings when switching Gallery variations', async () => {
await setup(
createGallery( {
columns: 2,
imageCrop: false,
layout: {
type: 'grid',
columnCount: 4,
minimumColumnWidth: '10rem',
},
} )
);
await selectBlock( 'Block: Gallery Grid' );
expect(
screen.queryByLabelText( 'Crop images to fit' )
).not.toBeInTheDocument();
expect(
screen.queryByRole( 'slider', { name: 'Columns' } )
).not.toBeInTheDocument();
expect(
screen.getByRole( 'radio', { name: 'Gallery Grid' } )
).toBeChecked();
await userEvent.click(
await screen.findByRole( 'radio', {
name: 'Transform to Gallery',
} )
);
// Switching layouts only changes the layout attribute. The Gallery's
// previous Flex settings are restored when switching back.
expect(
screen.getByRole( 'slider', { name: 'Columns' } )
).toHaveValue( '2' );
expect(
screen.getByLabelText( 'Crop images to fit' )
).not.toBeChecked();
await userEvent.click(
screen.getByRole( 'radio', {
name: 'Transform to Gallery Grid',
} )
);
await userEvent.click(
screen.getByRole( 'tab', { name: 'Styles' } )
);
expect(
await screen.findByRole( 'spinbutton', { name: 'Columns' } )
).toHaveDisplayValue( '4' );
expect(
await screen.findByRole( 'spinbutton', {
name: 'Minimum column width',
} )
).toHaveDisplayValue( '10' );
} );
} );
} );