mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Update theme components
This commit is contained in:
@@ -7,6 +7,7 @@ namespace Tests\Unit;
|
||||
use App\Http\Middleware\ContentSecurityPolicy;
|
||||
use Illuminate\Config\Repository;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Vite;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
@@ -16,32 +17,48 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ContentSecurityPolicyTest extends TestCase
|
||||
{
|
||||
private ?Container $originalContainer;
|
||||
private Container $originalContainer;
|
||||
|
||||
private ?Application $originalFacadeApplication;
|
||||
|
||||
private string $hotFile;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->originalContainer = Container::getInstance();
|
||||
$facadeApplication = Facade::getFacadeApplication();
|
||||
$this->originalFacadeApplication = $facadeApplication instanceof Application ? $facadeApplication : null;
|
||||
|
||||
$container = new Container;
|
||||
$container->instance('config', new Repository([
|
||||
$application = new Application(dirname(__DIR__, 2));
|
||||
$application->instance('config', new Repository([
|
||||
'app' => ['env' => 'production'],
|
||||
'captcha' => [
|
||||
'provider' => null,
|
||||
'turnstile' => ['enabled' => false],
|
||||
],
|
||||
]));
|
||||
$container->instance(Vite::class, new Vite);
|
||||
$application->instance(Vite::class, new Vite);
|
||||
|
||||
Container::setInstance($container);
|
||||
Facade::setFacadeApplication($container);
|
||||
Container::setInstance($application);
|
||||
Facade::setFacadeApplication($application);
|
||||
Facade::clearResolvedInstances();
|
||||
|
||||
$this->hotFile = sys_get_temp_dir().'/nntmux-csp-hot-'.bin2hex(random_bytes(8));
|
||||
app(Vite::class)->useHotFile($this->hotFile);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (is_file($this->hotFile)) {
|
||||
unlink($this->hotFile);
|
||||
}
|
||||
|
||||
Horizon::$nonceAttribute = '';
|
||||
Facade::clearResolvedInstances();
|
||||
Container::setInstance($this->originalContainer);
|
||||
Facade::setFacadeApplication($this->originalContainer);
|
||||
Facade::setFacadeApplication($this->originalFacadeApplication);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
@@ -59,4 +76,52 @@ class ContentSecurityPolicyTest extends TestCase
|
||||
$this->assertStringContainsString('https://fonts.bunny.net', (string) $response->headers->get('Content-Security-Policy'));
|
||||
$this->assertSame(4, substr_count((string) $response->getContent(), 'nonce="'.$nonce.'"'));
|
||||
}
|
||||
|
||||
public function test_local_vite_hot_reload_sources_are_added_to_the_csp(): void
|
||||
{
|
||||
config()->set('app.env', 'local');
|
||||
file_put_contents($this->hotFile, 'http://localhost:5173');
|
||||
|
||||
$response = (new ContentSecurityPolicy)->handle(
|
||||
Request::create('/'),
|
||||
static fn (): Response => new Response,
|
||||
);
|
||||
|
||||
$csp = (string) $response->headers->get('Content-Security-Policy');
|
||||
|
||||
$this->assertStringContainsString("style-src-elem 'self' 'unsafe-inline'", $csp);
|
||||
$this->assertStringContainsString('https://cdn.tiny.cloud http://localhost:5173', $csp);
|
||||
$this->assertStringContainsString('https://sp.tinymce.com http://localhost:5173 ws://localhost:5173', $csp);
|
||||
}
|
||||
|
||||
public function test_vite_hot_reload_sources_are_not_added_outside_local_environment(): void
|
||||
{
|
||||
file_put_contents($this->hotFile, 'http://localhost:5173');
|
||||
|
||||
$response = (new ContentSecurityPolicy)->handle(
|
||||
Request::create('/'),
|
||||
static fn (): Response => new Response,
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'http://localhost:5173',
|
||||
(string) $response->headers->get('Content-Security-Policy'),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_non_loopback_vite_hot_reload_sources_are_rejected(): void
|
||||
{
|
||||
config()->set('app.env', 'local');
|
||||
file_put_contents($this->hotFile, 'https://assets.example.com:5173');
|
||||
|
||||
$response = (new ContentSecurityPolicy)->handle(
|
||||
Request::create('/'),
|
||||
static fn (): Response => new Response,
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'https://assets.example.com:5173',
|
||||
(string) $response->headers->get('Content-Security-Policy'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class LayoutShellMarkupTest extends TestCase
|
||||
{
|
||||
public function test_user_and_admin_layouts_expose_accessible_shell_landmarks(): void
|
||||
{
|
||||
foreach (['main', 'admin'] as $layout) {
|
||||
$markup = $this->view("layouts/{$layout}.blade.php");
|
||||
|
||||
$this->assertStringContainsString('class="skip-link"', $markup);
|
||||
$this->assertStringContainsString('class="layout-shell', $markup);
|
||||
$this->assertStringContainsString('class="layout-sidebar', $markup);
|
||||
$this->assertStringContainsString('class="layout-topbar', $markup);
|
||||
$this->assertStringContainsString('id="main-content"', $markup);
|
||||
$this->assertStringContainsString('id="mobile-sidebar-backdrop"', $markup);
|
||||
$this->assertStringContainsString('aria-controls="sidebar"', $markup);
|
||||
$this->assertStringContainsString('aria-expanded="false"', $markup);
|
||||
}
|
||||
|
||||
$this->assertStringNotContainsString('assets/images/logo.svg', $this->view('layouts/main.blade.php'));
|
||||
}
|
||||
|
||||
public function test_mobile_sidebar_script_supports_dismissal_and_focus_state(): void
|
||||
{
|
||||
$script = $this->resource('js/alpine/components/mobile-nav.js');
|
||||
|
||||
$this->assertStringContainsString("event.key === 'Escape'", $script);
|
||||
$this->assertStringContainsString("mobileSidebarBackdrop?.addEventListener('click'", $script);
|
||||
$this->assertStringContainsString("mobileSidebarToggle.setAttribute('aria-expanded'", $script);
|
||||
$this->assertStringContainsString('mobileSidebarClose?.focus()', $script);
|
||||
}
|
||||
|
||||
public function test_shared_shell_styles_include_focus_and_reduced_motion_support(): void
|
||||
{
|
||||
$stylesheet = $this->resource('css/app.css');
|
||||
|
||||
$this->assertStringContainsString(':focus-visible', $stylesheet);
|
||||
$this->assertStringContainsString('@media (prefers-reduced-motion: reduce)', $stylesheet);
|
||||
$this->assertStringContainsString('.layout-flash--success', $stylesheet);
|
||||
$this->assertStringContainsString('.admin-page-header__icon', $stylesheet);
|
||||
}
|
||||
|
||||
public function test_shared_icon_only_controls_have_accessible_names(): void
|
||||
{
|
||||
$this->assertStringContainsString('aria-label="Search releases"', $this->view('partials/header-menu.blade.php'));
|
||||
$this->assertStringContainsString('aria-label="Close confirmation"', $this->view('partials/confirmation-modal.blade.php'));
|
||||
$this->assertStringContainsString('aria-label="Dismiss notification"', $this->view('partials/toast-notifications.blade.php'));
|
||||
}
|
||||
|
||||
private function view(string $path): string
|
||||
{
|
||||
return $this->resource("views/{$path}");
|
||||
}
|
||||
|
||||
private function resource(string $path): string
|
||||
{
|
||||
return (string) file_get_contents(__DIR__."/../../resources/{$path}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ViteDockerConfigurationTest extends TestCase
|
||||
{
|
||||
public function test_vite_uses_the_docker_published_port_without_fallback(): void
|
||||
{
|
||||
$viteConfig = $this->projectFile('vite.config.js');
|
||||
$composeConfig = $this->projectFile('docker-compose.yml');
|
||||
|
||||
$this->assertStringContainsString("loadEnv(mode, process.cwd(), '')", $viteConfig);
|
||||
$this->assertStringContainsString("environment.VITE_PORT || '5173'", $viteConfig);
|
||||
$this->assertStringContainsString("environment.VITE_DEV_SERVER_HOST || 'localhost'", $viteConfig);
|
||||
$this->assertStringContainsString("host: '0.0.0.0'", $viteConfig);
|
||||
$this->assertStringContainsString('strictPort: true', $viteConfig);
|
||||
$this->assertStringContainsString('origin: `http://${viteDevServerHost}:${vitePort}`', $viteConfig);
|
||||
$this->assertStringContainsString("'\${VITE_PORT:-5173}:\${VITE_PORT:-5173}'", $composeConfig);
|
||||
$this->assertStringContainsString('VITE_DEV_SERVER_HOST=localhost', $this->projectFile('.env.example'));
|
||||
}
|
||||
|
||||
public function test_container_startup_clears_stale_vite_hot_state(): void
|
||||
{
|
||||
$startContainer = $this->projectFile('docker/8.5/start-container');
|
||||
|
||||
$unlinkPosition = strpos($startContainer, 'unlink /var/www/html/public/hot');
|
||||
$supervisorPosition = strpos($startContainer, 'exec /usr/bin/supervisord');
|
||||
|
||||
$this->assertNotFalse($unlinkPosition);
|
||||
$this->assertNotFalse($supervisorPosition);
|
||||
$this->assertLessThan($supervisorPosition, $unlinkPosition);
|
||||
}
|
||||
|
||||
private function projectFile(string $path): string
|
||||
{
|
||||
return (string) file_get_contents(__DIR__."/../../{$path}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user