From 45a15b479294349638bbe334f8701f077aa899c4 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Fri, 14 Feb 2025 13:24:39 +0000
Subject: [PATCH] Searching: Split out search tests into their own dir

---
 tests/{Entity => Search}/EntitySearchTest.php | 181 +-----------------
 tests/Search/SearchIndexingTest.php           |  77 ++++++++
 .../{Entity => Search}/SearchOptionsTest.php  |   2 +-
 tests/Search/SiblingSearchTest.php            | 117 +++++++++++
 4 files changed, 196 insertions(+), 181 deletions(-)
 rename tests/{Entity => Search}/EntitySearchTest.php (74%)
 create mode 100644 tests/Search/SearchIndexingTest.php
 rename tests/{Entity => Search}/SearchOptionsTest.php (99%)
 create mode 100644 tests/Search/SiblingSearchTest.php

diff --git a/tests/Entity/EntitySearchTest.php b/tests/Search/EntitySearchTest.php
similarity index 74%
rename from tests/Entity/EntitySearchTest.php
rename to tests/Search/EntitySearchTest.php
index 5ace70e3a..9c76d0f71 100644
--- a/tests/Entity/EntitySearchTest.php
+++ b/tests/Search/EntitySearchTest.php
@@ -1,12 +1,9 @@
 <?php
 
-namespace Tests\Entity;
+namespace Search;
 
 use BookStack\Activity\Models\Tag;
 use BookStack\Entities\Models\Book;
-use BookStack\Entities\Models\Bookshelf;
-use BookStack\Entities\Models\Chapter;
-use Illuminate\Support\Str;
 use Tests\TestCase;
 
 class EntitySearchTest extends TestCase
@@ -312,113 +309,6 @@ class EntitySearchTest extends TestCase
         $defaultListTest->assertDontSee($templatePage->name);
     }
 
-    public function test_sibling_search_for_pages()
-    {
-        $chapter = $this->entities->chapterHasPages();
-        $this->assertGreaterThan(2, count($chapter->pages), 'Ensure we\'re testing with at least 1 sibling');
-        $page = $chapter->pages->first();
-
-        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
-        $search->assertSuccessful();
-        foreach ($chapter->pages as $page) {
-            $search->assertSee($page->name);
-        }
-
-        $search->assertDontSee($chapter->name);
-    }
-
-    public function test_sibling_search_for_pages_without_chapter()
-    {
-        $page = $this->entities->pageNotWithinChapter();
-        $bookChildren = $page->book->getDirectVisibleChildren();
-        $this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling');
-
-        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
-        $search->assertSuccessful();
-        foreach ($bookChildren as $child) {
-            $search->assertSee($child->name);
-        }
-
-        $search->assertDontSee($page->book->name);
-    }
-
-    public function test_sibling_search_for_chapters()
-    {
-        $chapter = $this->entities->chapter();
-        $bookChildren = $chapter->book->getDirectVisibleChildren();
-        $this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling');
-
-        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$chapter->id}&entity_type=chapter");
-        $search->assertSuccessful();
-        foreach ($bookChildren as $child) {
-            $search->assertSee($child->name);
-        }
-
-        $search->assertDontSee($chapter->book->name);
-    }
-
-    public function test_sibling_search_for_books()
-    {
-        $books = Book::query()->take(10)->get();
-        $book = $books->first();
-        $this->assertGreaterThan(2, count($books), 'Ensure we\'re testing with at least 1 sibling');
-
-        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$book->id}&entity_type=book");
-        $search->assertSuccessful();
-        foreach ($books as $expectedBook) {
-            $search->assertSee($expectedBook->name);
-        }
-    }
-
-    public function test_sibling_search_for_shelves()
-    {
-        $shelves = Bookshelf::query()->take(10)->get();
-        $shelf = $shelves->first();
-        $this->assertGreaterThan(2, count($shelves), 'Ensure we\'re testing with at least 1 sibling');
-
-        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$shelf->id}&entity_type=bookshelf");
-        $search->assertSuccessful();
-        foreach ($shelves as $expectedShelf) {
-            $search->assertSee($expectedShelf->name);
-        }
-    }
-
-    public function test_sibling_search_for_books_provides_results_in_alphabetical_order()
-    {
-        $contextBook = $this->entities->book();
-        $searchBook = $this->entities->book();
-
-        $searchBook->name = 'Zebras';
-        $searchBook->save();
-
-        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$contextBook->id}&entity_type=book");
-        $this->withHtml($search)->assertElementNotContains('a:first-child', 'Zebras');
-
-        $searchBook->name = '1AAAAAAArdvarks';
-        $searchBook->save();
-
-        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$contextBook->id}&entity_type=book");
-        $this->withHtml($search)->assertElementContains('a:first-child', '1AAAAAAArdvarks');
-    }
-
-    public function test_sibling_search_for_shelves_provides_results_in_alphabetical_order()
-    {
-        $contextShelf = $this->entities->shelf();
-        $searchShelf = $this->entities->shelf();
-
-        $searchShelf->name = 'Zebras';
-        $searchShelf->save();
-
-        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$contextShelf->id}&entity_type=bookshelf");
-        $this->withHtml($search)->assertElementNotContains('a:first-child', 'Zebras');
-
-        $searchShelf->name = '1AAAAAAArdvarks';
-        $searchShelf->save();
-
-        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$contextShelf->id}&entity_type=bookshelf");
-        $this->withHtml($search)->assertElementContains('a:first-child', '1AAAAAAArdvarks');
-    }
-
     public function test_search_works_on_updated_page_content()
     {
         $page = $this->entities->page();
@@ -453,75 +343,6 @@ class EntitySearchTest extends TestCase
         $this->withHtml($search)->assertElementContains('.entity-list > .page:nth-child(2)', 'Test page A');
     }
 
