diff --git a/.github/workflows/code_coverage.yml b/.github/workflows/code_coverage.yml index 5daecfb9e..db911b3ce 100644 --- a/.github/workflows/code_coverage.yml +++ b/.github/workflows/code_coverage.yml @@ -65,6 +65,7 @@ jobs: - name: Run codacy-coverage-reporter uses: codacy/codacy-coverage-reporter-action@v1 + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} with: project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} coverage-reports: tests/report/coverage.clover diff --git a/Tests/Integration/TestCase.php b/Tests/Integration/TestCase.php index be55aed71..a9aac290d 100644 --- a/Tests/Integration/TestCase.php +++ b/Tests/Integration/TestCase.php @@ -138,8 +138,8 @@ protected function setPropertyValue( $property, $class, $value ) { $ref->setValue( $class, $value ); } else { $previous = $ref->getValue(); - // Static property. - $ref->setValue( $value ); + // Static property. Pass null as the object argument to avoid PHP 8.4 deprecation. + $ref->setValue( null, $value ); } return $previous; diff --git a/Tests/Integration/classes/Tracking/Subscriber/Test_OptimizeHookIntegration.php b/Tests/Integration/classes/Tracking/Subscriber/Test_OptimizeHookIntegration.php new file mode 100644 index 000000000..7d86e2a80 --- /dev/null +++ b/Tests/Integration/classes/Tracking/Subscriber/Test_OptimizeHookIntegration.php @@ -0,0 +1,102 @@ + Subscriber hook chain. + * + * Verifies the Subscriber is registered as an event listener and that firing + * the hook triggers its track_media_optimized method. Uses a mock Tracking + * object swapped into the live Subscriber to prove the chain runs end-to-end. + * + * @package Imagify\Tests\Integration + * @category Test + */ + +namespace Imagify\Tests\Integration\classes\Tracking\Subscriber; + +use Imagify\Optimization\Process\ProcessInterface; +use Imagify\Tracking\Subscriber; +use Imagify\Tracking\Tracking; +use Imagify\Tests\Integration\TestCase; + +defined( 'ABSPATH' ) || exit; + +/** + * Tests the full integration path for imagify_after_optimize. + */ +class Test_OptimizeHookIntegration extends TestCase { + + /** + * Whether to use the Imagify API in these tests. + * + * @var bool + */ + protected $useApi = false; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase + + /** + * PHPUnit mock of the Tracking service injected into the live Subscriber. + * + * Used to verify that the Subscriber delegates to Tracking::track_media_optimized() + * when imagify_after_optimize fires. + * + * @var \PHPUnit\Framework\MockObject\MockObject|Tracking + */ + private $mock_tracking; + + /** + * Set up integration test fixtures. + * + * Resolve the real Subscriber from the DI container and inject a mock Tracking + * instance via reflection. Mocking Tracking keeps the Subscriber real while + * allowing us to assert end-to-end delegation without touching Mixpanel. + * + * @return void + */ + public function set_up(): void { // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCase + parent::set_up(); + + $container = apply_filters( 'imagify_container', null ); + + $this->assertNotNull( $container, 'Imagify container must be available.' ); + + // Resolve the real Subscriber instance from the DI container. + $subscriber = $container->get( Subscriber::class ); + + $this->assertInstanceOf( Subscriber::class, $subscriber ); + + // Create a PHPUnit mock. Real methods on Tracking are not called; we only assert delegation. + $this->mock_tracking = $this->createMock( Tracking::class ); + + // Inject mock into the real Subscriber's private $tracking property. + $tracking_property = new \ReflectionProperty( Subscriber::class, 'tracking' ); + $tracking_property->setAccessible( true ); + $tracking_property->setValue( $subscriber, $this->mock_tracking ); + } + + /** + * Test that firing imagify_after_optimize invokes the Subscriber callback. + * + * Verifies the full integration path: + * Plugin boots -> ServiceProvider registers Subscriber -> EventManager adds + * filter -> do_action fires -> Subscriber::track_media_optimized() runs. + * + * The Subscriber is real. The injected mock captures the delegation step. + * If the hook isn't wired, the mock expectation never fires and the test fails. + * + * @return void + */ + public function testFiringImagifyAfterOptimizeCallsTrackMediaOptimized(): void { + // Build a minimal ProcessInterface mock. Media stub keeps sibling hook listeners from getting a null check fatal. + $media_mock = $this->createMock( \Imagify\Media\MediaInterface::class ); + $mock_process = $this->createMock( ProcessInterface::class ); + $mock_process->method( 'get_media' )->willReturn( $media_mock ); + + $item = [ 'sizes_done' => [ 'full' ] ]; + + // Register expectation before the hook fires. + $this->mock_tracking->expects( $this->once() ) + ->method( 'track_media_optimized' ) + ->with( $mock_process, $item ); + + // Act: fire the real WordPress hook as production does. + do_action( 'imagify_after_optimize', $mock_process, $item ); + } +} diff --git a/Tests/Integration/inc/classes/ImagifyUser/TestCase.php b/Tests/Integration/inc/classes/ImagifyUser/TestCase.php index 0e2685154..662e0e3ff 100644 --- a/Tests/Integration/inc/classes/ImagifyUser/TestCase.php +++ b/Tests/Integration/inc/classes/ImagifyUser/TestCase.php @@ -11,6 +11,11 @@ abstract class TestCase extends BaseTestCase { public function set_up() { parent::set_up(); + // Skip API-dependent tests when no API key is configured (e.g. fork PRs without repo secrets). + if ( '' === $this->getApiCredential( 'IMAGIFY_TESTS_API_KEY' ) ) { + $this->markTestSkipped( 'IMAGIFY_TESTS_API_KEY is not configured.' ); + } + $this->originalUserInstance = $this->resetPropertyValue( 'user', Imagify::class ); //Clean up the transients for API cache diff --git a/Tests/e2e/specs/button-icon-alignment.spec.ts b/Tests/e2e/specs/button-icon-alignment.spec.ts index 35fd4e5e8..68335f310 100644 --- a/Tests/e2e/specs/button-icon-alignment.spec.ts +++ b/Tests/e2e/specs/button-icon-alignment.spec.ts @@ -14,6 +14,7 @@ test.describe( 'Button icon alignment (WP 7.0 compat)', () => { await loginAsAdmin( page ); } ); + test.skip( ! process.env.IMAGIFY_TESTS_API_KEY, 'IMAGIFY_TESTS_API_KEY not set — settings page dashicons require valid API key.' ); test( 'Settings page submit button dashicon has line-height: inherit', async ( { page } ) => { await page.goto( '/wp-admin/options-general.php?page=imagify' ); await page.waitForLoadState( 'networkidle' ); @@ -56,6 +57,7 @@ test.describe( 'Button icon alignment (WP 7.0 compat)', () => { await screenshotElement( page, 'button-icon-alignment-settings', dashiconInButton ); } ); + test.skip( ! process.env.IMAGIFY_TESTS_API_KEY, 'IMAGIFY_TESTS_API_KEY not set — settings page dashicons require valid API key.' ); test( 'Imagify button dashicons have vertical-align: middle', async ( { page } ) => { await page.goto( '/wp-admin/options-general.php?page=imagify' ); await page.waitForLoadState( 'networkidle' ); diff --git a/Tests/e2e/specs/generate-webp-button-responsive.spec.ts b/Tests/e2e/specs/generate-webp-button-responsive.spec.ts index e30fc8d60..32a4100d7 100644 --- a/Tests/e2e/specs/generate-webp-button-responsive.spec.ts +++ b/Tests/e2e/specs/generate-webp-button-responsive.spec.ts @@ -28,6 +28,7 @@ test.describe( 'Generate missing Next-Gen button — responsive layout (#1045)', } ); async function gotoSettingsAndGetButton( page: Page ) { + test.skip( ! process.env.IMAGIFY_TESTS_API_KEY, 'IMAGIFY_TESTS_API_KEY not set — .generate-missing-webp section only renders with valid API key.' ); await page.goto( '/wp-admin/options-general.php?page=imagify', { waitUntil: 'networkidle', } ); diff --git a/Tests/e2e/specs/htaccess-notice-dedup.spec.ts b/Tests/e2e/specs/htaccess-notice-dedup.spec.ts index bc8805dcf..689424152 100644 --- a/Tests/e2e/specs/htaccess-notice-dedup.spec.ts +++ b/Tests/e2e/specs/htaccess-notice-dedup.spec.ts @@ -50,6 +50,7 @@ test.describe( 'Issue #883 — .htaccess not writable: single error notice', () // Core regression — only ONE notice when .htaccess is read-only // ----------------------------------------------------------------------- + test.skip( ! process.env.IMAGIFY_TESTS_API_KEY, 'IMAGIFY_TESTS_API_KEY not set — WebP section only renders with valid API key.' ); test( 'Enabling display_nextgen with read-only .htaccess shows at most one error notice', async ( { page } ) => { const settings = new SettingsPage( page ); await settings.goto(); @@ -60,25 +61,7 @@ test.describe( 'Issue #883 — .htaccess not writable: single error notice', () // Imagify uses a custom toggle: the label overlays the checkbox so we // click the label (for="imagify_display_nextgen") rather than the input. const displayNextgenCheckbox = page.locator( '[name="imagify_settings[display_nextgen]"]' ).first(); - const checkboxCount = await displayNextgenCheckbox.count(); - - if ( checkboxCount === 0 ) { - // No API key — the webp section is hidden. Test what we can. - test.info().annotations.push( { - type: 'skip-reason', - description: 'display_nextgen checkbox not visible (API key not configured — WebP section hidden)', - } ); - - // Still verify that saving without changes produces at most one notice block. - await settings.saveButton.click(); - await page.waitForLoadState( 'networkidle' ); - - await settings.expectNoFatalError(); - await expect( page ).toHaveURL( /page=imagify/ ); - - await page.screenshot( { path: '.e2e-screenshots/htaccess-02-save-no-key.png' } ); - return; - } + await expect( displayNextgenCheckbox ).toBeVisible( { timeout: 10_000 } ); // Ensure the checkbox is checked (enabled). Click the label which overlays // the input — using force:true bypasses the intercepting label. diff --git a/Tests/e2e/specs/reset-internal-state.spec.ts b/Tests/e2e/specs/reset-internal-state.spec.ts index 17f568a03..9ccb7d3bf 100644 --- a/Tests/e2e/specs/reset-internal-state.spec.ts +++ b/Tests/e2e/specs/reset-internal-state.spec.ts @@ -19,16 +19,8 @@ test.describe( 'Reset Internal State — Troubleshooting section', () => { await loginAsAdmin( page ); } ); + test.skip( ! process.env.IMAGIFY_TESTS_API_KEY, 'IMAGIFY_TESTS_API_KEY not set — section only renders with valid API key.' ); test( 'Troubleshooting section is visible on settings page (requires API key)', async ( { page } ) => { - if ( ! process.env.IMAGIFY_TESTS_API_KEY ) { - // Hard-fail with an informative message instead of silently skipping. - expect( - process.env.IMAGIFY_TESTS_API_KEY, - 'IMAGIFY_TESTS_API_KEY env var must be set — the Troubleshooting section is only rendered when a valid API key is configured. Set this variable to a valid key and re-run.' - ).toBeTruthy(); - return; - } - const settings = new SettingsPage( page ); await settings.goto(); @@ -38,15 +30,8 @@ test.describe( 'Reset Internal State — Troubleshooting section', () => { await screenshotElement( page, 'reset-internal-state-section', section ); } ); + test.skip( ! process.env.IMAGIFY_TESTS_API_KEY, 'IMAGIFY_TESTS_API_KEY not set — section only renders with valid API key.' ); test( 'Reset Internal State button is present and has a nonce attribute (requires API key)', async ( { page } ) => { - if ( ! process.env.IMAGIFY_TESTS_API_KEY ) { - expect( - process.env.IMAGIFY_TESTS_API_KEY, - 'IMAGIFY_TESTS_API_KEY env var must be set — the Reset Internal State button is only rendered when a valid API key is configured.' - ).toBeTruthy(); - return; - } - const settings = new SettingsPage( page ); await settings.goto(); @@ -60,15 +45,8 @@ test.describe( 'Reset Internal State — Troubleshooting section', () => { await screenshotElement( page, 'reset-internal-state-button', button ); } ); + test.skip( ! process.env.IMAGIFY_TESTS_API_KEY, 'IMAGIFY_TESTS_API_KEY not set — section only renders with valid API key.' ); test( 'Reset Internal State button is clickable (requires API key)', async ( { page } ) => { - if ( ! process.env.IMAGIFY_TESTS_API_KEY ) { - expect( - process.env.IMAGIFY_TESTS_API_KEY, - 'IMAGIFY_TESTS_API_KEY env var must be set — the Reset Internal State button is only rendered when a valid API key is configured.' - ).toBeTruthy(); - return; - } - const settings = new SettingsPage( page ); await settings.goto(); diff --git a/inc/admin/meta-boxes.php b/inc/admin/meta-boxes.php index b936b9b52..5bd272c1d 100755 --- a/inc/admin/meta-boxes.php +++ b/inc/admin/meta-boxes.php @@ -60,21 +60,37 @@ function _imagify_attachment_submitbox_misc_actions() { } ?>