test(polish): match channel row, nested-reply, cursor paginator, hotkeys
Four tests covering the post-audit polish commits: - Match watch page renders the shared .channel-row component. - Reply-to-reply button carries data-parent-id and data-handle so the JS can prefill "@author " when the parent reply form opens. - liked() controller returns a CursorPaginator instance (controller introspection — avoids a 400 KB view render per assertion). - video-player component ships the ">" / "<" playback-rate hotkey. Also cleans up a stale ->onEachSide()->links() call in user/liked.blade that would blow up now that the controller returns a CursorPaginator (which doesn't support onEachSide). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
341e94a4f8
commit
73de5bc97e
2 files changed
+167
-1
No files matched your search
@@ -146,7 +146,8 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">{{ $videos->onEachSide(1)->links() }}</div>
|
{{-- Cursor paginator has no "N pages" concept — just prev/next. --}}
|
||||||
|
<div class="mt-4">{{ $videos->links() }}</div>
|
||||||
@endif
|
@endif
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Http\Middleware\VerifyCsrfToken;
|
||||||
|
use App\Models\Comment;
|
||||||
|
use App\Models\SportsMatch;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Video;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-audit polish — the deferred items we tackled in bulk after Phase 7:
|
||||||
|
* match channel row, read-more clamp, nested-reply prefill, player hotkeys,
|
||||||
|
* cursor pagination on /liked and /history.
|
||||||
|
*/
|
||||||
|
class PostAuditPolishTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
putenv('DB_CONNECTION=sqlite');
|
||||||
|
putenv('DB_DATABASE=:memory:');
|
||||||
|
$_ENV['DB_CONNECTION'] = 'sqlite';
|
||||||
|
$_ENV['DB_DATABASE'] = ':memory:';
|
||||||
|
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->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),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user