Skip to content

Commit a403c91

Browse files
committed
[TASK] Add unit tests for float class rewriting directives
Cover RewritesLegacyFloatClasses trait, ImageDirective decorator, and FigureDirective with 40 unit tests for 100% patch code coverage.
1 parent 0f0c670 commit a403c91

3 files changed

Lines changed: 482 additions & 0 deletions

File tree

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace T3Docs\Typo3DocsTheme\Tests\Unit\Directives;
6+
7+
use phpDocumentor\Guides\Nodes\CollectionNode;
8+
use phpDocumentor\Guides\Nodes\FigureNode;
9+
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
10+
use phpDocumentor\Guides\ReferenceResolvers\DocumentNameResolverInterface;
11+
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
12+
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
13+
use phpDocumentor\Guides\RestructuredText\Parser\DirectiveOption;
14+
use phpDocumentor\Guides\RestructuredText\Parser\DocumentParserContext;
15+
use phpDocumentor\Guides\RestructuredText\Parser\Productions\Rule;
16+
use phpDocumentor\Guides\ParserContext;
17+
use PHPUnit\Framework\Attributes\DataProvider;
18+
use PHPUnit\Framework\Attributes\Test;
19+
use PHPUnit\Framework\MockObject\MockObject;
20+
use PHPUnit\Framework\TestCase;
21+
use Psr\Log\LoggerInterface;
22+
use T3Docs\Typo3DocsTheme\Directives\FigureDirective;
23+
24+
final class FigureDirectiveTest extends TestCase
25+
{
26+
private FigureDirective $subject;
27+
private DocumentNameResolverInterface&MockObject $documentNameResolver;
28+
/** @var Rule<CollectionNode>&MockObject */
29+
private Rule&MockObject $startingRule;
30+
private LoggerInterface&MockObject $logger;
31+
32+
protected function setUp(): void
33+
{
34+
$this->documentNameResolver = $this->createMock(DocumentNameResolverInterface::class);
35+
$this->documentNameResolver->method('absoluteUrl')->willReturn('/resolved/image.png');
36+
$this->startingRule = $this->createMock(Rule::class);
37+
$this->logger = $this->createMock(LoggerInterface::class);
38+
39+
$this->subject = new FigureDirective(
40+
$this->documentNameResolver,
41+
$this->startingRule,
42+
$this->logger,
43+
);
44+
}
45+
46+
#[Test]
47+
public function getNameReturnsFigure(): void
48+
{
49+
self::assertSame('figure', $this->subject->getName());
50+
}
51+
52+
#[Test]
53+
public function processReturnsNullWhenStartingRuleReturnsNull(): void
54+
{
55+
$this->startingRule->method('apply')->willReturn(null);
56+
57+
$result = $this->subject->process(
58+
$this->createBlockContext(),
59+
new Directive('', 'figure', 'image.png'),
60+
);
61+
62+
self::assertNull($result);
63+
}
64+
65+
#[Test]
66+
public function processRewritesLegacyFloatLeftClass(): void
67+
{
68+
$this->startingRule->method('apply')->willReturn(new CollectionNode([new InlineCompoundNode([])]));
69+
70+
$directive = new Directive('', 'figure', 'image.png', [
71+
'class' => new DirectiveOption('class', 'float-left'),
72+
]);
73+
74+
$this->logger->expects(self::once())
75+
->method('warning')
76+
->with(self::stringContains('deprecated'));
77+
78+
$result = $this->subject->process($this->createBlockContext(), $directive);
79+
80+
self::assertInstanceOf(FigureNode::class, $result);
81+
// The directive class option should be rewritten for postProcessNode
82+
self::assertSame('float-start', $directive->getOption('class')->getValue());
83+
// The inner image should NOT have float classes
84+
self::assertNull($result->getImage()->getOption('class'));
85+
}
86+
87+
#[Test]
88+
public function processRewritesLegacyFloatRightClass(): void
89+
{
90+
$this->startingRule->method('apply')->willReturn(new CollectionNode([new InlineCompoundNode([])]));
91+
92+
$directive = new Directive('', 'figure', 'image.png', [
93+
'class' => new DirectiveOption('class', 'float-right'),
94+
]);
95+
96+
$this->logger->expects(self::once())->method('warning');
97+
98+
$result = $this->subject->process($this->createBlockContext(), $directive);
99+
100+
self::assertInstanceOf(FigureNode::class, $result);
101+
self::assertSame('float-end', $directive->getOption('class')->getValue());
102+
self::assertNull($result->getImage()->getOption('class'));
103+
}
104+
105+
#[Test]
106+
public function processPreservesNonFloatClassesOnInnerImage(): void
107+
{
108+
$this->startingRule->method('apply')->willReturn(new CollectionNode([new InlineCompoundNode([])]));
109+
110+
$directive = new Directive('', 'figure', 'image.png', [
111+
'class' => new DirectiveOption('class', 'with-shadow float-left'),
112+
]);
113+
114+
$this->logger->expects(self::once())->method('warning');
115+
116+
$result = $this->subject->process($this->createBlockContext(), $directive);
117+
118+
self::assertInstanceOf(FigureNode::class, $result);
119+
// Directive updated with rewritten classes
120+
self::assertSame('with-shadow float-start', $directive->getOption('class')->getValue());
121+
// Inner image gets only non-float classes
122+
self::assertSame('with-shadow', $result->getImage()->getOption('class'));
123+
}
124+
125+
#[Test]
126+
public function processDoesNotRewriteModernClasses(): void
127+
{
128+
$this->startingRule->method('apply')->willReturn(new CollectionNode([new InlineCompoundNode([])]));
129+
130+
$directive = new Directive('', 'figure', 'image.png', [
131+
'class' => new DirectiveOption('class', 'float-start'),
132+
]);
133+
134+
$this->logger->expects(self::never())->method('warning');
135+
136+
$result = $this->subject->process($this->createBlockContext(), $directive);
137+
138+
self::assertInstanceOf(FigureNode::class, $result);
139+
// Float classes stripped from inner image
140+
self::assertNull($result->getImage()->getOption('class'));
141+
}
142+
143+
#[Test]
144+
public function processHandlesNoClassOption(): void
145+
{
146+
$this->startingRule->method('apply')->willReturn(new CollectionNode([new InlineCompoundNode([])]));
147+
148+
$directive = new Directive('', 'figure', 'image.png');
149+
150+
$this->logger->expects(self::never())->method('warning');
151+
152+
$result = $this->subject->process($this->createBlockContext(), $directive);
153+
154+
self::assertInstanceOf(FigureNode::class, $result);
155+
self::assertNull($result->getImage()->getOption('class'));
156+
}
157+
158+
#[Test]
159+
public function processHandlesNonStringClassValue(): void
160+
{
161+
$this->startingRule->method('apply')->willReturn(new CollectionNode([new InlineCompoundNode([])]));
162+
163+
$directive = new Directive('', 'figure', 'image.png', [
164+
'class' => new DirectiveOption('class', true),
165+
]);
166+
167+
$this->logger->expects(self::never())->method('warning');
168+
169+
$result = $this->subject->process($this->createBlockContext(), $directive);
170+
171+
self::assertInstanceOf(FigureNode::class, $result);
172+
}
173+
174+
#[Test]
175+
public function processFiltersInvalidZoomMode(): void
176+
{
177+
$this->startingRule->method('apply')->willReturn(new CollectionNode([new InlineCompoundNode([])]));
178+
179+
$directive = new Directive('', 'figure', 'image.png', [
180+
'zoom' => new DirectiveOption('zoom', 'invalid-mode'),
181+
]);
182+
183+
$result = $this->subject->process($this->createBlockContext(), $directive);
184+
185+
self::assertInstanceOf(FigureNode::class, $result);
186+
self::assertNull($result->getOption('zoom'));
187+
}
188+
189+
#[Test]
190+
#[DataProvider('validZoomModeProvider')]
191+
public function processAcceptsValidZoomModes(string $mode): void
192+
{
193+
$this->startingRule->method('apply')->willReturn(new CollectionNode([new InlineCompoundNode([])]));
194+
195+
$directive = new Directive('', 'figure', 'image.png', [
196+
'zoom' => new DirectiveOption('zoom', $mode),
197+
]);
198+
199+
$result = $this->subject->process($this->createBlockContext(), $directive);
200+
201+
self::assertInstanceOf(FigureNode::class, $result);
202+
self::assertSame($mode, $result->getOption('zoom'));
203+
}
204+
205+
/** @return \Generator<string, array{string}> */
206+
public static function validZoomModeProvider(): \Generator
207+
{
208+
yield 'lightbox' => ['lightbox'];
209+
yield 'gallery' => ['gallery'];
210+
yield 'inline' => ['inline'];
211+
yield 'lens' => ['lens'];
212+
}
213+
214+
#[Test]
215+
public function processPassesImageOptionsToImageNode(): void
216+
{
217+
$this->startingRule->method('apply')->willReturn(new CollectionNode([new InlineCompoundNode([])]));
218+
219+
$directive = new Directive('', 'figure', 'image.png', [
220+
'width' => new DirectiveOption('width', '200'),
221+
'height' => new DirectiveOption('height', '100'),
222+
'alt' => new DirectiveOption('alt', 'test image'),
223+
'scale' => new DirectiveOption('scale', '50'),
224+
]);
225+
226+
$result = $this->subject->process($this->createBlockContext(), $directive);
227+
228+
self::assertInstanceOf(FigureNode::class, $result);
229+
self::assertSame('200', $result->getImage()->getOption('width'));
230+
self::assertSame('100', $result->getImage()->getOption('height'));
231+
self::assertSame('test image', $result->getImage()->getOption('alt'));
232+
self::assertSame('50', $result->getImage()->getOption('scale'));
233+
}
234+
235+
#[Test]
236+
public function processStripsFloatStartFromInnerImage(): void
237+
{
238+
$this->startingRule->method('apply')->willReturn(new CollectionNode([new InlineCompoundNode([])]));
239+
240+
$directive = new Directive('', 'figure', 'image.png', [
241+
'class' => new DirectiveOption('class', 'float-end with-border'),
242+
]);
243+
244+
$result = $this->subject->process($this->createBlockContext(), $directive);
245+
246+
self::assertInstanceOf(FigureNode::class, $result);
247+
// Float-end stripped, only with-border remains
248+
self::assertSame('with-border', $result->getImage()->getOption('class'));
249+
}
250+
251+
private function createBlockContext(): BlockContext
252+
{
253+
$parserContext = $this->createMock(ParserContext::class);
254+
$parserContext->method('getCurrentAbsolutePath')->willReturn('/test');
255+
$parserContext->method('getLoggerInformation')->willReturn(['rst-file' => 'test.rst']);
256+
$parserContext->method('getInitialHeaderLevel')->willReturn(1);
257+
258+
$documentParserContext = $this->createMock(DocumentParserContext::class);
259+
$documentParserContext->method('getContext')->willReturn($parserContext);
260+
$documentParserContext->method('getLoggerInformation')->willReturn(['rst-file' => 'test.rst']);
261+
262+
return new BlockContext($documentParserContext, '', false, 0);
263+
}
264+
}

0 commit comments

Comments
 (0)