254 lines
8.7 KiB
PHP
254 lines
8.7 KiB
PHP
<?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);
|
||
}
|
||
}
|