Skip to content

Commit 35b7caf

Browse files
[tests] Start closing coverage gaps (#133) (#134)
* [tests] Start closing coverage gaps (#133) * Update wiki submodule pointer for PR #134 * [tests] Expand coverage fixtures and helper branches (#133) * [tests] Expand low-coverage command and config coverage (#133) * Update wiki submodule pointer for PR #134 * [tests] Cover unified differ and resource diff helpers (#133) * [tests] Expand copy resource and filesystem coverage (#133) * Update wiki submodule pointer for PR #134 * [tests] Cover composer json and command corner cases (#133) * [docs] Update wiki pointer for synced docs (#133) * Update wiki submodule pointer for PR #134 * [tests] Expand command and helper coverage gaps (#133) --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent b53f6fb commit 35b7caf

35 files changed

Lines changed: 3268 additions & 18 deletions

.github/wiki

Submodule wiki updated from 0722b9a to ddcf608

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@
9191
"autoload-dev": {
9292
"psr-4": {
9393
"FastForward\\DevTools\\Tests\\": "tests/"
94-
}
94+
},
95+
"classmap": [
96+
"tests/Fixtures/"
97+
]
9598
},
9699
"bin": "bin/dev-tools",
97100
"config": {

tests/Changelog/Document/ChangelogDocumentTest.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,100 @@ public function promoteUnreleasedWillMergeWithAnExistingPublishedVersion(): void
8383
self::assertSame(['Preserve release sections'], $release->getEntriesFor(ChangelogEntryType::Fixed));
8484
self::assertFalse($promoted->getUnreleased()->hasEntries());
8585
}
86+
87+
/**
88+
* @return void
89+
*/
90+
#[Test]
91+
public function documentAccessorsWillResolveExpectedReleaseVariants(): void
92+
{
93+
$document = new ChangelogDocument([new ChangelogRelease('1.2.0', '2026-04-19')]);
94+
95+
self::assertSame(ChangelogDocument::UNRELEASED_VERSION, $document->getUnreleased()->getVersion());
96+
self::assertNull($document->getRelease('9.9.9'));
97+
self::assertSame('1.2.0', $document->getLatestPublishedRelease()?->getVersion());
98+
}
99+
100+
/**
101+
* @return void
102+
*/
103+
#[Test]
104+
public function getLatestPublishedReleaseWillReturnNullWhenOnlyUnreleasedExists(): void
105+
{
106+
self::assertNull(ChangelogDocument::create()->getLatestPublishedRelease());
107+
}
108+
109+
/**
110+
* @return void
111+
*/
112+
#[Test]
113+
public function withReleaseWillReplaceExistingVersionAndInsertUnreleasedAtTheTop(): void
114+
{
115+
$existing = new ChangelogRelease('1.2.0', '2026-04-01');
116+
$replacement = (new ChangelogRelease('1.2.0', '2026-04-19'))
117+
->withEntry(ChangelogEntryType::Added, 'Updated note');
118+
$document = (new ChangelogDocument([$existing]))
119+
->withRelease(new ChangelogRelease(ChangelogDocument::UNRELEASED_VERSION))
120+
->withRelease($replacement);
121+
122+
self::assertSame(
123+
[ChangelogDocument::UNRELEASED_VERSION, '1.2.0'],
124+
array_map(
125+
static fn(ChangelogRelease $release): string => $release->getVersion(),
126+
$document->getReleases(),
127+
),
128+
);
129+
self::assertSame(['Updated note'], $document->getRelease('1.2.0')?->getEntriesFor(ChangelogEntryType::Added));
130+
}
131+
132+
/**
133+
* @return void
134+
*/
135+
#[Test]
136+
public function promoteUnreleasedWillCreatePublishedReleaseWhenDocumentIsEmpty(): void
137+
{
138+
$document = new ChangelogDocument([]);
139+
140+
$promoted = $document->promoteUnreleased('1.0.0', '2026-04-20');
141+
142+
self::assertSame(
143+
[ChangelogDocument::UNRELEASED_VERSION, '1.0.0'],
144+
array_map(
145+
static fn(ChangelogRelease $release): string => $release->getVersion(),
146+
$promoted->getReleases(),
147+
),
148+
);
149+
self::assertSame('2026-04-20', $promoted->getRelease('1.0.0')?->getDate());
150+
}
151+
152+
/**
153+
* @return void
154+
*/
155+
#[Test]
156+
public function releaseHelpersWillNormalizeEntriesAndSupportImmutableMutators(): void
157+
{
158+
$release = new ChangelogRelease('1.3.0', null, [
159+
ChangelogEntryType::Added->value => ['Ship feature', 'Ship feature'],
160+
]);
161+
162+
self::assertSame('1.3.0', $release->getVersion());
163+
self::assertNull($release->getDate());
164+
self::assertFalse($release->isUnreleased());
165+
self::assertTrue($release->hasEntries());
166+
self::assertSame(['Ship feature'], $release->getEntriesFor(ChangelogEntryType::Added));
167+
168+
$sameRelease = $release->withEntry(ChangelogEntryType::Fixed, ' ');
169+
self::assertSame($release, $sameRelease);
170+
171+
$updated = $release
172+
->withEntry(ChangelogEntryType::Fixed, 'Repair behavior')
173+
->withEntries([
174+
ChangelogEntryType::Security->value => ['Security hardening'],
175+
])
176+
->withDate('2026-04-20');
177+
178+
self::assertSame('2026-04-20', $updated->getDate());
179+
self::assertSame(['Security hardening'], $updated->getEntriesFor(ChangelogEntryType::Security));
180+
self::assertSame([], $updated->getEntriesFor(ChangelogEntryType::Added));
181+
}
86182
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Fast Forward Development Tools for PHP projects.
7+
*
8+
* This file is part of fast-forward/dev-tools project.
9+
*
10+
* @author Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
11+
* @license https://opensource.org/licenses/MIT MIT License
12+
*
13+
* @see https://github.com/php-fast-forward/
14+
* @see https://github.com/php-fast-forward/dev-tools
15+
* @see https://github.com/php-fast-forward/dev-tools/issues
16+
* @see https://php-fast-forward.github.io/dev-tools/
17+
* @see https://datatracker.ietf.org/doc/html/rfc2119
18+
*/
19+
20+
namespace FastForward\DevTools\Tests\Changelog\Entry;
21+
22+
use FastForward\DevTools\Changelog\Entry\ChangelogEntryType;
23+
use InvalidArgumentException;
24+
use PHPUnit\Framework\Attributes\CoversClass;
25+
use PHPUnit\Framework\Attributes\Test;
26+
use PHPUnit\Framework\TestCase;
27+
28+
#[CoversClass(ChangelogEntryType::class)]
29+
final class ChangelogEntryTypeTest extends TestCase
30+
{
31+
/**
32+
* @return void
33+
*/
34+
#[Test]
35+
public function orderedWillReturnKeepAChangelogSectionOrder(): void
36+
{
37+
self::assertSame(
38+
[
39+
ChangelogEntryType::Added,
40+
ChangelogEntryType::Changed,
41+
ChangelogEntryType::Deprecated,
42+
ChangelogEntryType::Removed,
43+
ChangelogEntryType::Fixed,
44+
ChangelogEntryType::Security,
45+
],
46+
ChangelogEntryType::ordered(),
47+
);
48+
}
49+
50+
/**
51+
* @return void
52+
*/
53+
#[Test]
54+
public function fromInputWillNormalizeSupportedValues(): void
55+
{
56+
self::assertSame(ChangelogEntryType::Fixed, ChangelogEntryType::fromInput(' fixed '));
57+
self::assertSame(ChangelogEntryType::Security, ChangelogEntryType::fromInput('security'));
58+
}
59+
60+
/**
61+
* @return void
62+
*/
63+
#[Test]
64+
public function fromInputWillRejectUnsupportedValues(): void
65+
{
66+
$this->expectException(InvalidArgumentException::class);
67+
$this->expectExceptionMessage('Unsupported changelog type "unknown".');
68+
69+
ChangelogEntryType::fromInput('unknown');
70+
}
71+
}

tests/Changelog/Parser/ChangelogParserTest.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use PHPUnit\Framework\Attributes\Test;
2828
use PHPUnit\Framework\Attributes\UsesClass;
2929
use PHPUnit\Framework\TestCase;
30+
use ReflectionMethod;
3031

3132
#[CoversClass(ChangelogParser::class)]
3233
#[UsesClass(ChangelogDocument::class)]
@@ -69,4 +70,96 @@ public function parseWillExtractReleaseSectionsAndEntries(): void
6970
self::assertSame(['Add release preparation workflow'], $document->getUnreleased()->getEntries()['Added']);
7071
self::assertSame('2026-04-01', $document->getRelease('1.0.0')?->getDate());
7172
}
73+
74+
/**
75+
* @return void
76+
*/
77+
#[Test]
78+
public function parseWillReturnDefaultDocumentForEmptyContents(): void
79+
{
80+
$document = (new ChangelogParser())->parse(" \n\n");
81+
82+
self::assertSame([ChangelogDocument::UNRELEASED_VERSION], array_map(
83+
static fn(ChangelogRelease $release): string => $release->getVersion(),
84+
$document->getReleases(),
85+
));
86+
}
87+
88+
/**
89+
* @return void
90+
*/
91+
#[Test]
92+
public function parseWillReturnDefaultDocumentWhenNoReleaseHeadingsExist(): void
93+
{
94+
$document = (new ChangelogParser())->parse("# Changelog\n\nThis file has no release headings yet.\n");
95+
96+
self::assertSame(ChangelogDocument::UNRELEASED_VERSION, $document->getUnreleased()->getVersion());
97+
self::assertFalse($document->getUnreleased()->hasEntries());
98+
}
99+
100+
/**
101+
* @return void
102+
*/
103+
#[Test]
104+
public function parseWillIgnoreUnsupportedLinesAndDeduplicateEntriesWithinASection(): void
105+
{
106+
$document = (new ChangelogParser())->parse(<<<'MD'
107+
## [Unreleased]
108+
109+
### Added
110+
111+
Intro line that should be ignored
112+
- Add sync command
113+
- Add sync command
114+
*
115+
116+
### Fixed
117+
118+
- Repair coverage report
119+
-
120+
MD);
121+
122+
self::assertSame(['Add sync command'], $document->getUnreleased()->getEntriesFor(ChangelogEntryType::Added));
123+
self::assertSame(
124+
['Repair coverage report'],
125+
$document->getUnreleased()
126+
->getEntriesFor(ChangelogEntryType::Fixed)
127+
);
128+
self::assertSame([], $document->getUnreleased()->getEntriesFor(ChangelogEntryType::Security));
129+
}
130+
131+
/**
132+
* @return void
133+
*/
134+
#[Test]
135+
public function parseWillTreatEmptyCategoryBodiesAsEmptyEntryLists(): void
136+
{
137+
$document = (new ChangelogParser())->parse(<<<'MD'
138+
## [Unreleased]
139+
140+
### Added
141+
142+
### Fixed
143+
144+
Not a bullet entry
145+
MD);
146+
147+
self::assertSame([], $document->getUnreleased()->getEntriesFor(ChangelogEntryType::Added));
148+
self::assertSame([], $document->getUnreleased()->getEntriesFor(ChangelogEntryType::Fixed));
149+
}
150+
151+
/**
152+
* @return void
153+
*/
154+
#[Test]
155+
public function extractEntriesWillReturnEmptyArrayWhenCategoryHeadingIsMissing(): void
156+
{
157+
$parser = new ChangelogParser();
158+
$reflectionMethod = new ReflectionMethod($parser, 'extractEntries');
159+
160+
self::assertSame(
161+
[],
162+
$reflectionMethod->invoke($parser, "### Fixed\n\n- Repair release notes", ChangelogEntryType::Added),
163+
);
164+
}
72165
}

