diff --git a/README.md b/README.md
index 4febffd5..151869b7 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
-
+
diff --git a/composer.json b/composer.json
index ace8c975..9646056f 100644
--- a/composer.json
+++ b/composer.json
@@ -19,7 +19,7 @@
"require": {
"php": "^8.1.0",
"nunomaduro/collision": "^7.0.0",
- "nunomaduro/termwind": "^1.14.2",
+ "nunomaduro/termwind": "^1.15",
"pestphp/pest-plugin": "^2.0.0",
"phpunit/phpunit": "10.0.x-dev"
},
@@ -51,6 +51,7 @@
},
"require-dev": {
"pestphp/pest-dev-tools": "^2.1.0",
+ "pestphp/pest-plugin-arch": "^2.0.0",
"symfony/process": "^6.2.0"
},
"minimum-stability": "dev",
@@ -77,7 +78,7 @@
"test:integration": "php bin/pest --colors=always --group=integration -v",
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always",
"test": [
- "@rest:refacto",
+ "@test:refacto",
"@test:lint",
"@test:types",
"@test:unit",
diff --git a/overrides/Runner/TestSuiteLoader.php b/overrides/Runner/TestSuiteLoader.php
index 0719288b..6e9df439 100644
--- a/overrides/Runner/TestSuiteLoader.php
+++ b/overrides/Runner/TestSuiteLoader.php
@@ -98,14 +98,14 @@ final class TestSuiteLoader
self::$loadedClasses = array_merge($loadedClasses, self::$loadedClasses);
- if (empty(self::$loadedClasses)) {
+ if (empty($loadedClasses)) {
return $this->exceptionFor($suiteClassName, $suiteClassFile);
}
$testCaseFound = false;
- foreach (self::$loadedClasses as $loadedClass) {
- if (is_subclass_of($loadedClass, HasPrintableTestCaseName::class)) {
+ foreach (array_reverse($loadedClasses) as $loadedClass) {
+ if (is_subclass_of($loadedClass, HasPrintableTestCaseName::class) || is_subclass_of($loadedClass, TestCase::class)) {
$suiteClassName = $loadedClass;
$testCaseFound = true;
@@ -115,13 +115,7 @@ final class TestSuiteLoader
}
if (! $testCaseFound) {
- foreach (self::$loadedClasses as $loadedClass) {
- if (is_subclass_of($loadedClass, TestCase::class)) {
- $suiteClassName = $loadedClass;
-
- break;
- }
- }
+ return $this->exceptionFor($suiteClassName, $suiteClassFile);
}
if (! class_exists($suiteClassName, false)) {
diff --git a/src/Concerns/Retrievable.php b/src/Concerns/Retrievable.php
index a2716887..a95031b1 100644
--- a/src/Concerns/Retrievable.php
+++ b/src/Concerns/Retrievable.php
@@ -13,7 +13,6 @@ trait Retrievable
* @template TRetrievableValue
*
* Safely retrieve the value at the given key from an object or array.
- *
* @template TRetrievableValue
*
* @param array|object $value
diff --git a/src/Exceptions/InvalidExpectation.php b/src/Exceptions/InvalidExpectation.php
new file mode 100644
index 00000000..544baac3
--- /dev/null
+++ b/src/Exceptions/InvalidExpectation.php
@@ -0,0 +1,26 @@
+ $methods
+ *
+ * @throws self
+ */
+ public static function fromMethods(array $methods): never
+ {
+ throw new self(sprintf('Expectation [%s] is not valid.', implode('->', $methods)));
+ }
+}
diff --git a/src/Expectation.php b/src/Expectation.php
index 2da24e75..507e6915 100644
--- a/src/Expectation.php
+++ b/src/Expectation.php
@@ -6,6 +6,11 @@ namespace Pest;
use BadMethodCallException;
use Closure;
+use Pest\Arch\Contracts\ArchExpectation;
+use Pest\Arch\Expectations\ToDependOn;
+use Pest\Arch\Expectations\ToDependOnNothing;
+use Pest\Arch\Expectations\ToOnlyBeUsedOn;
+use Pest\Arch\Expectations\ToOnlyDependOn;
use Pest\Concerns\Extendable;
use Pest\Concerns\Pipeable;
use Pest\Concerns\Retrievable;
@@ -24,7 +29,7 @@ use PHPUnit\Framework\ExpectationFailedException;
*
* @template TValue
*
- * @property Expectation $not Creates the opposite expectation.
+ * @property OppositeExpectation $not Creates the opposite expectation.
* @property EachExpectation $each Creates an expectation on each element on the traversable value.
*
* @mixin Mixins\Expectation
@@ -350,4 +355,42 @@ final class Expectation
{
return new Any();
}
+
+ /**
+ * Asserts that the given expectation target depends on the given dependencies.
+ *
+ * @param array|string $dependencies
+ */
+ public function toDependOn(array|string $dependencies): ArchExpectation
+ {
+ return ToDependOn::make($this, $dependencies);
+ }
+
+ /**
+ * Asserts that the given expectation target does not have any dependencies.
+ */
+ public function toDependOnNothing(): ArchExpectation
+ {
+ return ToDependOnNothing::make($this);
+ }
+
+ /**
+ * Asserts that the given expectation dependency is only depended on by the given targets.
+ *
+ * @param array|string $targets
+ */
+ public function toOnlyBeUsedOn(array|string $targets): ArchExpectation
+ {
+ return ToOnlyBeUsedOn::make($this, $targets);
+ }
+
+ /**
+ * Asserts that the given expectation target does "only" depend on the given dependencies.
+ *
+ * @param array|string $targets
+ */
+ public function toOnlyDependOn(array|string $targets): ArchExpectation
+ {
+ return ToOnlyDependOn::make($this, $targets);
+ }
}
diff --git a/src/Expectations/OppositeExpectation.php b/src/Expectations/OppositeExpectation.php
index 9a8fefbe..f73b3345 100644
--- a/src/Expectations/OppositeExpectation.php
+++ b/src/Expectations/OppositeExpectation.php
@@ -4,6 +4,11 @@ declare(strict_types=1);
namespace Pest\Expectations;
+use Pest\Arch\Contracts\ArchExpectation;
+use Pest\Arch\Expectations\ToDependOn;
+use Pest\Arch\GroupArchExpectation;
+use Pest\Arch\SingleArchExpectation;
+use Pest\Exceptions\InvalidExpectation;
use Pest\Expectation;
use Pest\Support\Arr;
use PHPUnit\Framework\ExpectationFailedException;
@@ -52,6 +57,46 @@ final class OppositeExpectation
return $this->original;
}
+ /**
+ * Asserts that the given expectation target depends on the given dependencies.
+ *
+ * @param array|string $dependencies
+ */
+ public function toDependOn(array|string $dependencies): ArchExpectation
+ {
+ return GroupArchExpectation::fromExpectations(array_map(fn (string $target): SingleArchExpectation => ToDependOn::make($this->original, $target)->opposite(
+ fn () => $this->throwExpectationFailedException('toDependOn', $target),
+ ), is_string($dependencies) ? [$dependencies] : $dependencies));
+ }
+
+ /**
+ * Asserts that the given expectation dependency is only depended on by the given targets.
+ *
+ * @param array|string $targets
+ */
+ public function toOnlyBeUsedOn(array|string $targets): never
+ {
+ throw InvalidExpectation::fromMethods(['not', 'toOnlyBeUsedOn']);
+ }
+
+ /**
+ * Asserts that the given expectation target does "only" depend on the given dependencies.
+ *
+ * @param array|string $dependencies
+ */
+ public function toOnlyDependOn(array|string $dependencies): never
+ {
+ throw InvalidExpectation::fromMethods(['not', 'toOnlyDependOn']);
+ }
+
+ /**
+ * Asserts that the given expectation target does not have any dependencies.
+ */
+ public function toDependOnNothing(): never
+ {
+ throw InvalidExpectation::fromMethods(['not', 'toDependOnNothing']);
+ }
+
/**
* Handle dynamic method calls into the original expectation.
*
@@ -89,10 +134,12 @@ final class OppositeExpectation
/**
* Creates a new expectation failed exception with a nice readable message.
*
- * @param array $arguments
+ * @param array|string $arguments
*/
- private function throwExpectationFailedException(string $name, array $arguments = []): never
+ public function throwExpectationFailedException(string $name, array|string $arguments = []): never
{
+ $arguments = is_array($arguments) ? $arguments : [$arguments];
+
$exporter = new Exporter();
$toString = fn ($argument): string => $exporter->shortenedExport($argument);
diff --git a/src/Plugins/Memory.php b/src/Plugins/Memory.php
index 5d29960e..0755f743 100644
--- a/src/Plugins/Memory.php
+++ b/src/Plugins/Memory.php
@@ -46,7 +46,7 @@ final class Memory implements AddsOutput, HandlesArguments
{
if ($this->enabled) {
$this->output->writeln(sprintf(
- ' Memory:> %s MB>',
+ ' Memory:> %s MB>',
round(memory_get_usage(true) / 1000 ** 2, 3)
));
}
diff --git a/src/Repositories/DatasetsRepository.php b/src/Repositories/DatasetsRepository.php
index 9cb3a62c..3b00b7b4 100644
--- a/src/Repositories/DatasetsRepository.php
+++ b/src/Repositories/DatasetsRepository.php
@@ -162,11 +162,10 @@ final class DatasetsRepository
$datasets[$index] = iterator_to_array($datasets[$index], $preserveKeysForArrayIterator);
}
- // @phpstan-ignore-next-line
foreach ($datasets[$index] as $key => $values) {
$values = is_array($values) ? $values : [$values];
$processedDataset[] = [
- 'label' => self::getDatasetDescription($key, $values), // @phpstan-ignore-line
+ 'label' => self::getDatasetDescription($key, $values),
'values' => $values,
];
}
diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt
index 231c5ced..9ee929b7 100644
--- a/tests/.snapshots/success.txt
+++ b/tests/.snapshots/success.txt
@@ -781,6 +781,9 @@
PASS Tests\PHPUnit\CustomAffixes\snakecasespec
✓ it runs file names like `snake_case_spec.php`
+ PASS Tests\CustomTestCase\ExecutedTest
+ ✓ that gets executed
+
PASS Tests\PHPUnit\CustomTestCase\UsesPerDirectory
✓ closure was bound to CustomTestCase
@@ -817,6 +820,10 @@
✓ it show the actual dataset of multiple non-named datasets in their description
✓ it show the correct description for mixed named and not-named datasets
+ PASS Tests\Unit\Expectations\OppositeExpectation
+ ✓ it throw expectation failed exception with string argument
+ ✓ it throw expectation failed exception with array argument
+
PASS Tests\Unit\Plugins\Environment
✓ environment is set to CI when --ci option is used
✓ environment is set to Local when --ci option is not used
@@ -892,4 +899,4 @@
PASS Tests\Visual\Version
✓ visual snapshot of help command output
- Tests: 4 incomplete, 2 todos, 18 skipped, 621 passed (1531 assertions)
\ No newline at end of file
+
diff --git a/tests/Unit/Expectations/OppositeExpectation.php b/tests/Unit/Expectations/OppositeExpectation.php
new file mode 100644
index 00000000..448ebf6d
--- /dev/null
+++ b/tests/Unit/Expectations/OppositeExpectation.php
@@ -0,0 +1,16 @@
+throwExpectationFailedException('toBe', 'bar');
+})->throws(ExpectationFailedException::class, "Expecting 'foo' not to be 'bar'.");
+
+it('throw expectation failed exception with array argument', function (): void {
+ $expectation = new OppositeExpectation(expect('foo'));
+
+ $expectation->throwExpectationFailedException('toBe', ['bar']);
+})->throws(ExpectationFailedException::class, "Expecting 'foo' not to be 'bar'.");