]> BookStack Code Mirror - bookstack/blob - tests/Entity/PageTest.php
Create additional test helper classes
[bookstack] / tests / Entity / PageTest.php
1 <?php
2
3 namespace Tests\Entity;
4
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Page;
7 use Carbon\Carbon;
8 use Tests\TestCase;
9
10 class PageTest extends TestCase
11 {
12     public function test_create()
13     {
14         $chapter = $this->entities->chapter();
15         $page = Page::factory()->make([
16             'name' => 'My First Page',
17         ]);
18
19         $resp = $this->asEditor()->get($chapter->getUrl());
20         $this->withHtml($resp)->assertElementContains('a[href="' . $chapter->getUrl('/create-page') . '"]', 'New Page');
21
22         $resp = $this->get($chapter->getUrl('/create-page'));
23         /** @var Page $draftPage */
24         $draftPage = Page::query()
25             ->where('draft', '=', true)
26             ->orderBy('created_at', 'desc')
27             ->first();
28         $resp->assertRedirect($draftPage->getUrl());
29
30         $resp = $this->get($draftPage->getUrl());
31         $this->withHtml($resp)->assertElementContains('form[action="' . $draftPage->getUrl() . '"][method="POST"]', 'Save Page');
32
33         $resp = $this->post($draftPage->getUrl(), $draftPage->only('name', 'html'));
34         $draftPage->refresh();
35         $resp->assertRedirect($draftPage->getUrl());
36     }
37
38     public function test_page_view_when_creator_is_deleted_but_owner_exists()
39     {
40         $page = $this->entities->page();
41         $user = $this->users->viewer();
42         $owner = $this->users->editor();
43         $page->created_by = $user->id;
44         $page->owned_by = $owner->id;
45         $page->save();
46         $user->delete();
47
48         $resp = $this->asAdmin()->get($page->getUrl());
49         $resp->assertStatus(200);
50         $resp->assertSeeText('Owned by ' . $owner->name);
51     }
52
53     public function test_page_creation_with_markdown_content()
54     {
55         $this->setSettings(['app-editor' => 'markdown']);
56         $book = $this->entities->book();
57
58         $this->asEditor()->get($book->getUrl('/create-page'));
59         $draft = Page::query()->where('book_id', '=', $book->id)
60             ->where('draft', '=', true)->first();
61
62         $details = [
63             'markdown' => '# a title',
64             'html'     => '<h1>a title</h1>',
65             'name'     => 'my page',
66         ];
67         $resp = $this->post($book->getUrl("/draft/{$draft->id}"), $details);
68         $resp->assertRedirect();
69
70         $this->assertDatabaseHas('pages', [
71             'markdown' => $details['markdown'],
72             'name'     => $details['name'],
73             'id'       => $draft->id,
74             'draft'    => false,
75         ]);
76
77         $draft->refresh();
78         $resp = $this->get($draft->getUrl('/edit'));
79         $resp->assertSee('# a title');
80     }
81
82     public function test_page_delete()
83     {
84         $page = $this->entities->page();
85         $this->assertNull($page->deleted_at);
86
87         $deleteViewReq = $this->asEditor()->get($page->getUrl('/delete'));
88         $deleteViewReq->assertSeeText('Are you sure you want to delete this page?');
89
90         $deleteReq = $this->delete($page->getUrl());
91         $deleteReq->assertRedirect($page->getParent()->getUrl());
92         $this->assertActivityExists('page_delete', $page);
93
94         $page->refresh();
95         $this->assertNotNull($page->deleted_at);
96         $this->assertTrue($page->deletions()->count() === 1);
97
98         $redirectReq = $this->get($deleteReq->baseResponse->headers->get('location'));
99         $this->assertNotificationContains($redirectReq, 'Page Successfully Deleted');
100     }
101
102     public function test_page_full_delete_removes_all_revisions()
103     {
104         $page = $this->entities->page();
105         $page->revisions()->create([
106             'html' => '<p>ducks</p>',
107             'name' => 'my page revision',
108             'type' => 'draft',
109         ]);
110         $page->revisions()->create([
111             'html' => '<p>ducks</p>',
112             'name' => 'my page revision',
113             'type' => 'revision',
114         ]);
115
116         $this->assertDatabaseHas('page_revisions', [
117             'page_id' => $page->id,
118         ]);
119
120         $this->asEditor()->delete($page->getUrl());
121         $this->asAdmin()->post('/settings/recycle-bin/empty');
122
123         $this->assertDatabaseMissing('page_revisions', [
124             'page_id' => $page->id,
125         ]);
126     }
127
128     public function test_page_copy()
129     {
130         $page = $this->entities->page();
131         $page->html = '<p>This is some test content</p>';
132         $page->save();
133
134         $currentBook = $page->book;
135         $newBook = Book::where('id', '!=', $currentBook->id)->first();
136
137         $resp = $this->asEditor()->get($page->getUrl('/copy'));
138         $resp->assertSee('Copy Page');
139
140         $movePageResp = $this->post($page->getUrl('/copy'), [
141             'entity_selection' => 'book:' . $newBook->id,
142             'name'             => 'My copied test page',
143         ]);
144         $pageCopy = Page::where('name', '=', 'My copied test page')->first();
145
146         $movePageResp->assertRedirect($pageCopy->getUrl());
147         $this->assertTrue($pageCopy->book->id == $newBook->id, 'Page was copied to correct book');
148         $this->assertStringContainsString('This is some test content', $pageCopy->html);
149     }
150
151     public function test_page_copy_with_markdown_has_both_html_and_markdown()
152     {
153         $page = $this->entities->page();
154         $page->html = '<h1>This is some test content</h1>';
155         $page->markdown = '# This is some test content';
156         $page->save();
157         $newBook = Book::where('id', '!=', $page->book->id)->first();
158
159         $this->asEditor()->post($page->getUrl('/copy'), [
160             'entity_selection' => 'book:' . $newBook->id,
161             'name'             => 'My copied test page',
162         ]);
163         $pageCopy = Page::where('name', '=', 'My copied test page')->first();
164
165         $this->assertStringContainsString('This is some test content', $pageCopy->html);
166         $this->assertEquals('# This is some test content', $pageCopy->markdown);
167     }
168
169     public function test_page_copy_with_no_destination()
170     {
171         $page = $this->entities->page();
172         $currentBook = $page->book;
173
174         $resp = $this->asEditor()->get($page->getUrl('/copy'));
175         $resp->assertSee('Copy Page');
176
177         $movePageResp = $this->post($page->getUrl('/copy'), [
178             'name' => 'My copied test page',
179         ]);
180
181         $pageCopy = Page::where('name', '=', 'My copied test page')->first();
182
183         $movePageResp->assertRedirect($pageCopy->getUrl());
184         $this->assertTrue($pageCopy->book->id == $currentBook->id, 'Page was copied to correct book');
185         $this->assertTrue($pageCopy->id !== $page->id, 'Page copy is not the same instance');
186     }
187
188     public function test_page_can_be_copied_without_edit_permission()
189     {
190         $page = $this->entities->page();
191         $currentBook = $page->book;
192         $newBook = Book::where('id', '!=', $currentBook->id)->first();
193         $viewer = $this->users->viewer();
194
195         $resp = $this->actingAs($viewer)->get($page->getUrl());
196         $resp->assertDontSee($page->getUrl('/copy'));
197
198         $newBook->owned_by = $viewer->id;
199         $newBook->save();
200         $this->permissions->grantUserRolePermissions($viewer, ['page-create-own']);
201         $this->permissions->regenerateForEntity($newBook);
202
203         $resp = $this->actingAs($viewer)->get($page->getUrl());
204         $resp->assertSee($page->getUrl('/copy'));
205
206         $movePageResp = $this->post($page->getUrl('/copy'), [
207             'entity_selection' => 'book:' . $newBook->id,
208             'name'             => 'My copied test page',
209         ]);
210         $movePageResp->assertRedirect();
211
212         $this->assertDatabaseHas('pages', [
213             'name'       => 'My copied test page',
214             'created_by' => $viewer->id,
215             'book_id'    => $newBook->id,
216         ]);
217     }
218
219     public function test_old_page_slugs_redirect_to_new_pages()
220     {
221         $page = $this->entities->page();
222
223         // Need to save twice since revisions are not generated in seeder.
224         $this->asAdmin()->put($page->getUrl(), [
225             'name' => 'super test',
226             'html' => '<p></p>',
227         ]);
228
229         $page->refresh();
230         $pageUrl = $page->getUrl();
231
232         $this->put($pageUrl, [
233             'name' => 'super test page',
234             'html' => '<p></p>',
235         ]);
236
237         $this->get($pageUrl)
238             ->assertRedirect("/books/{$page->book->slug}/page/super-test-page");
239     }
240
241     public function test_page_within_chapter_deletion_returns_to_chapter()
242     {
243         $chapter = $this->entities->chapter();
244         $page = $chapter->pages()->first();
245
246         $this->asEditor()->delete($page->getUrl())
247             ->assertRedirect($chapter->getUrl());
248     }
249
250     public function test_recently_updated_pages_view()
251     {
252         $user = $this->users->editor();
253         $content = $this->entities->createChainBelongingToUser($user);
254
255         $resp = $this->asAdmin()->get('/pages/recently-updated');
256         $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', $content['page']->name);
257     }
258
259     public function test_recently_updated_pages_view_shows_updated_by_details()
260     {
261         $user = $this->users->editor();
262         $page = $this->entities->page();
263
264         $this->actingAs($user)->put($page->getUrl(), [
265             'name' => 'Updated title',
266             'html' => '<p>Updated content</p>',
267         ]);
268
269         $resp = $this->asAdmin()->get('/pages/recently-updated');
270         $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', 'Updated 1 second ago by ' . $user->name);
271     }
272
273     public function test_recently_updated_pages_view_shows_parent_chain()
274     {
275         $user = $this->users->editor();
276         $page = $this->entities->pageWithinChapter();
277
278         $this->actingAs($user)->put($page->getUrl(), [
279             'name' => 'Updated title',
280             'html' => '<p>Updated content</p>',
281         ]);
282
283         $resp = $this->asAdmin()->get('/pages/recently-updated');
284         $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', $page->chapter->getShortName(42));
285         $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', $page->book->getShortName(42));
286     }
287
288     public function test_recently_updated_pages_view_does_not_show_parent_if_not_visible()
289     {
290         $user = $this->users->editor();
291         $page = $this->entities->pageWithinChapter();
292
293         $this->actingAs($user)->put($page->getUrl(), [
294             'name' => 'Updated title',
295             'html' => '<p>Updated content</p>',
296         ]);
297
298         $this->permissions->setEntityPermissions($page->book);
299         $this->permissions->setEntityPermissions($page, ['view'], [$user->roles->first()]);
300
301         $resp = $this->get('/pages/recently-updated');
302         $resp->assertDontSee($page->book->getShortName(42));
303         $resp->assertDontSee($page->chapter->getShortName(42));
304         $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', 'Updated title');
305     }
306
307     public function test_recently_updated_pages_on_home()
308     {
309         /** @var Page $page */
310         $page = Page::query()->orderBy('updated_at', 'asc')->first();
311         Page::query()->where('id', '!=', $page->id)->update([
312             'updated_at' => Carbon::now()->subSecond(1),
313         ]);
314
315         $resp = $this->asAdmin()->get('/');
316         $this->withHtml($resp)->assertElementNotContains('#recently-updated-pages', $page->name);
317
318         $this->put($page->getUrl(), [
319             'name' => $page->name,
320             'html' => $page->html,
321         ]);
322
323         $resp = $this->get('/');
324         $this->withHtml($resp)->assertElementContains('#recently-updated-pages', $page->name);
325     }
326 }