tests/Changelog/Renderer/MarkdownRendererTest.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,68 @@ public function renderWillKeepOnlyOneBlankLineBetweenAnEmptyUnreleasedSectionAnd
148148
self::assertStringContainsString("## [Unreleased]\n\n## [1.2.0] - 2026-04-19", $output);
149149
self::assertStringNotContainsString("## [Unreleased]\n\n\n## [1.2.0] - 2026-04-19", $output);
150150
}
151+
152+
/**
153+
* @return void
154+
*/
155+
#[Test]
156+
public function renderWillOmitReferencesWhenRepositoryUrlIsMissingOrBlank(): void
157+
{
158+
$document = new ChangelogDocument([
159+
(new ChangelogRelease('1.2.0', '2026-04-19'))->withEntry(
160+
ChangelogEntryType::Added,
161+
'Ship changelog automation',
162+
),
163+
]);
164+
165+
$renderer = new MarkdownRenderer();
166+
167+
self::assertStringNotContainsString('[1.2.0]:', $renderer->render($document));
168+
self::assertStringNotContainsString('[1.2.0]:', $renderer->render($document, ' '));
169+
}
170+
171+
/**
172+
* @return void
173+
*/
174+
#[Test]
175+
public function renderWillNormalizeSshRepositoryUrlsAndTrimTrailingGitSuffix(): void
176+
{
177+
$document = new ChangelogDocument([
178+
(new ChangelogRelease('1.2.0', '2026-04-19'))->withEntry(
179+
ChangelogEntryType::Added,
180+
'Ship changelog automation',
181+
),
182+
]);
183+
184+
$output = (new MarkdownRenderer())->render($document, 'ssh://git@github.com/php-fast-forward/dev-tools.git');
185+
186+
self::assertStringContainsString(
187+
'[unreleased]: https://github.com/php-fast-forward/dev-tools/compare/v1.2.0...HEAD',
188+
$output,
189+
);
190+
self::assertStringContainsString(
191+
'[1.2.0]: https://github.com/php-fast-forward/dev-tools/releases/tag/v1.2.0',
192+
$output,
193+
);
194+
}
195+
196+
/**
197+
* @return void
198+
*/
199+
#[Test]
200+
public function renderWillOmitReferencesWhenOnlyUnreleasedSectionExists(): void
201+
{
202+
$output = (new MarkdownRenderer())->render(
203+
new ChangelogDocument([
204+
(new ChangelogRelease(ChangelogDocument::UNRELEASED_VERSION))->withEntry(
205+
ChangelogEntryType::Added,
206+
'Pending change',
207+
),
208+
]),
209+
'https://github.com/php-fast-forward/dev-tools'
210+
);
211+
212+
self::assertStringNotContainsString('[unreleased]:', $output);
213+
self::assertStringNotContainsString('releases/tag', $output);
214+
}
151215
}

0 commit comments

Comments
 (0)