3 namespace Tests\Entity;
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Chapter;
7 use BookStack\Entities\Models\Page;
8 use BookStack\Entities\Repos\PageRepo;
11 class SortTest extends TestCase
15 protected function setUp(): void
18 $this->book = Book::first();
21 public function test_drafts_do_not_show_up()
24 $pageRepo = app(PageRepo::class);
25 $draft = $pageRepo->getNewDraftPage($this->book);
27 $resp = $this->get($this->book->getUrl());
28 $resp->assertSee($draft->name);
30 $resp = $this->get($this->book->getUrl() . '/sort');
31 $resp->assertDontSee($draft->name);
34 public function test_page_move_into_book()
36 $page = Page::query()->first();
37 $currentBook = $page->book;
38 $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
40 $resp = $this->asEditor()->get($page->getUrl('/move'));
41 $resp->assertSee('Move Page');
43 $movePageResp = $this->put($page->getUrl('/move'), [
44 'entity_selection' => 'book:' . $newBook->id,
46 $page = Page::query()->find($page->id);
48 $movePageResp->assertRedirect($page->getUrl());
49 $this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book');
51 $newBookResp = $this->get($newBook->getUrl());
52 $newBookResp->assertSee('moved page');
53 $newBookResp->assertSee($page->name);
56 public function test_page_move_into_chapter()
58 $page = Page::query()->first();
59 $currentBook = $page->book;
60 $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
61 $newChapter = $newBook->chapters()->first();
63 $movePageResp = $this->actingAs($this->getEditor())->put($page->getUrl('/move'), [
64 'entity_selection' => 'chapter:' . $newChapter->id,
66 $page = Page::query()->find($page->id);
68 $movePageResp->assertRedirect($page->getUrl());
69 $this->assertTrue($page->book->id == $newBook->id, 'Page parent is now the new chapter');
71 $newChapterResp = $this->get($newChapter->getUrl());
72 $newChapterResp->assertSee($page->name);
75 public function test_page_move_from_chapter_to_book()
77 $oldChapter = Chapter::query()->first();
78 $page = $oldChapter->pages()->first();
79 $newBook = Book::query()->where('id', '!=', $oldChapter->book_id)->first();
81 $movePageResp = $this->actingAs($this->getEditor())->put($page->getUrl('/move'), [
82 'entity_selection' => 'book:' . $newBook->id,
86 $movePageResp->assertRedirect($page->getUrl());
87 $this->assertTrue($page->book->id == $newBook->id, 'Page parent is now the new book');
88 $this->assertTrue($page->chapter === null, 'Page has no parent chapter');
90 $newBookResp = $this->get($newBook->getUrl());
91 $newBookResp->assertSee($page->name);
94 public function test_page_move_requires_create_permissions_on_parent()
96 $page = Page::query()->first();
97 $currentBook = $page->book;
98 $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
99 $editor = $this->getEditor();
101 $this->setEntityRestrictions($newBook, ['view', 'update', 'delete'], $editor->roles->all());
103 $movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [
104 'entity_selection' => 'book:' . $newBook->id,
106 $this->assertPermissionError($movePageResp);
108 $this->setEntityRestrictions($newBook, ['view', 'update', 'delete', 'create'], $editor->roles->all());
109 $movePageResp = $this->put($page->getUrl('/move'), [
110 'entity_selection' => 'book:' . $newBook->id,
113 $page = Page::query()->find($page->id);
114 $movePageResp->assertRedirect($page->getUrl());
116 $this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book');
119 public function test_page_move_requires_delete_permissions()
121 $page = Page::query()->first();
122 $currentBook = $page->book;
123 $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
124 $editor = $this->getEditor();
126 $this->setEntityRestrictions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
127 $this->setEntityRestrictions($page, ['view', 'update', 'create'], $editor->roles->all());
129 $movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [
130 'entity_selection' => 'book:' . $newBook->id,
132 $this->assertPermissionError($movePageResp);
133 $pageView = $this->get($page->getUrl());
134 $pageView->assertDontSee($page->getUrl('/move'));
136 $this->setEntityRestrictions($page, ['view', 'update', 'create', 'delete'], $editor->roles->all());
137 $movePageResp = $this->put($page->getUrl('/move'), [
138 'entity_selection' => 'book:' . $newBook->id,
141 $page = Page::query()->find($page->id);
142 $movePageResp->assertRedirect($page->getUrl());
143 $this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book');
146 public function test_chapter_move()
148 $chapter = Chapter::query()->first();
149 $currentBook = $chapter->book;
150 $pageToCheck = $chapter->pages->first();
151 $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
153 $chapterMoveResp = $this->asEditor()->get($chapter->getUrl('/move'));
154 $chapterMoveResp->assertSee('Move Chapter');
156 $moveChapterResp = $this->put($chapter->getUrl('/move'), [
157 'entity_selection' => 'book:' . $newBook->id,
160 $chapter = Chapter::query()->find($chapter->id);
161 $moveChapterResp->assertRedirect($chapter->getUrl());
162 $this->assertTrue($chapter->book->id === $newBook->id, 'Chapter Book is now the new book');
164 $newBookResp = $this->get($newBook->getUrl());
165 $newBookResp->assertSee('moved chapter');
166 $newBookResp->assertSee($chapter->name);
168 $pageToCheck = Page::query()->find($pageToCheck->id);
169 $this->assertTrue($pageToCheck->book_id === $newBook->id, 'Chapter child page\'s book id has changed to the new book');
170 $pageCheckResp = $this->get($pageToCheck->getUrl());
171 $pageCheckResp->assertSee($newBook->name);
174 public function test_chapter_move_requires_delete_permissions()
176 $chapter = Chapter::query()->first();
177 $currentBook = $chapter->book;
178 $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
179 $editor = $this->getEditor();
181 $this->setEntityRestrictions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
182 $this->setEntityRestrictions($chapter, ['view', 'update', 'create'], $editor->roles->all());
184 $moveChapterResp = $this->actingAs($editor)->put($chapter->getUrl('/move'), [
185 'entity_selection' => 'book:' . $newBook->id,
187 $this->assertPermissionError($moveChapterResp);
188 $pageView = $this->get($chapter->getUrl());
189 $pageView->assertDontSee($chapter->getUrl('/move'));
191 $this->setEntityRestrictions($chapter, ['view', 'update', 'create', 'delete'], $editor->roles->all());
192 $moveChapterResp = $this->put($chapter->getUrl('/move'), [
193 'entity_selection' => 'book:' . $newBook->id,
196 $chapter = Chapter::query()->find($chapter->id);
197 $moveChapterResp->assertRedirect($chapter->getUrl());
198 $this->assertTrue($chapter->book->id == $newBook->id, 'Page book is now the new book');
201 public function test_chapter_move_requires_create_permissions_in_new_book()
203 $chapter = Chapter::query()->first();
204 $currentBook = $chapter->book;
205 $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
206 $editor = $this->getEditor();
208 $this->setEntityRestrictions($newBook, ['view', 'update', 'delete'], [$editor->roles->first()]);
209 $this->setEntityRestrictions($chapter, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]);
211 $moveChapterResp = $this->actingAs($editor)->put($chapter->getUrl('/move'), [
212 'entity_selection' => 'book:' . $newBook->id,
214 $this->assertPermissionError($moveChapterResp);
216 $this->setEntityRestrictions($newBook, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]);
217 $moveChapterResp = $this->put($chapter->getUrl('/move'), [
218 'entity_selection' => 'book:' . $newBook->id,
221 $chapter = Chapter::query()->find($chapter->id);
222 $moveChapterResp->assertRedirect($chapter->getUrl());
223 $this->assertTrue($chapter->book->id == $newBook->id, 'Page book is now the new book');
226 public function test_chapter_move_changes_book_for_deleted_pages_within()
228 /** @var Chapter $chapter */
229 $chapter = Chapter::query()->whereHas('pages')->first();
230 $currentBook = $chapter->book;
231 $pageToCheck = $chapter->pages->first();
232 $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
234 $pageToCheck->delete();
236 $this->asEditor()->put($chapter->getUrl('/move'), [
237 'entity_selection' => 'book:' . $newBook->id,
240 $pageToCheck->refresh();
241 $this->assertEquals($newBook->id, $pageToCheck->book_id);
244 public function test_book_sort_page_shows()
246 /** @var Book $bookToSort */
247 $bookToSort = Book::query()->first();
249 $resp = $this->asAdmin()->get($bookToSort->getUrl());
250 $resp->assertElementExists('a[href="' . $bookToSort->getUrl('/sort') . '"]');
252 $resp = $this->get($bookToSort->getUrl('/sort'));
253 $resp->assertStatus(200);
254 $resp->assertSee($bookToSort->name);
257 public function test_book_sort()
259 $oldBook = Book::query()->first();
260 $chapterToMove = $this->newChapter(['name' => 'chapter to move'], $oldBook);
261 $newBook = $this->newBook(['name' => 'New sort book']);
262 $pagesToMove = Page::query()->take(5)->get();
264 // Create request data
267 'id' => $chapterToMove->id,
269 'parentChapter' => false,
271 'book' => $newBook->id,
274 foreach ($pagesToMove as $index => $page) {
278 'parentChapter' => $index === count($pagesToMove) - 1 ? $chapterToMove->id : false,
280 'book' => $newBook->id,
284 $sortResp = $this->asEditor()->put($newBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)]);
285 $sortResp->assertRedirect($newBook->getUrl());
286 $sortResp->assertStatus(302);
287 $this->assertDatabaseHas('chapters', [
288 'id' => $chapterToMove->id,
289 'book_id' => $newBook->id,
292 $this->assertTrue($newBook->chapters()->count() === 1);
293 $this->assertTrue($newBook->chapters()->first()->pages()->count() === 1);
295 $checkPage = $pagesToMove[1];
296 $checkResp = $this->get($checkPage->refresh()->getUrl());
297 $checkResp->assertSee($newBook->name);
300 public function test_book_sort_makes_no_changes_if_new_chapter_does_not_align_with_new_book()
302 /** @var Page $page */
303 $page = Page::query()->where('chapter_id', '!=', 0)->first();
304 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
309 'parentChapter' => $otherChapter->id,
311 'book' => $page->book_id,
313 $this->asEditor()->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
315 $this->assertDatabaseHas('pages', [
316 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
320 public function test_book_sort_makes_no_changes_if_no_view_permissions_on_new_chapter()
322 /** @var Page $page */
323 $page = Page::query()->where('chapter_id', '!=', 0)->first();
324 /** @var Chapter $otherChapter */
325 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
326 $this->setEntityRestrictions($otherChapter);
331 'parentChapter' => $otherChapter->id,
333 'book' => $otherChapter->book_id,
335 $this->asEditor()->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
337 $this->assertDatabaseHas('pages', [
338 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
342 public function test_book_sort_makes_no_changes_if_no_view_permissions_on_new_book()
344 /** @var Page $page */
345 $page = Page::query()->where('chapter_id', '!=', 0)->first();
346 /** @var Chapter $otherChapter */
347 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
348 $editor = $this->getEditor();
349 $this->setEntityRestrictions($otherChapter->book, ['update', 'delete'], [$editor->roles()->first()]);
354 'parentChapter' => $otherChapter->id,
356 'book' => $otherChapter->book_id,
358 $this->actingAs($editor)->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
360 $this->assertDatabaseHas('pages', [
361 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
365 public function test_book_sort_makes_no_changes_if_no_update_or_create_permissions_on_new_chapter()
367 /** @var Page $page */
368 $page = Page::query()->where('chapter_id', '!=', 0)->first();
369 /** @var Chapter $otherChapter */
370 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
371 $editor = $this->getEditor();
372 $this->setEntityRestrictions($otherChapter, ['view', 'delete'], [$editor->roles()->first()]);
377 'parentChapter' => $otherChapter->id,
379 'book' => $otherChapter->book_id,
381 $this->actingAs($editor)->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
383 $this->assertDatabaseHas('pages', [
384 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
388 public function test_book_sort_makes_no_changes_if_no_update_permissions_on_moved_item()
390 /** @var Page $page */
391 $page = Page::query()->where('chapter_id', '!=', 0)->first();
392 /** @var Chapter $otherChapter */
393 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
394 $editor = $this->getEditor();
395 $this->setEntityRestrictions($page, ['view', 'delete'], [$editor->roles()->first()]);
400 'parentChapter' => $otherChapter->id,
402 'book' => $otherChapter->book_id,
404 $this->actingAs($editor)->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
406 $this->assertDatabaseHas('pages', [
407 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
411 public function test_book_sort_makes_no_changes_if_no_delete_permissions_on_moved_item()
413 /** @var Page $page */
414 $page = Page::query()->where('chapter_id', '!=', 0)->first();
415 /** @var Chapter $otherChapter */
416 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
417 $editor = $this->getEditor();
418 $this->setEntityRestrictions($page, ['view', 'update'], [$editor->roles()->first()]);
423 'parentChapter' => $otherChapter->id,
425 'book' => $otherChapter->book_id,
427 $this->actingAs($editor)->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
429 $this->assertDatabaseHas('pages', [
430 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
434 public function test_book_sort_item_returns_book_content()
436 $books = Book::all();
437 $bookToSort = $books[0];
438 $firstPage = $bookToSort->pages[0];
439 $firstChapter = $bookToSort->chapters[0];
441 $resp = $this->asAdmin()->get($bookToSort->getUrl() . '/sort-item');
443 // Ensure book details are returned
444 $resp->assertSee($bookToSort->name);
445 $resp->assertSee($firstPage->name);
446 $resp->assertSee($firstChapter->name);
449 public function test_pages_in_book_show_sorted_by_priority()
451 /** @var Book $book */
452 $book = Book::query()->whereHas('pages')->first();
453 $book->chapters()->forceDelete();
454 /** @var Page[] $pages */
455 $pages = $book->pages()->where('chapter_id', '=', 0)->take(2)->get();
456 $book->pages()->whereNotIn('id', $pages->pluck('id'))->delete();
458 $resp = $this->asEditor()->get($book->getUrl());
459 $resp->assertElementContains('.content-wrap a.page:nth-child(1)', $pages[0]->name);
460 $resp->assertElementContains('.content-wrap a.page:nth-child(2)', $pages[1]->name);
462 $pages[0]->forceFill(['priority' => 10])->save();
463 $pages[1]->forceFill(['priority' => 5])->save();
465 $resp = $this->asEditor()->get($book->getUrl());
466 $resp->assertElementContains('.content-wrap a.page:nth-child(1)', $pages[1]->name);
467 $resp->assertElementContains('.content-wrap a.page:nth-child(2)', $pages[0]->name);