From e87728d6f5dd5666312e5bce7168027a70bb02c2 Mon Sep 17 00:00:00 2001 From: Mojmir Fendek Date: Tue, 4 Feb 2020 14:45:43 +1300 Subject: [PATCH] Top page feature --- .travis.yml | 4 +- _config/config.yml | 16 ++ composer.json | 1 + docs/en/advanced_setup.md | 42 +++ src/Extensions/ElementalPageExtension.php | 6 +- src/Models/BaseElement.php | 1 + src/TopPage/DataExtension.php | 241 +++++++++++++++++ src/TopPage/FluentExtension.php | 48 ++++ src/TopPage/SiteTreeExtension.php | 270 +++++++++++++++++++ src/TopPage/TestState.php | 40 +++ tests/TopPage/TestBlockPage.php | 19 ++ tests/TopPage/TestChildPage.php | 27 ++ tests/TopPage/TestContent.php | 45 ++++ tests/TopPage/TestList.php | 36 +++ tests/TopPage/TopPageTest.php | 309 ++++++++++++++++++++++ tests/TopPage/TopPageTest.yml | 55 ++++ 16 files changed, 1154 insertions(+), 6 deletions(-) create mode 100644 src/TopPage/DataExtension.php create mode 100644 src/TopPage/FluentExtension.php create mode 100644 src/TopPage/SiteTreeExtension.php create mode 100644 src/TopPage/TestState.php create mode 100644 tests/TopPage/TestBlockPage.php create mode 100644 tests/TopPage/TestChildPage.php create mode 100644 tests/TopPage/TestContent.php create mode 100644 tests/TopPage/TestList.php create mode 100644 tests/TopPage/TopPageTest.php create mode 100644 tests/TopPage/TopPageTest.yml diff --git a/.travis.yml b/.travis.yml index aa28c644..d303a943 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,9 +17,9 @@ env: matrix: include: - - php: 5.6 + - php: 7.1 env: DB=MYSQL RECIPE_VERSION=4.4.x-dev PHPCS_TEST=1 PHPUNIT_TEST=1 - - php: 7.0 + - php: 7.1 env: DB=PGSQL RECIPE_VERSION=4.4.x-dev PHPUNIT_TEST=1 - php: 7.1 env: DB=MYSQL RECIPE_VERSION=4.4.x-dev PHPUNIT_COVERAGE_TEST=1 diff --git a/_config/config.yml b/_config/config.yml index 1ba18dca..4ff6cf96 100644 --- a/_config/config.yml +++ b/_config/config.yml @@ -31,3 +31,19 @@ Symbiote\GridFieldExtensions\GridFieldAddNewMultiClassHandler: SilverStripe\Core\Injector\Injector: SilverStripe\CMS\Controllers\CMSSiteTreeFilter_Search: class: DNADesign\Elemental\Controllers\ElementSiteTreeFilterSearch + SilverStripe\Dev\State\SapphireTestState: + properties: + States: + topPageTestState: '%$DNADesign\Elemental\TopPage\TestState' + +DNADesign\Elemental\Models\BaseElement: + extensions: + topPageDataExtension: DNADesign\Elemental\TopPage\DataExtension + +DNADesign\Elemental\Models\ElementalArea: + extensions: + topPageDataExtension: DNADesign\Elemental\TopPage\DataExtension + +Page: + extensions: + topPageSiteTreeExtension: DNADesign\Elemental\TopPage\SiteTreeExtension diff --git a/composer.json b/composer.json index fc1b3e7d..7575dec3 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "minimum-stability": "dev", "prefer-stable": true, "require": { + "php": "^7.1", "silverstripe/cms": "^4.4@dev", "silverstripe/admin": "^1.4@dev", "silverstripe/versioned-admin": "^1.2@dev", diff --git a/docs/en/advanced_setup.md b/docs/en/advanced_setup.md index 1a26dec4..91b72534 100644 --- a/docs/en/advanced_setup.md +++ b/docs/en/advanced_setup.md @@ -275,3 +275,45 @@ FluentState::singleton()->withState(function (FluentState $state) { This is very important as global state is reverted back after the callback is executed so it's safe to be used. Unit tests benefit mostly from this as this makes sure that there are no dependencies between unit tests as the global state is always changed only locally in one test. + +## Top page reference feature + +In some cases your project setup may have deeply nested blocks, for example: + +``` +Page + ElementalArea + RowBlock (represents grid row on frontend) + ElementalArea + AccordionBlock (block which can contain other content blocks) + ElementalArea + ContentBlock +``` + +It's quite common to use top page lookups from block context, i.e. a block is querying data from the page that the block belongs to. + +Most common cases are: + +* `CMS fields` - block level conditional logic depends on page data +* `templates` - block level render logic depends on page data + +This module uses some in-memory caching but this isn't good enough for such deeply nested data structures by default. + +In such cases it is recommended to use this feature which stores the top page reference on individual blocks and elemental areas. +This speeds up data lookup significantly. + +If your project makes use of the Fluent module, it is recommended to use the following extensions to replace the ones used by default: + +``` +DNADesign\Elemental\Models\BaseElement: + extensions: + topPageDataExtension: DNADesign\Elemental\TopPage\FluentExtension + +DNADesign\Elemental\Models\ElementalArea: + extensions: + topPageDataExtension: DNADesign\Elemental\TopPage\FluentExtension +``` + +This will store the locale of the top page on blocks which simplifies top page lookup in case the locale is unknown at the time of page lookup from block context. + +The page reference data on the blocks can also be used for maintenance dev tasks as it's easy to identify which blocks belong to which pages in which locale. diff --git a/src/Extensions/ElementalPageExtension.php b/src/Extensions/ElementalPageExtension.php index 995422c6..feeb93d1 100644 --- a/src/Extensions/ElementalPageExtension.php +++ b/src/Extensions/ElementalPageExtension.php @@ -2,16 +2,14 @@ namespace DNADesign\Elemental\Extensions; -use Exception; use DNADesign\Elemental\Models\ElementalArea; use SilverStripe\Control\Controller; use SilverStripe\View\Parsers\HTML4Value; -use SilverStripe\Core\Config\Config; use SilverStripe\View\SSViewer; /** - * @method ElementalArea ElementalArea - * @property ElementalArea ElementalArea + * @method ElementalArea ElementalArea() + * @property int ElementalAreaID */ class ElementalPageExtension extends ElementalAreasExtension { diff --git a/src/Models/BaseElement.php b/src/Models/BaseElement.php index 05cbca3c..d9983f21 100644 --- a/src/Models/BaseElement.php +++ b/src/Models/BaseElement.php @@ -39,6 +39,7 @@ * @property int $Sort * @property string $ExtraClass * @property string $Style + * @property int $ParentID * * @method ElementalArea Parent() */ diff --git a/src/TopPage/DataExtension.php b/src/TopPage/DataExtension.php new file mode 100644 index 00000000..9e2a7f13 --- /dev/null +++ b/src/TopPage/DataExtension.php @@ -0,0 +1,241 @@ + Page::class, + ]; + + /** + * @config + * @var array + */ + private static $indexes = [ + 'TopPageID' => true, + ]; + + /** + * @var bool + */ + private $topPageUpdate = true; + + /** + * Extension point in @see DataObject::onAfterWrite() + */ + public function onAfterWrite(): void + { + $this->setTopPage(); + } + + /** + * Extension point in @see DataObject::duplicate() + */ + public function onBeforeDuplicate(): void + { + $this->clearTopPage(); + } + + /** + * Extension point in @see DataObject::duplicate() + */ + public function onAfterDuplicate(): void + { + $this->updateTopPage(); + } + + /** + * Finds the top-level Page object for a Block / ElementalArea, using the cached TopPageID + * reference when possible. + * + * @return Page|null + * @throws ValidationException + */ + public function getTopPage(): ?Page + { + $list = [$this->owner]; + + while (count($list) > 0) { + /** @var DataObject|DataExtension $item */ + $item = array_shift($list); + + if ($item instanceof Page) { + // trivial case + return $item; + } + + if ($item->hasExtension(DataExtension::class) && $item->TopPageID > 0) { + // top page is stored inside data object - just fetch it via cached call + $page = Page::get_by_id($item->TopPageID); + + if ($page !== null && $page->exists()) { + return $page; + } + } + + if ($item instanceof BaseElement) { + // parent lookup via block + $parent = $item->Parent(); + + if ($parent !== null && $parent->exists()) { + array_push($list, $parent); + } + + continue; + } + + if ($item instanceof ElementalArea) { + // parent lookup via elemental area + $parent = $item->getOwnerPage(); + + if ($parent !== null && $parent->exists()) { + array_push($list, $parent); + } + + continue; + } + } + + return null; + } + + /** + * @param Page|null $page + * @throws ValidationException + */ + public function setTopPage(?Page $page = null): void + { + if (!$this->topPageUpdate) { + return; + } + + /** @var BaseElement|ElementalArea|Versioned|DataExtension $owner */ + $owner = $this->owner; + + if (!$owner->hasExtension(DataExtension::class)) { + return; + } + + if ($owner->TopPageID > 0) { + return; + } + + $page = $page ?? $owner->getTopPage(); + + if ($page === null) { + return; + } + + // set the page to properties in case this object is re-used later + $this->assignTopPage($page); + + if ($owner->hasExtension(Versioned::class)) { + $owner->writeWithoutVersion(); + + return; + } + + $owner->write(); + } + + public function getTopPageUpdate(): bool + { + return $this->topPageUpdate; + } + + /** + * Enable top page update + * useful for unit tests + */ + public function enableTopPageUpdate(): void + { + $this->topPageUpdate = true; + } + + /** + * Disable top page update + * useful for unit tests + */ + public function disableTopPageUpdate(): void + { + $this->topPageUpdate = false; + } + + /** + * Use this to wrap any code which is supposed to run with desired top page update setting + * useful for unit tests + * + * @param bool $update + * @param callable $callback + * @return mixed + */ + public function withTopPageUpdate(bool $update, callable $callback) + { + $original = $this->topPageUpdate; + $this->topPageUpdate = $update; + + try { + return $callback(); + } finally { + $this->topPageUpdate = $original; + } + } + + /** + * Registers the object for a TopPage update. Ensures that this operation is deferred to a point + * when all required relations have been written. + */ + protected function updateTopPage(): void + { + if (!$this->topPageUpdate) { + return; + } + + /** @var SiteTreeExtension $extension */ + $extension = singleton(SiteTreeExtension::class); + $extension->addDuplicatedObject($this->owner); + } + + /** + * Assigns top page relation + * + * @param Page $page + */ + protected function assignTopPage(Page $page): void + { + $this->owner->TopPageID = (int) $page->ID; + } + + /** + * Clears top page relation, this is useful when duplicating object as the new object doesn't necessarily + * belong to the original page + */ + protected function clearTopPage(): void + { + $this->owner->TopPageID = 0; + } +} diff --git a/src/TopPage/FluentExtension.php b/src/TopPage/FluentExtension.php new file mode 100644 index 00000000..2332a908 --- /dev/null +++ b/src/TopPage/FluentExtension.php @@ -0,0 +1,48 @@ + 'Varchar', + ]; + + /** + * Assigns top page relation and stores reference for page locale + * + * @param Page $page + */ + protected function assignTopPage(Page $page): void + { + parent::assignTopPage($page); + + $this->owner->TopPageLocale = FluentState::singleton()->getLocale(); + } + + protected function clearTopPage(): void + { + parent::clearTopPage(); + + $this->owner->TopPageLocale = null; + } +} diff --git a/src/TopPage/SiteTreeExtension.php b/src/TopPage/SiteTreeExtension.php new file mode 100644 index 00000000..82e089c4 --- /dev/null +++ b/src/TopPage/SiteTreeExtension.php @@ -0,0 +1,270 @@ +setTopPageForElementalArea(); + $this->processDuplicationFromOriginal(); + } + + /** + * Extension point in @see DataObject::duplicate() + * + * @param Page $original + */ + public function onBeforeDuplicate(Page $original): void + { + $this->initDuplication($original); + } + + /** + * Extension point in @see DataObject::duplicate() + * + * @param Page $original + * @param bool $doWrite + */ + public function onAfterDuplicate(Page $original, $doWrite): void + { + $this->processDuplication($original, (bool) $doWrite); + } + + /** + * Generates a unique key for the page + * + * @return string|null + */ + public function getDuplicationKey(): ?string + { + $owner = $this->owner; + + if (!$owner->isInDB()) { + return null; + } + + return sprintf('%s-%d', $owner->ClassName, $owner->ID); + } + + /** + * Registers the given object to receive an updated TopPage reference after the duplication + * operation completes, ensuring the new Page is written to the database beforehand. + * + * The registry uses a stack-like structure to allow accurate tracking of objects during + * duplication operations that include nested pages. + * + * @param DataObject $object + */ + public function addDuplicatedObject(DataObject $object): void + { + if (!$object->hasExtension(DataExtension::class)) { + return; + } + + $key = $this->getDuplicatedPageKey(); + + if ($key === null) { + return; + } + + if (array_key_exists($key, $this->duplicatedObjects)) { + array_unshift($this->duplicatedObjects[$key], $object); + + return; + } + + $this->duplicatedObjects[$key] = [$object]; + } + + /** + * Find currently duplicated page + * note: this doesn't change any stored data + * + * @return string|null + */ + protected function getDuplicatedPageKey(): ?string + { + $pages = $this->duplicatedPages; + + if (count($pages) === 0) { + return null; + } + + return array_shift($pages); + } + + /** + * @param Page|SiteTreeExtension $original + */ + protected function initDuplication(Page $original): void + { + /** @var DataExtension $extension */ + $extension = singleton(DataExtension::class); + if (!$extension->getTopPageUpdate()) { + return; + } + + $key = $original->getDuplicationKey(); + + if ($key === null) { + return; + } + + if (in_array($key, $this->duplicatedPages)) { + // this should never happen as it would indicate a duplication loop + return; + } + + array_unshift($this->duplicatedPages, $key); + } + + /** + * Update top page reference during duplication process + * + * @param Page $original + * @param bool $written + */ + protected function processDuplication(Page $original, bool $written): void + { + /** @var DataExtension $extension */ + $extension = singleton(DataExtension::class); + if (!$extension->getTopPageUpdate()) { + return; + } + + if ($written) { + $this->writeDuplication($original); + + return; + } + + // write may not be triggered as the page maybe have come up via relation + // in this case we have to delay the processing until the page is written + // store the origin reference on the object (in memory only) so we can pick it up later + $this->owner->duplicationOriginal = $original; + } + + /** + * Relevant only for duplicated object that were not written at the time of duplication + */ + protected function processDuplicationFromOriginal(): void + { + /** @var DataExtension $extension */ + $extension = singleton(DataExtension::class); + if (!$extension->getTopPageUpdate()) { + return; + } + + $owner = $this->owner; + + if (!isset($owner->duplicationOriginal)) { + return; + } + + $original = $owner->duplicationOriginal; + + if (!$original instanceof Page) { + return; + } + + unset($owner->duplicationOriginal); + $this->writeDuplication($original); + } + + /** + * @param Page|SiteTreeExtension $original + */ + protected function writeDuplication(Page $original): void + { + $key = $original->getDuplicationKey(); + $currentKey = $this->getDuplicatedPageKey(); + + if ($key !== $currentKey) { + // should never happen but it indicates that the nesting hierarchy was incorrect + return; + } + + if (array_key_exists($key, $this->duplicatedObjects)) { + $objects = $this->duplicatedObjects[$key]; + + /** @var DataObject|DataExtension $object */ + foreach ($objects as $object) { + // attach current page ID to the object + $object->setTopPage($this->owner); + } + } + + // mark page as processed + array_shift($this->duplicatedPages); + } + + /** + * Elemental area is created before related page is written so we have to set top page explicitly + * after page is written and the relations are available + */ + protected function setTopPageForElementalArea(): void + { + /** @var DataExtension $extension */ + $extension = singleton(DataExtension::class); + if (!$extension->getTopPageUpdate()) { + return; + } + + /** @var Page|ElementalPageExtension $owner */ + $owner = $this->owner; + + if (!$owner->hasExtension(ElementalPageExtension::class)) { + return; + } + + if (!$owner->ElementalAreaID) { + return; + } + + /** @var ElementalArea|DataExtension $area */ + $area = $owner->ElementalArea(); + + if (!$area->exists()) { + return; + } + + if (!$area->hasExtension(DataExtension::class)) { + return; + } + + $area->setTopPage($owner); + } +} diff --git a/src/TopPage/TestState.php b/src/TopPage/TestState.php new file mode 100644 index 00000000..d9975ed6 --- /dev/null +++ b/src/TopPage/TestState.php @@ -0,0 +1,40 @@ +disableTopPageUpdate(); + } + + public function tearDown(SapphireTest $test): void + { + } + + public function setUpOnce($class): void + { + $this->disableTopPageUpdate(); + } + + public function tearDownOnce($class): void + { + } + + /** + * We need to disable top page updates as it doesn't work with fixture builder and transactions in unit tests + * caused by an attempt to traverse unsaved relations which is normally fine + * the lookup failure causes the whole transaction to fail which is the whole unit test + * this has no impact on the functionality, though + */ + private function disableTopPageUpdate(): void + { + /** @var DataExtension $extension */ + $extension = singleton(DataExtension::class); + $extension->disableTopPageUpdate(); + } +} diff --git a/tests/TopPage/TestBlockPage.php b/tests/TopPage/TestBlockPage.php new file mode 100644 index 00000000..a3ab772d --- /dev/null +++ b/tests/TopPage/TestBlockPage.php @@ -0,0 +1,19 @@ + TestContent::class . '.ChildPage', + ]; +} diff --git a/tests/TopPage/TestContent.php b/tests/TopPage/TestContent.php new file mode 100644 index 00000000..e0c79035 --- /dev/null +++ b/tests/TopPage/TestContent.php @@ -0,0 +1,45 @@ + 'Varchar', + ]; + + private static $has_one = [ + 'ChildPage' => TestChildPage::class, + ]; + + private static $cascade_duplicates = [ + 'ChildPage', + ]; + + private static $cascade_deletes = [ + 'ChildPage', + ]; + + public function getType(): string + { + return 'Test element with content'; + } +} diff --git a/tests/TopPage/TestList.php b/tests/TopPage/TestList.php new file mode 100644 index 00000000..ebbe866f --- /dev/null +++ b/tests/TopPage/TestList.php @@ -0,0 +1,36 @@ + ElementalArea::class + ]; + + private static $owns = [ + 'Elements' + ]; + + private static $cascade_deletes = [ + 'Elements' + ]; + + private static $cascade_duplicates = [ + 'Elements' + ]; + + public function getType(): string + { + return 'Test element list'; + } +} diff --git a/tests/TopPage/TopPageTest.php b/tests/TopPage/TopPageTest.php new file mode 100644 index 00000000..403388c0 --- /dev/null +++ b/tests/TopPage/TopPageTest.php @@ -0,0 +1,309 @@ + [ + ElementalPageExtension::class, + ], + TestChildPage::class => [ + ElementalPageExtension::class, + ], + Page::class => [ + TopPage\SiteTreeExtension::class, + ], + ElementalArea::class => [ + TopPage\DataExtension::class, + ], + BaseElement::class => [ + TopPage\DataExtension::class, + ], + TestList::class => [ + ElementalAreasExtension::class, + ], + ]; + + /** + * @var array + */ + protected static $extra_dataobjects = [ + TestContent::class, + TestList::class, + TestBlockPage::class, + TestChildPage::class, + ]; + + /** + * @param string $pageIdentifier + * @param string $pageClass + * @param string $objectIdentifier + * @param string $objectClass + * @dataProvider objectsProvider + */ + public function testTestGetTopPage( + string $pageIdentifier, + string $pageClass, + string $objectIdentifier, + string $objectClass + ): void { + /** @var Page|TopPage\SiteTreeExtension $content */ + $page = $this->objFromFixture($pageClass, $pageIdentifier); + + /** @var DataObject|TopPage\DataExtension $object */ + $object = $this->objFromFixture($objectClass, $objectIdentifier); + + $topPage = $object->getTopPage(); + + $this->assertNotNull($topPage); + $this->assertEquals((int) $page->ID, (int) $topPage->ID); + } + + /** + * @param string $pageIdentifier + * @param string $pageClass + * @param string $objectIdentifier + * @param string $objectClass + * @dataProvider objectsProvider + */ + public function testTestUpdateTopPageEmptyCache( + string $pageIdentifier, + string $pageClass, + string $objectIdentifier, + string $objectClass + ): void { + /** @var TopPage\DataExtension $extension */ + $extension = singleton(TopPage\DataExtension::class); + $extension->withTopPageUpdate( + true, + function () use ($pageIdentifier, $pageClass, $objectIdentifier, $objectClass): void { + /** @var Page|TopPage\SiteTreeExtension $content */ + $page = $this->objFromFixture($pageClass, $pageIdentifier); + + /** @var DataObject|TopPage\DataExtension $object */ + $object = $this->objFromFixture($objectClass, $objectIdentifier); + + $this->assertEquals(0, (int) $object->TopPageID); + + $object->forceChange(); + $id = $object->write(); + $object = DataObject::get($object->ClassName)->byID($id); + + $this->assertEquals((int) $page->ID, (int) $object->TopPageID); + + // do a second write to make sure that we won't override existing top page + $object->forceChange(); + $id = $object->write(); + $object = DataObject::get($object->ClassName)->byID($id); + + $this->assertEquals((int) $page->ID, (int) $object->TopPageID); + } + ); + } + + public function testNewPage(): void + { + /** @var TopPage\DataExtension $extension */ + $extension = singleton(TopPage\DataExtension::class); + $extension->withTopPageUpdate( + true, + function (): void { + $page = TestBlockPage::create(); + $page->Title = 'New page test'; + $page->write(); + + /** @var ElementalArea|TopPage\DataExtension $area */ + $area = $page->ElementalArea(); + $this->assertEquals((int) $page->ID, (int) $area->TopPageID); + } + ); + } + + /** + * @param bool $populateTopPage + * @dataProvider populateTopPageProvider + */ + public function testNewBlock(bool $populateTopPage): void + { + /** @var TopPage\DataExtension $extension */ + $extension = singleton(TopPage\DataExtension::class); + $extension->withTopPageUpdate( + true, + function () use ($populateTopPage): void { + if ($populateTopPage) { + $this->populateTopPageForAllObjects(); + } + + /** @var TestBlockPage $page */ + $page = $this->objFromFixture(TestBlockPage::class, 'block-page1'); + + /** @var ElementalArea $area */ + $area = $this->objFromFixture(ElementalArea::class, 'area3'); + + /** @var TestContent|TopPage\DataExtension $content */ + $content = TestContent::create(); + $content->Title = 'Fresh block'; + + $area->Elements()->add($content); + $content = DataObject::get($content->ClassName)->byID($content->ID); + + $this->assertEquals((int) $page->ID, (int) $content->TopPageID); + } + ); + } + + public function testPageDuplication(): void + { + /** @var TopPage\DataExtension $extension */ + $extension = singleton(TopPage\DataExtension::class); + $extension->withTopPageUpdate( + true, + function (): void { + $this->populateTopPageForAllObjects(); + + /** @var TestBlockPage $page */ + $page = $this->objFromFixture(TestBlockPage::class, 'block-page1'); + + /** @var TestChildPage $childPage */ + $childPage = $this->objFromFixture(TestChildPage::class, 'child-page1'); + + $page->duplicate(); + $pages = TestBlockPage::get()->filter(['Title' => 'BlockPage1'])->sort('ID', 'DESC'); + + $this->assertCount(2, $pages); + + $pageClone = $pages->first(); + $childPages = TestChildPage::get()->filter(['Title' => 'ChildPage1'])->sort('ID', 'DESC'); + + $this->assertCount(2, $childPages); + + $childClone = $childPages->first(); + + $this->assertNotEquals((int) $childPage->ID, (int) $childClone->ID); + + $objects = [ + [TestList::class, 'List1', $pageClone], + [TestContent::class, 'Content1', $pageClone], + [TestList::class, 'List2', $childClone], + [TestContent::class, 'Content2', $childClone], + ]; + + foreach ($objects as $objectData) { + $class = array_shift($objectData); + $title = array_shift($objectData); + $page = array_shift($objectData); + + $items = DataObject::get($class)->filter(['Title' => $title])->sort('ID', 'DESC'); + + $this->assertCount(2, $items); + + /** @var DataObject|TopPage\DataExtension $objectClone */ + $objectClone = $items->first(); + + $this->assertEquals((int) $page->ID, (int) $objectClone->TopPageID); + + /** @var ElementalArea|TopPage\DataExtension $area */ + $area = $objectClone->Parent(); + + $this->assertEquals((int) $page->ID, (int) $area->TopPageID); + } + } + ); + } + + public function objectsProvider(): array + { + return [ + [ + 'block-page1', + TestBlockPage::class, + 'content1', + TestContent::class, + ], + [ + 'block-page1', + TestBlockPage::class, + 'list1', + TestList::class, + ], + [ + 'block-page1', + TestBlockPage::class, + 'area1', + ElementalArea::class, + ], + [ + 'block-page1', + TestBlockPage::class, + 'area3', + ElementalArea::class, + ], + [ + 'child-page1', + TestChildPage::class, + 'content2', + TestContent::class, + ], + [ + 'child-page1', + TestChildPage::class, + 'list2', + TestList::class, + ], + [ + 'child-page1', + TestChildPage::class, + 'area2', + ElementalArea::class, + ], + [ + 'child-page1', + TestChildPage::class, + 'area4', + ElementalArea::class, + ], + ]; + } + + public function populateTopPageProvider(): array + { + return [ + [true], + [false], + ]; + } + + private function populateTopPageForAllObjects(): void + { + $list = $this->objectsProvider(); + + foreach ($list as $objects) { + array_shift($objects); + array_shift($objects); + $identifier = array_shift($objects); + $class = array_shift($objects); + + $object = $this->objFromFixture($class, $identifier); + $object->forceChange(); + $object->write(); + } + } +} diff --git a/tests/TopPage/TopPageTest.yml b/tests/TopPage/TopPageTest.yml new file mode 100644 index 00000000..bfc11ba6 --- /dev/null +++ b/tests/TopPage/TopPageTest.yml @@ -0,0 +1,55 @@ +# Deeply nested setup +# +# Block page +# - Elemental area +# -- TestList +# --- ElementalArea +# ---- TestContent +# ----- ChildPage +# ------- ElementalArea +# -------- TestList +# --------- ElementalArea +# ---------- TestContent + +DNADesign\Elemental\Models\ElementalArea: + area1: + OwnerClassName: DNADesign\Elemental\Tests\TopPage\TestBlockPage + area2: + OwnerClassName: DNADesign\Elemental\Tests\TopPage\TestChildPage + area3: + OwnerClassName: DNADesign\Elemental\Tests\TopPage\TestList + area4: + OwnerClassName: DNADesign\Elemental\Tests\TopPage\TestList + +DNADesign\Elemental\Tests\TopPage\TestList: + list1: + Title: List1 + ParentID: =>DNADesign\Elemental\Models\ElementalArea.area1 + ElementsID: =>DNADesign\Elemental\Models\ElementalArea.area3 + list2: + Title: List2 + ParentID: =>DNADesign\Elemental\Models\ElementalArea.area2 + ElementsID: =>DNADesign\Elemental\Models\ElementalArea.area4 + +DNADesign\Elemental\Tests\TopPage\TestBlockPage: + block-page1: + Title: BlockPage1 + URLSegment: block-page1 + ElementalAreaID: =>DNADesign\Elemental\Models\ElementalArea.area1 + +DNADesign\Elemental\Tests\TopPage\TestChildPage: + child-page1: + Title: ChildPage1 + URLSegment: child-page1 + ElementalAreaID: =>DNADesign\Elemental\Models\ElementalArea.area2 + +DNADesign\Elemental\Tests\TopPage\TestContent: + content1: + Title: Content1 + Content: 'Some content' + ParentID: =>DNADesign\Elemental\Models\ElementalArea.area3 + ChildPageID: =>DNADesign\Elemental\Tests\TopPage\TestChildPage.child-page1 + content2: + Title: Content2 + Content: 'Some other content' + ParentID: =>DNADesign\Elemental\Models\ElementalArea.area4