Bicycle/tests/Unit/ViewTest.php
Egor Isaev c9b81cdcd1 dev
2026-08-14 13:09:54 +03:00

254 lines
8.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use System\Classes\View;
use System\Classes\MyException;
class ViewTest extends TestCase
{
private string $tmpFile;
// setStyle()/setScript()/setManifest() всегда ищут файл в APPPATH/media (это их работа —
// регистрировать реальные ассеты сайта, Core::findFile() не подменить временной директорией),
// поэтому фикстуры физически создаются в App/media/* — но с явно тестовым именем (uniqid),
// а не под видом настоящего "app.css"/"app.js" сайта, который тесту не принадлежит.
private string $fixtureCss;
private string $fixtureJs;
private string $fixtureManifest;
protected function setUp(): void
{
$this->tmpFile = sys_get_temp_dir() . '/bicycle_view_' . uniqid('', true) . '.html';
$unique = uniqid('viewtest_', true);
$this->fixtureCss = $unique . '.css';
$this->fixtureJs = $unique . '.js';
$this->fixtureManifest = $unique . '.json';
file_put_contents(APPPATH . '/media/css/' . $this->fixtureCss, '');
file_put_contents(APPPATH . '/media/js/' . $this->fixtureJs, '');
file_put_contents(APPPATH . '/media/' . $this->fixtureManifest, '{}');
}
protected function tearDown(): void
{
if (file_exists($this->tmpFile)) {
unlink($this->tmpFile);
}
foreach ([
APPPATH . '/media/css/' . $this->fixtureCss,
APPPATH . '/media/js/' . $this->fixtureJs,
APPPATH . '/media/' . $this->fixtureManifest,
] as $fixtureFile) {
if (file_exists($fixtureFile)) {
unlink($fixtureFile);
}
}
// Сбрасываем статические реестры CSS/JS/manifest между тестами
$ref = new \ReflectionClass(View::class);
foreach (['_styles' => [], '_scripts' => [], '_manifest' => null] as $prop => $default) {
$property = $ref->getProperty($prop);
$property->setAccessible(true);
$property->setValue(null, $default);
}
}
public function testRenderStaticContent(): void
{
file_put_contents($this->tmpFile, 'hello world');
$view = new View();
$view->_file = $this->tmpFile;
$this->assertSame('hello world', $view->render());
}
public function testRenderWithData(): void
{
file_put_contents($this->tmpFile, '<?php echo $name; ?>');
$view = new View();
$view->_file = $this->tmpFile;
$view->_data = ['name' => 'bicycle'];
$this->assertSame('bicycle', $view->render());
}
public function testRenderMultipleVars(): void
{
file_put_contents($this->tmpFile, '<?php echo "$a-$b"; ?>');
$view = new View();
$view->_file = $this->tmpFile;
$view->_data = ['a' => 'foo', 'b' => 'bar'];
$this->assertSame('foo-bar', $view->render());
}
public function testRenderThrowsWhenFileNotSet(): void
{
$this->expectException(MyException::class);
(new View())->render();
}
public function testSetFilenameFindsFileInAppPath(): void
{
$view = new View();
$view->setFilename('index', 'view/Index');
$this->assertStringContainsString('App', $view->_file);
$this->assertStringEndsWith('index.html', $view->_file);
$this->assertFileExists($view->_file);
}
public function testSetFilenameFindsFileInSysPath(): void
{
$view = new View();
$view->setFilename('404', 'view/errors');
$this->assertStringContainsString('System', $view->_file);
$this->assertFileExists($view->_file);
}
public function testSetFilenameThrowsWhenNotFound(): void
{
$this->expectException(MyException::class);
(new View())->setFilename('nonexistent_xyz', 'view/Index');
}
public function testSetFilenameThrowsWhenDirEmpty(): void
{
$this->expectException(MyException::class);
(new View())->setFilename('index', '');
}
public function testConstructorWithFileAndDir(): void
{
$view = new View('index', 'view/Index');
$this->assertNotEmpty($view->_file);
$this->assertFileExists($view->_file);
}
public function testConstructorWithData(): void
{
file_put_contents($this->tmpFile, '<?php echo $val; ?>');
$view = new View();
$view->_file = $this->tmpFile;
$view->_data = ['val' => 'test_value'];
$this->assertSame('test_value', $view->render());
}
public function testExtractSkipDoesNotOverrideLocalVar(): void
{
// EXTR_SKIP не перезаписывает уже существующие переменные в шаблоне
file_put_contents($this->tmpFile, '<?php $x = "local"; extract([], EXTR_SKIP); echo $x; ?>');
$view = new View();
$view->_file = $this->tmpFile;
$view->_data = ['x' => 'from_data'];
// Здесь x уже определена в шаблоне ДО extract — EXTR_SKIP не перезапишет
// Но View делает extract ДО include, поэтому $x = 'from_data' попадёт в шаблон
$this->assertSame('local', $view->render());
}
public function testSetFilenameReturnsSelf(): void
{
$view = new View();
$result = $view->setFilename('index', 'view/Index');
$this->assertSame($view, $result);
}
public function testSetStyleRegistersLinkTag(): void
{
(new View())->setStyle($this->fixtureCss);
ob_start();
(new View())->getStyles();
$html = ob_get_clean();
$this->assertMatchesRegularExpression(
'#<link rel="stylesheet" href="/App/media/css/' . preg_quote($this->fixtureCss, '#') . '\?v=\d+">#', $html
);
}
public function testSetStyleMissingFileEchoesAlert(): void
{
ob_start();
(new View())->setStyle('nonexistent_xyz.css');
$html = ob_get_clean();
$this->assertStringContainsString('alert-danger', $html);
$this->assertStringContainsString('nonexistent_xyz.css', $html);
ob_start();
(new View())->getStyles();
$this->assertSame('', ob_get_clean());
}
public function testSetScriptRegistersScriptTag(): void
{
(new View())->setScript($this->fixtureJs);
ob_start();
(new View())->getScripts();
$html = ob_get_clean();
$this->assertMatchesRegularExpression(
'#<script type="text/javascript" src="/App/media/js/' . preg_quote($this->fixtureJs, '#') . '\?v=\d+"></script>#', $html
);
}
public function testSetScriptCustomType(): void
{
(new View())->setScript($this->fixtureJs, 'module');
ob_start();
(new View())->getScripts();
$html = ob_get_clean();
$this->assertStringContainsString('type="module"', $html);
}
public function testSetScriptMissingFileEchoesAlert(): void
{
ob_start();
(new View())->setScript('nonexistent_xyz.js');
$html = ob_get_clean();
$this->assertStringContainsString('alert-danger', $html);
ob_start();
(new View())->getScripts();
$this->assertSame('', ob_get_clean());
}
public function testSetManifestRegistersLinkTag(): void
{
(new View())->setManifest($this->fixtureManifest);
ob_start();
(new View())->getStyles();
$html = ob_get_clean();
$this->assertStringContainsString('<link rel="manifest" href="/App/media/' . $this->fixtureManifest . '">', $html);
}
public function testSetManifestMissingFileEchoesAlert(): void
{
ob_start();
(new View())->setManifest('nonexistent_xyz.json');
$html = ob_get_clean();
$this->assertStringContainsString('alert-danger', $html);
}
public function testGetStylesAndScriptsAreSharedAcrossViewInstances(): void
{
// content и layout — разные объекты View, но реестр общий (см. Controller::render())
(new View())->setStyle($this->fixtureCss);
(new View())->setScript($this->fixtureJs);
ob_start();
(new View())->getStyles();
$styles = ob_get_clean();
ob_start();
(new View())->getScripts();
$scripts = ob_get_clean();
$this->assertStringContainsString($this->fixtureCss, $styles);
$this->assertStringContainsString($this->fixtureJs, $scripts);
}
}