From 831a0524ecfe6aa0f871a2a72ab71309c026246f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Perona?= Date: Fri, 14 Aug 2026 12:51:25 -0400 Subject: [PATCH 1/3] fix: consolidate divergent getDefaultVfs() definitions (#36) getDefaultVfs() was defined three times with divergent structures: the trait's wp-admin/wp-content tree was dead code because both Unit\VirtualFilesystemTestCase and Integration\VirtualFilesystemTestCase overrode it with an identical Tests/{Integration,Unit} tree. Move that Tests/{Integration,Unit} structure into VirtualFilesystemTestTrait::getDefaultVfs() as the single default, remove the two identical test-case overrides plus a third redundant copy in the trait's own unit test helper, and document the method as the intended single override point for consumers. Add a dedicated unit test asserting the default structure and its use in mergeStructure(). Co-Authored-By: Sonnet 5 --- .../VirtualFilesystemTestTrait/TestCase.php | 14 -------- .../getDefaultVfs.php | 34 +++++++++++++++++++ src/Integration/VirtualFilesystemTestCase.php | 14 -------- src/Unit/VirtualFilesystemTestCase.php | 14 -------- src/VirtualFilesystemTestTrait.php | 20 +++++------ 5 files changed, 42 insertions(+), 54 deletions(-) create mode 100644 Tests/Unit/VirtualFilesystemTestTrait/getDefaultVfs.php diff --git a/Tests/Unit/VirtualFilesystemTestTrait/TestCase.php b/Tests/Unit/VirtualFilesystemTestTrait/TestCase.php index f2c56fd..bb7e6bc 100644 --- a/Tests/Unit/VirtualFilesystemTestTrait/TestCase.php +++ b/Tests/Unit/VirtualFilesystemTestTrait/TestCase.php @@ -34,18 +34,4 @@ abstract class TestCase extends BaseTestCase { public function getPathToFixturesDir() { return WPMEDIA_PHPUNIT_ROOT_DIR . '/Tests/Fixtures/'; } - - /** - * Gets the default virtual directory filesystem structure. - * - * @return array default structure. - */ - public function getDefaultVfs() { - return [ - 'Tests' => [ - 'Integration' => [], - 'Unit' => [], - ], - ]; - } } diff --git a/Tests/Unit/VirtualFilesystemTestTrait/getDefaultVfs.php b/Tests/Unit/VirtualFilesystemTestTrait/getDefaultVfs.php new file mode 100644 index 0000000..84b5616 --- /dev/null +++ b/Tests/Unit/VirtualFilesystemTestTrait/getDefaultVfs.php @@ -0,0 +1,34 @@ + [ + 'Integration' => [], + 'Unit' => [], + ], + ]; + + $this->assertSame( $expected, $this->getDefaultVfs() ); + } + + public function testShouldBeUsedAsTheBaseForTheMergedStructure() { + $this->config = [ + 'structure' => [ + 'baz' => '', + ], + ]; + + $merged = $this->mergeStructure(); + + $this->assertArrayHasKey( 'Tests', $merged ); + $this->assertArrayHasKey( 'baz', $merged ); + } +} diff --git a/src/Integration/VirtualFilesystemTestCase.php b/src/Integration/VirtualFilesystemTestCase.php index 508d8b2..679a04f 100644 --- a/src/Integration/VirtualFilesystemTestCase.php +++ b/src/Integration/VirtualFilesystemTestCase.php @@ -36,18 +36,4 @@ abstract class VirtualFilesystemTestCase extends TestCase { * @var int */ protected $permissions = 0777; - - /** - * Gets the default virtual directory filesystem structure. - * - * @return array default structure. - */ - public function getDefaultVfs() { - return [ - 'Tests' => [ - 'Integration' => [], - 'Unit' => [], - ], - ]; - } } diff --git a/src/Unit/VirtualFilesystemTestCase.php b/src/Unit/VirtualFilesystemTestCase.php index 6906cdd..dcc8160 100644 --- a/src/Unit/VirtualFilesystemTestCase.php +++ b/src/Unit/VirtualFilesystemTestCase.php @@ -28,18 +28,4 @@ abstract class VirtualFilesystemTestCase extends TestCase { * @var int */ protected $permissions = 0777; - - /** - * Gets the default virtual directory filesystem structure. - * - * @return array default structure. - */ - public function getDefaultVfs() { - return [ - 'Tests' => [ - 'Integration' => [], - 'Unit' => [], - ], - ]; - } } diff --git a/src/VirtualFilesystemTestTrait.php b/src/VirtualFilesystemTestTrait.php index 10a2fea..b087c06 100644 --- a/src/VirtualFilesystemTestTrait.php +++ b/src/VirtualFilesystemTestTrait.php @@ -129,23 +129,19 @@ protected function mergeStructure() { /** * Gets the default virtual directory filesystem structure. * + * This is the single source of truth for the default structure used by + * {@see mergeStructure()}. Consumers that need a different default structure + * should override this method rather than defining a competing default + * elsewhere in the class hierarchy. + * * @return array default structure. */ public function getDefaultVfs() { return [ - 'wp-admin' => [], - 'wp-content' => [ - 'mu-plugins' => [], - 'plugins' => [ - 'wp-rocket' => [], - ], - 'themes' => [ - 'twentytwenty' => [], - ], - 'uploads' => [], + 'Tests' => [ + 'Integration' => [], + 'Unit' => [], ], - 'wp-includes' => [], - 'wp-config.php' => '', ]; } From 555825cbb2801fdd3582a6738c4b4e3874454e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Perona?= Date: Fri, 14 Aug 2026 13:10:27 -0400 Subject: [PATCH 2/3] fix: revert getDefaultVfs() default to WP-like structure (#36) Follow-up to the earlier consolidation: the trait's getDefaultVfs() default is now WP-like again (wp-admin/wp-content/wp-includes/ wp-config.php), restoring the original package default while keeping the "single override point" principle from issue #36 (src/Integration and src/Unit VirtualFilesystemTestCase no longer override it). Internal test suites that need the Tests/{Integration,Unit} structure instead override getDefaultVfs() at the sanctioned override point: Tests/Unit/VirtualFilesystemDirect/TestCase.php, whose getListing/ getDirsListing/getFilesListing fixtures assert the exact, full root listing. Also: - Add declare(strict_types=1) to every file touched this round. - Add these files to phpcs.xml.dist's incremental scope and bring them to zero PHPCS errors; pre-existing camelCase public API names in the trait (getDefaultVfs, rootVirtualUrl, etc.) are annotated with phpcs:ignore, since renaming them would be a breaking change for consumers and is out of scope for this issue. - Refactor the getDefaultVfs() unit test into a single @dataProvider-driven test method sourcing its expectations from Tests/Fixtures/VirtualFilesystemTestTrait/getDefaultVfs.php. composer test-unit: 96 tests, 298 assertions, 0 failures. composer phpcs: 0 errors across the scoped files. composer phpstan: 0 errors across all 71 analyzed files. Co-Authored-By: Sonnet 5 --- .../getDefaultVfs.php | 23 ++++++++++ .../Unit/VirtualFilesystemDirect/TestCase.php | 42 ++++++++++++++++++ .../getDefaultVfs.php | 40 +++++++++-------- phpcs.xml.dist | 4 ++ src/VirtualFilesystemTestTrait.php | 44 ++++++++++++------- 5 files changed, 118 insertions(+), 35 deletions(-) create mode 100644 Tests/Fixtures/VirtualFilesystemTestTrait/getDefaultVfs.php diff --git a/Tests/Fixtures/VirtualFilesystemTestTrait/getDefaultVfs.php b/Tests/Fixtures/VirtualFilesystemTestTrait/getDefaultVfs.php new file mode 100644 index 0000000..8b5b4c7 --- /dev/null +++ b/Tests/Fixtures/VirtualFilesystemTestTrait/getDefaultVfs.php @@ -0,0 +1,23 @@ + [ + [ + 'wp-admin' => [], + 'wp-content' => [ + 'mu-plugins' => [], + 'plugins' => [ + 'wp-rocket' => [], + ], + 'themes' => [ + 'twentytwenty' => [], + ], + 'uploads' => [], + ], + 'wp-includes' => [], + 'wp-config.php' => '', + ], + ], +]; diff --git a/Tests/Unit/VirtualFilesystemDirect/TestCase.php b/Tests/Unit/VirtualFilesystemDirect/TestCase.php index c7ba32d..2d2da60 100644 --- a/Tests/Unit/VirtualFilesystemDirect/TestCase.php +++ b/Tests/Unit/VirtualFilesystemDirect/TestCase.php @@ -1,23 +1,65 @@ init(); } + /** + * Gets the path to the Fixtures directory. + * + * @return string + */ public function getPathToFixturesDir() { return WPMEDIA_PHPUNIT_ROOT_DIR . '/Tests/Fixtures/'; } + /** + * Loads the test data matching the given file from the Fixtures directory. + * + * @param string $file Path or filename of the test class requesting the data. + * + * @return array test data. + */ protected function loadTestData( $file ) { return $this->getTestData( WPMEDIA_PHPUNIT_ROOT_DIR . '/Tests/Fixtures/', basename( $file, '.php' ) ); } + + /** + * Overrides the package's WP-like default with the `Tests/{Integration,Unit}` structure + * that the fixtures in this suite (see {@see Test_GetListing}, {@see Test_GetDirsListing}, + * {@see Test_GetFilesListing}) assert against as the full, exact listing of the virtual + * filesystem root. + * + * @return array default structure. + */ + public function getDefaultVfs() { + return [ + 'Tests' => [ + 'Integration' => [], + 'Unit' => [], + ], + ]; + } } diff --git a/Tests/Unit/VirtualFilesystemTestTrait/getDefaultVfs.php b/Tests/Unit/VirtualFilesystemTestTrait/getDefaultVfs.php index 84b5616..81b6bd5 100644 --- a/Tests/Unit/VirtualFilesystemTestTrait/getDefaultVfs.php +++ b/Tests/Unit/VirtualFilesystemTestTrait/getDefaultVfs.php @@ -1,34 +1,36 @@ [ - 'Integration' => [], - 'Unit' => [], - ], - ]; - + /** + * Asserts that getDefaultVfs() returns the expected default structure. + * + * @dataProvider getDefaultVfsDataProvider + * + * @param array $expected Expected default structure. + * + * @return void + */ + public function testShouldReturnTheDefaultStructure( $expected ) { $this->assertSame( $expected, $this->getDefaultVfs() ); } - public function testShouldBeUsedAsTheBaseForTheMergedStructure() { - $this->config = [ - 'structure' => [ - 'baz' => '', - ], - ]; - - $merged = $this->mergeStructure(); - - $this->assertArrayHasKey( 'Tests', $merged ); - $this->assertArrayHasKey( 'baz', $merged ); + /** + * Provides the expected default structure from the Fixtures directory. + * + * @return array test data. + */ + public function getDefaultVfsDataProvider() { + return $this->getTestData( __DIR__, 'getDefaultVfs' ); } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 9cc0e68..26c2a16 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -12,6 +12,10 @@ codebase is fully compliant. --> src/Integration/HttpRequestTrait.php + src/VirtualFilesystemTestTrait.php + Tests/Unit/VirtualFilesystemDirect/TestCase.php + Tests/Unit/VirtualFilesystemTestTrait/getDefaultVfs.php + Tests/Fixtures/VirtualFilesystemTestTrait/getDefaultVfs.php vendor/* diff --git a/src/VirtualFilesystemTestTrait.php b/src/VirtualFilesystemTestTrait.php index b087c06..1fd864b 100644 --- a/src/VirtualFilesystemTestTrait.php +++ b/src/VirtualFilesystemTestTrait.php @@ -1,5 +1,7 @@ initOriginals(); + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Public API property; renaming would be a breaking change for consumers. $this->filesystem = new VirtualFilesystemDirect( $this->rootVirtualDir, $this->mergeStructure(), $this->permissions ); - $this->rootVirtualUrl = $this->filesystem->getUrl( '/' ); + $this->rootVirtualUrl = $this->filesystem->getUrl( '/' ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Public API property; renaming would be a breaking change for consumers. } /** @@ -80,7 +83,7 @@ public function init() { * * @return mixed */ - public function providerTestData() { + public function providerTestData() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid -- Public API method; renaming would be a breaking change for consumers. $this->loadConfig(); return $this->config['test_data']; @@ -89,7 +92,7 @@ public function providerTestData() { /** * Loads the configuration for the vfs structure and test data. */ - protected function loadConfig() { + protected function loadConfig() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid -- Public API method; renaming would be a breaking change for consumers. $this->config = array_merge( [ 'vfs_dir' => '', @@ -105,7 +108,7 @@ protected function loadConfig() { * * @return string */ - public function getPathToFixturesDir() { + public function getPathToFixturesDir() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid -- Public API method; renaming would be a breaking change for consumers. return ''; } @@ -114,7 +117,7 @@ public function getPathToFixturesDir() { * * @return array merged structure */ - protected function mergeStructure() { + protected function mergeStructure() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid -- Public API method; renaming would be a breaking change for consumers. // If already merged, return it. if ( ! empty( $this->merged_structure ) ) { return $this->merged_structure; @@ -136,28 +139,37 @@ protected function mergeStructure() { * * @return array default structure. */ - public function getDefaultVfs() { + public function getDefaultVfs() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid -- Public API method and the package's documented single override point (issue #36); renaming would be a breaking change for consumers. return [ - 'Tests' => [ - 'Integration' => [], - 'Unit' => [], + 'wp-admin' => [], + 'wp-content' => [ + 'mu-plugins' => [], + 'plugins' => [ + 'wp-rocket' => [], + ], + 'themes' => [ + 'twentytwenty' => [], + ], + 'uploads' => [], ], + 'wp-includes' => [], + 'wp-config.php' => '', ]; } /** * Initializes the original files and directories properties for use in the tests. */ - protected function initOriginals() { + protected function initOriginals() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid -- Public API method; renaming would be a breaking change for consumers. // Bail out when "skip_initOriginals" is set to true. - if ( $this->skip_initOriginals ) { + if ( $this->skip_initOriginals ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Public API property; renaming would be a breaking change for consumers. return; } if ( ! empty( $this->config['vfs_dir'] ) && '/' !== $this->config['vfs_dir'] ) { - $vfs_dir = rtrim( $this->config['vfs_dir'], '/\\' ); // Remove trailing slash for the get. - $structure = $this->get( $this->config['structure'], $vfs_dir, [], '/' ); - $vfs_dir .= '/'; // Add the trailing slash for the flattening. + $vfs_dir = rtrim( $this->config['vfs_dir'], '/\\' ); // Remove trailing slash for the get. + $structure = $this->get( $this->config['structure'], $vfs_dir, [], '/' ); + $vfs_dir .= '/'; // Add the trailing slash for the flattening. } else { $vfs_dir = ''; $structure = $this->config['structure']; From 1263b156d322f203afb8ac2c581ea33df53c95b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Perona?= Date: Fri, 14 Aug 2026 14:01:39 -0400 Subject: [PATCH 3/3] fix: repoint PHPStan baseline entries after trait-attribution shift (#36) Removing the getDefaultVfs() override from src/Unit/VirtualFilesystemTestCase.php shifted PHPStan's attribution of three pre-existing ArrayTrait is_array()/is_null() errors from the trait-using class to the trait's own file (src/ArrayTrait.php). Repoint the two baseline entries so they match again, fixing the CI ignore.unmatched failures. No code behavior change. Co-Authored-By: Claude Opus 4.8 --- phpstan-baseline.neon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 53298e5..2a37334 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -178,13 +178,13 @@ parameters: rawMessage: 'Call to function is_array() with array will always evaluate to true.' identifier: function.alreadyNarrowedType count: 1 - path: src/Unit/VirtualFilesystemTestCase.php + path: src/ArrayTrait.php - rawMessage: 'Call to function is_null() with string will always evaluate to false.' identifier: function.impossibleType count: 2 - path: src/Unit/VirtualFilesystemTestCase.php + path: src/ArrayTrait.php - rawMessage: Constant WPMEDIA_PHPUNIT_ROOT_DIR not found.