-
Notifications
You must be signed in to change notification settings - Fork 67
test: add PHPUnit coverage for PHP runtime seams #3742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| name: PHP Unit | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ master, develop ] | ||
| paths: | ||
| - '**.php' | ||
| - 'composer.json' | ||
| - 'phpunit.xml.dist' | ||
| - 'tests/phpunit/**' | ||
| - '.github/workflows/php-unit.yml' | ||
| pull_request: | ||
| branches: [ master, develop ] | ||
| paths: | ||
| - '**.php' | ||
| - 'composer.json' | ||
| - 'phpunit.xml.dist' | ||
| - 'tests/phpunit/**' | ||
| - '.github/workflows/php-unit.yml' | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| phpunit: | ||
| name: PHPUnit PHP ${{ matrix.php_version }} | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| php_version: [ '7.4', '8.5' ] | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: shivammathur/setup-php@v2 | ||
| with: | ||
| php-version: ${{ matrix.php_version }} | ||
| coverage: none | ||
| tools: composer:v2 | ||
| - name: Install Composer dependencies | ||
| run: composer update --no-interaction --prefer-dist --no-progress | ||
| - name: Run PHPUnit | ||
| run: composer test |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <?xml version="1.0"?> | ||
| <phpunit | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd" | ||
| bootstrap="tests/phpunit/bootstrap.php" | ||
| colors="true" | ||
| beStrictAboutTestsThatDoNotTestAnything="true" | ||
| convertDeprecationsToExceptions="false" | ||
| > | ||
| <testsuites> | ||
| <testsuite name="free"> | ||
| <directory suffix="Test.php">tests/phpunit</directory> | ||
| </testsuite> | ||
| </testsuites> | ||
| </phpunit> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| <?php | ||
| /** | ||
| * Stored block-style sanitizers. | ||
| * | ||
| * @package Stackable | ||
| */ | ||
|
|
||
| use Brain\Monkey\Functions; | ||
|
|
||
| class BlockDefaultsSanitizeTest extends Stackable_TestCase { | ||
|
|
||
| protected function set_up() { | ||
| parent::set_up(); | ||
| Functions\when( 'wp_check_invalid_utf8' )->returnArg( 1 ); | ||
| Functions\when( 'parse_blocks' )->justReturn( array() ); | ||
| Functions\when( 'serialize_blocks' )->justReturn( '' ); | ||
| $this->require_plugin_file( 'src/deprecated/block-defaults/custom-block-styles.php' ); | ||
| } | ||
|
|
||
| private function styles() { | ||
| return new Stackable_Custom_Block_Styles(); | ||
| } | ||
|
|
||
| public function test_sanitize_block_name_rejects_core_blocks() { | ||
| $styles = $this->styles(); | ||
| $this->assertSame( '', $styles->sanitize_block_name( 'core/paragraph' ) ); | ||
| $this->assertSame( 'stackable/heading', $styles->sanitize_block_name( 'stackable/heading' ) ); | ||
| } | ||
|
|
||
| public function test_sanitize_style_slug_uses_sanitize_title() { | ||
| $this->assertSame( 'my-style', $this->styles()->sanitize_style_slug( 'My Style' ) ); | ||
| } | ||
|
|
||
| public function test_sanitize_array_setting_rejects_non_array() { | ||
| $this->assertSame( array(), $this->styles()->sanitize_array_setting( 'nope' ) ); | ||
| } | ||
|
|
||
| public function test_sanitize_stored_block_styles_drops_invalid_blocks() { | ||
| $styles = $this->styles(); | ||
| $stored = array( | ||
| array( | ||
| 'block' => 'core/paragraph', | ||
| 'styles' => array( | ||
| array( | ||
| 'slug' => 'plain', | ||
| 'name' => 'Plain', | ||
| 'data' => '{"attributes":{"text":"<script>x</script>"},"innerBlocks":[]}', | ||
| 'save' => '', | ||
| ), | ||
| ), | ||
| ), | ||
| array( | ||
| 'block' => 'stackable/heading', | ||
| 'styles' => array( | ||
| array( | ||
| 'slug' => 'hero', | ||
| 'name' => 'Hero', | ||
| 'data' => '{"attributes":{"text":"Hello <script>x</script>"},"innerBlocks":[]}', | ||
| 'save' => '', | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
| $out = $styles->sanitize_stored_block_styles( $stored ); | ||
| $this->assertCount( 1, $out ); | ||
| $this->assertSame( 'stackable/heading', $out[0]->block ); | ||
| $this->assertSame( 'hero', $out[0]->styles[0]->slug ); | ||
| $this->assertStringNotContainsString( '<script>', $out[0]->styles[0]->data ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| <?php | ||
| /** | ||
| * Inline CSS optimizer: parse, skip dynamic, combine selectors. | ||
| * | ||
| * @package Stackable | ||
| */ | ||
|
|
||
| class CssOptimizeTest extends Stackable_TestCase { | ||
|
|
||
| protected function set_up() { | ||
| parent::set_up(); | ||
| $this->require_plugin_file( 'src/css-optimize.php' ); | ||
| } | ||
|
|
||
| public function test_parse_block_style_collects_css_by_unique_id() { | ||
| $styles = array(); | ||
| Stackable_CSS_Optimize::parse_block_style( | ||
| array( | ||
| 'innerHTML' => '<div><style>.stk-aaaaaaa{color:red}</style></div>', | ||
| 'attrs' => array( 'uniqueId' => 'aaaaaaa' ), | ||
| ), | ||
| $styles | ||
| ); | ||
| $this->assertArrayHasKey( 'aaaaaaa', $styles ); | ||
| $this->assertSame( '.stk-aaaaaaa{color:red}', $styles['aaaaaaa'][0][1] ); | ||
| } | ||
|
|
||
| public function test_dynamic_style_is_not_collected() { | ||
| $styles = array(); | ||
| Stackable_CSS_Optimize::parse_block_style( | ||
| array( | ||
| 'innerHTML' => '<div><style>.stk-aaaaaaa{background:url(!#stk_dynamic/current-page/featured-image-data!#)}</style></div>', | ||
| 'attrs' => array( 'uniqueId' => 'aaaaaaa' ), | ||
| ), | ||
| $styles | ||
| ); | ||
| $this->assertSame( array(), $styles ); | ||
|
Comment on lines
+28
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '170,202p' src/css-optimize.php
sed -n '28,38p' tests/phpunit/CssOptimizeTest.phpRepository: gambitph/Stackable Length of output: 1683 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- stripos behavior ---'
if command -v php >/dev/null 2>&1; then
php -r '$value = "!`#stk_dynamic/current-page/featured-image-data`!#"; var_export(stripos($value, "!`#stk_dynamic`")); echo PHP_EOL;'
else
echo 'php unavailable'
fi
printf '%s\n' '--- related tests and implementation references ---'
rg -n -C 4 "stk_dynamic|parse_block_style|dynamic_style" src tests/phpunitRepository: gambitph/Stackable Length of output: 17242 Use a strict When 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| public function test_generate_css_combines_matching_rules() { | ||
| $css = Stackable_CSS_Optimize::generate_css( array( | ||
| '.stk-aaaaaaa .child{color:red}', | ||
| '.stk-bbbbbbb .child{color:red}', | ||
| ) ); | ||
| $this->assertStringContainsString( ':is(.stk-aaaaaaa, .stk-bbbbbbb)', $css ); | ||
| $this->assertStringContainsString( 'color:red', $css ); | ||
| } | ||
|
|
||
| public function test_zero_px_is_left_in_generated_css() { | ||
| $css = Stackable_CSS_Optimize::generate_css( array( | ||
| '.stk-aaaaaaa{margin:0px}', | ||
| ) ); | ||
| $this->assertStringContainsString( '0px', $css ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| <?php | ||
| /** | ||
| * Deactivation deletes leftover options. | ||
| * | ||
| * @package Stackable | ||
| */ | ||
|
|
||
| use Brain\Monkey\Functions; | ||
|
|
||
| class DeactivationCleanupTest extends Stackable_TestCase { | ||
|
|
||
| public function test_deactivation_deletes_cached_and_legacy_options() { | ||
| $deleted = array(); | ||
| Functions\when( 'delete_option' )->alias( function( $name ) use ( &$deleted ) { | ||
| $deleted[] = $name; | ||
| return true; | ||
| } ); | ||
|
|
||
| $this->require_plugin_file( 'plugin.php' ); | ||
| $this->assertTrue( function_exists( 'stackable_deactivation_cleanup' ) ); | ||
| stackable_deactivation_cleanup(); | ||
|
|
||
| $this->assertEqualsCanonicalizing( | ||
| array( | ||
| 'stackable_dynamic_content_other_fields_frontend', | ||
| 'stackable_dynamic_content_meta_keys_frontend', | ||
| 'stackable_inspector_premium_notice_status', | ||
| 'stackable_enable_navigation_panel', | ||
| 'stackable_custom_php_sigs', | ||
| 'stackable_disp_cond_custom_php_sigs', | ||
| ), | ||
| $deleted | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| <?php | ||
| /** | ||
| * Design Library URL validation. | ||
| * | ||
| * @package Stackable | ||
| */ | ||
|
|
||
| use Brain\Monkey\Functions; | ||
|
|
||
| class DesignLibraryValidateTest extends Stackable_TestCase { | ||
|
|
||
| protected function set_up() { | ||
| parent::set_up(); | ||
| Functions\when( 'wp_http_validate_url' )->alias( function( $url ) { | ||
| if ( 0 === stripos( $url, 'javascript:' ) ) { | ||
| return false; | ||
| } | ||
| if ( ! preg_match( '#^https?://#i', $url ) ) { | ||
| return false; | ||
| } | ||
| return $url; | ||
| } ); | ||
| $this->require_plugin_file( 'src/design-library/init.php' ); | ||
| } | ||
|
|
||
| public function test_https_image_url_is_valid() { | ||
| $this->assertTrue( | ||
| Stackable_Design_Library::validate_url( 'https://example.com/a.png', null, 'image_url' ) | ||
| ); | ||
| } | ||
|
|
||
| public function test_javascript_url_is_rejected() { | ||
| $result = Stackable_Design_Library::validate_url( 'javascript:alert(1)', null, 'image_url' ); | ||
| $this->assertInstanceOf( WP_Error::class, $result ); | ||
| } | ||
|
|
||
| public function test_non_url_is_rejected() { | ||
| $result = Stackable_Design_Library::validate_url( 'not-a-url', null, 'image_url' ); | ||
| $this->assertInstanceOf( WP_Error::class, $result ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| <?php | ||
| /** | ||
| * KSES allowlist for Stackable SVG/style tags. | ||
| * | ||
| * @package Stackable | ||
| */ | ||
|
|
||
| use Brain\Monkey\Functions; | ||
|
|
||
| class KsesTest extends Stackable_TestCase { | ||
|
|
||
| protected function set_up() { | ||
| parent::set_up(); | ||
| $this->require_plugin_file( 'src/kses.php' ); | ||
| } | ||
|
|
||
| /** | ||
| * @runInSeparateProcess | ||
| * @preserveGlobalState disabled | ||
| */ | ||
| public function test_missing_user_functions_leave_tags_unchanged() { | ||
| $tags = array( 'div' => array() ); | ||
| $out = stackable_allow_wp_kses_allowed_html( $tags, 'post' ); | ||
| $this->assertSame( $tags, $out ); | ||
| } | ||
|
|
||
| public function test_user_without_edit_posts_leaves_tags_unchanged() { | ||
| Functions\when( 'wp_get_current_user' )->justReturn( (object) array( 'ID' => 2 ) ); | ||
| Functions\when( 'current_user_can' )->justReturn( false ); | ||
| $tags = array( 'div' => array() ); | ||
| $out = stackable_allow_wp_kses_allowed_html( $tags, 'post' ); | ||
| $this->assertSame( $tags, $out ); | ||
| $this->assertArrayNotHasKey( 'svg', $out ); | ||
| } | ||
|
|
||
| public function test_edit_posts_adds_svg_path_and_style() { | ||
| Functions\when( 'wp_get_current_user' )->justReturn( (object) array( 'ID' => 1 ) ); | ||
| Functions\when( 'current_user_can' )->justReturn( true ); | ||
| $tags = array( 'div' => array() ); | ||
| $out = stackable_allow_wp_kses_allowed_html( $tags, 'post' ); | ||
| $this->assertArrayHasKey( 'svg', $out ); | ||
| $this->assertArrayHasKey( 'path', $out ); | ||
| $this->assertArrayHasKey( 'style', $out ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Put each sentence on its own line.
Line 64 contains the free and premium instructions on one physical line.
Split before
Premium:.As per coding guidelines: “When writing or substantially editing long Markdown files, put each full sentence on its own line.”
Proposed documentation update
📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines