Completed
Push — master ( 6a88d1...a8dc04 )
by Jaap
21:58 queued 12:04
created

StandardTagFactoryTest::validTagProvider()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 46
rs 9.1781
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of phpDocumentor.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @link http://phpdoc.org
12
 */
13
14
namespace phpDocumentor\Reflection\DocBlock;
15
16
use InvalidArgumentException;
17
use Mockery as m;
18
use phpDocumentor\Reflection\DocBlock\Tags\Author;
19
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
20
use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter;
21
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
22
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
23
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
24
use phpDocumentor\Reflection\DocBlock\Tags\See;
25
use phpDocumentor\Reflection\Fqsen;
26
use phpDocumentor\Reflection\FqsenResolver;
27
use phpDocumentor\Reflection\TypeResolver;
28
use phpDocumentor\Reflection\Types\Context;
29
use PHPUnit\Framework\TestCase;
30
31
/**
32
 * @coversDefaultClass \phpDocumentor\Reflection\DocBlock\StandardTagFactory
33
 * @covers ::<private>
34
 */
35
class StandardTagFactoryTest extends TestCase
36
{
37
    /**
38
     * Call Mockery::close after each test.
39
     */
40
    public function tearDown() : void
41
    {
42
        m::close();
43
    }
44
45
    /**
46
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
47
     * @uses \phpDocumentor\Reflection\DocBlock\Tags\Generic
48
     * @uses \phpDocumentor\Reflection\DocBlock\Tags\BaseTag
49
     * @uses \phpDocumentor\Reflection\DocBlock\Description
50
     *
51
     * @covers ::__construct
52
     * @covers ::create
53
     */
54
    public function testCreatingAGenericTag() : void
55
    {
56
        $expectedTagName         = 'unknown-tag';
57
        $expectedDescriptionText = 'This is a description';
58
        $expectedDescription     = new Description($expectedDescriptionText);
59
        $context                 = new Context('');
60
61
        $descriptionFactory = m::mock(DescriptionFactory::class);
62
        $descriptionFactory
63
            ->shouldReceive('create')
64
            ->once()
65
            ->with($expectedDescriptionText, $context)
66
            ->andReturn($expectedDescription);
67
68
        $tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class));
69
        $tagFactory->addService($descriptionFactory, DescriptionFactory::class);
70
71
        /** @var Generic $tag */
72
        $tag = $tagFactory->create('@' . $expectedTagName . ' This is a description', $context);
73
74
        $this->assertInstanceOf(Generic::class, $tag);
75
        $this->assertSame($expectedTagName, $tag->getName());
76
        $this->assertSame($expectedDescription, $tag->getDescription());
77
    }
78
79
    /**
80
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
81
     * @uses \phpDocumentor\Reflection\DocBlock\Tags\Author
82
     * @uses \phpDocumentor\Reflection\DocBlock\Tags\BaseTag
83
     *
84
     * @covers ::__construct
85
     * @covers ::create
86
     */
87
    public function testCreatingASpecificTag() : void
88
    {
89
        $context    = new Context('');
90
        $tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class));
91
92
        /** @var Author $tag */
93
        $tag = $tagFactory->create('@author Mike van Riel <[email protected]>', $context);
94
95
        $this->assertInstanceOf(Author::class, $tag);
96
        $this->assertSame('author', $tag->getName());
97
    }
98
99
    /**
100
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
101
     * @uses \phpDocumentor\Reflection\DocBlock\Tags\See
102
     * @uses \phpDocumentor\Reflection\DocBlock\Tags\BaseTag
103
     * @uses \phpDocumentor\Reflection\Fqsen
104
     * @uses \phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen
105
     *
106
     * @covers ::__construct
107
     * @covers ::create
108
     */
109
    public function testAnEmptyContextIsCreatedIfNoneIsProvided() : void
110
    {
111
        $fqsen              = '\Tag';
112
        $resolver           = m::mock(FqsenResolver::class)
113
            ->shouldReceive('resolve')
114
            ->with('Tag', m::type(Context::class))
115
            ->andReturn(new Fqsen($fqsen))
116
            ->getMock();
117
        $descriptionFactory = m::mock(DescriptionFactory::class);
118
        $descriptionFactory->shouldIgnoreMissing();
119
120
        $tagFactory = new StandardTagFactory($resolver);
121
        $tagFactory->addService($descriptionFactory, DescriptionFactory::class);
122
123
        /** @var See $tag */
124
        $tag = $tagFactory->create('@see Tag');
125
126
        $this->assertInstanceOf(See::class, $tag);
127
        $this->assertSame($fqsen, (string) $tag->getReference());
128
    }
129
130
    /**
131
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
132
     * @uses \phpDocumentor\Reflection\DocBlock\Tags\Author
133
     * @uses \phpDocumentor\Reflection\DocBlock\Tags\BaseTag
134
     *
135
     * @covers ::__construct
136
     * @covers ::create
137
     */
