From b866dee0cf01a8576e5fce5bb2bde54107aa9b62 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Sep 2025 18:19:16 +0100 Subject: [PATCH] Entities: Updated repos to act on refreshed clones Changes to core entity models are now done on clones to ensure clean state before save, and those clones are returned back if changes are needed after that action. --- .../Controllers/ChapterController.php | 2 +- app/Entities/Models/Book.php | 3 +++ app/Entities/Models/BookChild.php | 23 +++++++++--------- app/Entities/Models/Bookshelf.php | 3 +++ app/Entities/Models/Entity.php | 2 +- app/Entities/Models/EntityContainerData.php | 21 ++++++++++++++-- app/Entities/Repos/BaseRepo.php | 24 +++++++++++++++---- app/Entities/Repos/BookRepo.php | 6 ++--- app/Entities/Repos/BookshelfRepo.php | 5 ++-- app/Entities/Repos/ChapterRepo.php | 7 +++--- app/Entities/Repos/PageRepo.php | 11 ++++++--- app/Entities/Tools/BookContents.php | 9 +++---- app/Entities/Tools/HierarchyTransformer.php | 1 + app/Exports/ExportFormatter.php | 4 ++-- .../ZipExports/Models/ZipExportBook.php | 2 +- .../ZipExports/Models/ZipExportChapter.php | 2 +- app/References/ReferenceUpdater.php | 2 +- app/Sorting/BookSorter.php | 2 +- resources/views/books/parts/form.blade.php | 2 +- .../views/books/parts/list-item.blade.php | 2 +- resources/views/books/show.blade.php | 4 ++-- resources/views/chapters/show.blade.php | 4 ++-- resources/views/entities/grid-item.blade.php | 2 +- resources/views/exports/book.blade.php | 2 +- resources/views/exports/chapter.blade.php | 2 +- .../exports/parts/chapter-item.blade.php | 2 +- .../form/description-html-input.blade.php | 2 +- resources/views/shelves/parts/form.blade.php | 2 +- resources/views/shelves/show.blade.php | 6 ++--- tests/Exports/ZipExportTest.php | 4 ++-- 30 files changed, 100 insertions(+), 63 deletions(-) diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 9335e0a70..a1af29de2 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -130,7 +130,7 @@ class ChapterController extends Controller $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission(Permission::ChapterUpdate, $chapter); - $this->chapterRepo->update($chapter, $validated); + $chapter = $this->chapterRepo->update($chapter, $validated); return redirect($chapter->getUrl()); } diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index 34b8a81bb..b49cbb673 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -59,6 +59,9 @@ class Book extends Entity } } + // TODO - Still handle cover as relation through containerData (since it's used in code) + // TODO - Remove above since we can access that via containerData + /** * Get the Page that is used as default template for newly created pages within this Book. */ diff --git a/app/Entities/Models/BookChild.php b/app/Entities/Models/BookChild.php index ad54fb926..90c812f76 100644 --- a/app/Entities/Models/BookChild.php +++ b/app/Entities/Models/BookChild.php @@ -3,7 +3,6 @@ namespace BookStack\Entities\Models; use BookStack\References\ReferenceUpdater; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** @@ -27,25 +26,25 @@ abstract class BookChild extends Entity /** * Change the book that this entity belongs to. */ - public function changeBook(int $newBookId): Entity + public function changeBook(int $newBookId): self { - $oldUrl = $this->getUrl(); - $this->book_id = $newBookId; - $this->refreshSlug(); - $this->save(); - $this->refresh(); + $altered = $this->clone()->refresh(); + $oldUrl = $altered->getUrl(); + $altered->book_id = $newBookId; + $altered->refreshSlug(); + $altered->save(); - if ($oldUrl !== $this->getUrl()) { - app()->make(ReferenceUpdater::class)->updateEntityReferences($this, $oldUrl); + if ($oldUrl !== $altered->getUrl()) { + app()->make(ReferenceUpdater::class)->updateEntityReferences($altered, $oldUrl); } // Update all child pages if a chapter - if ($this instanceof Chapter) { - foreach ($this->pages()->withTrashed()->get() as $page) { + if ($altered instanceof Chapter) { + foreach ($altered->pages()->withTrashed()->get() as $page) { $page->changeBook($newBookId); } } - return $this; + return $altered; } } diff --git a/app/Entities/Models/Bookshelf.php b/app/Entities/Models/Bookshelf.php index da4356647..4f4bc43c3 100644 --- a/app/Entities/Models/Bookshelf.php +++ b/app/Entities/Models/Bookshelf.php @@ -59,6 +59,9 @@ class Bookshelf extends Entity } } + // TODO - Still handle cover as relation through containerData (since it's used in code) + // TODO - Remove above since we can access that via containerData + /** * Check if this shelf contains the given book. */ diff --git a/app/Entities/Models/Entity.php b/app/Entities/Models/Entity.php index 5f372d24b..dee42c1b7 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -101,7 +101,7 @@ abstract class Entity extends Model implements } /** - * Get the container-specific data for this page. + * Get the container-specific data for this item. */ public function containerData(): HasOne { diff --git a/app/Entities/Models/EntityContainerData.php b/app/Entities/Models/EntityContainerData.php index 57ee02b9e..e0d30f56f 100644 --- a/app/Entities/Models/EntityContainerData.php +++ b/app/Entities/Models/EntityContainerData.php @@ -4,6 +4,7 @@ namespace BookStack\Entities\Models; use BookStack\Uploads\Image; use BookStack\Util\HtmlContentFilter; +use Exception; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasOne; @@ -27,6 +28,22 @@ class EntityContainerData extends Model return $this->hasOne(Image::class, 'image_id'); } + /** + * Returns a shelf cover image URL, if cover not exists return default cover image. + */ + public function getCoverUrl(int $width = 440, int $height = 250, string|null $default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='): string|null + { + if (!$this->image_id) { + return $default; + } + + try { + return $this->cover->getThumb($width, $height, false) ?? $default; + } catch (Exception $err) { + return $default; + } + } + /** * Check if this data supports having a default template assigned. */ @@ -46,7 +63,7 @@ class EntityContainerData extends Model /** * Get the description as a cleaned/handled HTML string. */ - public function descriptionHtml(bool $raw = false): string + public function getDescriptionHtml(bool $raw = false): string { $html = $this->description_html ?: '

