Skip to content
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
},
"require-dev": {
"behat/behat": "^3.12.0",
"phpunit/phpunit": "^9.6.6"
"phpunit/phpunit": "^9.6.6",
"symfony/console": "^5.0 || ^6.0",
"dg/bypass-finals": "^1.4"
},
"autoload": {
"psr-4": {
Expand Down
3 changes: 3 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
bootstrap="vendor/autoload.php"
colors="true"
>
<extensions>
<extension class="Netlogix\XmlProcessor\Tests\Hooks\BypassFinalHook"/>
</extensions>
<coverage>
<include>
<directory suffix=".php">src/</directory>
Expand Down
27 changes: 27 additions & 0 deletions src/Factory/NodeProcessorProgressBarFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);

namespace Netlogix\XmlProcessor\Factory;

use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class NodeProcessorProgressBarFactory
{
public function createProgressBar(?OutputInterface $output = NULL): ?ProgressBar
{
if (!($output instanceof ConsoleOutputInterface)) {
return NULL;
}
$progressBar = new ProgressBar($output);
$progressBar->setBarCharacter('<fg=green>⚬</>');
$progressBar->setEmptyBarCharacter("<fg=red>⚬</>");
$progressBar->setProgressCharacter("<fg=green>➤</>");
$progressBar->setFormat(
"<comment>%node%</comment>\n%current% [%bar%]\n %memory:6s%\n"
);

return $progressBar;
}
}
63 changes: 63 additions & 0 deletions src/NodeProcessor/ProgressOutputNodeProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);

namespace Netlogix\XmlProcessor\NodeProcessor;


use Netlogix\XmlProcessor\Factory\NodeProcessorProgressBarFactory;
use Netlogix\XmlProcessor\NodeProcessor\Context\OpenContext;
use Netlogix\XmlProcessor\XmlProcessor;
use Netlogix\XmlProcessor\XmlProcessorContext;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ProgressOutputNodeProcessor implements NodeProcessorInterface, OpenNodeProcessorInterface
{
protected ?OutputInterface $output = NULL;

protected ?ProgressBar $progressBar = NULL;

protected NodeProcessorProgressBarFactory $progressBarFactory;

function __construct(?NodeProcessorProgressBarFactory $progressBarFactory = NULL)
{
$this->progressBarFactory = $progressBarFactory ?? new NodeProcessorProgressBarFactory();
}

public function SetOutput(?ConsoleOutputInterface $output = NULL): void
{
$this->output = $output;
$this->progressBar = NULL;
}

function getSubscribedEvents(string $nodePath, XmlProcessorContext $context): \Generator
{
if ($this->progressBar === NULL) {
yield XmlProcessor::EVENT_OPEN_FILE => [$this, 'openFile'];
} else {
yield 'NodeType_' . \XMLReader::ELEMENT => [$this, 'openElement'];
yield XmlProcessor::EVENT_END_OF_FILE => [$this, 'endOfFile'];
}
}

function openFile(): void
{
if ($this->progressBar !== NULL) {
$this->progressBar->finish();
}
$this->progressBar = $this->progressBarFactory->createProgressBar($this->output);
}

function openElement(OpenContext $context): void
{
$this->progressBar->advance();
$this->progressBar->setMessage($context->getNodePath(), 'node');
}

function endOfFile(): void
{
$this->progressBar->finish();
$this->progressBar = NULL;
}
}
18 changes: 18 additions & 0 deletions tests/Hooks/BypassFinalHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);

namespace Netlogix\XmlProcessor\Tests\Hooks;

use DG\BypassFinals;
use PHPUnit\Runner\BeforeTestHook;

final class BypassFinalHook implements BeforeTestHook
{
public function executeBeforeTest(string $test): void
{
BypassFinals::enable();
BypassFinals::setWhitelist([
'*/vendor/symfony/console/Helper/*',
]);
}
}
36 changes: 36 additions & 0 deletions tests/Unit/Factory/NodeProcessorProgressBarFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);

namespace Netlogix\XmlProcessor\Tests\Unit;

use Netlogix\XmlProcessor\Factory\NodeProcessorProgressBarFactory;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class NodeProcessorProgressBarFactoryTest extends TestCase
{
function test__construct(): void
{
$nodeProcessorProgressBarFactory = new NodeProcessorProgressBarFactoryTest();
self::assertInstanceOf(NodeProcessorProgressBarFactoryTest::class, $nodeProcessorProgressBarFactory);
}

function testCreateProgressBar(): void
{
$nodeProcessorProgressBarFactory = new NodeProcessorProgressBarFactory();
$progressBar = $nodeProcessorProgressBarFactory->createProgressBar();
self::assertNull($progressBar);

$progressBar = $nodeProcessorProgressBarFactory->createProgressBar($this->getMockBuilder(OutputInterface::class)->getMock());
self::assertNull($progressBar);

$output = $this::getMockForAbstractClass(ConsoleOutputInterface::class);
$output->method('getErrorOutput')->willReturn($output);
$output->method('isDecorated')->willReturn(true);

$progressBar = $nodeProcessorProgressBarFactory->createProgressBar($output);
self::assertInstanceOf(ProgressBar::class, $progressBar);
}
}
135 changes: 135 additions & 0 deletions tests/Unit/NodeProcessor/ProgressOutputNodeProcessorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);

