3 namespace Tests\Entity;
5 use BookStack\Auth\UserRepo;
6 use BookStack\Entities\Models\Book;
7 use BookStack\Entities\Models\Bookshelf;
8 use BookStack\Entities\Models\Chapter;
9 use BookStack\Entities\Models\Page;
10 use BookStack\Entities\Repos\PageRepo;
12 use Tests\BrowserKitTest;
14 class EntityTest extends BrowserKitTest
16 public function test_entity_creation()
19 $book = $this->bookCreation();
20 $chapter = $this->chapterCreation($book);
21 $this->pageCreation($chapter);
24 $this->bookUpdate($book);
27 public function bookUpdate(Book $book)
29 $newName = $book->name . ' Updated';
32 ->visit($book->getUrl() . '/edit')
35 ->type($newName, '#name')
37 // Check page url and text
38 ->seePageIs($book->getUrl() . '-updated')
41 return Book::find($book->id);
44 public function test_book_sort_page_shows()
47 $bookToSort = $books[0];
49 ->visit($bookToSort->getUrl())
51 ->seePageIs($bookToSort->getUrl() . '/sort')
53 ->see($bookToSort->name);
56 public function test_book_sort_item_returns_book_content()
59 $bookToSort = $books[0];
60 $firstPage = $bookToSort->pages[0];
61 $firstChapter = $bookToSort->chapters[0];
63 ->visit($bookToSort->getUrl() . '/sort-item')
64 // Ensure book details are returned
65 ->see($bookToSort->name)
66 ->see($firstPage->name)
67 ->see($firstChapter->name);
70 public function test_toggle_book_view()
72 $editor = $this->getEditor();
73 setting()->putUser($editor, 'books_view_type', 'grid');
75 $this->actingAs($editor)
77 ->pageHasElement('.featured-image-container')
78 ->submitForm('List View')
81 ->pageNotHasElement('.featured-image-container');
83 $this->actingAs($editor)
85 ->submitForm('Grid View')
87 ->pageHasElement('.featured-image-container');
90 public function pageCreation($chapter)
92 $page = factory(Page::class)->make([
93 'name' => 'My First Page',
97 // Navigate to page create form
98 ->visit($chapter->getUrl())
101 $draftPage = Page::where('draft', '=', true)->orderBy('created_at', 'desc')->first();
103 $this->seePageIs($draftPage->getUrl())
105 ->type($page->name, '#name')
106 ->type($page->html, '#html')
108 // Check redirect and page
109 ->seePageIs($chapter->book->getUrl() . '/page/my-first-page')
112 $page = Page::where('slug', '=', 'my-first-page')->where('chapter_id', '=', $chapter->id)->first();
117 public function chapterCreation(Book $book)
119 $chapter = factory(Chapter::class)->make([
120 'name' => 'My First Chapter',
124 // Navigate to chapter create page
125 ->visit($book->getUrl())
126 ->click('New Chapter')
127 ->seePageIs($book->getUrl() . '/create-chapter')
129 ->type($chapter->name, '#name')
130 ->type($chapter->description, '#description')
131 ->press('Save Chapter')
132 // Check redirect and landing page
133 ->seePageIs($book->getUrl() . '/chapter/my-first-chapter')
134 ->see($chapter->name)->see($chapter->description);
136 $chapter = Chapter::where('slug', '=', 'my-first-chapter')->where('book_id', '=', $book->id)->first();
141 public function bookCreation()
143 $book = factory(Book::class)->make([
144 'name' => 'My First Book',
148 // Choose to create a book
149 ->click('Create New Book')
150 ->seePageIs('/create-book')
151 // Fill out form & save
152 ->type($book->name, '#name')
153 ->type($book->description, '#description')
155 // Check it redirects correctly
156 ->seePageIs('/books/my-first-book')
157 ->see($book->name)->see($book->description);
159 // Ensure duplicate names are given different slugs
161 ->visit('/create-book')
162 ->type($book->name, '#name')
163 ->type($book->description, '#description')
164 ->press('Save Book');
166 $expectedPattern = '/\/books\/my-first-book-[0-9a-zA-Z]{3}/';
167 $this->assertMatchesRegularExpression($expectedPattern, $this->currentUri, "Did not land on expected page [$expectedPattern].\n");
169 $book = Book::where('slug', '=', 'my-first-book')->first();
174 public function test_entities_viewable_after_creator_deletion()
176 // Create required assets and revisions
177 $creator = $this->getEditor();
178 $updater = $this->getEditor();
179 $entities = $this->createEntityChainBelongingToUser($creator, $updater);
180 $this->actingAs($creator);
181 app(UserRepo::class)->destroy($creator);
182 app(PageRepo::class)->update($entities['page'], ['html' => '<p>hello!</p>>']);
184 $this->checkEntitiesViewable($entities);
187 public function test_entities_viewable_after_updater_deletion()
189 // Create required assets and revisions
190 $creator = $this->getEditor();
191 $updater = $this->getEditor();
192 $entities = $this->createEntityChainBelongingToUser($creator, $updater);
193 $this->actingAs($updater);
194 app(UserRepo::class)->destroy($updater);
195 app(PageRepo::class)->update($entities['page'], ['html' => '<p>Hello there!</p>']);
197 $this->checkEntitiesViewable($entities);
200 private function checkEntitiesViewable($entities)
202 // Check pages and books are visible.
204 $this->visit($entities['book']->getUrl())->seeStatusCode(200)
205 ->visit($entities['chapter']->getUrl())->seeStatusCode(200)
206 ->visit($entities['page']->getUrl())->seeStatusCode(200);
207 // Check revision listing shows no errors.
208 $this->visit($entities['page']->getUrl())
209 ->click('Revisions')->seeStatusCode(200);
212 public function test_recently_updated_pages_view()
214 $user = $this->getEditor();
215 $content = $this->createEntityChainBelongingToUser($user);
217 $this->asAdmin()->visit('/pages/recently-updated')
218 ->seeInNthElement('.entity-list .page', 0, $content['page']->name);
221 public function test_old_page_slugs_redirect_to_new_pages()
223 $page = Page::first();
224 $pageUrl = $page->getUrl();
225 $newPageUrl = '/books/' . $page->book->slug . '/page/super-test-page';
226 // Need to save twice since revisions are not generated in seeder.
227 $this->asAdmin()->visit($pageUrl)
228 ->clickInElement('#content', 'Edit')
229 ->type('super test', '#name')
230 ->press('Save Page');
232 $page = Page::first();
233 $pageUrl = $page->getUrl();
236 $this->visit($pageUrl)
237 ->clickInElement('#content', 'Edit')
238 ->type('super test page', '#name')
241 ->seePageIs($newPageUrl);
243 $this->visit($pageUrl)
244 ->seePageIs($newPageUrl);
247 public function test_recently_updated_pages_on_home()
249 $page = Page::orderBy('updated_at', 'asc')->first();
250 Page::where('id', '!=', $page->id)->update([
251 'updated_at' => Carbon::now()->subSecond(1),
253 $this->asAdmin()->visit('/')
254 ->dontSeeInElement('#recently-updated-pages', $page->name);
255 $this->visit($page->getUrl() . '/edit')
258 ->seeInElement('#recently-updated-pages', $page->name);
261 public function test_slug_multi_byte_url_safe()
263 $book = $this->newBook([
264 'name' => 'информация',
267 $this->assertEquals('informatsiya', $book->slug);
269 $book = $this->newBook([
273 $this->assertEquals('que', $book->slug);
276 public function test_slug_format()
278 $book = $this->newBook([
279 'name' => 'PartA / PartB / PartC',
282 $this->assertEquals('parta-partb-partc', $book->slug);
285 public function test_shelf_cancel_creation_returns_to_correct_place()
287 $shelf = Bookshelf::first();
289 // Cancel button from shelf goes back to shelf
290 $this->asEditor()->visit($shelf->getUrl('/create-book'))
293 ->seePageIs($shelf->getUrl());
295 // Cancel button from books goes back to books
296 $this->asEditor()->visit('/create-book')
299 ->seePageIs('/books');
301 // Cancel button from book edit goes back to book
302 $book = Book::first();
304 $this->asEditor()->visit($book->getUrl('/edit'))
307 ->seePageIs($book->getUrl());
310 public function test_page_within_chapter_deletion_returns_to_chapter()
312 $chapter = Chapter::query()->first();
313 $page = $chapter->pages()->first();
315 $this->asEditor()->visit($page->getUrl('/delete'))
316 ->submitForm('Confirm')
317 ->seePageIs($chapter->getUrl());