From 5e5de9f62ba266c14d2afd640f81b19dba479272 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sun, 28 Jun 2026 08:37:50 +0200 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20Add=20PHPUnit=20configuration=20and?= =?UTF-8?q?=20unit=20tests=20for=20Hyv=C3=A4=20compatibility=20detection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/phpunit.yml | 37 ++ .phpunit.cache/test-results | 1 + composer.json | 8 +- phpunit.xml.dist | 17 + .../Hyva/CompatibilityCheckCommand.php | 3 +- src/Service/Hyva/CompatibilityChecker.php | 25 +- src/Service/Hyva/IncompatibilityDetector.php | 48 +-- src/Service/Hyva/ModuleScanner.php | 52 +-- .../Hyva/IncompatibilityDetectorTest.php | 406 ++++++++++++++++++ 9 files changed, 500 insertions(+), 97 deletions(-) create mode 100644 .github/workflows/phpunit.yml create mode 100644 .phpunit.cache/test-results create mode 100644 phpunit.xml.dist create mode 100644 tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml new file mode 100644 index 00000000..35b0f284 --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,37 @@ +name: PHPUnit + +on: [pull_request] + +permissions: + contents: read + +env: + PHP_VERSION: "8.4" + +jobs: + phpunit: + name: Unit Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up PHP + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 + with: + php-version: ${{ env.PHP_VERSION }} + tools: composer:v2 + + - name: Cache Composer packages + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: ~/.composer/cache/files + key: ${{ runner.os }}-composer-phpunit-${{ hashFiles('composer.json') }} + restore-keys: ${{ runner.os }}-composer-phpunit + + - name: Install dev dependencies + run: composer install --no-interaction --no-progress + + - name: Run PHPUnit + run: vendor/bin/phpunit diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results new file mode 100644 index 00000000..d512808a --- /dev/null +++ b/.phpunit.cache/test-results @@ -0,0 +1 @@ +{"version":2,"defects":[],"times":{"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"requirejs define\"":0.002,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"requirejs require\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko observable\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko observableArray\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko computed\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko pureComputed\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko applyBindings\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko components register\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko bindingHandlers\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"jquery ajax shorthand\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"jquery ajax full name\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"mage requirejs module\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanAlpineJs":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagWordBoundaryFalsePositive":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"uiComponent tag\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"component uiComponent attribute\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"magento ui js component\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"referenceBlock remove\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanLayoutXml":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"data-mage-init\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"data-bind\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"x-magento-init\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"jquery dom manipulation\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"requirejs in template\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"ko virtual element\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"ko virtual element no space\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanHyvaPhtml":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"data-bind in html template\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"ko virtual element in html\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"x-magento-init in html\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayWhenFileNotExists":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayForUnknownExtension":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayOnFileReadError":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReportsCorrectLineNumbers":0}} \ No newline at end of file diff --git a/composer.json b/composer.json index 4523a4ce..7a200a8b 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,13 @@ }, "require-dev": { "carthage-software/mago": "^1.30", - "magento/magento-coding-standard": "^40" + "magento/magento-coding-standard": "^40", + "phpunit/phpunit": "^11.0" + }, + "autoload-dev": { + "psr-4": { + "OpenForgeProject\\MageForge\\Test\\": "tests/" + } }, "repositories": [ { diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..e067aaea --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + + tests/Unit + + + + + src + + + diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index fa6436f4..0b020efa 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -236,7 +236,6 @@ private function runScan( // Run the compatibility check $results = $this->compatibilityChecker->check( $this->io, - $output, $showAll, $scanThirdPartyOnly, $excludeVendor, @@ -310,7 +309,7 @@ private function displayDetailedIssues(array $results): void $this->io->text(sprintf('%s', $moduleName)); - $detailedIssues = $this->compatibilityChecker->getDetailedIssues($moduleName, $moduleData); + $detailedIssues = $this->compatibilityChecker->getDetailedIssues($moduleData); foreach ($detailedIssues as $fileData) { $this->io->text(sprintf(' %s', $fileData['file'])); diff --git a/src/Service/Hyva/CompatibilityChecker.php b/src/Service/Hyva/CompatibilityChecker.php index 55f979f3..afddb55a 100644 --- a/src/Service/Hyva/CompatibilityChecker.php +++ b/src/Service/Hyva/CompatibilityChecker.php @@ -6,7 +6,6 @@ use Magento\Framework\Component\ComponentRegistrar; use Magento\Framework\Component\ComponentRegistrarInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; /** @@ -54,7 +53,6 @@ public function __construct( * Check all modules for Hyvä compatibility * * @param SymfonyStyle $io Symfony Style IO for output - * @param OutputInterface $output Console output interface * @param bool $showAll Whether to show all modules (including compatible ones) * @param bool $thirdPartyOnly Whether to scan only third-party modules (excludes Magento_* modules) * @param bool $excludeVendor Whether to exclude modules from the vendor/ directory @@ -64,7 +62,6 @@ public function __construct( */ public function check( SymfonyStyle $io, - OutputInterface $output, bool $showAll = false, bool $thirdPartyOnly = false, bool $excludeVendor = true, @@ -116,13 +113,18 @@ public function check( ]; // Update summary - if ($isCompatible && !$hasWarnings) { + if ($isCompatible) { $results['summary']['compatible']++; } else { $results['summary']['incompatible']++; $results['hasIncompatibilities'] = true; } + // Warnings alone still trigger the detail/recommendation display + if ($hasWarnings) { + $results['hasIncompatibilities'] = true; + } + if ($moduleInfo['isHyvaAware']) { $results['summary']['hyvaAware']++; } @@ -195,19 +197,19 @@ public function formatResultsForDisplay(array $results, bool $showAll = false): */ private function getStatusDisplay(array $moduleData): string { - if ($moduleData['moduleInfo']['isHyvaAware']) { - return '✓ Hyvä-Aware'; - } + $hyvaTag = $moduleData['moduleInfo']['isHyvaAware'] ? ' (Hyvä-Aware)' : ''; if ($moduleData['compatible'] && !$moduleData['hasWarnings']) { - return '✓ Compatible'; + return $moduleData['moduleInfo']['isHyvaAware'] + ? '✓ Hyvä-Aware' + : '✓ Compatible'; } if ($moduleData['compatible']) { - return '⚠ Warnings'; + return sprintf('⚠ Warnings%s', $hyvaTag); } - return '✗ Incompatible'; + return sprintf('✗ Incompatible%s', $hyvaTag); } /** @@ -243,12 +245,11 @@ private function getIssuesDisplay(array $moduleData): string /** * Get detailed file issues for a module * - * @param string $moduleName * @param array $moduleData * @phpstan-param ModuleEntry $moduleData * @return array}> */ - public function getDetailedIssues(string $moduleName, array $moduleData): array + public function getDetailedIssues(array $moduleData): array { $scanResult = $moduleData['scanResult']; $files = $scanResult['files']; diff --git a/src/Service/Hyva/IncompatibilityDetector.php b/src/Service/Hyva/IncompatibilityDetector.php index c3be3133..b096dbbf 100644 --- a/src/Service/Hyva/IncompatibilityDetector.php +++ b/src/Service/Hyva/IncompatibilityDetector.php @@ -35,7 +35,7 @@ class IncompatibilityDetector 'severity' => self::SEVERITY_CRITICAL, ], [ - 'pattern' => '/ko\.observable|ko\.observableArray|ko\.computed/', + 'pattern' => '/\bko\.(observable|observableArray|computed|pureComputed|applyBindings|components|bindingHandlers)/', 'description' => 'Knockout.js usage', 'severity' => self::SEVERITY_CRITICAL, ], @@ -78,6 +78,11 @@ class IncompatibilityDetector 'description' => 'data-mage-init JavaScript initialization', 'severity' => self::SEVERITY_CRITICAL, ], + [ + 'pattern' => '/data-bind\s*=/', + 'description' => 'Knockout.js data-bind attribute', + 'severity' => self::SEVERITY_CRITICAL, + ], [ 'pattern' => '/x-magento-init/', 'description' => 'x-magento-init JavaScript initialization', @@ -93,6 +98,11 @@ class IncompatibilityDetector 'description' => 'RequireJS in template', 'severity' => self::SEVERITY_CRITICAL, ], + [ + 'pattern' => '/", + 'Knockout.js virtual element binding', + 'critical', + ], + 'ko virtual element no space' => [ + '', + 'Knockout.js virtual element binding', + 'critical', + ], + ]; + } + + public function testDoesNotFlagCleanHyvaPhtml(): void + { + $cleanPhtml = <<<'PHTML' + +
+ + +
+ + PHTML; + + $this->fileMock->method('fileGetContents')->willReturn($cleanPhtml); + $issues = $this->detector->detectInFile('product-view.phtml'); + $this->assertEmpty($issues, 'Clean Hyvä/Alpine.js template must not trigger any issues'); + } + + // ------------------------------------------------------------------------- + // HTML template patterns (Knockout component templates) + // ------------------------------------------------------------------------- + + /** + * @dataProvider incompatibleHtmlProvider + */ + public function testDetectsIncompatibleHtmlPattern( + string $content, + string $expectedDescription, + ): void { + $this->fileMock->method('fileGetContents')->willReturn($content); + $issues = $this->detector->detectInFile('view/frontend/web/template/listing.html'); + $this->assertIssueFound($issues, $expectedDescription, 'critical'); + } + + /** + * @return array + */ + public static function incompatibleHtmlProvider(): array + { + return [ + 'data-bind in html template' => [ + '
', + 'Knockout.js data-bind attribute', + ], + 'ko virtual element in html' => [ + '

Hello

', + 'Knockout.js virtual element binding', + ], + 'x-magento-init in html' => [ + '', + 'x-magento-init JavaScript initialization', + ], + ]; + } + + // ------------------------------------------------------------------------- + // Edge cases + // ------------------------------------------------------------------------- + + public function testReturnsEmptyArrayWhenFileNotExists(): void + { + $this->fileMock->method('isExists')->willReturn(false); + $issues = $this->detector->detectInFile('nonexistent.js'); + $this->assertEmpty($issues); + } + + public function testReturnsEmptyArrayForUnknownExtension(): void + { + $this->fileMock->method('fileGetContents')->willReturn("define(['jquery'], function() {});"); + $issues = $this->detector->detectInFile('script.coffee'); + $this->assertEmpty($issues, 'Unknown file extensions must be ignored'); + } + + public function testReturnsEmptyArrayOnFileReadError(): void + { + $this->fileMock->method('fileGetContents')->willThrowException(new \RuntimeException('Read error')); + $issues = $this->detector->detectInFile('test.js'); + $this->assertEmpty($issues, 'File read errors must be handled gracefully'); + } + + public function testReportsCorrectLineNumbers(): void + { + $content = "const x = 1;\nconst y = 2;\nko.applyBindings(viewModel);\nconst z = 3;"; + $this->fileMock->method('fileGetContents')->willReturn($content); + + $issues = $this->detector->detectInFile('test.js'); + + $this->assertNotEmpty($issues); + $this->assertSame(3, $issues[0]['line'], 'Line number must be 1-based'); + } + + // ------------------------------------------------------------------------- + // Helper + // ------------------------------------------------------------------------- + + /** + * @param array> $issues + */ + private function assertIssueFound(array $issues, string $description, string $severity): void + { + $this->assertNotEmpty( + $issues, + sprintf('Expected issue "%s" but no issues were detected at all', $description), + ); + + $found = array_filter( + $issues, + static fn(array $issue): bool => $issue['description'] === $description, + ); + + $this->assertNotEmpty( + $found, + sprintf( + 'Expected issue "%s" not found. Detected: %s', + $description, + implode(', ', array_column($issues, 'description')), + ), + ); + + $issue = reset($found); + $this->assertSame( + $severity, + $issue['severity'], + sprintf('Issue "%s" expected severity "%s" but got "%s"', $description, $severity, $issue['severity']), + ); + } +} From e8834ca90ea8817afee66c97ba261efdc5d5d088 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sun, 28 Jun 2026 08:52:57 +0200 Subject: [PATCH 2/7] feat: Implement PHPUnit compatibility checker and enhance IncompatibilityDetector tests --- .ddev/commands/web/phpunit | 18 ++++++++++++++++++ .phpunit.cache/test-results | 2 +- src/Service/Hyva/IncompatibilityDetector.php | 7 ++++++- src/Service/Hyva/ModuleScanner.php | 1 - .../Hyva/IncompatibilityDetectorTest.php | 6 +++--- 5 files changed, 28 insertions(+), 6 deletions(-) create mode 100755 .ddev/commands/web/phpunit diff --git a/.ddev/commands/web/phpunit b/.ddev/commands/web/phpunit new file mode 100755 index 00000000..28b3c5b6 --- /dev/null +++ b/.ddev/commands/web/phpunit @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -euo pipefail + +## Description: Run PHPUnit tests for the MageForge module +## Usage: phpunit [options] +## Example: ddev phpunit +## Example: ddev phpunit --testdox +## Example: ddev phpunit tests/Unit/Service/Hyva/ + +cd /var/www/html + +if [[ ! -x vendor/bin/phpunit ]]; then + echo "phpunit not found. Installing module dev dependencies..." + composer install --no-interaction +fi + +vendor/bin/phpunit "$@" diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results index d512808a..ba272a5a 100644 --- a/.phpunit.cache/test-results +++ b/.phpunit.cache/test-results @@ -1 +1 @@ -{"version":2,"defects":[],"times":{"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"requirejs define\"":0.002,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"requirejs require\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko observable\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko observableArray\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko computed\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko pureComputed\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko applyBindings\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko components register\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko bindingHandlers\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"jquery ajax shorthand\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"jquery ajax full name\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"mage requirejs module\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanAlpineJs":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagWordBoundaryFalsePositive":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"uiComponent tag\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"component uiComponent attribute\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"magento ui js component\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"referenceBlock remove\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanLayoutXml":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"data-mage-init\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"data-bind\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"x-magento-init\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"jquery dom manipulation\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"requirejs in template\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"ko virtual element\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"ko virtual element no space\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanHyvaPhtml":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"data-bind in html template\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"ko virtual element in html\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"x-magento-init in html\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayWhenFileNotExists":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayForUnknownExtension":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayOnFileReadError":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReportsCorrectLineNumbers":0}} \ No newline at end of file +{"version":2,"defects":[],"times":{"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"requirejs define\"":0.007,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"requirejs require\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko observable\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko observableArray\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko computed\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko pureComputed\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko applyBindings\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko components register\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko bindingHandlers\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"jquery ajax shorthand\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"jquery ajax full name\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"mage requirejs module\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanAlpineJs":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagWordBoundaryFalsePositive":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"uiComponent tag\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"component uiComponent attribute\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"magento ui js component\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"referenceBlock remove\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanLayoutXml":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"data-mage-init\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"data-bind\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"x-magento-init\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"jquery dom manipulation\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"requirejs in template\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"ko virtual element\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"ko virtual element no space\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanHyvaPhtml":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"data-bind in html template\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"ko virtual element in html\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"x-magento-init in html\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayWhenFileNotExists":0.001,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayForUnknownExtension":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayOnFileReadError":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReportsCorrectLineNumbers":0}} \ No newline at end of file diff --git a/src/Service/Hyva/IncompatibilityDetector.php b/src/Service/Hyva/IncompatibilityDetector.php index b096dbbf..91928acd 100644 --- a/src/Service/Hyva/IncompatibilityDetector.php +++ b/src/Service/Hyva/IncompatibilityDetector.php @@ -35,7 +35,12 @@ class IncompatibilityDetector 'severity' => self::SEVERITY_CRITICAL, ], [ - 'pattern' => '/\bko\.(observable|observableArray|computed|pureComputed|applyBindings|components|bindingHandlers)/', + 'pattern' => '/\bko\.(observable|observableArray|computed|pureComputed)/', + 'description' => 'Knockout.js usage', + 'severity' => self::SEVERITY_CRITICAL, + ], + [ + 'pattern' => '/\bko\.(applyBindings|components|bindingHandlers)/', 'description' => 'Knockout.js usage', 'severity' => self::SEVERITY_CRITICAL, ], diff --git a/src/Service/Hyva/ModuleScanner.php b/src/Service/Hyva/ModuleScanner.php index 178ca268..aa2881aa 100644 --- a/src/Service/Hyva/ModuleScanner.php +++ b/src/Service/Hyva/ModuleScanner.php @@ -197,5 +197,4 @@ private function getBasename(string $path): string } return substr($trimmed, $pos + 1); } - } diff --git a/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php b/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php index d931cef7..a8179be1 100644 --- a/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php +++ b/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php @@ -392,14 +392,14 @@ private function assertIssueFound(array $issues, string $description, string $se sprintf( 'Expected issue "%s" not found. Detected: %s', $description, - implode(', ', array_column($issues, 'description')), + implode(', ', array_map('strval', array_column($issues, 'description'))), ), ); - $issue = reset($found); + $issue = array_values($found)[0]; $this->assertSame( $severity, - $issue['severity'], + (string) $issue['severity'], sprintf('Issue "%s" expected severity "%s" but got "%s"', $description, $severity, $issue['severity']), ); } From 45fecc42248b31be4215afb0fe00467ec0904fc4 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sun, 28 Jun 2026 08:55:55 +0200 Subject: [PATCH 3/7] feat: Update .gitignore to include PHPUnit cache directory --- .gitignore | 3 +++ .phpunit.cache/test-results | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 .phpunit.cache/test-results diff --git a/.gitignore b/.gitignore index 732980ce..1a5f69b8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ /vendor/ /composer.lock +# PHPUnit +/.phpunit.cache/ + .vscode /magento/ /magento-temp/ diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results deleted file mode 100644 index ba272a5a..00000000 --- a/.phpunit.cache/test-results +++ /dev/null @@ -1 +0,0 @@ -{"version":2,"defects":[],"times":{"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"requirejs define\"":0.007,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"requirejs require\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko observable\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko observableArray\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko computed\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko pureComputed\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko applyBindings\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko components register\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"ko bindingHandlers\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"jquery ajax shorthand\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"jquery ajax full name\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleJsPattern with data set \"mage requirejs module\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanAlpineJs":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagWordBoundaryFalsePositive":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"uiComponent tag\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"component uiComponent attribute\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"magento ui js component\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleXmlPattern with data set \"referenceBlock remove\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanLayoutXml":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"data-mage-init\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"data-bind\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"x-magento-init\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"jquery dom manipulation\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"requirejs in template\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"ko virtual element\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatiblePhtmlPattern with data set \"ko virtual element no space\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDoesNotFlagCleanHyvaPhtml":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"data-bind in html template\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"ko virtual element in html\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testDetectsIncompatibleHtmlPattern with data set \"x-magento-init in html\"":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayWhenFileNotExists":0.001,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayForUnknownExtension":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReturnsEmptyArrayOnFileReadError":0,"OpenForgeProject\\MageForge\\Test\\Unit\\Service\\Hyva\\IncompatibilityDetectorTest::testReportsCorrectLineNumbers":0}} \ No newline at end of file From 0674ae9758d9ae470b9b26363d0ddcd2465d6aaa Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sun, 28 Jun 2026 09:13:07 +0200 Subject: [PATCH 4/7] fix: Simplify compatibility check method calls and return statements for better readability --- src/Console/Command/Hyva/CompatibilityCheckCommand.php | 7 +------ src/Service/Hyva/CompatibilityChecker.php | 4 +--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 0b020efa..2ae79c55 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -234,12 +234,7 @@ private function runScan( $excludeVendor = false; // Run the compatibility check - $results = $this->compatibilityChecker->check( - $this->io, - $showAll, - $scanThirdPartyOnly, - $excludeVendor, - ); + $results = $this->compatibilityChecker->check($this->io, $showAll, $scanThirdPartyOnly, $excludeVendor); // Determine display mode: // showAll = show all modules including compatible ones diff --git a/src/Service/Hyva/CompatibilityChecker.php b/src/Service/Hyva/CompatibilityChecker.php index afddb55a..5abf1e54 100644 --- a/src/Service/Hyva/CompatibilityChecker.php +++ b/src/Service/Hyva/CompatibilityChecker.php @@ -200,9 +200,7 @@ private function getStatusDisplay(array $moduleData): string $hyvaTag = $moduleData['moduleInfo']['isHyvaAware'] ? ' (Hyvä-Aware)' : ''; if ($moduleData['compatible'] && !$moduleData['hasWarnings']) { - return $moduleData['moduleInfo']['isHyvaAware'] - ? '✓ Hyvä-Aware' - : '✓ Compatible'; + return $moduleData['moduleInfo']['isHyvaAware'] ? '✓ Hyvä-Aware' : '✓ Compatible'; } if ($moduleData['compatible']) { From 12f01b925730a42dd683bb756698e699abf00da9 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sun, 28 Jun 2026 09:47:58 +0200 Subject: [PATCH 5/7] feat: Update PHPUnit workflow and fix CompatibilityCheckCommand method signature --- .github/workflows/phpunit.yml | 4 ++-- src/Console/Command/Hyva/CompatibilityCheckCommand.php | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 35b0f284..baf97f7a 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up PHP uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 @@ -24,7 +24,7 @@ jobs: tools: composer:v2 - name: Cache Composer packages - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.composer/cache/files key: ${{ runner.os }}-composer-phpunit-${{ hashFiles('composer.json') }} diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 2ae79c55..0fc385b7 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -176,7 +176,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp $this->io->newLine(); // Run scan with selected options - return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, $incompatibleOnly, $output); + return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, $incompatibleOnly); } catch (\Throwable $e) { $this->io->error('Interactive mode failed: ' . $e->getMessage()); $this->io->info('Falling back to default scan (third-party modules only)...'); @@ -204,7 +204,7 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): $this->io->title('Hyvä Theme Compatibility Check'); - return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, false, $output); + return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, false); } /** @@ -215,7 +215,6 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): * @param bool $includeVendor * @param bool $detailed * @param bool $incompatibleOnly - * @param OutputInterface $output * @return int */ private function runScan( @@ -224,7 +223,6 @@ private function runScan( bool $includeVendor, bool $detailed, bool $incompatibleOnly, - OutputInterface $output, ): int { // Determine filter logic: // - thirdPartyOnly: Only scan non-Magento_* modules (default behavior) From 16d3e4ee1d9d9c39c0f1142623f1c4dd5afa6e7d Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sun, 28 Jun 2026 09:54:11 +0200 Subject: [PATCH 6/7] feat: Enhance IncompatibilityDetector with improved regex patterns and add tests for false positives --- src/Service/Hyva/IncompatibilityDetector.php | 6 +++--- .../Hyva/IncompatibilityDetectorTest.php | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Service/Hyva/IncompatibilityDetector.php b/src/Service/Hyva/IncompatibilityDetector.php index 91928acd..6c3e6922 100644 --- a/src/Service/Hyva/IncompatibilityDetector.php +++ b/src/Service/Hyva/IncompatibilityDetector.php @@ -35,12 +35,12 @@ class IncompatibilityDetector 'severity' => self::SEVERITY_CRITICAL, ], [ - 'pattern' => '/\bko\.(observable|observableArray|computed|pureComputed)/', + 'pattern' => '/\bko\.(observable|observableArray|computed|pureComputed)\b/', 'description' => 'Knockout.js usage', 'severity' => self::SEVERITY_CRITICAL, ], [ - 'pattern' => '/\bko\.(applyBindings|components|bindingHandlers)/', + 'pattern' => '/\bko\.(applyBindings|components|bindingHandlers)\b/', 'description' => 'Knockout.js usage', 'severity' => self::SEVERITY_CRITICAL, ], @@ -84,7 +84,7 @@ class IncompatibilityDetector 'severity' => self::SEVERITY_CRITICAL, ], [ - 'pattern' => '/data-bind\s*=/', + 'pattern' => '/data-bind\b\s*=/', 'description' => 'Knockout.js data-bind attribute', 'severity' => self::SEVERITY_CRITICAL, ], diff --git a/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php b/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php index a8179be1..299a1e46 100644 --- a/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php +++ b/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php @@ -137,6 +137,24 @@ public function testDoesNotFlagWordBoundaryFalsePositive(): void $this->assertEmpty($issues, '"myko" prefix must not match the Knockout.js word-boundary pattern'); } + public function testDoesNotFlagTrailingWordBoundaryFalsePositives(): void + { + // ko.computedSomething / ko.componentsX must NOT match (trailing boundary) + $content = "var a = ko.computedSomething();\nvar b = ko.componentsX.register();\n"; + $this->fileMock->method('fileGetContents')->willReturn($content); + $issues = $this->detector->detectInFile('test.js'); + $this->assertEmpty($issues, 'ko.computedSomething and ko.componentsX must not trigger KO pattern'); + } + + public function testDoesNotFlagDataBindingsFalsePositive(): void + { + // data-bindings= must NOT match, only data-bind= should + $content = '
'; + $this->fileMock->method('fileGetContents')->willReturn($content); + $issues = $this->detector->detectInFile('template.phtml'); + $this->assertEmpty($issues, 'data-bindings= must not match the data-bind pattern'); + } + // ------------------------------------------------------------------------- // XML patterns // ------------------------------------------------------------------------- From 2330093f332d83c00573496b12e70bfa3700b612 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sun, 28 Jun 2026 18:26:59 +0200 Subject: [PATCH 7/7] feat: Update IncompatibilityDetectorTest to use attributes for data providers --- .../Hyva/IncompatibilityDetectorTest.php | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php b/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php index 299a1e46..4f2333de 100644 --- a/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php +++ b/tests/Unit/Service/Hyva/IncompatibilityDetectorTest.php @@ -6,6 +6,7 @@ use Magento\Framework\Filesystem\Driver\File; use OpenForgeProject\MageForge\Service\Hyva\IncompatibilityDetector; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -25,9 +26,7 @@ protected function setUp(): void // JS patterns // ------------------------------------------------------------------------- - /** - * @dataProvider incompatibleJsProvider - */ + #[DataProvider('incompatibleJsProvider')] public function testDetectsIncompatibleJsPattern( string $content, string $expectedDescription, @@ -159,9 +158,7 @@ public function testDoesNotFlagDataBindingsFalsePositive(): void // XML patterns // ------------------------------------------------------------------------- - /** - * @dataProvider incompatibleXmlProvider - */ + #[DataProvider('incompatibleXmlProvider')] public function testDetectsIncompatibleXmlPattern( string $content, string $expectedDescription, @@ -226,9 +223,7 @@ public function testDoesNotFlagCleanLayoutXml(): void // PHTML patterns // ------------------------------------------------------------------------- - /** - * @dataProvider incompatiblePhtmlProvider - */ + #[DataProvider('incompatiblePhtmlProvider')] public function testDetectsIncompatiblePhtmlPattern( string $content, string $expectedDescription, @@ -317,9 +312,7 @@ function productView() { // HTML template patterns (Knockout component templates) // ------------------------------------------------------------------------- - /** - * @dataProvider incompatibleHtmlProvider - */ + #[DataProvider('incompatibleHtmlProvider')] public function testDetectsIncompatibleHtmlPattern( string $content, string $expectedDescription,