Compare commits
5 Commits
967f40249d
...
82491ef160
Author | SHA1 | Date |
---|---|---|
Maxime Rainville | 82491ef160 | |
github-actions | f60e1bc236 | |
Maxime Rainville | 47a7008f60 | |
Maxime Rainville | d42fa9e71f | |
Guy Sartorelli | 50a0018363 |
|
@ -1239,7 +1239,7 @@ class DataObjectSchema
|
|||
}
|
||||
|
||||
// Validate bad types on parent relation
|
||||
if ($key === 'from' && $relationClass !== $parentClass) {
|
||||
if ($key === 'from' && $relationClass !== $parentClass && !is_subclass_of($parentClass, $relationClass)) {
|
||||
throw new InvalidArgumentException(
|
||||
"many_many through relation {$parentClass}.{$component} {$key} references a field name "
|
||||
. "{$joinClass}::{$relation} of type {$relationClass}; {$parentClass} expected"
|
||||
|
|
|
@ -105,9 +105,9 @@ trait AttributesHTML
|
|||
|
||||
$attributes = (array) $attributes;
|
||||
|
||||
$attributes = array_filter($attributes ?? [], function ($v) {
|
||||
return ($v || $v === 0 || $v === '0');
|
||||
});
|
||||
$attributes = array_filter($attributes ?? [], function ($v, $k) {
|
||||
return ($k === 'alt' || $v || $v === 0 || $v === '0');
|
||||
}, ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
if ($exclude) {
|
||||
$attributes = array_diff_key(
|
||||
|
|
|
@ -25,10 +25,12 @@ class ManyManyThroughListTest extends SapphireTest
|
|||
ManyManyThroughListTest\Item::class,
|
||||
ManyManyThroughListTest\JoinObject::class,
|
||||
ManyManyThroughListTest\TestObject::class,
|
||||
ManyManyThroughListTest\TestObjectSubclass::class,
|
||||
ManyManyThroughListTest\PolyItem::class,
|
||||
ManyManyThroughListTest\PolyJoinObject::class,
|
||||
ManyManyThroughListTest\PolyObjectA::class,
|
||||
ManyManyThroughListTest\PolyObjectB::class,
|
||||
ManyManyThroughListTest\PseudoPolyJoinObject::class,
|
||||
ManyManyThroughListTest\Locale::class,
|
||||
ManyManyThroughListTest\FallbackLocale::class,
|
||||
];
|
||||
|
@ -161,46 +163,82 @@ class ManyManyThroughListTest extends SapphireTest
|
|||
];
|
||||
}
|
||||
|
||||
public function testAdd()
|
||||
public function provideAdd(): array
|
||||
{
|
||||
/** @var ManyManyThroughListTest\TestObject $parent */
|
||||
$parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
|
||||
return [
|
||||
[
|
||||
'parentClass' => ManyManyThroughListTest\TestObject::class,
|
||||
'joinClass' => ManyManyThroughListTest\JoinObject::class,
|
||||
'joinProperty' => 'ManyManyThroughListTest_JoinObject',
|
||||
'relation' => 'Items',
|
||||
],
|
||||
[
|
||||
'parentClass' => ManyManyThroughListTest\TestObjectSubclass::class,
|
||||
'joinClass' => ManyManyThroughListTest\PseudoPolyJoinObject::class,
|
||||
'joinProperty' => 'ManyManyThroughListTest_PseudoPolyJoinObject',
|
||||
'relation' => 'MoreItems',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideAdd
|
||||
*/
|
||||
public function testAdd(string $parentClass, string $joinClass, string $joinProperty, string $relation)
|
||||
{
|
||||
$parent = $this->objFromFixture($parentClass, 'parent1');
|
||||
$newItem = new ManyManyThroughListTest\Item();
|
||||
$newItem->Title = 'my new item';
|
||||
$newItem->write();
|
||||
$parent->Items()->add($newItem, ['Title' => 'new join record']);
|
||||
$parent->$relation()->add($newItem, ['Title' => 'new join record']);
|
||||
|
||||
// Check select
|
||||
$newItem = $parent->Items()->filter(['Title' => 'my new item'])->first();
|
||||
$newItem = $parent->$relation()->filter(['Title' => 'my new item'])->first();
|
||||
$this->assertNotNull($newItem);
|
||||
$this->assertEquals('my new item', $newItem->Title);
|
||||
$this->assertInstanceOf(
|
||||
ManyManyThroughListTest\JoinObject::class,
|
||||
$joinClass,
|
||||
$newItem->getJoin()
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
ManyManyThroughListTest\JoinObject::class,
|
||||
$newItem->ManyManyThroughListTest_JoinObject
|
||||
$joinClass,
|
||||
$newItem->$joinProperty
|
||||
);
|
||||
$this->assertEquals('new join record', $newItem->ManyManyThroughListTest_JoinObject->Title);
|
||||
$this->assertEquals('new join record', $newItem->$joinProperty->Title);
|
||||
}
|
||||
|
||||
public function testRemove()
|
||||
public function provideRemove(): array
|
||||
{
|
||||
/** @var ManyManyThroughListTest\TestObject $parent */
|
||||
$parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
|
||||
return [
|
||||
[
|
||||
'parentClass' => ManyManyThroughListTest\TestObject::class,
|
||||
'relation' => 'Items',
|
||||
],
|
||||
[
|
||||
'parentClass' => ManyManyThroughListTest\TestObjectSubclass::class,
|
||||
'relation' => 'MoreItems',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideRemove
|
||||
*/
|
||||
public function testRemove(string $parentClass, string $relation)
|
||||
{
|
||||
$parent = $this->objFromFixture($parentClass, 'parent1');
|
||||
$this->assertListEquals(
|
||||
[
|
||||
['Title' => 'item 1'],
|
||||
['Title' => 'item 2']
|
||||
],
|
||||
$parent->Items()
|
||||
$parent->$relation()
|
||||
);
|
||||
$item1 = $parent->Items()->filter(['Title' => 'item 1'])->first();
|
||||
$parent->Items()->remove($item1);
|
||||
$item1 = $parent->$relation()->filter(['Title' => 'item 1'])->first();
|
||||
$parent->$relation()->remove($item1);
|
||||
$this->assertListEquals(
|
||||
[['Title' => 'item 2']],
|
||||
$parent->Items()
|
||||
$parent->$relation()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,11 @@ SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObject:
|
|||
Title: 'my object'
|
||||
parent2:
|
||||
Title: 'my object2'
|
||||
SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObjectSubclass:
|
||||
parent1:
|
||||
Title: 'my object'
|
||||
parent2:
|
||||
Title: 'my object2'
|
||||
SilverStripe\ORM\Tests\ManyManyThroughListTest\Item:
|
||||
# Having this one first means the IDs of records aren't the same as the IDs of the join objects.
|
||||
child0:
|
||||
|
@ -30,6 +35,25 @@ SilverStripe\ORM\Tests\ManyManyThroughListTest\JoinObject:
|
|||
Title: 'join 4'
|
||||
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObject.parent2
|
||||
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Item.child2
|
||||
SilverStripe\ORM\Tests\ManyManyThroughListTest\PseudoPolyJoinObject:
|
||||
join1:
|
||||
Title: 'join 1'
|
||||
Sort: 4
|
||||
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObjectSubclass.parent1
|
||||
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Item.child1
|
||||
join2:
|
||||
Title: 'join 2'
|
||||
Sort: 2
|
||||
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObjectSubclass.parent1
|
||||
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Item.child2
|
||||
join3:
|
||||
Title: 'join 3'
|
||||
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObjectSubclass.parent2
|
||||
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Item.child1
|
||||
join4:
|
||||
Title: 'join 4'
|
||||
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObjectSubclass.parent2
|
||||
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Item.child2
|
||||
SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyObjectA:
|
||||
obja1:
|
||||
Title: 'object A1'
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\ManyManyThroughListTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* @property string $Title
|
||||
* @method TestObject Parent()
|
||||
* @method Item Child()
|
||||
*/
|
||||
class PseudoPolyJoinObject extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'ManyManyThroughListTest_PseudoPolyJoinObject';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
'Sort' => 'Int',
|
||||
];
|
||||
|
||||
private static $has_one = [
|
||||
'Parent' => TestObject::class,
|
||||
'Child' => Item::class,
|
||||
];
|
||||
|
||||
private static $default_sort = '"Sort" ASC';
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\Tests\ManyManyThroughListTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\ManyManyThroughList;
|
||||
|
||||
/**
|
||||
* Basic parent object
|
||||
*
|
||||
* @property string $Title
|
||||
* @method ManyManyThroughList Items()
|
||||
*/
|
||||
class TestObjectSubclass extends TestObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'ManyManyThroughListTest_TestObjectSubclass';
|
||||
|
||||
private static $db = [
|
||||
'Title' => 'Varchar'
|
||||
];
|
||||
|
||||
private static $many_many = [
|
||||
'MoreItems' => [
|
||||
'through' => PseudoPolyJoinObject::class,
|
||||
'from' => 'Parent',
|
||||
'to' => 'Child',
|
||||
]
|
||||
];
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\View\Tests;
|
||||
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\View\Tests\AttributesHTMLTest\DummyAttributesHTML;
|
||||
|
||||
class AttributesHTMLTest extends SapphireTest
|
||||
{
|
||||
|
||||
public function provideGetAttribute(): array
|
||||
{
|
||||
return [
|
||||
'empty string' => ['test', '', 'Empty string is not converted to a different falsy value'],
|
||||
'Zero' => ['test', 0, 'Zero is not converted to a different falsy value'],
|
||||
'Null' => ['test', 0, 'Null is not converted to a different falsy value'],
|
||||
'False' => ['test', false, 'False is not converted to a different falsy value'],
|
||||
'Empty array' => ['test', [], 'Empty array is not converted to a different falsy value'],
|
||||
'True' => ['test', true, 'True is stored properly as an attribute'],
|
||||
'String' => ['test', 'test', 'String is stored properly as an attribute'],
|
||||
'Int' => ['test', -1, 'Int is stored properly as an attribute'],
|
||||
'Array' => ['test', ['foo' => 'bar'], 'Array is stored properly as an attribute'],
|
||||
];
|
||||
}
|
||||
|
||||
/** @dataProvider provideGetAttribute */
|
||||
public function testGetAttribute($name, $value, $message): void
|
||||
{
|
||||
$dummy = new DummyAttributesHTML();
|
||||
$this->assertNull(
|
||||
$dummy->getAttribute('non-existent attribute'),
|
||||
'Trying to access a non-existent attribute should return null'
|
||||
);
|
||||
|
||||
$dummy->setAttribute($name, $value);
|
||||
$this->assertSame(
|
||||
$value,
|
||||
$dummy->getAttribute($name),
|
||||
$message
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public function testGetAttributes(): void
|
||||
{
|
||||
$dummy = new DummyAttributesHTML();
|
||||
$dummy->setDefaultAttributes([]);
|
||||
$this->assertSame(
|
||||
[],
|
||||
$dummy->getAttributes(),
|
||||
'When no attributes are set and the default attributes are empty, an empty array should be returned'
|
||||
);
|
||||
|
||||
$dummy->setAttribute('empty', '');
|
||||
$dummy->setAttribute('foo', 'bar');
|
||||
$dummy->setAttribute('Number', 123);
|
||||
$dummy->setAttribute('Array', ['foo' => 'bar']);
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
'empty' => '',
|
||||
'foo' => 'bar',
|
||||
'Number' => 123,
|
||||
'Array' => ['foo' => 'bar'],
|
||||
],
|
||||
$dummy->getAttributes(),
|
||||
'All explicitly defined attributes should be returned'
|
||||
);
|
||||
|
||||
$dummy = new DummyAttributesHTML();
|
||||
$dummy->setDefaultAttributes([
|
||||
'foo' => 'Will be overridden',
|
||||
'bar' => 'Not overridden',
|
||||
]);
|
||||
$this->assertSame(
|
||||
[
|
||||
'foo' => 'Will be overridden',
|
||||
'bar' => 'Not overridden',
|
||||
],
|
||||
$dummy->getAttributes(),
|
||||
'When no attributes are set and the default attributes are used'
|
||||
);
|
||||
|
||||
$dummy->setAttribute('empty', '');
|
||||
$dummy->setAttribute('foo', 'bar');
|
||||
$dummy->setAttribute('Number', 123);
|
||||
$dummy->setAttribute('Array', ['foo' => 'bar']);
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
'foo' => 'bar',
|
||||
'bar' => 'Not overridden',
|
||||
'empty' => '',
|
||||
'Number' => 123,
|
||||
'Array' => ['foo' => 'bar'],
|
||||
],
|
||||
$dummy->getAttributes(),
|
||||
'Explicitly defined attributes overrides default ones'
|
||||
);
|
||||
}
|
||||
|
||||
public function testAttributesHTML(): void
|
||||
{
|
||||
$dummy = new DummyAttributesHTML();
|
||||
|
||||
$dummy->setAttribute('emptystring', '');
|
||||
$dummy->setAttribute('nullvalue', null);
|
||||
$dummy->setAttribute('false', false);
|
||||
$dummy->setAttribute('emptyarray', []);
|
||||
$dummy->setAttribute('zeroint', 0);
|
||||
$dummy->setAttribute('zerostring', '0');
|
||||
$dummy->setAttribute('alt', '');
|
||||
$dummy->setAttribute('array', ['foo' => 'bar']);
|
||||
$dummy->setAttribute('width', 123);
|
||||
$dummy->setAttribute('hack>', '">hack');
|
||||
|
||||
$html = $dummy->getAttributesHTML();
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'emptystring',
|
||||
$html,
|
||||
'Attribute with empty string are not rendered'
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'nullvalue',
|
||||
$html,
|
||||
'Attribute with null are not rendered'
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'false',
|
||||
$html,
|
||||
'Attribute with false are not rendered'
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'emptyarray',
|
||||
$html,
|
||||
'Attribute with empty array are not rendered'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'zeroint="0"',
|
||||
$html,
|
||||
'Attribute with a zero int value are rendered'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'zerostring="0"',
|
||||
$html,
|
||||
'Attribute with a zerostring value are rendered'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'alt=""',
|
||||
$html,
|
||||
'alt attribute is rendered even when empty set to an empty string'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'array="{"foo":"bar"}"',
|
||||
$html,
|
||||
'Array attribute is converted to JSON'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'width="123"',
|
||||
$html,
|
||||
'Numeric values are rendered with quotes'
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'hack">="">hack"',
|
||||
$html,
|
||||
'Attribute names and value are escaped'
|
||||
);
|
||||
|
||||
$html = $dummy->getAttributesHTML('zeroint', 'array');
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'zeroint="0"',
|
||||
$html,
|
||||
'Excluded attributes are not rendered'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'zerostring="0"',
|
||||
$html,
|
||||
'Attribute not excluded still render'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'alt=""',
|
||||
$html,
|
||||
'Attribute not excluded still render'
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'array',
|
||||
$html,
|
||||
'Excluded attributes are not rendered'
|
||||
);
|
||||
}
|
||||
|
||||
public function testAttributesHTMLwithExplicitAttr(): void
|
||||
{
|
||||
$dummy = new DummyAttributesHTML();
|
||||
|
||||
$this->assertEmpty(
|
||||
'',
|
||||
$dummy->getAttributesHTML(),
|
||||
'If no attributes are provided, an empty string should be returned'
|
||||
);
|
||||
|
||||
$attributes = [
|
||||
'emptystring' => '',
|
||||
'nullvalue' => null,
|
||||
'false' => false,
|
||||
'emptyarray' => [],
|
||||
'zeroint' => 0,
|
||||
'zerostring' => '0',
|
||||
'alt' => '',
|
||||
'array' => ['foo' => 'bar'],
|
||||
'width' => 123,
|
||||
'hack>' => '">hack',
|
||||
];
|
||||
|
||||
$html = $dummy->getAttributesHTML($attributes);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'emptystring',
|
||||
$html,
|
||||
'Attribute with empty string are not rendered'
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'nullvalue',
|
||||
$html,
|
||||
'Attribute with null are not rendered'
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'false',
|
||||
$html,
|
||||
'Attribute with false are not rendered'
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'emptyarray',
|
||||
$html,
|
||||
'Attribute with empty array are not rendered'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'zeroint="0"',
|
||||
$html,
|
||||
'Attribute with a zero int value are rendered'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'zerostring="0"',
|
||||
$html,
|
||||
'Attribute with a zerostring value are rendered'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'alt=""',
|
||||
$html,
|
||||
'alt attribute is rendered even when empty set to an empty string'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'array="{"foo":"bar"}"',
|
||||
$html,
|
||||
'Array attribute is converted to JSON'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'width="123"',
|
||||
$html,
|
||||
'Numeric values are rendered with quotes'
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(
|
||||
'hack">="">hack"',
|
||||
$html,
|
||||
'Attribute names and value are escaped'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\View\Tests\AttributesHTMLTest;
|
||||
|
||||
use SilverStripe\View\AttributesHTML;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
|
||||
/**
|
||||
* This call is used to test the AttributesHTML trait
|
||||
*/
|
||||
class DummyAttributesHTML implements TestOnly
|
||||
{
|
||||
use AttributesHTML;
|
||||
|
||||
private array $defaultAttributes = [];
|
||||
|
||||
/**
|
||||
* Trait requires this method to prepopulate the attributes
|
||||
*/
|
||||
protected function getDefaultAttributes(): array
|
||||
{
|
||||
return $this->defaultAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only there to allow to explicitly set the default attributes in the test.
|
||||
*/
|
||||
public function setDefaultAttributes(array $attributes): void
|
||||
{
|
||||
$this->defaultAttributes = $attributes;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue