Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .ddev/commands/web/phpunit
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ if [[ ${coverage} == true ]]; then
echo "Coverage reports: reports/coverage/index.html (HTML), reports/clover.xml (Clover)"
# pcov.enabled=1: opt in per-run (globally off, see .ddev/php/pcov.ini).
# opcache.jit=off: avoid the "JIT is incompatible" warning PCOV would trigger.
exec php -d pcov.enabled=1 -d opcache.jit=off -d opcache.jit_buffer_size=0 vendor/bin/phpunit \
php -d pcov.enabled=1 -d opcache.jit=off -d opcache.jit_buffer_size=0 vendor/bin/phpunit \
--coverage-text \
--coverage-html reports/coverage \
--coverage-clover reports/clover.xml \
"${args[@]}"
# Quality ratchet, only meaningful for a full-suite run; keep the threshold
# in sync with .github/workflows/phpunit.yml.
if [[ ${#args[@]} -eq 0 ]]; then
exec php tests/coverage-checker.php reports/clover.xml 20
fi
exit 0
fi

exec vendor/bin/phpunit "${args[@]}"
Comment thread
Morgy93 marked this conversation as resolved.
5 changes: 5 additions & 0 deletions .github/workflows/phpunit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ jobs:
--coverage-html reports/coverage \
--coverage-clover reports/clover.xml

- name: Enforce minimum line coverage
if: ${{ matrix.coverage }}
# Quality ratchet — raise the threshold as coverage grows.
run: php tests/coverage-checker.php reports/clover.xml 20

- name: Publish coverage summary
if: ${{ matrix.coverage && always() }}
run: |
Expand Down
3 changes: 3 additions & 0 deletions infection.json5
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"excludes": ["registration.php"],
},
"timeout": 10,
// Quality ratchet: fails the run (locally and in CI) when the covered-code
// MSI drops below this floor. Raise it as the test suite improves.
"minCoveredMsi": 90,
"logs": {
"text": "reports/infection/infection.log",
"html": "reports/infection/infection.html",
Expand Down
19 changes: 19 additions & 0 deletions tests/Unit/Exception/FetchLatestVersionExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace OpenForgeProject\MageForge\Test\Unit\Exception;

use OpenForgeProject\MageForge\Exception\FetchLatestVersionException;
use PHPUnit\Framework\TestCase;

class FetchLatestVersionExceptionTest extends TestCase
{
public function testIsRuntimeException(): void
{
$exception = new FetchLatestVersionException('fetch failed');

$this->assertInstanceOf(\RuntimeException::class, $exception);
$this->assertSame('fetch failed', $exception->getMessage());
}
}
33 changes: 33 additions & 0 deletions tests/Unit/Model/Config/Source/InspectorThemeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace OpenForgeProject\MageForge\Test\Unit\Model\Config\Source;

use Magento\Framework\Data\OptionSourceInterface;
use OpenForgeProject\MageForge\Model\Config\Source\InspectorTheme;
use PHPUnit\Framework\TestCase;

class InspectorThemeTest extends TestCase
{
public function testImplementsOptionSourceInterface(): void
{
$this->assertInstanceOf(OptionSourceInterface::class, new InspectorTheme());
}

public function testReturnsAllInspectorThemes(): void
{
$options = (new InspectorTheme())->toOptionArray();

$this->assertSame(['dark', 'light', 'auto'], array_column($options, 'value'));
}

public function testEveryOptionHasNonEmptyLabel(): void
{
foreach ((new InspectorTheme())->toOptionArray() as $option) {
$this->assertArrayHasKey('label', $option);
$this->assertIsString($option['label']);
$this->assertNotSame('', $option['label']);
}
}
}
36 changes: 36 additions & 0 deletions tests/Unit/Model/Config/Source/ToolbarPositionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace OpenForgeProject\MageForge\Test\Unit\Model\Config\Source;

use Magento\Framework\Data\OptionSourceInterface;
use OpenForgeProject\MageForge\Model\Config\Source\ToolbarPosition;
use PHPUnit\Framework\TestCase;

class ToolbarPositionTest extends TestCase
{
public function testImplementsOptionSourceInterface(): void
{
$this->assertInstanceOf(OptionSourceInterface::class, new ToolbarPosition());
}

public function testReturnsAllToolbarPositions(): void
{
$options = (new ToolbarPosition())->toOptionArray();

$this->assertSame(
['bottom-left', 'bottom-right', 'top-left', 'top-right'],
array_column($options, 'value'),
);
}

public function testEveryOptionHasNonEmptyLabel(): void
{
foreach ((new ToolbarPosition())->toOptionArray() as $option) {
$this->assertArrayHasKey('label', $option);
$this->assertIsString($option['label']);
$this->assertNotSame('', $option['label']);
}
}
}
41 changes: 41 additions & 0 deletions tests/Unit/Model/ThemeListTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace OpenForgeProject\MageForge\Test\Unit\Model;

use Magento\Framework\View\Design\Theme\ThemeList as MagentoThemeList;
use Magento\Framework\View\Design\ThemeInterface;
use OpenForgeProject\MageForge\Model\ThemeList;
use PHPUnit\Framework\TestCase;

class ThemeListTest extends TestCase
{
public function testReturnsAllThemesFromMagentoThemeList(): void
{
$themeOne = $this->createMock(ThemeInterface::class);
$themeTwo = $this->createMock(ThemeInterface::class);

$magentoThemeList = $this->createMock(MagentoThemeList::class);
$magentoThemeList
->method('getItems')
->willReturn(['frontend/Vendor/one' => $themeOne, 'frontend/Vendor/two' => $themeTwo]);

$themeList = new ThemeList($magentoThemeList);

$this->assertSame(
['frontend/Vendor/one' => $themeOne, 'frontend/Vendor/two' => $themeTwo],
$themeList->getAllThemes(),
);
}

public function testReturnsEmptyArrayWhenNoThemesExist(): void
{
$magentoThemeList = $this->createMock(MagentoThemeList::class);
$magentoThemeList->method('getItems')->willReturn([]);

$themeList = new ThemeList($magentoThemeList);

$this->assertSame([], $themeList->getAllThemes());
}
}
65 changes: 65 additions & 0 deletions tests/Unit/Model/ThemePathTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace OpenForgeProject\MageForge\Test\Unit\Model;

use Magento\Framework\Component\ComponentRegistrar;
use Magento\Framework\Component\ComponentRegistrarInterface;
use OpenForgeProject\MageForge\Model\ThemePath;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class ThemePathTest extends TestCase
{
private ComponentRegistrarInterface&MockObject $componentRegistrar;
private ThemePath $themePath;

protected function setUp(): void
{
$this->componentRegistrar = $this->createMock(ComponentRegistrarInterface::class);
$this->themePath = new ThemePath($this->componentRegistrar);
}

public function testReturnsFrontendThemePath(): void
{
$this->componentRegistrar
->method('getPaths')
->with(ComponentRegistrar::THEME)
->willReturn([
'frontend/Vendor/theme' => '/app/design/frontend/Vendor/theme',
'adminhtml/Vendor/theme' => '/app/design/adminhtml/Vendor/theme',
]);

$this->assertSame('/app/design/frontend/Vendor/theme', $this->themePath->getPath('Vendor/theme'));
}

public function testFallsBackToAdminhtmlThemePath(): void
{
$this->componentRegistrar
->method('getPaths')
->willReturn([
'adminhtml/Vendor/backend' => '/app/design/adminhtml/Vendor/backend',
]);

$this->assertSame('/app/design/adminhtml/Vendor/backend', $this->themePath->getPath('Vendor/backend'));
}

public function testReturnsNullForUnknownTheme(): void
{
$this->componentRegistrar
->method('getPaths')
->willReturn([
'frontend/Other/theme' => '/app/design/frontend/Other/theme',
]);

$this->assertNull($this->themePath->getPath('Vendor/unknown'));
}

public function testReturnsNullWhenNoThemesRegistered(): void
{
$this->componentRegistrar->method('getPaths')->willReturn([]);

$this->assertNull($this->themePath->getPath('Vendor/theme'));
}
}
59 changes: 59 additions & 0 deletions tests/Unit/Service/CacheCleanerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace OpenForgeProject\MageForge\Test\Unit\Service;

use Magento\Framework\Shell;
use OpenForgeProject\MageForge\Service\CacheCleaner;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Style\SymfonyStyle;

class CacheCleanerTest extends TestCase
{
private Shell&MockObject $shell;
private SymfonyStyle&MockObject $io;
private CacheCleaner $cacheCleaner;

protected function setUp(): void
{
$this->shell = $this->createMock(Shell::class);
$this->io = $this->createMock(SymfonyStyle::class);
$this->cacheCleaner = new CacheCleaner($this->shell);
}

public function testCleansFrontendCacheTypes(): void
{
$this->shell
->expects($this->once())
->method('execute')
->with('bin/magento cache:clean full_page block_html layout translate');

$this->assertTrue($this->cacheCleaner->clean($this->io, false));
}

public function testPrintsProgressInVerboseMode(): void
{
$this->io->expects($this->once())->method('text')->with('Cleaning cache...');
$this->io->expects($this->once())->method('success')->with('Cache cleaned successfully.');

$this->assertTrue($this->cacheCleaner->clean($this->io, true));
}

public function testStaysQuietWhenNotVerbose(): void
{
$this->io->expects($this->never())->method('text');
$this->io->expects($this->never())->method('success');

$this->assertTrue($this->cacheCleaner->clean($this->io, false));
}

public function testReturnsFalseAndPrintsErrorWhenShellFails(): void
{
$this->shell->method('execute')->willThrowException(new \RuntimeException('cache backend gone'));
$this->io->expects($this->once())->method('error')->with('Failed to clean cache: cache backend gone');

$this->assertFalse($this->cacheCleaner->clean($this->io, true));
}
}
Loading
Loading