diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index b902597..43143ab 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -15,3 +15,6 @@ - [Работа с референс-кодом из других проектов](feedback_reference_code_handling.md) — пользователь кидает код из eoffice_v3 и т.п.: адаптировать критически, не копировать вслепую, но уважать явные повторные сигналы по неймингу - [Идея: CLI-точка входа](idea_cli_entrypoint.md) — отложенный cli.php для миграций/крона - [Никакого inline JS в HTML](feedback_no_inline_js.md) — только jQuery `.on()` в отдельном .js-файле, даже для мелочи вроде onchange +- [Независимость движка от приложения](feedback_engine_app_independence.md) — Services/System не должны импортировать App\* или знать схему конкретного проекта +- [Бюджет — актуальный план (2026)](бюджет_текущий_план.md) — что реально согласовано в разговоре; старый бюджет_проект_план.md — только источник идей, не требований +- [Не переиспользовать память молча после сброса](feedback_no_silent_memory_reuse_after_reset.md) — после "снеси всё, начнём заново" каждое решение переподтверждается заново, даже если уже записано diff --git a/App/Controller/AboutController.php b/App/Controller/AboutController.php deleted file mode 100644 index 96dcd41..0000000 --- a/App/Controller/AboutController.php +++ /dev/null @@ -1,26 +0,0 @@ -render('about'); - } -} diff --git a/App/Controller/Admin/AdminController.php b/App/Controller/Admin/AdminController.php deleted file mode 100644 index 4a3e9d3..0000000 --- a/App/Controller/Admin/AdminController.php +++ /dev/null @@ -1,29 +0,0 @@ - В админку пускаем только роль admin */ - protected array $_auth_roles = ['admin']; -} diff --git a/App/Controller/Admin/LogsController.php b/App/Controller/Admin/LogsController.php deleted file mode 100644 index e3f0065..0000000 --- a/App/Controller/Admin/LogsController.php +++ /dev/null @@ -1,44 +0,0 @@ - $request?->query('date') ?: date('Y-m-d'), - 'channel' => $request?->query('channel') ?: 'action', - 'level' => $request?->query('level') ?: '', - 'q' => $request?->query('q') ?: '', - ]; - - return $this->render('logs', [ - 'entries' => $reader->read($filters), - 'filters' => $filters, - 'dates' => $reader->dates(), - ]); - } -} diff --git a/App/Controller/FeedbackController.php b/App/Controller/FeedbackController.php deleted file mode 100644 index 1527f41..0000000 --- a/App/Controller/FeedbackController.php +++ /dev/null @@ -1,52 +0,0 @@ -method() === HTTPRequest::POST) { - // CSRF уже проверен в BaseController::before(). Здесь — только данные. - $validation = Validation::factory($request->post()) - ->label('email', 'E-mail') - ->label('message', 'Сообщение') - ->rule('email', 'required') - ->rule('email', 'email') - ->rule('message', 'required') - ->rule('message', 'min_length', [10]); - - if ($validation->check()) { - // Данные чистые — здесь было бы сохранение/отправка письма. - return $this->render('feedback_ok', ['email' => $request->post('email')]); - } - - $errors = $validation->errors(); - } - - return $this->render('feedback', ['errors' => $errors]); - } -} diff --git a/App/Controller/IndexController.php b/App/Controller/IndexController.php index 2c3c308..11c0264 100644 --- a/App/Controller/IndexController.php +++ b/App/Controller/IndexController.php @@ -8,14 +8,15 @@ namespace App\Controller; -use App\Repositories\BudgetRepository; +use Services\Auth; use System\Classes\Controller; -use System\Classes\HTTP\Client\Curl; -use System\Classes\HTTP\Client\Request as HttpRequest; +use System\Classes\HTTP\Request as HTTPRequest; use System\Classes\MyException; +use System\Classes\Request; +use System\Classes\Validation; /** - * Демонстрационный контроллер главной страницы. + * Главная страница = страница входа (пока в проекте нет ничего, кроме авторизации). */ class IndexController extends Controller { @@ -25,30 +26,23 @@ class IndexController extends Controller */ public function indexAction(): string { - $budgetR = new BudgetRepository(); + $request = Request::$current; + $error = null; - $budgetR->getB(); + if ($request->method() === HTTPRequest::POST) { + $validation = Validation::factory($request->post()) + ->label('login', 'Логин') + ->label('password', 'Пароль') + ->rule('login', 'required') + ->rule('password', 'required'); - return $this->render('index'); - } + if ($validation->check() && Auth::instance()->login($request->post('login'), $request->post('password'))) { + return $this->renderContent('index', ['error' => null, 'user' => Auth::instance()->getUser()]); + } - /** - * Демонстрация POST-запроса с JSON-телом к внешнему API. - * - * @return string - * @throws \JsonException - */ - public function postAction(): string - { - // POST с JSON-телом - $response = (new Curl())->execute( - HttpRequest::factory('https://jsonplaceholder.typicode.com/posts') - ->method('POST') - ->json(['title' => 'Bicycle', 'body' => 'test', 'userId' => 1]) - ); + $error = 'Неверный логин или пароль'; + } - $created = $response->isSuccess() ? $response->json() : []; - - return $this->render('index', ['created' => $created]); + return $this->renderContent('index', ['error' => $error, 'user' => Auth::instance()->getUser()]); } } diff --git a/App/Controller/LoginController.php b/App/Controller/LoginController.php deleted file mode 100644 index eb348aa..0000000 --- a/App/Controller/LoginController.php +++ /dev/null @@ -1,62 +0,0 @@ -method() === HTTPRequest::POST) { - $validation = Validation::factory($request->post()) - ->label('login', 'Логин') - ->label('password', 'Пароль') - ->rule('login', 'required') - ->rule('password', 'required'); - - if ($validation->check() && Auth::instance()->login($request->post('login'), $request->post('password'))) { - HTTPException::factory(302, '/admin/logs')->getResponse(); - } - - $error = 'Неверный логин или пароль'; - } - - return $this->render('login', ['error' => $error]); - } - - /** - * Разлогинивает пользователя и возвращает на главную. - * - * @return string - */ - public function logoutAction(): string - { - Auth::instance()->logout(); - - HTTPException::factory(302, '/')->getResponse(); - } -} diff --git a/App/Controller/NewsController.php b/App/Controller/NewsController.php deleted file mode 100644 index d24a755..0000000 --- a/App/Controller/NewsController.php +++ /dev/null @@ -1,30 +0,0 @@ - '2026-08-07', 'title' => 'Запущен Bicycle', 'text' => 'Первый релиз самописного PHP MVC-фреймворка.'], - ]; - - return $this->render('news', ['news' => $news]); - } -} diff --git a/App/Repositories/BudgetRepository.php b/App/Repositories/BudgetRepository.php deleted file mode 100644 index 70631c5..0000000 --- a/App/Repositories/BudgetRepository.php +++ /dev/null @@ -1,36 +0,0 @@ -pdo->prepare($sql); - $stmt->execute(); - - return $stmt->fetchAll(); - } -} diff --git a/App/config/config.php b/App/config/config.php index fe3724c..9ab9338 100644 --- a/App/config/config.php +++ b/App/config/config.php @@ -10,10 +10,11 @@ return [ 'default' => [ 'driver' => 'pdo', 'connection' => [ - 'host' => '192.168.1.104', + 'host' => '192.168.1.36', + 'port' => 3307, 'user' => 'root', - 'password' => 'password', - 'dbname' => 'MatMed_BudgetV2' + 'password' => 'qwerty', + 'dbname' => 'budget' ], ], ], diff --git a/App/media/css/app.css b/App/media/css/app.css deleted file mode 100644 index 91df627..0000000 --- a/App/media/css/app.css +++ /dev/null @@ -1 +0,0 @@ -/* Bicycle — собственные стили сайта поверх Bootstrap */ diff --git a/App/media/js/app.js b/App/media/js/app.js deleted file mode 100644 index 969aa95..0000000 --- a/App/media/js/app.js +++ /dev/null @@ -1 +0,0 @@ -// Bicycle — собственный JS сайта diff --git a/App/media/js/logs.js b/App/media/js/logs.js deleted file mode 100644 index ba096c7..0000000 --- a/App/media/js/logs.js +++ /dev/null @@ -1,6 +0,0 @@ -$(function () { - // Смена даты/канала/уровня — сразу отправляет форму; текст ищем по кнопке "Найти". - $('#logs-filters').on('change', 'select[name="date"], select[name="channel"], select[name="level"]', function () { - $(this).closest('form').trigger('submit'); - }); -}); diff --git a/App/media/manifest.json b/App/media/manifest.json deleted file mode 100644 index e8570e9..0000000 --- a/App/media/manifest.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Bicycle", - "short_name": "Bicycle", - "start_url": "/", - "display": "standalone" -} diff --git a/App/view/About/about.html b/App/view/About/about.html deleted file mode 100644 index d5a5b0a..0000000 --- a/App/view/About/about.html +++ /dev/null @@ -1,13 +0,0 @@ -setStyle('app.css'); -$this->setScript('app.js'); -?> - -

О нас

-

Bicycle — самописный PHP MVC micro-framework. Название отражает идиому «изобрести велосипед».

diff --git a/App/view/Admin/Logs/logs.html b/App/view/Admin/Logs/logs.html deleted file mode 100644 index 701cfed..0000000 --- a/App/view/Admin/Logs/logs.html +++ /dev/null @@ -1,81 +0,0 @@ - [ - 'error' => 'danger', - 'warning' => 'warning', - 'info' => 'primary', - 'debug' => 'secondary', -][$l] ?? 'secondary'; - -$this->setScript('logs.js'); -?> -

Логи

- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- - -
Записей нет.
- - - - - - - - - - - - - - - - - - -
ВремяУровеньСообщение
- diff --git a/App/view/Feedback/feedback.html b/App/view/Feedback/feedback.html deleted file mode 100644 index 42ae34b..0000000 --- a/App/view/Feedback/feedback.html +++ /dev/null @@ -1,37 +0,0 @@ - сообщение] - */ - -use System\Classes\CSRF; - -?> - -
- - -