138
    public function testPassingYourOwnSetOfTagHandlers() : void
139
    {
140
        $context    = new Context('');
141
        $tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class), ['user' => Author::class]);
142
143
        /** @var Author $tag */
144
        $tag = $tagFactory->create('@user Mike van Riel <[email protected]>', $context);
145
146
        $this->assertInstanceOf(Author::class, $tag);
147
        $this->assertSame('author', $tag->getName());
148
    }
149
150
    /**
151
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
152
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
153
     *
154
     * @covers ::create
155
     */
156
    public function testExceptionIsThrownIfProvidedTagIsNotWellformed() : void
157
    {
158
        $this->expectException('InvalidArgumentException');
159
        $this->expectExceptionMessage(
160
            'The tag "@user[myuser" does not seem to be wellformed, please check it for errors'
161
        );
162
        $tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class));
163
        $tagFactory->create('@user[myuser');
164
    }
165
166
    /**
167
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
168
     *
169
     * @covers ::__construct
170
     * @covers ::addParameter
171
     */
172
    public function testAddParameterToServiceLocator() : void
173
    {
174
        $resolver   = m::mock(FqsenResolver::class);
175
        $tagFactory = new StandardTagFactory($resolver);
176
        $tagFactory->addParameter('myParam', 'myValue');
177
178
        $this->assertAttributeSame(
179
            [FqsenResolver::class => $resolver, 'myParam' => 'myValue'],
180
            'serviceLocator',
181
            $tagFactory
182
        );
183
    }
184
185
    /**
186
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
187
     *
188
     * @covers ::addService
189
     */
190
    public function testAddServiceToServiceLocator() : void
191
    {
192
        $service = new PassthroughFormatter();
193
194
        $resolver   = m::mock(FqsenResolver::class);
195
        $tagFactory = new StandardTagFactory($resolver);
196
        $tagFactory->addService($service);
197
198
        $this->assertAttributeSame(
199
            [FqsenResolver::class => $resolver, PassthroughFormatter::class => $service],
200
            'serviceLocator',
201
            $tagFactory
202
        );
203
    }
204
205
    /**
206
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
207
     *
208
     * @covers ::addService
209
     */
210
    public function testInjectConcreteServiceForInterfaceToServiceLocator() : void
211
    {
212
        $interfaceName = Formatter::class;
213
        $service       = new PassthroughFormatter();
214
215
        $resolver   = m::mock(FqsenResolver::class);
216
        $tagFactory = new StandardTagFactory($resolver);
217
        $tagFactory->addService($service, $interfaceName);
218
219
        $this->assertAttributeSame(
220
            [FqsenResolver::class => $resolver, $interfaceName => $service],
221
            'serviceLocator',
222
            $tagFactory
223
        );
224
    }
225
226
    /**
227
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
228
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
229
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::create
230
     * @uses \phpDocumentor\Reflection\DocBlock\Tags\Author
231
     *
232
     * @covers ::registerTagHandler
233
     */
234
    public function testRegisteringAHandlerForANewTag() : void
235
    {
236
        $resolver   = m::mock(FqsenResolver::class);
237
        $tagFactory = new StandardTagFactory($resolver);
238
239
        $tagFactory->registerTagHandler('my-tag', Author::class);
240
241
        // Assert by trying to create one
242
        $tag = $tagFactory->create('@my-tag Mike van Riel <[email protected]>');
243
        $this->assertInstanceOf(Author::class, $tag);
244
    }
245
246
    /**
247
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
248
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
249
     *
250
     * @covers ::registerTagHandler
251
     */
252
    public function testHandlerRegistrationFailsIfProvidedTagNameIsNamespaceButNotFullyQualified() : void
253
    {
254
        $this->expectException('InvalidArgumentException');
255
        $resolver   = m::mock(FqsenResolver::class);
256
        $tagFactory = new StandardTagFactory($resolver);
257
        // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName
258
        $tagFactory->registerTagHandler(\Name\Spaced\Tag::class, Author::class);
259
    }
260
261
    /**
262
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
263
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
264
     *
265
     * @covers ::registerTagHandler
266
     */
267
    public function testHandlerRegistrationFailsIfProvidedHandlerIsEmpty() : void
268
    {
269
        $this->expectException('InvalidArgumentException');
270
        $resolver   = m::mock(FqsenResolver::class);
271
        $tagFactory = new StandardTagFactory($resolver);
272
        $tagFactory->registerTagHandler('my-tag', '');
273
    }
274
275
    /**
276
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
277
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
278
     *
279
     * @covers ::registerTagHandler
280
     */
281
    public function testHandlerRegistrationFailsIfProvidedHandlerIsNotAnExistingClassName() : void
282
    {
283
        $this->expectException('InvalidArgumentException');
284
        $resolver   = m::mock(FqsenResolver::class);
285
        $tagFactory = new StandardTagFactory($resolver);
286
        $tagFactory->registerTagHandler('my-tag', 'IDoNotExist');
287
    }