-    public function test_terms_in_headers_have_an_adjusted_index_score()
-    {
-        $page = $this->entities->newPage(['name' => 'Test page A', 'html' => '
-            <p>TermA</p>
-            <h1>TermB <strong>TermNested</strong></h1>
-            <h2>TermC</h2>
-            <h3>TermD</h3>
-            <h4>TermE</h4>
-            <h5>TermF</h5>
-            <h6>TermG</h6>
-        ']);
-
-        $scoreByTerm = $page->searchTerms()->pluck('score', 'term');
-
-        $this->assertEquals(1, $scoreByTerm->get('TermA'));
-        $this->assertEquals(10, $scoreByTerm->get('TermB'));
-        $this->assertEquals(10, $scoreByTerm->get('TermNested'));
-        $this->assertEquals(5, $scoreByTerm->get('TermC'));
-        $this->assertEquals(4, $scoreByTerm->get('TermD'));
-        $this->assertEquals(3, $scoreByTerm->get('TermE'));
-        $this->assertEquals(2, $scoreByTerm->get('TermF'));
-        // Is 1.5 but stored as integer, rounding up
-        $this->assertEquals(2, $scoreByTerm->get('TermG'));
-    }
-
-    public function test_indexing_works_as_expected_for_page_with_lots_of_terms()
-    {
-        $this->markTestSkipped('Time consuming test');
-
-        $count = 100000;
-        $text = '';
-        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_#';
-        for ($i = 0; $i < $count; $i++) {
-            $text .= substr(str_shuffle($chars), 0, 5) . ' ';
-        }
-
-        $page = $this->entities->newPage(['name' => 'Test page A', 'html' => '<p>' . $text . '</p>']);
-
-        $termCount = $page->searchTerms()->count();
-
-        // Expect at least 90% unique rate
-        $this->assertGreaterThan($count * 0.9, $termCount);
-    }
-
-    public function test_name_and_content_terms_are_merged_to_single_score()
-    {
-        $page = $this->entities->newPage(['name' => 'TermA', 'html' => '
-            <p>TermA</p>
-        ']);
-
-        $scoreByTerm = $page->searchTerms()->pluck('score', 'term');
-
-        // Scores 40 for being in the name then 1 for being in the content
-        $this->assertEquals(41, $scoreByTerm->get('TermA'));
-    }
-
-    public function test_tag_names_and_values_are_indexed_for_search()
-    {
-        $page = $this->entities->newPage(['name' => 'PageA', 'html' => '<p>content</p>', 'tags' => [
-            ['name' => 'Animal', 'value' => 'MeowieCat'],
-            ['name' => 'SuperImportant'],
-        ]]);
-
-        $scoreByTerm = $page->searchTerms()->pluck('score', 'term');
-        $this->assertEquals(5, $scoreByTerm->get('MeowieCat'));
-        $this->assertEquals(3, $scoreByTerm->get('Animal'));
-        $this->assertEquals(3, $scoreByTerm->get('SuperImportant'));
-    }
-
     public function test_matching_terms_in_search_results_are_highlighted()
     {
         $this->entities->newPage(['name' => 'My Meowie Cat', 'html' => '<p>A superimportant page about meowieable animals</p>', 'tags' => [
diff --git a/tests/Search/SearchIndexingTest.php b/tests/Search/SearchIndexingTest.php
new file mode 100644
index 000000000..43219a4ed
--- /dev/null
+++ b/tests/Search/SearchIndexingTest.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Search;
+
+use Tests\TestCase;
+
+class SearchIndexingTest extends TestCase
+{
+    public function test_terms_in_headers_have_an_adjusted_index_score()
+    {
+        $page = $this->entities->newPage(['name' => 'Test page A', 'html' => '
+            <p>TermA</p>
+            <h1>TermB <strong>TermNested</strong></h1>
+            <h2>TermC</h2>
+            <h3>TermD</h3>
+            <h4>TermE</h4>
+            <h5>TermF</h5>
+            <h6>TermG</h6>
+        ']);
+
+        $scoreByTerm = $page->searchTerms()->pluck('score', 'term');
+
+        $this->assertEquals(1, $scoreByTerm->get('TermA'));
+        $this->assertEquals(10, $scoreByTerm->get('TermB'));
+        $this->assertEquals(10, $scoreByTerm->get('TermNested'));
+        $this->assertEquals(5, $scoreByTerm->get('TermC'));
+        $this->assertEquals(4, $scoreByTerm->get('TermD'));
+        $this->assertEquals(3, $scoreByTerm->get('TermE'));
+        $this->assertEquals(2, $scoreByTerm->get('TermF'));
+        // Is 1.5 but stored as integer, rounding up
+        $this->assertEquals(2, $scoreByTerm->get('TermG'));
+    }
+
+    public function test_indexing_works_as_expected_for_page_with_lots_of_terms()
+    {
+        $this->markTestSkipped('Time consuming test');
+
+        $count = 100000;
+        $text = '';
+        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_#';
+        for ($i = 0; $i < $count; $i++) {
+            $text .= substr(str_shuffle($chars), 0, 5) . ' ';
+        }
+
+        $page = $this->entities->newPage(['name' => 'Test page A', 'html' => '<p>' . $text . '</p>']);
+
+        $termCount = $page->searchTerms()->count();
+
+        // Expect at least 90% unique rate
+        $this->assertGreaterThan($count * 0.9, $termCount);
+    }
+
+    public function test_name_and_content_terms_are_merged_to_single_score()
+    {
+        $page = $this->entities->newPage(['name' => 'TermA', 'html' => '
+            <p>TermA</p>
+        ']);
+
+        $scoreByTerm = $page->searchTerms()->pluck('score', 'term');
+
+        // Scores 40 for being in the name then 1 for being in the content
+        $this->assertEquals(41, $scoreByTerm->get('TermA'));
+    }
+
+    public function test_tag_names_and_values_are_indexed_for_search()
+    {
+        $page = $this->entities->newPage(['name' => 'PageA', 'html' => '<p>content</p>', 'tags' => [
+            ['name' => 'Animal', 'value' => 'MeowieCat'],
+            ['name' => 'SuperImportant'],
+        ]]);
+
+        $scoreByTerm = $page->searchTerms()->pluck('score', 'term');
+        $this->assertEquals(5, $scoreByTerm->get('MeowieCat'));
+        $this->assertEquals(3, $scoreByTerm->get('Animal'));
+        $this->assertEquals(3, $scoreByTerm->get('SuperImportant'));
+    }
+}
diff --git a/tests/Entity/SearchOptionsTest.php b/tests/Search/SearchOptionsTest.php
similarity index 99%
rename from tests/Entity/SearchOptionsTest.php
rename to tests/Search/SearchOptionsTest.php
index 0c2ad271c..39c20c195 100644
--- a/tests/Entity/SearchOptionsTest.php
+++ b/tests/Search/SearchOptionsTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Tests\Entity;
+namespace Search;
 
 use BookStack\Search\Options\ExactSearchOption;
 use BookStack\Search\Options\FilterSearchOption;
diff --git a/tests/Search/SiblingSearchTest.php b/tests/Search/SiblingSearchTest.php
new file mode 100644
index 000000000..63865af8a
--- /dev/null
+++ b/tests/Search/SiblingSearchTest.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Search;
+
+use BookStack\Entities\Models\Book;
+use BookStack\Entities\Models\Bookshelf;
+use Tests\TestCase;
+
+class SiblingSearchTest extends TestCase
+{
+    public function test_sibling_search_for_pages()
+    {
+        $chapter = $this->entities->chapterHasPages();
+        $this->assertGreaterThan(2, count($chapter->pages), 'Ensure we\'re testing with at least 1 sibling');
+        $page = $chapter->pages->first();
+
+        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
+        $search->assertSuccessful();
+        foreach ($chapter->pages as $page) {
+            $search->assertSee($page->name);
+        }
+
+        $search->assertDontSee($chapter->name);
+    }
+
+    public function test_sibling_search_for_pages_without_chapter()
+    {
+        $page = $this->entities->pageNotWithinChapter();
+        $bookChildren = $page->book->getDirectVisibleChildren();
+        $this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling');
+
+        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
+        $search->assertSuccessful();
+        foreach ($bookChildren as $child) {
+            $search->assertSee($child->name);
+        }
+
+        $search->assertDontSee($page->book->name);
+    }
+
+    public function test_sibling_search_for_chapters()
+    {
+        $chapter = $this->entities->chapter();
+        $bookChildren = $chapter->book->getDirectVisibleChildren();
+        $this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling');
+
+        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$chapter->id}&entity_type=chapter");
+        $search->assertSuccessful();
+        foreach ($bookChildren as $child) {
+            $search->assertSee($child->name);
+        }
+
+        $search->assertDontSee($chapter->book->name);
+    }
+
+    public function test_sibling_search_for_books()
+    {
+        $books = Book::query()->take(10)->get();
+        $book = $books->first();
+        $this->assertGreaterThan(2, count($books), 'Ensure we\'re testing with at least 1 sibling');
+
+        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$book->id}&entity_type=book");
+        $search->assertSuccessful();
+        foreach ($books as $expectedBook) {
+            $search->assertSee($expectedBook->name);
+        }
+    }
+
+    public function test_sibling_search_for_shelves()
+    {
+        $shelves = Bookshelf::query()->take(10)->get();
+        $shelf = $shelves->first();
+        $this->assertGreaterThan(2, count($shelves), 'Ensure we\'re testing with at least 1 sibling');
+
+        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$shelf->id}&entity_type=bookshelf");
+        $search->assertSuccessful();
+        foreach ($shelves as $expectedShelf) {
+            $search->assertSee($expectedShelf->name);
+        }
+    }
+
+    public function test_sibling_search_for_books_provides_results_in_alphabetical_order()
+    {
+        $contextBook = $this->entities->book();
+        $searchBook = $this->entities->book();
+
+        $searchBook->name = 'Zebras';
+        $searchBook->save();
+
+        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$contextBook->id}&entity_type=book");
+        $this->withHtml($search)->assertElementNotContains('a:first-child', 'Zebras');
+
+        $searchBook->name = '1AAAAAAArdvarks';
+        $searchBook->save();
+
+        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$contextBook->id}&entity_type=book");
+        $this->withHtml($search)->assertElementContains('a:first-child', '1AAAAAAArdvarks');
+    }
+
+    public function test_sibling_search_for_shelves_provides_results_in_alphabetical_order()
+    {
+        $contextShelf = $this->entities->shelf();
+        $searchShelf = $this->entities->shelf();
+
+        $searchShelf->name = 'Zebras';
+        $searchShelf->save();
+
+        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$contextShelf->id}&entity_type=bookshelf");
+        $this->withHtml($search)->assertElementNotContains('a:first-child', 'Zebras');
+
+        $searchShelf->name = '1AAAAAAArdvarks';
+        $searchShelf->save();
+
+        $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$contextShelf->id}&entity_type=bookshelf");
+        $this->withHtml($search)->assertElementContains('a:first-child', '1AAAAAAArdvarks');
+    }
+}