-
{{ \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);
+ }
+}