]> BookStack Code Mirror - bookstack/blob - tests/Activity/WatchTest.php
fix typo
[bookstack] / tests / Activity / WatchTest.php
1 <?php
2
3 namespace Tests\Activity;
4
5 use BookStack\Activity\Notifications\Messages\CommentCreationNotification;
6 use BookStack\Activity\Notifications\Messages\PageCreationNotification;
7 use BookStack\Activity\Notifications\Messages\PageUpdateNotification;
8 use BookStack\Activity\Tools\UserEntityWatchOptions;
9 use BookStack\Activity\WatchLevels;
10 use BookStack\Entities\Models\Entity;
11 use BookStack\Settings\UserNotificationPreferences;
12 use Illuminate\Support\Facades\Notification;
13 use Tests\TestCase;
14
15 class WatchTest extends TestCase
16 {
17     public function test_watch_action_exists_on_entity_unless_active()
18     {
19         $editor = $this->users->editor();
20         $this->actingAs($editor);
21
22         $entities = [$this->entities->book(), $this->entities->chapter(), $this->entities->page()];
23         /** @var Entity $entity */
24         foreach ($entities as $entity) {
25             $resp = $this->get($entity->getUrl());
26             $this->withHtml($resp)->assertElementContains('form[action$="/watching/update"] button.icon-list-item', 'Watch');
27
28             $watchOptions = new UserEntityWatchOptions($editor, $entity);
29             $watchOptions->updateLevelByValue(WatchLevels::COMMENTS);
30
31             $resp = $this->get($entity->getUrl());
32             $this->withHtml($resp)->assertElementNotExists('form[action$="/watching/update"] button.icon-list-item');
33         }
34     }
35
36     public function test_watch_action_only_shows_with_permission()
37     {
38         $viewer = $this->users->viewer();
39         $this->actingAs($viewer);
40
41         $entities = [$this->entities->book(), $this->entities->chapter(), $this->entities->page()];
42         /** @var Entity $entity */
43         foreach ($entities as $entity) {
44             $resp = $this->get($entity->getUrl());
45             $this->withHtml($resp)->assertElementNotExists('form[action$="/watching/update"] button.icon-list-item');
46         }
47
48         $this->permissions->grantUserRolePermissions($viewer, ['receive-notifications']);
49
50         /** @var Entity $entity */
51         foreach ($entities as $entity) {
52             $resp = $this->get($entity->getUrl());
53             $this->withHtml($resp)->assertElementExists('form[action$="/watching/update"] button.icon-list-item');
54         }
55     }
56
57     public function test_watch_update()
58     {
59         $editor = $this->users->editor();
60         $book = $this->entities->book();
61
62         $this->actingAs($editor)->get($book->getUrl());
63         $resp = $this->put('/watching/update', [
64             'type' => get_class($book),
65             'id' => $book->id,
66             'level' => 'comments'
67         ]);
68
69         $resp->assertRedirect($book->getUrl());
70         $this->assertSessionHas('success');
71         $this->assertDatabaseHas('watches', [
72             'watchable_id' => $book->id,
73             'watchable_type' => $book->getMorphClass(),
74             'user_id' => $editor->id,
75             'level' => WatchLevels::COMMENTS,
76         ]);
77
78         $resp = $this->put('/watching/update', [
79             'type' => get_class($book),
80             'id' => $book->id,
81             'level' => 'default'
82         ]);
83         $resp->assertRedirect($book->getUrl());
84         $this->assertDatabaseMissing('watches', [
85             'watchable_id' => $book->id,
86             'watchable_type' => $book->getMorphClass(),
87             'user_id' => $editor->id,
88         ]);
89     }
90
91     public function test_watch_update_fails_for_guest()
92     {
93         $this->setSettings(['app-public' => 'true']);
94         $guest = $this->users->guest();
95         $this->permissions->grantUserRolePermissions($guest, ['receive-notifications']);
96         $book = $this->entities->book();
97
98         $resp = $this->put('/watching/update', [
99             'type' => get_class($book),
100             'id' => $book->id,
101             'level' => 'comments'
102         ]);
103
104         $this->assertPermissionError($resp);
105         $guest->unsetRelations();
106     }
107
108     public function test_watch_detail_display_reflects_state()
109     {
110         $editor = $this->users->editor();
111         $book = $this->entities->bookHasChaptersAndPages();
112         $chapter = $book->chapters()->first();
113         $page = $chapter->pages()->first();
114
115         (new UserEntityWatchOptions($editor, $book))->updateLevelByValue(WatchLevels::UPDATES);
116
117         $this->actingAs($editor)->get($book->getUrl())->assertSee('Watching new pages and updates');
118         $this->get($chapter->getUrl())->assertSee('Watching via parent book');
119         $this->get($page->getUrl())->assertSee('Watching via parent book');
120
121         (new UserEntityWatchOptions($editor, $chapter))->updateLevelByValue(WatchLevels::COMMENTS);
122         $this->get($chapter->getUrl())->assertSee('Watching new pages, updates & comments');
123         $this->get($page->getUrl())->assertSee('Watching via parent chapter');
124
125         (new UserEntityWatchOptions($editor, $page))->updateLevelByValue(WatchLevels::UPDATES);
126         $this->get($page->getUrl())->assertSee('Watching new pages and updates');
127     }
128
129     public function test_watch_detail_ignore_indicator_cascades()
130     {
131         $editor = $this->users->editor();
132         $book = $this->entities->bookHasChaptersAndPages();
133         (new UserEntityWatchOptions($editor, $book))->updateLevelByValue(WatchLevels::IGNORE);
134
135         $this->actingAs($editor)->get($book->getUrl())->assertSee('Ignoring notifications');
136         $this->get($book->chapters()->first()->getUrl())->assertSee('Ignoring via parent book');
137         $this->get($book->pages()->first()->getUrl())->assertSee('Ignoring via parent book');
138     }
139
140     public function test_watch_option_menu_shows_current_active_state()
141     {
142         $editor = $this->users->editor();
143         $book = $this->entities->book();
144         $options = new UserEntityWatchOptions($editor, $book);
145
146         $respHtml = $this->withHtml($this->actingAs($editor)->get($book->getUrl()));
147         $respHtml->assertElementNotExists('form[action$="/watching/update"] svg[data-icon="check-circle"]');
148
149         $options->updateLevelByValue(WatchLevels::COMMENTS);
150         $respHtml = $this->withHtml($this->actingAs($editor)->get($book->getUrl()));
151         $respHtml->assertElementExists('form[action$="/watching/update"] button[value="comments"] svg[data-icon="check-circle"]');
152
153         $options->updateLevelByValue(WatchLevels::IGNORE);
154         $respHtml = $this->withHtml($this->actingAs($editor)->get($book->getUrl()));
155         $respHtml->assertElementExists('form[action$="/watching/update"] button[value="ignore"] svg[data-icon="check-circle"]');
156     }
157
158     public function test_watch_option_menu_limits_options_for_pages()
159     {
160         $editor = $this->users->editor();
161         $book = $this->entities->bookHasChaptersAndPages();
162         (new UserEntityWatchOptions($editor, $book))->updateLevelByValue(WatchLevels::IGNORE);
163
164         $respHtml = $this->withHtml($this->actingAs($editor)->get($book->getUrl()));
165         $respHtml->assertElementExists('form[action$="/watching/update"] button[name="level"][value="new"]');
166
167         $respHtml = $this->withHtml($this->get($book->pages()->first()->getUrl()));
168         $respHtml->assertElementExists('form[action$="/watching/update"] button[name="level"][value="updates"]');
169         $respHtml->assertElementNotExists('form[action$="/watching/update"] button[name="level"][value="new"]');
170     }
171
172     public function test_notify_own_page_changes()
173     {
174         $editor = $this->users->editor();
175         $entities = $this->entities->createChainBelongingToUser($editor);
176         $prefs = new UserNotificationPreferences($editor);
177         $prefs->updateFromSettingsArray(['own-page-changes' => 'true']);
178
179         $notifications = Notification::fake();
180
181         $this->asAdmin();
182         $this->entities->updatePage($entities['page'], ['name' => 'My updated page', 'html' => 'Hello']);
183         $notifications->assertSentTo($editor, PageUpdateNotification::class);
184     }
185
186     public function test_notify_own_page_comments()
187     {
188         $editor = $this->users->editor();
189         $entities = $this->entities->createChainBelongingToUser($editor);
190         $prefs = new UserNotificationPreferences($editor);
191         $prefs->updateFromSettingsArray(['own-page-comments' => 'true']);
192
193         $notifications = Notification::fake();
194
195         $this->asAdmin()->post("/comment/{$entities['page']->id}", [
196             'text' => 'My new comment'
197         ]);
198         $notifications->assertSentTo($editor, CommentCreationNotification::class);
199     }
200
201     public function test_notify_comment_replies()
202     {
203         $editor = $this->users->editor();
204         $entities = $this->entities->createChainBelongingToUser($editor);
205         $prefs = new UserNotificationPreferences($editor);
206         $prefs->updateFromSettingsArray(['comment-replies' => 'true']);
207
208         $notifications = Notification::fake();
209
210         $this->actingAs($editor)->post("/comment/{$entities['page']->id}", [
211             'text' => 'My new comment'
212         ]);
213         $comment = $entities['page']->comments()->first();
214
215         $this->asAdmin()->post("/comment/{$entities['page']->id}", [
216             'text' => 'My new comment response',
217             'parent_id' => $comment->id,
218         ]);
219         $notifications->assertSentTo($editor, CommentCreationNotification::class);
220     }
221
222     public function test_notify_watch_parent_book_ignore()
223     {
224         $editor = $this->users->editor();
225         $entities = $this->entities->createChainBelongingToUser($editor);
226         $watches = new UserEntityWatchOptions($editor, $entities['book']);
227         $prefs = new UserNotificationPreferences($editor);
228         $watches->updateLevelByValue(WatchLevels::IGNORE);
229         $prefs->updateFromSettingsArray(['own-page-changes' => 'true', 'own-page-comments' => true]);
230
231         $notifications = Notification::fake();
232
233         $this->asAdmin()->post("/comment/{$entities['page']->id}", [
234             'text' => 'My new comment response',
235         ]);
236         $this->entities->updatePage($entities['page'], ['name' => 'My updated page', 'html' => 'Hello']);
237         $notifications->assertNothingSent();
238     }
239
240     public function test_notify_watch_parent_book_comments()
241     {
242         $notifications = Notification::fake();
243         $editor = $this->users->editor();
244         $admin = $this->users->admin();
245         $entities = $this->entities->createChainBelongingToUser($editor);
246         $watches = new UserEntityWatchOptions($editor, $entities['book']);
247         $watches->updateLevelByValue(WatchLevels::COMMENTS);
248
249         // Comment post
250         $this->actingAs($admin)->post("/comment/{$entities['page']->id}", [
251             'text' => 'My new comment response',
252         ]);
253
254         $notifications->assertSentTo($editor, function (CommentCreationNotification $notification) use ($editor, $admin, $entities) {
255             $mail = $notification->toMail($editor);
256             $mailContent = html_entity_decode(strip_tags($mail->render()));
257             return $mail->subject === 'New comment on page: ' . $entities['page']->getShortName()
258                 && str_contains($mailContent, 'View Comment')
259                 && str_contains($mailContent, 'Page Name: ' . $entities['page']->name)
260                 && str_contains($mailContent, 'Commenter: ' . $admin->name)
261                 && str_contains($mailContent, 'Comment: My new comment response');
262         });
263     }
264
265     public function test_notify_watch_parent_book_updates()
266     {
267         $notifications = Notification::fake();
268         $editor = $this->users->editor();
269         $admin = $this->users->admin();
270         $entities = $this->entities->createChainBelongingToUser($editor);
271         $watches = new UserEntityWatchOptions($editor, $entities['book']);
272         $watches->updateLevelByValue(WatchLevels::UPDATES);
273
274         $this->actingAs($admin);
275         $this->entities->updatePage($entities['page'], ['name' => 'Updated page', 'html' => 'new page content']);
276
277         $notifications->assertSentTo($editor, function (PageUpdateNotification $notification) use ($editor, $admin) {
278             $mail = $notification->toMail($editor);
279             $mailContent = html_entity_decode(strip_tags($mail->render()));
280             return $mail->subject === 'Updated page: Updated page'
281                 && str_contains($mailContent, 'View Page')
282                 && str_contains($mailContent, 'Page Name: Updated page')
283                 && str_contains($mailContent, 'Updated By: ' . $admin->name)
284                 && str_contains($mailContent, 'you won\'t be sent notifications for further edits to this page by the same editor');
285         });
286
287         // Test debounce
288         $notifications = Notification::fake();
289         $this->entities->updatePage($entities['page'], ['name' => 'Updated page', 'html' => 'new page content']);
290         $notifications->assertNothingSentTo($editor);
291     }
292
293     public function test_notify_watch_parent_book_new()
294     {
295         $notifications = Notification::fake();
296         $editor = $this->users->editor();
297         $admin = $this->users->admin();
298         $entities = $this->entities->createChainBelongingToUser($editor);
299         $watches = new UserEntityWatchOptions($editor, $entities['book']);
300         $watches->updateLevelByValue(WatchLevels::NEW);
301
302         $this->actingAs($admin)->get($entities['chapter']->getUrl('/create-page'));
303         $page = $entities['chapter']->pages()->where('draft', '=', true)->first();
304         $this->post($page->getUrl(), ['name' => 'My new page', 'html' => 'My new page content']);
305
306         $notifications->assertSentTo($editor, function (PageCreationNotification $notification) use ($editor, $admin) {
307             $mail = $notification->toMail($editor);
308             $mailContent = html_entity_decode(strip_tags($mail->render()));
309             return $mail->subject === 'New page: My new page'
310                 && str_contains($mailContent, 'View Page')
311                 && str_contains($mailContent, 'Page Name: My new page')
312                 && str_contains($mailContent, 'Created By: ' . $admin->name);
313         });
314     }
315
316     public function test_notifications_not_sent_if_lacking_view_permission_for_related_item()
317     {
318         $notifications = Notification::fake();
319         $editor = $this->users->editor();
320         $page = $this->entities->page();
321
322         $watches = new UserEntityWatchOptions($editor, $page);
323         $watches->updateLevelByValue(WatchLevels::COMMENTS);
324         $this->permissions->disableEntityInheritedPermissions($page);
325
326         $this->asAdmin()->post("/comment/{$page->id}", [
327             'text' => 'My new comment response',
328         ])->assertOk();
329
330         $notifications->assertNothingSentTo($editor);
331     }
332 }