' . nl2br(e($this->description)) . '

'; if ($raw) { @@ -69,7 +86,7 @@ class EntityContainerData extends Model } if (empty($html) && !empty($plaintext)) { - $this->description_html = $this->descriptionHtml(); + $this->description_html = $this->getDescriptionHtml(); } } } diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index eea1ec1fa..e74324732 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -3,9 +3,7 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\TagRepo; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\BookChild; -use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\EntityContainerData; use BookStack\Entities\Queries\PageQueries; @@ -31,9 +29,13 @@ class BaseRepo /** * Create a new entity in the system. + * @template T of Entity + * @param T $entity + * @return T */ - public function create(Entity $entity, array $input): void + public function create(Entity $entity, array $input): Entity { + $entity = $entity->clone()->refresh(); $entityInput = array_intersect_key($input, ['name', 'priority']); $entity->forceFill($entityInput); $entity->forceFill([ @@ -59,13 +61,19 @@ class BaseRepo $entity->indexForSearch(); $this->referenceStore->updateForEntity($entity); + + return $entity; } /** * Update the given entity. + * @template T of Entity + * @param T $entity + * @return T */ - public function update(Entity $entity, array $input): void + public function update(Entity $entity, array $input): Entity { + $entity = $entity->clone()->refresh(); $oldUrl = $entity->getUrl(); $entity->fill($input); @@ -78,6 +86,7 @@ class BaseRepo $entity->save(); if ($entity->shouldHaveContainerData() && $entity->containerData) { $this->updateContainerDescription($entity->containerData, $input); + $entity->containerData->save(); } if (isset($input['tags'])) { @@ -91,6 +100,8 @@ class BaseRepo if ($oldUrl !== $entity->getUrl()) { $this->referenceUpdater->updateEntityReferences($entity, $oldUrl); } + + return $entity; } /** @@ -147,7 +158,7 @@ class BaseRepo } /** - * Sort the parent of the given entity, if any auto sort actions are set for it. + * Sort the parent of the given entity if any auto sort actions are set for it. * Typically ran during create/update/insert events. */ public function sortParent(Entity $entity): void @@ -158,6 +169,9 @@ class BaseRepo } } + /** + * Update the description of the given container data from input data. + */ protected function updateContainerDescription(EntityContainerData $data, array $input): void { if (isset($input['description_html'])) { diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php index 207b45396..c689a8413 100644 --- a/app/Entities/Repos/BookRepo.php +++ b/app/Entities/Repos/BookRepo.php @@ -30,9 +30,7 @@ class BookRepo public function create(array $input): Book { return (new DatabaseTransaction(function () use ($input) { - $book = new Book(); - - $this->baseRepo->create($book, $input); + $book = $this->baseRepo->create(new Book(), $input); $this->baseRepo->updateCoverImage($book->containerData, $input['image'] ?? null); $this->baseRepo->updateDefaultTemplate($book->containerData, intval($input['default_template_id'] ?? null)); Activity::add(ActivityType::BOOK_CREATE, $book); @@ -52,7 +50,7 @@ class BookRepo */ public function update(Book $book, array $input): Book { - $this->baseRepo->update($book, $input); + $book = $this->baseRepo->update($book, $input); if (array_key_exists('default_template_id', $input)) { $this->baseRepo->updateDefaultTemplate($book->containerData, intval($input['default_template_id'])); diff --git a/app/Entities/Repos/BookshelfRepo.php b/app/Entities/Repos/BookshelfRepo.php index 56fb6932f..3185c4f04 100644 --- a/app/Entities/Repos/BookshelfRepo.php +++ b/app/Entities/Repos/BookshelfRepo.php @@ -25,8 +25,7 @@ class BookshelfRepo public function create(array $input, array $bookIds): Bookshelf { return (new DatabaseTransaction(function () use ($input, $bookIds) { - $shelf = new Bookshelf(); - $this->baseRepo->create($shelf, $input); + $shelf = $this->baseRepo->create(new Bookshelf(), $input); $this->baseRepo->updateCoverImage($shelf->containerData, $input['image'] ?? null); $this->updateBooks($shelf, $bookIds); Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf); @@ -39,7 +38,7 @@ class BookshelfRepo */ public function update(Bookshelf $shelf, array $input, ?array $bookIds): Bookshelf { - $this->baseRepo->update($shelf, $input); + $shelf = $this->baseRepo->update($shelf, $input); if (!is_null($bookIds)) { $this->updateBooks($shelf, $bookIds); diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php index ba91e7d06..59b7036d8 100644 --- a/app/Entities/Repos/ChapterRepo.php +++ b/app/Entities/Repos/ChapterRepo.php @@ -33,7 +33,8 @@ class ChapterRepo $chapter = new Chapter(); $chapter->book_id = $parentBook->id; $chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1; - $this->baseRepo->create($chapter, $input); + + $chapter = $this->baseRepo->create($chapter, $input); $this->baseRepo->updateDefaultTemplate($chapter->containerData, intval($input['default_template_id'] ?? null)); Activity::add(ActivityType::CHAPTER_CREATE, $chapter); @@ -48,7 +49,7 @@ class ChapterRepo */ public function update(Chapter $chapter, array $input): Chapter { - $this->baseRepo->update($chapter, $input); + $chapter = $this->baseRepo->update($chapter, $input); if (array_key_exists('default_template_id', $input)) { $this->baseRepo->updateDefaultTemplate($chapter->containerData, intval($input['default_template_id'])); @@ -93,7 +94,7 @@ class ChapterRepo } return (new DatabaseTransaction(function () use ($chapter, $parent) { - $chapter->changeBook($parent->id); + $chapter = $chapter->changeBook($parent->id); $chapter->rebuildPermissions(); Activity::add(ActivityType::CHAPTER_MOVE, $chapter); diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 48c815ced..fdb9d66f3 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -85,7 +85,9 @@ class PageRepo $draft->pageData->revision_count = 1; $draft->pageData->priority = $this->getNewPriority($draft); $this->updateTemplateStatusAndContentFromInput($draft, $input); - $this->baseRepo->update($draft, $input); + + $draft = $this->baseRepo->update($draft, $input); + $draft->pageData->save(); $draft->rebuildPermissions(); $summary = trim($input['summary'] ?? '') ?: trans('entities.pages_initial_revision'); @@ -115,13 +117,15 @@ class PageRepo */ public function update(Page $page, array $input): Page { + $page = $page->clone()->refresh(); + // Hold the old details to compare later $oldName = $page->name; $oldHtml = $page->pageData->html; $oldMarkdown = $page->pageData->markdown; $this->updateTemplateStatusAndContentFromInput($page, $input); - $this->baseRepo->update($page, $input); + $page = $this->baseRepo->update($page, $input); // Update with new details $page->pageData->revision_count++; @@ -187,6 +191,7 @@ class PageRepo if ($page->draft) { $this->updateTemplateStatusAndContentFromInput($page, $input); $page->forceFill(array_intersect_key($input, array_flip(['name'])))->save(); + $page->pageData->save(); $page->save(); return $page; @@ -285,7 +290,7 @@ class PageRepo return (new DatabaseTransaction(function () use ($page, $parent) { $page->pageData->chapter_id = ($parent instanceof Chapter) ? $parent->id : null; $newBookId = ($parent instanceof Chapter) ? $parent->book->id : $parent->id; - $page->changeBook($newBookId); + $page = $page->changeBook($newBookId); $page->rebuildPermissions(); Activity::add(ActivityType::PAGE_MOVE, $page); diff --git a/app/Entities/Tools/BookContents.php b/app/Entities/Tools/BookContents.php index 7dd3f3e11..4bbab6265 100644 --- a/app/Entities/Tools/BookContents.php +++ b/app/Entities/Tools/BookContents.php @@ -3,13 +3,10 @@ namespace BookStack\Entities\Tools; use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; -use BookStack\Sorting\BookSortMap; -use BookStack\Sorting\BookSortMapItem; use Illuminate\Support\Collection; class BookContents @@ -29,7 +26,7 @@ class BookContents { $maxPage = $this->book->pages() ->where('draft', '=', false) - ->where('chapter_id', '=', 0) + ->whereDoesntHave('chapter') ->max('priority'); $maxChapter = $this->book->chapters() @@ -80,11 +77,11 @@ class BookContents protected function bookChildSortFunc(): callable { return function (Entity $entity) { - if (isset($entity['draft']) && $entity['draft']) { + if ($entity->getAttribute('draft') ?? false) { return -100; } - return $entity['priority'] ?? 0; + return $entity->getAttribute('priority') ?? 0; }; } diff --git a/app/Entities/Tools/HierarchyTransformer.php b/app/Entities/Tools/HierarchyTransformer.php index b0d8880f4..fa45fcd11 100644 --- a/app/Entities/Tools/HierarchyTransformer.php +++ b/app/Entities/Tools/HierarchyTransformer.php @@ -34,6 +34,7 @@ class HierarchyTransformer /** @var Page $page */ foreach ($chapter->pages as $page) { $page->chapter_id = 0; + $page->save(); $page->changeBook($book->id); } diff --git a/app/Exports/ExportFormatter.php b/app/Exports/ExportFormatter.php index 9515207e4..cccf0d51e 100644 --- a/app/Exports/ExportFormatter.php +++ b/app/Exports/ExportFormatter.php @@ -318,7 +318,7 @@ class ExportFormatter { $text = '# ' . $chapter->name . "\n\n"; - $description = (new HtmlToMarkdown($chapter->containerData->descriptionHtml()))->convert(); + $description = (new HtmlToMarkdown($chapter->containerData->getDescriptionHtml()))->convert(); if ($description) { $text .= $description . "\n\n"; } @@ -338,7 +338,7 @@ class ExportFormatter $bookTree = (new BookContents($book))->getTree(false, true); $text = '# ' . $book->name . "\n\n"; - $description = (new HtmlToMarkdown($book->containerData->descriptionHtml()))->convert(); + $description = (new HtmlToMarkdown($book->containerData->getDescriptionHtml()))->convert(); if ($description) { $text .= $description . "\n\n"; } diff --git a/app/Exports/ZipExports/Models/ZipExportBook.php b/app/Exports/ZipExports/Models/ZipExportBook.php index 5dd3775c3..7e7f472d7 100644 --- a/app/Exports/ZipExports/Models/ZipExportBook.php +++ b/app/Exports/ZipExports/Models/ZipExportBook.php @@ -55,7 +55,7 @@ final class ZipExportBook extends ZipExportModel $instance = new self(); $instance->id = $model->id; $instance->name = $model->name; - $instance->description_html = $model->containerData->descriptionHtml(); + $instance->description_html = $model->containerData->getDescriptionHtml(); if ($model->containerData->cover) { $instance->cover = $files->referenceForImage($model->containerData->cover); diff --git a/app/Exports/ZipExports/Models/ZipExportChapter.php b/app/Exports/ZipExports/Models/ZipExportChapter.php index f27f117a7..06d615b1a 100644 --- a/app/Exports/ZipExports/Models/ZipExportChapter.php +++ b/app/Exports/ZipExports/Models/ZipExportChapter.php @@ -40,7 +40,7 @@ final class ZipExportChapter extends ZipExportModel $instance = new self(); $instance->id = $model->id; $instance->name = $model->name; - $instance->description_html = $model->containerData->descriptionHtml(); + $instance->description_html = $model->containerData->getDescriptionHtml(); $instance->priority = $model->priority; $instance->tags = ZipExportTag::fromModelArray($model->tags()->get()->all()); diff --git a/app/References/ReferenceUpdater.php b/app/References/ReferenceUpdater.php index c4f903da7..f80f1af07 100644 --- a/app/References/ReferenceUpdater.php +++ b/app/References/ReferenceUpdater.php @@ -70,7 +70,7 @@ class ReferenceUpdater protected function updateReferencesWithinDescription(EntityContainerData $containerData, string $oldLink, string $newLink): void { - $html = $this->updateLinksInHtml($containerData->descriptionHtml(true) ?: '', $oldLink, $newLink); + $html = $this->updateLinksInHtml($containerData->getDescriptionHtml(true) ?: '', $oldLink, $newLink); $containerData->setDescriptionHtml($html); $containerData->save(); } diff --git a/app/Sorting/BookSorter.php b/app/Sorting/BookSorter.php index 1152101d2..77180a296 100644 --- a/app/Sorting/BookSorter.php +++ b/app/Sorting/BookSorter.php @@ -155,7 +155,7 @@ class BookSorter // Action the required changes if ($bookChanged) { - $model->changeBook($newBook->id); + $model = $model->changeBook($newBook->id); } if ($model instanceof Page && $chapterChanged) { diff --git a/resources/views/books/parts/form.blade.php b/resources/views/books/parts/form.blade.php index c79aa599e..bd7f858cf 100644 --- a/resources/views/books/parts/form.blade.php +++ b/resources/views/books/parts/form.blade.php @@ -18,7 +18,7 @@ @include('form.image-picker', [ 'defaultImage' => url('/book_default_cover.png'), - 'currentImage' => (isset($model) && $model->containerData->cover) ? $model->getBookCover() : url('/book_default_cover.png') , + 'currentImage' => (($model ?? null)?->containerData?->getCoverUrl(440, 250, url('/book_default_cover.png')) ?? url('/book_default_cover.png')), 'name' => 'image', 'imageClass' => 'cover' ]) diff --git a/resources/views/books/parts/list-item.blade.php b/resources/views/books/parts/list-item.blade.php index 88c314d82..a3ff0971f 100644 --- a/resources/views/books/parts/list-item.blade.php +++ b/resources/views/books/parts/list-item.blade.php @@ -5,7 +5,7 @@

