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, '');
$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, '');
$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, '');
$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, '');
$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(
'##', $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(
'##', $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('', $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);
}
}