3 namespace Tests\Sorting;
5 use BookStack\Activity\ActivityType;
6 use BookStack\Entities\Models\Book;
7 use BookStack\Sorting\SortRule;
8 use Tests\Api\TestsApi;
11 class SortRuleTest extends TestCase
15 public function test_manage_settings_permission_required()
17 $rule = SortRule::factory()->create();
18 $user = $this->users->viewer();
19 $this->actingAs($user);
22 ['GET', '/settings/sorting'],
23 ['POST', '/settings/sorting/rules'],
24 ['GET', "/settings/sorting/rules/{$rule->id}"],
25 ['PUT', "/settings/sorting/rules/{$rule->id}"],
26 ['DELETE', "/settings/sorting/rules/{$rule->id}"],
29 foreach ($actions as [$method, $path]) {
30 $resp = $this->call($method, $path);
31 $this->assertPermissionError($resp);
34 $this->permissions->grantUserRolePermissions($user, ['settings-manage']);
36 foreach ($actions as [$method, $path]) {
37 $resp = $this->call($method, $path);
38 $this->assertNotPermissionError($resp);
42 public function test_create_flow()
44 $resp = $this->asAdmin()->get('/settings/sorting');
45 $this->withHtml($resp)->assertLinkExists(url('/settings/sorting/rules/new'));
47 $resp = $this->get('/settings/sorting/rules/new');
48 $this->withHtml($resp)->assertElementExists('form[action$="/settings/sorting/rules"] input[name="name"]');
49 $resp->assertSeeText('Name - Alphabetical (Asc)');
51 $details = ['name' => 'My new sort', 'sequence' => 'name_asc'];
52 $resp = $this->post('/settings/sorting/rules', $details);
53 $resp->assertRedirect('/settings/sorting');
55 $this->assertActivityExists(ActivityType::SORT_RULE_CREATE);
56 $this->assertDatabaseHas('sort_rules', $details);
59 public function test_listing_in_settings()
61 $rule = SortRule::factory()->create(['name' => 'My super sort rule', 'sequence' => 'name_asc']);
62 $books = Book::query()->limit(5)->get();
63 foreach ($books as $book) {
64 $book->sort_rule_id = $rule->id;
68 $resp = $this->asAdmin()->get('/settings/sorting');
69 $resp->assertSeeText('My super sort rule');
70 $resp->assertSeeText('Name - Alphabetical (Asc)');
71 $this->withHtml($resp)->assertElementContains('.item-list-row [title="Assigned to 5 Books"]', '5');
74 public function test_update_flow()
76 $rule = SortRule::factory()->create(['name' => 'My sort rule to update', 'sequence' => 'name_asc']);
78 $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
79 $respHtml = $this->withHtml($resp);
80 $respHtml->assertElementContains('.configured-option-list', 'Name - Alphabetical (Asc)');
81 $respHtml->assertElementNotContains('.available-option-list', 'Name - Alphabetical (Asc)');
83 $updateData = ['name' => 'My updated sort', 'sequence' => 'name_desc,chapters_last'];
84 $resp = $this->put("/settings/sorting/rules/{$rule->id}", $updateData);
86 $resp->assertRedirect('/settings/sorting');
87 $this->assertActivityExists(ActivityType::SORT_RULE_UPDATE);
88 $this->assertDatabaseHas('sort_rules', $updateData);
91 public function test_update_triggers_resort_on_assigned_books()
93 $book = $this->entities->bookHasChaptersAndPages();
94 $chapter = $book->chapters()->first();
95 $rule = SortRule::factory()->create(['name' => 'My sort rule to update', 'sequence' => 'name_asc']);
96 $book->sort_rule_id = $rule->id;
98 $chapter->priority = 10000;
101 $resp = $this->asAdmin()->put("/settings/sorting/rules/{$rule->id}", ['name' => $rule->name, 'sequence' => 'chapters_last']);
102 $resp->assertRedirect('/settings/sorting');
105 $this->assertNotEquals(10000, $chapter->priority);
108 public function test_delete_flow()
110 $rule = SortRule::factory()->create();
112 $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
113 $resp->assertSeeText('Delete Sort Rule');
115 $resp = $this->delete("settings/sorting/rules/{$rule->id}");
116 $resp->assertRedirect('/settings/sorting');
118 $this->assertActivityExists(ActivityType::SORT_RULE_DELETE);
119 $this->assertDatabaseMissing('sort_rules', ['id' => $rule->id]);
122 public function test_delete_requires_confirmation_if_books_assigned()
124 $rule = SortRule::factory()->create();
125 $books = Book::query()->limit(5)->get();
126 foreach ($books as $book) {
127 $book->sort_rule_id = $rule->id;
131 $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
132 $resp->assertSeeText('Delete Sort Rule');
134 $resp = $this->delete("settings/sorting/rules/{$rule->id}");
135 $resp->assertRedirect("/settings/sorting/rules/{$rule->id}#delete");
136 $resp = $this->followRedirects($resp);
138 $resp->assertSeeText('This sort rule is currently used on 5 book(s). Are you sure you want to delete this?');
139 $this->assertDatabaseHas('sort_rules', ['id' => $rule->id]);
141 $resp = $this->delete("settings/sorting/rules/{$rule->id}", ['confirm' => 'true']);
142 $resp->assertRedirect('/settings/sorting');
143 $this->assertDatabaseMissing('sort_rules', ['id' => $rule->id]);
144 $this->assertDatabaseMissing('books', ['sort_rule_id' => $rule->id]);
147 public function test_page_create_triggers_book_sort()
149 $book = $this->entities->bookHasChaptersAndPages();
150 $rule = SortRule::factory()->create(['sequence' => 'name_asc,chapters_first']);
151 $book->sort_rule_id = $rule->id;
154 $resp = $this->actingAsApiEditor()->post("/api/pages", [
155 'book_id' => $book->id,
156 'name' => '1111 page',
161 $this->assertDatabaseHas('pages', [
162 'book_id' => $book->id,
163 'name' => '1111 page',
164 'priority' => $book->chapters()->count() + 1,
168 public function test_auto_book_sort_does_not_touch_timestamps()
170 $book = $this->entities->bookHasChaptersAndPages();
171 $rule = SortRule::factory()->create(['sequence' => 'name_asc,chapters_first']);
172 $book->sort_rule_id = $rule->id;
174 $page = $book->pages()->first();
175 $chapter = $book->chapters()->first();
177 $resp = $this->actingAsApiEditor()->put("/api/pages/{$page->id}", [
178 'name' => '1111 page',
182 $oldTime = $chapter->updated_at->unix();
183 $oldPriority = $chapter->priority;
185 $this->assertEquals($oldTime, $chapter->updated_at->unix());
186 $this->assertNotEquals($oldPriority, $chapter->priority);
189 public function test_name_numeric_ordering()
191 $book = Book::factory()->create();
192 $rule = SortRule::factory()->create(['sequence' => 'name_numeric_asc']);
193 $book->sort_rule_id = $rule->id;
195 $this->permissions->regenerateForEntity($book);
205 foreach ($namesToAdd as $name) {
206 $this->actingAsApiEditor()->post("/api/pages", [
207 'book_id' => $book->id,
209 'markdown' => 'Hello'
213 foreach ($namesToAdd as $index => $name) {
214 $this->assertDatabaseHas('pages', [
215 'book_id' => $book->id,
217 'priority' => $index + 1,