withoutMiddleware([ VerifyCsrfToken::class, \Illuminate\Session\Middleware\AuthenticateSession::class, ]); } // ─── Match page: channel row ───────────────────── public function test_match_watch_page_renders_the_channel_row(): void { $owner = $this->makeUser('match-owner'); $video = Video::create([ 'user_id' => $owner->id, 'title' => 'MatchTitle', 'description' => '', 'filename' => 'users/x/sports/y/video.mp4', 'path' => 'users/x/sports/y/video.mp4', 'type' => 'match', 'visibility' => 'public', 'status' => 'ready', 'share_token' => bin2hex(random_bytes(8)), ]); // Minimal sports_match so the match blade renders without null checks exploding. SportsMatch::create([ 'video_id' => $video->id, 'user_id' => $owner->id, 'title' => 'MatchTitle', 'participants' => [], 'sport' => 'karate', ]); $html = $this->get(route('videos.show', $video))->getContent(); $this->assertStringContainsString('channel-row', $html, 'Match watch page must render the shared channel row component.'); } // ─── Comment reply: nested handle button present ── public function test_reply_to_reply_button_carries_data_handle(): void { $owner = $this->makeUser('replyowner'); $this->actingAs($owner); $video = Video::create([ 'user_id' => $owner->id, 'title' => 'Vid', 'description'=> '', 'filename' => 'users/x/videos/y/video.mp4', 'path' => 'users/x/videos/y/video.mp4', 'type' => 'generic', 'visibility' => 'public', 'status' => 'ready', 'share_token'=> bin2hex(random_bytes(8)), ]); $parent = Comment::create([ 'user_id' => $owner->id, 'video_id' => $video->id, 'body' => 'top', ]); $replier = $this->makeUser('replier'); Comment::create([ 'user_id' => $replier->id, 'video_id' => $video->id, 'parent_id' => $parent->id, 'body' => 'nested', ]); $html = view('components.video-comments', ['video' => $video])->render(); // The nested reply trigger must carry the reply author's handle so the // JS can prefill "@handle " when the parent's reply form opens. $this->assertMatchesRegularExpression( '/_comment-reply-nested-trigger[^"]*"\s+data-parent-id="' . $parent->id . '"\s+data-handle="' . preg_quote($replier->username, '/') . '"/', $html ); } // ─── Liked feed: cursor pagination ──────────────── public function test_liked_controller_returns_a_cursor_paginator(): void { // The controller introspection is enough to confirm the pagination // mode — the full HTML path depends on view rendering that costs // 400 KB per test and isn't the behaviour we're asserting on. $user = $this->makeUser('liker'); $this->actingAs($user); $route = app('router')->getRoutes()->getByName('liked'); [$class, $method] = explode('@', $route->getActionName()); $controller = app($class); $response = $controller->$method(); $videos = $response->getData()['videos'] ?? null; $this->assertInstanceOf( \Illuminate\Pagination\CursorPaginator::class, $videos, 'liked() must return a CursorPaginator instance.' ); } // ─── Player hotkey code is present ──────────────── public function test_player_component_handles_shift_dot_hotkey(): void { // The player component accepts a $video context — pass a bare // record so the view renders. $u = $this->makeUser('p'); $v = Video::create([ 'user_id' => $u->id, 'title' => 't', 'description'=> '', 'filename' => 'users/x/videos/y/video.mp4', 'path' => 'users/x/videos/y/video.mp4', 'type' => 'generic', 'visibility' => 'public', 'status' => 'ready', 'share_token'=> bin2hex(random_bytes(8)), ]); $html = view('components.video-player', ['video' => $v])->render(); $this->assertStringContainsString("case '>':", $html); $this->assertStringContainsString("case '<':", $html); $this->assertStringContainsString('video.playbackRate', $html); } // ─── Helpers ───────────────────────────────────── private function makeUser(string $prefix): User { return User::factory()->create([ 'username' => $prefix.'-'.substr(bin2hex(random_bytes(4)), 0, 6), ]); } }