From 945d47640918b276fb67771da5fd92f2e0624634 Mon Sep 17 00:00:00 2001 From: nuno maduro Date: Wed, 15 Apr 2026 08:34:06 -0700 Subject: [PATCH] fix: allow to update individual screenshots --- src/Mixins/Expectation.php | 32 ++++++++---- src/Plugins/Snapshot.php | 99 +++++++++++++++++++++++++++++++++++++- 2 files changed, 120 insertions(+), 11 deletions(-) diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index f235c748..014e69fc 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -14,6 +14,7 @@ use InvalidArgumentException; use JsonSerializable; use Pest\Exceptions\InvalidExpectationValue; use Pest\Matchers\Any; +use Pest\Plugins\Snapshot; use Pest\Support\Arr; use Pest\Support\Exporter; use Pest\Support\NullClosure; @@ -851,18 +852,31 @@ final class Expectation default => InvalidExpectationValue::expected('array|object|string'), }; - if ($snapshots->has()) { - [$filename, $content] = $snapshots->get(); - - Assert::assertSame( - 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 { + if (! $snapshots->has()) { $filename = $snapshots->save($string); TestSuite::getInstance()->registerSnapshotChange("Snapshot created at [$filename]"); + } else { + [$filename, $content] = $snapshots->get(); + + $normalizedContent = strtr($content, ["\r\n" => "\n", "\r" => "\n"]); + $normalizedString = strtr($string, ["\r\n" => "\n", "\r" => "\n"]); + + if (Snapshot::$updateSnapshots && $normalizedContent !== $normalizedString) { + $snapshots->save($string); + + TestSuite::getInstance()->registerSnapshotChange("Snapshot updated at [$filename]"); + } else { + if (Snapshot::$updateSnapshots) { + TestSuite::getInstance()->registerSnapshotChange("Snapshot unchanged at [$filename]"); + } + + Assert::assertSame( + $normalizedContent, + $normalizedString, + $message === '' ? "Failed asserting that the string value matches its snapshot ($filename)." : $message + ); + } } return $this; diff --git a/src/Plugins/Snapshot.php b/src/Plugins/Snapshot.php index 5022caa7..e0ac0505 100644 --- a/src/Plugins/Snapshot.php +++ b/src/Plugins/Snapshot.php @@ -14,12 +14,19 @@ final class Snapshot implements HandlesArguments { use Concerns\HandleArguments; + /** + * Whether snapshots should be updated on this run. + */ + public static bool $updateSnapshots = false; + /** * {@inheritDoc} */ public function handleArguments(array $arguments): array { - if (Parallel::isWorker()) { + if (Parallel::isWorker() && Parallel::getGlobal('UPDATE_SNAPSHOTS') === true) { + self::$updateSnapshots = true; + return $arguments; } @@ -27,8 +34,96 @@ final class Snapshot implements HandlesArguments return $arguments; } - TestSuite::getInstance()->snapshots->flush(); + self::$updateSnapshots = true; + + if ($this->isFullRun($arguments)) { + TestSuite::getInstance()->snapshots->flush(); + } + + if ($this->hasArgument('--parallel', $arguments) || $this->hasArgument('-p', $arguments)) { + Parallel::setGlobal('UPDATE_SNAPSHOTS', true); + } return $this->popArgument('--update-snapshots', $arguments); } + + /** + * Options that take a value as the next argument (rather than via "=value"). + * + * @var list + */ + private const array FLAGS_WITH_VALUES = [ + '--filter', + '--group', + '--exclude-group', + '--test-suffix', + '--covers', + '--uses', + '--cache-directory', + '--cache-result-file', + '--configuration', + '--colors', + '--test-directory', + '--bootstrap', + '--order-by', + '--random-order-seed', + '--log-junit', + '--log-teamcity', + '--log-events-text', + '--log-events-verbose-text', + '--coverage-clover', + '--coverage-cobertura', + '--coverage-crap4j', + '--coverage-html', + '--coverage-php', + '--coverage-text', + '--coverage-xml', + '--assignee', + '--issue', + '--ticket', + '--pr', + '--pull-request', + '--retry', + '--shard', + '--repeat', + ]; + + /** + * Determines whether the command targets the entire suite (no filter, no path). + * + * @param array $arguments + */ + private function isFullRun(array $arguments): bool + { + if ($this->hasArgument('--filter', $arguments)) { + return false; + } + + $tokens = array_slice($arguments, 1); + $skipNext = false; + + foreach ($tokens as $arg) { + if ($skipNext) { + $skipNext = false; + + continue; + } + + if ($arg === '') { + continue; + } + + if ($arg[0] === '-') { + if (in_array($arg, self::FLAGS_WITH_VALUES, true)) { + $skipNext = true; + } + + continue; + } + + return false; + } + + return true; + } }