- - - - -

- -

- - - - -

- - -
diff --git a/App/view/Feedback/feedback_ok.html b/App/view/Feedback/feedback_ok.html deleted file mode 100644 index fa0c75f..0000000 --- a/App/view/Feedback/feedback_ok.html +++ /dev/null @@ -1,13 +0,0 @@ - - -

Спасибо! Сообщение от принято.

diff --git a/App/view/Index/index.html b/App/view/Index/index.html index 8f7051a..f0a2e7e 100644 --- a/App/view/Index/index.html +++ b/App/view/Index/index.html @@ -1,13 +1,54 @@ + + + + + + Бюджет + + + +
+
+

Вход

-

Главная

-

Добро пожаловать в Bicycle — самописный PHP MVC micro-framework.

+ +

Вы вошли как .

+ + +
+ +
+ + +
+ +
+ +
+ +
+ + +
+ +
+
+ + diff --git a/App/view/Login/login.html b/App/view/Login/login.html deleted file mode 100644 index 3f08324..0000000 --- a/App/view/Login/login.html +++ /dev/null @@ -1,34 +0,0 @@ - -

Вход

- - -
- - -
- - -
- -
- -
- -
- - -
diff --git a/App/view/News/news.html b/App/view/News/news.html deleted file mode 100644 index 0861a1b..0000000 --- a/App/view/News/news.html +++ /dev/null @@ -1,18 +0,0 @@ - - -

