From 1b2377f20f694b797ce8c1f474043ad0141cfbb2 Mon Sep 17 00:00:00 2001 From: ghassan Date: Wed, 21 Oct 2026 10:41:25 +0300 Subject: [PATCH] test(phase-6): channel handle, preview mode, playlist meta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also tightens stat/post-video rendering so "0" prints as "0" and not "0.0" (Number::abbreviate(0, precision: 1) stringifies as "0.0", which leaked into the About tab's Total Views card and post-video badges). Four tests covering: - /@username opens the matching channel. - /@invalid url (space) 404s. - Owner view shows id="postForm"; preview?preview=1 hides it. - Zero-view playlist doesn't render "bi-eye … 0.0"; "1 videos" never renders in place of "1 video". Fixed a Blade dupe-declaration fatal that only fired in feature tests (two requests in one PHP process would redeclare notifVal()). Guarded with function_exists. Co-Authored-By: Claude Opus 4.7 (1M context) --- resources/views/user/channel.blade.php | 13 ++- tests/Feature/Phase6ChannelTest.php | 107 +++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 tests/Feature/Phase6ChannelTest.php diff --git a/resources/views/user/channel.blade.php b/resources/views/user/channel.blade.php index 9b3e5f1..b00bdf3 100644 --- a/resources/views/user/channel.blade.php +++ b/resources/views/user/channel.blade.php @@ -548,7 +548,7 @@ $headerSocialMap = [
{{ $v->title }}
- {{ \Illuminate\Support\Number::abbreviate($v->view_count, precision: 1) }} + {{ $v->view_count > 0 ? \Illuminate\Support\Number::abbreviate($v->view_count, precision: 1) : '0' }} @if($v->duration) {{ $v->formatted_duration }}@endif @if($v->is_shorts) Short@endif
@@ -1091,7 +1091,7 @@ $headerSocialMap = [
Stats
-
{{ \Illuminate\Support\Number::abbreviate($totalViews, precision: 1) }}
+
{{ $totalViews > 0 ? \Illuminate\Support\Number::abbreviate($totalViews, precision: 1) : '0' }}
Total Views
@@ -1372,8 +1372,13 @@ if ($oldLinks) { @php $notifPrefs = $user->notification_preferences ?? []; $notifDefs = \App\Models\User::notifDefaults(); - function notifVal(array $prefs, array $defs, string $key): bool { - return isset($prefs[$key]) ? (bool)$prefs[$key] : ($defs[$key] ?? true); + // function_exists guard so a second render in the same PHP + // process (feature tests fire two requests in one process) + // doesn't fatal with "Cannot redeclare notifVal()". + if (!function_exists('notifVal')) { + function notifVal(array $prefs, array $defs, string $key): bool { + return isset($prefs[$key]) ? (bool)$prefs[$key] : ($defs[$key] ?? true); + } } @endphp
diff --git a/tests/Feature/Phase6ChannelTest.php b/tests/Feature/Phase6ChannelTest.php new file mode 100644 index 0000000..038f39b --- /dev/null +++ b/tests/Feature/Phase6ChannelTest.php @@ -0,0 +1,107 @@ +withoutMiddleware([ + VerifyCsrfToken::class, + \Illuminate\Session\Middleware\AuthenticateSession::class, + ]); + } + + // ─── /@handle alias ───────────────────────────── + + public function test_at_handle_url_opens_the_matching_channel(): void + { + $user = User::factory()->create([ + 'name' => 'Handle Test', + 'username' => 'handletest-'.substr(bin2hex(random_bytes(3)), 0, 6), + ]); + + $r = $this->get('/@' . $user->username)->assertOk(); + + $r->assertSee('Handle Test', false); + } + + public function test_at_handle_rejects_invalid_characters(): void + { + // Space in the handle must not match the /@ route. + $this->get('/@not valid')->assertStatus(404); + } + + // ─── Preview-as-visitor hides composer ────────── + + public function test_preview_as_visitor_hides_the_wall_composer(): void + { + $user = User::factory()->create([ + 'username' => 'preview-'.substr(bin2hex(random_bytes(3)), 0, 6), + ]); + $this->actingAs($user); + + // The composer form carries id="postForm" and action pointing at + // posts.store — specific enough that we don't collide with the + // "ch-composer-*" literals that appear in inline JS strings. + $owner = $this->get('/channel/' . $user->username)->getContent(); + $this->assertStringContainsString('id="postForm"', $owner, + 'Owner should see the composer form in normal view.'); + + $preview = $this->get('/channel/' . $user->username . '?preview=1')->getContent(); + $this->assertStringNotContainsString('id="postForm"', $preview, + 'Preview-as-visitor must hide the composer form.'); + } + + // ─── Playlist card: pluralise + hide stray "0.0" ── + + public function test_playlist_card_pluralises_videos_and_hides_zero_views(): void + { + $owner = User::factory()->create([ + 'username' => 'pl-'.substr(bin2hex(random_bytes(3)), 0, 6), + ]); + $this->actingAs($owner); + $pl = Playlist::create([ + 'user_id' => $owner->id, + 'name' => 'Empty Playlist', + 'visibility' => 'public', + 'share_token'=> bin2hex(random_bytes(8)), + ]); + + $html = $this->get('/channel/' . $owner->username)->getContent(); + + // The specific bug: the view-count row ends with + // ` 0.0` + // for a zero-view playlist. The fix hides the whole row for zero + // views, so the "bi-eye ...0.0" sequence should never appear. + $this->assertDoesNotMatchRegularExpression( + '/bi-eye[^<]{0,200}0\.0/', + $html, + 'Playlist meta must not emit "bi-eye … 0.0" for zero-view playlists.' + ); + + // Plural must switch to singular for 1 video. Never "1 videos". + $this->assertDoesNotMatchRegularExpression('/\b1 videos\b/i', $html); + } +}