From d45fe53b3f94551ccdefdac6a5d53f4cf4245fca Mon Sep 17 00:00:00 2001 From: tijmen Date: Thu, 17 Sep 2026 09:15:50 +0200 Subject: [PATCH 1/6] ensure conversion sizes are consistent --- src/class-tiny-image.php | 20 ++++----- test/unit/TinyImageTest.php | 87 +++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/src/class-tiny-image.php b/src/class-tiny-image.php index 60fac156..1306fa66 100644 --- a/src/class-tiny-image.php +++ b/src/class-tiny-image.php @@ -621,7 +621,8 @@ public function can_be_converted() { /** * Get the targeted conversion. - * If original is already converted, then we use the originals' mimetype. + * If a size is already converted, then we use that size's mimetype so + * sizes never mix formats, even when the settings changed since. * If nothing is converted yet, we use the settings conversion settings. * * @since 3.6.4 @@ -635,15 +636,14 @@ private function convert_to() { return array(); } - if ( isset( $this->sizes[ self::ORIGINAL ] ) ) { - // original is not in sizes so mimetypes are open - return $convert_settings['convert_to']; - } - - $original_img_size = $this->sizes[ self::ORIGINAL ]; - if ( $original_img_size->converted() ) { - // original has been convert so use that mimetype to convert to - return array( $original_img_size->meta['convert']['type'] ); + foreach ( $this->sizes as $size ) { + if ( ! $size->converted() ) { + continue; + } + $type = $size->meta['convert']['type']; + if ( in_array( $type, array( 'image/avif', 'image/webp' ), true ) ) { + return array( $type ); + } } return $convert_settings['convert_to']; diff --git a/test/unit/TinyImageTest.php b/test/unit/TinyImageTest.php index df4072b3..bbffeed2 100644 --- a/test/unit/TinyImageTest.php +++ b/test/unit/TinyImageTest.php @@ -1,5 +1,7 @@ assertEquals(array('image/webp'), $compress_calls[1]['convert_to']); } + /** + * A size added after the original was converted gets the original's format. + */ + public function test_later_compression_converts_to_format_of_earlier_conversion() + { + $this->wp->addOption('tinypng_convert_format', array( + 'convert' => 'on', + 'convert_to' => 'smallest', + )); + $this->wp->addOption('tinypng_sizes', array( + Tiny_Image::ORIGINAL => 'on', + 'thumbnail' => 'on', + )); + $this->wp->stub('get_post_mime_type', function () { + return 'image/png'; + }); + $this->wp->createImages(array()); + + $settings = new Tiny_Settings(); + $mock_compressor = $this->createMock(Tiny_Compress::class); + $convert_to_calls = array(); + $mock_compressor->method('compress_file') + ->willReturnCallback(function ($file, $resize, $preserve, $convert_to) use (&$convert_to_calls) { + $convert_to_calls[] = $convert_to; + return array( + 'input' => array('size' => 12345), + 'output' => array('size' => 12345, 'type' => 'image/png'), + 'convert' => array('type' => 'image/avif', 'size' => 9000, 'path' => 'vfs://root/test.avif'), + ); + }); + $settings->set_compressor($mock_compressor); + + // first run: only the original exists + $tinyimg = new Tiny_Image($settings, 999, $this->wp->getTestMetadata()); + $tinyimg->compress(); + + // second run: a thumbnail has been added since + $this->wp->createImage(1000, '14/01', 'test-thumbnail.png'); + $tinyimg = new Tiny_Image($settings, 999, $this->wp->getTestMetadata()); + $tinyimg->compress(); + + + assertEquals(array('image/avif', 'image/webp'), $convert_to_calls[0], 'original can be converted to avif and webp'); + assertEquals(array('image/avif'), $convert_to_calls[1], 'original output was avif so expect subsequent sizes to avif'); + } + + /** + * Marking as compressed records the original's own mimetype as conversion, + * a size added afterwards must not be converted to that mimetype. + */ + public function test_compression_after_mark_as_compressed_uses_conversion_settings() + { + $this->wp->addOption('tinypng_convert_format', array( + 'convert' => 'on', + 'convert_to' => 'smallest', + )); + $this->wp->addOption('tinypng_sizes', array( + Tiny_Image::ORIGINAL => 'on', + 'thumbnail' => 'on', + )); + $this->wp->stub('get_post_mime_type', function () { + return 'image/png'; + }); + $this->wp->createImages(array()); + + $settings = new Tiny_Settings(); + $tinyimg = new Tiny_Image($settings, 999, $this->wp->getTestMetadata()); + $tinyimg->mark_as_compressed(); + + $mock_compressor = $this->createMock(Tiny_Compress::class); + + // assert that + $mock_compressor->expects($this->once()) + ->method('compress_file') + ->with($this->anything(), $this->anything(), $this->anything(), array('image/avif', 'image/webp')) + ->willReturn(array( + 'input' => array('size' => 1000), + 'output' => array('size' => 1000, 'type' => 'image/png'), + )); + $settings->set_compressor($mock_compressor); + + $this->wp->createImage(1000, '14/01', 'test-thumbnail.png'); + $tinyimg = new Tiny_Image($settings, 999, $this->wp->getTestMetadata()); + $tinyimg->compress(); + } } From 0750c1f60cd3c81f55df21099951126c7ab61c49 Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 30 Sep 2026 10:35:52 +0200 Subject: [PATCH 2/6] add unit test for thumbnail first conversion --- test/unit/TinyImageTest.php | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/unit/TinyImageTest.php b/test/unit/TinyImageTest.php index bbffeed2..f35b12ff 100644 --- a/test/unit/TinyImageTest.php +++ b/test/unit/TinyImageTest.php @@ -415,6 +415,64 @@ public function test_later_compression_converts_to_format_of_earlier_conversion( assertEquals(array('image/avif'), $convert_to_calls[1], 'original output was avif so expect subsequent sizes to avif'); } + public function test_first_conversion_decides_mimetype() + { + $this->wp->addOption('tinypng_convert_format', array( + 'convert' => 'on', + 'convert_to' => 'smallest', + )); + + // first run only compresses thumbnails + $this->wp->addOption('tinypng_sizes', array( + Tiny_Image::ORIGINAL => 'on', + 'thumbnail' => 'on', + )); + + $this->wp->createImage(1000, '14/01', 'test-thumbnail.png'); + + $this->wp->stub('get_post_mime_type', function () { + return 'image/png'; + }); + + $convert_to_calls = array(); + $mock_compressor = $this->createMock(Tiny_Compress::class); + $mock_compressor->method('compress_file') + ->willReturnCallback(function ($file, $resize, $preserve, $convert_to) use (&$convert_to_calls) { + $convert_to_calls[] = array( + 'format' => $convert_to, + 'size' => $file, + ); + return array( + 'input' => array('size' => 1000), + 'output' => array('size' => 500, 'type' => 'image/png'), + 'convert' => array('type' => 'image/avif', 'size' => 300, 'path' => 'vfs://root/test_100x100.avif'), + ); + }); + + $settings = new Tiny_Settings(); + $settings->set_compressor($mock_compressor); + + $tinyimg = new Tiny_Image($settings, 999, $this->wp->getTestMetadata()); + $tinyimg->compress(); + + // first run only compresses thumbnails + assertEquals($convert_to_calls[0], array( + 'size' => "vfs://root/wp-content/uploads/14/01/test-thumbnail.png", + 'format' => array('image/avif', 'image/webp') + ), 'first run should be either webp or avif'); + + // second run: original added + $this->wp->createImage(2500, '14/01', 'test.png'); + $tinyimg = new Tiny_Image($settings, 999, $this->wp->getTestMetadata()); + $tinyimg->compress(); + + // second run compresses the original forced on avif + assertEquals($convert_to_calls[1], array( + 'size' => "vfs://root/wp-content/uploads/14/01/test.png", + 'format' => array('image/avif') + ), 'thumbnail was avif, so original should only have avif'); + } + /** * Marking as compressed records the original's own mimetype as conversion, * a size added afterwards must not be converted to that mimetype. From 11ef4d595498db432acb12fef10f650061876ce6 Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 30 Sep 2026 10:36:20 +0200 Subject: [PATCH 3/6] Add phpunit debug with input filter --- .vscode/launch.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 191e7c26..cd21dcba 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,6 +9,27 @@ "type": "php", "request": "launch", "port": 9003 + }, + { + "name": "XDebug PHPUnit (filter)", + "type": "php", + "request": "launch", + "program": "${workspaceFolder}/vendor/bin/phpunit", + "args": ["--filter", "${input:phpunitFilter}"], + "cwd": "${workspaceFolder}", + "port": 0, + "runtimeArgs": ["-dxdebug.start_with_request=yes"], + "env": { + "XDEBUG_MODE": "debug,develop", + "XDEBUG_CONFIG": "client_port=${port}" + } + } + ], + "inputs": [ + { + "id": "phpunitFilter", + "type": "promptString", + "description": "PHPUnit --filter (test method, class, or Class::method)" } ] } From a4f21e6af5ee524aa7479af7e78e17e3afd6eacb Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 30 Sep 2026 10:42:32 +0200 Subject: [PATCH 4/6] turn off debugger on bin/unit-tests --- bin/unit-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/unit-tests b/bin/unit-tests index f92f3065..b3928ab4 100755 --- a/bin/unit-tests +++ b/bin/unit-tests @@ -1,2 +1,2 @@ #!/bin/bash -vendor/bin/phpunit test/unit $@ +XDEBUG_MODE=${XDEBUG_MODE:-off} vendor/bin/phpunit test/unit "$@" From 91b41af159d27663cd302a4dea858f24c7a41a28 Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 30 Sep 2026 10:43:13 +0200 Subject: [PATCH 5/6] reduce test noise from migration --- test/unit/TinyMigrateTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/TinyMigrateTest.php b/test/unit/TinyMigrateTest.php index 146c40e6..7ceea0b5 100644 --- a/test/unit/TinyMigrateTest.php +++ b/test/unit/TinyMigrateTest.php @@ -9,6 +9,8 @@ class Tiny_Migrate_Test extends Tiny_TestCase public function set_up() { parent::set_up(); + // reduce test noise by swalloing error_log + $this->iniSet('error_log', '/dev/null'); $this->wp->stub('query', function() { return 1; }); From ec697e2167089fe99550430d0907715c53710aa1 Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 30 Sep 2026 10:45:11 +0200 Subject: [PATCH 6/6] turn off debugger on phpcs --- bin/check-style | 2 +- bin/format-style | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/check-style b/bin/check-style index 775595d4..1d8534a1 100755 --- a/bin/check-style +++ b/bin/check-style @@ -1,2 +1,2 @@ #!/bin/bash -vendor/bin/phpcs --standard=phpcs.xml "$@" +XDEBUG_MODE=${XDEBUG_MODE:-off} vendor/bin/phpcs --standard=phpcs.xml "$@" diff --git a/bin/format-style b/bin/format-style index 6b4b0112..947c2e9a 100755 --- a/bin/format-style +++ b/bin/format-style @@ -1,2 +1,2 @@ #!/bin/bash -vendor/bin/phpcbf --standard=phpcs.xml "$@" +XDEBUG_MODE=${XDEBUG_MODE:-off} vendor/bin/phpcbf --standard=phpcs.xml "$@"