{$state}()->create(['title' => $title] + $attrs); } function lbUpvotes(Product $product, int $count, ?Carbon $at = null): void { Upvote::factory()->count($count)->create([ 'product_id' => $product->id, 'created_at' => $at ?? now(), ]); } test('top today ranks by upvotes received today, not by product age', function () { $old = lbProduct('Old Favourite', attrs: ['created_at' => now()->subYear()]); $new = lbProduct('Fresh Launch'); lbUpvotes($old, 3); lbUpvotes($new, 1); $this->get(route('leaderboard', ['period' => 'today'])) ->assertOk() ->assertSeeInOrder(['Old Favourite', 'Fresh Launch']); }); test('today only counts upvotes since midnight', function () { $a = lbProduct('Upvoted Yesterday'); $b = lbProduct('Upvoted Today'); lbUpvotes($a, 5, now()->startOfDay()->subMinute()); lbUpvotes($b, 1, now()->startOfDay()); $this->get(route('leaderboard', ['period' => 'today'])) ->assertSee('Upvoted Today') ->assertDontSee('Upvoted Yesterday'); }); test('week counts the last 7 days and month the last 30', function () { $sixDays = lbProduct('Six Days Ago'); $eightDays = lbProduct('Eight Days Ago'); $twentyNine = lbProduct('Twenty Nine Days Ago'); $thirtyOne = lbProduct('Thirty One Days Ago'); lbUpvotes($sixDays, 1, now()->subDays(6)); lbUpvotes($eightDays, 1, now()->subDays(8)); lbUpvotes($twentyNine, 1, now()->subDays(29)); lbUpvotes($thirtyOne, 1, now()->subDays(31)); $this->get(route('leaderboard', ['period' => 'week'])) ->assertSee('Six Days Ago') ->assertDontSee('Eight Days Ago') ->assertDontSee('Twenty Nine Days Ago'); $this->get(route('leaderboard', ['period' => 'month'])) ->assertSee('Six Days Ago') ->assertSee('Eight Days Ago') ->assertSee('Twenty Nine Days Ago') ->assertDontSee('Thirty One Days Ago'); }); test('all time counts every upvote', function () { $ancient = lbProduct('Ancient Product'); lbUpvotes($ancient, 1, now()->subYears(2)); $this->get(route('leaderboard', ['period' => 'all']))->assertSee('Ancient Product'); }); test('only approved products with upvotes in the period are ranked', function () { $ok = lbProduct('Ranked Product'); $pending = lbProduct('Pending Product', 'pending'); $rejected = lbProduct('Rejected Product', 'rejected'); lbProduct('Zero Upvotes'); lbUpvotes($ok, 1); lbUpvotes($pending, 4); lbUpvotes($rejected, 4); $this->get(route('leaderboard')) ->assertSee('Ranked Product') ->assertDontSee('Pending Product') ->assertDontSee('Rejected Product') ->assertDontSee('Zero Upvotes'); }); test('ties are broken by total upvotes, then by newest', function () { $moreTotal = lbProduct('More Total', attrs: ['created_at' => now()->subDays(10)]); $fewerTotal = lbProduct('Fewer Total', attrs: ['created_at' => now()->subDays(5)]); $newerTie = lbProduct('Newer Tie', attrs: ['created_at' => now()->subDay()]); $olderTie = lbProduct('Older Tie', attrs: ['created_at' => now()->subDays(2)]); lbUpvotes($moreTotal, 2); lbUpvotes($moreTotal, 3, now()->subDays(40)); lbUpvotes($fewerTotal, 2); lbUpvotes($newerTie, 1); lbUpvotes($olderTie, 1); $this->get(route('leaderboard', ['period' => 'today'])) ->assertSeeInOrder(['More Total', 'Fewer Total', 'Newer Tie', 'Older Tie']); }); test('a removed upvote is not counted', function () { $product = lbProduct('Changed Mind'); lbUpvotes($product, 2); Upvote::where('product_id', $product->id)->first()->delete(); $this->get(route('leaderboard'))->assertSee('1 today', false); }); test('new tab lists approved products newest first', function () { lbProduct('Oldest One', attrs: ['created_at' => now()->subDays(3)]); lbProduct('Newest One', attrs: ['created_at' => now()->subHour()]); lbProduct('Middle One', attrs: ['created_at' => now()->subDay()]); lbProduct('Pending One', 'pending'); $this->get(route('leaderboard', ['tab' => 'new'])) ->assertOk() ->assertSeeInOrder(['Newest One', 'Middle One', 'Oldest One']) ->assertDontSee('Pending One'); }); test('lists 10 per page, ranks continue on page 2 and links keep the filters', function () { foreach (range(1, 12) as $i) { lbUpvotes(lbProduct("Product $i"), 20 - $i); } $page1 = $this->get(route('leaderboard', ['period' => 'week'])); $page1->assertSee('data-rank="10"', false) ->assertDontSee('data-rank="11"', false) ->assertSee('period=week', false) ->assertSee('page=2', false); $this->get(route('leaderboard', ['period' => 'week', 'page' => 2])) ->assertSee('data-rank="11"', false) ->assertSee('data-rank="12"', false) ->assertSee('Product 12') ->assertDontSee('Product 1<', false); }); test('invalid tab and period fall back to top today', function () { $product = lbProduct('Fallback Product'); lbUpvotes($product, 1); $this->get(route('leaderboard', ['tab' => 'nonsense', 'period' => 'decade'])) ->assertOk() ->assertSee('Fallback Product'); }); test('empty period shows a message', function () { $this->get(route('leaderboard', ['period' => 'today'])) ->assertOk() ->assertSee('No upvotes yet today'); }); test('navbar links to the leaderboard', function () { $this->get(route('leaderboard'))->assertSee(route('leaderboard'), false); $this->get('/products')->assertSee(route('leaderboard'), false); });