Write test for the Description object and stabilize the API

This commit is contained in:
Mike van Riel
2015-06-13 21:25:21 +02:00
committed by Mike van Riel
parent 53d5adb324
commit c24f8ead92
2 changed files with 86 additions and 74 deletions
+53 -7
View File
@@ -14,29 +14,65 @@ namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Description\Formatter; use phpDocumentor\Reflection\DocBlock\Description\Formatter;
use phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter; use phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter;
use Webmozart\Assert\Assert;
/**
* Object representing to description for a DocBlock.
*
* A Description object can consist of plain text but can also include tags. A Description Formatter can then combine
* a body template with sprintf-style placeholders together with formatted tags in order to reconstitute a complete
* description text using the format that you would prefer.
*
* Because parsing a Description text can be a verbose process this is handled by the {@see DescriptionFactory}. It is
* thus recommended to use that to create a Description object, like this:
*
* $description = $descriptionFactory->create('This is a {@see Description}', $context);
*
* The description factory will interpret the given body and create a body template and list of tags from them, and pass
* that onto the constructor if this class.
*
* > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace
* > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial
* > type names and FQSENs.
*
* If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this:
*
* $description = new Description(
* 'This is a %1$s',
* [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ]
* );
*
* It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object
* is mainly responsible for rendering.
*
* @see DescriptionFactory to create a new Description.
* @see Description\Formatter for the formatting of the body and tags.
*/
class Description class Description
{ {
/** @var string */ /** @var string */
private $body; private $bodyTemplate;
/** @var Tag[] */ /** @var Tag[] */
private $tags; private $tags;
/** /**
* Initializes a this object with a series of tokens of which a description consists. * Initializes a Description with its body (template) and a listing of the tags used in the body template.
* *
* @param string $body * @param string $bodyTemplate
* @param Tag[] $tags * @param Tag[] $tags
*/ */
public function __construct($body, array $tags = []) public function __construct($bodyTemplate, array $tags = [])
{ {
$this->body = $body; Assert::string($bodyTemplate);
$this->bodyTemplate = $bodyTemplate;
$this->tags = $tags; $this->tags = $tags;
} }
/** /**
* Renders this description as a string where the provided formatter will format tags for the expected output. * Renders this description as a string where the provided formatter will format the tags in the expected string
* format.
* *
* @param Formatter|null $formatter * @param Formatter|null $formatter
* *
@@ -48,6 +84,16 @@ class Description
$formatter = new PassthroughFormatter(); $formatter = new PassthroughFormatter();
} }
return vsprintf($this->body, $formatter->format($this->tags)); return vsprintf($this->bodyTemplate, $formatter->format($this->tags));
}
/**
* Returns a plain string representation of this description.
*
* @return string
*/
public function __toString()
{
return $this->render();
} }
} }
+33 -67
View File
@@ -13,8 +13,8 @@
namespace phpDocumentor\Reflection\DocBlock; namespace phpDocumentor\Reflection\DocBlock;
use Mockery as m; use Mockery as m;
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated; use phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter;
use phpDocumentor\Reflection\DocBlock\Tags\Link; use phpDocumentor\Reflection\DocBlock\Tags\Other;
/** /**
* @coversDefaultClass \phpDocumentor\Reflection\DocBlock\Description * @coversDefaultClass \phpDocumentor\Reflection\DocBlock\Description
@@ -22,88 +22,54 @@ use phpDocumentor\Reflection\DocBlock\Tags\Link;
class DescriptionTest extends \PHPUnit_Framework_TestCase class DescriptionTest extends \PHPUnit_Framework_TestCase
{ {
/** /**
* @param array $examples
* @dataProvider provideExampleDescriptions
* @covers ::__construct * @covers ::__construct
* @covers ::render * @covers ::render
* @covers ::parse * @uses \phpDocumentor\Reflection\DocBlock\Tags\Other
* @uses phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter * @uses \phpDocumentor\Reflection\DocBlock\Tags\BaseTag
* @uses phpDocumentor\Reflection\DocBlock\Tag * @uses \phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter
* @uses phpDocumentor\Reflection\DocBlock\Tags\Link
*/ */
public function testParsesDescription($example) public function testDescriptionCanRenderUsingABodyWithPlaceholdersAndTags()
{ {
$object = new Description($example); $body = 'This is a %1$s body.';
$expected = 'This is a {@internal significant } body.';
$tags = [new Other('internal', new Description('significant '))];
$this->assertSame($example, $object->render()); $fixture = new Description($body, $tags);
// without formatter (thus the PassthroughFormatter by default)
$this->assertSame($expected, $fixture->render());
// with a custom formatter
$formatter = m::mock(PassthroughFormatter::class);
$formatter->shouldReceive('format')->with($tags)->andReturn(['{@internal significant }']);
$this->assertSame($expected, $fixture->render($formatter));
} }
/** /**
* @covers ::__construct * @covers ::__construct
* @covers ::render * @covers ::render
* @covers ::parse * @covers ::__toString
* @uses phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter * @uses \phpDocumentor\Reflection\DocBlock\Tags\Other
* @uses \phpDocumentor\Reflection\DocBlock\Tags\BaseTag
* @uses \phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter
*/ */
public function testInlineTagEscapingSequence() public function testDescriptionCanBeCastToString()
{ {
$fixture = 'This is text for a description with literal {{@}link}.'; $body = 'This is a %1$s body.';
$expected = 'This is text for a description with literal {@link}.'; $expected = 'This is a {@internal significant } body.';
$object = new Description($fixture); $tags = [new Other('internal', new Description('significant '))];
$this->assertSame($expected, $object->render());
$fixture = new Description($body, $tags);
$this->assertSame($expected, (string)$fixture);
} }
/** /**
* @covers ::__construct * @covers ::__construct
* @covers ::render * @expectedException \InvalidArgumentException
* @covers ::parse
* @uses phpDocumentor\Reflection\DocBlock\Tag
* @uses phpDocumentor\Reflection\DocBlock\Tags\Version
* @uses phpDocumentor\Reflection\DocBlock\Tags\Link
*/ */
public function testFormatterReceivesContentsAsTokens() public function testBodyTemplateMustBeAString()
{ {
$fixture = <<<DESCRIPTION new Description([]);
This is a description with {a} {@link http://phpdoc.org/ link} or {@deprecated inline tag with {@link http://phpdoc.org
another link} in it}. Here is a solitary } and a { to test the regex. We can also escape at-signs like this
{@}example.com or {{@}link}.
DESCRIPTION;
$expected = [
'This is a description with {a} ',
Link::create('@link http://phpdoc.org/ link'),
' or ',
Deprecated::create("@deprecated inline tag with {@link http://phpdoc.org\nanother link} in it"),
". Here is a solitary } and a { to test the regex. We can also escape at-signs like this\n"
. "@example.com or {@link}."
];
$formatter = m::mock('phpDocumentor\Reflection\DocBlock\Description\Formatter');
$formatter->shouldReceive('format')->with($expected)->andReturn($fixture);
$object = new Description($fixture);
$this->assertSame($fixture, $object->render($formatter));
}
/**
* Provides a series of example strings that the parser should correctly interpret and return.
*
* @return string[][]
*/
public function provideExampleDescriptions()
{
return [
['This is text for a description.'],
['This is text for a {@link http://phpdoc.org/ description} that uses an inline tag.'],
['{@link http://phpdoc.org/ This} is text for a description that starts with an inline tag.'],
[
'This is text for a description with {@internal inline tag with {@link http://phpdoc.org another '
. 'inline tag} in it}.'
],
['This is text for a description containing { that is literal.'],
['This is text for a description containing {@internal inline tag that has { that is literal}.'],
['This is text for a description with {} that is not a tag.'],
['This is text for a description with {@internal inline tag with {} that is not an inline tag}.'],
['This is text for a description with an {@internal inline tag with literal {{@}link{} in it}.']
];
} }
} }