{{ $book->name }}

-

{{ $book->containerData->description }}

+

{{ $book->description }}

\ No newline at end of file diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index 8dceaf2c1..d89c38659 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -7,7 +7,7 @@ @stop @push('social-meta') - + @if($book->containerData->cover) @endif @@ -26,7 +26,7 @@

{{$book->name}}

-
{!! $book->containerData->descriptionHtml() !!}
+
{!! $book->containerData->getDescriptionHtml() !!}
@if(count($bookChildren) > 0)
@foreach($bookChildren as $childElement) diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 8566c96e6..d3b90f640 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -7,7 +7,7 @@ @stop @push('social-meta') - + @endpush @include('entities.body-tag-classes', ['entity' => $chapter]) @@ -24,7 +24,7 @@

{{ $chapter->name }}

-
{!! $chapter->containerData->descriptionHtml() !!}
+
{!! $chapter->containerData->getDescriptionHtml() !!}
@if(count($pages) > 0)
@foreach($pages as $page) diff --git a/resources/views/entities/grid-item.blade.php b/resources/views/entities/grid-item.blade.php index 6028d85df..2f0aacc46 100644 --- a/resources/views/entities/grid-item.blade.php +++ b/resources/views/entities/grid-item.blade.php @@ -1,7 +1,7 @@
-
{!! $shelf->containerData->descriptionHtml() !!}
+
{!! $shelf->containerData->getDescriptionHtml() !!}
@if(count($sortedVisibleShelfBooks) > 0) @if($view === 'list')
diff --git a/tests/Exports/ZipExportTest.php b/tests/Exports/ZipExportTest.php index 5e7ba4b56..55e7d12a4 100644 --- a/tests/Exports/ZipExportTest.php +++ b/tests/Exports/ZipExportTest.php @@ -227,7 +227,7 @@ class ZipExportTest extends TestCase $bookData = $zip->data['book']; $this->assertEquals($book->id, $bookData['id']); $this->assertEquals($book->name, $bookData['name']); - $this->assertEquals($book->containerData->descriptionHtml(), $bookData['description_html']); + $this->assertEquals($book->containerData->getDescriptionHtml(), $bookData['description_html']); $this->assertCount(2, $bookData['tags']); $this->assertCount($book->directPages()->count(), $bookData['pages']); $this->assertCount($book->chapters()->count(), $bookData['chapters']); @@ -264,7 +264,7 @@ class ZipExportTest extends TestCase $chapterData = $zip->data['chapter']; $this->assertEquals($chapter->id, $chapterData['id']); $this->assertEquals($chapter->name, $chapterData['name']); - $this->assertEquals($chapter->containerData->descriptionHtml(), $chapterData['description_html']); + $this->assertEquals($chapter->containerData->getDescriptionHtml(), $chapterData['description_html']); $this->assertCount(2, $chapterData['tags']); $this->assertEquals($chapter->priority, $chapterData['priority']); $this->assertCount($chapter->pages()->count(), $chapterData['pages']);