diff --git a/lib/compat/plugin/media.php b/lib/compat/plugin/media.php deleted file mode 100644 index eafd8b054aea32..00000000000000 --- a/lib/compat/plugin/media.php +++ /dev/null @@ -1,19 +0,0 @@ -assertFalse( $enabled ); + public function test_client_side_media_processing_enabled_by_default_in_plugin() { + $this->assertTrue( gutenberg_is_client_side_media_processing_enabled() ); } /** - * Tests that client-side media processing can be enabled via filter. + * Tests that client-side media processing can be disabled via filter. * * @covers ::gutenberg_is_client_side_media_processing_enabled */ - public function test_client_side_media_processing_can_be_enabled() { - $this->assertTrue( gutenberg_is_client_side_media_processing_enabled() ); + public function test_client_side_media_processing_can_be_disabled_via_filter() { + add_filter( 'wp_client_side_media_processing_enabled', '__return_false' ); + $this->assertFalse( gutenberg_is_client_side_media_processing_enabled() ); + remove_filter( 'wp_client_side_media_processing_enabled', '__return_false' ); } /** @@ -235,14 +227,11 @@ public function test_client_side_media_processing_can_be_enabled() { * @covers ::gutenberg_override_attachments_rest_controller */ public function test_compat_rest_controller_used_when_filter_disabled() { - // Remove the test bootstrap override so the disable filter takes effect. - remove_filter( 'wp_client_side_media_processing_enabled', '__return_true', 20 ); add_filter( 'wp_client_side_media_processing_enabled', '__return_false' ); $result = gutenberg_override_attachments_rest_controller( array(), 'attachment' ); remove_filter( 'wp_client_side_media_processing_enabled', '__return_false' ); - add_filter( 'wp_client_side_media_processing_enabled', '__return_true', 20 ); $this->assertSame( array( 'rest_controller_class' => 'Gutenberg_REST_Attachments_Controller_6_9' ), @@ -256,7 +245,7 @@ public function test_compat_rest_controller_used_when_filter_disabled() { * @covers ::gutenberg_override_attachments_rest_controller */ public function test_compat_rest_controller_not_used_when_filter_enabled() { - // Feature is enabled via test bootstrap filter at priority 20. + // Feature is enabled by default (core compat layer). $result = gutenberg_override_attachments_rest_controller( array(), 'attachment' ); $this->assertSame( array(), $result ); diff --git a/test/e2e/specs/editor/various/big-image-size-threshold.spec.js b/test/e2e/specs/editor/various/big-image-size-threshold.spec.js index a574b4f4ef83c4..30239f121c7e38 100644 --- a/test/e2e/specs/editor/various/big-image-size-threshold.spec.js +++ b/test/e2e/specs/editor/various/big-image-size-threshold.spec.js @@ -21,38 +21,6 @@ test.use( { }, } ); -/** - * Waits for the upload queue to drain and returns the attachment ID - * from the currently selected image block. - * - * @param {Page} page Playwright page object. - * @return {Promise} The attachment ID. - */ -async function waitForUploadAndGetImageId( page ) { - // Wait for the upload queue to be empty. - await page.waitForFunction( - () => { - const uploadStore = window.wp.data.select( 'core/upload-media' ); - if ( ! uploadStore ) { - return true; // Store not available, upload happened server-side. - } - const items = uploadStore.getItems(); - return items.length === 0; - }, - { timeout: 120000 } - ); - - // Get the image ID from the block. - const imageId = await page.evaluate( - () => - window.wp.data.select( 'core/block-editor' ).getSelectedBlock() - ?.attributes?.id - ); - - expect( imageId ).toBeTruthy(); - return imageId; -} - test.describe( 'Big image size threshold', () => { test.beforeAll( async ( { requestUtils } ) => { await requestUtils.deleteAllMedia(); @@ -66,60 +34,14 @@ test.describe( 'Big image size threshold', () => { await requestUtils.deleteAllMedia(); } ); - test( 'should preserve original filename through client-side upload', async ( { - page, - editor, - imageBlockUtils, - requestUtils, - } ) => { - // Skip if cross-origin isolation is not enabled. - const isCrossOriginIsolated = await page.evaluate( - () => window.crossOriginIsolated - ); - // eslint-disable-next-line playwright/no-skipped-test - test.skip( - ! isCrossOriginIsolated, - 'Cross-origin isolation headers not configured on server' - ); - - await editor.insertBlock( { name: 'core/image' } ); - - const imageBlock = editor.canvas.locator( - 'role=document[name="Block: Image"i]' - ); - await expect( imageBlock ).toBeVisible(); - - // Upload with a specific filename to verify it is preserved - // through the cross-realm File handling in convertBlobToFile(). - await imageBlockUtils.uploadWithName( - imageBlock.locator( 'data-testid=form-file-upload-input' ), - '1024x768_e2e_test_image_size.jpeg', - 'my-vacation-photo.jpeg' - ); - - const image = imageBlock.getByRole( 'img', { - name: 'This image has an empty alt attribute', - } ); - await expect( image ).toBeVisible(); - - const imageId = await waitForUploadAndGetImageId( page ); - - const media = await requestUtils.rest( { - method: 'GET', - path: `/wp/v2/media/${ imageId }`, - } ); - - // The uploaded filename should be preserved in the source URL. - expect( media.source_url ).toContain( 'my-vacation-photo' ); - } ); - - test( 'should create scaled version with original_image metadata for large images', async ( { + test( 'should scale down images larger than the threshold', async ( { page, editor, imageBlockUtils, requestUtils, } ) => { // Skip if cross-origin isolation is not enabled. + // The vips library requires SharedArrayBuffer which needs cross-origin isolation. const isCrossOriginIsolated = await page.evaluate( () => window.crossOriginIsolated ); @@ -145,115 +67,85 @@ test.describe( 'Big image size threshold', () => { await expect( imageBlock ).toBeVisible(); // Upload a large image (3200x2400) that exceeds the default threshold (2560). - await imageBlockUtils.uploadWithName( - imageBlock.locator( 'data-testid=form-file-upload-input' ), - '3200x2400_e2e_test_image_responsive_lightbox.jpeg', - 'landscape-photo.jpeg' - ); - - const image = imageBlock.getByRole( 'img', { - name: 'This image has an empty alt attribute', - } ); - await expect( image ).toBeVisible(); - - const imageId = await waitForUploadAndGetImageId( page ); - - const media = await requestUtils.rest( { - method: 'GET', - path: `/wp/v2/media/${ imageId }`, - } ); - - // The scaled version should be set as the main image. - expect( media.source_url ).toContain( '-scaled' ); - - // The original_image metadata should be set to the unscaled filename. - expect( media.media_details.original_image ).toBe( - 'landscape-photo.jpeg' - ); - - // The scaled image should be within the threshold dimensions. - expect( media.media_details.width ).toBeLessThanOrEqual( 2560 ); - expect( media.media_details.height ).toBeLessThanOrEqual( 2560 ); - - // The max dimension should be exactly 2560 (the default threshold). - const maxDimension = Math.max( - media.media_details.width, - media.media_details.height - ); - expect( maxDimension ).toBe( 2560 ); - - // The scaled filename should not have a numeric suffix (e.g. -scaled-1). - // This verifies the regex fix for filter_wp_unique_filename(). - expect( media.source_url ).not.toMatch( /-scaled-\d+/ ); - } ); - - test( 'should generate thumbnails with correct base filename', async ( { - page, - editor, - imageBlockUtils, - requestUtils, - } ) => { - // Skip if cross-origin isolation is not enabled. - const isCrossOriginIsolated = await page.evaluate( - () => window.crossOriginIsolated - ); - // eslint-disable-next-line playwright/no-skipped-test - test.skip( - ! isCrossOriginIsolated, - 'Cross-origin isolation headers not configured on server' - ); - - await editor.insertBlock( { name: 'core/image' } ); - - const imageBlock = editor.canvas.locator( - 'role=document[name="Block: Image"i]' - ); - await expect( imageBlock ).toBeVisible(); - - // Upload a large image with a known filename. - await imageBlockUtils.uploadWithName( + await imageBlockUtils.upload( imageBlock.locator( 'data-testid=form-file-upload-input' ), - '3200x2400_e2e_test_image_responsive_lightbox.jpeg', - 'my-photo.jpeg' + '3200x2400_e2e_test_image_responsive_lightbox.jpeg' ); + // Wait for the upload to complete. const image = imageBlock.getByRole( 'img', { name: 'This image has an empty alt attribute', } ); await expect( image ).toBeVisible(); - const imageId = await waitForUploadAndGetImageId( page ); - - const media = await requestUtils.rest( { - method: 'GET', - path: `/wp/v2/media/${ imageId }`, - } ); - - // Verify thumbnails were generated. - const sizes = media.media_details.sizes; - expect( sizes ).toBeDefined(); - - // Check that at least some standard sizes were created. - const hasStandardSizes = sizes.thumbnail || sizes.medium || sizes.large; - expect( hasStandardSizes ).toBeTruthy(); - - // Verify thumbnail filenames use the correct base filename (my-photo), - // not a UUID or wrong name. This validates the attachment.filename fix - // in generateThumbnails(). - const sizeEntries = Object.entries( sizes ); - for ( const [ sizeName, sizeData ] of sizeEntries ) { - if ( sizeName === 'full' ) { - continue; + // Wait for the image URL to be updated to the final uploaded URL. + await page.waitForFunction( + () => { + const uploadStore = + window.wp.data.select( 'core/upload-media' ); + if ( ! uploadStore ) { + return true; // Store not available, upload happened server-side. + } + const items = uploadStore.getItems(); + return items.length === 0; + }, + { timeout: 120000 } + ); + + // Get the image ID from the block. + const imageId = await page.evaluate( + () => + window.wp.data.select( 'core/block-editor' ).getSelectedBlock() + ?.attributes?.id + ); + + if ( imageId ) { + // Fetch the attachment details from the REST API. + const media = await requestUtils.rest( { + method: 'GET', + path: `/wp/v2/media/${ imageId }`, + } ); + + // The image should be scaled down (either client-side or server-side). + expect( media.media_details.width ).toBeLessThanOrEqual( 2560 ); + expect( media.media_details.height ).toBeLessThanOrEqual( 2560 ); + + if ( media.source_url.includes( '-scaled' ) ) { + // When client-side scaling adds the -scaled suffix, + // original_image may or may not be set depending on whether + // the server also processes the image. Only check if present. + if ( media.media_details.original_image ) { + expect( media.media_details.original_image ).toBeDefined(); + } + + // Verify thumbnails were generated. + const sizes = media.media_details.sizes; + expect( sizes ).toBeDefined(); + + // Check that at least some standard sizes were created. + // The exact sizes depend on theme/site configuration. + const hasStandardSizes = + sizes.thumbnail || sizes.medium || sizes.large; + expect( hasStandardSizes ).toBeTruthy(); + + // If thumbnail exists, verify it has reasonable dimensions. + // Default thumbnail size is 150x150. + if ( sizes.thumbnail ) { + expect( sizes.thumbnail.width ).toBeLessThanOrEqual( 150 ); + expect( sizes.thumbnail.height ).toBeLessThanOrEqual( 150 ); + } + + // If medium exists, verify dimensions. + // Default medium size is 300x300. + if ( sizes.medium ) { + expect( sizes.medium.width ).toBeLessThanOrEqual( 300 ); + expect( sizes.medium.height ).toBeLessThanOrEqual( 300 ); + } } - // Each thumbnail file should start with "my-photo-". - expect( sizeData.file ).toMatch( /^my-photo-/ ); } - - // Verify the scaled version filename. - expect( media.source_url ).toContain( 'my-photo-scaled' ); } ); - test( 'should not scale or set original_image for images below threshold', async ( { + test( 'should not scale images smaller than the threshold', async ( { page, editor, imageBlockUtils, @@ -277,33 +169,51 @@ test.describe( 'Big image size threshold', () => { await expect( imageBlock ).toBeVisible(); // Upload a small image (1024x768) that is below the default threshold (2560). - await imageBlockUtils.uploadWithName( + await imageBlockUtils.upload( imageBlock.locator( 'data-testid=form-file-upload-input' ), - '1024x768_e2e_test_image_size.jpeg', - 'small-photo.jpeg' + '1024x768_e2e_test_image_size.jpeg' ); + // Wait for the upload to complete. const image = imageBlock.getByRole( 'img', { name: 'This image has an empty alt attribute', } ); await expect( image ).toBeVisible(); - const imageId = await waitForUploadAndGetImageId( page ); - - const media = await requestUtils.rest( { - method: 'GET', - path: `/wp/v2/media/${ imageId }`, - } ); - - // The image should NOT be scaled since it's below the threshold. - expect( media.source_url ).not.toContain( '-scaled' ); - - // original_image should NOT be set for images below the threshold. - expect( media.media_details.original_image ).toBeUndefined(); - - // Original dimensions should be preserved exactly. - expect( media.media_details.width ).toBe( 1024 ); - expect( media.media_details.height ).toBe( 768 ); + // Wait for the upload queue to be empty. + await page.waitForFunction( + () => { + const uploadStore = + window.wp.data.select( 'core/upload-media' ); + if ( ! uploadStore ) { + return true; + } + const items = uploadStore.getItems(); + return items.length === 0; + }, + { timeout: 120000 } + ); + + // Get the image ID from the block. + const imageId = await page.evaluate( + () => + window.wp.data.select( 'core/block-editor' ).getSelectedBlock() + ?.attributes?.id + ); + + if ( imageId ) { + // Fetch the attachment details from the REST API. + const media = await requestUtils.rest( { + method: 'GET', + path: `/wp/v2/media/${ imageId }`, + } ); + + // The image should NOT be scaled since it's below the threshold. + expect( media.source_url ).not.toContain( '-scaled' ); + // Original dimensions should be preserved. + expect( media.media_details.width ).toBe( 1024 ); + expect( media.media_details.height ).toBe( 768 ); + } } ); } ); @@ -335,28 +245,4 @@ class ImageBlockUtils { return fileName; } - - /** - * Uploads a file while preserving or renaming to a specific filename. - * Unlike upload() which renames to a UUID, this keeps the original name - * (or uses a custom rename), enabling tests that verify filename handling. - * - * @param {import('@playwright/test').Locator} inputElement The file input element. - * @param {string} customFile Source file in assets directory. - * @param {string|null} rename Optional filename to use instead of original. - * @return {Promise} The filename used for upload. - */ - async uploadWithName( inputElement, customFile, rename = null ) { - const tmpDirectory = await fs.mkdtemp( - path.join( os.tmpdir(), 'gutenberg-test-image-' ) - ); - const targetName = rename || customFile; - const tmpFileName = path.join( tmpDirectory, targetName ); - const filePath = path.join( this.basePath, customFile ); - await fs.copyFile( filePath, tmpFileName ); - - await inputElement.setInputFiles( tmpFileName ); - - return targetName; - } } diff --git a/test/e2e/specs/editor/various/cross-origin-isolation.spec.js b/test/e2e/specs/editor/various/cross-origin-isolation.spec.js index 93baca114212ad..77480feedccdc1 100644 --- a/test/e2e/specs/editor/various/cross-origin-isolation.spec.js +++ b/test/e2e/specs/editor/various/cross-origin-isolation.spec.js @@ -35,10 +35,7 @@ test.use( { }, } ); -// Client-side media processing (and cross-origin isolation) is temporarily -// disabled in the Gutenberg plugin. Skip until re-enabled. -// See https://github.com/WordPress/gutenberg/pull/75756 -test.describe.skip( 'Cross-origin isolation', () => { +test.describe( 'Cross-origin isolation', () => { test.beforeEach( async ( { admin } ) => { await admin.createNewPost(); } );