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_changes_book_for_deleted_pages_within()
203 /** @var Chapter $chapter */
204 $chapter = Chapter::query()->whereHas('pages')->first();
205 $currentBook = $chapter->book;
206 $pageToCheck = $chapter->pages->first();
207 $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
209 $pageToCheck->delete();
211 $this->asEditor()->put($chapter->getUrl('/move'), [
212 'entity_selection' => 'book:' . $newBook->id,
215 $pageToCheck->refresh();
216 $this->assertEquals($newBook->id, $pageToCheck->book_id);
219 public function test_book_sort_page_shows()
221 /** @var Book $bookToSort */
222 $bookToSort = Book::query()->first();
224 $resp = $this->asAdmin()->get($bookToSort->getUrl());
225 $resp->assertElementExists('a[href="' . $bookToSort->getUrl('/sort') . '"]');
227 $resp = $this->get($bookToSort->getUrl('/sort'));
228 $resp->assertStatus(200);
229 $resp->assertSee($bookToSort->name);
232 public function test_book_sort()
234 $oldBook = Book::query()->first();
235 $chapterToMove = $this->newChapter(['name' => 'chapter to move'], $oldBook);
236 $newBook = $this->newBook(['name' => 'New sort book']);
237 $pagesToMove = Page::query()->take(5)->get();
239 // Create request data
242 'id' => $chapterToMove->id,
244 'parentChapter' => false,
246 'book' => $newBook->id,
249 foreach ($pagesToMove as $index => $page) {
253 'parentChapter' => $index === count($pagesToMove) - 1 ? $chapterToMove->id : false,
255 'book' => $newBook->id,
259 $sortResp = $this->asEditor()->put($newBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)]);
260 $sortResp->assertRedirect($newBook->getUrl());
261 $sortResp->assertStatus(302);
262 $this->assertDatabaseHas('chapters', [
263 'id' => $chapterToMove->id,
264 'book_id' => $newBook->id,
267 $this->assertTrue($newBook->chapters()->count() === 1);
268 $this->assertTrue($newBook->chapters()->first()->pages()->count() === 1);
270 $checkPage = $pagesToMove[1];
271 $checkResp = $this->get($checkPage->refresh()->getUrl());
272 $checkResp->assertSee($newBook->name);
275 public function test_book_sort_makes_no_changes_if_new_chapter_does_not_align_with_new_book()
277 /** @var Page $page */
278 $page = Page::query()->where('chapter_id', '!=', 0)->first();
279 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
284 'parentChapter' => $otherChapter->id,
286 'book' => $page->book_id,
288 $this->asEditor()->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
290 $this->assertDatabaseHas('pages', [
291 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
295 public function test_book_sort_makes_no_changes_if_no_view_permissions_on_new_chapter()
297 /** @var Page $page */
298 $page = Page::query()->where('chapter_id', '!=', 0)->first();
299 /** @var Chapter $otherChapter */
300 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
301 $this->setEntityRestrictions($otherChapter);
306 'parentChapter' => $otherChapter->id,
308 'book' => $otherChapter->book_id,
310 $this->asEditor()->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
312 $this->assertDatabaseHas('pages', [
313 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
317 public function test_book_sort_makes_no_changes_if_no_view_permissions_on_new_book()
319 /** @var Page $page */
320 $page = Page::query()->where('chapter_id', '!=', 0)->first();
321 /** @var Chapter $otherChapter */
322 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
323 $editor = $this->getEditor();
324 $this->setEntityRestrictions($otherChapter->book, ['update', 'delete'], [$editor->roles()->first()]);
329 'parentChapter' => $otherChapter->id,
331 'book' => $otherChapter->book_id,
333 $this->actingAs($editor)->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
335 $this->assertDatabaseHas('pages', [
336 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
340 public function test_book_sort_makes_no_changes_if_no_update_or_create_permissions_on_new_chapter()
342 /** @var Page $page */
343 $page = Page::query()->where('chapter_id', '!=', 0)->first();
344 /** @var Chapter $otherChapter */
345 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
346 $editor = $this->getEditor();
347 $this->setEntityRestrictions($otherChapter, ['view', 'delete'], [$editor->roles()->first()]);
352 'parentChapter' => $otherChapter->id,
354 'book' => $otherChapter->book_id,
356 $this->actingAs($editor)->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
358 $this->assertDatabaseHas('pages', [
359 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
363 public function test_book_sort_makes_no_changes_if_no_update_permissions_on_moved_item()
365 /** @var Page $page */
366 $page = Page::query()->where('chapter_id', '!=', 0)->first();
367 /** @var Chapter $otherChapter */
368 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
369 $editor = $this->getEditor();
370 $this->setEntityRestrictions($page, ['view', 'delete'], [$editor->roles()->first()]);
375 'parentChapter' => $otherChapter->id,
377 'book' => $otherChapter->book_id,
379 $this->actingAs($editor)->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
381 $this->assertDatabaseHas('pages', [
382 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
386 public function test_book_sort_makes_no_changes_if_no_delete_permissions_on_moved_item()
388 /** @var Page $page */
389 $page = Page::query()->where('chapter_id', '!=', 0)->first();
390 /** @var Chapter $otherChapter */
391 $otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
392 $editor = $this->getEditor();
393 $this->setEntityRestrictions($page, ['view', 'update'], [$editor->roles()->first()]);
398 'parentChapter' => $otherChapter->id,
400 'book' => $otherChapter->book_id,
402 $this->actingAs($editor)->put($page->book->getUrl('/sort'), ['sort-tree' => json_encode([$sortData])])->assertRedirect();
404 $this->assertDatabaseHas('pages', [
405 'id' => $page->id, 'chapter_id' => $page->chapter_id, 'book_id' => $page->book_id,
410 public function test_book_sort_item_returns_book_content()
412 $books = Book::all();
413 $bookToSort = $books[0];
414 $firstPage = $bookToSort->pages[0];
415 $firstChapter = $bookToSort->chapters[0];
417 $resp = $this->asAdmin()->get($bookToSort->getUrl() . '/sort-item');
419 // Ensure book details are returned
420 $resp->assertSee($bookToSort->name);
421 $resp->assertSee($firstPage->name);
422 $resp->assertSee($firstChapter->name);
425 public function test_pages_in_book_show_sorted_by_priority()
427 /** @var Book $book */
428 $book = Book::query()->whereHas('pages')->first();
429 $book->chapters()->forceDelete();
430 /** @var Page[] $pages */
431 $pages = $book->pages()->where('chapter_id', '=', 0)->take(2)->get();
432 $book->pages()->whereNotIn('id', $pages->pluck('id'))->delete();
434 $resp = $this->asEditor()->get($book->getUrl());
435 $resp->assertElementContains('.content-wrap a.page:nth-child(1)', $pages[0]->name);
436 $resp->assertElementContains('.content-wrap a.page:nth-child(2)', $pages[1]->name);
438 $pages[0]->forceFill(['priority' => 10])->save();
439 $pages[1]->forceFill(['priority' => 5])->save();
441 $resp = $this->asEditor()->get($book->getUrl());
442 $resp->assertElementContains('.content-wrap a.page:nth-child(1)', $pages[1]->name);
443 $resp->assertElementContains('.content-wrap a.page:nth-child(2)', $pages[0]->name);