From db2523947e9807038b53dfed30470f4b2ce8ac56 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 17 Dec 2025 07:37:47 +0700 Subject: [PATCH 1/3] Add unit test for TicketSwapErrorFormatter->formatErrors() --- tests/TicketSwapErrorFormatterTest.php | 109 +++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 8 deletions(-) diff --git a/tests/TicketSwapErrorFormatterTest.php b/tests/TicketSwapErrorFormatterTest.php index cd9321c..34810df 100644 --- a/tests/TicketSwapErrorFormatterTest.php +++ b/tests/TicketSwapErrorFormatterTest.php @@ -4,10 +4,12 @@ namespace TicketSwap\PHPStanErrorFormatter; +use PHPStan\Analyser\Error; use PHPStan\Command\AnalysisResult; use PHPStan\Command\ErrorFormatter\ErrorFormatter; use PHPStan\Command\Output; use PHPStan\File\NullRelativePathHelper; +use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; /** @@ -15,7 +17,7 @@ */ final class TicketSwapErrorFormatterTest extends TestCase { - private const PHPSTOR_EDITOR_URL = 'phpstorm://open?file=%file%&line=%line%'; + private const PHPSTORM_EDITOR_URL = 'phpstorm://open?file=%file%&line=%line%'; private TicketSwapErrorFormatter $formatter; @@ -31,7 +33,7 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in return 0; } }, - self::PHPSTOR_EDITOR_URL, + self::PHPSTORM_EDITOR_URL, [] ); } @@ -85,7 +87,7 @@ public static function provideLinkFormats() : iterable 20, '/www/project/src/Core/Admin/Controller/Dashboard/User/AddUserController.php', 'src/Core/Admin/Controller/Dashboard/User/AddUserController.php', - self::PHPSTOR_EDITOR_URL, + self::PHPSTORM_EDITOR_URL, true, ]; yield [ @@ -94,7 +96,7 @@ public static function provideLinkFormats() : iterable 20, '/www/project/src/Core/Admin/Controller/Dashboard/User/AddUserController.php', 'src/Core/Admin/Controller/Dashboard/User/AddUserController.php', - self::PHPSTOR_EDITOR_URL, + self::PHPSTORM_EDITOR_URL, true, ]; yield [ @@ -103,7 +105,7 @@ public static function provideLinkFormats() : iterable 20, '/www/project/src/Core/Admin/Controller/Dashboard/User/AddUserController.php', 'src/Core/Admin/Controller/Dashboard/User/AddUserController.php', - self::PHPSTOR_EDITOR_URL, + self::PHPSTORM_EDITOR_URL, true, ]; yield [ @@ -112,7 +114,7 @@ public static function provideLinkFormats() : iterable 20, '/www/project/src/Core/Admin/Controller/Dashboard/User/AddUserController.php', 'src/Core/Admin/Controller/Dashboard/User/AddUserController.php', - self::PHPSTOR_EDITOR_URL, + self::PHPSTORM_EDITOR_URL, true, ]; yield [ @@ -121,7 +123,7 @@ public static function provideLinkFormats() : iterable 20, '/www/project/src/Core/Admin/Controller/Dashboard/User/AddUserController.php', 'src/Core/Admin/Controller/Dashboard/User/AddUserController.php', - self::PHPSTOR_EDITOR_URL, + self::PHPSTORM_EDITOR_URL, true, ]; yield [ @@ -130,7 +132,7 @@ public static function provideLinkFormats() : iterable 20, '/www/project/src/Core/Admin/Controller/Dashboard/User/AddUserController.php', 'src/Core/Admin/Controller/Dashboard/User/AddUserController.php', - self::PHPSTOR_EDITOR_URL, + self::PHPSTORM_EDITOR_URL, false, ]; yield [ @@ -255,4 +257,95 @@ public function testHighlight(string $expected, string $message, ?string $tip, ? ) ); } + + public function testFormatErrorsNoErrorsWritesNoErrorsAndReturnsZero() : void + { + $analysisResult = new AnalysisResult( + [], + [], + [], + [], + [], + false, + null, + false, + 0, + false, + [], + ); + + $writes = []; + $output = $this->createMock(Output::class); + $output->method('isDecorated')->willReturn(true); + $output->method('isVerbose')->willReturn(false); + $output->method('isVeryVerbose')->willReturn(false); + $output->method('isDebug')->willReturn(false); + $output->method('getStyle')->willReturnCallback(function () { return Stub::returnValueMap([]); }); + $output->method('writeLineFormatted')->willReturnCallback(function (string $line) use (&$writes) : void { $writes[] = $line; }); + $output->method('writeRaw')->willReturnCallback(function (string $line) use (&$writes) : void { $writes[] = $line; }); + + $result = $this->formatter->formatErrors($analysisResult, $output); + + self::assertSame(0, $result); + self::assertSame(['No errors', ''], $writes); + } + + public function testFormatErrorsWithErrorsPrintsMessagesLinksSummaryAndReturnsOne() : void + { + $fileError = new Error( + 'Parameter #1 $var expects string, int given.', + '/www/project/src/Foo/Bar.php', + 12, + null, + '/www/project/src/Foo/Bar.php', + null, + 'Adjust in %configurationFile%', + null, + null, + 'argument.type', + [], + ); + + $analysisResult = new AnalysisResult( + [$fileError], + [], + [], + [], + [], + false, + '/www/project/phpstan.neon', + false, + 0, + false, + [], + ); + + $writes = []; + $output = $this->createMock(Output::class); + $output->method('isDecorated')->willReturn(true); + $output->method('isVerbose')->willReturn(false); + $output->method('isVeryVerbose')->willReturn(false); + $output->method('isDebug')->willReturn(false); + $output->method('getStyle')->willReturnCallback(function () { return Stub::returnValueMap([]); }); + $output->method('writeLineFormatted')->willReturnCallback(function (string $line) use (&$writes) : void { $writes[] = $line; }); + $output->method('writeRaw')->willReturnCallback(function (string $line) use (&$writes) : void { $writes[] = $line; }); + + $result = $this->formatter->formatErrors($analysisResult, $output); + + self::assertSame(1, $result); + + $expectedLink = "↳ /www/project/.../Foo/Bar.php:12\n"; + $expectedSummary = 'Found 1 error'; + + $linkFound = false; + foreach ($writes as $w) { + if (strpos($w, $expectedLink) !== false) { + $linkFound = true; + break; + } + } + self::assertTrue($linkFound, 'Expected link not found. Output writes: ' . implode("\n---\n", $writes)); + self::assertContains($expectedSummary, $writes); + self::assertContains('', $writes); + } } From 8c7f60b140e0a58a75e38a1a2e501d5bd2b3959f Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 18 Dec 2025 06:30:30 +0700 Subject: [PATCH 2/3] update to use class implements output --- tests/TicketSwapErrorFormatterTest.php | 236 ++++++++++++++++++++++--- 1 file changed, 216 insertions(+), 20 deletions(-) diff --git a/tests/TicketSwapErrorFormatterTest.php b/tests/TicketSwapErrorFormatterTest.php index 34810df..b9ad4ab 100644 --- a/tests/TicketSwapErrorFormatterTest.php +++ b/tests/TicketSwapErrorFormatterTest.php @@ -6,10 +6,11 @@ use PHPStan\Analyser\Error; use PHPStan\Command\AnalysisResult; +use PHPStan\Command\ProgressBar; use PHPStan\Command\ErrorFormatter\ErrorFormatter; use PHPStan\Command\Output; +use PHPStan\Command\OutputStyle; use PHPStan\File\NullRelativePathHelper; -use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; /** @@ -274,20 +275,117 @@ public function testFormatErrorsNoErrorsWritesNoErrorsAndReturnsZero() : void [], ); - $writes = []; - $output = $this->createMock(Output::class); - $output->method('isDecorated')->willReturn(true); - $output->method('isVerbose')->willReturn(false); - $output->method('isVeryVerbose')->willReturn(false); - $output->method('isDebug')->willReturn(false); - $output->method('getStyle')->willReturnCallback(function () { return Stub::returnValueMap([]); }); - $output->method('writeLineFormatted')->willReturnCallback(function (string $line) use (&$writes) : void { $writes[] = $line; }); - $output->method('writeRaw')->willReturnCallback(function (string $line) use (&$writes) : void { $writes[] = $line; }); + $writesWrapper = ['writes' => []]; + $output = new class($writesWrapper) implements Output { + private array $writesWrapper; + + public function __construct(array $writesWrapper) + { + $this->writesWrapper = $writesWrapper; + } + + public function writeFormatted(string $message) : void + { + } + + public function writeLineFormatted(string $message) : void + { + $this->writesWrapper['writes'][] = $message; + } + + public function writeRaw(string $message) : void + { + $this->writesWrapper['writes'][] = $message; + } + + public function getStyle() : OutputStyle + { + return new class() implements OutputStyle { + public function title(string $message) : void + { + } + + public function section(string $message) : void + { + } + + public function listing(array $elements) : void + { + } + + public function success(string $message) : void + { + } + + public function error(string $message) : void + { + } + + public function warning(string $message) : void + { + } + + public function note(string $message) : void + { + } + + public function caution(string $message) : void + { + } + + public function table(array $headers, array $rows) : void + { + } + + public function createProgressBar(int $max = 0) : ProgressBar + { + return new class() implements ProgressBar { + public function start(int $max = 0) : void + { + } + + public function advance(int $step = 1) : void + { + } + + public function finish() : void + { + } + }; + } + }; + } + + public function isVerbose() : bool + { + return false; + } + + public function isVeryVerbose() : bool + { + return false; + } + + public function isDebug() : bool + { + return false; + } + + public function isDecorated() : bool + { + return true; + } + + public function getWrites() : array + { + return $this->writesWrapper['writes']; + } + }; $result = $this->formatter->formatErrors($analysisResult, $output); self::assertSame(0, $result); - self::assertSame(['No errors', ''], $writes); + self::assertSame(['No errors', ''], $output->getWrites()); } public function testFormatErrorsWithErrorsPrintsMessagesLinksSummaryAndReturnsOne() : void @@ -320,15 +418,112 @@ public function testFormatErrorsWithErrorsPrintsMessagesLinksSummaryAndReturnsOn [], ); - $writes = []; - $output = $this->createMock(Output::class); - $output->method('isDecorated')->willReturn(true); - $output->method('isVerbose')->willReturn(false); - $output->method('isVeryVerbose')->willReturn(false); - $output->method('isDebug')->willReturn(false); - $output->method('getStyle')->willReturnCallback(function () { return Stub::returnValueMap([]); }); - $output->method('writeLineFormatted')->willReturnCallback(function (string $line) use (&$writes) : void { $writes[] = $line; }); - $output->method('writeRaw')->willReturnCallback(function (string $line) use (&$writes) : void { $writes[] = $line; }); + $writesWrapper = ['writes' => []]; + $output = new class($writesWrapper) implements Output { + private array $writesWrapper; + + public function __construct(array $writesWrapper) + { + $this->writesWrapper = $writesWrapper; + } + + public function writeFormatted(string $message) : void + { + } + + public function writeLineFormatted(string $message) : void + { + $this->writesWrapper['writes'][] = $message; + } + + public function writeRaw(string $message) : void + { + $this->writesWrapper['writes'][] = $message; + } + + public function getStyle() : OutputStyle + { + return new class() implements OutputStyle { + public function title(string $message) : void + { + } + + public function section(string $message) : void + { + } + + public function listing(array $elements) : void + { + } + + public function success(string $message) : void + { + } + + public function error(string $message) : void + { + } + + public function warning(string $message) : void + { + } + + public function note(string $message) : void + { + } + + public function caution(string $message) : void + { + } + + public function table(array $headers, array $rows) : void + { + } + + public function createProgressBar(int $max = 0) : ProgressBar + { + return new class() implements ProgressBar { + public function start(int $max = 0) : void + { + } + + public function advance(int $step = 1) : void + { + } + + public function finish() : void + { + } + }; + } + }; + } + + public function isVerbose() : bool + { + return false; + } + + public function isVeryVerbose() : bool + { + return false; + } + + public function isDebug() : bool + { + return false; + } + + public function isDecorated() : bool + { + return true; + } + + public function getWrites() : array + { + return $this->writesWrapper['writes']; + } + }; $result = $this->formatter->formatErrors($analysisResult, $output); @@ -337,6 +532,7 @@ public function testFormatErrorsWithErrorsPrintsMessagesLinksSummaryAndReturnsOn $expectedLink = "↳ /www/project/.../Foo/Bar.php:12\n"; $expectedSummary = 'Found 1 error'; + $writes = $output->getWrites(); $linkFound = false; foreach ($writes as $w) { if (strpos($w, $expectedLink) !== false) { From 4ff4752127e8c6b6a0813a89af9001cf5446ab05 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 18 Dec 2025 06:34:53 +0700 Subject: [PATCH 3/3] final touch: make separate method for create Output --- tests/TicketSwapErrorFormatterTest.php | 148 +++++-------------------- 1 file changed, 26 insertions(+), 122 deletions(-) diff --git a/tests/TicketSwapErrorFormatterTest.php b/tests/TicketSwapErrorFormatterTest.php index b9ad4ab..2b85231 100644 --- a/tests/TicketSwapErrorFormatterTest.php +++ b/tests/TicketSwapErrorFormatterTest.php @@ -259,24 +259,12 @@ public function testHighlight(string $expected, string $message, ?string $tip, ? ); } - public function testFormatErrorsNoErrorsWritesNoErrorsAndReturnsZero() : void + /** + * @param array{writes: array} $writesWrapper + */ + private function createOutput(array $writesWrapper) : Output { - $analysisResult = new AnalysisResult( - [], - [], - [], - [], - [], - false, - null, - false, - 0, - false, - [], - ); - - $writesWrapper = ['writes' => []]; - $output = new class($writesWrapper) implements Output { + return new class($writesWrapper) implements Output { private array $writesWrapper; public function __construct(array $writesWrapper) @@ -381,6 +369,26 @@ public function getWrites() : array return $this->writesWrapper['writes']; } }; + } + + public function testFormatErrorsNoErrorsWritesNoErrorsAndReturnsZero() : void + { + $analysisResult = new AnalysisResult( + [], + [], + [], + [], + [], + false, + null, + false, + 0, + false, + [], + ); + + $writesWrapper = ['writes' => []]; + $output = $this->createOutput($writesWrapper); $result = $this->formatter->formatErrors($analysisResult, $output); @@ -419,111 +427,7 @@ public function testFormatErrorsWithErrorsPrintsMessagesLinksSummaryAndReturnsOn ); $writesWrapper = ['writes' => []]; - $output = new class($writesWrapper) implements Output { - private array $writesWrapper; - - public function __construct(array $writesWrapper) - { - $this->writesWrapper = $writesWrapper; - } - - public function writeFormatted(string $message) : void - { - } - - public function writeLineFormatted(string $message) : void - { - $this->writesWrapper['writes'][] = $message; - } - - public function writeRaw(string $message) : void - { - $this->writesWrapper['writes'][] = $message; - } - - public function getStyle() : OutputStyle - { - return new class() implements OutputStyle { - public function title(string $message) : void - { - } - - public function section(string $message) : void - { - } - - public function listing(array $elements) : void - { - } - - public function success(string $message) : void - { - } - - public function error(string $message) : void - { - } - - public function warning(string $message) : void - { - } - - public function note(string $message) : void - { - } - - public function caution(string $message) : void - { - } - - public function table(array $headers, array $rows) : void - { - } - - public function createProgressBar(int $max = 0) : ProgressBar - { - return new class() implements ProgressBar { - public function start(int $max = 0) : void - { - } - - public function advance(int $step = 1) : void - { - } - - public function finish() : void - { - } - }; - } - }; - } - - public function isVerbose() : bool - { - return false; - } - - public function isVeryVerbose() : bool - { - return false; - } - - public function isDebug() : bool - { - return false; - } - - public function isDecorated() : bool - { - return true; - } - - public function getWrites() : array - { - return $this->writesWrapper['writes']; - } - }; + $output = $this->createOutput($writesWrapper); $result = $this->formatter->formatErrors($analysisResult, $output);