From e4e3b25c22e1be9f33d96b6d77d86b46b0c467d2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 16 Jan 2017 21:24:48 +0000 Subject: [PATCH 1/4] Started page transclusion system --- app/Http/Controllers/PageController.php | 8 +++- app/Repos/EntityRepo.php | 39 ++++++++++++++++++++ resources/views/pages/page-display.blade.php | 2 +- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 6ed9fc30c..276665e52 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -156,15 +156,19 @@ class PageController extends Controller return redirect($page->getUrl()); } + $this->checkOwnablePermission('page-view', $page); + $pageContent = $this->entityRepo->renderPage($page); $sidebarTree = $this->entityRepo->getBookChildren($page->book); $pageNav = $this->entityRepo->getPageNav($page); Views::add($page); $this->setPageTitle($page->getShortName()); - return view('pages/show', ['page' => $page, 'book' => $page->book, - 'current' => $page, 'sidebarTree' => $sidebarTree, 'pageNav' => $pageNav]); + return view('pages/show', [ + 'page' => $page,'book' => $page->book, + 'current' => $page, 'sidebarTree' => $sidebarTree, + 'pageNav' => $pageNav, 'pageContent' => $pageContent]); } /** diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php index 6eaf0169a..312e3536e 100644 --- a/app/Repos/EntityRepo.php +++ b/app/Repos/EntityRepo.php @@ -13,6 +13,7 @@ use Carbon\Carbon; use DOMDocument; use DOMXPath; use Illuminate\Support\Collection; +use Symfony\Component\DomCrawler\Crawler; class EntityRepo { @@ -796,6 +797,44 @@ class EntityRepo return $html; } + + /** + * Render the page for viewing, Parsing and performing features such as page transclusion. + * @param Page $page + * @return mixed|string + */ + public function renderPage(Page $page) + { + libxml_use_internal_errors(true); + $doc = new DOMDocument(); + $doc->loadHTML(mb_convert_encoding(''.$page->html.'', 'HTML-ENTITIES', 'UTF-8')); + $xpath = new DOMXpath($doc); + + $bsElems = $xpath->query('body/div[@bs-embed-page]'); + if (is_null($bsElems)) return $page->html; + foreach ($bsElems as $bsElem) { + $pageId = intval($bsElem->getAttribute('bs-embed-page')); + $embeddedPage = $this->getById('page', $pageId); + if ($embeddedPage !== null) { + $innerPage = $doc->createDocumentFragment(); + $innerPage->appendXML($embeddedPage->html); + // Empty div then append in child content + foreach ($bsElem->childNodes as $child) { + $bsElem->removeChild($child); + } + $bsElem->appendChild($innerPage); + } + } + + $body = $doc->getElementsByTagName('body')->item(0); + $html = ''; + foreach ($body->childNodes as $node) { + $html .= $doc->saveHTML($node); + } + + return $html; + } + /** * Get a new draft page instance. * @param Book $book diff --git a/resources/views/pages/page-display.blade.php b/resources/views/pages/page-display.blade.php index fb6ca3045..6eb927687 100644 --- a/resources/views/pages/page-display.blade.php +++ b/resources/views/pages/page-display.blade.php @@ -7,6 +7,6 @@ @if (isset($diff) && $diff) {!! $diff !!} @else - {!! $page->html !!} + {!! isset($pageContent) ? $pageContent : $page->html !!} @endif \ No newline at end of file From a4f6bc63f007e159068a497d558a189829dcd6ab Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 21 Jan 2017 13:53:00 +0000 Subject: [PATCH 2/4] Updated page include logic to use blade-style tags It will also snippets of a page if and id is provided in a tag --- app/Http/Controllers/PageController.php | 4 +- app/Http/Middleware/Localization.php | 4 +- app/Repos/EntityRepo.php | 67 +++++++++++++----------- app/Services/ExportService.php | 33 +++++++++--- resources/assets/sass/_blocks.scss | 3 -- resources/assets/sass/_components.scss | 3 -- resources/assets/sass/export-styles.scss | 2 +- 7 files changed, 66 insertions(+), 50 deletions(-) diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 276665e52..4ed10d61e 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -156,12 +156,11 @@ class PageController extends Controller return redirect($page->getUrl()); } - $this->checkOwnablePermission('page-view', $page); $pageContent = $this->entityRepo->renderPage($page); $sidebarTree = $this->entityRepo->getBookChildren($page->book); - $pageNav = $this->entityRepo->getPageNav($page); + $pageNav = $this->entityRepo->getPageNav($pageContent); Views::add($page); $this->setPageTitle($page->getShortName()); @@ -434,6 +433,7 @@ class PageController extends Controller { $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug); $pdfContent = $this->exportService->pageToPdf($page); +// return $pdfContent; return response()->make($pdfContent, 200, [ 'Content-Type' => 'application/octet-stream', 'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.pdf' diff --git a/app/Http/Middleware/Localization.php b/app/Http/Middleware/Localization.php index 3624e0e01..31cb5d9a2 100644 --- a/app/Http/Middleware/Localization.php +++ b/app/Http/Middleware/Localization.php @@ -1,6 +1,4 @@ -entityQuery($type, $allowDrafts)->findOrFail($id); + return $this->entityQuery($type, $allowDrafts)->find($id); } /** @@ -805,34 +804,42 @@ class EntityRepo */ public function renderPage(Page $page) { - libxml_use_internal_errors(true); - $doc = new DOMDocument(); - $doc->loadHTML(mb_convert_encoding(''.$page->html.'', 'HTML-ENTITIES', 'UTF-8')); - $xpath = new DOMXpath($doc); + $content = $page->html; + $matches = []; + preg_match_all("/{{@\s?([0-9].*?)}}/", $content, $matches); + if (count($matches[0]) === 0) return $content; - $bsElems = $xpath->query('body/div[@bs-embed-page]'); - if (is_null($bsElems)) return $page->html; - foreach ($bsElems as $bsElem) { - $pageId = intval($bsElem->getAttribute('bs-embed-page')); - $embeddedPage = $this->getById('page', $pageId); - if ($embeddedPage !== null) { - $innerPage = $doc->createDocumentFragment(); - $innerPage->appendXML($embeddedPage->html); - // Empty div then append in child content - foreach ($bsElem->childNodes as $child) { - $bsElem->removeChild($child); - } - $bsElem->appendChild($innerPage); + foreach ($matches[1] as $index => $includeId) { + $splitInclude = explode('#', $includeId, 2); + $pageId = intval($splitInclude[0]); + if (is_nan($pageId)) continue; + + $page = $this->getById('page', $pageId); + if ($page === null) { + $content = str_replace($matches[0][$index], '', $content); + continue; } + + if (count($splitInclude) === 1) { + $content = str_replace($matches[0][$index], $page->html, $content); + continue; + } + + $doc = new DOMDocument(); + $doc->loadHTML(mb_convert_encoding(''.$page->html.'', 'HTML-ENTITIES', 'UTF-8')); + $matchingElem = $doc->getElementById($splitInclude[1]); + if ($matchingElem === null) { + $content = str_replace($matches[0][$index], '', $content); + continue; + } + $innerContent = ''; + foreach ($matchingElem->childNodes as $childNode) { + $innerContent .= $doc->saveHTML($childNode); + } + $content = str_replace($matches[0][$index], trim($innerContent), $content); } - $body = $doc->getElementsByTagName('body')->item(0); - $html = ''; - foreach ($body->childNodes as $node) { - $html .= $doc->saveHTML($node); - } - - return $html; + return $content; } /** @@ -874,15 +881,15 @@ class EntityRepo /** * Parse the headers on the page to get a navigation menu - * @param Page $page + * @param String $pageContent * @return array */ - public function getPageNav(Page $page) + public function getPageNav($pageContent) { - if ($page->html == '') return []; + if ($pageContent == '') return []; libxml_use_internal_errors(true); $doc = new DOMDocument(); - $doc->loadHTML(mb_convert_encoding($page->html, 'HTML-ENTITIES', 'UTF-8')); + $doc->loadHTML(mb_convert_encoding($pageContent, 'HTML-ENTITIES', 'UTF-8')); $xPath = new DOMXPath($doc); $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6"); diff --git a/app/Services/ExportService.php b/app/Services/ExportService.php index 50ba75c17..880bc54ad 100644 --- a/app/Services/ExportService.php +++ b/app/Services/ExportService.php @@ -1,10 +1,22 @@ entityRepo = $entityRepo; + } + /** * Convert a page to a self-contained HTML file. * Includes required CSS & image content. Images are base64 encoded into the HTML. @@ -14,7 +26,7 @@ class ExportService public function pageToContainedHtml(Page $page) { $cssContent = file_get_contents(public_path('/css/export-styles.css')); - $pageHtml = view('pages/export', ['page' => $page, 'css' => $cssContent])->render(); + $pageHtml = view('pages/export', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render(); return $this->containHtml($pageHtml); } @@ -26,7 +38,8 @@ class ExportService public function pageToPdf(Page $page) { $cssContent = file_get_contents(public_path('/css/export-styles.css')); - $pageHtml = view('pages/pdf', ['page' => $page, 'css' => $cssContent])->render(); + $pageHtml = view('pages/pdf', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render(); +// return $pageHtml; $useWKHTML = config('snappy.pdf.binary') !== false; $containedHtml = $this->containHtml($pageHtml); if ($useWKHTML) { @@ -59,9 +72,13 @@ class ExportService $pathString = $srcString; } if ($isLocal && !file_exists($pathString)) continue; - $imageContent = file_get_contents($pathString); - $imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent); - $newImageString = str_replace($srcString, $imageEncoded, $oldImgString); + try { + $imageContent = file_get_contents($pathString); + $imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent); + $newImageString = str_replace($srcString, $imageEncoded, $oldImgString); + } catch (\ErrorException $e) { + $newImageString = ''; + } $htmlContent = str_replace($oldImgString, $newImageString, $htmlContent); } } @@ -88,14 +105,14 @@ class ExportService /** * Converts the page contents into simple plain text. - * This method filters any bad looking content to - * provide a nice final output. + * This method filters any bad looking content to provide a nice final output. * @param Page $page * @return mixed */ public function pageToPlainText(Page $page) { - $text = $page->text; + $html = $this->entityRepo->renderPage($page); + $text = strip_tags($html); // Replace multiple spaces with single spaces $text = preg_replace('/\ {2,}/', ' ', $text); // Reduce multiple horrid whitespace characters. diff --git a/resources/assets/sass/_blocks.scss b/resources/assets/sass/_blocks.scss index 7eb595d36..a2023aa37 100644 --- a/resources/assets/sass/_blocks.scss +++ b/resources/assets/sass/_blocks.scss @@ -136,9 +136,6 @@ background-color: #EEE; padding: $-s; display: block; - > * { - display: inline-block; - } &:before { font-family: 'Material-Design-Iconic-Font'; padding-right: $-s; diff --git a/resources/assets/sass/_components.scss b/resources/assets/sass/_components.scss index c8fd8bcfa..5328057d9 100644 --- a/resources/assets/sass/_components.scss +++ b/resources/assets/sass/_components.scss @@ -70,9 +70,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { #entity-selector-wrap .popup-body .form-group { margin: 0; } -//body.ie #entity-selector-wrap .popup-body .form-group { -// min-height: 60vh; -//} .image-manager-body { min-height: 70vh; diff --git a/resources/assets/sass/export-styles.scss b/resources/assets/sass/export-styles.scss index 60450f3e2..7e1ab4e9e 100644 --- a/resources/assets/sass/export-styles.scss +++ b/resources/assets/sass/export-styles.scss @@ -1,4 +1,4 @@ -//@import "reset"; +@import "reset"; @import "variables"; @import "mixins"; @import "html"; From 56d58ad8e5fe3a7856046dd71154b2312ffcb2d5 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 21 Jan 2017 14:56:47 +0000 Subject: [PATCH 3/4] Updated pointer to be able to show includes Also fixed pointer copying on flash-blocked browsers --- package.json | 6 ++-- public/ZeroClipboard.swf | Bin 6580 -> 0 bytes resources/assets/js/pages/page-show.js | 44 +++++++++++++++++-------- resources/assets/sass/_buttons.scss | 3 +- resources/assets/sass/_pages.scss | 4 +++ resources/views/pages/show.blade.php | 6 ++-- 6 files changed, 43 insertions(+), 20 deletions(-) delete mode 100644 public/ZeroClipboard.swf diff --git a/package.json b/package.json index ec5911b93..b0805c918 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "laravel-elixir": "^6.0.0-11", "laravel-elixir-browserify-official": "^0.1.3", "marked": "^0.3.5", - "moment": "^2.12.0", - "zeroclipboard": "^2.2.0" + "moment": "^2.12.0" + }, + "dependencies": { + "clipboard": "^1.5.16" } } diff --git a/public/ZeroClipboard.swf b/public/ZeroClipboard.swf deleted file mode 100644 index 8bad6a3e34f1b0b055da3ee8e506fe627cf75987..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6580 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 diff --git a/resources/assets/js/pages/page-show.js b/resources/assets/js/pages/page-show.js index 4f8f6e0f1..0f45e1987 100644 --- a/resources/assets/js/pages/page-show.js +++ b/resources/assets/js/pages/page-show.js @@ -1,13 +1,16 @@ "use strict"; // Configure ZeroClipboard -import ZeroClipBoard from "zeroclipboard"; +import Clipboard from "clipboard"; export default window.setupPageShow = function (pageId) { // Set up pointer let $pointer = $('#pointer').detach(); + let pointerShowing = false; let $pointerInner = $pointer.children('div.pointer').first(); let isSelection = false; + let pointerModeLink = true; + let pointerSectionId = ''; // Select all contents on input click $pointer.on('click', 'input', function (e) { @@ -15,19 +18,34 @@ export default window.setupPageShow = function (pageId) { e.stopPropagation(); }); - // Set up copy-to-clipboard - ZeroClipBoard.config({ - swfPath: window.baseUrl('/ZeroClipboard.swf') + // Pointer mode toggle + $pointer.on('click', 'span.icon', event => { + let $icon = $(event.currentTarget); + pointerModeLink = !pointerModeLink; + $icon.html(pointerModeLink ? '' : ''); + updatePointerContent(); }); - new ZeroClipBoard($pointer.find('button').first()[0]); + + // Set up clipboard + let clipboard = new Clipboard('#pointer button'); // Hide pointer when clicking away - $(document.body).find('*').on('click focus', function (e) { - if (!isSelection) { - $pointer.detach(); - } + $(document.body).find('*').on('click focus', event => { + if (!pointerShowing || isSelection) return; + let target = $(event.target); + if (target.is('.zmdi') || $(event.target).closest('#pointer').length === 1) return; + + $pointer.detach(); + pointerShowing = false; }); + function updatePointerContent() { + let inputText = pointerModeLink ? window.baseUrl(`/link/${pageId}#${pointerSectionId}`) : `{{@${pageId}#${pointerSectionId}}}`; + if (pointerModeLink && inputText.indexOf('http') !== 0) inputText = window.location.protocol + "//" + window.location.host + inputText; + + $pointer.find('input').val(inputText); + } + // Show pointer when selecting a single block of tagged content $('.page-content [id^="bkmrk"]').on('mouseup keyup', function (e) { e.stopPropagation(); @@ -36,12 +54,12 @@ export default window.setupPageShow = function (pageId) { // Show pointer and set link let $elem = $(this); - let link = window.baseUrl('/link/' + pageId + '#' + $elem.attr('id')); - if (link.indexOf('http') !== 0) link = window.location.protocol + "//" + window.location.host + link; - $pointer.find('input').val(link); - $pointer.find('button').first().attr('data-clipboard-text', link); + pointerSectionId = $elem.attr('id'); + updatePointerContent(); + $elem.before($pointer); $pointer.show(); + pointerShowing = true; // Set pointer to sit near mouse-up position let pointerLeftOffset = (e.pageX - $elem.offset().left - ($pointerInner.width() / 2)); diff --git a/resources/assets/sass/_buttons.scss b/resources/assets/sass/_buttons.scss index 5de889673..791a5bb72 100644 --- a/resources/assets/sass/_buttons.scss +++ b/resources/assets/sass/_buttons.scss @@ -108,5 +108,4 @@ $button-border-radius: 2px; cursor: default; box-shadow: none; } -} - +} \ No newline at end of file diff --git a/resources/assets/sass/_pages.scss b/resources/assets/sass/_pages.scss index 0052a3319..e5334c69c 100755 --- a/resources/assets/sass/_pages.scss +++ b/resources/assets/sass/_pages.scss @@ -138,6 +138,10 @@ font-size: 18px; padding-top: 4px; } + span.icon { + cursor: pointer; + user-select: none; + } .button { line-height: 1; margin: 0 0 0 -4px; diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index a734b1b95..fd6cebf41 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -53,9 +53,9 @@
- - - + + +
From 2d4034f3b7a0828a47859f0fc313dfb0b506e622 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 21 Jan 2017 16:16:27 +0000 Subject: [PATCH 4/4] Added transclusion tests and fixed other tests --- app/Services/PermissionService.php | 5 +- tests/Entity/PageContentTest.php | 33 +++++++++++++ tests/Entity/{TagTests.php => TagTest.php} | 55 ++-------------------- tests/TestCase.php | 2 +- 4 files changed, 42 insertions(+), 53 deletions(-) create mode 100644 tests/Entity/PageContentTest.php rename tests/Entity/{TagTests.php => TagTest.php} (59%) diff --git a/app/Services/PermissionService.php b/app/Services/PermissionService.php index 6363541ef..65fe0f33e 100644 --- a/app/Services/PermissionService.php +++ b/app/Services/PermissionService.php @@ -157,7 +157,7 @@ class PermissionService */ public function buildJointPermissionsForEntity(Entity $entity) { - $roles = $this->role->with('jointPermissions')->get(); + $roles = $this->role->get(); $entities = collect([$entity]); if ($entity->isA('book')) { @@ -177,7 +177,7 @@ class PermissionService */ public function buildJointPermissionsForEntities(Collection $entities) { - $roles = $this->role->with('jointPermissions')->get(); + $roles = $this->role->get(); $this->deleteManyJointPermissionsForEntities($entities); $this->createManyJointPermissions($entities, $roles); } @@ -564,6 +564,7 @@ class PermissionService }); }); }); + $this->clean(); return $q; } diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php new file mode 100644 index 000000000..deeacb593 --- /dev/null +++ b/tests/Entity/PageContentTest.php @@ -0,0 +1,33 @@ +get(2); + + $secondPage->html = "

