Files
fitness/tests/Feature/WorkoutCommunityTest.php
T
rootandClaude Sonnet 5 e74aa2bda4 Add a community workout library
Workouts were private to their owner with no way to browse or copy
another user's - FavoriteController::toggleWorkout even had a comment
anticipating sharing ("users can still favorite others' workouts if
shared later") that was never followed up on.

Adds an is_public flag workouts can be toggled to from their own page,
a /workouts/community browse+search page, and a copy action that
duplicates a public workout (including all its exercises/sets/reps)
into the copier's own library.

Making this work required splitting workout authorization into two
tiers: authorizeView() (owner, the assigning trainer, or anyone if the
workout is public - read-only) and the existing authorizeWorkout()
(owner or assigning trainer - can add/edit/remove exercises). Deletion
stays strictly owner-only as before. The show page now hides the
"Add Exercise" panel and per-exercise edit/remove controls for viewers
who can see but not manage a workout.

Found and fixed a real Blade compiler quirk via live testing: `@if`
directly after a word character with no space (`exercises@if(...)`)
doesn't compile - Blade's directive regex treats it as email-like text
and leaves it literal - while the matching `@endif` still compiles,
producing invalid mismatched PHP. Fixed by adding a space.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 12:39:16 +03:00

158 lines
5.8 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Exercise;
use App\Models\User;
use App\Models\Workout;
use App\Models\WorkoutExercise;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class WorkoutCommunityTest extends TestCase
{
use RefreshDatabase;
public function test_community_index_only_lists_public_workouts(): void
{
$user = User::factory()->create();
$public = Workout::factory()->create(['user_id' => $user->id, 'is_public' => true, 'title' => 'Public One']);
Workout::factory()->create(['user_id' => $user->id, 'is_public' => false, 'title' => 'Private One']);
$response = $this->actingAs($user)->get(route('workouts.community'));
$response->assertOk();
$response->assertSee('Public One');
$response->assertDontSee('Private One');
}
public function test_community_index_can_search_by_title(): void
{
$user = User::factory()->create();
Workout::factory()->create(['user_id' => $user->id, 'is_public' => true, 'title' => 'Leg Day Blast']);
Workout::factory()->create(['user_id' => $user->id, 'is_public' => true, 'title' => 'Upper Body Push']);
$response = $this->actingAs($user)->get(route('workouts.community', ['q' => 'Leg']));
$response->assertOk();
$response->assertSee('Leg Day Blast');
$response->assertDontSee('Upper Body Push');
}
public function test_owner_can_toggle_a_workout_public(): void
{
$user = User::factory()->create();
$workout = Workout::factory()->create(['user_id' => $user->id, 'is_public' => false]);
$this->actingAs($user)->post(route('workouts.toggle-public', $workout));
$this->assertTrue($workout->fresh()->is_public);
$this->actingAs($user)->post(route('workouts.toggle-public', $workout));
$this->assertFalse($workout->fresh()->is_public);
}
public function test_other_user_cannot_toggle_someone_elses_workout_public(): void
{
$owner = User::factory()->create();
$other = User::factory()->create();
$workout = Workout::factory()->create(['user_id' => $owner->id, 'is_public' => false]);
$response = $this->actingAs($other)->post(route('workouts.toggle-public', $workout));
$response->assertForbidden();
$this->assertFalse($workout->fresh()->is_public);
}
public function test_any_user_can_view_a_public_workout(): void
{
$owner = User::factory()->create();
$viewer = User::factory()->create();
$workout = Workout::factory()->create(['user_id' => $owner->id, 'is_public' => true, 'title' => 'Full Body Blast']);
$response = $this->actingAs($viewer)->get(route('workouts.show', $workout));
$response->assertOk();
$response->assertSee('Full Body Blast');
}
public function test_viewer_of_a_public_workout_cannot_manage_its_exercises(): void
{
$owner = User::factory()->create();
$viewer = User::factory()->create();
$workout = Workout::factory()->create(['user_id' => $owner->id, 'is_public' => true]);
$exercise = Exercise::factory()->create();
$response = $this->actingAs($viewer)->get(route('workouts.show', $workout));
$response->assertOk();
$response->assertDontSee('Add Exercise');
$addResponse = $this->actingAs($viewer)->post(route('workouts.exercises.add', $workout), [
'exercise_id' => $exercise->id,
]);
$addResponse->assertForbidden();
}
public function test_non_owner_cannot_view_a_private_workout(): void
{
$owner = User::factory()->create();
$viewer = User::factory()->create();
$workout = Workout::factory()->create(['user_id' => $owner->id, 'is_public' => false]);
$response = $this->actingAs($viewer)->get(route('workouts.show', $workout));
$response->assertForbidden();
}
public function test_user_can_copy_a_public_workout_with_its_exercises(): void
{
$owner = User::factory()->create();
$copier = User::factory()->create();
$workout = Workout::factory()->create([
'user_id' => $owner->id,
'is_public' => true,
'title' => 'Original Workout',
'estimated_minutes' => 45,
]);
WorkoutExercise::factory()->create(['workout_id' => $workout->id, 'sets' => 4, 'reps' => 6]);
$response = $this->actingAs($copier)->post(route('workouts.copy', $workout));
$response->assertRedirect();
$this->assertDatabaseHas('workouts', [
'user_id' => $copier->id,
'title' => 'Original Workout (copy)',
'estimated_minutes' => 45,
]);
$copiedWorkout = Workout::where('user_id', $copier->id)->firstOrFail();
$this->assertEquals(1, $copiedWorkout->items()->count());
$this->assertEquals(4, $copiedWorkout->items()->first()->sets);
// The original is untouched
$this->assertEquals(1, $workout->fresh()->items()->count());
}
public function test_user_cannot_copy_a_private_workout_they_do_not_own(): void
{
$owner = User::factory()->create();
$other = User::factory()->create();
$workout = Workout::factory()->create(['user_id' => $owner->id, 'is_public' => false]);
$response = $this->actingAs($other)->post(route('workouts.copy', $workout));
$response->assertForbidden();
}
public function test_owner_can_copy_their_own_private_workout(): void
{
$owner = User::factory()->create();
$workout = Workout::factory()->create(['user_id' => $owner->id, 'is_public' => false]);
$response = $this->actingAs($owner)->post(route('workouts.copy', $workout));
$response->assertRedirect();
$this->assertEquals(2, Workout::where('user_id', $owner->id)->count());
}
}