From 15426937144a2cd6e34428dd0a64f8c9189519da Mon Sep 17 00:00:00 2001 From: Huub <50170696+huubl@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:34:19 +0200 Subject: [PATCH] Revert "Block Bindings: Refactor e2e tests (#65526)" This reverts commit b3413730c71fc058118f02c94143ab5a087695ce. --- packages/e2e-tests/plugins/block-bindings.php | 133 +- .../e2e-tests/plugins/block-bindings/index.js | 55 - .../editor/various/block-bindings.spec.js | 2451 +++++++++++++++++ .../block-bindings/custom-sources.spec.js | 1204 -------- .../various/block-bindings/post-meta.spec.js | 551 ---- .../block-bindings/index.php | 9 - .../block-bindings/style.css | 15 - .../templates/custom-template.html | 3 - .../block-bindings/templates/index.html | 8 - .../templates/single-movie.html | 2 - .../block-bindings/templates/single.html | 2 - .../block-bindings/theme.json | 18 - 12 files changed, 2472 insertions(+), 1979 deletions(-) delete mode 100644 packages/e2e-tests/plugins/block-bindings/index.js create mode 100644 test/e2e/specs/editor/various/block-bindings.spec.js delete mode 100644 test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js delete mode 100644 test/e2e/specs/editor/various/block-bindings/post-meta.spec.js delete mode 100644 test/gutenberg-test-themes/block-bindings/index.php delete mode 100644 test/gutenberg-test-themes/block-bindings/style.css delete mode 100644 test/gutenberg-test-themes/block-bindings/templates/custom-template.html delete mode 100644 test/gutenberg-test-themes/block-bindings/templates/index.html delete mode 100644 test/gutenberg-test-themes/block-bindings/templates/single-movie.html delete mode 100644 test/gutenberg-test-themes/block-bindings/templates/single.html delete mode 100644 test/gutenberg-test-themes/block-bindings/theme.json diff --git a/packages/e2e-tests/plugins/block-bindings.php b/packages/e2e-tests/plugins/block-bindings.php index ffbe50ab3cc902..0629a397286023 100644 --- a/packages/e2e-tests/plugins/block-bindings.php +++ b/packages/e2e-tests/plugins/block-bindings.php @@ -8,158 +8,67 @@ */ /** - * Code necessary for testing block bindings: - * - Enqueues a custom script to register sources in the client. - * - Registers sources in the server. - * - Registers a custom post type and custom fields. - */ +* Register custom fields and custom block bindings sources. +*/ function gutenberg_test_block_bindings_registration() { - // Define fields list. - $upload_dir = wp_upload_dir(); - $testing_url = $upload_dir['url'] . '/1024x768_e2e_test_image_size.jpeg'; - $fields_list = array( - 'text_field' => array( - 'label' => 'Text Field Label', - 'value' => 'Text Field Value', - ), - 'url_field' => array( - 'label' => 'URL Field Label', - 'value' => $testing_url, - ), - 'empty_field' => array( - 'label' => 'Empty Field Label', - 'value' => '', - ), - ); - - // Enqueue a custom script for the plugin. - wp_enqueue_script( - 'gutenberg-test-block-bindings', - plugins_url( 'block-bindings/index.js', __FILE__ ), - array( - 'wp-blocks', - 'wp-private-apis', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'block-bindings/index.js' ), - true - ); - - // Pass data to the script. - wp_localize_script( - 'gutenberg-test-block-bindings', - 'testingBindings', - array( - 'fieldsList' => $fields_list, - ) - ); - // Register custom block bindings sources. register_block_bindings_source( - 'testing/complete-source', - array( - 'label' => 'Complete Source', - 'get_value_callback' => function ( $source_args ) use ( $fields_list ) { - if ( ! isset( $source_args['key'] ) || ! isset( $fields_list[ $source_args['key'] ] ) ) { - return null; - } - return $fields_list[ $source_args['key'] ]['value']; }, - ) - ); - register_block_bindings_source( - 'testing/server-only-source', + 'core/server-source', array( 'label' => 'Server Source', 'get_value_callback' => function () {}, ) ); - // Register "movie" custom post type. - register_post_type( - 'movie', - array( - 'label' => 'Movie', - 'public' => true, - 'supports' => array( 'title', 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes', 'thumbnail', 'custom-fields', 'post-formats' ), - 'has_archive' => true, - 'show_in_rest' => true, - ) - ); - - // Register global custom fields. + // Register custom fields. register_meta( 'post', 'text_custom_field', array( - 'default' => 'Value of the text custom field', 'show_in_rest' => true, - 'single' => true, 'type' => 'string', + 'single' => true, + 'default' => 'Value of the text custom field', ) ); register_meta( 'post', 'url_custom_field', array( - 'default' => '#url-custom-field', 'show_in_rest' => true, - 'single' => true, 'type' => 'string', - ) - ); - // Register CPT custom fields. - register_meta( - 'post', - 'movie_field', - array( - 'label' => 'Movie field label', - 'default' => 'Movie field default value', - 'object_subtype' => 'movie', - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', - ) - ); - register_meta( - 'post', - 'field_with_only_label', - array( - 'label' => 'Field with only label', - 'object_subtype' => 'movie', - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', + 'single' => true, + 'default' => '#url-custom-field', ) ); register_meta( 'post', - 'field_without_label_or_default', + 'empty_field', array( - 'object_subtype' => 'movie', - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', + 'show_in_rest' => true, + 'type' => 'string', + 'single' => true, + 'default' => '', ) ); register_meta( 'post', '_protected_field', array( - 'default' => 'Protected field value', - 'object_subtype' => 'movie', - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', + 'type' => 'string', + 'show_in_rest' => true, + 'single' => true, + 'default' => 'protected field value', ) ); register_meta( 'post', 'show_in_rest_false_field', array( - 'default' => 'show_in_rest false field value', - 'object_subtype' => 'movie', - 'show_in_rest' => false, - 'single' => true, - 'type' => 'string', + 'show_in_rest' => false, + 'type' => 'string', + 'single' => true, + 'default' => 'show_in_rest false field value', ) ); } diff --git a/packages/e2e-tests/plugins/block-bindings/index.js b/packages/e2e-tests/plugins/block-bindings/index.js deleted file mode 100644 index dfdc706b69c64d..00000000000000 --- a/packages/e2e-tests/plugins/block-bindings/index.js +++ /dev/null @@ -1,55 +0,0 @@ -const { unlock } = - wp.privateApis.__dangerousOptInToUnstableAPIsOnlyForCoreModules( - 'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.', - '@wordpress/blocks' - ); - -const { registerBlockBindingsSource } = unlock( wp.blocks.privateApis ); -const { fieldsList } = window.testingBindings || {}; - -const getValues = ( { bindings } ) => { - const newValues = {}; - for ( const [ attributeName, source ] of Object.entries( bindings ) ) { - newValues[ attributeName ] = fieldsList[ source.args.key ]?.value; - } - return newValues; -}; -const setValues = ( { registry, bindings } ) => { - Object.values( bindings ).forEach( ( { args, newValue } ) => { - // Example of what could be done. - registry.dispatch( 'core' ).editEntityRecord( 'postType', 'post', 1, { - meta: { [ args?.key ]: newValue }, - } ); - } ); -}; - -registerBlockBindingsSource( { - name: 'testing/complete-source', - label: 'Complete Source', - getValues, - setValues, - canUserEditValue: () => true, - getFieldsList: () => fieldsList, -} ); - -registerBlockBindingsSource( { - name: 'testing/can-user-edit-false', - label: 'Can User Edit: False', - getValues, - setValues, - canUserEditValue: () => false, -} ); - -registerBlockBindingsSource( { - name: 'testing/can-user-edit-undefined', - label: 'Can User Edit: Undefined', - getValues, - setValues, -} ); - -registerBlockBindingsSource( { - name: 'testing/set-values-undefined', - label: 'Set Values: Undefined', - getValues, - canUserEditValue: () => true, -} ); diff --git a/test/e2e/specs/editor/various/block-bindings.spec.js b/test/e2e/specs/editor/various/block-bindings.spec.js new file mode 100644 index 00000000000000..f172a424bb1729 --- /dev/null +++ b/test/e2e/specs/editor/various/block-bindings.spec.js @@ -0,0 +1,2451 @@ +/** + * External dependencies + */ +const path = require( 'path' ); +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Block bindings', () => { + let imagePlaceholderSrc; + let imageCustomFieldSrc; + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'emptytheme' ); + await requestUtils.activatePlugin( 'gutenberg-test-block-bindings' ); + await requestUtils.deleteAllMedia(); + const placeholderMedia = await requestUtils.uploadMedia( + path.join( './test/e2e/assets', '10x10_e2e_test_image_z9T8jK.png' ) + ); + imagePlaceholderSrc = placeholderMedia.source_url; + } ); + + test.afterEach( async ( { requestUtils } ) => { + await requestUtils.deleteAllPosts(); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.deleteAllMedia(); + await requestUtils.activateTheme( 'twentytwentyone' ); + await requestUtils.deactivatePlugin( 'gutenberg-test-block-bindings' ); + } ); + + test.describe( 'Template context', () => { + test.beforeEach( async ( { admin, editor } ) => { + await admin.visitSiteEditor( { + postId: 'emptytheme//index', + postType: 'wp_template', + canvas: 'edit', + } ); + await editor.openDocumentSettingsSidebar(); + } ); + + test.describe( 'Paragraph', () => { + test( 'should show the key of the custom field in post meta', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await expect( paragraphBlock ).toHaveText( + 'text_custom_field' + ); + } ); + + test( 'should always show the source label in server-only sources', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'core/server-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await expect( paragraphBlock ).toHaveText( 'Server Source' ); + } ); + + test( 'should lock the appropriate controls with a registered source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await paragraphBlock.click(); + + // Alignment controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Align text' } ) + ).toBeVisible(); + + // Format controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeHidden(); + + // Paragraph is not editable. + await expect( paragraphBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + } ); + + test( 'should lock the appropriate controls when source is not defined', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'plugin/undefined-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await paragraphBlock.click(); + + // Alignment controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Align text' } ) + ).toBeVisible(); + + // Format controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeHidden(); + + // Paragraph is not editable. + await expect( paragraphBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + } ); + } ); + + test.describe( 'Heading', () => { + test( 'should show the key of the custom field', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'heading default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const headingBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Heading', + } ); + await expect( headingBlock ).toHaveText( 'text_custom_field' ); + } ); + + test( 'should lock the appropriate controls with a registered source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'heading default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const headingBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Heading', + } ); + await headingBlock.click(); + + // Alignment controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Align text' } ) + ).toBeVisible(); + + // Format controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeHidden(); + + // Heading is not editable. + await expect( headingBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + } ); + + test( 'should lock the appropriate controls when source is not defined', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'heading default content', + metadata: { + bindings: { + content: { + source: 'plugin/undefined-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const headingBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Heading', + } ); + await headingBlock.click(); + + // Alignment controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Align text' } ) + ).toBeVisible(); + + // Format controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeHidden(); + + // Heading is not editable. + await expect( headingBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + } ); + } ); + + test.describe( 'Button', () => { + test( 'should show the key of the custom field when text is bound', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + text: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + const buttonBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ); + await expect( buttonBlock ).toHaveText( 'text_custom_field' ); + } ); + + test( 'should lock text controls when text is bound to a registered source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + text: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + const buttonBlock = editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ); + await buttonBlock.click(); + + // Alignment controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Align text' } ) + ).toBeVisible(); + + // Format controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeHidden(); + + // Button is not editable. + await expect( buttonBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + + // Link controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Unlink' } ) + ).toBeVisible(); + } ); + + test( 'should lock text controls when text is bound to an undefined source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + text: { + source: 'plugin/undefined-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + const buttonBlock = editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ); + await buttonBlock.click(); + + // Alignment controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Align text' } ) + ).toBeVisible(); + + // Format controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeHidden(); + + // Button is not editable. + await expect( buttonBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + + // Link controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Unlink' } ) + ).toBeVisible(); + } ); + + test( 'should lock url controls when url is bound to a registered source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + const buttonBlock = editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ); + await buttonBlock.click(); + + // Format controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeVisible(); + + // Button is editable. + await expect( buttonBlock ).toHaveAttribute( + 'contenteditable', + 'true' + ); + + // Link controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Link' } ) + ).toBeHidden(); + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Unlink' } ) + ).toBeHidden(); + } ); + + test( 'should lock url controls when url is bound to an undefined source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + url: { + source: 'plugin/undefined-source', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + const buttonBlock = editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ); + await buttonBlock.click(); + + // Format controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeVisible(); + + // Button is editable. + await expect( buttonBlock ).toHaveAttribute( + 'contenteditable', + 'true' + ); + + // Link controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Link' } ) + ).toBeHidden(); + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Unlink' } ) + ).toBeHidden(); + } ); + + test( 'should lock url and text controls when both are bound', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + text: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + const buttonBlock = editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ); + await buttonBlock.click(); + + // Alignment controls are visible. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Align text' } ) + ).toBeVisible(); + + // Format controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Bold', + } ) + ).toBeHidden(); + + // Button is not editable. + await expect( buttonBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + + // Link controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Link' } ) + ).toBeHidden(); + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Unlink' } ) + ).toBeHidden(); + } ); + } ); + + test.describe( 'Image', () => { + test( 'should show the upload form when url is not bound', async ( { + editor, + } ) => { + await editor.insertBlock( { name: 'core/image' } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + await expect( + imageBlock.getByRole( 'button', { name: 'Upload' } ) + ).toBeVisible(); + } ); + + test( 'should NOT show the upload form when url is bound to a registered source', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + await expect( + imageBlock.getByRole( 'button', { name: 'Upload' } ) + ).toBeHidden(); + } ); + + test( 'should NOT show the upload form when url is bound to an undefined source', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + url: { + source: 'plugin/undefined-source', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + await expect( + imageBlock.getByRole( 'button', { name: 'Upload' } ) + ).toBeHidden(); + } ); + + test( 'should lock url controls when url is bound to a registered source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + + // Replace controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + ).toBeHidden(); + + // Image placeholder doesn't show the upload button. + await expect( + imageBlock.getByRole( 'button', { name: 'Upload' } ) + ).toBeHidden(); + + // Alt textarea is enabled and with the original value. + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + ).toBeEnabled(); + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'default alt value' ); + + // Title input is enabled and with the original value. + await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { name: 'Advanced' } ) + .click(); + + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + ).toBeEnabled(); + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'default title value' ); + } ); + + test( 'should lock url controls when url is bound to an undefined source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + url: { + source: 'plugin/undefined-source', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + + // Replace controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + ).toBeHidden(); + + // Image placeholder doesn't show the upload button. + await expect( + imageBlock.getByRole( 'button', { name: 'Upload' } ) + ).toBeHidden(); + + // Alt textarea is enabled and with the original value. + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + ).toBeEnabled(); + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'default alt value' ); + + // Title input is enabled and with the original value. + await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { name: 'Advanced' } ) + .click(); + + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + ).toBeEnabled(); + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'default title value' ); + } ); + + test( 'should disable alt textarea when alt is bound to a registered source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + alt: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + + // Replace controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + ).toBeVisible(); + + // Alt textarea is disabled and with the custom field value. + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + ).toHaveAttribute( 'readonly' ); + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'text_custom_field' ); + + // Title input is enabled and with the original value. + await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { name: 'Advanced' } ) + .click(); + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + ).toBeEnabled(); + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'default title value' ); + } ); + + test( 'should disable alt textarea when alt is bound to an undefined source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + alt: { + source: 'plguin/undefined-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + + // Replace controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + ).toBeVisible(); + + // Alt textarea is disabled and with the custom field value. + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + ).toHaveAttribute( 'readonly' ); + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'default alt value' ); + + // Title input is enabled and with the original value. + await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { name: 'Advanced' } ) + .click(); + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + ).toBeEnabled(); + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'default title value' ); + } ); + + test( 'should disable title input when title is bound to a registered source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + title: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + + // Replace controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + ).toBeVisible(); + + // Alt textarea is enabled and with the original value. + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + ).toBeEnabled(); + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'default alt value' ); + + // Title input is disabled and with the custom field value. + await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { name: 'Advanced' } ) + .click(); + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + ).toHaveAttribute( 'readonly' ); + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'text_custom_field' ); + } ); + + test( 'should disable title input when title is bound to an undefined source', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + title: { + source: 'plugin/undefined-source', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + + // Replace controls exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + ).toBeVisible(); + + // Alt textarea is enabled and with the original value. + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + ).toBeEnabled(); + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'default alt value' ); + + // Title input is disabled and with the custom field value. + await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { name: 'Advanced' } ) + .click(); + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + ).toHaveAttribute( 'readonly' ); + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'default title value' ); + } ); + + test( 'Multiple bindings should lock the appropriate controls', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + alt: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await imageBlock.click(); + + // Replace controls don't exist. + await expect( + page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + ).toBeHidden(); + + // Image placeholder doesn't show the upload button. + await expect( + imageBlock.getByRole( 'button', { name: 'Upload' } ) + ).toBeHidden(); + + // Alt textarea is disabled and with the custom field value. + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + ).toHaveAttribute( 'readonly' ); + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'text_custom_field' ); + + // Title input is enabled and with the original value. + await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { name: 'Advanced' } ) + .click(); + await expect( + page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + ).toBeEnabled(); + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'default title value' ); + } ); + } ); + } ); + + test.describe( 'Post/page context', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost( { title: 'Test bindings' } ); + } ); + test.describe( 'Paragraph', () => { + test( 'should show the value of the custom field when exists', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'paragraph-binding', + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await expect( paragraphBlock ).toHaveText( + 'Value of the text custom field' + ); + + // Check the frontend shows the value of the custom field. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#paragraph-binding' ) + ).toHaveText( 'Value of the text custom field' ); + } ); + + test( "should show the value of the key when custom field doesn't exist", async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'paragraph-binding', + content: 'fallback value', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'non_existing_custom_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await expect( paragraphBlock ).toHaveText( + 'non_existing_custom_field' + ); + // Paragraph is not editable. + await expect( paragraphBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + + // Check the frontend doesn't show the content. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#paragraph-binding' ) + ).toHaveText( 'fallback value' ); + } ); + + test( 'should prioritize the placeholder attribute over the placeholder generated by the bindings API', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + placeholder: 'My custom placeholder', + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'empty_field' }, + }, + }, + }, + }, + } ); + + const paragraphBlock = editor.canvas.getByRole( 'document', { + // Aria-label is changed for empty paragraphs. + name: 'Empty empty_field; start writing to edit its value', + } ); + + await expect( paragraphBlock ).toBeEmpty(); + + const placeholder = paragraphBlock.locator( 'span' ); + await expect( placeholder ).toHaveAttribute( + 'data-rich-text-placeholder', + 'My custom placeholder' + ); + } ); + + test( 'should show the prompt placeholder in field with empty value', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'empty_field' }, + }, + }, + }, + }, + } ); + + const paragraphBlock = editor.canvas.getByRole( 'document', { + // Aria-label is changed for empty paragraphs. + name: 'Empty empty_field; start writing to edit its value', + } ); + + await expect( paragraphBlock ).toBeEmpty(); + + const placeholder = paragraphBlock.locator( 'span' ); + await expect( placeholder ).toHaveAttribute( + 'data-rich-text-placeholder', + 'Add empty_field' + ); + } ); + + test( 'should not show the value of a protected meta field', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'paragraph-binding', + content: 'fallback value', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: '_protected_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await expect( paragraphBlock ).toHaveText( '_protected_field' ); + // Check the frontend doesn't show the content. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#paragraph-binding' ) + ).toHaveText( 'fallback value' ); + } ); + + test( 'should not show the value of a meta field with `show_in_rest` false', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'paragraph-binding', + content: 'fallback value', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'show_in_rest_false_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await expect( paragraphBlock ).toHaveText( + 'show_in_rest_false_field' + ); + // Check the frontend doesn't show the content. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#paragraph-binding' ) + ).toHaveText( 'fallback value' ); + } ); + + test( 'should add empty paragraph block when pressing enter', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + // Select the paragraph and press Enter at the end of it. + const paragraph = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await editor.selectBlocks( paragraph ); + await page.keyboard.press( 'End' ); + await page.keyboard.press( 'Enter' ); + const [ initialParagraph, newEmptyParagraph ] = + await editor.canvas + .locator( '[data-type="core/paragraph"]' ) + .all(); + await expect( initialParagraph ).toHaveText( + 'Value of the text custom field' + ); + await expect( newEmptyParagraph ).toHaveText( '' ); + await expect( newEmptyParagraph ).toBeEditable(); + } ); + + test( 'should NOT be possible to edit the value of the custom field when it is protected', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'protected-field-binding', + content: 'fallback value', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: '_protected_field' }, + }, + }, + }, + }, + } ); + + const protectedFieldBlock = editor.canvas.getByRole( + 'document', + { + name: 'Block: Paragraph', + } + ); + + await expect( protectedFieldBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + } ); + + test( 'should NOT be possible to edit the value of the custom field when it is not shown in the REST API', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'show-in-rest-false-binding', + content: 'fallback value', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'show_in_rest_false_field' }, + }, + }, + }, + }, + } ); + + const showInRestFalseBlock = editor.canvas.getByRole( + 'document', + { + name: 'Block: Paragraph', + } + ); + + await expect( showInRestFalseBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + } ); + test( 'should show a selector for content', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + } ); + await page.getByLabel( 'Attributes options' ).click(); + const contentAttribute = page.getByRole( 'menuitemcheckbox', { + name: 'Show content', + } ); + await expect( contentAttribute ).toBeVisible(); + } ); + test( 'should use a selector to update the content', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'fallback value', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'undefined_field' }, + }, + }, + }, + }, + } ); + await page.getByRole( 'button', { name: 'content' } ).click(); + + await page + .getByRole( 'menuitemradio' ) + .filter( { hasText: 'text_custom_field' } ) + .click(); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + await expect( paragraphBlock ).toHaveText( + 'Value of the text custom field' + ); + } ); + } ); + + test.describe( 'Heading', () => { + test( 'should show the value of the custom field', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + anchor: 'heading-binding', + content: 'heading default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const headingBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Heading', + } ); + await expect( headingBlock ).toHaveText( + 'Value of the text custom field' + ); + + // Check the frontend shows the value of the custom field. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#heading-binding' ) + ).toHaveText( 'Value of the text custom field' ); + } ); + + test( 'should add empty paragraph block when pressing enter', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + anchor: 'heading-binding', + content: 'heading default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + + // Select the heading and press Enter at the end of it. + const heading = editor.canvas.getByRole( 'document', { + name: 'Block: Heading', + } ); + await editor.selectBlocks( heading ); + await page.keyboard.press( 'End' ); + await page.keyboard.press( 'Enter' ); + // Can't use `editor.getBlocks` because it doesn't return the meta value shown in the editor. + const [ initialHeading, newEmptyParagraph ] = + await editor.canvas.locator( '[data-block]' ).all(); + // First block should be the original block. + await expect( initialHeading ).toHaveAttribute( + 'data-type', + 'core/heading' + ); + await expect( initialHeading ).toHaveText( + 'Value of the text custom field' + ); + // Second block should be an empty paragraph block. + await expect( newEmptyParagraph ).toHaveAttribute( + 'data-type', + 'core/paragraph' + ); + await expect( newEmptyParagraph ).toHaveText( '' ); + await expect( newEmptyParagraph ).toBeEditable(); + } ); + test( 'should show a selector for content', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + } ); + await page.getByLabel( 'Attributes options' ).click(); + const contentAttribute = page.getByRole( 'menuitemcheckbox', { + name: 'Show content', + } ); + await expect( contentAttribute ).toBeVisible(); + } ); + } ); + + test.describe( 'Button', () => { + test( 'should show the value of the custom field when text is bound', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + anchor: 'button-text-binding', + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + text: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + const buttonBlock = editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ); + await buttonBlock.click(); + await expect( buttonBlock ).toHaveText( + 'Value of the text custom field' + ); + + // Check the frontend shows the value of the custom field. + const previewPage = await editor.openPreviewPage(); + const buttonDom = previewPage.locator( + '#button-text-binding a' + ); + await expect( buttonDom ).toHaveText( + 'Value of the text custom field' + ); + await expect( buttonDom ).toHaveAttribute( + 'href', + '#default-url' + ); + } ); + + test( 'should use the value of the custom field when url is bound', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + anchor: 'button-url-binding', + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + + // Check the frontend shows the original value of the custom field. + const previewPage = await editor.openPreviewPage(); + const buttonDom = previewPage.locator( + '#button-url-binding a' + ); + await expect( buttonDom ).toHaveText( 'button default text' ); + await expect( buttonDom ).toHaveAttribute( + 'href', + '#url-custom-field' + ); + } ); + + test( 'should use the values of the custom fields when text and url are bound', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + anchor: 'button-multiple-bindings', + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + text: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + + // Check the frontend uses the values of the custom fields. + const previewPage = await editor.openPreviewPage(); + const buttonDom = previewPage.locator( + '#button-multiple-bindings a' + ); + await expect( buttonDom ).toHaveText( + 'Value of the text custom field' + ); + await expect( buttonDom ).toHaveAttribute( + 'href', + '#url-custom-field' + ); + } ); + + test( 'should add empty button block when pressing enter', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + anchor: 'button-text-binding', + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + text: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + await editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ) + .click(); + await page.keyboard.press( 'End' ); + await page.keyboard.press( 'Enter' ); + const [ initialButton, newEmptyButton ] = await editor.canvas + .locator( '[data-type="core/button"]' ) + .all(); + // First block should be the original block. + await expect( initialButton ).toHaveText( + 'Value of the text custom field' + ); + // Second block should be an empty paragraph block. + await expect( newEmptyButton ).toHaveText( '' ); + await expect( newEmptyButton ).toBeEditable(); + } ); + test( 'should show a selector for url, text, linkTarget and rel', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + }, + ], + } ); + await editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ) + .click(); + await page + .getByRole( 'tabpanel', { + name: 'Settings', + } ) + .getByLabel( 'Attributes options' ) + .click(); + const urlAttribute = page.getByRole( 'menuitemcheckbox', { + name: 'Show url', + } ); + await expect( urlAttribute ).toBeVisible(); + const textAttribute = page.getByRole( 'menuitemcheckbox', { + name: 'Show text', + } ); + await expect( textAttribute ).toBeVisible(); + const linkTargetAttribute = page.getByRole( + 'menuitemcheckbox', + { + name: 'Show linkTarget', + } + ); + await expect( linkTargetAttribute ).toBeVisible(); + const relAttribute = page.getByRole( 'menuitemcheckbox', { + name: 'Show rel', + } ); + await expect( relAttribute ).toBeVisible(); + } ); + } ); + + test.describe( 'Image', () => { + test.beforeAll( async ( { requestUtils } ) => { + const customFieldMedia = await requestUtils.uploadMedia( + path.join( + './test/e2e/assets', + '1024x768_e2e_test_image_size.jpeg' + ) + ); + imageCustomFieldSrc = customFieldMedia.source_url; + } ); + + test.beforeEach( async ( { editor, page, requestUtils } ) => { + const postId = await editor.publishPost(); + await requestUtils.rest( { + method: 'POST', + path: '/wp/v2/posts/' + postId, + data: { + meta: { + url_custom_field: imageCustomFieldSrc, + }, + }, + } ); + await page.reload(); + } ); + test( 'should show the value of the custom field when url is bound', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + anchor: 'image-url-binding', + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlockImg = editor.canvas + .getByRole( 'document', { + name: 'Block: Image', + } ) + .locator( 'img' ); + await expect( imageBlockImg ).toHaveAttribute( + 'src', + imageCustomFieldSrc + ); + + // Check the frontend uses the value of the custom field. + const previewPage = await editor.openPreviewPage(); + const imageDom = previewPage.locator( + '#image-url-binding img' + ); + await expect( imageDom ).toHaveAttribute( + 'src', + imageCustomFieldSrc + ); + await expect( imageDom ).toHaveAttribute( + 'alt', + 'default alt value' + ); + await expect( imageDom ).toHaveAttribute( + 'title', + 'default title value' + ); + } ); + + test( 'should show value of the custom field in the alt textarea when alt is bound', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + anchor: 'image-alt-binding', + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + alt: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlockImg = editor.canvas + .getByRole( 'document', { + name: 'Block: Image', + } ) + .locator( 'img' ); + await imageBlockImg.click(); + + // Image src is the placeholder. + await expect( imageBlockImg ).toHaveAttribute( + 'src', + imagePlaceholderSrc + ); + + // Alt textarea should have the custom field value. + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'Value of the text custom field' ); + + // Check the frontend uses the value of the custom field. + const previewPage = await editor.openPreviewPage(); + const imageDom = previewPage.locator( + '#image-alt-binding img' + ); + await expect( imageDom ).toHaveAttribute( + 'src', + imagePlaceholderSrc + ); + await expect( imageDom ).toHaveAttribute( + 'alt', + 'Value of the text custom field' + ); + await expect( imageDom ).toHaveAttribute( + 'title', + 'default title value' + ); + } ); + + test( 'should show value of the custom field in the title input when title is bound', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + anchor: 'image-title-binding', + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + title: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlockImg = editor.canvas + .getByRole( 'document', { + name: 'Block: Image', + } ) + .locator( 'img' ); + await imageBlockImg.click(); + + // Image src is the placeholder. + await expect( imageBlockImg ).toHaveAttribute( + 'src', + imagePlaceholderSrc + ); + + // Title input should have the custom field value. + const advancedButton = page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { + name: 'Advanced', + } ); + const isAdvancedPanelOpen = + await advancedButton.getAttribute( 'aria-expanded' ); + if ( isAdvancedPanelOpen === 'false' ) { + await advancedButton.click(); + } + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'Value of the text custom field' ); + + // Check the frontend uses the value of the custom field. + const previewPage = await editor.openPreviewPage(); + const imageDom = previewPage.locator( + '#image-title-binding img' + ); + await expect( imageDom ).toHaveAttribute( + 'src', + imagePlaceholderSrc + ); + await expect( imageDom ).toHaveAttribute( + 'alt', + 'default alt value' + ); + await expect( imageDom ).toHaveAttribute( + 'title', + 'Value of the text custom field' + ); + } ); + + test( 'Multiple bindings should show the value of the custom fields', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + anchor: 'image-multiple-bindings', + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + alt: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlockImg = editor.canvas + .getByRole( 'document', { + name: 'Block: Image', + } ) + .locator( 'img' ); + await imageBlockImg.click(); + + // Image src is the custom field value. + await expect( imageBlockImg ).toHaveAttribute( + 'src', + imageCustomFieldSrc + ); + + // Alt textarea should have the custom field value. + const altValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ) + .inputValue(); + expect( altValue ).toBe( 'Value of the text custom field' ); + + // Title input should have the original value. + const advancedButton = page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByRole( 'button', { + name: 'Advanced', + } ); + const isAdvancedPanelOpen = + await advancedButton.getAttribute( 'aria-expanded' ); + if ( isAdvancedPanelOpen === 'false' ) { + await advancedButton.click(); + } + const titleValue = await page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Title attribute' ) + .inputValue(); + expect( titleValue ).toBe( 'default title value' ); + + // Check the frontend uses the values of the custom fields. + const previewPage = await editor.openPreviewPage(); + const imageDom = previewPage.locator( + '#image-multiple-bindings img' + ); + await expect( imageDom ).toHaveAttribute( + 'src', + imageCustomFieldSrc + ); + await expect( imageDom ).toHaveAttribute( + 'alt', + 'Value of the text custom field' + ); + await expect( imageDom ).toHaveAttribute( + 'title', + 'default title value' + ); + } ); + test( 'should show a selector for url, id, title and alt', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + } ); + await page + .getByRole( 'tabpanel', { + name: 'Settings', + } ) + .getByLabel( 'Attributes options' ) + .click(); + const urlAttribute = page.getByRole( 'menuitemcheckbox', { + name: 'Show url', + } ); + await expect( urlAttribute ).toBeVisible(); + const idAttribute = page.getByRole( 'menuitemcheckbox', { + name: 'Show id', + } ); + await expect( idAttribute ).toBeVisible(); + const titleAttribute = page.getByRole( 'menuitemcheckbox', { + name: 'Show title', + } ); + await expect( titleAttribute ).toBeVisible(); + const altAttribute = page.getByRole( 'menuitemcheckbox', { + name: 'Show alt', + } ); + await expect( altAttribute ).toBeVisible(); + } ); + } ); + + test.describe( 'Edit custom fields', () => { + test( 'should be possible to edit the value of the custom field from the paragraph', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'paragraph-binding', + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + + await expect( paragraphBlock ).toHaveAttribute( + 'contenteditable', + 'true' + ); + await paragraphBlock.fill( 'new value' ); + // Check that the paragraph content attribute didn't change. + const [ paragraphBlockObject ] = await editor.getBlocks(); + expect( paragraphBlockObject.attributes.content ).toBe( + 'paragraph default content' + ); + // Check the value of the custom field is being updated by visiting the frontend. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#paragraph-binding' ) + ).toHaveText( 'new value' ); + } ); + + // Related issue: https://github.com/WordPress/gutenberg/issues/62347 + test( 'should be possible to use symbols and numbers as the custom field value', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'paragraph-binding', + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + + await expect( paragraphBlock ).toHaveAttribute( + 'contenteditable', + 'true' + ); + await paragraphBlock.fill( '$10.00' ); + // Check the value of the custom field is being updated by visiting the frontend. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#paragraph-binding' ) + ).toHaveText( '$10.00' ); + } ); + + test( 'should be possible to edit the value of the url custom field from the button', async ( { + editor, + page, + pageUtils, + } ) => { + await editor.insertBlock( { + name: 'core/buttons', + innerBlocks: [ + { + name: 'core/button', + attributes: { + anchor: 'button-url-binding', + text: 'button default text', + url: '#default-url', + metadata: { + bindings: { + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + }, + ], + } ); + + // Edit the url. + const buttonBlock = editor.canvas + .getByRole( 'document', { + name: 'Block: Button', + exact: true, + } ) + .getByRole( 'textbox' ); + await buttonBlock.click(); + await page + .getByRole( 'button', { name: 'Edit link', exact: true } ) + .click(); + await page + .getByPlaceholder( 'Search or type URL' ) + .fill( '#url-custom-field-modified' ); + await pageUtils.pressKeys( 'Enter' ); + + // Check that the button url attribute didn't change. + const [ buttonsObject ] = await editor.getBlocks(); + expect( buttonsObject.innerBlocks[ 0 ].attributes.url ).toBe( + '#default-url' + ); + // Check the value of the custom field is being updated by visiting the frontend. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#button-url-binding a' ) + ).toHaveAttribute( 'href', '#url-custom-field-modified' ); + } ); + + test( 'should be possible to edit the value of the url custom field from the image', async ( { + editor, + page, + pageUtils, + requestUtils, + } ) => { + const customFieldMedia = await requestUtils.uploadMedia( + path.join( + './test/e2e/assets', + '1024x768_e2e_test_image_size.jpeg' + ) + ); + imageCustomFieldSrc = customFieldMedia.source_url; + + await editor.insertBlock( { + name: 'core/image', + attributes: { + anchor: 'image-url-binding', + url: imagePlaceholderSrc, + alt: 'default alt value', + title: 'default title value', + metadata: { + bindings: { + url: { + source: 'core/post-meta', + args: { key: 'url_custom_field' }, + }, + }, + }, + }, + } ); + + // Edit image url. + await page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { + name: 'Replace', + } ) + .click(); + await page + .getByRole( 'button', { name: 'Edit link', exact: true } ) + .click(); + await page + .getByPlaceholder( 'Search or type URL' ) + .fill( imageCustomFieldSrc ); + await pageUtils.pressKeys( 'Enter' ); + + // Check that the image url attribute didn't change and still uses the placeholder. + const [ imageBlockObject ] = await editor.getBlocks(); + expect( imageBlockObject.attributes.url ).toBe( + imagePlaceholderSrc + ); + + // Check the value of the custom field is being updated by visiting the frontend. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#image-url-binding img' ) + ).toHaveAttribute( 'src', imageCustomFieldSrc ); + } ); + + test( 'should be possible to edit the value of the text custom field from the image alt', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/image', + attributes: { + anchor: 'image-alt-binding', + url: imagePlaceholderSrc, + alt: 'default alt value', + metadata: { + bindings: { + alt: { + source: 'core/post-meta', + args: { key: 'text_custom_field' }, + }, + }, + }, + }, + } ); + const imageBlockImg = editor.canvas + .getByRole( 'document', { + name: 'Block: Image', + } ) + .locator( 'img' ); + await imageBlockImg.click(); + + // Edit the custom field value in the alt textarea. + const altInputArea = page + .getByRole( 'tabpanel', { name: 'Settings' } ) + .getByLabel( 'Alternative text' ); + await expect( altInputArea ).not.toHaveAttribute( 'readonly' ); + await altInputArea.fill( 'new value' ); + + // Check that the image alt attribute didn't change. + const [ imageBlockObject ] = await editor.getBlocks(); + expect( imageBlockObject.attributes.alt ).toBe( + 'default alt value' + ); + // Check the value of the custom field is being updated by visiting the frontend. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#image-alt-binding img' ) + ).toHaveAttribute( 'alt', 'new value' ); + } ); + + test( 'should not be possible to edit the value of the protected custom fields', async ( { + editor, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'paragraph default content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { key: '_protected_field' }, + }, + }, + }, + }, + } ); + const paragraphBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Paragraph', + } ); + + await expect( paragraphBlock ).toHaveAttribute( + 'contenteditable', + 'false' + ); + } ); + } ); + } ); + + test.describe( 'Sources registration', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost( { title: 'Test bindings' } ); + } ); + + test( 'should show the label of a source only registered in the server', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + metadata: { + bindings: { + content: { + source: 'core/server-source', + }, + }, + }, + }, + } ); + + const bindingsPanel = page.locator( + '.block-editor-bindings__panel' + ); + await expect( bindingsPanel ).toContainText( 'Server Source' ); + } ); + } ); +} ); diff --git a/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js b/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js deleted file mode 100644 index d6563ce9cb5f5f..00000000000000 --- a/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js +++ /dev/null @@ -1,1204 +0,0 @@ -/** - * External dependencies - */ -const path = require( 'path' ); -/** - * WordPress dependencies - */ -const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); - -test.describe( 'Registered sources', () => { - let imagePlaceholderSrc; - let testingImgSrc; - test.beforeAll( async ( { requestUtils } ) => { - await requestUtils.activateTheme( - 'gutenberg-test-themes/block-bindings' - ); - await requestUtils.activatePlugin( 'gutenberg-test-block-bindings' ); - await requestUtils.deleteAllMedia(); - const placeholderMedia = await requestUtils.uploadMedia( - path.join( './test/e2e/assets', '10x10_e2e_test_image_z9T8jK.png' ) - ); - imagePlaceholderSrc = placeholderMedia.source_url; - - const testingImgMedia = await requestUtils.uploadMedia( - path.join( - './test/e2e/assets', - '1024x768_e2e_test_image_size.jpeg' - ) - ); - testingImgSrc = testingImgMedia.source_url; - } ); - - test.beforeEach( async ( { admin } ) => { - await admin.createNewPost( { title: 'Test bindings' } ); - } ); - - test.afterEach( async ( { requestUtils } ) => { - await requestUtils.deleteAllPosts(); - } ); - - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllMedia(); - await requestUtils.activateTheme( 'twentytwentyone' ); - await requestUtils.deactivatePlugin( 'gutenberg-test-block-bindings' ); - } ); - - test.describe( 'getValues', () => { - test( 'should show the returned value in paragraph content', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - anchor: 'connected-paragraph', - content: 'paragraph default content', - metadata: { - bindings: { - content: { - source: 'testing/complete-source', - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( 'Text Field Value' ); - - // Check the frontend shows the value of the custom field. - const previewPage = await editor.openPreviewPage(); - await expect( - previewPage.locator( '#connected-paragraph' ) - ).toHaveText( 'Text Field Value' ); - } ); - test( 'should show the returned value in heading content', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/heading', - attributes: { - anchor: 'connected-heading', - content: 'heading default content', - metadata: { - bindings: { - content: { - source: 'testing/complete-source', - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - const headingBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Heading', - } ); - await expect( headingBlock ).toHaveText( 'Text Field Value' ); - - // Check the frontend shows the value of the custom field. - const previewPage = await editor.openPreviewPage(); - await expect( - previewPage.locator( '#connected-heading' ) - ).toHaveText( 'Text Field Value' ); - } ); - test( 'should show the returned values in button attributes', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/buttons', - innerBlocks: [ - { - name: 'core/button', - attributes: { - anchor: 'connected-button', - text: 'button default text', - url: '#default-url', - metadata: { - bindings: { - text: { - source: 'testing/complete-source', - args: { key: 'text_field' }, - }, - url: { - source: 'testing/complete-source', - args: { key: 'url_field' }, - }, - }, - }, - }, - }, - ], - } ); - - // Check the frontend uses the values of the custom fields. - const previewPage = await editor.openPreviewPage(); - const buttonDom = previewPage.locator( '#connected-button a' ); - await expect( buttonDom ).toHaveText( 'Text Field Value' ); - await expect( buttonDom ).toHaveAttribute( 'href', testingImgSrc ); - } ); - test( 'should show the returned values in image attributes', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/image', - attributes: { - anchor: 'connected-image', - url: imagePlaceholderSrc, - alt: 'default alt value', - title: 'default title value', - metadata: { - bindings: { - url: { - source: 'testing/complete-source', - args: { key: 'url_field' }, - }, - alt: { - source: 'testing/complete-source', - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - const imageBlockImg = editor.canvas - .getByRole( 'document', { - name: 'Block: Image', - } ) - .locator( 'img' ); - await imageBlockImg.click(); - - // Image src is the custom field value. - await expect( imageBlockImg ).toHaveAttribute( - 'src', - testingImgSrc - ); - - // Alt textarea should have the custom field value. - const altValue = await page - .getByRole( 'tabpanel', { name: 'Settings' } ) - .getByLabel( 'Alternative text' ) - .inputValue(); - expect( altValue ).toBe( 'Text Field Value' ); - - // Title input should have the original value. - const advancedButton = page - .getByRole( 'tabpanel', { name: 'Settings' } ) - .getByRole( 'button', { - name: 'Advanced', - } ); - const isAdvancedPanelOpen = - await advancedButton.getAttribute( 'aria-expanded' ); - if ( isAdvancedPanelOpen === 'false' ) { - await advancedButton.click(); - } - const titleValue = await page - .getByRole( 'tabpanel', { name: 'Settings' } ) - .getByLabel( 'Title attribute' ) - .inputValue(); - expect( titleValue ).toBe( 'default title value' ); - - // Check the frontend uses the values of the custom fields. - const previewPage = await editor.openPreviewPage(); - const imageDom = previewPage.locator( '#connected-image img' ); - await expect( imageDom ).toHaveAttribute( 'src', testingImgSrc ); - await expect( imageDom ).toHaveAttribute( - 'alt', - 'Text Field Value' - ); - await expect( imageDom ).toHaveAttribute( - 'title', - 'default title value' - ); - } ); - test( 'should fall back to source label when `getValues` is undefined', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'paragraph default content', - metadata: { - bindings: { - content: { - source: 'testing/server-only-source', - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( 'Server Source' ); - } ); - test( 'should fall back to null when `getValues` is undefined in URL attributes', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/image', - attributes: { - metadata: { - bindings: { - url: { - source: 'testing/server-only-source', - args: { key: 'url_field' }, - }, - }, - }, - }, - } ); - const imageBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Image', - } ); - await expect( - imageBlock.locator( '.components-placeholder__fieldset' ) - ).toHaveText( 'Connected to Server Source' ); - } ); - } ); - - test.describe( 'should lock editing', () => { - // Logic reused accross all the tests that check paragraph editing is locked. - async function testParagraphControlsAreLocked( { - source, - editor, - page, - } ) { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'paragraph default content', - metadata: { - bindings: { - content: { - source, - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await paragraphBlock.click(); - - // Alignment controls exist. - await expect( - page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { name: 'Align text' } ) - ).toBeVisible(); - - // Format controls don't exist. - await expect( - page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { - name: 'Bold', - } ) - ).toBeHidden(); - - // Paragraph is not editable. - await expect( paragraphBlock ).toHaveAttribute( - 'contenteditable', - 'false' - ); - } - test.describe( 'canUserEditValue returns false', () => { - test( 'paragraph', async ( { editor, page } ) => { - await testParagraphControlsAreLocked( { - source: 'testing/can-user-edit-false', - editor, - page, - } ); - } ); - test( 'heading', async ( { editor, page } ) => { - await editor.insertBlock( { - name: 'core/heading', - attributes: { - content: 'heading default content', - metadata: { - bindings: { - content: { - source: 'testing/can-user-edit-false', - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - const headingBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Heading', - } ); - await headingBlock.click(); - - // Alignment controls exist. - await expect( - page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { name: 'Align text' } ) - ).toBeVisible(); - - // Format controls don't exist. - await expect( - page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { - name: 'Bold', - } ) - ).toBeHidden(); - - // Heading is not editable. - await expect( headingBlock ).toHaveAttribute( - 'contenteditable', - 'false' - ); - } ); - test( 'button', async ( { editor, page } ) => { - await editor.insertBlock( { - name: 'core/buttons', - innerBlocks: [ - { - name: 'core/button', - attributes: { - text: 'button default text', - url: '#default-url', - metadata: { - bindings: { - text: { - source: 'testing/can-user-edit-false', - args: { key: 'text_field' }, - }, - url: { - source: 'testing/can-user-edit-false', - args: { key: 'url_field' }, - }, - }, - }, - }, - }, - ], - } ); - const buttonBlock = editor.canvas - .getByRole( 'document', { - name: 'Block: Button', - exact: true, - } ) - .getByRole( 'textbox' ); - await buttonBlock.click(); - - // Alignment controls are visible. - await expect( - page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { name: 'Align text' } ) - ).toBeVisible(); - - // Format controls don't exist. - await expect( - page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { - name: 'Bold', - } ) - ).toBeHidden(); - - // Button is not editable. - await expect( buttonBlock ).toHaveAttribute( - 'contenteditable', - 'false' - ); - - // Link controls don't exist. - await expect( - page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { name: 'Link' } ) - ).toBeHidden(); - await expect( - page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { name: 'Unlink' } ) - ).toBeHidden(); - } ); - test( 'image', async ( { editor, page } ) => { - await editor.insertBlock( { - name: 'core/image', - attributes: { - url: imagePlaceholderSrc, - alt: 'default alt value', - title: 'default title value', - metadata: { - bindings: { - url: { - source: 'testing/can-user-edit-false', - args: { key: 'url_field' }, - }, - alt: { - source: 'testing/can-user-edit-false', - args: { key: 'text_field' }, - }, - title: { - source: 'testing/can-user-edit-false', - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - const imageBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Image', - } ); - await imageBlock.click(); - - // Replace controls don't exist. - await expect( - page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { - name: 'Replace', - } ) - ).toBeHidden(); - - // Image placeholder doesn't show the upload button. - await expect( - imageBlock.getByRole( 'button', { name: 'Upload' } ) - ).toBeHidden(); - - // Alt textarea is disabled and with the custom field value. - await expect( - page - .getByRole( 'tabpanel', { name: 'Settings' } ) - .getByLabel( 'Alternative text' ) - ).toHaveAttribute( 'readonly' ); - const altValue = await page - .getByRole( 'tabpanel', { name: 'Settings' } ) - .getByLabel( 'Alternative text' ) - .inputValue(); - expect( altValue ).toBe( 'Text Field Value' ); - - // Title input is enabled and with the original value. - await page - .getByRole( 'tabpanel', { name: 'Settings' } ) - .getByRole( 'button', { name: 'Advanced' } ) - .click(); - await expect( - page - .getByRole( 'tabpanel', { name: 'Settings' } ) - .getByLabel( 'Title attribute' ) - ).toHaveAttribute( 'readonly' ); - const titleValue = await page - .getByRole( 'tabpanel', { name: 'Settings' } ) - .getByLabel( 'Title attribute' ) - .inputValue(); - expect( titleValue ).toBe( 'Text Field Value' ); - } ); - } ); - // The following tests just check the paragraph and assume is the case for the rest of the blocks. - test( 'canUserEditValue is not defined', async ( { editor, page } ) => { - await testParagraphControlsAreLocked( { - source: 'testing/can-user-edit-undefined', - editor, - page, - } ); - } ); - test( 'setValues is not defined', async ( { editor, page } ) => { - await testParagraphControlsAreLocked( { - source: 'testing/complete-source-undefined', - editor, - page, - } ); - } ); - test( 'source is not defined', async ( { editor, page } ) => { - await testParagraphControlsAreLocked( { - source: 'testing/undefined-source', - editor, - page, - } ); - } ); - } ); - - // Use `core/post-meta` source to test editing to avoid overcomplicating custom sources. - // It needs a source that can be consumed and edited from the server and the editor. - test.describe( 'setValues', () => { - test( 'should be possible to edit the value from paragraph content', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - anchor: 'connected-paragraph', - content: 'paragraph default content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { key: 'text_custom_field' }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - - await expect( paragraphBlock ).toHaveAttribute( - 'contenteditable', - 'true' - ); - await paragraphBlock.fill( 'new value' ); - // Check that the paragraph content attribute didn't change. - const [ paragraphBlockObject ] = await editor.getBlocks(); - expect( paragraphBlockObject.attributes.content ).toBe( - 'paragraph default content' - ); - // Check the value of the custom field is being updated by visiting the frontend. - const previewPage = await editor.openPreviewPage(); - await expect( - previewPage.locator( '#connected-paragraph' ) - ).toHaveText( 'new value' ); - } ); - // Related issue: https://github.com/WordPress/gutenberg/issues/62347 - test( 'should be possible to use symbols and numbers as the custom field value', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - anchor: 'paragraph-binding', - content: 'paragraph default content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { key: 'text_custom_field' }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - - await expect( paragraphBlock ).toHaveAttribute( - 'contenteditable', - 'true' - ); - await paragraphBlock.fill( '$10.00' ); - // Check the value of the custom field is being updated by visiting the frontend. - const previewPage = await editor.openPreviewPage(); - await expect( - previewPage.locator( '#paragraph-binding' ) - ).toHaveText( '$10.00' ); - } ); - test( 'should be possible to edit the value of the url custom field from the button', async ( { - editor, - page, - pageUtils, - } ) => { - await editor.insertBlock( { - name: 'core/buttons', - innerBlocks: [ - { - name: 'core/button', - attributes: { - anchor: 'button-url-binding', - text: 'button default text', - url: '#default-url', - metadata: { - bindings: { - url: { - source: 'core/post-meta', - args: { key: 'url_custom_field' }, - }, - }, - }, - }, - }, - ], - } ); - - // Edit the url. - const buttonBlock = editor.canvas - .getByRole( 'document', { - name: 'Block: Button', - exact: true, - } ) - .getByRole( 'textbox' ); - await buttonBlock.click(); - await page - .getByRole( 'button', { name: 'Edit link', exact: true } ) - .click(); - await page - .getByPlaceholder( 'Search or type URL' ) - .fill( '#url-custom-field-modified' ); - await pageUtils.pressKeys( 'Enter' ); - - // Check that the button url attribute didn't change. - const [ buttonsObject ] = await editor.getBlocks(); - expect( buttonsObject.innerBlocks[ 0 ].attributes.url ).toBe( - '#default-url' - ); - // Check the value of the custom field is being updated by visiting the frontend. - const previewPage = await editor.openPreviewPage(); - await expect( - previewPage.locator( '#button-url-binding a' ) - ).toHaveAttribute( 'href', '#url-custom-field-modified' ); - } ); - test( 'should be possible to edit the value of the url custom field from the image', async ( { - editor, - page, - pageUtils, - requestUtils, - } ) => { - const customFieldMedia = await requestUtils.uploadMedia( - path.join( - './test/e2e/assets', - '1024x768_e2e_test_image_size.jpeg' - ) - ); - testingImgSrc = customFieldMedia.source_url; - - await editor.insertBlock( { - name: 'core/image', - attributes: { - anchor: 'image-url-binding', - url: imagePlaceholderSrc, - alt: 'default alt value', - title: 'default title value', - metadata: { - bindings: { - url: { - source: 'core/post-meta', - args: { key: 'url_custom_field' }, - }, - }, - }, - }, - } ); - - // Edit image url. - await page - .getByRole( 'toolbar', { name: 'Block tools' } ) - .getByRole( 'button', { - name: 'Replace', - } ) - .click(); - await page - .getByRole( 'button', { name: 'Edit link', exact: true } ) - .click(); - await page - .getByPlaceholder( 'Search or type URL' ) - .fill( testingImgSrc ); - await pageUtils.pressKeys( 'Enter' ); - - // Check that the image url attribute didn't change and still uses the placeholder. - const [ imageBlockObject ] = await editor.getBlocks(); - expect( imageBlockObject.attributes.url ).toBe( - imagePlaceholderSrc - ); - - // Check the value of the custom field is being updated by visiting the frontend. - const previewPage = await editor.openPreviewPage(); - await expect( - previewPage.locator( '#image-url-binding img' ) - ).toHaveAttribute( 'src', testingImgSrc ); - } ); - test( 'should be possible to edit the value of the text custom field from the image alt', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/image', - attributes: { - anchor: 'image-alt-binding', - url: imagePlaceholderSrc, - alt: 'default alt value', - metadata: { - bindings: { - alt: { - source: 'core/post-meta', - args: { key: 'text_custom_field' }, - }, - }, - }, - }, - } ); - const imageBlockImg = editor.canvas - .getByRole( 'document', { - name: 'Block: Image', - } ) - .locator( 'img' ); - await imageBlockImg.click(); - - // Edit the custom field value in the alt textarea. - const altInputArea = page - .getByRole( 'tabpanel', { name: 'Settings' } ) - .getByLabel( 'Alternative text' ); - await expect( altInputArea ).not.toHaveAttribute( 'readonly' ); - await altInputArea.fill( 'new value' ); - - // Check that the image alt attribute didn't change. - const [ imageBlockObject ] = await editor.getBlocks(); - expect( imageBlockObject.attributes.alt ).toBe( - 'default alt value' - ); - // Check the value of the custom field is being updated by visiting the frontend. - const previewPage = await editor.openPreviewPage(); - await expect( - previewPage.locator( '#image-alt-binding img' ) - ).toHaveAttribute( 'alt', 'new value' ); - } ); - } ); - - test.describe( 'getFieldsList', () => { - test( 'should be possible to update attribute value through bindings UI', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - } ); - await page.getByLabel( 'Attributes options' ).click(); - await page - .getByRole( 'menuitemcheckbox', { - name: 'Show content', - } ) - .click(); - await page.getByRole( 'button', { name: 'content' } ).click(); - await page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'Text Field Label' } ) - .click(); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( 'Text Field Value' ); - } ); - test( 'should be possible to connect the paragraph content', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - } ); - await page.getByLabel( 'Attributes options' ).click(); - const contentAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show content', - } ); - await expect( contentAttribute ).toBeVisible(); - } ); - test( 'should be possible to connect the heading content', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/heading', - } ); - await page.getByLabel( 'Attributes options' ).click(); - const contentAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show content', - } ); - await expect( contentAttribute ).toBeVisible(); - } ); - test( 'should be possible to connect the button supported attributes', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/buttons', - innerBlocks: [ - { - name: 'core/button', - }, - ], - } ); - await editor.canvas - .getByRole( 'document', { - name: 'Block: Button', - exact: true, - } ) - .getByRole( 'textbox' ) - .click(); - await page - .getByRole( 'tabpanel', { - name: 'Settings', - } ) - .getByLabel( 'Attributes options' ) - .click(); - const urlAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show url', - } ); - await expect( urlAttribute ).toBeVisible(); - const textAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show text', - } ); - await expect( textAttribute ).toBeVisible(); - const linkTargetAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show linkTarget', - } ); - await expect( linkTargetAttribute ).toBeVisible(); - const relAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show rel', - } ); - await expect( relAttribute ).toBeVisible(); - // Check not supported attributes are not included. - const tagNameAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show tagName', - } ); - await expect( tagNameAttribute ).toBeHidden(); - } ); - test( 'should be possible to connect the image supported attributes', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/image', - } ); - await page - .getByRole( 'tabpanel', { - name: 'Settings', - } ) - .getByLabel( 'Attributes options' ) - .click(); - const urlAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show url', - } ); - await expect( urlAttribute ).toBeVisible(); - const idAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show id', - } ); - await expect( idAttribute ).toBeVisible(); - const titleAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show title', - } ); - await expect( titleAttribute ).toBeVisible(); - const altAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show alt', - } ); - await expect( altAttribute ).toBeVisible(); - // Check not supported attributes are not included. - const linkClassAttribute = page.getByRole( 'menuitemcheckbox', { - name: 'Show linkClass', - } ); - await expect( linkClassAttribute ).toBeHidden(); - } ); - test( 'should show all the available fields in the dropdown UI', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'default value', - metadata: { - bindings: { - content: { - source: 'testing/complete-source', - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - await page.getByRole( 'button', { name: 'content' } ).click(); - const textField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'Text Field Label' } ); - await expect( textField ).toBeVisible(); - await expect( textField ).toBeChecked(); - const urlField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'URL Field Label' } ); - await expect( urlField ).toBeVisible(); - await expect( urlField ).not.toBeChecked(); - } ); - test( 'should show the connected fields in the attributes panel', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'default value', - metadata: { - bindings: { - content: { - source: 'testing/complete-source', - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - const contentButton = page.getByRole( 'button', { - name: 'content', - } ); - await expect( contentButton ).toContainText( 'Text Field Label' ); - } ); - } ); - - test.describe( 'RichText workflows', () => { - test( 'should add empty paragraph block when pressing enter in paragraph', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'paragraph default content', - metadata: { - bindings: { - content: { - source: 'testing/complete-source', - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - // Select the paragraph and press Enter at the end of it. - const paragraph = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await editor.selectBlocks( paragraph ); - await page.keyboard.press( 'End' ); - await page.keyboard.press( 'Enter' ); - const [ initialParagraph, newEmptyParagraph ] = await editor.canvas - .locator( '[data-type="core/paragraph"]' ) - .all(); - await expect( initialParagraph ).toHaveText( 'Text Field Value' ); - await expect( newEmptyParagraph ).toHaveText( '' ); - await expect( newEmptyParagraph ).toBeEditable(); - } ); - test( 'should add empty paragraph block when pressing enter in heading', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/heading', - attributes: { - anchor: 'heading-binding', - content: 'heading default content', - metadata: { - bindings: { - content: { - source: 'testing/complete-source', - args: { key: 'text_field' }, - }, - }, - }, - }, - } ); - - // Select the heading and press Enter at the end of it. - const heading = editor.canvas.getByRole( 'document', { - name: 'Block: Heading', - } ); - await editor.selectBlocks( heading ); - await page.keyboard.press( 'End' ); - await page.keyboard.press( 'Enter' ); - // Can't use `editor.getBlocks` because it doesn't return the meta value shown in the editor. - const [ initialHeading, newEmptyParagraph ] = await editor.canvas - .locator( '[data-block]' ) - .all(); - // First block should be the original block. - await expect( initialHeading ).toHaveAttribute( - 'data-type', - 'core/heading' - ); - await expect( initialHeading ).toHaveText( 'Text Field Value' ); - // Second block should be an empty paragraph block. - await expect( newEmptyParagraph ).toHaveAttribute( - 'data-type', - 'core/paragraph' - ); - await expect( newEmptyParagraph ).toHaveText( '' ); - await expect( newEmptyParagraph ).toBeEditable(); - } ); - test( 'should add empty button block when pressing enter in button', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/buttons', - innerBlocks: [ - { - name: 'core/button', - attributes: { - anchor: 'button-text-binding', - text: 'button default text', - url: '#default-url', - metadata: { - bindings: { - text: { - source: 'testing/complete-source', - args: { key: 'text_field' }, - }, - }, - }, - }, - }, - ], - } ); - await editor.canvas - .getByRole( 'document', { - name: 'Block: Button', - exact: true, - } ) - .getByRole( 'textbox' ) - .click(); - await page.keyboard.press( 'End' ); - await page.keyboard.press( 'Enter' ); - const [ initialButton, newEmptyButton ] = await editor.canvas - .locator( '[data-type="core/button"]' ) - .all(); - // First block should be the original block. - await expect( initialButton ).toHaveText( 'Text Field Value' ); - // Second block should be an empty paragraph block. - await expect( newEmptyButton ).toHaveText( '' ); - await expect( newEmptyButton ).toBeEditable(); - } ); - test( 'should show placeholder prompt when value is empty and can edit', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'paragraph default content', - metadata: { - bindings: { - content: { - source: 'testing/complete-source', - args: { key: 'empty_field' }, - }, - }, - }, - }, - } ); - - const paragraphBlock = editor.canvas.getByRole( 'document', { - // Aria-label is changed for empty paragraphs. - name: 'Empty empty_field; start writing to edit its value', - } ); - - await expect( paragraphBlock ).toBeEmpty(); - - const placeholder = paragraphBlock.locator( 'span' ); - await expect( placeholder ).toHaveAttribute( - 'data-rich-text-placeholder', - 'Add Empty Field Label' - ); - } ); - test( 'should show source label when value is empty, cannot edit, and `getFieldsList` is undefined', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'paragraph default content', - metadata: { - bindings: { - content: { - source: 'testing/can-user-edit-false', - args: { key: 'empty_field' }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - // Aria-label is changed for empty paragraphs. - name: 'empty_field', - } ); - await expect( paragraphBlock ).toBeEmpty(); - const placeholder = paragraphBlock.locator( 'span' ); - await expect( placeholder ).toHaveAttribute( - 'data-rich-text-placeholder', - 'Can User Edit: False' - ); - } ); - test( 'should show placeholder attribute over bindings placeholder', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - placeholder: 'My custom placeholder', - content: 'paragraph default content', - metadata: { - bindings: { - content: { - source: 'testing/complete-source', - args: { key: 'empty_field' }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - // Aria-label is changed for empty paragraphs. - name: 'empty_field', - } ); - - await expect( paragraphBlock ).toBeEmpty(); - - const placeholder = paragraphBlock.locator( 'span' ); - await expect( placeholder ).toHaveAttribute( - 'data-rich-text-placeholder', - 'My custom placeholder' - ); - } ); - } ); - - test( 'should show the label of a source only registered in the server in blocks connected', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - metadata: { - bindings: { - content: { - source: 'testing/server-only-source', - }, - }, - }, - }, - } ); - - const contentButton = page.getByRole( 'button', { - name: 'content', - } ); - await expect( contentButton ).toContainText( 'Server Source' ); - } ); - test( 'should show an "Invalid source" warning for not registered sources', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - metadata: { - bindings: { - content: { - source: 'testing/undefined-source', - }, - }, - }, - }, - } ); - - const contentButton = page.getByRole( 'button', { - name: 'content', - } ); - await expect( contentButton ).toContainText( 'Invalid source' ); - } ); -} ); diff --git a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js deleted file mode 100644 index d82def6feb66bf..00000000000000 --- a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js +++ /dev/null @@ -1,551 +0,0 @@ -/** - * WordPress dependencies - */ -const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); - -test.describe( 'Post Meta source', () => { - test.beforeAll( async ( { requestUtils } ) => { - await requestUtils.activateTheme( - 'gutenberg-test-themes/block-bindings' - ); - await requestUtils.activatePlugin( 'gutenberg-test-block-bindings' ); - } ); - - test.afterEach( async ( { requestUtils } ) => { - await requestUtils.deleteAllPosts(); - } ); - - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllMedia(); - await requestUtils.activateTheme( 'twentytwentyone' ); - await requestUtils.deactivatePlugin( 'gutenberg-test-block-bindings' ); - } ); - - test.describe( 'Movie CPT template', () => { - test.beforeEach( async ( { admin, editor } ) => { - await admin.visitSiteEditor( { - postId: 'gutenberg-test-themes/block-bindings//single-movie', - postType: 'wp_template', - canvas: 'edit', - } ); - await editor.openDocumentSettingsSidebar(); - } ); - - test.describe( 'Block attributes values', () => { - test( 'should not be possible to edit connected blocks', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'movie_field', - }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveAttribute( - 'contenteditable', - 'false' - ); - } ); - test( 'should show the default value if it is defined', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'movie_field', - }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( - 'Movie field default value' - ); - } ); - test( 'should fall back to the field label if the default value is not defined', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'field_with_only_label', - }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( - 'Field with only label' - ); - } ); - test( 'should fall back to the field key if the field label is not defined', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'field_without_label_or_default', - }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( - 'field_without_label_or_default' - ); - } ); - } ); - - test.describe( 'Attributes panel', () => { - test( 'should show the field label if it is defined', async ( { - editor, - page, - } ) => { - /** - * Create connection manually until this issue is solved: - * https://github.com/WordPress/gutenberg/pull/65604 - * - * Once solved, block with the binding can be directly inserted. - */ - await editor.insertBlock( { - name: 'core/paragraph', - } ); - await page.getByLabel( 'Attributes options' ).click(); - await page - .getByRole( 'menuitemcheckbox', { - name: 'Show content', - } ) - .click(); - const contentBinding = page.getByRole( 'button', { - name: 'content', - } ); - await contentBinding.click(); - await page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'Movie field label' } ) - .click(); - await expect( contentBinding ).toContainText( - 'Movie field label' - ); - } ); - test( 'should fall back to the field key if the field label is not defined', async ( { - editor, - page, - } ) => { - /** - * Create connection manually until this issue is solved: - * https://github.com/WordPress/gutenberg/pull/65604 - * - * Once solved, block with the binding can be directly inserted. - */ - await editor.insertBlock( { - name: 'core/paragraph', - } ); - await page.getByLabel( 'Attributes options' ).click(); - await page - .getByRole( 'menuitemcheckbox', { - name: 'Show content', - } ) - .click(); - const contentBinding = page.getByRole( 'button', { - name: 'content', - } ); - await contentBinding.click(); - await page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'field_without_label_or_default' } ) - .click(); - await expect( contentBinding ).toContainText( - 'field_without_label_or_default' - ); - } ); - } ); - - test.describe( 'Fields list dropdown', () => { - // Insert block and open the dropdown for every test. - test.beforeEach( async ( { editor, page } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - } ); - await page.getByLabel( 'Attributes options' ).click(); - await page - .getByRole( 'menuitemcheckbox', { - name: 'Show content', - } ) - .click(); - await page - .getByRole( 'button', { - name: 'content', - } ) - .click(); - } ); - - test( 'should include movie fields in UI to connect attributes', async ( { - page, - } ) => { - const movieField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'Movie field label' } ); - await expect( movieField ).toBeVisible(); - } ); - test( 'should include global fields in UI to connect attributes', async ( { - page, - } ) => { - const globalField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'text_custom_field' } ); - await expect( globalField ).toBeVisible(); - } ); - test( 'should not include protected fields', async ( { page } ) => { - // Ensure the fields have loaded by checking the field is visible. - const globalField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'text_custom_field' } ); - await expect( globalField ).toBeVisible(); - // Check the protected fields are not visible. - const protectedField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: '_protected_field' } ); - await expect( protectedField ).toBeHidden(); - const showInRestFalseField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'show_in_rest_false_field' } ); - await expect( showInRestFalseField ).toBeHidden(); - } ); - test( 'should show the default value if it is defined', async ( { - page, - } ) => { - const fieldButton = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'Movie field label' } ); - await expect( fieldButton ).toContainText( - 'Movie field default value' - ); - } ); - test( 'should not show anything if the default value is not defined', async ( { - page, - } ) => { - const fieldButton = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'Field with only label' } ); - // Check it only contains the field label. - await expect( fieldButton ).toHaveText( - 'Field with only label' - ); - } ); - } ); - } ); - - test.describe( 'Custom template', () => { - test.beforeEach( async ( { admin, editor } ) => { - await admin.visitSiteEditor( { - postId: 'gutenberg-test-themes/block-bindings//custom-template', - postType: 'wp_template', - canvas: 'edit', - } ); - await editor.openDocumentSettingsSidebar(); - } ); - - test( 'should not include post meta fields in UI to connect attributes', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'text_custom_field', - }, - }, - }, - }, - }, - } ); - await page - .getByRole( 'button', { - name: 'content', - } ) - .click(); - // Check the fields registered by other sources are there. - const customSourceField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'Text Field Label' } ); - await expect( customSourceField ).toBeVisible(); - // Check the post meta fields are not visible. - const globalField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'text_custom_field' } ); - await expect( globalField ).toBeHidden(); - const movieField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'Movie field label' } ); - await expect( movieField ).toBeHidden(); - } ); - test( 'should show the key in attributes connected to post meta', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'text_custom_field', - }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( 'text_custom_field' ); - } ); - } ); - - test.describe( 'Movie CPT post', () => { - test.beforeEach( async ( { admin } ) => { - // CHECK HOW TO CREATE A MOVIE. - await admin.createNewPost( { - postType: 'movie', - title: 'Test bindings', - } ); - } ); - - test( 'should show the custom field value of that specific post', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - anchor: 'connected-paragraph', - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'movie_field', - }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( - 'Movie field default value' - ); - // Check the frontend shows the value of the custom field. - const previewPage = await editor.openPreviewPage(); - await expect( - previewPage.locator( '#connected-paragraph' ) - ).toHaveText( 'Movie field default value' ); - } ); - test( 'should fall back to the key when custom field is not accessible', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'unaccessible_field', - }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( 'unaccessible_field' ); - await expect( paragraphBlock ).toHaveAttribute( - 'contenteditable', - 'false' - ); - } ); - test( 'should not show or edit the value of a protected field', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: '_protected_field', - }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( '_protected_field' ); - await expect( paragraphBlock ).toHaveAttribute( - 'contenteditable', - 'false' - ); - } ); - test( 'should not show or edit the value of a field with `show_in_rest` set to false', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'show_in_rest_false_field', - }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( - 'show_in_rest_false_field' - ); - await expect( paragraphBlock ).toHaveAttribute( - 'contenteditable', - 'false' - ); - } ); - test( 'should be possible to edit the value of the connected custom fields', async ( { - editor, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { - anchor: 'connected-paragraph', - content: 'fallback content', - metadata: { - bindings: { - content: { - source: 'core/post-meta', - args: { - key: 'movie_field', - }, - }, - }, - }, - }, - } ); - const paragraphBlock = editor.canvas.getByRole( 'document', { - name: 'Block: Paragraph', - } ); - await expect( paragraphBlock ).toHaveText( - 'Movie field default value' - ); - await expect( paragraphBlock ).toHaveAttribute( - 'contenteditable', - 'true' - ); - await paragraphBlock.fill( 'new value' ); - // Check that the paragraph content attribute didn't change. - const [ paragraphBlockObject ] = await editor.getBlocks(); - expect( paragraphBlockObject.attributes.content ).toBe( - 'fallback content' - ); - // Check the value of the custom field is being updated by visiting the frontend. - const previewPage = await editor.openPreviewPage(); - await expect( - previewPage.locator( '#connected-paragraph' ) - ).toHaveText( 'new value' ); - } ); - test( 'should be possible to connect movie fields through the attributes panel', async ( { - editor, - page, - } ) => { - await editor.insertBlock( { - name: 'core/paragraph', - } ); - await page.getByLabel( 'Attributes options' ).click(); - await page - .getByRole( 'menuitemcheckbox', { - name: 'Show content', - } ) - .click(); - await page - .getByRole( 'button', { - name: 'content', - } ) - .click(); - const movieField = page - .getByRole( 'menuitemradio' ) - .filter( { hasText: 'Movie field label' } ); - await expect( movieField ).toBeVisible(); - } ); - } ); -} ); diff --git a/test/gutenberg-test-themes/block-bindings/index.php b/test/gutenberg-test-themes/block-bindings/index.php deleted file mode 100644 index 0c6530acc1aaff..00000000000000 --- a/test/gutenberg-test-themes/block-bindings/index.php +++ /dev/null @@ -1,9 +0,0 @@ - -