Hello, This is a test

This is a second block of content

"; + $secondPage->save(); + + $this->asAdmin()->visit($page->getUrl()) + ->dontSee('Hello, This is a test'); + + $originalHtml = $page->html; + $page->html .= "{{@{$secondPage->id}}}"; + $page->save(); + + $this->asAdmin()->visit($page->getUrl()) + ->see('Hello, This is a test') + ->see('This is a second block of content'); + + $page->html = $originalHtml . " Well {{@{$secondPage->id}#section2}}"; + $page->save(); + + $this->asAdmin()->visit($page->getUrl()) + ->dontSee('Hello, This is a test') + ->see('Well This is a second block of content'); + } + +} diff --git a/tests/Entity/TagTests.php b/tests/Entity/TagTest.php similarity index 59% rename from tests/Entity/TagTests.php rename to tests/Entity/TagTest.php index 0520e1a00..2d42ffa4b 100644 --- a/tests/Entity/TagTests.php +++ b/tests/Entity/TagTest.php @@ -4,7 +4,7 @@ use BookStack\Tag; use BookStack\Page; use BookStack\Services\PermissionService; -class TagTests extends \TestCase +class TagTest extends \TestCase { protected $defaultTagCount = 20; @@ -86,61 +86,16 @@ class TagTests extends \TestCase $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'color'])); $page = $this->getPageWithTags($attrs); - $this->asAdmin()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']); - $this->asEditor()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']); + $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']); + $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']); // Set restricted permission the page $page->restricted = true; $page->save(); $permissionService->buildJointPermissionsForEntity($page); - $this->asAdmin()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']); - $this->asEditor()->get('/ajax/tags/suggest?search=co')->seeJsonEquals([]); - } - - public function test_entity_tag_updating() - { - $page = $this->getPageWithTags(); - - $testJsonData = [ - ['name' => 'color', 'value' => 'red'], - ['name' => 'color', 'value' => ' blue '], - ['name' => 'city', 'value' => 'London '], - ['name' => 'country', 'value' => ' England'], - ]; - $testResponseJsonData = [ - ['name' => 'color', 'value' => 'red'], - ['name' => 'color', 'value' => 'blue'], - ['name' => 'city', 'value' => 'London'], - ['name' => 'country', 'value' => 'England'], - ]; - - // Do update request - $this->asAdmin()->json("POST", "/ajax/tags/update/page/" . $page->id, ['tags' => $testJsonData]); - $updateData = json_decode($this->response->getContent()); - // Check data is correct - $testDataCorrect = true; - foreach ($updateData->tags as $data) { - $testItem = ['name' => $data->name, 'value' => $data->value]; - if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false; - } - $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s"; - $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($updateData))); - $this->assertTrue(isset($updateData->message), "No message returned in tag update response"); - - // Do get request - $this->asAdmin()->get("/ajax/tags/get/page/" . $page->id); - $getResponseData = json_decode($this->response->getContent()); - // Check counts - $this->assertTrue(count($getResponseData) === count($testJsonData), "The received tag count is incorrect"); - // Check data is correct - $testDataCorrect = true; - foreach ($getResponseData as $data) { - $testItem = ['name' => $data->name, 'value' => $data->value]; - if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false; - } - $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s"; - $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($getResponseData))); + $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']); + $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals([]); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index d3620eae0..4f3df4b90 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -60,7 +60,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase */ public function asEditor() { - if($this->editor === null) { + if ($this->editor === null) { $this->editor = $this->getEditor(); } return $this->actingAs($this->editor);