Merge pull request #881 from fabio-ivona/snaphsots-cleanup

[2.x] Multiple snapshots
This commit is contained in:
Nuno Maduro
2023-08-02 21:43:56 +00:00
committed by GitHub
19 changed files with 152 additions and 55 deletions

View File

@ -189,7 +189,29 @@ final class TestCaseFactory
private static \$__filename = '$filename';
private array \$__snapshotChanges = [];
$methodsCode
/** @postCondition */
protected function __MarkTestIncompleteIfSnapshotHaveChanged(): void
{
if (empty(\$this->__snapshotChanges)) {
return;
}
if (count(\$this->__snapshotChanges) === 1) {
\$this->markTestIncomplete(\$this->__snapshotChanges[0]);
return;
}
\$formattedMessages = implode(PHP_EOL, array_map(function (string \$message) {
return "- \$message";
}, \$this->__snapshotChanges));
\$this->markTestIncomplete(\$formattedMessages);
}
}
PHP;

View File

@ -805,6 +805,12 @@ final class Expectation
*/
public function toMatchSnapshot(string $message = ''): self
{
$snapshots = TestSuite::getInstance()->snapshots;
$snapshots->startNewExpectation();
$testCase = TestSuite::getInstance()->test;
assert($testCase instanceof TestCase);
$string = match (true) {
is_string($this->value) => $this->value,
is_object($this->value) && method_exists($this->value, '__toString') => $this->value->__toString(),
@ -817,22 +823,18 @@ final class Expectation
default => InvalidExpectationValue::expected('array|object|string'),
};
$testCase = TestSuite::getInstance()->test;
assert($testCase instanceof TestCase);
$snapshots = TestSuite::getInstance()->snapshots;
if ($snapshots->has($testCase, $string)) {
[$filename, $content] = $snapshots->get($testCase, $string);
if ($snapshots->has()) {
[$filename, $content] = $snapshots->get();
Assert::assertSame(
$content,
$string,
strtr($content, ["\r\n" => "\n", "\r" => "\n"]),
strtr($string, ["\r\n" => "\n", "\r" => "\n"]),
$message === '' ? "Failed asserting that the string value matches its snapshot ($filename)." : $message
);
} else {
$filename = $snapshots->save($testCase, $string);
$filename = $snapshots->save($string);
$testCase::markTestIncomplete('Snapshot created at ['.$filename.'].');
TestSuite::getInstance()->registerSnapshotChange("Snapshot created at [$filename]");
}
return $this;

View File

@ -5,14 +5,16 @@ declare(strict_types=1);
namespace Pest\Repositories;
use Pest\Exceptions\ShouldNotHappen;
use Pest\Support\Str;
use PHPUnit\Framework\TestCase;
use Pest\TestSuite;
/**
* @internal
*/
final class SnapshotRepository
{
/** @var array<string, int> */
private static array $expectationsCounter = [];
/**
* Creates a snapshot repository instance.
*/
@ -25,11 +27,9 @@ final class SnapshotRepository
/**
* Checks if the snapshot exists.
*/
public function has(TestCase $testCase, string $description): bool
public function has(): bool
{
[$filename, $description] = $this->getFilenameAndDescription($testCase);
return file_exists($this->getSnapshotFilename($filename, $description));
return file_exists($this->getSnapshotFilename());
}
/**
@ -39,11 +39,9 @@ final class SnapshotRepository
*
* @throws ShouldNotHappen
*/
public function get(TestCase $testCase, string $description): array
public function get(): array
{
[$filename, $description] = $this->getFilenameAndDescription($testCase);
$contents = file_get_contents($snapshotFilename = $this->getSnapshotFilename($filename, $description));
$contents = file_get_contents($snapshotFilename = $this->getSnapshotFilename());
if ($contents === false) {
throw ShouldNotHappen::fromMessage('Snapshot file could not be read.');
@ -57,11 +55,9 @@ final class SnapshotRepository
/**
* Saves the given snapshot for the given test case.
*/
public function save(TestCase $testCase, string $snapshot): string
public function save(string $snapshot): string
{
[$filename, $description] = $this->getFilenameAndDescription($testCase);
$snapshotFilename = $this->getSnapshotFilename($filename, $description);
$snapshotFilename = $this->getSnapshotFilename();
if (! file_exists(dirname($snapshotFilename))) {
mkdir(dirname($snapshotFilename), 0755, true);
@ -103,33 +99,43 @@ final class SnapshotRepository
}
}
/**
* Gets the snapshot's "filename" and "description".
*
* @return array{0: string, 1: string}
*/
private function getFilenameAndDescription(TestCase $testCase): array
{
$filename = (fn () => self::$__filename)->call($testCase, $testCase::class); // @phpstan-ignore-line
$description = str_replace('__pest_evaluable_', '', $testCase->name());
$datasetAsString = str_replace('__pest_evaluable_', '', Str::evaluable($testCase->dataSetAsStringWithData()));
$description = str_replace(' ', '_', $description.$datasetAsString);
return [$filename, $description];
}
/**
* Gets the snapshot's "filename".
*/
private function getSnapshotFilename(string $filename, string $description): string
private function getSnapshotFilename(): string
{
$relativePath = str_replace($this->testsPath, '', $filename);
$relativePath = str_replace($this->testsPath, '', TestSuite::getInstance()->getFilename());
// remove extension from filename
$relativePath = substr($relativePath, 0, (int) strrpos($relativePath, '.'));
$description = TestSuite::getInstance()->getDescription();
if ($this->getCurrentSnapshotCounter() > 1) {
$description .= '__'.$this->getCurrentSnapshotCounter();
}
return sprintf('%s/%s.snap', $this->testsPath.'/'.$this->snapshotsPath.$relativePath, $description);
}
private function getCurrentSnapshotKey(): string
{
return TestSuite::getInstance()->getFilename().'###'.TestSuite::getInstance()->getDescription();
}
private function getCurrentSnapshotCounter(): int
{
return self::$expectationsCounter[$this->getCurrentSnapshotKey()] ?? 0;
}
public function startNewExpectation(): void
{
$key = $this->getCurrentSnapshotKey();
if (! isset(self::$expectationsCounter[$key])) {
self::$expectationsCounter[$key] = 0;
}
self::$expectationsCounter[$key]++;
}
}

View File

@ -11,6 +11,7 @@ use Pest\Repositories\BeforeAllRepository;
use Pest\Repositories\BeforeEachRepository;
use Pest\Repositories\SnapshotRepository;
use Pest\Repositories\TestRepository;
use Pest\Support\Str;
use PHPUnit\Framework\TestCase;
/**
@ -105,4 +106,28 @@ final class TestSuite
return self::$instance;
}
public function getFilename(): string
{
assert($this->test instanceof TestCase);
return (fn () => self::$__filename)->call($this->test, $this->test::class); // @phpstan-ignore-line
}
public function getDescription(): string
{
assert($this->test instanceof TestCase);
$description = str_replace('__pest_evaluable_', '', $this->test->name());
$datasetAsString = str_replace('__pest_evaluable_', '', Str::evaluable($this->test->dataSetAsStringWithData()));
return str_replace(' ', '_', $description.$datasetAsString);
}
public function registerSnapshotChange(string $message): void
{
assert($this->test instanceof TestCase);
(fn (): string => $this->__snapshotChanges[] = $message)->call($this->test, $this->test::class); // @phpstan-ignore-line
}
}

View File

@ -0,0 +1,7 @@
<div class="container">
<div class="row">
<div class="col-md-12">
<h1>Snapshot</h1>
</div>
</div>
</div>

View File

@ -0,0 +1,3 @@
{
"key": " <div class=\"container\">\n <div class=\"row\">\n <div class=\"col-md-12\">\n <h1>Snapshot<\/h1>\n <\/div>\n <\/div>\n <\/div>"
}

View File

@ -0,0 +1,7 @@
<div class="container">
<div class="row">
<div class="col-md-12">
<h1>Snapshot</h1>
</div>
</div>
</div>

View File

@ -0,0 +1,3 @@
{
"key": " <div class=\"container\">\n <div class=\"row\">\n <div class=\"col-md-12\">\n <h1>Snapshot<\/h1>\n <\/div>\n <\/div>\n <\/div>"
}

View File

@ -16,13 +16,13 @@ beforeEach(function () {
});
test('pass', function () {
TestSuite::getInstance()->snapshots->save($this, $this->snapshotable);
TestSuite::getInstance()->snapshots->save($this->snapshotable);
expect($this->snapshotable)->toMatchSnapshot();
});
test('pass with `__toString`', function () {
TestSuite::getInstance()->snapshots->save($this, $this->snapshotable);
TestSuite::getInstance()->snapshots->save($this->snapshotable);
$object = new class($this->snapshotable)
{
@ -40,7 +40,7 @@ test('pass with `__toString`', function () {
});
test('pass with `toString`', function () {
TestSuite::getInstance()->snapshots->save($this, $this->snapshotable);
TestSuite::getInstance()->snapshots->save($this->snapshotable);
$object = new class($this->snapshotable)
{
@ -58,8 +58,8 @@ test('pass with `toString`', function () {
});
test('pass with dataset', function ($data) {
TestSuite::getInstance()->snapshots->save($this, $this->snapshotable);
[$filename] = TestSuite::getInstance()->snapshots->get($this, $this->snapshotable);
TestSuite::getInstance()->snapshots->save($this->snapshotable);
[$filename] = TestSuite::getInstance()->snapshots->get();
expect($filename)->toEndWith('pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap')
->and($this->snapshotable)->toMatchSnapshot();
@ -67,8 +67,8 @@ test('pass with dataset', function ($data) {
describe('within describe', function () {
test('pass with dataset', function ($data) {
TestSuite::getInstance()->snapshots->save($this, $this->snapshotable);
[$filename] = TestSuite::getInstance()->snapshots->get($this, $this->snapshotable);
TestSuite::getInstance()->snapshots->save($this->snapshotable);
[$filename] = TestSuite::getInstance()->snapshots->get();
expect($filename)->toEndWith('pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap')
->and($this->snapshotable)->toMatchSnapshot();
@ -76,7 +76,7 @@ describe('within describe', function () {
})->with(['my-datas-set-value']);
test('pass with `toArray`', function () {
TestSuite::getInstance()->snapshots->save($this, json_encode(['key' => $this->snapshotable], JSON_PRETTY_PRINT));
TestSuite::getInstance()->snapshots->save(json_encode(['key' => $this->snapshotable], JSON_PRETTY_PRINT));
$object = new class($this->snapshotable)
{
@ -96,7 +96,7 @@ test('pass with `toArray`', function () {
});
test('pass with array', function () {
TestSuite::getInstance()->snapshots->save($this, json_encode(['key' => $this->snapshotable], JSON_PRETTY_PRINT));
TestSuite::getInstance()->snapshots->save(json_encode(['key' => $this->snapshotable], JSON_PRETTY_PRINT));
expect([
'key' => $this->snapshotable,
@ -104,19 +104,31 @@ test('pass with array', function () {
});
test('failures', function () {
TestSuite::getInstance()->snapshots->save($this, $this->snapshotable);
TestSuite::getInstance()->snapshots->save($this->snapshotable);
expect('contain that does not match snapshot')->toMatchSnapshot();
})->throws(ExpectationFailedException::class, 'Failed asserting that two strings are identical.');
test('failures with custom message', function () {
TestSuite::getInstance()->snapshots->save($this, $this->snapshotable);
TestSuite::getInstance()->snapshots->save($this->snapshotable);
expect('contain that does not match snapshot')->toMatchSnapshot('oh no');
})->throws(ExpectationFailedException::class, 'oh no');
test('not failures', function () {
TestSuite::getInstance()->snapshots->save($this, $this->snapshotable);
TestSuite::getInstance()->snapshots->save($this->snapshotable);
expect($this->snapshotable)->not->toMatchSnapshot();
})->throws(ExpectationFailedException::class);
test('multiple snapshot expectations', function () {
expect('foo bar 1')->toMatchSnapshot();
expect('foo bar 2')->toMatchSnapshot();
});
test('multiple snapshot expectations with datasets', function () {
expect('foo bar 1')->toMatchSnapshot();
expect('foo bar 2')->toMatchSnapshot();
})->with([1, 'foo', 'bar', 'baz']);