Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 93 additions & 37 deletions src/Render/TerminalInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,38 @@

namespace Testo\Render;

use Testo\Interceptor\TestCaseRunInterceptor;
use Testo\Interceptor\TestRunInterceptor;
use Testo\Interceptor\TestSuiteRunInterceptor;
use Testo\Common\Container;
use Testo\Config\EventListenerCollector;
use Testo\Config\PluginConfigurator;
use Testo\Render\Terminal\ColorMode;
use Testo\Render\Terminal\Style;
use Testo\Render\Terminal\TerminalLogger;
use Testo\Test\Dto\CaseInfo;
use Testo\Test\Dto\CaseResult;
use Testo\Test\Dto\SuiteInfo;
use Testo\Test\Dto\SuiteResult;
use Testo\Test\Dto\TestInfo;
use Testo\Test\Dto\TestResult;
use Testo\Test\Event\Test\TestBatchFinished;
use Testo\Test\Event\Test\TestBatchStarting;
use Testo\Test\Event\Test\TestDataSetFinished;
use Testo\Test\Event\Test\TestDataSetStarting;
use Testo\Test\Event\Test\TestPipelineFinished;
use Testo\Test\Event\TestCase\TestCaseFinished;
use Testo\Test\Event\TestCase\TestCaseStarting;
use Testo\Test\Event\TestSuite\TestSuiteFinished;
use Testo\Test\Event\TestSuite\TestSuiteStarting;

/**
* Terminal interceptor for rendering test results with configurable output.
*
* Implements StdoutRenderer to ensure only one stdout renderer is active.
* Supports multiple output formats (Compact, Verbose, Dots) and color modes.
*/
final class TerminalInterceptor implements
TestRunInterceptor,
TestCaseRunInterceptor,
TestSuiteRunInterceptor
final class TerminalInterceptor implements PluginConfigurator
{
/**
* Tracks whether we're inside a DataProvider batch.
*
* @var array<non-empty-string, bool>
*/
private array $isBatch = [];

public function __construct(
private readonly TerminalLogger $logger,
ColorMode $colorMode = ColorMode::Always,
Expand All @@ -36,48 +44,96 @@ public function __construct(
Style::setColorsEnabled($colorMode->shouldUseColors());
}

public function runTest(TestInfo $info, callable $next): TestResult
public function configure(Container $container): void
{
$this->logger->testStartedFromInfo($info);
$listeners = $container->get(EventListenerCollector::class);

// Test Pipeline events (lifecycle of entire test through all interceptors)
$listeners->addListener(TestPipelineFinished::class, $this->onTestPipelineFinished(...));

// Test Batch events (for DataProvider)
$listeners->addListener(TestBatchStarting::class, $this->onTestBatchStarting(...));
$listeners->addListener(TestBatchFinished::class, $this->onTestBatchFinished(...));

// DataSet events (for individual datasets within DataProvider)
$listeners->addListener(TestDataSetStarting::class, $this->onTestDataSetStarting(...));
$listeners->addListener(TestDataSetFinished::class, $this->onTestDataSetFinished(...));

// TestCase events
$listeners->addListener(TestCaseStarting::class, $this->onTestCaseStarting(...));
$listeners->addListener(TestCaseFinished::class, $this->onTestCaseFinished(...));

$start = \microtime(true);
/** @var TestResult $result */
$result = $next($info);
$duration = (int) \round((\microtime(true) - $start) * 1000);
// TestSuite events
$listeners->addListener(TestSuiteStarting::class, $this->onTestSuiteStarting(...));
$listeners->addListener(TestSuiteFinished::class, $this->onTestSuiteFinished(...));
}

private static function getId(TestInfo $testInfo): string
{
return \spl_object_hash($testInfo->testDefinition);
}

private function onTestPipelineFinished(TestPipelineFinished $event): void
{
// Check if this test was inside a DataProvider batch
$id = self::getId($event->testInfo);
if (isset($this->isBatch[$id])) {
// DataProvider test - already handled in dataset events
unset($this->isBatch[$id]);
return;
}

$this->logger->handleTestResult($result, $duration);
return $result;
// Regular test without DataProvider - log it now
$this->logger->testStartedFromInfo($event->testInfo);
$duration = (int) $event->testResult->getAttribute('duration');
$this->logger->handleTestResult($event->testResult, $duration);
}

public function runTestCase(CaseInfo $info, callable $next): CaseResult
private function onTestBatchStarting(TestBatchStarting $event): void
{
$this->logger->caseStartedFromInfo($info);
// Mark that we're inside a batch
$id = self::getId($event->testInfo);
$this->isBatch[$id] = true;
}

/** @var CaseResult $result */
$result = $next($info);
private function onTestBatchFinished(TestBatchFinished $event): void
{
// Batch finished - cleanup is done in TestPipelineFinished
}

$this->logger->handleCaseResult($info, $result);
return $result;
private function onTestDataSetStarting(TestDataSetStarting $event): void
{
// Log individual dataset start
$this->logger->testStartedFromInfo($event->testInfo);
}

public function runTestSuite(SuiteInfo $info, callable $next): SuiteResult
private function onTestDataSetFinished(TestDataSetFinished $event): void
{
$this->logger->suiteStartedFromInfo($info);
// Handle individual dataset result
$duration = (int) $event->testResult->getAttribute('duration');
$this->logger->handleTestResult($event->testResult, $duration);
}

/** @var SuiteResult $result */
$result = $next($info);
private function onTestCaseStarting(TestCaseStarting $event): void
{
$this->logger->caseStartedFromInfo($event->caseInfo);
}

$this->logger->handleSuiteResult($info, $result);
private function onTestCaseFinished(TestCaseFinished $event): void
{
$this->logger->handleCaseResult($event->caseInfo, $event->caseResult);
}

return $result;
private function onTestSuiteStarting(TestSuiteStarting $event): void
{
$this->logger->suiteStartedFromInfo($event->suiteInfo);
}

/**
* Prints final summary after all tests complete.
* Should be called after test execution finishes.
*/
public function printSummary(): void
private function onTestSuiteFinished(TestSuiteFinished $event): void
{
$this->logger->handleSuiteResult($event->suiteInfo, $event->suiteResult);

// Print final summary after all tests in suite complete
$this->logger->printSummary();
}
}