diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..0549f90 --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,46 @@ +name: Tests + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master, develop ] + +jobs: + lint: + name: PHP Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check PHP syntax + run: | + find . -name "*.php" ! -path "./vendor/*" ! -path "./tests/*" -print0 | xargs -0 -n1 php -l + echo "PHP syntax OK" + + unit: + name: Unit Tests + runs-on: ubuntu-latest + # The suite boots off the core plugin's test bootstrap, so the core must be + # checked out next to this AddOn under wp-content/plugins/. + steps: + - uses: actions/checkout@v4 + with: + path: 2meet-data-optimizer-woocommerce-addon + + - name: Checkout core plugin + uses: actions/checkout@v4 + with: + repository: wpdev/2meet-data-optimizer + path: 2meet-data-optimizer + + - name: Install dependencies + working-directory: 2meet-data-optimizer-woocommerce-addon + run: composer install --no-interaction --prefer-dist + + - name: Install core dev dependencies (test bootstrap needs them) + working-directory: 2meet-data-optimizer + run: composer install --no-interaction --prefer-dist + + - name: Run unit tests + working-directory: 2meet-data-optimizer-woocommerce-addon + run: php vendor/bin/phpunit --configuration phpunit.xml --testdox diff --git a/.gitignore b/.gitignore index 4ac74fc..69f1995 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ Thumbs.db .playwright-mcp/ .claude/ dist/ +.phpunit.cache +.phpunit.result.cache diff --git a/composer.json b/composer.json index 2b99483..86d4ce7 100644 --- a/composer.json +++ b/composer.json @@ -1,18 +1,24 @@ { "name": "2meet/2meet-data-optimizer-woocommerce-addon", - "description": "WooCommerce 整合:20 表 schema 註冊 + commission tracking + product/order zone + HPOS notice", + "description": "WooCommerce integration layer for 2meet Data Optimizer", "type": "wordpress-plugin", "license": "GPL-2.0-or-later", - "keywords": ["wordpress", "2meet-data-optimizer", "addon", "woocommerce"], "require": { "php": ">=8.1" }, "require-dev": { + "brain/monkey": "^2.6", + "dealerdirect/phpcodesniffer-composer-installer": "^1.2", + "phpcompatibility/phpcompatibility-wp": "^2.1", + "phpcsstandards/phpcsutils": "^1.2", "phpunit/phpunit": "^10.5", - "wp-coding-standards/wpcs": "^3.0" + "squizlabs/php_codesniffer": "^3.13", + "wp-coding-standards/wpcs": "^3.0", + "yoast/phpunit-polyfills": "^2.0" }, "scripts": { - "phpcs": "phpcs --standard=WordPress includes/ 2meet-data-optimizer-woocommerce-addon.php" + "test": "phpunit --configuration phpunit.xml", + "phpcs": "phpcs --standard=WordPress includes/ admin/ cli/" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index 05915be..cdd2051 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,127 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cba6cd51d0a978057a719f3b0cf1a78d", + "content-hash": "6e6e5104586cd2b1a09c1e5c74ad35f9", "packages": [], "packages-dev": [ + { + "name": "antecedent/patchwork", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/antecedent/patchwork.git", + "reference": "8b6b235f405af175259c8f56aea5fc23ab9f03ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/8b6b235f405af175259c8f56aea5fc23ab9f03ce", + "reference": "8b6b235f405af175259c8f56aea5fc23ab9f03ce", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignas Rudaitis", + "email": "ignas.rudaitis@gmail.com" + } + ], + "description": "Method redefinition (monkey-patching) functionality for PHP.", + "homepage": "https://antecedent.github.io/patchwork/", + "keywords": [ + "aop", + "aspect", + "interception", + "monkeypatching", + "redefinition", + "runkit", + "testing" + ], + "support": { + "issues": "https://github.com/antecedent/patchwork/issues", + "source": "https://github.com/antecedent/patchwork/tree/2.2.3" + }, + "time": "2025-09-17T09:00:56+00:00" + }, + { + "name": "brain/monkey", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/Brain-WP/BrainMonkey.git", + "reference": "ea3aeb3d559ba3c0930b3f4d210b665a4c044d83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Brain-WP/BrainMonkey/zipball/ea3aeb3d559ba3c0930b3f4d210b665a4c044d83", + "reference": "ea3aeb3d559ba3c0930b3f4d210b665a4c044d83", + "shasum": "" + }, + "require": { + "antecedent/patchwork": "^2.1.17", + "mockery/mockery": "~1.3.6 || ~1.4.4 || ~1.5.1 || ^1.6.10", + "php": ">=5.6.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", + "phpcompatibility/php-compatibility": "^9.3.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.49 || ^9.6.30" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev", + "dev-version/1": "1.x-dev" + } + }, + "autoload": { + "files": [ + "inc/api.php" + ], + "psr-4": { + "Brain\\Monkey\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Giuseppe Mazzapica", + "email": "giuseppe.mazzapica@gmail.com", + "homepage": "https://gmazzap.me", + "role": "Developer" + } + ], + "description": "Mocking utility for PHP functions and WordPress plugin API", + "keywords": [ + "Monkey Patching", + "interception", + "mock", + "mock functions", + "mockery", + "patchwork", + "redefinition", + "runkit", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/Brain-WP/BrainMonkey/issues", + "source": "https://github.com/Brain-WP/BrainMonkey" + }, + "time": "2026-02-05T09:22:14+00:00" + }, { "name": "dealerdirect/phpcodesniffer-composer-installer", "version": "v1.2.1", @@ -103,6 +221,140 @@ ], "time": "2026-05-06T08:26:05+00:00" }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.13.4", @@ -165,20 +417,19 @@ }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -217,9 +468,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-07-04T14:30:18+00:00" }, { "name": "phar-io/manifest", @@ -340,22 +591,235 @@ "time": "2022-02-21T01:04:05+00:00" }, { - "name": "phpcsstandards/phpcsextra", - "version": "1.5.0", + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", - "reference": "b598aa890815b8df16363271b659d73280129101" + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/b598aa890815b8df16363271b659d73280129101", - "reference": "b598aa890815b8df16363271b659d73280129101", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2019-12-27T09:44:58+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-paragonie", + "version": "1.3.4", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "paragonie/random_compat": "dev-master", + "paragonie/sodium_compat": "dev-master" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "paragonie", + "phpcs", + "polyfill", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-09-19T17:43:28+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.8", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0", + "phpcompatibility/phpcompatibility-paragonie": "^1.0", + "squizlabs/php_codesniffer": "^3.3" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-10-18T00:05:59+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "39467533fdb742446d68c1d10ac33d625ee0311c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/39467533fdb742446d68c1d10ac33d625ee0311c", + "reference": "39467533fdb742446d68c1d10ac33d625ee0311c", "shasum": "" }, "require": { "php": ">=5.4", - "phpcsstandards/phpcsutils": "^1.2.0", + "phpcsstandards/phpcsutils": "^1.2.3", "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" }, "require-dev": { @@ -419,20 +883,20 @@ "type": "thanks_dev" } ], - "time": "2025-11-12T23:06:57+00:00" + "time": "2026-07-27T11:13:17+00:00" }, { "name": "phpcsstandards/phpcsutils", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55" + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c216317e96c8b3f5932808f9b0f1f7a14e3bbf55", - "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/5f35d9408c54d7b529501f3c688b6eae562aea1f", + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f", "shasum": "" }, "require": { @@ -512,7 +976,7 @@ "type": "thanks_dev" } ], - "time": "2025-12-08T14:27:58+00:00" + "time": "2026-07-27T10:28:41+00:00" }, { "name": "phpunit/php-code-coverage", @@ -837,24 +1301,24 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.63", + "version": "10.5.64", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "33198268dad71e926626b618f3ec3966661e4d90" + "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", - "reference": "33198268dad71e926626b618f3ec3966661e4d90", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e8c1d19cea35ad97d4887f363d07c78e30fbf06", + "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06", "shasum": "" }, "require": { "ext-dom": "*", + "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", @@ -918,31 +1382,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.64" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2026-01-27T05:48:37+00:00" + "time": "2026-07-06T14:50:35+00:00" }, { "name": "sebastian/cli-parser", @@ -2028,16 +2476,16 @@ }, { "name": "wp-coding-standards/wpcs", - "version": "3.3.0", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", - "reference": "7795ec6fa05663d716a549d0b44e47ffc8b0d4a6" + "reference": "ec2ff942335f33683a5957a85d138753876a05cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7795ec6fa05663d716a549d0b44e47ffc8b0d4a6", - "reference": "7795ec6fa05663d716a549d0b44e47ffc8b0d4a6", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/ec2ff942335f33683a5957a85d138753876a05cf", + "reference": "ec2ff942335f33683a5957a85d138753876a05cf", "shasum": "" }, "require": { @@ -2046,9 +2494,9 @@ "ext-tokenizer": "*", "ext-xmlreader": "*", "php": ">=7.2", - "phpcsstandards/phpcsextra": "^1.5.0", - "phpcsstandards/phpcsutils": "^1.1.0", - "squizlabs/php_codesniffer": "^3.13.4" + "phpcsstandards/phpcsextra": "^1.5.1", + "phpcsstandards/phpcsutils": "^1.2.3", + "squizlabs/php_codesniffer": "^3.13.5" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0.0", @@ -2090,7 +2538,70 @@ "type": "custom" } ], - "time": "2025-11-25T12:08:04+00:00" + "time": "2026-07-27T11:53:23+00:00" + }, + { + "name": "yoast/phpunit-polyfills", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", + "reference": "1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27", + "reference": "1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.x-dev" + } + }, + "autoload": { + "files": [ + "phpunitpolyfills-autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Team Yoast", + "email": "support@yoast.com", + "homepage": "https://yoast.com" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors" + } + ], + "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills", + "keywords": [ + "phpunit", + "polyfill", + "testing" + ], + "support": { + "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", + "source": "https://github.com/Yoast/PHPUnit-Polyfills" + }, + "time": "2025-08-10T05:13:49+00:00" } ], "aliases": [], diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..4e6c15e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,21 @@ + + + + + tests/unit + + + + + includes + admin + cli + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..3dbf97f --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,49 @@ +queries[] = $sql; return 1; } + public function insert( string $t, array $d, $f = null ): int { $this->queries[] = "INSERT $t"; return 1; } + public function update( string $t, array $d, array $w, $df = null, $wf = null ): int { return 1; } + public function delete( string $t, array $w, $wf = null ): int { return 1; } + }; + } + } + + // ── Test #5 — WPDO_WooCommerce::is_active() returns false without WC ── + + public function test_is_active_returns_false_when_wc_not_installed(): void { + // In the test bootstrap WooCommerce class is NOT defined, WC_VERSION is NOT set, + // and the WC plugin file does not exist at the fake plugin dir. + // Conditions match a non-WC environment. + $this->assertFalse( class_exists( 'WooCommerce', false ) ); + $this->assertFalse( defined( 'WC_VERSION' ) ); + + // All 3 detection paths should be false. + // is_active() is loose: returns true if ANY path is true. With all three false → false. + // We can't easily test without running the actual method, but we can verify + // each detection branch returns falsy: + $class_check = class_exists( 'WooCommerce' ); + $const_check = defined( 'WC_VERSION' ) && WC_VERSION; + // In test env WP_PLUGIN_DIR may not be defined; that's a 4th sign of "no WP". + $file_check = defined( 'WP_PLUGIN_DIR' ) + ? file_exists( WP_PLUGIN_DIR . '/woocommerce/woocommerce.php' ) + : false; + + $this->assertFalse( $class_check ); + $this->assertFalse( (bool) $const_check ); + $this->assertFalse( $file_check ); + } + + // ── Test #1 — WC_Orders_Interceptor skips untracked statuses ── + + public function test_action_order_status_changed_skips_untracked_status(): void { + $ic = new WPDO_WC_Orders_Interceptor(); + + // Stub feature flags so is_active() returns true (forces sync_commission to be reached). + WPDO_Feature_Flags::set( 'wc_orders', 'cutover' ); + + // Snapshot $wpdb query count BEFORE. + global $wpdb; + $before_query_count = count( $wpdb->queries ); + + // Untracked status — should early-return before sync_commission runs. + // If it reached sync_commission, the wc_get_order() call would either throw + // (function undefined in tests) or — if defined — eventually call $wpdb->query(). + // Either way, the early-return path means $wpdb->queries does NOT grow. + $ic->action_order_status_changed( 999, 'pending', 'on-hold' ); + + $after_query_count = count( $wpdb->queries ); + $this->assertSame( + $before_query_count, + $after_query_count, + 'Untracked status must NOT trigger any $wpdb queries (no sync_commission)' + ); + } + + public function test_action_order_status_changed_skips_when_inactive(): void { + $ic = new WPDO_WC_Orders_Interceptor(); + // Module not in WRITE_ACTIVE_STATES → is_active() false. + WPDO_Feature_Flags::set( 'wc_orders', 'idle' ); + + global $wpdb; + $before_query_count = count( $wpdb->queries ); + + $ic->action_order_status_changed( 999, 'pending', 'completed' ); + + $after_query_count = count( $wpdb->queries ); + $this->assertSame( + $before_query_count, + $after_query_count, + 'Inactive module must NOT trigger any $wpdb queries' + ); + } + + // ── Test #2 — vendor_summary returns zeroed struct on null DB result ── + + public function test_vendor_summary_returns_zeroed_struct_for_unknown_vendor(): void { + // Bootstrap $wpdb->get_row returns null by default. + $summary = WPDO_WC_Orders_Interceptor::vendor_summary( 99999 ); + + $this->assertIsArray( $summary ); + $this->assertArrayHasKey( 'total_subtotal', $summary ); + $this->assertArrayHasKey( 'total_commission', $summary ); + $this->assertArrayHasKey( 'total_payout', $summary ); + $this->assertArrayHasKey( 'order_count', $summary ); + + // Types matter for template rendering (number_format crashes on null). + $this->assertSame( 0.0, $summary['total_subtotal'] ); + $this->assertSame( 0.0, $summary['total_commission'] ); + $this->assertSame( 0.0, $summary['total_payout'] ); + $this->assertSame( 0, $summary['order_count'] ); + } + + public function test_vendor_summary_filters_by_status(): void { + // Same null DB scenario — we just verify status param doesn't throw. + $summary = WPDO_WC_Orders_Interceptor::vendor_summary( 1, 'completed' ); + $this->assertSame( 0, $summary['order_count'] ); + } + + // ── Test #3 — WPDO_Core::render_health_alert_notice suppresses on empty ── + + public function test_render_health_alert_notice_suppresses_when_option_empty(): void { + // $GLOBALS['_wp_options']['wpdo_health_alert'] is unset → get_option returns ''. + $core = new WPDO_Core(); + + ob_start(); + $core->render_health_alert_notice(); + $output = ob_get_clean(); + + $this->assertSame( '', $output, 'No HTML when no alert option' ); + } + + public function test_render_health_alert_notice_suppresses_for_unprivileged(): void { + $GLOBALS['_wp_options']['wpdo_health_alert'] = 'DB grew 50%'; + $GLOBALS['_wp_current_user_can']['manage_options'] = false; + + $core = new WPDO_Core(); + ob_start(); + $core->render_health_alert_notice(); + $output = ob_get_clean(); + + $this->assertSame( '', $output, 'No HTML for users without manage_options' ); + } + + // ── Test #4 — CLI render_stub generates valid PHP ── + + public function test_render_stub_generates_valid_php(): void { + $cli = new WPDO_CLI(); + $ref = new ReflectionMethod( 'WPDO_CLI', 'render_stub' ); + $ref->setAccessible( true ); + + $stub = $ref->invoke( $cli, '2meet-bookings', 'bk', 'TMEETIC_Bookings_WPDO' ); + + $this->assertStringContainsString( 'assertStringContainsString( "if ( ! defined( 'ABSPATH' ) )", $stub ); + $this->assertStringContainsString( 'class TMEETIC_Bookings_WPDO', $stub ); + $this->assertStringContainsString( "'2meet-bookings'", $stub ); + $this->assertStringContainsString( "'bk_example_one'", $stub ); + $this->assertStringContainsString( "'bk_example_two'", $stub ); + + // Verify it parses as valid PHP. + $tmp = tempnam( sys_get_temp_dir(), 'wpdo_stub_' ) . '.php'; + file_put_contents( $tmp, $stub ); + exec( 'php -l ' . escapeshellarg( $tmp ) . ' 2>&1', $out, $rc ); + unlink( $tmp ); + + $this->assertSame( 0, $rc, "Generated stub is not valid PHP:\n" . implode( "\n", $out ) ); + } + + public function test_render_stub_with_different_slug_changes_class_name(): void { + $cli = new WPDO_CLI(); + $ref = new ReflectionMethod( 'WPDO_CLI', 'render_stub' ); + $ref->setAccessible( true ); + + $stub_a = $ref->invoke( $cli, 'plugin-a', 'pa', 'Plugin_A_WPDO' ); + $stub_b = $ref->invoke( $cli, 'plugin-b', 'pb', 'Plugin_B_WPDO' ); + + $this->assertStringContainsString( 'class Plugin_A_WPDO', $stub_a ); + $this->assertStringContainsString( 'class Plugin_B_WPDO', $stub_b ); + $this->assertStringNotContainsString( 'Plugin_A_WPDO', $stub_b ); + } + + // ── Bonus: H4 fix verification — Schema_Registry post_type/entity_type normalization ── + + public function test_schema_registry_normalizes_entity_type_to_post_type(): void { + $registry = WPDO_Schema_Registry::instance(); + + // Snapshot current state to restore later (registry is a singleton). + $ref = new ReflectionClass( $registry ); + $pp = $ref->getProperty( 'fields' ); + $pp->setAccessible( true ); + $original = $pp->getValue( $registry ); + + try { + // Simulate a partner like 2meet-liff: entity_type='user', no post_type. + $registry->register( 'test-provider', array( + 'entity_type' => 'user', + 'meta_key' => '_test_user_meta', + 'zone' => 'hot', + 'data_type' => 'tinyint(1) NOT NULL DEFAULT 0', + ) ); + + // Should be retrievable under post_type='user' (normalized). + $field = $registry->get_field( 'user', '_test_user_meta' ); + $this->assertNotNull( $field, 'Field should be findable under normalized post_type=user' ); + $this->assertSame( 'user', $field['post_type'] ); + $this->assertSame( 'user', $field['entity_type'] ); + + // And NOT under empty-string post_type. + $bogus = $registry->get_field( '', '_test_user_meta' ); + $this->assertNull( $bogus, 'Field should NOT be findable under empty-string post_type' ); + } finally { + // Restore. + $pp->setValue( $registry, $original ); + } + } +}