Skip to content

Commit

Permalink
Added test for Rectors making additional changes (#545)
Browse files Browse the repository at this point in the history
* Added test for Rectors making additional changes

* Fix empty expected content

* fix - added missing parameter

* Moved multiple files change to trait and added docs

* Rewritten test for Update file name by class name

* Separate test using multiple files changed trait

* Separated test

* Created separated test with separated rector

* Fix phpstan

* Changed namespace for dummy rector
  • Loading branch information
lulco committed Aug 6, 2021
1 parent 7398a66 commit c2acf79
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 0 deletions.
136 changes: 136 additions & 0 deletions docs/testing/multiple_files_changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Multiple files changed

Sometimes Rector does changes in multiple files. How to test it?

## Rector
Add file with content in Rector:

```php
$addedFileWithContent = new \Rector\FileSystemRector\ValueObject\AddedFileWithContent($filePath, $content);
$this->removedAndAddedFilesCollector->addAddedFile($addedFileWithContent);
```

## RectorTest
In RectorTest just use
```php
$this->doTestFileInfoWithAdditionalChanges($fileInfo);
```
instead of

```php
$this->doTestFileInfo($fileInfo);
```

## Test fixtures
Fixture contains more parts separated by `-----` like in classic tests:
```
1) original content
-----
2) expected content (keep empty if no change should happen)
-----
3) path to another changed file (relative to file processed)
-----
4) original content of another file (keep empty if file doesn't exist yet)
-----
5) expected content of another file
```

Parts 3-5 can be multiplied in case there are more files created / updated.

### Fixture examples
Example #1: Rector is not changing processed PHP file, but changes corresponding template file (adds {varType} for each variable).
```
<?php
namespace Rector\Nette\Tests\Rector\Class_\LatteVarTypesBasedOnPresenterTemplateParametersRector\Fixture;
use Nette\Application\UI\Presenter;
class SomePresenter extends Presenter
{
public function renderDefault(): void
{
$this->template->title = 'My title';
$this->template->count = 123;
}
}
?>
-----
-----
templates/Some/default.latte
-----
<h1>{$title}</h1>
<span class="count">{$count}</span>
-----
{varType string $title}
{varType int $count}
<h1>{$title}</h1>
<span class="count">{$count}</span>
```

Example #2: Rector creates Template class, uses it in processed PHP file, and also in corresponding template file (adds {templateType}).
```
<?php
namespace Rector\Nette\Tests\Rector\Class_\LatteVarTypesBasedOnPresenterTemplateParametersRector\Fixture;
use Nette\Application\UI\Presenter;
class SomePresenter extends Presenter
{
public function renderDefault(): void
{
$this->template->title = 'My title';
$this->template->count = 123;
}
}
?>
-----
<?php
namespace Rector\Nette\Tests\Rector\Class_\LatteVarTypesBasedOnPresenterTemplateParametersRector\Fixture;
use Nette\Application\UI\Presenter;
/**
* @property-read SomeTemplate $template
*/
class SomePresenter extends Presenter
{
public function renderDefault(): void
{
$this->template->title = 'My title';
$this->template->count = 123;
}
}
?>
-----
SomeTemplate.php
-----
-----
<?php
namespace Rector\Nette\Tests\Rector\Class_\LatteVarTypesBasedOnPresenterTemplateParametersRector\Fixture;
use Nette\Bridges\ApplicationLatte\Template;
class SomeTemplate extends Template
{
public string $title;
public int $count;
}
-----
templates/Some/default.latte
-----
<h1>{$title}</h1>
<span class="count">{$count}</span>
-----
{templateType \Rector\Nette\Tests\Rector\Class_\LatteVarTypesBasedOnPresenterTemplateParametersRector\Fixture\SomeTemplate}
<h1>{$title}</h1>
<span class="count">{$count}</span>
```
3 changes: 3 additions & 0 deletions packages/Testing/PHPUnit/AbstractRectorTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider;
use Rector\Testing\Contract\RectorTestInterface;
use Rector\Testing\PHPUnit\Behavior\MovingFilesTrait;
use Rector\Testing\PHPUnit\Behavior\MultipleFilesChangedTrait;
use Symplify\EasyTesting\DataProvider\StaticFixtureFinder;
use Symplify\EasyTesting\DataProvider\StaticFixtureUpdater;
use Symplify\EasyTesting\StaticFixtureSplitter;
Expand All @@ -29,6 +30,8 @@ abstract class AbstractRectorTestCase extends AbstractTestCase implements Rector
{
use MovingFilesTrait;

use MultipleFilesChangedTrait;

protected ParameterProvider $parameterProvider;

protected RemovedAndAddedFilesCollector $removedAndAddedFilesCollector;
Expand Down
99 changes: 99 additions & 0 deletions packages/Testing/PHPUnit/Behavior/MultipleFilesChangedTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

namespace Rector\Testing\PHPUnit\Behavior;

use Rector\Core\Exception\ShouldNotHappenException;
use Symplify\SmartFileSystem\SmartFileInfo;

trait MultipleFilesChangedTrait
{
protected function doTestFileInfoWithAdditionalChanges(
SmartFileInfo $fixtureFileInfo,
bool $allowMatches = true
): void {
$separator = '-----';
[$originalContent, $expectedContent, $additionalInfo] = explode(
$separator,
$fixtureFileInfo->getContents(),
3
);

$additionalChanges = explode($separator, $additionalInfo);
/** @var array<array{0: ?string, 1: ?string, 2: ?string}> $additionalFileChanges */
$additionalFileChanges = array_chunk($additionalChanges, 3);
$expectedFileChanges = $this->prepareAdditionalChangedFiles($additionalFileChanges);

$fixturePath = $this->getFixtureTempDirectory() . '/' . $fixtureFileInfo->getFilename();
$this->createFixtureDir($fixturePath);
$fixtureContent = $originalContent;
if (trim($expectedContent)) {
$fixtureContent .= $separator . $expectedContent;
}
file_put_contents($fixturePath, $fixtureContent);
$newFileInfo = new SmartFileInfo($fixturePath);
$this->doTestFileInfo($newFileInfo, $allowMatches);

$this->checkAdditionalChanges($expectedFileChanges);

if (file_exists($fixturePath)) {
unlink($fixturePath);
}
}

/**
* @param array<array{0: ?string, 1: ?string, 2: ?string}> $additionalFileChanges
* @return array<string, string>
*/
private function prepareAdditionalChangedFiles(array $additionalFileChanges): array
{
$expectedFileChanges = [];
foreach ($additionalFileChanges as $additionalFileChange) {
$path = isset($additionalFileChange[0]) ? trim($additionalFileChange[0]) : null;
if ($path === null) {
throw new ShouldNotHappenException('Path for additional change must be set');
}
$fullPath = $this->getFixtureTempDirectory() . '/' . $path;

$input = isset($additionalFileChange[1]) ? trim($additionalFileChange[1]) : null;
if ($input) {
$this->createFixtureDir($fullPath);
file_put_contents($fullPath, $input);
}

$expectedFileChanges[$fullPath] = isset($additionalFileChange[2]) ? trim($additionalFileChange[2]) : '';
}
return $expectedFileChanges;
}

/**
* @param array<string, string> $expectedFileChanges
*/
private function checkAdditionalChanges(array $expectedFileChanges): void
{
$addedFilesWithContent = $this->removedAndAddedFilesCollector->getAddedFilesWithContent();
$addedFiles = [];
foreach ($addedFilesWithContent as $addedFileWithContent) {
$addedFiles[$addedFileWithContent->getFilePath()] = $addedFileWithContent;
}

foreach ($expectedFileChanges as $path => $expectedFileChange) {
$addedFile = $addedFiles[$path] ?? null;
$this->assertSame($path, $addedFile ? $addedFile->getFilePath() : null);
$realFileContent = $addedFile ? trim($addedFile->getFileContent()) : null;
$this->assertSame($expectedFileChange, $realFileContent);
if (file_exists($path)) {
unlink($path);
}
}
}

private function createFixtureDir(string $fileName): void
{
$dirName = dirname($fileName);
if (! file_exists($dirName)) {
mkdir($dirName, 0777, true);
}
}
}
19 changes: 19 additions & 0 deletions tests/PhpUnit/MultipleFilesChangedTrait/Fixture/fixture.php.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Rector\Core\Tests\PhpUnit\MultipleFilesChangedTrait\Fixture;

final class MyClassName
{

}
-----
-----
names.json
-----
-----
{
"short": "MyClassName",
"fqn": "Rector\\Core\\Tests\\PhpUnit\\MultipleFilesChangedTrait\\Fixture\\MyClassName"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Rector\Core\Tests\PhpUnit\MultipleFilesChangedTrait;

use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

final class MultipleFilesChangedTraitTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $smartFileInfo): void
{
$this->doTestFileInfoWithAdditionalChanges($smartFileInfo);
}

/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Rector\Core\Tests\PhpUnit\MultipleFilesChangedTrait\Rector\Class_;

use Nette\Utils\Json;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use Rector\Core\Rector\AbstractRector;
use Rector\FileSystemRector\ValueObject\AddedFileWithContent;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\Core\Tests\PhpUnit\MultipleFilesChangedTrait\MultipleFilesChangedTraitTest
*/
final class CreateJsonWithNamesForClassRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Creates json with names for class', []);
}

public function getNodeTypes(): array
{
return [Class_::class];
}

/**
* @param Class_ $node
*/
public function refactor(Node $node)
{
$smartFileInfo = $this->file->getSmartFileInfo();
$targetFilePath = $smartFileInfo->getRealPathDirectory() . '/names.json';

$content = Json::encode([
'short' => $this->nodeNameResolver->getShortName($node),
'fqn' => $this->getName($node),
], Json::PRETTY);

$addedFileWithContent = new AddedFileWithContent($targetFilePath, $content);
$this->removedAndAddedFilesCollector->addAddedFile($addedFileWithContent);
return null;
}
}
11 changes: 11 additions & 0 deletions tests/PhpUnit/MultipleFilesChangedTrait/config/configured_rule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

use Rector\Core\Tests\PhpUnit\MultipleFilesChangedTrait\Rector\Class_\CreateJsonWithNamesForClassRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(CreateJsonWithNamesForClassRector::class);
};

0 comments on commit c2acf79

Please sign in to comment.