Files
2026-07-01 11:20:21 +00:00

92 lines
5.4 KiB
PHP

<?php
require __DIR__.'/bootstrap.php';
function test_fail($message)
{
fwrite(STDERR, "FAIL: {$message}\n");
exit(1);
}
function test_assert($condition, $message)
{
if(!$condition)
test_fail($message);
echo "ok - {$message}\n";
}
function reset_request($request_uri)
{
$_SERVER['REQUEST_URI'] = $request_uri;
$_GET = array();
$_POST = array();
$_REQUEST = array();
URL::$route = array();
URL::$locator = '';
URL::MakeRoute();
}
reset_request('/web-app-starter/?themes&theme=portal-dark');
$themes_route = URL::ResolveViewFile('views');
test_assert($themes_route !== false && $themes_route['file'] === 'views/themes.php', 'theme gallery route resolves to the expected view');
reset_request('/web-app-starter/?workspace/projects');
$workspace_route = URL::ResolveViewFile('views');
test_assert($workspace_route !== false && $workspace_route['file'] === 'views/workspace/index.php', 'nested workspace route resolves to parent index');
test_assert(($workspace_route['param'] ?? null) === 'projects', 'nested workspace route exposes the trailing path segment as param');
reset_request('/web-app-starter/?toolbox');
$toolbox_route = URL::ResolveViewFile('views');
test_assert($toolbox_route !== false && $toolbox_route['file'] === 'views/toolbox.php', 'toolbox route resolves to the downstream backport demo');
reset_request('/web-app-starter/?realtime-demo');
$realtime_route = URL::ResolveViewFile('views');
test_assert($realtime_route !== false && $realtime_route['file'] === 'views/realtime-demo.php', 'realtime demo route resolves');
reset_request('/web-app-starter/?api/realtime');
$realtime_api_route = URL::ResolveViewFile('views');
test_assert($realtime_api_route !== false && $realtime_api_route['file'] === 'views/api/realtime.php', 'realtime backend API route resolves');
test_assert(URL::Link('', ['theme' => 'portal-dark']) === '/web-app-starter/?theme=portal-dark', 'root theme link omits the empty route segment');
test_assert(URL::Link('themes', ['theme' => 'portal-dark']) === '/web-app-starter/?themes&theme=portal-dark', 'named route links keep the route segment and query parameters');
test_assert(component_exists('components/example/theme-switcher'), 'component existence works for explicit component paths');
test_assert(component_exists('example/theme-switcher'), 'component existence works for shorthand component paths');
test_assert(component_exists('callable-fixture'), 'component existence works for caller-relative components directory');
test_assert(component_exists('basic/action-button'), 'action-button component exists');
test_assert(component_exists('basic/info-pill'), 'info-pill component exists');
test_assert(component_exists('navigation/tile-grid'), 'tile-grid component exists');
$callable_fixture_html = component('callable-fixture');
test_assert(str_contains($callable_fixture_html, 'callable fixture'), 'ERP-style callable component files render through the default render method');
$multi_fixture_html = component_call('multi-fixture', 'custom');
test_assert(str_contains($multi_fixture_html, 'custom fixture'), 'component_call preserves caller-relative lookup for named render methods');
$action_button_html = component('basic/action-button', ['label' => 'Load', 'route' => 'toolbox', 'htmx' => true, 'target' => '#target']);
test_assert(str_contains($action_button_html, 'hx-get=') && str_contains($action_button_html, '#target'), 'action-button renders htmx-shaped route actions');
$info_pill_html = component('basic/info-pill', ['label' => 'Details', 'content' => 'Escaped hover detail']);
test_assert(str_contains($info_pill_html, 'info-pill__trigger') && str_contains($info_pill_html, 'Escaped hover detail'), 'info-pill renders accessible hover detail');
$tile_grid_html = component('navigation/tile-grid', ['items' => [['caption' => 'Themes', 'url' => 'themes']]]);
test_assert(str_contains($tile_grid_html, 'tile-grid__tile') && str_contains($tile_grid_html, 'Themes'), 'tile-grid renders reusable navigation tiles');
$theme_switcher_html = component('example/theme-switcher');
test_assert(str_contains($theme_switcher_html, 'theme-switcher'), 'component rendering returns buffered output through the shorthand component API');
ob_start();
$wrapped_theme_switcher_html = component('example/theme-switcher', [':before' => '<b>', ':after' => '</b>']);
$component_hook_output = ob_get_clean();
test_assert($wrapped_theme_switcher_html !== '' && $component_hook_output === '<b></b>', 'component before/after hooks remain compatible with buffered components');
test_assert(short_id('0x1234567890abcdef', 4, 4) === '0x12…cdef', 'short_id compacts long identifiers');
test_assert(str_contains(id_token('0x1234567890abcdef'), 'data-copy='), 'id_token renders a copy-friendly identifier token');
test_assert(str_starts_with(Realtime::live_url('/live/'), 'ws'), 'Realtime::live_url derives a websocket URL');
$missing_broker_result = Realtime::command([['type' => 'list']]);
test_assert($missing_broker_result['result'] === false || isset($missing_broker_result['status']), 'Realtime::command returns structured status when broker is unavailable or reachable');
test_assert(Filebase::read_file(__DIR__.'/../tmp/missing-filebase-read-check') === '', 'Filebase::read_file handles missing files as empty content');
$portal_dark = cfg('theme/options/portal-dark');
test_assert(!empty($portal_dark['description']) && !empty($portal_dark['footer_text']), 'theme metadata is centralized in config');
echo "All smoke tests passed.\n";