diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000..6a4243f6ae
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.phpunit.cache
+vendor
+tmp
diff --git a/.gitignore b/.gitignore
index f6b653c766..72ef66c5ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,7 @@ composer.lock
/vendor
.phpunit.cache
+build/coverage.xml
+coverage-priv.xml
tmp
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000..32ae9e029b
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,20 @@
+FROM php:8.4-cli-bookworm
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends curl git unzip libicu-dev $PHPIZE_DEPS \
+ && docker-php-ext-install intl \
+ && curl -fsSL https://github.com/krakjoe/pcov/archive/refs/tags/v1.0.12.tar.gz -o /tmp/pcov.tar.gz \
+ && tar xzf /tmp/pcov.tar.gz -C /tmp \
+ && cd /tmp/pcov-1.0.12 \
+ && phpize \
+ && ./configure \
+ && make -j"$(nproc)" \
+ && make install \
+ && docker-php-ext-enable pcov \
+ && rm -rf /tmp/pcov.tar.gz /tmp/pcov-1.0.12 \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
+
+WORKDIR /app
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000..c7fb9e6703
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,8 @@
+services:
+ php:
+ build: .
+ working_dir: /app
+ volumes:
+ - .:/app
+ environment:
+ COMPOSER_ALLOW_SUPERUSER: 1
diff --git a/docker/check.sh b/docker/check.sh
new file mode 100755
index 0000000000..ad1093c551
--- /dev/null
+++ b/docker/check.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+source docker/common.sh
+
+install_dependencies
+
+docker compose run --rm php composer validate --ansi
+docker compose run --rm php vendor/bin/phpunit
+docker compose run --rm php vendor/bin/phpstan analyse --memory-limit=512M --ansi
+docker compose run --rm php vendor/bin/ecs check --ansi
+docker compose run --rm php vendor/bin/composer-dependency-analyser
diff --git a/docker/common.sh b/docker/common.sh
new file mode 100755
index 0000000000..2cc7bf90ee
--- /dev/null
+++ b/docker/common.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+install_dependencies() {
+ if [[ ! -f vendor/bin/phpunit ]]; then
+ docker compose run --rm php composer update --no-interaction
+ return
+ fi
+
+ docker compose run --rm php composer install --no-interaction \
+ || docker compose run --rm php composer update --no-interaction
+}
diff --git a/docker/coverage.sh b/docker/coverage.sh
new file mode 100755
index 0000000000..6650e1f39c
--- /dev/null
+++ b/docker/coverage.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+source docker/common.sh
+
+install_dependencies
+docker compose run --rm php vendor/bin/phpunit --configuration phpunit.coverage.xml "$@"
diff --git a/docker/test.sh b/docker/test.sh
new file mode 100755
index 0000000000..15cf4f6fdf
--- /dev/null
+++ b/docker/test.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+source docker/common.sh
+
+install_dependencies
+docker compose run --rm php vendor/bin/phpunit
diff --git a/phpstan.neon b/phpstan.neon
index 8d4af5e8ef..622f3ce512 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -29,16 +29,6 @@ parameters:
identifier: method.alreadyNarrowedType
path: tests
- # compatibility with PHPUnit 9 and 10
- -
- identifier: arguments.count
- path: src/Testing/MockWire.php
-
- # type from service definition
- -
- identifier: argument.type
- path: src/Testing/MockWire.php
-
# magic contract
-
identifier: public.method.unused
diff --git a/phpunit.coverage.xml b/phpunit.coverage.xml
new file mode 100644
index 0000000000..d5118e3bc6
--- /dev/null
+++ b/phpunit.coverage.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ tests
+ tests/Testing/UnitTestFilePathsFinder/Fixture
+
+
+
+
+
+ src
+
+
+
+
+
+
+
+
+
diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php
index af962ab76e..6849f3b64d 100644
--- a/src/DependencyInjection/ContainerFactory.php
+++ b/src/DependencyInjection/ContainerFactory.php
@@ -18,6 +18,7 @@ public function create(): Container
$container = new Container();
$container->autodiscover(__DIR__ . '/../Command');
+ $container->autodiscover(__DIR__ . '/../Testing/Command');
$container->service(Parser::class, static function (): Parser {
$phpParserFactory = new ParserFactory();
diff --git a/src/Finder/FilesFinder.php b/src/Finder/FilesFinder.php
index 18b687299c..15b013bfd4 100644
--- a/src/Finder/FilesFinder.php
+++ b/src/Finder/FilesFinder.php
@@ -90,14 +90,16 @@ public static function findJsonFiles(array $sources): array
}
}
- $jsonFileFinder = Finder::create()
- ->files()
- ->in($directories)
- ->name('*.json')
- ->sortByName();
-
- foreach ($jsonFileFinder->getIterator() as $fileInfo) {
- $jsonFileInfos[] = $fileInfo;
+ if ($directories !== []) {
+ $jsonFileFinder = Finder::create()
+ ->files()
+ ->in($directories)
+ ->name('*.json')
+ ->sortByName();
+
+ foreach ($jsonFileFinder->getIterator() as $fileInfo) {
+ $jsonFileInfos[] = $fileInfo;
+ }
}
return $jsonFileInfos;
diff --git a/src/PhpParser/NodeVisitor/EntityClassNameCollectingNodeVisitor.php b/src/PhpParser/NodeVisitor/EntityClassNameCollectingNodeVisitor.php
index a672151c18..57c18a7749 100644
--- a/src/PhpParser/NodeVisitor/EntityClassNameCollectingNodeVisitor.php
+++ b/src/PhpParser/NodeVisitor/EntityClassNameCollectingNodeVisitor.php
@@ -35,7 +35,8 @@ public function enterNode(Node $node): ?Node
}
// must be named
- if (! $node->namespacedName instanceof Name) {
+ $namespacedName = $node->namespacedName ?? null;
+ if (! $namespacedName instanceof Name) {
return null;
}
diff --git a/src/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor.php b/src/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor.php
index c2523593b5..5a1d3b9806 100644
--- a/src/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor.php
+++ b/src/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor.php
@@ -170,7 +170,7 @@ private function getClassName(): string
throw new ShouldNotHappenException('Class_ node is missing');
}
- $namespaceName = $this->currentClass->namespacedName;
+ $namespaceName = $this->currentClass->namespacedName ?? null;
if (! $namespaceName instanceof Name) {
throw new ShouldNotHappenException();
}
diff --git a/src/PhpParser/NodeVisitor/NeedForFinalizeNodeVisitor.php b/src/PhpParser/NodeVisitor/NeedForFinalizeNodeVisitor.php
index f95fbf1623..895a7bf423 100644
--- a/src/PhpParser/NodeVisitor/NeedForFinalizeNodeVisitor.php
+++ b/src/PhpParser/NodeVisitor/NeedForFinalizeNodeVisitor.php
@@ -45,7 +45,8 @@ public function enterNode(Node $node): ?Node
}
// we need a name to make it work
- if (! $node->namespacedName instanceof Name) {
+ $namespacedName = $node->namespacedName ?? null;
+ if (! $namespacedName instanceof Name) {
return null;
}
diff --git a/src/Testing/MockWire.php b/src/Testing/MockWire.php
index 2fba87a322..8af347f676 100644
--- a/src/Testing/MockWire.php
+++ b/src/Testing/MockWire.php
@@ -11,6 +11,7 @@
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionParameter;
+use Webmozart\Assert\Assert;
/**
* @api used in public
@@ -112,16 +113,11 @@ private static function matchPassedMockOrCreate(
}
}
- // fallback to directly created mock
- // support for PHPUnit 10 and 9
- $testCaseReflectionClass = new ReflectionClass(TestCase::class);
- $testCaseConstructor = $testCaseReflectionClass->getConstructor();
- if ($testCaseConstructor instanceof ReflectionMethod && $testCaseConstructor->getNumberOfRequiredParameters() > 0) {
- $phpunitMocker = new PHPUnitMocker('testName');
- } else {
- $phpunitMocker = new PHPUnitMocker();
- }
+ $phpunitMocker = new PHPUnitMocker('mock');
+
+ $parameterClassName = $reflectionParameter->getType()->getName();
+ Assert::classExists($parameterClassName);
- return $phpunitMocker->create($reflectionParameter->getType()->getName());
+ return $phpunitMocker->create($parameterClassName);
}
}
diff --git a/stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php
index 6f63401868..37feb5d4e0 100644
--- a/stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php
+++ b/stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php
@@ -2,6 +2,8 @@
namespace Symfony\Bundle\FrameworkBundle\Test;
-class KernelTestCase
+use PHPUnit\Framework\TestCase;
+
+class KernelTestCase extends TestCase
{
}
diff --git a/tests/CachedPhpParser/CachedPhpParserTest.php b/tests/CachedPhpParser/CachedPhpParserTest.php
new file mode 100644
index 0000000000..b014abd298
--- /dev/null
+++ b/tests/CachedPhpParser/CachedPhpParserTest.php
@@ -0,0 +1,50 @@
+cachedPhpParser = $this->make(CachedPhpParser::class);
+ }
+
+ public function testParseFileCachesResult(): void
+ {
+ $filePath = __DIR__ . '/../PhpParser/Finder/ClassConstFinder/Fixture/SomeClassWithConstants.php';
+
+ $firstParse = $this->cachedPhpParser->parseFile($filePath);
+ $secondParse = $this->cachedPhpParser->parseFile($filePath);
+
+ $this->assertSame($firstParse, $secondParse);
+ $this->assertNotEmpty($firstParse);
+ }
+
+ public function testParseError(): void
+ {
+ $tempFile = sys_get_temp_dir() . '/swiss-knife-parse-error-' . uniqid() . '.php';
+ FileSystem::write($tempFile, 'expectException(RuntimeException::class);
+ $this->expectExceptionMessage('Could not parse file "' . $tempFile . '"');
+
+ $this->cachedPhpParser->parseFile($tempFile);
+ } finally {
+ FileSystem::delete($tempFile);
+ }
+ }
+}
diff --git a/tests/Command/AliceYamlFixturesToPhpCommandTest.php b/tests/Command/AliceYamlFixturesToPhpCommandTest.php
new file mode 100644
index 0000000000..8d2ec4ae2a
--- /dev/null
+++ b/tests/Command/AliceYamlFixturesToPhpCommandTest.php
@@ -0,0 +1,41 @@
+tempDirectory = sys_get_temp_dir() . '/swiss-knife-alice-' . uniqid();
+ FileSystem::createDir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testRun(): void
+ {
+ $yamlPath = $this->tempDirectory . '/fixture.yml';
+ FileSystem::write($yamlPath, "SomeEntity:\n name: test\n", null);
+
+ $command = $this->make(AliceYamlFixturesToPhpCommand::class);
+ $exitCode = $command->run([$this->tempDirectory]);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ $this->assertFileExists($this->tempDirectory . '/fixture.php');
+ $this->assertFileDoesNotExist($yamlPath);
+ }
+}
diff --git a/tests/Command/AliceYamlFixturesToPhpYamlExtensionTest.php b/tests/Command/AliceYamlFixturesToPhpYamlExtensionTest.php
new file mode 100644
index 0000000000..039b4bec1f
--- /dev/null
+++ b/tests/Command/AliceYamlFixturesToPhpYamlExtensionTest.php
@@ -0,0 +1,40 @@
+tempDirectory = sys_get_temp_dir() . '/swiss-knife-alice-yaml-' . uniqid();
+ FileSystem::createDir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testYamlExtension(): void
+ {
+ $yamlPath = $this->tempDirectory . '/fixture.yaml';
+ FileSystem::write($yamlPath, "SomeEntity:\n name: test\n", null);
+
+ $command = $this->make(AliceYamlFixturesToPhpCommand::class);
+ $exitCode = $command->run([$this->tempDirectory]);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ $this->assertFileExists($this->tempDirectory . '/fixture.php');
+ }
+}
diff --git a/tests/Command/CheckCommentedCodeCommandTest.php b/tests/Command/CheckCommentedCodeCommandTest.php
new file mode 100644
index 0000000000..c154a5fd75
--- /dev/null
+++ b/tests/Command/CheckCommentedCodeCommandTest.php
@@ -0,0 +1,30 @@
+make(CheckCommentedCodeCommand::class);
+
+ $exitCode = $command->run([__DIR__ . '/Fixture/CheckCommentedCode/Clean'], [], 4);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ }
+
+ public function testErrorWhenCommentedCodeFound(): void
+ {
+ $command = $this->make(CheckCommentedCodeCommand::class);
+
+ $exitCode = $command->run([__DIR__ . '/Fixture/CheckCommentedCode/Commented'], [], 2);
+
+ $this->assertSame(ExitCode::ERROR, $exitCode);
+ }
+}
diff --git a/tests/Command/CheckConflictsCommandTest.php b/tests/Command/CheckConflictsCommandTest.php
new file mode 100644
index 0000000000..7b0e872b12
--- /dev/null
+++ b/tests/Command/CheckConflictsCommandTest.php
@@ -0,0 +1,57 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+ chdir(__DIR__);
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ }
+
+ public function testSuccessWhenNoConflicts(): void
+ {
+ $command = $this->make(CheckConflictsCommand::class);
+
+ $exitCode = $command->run(['Fixture/CheckConflicts/no-conflicts']);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ }
+
+ public function testErrorWhenConflictsFound(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-check-conflicts-' . uniqid();
+ FileSystem::createDir($tempDirectory);
+ FileSystem::write($tempDirectory . '/conflict.txt', "<<<<<<< HEAD\n=======\n", null);
+
+ try {
+ chdir($tempDirectory);
+
+ $command = $this->make(CheckConflictsCommand::class);
+ $exitCode = $command->run(['.']);
+
+ $this->assertSame(ExitCode::ERROR, $exitCode);
+ } finally {
+ chdir($this->originalCwd);
+ FileSystem::delete($tempDirectory);
+ }
+ }
+}
diff --git a/tests/Command/CommandMetadataTest.php b/tests/Command/CommandMetadataTest.php
new file mode 100644
index 0000000000..da5297f0ef
--- /dev/null
+++ b/tests/Command/CommandMetadataTest.php
@@ -0,0 +1,60 @@
+}>
+ */
+ public static function provideCommands(): iterable
+ {
+ yield 'check-conflicts' => [CheckConflictsCommand::class];
+ yield 'check-commented-code' => [CheckCommentedCodeCommand::class];
+ yield 'dump-editorconfig' => [DumpEditorconfigCommand::class];
+ yield 'finalize-classes' => [FinalizeClassesCommand::class];
+ yield 'find-multi-classes' => [FindMultiClassesCommand::class];
+ yield 'generate-symfony-config-builders' => [GenerateSymfonyConfigBuildersCommand::class];
+ yield 'generate-symfony-smoke-tests' => [GenerateSymfonySmokeTestsCommand::class];
+ yield 'namespace-to-psr-4' => [NamespaceToPSR4Command::class];
+ yield 'pretty-json' => [PrettyJsonCommand::class];
+ yield 'privatize-constants' => [PrivatizeConstantsCommand::class];
+ yield 'search-regex' => [SearchRegexCommand::class];
+ yield 'split-config-per-package' => [SplitSymfonyConfigToPerPackageCommand::class];
+ yield 'spot-lazy-traits' => [SpotLazyTraitsCommand::class];
+ yield 'alice-yaml-fixtures-to-php' => [AliceYamlFixturesToPhpCommand::class];
+ yield 'detect-unit-tests' => [DetectUnitTestsCommand::class];
+ }
+
+ /**
+ * @param class-string $commandClass
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('provideCommands')]
+ public function testMetadata(string $commandClass): void
+ {
+ $command = $this->make($commandClass);
+
+ $this->assertNotEmpty($command->getName());
+ $this->assertNotEmpty($command->getDescription());
+ }
+}
diff --git a/tests/Command/DetectUnitTestsCommandTest.php b/tests/Command/DetectUnitTestsCommandTest.php
new file mode 100644
index 0000000000..af511a72d8
--- /dev/null
+++ b/tests/Command/DetectUnitTestsCommandTest.php
@@ -0,0 +1,58 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+
+ $this->tempDirectory = sys_get_temp_dir() . '/swiss-knife-detect-unit-' . uniqid();
+ FileSystem::createDir($this->tempDirectory);
+ chdir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ @unlink($this->tempDirectory . '/phpunit-unit-files.xml');
+ chdir($this->originalCwd);
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testRunWithUnitTests(): void
+ {
+ FileSystem::copy(
+ __DIR__ . '/../Testing/UnitTestFilePathsFinder/Fixture/RandomTest.php',
+ $this->tempDirectory . '/RandomTest.php'
+ );
+
+ $command = $this->make(DetectUnitTestsCommand::class);
+ $exitCode = $command->run([$this->tempDirectory]);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ $this->assertFileExists($this->tempDirectory . '/phpunit-unit-files.xml');
+ }
+
+ public function testRunWithNoUnitTests(): void
+ {
+ $command = $this->make(DetectUnitTestsCommand::class);
+ $exitCode = $command->run([$this->tempDirectory]);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ }
+}
diff --git a/tests/Command/DumpEditorconfigCommandTest.php b/tests/Command/DumpEditorconfigCommandTest.php
new file mode 100644
index 0000000000..4c2e63c476
--- /dev/null
+++ b/tests/Command/DumpEditorconfigCommandTest.php
@@ -0,0 +1,55 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+
+ $this->tempDirectory = sys_get_temp_dir() . '/swiss-knife-editorconfig-' . uniqid();
+ FileSystem::createDir($this->tempDirectory);
+ chdir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testSuccess(): void
+ {
+ $command = $this->make(DumpEditorconfigCommand::class);
+
+ $exitCode = $command->run();
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ $this->assertFileExists($this->tempDirectory . '/.editorconfig');
+ }
+
+ public function testErrorWhenAlreadyExists(): void
+ {
+ FileSystem::write($this->tempDirectory . '/.editorconfig', 'root = true', null);
+
+ $command = $this->make(DumpEditorconfigCommand::class);
+ $exitCode = $command->run();
+
+ $this->assertSame(ExitCode::ERROR, $exitCode);
+ }
+}
diff --git a/tests/Command/FinalizeClassesCommandExtendedTest.php b/tests/Command/FinalizeClassesCommandExtendedTest.php
new file mode 100644
index 0000000000..8c55cf4f48
--- /dev/null
+++ b/tests/Command/FinalizeClassesCommandExtendedTest.php
@@ -0,0 +1,40 @@
+make(FinalizeClassesCommand::class);
+ $exitCode = $command->run([$tempDirectory], dryRun: false, noProgress: true);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ $this->assertStringContainsString('final class', FileSystem::read($tempDirectory . '/ToFinalize.php'));
+ } finally {
+ FileSystem::delete($tempDirectory);
+ }
+ }
+}
diff --git a/tests/Command/FinalizeClassesCommandProgressTest.php b/tests/Command/FinalizeClassesCommandProgressTest.php
new file mode 100644
index 0000000000..2c6da7f75d
--- /dev/null
+++ b/tests/Command/FinalizeClassesCommandProgressTest.php
@@ -0,0 +1,53 @@
+make(FinalizeClassesCommand::class);
+ $exitCode = $command->run([$tempDirectory], dryRun: true, noProgress: false);
+
+ $this->assertSame(ExitCode::ERROR, $exitCode);
+ } finally {
+ FileSystem::delete($tempDirectory);
+ }
+ }
+
+ public function testWithSkipMockedAndProgressBar(): void
+ {
+ $command = $this->make(FinalizeClassesCommand::class);
+
+ $exitCode = $command->run(
+ [__DIR__ . '/Fixture/FinalizeClasses'],
+ dryRun: true,
+ skipMocked: true,
+ noProgress: false
+ );
+
+ $this->assertContains($exitCode, [ExitCode::SUCCESS, ExitCode::ERROR]);
+ }
+}
diff --git a/tests/Command/FinalizeClassesCommandTest.php b/tests/Command/FinalizeClassesCommandTest.php
new file mode 100644
index 0000000000..a05b89c5df
--- /dev/null
+++ b/tests/Command/FinalizeClassesCommandTest.php
@@ -0,0 +1,39 @@
+make(FinalizeClassesCommand::class);
+
+ $exitCode = $command->run(
+ [__DIR__ . '/Fixture/FinalizeClasses'],
+ dryRun: true,
+ noProgress: true
+ );
+
+ $this->assertContains($exitCode, [ExitCode::SUCCESS, ExitCode::ERROR]);
+ }
+
+ public function testDryRunWithSkipMocked(): void
+ {
+ $command = $this->make(FinalizeClassesCommand::class);
+
+ $exitCode = $command->run(
+ [__DIR__ . '/Fixture/FinalizeClasses'],
+ dryRun: true,
+ skipMocked: true,
+ noProgress: true
+ );
+
+ $this->assertContains($exitCode, [ExitCode::SUCCESS, ExitCode::ERROR]);
+ }
+}
diff --git a/tests/Command/FindMultiClassesCommandTest.php b/tests/Command/FindMultiClassesCommandTest.php
new file mode 100644
index 0000000000..a6ee03557a
--- /dev/null
+++ b/tests/Command/FindMultiClassesCommandTest.php
@@ -0,0 +1,30 @@
+make(FindMultiClassesCommand::class);
+
+ $exitCode = $command->run([__DIR__ . '/Fixture/FindMultiClasses/single'], []);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ }
+
+ public function testErrorWhenMultipleClassesFound(): void
+ {
+ $command = $this->make(FindMultiClassesCommand::class);
+
+ $exitCode = $command->run([__DIR__ . '/../Finder/MultiClassFixture'], []);
+
+ $this->assertSame(ExitCode::ERROR, $exitCode);
+ }
+}
diff --git a/tests/Command/Fixture/CheckCommentedCode/Clean/CleanClass.php b/tests/Command/Fixture/CheckCommentedCode/Clean/CleanClass.php
new file mode 100644
index 0000000000..3fa8f70078
--- /dev/null
+++ b/tests/Command/Fixture/CheckCommentedCode/Clean/CleanClass.php
@@ -0,0 +1,13 @@
+extension('framework', [
+ 'secret' => 'test',
+ ]);
+ $containerConfigurator->extension('doctrine', [
+ 'dbal' => [
+ 'url' => 'sqlite:///:memory:',
+ ],
+ ]);
+};
diff --git a/tests/Command/GenerateSymfonyConfigBuildersCommandTest.php b/tests/Command/GenerateSymfonyConfigBuildersCommandTest.php
new file mode 100644
index 0000000000..e5c15b7c2e
--- /dev/null
+++ b/tests/Command/GenerateSymfonyConfigBuildersCommandTest.php
@@ -0,0 +1,19 @@
+make(GenerateSymfonyConfigBuildersCommand::class);
+
+ $this->assertSame(ExitCode::ERROR, $command->run());
+ }
+}
diff --git a/tests/Command/GenerateSymfonySmokeTestsCommandExtendedTest.php b/tests/Command/GenerateSymfonySmokeTestsCommandExtendedTest.php
new file mode 100644
index 0000000000..89d85db985
--- /dev/null
+++ b/tests/Command/GenerateSymfonySmokeTestsCommandExtendedTest.php
@@ -0,0 +1,44 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+
+ $this->tempDirectory = sys_get_temp_dir() . '/swiss-knife-smoke-error-' . uniqid();
+ FileSystem::createDir($this->tempDirectory);
+ FileSystem::createDir($this->tempDirectory . '/tests/Unit/Smoke');
+ FileSystem::write($this->tempDirectory . '/composer.json', '{"require":{"some/package":"^1.0"}}', null);
+ chdir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testNoMatchingPackages(): void
+ {
+ $command = $this->make(GenerateSymfonySmokeTestsCommand::class);
+
+ $this->assertSame(ExitCode::ERROR, $command->run());
+ }
+}
diff --git a/tests/Command/GenerateSymfonySmokeTestsCommandTest.php b/tests/Command/GenerateSymfonySmokeTestsCommandTest.php
new file mode 100644
index 0000000000..0b1c158a6a
--- /dev/null
+++ b/tests/Command/GenerateSymfonySmokeTestsCommandTest.php
@@ -0,0 +1,59 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+
+ $this->tempDirectory = sys_get_temp_dir() . '/swiss-knife-smoke-tests-' . uniqid();
+ FileSystem::createDir($this->tempDirectory . '/tests/Unit/Smoke');
+
+ FileSystem::write($this->tempDirectory . '/composer.json', Json::encode([
+ 'require' => [
+ 'symfony/dependency-injection' => '^7.0',
+ ],
+ 'autoload-dev' => [
+ 'psr-4' => [
+ 'App\\Tests\\' => 'tests/',
+ ],
+ ],
+ ]), null);
+
+ chdir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testRun(): void
+ {
+ $command = $this->make(GenerateSymfonySmokeTestsCommand::class);
+
+ $exitCode = $command->run();
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ $this->assertFileExists($this->tempDirectory . '/tests/Unit/Smoke/ServiceContainerTest.php');
+ $this->assertFileExists($this->tempDirectory . '/tests/Unit/Smoke/AbstractContainerTestCase.php');
+ }
+}
diff --git a/tests/Command/GenerateSymfonySmokeTestsSkipExistingTest.php b/tests/Command/GenerateSymfonySmokeTestsSkipExistingTest.php
new file mode 100644
index 0000000000..7c6a2f6b8f
--- /dev/null
+++ b/tests/Command/GenerateSymfonySmokeTestsSkipExistingTest.php
@@ -0,0 +1,49 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+
+ $this->tempDirectory = sys_get_temp_dir() . '/swiss-knife-smoke-skip-' . uniqid();
+ FileSystem::createDir($this->tempDirectory . '/tests/Unit/Smoke');
+ FileSystem::write($this->tempDirectory . '/tests/Unit/Smoke/ServiceContainerTest.php', 'tempDirectory . '/composer.json', Json::encode([
+ 'require' => ['symfony/dependency-injection' => '^7.0'],
+ 'autoload-dev' => ['psr-4' => ['App\\Tests\\' => 'tests/']],
+ ]), null);
+
+ chdir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testSkipExistingFiles(): void
+ {
+ $command = $this->make(GenerateSymfonySmokeTestsCommand::class);
+
+ $this->assertSame(ExitCode::SUCCESS, $command->run());
+ }
+}
diff --git a/tests/Command/NamespaceToPSR4CommandTest.php b/tests/Command/NamespaceToPSR4CommandTest.php
index 5859114239..441081073d 100644
--- a/tests/Command/NamespaceToPSR4CommandTest.php
+++ b/tests/Command/NamespaceToPSR4CommandTest.php
@@ -11,6 +11,8 @@
final class NamespaceToPSR4CommandTest extends AbstractTestCase
{
+ private string $originalCwd;
+
private CLIRequestMapper $cliRequestMapper;
private NamespaceToPSR4Command $namespaceToPSR4Command;
@@ -19,10 +21,18 @@ protected function setUp(): void
{
parent::setUp();
+ $originalCwd = getcwd();
+ $this->originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+
$this->cliRequestMapper = $this->make(CLIRequestMapper::class);
$this->namespaceToPSR4Command = $this->make(NamespaceToPSR4Command::class);
}
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ }
+
/**
* @see https://github.com/rectorphp/swiss-knife/issues/124
*/
@@ -38,4 +48,65 @@ public function testNamespaceRootOptionIsKeptAsString(): void
$this->assertSame(['app', 'App\\'], $arguments);
}
+
+ public function testRunFixesIncorrectNamespace(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-namespace-' . uniqid();
+ \Nette\Utils\FileSystem::createDir($tempDirectory . '/Sub');
+ \Nette\Utils\FileSystem::write(
+ $tempDirectory . '/Sub/SomeClass.php',
+ <<<'PHP'
+namespaceToPSR4Command->run('.', 'App\\Tests');
+
+ $this->assertSame(\Entropy\Console\Enum\ExitCode::SUCCESS, $exitCode);
+
+ $contents = file_get_contents($tempDirectory . '/Sub/SomeClass.php');
+ $this->assertStringContainsString('namespace App\\Tests\\Sub;', (string) $contents);
+
+ \Nette\Utils\FileSystem::delete($tempDirectory);
+ }
+
+ public function testRunWithCorrectNamespace(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-namespace-ok-' . uniqid();
+ \Nette\Utils\FileSystem::createDir($tempDirectory);
+ \Nette\Utils\FileSystem::write(
+ $tempDirectory . '/SomeClass.php',
+ <<<'PHP'
+namespaceToPSR4Command->run('.', 'App\\Tests');
+
+ $this->assertSame(\Entropy\Console\Enum\ExitCode::SUCCESS, $exitCode);
+
+ \Nette\Utils\FileSystem::delete($tempDirectory);
+ }
}
diff --git a/tests/Command/PrettyJsonCommandTest.php b/tests/Command/PrettyJsonCommandTest.php
new file mode 100644
index 0000000000..75a78a5f78
--- /dev/null
+++ b/tests/Command/PrettyJsonCommandTest.php
@@ -0,0 +1,46 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+ chdir(__DIR__);
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ }
+
+ public function testDryRun(): void
+ {
+ $command = $this->make(PrettyJsonCommand::class);
+
+ $exitCode = $command->run(['Fixture/PrettyJson'], dryRun: true);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ }
+
+ public function testErrorWhenNoJsonFiles(): void
+ {
+ $command = $this->make(PrettyJsonCommand::class);
+
+ $exitCode = $command->run(['Fixture/PrettyJson/empty-dir']);
+
+ $this->assertSame(ExitCode::ERROR, $exitCode);
+ }
+}
diff --git a/tests/Command/PrettyJsonCommandWriteTest.php b/tests/Command/PrettyJsonCommandWriteTest.php
new file mode 100644
index 0000000000..7a86e07ebe
--- /dev/null
+++ b/tests/Command/PrettyJsonCommandWriteTest.php
@@ -0,0 +1,38 @@
+tempDirectory = sys_get_temp_dir() . '/swiss-knife-pretty-json-' . uniqid();
+ \Nette\Utils\FileSystem::createDir($this->tempDirectory);
+ \Nette\Utils\FileSystem::write($this->tempDirectory . '/ugly.json', '{"a":1}', null);
+ }
+
+ protected function tearDown(): void
+ {
+ \Nette\Utils\FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testWritePrettyJson(): void
+ {
+ $command = $this->make(PrettyJsonCommand::class);
+
+ $exitCode = $command->run([$this->tempDirectory . '/ugly.json'], dryRun: false);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ $this->assertStringContainsString("\n", \Nette\Utils\FileSystem::read($this->tempDirectory . '/ugly.json'));
+ }
+}
diff --git a/tests/Command/PrivatizeConstantsCommandExtendedTest.php b/tests/Command/PrivatizeConstantsCommandExtendedTest.php
new file mode 100644
index 0000000000..a86eb79580
--- /dev/null
+++ b/tests/Command/PrivatizeConstantsCommandExtendedTest.php
@@ -0,0 +1,150 @@
+make(PrivatizeConstantsCommand::class);
+ $exitCode = $command->run([$tempDirectory], dryRun: false);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ $this->assertStringContainsString('private const VALUE', FileSystem::read($tempDirectory . '/WithConstant.php'));
+ } finally {
+ FileSystem::delete($tempDirectory);
+ }
+ }
+
+ public function testDryRunWithPrivatizableConstant(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-priv-dry-' . uniqid();
+ FileSystem::createDir($tempDirectory);
+ FileSystem::write($tempDirectory . '/WithConstant.php', <<<'PHP'
+make(PrivatizeConstantsCommand::class);
+ $exitCode = $command->run([$tempDirectory], dryRun: true);
+
+ $this->assertSame(ExitCode::ERROR, $exitCode);
+ } finally {
+ FileSystem::delete($tempDirectory);
+ }
+ }
+
+ public function testExternalConstantUsage(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-priv-ext-' . uniqid();
+ FileSystem::createDir($tempDirectory);
+ FileSystem::write($tempDirectory . '/WithPublicConstant.php', <<<'PHP'
+make(PrivatizeConstantsCommand::class);
+ $exitCode = $command->run([$tempDirectory], dryRun: true);
+
+ $this->assertContains($exitCode, [ExitCode::SUCCESS, ExitCode::ERROR]);
+ } finally {
+ FileSystem::delete($tempDirectory);
+ }
+ }
+
+ public function testNoConstantsInFile(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-priv-none-' . uniqid();
+ FileSystem::createDir($tempDirectory);
+ FileSystem::write($tempDirectory . '/NoConstants.php', <<<'PHP'
+make(PrivatizeConstantsCommand::class);
+ $exitCode = $command->run([$tempDirectory]);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ } finally {
+ FileSystem::delete($tempDirectory);
+ }
+ }
+}
diff --git a/tests/Command/PrivatizeConstantsCommandTest.php b/tests/Command/PrivatizeConstantsCommandTest.php
new file mode 100644
index 0000000000..369300e208
--- /dev/null
+++ b/tests/Command/PrivatizeConstantsCommandTest.php
@@ -0,0 +1,39 @@
+make(PrivatizeConstantsCommand::class);
+
+ $exitCode = $command->run(
+ [__DIR__ . '/Fixture/PrivatizeConstants'],
+ dryRun: true
+ );
+
+ $this->assertContains($exitCode, [ExitCode::SUCCESS, ExitCode::ERROR]);
+ }
+
+ public function testEmptySources(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-privatize-empty-' . uniqid();
+ mkdir($tempDirectory);
+
+ try {
+ $command = $this->make(PrivatizeConstantsCommand::class);
+ $exitCode = $command->run([$tempDirectory]);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ } finally {
+ rmdir($tempDirectory);
+ }
+ }
+}
diff --git a/tests/Command/SearchRegexCommandTest.php b/tests/Command/SearchRegexCommandTest.php
new file mode 100644
index 0000000000..48ac594df5
--- /dev/null
+++ b/tests/Command/SearchRegexCommandTest.php
@@ -0,0 +1,24 @@
+make(SearchRegexCommand::class);
+
+ $exitCode = $command->run(
+ '#class SearchRegexTarget#',
+ __DIR__ . '/Fixture/SearchRegex'
+ );
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ }
+}
diff --git a/tests/Command/SearchRegexDefaultDirectoryTest.php b/tests/Command/SearchRegexDefaultDirectoryTest.php
new file mode 100644
index 0000000000..bc102c489c
--- /dev/null
+++ b/tests/Command/SearchRegexDefaultDirectoryTest.php
@@ -0,0 +1,37 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+ chdir(__DIR__ . '/Fixture/SearchRegex');
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ }
+
+ public function testDefaultProjectDirectory(): void
+ {
+ $command = $this->make(SearchRegexCommand::class);
+
+ $exitCode = $command->run('#class SearchRegexTarget#');
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ }
+}
diff --git a/tests/Command/SplitSymfonyConfigNoExtensionsTest.php b/tests/Command/SplitSymfonyConfigNoExtensionsTest.php
new file mode 100644
index 0000000000..583e873d94
--- /dev/null
+++ b/tests/Command/SplitSymfonyConfigNoExtensionsTest.php
@@ -0,0 +1,39 @@
+import('services.php');
+};
+PHP, null);
+
+ try {
+ $command = $this->make(SplitSymfonyConfigToPerPackageCommand::class);
+ $exitCode = $command->run($tempDirectory . '/config.php', $tempDirectory . '/out');
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ } finally {
+ FileSystem::delete($tempDirectory);
+ }
+ }
+}
diff --git a/tests/Command/SplitSymfonyConfigToPerPackageCommandTest.php b/tests/Command/SplitSymfonyConfigToPerPackageCommandTest.php
new file mode 100644
index 0000000000..242f658842
--- /dev/null
+++ b/tests/Command/SplitSymfonyConfigToPerPackageCommandTest.php
@@ -0,0 +1,46 @@
+tempDirectory = sys_get_temp_dir() . '/swiss-knife-split-config-' . uniqid();
+ FileSystem::createDir($this->tempDirectory . '/packages');
+ FileSystem::copy(
+ __DIR__ . '/Fixture/SplitSymfonyConfig/config.php',
+ $this->tempDirectory . '/config.php'
+ );
+ }
+
+ protected function tearDown(): void
+ {
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testRun(): void
+ {
+ $command = $this->make(SplitSymfonyConfigToPerPackageCommand::class);
+
+ $exitCode = $command->run(
+ $this->tempDirectory . '/config.php',
+ $this->tempDirectory . '/packages'
+ );
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ $this->assertFileExists($this->tempDirectory . '/packages/framework.php');
+ $this->assertFileExists($this->tempDirectory . '/packages/doctrine.php');
+ }
+}
diff --git a/tests/Command/SpotLazyTraitsCommandTest.php b/tests/Command/SpotLazyTraitsCommandTest.php
new file mode 100644
index 0000000000..3125aad408
--- /dev/null
+++ b/tests/Command/SpotLazyTraitsCommandTest.php
@@ -0,0 +1,37 @@
+make(SpotLazyTraitsCommand::class);
+
+ $exitCode = $command->run([__DIR__ . '/../Traits/Fixture'], maxUsed: 2);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ }
+
+ public function testWithoutTraits(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-no-traits-' . uniqid();
+ FileSystem::createDir($tempDirectory);
+
+ try {
+ $command = $this->make(SpotLazyTraitsCommand::class);
+ $exitCode = $command->run([$tempDirectory]);
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ } finally {
+ FileSystem::delete($tempDirectory);
+ }
+ }
+}
diff --git a/tests/Coverage/RemainingCoverageTest.php b/tests/Coverage/RemainingCoverageTest.php
new file mode 100644
index 0000000000..96c46c3123
--- /dev/null
+++ b/tests/Coverage/RemainingCoverageTest.php
@@ -0,0 +1,572 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ }
+
+ public function testNamespaceToPSR4SkipsCorrectNamespaceAndReportsAllCorrect(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-ns-all-ok-' . uniqid();
+ FileSystem::createDir($tempDirectory);
+ FileSystem::write(
+ $tempDirectory . '/SomeClass.php',
+ "make(NamespaceToPSR4Command::class);
+ $exitCode = $command->run('.', 'App\\Tests');
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ FileSystem::delete($tempDirectory);
+ }
+
+ public function testNamespaceToPSR4FixesMultipleFiles(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-ns-multi-' . uniqid();
+ FileSystem::createDir($tempDirectory . '/A');
+ FileSystem::createDir($tempDirectory . '/B');
+ FileSystem::write($tempDirectory . '/A/One.php', "make(NamespaceToPSR4Command::class);
+ $exitCode = $command->run('.', 'App\\Tests');
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ FileSystem::delete($tempDirectory);
+ }
+
+ public function testSearchRegexSkipsFilesWithoutMatches(): void
+ {
+ $command = $this->make(SearchRegexCommand::class);
+ $exitCode = $command->run('#class\\s+#', __DIR__ . '/../Finder/Fixture');
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ }
+
+ public function testPrivatizeConstantsKeepsPublicWhenUsedExternally(): void
+ {
+ $command = $this->make(PrivatizeConstantsCommand::class);
+ $exitCode = $command->run(
+ [__DIR__ . '/../PhpParser/Finder/ClassConstantFetchFinder/Fixture/Standard'],
+ dryRun: true
+ );
+
+ $this->assertSame(ExitCode::SUCCESS, $exitCode);
+ }
+
+ public function testSplitSymfonyConfigThrowsOnNonStringExtensionArgument(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-split-bad-' . uniqid();
+ FileSystem::createDir($tempDirectory);
+ $configPath = $tempDirectory . '/config.php';
+ FileSystem::write(
+ $configPath,
+ <<<'PHP'
+extension(123);
+};
+PHP,
+ null
+ );
+
+ $command = $this->make(SplitSymfonyConfigToPerPackageCommand::class);
+
+ $this->expectException(ShouldNotHappenException::class);
+ $command->run($configPath, $tempDirectory . '/out');
+
+ FileSystem::delete($tempDirectory);
+ }
+
+ public function testPhpFilesFinderExcludesByExactFilePath(): void
+ {
+ $fixtureDirectory = __DIR__ . '/../Finder/Fixture';
+ $controllerPath = $fixtureDirectory . '/AController.php';
+
+ $filesByExactPath = PhpFilesFinder::find([$fixtureDirectory], [realpath($controllerPath)]);
+ $this->assertCount(2, $filesByExactPath);
+ }
+
+ public function testClassConstantFetchFinderDebugPrintsTraversalError(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-ccf-debug-' . uniqid();
+ FileSystem::createDir($tempDirectory);
+ FileSystem::write(
+ $tempDirectory . '/Bad.php',
+ "make(ClassConstantFetchFinder::class);
+ $fileInfos = PhpFilesFinder::find([$tempDirectory]);
+ $progressBar = new ProgressBar(new OutputColorizer());
+ $progressBar->start(1);
+
+ $fetches = $finder->find($fileInfos, $progressBar, isDebug: true);
+
+ $this->assertSame([], $fetches);
+ FileSystem::delete($tempDirectory);
+ }
+
+ public function testAddImportConfigMethodCallVisitorSkipsClosureWithMultipleParameters(): void
+ {
+ $closure = new Closure();
+ $closure->params = [new \PhpParser\Node\Param(new Variable('a')), new \PhpParser\Node\Param(new Variable('b'))];
+
+ $visitor = new AddImportConfigMethodCallNodeVisitor('/out');
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor($visitor);
+ $nodeTraverser->traverse([$closure]);
+
+ $this->assertCount(0, $closure->stmts ?? []);
+ }
+
+ public function testEntityClassNameCollectingNodeVisitorSkipsUnnamedClassAndDocWithoutAt(): void
+ {
+ $visitor = new EntityClassNameCollectingNodeVisitor();
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor(new NameResolver());
+ $nodeTraverser->addVisitor($visitor);
+
+ $visitorWithoutNameResolver = new EntityClassNameCollectingNodeVisitor();
+ $nodeTraverserWithoutResolver = new NodeTraverser();
+ $nodeTraverserWithoutResolver->addVisitor($visitorWithoutNameResolver);
+ $nodeTraverserWithoutResolver->traverse($this->parseCode('traverse($this->parseCode('assertSame([], $visitor->getEntityClassNames());
+ }
+
+ public function testExtractSymfonyExtensionCallNodeVisitorSkipsNonMethodCallAndDynamicName(): void
+ {
+ $notMethodCall = new Expression(new Variable('foo'));
+ $dynamicNameCall = new Expression(new MethodCall(new Variable('c'), new String_('extension')));
+
+ $visitor = new ExtractSymfonyExtensionCallNodeVisitor();
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor($visitor);
+ $nodeTraverser->traverse([$notMethodCall, $dynamicNameCall]);
+
+ $this->assertSame([], $visitor->getExtensionMethodCalls());
+ }
+
+ public function testFindClassConstFetchNodeVisitorEdgeCases(): void
+ {
+ require_once __DIR__ . '/../PhpParser/Finder/ClassConstantFetchFinder/Fixture/Standard/AnotherClassWithConstant.php';
+
+ $dynamicClassFetch = <<<'PHP'
+addVisitor(new NameResolver());
+ $nodeTraverser->addVisitor($visitor);
+ $nodeTraverser->traverse($this->parseCode($dynamicClassFetch));
+
+ $staticCurrent = <<<'PHP'
+addVisitor(new NameResolver());
+ $nodeTraverser3->addVisitor($visitor3);
+ $nodeTraverser3->traverse($this->parseCode($staticCurrent));
+ $this->assertCount(1, $visitor3->getClassConstantFetches());
+
+ $vendorParent = <<<'PHP'
+addVisitor(new NameResolver());
+ $nodeTraverserVendor->addVisitor($visitorVendor);
+ $nodeTraverserVendor->traverse($this->parseCode($vendorParent));
+ $this->assertSame([], $visitorVendor->getClassConstantFetches());
+
+ $vendorExternal = <<<'PHP'
+addVisitor(new NameResolver());
+ $nodeTraverserExternal->addVisitor($visitorExternal);
+ $nodeTraverserExternal->traverse($this->parseCode($vendorExternal));
+ $this->assertSame([], $visitorExternal->getClassConstantFetches());
+
+ if (! trait_exists('TraitFetch\\SomeTrait')) {
+ eval('namespace TraitFetch; trait SomeTrait { public const FOO = "bar"; }');
+ }
+
+ $traitFetch = <<<'PHP'
+addVisitor(new NameResolver());
+ $nodeTraverser4->addVisitor($visitor4);
+ $nodeTraverser4->traverse($this->parseCode($traitFetch));
+ $this->assertCount(1, $visitor4->getClassConstantFetches());
+ }
+
+ public function testFindClassConstFetchNodeVisitorMissingParentThrows(): void
+ {
+ $missingParent = <<<'PHP'
+addVisitor(new NameResolver());
+ $nodeTraverser5->addVisitor($visitor5);
+ $this->expectException(ShouldNotHappenException::class);
+ $nodeTraverser5->traverse($this->parseCode($missingParent));
+ }
+
+ public function testFindClassConstFetchNodeVisitorGetClassNameThrowsWithoutNamespace(): void
+ {
+ $globalSelf = <<<'PHP'
+addVisitor($visitor);
+ $this->expectException(ShouldNotHappenException::class);
+ $nodeTraverser->traverse($this->parseCode($globalSelf));
+ }
+
+ public function testFindNonPrivateClassConstNodeVisitorSkipsPrivateConstants(): void
+ {
+ $visitor = new FindNonPrivateClassConstNodeVisitor();
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor(new NameResolver());
+ $nodeTraverser->addVisitor($visitor);
+
+ $code = <<<'PHP'
+traverse($this->parseCode($code));
+
+ $constants = $visitor->getClassConstants();
+ $this->assertCount(1, $constants);
+ $this->assertSame('VISIBLE', $constants[0]->getConstantName());
+ }
+
+ public function testMockedClassNameCollectingNodeVisitorSkipsDynamicMethodAndMissingArgs(): void
+ {
+ $visitor = new MockedClassNameCollectingNodeVisitor();
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor($visitor);
+
+ $code = <<<'PHP'
+{'createMock'}();
+ $this->createMock();
+ }
+}
+PHP;
+ $nodeTraverser->traverse($this->parseCode($code));
+
+ $this->assertSame([], $visitor->getMockedClassNames());
+ }
+
+ public function testNeedForFinalizeNodeVisitorSkipsClassWithoutNamespace(): void
+ {
+ $visitor = new NeedForFinalizeNodeVisitor([]);
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor($visitor);
+
+ $nodeTraverser->traverse($this->parseCode('assertFalse($visitor->isNeeded());
+ }
+
+ public function testParentClassNameCollectingNodeVisitorFiltersVendorParents(): void
+ {
+ $visitor = new ParentClassNameCollectingNodeVisitor();
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor(new NameResolver());
+ $nodeTraverser->addVisitor($visitor);
+
+ $code = <<<'PHP'
+traverse($this->parseCode($code));
+
+ $this->assertSame(['App\\Legit\\Parent'], $visitor->getParentClassNames());
+ }
+
+ public function testTemplateDecoratorUsesAppKernel(): void
+ {
+ if (! class_exists('App\\Kernel', false)) {
+ eval('namespace App; class Kernel {}');
+ }
+
+ $templateDecorator = new TemplateDecorator();
+ $decorated = $templateDecorator->decorate('__KERNEL_CLASS_PLACEHOLDER__');
+
+ $this->assertStringContainsString('App\\Kernel', $decorated);
+ }
+
+ #[\PHPUnit\Framework\Attributes\RunInSeparateProcess]
+ public function testTemplateDecoratorUsesLegacyAppKernel(): void
+ {
+ if (! class_exists('AppKernel', false)) {
+ eval('class AppKernel {}');
+ }
+
+ $templateDecorator = new TemplateDecorator();
+ $decorated = $templateDecorator->decorate('__KERNEL_CLASS_PLACEHOLDER__');
+
+ $this->assertStringContainsString('AppKernel', $decorated);
+ }
+
+ public function testFindClassConstFetchNodeVisitorGetClassNameThrowsWhenCurrentClassMissing(): void
+ {
+ $visitor = new FindClassConstFetchNodeVisitor();
+ $reflectionMethod = new \ReflectionMethod(FindClassConstFetchNodeVisitor::class, 'getClassName');
+ $reflectionMethod->setAccessible(true);
+
+ $this->expectException(ShouldNotHappenException::class);
+ $reflectionMethod->invoke($visitor);
+ }
+
+ public function testFindClassConstFetchNodeVisitorDoesClassExistForInterface(): void
+ {
+ $visitor = new FindClassConstFetchNodeVisitor();
+ $reflectionMethod = new \ReflectionMethod(FindClassConstFetchNodeVisitor::class, 'doesClassExist');
+ $reflectionMethod->setAccessible(true);
+
+ $this->assertTrue($reflectionMethod->invoke($visitor, 'Countable'));
+ }
+
+ public function testTestCaseClassFinderSkipsExistingInterfaceAndTrait(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-tccf-' . uniqid();
+ FileSystem::createDir($tempDirectory);
+ FileSystem::write($tempDirectory . '/SomeInterface.php', 'findInDirectories([$tempDirectory]);
+ $classesAgain = $finder->findInDirectories([$tempDirectory]);
+
+ $this->assertArrayHasKey('SomeTestInterface', $classes);
+ $this->assertArrayHasKey('SomeTestTrait', $classes);
+ $this->assertArrayHasKey('SomeTestInterface', $classesAgain);
+ $this->assertArrayHasKey('SomeTestTrait', $classesAgain);
+
+ FileSystem::delete($tempDirectory);
+ }
+
+ public function testUnitTestFilterExcludesKernelTestCase(): void
+ {
+ if (! class_exists(\Symfony\Bundle\FrameworkBundle\Test\KernelTestCase::class)) {
+ eval('namespace Symfony\Bundle\FrameworkBundle\Test; class KernelTestCase extends \PHPUnit\Framework\TestCase {}');
+ }
+
+ eval('namespace SwissKnifeKernelTest; class MyKernelTest extends \Symfony\Bundle\FrameworkBundle\Test\KernelTestCase {}');
+
+ $unitTestFilter = new UnitTestFilter();
+ $isUnitTest = new \ReflectionMethod(UnitTestFilter::class, 'isUnitTest');
+ $isUnitTest->setAccessible(true);
+
+ $this->assertFalse($isUnitTest->invoke($unitTestFilter, 'SwissKnifeKernelTest\\MyKernelTest'));
+
+ $filtered = $unitTestFilter->filter([
+ 'SwissKnifeKernelTest\\MyKernelTest' => '/path/MyKernelTest.php',
+ 'PHPUnit\\Framework\\TestCase' => '/path/TestCase.php',
+ ]);
+
+ $this->assertSame(['PHPUnit\\Framework\\TestCase' => '/path/TestCase.php'], $filtered);
+ }
+
+ public function testTraitSpotterSkipsUnknownTraitUsage(): void
+ {
+ $tempDirectory = sys_get_temp_dir() . '/swiss-knife-trait-' . uniqid();
+ FileSystem::createDir($tempDirectory);
+ FileSystem::write($tempDirectory . '/KnownTrait.php', "make(TraitSpotter::class);
+ $result = $traitSpotter->analyse([$tempDirectory]);
+
+ $this->assertSame(1, $result->getTraitCount());
+
+ FileSystem::delete($tempDirectory);
+ }
+
+ public function testIsClassConstantUsedPubliclyBranches(): void
+ {
+ $command = $this->make(PrivatizeConstantsCommand::class);
+
+ $externalFetch = new ExternalClassAccessConstantFetch('SomeClass', 'FOO');
+ $currentFetch = new CurrentClassConstantFetch('SomeClass', 'FOO');
+
+ $reflectionMethod = new \ReflectionMethod(PrivatizeConstantsCommand::class, 'isClassConstantUsedPublicly');
+ $reflectionMethod->setAccessible(true);
+
+ $classConstant = new \Rector\SwissKnife\ValueObject\ClassConstant('OtherClass', 'BAR');
+
+ $isPublic = $reflectionMethod->invoke($command, [$externalFetch, $currentFetch], $classConstant);
+ $this->assertFalse($isPublic);
+
+ $matchingExternal = new \Rector\SwissKnife\ValueObject\ClassConstant('SomeClass', 'FOO');
+ $isPublicExternal = $reflectionMethod->invoke($command, [$externalFetch], $matchingExternal);
+ $this->assertTrue($isPublicExternal);
+ }
+
+ /**
+ * @return array<\PhpParser\Node>
+ */
+ private function parseCode(string $code): array
+ {
+ $parser = (new ParserFactory())->createForNewestSupportedVersion();
+ $nodes = $parser->parse($code);
+ $this->assertNotNull($nodes);
+
+ return $nodes;
+ }
+}
diff --git a/tests/DependencyInjection/ContainerFactoryTest.php b/tests/DependencyInjection/ContainerFactoryTest.php
new file mode 100644
index 0000000000..d8675ac91d
--- /dev/null
+++ b/tests/DependencyInjection/ContainerFactoryTest.php
@@ -0,0 +1,36 @@
+create();
+
+ $this->assertInstanceOf(Container::class, $container);
+ $this->assertInstanceOf(Parser::class, $container->make(Parser::class));
+ $this->assertInstanceOf(CheckConflictsCommand::class, $container->make(CheckConflictsCommand::class));
+ $this->assertInstanceOf(DetectUnitTestsCommand::class, $container->make(DetectUnitTestsCommand::class));
+ }
+
+ public function testCommandsImplementCommandInterface(): void
+ {
+ $containerFactory = new ContainerFactory();
+ $container = $containerFactory->create();
+
+ $checkConflictsCommand = $container->make(CheckConflictsCommand::class);
+ $this->assertInstanceOf(CommandInterface::class, $checkConflictsCommand);
+ }
+}
diff --git a/tests/Enum/StaticAccessorTest.php b/tests/Enum/StaticAccessorTest.php
new file mode 100644
index 0000000000..11e11ae90a
--- /dev/null
+++ b/tests/Enum/StaticAccessorTest.php
@@ -0,0 +1,17 @@
+assertSame('static', StaticAccessor::STATIC);
+ $this->assertSame('self', StaticAccessor::SELF);
+ }
+}
diff --git a/tests/Enum/SymfonyClassTest.php b/tests/Enum/SymfonyClassTest.php
new file mode 100644
index 0000000000..a92ab80250
--- /dev/null
+++ b/tests/Enum/SymfonyClassTest.php
@@ -0,0 +1,17 @@
+assertSame(ContainerConfigurator::class, SymfonyClass::CONTAINER_CONFIGURATOR_CLASS);
+ }
+}
diff --git a/tests/FileSystem/JsonAnalyzerTest.php b/tests/FileSystem/JsonAnalyzerTest.php
new file mode 100644
index 0000000000..0741e996f1
--- /dev/null
+++ b/tests/FileSystem/JsonAnalyzerTest.php
@@ -0,0 +1,26 @@
+jsonAnalyzer = new JsonAnalyzer();
+ }
+
+ public function testIsPrettyPrinted(): void
+ {
+ $this->assertFalse($this->jsonAnalyzer->isPrettyPrinted('{"a":1}'));
+
+ $prettyJson = "{\n \"a\": 1\n}";
+ $this->assertTrue($this->jsonAnalyzer->isPrettyPrinted($prettyJson));
+ }
+}
diff --git a/tests/FileSystem/PathHelperTest.php b/tests/FileSystem/PathHelperTest.php
new file mode 100644
index 0000000000..b49a837b5c
--- /dev/null
+++ b/tests/FileSystem/PathHelperTest.php
@@ -0,0 +1,41 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+
+ $this->tempDirectory = sys_get_temp_dir() . '/swiss-knife-path-helper-' . uniqid();
+ FileSystem::createDir($this->tempDirectory);
+ chdir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testRelativeToCwd(): void
+ {
+ $filePath = $this->tempDirectory . '/some/nested/file.php';
+ FileSystem::createDir(dirname($filePath));
+ FileSystem::write($filePath, 'assertSame('some/nested/file.php', PathHelper::relativeToCwd($filePath));
+ }
+}
diff --git a/tests/Finder/FilesFinderExtendedTest.php b/tests/Finder/FilesFinderExtendedTest.php
new file mode 100644
index 0000000000..27ccfdaefa
--- /dev/null
+++ b/tests/Finder/FilesFinderExtendedTest.php
@@ -0,0 +1,52 @@
+tempDirectory = sys_get_temp_dir() . '/swiss-knife-files-finder-' . uniqid();
+ FileSystem::createDir($this->tempDirectory . '/twig');
+ FileSystem::write($this->tempDirectory . '/twig/template.twig', 'content', null);
+ FileSystem::write($this->tempDirectory . '/single.json', '{"a":1}', null);
+ FileSystem::write($this->tempDirectory . '/fixture.yml', 'key: value', null);
+ }
+
+ protected function tearDown(): void
+ {
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testFindTwigFiles(): void
+ {
+ $files = FilesFinder::findTwigFiles([$this->tempDirectory]);
+
+ $this->assertCount(1, $files);
+ }
+
+ public function testFindJsonFilesWithSingleFile(): void
+ {
+ $jsonFile = realpath($this->tempDirectory . '/single.json');
+ $this->assertNotFalse($jsonFile);
+
+ $files = FilesFinder::findJsonFiles([$jsonFile]);
+
+ $this->assertCount(1, $files);
+ }
+
+ public function testFindYamlFiles(): void
+ {
+ $files = FilesFinder::findYamlFiles([$this->tempDirectory]);
+
+ $this->assertCount(1, $files);
+ }
+}
diff --git a/tests/Finder/MultiClassFixture/TwoClassesInOneFile.php b/tests/Finder/MultiClassFixture/TwoClassesInOneFile.php
new file mode 100644
index 0000000000..a877a9b09b
--- /dev/null
+++ b/tests/Finder/MultiClassFixture/TwoClassesInOneFile.php
@@ -0,0 +1,13 @@
+make(MultipleClassInOneFileFinder::class);
+
+ $multipleClassesByFile = $multipleClassInOneFileFinder->findInDirectories(
+ [__DIR__ . '/MultiClassFixture'],
+ []
+ );
+
+ $twoClassesFilePath = __DIR__ . '/MultiClassFixture/TwoClassesInOneFile.php';
+ $this->assertArrayHasKey($twoClassesFilePath, $multipleClassesByFile);
+ $this->assertCount(2, $multipleClassesByFile[$twoClassesFilePath]);
+ }
+}
diff --git a/tests/Git/ConflictResolver/ConflictResolverExtractFromFileInfosTest.php b/tests/Git/ConflictResolver/ConflictResolverExtractFromFileInfosTest.php
new file mode 100644
index 0000000000..33e8e3c596
--- /dev/null
+++ b/tests/Git/ConflictResolver/ConflictResolverExtractFromFileInfosTest.php
@@ -0,0 +1,41 @@
+conflictResolver = new ConflictResolver();
+ }
+
+ public function testExtractFromFileInfosSkipsFixtures(): void
+ {
+ $fixturePath = __DIR__ . '/Fixture/some_file.txt';
+ $outsidePath = sys_get_temp_dir() . '/swiss-knife-conflict-' . uniqid() . '.txt';
+ file_put_contents($outsidePath, "<<<<<<< HEAD\ncontent\n=======\n");
+
+ try {
+ $result = $this->conflictResolver->extractFromFileInfos([$fixturePath, $outsidePath]);
+
+ $this->assertArrayNotHasKey($fixturePath, $result);
+ $this->assertSame([$outsidePath => 1], $result);
+ } finally {
+ @unlink($outsidePath);
+ }
+ }
+
+ public function testExtractFromFileInfosSkipsFilesWithoutConflicts(): void
+ {
+ $correctFilePath = __DIR__ . '/Fixture/correct_file.txt';
+
+ $this->assertSame([], $this->conflictResolver->extractFromFileInfos([$correctFilePath]));
+ }
+}
diff --git a/tests/MockedClassResolver/MockedClassResolverTest.php b/tests/MockedClassResolver/MockedClassResolverTest.php
new file mode 100644
index 0000000000..b7841839bc
--- /dev/null
+++ b/tests/MockedClassResolver/MockedClassResolverTest.php
@@ -0,0 +1,37 @@
+make(MockedClassResolver::class);
+
+ $fixtureDirectory = __DIR__ . '/../PhpParser/NodeVisitor/MockedClassNameCollectingNodeVisitor/Fixture';
+ $progressCalls = 0;
+
+ $mockedClassNames = $mockedClassResolver->resolve(
+ [$fixtureDirectory],
+ static function () use (&$progressCalls): void {
+ ++$progressCalls;
+ }
+ );
+
+ $this->assertSame(
+ [
+ 'SomeNamespace\SomeConfiguredStubClass',
+ 'SomeNamespace\SomeIntersectionClass',
+ 'SomeNamespace\SomeMockedClass',
+ 'SomeNamespace\SomeStubClass',
+ ],
+ $mockedClassNames
+ );
+ $this->assertGreaterThan(0, $progressCalls);
+ }
+}
diff --git a/tests/PhpParser/Finder/ClassConstantFetchFinder/ClassConstantFetchFinderTest.php b/tests/PhpParser/Finder/ClassConstantFetchFinder/ClassConstantFetchFinderTest.php
index f2e8a2de71..9670f07d88 100644
--- a/tests/PhpParser/Finder/ClassConstantFetchFinder/ClassConstantFetchFinderTest.php
+++ b/tests/PhpParser/Finder/ClassConstantFetchFinder/ClassConstantFetchFinderTest.php
@@ -13,6 +13,8 @@
use Rector\SwissKnife\Tests\AbstractTestCase;
use Rector\SwissKnife\ValueObject\ClassConstantFetch\CurrentClassConstantFetch;
use Rector\SwissKnife\ValueObject\ClassConstantFetch\ExternalClassAccessConstantFetch;
+use Rector\SwissKnife\ValueObject\ClassConstantFetch\ParentClassConstantFetch;
+use Rector\SwissKnife\ValueObject\ClassConstantFetch\StaticClassConstantFetch;
use RuntimeException;
final class ClassConstantFetchFinderTest extends AbstractTestCase
@@ -45,6 +47,18 @@ public function test(): void
$this->assertInstanceOf(ExternalClassAccessConstantFetch::class, $secondClassConstantFetch);
}
+ public function testParentAndStaticFetches(): void
+ {
+ require_once __DIR__ . '/Fixture/ParentAndStatic/ParentClassWithConstant.php';
+
+ $classConstantFetches = $this->findInDirectory(__DIR__ . '/Fixture/ParentAndStatic');
+
+ $fetchTypes = array_map(static fn ($fetch) => $fetch::class, $classConstantFetches);
+
+ $this->assertContains(ParentClassConstantFetch::class, $fetchTypes);
+ $this->assertContains(StaticClassConstantFetch::class, $fetchTypes);
+ }
+
public function testParseError(): void
{
$this->expectException(RuntimeException::class);
@@ -61,11 +75,18 @@ public function testParseError(): void
/**
* @return ClassConstantFetchInterface[]
*/
- private function findInDirectory(string $directory): array
+ private function findInDirectory(string $directory, ?string $fileName = null): array
{
$progressBar = new ProgressBar(new OutputColorizer());
$fileInfos = PhpFilesFinder::find([$directory]);
+ if ($fileName !== null) {
+ $fileInfos = array_values(array_filter(
+ $fileInfos,
+ static fn ($fileInfo) => str_ends_with($fileInfo->getRealPath(), $fileName)
+ ));
+ }
+
return $this->classConstantFetchFinder->find($fileInfos, $progressBar, false);
}
}
diff --git a/tests/PhpParser/Finder/ClassConstantFetchFinder/Fixture/ParentAndStatic/ClassWithParentAndStatic.php b/tests/PhpParser/Finder/ClassConstantFetchFinder/Fixture/ParentAndStatic/ClassWithParentAndStatic.php
new file mode 100644
index 0000000000..1fc64b14fc
--- /dev/null
+++ b/tests/PhpParser/Finder/ClassConstantFetchFinder/Fixture/ParentAndStatic/ClassWithParentAndStatic.php
@@ -0,0 +1,16 @@
+createStmts($extensionMethodCall);
+
+ $this->assertCount(3, $stmts);
+
+ $prettyPrinter = new \PhpParser\PrettyPrinter\Standard();
+ $printed = $prettyPrinter->prettyPrintFile($stmts);
+
+ $this->assertStringContainsString('strict_types=1', $printed);
+ $this->assertStringContainsString(ContainerConfigurator::class, $printed);
+ $this->assertStringContainsString(SymfonyClass::CONTAINER_CONFIGURATOR_CLASS, $printed);
+ }
+}
diff --git a/tests/PhpParser/NodeVisitor/AddImportConfigMethodCallNodeVisitorTest.php b/tests/PhpParser/NodeVisitor/AddImportConfigMethodCallNodeVisitorTest.php
new file mode 100644
index 0000000000..b76070a3ab
--- /dev/null
+++ b/tests/PhpParser/NodeVisitor/AddImportConfigMethodCallNodeVisitorTest.php
@@ -0,0 +1,30 @@
+params[] = new Param(new Variable('containerConfigurator'), null, new Identifier('ContainerConfigurator'));
+
+ $addImportConfigMethodCallNodeVisitor = new AddImportConfigMethodCallNodeVisitor('/config/packages');
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor($addImportConfigMethodCallNodeVisitor);
+
+ $nodeTraverser->traverse([$closure]);
+
+ $this->assertCount(1, $closure->stmts);
+ }
+}
diff --git a/tests/PhpParser/NodeVisitor/EntityClassNameCollectingNodeVisitor/EntityClassNameCollectingNodeVisitorTest.php b/tests/PhpParser/NodeVisitor/EntityClassNameCollectingNodeVisitor/EntityClassNameCollectingNodeVisitorTest.php
new file mode 100644
index 0000000000..5ffa32cc02
--- /dev/null
+++ b/tests/PhpParser/NodeVisitor/EntityClassNameCollectingNodeVisitor/EntityClassNameCollectingNodeVisitorTest.php
@@ -0,0 +1,53 @@
+addVisitor(new NameResolver());
+ $nodeTraverser->addVisitor($visitor);
+
+ $parser = (new ParserFactory())->createForNewestSupportedVersion();
+ $stmts = $parser->parse((string) file_get_contents($filePath));
+ $this->assertNotNull($stmts);
+
+ $nodeTraverser->traverse($stmts);
+
+ $this->assertSame($expectedEntityClassNames, $visitor->getEntityClassNames());
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideData(): iterable
+ {
+ yield 'entity attribute' => [
+ __DIR__ . '/Fixture/EntityWithAttribute.php',
+ ['Rector\SwissKnife\Tests\PhpParser\NodeVisitor\EntityClassNameCollectingNodeVisitor\Fixture\SomeEntity'],
+ ];
+
+ yield 'document annotation' => [
+ __DIR__ . '/Fixture/DocumentWithAnnotation.php',
+ ['Rector\SwissKnife\Tests\PhpParser\NodeVisitor\EntityClassNameCollectingNodeVisitor\Fixture\SomeDocument'],
+ ];
+
+ yield 'non entity' => [__DIR__ . '/Fixture/RegularClass.php', []];
+ }
+}
diff --git a/tests/PhpParser/NodeVisitor/EntityClassNameCollectingNodeVisitor/Fixture/DocumentWithAnnotation.php b/tests/PhpParser/NodeVisitor/EntityClassNameCollectingNodeVisitor/Fixture/DocumentWithAnnotation.php
new file mode 100644
index 0000000000..5e7780ddce
--- /dev/null
+++ b/tests/PhpParser/NodeVisitor/EntityClassNameCollectingNodeVisitor/Fixture/DocumentWithAnnotation.php
@@ -0,0 +1,12 @@
+addVisitor($extractSymfonyExtensionCallNodeVisitor);
+
+ $stmts = $nodeTraverser->traverse([$extensionCall, $otherCall]);
+
+ $this->assertCount(1, $stmts);
+ $this->assertCount(1, $extractSymfonyExtensionCallNodeVisitor->getExtensionMethodCalls());
+ }
+}
diff --git a/tests/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor/ClassConstantFetchFinderDebugTest.php b/tests/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor/ClassConstantFetchFinderDebugTest.php
new file mode 100644
index 0000000000..ce15edaee3
--- /dev/null
+++ b/tests/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor/ClassConstantFetchFinderDebugTest.php
@@ -0,0 +1,32 @@
+create();
+ $finder = $container->make(ClassConstantFetchFinder::class);
+
+ $fileInfos = PhpFilesFinder::find([
+ __DIR__ . '/../../Finder/ClassConstantFetchFinder/Fixture/Standard',
+ ]);
+
+ $progressBar = new ProgressBar(new OutputColorizer());
+ $progressBar->start(1);
+
+ $fetches = $finder->find($fileInfos, $progressBar, isDebug: true);
+
+ $this->assertNotEmpty($fetches);
+ }
+}
diff --git a/tests/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor/FindClassConstFetchNodeVisitorExtendedTest.php b/tests/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor/FindClassConstFetchNodeVisitorExtendedTest.php
new file mode 100644
index 0000000000..6a8f7fbe55
--- /dev/null
+++ b/tests/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor/FindClassConstFetchNodeVisitorExtendedTest.php
@@ -0,0 +1,151 @@
+addVisitor(new NameResolver());
+ $nodeTraverser->addVisitor($visitor);
+
+ $code = <<<'PHP'
+traverse($this->parseCode($code));
+
+ $this->assertSame([], $visitor->getClassConstantFetches());
+ }
+
+ public function testSelfMagicClassConstantIsSkipped(): void
+ {
+ $visitor = new FindClassConstFetchNodeVisitor();
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor(new NameResolver());
+ $nodeTraverser->addVisitor($visitor);
+
+ $code = <<<'PHP'
+traverse($this->parseCode($code));
+
+ $this->assertSame([], $visitor->getClassConstantFetches());
+ }
+
+ public function testDynamicConstantNameIsSkipped(): void
+ {
+ $visitor = new FindClassConstFetchNodeVisitor();
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor(new NameResolver());
+ $nodeTraverser->addVisitor($visitor);
+
+ $code = <<<'PHP'
+traverse($this->parseCode($code));
+
+ $this->assertSame([], $visitor->getClassConstantFetches());
+ }
+
+ public function testCurrentClassConstant(): void
+ {
+ $visitor = new FindClassConstFetchNodeVisitor();
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor(new NameResolver());
+ $nodeTraverser->addVisitor($visitor);
+
+ $code = <<<'PHP'
+traverse($this->parseCode($code));
+
+ $fetches = $visitor->getClassConstantFetches();
+ $this->assertCount(1, $fetches);
+ $this->assertInstanceOf(CurrentClassConstantFetch::class, $fetches[0]);
+ }
+
+ public function testInterfaceExists(): void
+ {
+ $visitor = new FindClassConstFetchNodeVisitor();
+ $nodeTraverser = new NodeTraverser();
+ $nodeTraverser->addVisitor(new NameResolver());
+ $nodeTraverser->addVisitor($visitor);
+
+ $code = <<<'PHP'
+traverse($this->parseCode($code));
+
+ $this->assertSame([], $visitor->getClassConstantFetches());
+ }
+
+ /**
+ * @return array
+ */
+ private function parseCode(string $code): array
+ {
+ $parser = (new ParserFactory())->createForNewestSupportedVersion();
+ $nodes = $parser->parse($code);
+ $this->assertNotNull($nodes);
+
+ return $nodes;
+ }
+}
diff --git a/tests/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor/FindClassConstFetchNodeVisitorTest.php b/tests/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor/FindClassConstFetchNodeVisitorTest.php
new file mode 100644
index 0000000000..65ff7672e5
--- /dev/null
+++ b/tests/PhpParser/NodeVisitor/FindClassConstFetchNodeVisitor/FindClassConstFetchNodeVisitorTest.php
@@ -0,0 +1,100 @@
+createForNewestSupportedVersion();
+ $code = <<<'PHP'
+parse($code);
+ $this->assertNotNull($stmts);
+
+ $nodeTraverser->traverse($stmts);
+
+ $this->assertSame([], $visitor->getClassConstantFetches());
+ }
+
+ public function testExternalClassFetch(): void
+ {
+ require_once __DIR__ . '/../../Finder/ClassConstantFetchFinder/Fixture/Standard/AnotherClassWithConstant.php';
+
+ $visitor = new FindClassConstFetchNodeVisitor();
+ $nodeTraverser = NodeTraverserFactory::create($visitor);
+
+ $parser = (new ParserFactory())->createForNewestSupportedVersion();
+ $code = <<<'PHP'
+parse($code);
+ $this->assertNotNull($stmts);
+
+ $nodeTraverser->traverse($stmts);
+
+ $fetches = $visitor->getClassConstantFetches();
+ $this->assertCount(1, $fetches);
+ $this->assertInstanceOf(ExternalClassAccessConstantFetch::class, $fetches[0]);
+ }
+
+ public function testNotImplementedYetException(): void
+ {
+ $this->expectException(NotImplementedYetException::class);
+
+ $visitor = new FindClassConstFetchNodeVisitor();
+ $nodeTraverser = NodeTraverserFactory::create($visitor);
+
+ $parser = (new ParserFactory())->createForNewestSupportedVersion();
+ $code = <<<'PHP'
+parse($code);
+ $this->assertNotNull($stmts);
+
+ $nodeTraverser->traverse($stmts);
+ }
+}
diff --git a/tests/RobotLoader/PhpClassLoaderTest.php b/tests/RobotLoader/PhpClassLoaderTest.php
new file mode 100644
index 0000000000..55ee541da0
--- /dev/null
+++ b/tests/RobotLoader/PhpClassLoaderTest.php
@@ -0,0 +1,30 @@
+load(
+ [__DIR__ . '/../Finder/MultiClassFixture'],
+ []
+ );
+
+ $this->assertArrayHasKey(
+ 'Rector\SwissKnife\Tests\Finder\MultiClassFixture\FirstClassInFile',
+ $indexedClasses
+ );
+ $this->assertArrayHasKey(
+ 'Rector\SwissKnife\Tests\Finder\MultiClassFixture\SecondClassInFile',
+ $indexedClasses
+ );
+ }
+}
diff --git a/tests/SmokeTestgen/FileSystem/TestsDirectoryResolverTest.php b/tests/SmokeTestgen/FileSystem/TestsDirectoryResolverTest.php
new file mode 100644
index 0000000000..8c54d0d0f6
--- /dev/null
+++ b/tests/SmokeTestgen/FileSystem/TestsDirectoryResolverTest.php
@@ -0,0 +1,43 @@
+tempDirectory = sys_get_temp_dir() . '/swiss-knife-tests-dir-' . uniqid();
+ FileSystem::createDir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testResolveWithUnitDirectory(): void
+ {
+ FileSystem::createDir($this->tempDirectory . '/tests/Unit');
+
+ $testsDirectoryResolver = new TestsDirectoryResolver();
+ $smokeDirectory = $testsDirectoryResolver->resolveSmokeUnitTestDirectory($this->tempDirectory);
+
+ $this->assertSame('tests/Unit/Smoke', $smokeDirectory);
+ }
+
+ public function testResolveWithFallback(): void
+ {
+ $testsDirectoryResolver = new TestsDirectoryResolver();
+ $smokeDirectory = $testsDirectoryResolver->resolveSmokeUnitTestDirectory($this->tempDirectory);
+
+ $this->assertSame('tests/Unit/Smoke', $smokeDirectory);
+ }
+}
diff --git a/tests/SmokeTestgen/Templating/TemplateDecoratorFallbackTest.php b/tests/SmokeTestgen/Templating/TemplateDecoratorFallbackTest.php
new file mode 100644
index 0000000000..3b2cdbff7d
--- /dev/null
+++ b/tests/SmokeTestgen/Templating/TemplateDecoratorFallbackTest.php
@@ -0,0 +1,41 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+
+ $this->tempDirectory = sys_get_temp_dir() . '/swiss-knife-template-fallback-' . uniqid();
+ FileSystem::createDir($this->tempDirectory);
+ chdir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testFallbackNamespaceWithoutComposerJson(): void
+ {
+ $templateDecorator = new TemplateDecorator();
+ $decorated = $templateDecorator->decorate('__KERNEL_CLASS_PLACEHOLDER__ __SMOKE_TEST_NAMESPACE__');
+
+ $this->assertStringContainsString('App\\Tests\\Unit\\Smoke', $decorated);
+ $this->assertStringContainsString('Kernel', $decorated);
+ }
+}
diff --git a/tests/SmokeTestgen/Templating/TemplateDecoratorTest.php b/tests/SmokeTestgen/Templating/TemplateDecoratorTest.php
new file mode 100644
index 0000000000..1fc4ddfe16
--- /dev/null
+++ b/tests/SmokeTestgen/Templating/TemplateDecoratorTest.php
@@ -0,0 +1,53 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+
+ $this->tempDirectory = sys_get_temp_dir() . '/swiss-knife-template-decorator-' . uniqid();
+ FileSystem::createDir($this->tempDirectory);
+ chdir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testDecorate(): void
+ {
+ FileSystem::write($this->tempDirectory . '/composer.json', Json::encode([
+ 'autoload-dev' => [
+ 'psr-4' => [
+ 'App\\Tests\\' => 'tests/',
+ ],
+ ],
+ ]), null);
+
+ $templateDecorator = new TemplateDecorator();
+ $decorated = $templateDecorator->decorate(
+ 'namespace __SMOKE_TEST_NAMESPACE__; class __KERNEL_CLASS_PLACEHOLDER__Test {}'
+ );
+
+ $this->assertStringContainsString('App\\Tests\\Unit\\Smoke', $decorated);
+ $this->assertStringNotContainsString('__SMOKE_TEST_NAMESPACE__', $decorated);
+ $this->assertStringNotContainsString('__KERNEL_CLASS_PLACEHOLDER__', $decorated);
+ }
+}
diff --git a/tests/SmokeTestgen/TestByPackageSubscriber/ServiceContainerTestByPackageSubscriberTest.php b/tests/SmokeTestgen/TestByPackageSubscriber/ServiceContainerTestByPackageSubscriberTest.php
new file mode 100644
index 0000000000..969b496cf0
--- /dev/null
+++ b/tests/SmokeTestgen/TestByPackageSubscriber/ServiceContainerTestByPackageSubscriberTest.php
@@ -0,0 +1,22 @@
+assertSame(
+ ['symfony/symfony', 'symfony/dependency-injection'],
+ $subscriber->getPackageNames()
+ );
+ $this->assertFileExists($subscriber->getTemplateFilePath());
+ }
+}
diff --git a/tests/Testing/Finder/TestCaseClassFinderTest.php b/tests/Testing/Finder/TestCaseClassFinderTest.php
new file mode 100644
index 0000000000..4cb4e1019f
--- /dev/null
+++ b/tests/Testing/Finder/TestCaseClassFinderTest.php
@@ -0,0 +1,43 @@
+assertTrue(true);
+ }
+}
+PHP, null);
+
+ $testCaseClassFinder = new TestCaseClassFinder();
+ $classes = $testCaseClassFinder->findInDirectories([$tempDirectory]);
+
+ $this->assertArrayHasKey('TempTests\SomeUnitTest', $classes);
+
+ FileSystem::delete($tempDirectory);
+ }
+}
diff --git a/tests/Testing/MockWire/Fixture/ClassWithUntypedParameter.php b/tests/Testing/MockWire/Fixture/ClassWithUntypedParameter.php
new file mode 100644
index 0000000000..ea9fdc06bb
--- /dev/null
+++ b/tests/Testing/MockWire/Fixture/ClassWithUntypedParameter.php
@@ -0,0 +1,12 @@
+expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Class "NonExistingClass" used in');
+
+ /** @phpstan-ignore argument.type (intentionally invalid class name) */
+ MockWire::create('NonExistingClass', [new \stdClass()]);
+ }
+
+ public function testNonObjectDependency(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('All constructor dependencies must be objects');
+
+ /** @phpstan-ignore argument.type (intentionally non-object dependency) */
+ MockWire::create(ClassWithConstructorDependencies::class, ['not-an-object']);
+ }
+
+ public function testEmptyDependencies(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('empty arguments');
+
+ MockWire::create(ClassWithConstructorDependencies::class, []);
+ }
+
+ public function testNoConstructor(): void
+ {
+ $object = MockWire::create(SecondDependency::class, [new \stdClass()]);
+
+ $this->assertInstanceOf(SecondDependency::class, $object);
+ }
+
+ public function testUntypedParameter(): void
+ {
+ require_once __DIR__ . '/Fixture/ClassWithUntypedParameter.php';
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Only typed parameters can be automocked');
+
+ MockWire::create(
+ \Rector\SwissKnife\Tests\Testing\MockWire\Fixture\ClassWithUntypedParameter::class,
+ [new \stdClass()]
+ );
+ }
+}
diff --git a/tests/Testing/PHPUnitMockerTest.php b/tests/Testing/PHPUnitMockerTest.php
new file mode 100644
index 0000000000..801cbdd6f7
--- /dev/null
+++ b/tests/Testing/PHPUnitMockerTest.php
@@ -0,0 +1,20 @@
+create(\stdClass::class);
+
+ $this->assertInstanceOf(MockObject::class, $mock);
+ }
+}
diff --git a/tests/Testing/Printer/PHPUnitXmlPrinterTest.php b/tests/Testing/Printer/PHPUnitXmlPrinterTest.php
new file mode 100644
index 0000000000..21af4839fb
--- /dev/null
+++ b/tests/Testing/Printer/PHPUnitXmlPrinterTest.php
@@ -0,0 +1,43 @@
+originalCwd = $originalCwd === false ? __DIR__ : $originalCwd;
+
+ $this->tempDirectory = sys_get_temp_dir() . '/swiss-knife-xml-printer-' . uniqid();
+ FileSystem::createDir($this->tempDirectory);
+ chdir($this->tempDirectory);
+ }
+
+ protected function tearDown(): void
+ {
+ chdir($this->originalCwd);
+ FileSystem::delete($this->tempDirectory);
+ }
+
+ public function testPrintFiles(): void
+ {
+ $filePath = $this->tempDirectory . '/SomeTest.php';
+ FileSystem::write($filePath, 'printFiles([$filePath]);
+
+ $this->assertSame('SomeTest.php' . PHP_EOL, $output);
+ }
+}
diff --git a/tests/Testing/UnitTestFilterExtendedTest.php b/tests/Testing/UnitTestFilterExtendedTest.php
new file mode 100644
index 0000000000..7287bf06b0
--- /dev/null
+++ b/tests/Testing/UnitTestFilterExtendedTest.php
@@ -0,0 +1,28 @@
+make(UnitTestFilter::class);
+
+ $testClassesToFilePaths = [
+ 'Symfony\Component\Form\Test\TypeTestCase' => '/some/path/TypeTestCase.php',
+ 'Rector\SwissKnife\Tests\AbstractTestCase' => __DIR__ . '/../AbstractTestCase.php',
+ ];
+
+ $filtered = $unitTestFilter->filter($testClassesToFilePaths);
+
+ $this->assertSame(
+ ['Rector\SwissKnife\Tests\AbstractTestCase' => __DIR__ . '/../AbstractTestCase.php'],
+ $filtered
+ );
+ }
+}
diff --git a/tests/Testing/UnitTestFilterTest.php b/tests/Testing/UnitTestFilterTest.php
new file mode 100644
index 0000000000..b1230fdb83
--- /dev/null
+++ b/tests/Testing/UnitTestFilterTest.php
@@ -0,0 +1,30 @@
+make(UnitTestFilter::class);
+
+ $testClassesToFilePaths = [
+ 'Rector\SwissKnife\Tests\AbstractTestCase' => __DIR__ . '/../AbstractTestCase.php',
+ 'Symfony\Bundle\FrameworkBundle\Test\KernelTestCase' => '/some/path/KernelTestCase.php',
+ 'NotATestClass' => '/some/path/NotATestClass.php',
+ ];
+
+ $filtered = $unitTestFilter->filter($testClassesToFilePaths);
+
+ $this->assertSame(
+ ['Rector\SwissKnife\Tests\AbstractTestCase' => __DIR__ . '/../AbstractTestCase.php'],
+ $filtered
+ );
+ }
+}
diff --git a/tests/ValueObject/ClassConstantFetch/AbstractClassConstantFetchTest.php b/tests/ValueObject/ClassConstantFetch/AbstractClassConstantFetchTest.php
new file mode 100644
index 0000000000..4b49088f36
--- /dev/null
+++ b/tests/ValueObject/ClassConstantFetch/AbstractClassConstantFetchTest.php
@@ -0,0 +1,51 @@
+assertSame('SomeClass', $fetch->getClassName());
+ $this->assertSame('FOO', $fetch->getConstantName());
+
+ $this->assertTrue($fetch->isClassConstantMatch(new ClassConstant('SomeClass', 'FOO')));
+ $this->assertFalse($fetch->isClassConstantMatch(new ClassConstant('OtherClass', 'FOO')));
+ $this->assertFalse($fetch->isClassConstantMatch(new ClassConstant('SomeClass', 'BAR')));
+ }
+
+ public function testExternalClassAccessConstantFetch(): void
+ {
+ $fetch = new ExternalClassAccessConstantFetch('ExternalClass', 'BAR');
+ $this->assertTrue($fetch->isClassConstantMatch(new ClassConstant('ExternalClass', 'BAR')));
+ $this->assertFalse($fetch->isClassConstantMatch(new ClassConstant('ExternalClass', 'BAZ')));
+ }
+
+ public function testParentClassConstantFetch(): void
+ {
+ $fetch = new ParentClassConstantFetch('ChildClass', 'PARENT_CONST');
+ $this->assertSame('ChildClass', $fetch->getClassName());
+ $this->assertSame('PARENT_CONST', $fetch->getConstantName());
+ $this->assertTrue($fetch->isClassConstantMatch(new ClassConstant('ChildClass', 'PARENT_CONST')));
+ $this->assertFalse($fetch->isClassConstantMatch(new ClassConstant('ParentClass', 'PARENT_CONST')));
+ }
+
+ public function testStaticClassConstantFetch(): void
+ {
+ $fetch = new StaticClassConstantFetch('SomeClass', 'STATIC_CONST');
+ $this->assertSame('SomeClass', $fetch->getClassName());
+ $this->assertSame('STATIC_CONST', $fetch->getConstantName());
+ $this->assertTrue($fetch->isClassConstantMatch(new ClassConstant('SomeClass', 'STATIC_CONST')));
+ $this->assertFalse($fetch->isClassConstantMatch(new ClassConstant('SomeClass', 'OTHER')));
+ }
+}
diff --git a/tests/ValueObject/ClassConstantTest.php b/tests/ValueObject/ClassConstantTest.php
new file mode 100644
index 0000000000..c3886af17b
--- /dev/null
+++ b/tests/ValueObject/ClassConstantTest.php
@@ -0,0 +1,32 @@
+assertSame('SomeClass', $classConstant->getClassName());
+ $this->assertSame('SOME_CONSTANT', $classConstant->getConstantName());
+ }
+
+ public function testEmptyClassName(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ new ClassConstant('', 'SOME_CONSTANT');
+ }
+
+ public function testEmptyConstantName(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ new ClassConstant('SomeClass', '');
+ }
+}
diff --git a/tests/ValueObject/Traits/TraitSpottingResultTest.php b/tests/ValueObject/Traits/TraitSpottingResultTest.php
new file mode 100644
index 0000000000..e8b9a48a0b
--- /dev/null
+++ b/tests/ValueObject/Traits/TraitSpottingResultTest.php
@@ -0,0 +1,40 @@
+markUsedIn(__DIR__ . '/../../Traits/Fixture/TraitUser.php');
+
+ $secondTraitMetadata = new TraitMetadata(__DIR__ . '/../../Traits/Fixture/AnotherTrait.php', 'AnotherTrait');
+
+ $traitSpottingResult = new TraitSpottingResult([$firstTraitMetadata, $secondTraitMetadata]);
+
+ $this->assertSame(2, $traitSpottingResult->getTraitCount());
+
+ $traitFilePaths = $traitSpottingResult->getTraitFilePaths();
+ $this->assertCount(2, $traitFilePaths);
+ $this->assertSame(
+ [
+ __DIR__ . '/../../Traits/Fixture/AnotherTrait.php',
+ __DIR__ . '/../../Traits/Fixture/SomeTrait.php',
+ ],
+ $traitFilePaths
+ );
+
+ $lazyTraits = $traitSpottingResult->getTraitMaximumUsedTimes(1);
+ $this->assertCount(1, $lazyTraits);
+ $this->assertSame('SomeTrait', $lazyTraits[0]->getShortTraitName());
+
+ $this->assertSame([], $traitSpottingResult->getTraitMaximumUsedTimes(0));
+ }
+}
diff --git a/tests/ValueObject/VisibilityChangeStatsTest.php b/tests/ValueObject/VisibilityChangeStatsTest.php
new file mode 100644
index 0000000000..95c79ef31b
--- /dev/null
+++ b/tests/ValueObject/VisibilityChangeStatsTest.php
@@ -0,0 +1,30 @@
+assertFalse($visibilityChangeStats->hasAnyChange());
+ $this->assertSame(0, $visibilityChangeStats->getPrivateCount());
+
+ $visibilityChangeStats->countPrivate();
+ $visibilityChangeStats->countPrivate();
+
+ $this->assertTrue($visibilityChangeStats->hasAnyChange());
+ $this->assertSame(2, $visibilityChangeStats->getPrivateCount());
+
+ $otherStats = new VisibilityChangeStats();
+ $otherStats->countPrivate();
+
+ $visibilityChangeStats->merge($otherStats);
+ $this->assertSame(3, $visibilityChangeStats->getPrivateCount());
+ }
+}