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');
-?>
-Логи
-
-
-
-
- Записей нет.
-
-
-
-
- | Время |
- Уровень |
- Сообщение |
-
-
-
-
-
- | = htmlspecialchars($e['datetime'], ENT_QUOTES) ?> |
- = htmlspecialchars($e['level'], ENT_QUOTES) ?> |
- = htmlspecialchars($e['message'], ENT_QUOTES, 'UTF-8') ?> |
-
-
-
-
-
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 @@
-
-
-Спасибо! Сообщение от = htmlspecialchars($email, ENT_QUOTES, 'UTF-8') ?> принято.
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.
+
+
Вы вошли как = htmlspecialchars($user['login'], ENT_QUOTES, 'UTF-8') ?>.
+
+
+
= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?>
+
+
+
+
+
+
+
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 @@
-
-Вход
-
-
- = htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?>
-
-
-
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 @@
-
-
-Новости
-
-
-
- = htmlspecialchars($item['date'], ENT_QUOTES, 'UTF-8') ?>
- = htmlspecialchars($item['title'], ENT_QUOTES, 'UTF-8') ?>
- = htmlspecialchars($item['text'], ENT_QUOTES, 'UTF-8') ?>
-
-
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(); ?>
-= $menu_top ?>
-
-
-
+
+
+ = $content ?>
+
getScripts(); ?>
-= \System\Classes\ProfilerToolbar::render() ?>
+= ProfilerToolbar::render() ?>
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);
}
}