feat: toHaveAllMethodsDocumented and toHaveAllPropertiesDocumented

This commit is contained in:
Nuno Maduro
2024-07-19 14:03:59 +01:00
parent 99107544ff
commit f2691623cf
18 changed files with 154 additions and 4 deletions

View File

@ -5,3 +5,5 @@
5. `arch()->preset` 5. `arch()->preset`
6. `toHaveFileSystemPermissions` 6. `toHaveFileSystemPermissions`
7. `toHaveLineCountLessThan` 7. `toHaveLineCountLessThan`
8. `toHaveAllMethodsDocumented`
9. `toHaveAllPropertiesDocumented`

View File

@ -11,6 +11,9 @@ use Pest\PendingCalls\UsesCall;
*/ */
final class Configuration final class Configuration
{ {
/**
* The filename of the configuration.
*/
private readonly string $filename; private readonly string $filename;
/** /**

View File

@ -33,6 +33,8 @@ use Pest\Support\ExpectationPipeline;
use PHPUnit\Architecture\Elements\ObjectDescription; use PHPUnit\Architecture\Elements\ObjectDescription;
use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\ExpectationFailedException;
use ReflectionEnum; use ReflectionEnum;
use ReflectionMethod;
use ReflectionProperty;
/** /**
* @template TValue * @template TValue
@ -460,6 +462,43 @@ final class Expectation
); );
} }
/**
* Asserts that the given expectation target have all methods documented.
*/
public function toHaveAllMethodsDocumented(): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter(
$object->reflectionClass->getMethods(),
fn (ReflectionMethod $method): bool => $method->class === $object->name
&& $method->getDocComment() === false,
) === [],
'to have all methods documented',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
);
}
/**
* Asserts that the given expectation target have all properties documented.
*/
public function toHaveAllPropertiesDocumented(): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter(
$object->reflectionClass->getProperties(),
fn (ReflectionProperty $property): bool => $property->class === $object->name
&& $property->isPromoted() === false
&& $property->getDocComment() === false,
) === [],
'to have all properties documented',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
);
}
/** /**
* Asserts that the given expectation target use the "declare(strict_types=1)" declaration. * Asserts that the given expectation target use the "declare(strict_types=1)" declaration.
*/ */
@ -768,6 +807,9 @@ final class Expectation
return ToUseNothing::make($this); return ToUseNothing::make($this);
} }
/**
* Not supported.
*/
public function toBeUsed(): never public function toBeUsed(): never
{ {
throw InvalidExpectation::fromMethods(['toBeUsed']); throw InvalidExpectation::fromMethods(['toBeUsed']);

View File

@ -17,6 +17,9 @@ use function expect;
*/ */
final class EachExpectation final class EachExpectation
{ {
/**
* Indicates if the expectation is the opposite.
*/
private bool $opposite = false; private bool $opposite = false;
/** /**

View File

@ -25,8 +25,14 @@ final class HigherOrderExpectation
*/ */
private Expectation|EachExpectation $expectation; private Expectation|EachExpectation $expectation;
/**
* Indicates if the expectation is the opposite.
*/
private bool $opposite = false; private bool $opposite = false;
/**
* Indicates if the expectation should reset the value.
*/
private bool $shouldReset = false; private bool $shouldReset = false;
/** /**

View File

@ -96,6 +96,22 @@ final class OppositeExpectation
throw InvalidExpectation::fromMethods(['not', 'toHaveLineCountLessThan']); throw InvalidExpectation::fromMethods(['not', 'toHaveLineCountLessThan']);
} }
/**
* Not supported.
*/
public function toHaveAllMethodsDocumented(): ArchExpectation
{
throw InvalidExpectation::fromMethods(['not', 'toHaveAllMethodsDocumented']);
}
/**
* Not supported.
*/
public function toHaveAllPropertiesDocumented(): ArchExpectation
{
throw InvalidExpectation::fromMethods(['not', 'toHaveAllPropertiesDocumented']);
}
/** /**
* Asserts that the given expectation target does not use the "declare(strict_types=1)" declaration. * Asserts that the given expectation target does not use the "declare(strict_types=1)" declaration.
*/ */

View File

@ -26,8 +26,14 @@ use PHPUnit\TestRunner\TestResult\TestResult as PhpUnitTestResult;
*/ */
final class Converter final class Converter
{ {
/**
* The prefix for the test suite name.
*/
private const PREFIX = 'P\\'; private const PREFIX = 'P\\';
/**
* The state generator.
*/
private readonly StateGenerator $stateGenerator; private readonly StateGenerator $stateGenerator;
/** /**

View File

@ -9,6 +9,9 @@ namespace Pest\Logging\TeamCity;
*/ */
final class ServiceMessage final class ServiceMessage
{ {
/**
* The flow ID.
*/
private static ?int $flowId = null; private static ?int $flowId = null;
/** /**

View File

@ -43,8 +43,14 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
final class TeamCityLogger final class TeamCityLogger
{ {
/**
* The current time.
*/
private ?HRTime $time = null; private ?HRTime $time = null;
/**
* Indicates if the summary test count has been printed.
*/
private bool $isSummaryTestCountPrinted = false; private bool $isSummaryTestCountPrinted = false;
/** /**

View File

@ -46,15 +46,27 @@ use function usleep;
*/ */
final class WrapperRunner implements RunnerInterface final class WrapperRunner implements RunnerInterface
{ {
/**
* The time to sleep between cycles.
*/
private const CYCLE_SLEEP = 10000; private const CYCLE_SLEEP = 10000;
/**
* The result printer.
*/
private readonly ResultPrinter $printer; private readonly ResultPrinter $printer;
/**
* The timer.
*/
private readonly Timer $timer; private readonly Timer $timer;
/** @var list<non-empty-string> */ /** @var list<non-empty-string> */
private array $pending = []; private array $pending = [];
/**
* The exit code.
*/
private int $exitcode = -1; private int $exitcode = -1;
/** @var array<positive-int,WrapperWorker> */ /** @var array<positive-int,WrapperWorker> */
@ -84,6 +96,9 @@ final class WrapperRunner implements RunnerInterface
/** @var non-empty-string[] */ /** @var non-empty-string[] */
private readonly array $parameters; private readonly array $parameters;
/**
* The code coverage filter registry.
*/
private CodeCoverageFilterRegistry $codeCoverageFilterRegistry; private CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
public function __construct( public function __construct(

View File

@ -13,6 +13,9 @@ use ReflectionParameter;
*/ */
final class Container final class Container
{ {
/**
* The instance of the container.
*/
private static ?Container $instance = null; private static ?Container $instance = null;
/** /**

View File

@ -14,15 +14,21 @@ use Symfony\Component\Process\Process;
final class GitDirtyTestCaseFilter implements TestCaseFilter final class GitDirtyTestCaseFilter implements TestCaseFilter
{ {
/** /**
* @var string[]|null * @var array<int, string>|null
*/ */
private ?array $changedFiles = null; private ?array $changedFiles = null;
/**
* Creates a new instance of the filter.
*/
public function __construct(private readonly string $projectRoot) public function __construct(private readonly string $projectRoot)
{ {
// ... // ...
} }
/**
* {@inheritdoc}
*/
public function accept(string $testCaseFilename): bool public function accept(string $testCaseFilename): bool
{ {
if ($this->changedFiles === null) { if ($this->changedFiles === null) {
@ -41,6 +47,9 @@ final class GitDirtyTestCaseFilter implements TestCaseFilter
return in_array($relativePath, $this->changedFiles, true); return in_array($relativePath, $this->changedFiles, true);
} }
/**
* Loads the changed files.
*/
private function loadChangedFiles(): void private function loadChangedFiles(): void
{ {
$process = new Process(['git', 'status', '--short', '--', '*.php']); $process = new Process(['git', 'status', '--short', '--', '*.php']);

View File

@ -715,6 +715,14 @@
✓ failures with custom message ✓ failures with custom message
✓ not failures ✓ not failures
PASS Tests\Features\Expect\toHaveAllMethodsDocumented
✓ it passes
✓ it fails
PASS Tests\Features\Expect\toHaveAllPropertiesDocumented
✓ it passes
✓ it fails
PASS Tests\Features\Expect\toHaveAttribute PASS Tests\Features\Expect\toHaveAttribute
✓ class has attribute ✓ class has attribute
✓ opposite class has attribute ✓ opposite class has attribute
@ -1525,4 +1533,4 @@
WARN Tests\Visual\Version WARN Tests\Visual\Version
- visual snapshot of help command output - visual snapshot of help command output
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 24 skipped, 1068 passed (2614 assertions) Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 24 skipped, 1072 passed (2620 assertions)

View File

@ -0,0 +1,13 @@
<?php
use Pest\Arch\Exceptions\ArchExpectationFailedException;
use Pest\Expectation;
use Tests\Fixtures\Inheritance\ExampleTest;
it('passes', function () {
expect(Expectation::class)->toHaveAllMethodsDocumented();
});
it('fails', function () {
expect(ExampleTest::class)->toHaveAllMethodsDocumented();
})->throws(ArchExpectationFailedException::class);

View File

@ -0,0 +1,13 @@
<?php
use Pest\Arch\Exceptions\ArchExpectationFailedException;
use Pest\Expectation;
use Tests\Fixtures\Inheritance\ExampleTest;
it('passes', function () {
expect(Expectation::class)->toHaveAllPropertiesDocumented();
});
it('fails', function () {
expect(ExampleTest::class)->toHaveAllPropertiesDocumented();
})->throws(ArchExpectationFailedException::class);

View File

@ -4,7 +4,7 @@ use Pest\Arch\Exceptions\ArchExpectationFailedException;
use Pest\Expectation; use Pest\Expectation;
it('passes', function () { it('passes', function () {
expect(Expectation::class)->toHaveLineCountLessThan(1000); expect(Expectation::class)->toHaveLineCountLessThan(2000);
}); });
it('fails', function () { it('fails', function () {

View File

@ -4,6 +4,8 @@ namespace Tests\Fixtures\Inheritance;
class ExampleTest extends Base\ExampleTest class ExampleTest extends Base\ExampleTest
{ {
protected $foo;
public function testExample() public function testExample()
{ {
$this->assertTrue(true); $this->assertTrue(true);

View File

@ -16,7 +16,7 @@ $run = function () {
test('parallel', function () use ($run) { test('parallel', function () use ($run) {
expect($run('--exclude-group=integration')) expect($run('--exclude-group=integration'))
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 19 skipped, 1054 passed (2582 assertions)') ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 19 skipped, 1058 passed (2588 assertions)')
->toContain('Parallel: 3 processes'); ->toContain('Parallel: 3 processes');
})->skipOnWindows(); })->skipOnWindows();