namespace Netlogix\XmlProcessor\Tests\Unit\NodeProcessor;

use Netlogix\XmlProcessor\Factory\NodeProcessorProgressBarFactory;
use Netlogix\XmlProcessor\NodeProcessor\Context\OpenContext;
use Netlogix\XmlProcessor\NodeProcessor\ProgressOutputNodeProcessor;
use Netlogix\XmlProcessor\XmlProcessor;
use Netlogix\XmlProcessor\XmlProcessorContext;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;

class ProgressOutputNodeProcessorTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

$reflectionClass = new \ReflectionClass(ProgressOutputNodeProcessor::class);
$this->progressBarProperty = $reflectionClass->getProperty('progressBar');
$this->progressBarProperty->setAccessible(true);
}

public function test__construct(): void
{
$nodeProcessor = new ProgressOutputNodeProcessor();
self::assertInstanceOf(ProgressOutputNodeProcessor::class, $nodeProcessor);
}

public function testGetSubscribedEvents(): void
{
$progressBarFactory = $this::getMockBuilder(NodeProcessorProgressBarFactory::class)
->disableOriginalConstructor()
->getMock();
$progressBarFactory->method('createProgressBar')->willReturn($this->createMock(ProgressBar::class));
$nodeProcessor = new ProgressOutputNodeProcessor($progressBarFactory);
$context = $this->getMockBuilder(XmlProcessorContext::class)
->disableOriginalConstructor()
->getMock();

self::assertEquals(
[
XmlProcessor::EVENT_OPEN_FILE => [$nodeProcessor, 'openFile']
],
iterator_to_array($nodeProcessor->getSubscribedEvents('test', $context))
);

$nodeProcessor->setOutput($this->getOutputMock());
$nodeProcessor->openFile();

self::assertEquals(
[
'NodeType_' . \XMLReader::ELEMENT => [$nodeProcessor, 'openElement'],
XmlProcessor::EVENT_END_OF_FILE => [$nodeProcessor, 'endOfFile']
],
iterator_to_array($nodeProcessor->getSubscribedEvents('test', $context))
);
}

public function testOpenFile(): void
{
$nodeProcessor = new ProgressOutputNodeProcessor();
$nodeProcessor->openFile();
self::assertNull($this->progressBarProperty->getValue($nodeProcessor));

$progressBar = $this->getMockBuilder(ProgressBar::class)->disableOriginalConstructor()
->getMock();
$progressBar->expects($this->once())->method('finish');
$progressBarFactory = $this::getMockBuilder(NodeProcessorProgressBarFactory::class)
->disableOriginalConstructor()
->getMock();
$progressBarFactory->method('createProgressBar')->willReturn($progressBar);

$nodeProcessor = new ProgressOutputNodeProcessor($progressBarFactory);
$nodeProcessor->setOutput($this->getOutputMock());
$nodeProcessor->openFile();
self::assertInstanceOf(ProgressBar::class, $this->progressBarProperty->getValue($nodeProcessor));
$nodeProcessor->openFile();
self::assertInstanceOf(ProgressBar::class, $this->progressBarProperty->getValue($nodeProcessor));
}

public function testOpenElement(): void
{

$progressBar = $this->getMockBuilder(ProgressBar::class)->disableOriginalConstructor()
->getMock();

$progressBar->expects($this->once())->method('advance')->with(1);
$progressBar->expects($this->once())->method('setMessage')->with('foo/bar', 'node');
$progressBar->expects($this->once())->method('finish');
$progressBarFactory = $this::getMockBuilder(NodeProcessorProgressBarFactory::class)
->disableOriginalConstructor()
->getMock();
$progressBarFactory->expects($this->exactly(2))->method('createProgressBar')->willReturn($progressBar);

$nodeProcessor = new ProgressOutputNodeProcessor($progressBarFactory);
$nodeProcessor->setOutput($this->getOutputMock());
$nodeProcessor->openFile();

$openContext = $this->createMock(OpenContext::class);
$openContext->method('getNodePath')->willReturn('foo/bar');

$nodeProcessor->openElement($openContext);
$nodeProcessor->openFile();
}

function testEndOfFile(): void
{
$progressBar = $this->getMockBuilder(ProgressBar::class)->disableOriginalConstructor()
->getMock();
$progressBar->expects($this->once())->method('finish');
$progressBarFactory = $this::getMockBuilder(NodeProcessorProgressBarFactory::class)
->disableOriginalConstructor()
->getMock();
$progressBarFactory->method('createProgressBar')->willReturn($progressBar);

$nodeProcessor = new ProgressOutputNodeProcessor($progressBarFactory);
$nodeProcessor->setOutput($this->getOutputMock());
$nodeProcessor->openFile();

$nodeProcessor->endOfFile();
$progressBar = $this->progressBarProperty;
self::assertNull($progressBar->getValue($nodeProcessor));
}

private function getOutputMock(): ConsoleOutputInterface
{
$output = $this::getMockForAbstractClass(ConsoleOutputInterface::class);
$output->method('getErrorOutput')->willReturn($output);
$output->method('isDecorated')->willReturn(true);
return $output;
}
}
Loading