Skip to content

Commit

Permalink
Add unit tests for new ManyManyThrough support
Browse files Browse the repository at this point in the history
The previous commit (9fa9ef8) added support for the new SilverStripe 4
feature of Many Many relationships through an intermediary object. After
much head scratching and community testing, the solution was proven to
work, however had no automated tests to confirm as such. This commit
rectifies that by testing both versioned and unversioned DataObjects in
a many_many through style relationship. Some minor tidy and comments
were also added as per feedback on the functionality code changes.
  • Loading branch information
Dylan Wagstaff committed Jun 25, 2018
1 parent 9fa9ef8 commit 08ed6f6
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 29 deletions.
23 changes: 13 additions & 10 deletions src/GridFieldOrderableRows.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ public function getExtraSortFields()
* Checks to see if the relationship list is for a type of many_many
*
* @param SS_List $list
*
* @return bool
*/
protected function isManyMany(SS_List $list)
{
Expand Down Expand Up @@ -493,7 +495,7 @@ public function handleSave(GridField $grid, DataObjectInterface $record)
*/
protected function executeReorder(GridField $grid, $sortedIDs)
{
if (!is_array($sortedIDs)) {
if (!is_array($sortedIDs) || empty($sortedIDs)) {
return false;
}
$sortField = $this->getSortField();
Expand Down Expand Up @@ -540,6 +542,7 @@ protected function executeReorder(GridField $grid, $sortedIDs)
$toRelationName = $manipulator->getLocalKey();
$sortlist = DataList::create($joinClass)->filter([
$toRelationName => $items->column('ID'),
// first() is safe as there are earlier checks to ensure our list to sort is valid
$fromRelationName => $items->first()->getJoin()->$fromRelationName,
]);
$current = $sortlist->map($toRelationName, $sortField)->toArray();
Expand Down Expand Up @@ -583,19 +586,19 @@ protected function reorderItems($list, array $values, array $sortedIDs)
// - Related item is versioned...
// - Through object is versioned: handle as versioned
// - Through object is NOT versioned: THROW an error.
$isManyMany = $this->isManyMany($list);
if ($isManyMany && $list instanceof ManyManyThroughList) {
if ($list instanceof ManyManyThroughList) {
$listClassVersioned = $class::create()->hasExtension(Versioned::class);
// We'll be updating the join class, not the relation class.
$class = $this->getManyManyInspector($list)->getJoinClass();
$isVersioned = $class::create()->hasExtension(Versioned::class);

// If one side of the relationship is versioned and the other is not, throw an error.
if ($listClassVersioned xor $isVersioned) {
throw new Exception(
'ManyManyThrough cannot mismatch Versioning between join class and related class'
);
}
} elseif (!$isManyMany) {
} elseif (!$this->isManyMany($list)) {
$isVersioned = $class::create()->hasExtension(Versioned::class);
}

Expand Down Expand Up @@ -712,12 +715,12 @@ protected function getSortTableClauseForIds(DataList $list, $ids)
}

if ($this->isManyMany($list)) {
$intropector = $this->getManyManyInspector($list);
$introspector = $this->getManyManyInspector($list);
$extra = $list instanceof ManyManyList ?
$intropector->getExtraFields() :
DataObjectSchema::create()->fieldSpecs($intropector->getJoinClass(), DataObjectSchema::DB_ONLY);
$key = $intropector->getLocalKey();
$foreignKey = $intropector->getForeignKey();
$introspector->getExtraFields() :
DataObjectSchema::create()->fieldSpecs($introspector->getJoinClass(), DataObjectSchema::DB_ONLY);
$key = $introspector->getLocalKey();
$foreignKey = $introspector->getForeignKey();
$foreignID = (int) $list->getForeignID();

if ($extra && array_key_exists($this->getSortField(), $extra)) {
Expand All @@ -739,7 +742,7 @@ protected function getSortTableClauseForIds(DataList $list, $ids)
* these functions are moved to ManyManyThroughQueryManipulator, but otherwise retain
* the same signature.
*
* @param ManyManyList|ManyManyThroughList
* @param ManyManyList|ManyManyThroughList $list
*
* @return ManyManyList|ManyManyThroughQueryManipulator
*/
Expand Down
43 changes: 32 additions & 11 deletions tests/GridFieldOrderableRowsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
use Symbiote\GridFieldExtensions\Tests\Stub\StubSubclass;
use Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned;
use Symbiote\GridFieldExtensions\Tests\Stub\StubUnorderable;
use Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner;
use Symbiote\GridFieldExtensions\Tests\Stub\ThroughIntermediary;
use Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs;

/**
* Tests for the {@link GridFieldOrderableRows} component.
Expand All @@ -23,7 +26,10 @@ class GridFieldOrderableRowsTest extends SapphireTest
/**
* @var string
*/
protected static $fixture_file = 'GridFieldOrderableRowsTest.yml';
protected static $fixture_file = [
'GridFieldOrderableRowsTest.yml',
'OrderableRowsThroughTest.yml'
];

/**
* @var array
Expand All @@ -36,27 +42,42 @@ class GridFieldOrderableRowsTest extends SapphireTest
StubOrderableChild::class,
StubOrderedVersioned::class,
StubSubclassOrderedVersioned::class,
ThroughDefiner::class,
ThroughIntermediary::class,
ThroughBelongs::class,
];

public function testReorderItems()
public function reorderItemsProvider()
{
return [
[StubParent::class . '.parent', 'MyManyMany', 'ManyManySort'],
[ThroughDefiner::class . '.DefinerOne', 'Belongings', 'Sort'],
];
}

/**
* @dataProvider reorderItemsProvider
*/
public function testReorderItems($fixtureID, $relationName, $sortName)
{
$orderable = new GridFieldOrderableRows('ManyManySort');
$orderable = new GridFieldOrderableRows($sortName);
$reflection = new ReflectionMethod($orderable, 'executeReorder');
$reflection->setAccessible(true);

$parent = $this->objFromFixture(StubParent::class, 'parent');

$config = new GridFieldConfig_RelationEditor();
$config->addComponent($orderable);

list($parentClass, $parentInstanceID) = explode('.', $fixtureID);
$parent = $this->objFromFixture($parentClass, $parentInstanceID);

$grid = new GridField(
'MyManyMany',
'My Many Many',
$parent->MyManyMany()->sort('ManyManySort'),
$relationName,
'Testing Many Many',
$parent->$relationName()->sort($sortName),
$config
);

$originalOrder = $parent->MyManyMany()->sort('ManyManySort')->column('ID');
$originalOrder = $parent->$relationName()->sort($sortName)->column('ID');
$desiredOrder = [];

// Make order non-contiguous, and 1-based
Expand All @@ -68,7 +89,7 @@ public function testReorderItems()

$reflection->invoke($orderable, $grid, $desiredOrder);

$newOrder = $parent->MyManyMany()->sort('ManyManySort')->map('ManyManySort', 'ID')->toArray();
$newOrder = $parent->$relationName()->sort($sortName)->map($sortName, 'ID')->toArray();

$this->assertEquals($desiredOrder, $newOrder);
}
Expand Down Expand Up @@ -157,7 +178,7 @@ public function testReorderItemsSubclassVersioned()
foreach ($parent->MyHasManySubclassOrderedVersioned() as $item) {
/** @var StubSubclassOrderedVersioned|Versioned $item */
if ($item->stagesDiffer()) {
$this->fail('Unexpected diference found on stages');
$this->fail('Unexpected difference found on stages');
}
}

Expand Down
40 changes: 40 additions & 0 deletions tests/OrderableRowsThroughTest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner:
DefinerOne:
Title: 'One'
DefinerTwo:
Title: 'Two'

Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs:
BelongsOne:
Title: 'One'
BelongsTwo:
Title: 'Two'
BelongsThree:
Title: 'Three'

Symbiote\GridFieldExtensions\Tests\Stub\ThroughIntermediary:
One:
Title: 'One'
Defining: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner.DefinerOne
Belonging: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs.BelongsOne
Sort: 3
Two:
Title: 'Two'
Defining: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner.DefinerOne
Belonging: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs.BelongsTwo
Sort: 2
Three:
Title: 'Three'
Defining: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner.DefinerOne
Belonging: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs.BelongsThree
Sort: 1
Four:
Title: 'Four'
Defining: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner.DefinerTwo
Belonging: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs.BelongsTwo
Sort: 1
Five:
Title: 'Five'
Defining: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner.DefinerTwo
Belonging: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs.BelongsThree
Sort: 2
109 changes: 109 additions & 0 deletions tests/OrderableRowsThroughVersionedTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Symbiote\GridFieldExtensions\Tests;

use ReflectionMethod;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Versioned\Versioned;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
use Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner;
use Symbiote\GridFieldExtensions\Tests\Stub\ThroughIntermediary;
use Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs;
use Symbiote\GridFieldExtensions\GridFieldOrderableRows;

class OrderableRowsThroughTest extends SapphireTest
{
protected static $fixture_file = 'OrderableRowsThroughTest.yml';

protected static $extra_dataobjects = [
ThroughDefiner::class,
ThroughIntermediary::class,
ThroughBelongs::class,
];

protected static $required_extensions = [
ThroughDefiner::class => [Versioned::class],
ThroughIntermediary::class => [Versioned::class],
ThroughBelongs::class => [Versioned::class],
];

/**
* Mostly the same as GridFieldOrderableRowsTest::testReorderItems
* but with some Versioned calls & checks mixed in.
*/
public function testReorderingSaves()
{
$parent = $this->objFromFixture(ThroughDefiner::class, 'DefinerOne');
$sortName = 'Sort';

$orderable = new GridFieldOrderableRows($sortName);
$reflection = new ReflectionMethod($orderable, 'executeReorder');
$reflection->setAccessible(true);

$config = new GridFieldConfig_RelationEditor();
$config->addComponent($orderable);

// This test data is versioned - ensure we're all published
$parent->publishRecursive();
// there should be no difference between stages at this point
foreach ($parent->Belongings() as $item) {
$this->assertFalse(
$item->stagesDiffer(),
'No records should be different from their published versions'
);
}

$grid = new GridField(
'Belongings',
'Testing Many Many',
$parent->Belongings()->sort($sortName),
$config
);

$originalOrder = $parent->Belongings()->sort($sortName)->column('ID');
$desiredOrder = [];

// Make order non-contiguous, and 1-based
foreach (array_reverse($originalOrder) as $index => $id) {
$desiredOrder[$index * 2 + 1] = $id;
}

$this->assertNotEquals($originalOrder, $desiredOrder);

$reflection->invoke($orderable, $grid, $desiredOrder);

$newOrder = $parent->Belongings()->sort($sortName)->map($sortName, 'ID')->toArray();

$this->assertEquals($desiredOrder, $newOrder);

// pass forward to testReorderedPublish
return $parent;
}

/**
* @depends testReorderingSaves
* @param ThroughDefiner $parent passed through from testReorderingSaves
*/
public function testReorderedPublish($parent)
{
// reorder should have been handled as versioned - there should be a difference between stages now
$differenceFound = false;
foreach ($parent->Belongings() as $item) {
if ($item->stagesDiffer()) {
$differenceFound = true;
break;
}
}
$this->assertTrue($differenceFound, 'At least one record should be changed');

$parent->publishRecursive();

foreach ($parent->Belongings() as $item) {
$this->assertFalse(
$item->stagesDiffer(),
'No records should be different from their published versions anymore'
);
}
}
}
16 changes: 8 additions & 8 deletions tests/Stub/StubParent.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@

class StubParent extends DataObject implements TestOnly
{
private static $has_many = array(
private static $has_many = [
'MyHasMany' => StubOrdered::class,
'MyHasManySubclass' => StubSubclass::class,
'MyHasManySubclassOrderedVersioned' => StubSubclassOrderedVersioned::class,
);
];

private static $many_many = array(
'MyManyMany' => StubOrdered::class
);
private static $many_many = [
'MyManyMany' => StubOrdered::class,
];

private static $many_many_extraFields = array(
'MyManyMany' => array('ManyManySort' => 'Int')
);
private static $many_many_extraFields = [
'MyManyMany' => ['ManyManySort' => 'Int'],
];

private static $table_name = 'StubParent';
}
19 changes: 19 additions & 0 deletions tests/Stub/ThroughBelongs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Symbiote\GridFieldExtensions\Tests\Stub;

use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\TestOnly;

class ThroughBelongs extends DataObject implements TestOnly
{
private static $table_name = 'BelongsThrough';

private static $db = [
'Title' => 'Varchar',
];

private static $belongs_many_many = [
'Definers' => ThroughDefiner::class,
];
}
27 changes: 27 additions & 0 deletions tests/Stub/ThroughDefiner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Symbiote\GridFieldExtensions\Tests\Stub;

use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\TestOnly;

class ThroughDefiner extends DataObject implements TestOnly
{
private static $table_name = 'ManyThrough';

private static $db = [
'Title' => 'Varchar',
];

private static $many_many = [
'Belongings' => [
'through' => ThroughIntermediary::class,
'from' => 'Defining',
'to' => 'Belonging',
]
];

private static $owns = [
'Belongings'
];
}
Loading

0 comments on commit 08ed6f6

Please sign in to comment.