diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..82c418f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,53 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + test: + name: Test (PHP ${{ matrix.php }}) + runs-on: ubuntu-latest + + strategy: + # Run every PHP version even if one fails so we see the full matrix. + fail-fast: false + matrix: + # Floor is the lowest PHP still in security support (Requires PHP: 8.2); + # 8.5 is the current latest. Keep in sync with the plugin's Requires PHP. + php: ['8.2', '8.3', '8.4', '8.5'] + + steps: + - name: Checkout plugin + uses: actions/checkout@v4 + + # The regression harness needs WordPress core's HTML API sources. + # Pin to a specific tag for reproducible runs. Bump this DELIBERATELY + # (not via automation): 7.0.0 is the latest stable release as of 2026-06 + # (latest 6.x is 6.9.4). A shallow checkout keeps the clone small. + - name: Checkout WordPress source + uses: actions/checkout@v4 + with: + repository: WordPress/wordpress-develop + ref: '7.0.0' + path: wordpress-develop + fetch-depth: 1 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - name: Install Composer dependencies + run: composer install --prefer-dist --no-progress + + - name: Run tests (regression harness + phpcs) + env: + WP_CORE_DIR: ${{ github.workspace }}/wordpress-develop/src + run: composer test diff --git a/composer.json b/composer.json index 883613d..d392ab5 100644 --- a/composer.json +++ b/composer.json @@ -8,5 +8,18 @@ "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } + }, + "scripts": { + "test": [ + "@test:harness", + "@test:phpcs" + ], + "test:harness": "php tests/html-api-integration-regression.php", + "test:phpcs": "phpcs" + }, + "scripts-descriptions": { + "test": "Run the HTML API regression harness and phpcs (requires WP_CORE_DIR for the harness).", + "test:harness": "Run the HTML API integration regression harness (requires WP_CORE_DIR).", + "test:phpcs": "Run phpcs against the plugin source using phpcs.xml." } } diff --git a/tests/html-api-integration-regression.php b/tests/html-api-integration-regression.php index 99dc939..88248b0 100644 --- a/tests/html-api-integration-regression.php +++ b/tests/html-api-integration-regression.php @@ -67,30 +67,61 @@ function do_action( $hook_name, ...$args ) { } if ( ! class_exists( 'WP_HTML_Processor' ) ) { - require "{$include_dir}/class-wp-token-map.php"; + $required_files = array( + "{$include_dir}/class-wp-token-map.php", + "{$include_dir}/html-api/class-wp-html-text-replacement.php", + "{$include_dir}/html-api/class-wp-html-span.php", + "{$include_dir}/html-api/class-wp-html-attribute-token.php", + "{$include_dir}/html-api/class-wp-html-doctype-info.php", + "{$include_dir}/html-api/class-wp-html-unsupported-exception.php", + "{$include_dir}/html-api/class-wp-html-decoder.php", + "{$include_dir}/html-api/class-wp-html-tag-processor.php", + "{$include_dir}/html-api/class-wp-html-token.php", + "{$include_dir}/html-api/class-wp-html-stack-event.php", + "{$include_dir}/html-api/class-wp-html-open-elements.php", + "{$include_dir}/html-api/class-wp-html-active-formatting-elements.php", + "{$include_dir}/html-api/class-wp-html-processor-state.php", + "{$include_dir}/html-api/class-wp-html-processor.php", + ); - foreach ( - array( - 'class-wp-html-text-replacement.php', - 'class-wp-html-span.php', - 'class-wp-html-attribute-token.php', - 'class-wp-html-doctype-info.php', - 'class-wp-html-unsupported-exception.php', - 'class-wp-html-decoder.php', - 'class-wp-html-tag-processor.php', - 'class-wp-html-token.php', - 'class-wp-html-stack-event.php', - 'class-wp-html-open-elements.php', - 'class-wp-html-active-formatting-elements.php', - 'class-wp-html-processor-state.php', - 'class-wp-html-processor.php', - ) as $file - ) { - require "{$include_dir}/html-api/{$file}"; + // Verify every dependency exists before requiring any of them so a moved or + // renamed file produces a clear message instead of a fatal mid-bootstrap. + $missing_files = array(); + foreach ( $required_files as $file ) { + if ( ! file_exists( $file ) ) { + $missing_files[] = $file; + } + } + + if ( $missing_files ) { + fwrite( STDERR, "Cannot bootstrap the HTML API from WP_CORE_DIR; the following required files are missing:\n" ); + fwrite( STDERR, ' - ' . implode( "\n - ", $missing_files ) . "\n" ); + fwrite( STDERR, "This WordPress version may be incompatible with the regression harness.\n" ); + exit( 2 ); + } + + foreach ( $required_files as $file ) { + require $file; } } -require dirname( __DIR__ ) . '/html-api-debugger/html-api-integration.php'; +// Bail clearly if the HTML API still is not available after bootstrap. +if ( ! class_exists( 'WP_HTML_Processor' ) ) { + fwrite( STDERR, "Bootstrap completed but WP_HTML_Processor is undefined; the HTML API may have changed in this WordPress version.\n" ); + exit( 2 ); +} + +$integration_file = dirname( __DIR__ ) . '/html-api-debugger/html-api-integration.php'; +if ( ! file_exists( $integration_file ) ) { + fwrite( STDERR, "Missing plugin integration file: {$integration_file}\n" ); + exit( 2 ); +} +require $integration_file; + +if ( ! function_exists( 'HTML_API_Debugger\HTML_API_Integration\get_tree' ) ) { + fwrite( STDERR, "Plugin integration loaded but HTML_API_Debugger\\HTML_API_Integration\\get_tree() is undefined.\n" ); + exit( 2 ); +} /** * Convert a debugger tree into stable text lines.