createBook(1, 'Never seen'); $this->createBook(2, 'Seen long ago')->setLastSeen(new \DateTimeImmutable('2026-01-10 12:00')); $this->createBook(3, 'Seen on the day')->setLastSeen(new \DateTimeImmutable('2026-11-01 00:00')); $this->createBook(4, 'Seen later')->setLastSeen(new \DateTimeImmutable('2026-11-05 18:00')); $this->createBook(5, 'Lent out')->lendTo('A Reader', new \DateTimeImmutable('2026-01-01')); $this->entityManager()->flush(); } public function testPreviewListsBooksNotSeenSinceTheDate(): void { $crawler = $this->preview('2026-11-01'); self::assertResponseIsSuccessful(); self::assertSame([1, 2], $crawler->filter('.results .tile .label-primary')->each(static fn($node): int => (int)$node->text())); self::assertSelectorTextContains('.bulk-oust-confirm', '2 books were not seen on or after 2026-11-01'); self::assertSelectorTextContains('.bulk-oust-borrowed', '1 borrowed books'); self::assertNotNull($this->findBook(1)); } public function testConfirmingOustsTheListedBooks(): void { $crawler = $this->preview('2026-11-01'); $this->client->submit($crawler->selectButton('Take them all out')->form()); self::assertResponseRedirects('/ousted'); $this->client->followRedirect(); self::assertSelectorTextContains('.flash', '2 books have been taken out'); self::assertNull($this->findBook(1)); self::assertNull($this->findBook(2)); self::assertNotNull($this->findBook(3)); self::assertNotNull($this->findBook(4)); self::assertNotNull($this->findBook(5)); self::assertSame('Inventory 2026', $this->entityManager()->find(OustedBook::class, 2)?->getReason()); } public function testNothingIsOustedIfTheSelectionChanged(): void { $crawler = $this->preview('2026-11-01'); $form = $crawler->selectButton('Take them all out')->form(); // One book found and another added: the count stays the same, the books do not. $this->findBook(2)?->setLastSeen(new \DateTimeImmutable('2026-11-02')); $this->entityManager()->flush(); $this->createBook(6, 'Added meanwhile'); $this->client->submit($form); self::assertResponseRedirects(); $this->client->followRedirect(); self::assertSelectorTextContains('.flash', 'has changed'); self::assertNotNull($this->findBook(1)); self::assertNotNull($this->findBook(6)); } public function testConfirmationRequiresAValidCsrfToken(): void { $this->client->request('POST', '/ousted/bulk', ['bulk_oust' => [ 'notSeenSince' => '2026-11-01', 'reason' => 'Forged', 'selection' => 'anything', '_token' => 'forged', ]]); self::assertResponseRedirects('/ousted/bulk'); self::assertNotNull($this->findBook(1)); } public function testPreviewWithNothingToOust(): void { $this->findBook(1)?->setLastSeen(new \DateTimeImmutable('2026-03-01')); $this->entityManager()->flush(); $this->preview('2026-01-01'); self::assertSelectorTextContains('.toast-success', 'Every book on the shelves was seen'); self::assertSelectorNotExists('.bulk-oust-confirm'); } private function preview(string $date): Crawler { $crawler = $this->client->request('GET', '/ousted/bulk'); return $this->client->submit($crawler->selectButton('Show the books')->form([ 'bulk_oust[notSeenSince]' => $date, 'bulk_oust[reason]' => 'Inventory 2026', ])); } }