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