assertSame($expected, self::trimLines($formattedWithoutColors)); $this->assertNotEquals($expected, self::trimLines($formatted)); } public function reflectors() { $expectClass = <<<'EOS' 14: class SomeClass 15: { 16: const SOME_CONST = 'some const'; 17: private $someProp = 'some prop'; 18: 19: public function someMethod($someParam) 20: { 21: return 'some method'; 22: } 23: 24: public static function someClosure() 25: { 26: return function () { 27: return 'some closure'; 28: }; 29: } 30: } EOS; $expectMethod = <<<'EOS' 19: public function someMethod($someParam) 20: { 21: return 'some method'; 22: } EOS; $expectClosure = <<<'EOS' 26: return function () { 27: return 'some closure'; 28: }; EOS; return [ [new \ReflectionClass(SomeClass::class), $expectClass], [new \ReflectionObject(new SomeClass()), $expectClass], [new \ReflectionMethod(SomeClass::class, 'someMethod'), $expectMethod], [new \ReflectionFunction(SomeClass::someClosure()), $expectClosure], ]; } /** * @dataProvider invalidReflectors */ public function testCodeFormatterThrowsExceptionForReflectorsItDoesntUnderstand($reflector) { $this->expectException(\Psy\Exception\RuntimeException::class); CodeFormatter::format($reflector); $this->fail(); } public function invalidReflectors() { $reflectors = [ [new \ReflectionExtension('json')], [new \ReflectionParameter([SomeClass::class, 'someMethod'], 'someParam')], [new \ReflectionProperty(SomeClass::class, 'someProp')], ]; if (\version_compare(\PHP_VERSION, '7.1.0', '>=')) { $reflectors[] = [new \ReflectionClassConstant(SomeClass::class, 'SOME_CONST')]; } return $reflectors; } /** * @dataProvider filenames */ public function testCodeFormatterThrowsExceptionForMissingFile($filename) { $this->expectException(\Psy\Exception\RuntimeException::class); $reflector = $this->getMockBuilder(\ReflectionClass::class) ->disableOriginalConstructor() ->getMock(); $reflector ->expects($this->once()) ->method('getFileName') ->willReturn($filename); CodeFormatter::format($reflector); $this->fail(); } public function filenames() { return [[false], ['not a file']]; } /** * @dataProvider validCode */ public function testFormatCode($code, $startLine, $endLine, $markLine, $expected) { $formatted = CodeFormatter::formatCode($code, $startLine, $endLine, $markLine); $formattedWithoutColors = self::stripTags($formatted); $this->assertSame($expected, self::trimLines($formattedWithoutColors)); $this->assertNotEquals($expected, self::trimLines($formatted)); } public function validCode() { $someCode = <<<'EOS' 20: { 21: return 'some method'; 22: } EOS; return [ [$someCode, 1, null, null, $someCodeExpected], [$someCode, 19, 22, null, $someCodeSnippet], [$someCode, 19, 22, 20, $someCodeSnippetWithMarker], ]; } /** * Test some smaller ones with spans... we don't want the test to be tooo flaky so we don't * explicitly test the exact formatting above. Just to be safe, let's add a couple of tests * that *do* expect specific formatting. * * @dataProvider smallCodeLines */ public function testFormatSmallCodeLines($code, $startLine, $endLine, $markLine, $expected) { $formatted = CodeFormatter::formatCode($code, $startLine, $endLine, $markLine); $this->assertSame($expected, self::trimLines($formatted)); } public function smallCodeLines() { return [ ['1: \\= 42;'], ['1: \\echo "yay $foo!";'], // Start and end lines ["1: \\echo \'wat\';'], ["2: $foo = 42;'], ["2: $foo = 42;'], // With a line marker ["> : $foo = 42;'], // Line marker before or after our line range ["2: $foo = 42;'], ["1: \echo \'wat\';'], ]; } /** * Remove tags from formatted output. This is kind of ugly o_O. */ private static function stripTags($code) { $tagRegex = '[a-z][^<>]*+'; $output = \preg_replace("#<(($tagRegex) | /($tagRegex)?)>#ix", '', $code); return \str_replace('\\<', '<', $output); } private static function trimLines($code) { return \rtrim(\implode("\n", \array_map('rtrim', \explode("\n", $code)))); } }