Новости

- - -
-
-

-

-
- diff --git a/App/view/layout.html b/App/view/layout.html index 25c9852..6e22410 100644 --- a/App/view/layout.html +++ b/App/view/layout.html @@ -1,41 +1,37 @@ - Bicycle + Бюджет getStyles(); ?> - - -
-
- -
-
+ +
+ +
getScripts(); ?> - + diff --git a/App/view/menu_side.html b/App/view/menu_side.html deleted file mode 100644 index cf6733b..0000000 --- a/App/view/menu_side.html +++ /dev/null @@ -1,16 +0,0 @@ - -
- - - - - -
diff --git a/App/view/menu_top.html b/App/view/menu_top.html deleted file mode 100644 index 0dd8723..0000000 --- a/App/view/menu_top.html +++ /dev/null @@ -1,23 +0,0 @@ - - diff --git a/CLAUDE.md b/CLAUDE.md index 8a0189a..d6433c7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -580,11 +580,11 @@ $this->setScript('about.js'); Расстановка имён — как в проекте eoffice_v3: - `System\Classes\BaseController` (abstract) — голое ядро: жизненный цикл `executeAction()` (`before()` → экшен → `after()`), хуки по умолчанию пустые. Нейтрально к вебу/API. -- `System\Classes\Controller extends BaseController` — веб: `render()` (layout + верхнее/боковое меню + content) и `renderContent()` (только content, без layout и меню — для ajax-фрагментов), авто-CSRF в `before()`, `json($data, $status)` для JSON-ответов. Меню — отдельные хуки: `menuTop()`/`menuSide()` отдают пункты (массив `[['title','url','active'], ...]`), `renderMenuTop()`/`renderMenuSide()` рендерят их через `App/view/menu_top.html`/`menu_side.html` в готовый HTML *до* передачи в layout (`renderMenuSide()` — пустая строка, если пунктов нет). Оба хука — общие для всего сайта (не переопределяются в конкретных контроллерах), активный пункт — по текущему URI, видны на любой странице любому пользователю независимо от роли/авторизации (сейчас в `menuSide()` заглушка — «Главная», «О нас», «Новости»; для последних двух заведены `AboutController`/`NewsController`). Layout — `App/view/layout.html`: верхнее меню всегда, боковое — колонка `aside` (Bootstrap grid) появляется, только если `$menu_side` не пуст; иначе `content` на всю ширину `
`. Никакой проверки `isAjax()` нет — экшен сам решает, вызывать `render()` или `renderContent()`. CSS/JS — Bootstrap, Bootstrap Icons, jQuery из `/vendor/`. Единый layout для всего сайта, включая админку. **Контроллеры приложения наследуют `Controller` напрямую.** +- `System\Classes\Controller extends BaseController` — веб: `render()` (layout + content) и `renderContent()` (только content, без layout — для ajax-фрагментов), авто-CSRF в `before()`, `json($data, $status)` для JSON-ответов. **Меню — не забота движка**: раньше `menuTop()`/`menuSide()` строили пункты меню PHP-массивом прямо в `System\Classes\Controller` — это было ошибкой (движок не должен знать структуру навигации конкретного проекта, см. `.claude/memory/feedback_engine_app_independence.md` в репозитории — тот же принцип, только для меню, а не для БД). Убрано; навигация — обычная разметка прямо в `App/view/layout.html`, без промежуточного PHP-слоя. Никакой проверки `isAjax()` нет — экшен сам решает, вызывать `render()` или `renderContent()`. CSS/JS — Bootstrap, Bootstrap Icons, jQuery из `/vendor/`. **Контроллеры приложения наследуют `Controller` напрямую.** API-контроллер делается не отдельным классом, а флагом: `extends Controller` + `$_csrf_protection = false` + ответы через `json()` (так же, как в eoffice_v3). -**Админка** (`App/Controller/Admin/`): базовый `App\Controller\Admin\AdminController extends Controller` своего layout и меню не задаёт — рендерится в том же `App/view/layout.html`, что и весь сайт, с тем же общим `Controller::menuSide()` (не связан с админкой — заглушка на весь сайт). Включает `$_auth_protection = true` — доступ только авторизованным (см. [Auth](#auth-servicesauth)), неавторизованный редиректится на `/login`. Конкретные страницы (`LogsController` и т.п.) наследуют `AdminController`. +> Проект (`App/*`) сейчас пуст — снесён и переписывается с нуля, см. `.claude/memory/бюджет_текущий_план.md`. Упоминания конкретных контроллеров приложения (админка, демо-страницы и т.п.) ниже по файлу могут быть устаревшими до следующего прохода по документации. `Request::execute()` вызывает `executeAction({action}Action)`, поэтому `before()/after()` работают для любого контроллера прозрачно. Авторизация конкретного контроллера включается/выключается флагом `$_auth_protection` (см. раздел Auth), а не переопределением `before()` вручную. diff --git a/Services/DataBase/Classes/PdoDriver.php b/Services/DataBase/Classes/PdoDriver.php index 33776d7..9119b79 100644 --- a/Services/DataBase/Classes/PdoDriver.php +++ b/Services/DataBase/Classes/PdoDriver.php @@ -27,7 +27,7 @@ class PdoDriver protected int $_transaction_level = 0; /** - * @param array $config Параметры подключения: host|hosts, dbname, user, password, charset (по умолчанию utf8mb4) + * @param array $config Параметры подключения: host|hosts, port, dbname, user, password, charset (по умолчанию utf8mb4) */ public function __construct(protected array $config) { @@ -46,6 +46,7 @@ class PdoDriver } $hosts = $this->config['hosts'] ?? [$this->config['host'] ?? 'localhost']; + $port = $this->config['port'] ?? null; $dbname = $this->config['dbname'] ?? ''; $user = $this->config['user'] ?? ''; $password = $this->config['password'] ?? ''; @@ -54,7 +55,7 @@ class PdoDriver $errors = []; foreach ($hosts as $host) { - $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset"; + $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset" . ($port !== null ? ";port=$port" : ''); try { $pdo = new PDO($dsn, $user, $password, [ diff --git a/System/Classes/Controller.php b/System/Classes/Controller.php index 131608a..fcb29eb 100644 --- a/System/Classes/Controller.php +++ b/System/Classes/Controller.php @@ -107,7 +107,9 @@ class Controller extends BaseController /** - * Рендерит шаблон контента внутри layout вместе с верхним и боковым меню. + * Рендерит шаблон контента внутри layout. Меню — не забота движка: разметка/ссылки + * навигации живут прямо в App/view/layout.html (см. этот файл в проекте), а не строятся + * тут из PHP-массива. * * @param string $template Имя шаблона контента без расширения * @param array $data Данные, передаваемые в шаблон @@ -118,9 +120,7 @@ class Controller extends BaseController protected function render(string $template, array $data = [], string $dir = ''): string { return (new View($this->_layout, 'view', [ - 'menu_top' => $this->renderMenuTop(), - 'menu_side' => $this->renderMenuSide(), - 'content' => $this->renderContent($template, $data, $dir), + 'content' => $this->renderContent($template, $data, $dir), ]))->render(); } @@ -146,84 +146,6 @@ class Controller extends BaseController return (new View($template, $dir, $data))->render(); } - /** - * HTML верхнего меню сайта — рендерит {@see menuTop()} через шаблон view/menu_top. - * - * @return string - * @throws MyException - */ - protected function renderMenuTop(): string - { - return (new View('menu_top', 'view', ['menu' => $this->menuTop()]))->render(); - } - - /** - * HTML бокового меню раздела — рендерит {@see menuSide()} через шаблон view/menu_side. - * Пустая строка, если пунктов нет (по умолчанию бокового меню нет вовсе; - * layout.html в этом случае не рисует колонку сайдбара). - * - * @return string - * @throws MyException - */ - protected function renderMenuSide(): string - { - $items = $this->menuSide(); - - return $items ? (new View('menu_side', 'view', ['menu' => $items]))->render() : ''; - } - - /** - * Пункты верхнего меню сайта. Активный пункт — по текущему URI. - * - * @return array - */ - protected function menuTop(): array - { - $uri = Request::$current?->uri() ?? ''; - $user = Auth::instance()->getUser(); - $items = [ - ['title' => 'Главная', 'url' => '/'], - ['title' => 'Обратная связь', 'url' => '/feedback'], - ]; - - if (($user['role'] ?? null) === 'admin') { - $items[] = ['title' => 'Админка', 'url' => '/admin/logs']; - } - - $items[] = $user - ? ['title' => 'Выйти (' . $user['login'] . ')', 'url' => '/login/logout'] - : ['title' => 'Войти', 'url' => '/login']; - - foreach ($items as &$item) { - $item['active'] = trim($item['url'], '/') === $uri; - } - - return $items; - } - - /** - * Пункты бокового меню — общие для всего сайта (как и menuTop()), видны - * на любой странице любому пользователю, независимо от роли и авторизации. - * Активный пункт — по текущему URI. - * - * @return array - */ - protected function menuSide(): array - { - $uri = Request::$current?->uri() ?? ''; - $items = [ - ['title' => 'Главная', 'url' => '/'], - ['title' => 'О нас', 'url' => '/about'], - ['title' => 'Новости', 'url' => '/news'], - ]; - - foreach ($items as &$item) { - $item['active'] = trim($item['url'], '/') === $uri; - } - - return $items; - } - /** * Формирует JSON-ответ: ставит статус и Content-Type, кодирует данные. * diff --git a/tests/Unit/ViewTest.php b/tests/Unit/ViewTest.php index 423e782..40e2941 100644 --- a/tests/Unit/ViewTest.php +++ b/tests/Unit/ViewTest.php @@ -10,9 +10,26 @@ 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 @@ -21,6 +38,16 @@ class ViewTest extends TestCase 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) { @@ -128,12 +155,12 @@ class ViewTest extends TestCase public function testSetStyleRegistersLinkTag(): void { - (new View())->setStyle('app.css'); + (new View())->setStyle($this->fixtureCss); ob_start(); (new View())->getStyles(); $html = ob_get_clean(); - $this->assertStringContainsString('', $html); + $this->assertStringContainsString('', $html); } public function testSetStyleMissingFileEchoesAlert(): void @@ -152,19 +179,19 @@ class ViewTest extends TestCase public function testSetScriptRegistersScriptTag(): void { - (new View())->setScript('app.js'); + (new View())->setScript($this->fixtureJs); ob_start(); (new View())->getScripts(); $html = ob_get_clean(); $this->assertStringContainsString( - '', $html + '', $html ); } public function testSetScriptCustomType(): void { - (new View())->setScript('app.js', 'module'); + (new View())->setScript($this->fixtureJs, 'module'); ob_start(); (new View())->getScripts(); $html = ob_get_clean(); @@ -187,12 +214,12 @@ class ViewTest extends TestCase public function testSetManifestRegistersLinkTag(): void { - (new View())->setManifest('manifest.json'); + (new View())->setManifest($this->fixtureManifest); ob_start(); (new View())->getStyles(); $html = ob_get_clean(); - $this->assertStringContainsString('', $html); + $this->assertStringContainsString('', $html); } public function testSetManifestMissingFileEchoesAlert(): void @@ -207,8 +234,8 @@ class ViewTest extends TestCase public function testGetStylesAndScriptsAreSharedAcrossViewInstances(): void { // content и layout — разные объекты View, но реестр общий (см. Controller::render()) - (new View())->setStyle('app.css'); - (new View())->setScript('app.js'); + (new View())->setStyle($this->fixtureCss); + (new View())->setScript($this->fixtureJs); ob_start(); (new View())->getStyles(); @@ -218,7 +245,7 @@ class ViewTest extends TestCase (new View())->getScripts(); $scripts = ob_get_clean(); - $this->assertStringContainsString('app.css', $styles); - $this->assertStringContainsString('app.js', $scripts); + $this->assertStringContainsString($this->fixtureCss, $styles); + $this->assertStringContainsString($this->fixtureJs, $scripts); } }