3 namespace Tests\Entity;
5 use BookStack\Activity\ActivityType;
6 use BookStack\Entities\Models\Page;
9 class PageRevisionTest extends TestCase
11 public function test_revision_links_visible_to_viewer()
13 $page = $this->entities->page();
15 $html = $this->withHtml($this->asViewer()->get($page->getUrl()));
16 $html->assertLinkExists($page->getUrl('/revisions'));
17 $html->assertElementContains('a', 'Revisions');
18 $html->assertElementContains('a', 'Revision #1');
21 public function test_page_revision_views_viewable()
24 $page = $this->entities->page();
25 $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '<p>new content</p>']);
26 $pageRevision = $page->revisions->last();
28 $resp = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id);
29 $resp->assertStatus(200);
30 $resp->assertSee('new content');
32 $resp = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id . '/changes');
33 $resp->assertStatus(200);
34 $resp->assertSee('new content');
37 public function test_page_revision_preview_shows_content_of_revision()
40 $page = $this->entities->page();
41 $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '<p>new revision content</p>']);
42 $pageRevision = $page->revisions->last();
43 $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '<p>Updated content</p>']);
45 $revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id);
46 $revisionView->assertStatus(200);
47 $revisionView->assertSee('new revision content');
50 public function test_page_revision_restore_updates_content()
53 $page = $this->entities->page();
54 $this->createRevisions($page, 1, ['name' => 'updated page abc123', 'html' => '<p>new contente def456</p>']);
55 $this->createRevisions($page, 1, ['name' => 'updated page again', 'html' => '<p>new content</p>']);
56 $page = Page::find($page->id);
58 $pageView = $this->get($page->getUrl());
59 $pageView->assertDontSee('abc123');
60 $pageView->assertDontSee('def456');
62 $revToRestore = $page->revisions()->where('name', 'like', '%abc123')->first();
63 $restoreReq = $this->put($page->getUrl() . '/revisions/' . $revToRestore->id . '/restore');
64 $page = Page::find($page->id);
66 $restoreReq->assertStatus(302);
67 $restoreReq->assertRedirect($page->getUrl());
69 $pageView = $this->get($page->getUrl());
70 $pageView->assertSee('abc123');
71 $pageView->assertSee('def456');
74 public function test_page_revision_restore_with_markdown_retains_markdown_content()
77 $page = $this->entities->page();
78 $this->createRevisions($page, 1, ['name' => 'updated page abc123', 'markdown' => '## New Content def456']);
79 $this->createRevisions($page, 1, ['name' => 'updated page again', 'markdown' => '## New Content Updated']);
80 $page = Page::find($page->id);
82 $pageView = $this->get($page->getUrl());
83 $pageView->assertDontSee('abc123');
84 $pageView->assertDontSee('def456');
86 $revToRestore = $page->revisions()->where('name', 'like', '%abc123')->first();
87 $restoreReq = $this->put($page->getUrl() . '/revisions/' . $revToRestore->id . '/restore');
88 $page = Page::find($page->id);
90 $restoreReq->assertStatus(302);
91 $restoreReq->assertRedirect($page->getUrl());
93 $pageView = $this->get($page->getUrl());
94 $this->assertDatabaseHas('pages', [
96 'markdown' => '## New Content def456',
98 $pageView->assertSee('abc123');
99 $pageView->assertSee('def456');
102 public function test_page_revision_restore_sets_new_revision_with_summary()
105 $page = $this->entities->page();
106 $this->createRevisions($page, 1, ['name' => 'updated page abc123', 'html' => '<p>new contente def456</p>', 'summary' => 'My first update']);
107 $this->createRevisions($page, 1, ['html' => '<p>new content</p>']);
110 $revToRestore = $page->revisions()->where('name', 'like', '%abc123')->first();
111 $this->put($page->getUrl() . '/revisions/' . $revToRestore->id . '/restore');
114 $this->assertDatabaseHas('page_revisions', [
115 'page_id' => $page->id,
116 'text' => 'new contente def456',
118 'summary' => "Restored from #{$revToRestore->id}; My first update",
121 $detail = "Revision #{$revToRestore->revision_number} (ID: {$revToRestore->id}) for page ID {$revToRestore->page_id}";
122 $this->assertActivityExists(ActivityType::REVISION_RESTORE, null, $detail);
125 public function test_page_revision_count_increments_on_update()
127 $page = $this->entities->page();
128 $startCount = $page->revision_count;
129 $this->createRevisions($page, 1);
131 $this->assertTrue(Page::find($page->id)->revision_count === $startCount + 1);
134 public function test_revision_count_shown_in_page_meta()
136 $page = $this->entities->page();
137 $this->createRevisions($page, 2);
139 $pageView = $this->asViewer()->get($page->getUrl());
140 $pageView->assertSee('Revision #' . $page->revision_count);
143 public function test_revision_deletion()
145 $page = $this->entities->page();
146 $this->createRevisions($page, 2);
147 $beforeRevisionCount = $page->revisions->count();
149 // Delete the first revision
150 $revision = $page->revisions->get(1);
151 $resp = $this->asEditor()->delete($revision->getUrl('/delete/'));
152 $resp->assertRedirect($page->getUrl('/revisions'));
155 $afterRevisionCount = $page->revisions->count();
157 $this->assertTrue($beforeRevisionCount === ($afterRevisionCount + 1));
159 $detail = "Revision #{$revision->revision_number} (ID: {$revision->id}) for page ID {$revision->page_id}";
160 $this->assertActivityExists(ActivityType::REVISION_DELETE, null, $detail);
162 // Try to delete the latest revision
163 $beforeRevisionCount = $page->revisions->count();
164 $resp = $this->asEditor()->delete($page->currentRevision->getUrl('/delete/'));
165 $resp->assertRedirect($page->getUrl('/revisions'));
168 $afterRevisionCount = $page->revisions->count();
169 $this->assertTrue($beforeRevisionCount === $afterRevisionCount);
172 public function test_revision_limit_enforced()
174 config()->set('app.revision_limit', 2);
175 $page = $this->entities->page();
176 $this->createRevisions($page, 12);
178 $revisionCount = $page->revisions()->count();
179 $this->assertEquals(2, $revisionCount);
182 public function test_false_revision_limit_allows_many_revisions()
184 config()->set('app.revision_limit', false);
185 $page = $this->entities->page();
186 $this->createRevisions($page, 12);
188 $revisionCount = $page->revisions()->count();
189 $this->assertEquals(12, $revisionCount);
192 public function test_revision_list_shows_editor_type()
194 $page = $this->entities->page();
195 $this->createRevisions($page, 1, ['html' => 'new page html']);
197 $resp = $this->asAdmin()->get($page->refresh()->getUrl('/revisions'));
198 $this->withHtml($resp)->assertElementContains('.item-list-row > div:nth-child(2)', 'WYSIWYG)');
199 $this->withHtml($resp)->assertElementNotContains('.item-list-row > div:nth-child(2)', 'Markdown)');
201 $this->createRevisions($page, 1, ['markdown' => '# Some markdown content']);
202 $resp = $this->get($page->refresh()->getUrl('/revisions'));
203 $this->withHtml($resp)->assertElementContains('.item-list-row > div:nth-child(2)', 'Markdown)');
206 public function test_revision_changes_link_not_shown_for_oldest_revision()
208 $page = $this->entities->page();
209 $this->createRevisions($page, 3, ['html' => 'new page html']);
211 $resp = $this->asAdmin()->get($page->refresh()->getUrl('/revisions'));
212 $html = $this->withHtml($resp);
214 $html->assertElementNotExists('.item-list > .item-list-row:last-child a[href*="/changes"]');
215 $html->assertElementContains('.item-list > .item-list-row:nth-child(2)', 'Changes');
218 public function test_revision_restore_action_only_visible_with_permission()
220 $page = $this->entities->page();
221 $this->createRevisions($page, 2);
223 $viewer = $this->users->viewer();
224 $this->actingAs($viewer);
225 $respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
226 $respHtml->assertElementNotContains('.actions a', 'Restore');
227 $respHtml->assertElementNotExists('form[action$="/restore"]');
229 $this->permissions->grantUserRolePermissions($viewer, ['page-update-all']);
231 $respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
232 $respHtml->assertElementContains('.actions a', 'Restore');
233 $respHtml->assertElementExists('form[action$="/restore"]');
236 public function test_revision_delete_action_only_visible_with_permission()
238 $page = $this->entities->page();
239 $this->createRevisions($page, 2);
241 $viewer = $this->users->viewer();
242 $this->actingAs($viewer);
243 $respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
244 $respHtml->assertElementNotContains('.actions a', 'Delete');
245 $respHtml->assertElementNotExists('form[action$="/delete"]');
247 $this->permissions->grantUserRolePermissions($viewer, ['page-delete-all']);
249 $respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
250 $respHtml->assertElementContains('.actions a', 'Delete');
251 $respHtml->assertElementExists('form[action$="/delete"]');
254 protected function createRevisions(Page $page, int $times, array $attrs = [])
258 for ($i = 0; $i < $times; $i++) {
259 $data = ['name' => 'Page update' . $i, 'summary' => 'Update entry' . $i];
260 if (!isset($attrs['markdown'])) {
261 $data['html'] = '<p>My update page</p>';
263 $this->asAdmin()->put($page->getUrl(), array_merge($data, $attrs));
267 $this->actingAs($user);