288
289
    /**
290
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
291
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
292
     *
293
     * @covers ::registerTagHandler
294
     */
295
    public function testHandlerRegistrationFailsIfProvidedHandlerDoesNotImplementTheTagInterface() : void
296
    {
297
        $this->expectException('InvalidArgumentException');
298
        $resolver   = m::mock(FqsenResolver::class);
299
        $tagFactory = new StandardTagFactory($resolver);
300
        $tagFactory->registerTagHandler('my-tag', 'stdClass');
301
    }
302
303
    /**
304
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
305
     * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
306
     * @uses \phpDocumentor\Reflection\Docblock\Description
307
     * @uses \phpDocumentor\Reflection\Docblock\Tags\Return_
308
     * @uses \phpDocumentor\Reflection\Docblock\Tags\BaseTag
309
     *
310
     * @covers ::create
311
     */
312
    public function testReturnTagIsMappedCorrectly() : void
313
    {
314
        $context = new Context('');
315
316
        $descriptionFactory = m::mock(DescriptionFactory::class);
317
        $descriptionFactory
318
            ->shouldReceive('create')
319
            ->once()
320
            ->with('', $context)
321
            ->andReturn(new Description(''));
322
323
        $typeResolver = new TypeResolver();
324
325
        $tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class));
326
        $tagFactory->addService($descriptionFactory, DescriptionFactory::class);
327
        $tagFactory->addService($typeResolver, TypeResolver::class);
328
329
        /** @var Return_ $tag */
330
        $tag = $tagFactory->create('@return mixed', $context);
331
332
        $this->assertInstanceOf(Return_::class, $tag);
333
        $this->assertSame('return', $tag->getName());
334
    }
335
336
    public function testInvalidTagIsReturnedOnFailure() : void
337
    {
338
        $tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class));
339
340
        /** @var InvalidTag $tag */
341
        $tag = $tagFactory->create('@see $name some invalid tag');
342
343
        $this->assertInstanceOf(InvalidTag::class, $tag);
344
    }
345
346
    /**
347
     * @dataProvider validTagProvider
348
     */
349
    public function testValidFormattedTags(string $input, string $tagName, string $render) : void
350
    {
351
        $fqsenResolver = $this->prophesize(FqsenResolver::class);
352
        $tagFactory = new StandardTagFactory($fqsenResolver->reveal());
353
        $tagFactory->registerTagHandler('tag', Generic::class);
354
        $tag = $tagFactory->create($input);
355
356
        self::assertSame($tagName, $tag->getName());
357
        self::assertSame($render, $tag->render());
358
    }
359
360
    /**
361
     * @return string[][]
362
     *
363
     * @phpstan-return array<string, array<int, string>>
364
     */
365
    public function validTagProvider() : array
366
    {
367
        //rendered result is adding a space, because the tags are not rendered properly.
368
        return [
369
            'tag without body' => [
370
                '@tag',
371
                'tag',
372
                '@tag',
373
            ],
374
            'tag specialization' => [
375
                '@tag:some-spec body',
376
                'tag:some-spec',
377
                '@tag:some-spec body',
378
            ],
379
            'tag specialization followed by parenthesis' => [
380
                '@tag:some-spec(body)',
381
                'tag:some-spec',
382
                '@tag:some-spec (body)',
383
            ],
384
            'tag with textual description' => [
385
                '@tag some text',
386
                'tag',
387
                '@tag some text',
388
            ],
389
            'tag body starting with sqare brackets is allowed' => [
390
                '@tag [is valid]',
391
                'tag',
392
                '@tag [is valid]',
393
            ],
394
            'tag body starting with curly brackets is allowed' => [
395
                '@tag {is valid}',
396
                'tag',
397
                '@tag {is valid}',
398
            ],
399
            'tag name followed by curly brackets directly is allowed' => [
400
                '@tag{is valid}',
401
                'tag',
402
                '@tag {is valid}',
403
            ],
404
            'parenthesis directly following a tag name is valid' => [
405
                '@tag(is valid)',
406
                'tag',
407
                '@tag (is valid)',
408
            ],
409
        ];
410
    }
411
412
    /**
413
     * @dataProvider invalidTagProvider
414
     */
415
    public function testInValidFormattedTags(string $input) : void
416
    {
417
        $this->expectException(InvalidArgumentException::class);
418
        $fqsenResolver = $this->prophesize(FqsenResolver::class);
419
        $tagFactory = new StandardTagFactory($fqsenResolver->reveal());
420
        $tagFactory->registerTagHandler('tag', Generic::class);
421
        $tagFactory->create($input);
422
    }
423
424
    /**
425
     * @return string[][]
426
     *
427
     * @phpstan-return list<array<int, string>>
428
     */
429
    public function invalidTagProvider() : array
430
    {
431
        return [
432
            ['@tag[invalid]'],
433
            ['@tag@invalid'],
434
        ];
435
    }
436
}
437