169 lines
6.3 KiB
PHP
169 lines
6.3 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace Tests\Feature;
|
||
|
|
|
||
|
|
use App\Models\User;
|
||
|
|
use App\Models\Workout;
|
||
|
|
use App\Models\WorkoutExercise;
|
||
|
|
use App\Models\WorkoutSession;
|
||
|
|
use App\Models\WorkoutSessionItem;
|
||
|
|
use App\Support\Gamification;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
use Tests\TestCase;
|
||
|
|
|
||
|
|
class GamificationTest extends TestCase
|
||
|
|
{
|
||
|
|
use RefreshDatabase;
|
||
|
|
|
||
|
|
protected function completeSession(User $user, Workout $workout, string $completedAt): WorkoutSession
|
||
|
|
{
|
||
|
|
return WorkoutSession::factory()->create([
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'workout_id' => $workout->id,
|
||
|
|
'completed_at' => $completedAt,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_user_with_no_sessions_has_no_streak_or_badges(): void
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
|
||
|
|
$result = Gamification::forUser($user);
|
||
|
|
|
||
|
|
$this->assertEquals(0, $result['current_streak']);
|
||
|
|
$this->assertEquals(0, $result['longest_streak']);
|
||
|
|
$this->assertTrue(collect($result['badges'])->every(fn ($b) => $b['earned'] === false));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_a_session_completed_today_counts_as_a_one_day_streak(): void
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$workout = Workout::factory()->create(['user_id' => $user->id]);
|
||
|
|
$this->completeSession($user, $workout, now()->toDateString());
|
||
|
|
|
||
|
|
$result = Gamification::forUser($user);
|
||
|
|
|
||
|
|
$this->assertEquals(1, $result['current_streak']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_consecutive_days_extend_the_current_streak(): void
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$workout = Workout::factory()->create(['user_id' => $user->id]);
|
||
|
|
$this->completeSession($user, $workout, now()->toDateString());
|
||
|
|
$this->completeSession($user, $workout, now()->subDay()->toDateString());
|
||
|
|
$this->completeSession($user, $workout, now()->subDays(2)->toDateString());
|
||
|
|
|
||
|
|
$result = Gamification::forUser($user);
|
||
|
|
|
||
|
|
$this->assertEquals(3, $result['current_streak']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_a_gap_breaks_the_current_streak_but_longest_streak_remembers_it(): void
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$workout = Workout::factory()->create(['user_id' => $user->id]);
|
||
|
|
// A 3-day streak, a week ago, with a gap since.
|
||
|
|
$this->completeSession($user, $workout, now()->subDays(10)->toDateString());
|
||
|
|
$this->completeSession($user, $workout, now()->subDays(9)->toDateString());
|
||
|
|
$this->completeSession($user, $workout, now()->subDays(8)->toDateString());
|
||
|
|
// Today, isolated.
|
||
|
|
$this->completeSession($user, $workout, now()->toDateString());
|
||
|
|
|
||
|
|
$result = Gamification::forUser($user);
|
||
|
|
|
||
|
|
$this->assertEquals(1, $result['current_streak']);
|
||
|
|
$this->assertEquals(3, $result['longest_streak']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_missing_only_today_does_not_break_the_streak_yet(): void
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$workout = Workout::factory()->create(['user_id' => $user->id]);
|
||
|
|
$this->completeSession($user, $workout, now()->subDay()->toDateString());
|
||
|
|
$this->completeSession($user, $workout, now()->subDays(2)->toDateString());
|
||
|
|
|
||
|
|
$result = Gamification::forUser($user);
|
||
|
|
|
||
|
|
$this->assertEquals(2, $result['current_streak']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_first_steps_badge_earned_after_one_completed_session(): void
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$workout = Workout::factory()->create(['user_id' => $user->id]);
|
||
|
|
$this->completeSession($user, $workout, now()->toDateString());
|
||
|
|
|
||
|
|
$badges = collect(Gamification::forUser($user)['badges'])->keyBy('key');
|
||
|
|
|
||
|
|
$this->assertTrue($badges['first_steps']['earned']);
|
||
|
|
$this->assertFalse($badges['consistent']['earned']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_consistent_badge_earned_after_ten_completed_sessions(): void
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$workout = Workout::factory()->create(['user_id' => $user->id]);
|
||
|
|
for ($i = 0; $i < 10; $i++) {
|
||
|
|
WorkoutSession::factory()->create([
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'workout_id' => $workout->id,
|
||
|
|
'completed_at' => now()->subDays($i * 3)->toDateString(),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
$badges = collect(Gamification::forUser($user)['badges'])->keyBy('key');
|
||
|
|
|
||
|
|
$this->assertTrue($badges['consistent']['earned']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_century_club_badge_earned_from_a_completed_hundred_kilo_set(): void
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$workout = Workout::factory()->create(['user_id' => $user->id]);
|
||
|
|
$item = WorkoutExercise::factory()->create(['workout_id' => $workout->id]);
|
||
|
|
$session = WorkoutSession::factory()->create(['user_id' => $user->id, 'workout_id' => $workout->id]);
|
||
|
|
WorkoutSessionItem::factory()->create([
|
||
|
|
'session_id' => $session->id,
|
||
|
|
'workout_exercise_id' => $item->id,
|
||
|
|
'done' => true,
|
||
|
|
'actual_weight' => 100,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$badges = collect(Gamification::forUser($user)['badges'])->keyBy('key');
|
||
|
|
|
||
|
|
$this->assertTrue($badges['century_club']['earned']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_century_club_badge_not_earned_from_an_incomplete_set(): void
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$workout = Workout::factory()->create(['user_id' => $user->id]);
|
||
|
|
$item = WorkoutExercise::factory()->create(['workout_id' => $workout->id]);
|
||
|
|
$session = WorkoutSession::factory()->create(['user_id' => $user->id, 'workout_id' => $workout->id]);
|
||
|
|
WorkoutSessionItem::factory()->create([
|
||
|
|
'session_id' => $session->id,
|
||
|
|
'workout_exercise_id' => $item->id,
|
||
|
|
'done' => false,
|
||
|
|
'actual_weight' => 150,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$badges = collect(Gamification::forUser($user)['badges'])->keyBy('key');
|
||
|
|
|
||
|
|
$this->assertFalse($badges['century_club']['earned']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_dashboard_renders_streak_and_badges(): void
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$workout = Workout::factory()->create(['user_id' => $user->id]);
|
||
|
|
$this->completeSession($user, $workout, now()->toDateString());
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->get(route('dashboard'));
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
$response->assertSee('First Steps');
|
||
|
|
$response->assertSee('1', false);
|
||
|
|
}
|
||
|
|
}
|