]> BookStack Code Mirror - bookstack/blob - tests/Sorting/SortRuleTest.php
b0f20cba5aee9b13012de796eb549c45717df278
[bookstack] / tests / Sorting / SortRuleTest.php
1 <?php
2
3 namespace Tests\Sorting;
4
5 use BookStack\Activity\ActivityType;
6 use BookStack\Entities\Models\Book;
7 use BookStack\Sorting\SortRule;
8 use BookStack\Sorting\SortRuleOperation;
9 use Tests\Api\TestsApi;
10 use Tests\TestCase;
11
12 class SortRuleTest extends TestCase
13 {
14     use TestsApi;
15
16     public function test_manage_settings_permission_required()
17     {
18         $rule = SortRule::factory()->create();
19         $user = $this->users->viewer();
20         $this->actingAs($user);
21
22         $actions = [
23             ['GET', '/settings/sorting'],
24             ['POST', '/settings/sorting/rules'],
25             ['GET', "/settings/sorting/rules/{$rule->id}"],
26             ['PUT', "/settings/sorting/rules/{$rule->id}"],
27             ['DELETE', "/settings/sorting/rules/{$rule->id}"],
28         ];
29
30         foreach ($actions as [$method, $path]) {
31             $resp = $this->call($method, $path);
32             $this->assertPermissionError($resp);
33         }
34
35         $this->permissions->grantUserRolePermissions($user, ['settings-manage']);
36
37         foreach ($actions as [$method, $path]) {
38             $resp = $this->call($method, $path);
39             $this->assertNotPermissionError($resp);
40         }
41     }
42
43     public function test_create_flow()
44     {
45         $resp = $this->asAdmin()->get('/settings/sorting');
46         $this->withHtml($resp)->assertLinkExists(url('/settings/sorting/rules/new'));
47
48         $resp = $this->get('/settings/sorting/rules/new');
49         $this->withHtml($resp)->assertElementExists('form[action$="/settings/sorting/rules"] input[name="name"]');
50         $resp->assertSeeText('Name - Alphabetical (Asc)');
51
52         $details = ['name' => 'My new sort', 'sequence' => 'name_asc'];
53         $resp = $this->post('/settings/sorting/rules', $details);
54         $resp->assertRedirect('/settings/sorting');
55
56         $this->assertActivityExists(ActivityType::SORT_RULE_CREATE);
57         $this->assertDatabaseHas('sort_rules', $details);
58     }
59
60     public function test_listing_in_settings()
61     {
62         $rule = SortRule::factory()->create(['name' => 'My super sort rule', 'sequence' => 'name_asc']);
63         $books = Book::query()->limit(5)->get();
64         foreach ($books as $book) {
65             $book->sort_rule_id = $rule->id;
66             $book->save();
67         }
68
69         $resp = $this->asAdmin()->get('/settings/sorting');
70         $resp->assertSeeText('My super sort rule');
71         $resp->assertSeeText('Name - Alphabetical (Asc)');
72         $this->withHtml($resp)->assertElementContains('.item-list-row [title="Assigned to 5 Books"]', '5');
73     }
74
75     public function test_update_flow()
76     {
77         $rule = SortRule::factory()->create(['name' => 'My sort rule to update', 'sequence' => 'name_asc']);
78
79         $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
80         $respHtml = $this->withHtml($resp);
81         $respHtml->assertElementContains('.configured-option-list', 'Name - Alphabetical (Asc)');
82         $respHtml->assertElementNotContains('.available-option-list', 'Name - Alphabetical (Asc)');
83
84         $updateData = ['name' => 'My updated sort', 'sequence' => 'name_desc,chapters_last'];
85         $resp = $this->put("/settings/sorting/rules/{$rule->id}", $updateData);
86
87         $resp->assertRedirect('/settings/sorting');
88         $this->assertActivityExists(ActivityType::SORT_RULE_UPDATE);
89         $this->assertDatabaseHas('sort_rules', $updateData);
90     }
91
92     public function test_update_triggers_resort_on_assigned_books()
93     {
94         $book = $this->entities->bookHasChaptersAndPages();
95         $chapter = $book->chapters()->first();
96         $rule = SortRule::factory()->create(['name' => 'My sort rule to update', 'sequence' => 'name_asc']);
97         $book->sort_rule_id = $rule->id;
98         $book->save();
99         $chapter->priority = 10000;
100         $chapter->save();
101
102         $resp = $this->asAdmin()->put("/settings/sorting/rules/{$rule->id}", ['name' => $rule->name, 'sequence' => 'chapters_last']);
103         $resp->assertRedirect('/settings/sorting');
104
105         $chapter->refresh();
106         $this->assertNotEquals(10000, $chapter->priority);
107     }
108
109     public function test_delete_flow()
110     {
111         $rule = SortRule::factory()->create();
112
113         $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
114         $resp->assertSeeText('Delete Sort Rule');
115
116         $resp = $this->delete("settings/sorting/rules/{$rule->id}");
117         $resp->assertRedirect('/settings/sorting');
118
119         $this->assertActivityExists(ActivityType::SORT_RULE_DELETE);
120         $this->assertDatabaseMissing('sort_rules', ['id' => $rule->id]);
121     }
122
123     public function test_delete_requires_confirmation_if_books_assigned()
124     {
125         $rule = SortRule::factory()->create();
126         $books = Book::query()->limit(5)->get();
127         foreach ($books as $book) {
128             $book->sort_rule_id = $rule->id;
129             $book->save();
130         }
131
132         $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
133         $resp->assertSeeText('Delete Sort Rule');
134
135         $resp = $this->delete("settings/sorting/rules/{$rule->id}");
136         $resp->assertRedirect("/settings/sorting/rules/{$rule->id}#delete");
137         $resp = $this->followRedirects($resp);
138
139         $resp->assertSeeText('This sort rule is currently used on 5 book(s). Are you sure you want to delete this?');
140         $this->assertDatabaseHas('sort_rules', ['id' => $rule->id]);
141
142         $resp = $this->delete("settings/sorting/rules/{$rule->id}", ['confirm' => 'true']);
143         $resp->assertRedirect('/settings/sorting');
144         $this->assertDatabaseMissing('sort_rules', ['id' => $rule->id]);
145         $this->assertDatabaseMissing('books', ['sort_rule_id' => $rule->id]);
146     }
147
148     public function test_page_create_triggers_book_sort()
149     {
150         $book = $this->entities->bookHasChaptersAndPages();
151         $rule = SortRule::factory()->create(['sequence' => 'name_asc,chapters_first']);
152         $book->sort_rule_id = $rule->id;
153         $book->save();
154
155         $resp = $this->actingAsApiEditor()->post("/api/pages", [
156             'book_id' => $book->id,
157             'name' => '1111 page',
158             'markdown' => 'Hi'
159         ]);
160         $resp->assertOk();
161
162         $this->assertDatabaseHas('pages', [
163             'book_id' => $book->id,
164             'name' => '1111 page',
165             'priority' => $book->chapters()->count() + 1,
166         ]);
167     }
168
169     public function test_auto_book_sort_does_not_touch_timestamps()
170     {
171         $book = $this->entities->bookHasChaptersAndPages();
172         $rule = SortRule::factory()->create(['sequence' => 'name_asc,chapters_first']);
173         $book->sort_rule_id = $rule->id;
174         $book->save();
175         $page = $book->pages()->first();
176         $chapter = $book->chapters()->first();
177
178         $resp = $this->actingAsApiEditor()->put("/api/pages/{$page->id}", [
179             'name' => '1111 page',
180         ]);
181         $resp->assertOk();
182
183         $oldTime = $chapter->updated_at->unix();
184         $oldPriority = $chapter->priority;
185         $chapter->refresh();
186         $this->assertEquals($oldTime, $chapter->updated_at->unix());
187         $this->assertNotEquals($oldPriority, $chapter->priority);
188     }
189
190     public function test_name_alphabetical_ordering()
191     {
192         $book = Book::factory()->create();
193         $rule = SortRule::factory()->create(['sequence' => 'name_asc']);
194         $book->sort_rule_id = $rule->id;
195         $book->save();
196         $this->permissions->regenerateForEntity($book);
197
198         $namesToAdd = [
199             "Beans",
200             "bread",
201             "Éclaire",
202             "egg",
203             "Milk",
204             "pizza",
205             "Tomato",
206         ];
207
208         $reverseNamesToAdd = array_reverse($namesToAdd);
209         foreach ($reverseNamesToAdd as $name) {
210             $this->actingAsApiEditor()->post("/api/pages", [
211                 'book_id' => $book->id,
212                 'name' => $name,
213                 'markdown' => 'Hello'
214             ]);
215         }
216
217         foreach ($namesToAdd as $index => $name) {
218             $this->assertDatabaseHas('pages', [
219                 'book_id' => $book->id,
220                 'name' => $name,
221                 'priority' => $index + 1,
222             ]);
223         }
224     }
225
226     public function test_name_numeric_ordering()
227     {
228         $book = Book::factory()->create();
229         $rule = SortRule::factory()->create(['sequence' => 'name_numeric_asc']);
230         $book->sort_rule_id = $rule->id;
231         $book->save();
232         $this->permissions->regenerateForEntity($book);
233
234         $namesToAdd = [
235             "1 - Pizza",
236             "2.0 - Tomato",
237             "2.5 - Beans",
238             "10 - Bread",
239             "20 - Milk",
240         ];
241
242         $reverseNamesToAdd = array_reverse($namesToAdd);
243         foreach ($reverseNamesToAdd as $name) {
244             $this->actingAsApiEditor()->post("/api/pages", [
245                 'book_id' => $book->id,
246                 'name' => $name,
247                 'markdown' => 'Hello'
248             ]);
249         }
250
251         foreach ($namesToAdd as $index => $name) {
252             $this->assertDatabaseHas('pages', [
253                 'book_id' => $book->id,
254                 'name' => $name,
255                 'priority' => $index + 1,
256             ]);
257         }
258     }
259
260     public function test_each_sort_rule_operation_has_a_comparison_function()
261     {
262         $operations = SortRuleOperation::cases();
263
264         foreach ($operations as $operation) {
265             $comparisonFunc = $operation->getSortFunction();
266             $this->assertIsCallable($comparisonFunc);
267         }
268     }
269 }