Skip to content

Commit 9ef392d

Browse files
committed
test: add unit tests for Separate enum and DocBlockHeader generator
1 parent 292d624 commit 9ef392d

File tree

3 files changed

+727
-3
lines changed

3 files changed

+727
-3
lines changed

tests/src/Enum/SeparateTest.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Composer package "php-doc-block-header-fixer".
7+
*
8+
* Copyright (C) 2025 Konrad Michalik <hej@konradmichalik.dev>
9+
*
10+
* This program is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU General Public License as published by
12+
* the Free Software Foundation, either version 3 of the License, or
13+
* (at your option) any later version.
14+
*
15+
* This program is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU General Public License
21+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
24+
namespace KonradMichalik\PhpDocBlockHeaderFixer\Tests\Enum;
25+
26+
use KonradMichalik\PhpDocBlockHeaderFixer\Enum\Separate;
27+
use PHPUnit\Framework\TestCase;
28+
use ValueError;
29+
30+
/**
31+
* @internal
32+
*/
33+
#[\PHPUnit\Framework\Attributes\CoversClass(Separate::class)]
34+
final class SeparateTest extends TestCase
35+
{
36+
public function testEnumCases(): void
37+
{
38+
$cases = Separate::cases();
39+
40+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
41+
self::assertCount(4, $cases);
42+
self::assertContains(Separate::Top, $cases);
43+
self::assertContains(Separate::Bottom, $cases);
44+
self::assertContains(Separate::Both, $cases);
45+
self::assertContains(Separate::None, $cases);
46+
}
47+
48+
public function testEnumValues(): void
49+
{
50+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
51+
self::assertSame('top', Separate::Top->value);
52+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
53+
self::assertSame('bottom', Separate::Bottom->value);
54+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
55+
self::assertSame('both', Separate::Both->value);
56+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
57+
self::assertSame('none', Separate::None->value);
58+
}
59+
60+
public function testGetListReturnsAllValues(): void
61+
{
62+
$list = Separate::getList();
63+
64+
self::assertSame(['top', 'bottom', 'both', 'none'], $list);
65+
}
66+
67+
public function testGetListReturnsNonEmptyList(): void
68+
{
69+
$list = Separate::getList();
70+
71+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
72+
self::assertNotEmpty($list);
73+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
74+
self::assertIsArray($list);
75+
76+
foreach ($list as $key => $value) {
77+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
78+
self::assertIsInt($key);
79+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
80+
self::assertIsString($value);
81+
}
82+
}
83+
84+
public function testFromString(): void
85+
{
86+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
87+
self::assertSame(Separate::Top, Separate::from('top'));
88+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
89+
self::assertSame(Separate::Bottom, Separate::from('bottom'));
90+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
91+
self::assertSame(Separate::Both, Separate::from('both'));
92+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
93+
self::assertSame(Separate::None, Separate::from('none'));
94+
}
95+
96+
public function testTryFromString(): void
97+
{
98+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
99+
self::assertSame(Separate::Top, Separate::tryFrom('top'));
100+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
101+
self::assertSame(Separate::Bottom, Separate::tryFrom('bottom'));
102+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
103+
self::assertSame(Separate::Both, Separate::tryFrom('both'));
104+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
105+
self::assertSame(Separate::None, Separate::tryFrom('none'));
106+
107+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
108+
self::assertNull(Separate::tryFrom('invalid'));
109+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
110+
self::assertNull(Separate::tryFrom(''));
111+
}
112+
113+
public function testFromInvalidStringThrowsException(): void
114+
{
115+
$this->expectException(ValueError::class);
116+
117+
Separate::from('invalid');
118+
}
119+
}
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Composer package "php-doc-block-header-fixer".
7+
*
8+
* Copyright (C) 2025 Konrad Michalik <hej@konradmichalik.dev>
9+
*
10+
* This program is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU General Public License as published by
12+
* the Free Software Foundation, either version 3 of the License, or
13+
* (at your option) any later version.
14+
*
15+
* This program is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU General Public License
21+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
24+
namespace KonradMichalik\PhpDocBlockHeaderFixer\Tests\Generators;
25+
26+
use InvalidArgumentException;
27+
use KonradMichalik\PhpDocBlockHeaderFixer\Enum\Separate;
28+
use KonradMichalik\PhpDocBlockHeaderFixer\Generators\DocBlockHeader;
29+
use KonradMichalik\PhpDocBlockHeaderFixer\Generators\Generator;
30+
use PHPUnit\Framework\TestCase;
31+
use ReflectionClass;
32+
33+
/**
34+
* @internal
35+
*/
36+
#[\PHPUnit\Framework\Attributes\CoversClass(DocBlockHeader::class)]
37+
final class DocBlockHeaderTest extends TestCase
38+
{
39+
public function testImplementsGeneratorInterface(): void
40+
{
41+
$docBlockHeader = DocBlockHeader::create(['author' => 'John Doe']);
42+
43+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
44+
self::assertInstanceOf(Generator::class, $docBlockHeader);
45+
}
46+
47+
public function testCreateWithValidAnnotations(): void
48+
{
49+
$annotations = ['author' => 'John Doe', 'license' => 'MIT'];
50+
$docBlockHeader = DocBlockHeader::create($annotations);
51+
52+
self::assertSame($annotations, $docBlockHeader->annotations);
53+
self::assertTrue($docBlockHeader->preserveExisting);
54+
self::assertSame(Separate::Both, $docBlockHeader->separate);
55+
}
56+
57+
public function testCreateWithCustomParameters(): void
58+
{
59+
$annotations = ['author' => 'Jane Smith'];
60+
$docBlockHeader = DocBlockHeader::create(
61+
$annotations,
62+
false,
63+
Separate::None,
64+
);
65+
66+
self::assertSame($annotations, $docBlockHeader->annotations);
67+
self::assertFalse($docBlockHeader->preserveExisting);
68+
self::assertSame(Separate::None, $docBlockHeader->separate);
69+
}
70+
71+
public function testCreateWithArrayValueAnnotations(): void
72+
{
73+
$annotations = [
74+
'author' => ['John Doe <john@example.com>', 'Jane Smith <jane@example.com>'],
75+
'license' => 'MIT',
76+
];
77+
$docBlockHeader = DocBlockHeader::create($annotations);
78+
79+
self::assertSame($annotations, $docBlockHeader->annotations);
80+
}
81+
82+
public function testToArrayReturnsCorrectStructure(): void
83+
{
84+
$annotations = ['author' => 'John Doe', 'license' => 'MIT'];
85+
$docBlockHeader = DocBlockHeader::create($annotations, false, Separate::Top);
86+
87+
$result = $docBlockHeader->__toArray();
88+
89+
$expected = [
90+
'KonradMichalik/docblock_header_comment' => [
91+
'annotations' => $annotations,
92+
'preserve_existing' => false,
93+
'separate' => 'top',
94+
],
95+
];
96+
97+
self::assertSame($expected, $result);
98+
}
99+
100+
public function testToArrayWithDefaultParameters(): void
101+
{
102+
$annotations = ['author' => 'John Doe'];
103+
$docBlockHeader = DocBlockHeader::create($annotations);
104+
105+
$result = $docBlockHeader->__toArray();
106+
107+
$expected = [
108+
'KonradMichalik/docblock_header_comment' => [
109+
'annotations' => $annotations,
110+
'preserve_existing' => true,
111+
'separate' => 'both',
112+
],
113+
];
114+
115+
self::assertSame($expected, $result);
116+
}
117+
118+
public function testValidateAnnotationsWithValidKeys(): void
119+
{
120+
$validAnnotations = [
121+
'author' => 'John Doe',
122+
'copyright' => '2024',
123+
'license' => 'MIT',
124+
'version' => '1.0.0',
125+
'since' => '1.0.0',
126+
'package' => 'MyPackage',
127+
'subpackage' => 'SubPackage',
128+
'see' => 'https://example.com',
129+
'link' => 'https://example.com',
130+
'todo' => 'Fix this',
131+
'fixme' => 'Fix this',
132+
'deprecated' => 'Use something else',
133+
'internal' => 'Internal use only',
134+
'api' => 'Public API',
135+
'category' => 'Category',
136+
'example' => 'Example usage',
137+
'ignore' => 'Ignore this',
138+
'uses' => 'Uses something',
139+
'used-by' => 'Used by something',
140+
'throws' => 'Exception',
141+
'method' => 'methodName()',
142+
'property' => '$propertyName',
143+
'property-read' => '$readOnlyProperty',
144+
'property-write' => '$writeOnlyProperty',
145+
'param' => 'string $param',
146+
'return' => 'string',
147+
'var' => 'string',
148+
'global' => '$GLOBALS',
149+
'static' => 'Static method',
150+
'final' => 'Final class',
151+
'abstract' => 'Abstract class',
152+
];
153+
154+
// This should not throw an exception
155+
$docBlockHeader = DocBlockHeader::create($validAnnotations);
156+
/* @phpstan-ignore-next-line staticMethod.alreadyNarrowedType */
157+
self::assertInstanceOf(DocBlockHeader::class, $docBlockHeader);
158+
}
159+
160+
public function testValidateAnnotationsThrowsExceptionForNonStringKey(): void
161+
{
162+
$this->expectException(InvalidArgumentException::class);
163+
$this->expectExceptionMessage('Annotation key must be a string, integer given');
164+
165+
// @phpstan-ignore-next-line argument.type
166+
DocBlockHeader::create([123 => 'invalid']);
167+
}
168+
169+
public function testValidateAnnotationsThrowsExceptionForEmptyKey(): void
170+
{
171+
$this->expectException(InvalidArgumentException::class);
172+
$this->expectExceptionMessage('Annotation key cannot be empty');
173+
174+
DocBlockHeader::create(['' => 'value']);
175+
}
176+
177+
public function testValidateAnnotationsThrowsExceptionForWhitespaceOnlyKey(): void
178+
{
179+
$this->expectException(InvalidArgumentException::class);
180+
$this->expectExceptionMessage('Annotation key cannot be empty');
181+
182+
DocBlockHeader::create([' ' => 'value']);
183+
}
184+
185+
public function testValidateAnnotationsThrowsExceptionForInvalidKeyFormat(): void
186+
{
187+
$this->expectException(InvalidArgumentException::class);
188+
$this->expectExceptionMessage('Invalid annotation key "123invalid". Must start with letter and contain only letters, numbers, underscore, or dash.');
189+
190+
DocBlockHeader::create(['123invalid' => 'value']);
191+
}
192+
193+
public function testValidateAnnotationsThrowsExceptionForInvalidKeyCharacters(): void
194+
{
195+
$this->expectException(InvalidArgumentException::class);
196+
$this->expectExceptionMessage('Invalid annotation key "invalid@key". Must start with letter and contain only letters, numbers, underscore, or dash.');
197+
198+
DocBlockHeader::create(['invalid@key' => 'value']);
199+
}
200+
201+
public function testValidateAnnotationsThrowsExceptionForUnknownAnnotation(): void
202+
{
203+
$this->expectException(InvalidArgumentException::class);
204+
$this->expectExceptionMessage('Unknown annotation "unknownAnnotation"');
205+
206+
DocBlockHeader::create(['unknownAnnotation' => 'value']);
207+
}
208+
209+
public function testValidateAnnotationsAcceptsValidKeyWithHyphen(): void
210+
{
211+
$docBlockHeader = DocBlockHeader::create(['property-read' => '$property']);
212+
213+
self::assertSame(['property-read' => '$property'], $docBlockHeader->annotations);
214+
}
215+
216+
public function testValidateAnnotationsRejectsKeyWithUnderscore(): void
217+
{
218+
// This should throw an exception because 'used_by' with underscore is not in allowed list
219+
$this->expectException(InvalidArgumentException::class);
220+
$this->expectExceptionMessage('Unknown annotation "used_by"');
221+
222+
DocBlockHeader::create(['used_by' => 'Something']);
223+
}
224+
225+
public function testValidateAnnotationsAcceptsValidKeyWithNumbers(): void
226+
{
227+
// Note: There are no standard annotations with numbers in the allowed list
228+
// Let's test that validation works correctly for keys with numbers
229+
$this->expectException(InvalidArgumentException::class);
230+
$this->expectExceptionMessage('Unknown annotation "author2"');
231+
232+
DocBlockHeader::create(['author2' => 'John Doe']);
233+
}
234+
235+
public function testCreateWithEmptyAnnotations(): void
236+
{
237+
$docBlockHeader = DocBlockHeader::create([]);
238+
239+
self::assertSame([], $docBlockHeader->annotations);
240+
self::assertTrue($docBlockHeader->preserveExisting);
241+
self::assertSame(Separate::Both, $docBlockHeader->separate);
242+
}
243+
244+
public function testPropertiesAreReadonly(): void
245+
{
246+
$docBlockHeader = DocBlockHeader::create(['author' => 'John Doe']);
247+
248+
self::assertSame(['author' => 'John Doe'], $docBlockHeader->annotations);
249+
self::assertTrue($docBlockHeader->preserveExisting);
250+
self::assertSame(Separate::Both, $docBlockHeader->separate);
251+
252+
// Properties should be readonly - but we can't test this directly in PHP 8.1+
253+
// The readonly modifier is enforced at the language level
254+
}
255+
256+
public function testConstructorIsPrivate(): void
257+
{
258+
$reflection = new ReflectionClass(DocBlockHeader::class);
259+
$constructor = $reflection->getConstructor();
260+
261+
self::assertNotNull($constructor);
262+
self::assertTrue($constructor->isPrivate());
263+
}
264+
265+
public function testClassIsFinal(): void
266+
{
267+
$reflection = new ReflectionClass(DocBlockHeader::class);
268+
269+
self::assertTrue($reflection->isFinal());
270+
}
271+
}

0 commit comments

Comments
 (0)