]> BookStack Code Mirror - bookstack/blob - tests/Sorting/SortRuleTest.php
Sorting: Updated sort set command, Changed sort timestamp handling
[bookstack] / tests / Sorting / SortRuleTest.php
1 <?php
2
3 namespace Sorting;
4
5 use BookStack\Activity\ActivityType;
6 use BookStack\Entities\Models\Book;
7 use BookStack\Sorting\SortRule;
8 use Tests\Api\TestsApi;
9 use Tests\TestCase;
10
11 class SortRuleTest extends TestCase
12 {
13     use TestsApi;
14
15     public function test_manage_settings_permission_required()
16     {
17         $rule = SortRule::factory()->create();
18         $user = $this->users->viewer();
19         $this->actingAs($user);
20
21         $actions = [
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}"],
27         ];
28
29         foreach ($actions as [$method, $path]) {
30             $resp = $this->call($method, $path);
31             $this->assertPermissionError($resp);
32         }
33
34         $this->permissions->grantUserRolePermissions($user, ['settings-manage']);
35
36         foreach ($actions as [$method, $path]) {
37             $resp = $this->call($method, $path);
38             $this->assertNotPermissionError($resp);
39         }
40     }
41
42     public function test_create_flow()
43     {
44         $resp = $this->asAdmin()->get('/settings/sorting');
45         $this->withHtml($resp)->assertLinkExists(url('/settings/sorting/rules/new'));
46
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)');
50
51         $details = ['name' => 'My new sort', 'sequence' => 'name_asc'];
52         $resp = $this->post('/settings/sorting/rules', $details);
53         $resp->assertRedirect('/settings/sorting');
54
55         $this->assertActivityExists(ActivityType::SORT_RULE_CREATE);
56         $this->assertDatabaseHas('sort_rules', $details);
57     }
58
59     public function test_listing_in_settings()
60     {
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;
65             $book->save();
66         }
67
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');
72     }
73
74     public function test_update_flow()
75     {
76         $rule = SortRule::factory()->create(['name' => 'My sort rule to update', 'sequence' => 'name_asc']);
77
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)');
82
83         $updateData = ['name' => 'My updated sort', 'sequence' => 'name_desc,chapters_last'];
84         $resp = $this->put("/settings/sorting/rules/{$rule->id}", $updateData);
85
86         $resp->assertRedirect('/settings/sorting');
87         $this->assertActivityExists(ActivityType::SORT_RULE_UPDATE);
88         $this->assertDatabaseHas('sort_rules', $updateData);
89     }
90
91     public function test_update_triggers_resort_on_assigned_books()
92     {
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;
97         $book->save();
98         $chapter->priority = 10000;
99         $chapter->save();
100
101         $resp = $this->asAdmin()->put("/settings/sorting/rules/{$rule->id}", ['name' => $rule->name, 'sequence' => 'chapters_last']);
102         $resp->assertRedirect('/settings/sorting');
103
104         $chapter->refresh();
105         $this->assertNotEquals(10000, $chapter->priority);
106     }
107
108     public function test_delete_flow()
109     {
110         $rule = SortRule::factory()->create();
111
112         $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
113         $resp->assertSeeText('Delete Sort Rule');
114
115         $resp = $this->delete("settings/sorting/rules/{$rule->id}");
116         $resp->assertRedirect('/settings/sorting');
117
118         $this->assertActivityExists(ActivityType::SORT_RULE_DELETE);
119         $this->assertDatabaseMissing('sort_rules', ['id' => $rule->id]);
120     }
121
122     public function test_delete_requires_confirmation_if_books_assigned()
123     {
124         $rule = SortRule::factory()->create();
125         $books = Book::query()->limit(5)->get();
126         foreach ($books as $book) {
127             $book->sort_rule_id = $rule->id;
128             $book->save();
129         }
130
131         $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
132         $resp->assertSeeText('Delete Sort Rule');
133
134         $resp = $this->delete("settings/sorting/rules/{$rule->id}");
135         $resp->assertRedirect("/settings/sorting/rules/{$rule->id}#delete");
136         $resp = $this->followRedirects($resp);
137
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]);
140
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]);
145     }
146
147     public function test_page_create_triggers_book_sort()
148     {
149         $book = $this->entities->bookHasChaptersAndPages();
150         $rule = SortRule::factory()->create(['sequence' => 'name_asc,chapters_first']);
151         $book->sort_rule_id = $rule->id;
152         $book->save();
153
154         $resp = $this->actingAsApiEditor()->post("/api/pages", [
155             'book_id' => $book->id,
156             'name' => '1111 page',
157             'markdown' => 'Hi'
158         ]);
159         $resp->assertOk();
160
161         $this->assertDatabaseHas('pages', [
162             'book_id' => $book->id,
163             'name' => '1111 page',
164             'priority' => $book->chapters()->count() + 1,
165         ]);
166     }
167
168     public function test_auto_book_sort_does_not_touch_timestamps()
169     {
170         $book = $this->entities->bookHasChaptersAndPages();
171         $rule = SortRule::factory()->create(['sequence' => 'name_asc,chapters_first']);
172         $book->sort_rule_id = $rule->id;
173         $book->save();
174         $page = $book->pages()->first();
175         $chapter = $book->chapters()->first();
176
177         $resp = $this->actingAsApiEditor()->put("/api/pages/{$page->id}", [
178             'name' => '1111 page',
179         ]);
180         $resp->assertOk();
181
182         $oldTime = $chapter->updated_at->unix();
183         $oldPriority = $chapter->priority;
184         $chapter->refresh();
185         $this->assertEquals($oldTime, $chapter->updated_at->unix());
186         $this->assertNotEquals($oldPriority, $chapter->priority);
187     }
188
189     public function test_name_numeric_ordering()
190     {
191         $book = Book::factory()->create();
192         $rule = SortRule::factory()->create(['sequence' => 'name_numeric_asc']);
193         $book->sort_rule_id = $rule->id;
194         $book->save();
195         $this->permissions->regenerateForEntity($book);
196
197         $namesToAdd = [
198             "1 - Pizza",
199             "2.0 - Tomato",
200             "2.5 - Beans",
201             "10 - Bread",
202             "20 - Milk",
203         ];
204
205         foreach ($namesToAdd as $name) {
206             $this->actingAsApiEditor()->post("/api/pages", [
207                 'book_id' => $book->id,
208                 'name' => $name,
209                 'markdown' => 'Hello'
210             ]);
211         }
212
213         foreach ($namesToAdd as $index => $name) {
214             $this->assertDatabaseHas('pages', [
215                 'book_id' => $book->id,
216                 'name' => $name,
217                 'priority' => $index + 1,
218             ]);
219         }
220     }
221 }