]> BookStack Code Mirror - bookstack/blob - tests/Entity/ExportTest.php
Apply fixes from StyleCI
[bookstack] / tests / Entity / ExportTest.php
1 <?php
2
3 namespace Tests\Entity;
4
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Chapter;
7 use BookStack\Entities\Models\Page;
8 use Illuminate\Support\Facades\Storage;
9 use Illuminate\Support\Str;
10 use Tests\TestCase;
11
12 class ExportTest extends TestCase
13 {
14     public function test_page_text_export()
15     {
16         $page = Page::query()->first();
17         $this->asEditor();
18
19         $resp = $this->get($page->getUrl('/export/plaintext'));
20         $resp->assertStatus(200);
21         $resp->assertSee($page->name);
22         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.txt"');
23     }
24
25     public function test_page_pdf_export()
26     {
27         $page = Page::query()->first();
28         $this->asEditor();
29
30         $resp = $this->get($page->getUrl('/export/pdf'));
31         $resp->assertStatus(200);
32         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf"');
33     }
34
35     public function test_page_html_export()
36     {
37         $page = Page::query()->first();
38         $this->asEditor();
39
40         $resp = $this->get($page->getUrl('/export/html'));
41         $resp->assertStatus(200);
42         $resp->assertSee($page->name);
43         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.html"');
44     }
45
46     public function test_book_text_export()
47     {
48         $page = Page::query()->first();
49         $book = $page->book;
50         $this->asEditor();
51
52         $resp = $this->get($book->getUrl('/export/plaintext'));
53         $resp->assertStatus(200);
54         $resp->assertSee($book->name);
55         $resp->assertSee($page->name);
56         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.txt"');
57     }
58
59     public function test_book_pdf_export()
60     {
61         $page = Page::query()->first();
62         $book = $page->book;
63         $this->asEditor();
64
65         $resp = $this->get($book->getUrl('/export/pdf'));
66         $resp->assertStatus(200);
67         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.pdf"');
68     }
69
70     public function test_book_html_export()
71     {
72         $page = Page::query()->first();
73         $book = $page->book;
74         $this->asEditor();
75
76         $resp = $this->get($book->getUrl('/export/html'));
77         $resp->assertStatus(200);
78         $resp->assertSee($book->name);
79         $resp->assertSee($page->name);
80         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html"');
81     }
82
83     public function test_book_html_export_shows_chapter_descriptions()
84     {
85         $chapterDesc = 'My custom test chapter description ' . Str::random(12);
86         $chapter = Chapter::query()->first();
87         $chapter->description = $chapterDesc;
88         $chapter->save();
89
90         $book = $chapter->book;
91         $this->asEditor();
92
93         $resp = $this->get($book->getUrl('/export/html'));
94         $resp->assertSee($chapterDesc);
95     }
96
97     public function test_chapter_text_export()
98     {
99         $chapter = Chapter::query()->first();
100         $page = $chapter->pages[0];
101         $this->asEditor();
102
103         $resp = $this->get($chapter->getUrl('/export/plaintext'));
104         $resp->assertStatus(200);
105         $resp->assertSee($chapter->name);
106         $resp->assertSee($page->name);
107         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt"');
108     }
109
110     public function test_chapter_pdf_export()
111     {
112         $chapter = Chapter::query()->first();
113         $this->asEditor();
114
115         $resp = $this->get($chapter->getUrl('/export/pdf'));
116         $resp->assertStatus(200);
117         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf"');
118     }
119
120     public function test_chapter_html_export()
121     {
122         $chapter = Chapter::query()->first();
123         $page = $chapter->pages[0];
124         $this->asEditor();
125
126         $resp = $this->get($chapter->getUrl('/export/html'));
127         $resp->assertStatus(200);
128         $resp->assertSee($chapter->name);
129         $resp->assertSee($page->name);
130         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html"');
131     }
132
133     public function test_page_html_export_contains_custom_head_if_set()
134     {
135         $page = Page::query()->first();
136
137         $customHeadContent = '<style>p{color: red;}</style>';
138         $this->setSettings(['app-custom-head' => $customHeadContent]);
139
140         $resp = $this->asEditor()->get($page->getUrl('/export/html'));
141         $resp->assertSee($customHeadContent);
142     }
143
144     public function test_page_html_export_does_not_break_with_only_comments_in_custom_head()
145     {
146         $page = Page::query()->first();
147
148         $customHeadContent = '<!-- A comment -->';
149         $this->setSettings(['app-custom-head' => $customHeadContent]);
150
151         $resp = $this->asEditor()->get($page->getUrl('/export/html'));
152         $resp->assertStatus(200);
153         $resp->assertSee($customHeadContent);
154     }
155
156     public function test_page_html_export_use_absolute_dates()
157     {
158         $page = Page::query()->first();
159
160         $resp = $this->asEditor()->get($page->getUrl('/export/html'));
161         $resp->assertSee($page->created_at->formatLocalized('%e %B %Y %H:%M:%S'));
162         $resp->assertDontSee($page->created_at->diffForHumans());
163         $resp->assertSee($page->updated_at->formatLocalized('%e %B %Y %H:%M:%S'));
164         $resp->assertDontSee($page->updated_at->diffForHumans());
165     }
166
167     public function test_page_export_does_not_include_user_or_revision_links()
168     {
169         $page = Page::query()->first();
170
171         $resp = $this->asEditor()->get($page->getUrl('/export/html'));
172         $resp->assertDontSee($page->getUrl('/revisions'));
173         $resp->assertDontSee($page->createdBy->getProfileUrl());
174         $resp->assertSee($page->createdBy->name);
175     }
176
177     public function test_page_export_sets_right_data_type_for_svg_embeds()
178     {
179         $page = Page::query()->first();
180         Storage::disk('local')->makeDirectory('uploads/images/gallery');
181         Storage::disk('local')->put('uploads/images/gallery/svg_test.svg', '<svg></svg>');
182         $page->html = '<img src="http://localhost/uploads/images/gallery/svg_test.svg">';
183         $page->save();
184
185         $this->asEditor();
186         $resp = $this->get($page->getUrl('/export/html'));
187         Storage::disk('local')->delete('uploads/images/gallery/svg_test.svg');
188
189         $resp->assertStatus(200);
190         $resp->assertSee('<img src="data:image/svg+xml;base64');
191     }
192
193     public function test_page_image_containment_works_on_multiple_images_within_a_single_line()
194     {
195         $page = Page::query()->first();
196         Storage::disk('local')->makeDirectory('uploads/images/gallery');
197         Storage::disk('local')->put('uploads/images/gallery/svg_test.svg', '<svg></svg>');
198         Storage::disk('local')->put('uploads/images/gallery/svg_test2.svg', '<svg></svg>');
199         $page->html = '<img src="http://localhost/uploads/images/gallery/svg_test.svg" class="a"><img src="http://localhost/uploads/images/gallery/svg_test2.svg" class="b">';
200         $page->save();
201
202         $resp = $this->asEditor()->get($page->getUrl('/export/html'));
203         Storage::disk('local')->delete('uploads/images/gallery/svg_test.svg');
204         Storage::disk('local')->delete('uploads/images/gallery/svg_test2.svg');
205
206         $resp->assertDontSee('http://localhost/uploads/images/gallery/svg_test');
207     }
208
209     public function test_page_export_contained_html_image_fetches_only_run_when_url_points_to_image_upload_folder()
210     {
211         $page = Page::query()->first();
212         $page->html = '<img src="http://localhost/uploads/images/gallery/svg_test.svg"/>'
213             . '<img src="http://localhost/uploads/svg_test.svg"/>'
214             . '<img src="/uploads/svg_test.svg"/>';
215         $storageDisk = Storage::disk('local');
216         $storageDisk->makeDirectory('uploads/images/gallery');
217         $storageDisk->put('uploads/images/gallery/svg_test.svg', '<svg>good</svg>');
218         $storageDisk->put('uploads/svg_test.svg', '<svg>bad</svg>');
219         $page->save();
220
221         $resp = $this->asEditor()->get($page->getUrl('/export/html'));
222
223         $storageDisk->delete('uploads/images/gallery/svg_test.svg');
224         $storageDisk->delete('uploads/svg_test.svg');
225
226         $resp->assertDontSee('http://localhost/uploads/images/gallery/svg_test.svg');
227         $resp->assertSee('http://localhost/uploads/svg_test.svg');
228         $resp->assertSee('src="/uploads/svg_test.svg"');
229     }
230
231     public function test_exports_removes_scripts_from_custom_head()
232     {
233         $entities = [
234             Page::query()->first(), Chapter::query()->first(), Book::query()->first(),
235         ];
236         setting()->put('app-custom-head', '<script>window.donkey = "cat";</script><style>.my-test-class { color: red; }</style>');
237
238         foreach ($entities as $entity) {
239             $resp = $this->asEditor()->get($entity->getUrl('/export/html'));
240             $resp->assertDontSee('window.donkey');
241             $resp->assertDontSee('script');
242             $resp->assertSee('.my-test-class { color: red; }');
243         }
244     }
245
246     public function test_page_export_with_deleted_creator_and_updater()
247     {
248         $user = $this->getViewer(['name' => 'ExportWizardTheFifth']);
249         $page = Page::query()->first();
250         $page->created_by = $user->id;
251         $page->updated_by = $user->id;
252         $page->save();
253
254         $resp = $this->asEditor()->get($page->getUrl('/export/html'));
255         $resp->assertSee('ExportWizardTheFifth');
256
257         $user->delete();
258         $resp = $this->get($page->getUrl('/export/html'));
259         $resp->assertStatus(200);
260         $resp->assertDontSee('ExportWizardTheFifth');
261     }
262
263     public function test_page_markdown_export()
264     {
265         $page = Page::query()->first();
266
267         $resp = $this->asEditor()->get($page->getUrl('/export/markdown'));
268         $resp->assertStatus(200);
269         $resp->assertSee($page->name);
270         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.md"');
271     }
272
273     public function test_page_markdown_export_uses_existing_markdown_if_apparent()
274     {
275         $page = Page::query()->first()->forceFill([
276             'markdown' => '# A header',
277             'html'     => '<h1>Dogcat</h1>',
278         ]);
279         $page->save();
280
281         $resp = $this->asEditor()->get($page->getUrl('/export/markdown'));
282         $resp->assertSee('A header');
283         $resp->assertDontSee('Dogcat');
284     }
285
286     public function test_page_markdown_export_converts_html_where_no_markdown()
287     {
288         $page = Page::query()->first()->forceFill([
289             'markdown' => '',
290             'html'     => '<h1>Dogcat</h1><p>Some <strong>bold</strong> text</p>',
291         ]);
292         $page->save();
293
294         $resp = $this->asEditor()->get($page->getUrl('/export/markdown'));
295         $resp->assertSee("# Dogcat\n\nSome **bold** text");
296     }
297
298     public function test_page_markdown_export_does_not_convert_callouts()
299     {
300         $page = Page::query()->first()->forceFill([
301             'markdown' => '',
302             'html'     => '<h1>Dogcat</h1><p class="callout info">Some callout text</p><p>Another line</p>',
303         ]);
304         $page->save();
305
306         $resp = $this->asEditor()->get($page->getUrl('/export/markdown'));
307         $resp->assertSee("# Dogcat\n\n<p class=\"callout info\">Some callout text</p>\n\nAnother line");
308     }
309
310     public function test_page_markdown_export_handles_bookstacks_wysiwyg_codeblock_format()
311     {
312         $page = Page::query()->first()->forceFill([
313             'markdown' => '',
314             'html'     => '<h1>Dogcat</h1>' . "\r\n" . '<pre id="bkmrk-var-a-%3D-%27cat%27%3B"><code class="language-JavaScript">var a = \'cat\';</code></pre><p>Another line</p>',
315         ]);
316         $page->save();
317
318         $resp = $this->asEditor()->get($page->getUrl('/export/markdown'));
319         $resp->assertSee("# Dogcat\n\n```JavaScript\nvar a = 'cat';\n```\n\nAnother line");
320     }
321
322     public function test_chapter_markdown_export()
323     {
324         $chapter = Chapter::query()->first();
325         $page = $chapter->pages()->first();
326         $resp = $this->asEditor()->get($chapter->getUrl('/export/markdown'));
327
328         $resp->assertSee('# ' . $chapter->name);
329         $resp->assertSee('# ' . $page->name);
330     }
331
332     public function test_book_markdown_export()
333     {
334         $book = Book::query()->whereHas('pages')->whereHas('chapters')->first();
335         $chapter = $book->chapters()->first();
336         $page = $chapter->pages()->first();
337         $resp = $this->asEditor()->get($book->getUrl('/export/markdown'));
338
339         $resp->assertSee('# ' . $book->name);
340         $resp->assertSee('# ' . $chapter->name);
341         $resp->assertSee('# ' . $page->name);
342     }
343 }