'UTC']); CarbonImmutable::setTestNow('2026-09-04T12:00:00Z'); } protected function tearDown(): void { CarbonImmutable::setTestNow(); parent::tearDown(); } public function test_requires_sso(): void { $this->get('/')->assertStatus(401); } public function test_renders_groups_cards_archive_and_settings(): void { $this->get('/', ['X-Remote-User' => 'nikita']); $user = User::sole(); $source = Source::create(['name' => 'docs', 'token_hash' => hash('sha256', 'x')]); $manual = Source::create(['name' => 'manual', 'token_hash' => hash('sha256', 'y')]); $make = fn (Source $src, string $topic, EventState $state, string $due, string $time = '24:00:00', ?string $doneLabel = null, ?string $link = '/x') => Event::create([ 'source_id' => $src->id, 'user_id' => $user->id, 'source_ref' => 'r', 'topic' => $topic, 'due_mode' => DueMode::Local, 'due_date' => $due, 'due_time' => $time, 'fire_on' => $due, 'after_due' => AfterDue::Keep, 'payload' => array_filter(['title' => "Событие $topic", 'subtitle' => 'подзаголовок', 'deep_link' => $link, 'done_label' => $doneLabel]), 'state' => $state, 'quiet_until' => CarbonImmutable::parse('2000-01-01Z'), ]); $make($source, 'a', EventState::Today, '2026-09-04', '19:40:00', 'Поздравил'); $make($source, 'b', EventState::Overdue, '2026-09-01'); $make($manual, 'c', EventState::Preparing, '2026-09-30', '24:00:00', null, null); $make($source, 'd', EventState::Scheduled, '2026-12-15'); $make($source, 'e', EventState::Done, '2026-08-28', '24:00:00', 'Оплатил'); $make($source, 'f', EventState::Expired, '2026-08-20'); $html = $this->get('/', ['X-Remote-User' => 'nikita'])->assertOk()->getContent(); // шапка и счётчик $this->assertStringContainsString('