diff --git a/src/Exceptions/AfterAllAlreadyExist.php b/src/Exceptions/AfterAllAlreadyExist.php index 297a97a3..30e03f31 100644 --- a/src/Exceptions/AfterAllAlreadyExist.php +++ b/src/Exceptions/AfterAllAlreadyExist.php @@ -16,6 +16,6 @@ final class AfterAllAlreadyExist extends InvalidArgumentException implements Exc { public function __construct(string $filename) { - parent::__construct(sprintf('The afterAll already exists in the filename [%s].', $filename)); + parent::__construct(sprintf('The [afterAll] hook is already defined in [%s]. Each test file may only define it once.', $filename)); } } diff --git a/src/Exceptions/AfterAllWithinDescribe.php b/src/Exceptions/AfterAllWithinDescribe.php index 8addbbc4..2d66c3ea 100644 --- a/src/Exceptions/AfterAllWithinDescribe.php +++ b/src/Exceptions/AfterAllWithinDescribe.php @@ -16,6 +16,6 @@ final class AfterAllWithinDescribe extends InvalidArgumentException implements E { public function __construct(string $filename) { - parent::__construct(sprintf('The afterAll method can not be used within describe functions. Filename [%s].', $filename)); + parent::__construct(sprintf('The [afterAll] hook may not be used inside a [describe] block. Please move it to the top level of [%s].', $filename)); } } diff --git a/src/Exceptions/AfterBeforeTestFunction.php b/src/Exceptions/AfterBeforeTestFunction.php index e61c2a81..fdd016db 100644 --- a/src/Exceptions/AfterBeforeTestFunction.php +++ b/src/Exceptions/AfterBeforeTestFunction.php @@ -16,6 +16,6 @@ final class AfterBeforeTestFunction extends InvalidArgumentException implements { public function __construct(string $filename) { - parent::__construct('After method cannot be used with before the [test|it] functions in the filename ['.$filename.'].'); + parent::__construct(sprintf('The [after] hook may only be chained onto [beforeEach] inside a [describe] block. Please move it inside one in [%s].', $filename)); } } diff --git a/src/Exceptions/BeforeAllAlreadyExist.php b/src/Exceptions/BeforeAllAlreadyExist.php index 1239f64b..0b01341c 100644 --- a/src/Exceptions/BeforeAllAlreadyExist.php +++ b/src/Exceptions/BeforeAllAlreadyExist.php @@ -16,6 +16,6 @@ final class BeforeAllAlreadyExist extends InvalidArgumentException implements Ex { public function __construct(string $filename) { - parent::__construct(sprintf('The beforeAll already exists in the filename [%s].', $filename)); + parent::__construct(sprintf('The [beforeAll] hook is already defined in [%s]. Each test file may only define it once.', $filename)); } } diff --git a/src/Exceptions/BeforeAllWithinDescribe.php b/src/Exceptions/BeforeAllWithinDescribe.php index e5574963..e12c025e 100644 --- a/src/Exceptions/BeforeAllWithinDescribe.php +++ b/src/Exceptions/BeforeAllWithinDescribe.php @@ -16,6 +16,6 @@ final class BeforeAllWithinDescribe extends InvalidArgumentException implements { public function __construct(string $filename) { - parent::__construct(sprintf('The beforeAll method can not be used within describe functions. Filename [%s].', $filename)); + parent::__construct(sprintf('The [beforeAll] hook may not be used inside a [describe] block. Please move it to the top level of [%s].', $filename)); } } diff --git a/src/Exceptions/DatasetAlreadyExists.php b/src/Exceptions/DatasetAlreadyExists.php index fba9199b..9575ed06 100644 --- a/src/Exceptions/DatasetAlreadyExists.php +++ b/src/Exceptions/DatasetAlreadyExists.php @@ -16,6 +16,6 @@ final class DatasetAlreadyExists extends InvalidArgumentException implements Exc { public function __construct(string $name, string $scope) { - parent::__construct(sprintf('A dataset with the name [%s] already exists in scope [%s].', $name, $scope)); + parent::__construct(sprintf('A dataset named [%s] is already registered in [%s]. Please choose a different name.', $name, $scope)); } } diff --git a/src/Exceptions/DatasetArgumentsMismatch.php b/src/Exceptions/DatasetArgumentsMismatch.php index 2a341868..5a4f1d9d 100644 --- a/src/Exceptions/DatasetArgumentsMismatch.php +++ b/src/Exceptions/DatasetArgumentsMismatch.php @@ -11,9 +11,9 @@ final class DatasetArgumentsMismatch extends Exception public function __construct(int $requiredCount, int $suppliedCount) { if ($requiredCount <= $suppliedCount) { - parent::__construct('Test argument names and dataset keys do not match'); + parent::__construct('The test arguments do not match the dataset keys. Please make sure each argument is named after a key in the dataset.'); } else { - parent::__construct(sprintf('Test expects %d arguments but dataset only provides %d', $requiredCount, $suppliedCount)); + parent::__construct(sprintf('The test expects [%d] argument(s), but the dataset only provides [%d].', $requiredCount, $suppliedCount)); } } } diff --git a/src/Exceptions/DatasetDoesNotExist.php b/src/Exceptions/DatasetDoesNotExist.php index 2ea344d6..ca09ab7a 100644 --- a/src/Exceptions/DatasetDoesNotExist.php +++ b/src/Exceptions/DatasetDoesNotExist.php @@ -16,6 +16,6 @@ final class DatasetDoesNotExist extends InvalidArgumentException implements Exce { public function __construct(string $name) { - parent::__construct(sprintf("A dataset with the name [%s] does not exist. You can create it using `dataset('%s', ['a', 'b']);`.", $name, $name)); + parent::__construct(sprintf("A dataset named [%s] does not exist. You may create one using `dataset('%s', ['a', 'b']);`.", $name, $name)); } } diff --git a/src/Exceptions/DatasetMissing.php b/src/Exceptions/DatasetMissing.php index 4dfa773a..66702079 100644 --- a/src/Exceptions/DatasetMissing.php +++ b/src/Exceptions/DatasetMissing.php @@ -20,11 +20,11 @@ final class DatasetMissing extends BadFunctionCallException implements Exception public function __construct(string $file, string $name, array $arguments) { parent::__construct(sprintf( - 'A test with the description [%s] has [%d] argument(s) ([%s]) and no dataset(s) provided in [%s]', + 'The test [%s] in [%s] expects [%d] argument(s) ([%s]), but no dataset was provided. Please chain [with()] onto the test to supply one.', $name, + $file, count($arguments), implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($arguments), $arguments)), - $file, )); } } diff --git a/src/Exceptions/ExpectationNotFound.php b/src/Exceptions/ExpectationNotFound.php index 2658973f..81e5db96 100644 --- a/src/Exceptions/ExpectationNotFound.php +++ b/src/Exceptions/ExpectationNotFound.php @@ -13,6 +13,6 @@ final class ExpectationNotFound extends Exception { public static function fromName(string $name): ExpectationNotFound { - return new self("Expectation [$name] does not exist."); + return new self("The expectation [$name] does not exist. You may register it using [expect()->extend()]."); } } diff --git a/src/Exceptions/FileOrFolderNotFound.php b/src/Exceptions/FileOrFolderNotFound.php index 6cc5c226..24c9f806 100644 --- a/src/Exceptions/FileOrFolderNotFound.php +++ b/src/Exceptions/FileOrFolderNotFound.php @@ -16,6 +16,6 @@ final class FileOrFolderNotFound extends InvalidArgumentException implements Exc { public function __construct(string $filename) { - parent::__construct(sprintf('The file or folder with the name [%s] could not be found.', $filename)); + parent::__construct(sprintf('The file or folder [%s] could not be found. Please check the path and try again.', $filename)); } } diff --git a/src/Exceptions/InvalidExpectation.php b/src/Exceptions/InvalidExpectation.php index 544baac3..5ce9cdeb 100644 --- a/src/Exceptions/InvalidExpectation.php +++ b/src/Exceptions/InvalidExpectation.php @@ -21,6 +21,6 @@ final class InvalidExpectation extends LogicException implements ExceptionInterf */ public static function fromMethods(array $methods): never { - throw new self(sprintf('Expectation [%s] is not valid.', implode('->', $methods))); + throw new self(sprintf('The expectation [%s] does not exist. Please check the spelling, or register it using [expect()->extend()].', implode('->', $methods))); } } diff --git a/src/Exceptions/InvalidExpectationValue.php b/src/Exceptions/InvalidExpectationValue.php index 7a223f8c..dc594bae 100644 --- a/src/Exceptions/InvalidExpectationValue.php +++ b/src/Exceptions/InvalidExpectationValue.php @@ -16,6 +16,6 @@ final class InvalidExpectationValue extends InvalidArgumentException */ public static function expected(string $type): never { - throw new self(sprintf('Invalid expectation value type. Expected [%s].', $type)); + throw new self(sprintf('This expectation may only be used on a value of type [%s].', $type)); } } diff --git a/src/Exceptions/InvalidPestCommand.php b/src/Exceptions/InvalidPestCommand.php index 42e766f6..6390d330 100644 --- a/src/Exceptions/InvalidPestCommand.php +++ b/src/Exceptions/InvalidPestCommand.php @@ -16,6 +16,6 @@ final class InvalidPestCommand extends InvalidArgumentException implements Excep { public function __construct() { - parent::__construct('Please run [./vendor/bin/pest] instead.'); + parent::__construct('Pest must be run through its own binary. Please run [./vendor/bin/pest] instead.'); } } diff --git a/src/Exceptions/InvalidTestClassName.php b/src/Exceptions/InvalidTestClassName.php index 2c4d9dc1..f7a35259 100644 --- a/src/Exceptions/InvalidTestClassName.php +++ b/src/Exceptions/InvalidTestClassName.php @@ -17,7 +17,7 @@ final class InvalidTestClassName extends InvalidArgumentException implements Exc public static function fromClassName(string $filename, string $className): self { return new self(sprintf( - 'The test file [%s] would create the class [%s], which is not a valid PHP class name. Please rename the test file.', + 'The test file [%s] would create the class [%s], which is not a valid PHP class name. Please rename the file.', $filename, $className, )); @@ -26,7 +26,7 @@ final class InvalidTestClassName extends InvalidArgumentException implements Exc public static function fromNamespace(string $filename, string $namespace, string $part): self { return new self(sprintf( - 'The test file [%s] would create the namespace [%s], which is not a valid PHP namespace, as [%s] may not be used as a namespace name. Please rename the folder in question.', + 'The test file [%s] would create the namespace [%s], which is not a valid PHP namespace, because [%s] may not be used as a namespace name. Please rename that folder.', $filename, $namespace, $part, diff --git a/src/Exceptions/MissingDependency.php b/src/Exceptions/MissingDependency.php index 5e059afe..3a79c7d8 100644 --- a/src/Exceptions/MissingDependency.php +++ b/src/Exceptions/MissingDependency.php @@ -16,6 +16,6 @@ final class MissingDependency extends InvalidArgumentException implements Except { public function __construct(string $feature, string $dependency) { - parent::__construct(sprintf('The feature [%s] requires [%s].', $feature, $dependency)); + parent::__construct(sprintf('The [%s] feature requires [%s]. Please install it and try again.', $feature, $dependency)); } } diff --git a/src/Exceptions/ShouldNotHappen.php b/src/Exceptions/ShouldNotHappen.php index 772463c9..7634d6f2 100644 --- a/src/Exceptions/ShouldNotHappen.php +++ b/src/Exceptions/ShouldNotHappen.php @@ -17,7 +17,7 @@ final class ShouldNotHappen extends RuntimeException $message = $exception->getMessage(); parent::__construct(sprintf(<<<'EOF' -This should not happen - please create an new issue here: https://github.com/pestphp/pest/issues +This should not have happened. Please report it here: https://github.com/pestphp/pest/issues Issue: %s PHP version: %s diff --git a/src/Exceptions/TestAlreadyExist.php b/src/Exceptions/TestAlreadyExist.php index a996a652..62e136d6 100644 --- a/src/Exceptions/TestAlreadyExist.php +++ b/src/Exceptions/TestAlreadyExist.php @@ -16,6 +16,6 @@ final class TestAlreadyExist extends InvalidArgumentException implements Excepti { public function __construct(string $fileName, string $description) { - parent::__construct(sprintf('A test with the description [%s] already exists in the filename [%s].', $description, $fileName)); + parent::__construct(sprintf('A test named [%s] already exists in [%s]. Please give this test a different description.', $description, $fileName)); } } diff --git a/src/Exceptions/TestCaseAlreadyInUse.php b/src/Exceptions/TestCaseAlreadyInUse.php index 05f10a21..d095e35b 100644 --- a/src/Exceptions/TestCaseAlreadyInUse.php +++ b/src/Exceptions/TestCaseAlreadyInUse.php @@ -17,7 +17,7 @@ final class TestCaseAlreadyInUse extends InvalidArgumentException implements Exc public function __construct(string $inUse, string $newOne, string $folder) { parent::__construct(sprintf( - 'Test case [%s] can not be used. The folder [%s] already uses the test case [%s].', + 'The test case [%s] may not be used here. The folder [%s] is already bound to the test case [%s].', $newOne, $folder, $inUse, diff --git a/src/Exceptions/TestCaseClassOrTraitNotFound.php b/src/Exceptions/TestCaseClassOrTraitNotFound.php index 431476aa..521e2dcb 100644 --- a/src/Exceptions/TestCaseClassOrTraitNotFound.php +++ b/src/Exceptions/TestCaseClassOrTraitNotFound.php @@ -16,6 +16,6 @@ final class TestCaseClassOrTraitNotFound extends InvalidArgumentException implem { public function __construct(string $testCaseClass) { - parent::__construct(sprintf('The class [%s] was not found.', $testCaseClass)); + parent::__construct(sprintf('The class or trait [%s] could not be found. Please check the name, and make sure it is autoloadable.', $testCaseClass)); } } diff --git a/src/Exceptions/TestClosureMustNotBeStatic.php b/src/Exceptions/TestClosureMustNotBeStatic.php index dda25e79..20fff78b 100644 --- a/src/Exceptions/TestClosureMustNotBeStatic.php +++ b/src/Exceptions/TestClosureMustNotBeStatic.php @@ -19,7 +19,7 @@ final class TestClosureMustNotBeStatic extends InvalidArgumentException implemen { parent::__construct( sprintf( - 'Test closure must not be static. Please remove the [static] keyword from the [%s] method in [%s].', + 'Test closures may not be static. Please remove the [static] keyword from the test [%s] in [%s].', $method->description, $method->filename ) diff --git a/src/Exceptions/TestDescriptionMissing.php b/src/Exceptions/TestDescriptionMissing.php index 591020f7..2d7af707 100644 --- a/src/Exceptions/TestDescriptionMissing.php +++ b/src/Exceptions/TestDescriptionMissing.php @@ -16,6 +16,6 @@ final class TestDescriptionMissing extends InvalidArgumentException implements E { public function __construct(string $fileName) { - parent::__construct(sprintf('Test description is missing in the filename [%s].', $fileName)); + parent::__construct(sprintf('A test in [%s] is missing its description. Please give every test a description.', $fileName)); } } diff --git a/src/Exceptions/TiaRequiresPestTests.php b/src/Exceptions/TiaRequiresPestTests.php index 71d7615a..39595d52 100644 --- a/src/Exceptions/TiaRequiresPestTests.php +++ b/src/Exceptions/TiaRequiresPestTests.php @@ -19,7 +19,7 @@ final class TiaRequiresPestTests extends RuntimeException implements ExceptionIn public function __construct(private readonly string $className, string $filename) { parent::__construct(sprintf( - 'Tia mode requires only functional based Pest tests, but encountered PHPUnit class [%s] in [%s].', + 'Tia mode supports functional Pest tests only, but found the PHPUnit class [%s] in [%s]. Please convert it to a Pest test, or run without Tia.', $className, $filename, )); diff --git a/src/Exceptions/TiaRequiresRepositoryRoot.php b/src/Exceptions/TiaRequiresRepositoryRoot.php index 5a122197..8ded003f 100644 --- a/src/Exceptions/TiaRequiresRepositoryRoot.php +++ b/src/Exceptions/TiaRequiresRepositoryRoot.php @@ -19,7 +19,7 @@ final class TiaRequiresRepositoryRoot extends RuntimeException implements Except public function __construct(private readonly string $subdirectoryPrefix) { parent::__construct(sprintf( - 'Tia mode requires the project root to be the git repository root, but it sits in the subdirectory [%s] of a larger repo.', + 'Tia mode requires the project root to be the git repository root, but this project sits in the subdirectory [%s] of a larger repository. Please give it its own repository to use Tia.', $this->subdirectoryPrefix, )); } diff --git a/src/Expectation.php b/src/Expectation.php index 79e9aba0..cee50b5a 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -318,7 +318,7 @@ final class Expectation if (! is_object($this->value)) { throw new BadMethodCallException(sprintf( - 'Method "%s" does not exist in %s.', + 'Method [%s] does not exist in [%s].', $method, gettype($this->value) )); @@ -534,7 +534,7 @@ final class Expectation return Targeted::make( $this, fn (ObjectDescription $object): bool => count(array_filter($methods, fn (string $method): bool => isset($object->reflectionClass) && $object->reflectionClass->hasMethod($method))) === count($methods), - sprintf("to have method '%s'", implode("', '", $methods)), + sprintf('to have method [%s]', implode('], [', $methods)), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); } @@ -659,7 +659,7 @@ final class Expectation return Targeted::make( $this, fn (ObjectDescription $object): bool => isset($object->reflectionClass) && ($class === $object->reflectionClass->getName() || $object->reflectionClass->isSubclassOf($class)), - sprintf("to extend '%s'", $class), + sprintf('to extend [%s]', $class), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); } @@ -753,7 +753,7 @@ final class Expectation return Targeted::make( $this, fn (ObjectDescription $object): bool => isset($object->reflectionClass) && str_starts_with($object->reflectionClass->getShortName(), $prefix), - "to have prefix '{$prefix}'", + "to have prefix [{$prefix}]", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); } @@ -763,7 +763,7 @@ final class Expectation return Targeted::make( $this, fn (ObjectDescription $object): bool => isset($object->reflectionClass) && str_ends_with($object->reflectionClass->getName(), $suffix), - "to have suffix '{$suffix}'", + "to have suffix [{$suffix}]", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); } @@ -930,7 +930,7 @@ final class Expectation return Targeted::make( $this, fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->getAttributes($attribute) !== [], - "to have attribute '{$attribute}'", + "to have attribute [{$attribute}]", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); } diff --git a/src/Expectations/OppositeExpectation.php b/src/Expectations/OppositeExpectation.php index 3693a352..b85150a0 100644 --- a/src/Expectations/OppositeExpectation.php +++ b/src/Expectations/OppositeExpectation.php @@ -296,7 +296,7 @@ final readonly class OppositeExpectation }, $methods === [] ? 'not to have public methods' - : sprintf("not to have public methods besides '%s'", implode("', '", $methods)), + : sprintf('not to have public methods besides [%s]', implode('], [', $methods)), FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)), ); } @@ -337,7 +337,7 @@ final readonly class OppositeExpectation }, $methods === [] ? 'not to have protected methods' - : sprintf("not to have protected methods besides '%s'", implode("', '", $methods)), + : sprintf('not to have protected methods besides [%s]', implode('], [', $methods)), FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)), ); } @@ -378,7 +378,7 @@ final readonly class OppositeExpectation }, $methods === [] ? 'not to have private methods' - : sprintf("not to have private methods besides '%s'", implode("', '", $methods)), + : sprintf('not to have private methods besides [%s]', implode('], [', $methods)), FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)), ); } @@ -450,7 +450,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isSubclassOf($class), - sprintf("not to extend '%s'", $class), + sprintf('not to extend [%s]', $class), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); } @@ -535,7 +535,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! str_starts_with($object->reflectionClass->getShortName(), $prefix), - "not to have prefix '{$prefix}'", + "not to have prefix [{$prefix}]", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); } @@ -548,7 +548,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! str_ends_with($object->reflectionClass->getName(), $suffix), - "not to have suffix '{$suffix}'", + "not to have suffix [{$suffix}]", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); } @@ -615,7 +615,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || $object->reflectionClass->getAttributes($attribute) === [], - "to not have attribute '{$attribute}'", + "to not have attribute [{$attribute}]", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')) ); } diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index dc5910de..69bece9e 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -170,7 +170,7 @@ final class TestCaseFactory eval($classCode); } catch (ParseError $caught) { throw new RuntimeException(sprintf( - "Unable to create test case for test file at %s. \n %s", + "Unable to create test case for test file at [%s]. \n %s", $filename, $classCode ), 1, $caught); @@ -237,6 +237,6 @@ final class TestCaseFactory } } - throw ShouldNotHappen::fromMessage(sprintf('Method %s not found.', $methodName)); + throw ShouldNotHappen::fromMessage(sprintf('Method [%s] not found.', $methodName)); } } diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index b364431e..127f599e 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -541,7 +541,7 @@ final class Expectation /* @phpstan-ignore-next-line */ } catch (ExpectationFailedException $exception) { if ($message === '') { - $message = "Failed asserting that an array has the key '$key'"; + $message = "Failed asserting that an array has the key [$key]"; } throw new ExpectationFailedException($message, $exception->getComparisonFailure()); @@ -863,10 +863,10 @@ final class Expectation Assert::assertTrue(true); if (! $exception instanceof Throwable && ! class_exists($exception)) { - throw new ExpectationFailedException("Exception with message \"$exception\" not thrown."); + throw new ExpectationFailedException("Exception with message [$exception] not thrown."); } - throw new ExpectationFailedException("Exception \"$exception\" not thrown."); + throw new ExpectationFailedException("Exception [$exception] not thrown."); } private function export(mixed $value): string diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index a0d4702d..2eecfe8c 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -504,7 +504,7 @@ final class TestCall // @phpstan-ignore-line $isFunction = function_exists($classOrFunction); if (! $isClass && ! $isTrait && ! $isFunction) { - throw new InvalidArgumentException(sprintf('No class, trait or method named "%s" has been found.', $classOrFunction)); + throw new InvalidArgumentException(sprintf('No class, trait or method named [%s] has been found.', $classOrFunction)); } if ($isClass) { diff --git a/src/Plugins/Tia/BaselineSync.php b/src/Plugins/Tia/BaselineSync.php index 5b147883..1f4d63ce 100644 --- a/src/Plugins/Tia/BaselineSync.php +++ b/src/Plugins/Tia/BaselineSync.php @@ -17,7 +17,7 @@ use Symfony\Component\Process\Process; */ final readonly class BaselineSync { - private const string WORKFLOW_FILE = 'tia-baseline.yml'; + private const string DEFAULT_WORKFLOW_FILE = 'tia-baseline.yml'; private const string ARTIFACT_NAME = 'pest-tia-baseline'; @@ -57,8 +57,14 @@ final readonly class BaselineSync public function __construct( private State $state, private OutputInterface $output, + private WatchPatterns $watchPatterns, ) {} + private function workflowFile(): string + { + return $this->watchPatterns->baselineWorkflow() ?? self::DEFAULT_WORKFLOW_FILE; + } + private function renderBadge(string $type, string $content): void { View::render('components.badge', ['type' => $type, 'content' => $content]); @@ -276,7 +282,7 @@ final readonly class BaselineSync Panic::with(new BaselineFetchFailed( sprintf('%s — %s', $contextPrefix, $diagnosis['message']), - 'Verify workflow tia-baseline.yml, artifact pest-tia-baseline, and gh token scope.', + sprintf('Verify workflow [%s], artifact [%s], and gh token scope.', $this->workflowFile(), self::ARTIFACT_NAME), $hasAnchor, )); } @@ -528,7 +534,7 @@ final readonly class BaselineSync $process = new Process([ 'gh', 'run', 'list', '-R', $repo, - '--workflow', self::WORKFLOW_FILE, + '--workflow', $this->workflowFile(), '--status', 'success', '--limit', '1', '--json', 'databaseId', diff --git a/src/Plugins/Tia/Configuration.php b/src/Plugins/Tia/Configuration.php index fc7a42bd..72b6cf8d 100644 --- a/src/Plugins/Tia/Configuration.php +++ b/src/Plugins/Tia/Configuration.php @@ -51,12 +51,16 @@ final class Configuration /** * @return $this */ - public function baselined(): self + public function baselined(?string $workflow = null): self { /** @var WatchPatterns $watchPatterns */ $watchPatterns = Container::getInstance()->get(WatchPatterns::class); $watchPatterns->markBaselined(); + if ($workflow !== null) { + $watchPatterns->setBaselineWorkflow($workflow); + } + return $this; } diff --git a/src/Plugins/Tia/CoverageCollector.php b/src/Plugins/Tia/CoverageCollector.php index 1aaf34cb..eaa0c641 100644 --- a/src/Plugins/Tia/CoverageCollector.php +++ b/src/Plugins/Tia/CoverageCollector.php @@ -27,10 +27,12 @@ final class CoverageCollector } try { - $lineCoverage = PhpUnitCodeCoverage::instance() + $data = PhpUnitCodeCoverage::instance() ->codeCoverage() - ->getData() - ->lineCoverage(); + ->getData(); + + $lineCoverage = $data->lineCoverage(); + $idByIndex = $data->testIds(); } catch (Throwable) { return []; } @@ -46,12 +48,16 @@ final class CoverageCollector continue; } - foreach ($hits as $id) { - $testIds[$id] = true; + foreach (array_keys($hits) as $index) { + if (! isset($idByIndex[$index])) { + continue; + } + + $testIds[$index] = $idByIndex[$index]; } } - foreach (array_keys($testIds) as $testId) { + foreach ($testIds as $testId) { $testFile = $this->testIdToFile($testId); if ($testFile === null) { diff --git a/src/Plugins/Tia/CoverageMerger.php b/src/Plugins/Tia/CoverageMerger.php index 370c1ecb..bed832f9 100644 --- a/src/Plugins/Tia/CoverageMerger.php +++ b/src/Plugins/Tia/CoverageMerger.php @@ -100,19 +100,32 @@ final class CoverageMerger } $cachedData = $cached->getData(); + + $staleIndexes = []; + + foreach ($cachedData->testIds() as $index => $id) { + if (in_array($id, $currentIds, true)) { + $staleIndexes[$index] = true; + } + } + + if ($staleIndexes === []) { + return; + } + $lineCoverage = $cachedData->lineCoverage(); foreach ($lineCoverage as $file => $lines) { - foreach ($lines as $line => $ids) { - if ($ids === null) { + foreach ($lines as $line => $hits) { + if ($hits === null) { continue; } - if ($ids === []) { + if ($hits === []) { continue; } - $filtered = array_values(array_diff($ids, $currentIds)); + $filtered = array_diff_key($hits, $staleIndexes); - if ($filtered !== $ids) { + if ($filtered !== $hits) { $lineCoverage[$file][$line] = $filtered; } } @@ -126,21 +139,28 @@ final class CoverageMerger */ private static function collectTestIds(CodeCoverage $coverage): array { + $data = $coverage->getData(); + $idByIndex = $data->testIds(); + $ids = []; - foreach ($coverage->getData()->lineCoverage() as $lines) { + foreach ($data->lineCoverage() as $lines) { foreach ($lines as $hits) { if ($hits === null) { continue; } - foreach ($hits as $id) { - $ids[$id] = true; + foreach (array_keys($hits) as $index) { + if (! isset($idByIndex[$index])) { + continue; + } + + $ids[$index] = $idByIndex[$index]; } } } - return array_keys($ids); + return array_values($ids); } private static function state(): State diff --git a/src/Plugins/Tia/WatchPatterns.php b/src/Plugins/Tia/WatchPatterns.php index 8d1e9837..35ed409e 100644 --- a/src/Plugins/Tia/WatchPatterns.php +++ b/src/Plugins/Tia/WatchPatterns.php @@ -46,6 +46,8 @@ final class WatchPatterns private ?string $defaultBranch = null; + private ?string $baselineWorkflow = null; + public function useDefaults(string $projectRoot): void { $testPath = TestSuite::getInstance()->testPath; @@ -189,6 +191,18 @@ final class WatchPatterns return $this->defaultBranch; } + public function setBaselineWorkflow(string $workflow): void + { + $workflow = trim($workflow); + + $this->baselineWorkflow = $workflow === '' ? null : $workflow; + } + + public function baselineWorkflow(): ?string + { + return $this->baselineWorkflow; + } + public function reset(): void { $this->patterns = []; @@ -198,6 +212,7 @@ final class WatchPatterns $this->filtered = false; $this->baselined = false; $this->defaultBranch = null; + $this->baselineWorkflow = null; } private function keyMatches(string $key, string $file): bool diff --git a/src/Support/Container.php b/src/Support/Container.php index bf1895ad..e161c6c7 100644 --- a/src/Support/Container.php +++ b/src/Support/Container.php @@ -73,7 +73,7 @@ final class Container if ($type instanceof \ReflectionType && $type->isBuiltin()) { $candidate = $param->getName(); } else { - throw ShouldNotHappen::fromMessage(sprintf('The type of `$%s` in `%s` cannot be determined.', $id, $param->getName())); + throw ShouldNotHappen::fromMessage(sprintf('The type of [$%s] in [%s] cannot be determined.', $id, $param->getName())); } } @@ -88,6 +88,6 @@ final class Container return $reflectionClass->newInstance(); } - throw ShouldNotHappen::fromMessage(sprintf('A dependency with the name `%s` cannot be resolved.', $id)); + throw ShouldNotHappen::fromMessage(sprintf('A dependency with the name [%s] cannot be resolved.', $id)); } } diff --git a/src/Support/Coverage.php b/src/Support/Coverage.php index 92110713..321ce2ce 100644 --- a/src/Support/Coverage.php +++ b/src/Support/Coverage.php @@ -74,7 +74,7 @@ final class Coverage return 0.0; } - throw ShouldNotHappen::fromMessage(sprintf('Coverage not found in path: %s.', $reportPath)); + throw ShouldNotHappen::fromMessage(sprintf('Coverage not found in path: [%s].', $reportPath)); } CoverageMerger::applyIfMarked($reportPath); diff --git a/tests/Features/Covers/ExceptionHandling.php b/tests/Features/Covers/ExceptionHandling.php index 99e6a24a..0e3d58df 100644 --- a/tests/Features/Covers/ExceptionHandling.php +++ b/tests/Features/Covers/ExceptionHandling.php @@ -9,4 +9,4 @@ it('throws exception if no class nor method has been found', function (): void { $testCall = new TestCall(TestSuite::getInstance(), 'filename', 'description', fn () => 'closure'); $testCall->covers('fakeName'); -})->throws(InvalidArgumentException::class, 'No class, trait or method named "fakeName" has been found.'); +})->throws(InvalidArgumentException::class, 'No class, trait or method named [fakeName] has been found.'); diff --git a/tests/Features/DatasetProviderErrors.php b/tests/Features/DatasetProviderErrors.php index 9db94cfd..45bea2f0 100644 --- a/tests/Features/DatasetProviderErrors.php +++ b/tests/Features/DatasetProviderErrors.php @@ -21,7 +21,7 @@ test('reports missing datasets as errors for a single file run', function () use $result = $run('tests/Fixtures/Suites/IssueOnly.php'); expect($result['output']) - ->toContain("A dataset with the name [missing] does not exist. You can create it using `dataset('missing', ['a', 'b']);`.") + ->toContain("A dataset named [missing] does not exist. You may create one using `dataset('missing', ['a', 'b']);`.") ->toContain('FAILED') ->toContain('Tests: 1 failed') ->and($result['code'])->not->toBe(0); @@ -31,7 +31,7 @@ test('reports missing datasets as errors alongside passing tests', function () u $result = $run('tests/Fixtures/Suites/IssueWithPassing.php'); expect($result['output']) - ->toContain("A dataset with the name [missing] does not exist. You can create it using `dataset('missing', ['a', 'b']);`.") + ->toContain("A dataset named [missing] does not exist. You may create one using `dataset('missing', ['a', 'b']);`.") ->toContain('1 passed') ->toContain('1 failed') ->and($result['code'])->not->toBe(0); diff --git a/tests/Features/DatasetsTests.php b/tests/Features/DatasetsTests.php index d70bb05c..ec53d03f 100644 --- a/tests/Features/DatasetsTests.php +++ b/tests/Features/DatasetsTests.php @@ -10,12 +10,12 @@ beforeEach(function (): void { }); it('throws exception if dataset does not exist', function (): void { - expect(fn () => DatasetsRepository::resolve(['first'], __FILE__))->toThrow(DatasetDoesNotExist::class, "A dataset with the name [first] does not exist. You can create it using `dataset('first', ['a', 'b']);`."); + expect(fn () => DatasetsRepository::resolve(['first'], __FILE__))->toThrow(DatasetDoesNotExist::class, "A dataset named [first] does not exist. You may create one using `dataset('first', ['a', 'b']);`."); }); it('throws exception if dataset already exist', function (): void { DatasetsRepository::set('second', [[]], __DIR__); - expect(fn () => DatasetsRepository::set('second', [[]], __DIR__))->toThrow(DatasetAlreadyExists::class, 'A dataset with the name [second] already exists in scope ['.__DIR__.'].'); + expect(fn () => DatasetsRepository::set('second', [[]], __DIR__))->toThrow(DatasetAlreadyExists::class, 'A dataset named [second] is already registered in ['.__DIR__.'].'); }); it('sets closures', function (): void { diff --git a/tests/Features/Expect/toBeBase64.php b/tests/Features/Expect/toBeBase64.php index a872c21a..91bcdadf 100644 --- a/tests/Features/Expect/toBeBase64.php +++ b/tests/Features/Expect/toBeBase64.php @@ -31,7 +31,7 @@ test('failures with malformed input', function (): void { test('failures with invalid type', function (): void { expect([])->toBeBase64(); -})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].'); +})->throws(InvalidExpectationValue::class, 'This expectation may only be used on a value of type [string].'); test('failures with custom message', function (): void { expect('!!invalid!!')->toBeBase64('oh no!'); diff --git a/tests/Features/Expect/toBeDomain.php b/tests/Features/Expect/toBeDomain.php index 1d818ee2..54a9c59b 100644 --- a/tests/Features/Expect/toBeDomain.php +++ b/tests/Features/Expect/toBeDomain.php @@ -22,7 +22,7 @@ test('failures with leading dot', function (): void { test('failures with invalid type', function (): void { expect([])->toBeDomain(); -})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].'); +})->throws(InvalidExpectationValue::class, 'This expectation may only be used on a value of type [string].'); test('failures with custom message', function (): void { expect('example')->toBeDomain('oh no!'); diff --git a/tests/Features/Expect/toBeHexadecimal.php b/tests/Features/Expect/toBeHexadecimal.php index 6c838074..7e10e8dc 100644 --- a/tests/Features/Expect/toBeHexadecimal.php +++ b/tests/Features/Expect/toBeHexadecimal.php @@ -31,7 +31,7 @@ test('failures with empty string', function (): void { test('failures with invalid type', function (): void { expect([])->toBeHexadecimal(); -})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].'); +})->throws(InvalidExpectationValue::class, 'This expectation may only be used on a value of type [string].'); test('failures with custom message', function (): void { expect('xyz')->toBeHexadecimal('oh no!'); diff --git a/tests/Features/Expect/toBeHostname.php b/tests/Features/Expect/toBeHostname.php index 381ef704..f2791368 100644 --- a/tests/Features/Expect/toBeHostname.php +++ b/tests/Features/Expect/toBeHostname.php @@ -22,7 +22,7 @@ test('failures with trailing hyphen', function (): void { test('failures with invalid type', function (): void { expect([])->toBeHostname(); -})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].'); +})->throws(InvalidExpectationValue::class, 'This expectation may only be used on a value of type [string].'); test('failures with custom message', function (): void { expect('-example')->toBeHostname('oh no!'); diff --git a/tests/Features/Expect/toBeIpAddress.php b/tests/Features/Expect/toBeIpAddress.php index ed4d2d0b..90a3faa9 100644 --- a/tests/Features/Expect/toBeIpAddress.php +++ b/tests/Features/Expect/toBeIpAddress.php @@ -17,7 +17,7 @@ test('failures', function (): void { test('failures with invalid type', function (): void { expect([])->toBeIpAddress(); -})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].'); +})->throws(InvalidExpectationValue::class, 'This expectation may only be used on a value of type [string].'); test('failures with custom message', function (): void { expect('not-an-ip')->toBeIpAddress('oh no!'); diff --git a/tests/Features/Expect/toBeMacAddress.php b/tests/Features/Expect/toBeMacAddress.php index d5a438ad..d6bfca6a 100644 --- a/tests/Features/Expect/toBeMacAddress.php +++ b/tests/Features/Expect/toBeMacAddress.php @@ -17,7 +17,7 @@ test('failures', function (): void { test('failures with invalid type', function (): void { expect([])->toBeMacAddress(); -})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].'); +})->throws(InvalidExpectationValue::class, 'This expectation may only be used on a value of type [string].'); test('failures with custom message', function (): void { expect('not-a-mac')->toBeMacAddress('oh no!'); diff --git a/tests/Features/Expect/toBeUlid.php b/tests/Features/Expect/toBeUlid.php index 180ab727..ab0d510a 100644 --- a/tests/Features/Expect/toBeUlid.php +++ b/tests/Features/Expect/toBeUlid.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\ExpectationFailedException; test('failures with wrong type', function (): void { expect([])->toBeUlid(); -})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].'); +})->throws(InvalidExpectationValue::class, 'This expectation may only be used on a value of type [string].'); test('pass', function (): void { expect('01ARZ3NDEKTSV4RRFFQ69G5FAV')->toBeUlid() diff --git a/tests/Features/Expect/toBeUuid.php b/tests/Features/Expect/toBeUuid.php index 24138b22..8baba5e4 100644 --- a/tests/Features/Expect/toBeUuid.php +++ b/tests/Features/Expect/toBeUuid.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\ExpectationFailedException; test('failures with wrong type', function (): void { expect([])->toBeUuid(); -})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].'); +})->throws(InvalidExpectationValue::class, 'This expectation may only be used on a value of type [string].'); test('pass', function (): void { expect('3cafb226-4326-11ee-a516-846993788c86')->toBeUuid() diff --git a/tests/Features/Expect/toHaveCount.php b/tests/Features/Expect/toHaveCount.php index d26031d0..bc33d751 100644 --- a/tests/Features/Expect/toHaveCount.php +++ b/tests/Features/Expect/toHaveCount.php @@ -11,7 +11,7 @@ test('pass', function (): void { test('failures with invalid type', function (): void { expect('foo')->toHaveCount(3); -})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [countable|iterable]'); +})->throws(InvalidExpectationValue::class, 'This expectation may only be used on a value of type [countable|iterable]'); test('failures', function (): void { expect([1, 2, 3])->toHaveCount(4); diff --git a/tests/Features/Expect/toHaveKey.php b/tests/Features/Expect/toHaveKey.php index 34063559..ce9f7455 100644 --- a/tests/Features/Expect/toHaveKey.php +++ b/tests/Features/Expect/toHaveKey.php @@ -24,7 +24,7 @@ test('pass with value check and plain key with dots')->expect($test_array)->toHa test('failures', function () use ($test_array): void { expect($test_array)->toHaveKey('foo'); -})->throws(ExpectationFailedException::class, "Failed asserting that an array has the key 'foo'"); +})->throws(ExpectationFailedException::class, 'Failed asserting that an array has the key [foo]'); test('failures with custom message', function () use ($test_array): void { expect($test_array)->toHaveKey('foo', message: 'oh no!'); @@ -36,7 +36,7 @@ test('failures with custom message and Any matcher', function () use ($test_arra test('failures with nested key', function () use ($test_array): void { expect($test_array)->toHaveKey('d.bar'); -})->throws(ExpectationFailedException::class, "Failed asserting that an array has the key 'd.bar'"); +})->throws(ExpectationFailedException::class, 'Failed asserting that an array has the key [d.bar]'); test('failures with nested key and custom message', function () use ($test_array): void { expect($test_array)->toHaveKey('d.bar', message: 'oh no!'); @@ -48,7 +48,7 @@ test('failures with nested key and custom message with Any matcher', function () test('failures with plain key with dots', function () use ($test_array): void { expect($test_array)->toHaveKey('missing.key.with.dots'); -})->throws(ExpectationFailedException::class, "Failed asserting that an array has the key 'missing.key.with.dots'"); +})->throws(ExpectationFailedException::class, 'Failed asserting that an array has the key [missing.key.with.dots]'); test('fails with wrong value', function () use ($test_array): void { expect($test_array)->toHaveKey('c', 'bar'); diff --git a/tests/Features/Expect/toHaveSameSize.php b/tests/Features/Expect/toHaveSameSize.php index 9955a0fb..25ea41d5 100644 --- a/tests/Features/Expect/toHaveSameSize.php +++ b/tests/Features/Expect/toHaveSameSize.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\ExpectationFailedException; test('failures with wrong type', function (): void { expect('foo')->toHaveSameSize([1]); -})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [countable|iterable].'); +})->throws(InvalidExpectationValue::class, 'This expectation may only be used on a value of type [countable|iterable].'); test('pass', function (): void { expect([1, 2, 3])->toHaveSameSize([4, 5, 6]); diff --git a/tests/Features/Expect/toThrow.php b/tests/Features/Expect/toThrow.php index 84ee8e6c..c754120d 100644 --- a/tests/Features/Expect/toThrow.php +++ b/tests/Features/Expect/toThrow.php @@ -33,11 +33,11 @@ test('passes', function (): void { test('failures 1', function (): void { expect(function (): void {})->toThrow(RuntimeException::class); -})->throws(ExpectationFailedException::class, 'Exception "'.RuntimeException::class.'" not thrown.'); +})->throws(ExpectationFailedException::class, 'Exception ['.RuntimeException::class.'] not thrown.'); test('failures 2', function (): void { expect(function (): void {})->toThrow(function (RuntimeException $e): void {}); -})->throws(ExpectationFailedException::class, 'Exception "'.RuntimeException::class.'" not thrown.'); +})->throws(ExpectationFailedException::class, 'Exception ['.RuntimeException::class.'] not thrown.'); test('failures 3', function (): void { expect(function (): void { @@ -64,7 +64,7 @@ test('failures 5', function (): void { test('failures 6', function (): void { expect(function (): void {})->toThrow('actual message'); -})->throws(ExpectationFailedException::class, 'Exception with message "actual message" not thrown'); +})->throws(ExpectationFailedException::class, 'Exception with message [actual message] not thrown'); test('failures 7', function (): void { expect(function (): void { diff --git a/tests/Features/Tia/DefaultBranchResolution.php b/tests/Features/Tia/DefaultBranchResolution.php index 214c8cc8..ab4c020f 100644 --- a/tests/Features/Tia/DefaultBranchResolution.php +++ b/tests/Features/Tia/DefaultBranchResolution.php @@ -175,7 +175,7 @@ test('tia still requires git', function (): void { $result = $project->pest('--tia'); - expect($result->output)->toContain('The feature [Tia mode] requires [git].') + expect($result->output)->toContain('The [Tia mode] feature requires [git].') ->and($result->exitCode)->not->toBe(0); })->skipOnWindows(); diff --git a/tests/Unit/TestSuite.php b/tests/Unit/TestSuite.php index fe751465..18b26c83 100644 --- a/tests/Unit/TestSuite.php +++ b/tests/Unit/TestSuite.php @@ -15,7 +15,7 @@ it('does not allow to add the same test description twice', function (): void { $testSuite->tests->set($method); })->throws( TestAlreadyExist::class, - sprintf('A test with the description [%s] already exists in the filename [%s].', 'bar', 'foo'), + sprintf('A test named [%s] already exists in [%s]. Please give this test a different description.', 'bar', 'foo'), ); it('does not allow static closures', function (): void { @@ -27,7 +27,7 @@ it('does not allow static closures', function (): void { $testSuite->tests->set($method); })->throws( TestClosureMustNotBeStatic::class, - 'Test closure must not be static. Please remove the [static] keyword from the [bar] method in [foo].', + 'Test closures may not be static. Please remove the [static] keyword from the test [bar] in [foo].', ); it('alerts users about tests with arguments but no input', function (): void { @@ -40,7 +40,7 @@ it('alerts users about tests with arguments but no input', function (): void { $testSuite->tests->set($method); })->throws( DatasetMissing::class, - sprintf('A test with the description [%s] has [%d] argument(s) ([%s]) and no dataset(s) provided in [%s]', 'bar', 1, 'int $arg', 'foo'), + sprintf('The test [%s] in [%s] expects [%d] argument(s) ([%s]), but no dataset was provided. Please chain [with()] onto the test to supply one.', 'bar', 'foo', 1, 'int $arg'), ); it('can return an array of all test suite filenames', function (): void { diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 674b18b1..2ad76c41 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -45,7 +45,7 @@ test('a parallel test can extend another test with same name', function () use ( test('parallel reports invalid datasets as failures', function () use ($run): void { expect($run('tests/Fixtures/Suites/ParallelInvalidDataset')) - ->toContain("A dataset with the name [missing.dataset] does not exist. You can create it using `dataset('missing.dataset', ['a', 'b']);`.") + ->toContain("A dataset named [missing.dataset] does not exist. You may create one using `dataset('missing.dataset', ['a', 'b']);`.") ->toContain('Tests: 1 failed, 1 passed (1 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows();