3 namespace Tests\Activity;
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;
15 class WatchTest extends TestCase
17 public function test_watch_action_exists_on_entity_unless_active()
19 $editor = $this->users->editor();
20 $this->actingAs($editor);
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');
28 $watchOptions = new UserEntityWatchOptions($editor, $entity);
29 $watchOptions->updateLevelByValue(WatchLevels::COMMENTS);
31 $resp = $this->get($entity->getUrl());
32 $this->withHtml($resp)->assertElementNotExists('form[action$="/watching/update"] button.icon-list-item');
36 public function test_watch_action_only_shows_with_permission()
38 $viewer = $this->users->viewer();
39 $this->actingAs($viewer);
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');
48 $this->permissions->grantUserRolePermissions($viewer, ['receive-notifications']);
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');
57 public function test_watch_update()
59 $editor = $this->users->editor();
60 $book = $this->entities->book();
62 $this->actingAs($editor)->get($book->getUrl());
63 $resp = $this->put('/watching/update', [
64 'type' => get_class($book),
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,
78 $resp = $this->put('/watching/update', [
79 'type' => get_class($book),
83 $resp->assertRedirect($book->getUrl());
84 $this->assertDatabaseMissing('watches', [
85 'watchable_id' => $book->id,
86 'watchable_type' => $book->getMorphClass(),
87 'user_id' => $editor->id,
91 public function test_watch_update_fails_for_guest()
93 $this->setSettings(['app-public' => 'true']);
94 $guest = $this->users->guest();
95 $this->permissions->grantUserRolePermissions($guest, ['receive-notifications']);
96 $book = $this->entities->book();
98 $resp = $this->put('/watching/update', [
99 'type' => get_class($book),
101 'level' => 'comments'
104 $this->assertPermissionError($resp);
105 $guest->unsetRelations();
108 public function test_watch_detail_display_reflects_state()
110 $editor = $this->users->editor();
111 $book = $this->entities->bookHasChaptersAndPages();
112 $chapter = $book->chapters()->first();
113 $page = $chapter->pages()->first();
115 (new UserEntityWatchOptions($editor, $book))->updateLevelByValue(WatchLevels::UPDATES);
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');
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');
125 (new UserEntityWatchOptions($editor, $page))->updateLevelByValue(WatchLevels::UPDATES);
126 $this->get($page->getUrl())->assertSee('Watching new pages and updates');
129 public function test_watch_detail_ignore_indicator_cascades()
131 $editor = $this->users->editor();
132 $book = $this->entities->bookHasChaptersAndPages();
133 (new UserEntityWatchOptions($editor, $book))->updateLevelByValue(WatchLevels::IGNORE);
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');
140 public function test_watch_option_menu_shows_current_active_state()
142 $editor = $this->users->editor();
143 $book = $this->entities->book();
144 $options = new UserEntityWatchOptions($editor, $book);
146 $respHtml = $this->withHtml($this->actingAs($editor)->get($book->getUrl()));
147 $respHtml->assertElementNotExists('form[action$="/watching/update"] svg[data-icon="check-circle"]');
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"]');
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"]');
158 public function test_watch_option_menu_limits_options_for_pages()
160 $editor = $this->users->editor();
161 $book = $this->entities->bookHasChaptersAndPages();
162 (new UserEntityWatchOptions($editor, $book))->updateLevelByValue(WatchLevels::IGNORE);
164 $respHtml = $this->withHtml($this->actingAs($editor)->get($book->getUrl()));
165 $respHtml->assertElementExists('form[action$="/watching/update"] button[name="level"][value="new"]');
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"]');
172 public function test_notify_own_page_changes()
174 $editor = $this->users->editor();
175 $entities = $this->entities->createChainBelongingToUser($editor);
176 $prefs = new UserNotificationPreferences($editor);
177 $prefs->updateFromSettingsArray(['own-page-changes' => 'true']);
179 $notifications = Notification::fake();
182 $this->entities->updatePage($entities['page'], ['name' => 'My updated page', 'html' => 'Hello']);
183 $notifications->assertSentTo($editor, PageUpdateNotification::class);
186 public function test_notify_own_page_comments()
188 $editor = $this->users->editor();
189 $entities = $this->entities->createChainBelongingToUser($editor);
190 $prefs = new UserNotificationPreferences($editor);
191 $prefs->updateFromSettingsArray(['own-page-comments' => 'true']);
193 $notifications = Notification::fake();
195 $this->asAdmin()->post("/comment/{$entities['page']->id}", [
196 'text' => 'My new comment'
198 $notifications->assertSentTo($editor, CommentCreationNotification::class);
201 public function test_notify_comment_replies()
203 $editor = $this->users->editor();
204 $entities = $this->entities->createChainBelongingToUser($editor);
205 $prefs = new UserNotificationPreferences($editor);
206 $prefs->updateFromSettingsArray(['comment-replies' => 'true']);
208 $notifications = Notification::fake();
210 $this->actingAs($editor)->post("/comment/{$entities['page']->id}", [
211 'text' => 'My new comment'
213 $comment = $entities['page']->comments()->first();
215 $this->asAdmin()->post("/comment/{$entities['page']->id}", [
216 'text' => 'My new comment response',
217 'parent_id' => $comment->id,
219 $notifications->assertSentTo($editor, CommentCreationNotification::class);
222 public function test_notify_watch_parent_book_ignore()
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]);
231 $notifications = Notification::fake();
233 $this->asAdmin()->post("/comment/{$entities['page']->id}", [
234 'text' => 'My new comment response',
236 $this->entities->updatePage($entities['page'], ['name' => 'My updated page', 'html' => 'Hello']);
237 $notifications->assertNothingSent();
240 public function test_notify_watch_parent_book_comments()
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);
250 $this->actingAs($admin)->post("/comment/{$entities['page']->id}", [
251 'text' => 'My new comment response',
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');
265 public function test_notify_watch_parent_book_updates()
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);
274 $this->actingAs($admin);
275 $this->entities->updatePage($entities['page'], ['name' => 'Updated page', 'html' => 'new page content']);
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');
288 $notifications = Notification::fake();
289 $this->entities->updatePage($entities['page'], ['name' => 'Updated page', 'html' => 'new page content']);
290 $notifications->assertNothingSentTo($editor);
293 public function test_notify_watch_parent_book_new()
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);
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']);
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);
316 public function test_notifications_not_sent_if_lacking_view_permission_for_related_item()
318 $notifications = Notification::fake();
319 $editor = $this->users->editor();
320 $page = $this->entities->page();
322 $watches = new UserEntityWatchOptions($editor, $page);
323 $watches->updateLevelByValue(WatchLevels::COMMENTS);
324 $this->permissions->disableEntityInheritedPermissions($page);
326 $this->asAdmin()->post("/comment/{$page->id}", [
327 'text' => 'My new comment response',
330 $notifications->assertNothingSentTo($editor);