MNT Add tests for using GridField with arbitrary data

Note that the main tests are added as behat tests in the admin module
This commit is contained in:
Guy Sartorelli 2023-11-23 17:24:52 +13:00
parent 2cafba2bd5
commit 7073246a37
No known key found for this signature in database
GPG Key ID: F313E3B9504D496A
15 changed files with 902 additions and 14 deletions

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Forms\Tests\GridField; namespace SilverStripe\Forms\Tests\GridField;
use LogicException;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Dev\CSSContentParser; use SilverStripe\Dev\CSSContentParser;
@ -17,6 +18,7 @@ use SilverStripe\Forms\Tests\GridField\GridFieldTest\Player;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Stadium; use SilverStripe\Forms\Tests\GridField\GridFieldTest\Stadium;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Team; use SilverStripe\Forms\Tests\GridField\GridFieldTest\Team;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
class GridFieldAddExistingAutocompleterTest extends FunctionalTest class GridFieldAddExistingAutocompleterTest extends FunctionalTest
{ {
@ -167,6 +169,118 @@ class GridFieldAddExistingAutocompleterTest extends FunctionalTest
); );
} }
public function testGetHTMLFragmentsNeedsDataObject()
{
$component = new GridFieldAddExistingAutocompleter();
$gridField = $this->getGridFieldForComponent($component);
$list = new ArrayList();
$dataClass = ArrayData::class;
$list->setDataClass($dataClass);
$gridField->setList($list);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
GridFieldAddExistingAutocompleter::class
. " must be used with DataObject subclasses. Found '$dataClass'"
);
// Calling the method will throw an exception.
$component->getHTMLFragments($gridField);
}
public function testGetManipulatedDataNeedsDataObject()
{
$component = new GridFieldAddExistingAutocompleter();
$gridField = $this->getGridFieldForComponent($component);
$list = new ArrayList();
$dataClass = ArrayData::class;
$list->setDataClass($dataClass);
$gridField->setList($list);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
GridFieldAddExistingAutocompleter::class
. " must be used with DataObject subclasses. Found '$dataClass'"
);
// Calling the method will throw an exception.
$component->getManipulatedData($gridField, $list);
}
public function testDoSearchNeedsDataObject()
{
$component = new GridFieldAddExistingAutocompleter();
$gridField = $this->getGridFieldForComponent($component);
$list = new ArrayList();
$dataClass = ArrayData::class;
$list->setDataClass($dataClass);
$gridField->setList($list);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
GridFieldAddExistingAutocompleter::class
. " must be used with DataObject subclasses. Found '$dataClass'"
);
// Calling the method will throw an exception.
$component->doSearch($gridField, new HTTPRequest('GET', ''));
}
public function testScaffoldSearchFieldsNeedsDataObject()
{
$component = new GridFieldAddExistingAutocompleter();
$gridField = $this->getGridFieldForComponent($component);
$list = new ArrayList();
$dataClass = ArrayData::class;
$list->setDataClass($dataClass);
$gridField->setList($list);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
GridFieldAddExistingAutocompleter::class
. " must be used with DataObject subclasses. Found '$dataClass'"
);
// Calling the method will either throw an exception or not.
// The test pass/failure is explicitly about whether an exception is thrown.
$component->scaffoldSearchFields($dataClass);
}
public function testGetPlaceholderTextNeedsDataObject()
{
$component = new GridFieldAddExistingAutocompleter();
$gridField = $this->getGridFieldForComponent($component);
$list = new ArrayList();
$dataClass = ArrayData::class;
$list->setDataClass($dataClass);
$gridField->setList($list);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
GridFieldAddExistingAutocompleter::class
. " must be used with DataObject subclasses. Found '$dataClass'"
);
// Calling the method will either throw an exception or not.
// The test pass/failure is explicitly about whether an exception is thrown.
$component->getPlaceholderText($dataClass);
}
public function testSetPlaceholderTextDoesntNeedDataObject()
{
$component = new GridFieldAddExistingAutocompleter();
$gridField = $this->getGridFieldForComponent($component);
$list = new ArrayList();
$dataClass = ArrayData::class;
$list->setDataClass($dataClass);
$gridField->setList($list);
// Prevent from being marked risky.
// This test passes if there's no exception thrown.
$this->expectNotToPerformAssertions();
$component->setPlaceholderText('');
}
protected function getGridFieldForComponent($component) protected function getGridFieldForComponent($component)
{ {
$config = GridFieldConfig::create()->addComponents( $config = GridFieldConfig::create()->addComponents(

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Forms\Tests\GridField; namespace SilverStripe\Forms\Tests\GridField;
use LogicException;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
@ -9,11 +10,14 @@ use SilverStripe\Forms\Form;
use SilverStripe\Forms\GridField\GridField; use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldAddNewButton; use SilverStripe\Forms\GridField\GridFieldAddNewButton;
use SilverStripe\Forms\GridField\GridFieldConfig; use SilverStripe\Forms\GridField\GridFieldConfig;
use SilverStripe\Forms\GridField\GridFieldConfig_Base;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\Person; use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\Person;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\PeopleGroup; use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\PeopleGroup;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\Category; use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\Category;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\TestController; use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\TestController;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\SS_List; use SilverStripe\ORM\SS_List;
use SilverStripe\View\ArrayData;
class GridFieldAddNewButtonTest extends SapphireTest class GridFieldAddNewButtonTest extends SapphireTest
{ {
@ -76,6 +80,24 @@ class GridFieldAddNewButtonTest extends SapphireTest
$this->mockButtonFragments($list, null); $this->mockButtonFragments($list, null);
} }
public function testGetHTMLFragmentsThrowsException()
{
$component = new GridFieldAddNewButton();
$config = new GridFieldConfig_Base();
$config->addComponent($component);
$gridField = new GridField('dummy', 'dummy', new ArrayList(), $config);
$modelClass = ArrayData::class;
$gridField->setModelClass($modelClass);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
GridFieldAddNewButton::class . ' cannot be used with models that do not implement canCreate().'
. " Remove this component from your GridField or implement canCreate() on $modelClass"
);
$component->getHTMLFragments($gridField);
}
protected function mockButtonFragments(SS_List $list, $parent = null) protected function mockButtonFragments(SS_List $list, $parent = null)
{ {
$form = Form::create( $form = Form::create(

View File

@ -3,15 +3,18 @@
namespace SilverStripe\Forms\Tests\GridField; namespace SilverStripe\Forms\Tests\GridField;
use InvalidArgumentException; use InvalidArgumentException;
use LogicException;
use SilverStripe\Forms\GridField\GridFieldDataColumns; use SilverStripe\Forms\GridField\GridFieldDataColumns;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\GridField\GridField; use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_Base;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
use stdClass; use stdClass;
class GridFieldDataColumnsTest extends SapphireTest class GridFieldDataColumnsTest extends SapphireTest
{ {
/** /**
* @covers \SilverStripe\Forms\GridField\GridFieldDataColumns::getDisplayFields * @covers \SilverStripe\Forms\GridField\GridFieldDataColumns::getDisplayFields
*/ */
@ -23,6 +26,19 @@ class GridFieldDataColumnsTest extends SapphireTest
$this->assertEquals($expected, $columns->getDisplayFields($obj)); $this->assertEquals($expected, $columns->getDisplayFields($obj));
} }
/**
* @covers \SilverStripe\Forms\GridField\GridFieldDataColumns::getDisplayFields
*/
public function testGridFieldGetDisplayFieldsWithArrayList()
{
$list = new ArrayList([new ArrayData(['Title' => 'My Item'])]);
$obj = new GridField('testfield', 'testfield', $list);
$expected = ['Title' => 'Title'];
$columns = $obj->getConfig()->getComponentByType(GridFieldDataColumns::class);
$columns->setDisplayFields($expected);
$this->assertEquals($expected, $columns->getDisplayFields($obj));
}
/** /**
* @covers \SilverStripe\Forms\GridField\GridFieldDataColumns::setDisplayFields * @covers \SilverStripe\Forms\GridField\GridFieldDataColumns::setDisplayFields
* @covers \SilverStripe\Forms\GridField\GridFieldDataColumns::getDisplayFields * @covers \SilverStripe\Forms\GridField\GridFieldDataColumns::getDisplayFields
@ -76,4 +92,22 @@ class GridFieldDataColumnsTest extends SapphireTest
$columns->getFieldFormatting() $columns->getFieldFormatting()
); );
} }
public function testGetDisplayFieldsThrowsException()
{
$component = new GridFieldDataColumns();
$config = new GridFieldConfig_Base();
$config->addComponent($component);
$gridField = new GridField('dummy', 'dummy', new ArrayList(), $config);
$modelClass = ArrayData::class;
$gridField->setModelClass($modelClass);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
'Cannot dynamically determine columns. Pass the column names to setDisplayFields()'
. " or implement a summaryFields() method on $modelClass"
);
$component->getDisplayFields($gridField);
}
} }

View File

@ -2,6 +2,8 @@
namespace SilverStripe\Forms\Tests\GridField; namespace SilverStripe\Forms\Tests\GridField;
use LogicException;
use ReflectionMethod;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Control\HTTPResponse_Exception;
@ -12,6 +14,7 @@ use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
use SilverStripe\Forms\GridField\GridField; use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig; use SilverStripe\Forms\GridField\GridFieldConfig;
use SilverStripe\Forms\GridField\GridFieldConfig_Base;
use SilverStripe\Forms\GridField\GridFieldDeleteAction; use SilverStripe\Forms\GridField\GridFieldDeleteAction;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader; use SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Permissions; use SilverStripe\Forms\Tests\GridField\GridFieldTest\Permissions;
@ -22,6 +25,7 @@ use SilverStripe\ORM\DataList;
use SilverStripe\ORM\ValidationException; use SilverStripe\ORM\ValidationException;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\Security\SecurityToken; use SilverStripe\Security\SecurityToken;
use SilverStripe\View\ArrayData;
class GridFieldDeleteActionTest extends SapphireTest class GridFieldDeleteActionTest extends SapphireTest
{ {
@ -230,4 +234,70 @@ class GridFieldDeleteActionTest extends SapphireTest
$group = $action->getGroup($gridField, $this->list->first(), 'dummy'); $group = $action->getGroup($gridField, $this->list->first(), 'dummy');
$this->assertNull($group, 'A menu group does not exist when the user cannot delete'); $this->assertNull($group, 'A menu group does not exist when the user cannot delete');
} }
public function provideHandleActionThrowsException()
{
return [
'unlinks relation' => [true],
'deletes related record' => [false],
];
}
/**
* @dataProvider provideHandleActionThrowsException
*/
public function testHandleActionThrowsException(bool $unlinkRelation)
{
$component = new GridFieldDeleteAction();
$config = new GridFieldConfig_Base();
$config->addComponent($component);
$gridField = new GridField('dummy', 'dummy', new ArrayList([new ArrayData(['ID' => 1])]), $config);
$modelClass = ArrayData::class;
$gridField->setModelClass($modelClass);
$this->expectException(LogicException::class);
$permissionMethod = $unlinkRelation ? 'canEdit' : 'canDelete';
$this->expectExceptionMessage(
GridFieldDeleteAction::class . " cannot be used with models that don't implement {$permissionMethod}()."
. " Remove this component from your GridField or implement {$permissionMethod}() on $modelClass"
);
// Calling the method will throw an exception.
$secondArg = $unlinkRelation ? 'unlinkrelation' : 'deleterecord';
$component->handleAction($gridField, $secondArg, ['RecordID' => 1], []);
}
public function provideGetRemoveActionThrowsException()
{
return [
'removes relation' => [true],
'deletes related record' => [false],
];
}
/**
* @dataProvider provideGetRemoveActionThrowsException
*/
public function testGetRemoveActionThrowsException(bool $removeRelation)
{
$component = new GridFieldDeleteAction();
$component->setRemoveRelation($removeRelation);
$config = new GridFieldConfig_Base();
$config->addComponent($component);
$gridField = new GridField('dummy', 'dummy', new ArrayList([new ArrayData(['ID' => 1])]), $config);
$modelClass = ArrayData::class;
$gridField->setModelClass($modelClass);
$this->expectException(LogicException::class);
$permissionMethod = $removeRelation ? 'canEdit' : 'canDelete';
$this->expectExceptionMessage(
GridFieldDeleteAction::class . " cannot be used with models that don't implement {$permissionMethod}()."
. " Remove this component from your GridField or implement {$permissionMethod}() on $modelClass"
);
// Calling the method will throw an exception.
$reflectionMethod = new ReflectionMethod($component, 'getRemoveAction');
$reflectionMethod->setAccessible(true);
$reflectionMethod->invokeArgs($component, [$gridField, new ArrayData(), '']);
}
} }

View File

@ -2,14 +2,20 @@
namespace SilverStripe\Forms\Tests\GridField; namespace SilverStripe\Forms\Tests\GridField;
use LogicException;
use ReflectionMethod;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Dev\CSSContentParser; use SilverStripe\Dev\CSSContentParser;
use SilverStripe\Dev\FunctionalTest; use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
use SilverStripe\Forms\GridField\GridField; use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
use SilverStripe\Forms\GridField\GridFieldDetailForm; use SilverStripe\Forms\GridField\GridFieldDetailForm;
use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest; use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest;
use SilverStripe\Forms\HiddenField; use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\ArrayDataWithID;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\Category; use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\Category;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\CategoryController; use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\CategoryController;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\GroupController; use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\GroupController;
@ -17,6 +23,8 @@ use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\PeopleGroup;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\Person; use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\Person;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\PolymorphicPeopleGroup; use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\PolymorphicPeopleGroup;
use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\TestController; use SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest\TestController;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
class GridFieldDetailFormTest extends FunctionalTest class GridFieldDetailFormTest extends FunctionalTest
{ {
@ -460,4 +468,121 @@ class GridFieldDetailFormTest extends FunctionalTest
$this->autoFollowRedirection = $origAutoFollow; $this->autoFollowRedirection = $origAutoFollow;
} }
public function provideGetRecordFromRequestFindExisting()
{
return [
'No records' => [
'data' => [],
'hasRecord' => false,
],
'Records exist but without ID field' => [
'data' => [new ArrayDataWithID()],
'hasRecord' => false,
],
'Record exists with matching ID' => [
'data' => [new ArrayDataWithID(['ID' => 32])],
'hasRecord' => true,
],
'Record exists, no matching ID' => [
'data' => [new ArrayDataWithID(['ID' => 1])],
'hasRecord' => false,
],
];
}
/**
* @dataProvider provideGetRecordFromRequestFindExisting
*/
public function testGetRecordFromRequestFindExisting(array $data, bool $hasRecord)
{
$controller = new TestController();
$form = $controller->Form(null, new ArrayList($data));
$gridField = $form->Fields()->dataFieldByName('testfield');
if (empty($data)) {
$gridField->setModelClass(ArrayDataWithID::class);
}
$component = $gridField->getConfig()->getComponentByType(GridFieldDetailForm::class);
$request = new HTTPRequest('GET', $gridField->Link('item/32'));
$request->match(Controller::join_links($gridField->Link(), 'item/$ID'));
$reflectionMethod = new ReflectionMethod($component, 'getRecordFromRequest');
$reflectionMethod->setAccessible(true);
$this->assertSame($hasRecord, (bool) $reflectionMethod->invoke($component, $gridField, $request));
}
public function provideGetRecordFromRequestCreateNew()
{
// Note that in all of these scenarios a new record gets created, so it *shouldn't* matter what's already in there.
return [
'No records' => [
'data' => [],
],
'Records exist but without ID field' => [
'data' => [new ArrayDataWithID()],
],
'Record exists with ID field' => [
'data' => [new ArrayDataWithID(['ID' => 32])],
],
];
}
/**
* @dataProvider provideGetRecordFromRequestCreateNew
*/
public function testGetRecordFromRequestCreateNew(array $data)
{
$controller = new TestController();
$form = $controller->Form(null, new ArrayList($data));
$gridField = $form->Fields()->dataFieldByName('testfield');
if (empty($data)) {
$gridField->setModelClass(ArrayDataWithID::class);
}
$component = $gridField->getConfig()->getComponentByType(GridFieldDetailForm::class);
$request = new HTTPRequest('GET', $gridField->Link('item/new'));
$request->match(Controller::join_links($gridField->Link(), 'item/$ID'));
$reflectionMethod = new ReflectionMethod($component, 'getRecordFromRequest');
$reflectionMethod->setAccessible(true);
$this->assertEquals(new ArrayDataWithID(['ID' => 0]), $reflectionMethod->invoke($component, $gridField, $request));
}
public function provideGetRecordFromRequestWithoutData()
{
// Note that in all of these scenarios a new record gets created, so it *shouldn't* matter what's already in there.
return [
'No records' => [
'data' => [],
],
'Records exist but without ID field' => [
'data' => [new ArrayData()],
],
'Record exists with ID field' => [
'data' => [new ArrayData(['ID' => 32])],
],
];
}
/**
* @dataProvider provideGetRecordFromRequestWithoutData
*/
public function testGetRecordFromRequestWithoutData(array $data)
{
$controller = new TestController();
$form = $controller->Form(null, new ArrayList($data));
$gridField = $form->Fields()->dataFieldByName('testfield');
if (empty($data)) {
$gridField->setModelClass(ArrayData::class);
}
$component = $gridField->getConfig()->getComponentByType(GridFieldDetailForm::class);
$request = new HTTPRequest('GET', $gridField->Link('item/new'));
$request->match(Controller::join_links($gridField->Link(), 'item/$ID'));
$this->expectException(LogicException::class);
$this->expectExceptionMessage(ArrayData::class . ' must have an ID field.');
$reflectionMethod = new ReflectionMethod($component, 'getRecordFromRequest');
$reflectionMethod->setAccessible(true);
$reflectionMethod->invoke($component, $gridField, $request);
}
} }

View File

@ -0,0 +1,15 @@
<?php
namespace SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\View\ArrayData;
class ArrayDataWithID extends ArrayData implements TestOnly
{
public function __construct($value = [])
{
$value['ID'] ??= 0;
parent::__construct($value);
}
}

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest; namespace SilverStripe\Forms\Tests\GridField\GridFieldDetailFormTest;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Dev\TestOnly; use SilverStripe\Dev\TestOnly;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
@ -12,6 +13,7 @@ use SilverStripe\Forms\GridField\GridFieldDetailForm;
use SilverStripe\Forms\GridField\GridFieldEditButton; use SilverStripe\Forms\GridField\GridFieldEditButton;
use SilverStripe\Forms\GridField\GridFieldToolbarHeader; use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
use SilverStripe\Forms\GridField\GridFieldViewButton; use SilverStripe\Forms\GridField\GridFieldViewButton;
use SilverStripe\ORM\SS_List;
class TestController extends Controller implements TestOnly class TestController extends Controller implements TestOnly
{ {
@ -32,14 +34,17 @@ class TestController extends Controller implements TestOnly
protected $template = 'BlankPage'; protected $template = 'BlankPage';
public function Form() public function Form(?HTTPRequest $request = null, ?SS_List $list = null)
{ {
$group = PeopleGroup::get() if (!$list) {
->filter('Name', 'My Group') $group = PeopleGroup::get()
->sort('Name') ->filter('Name', 'My Group')
->First(); ->sort('Name')
->First();
$list = $group->People();
}
$field = new GridField('testfield', 'testfield', $group->People()); $field = new GridField('testfield', 'testfield', $list);
$field->getConfig()->addComponent(new GridFieldToolbarHeader()); $field->getConfig()->addComponent(new GridFieldToolbarHeader());
$field->getConfig()->addComponent(new GridFieldAddNewButton('toolbar-header-right')); $field->getConfig()->addComponent(new GridFieldAddNewButton('toolbar-header-right'));
$field->getConfig()->addComponent(new GridFieldViewButton()); $field->getConfig()->addComponent(new GridFieldViewButton());

View File

@ -0,0 +1,34 @@
<?php
namespace SilverStripe\Forms\Tests\GridField;
use LogicException;
use SilverStripe\Control\Controller;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_Base;
use SilverStripe\Forms\GridField\GridFieldDetailForm;
use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
class GridFieldDetailForm_ItemRequestTest extends SapphireTest
{
protected $usesDatabase = false;
public function testItemEditFormThrowsException()
{
$gridField = new GridField('dummy', 'dummy', new ArrayList(), new GridFieldConfig_Base());
$modelClass = ArrayData::class;
$gridField->setModelClass($modelClass);
$itemRequest = new GridFieldDetailForm_ItemRequest($gridField, new GridFieldDetailForm(), new ArrayData(), new Controller(), '');
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
'Cannot dynamically determine form fields. Pass the fields to GridFieldDetailForm::setFields()'
. " or implement a getCMSFields() method on $modelClass"
);
$itemRequest->ItemEditForm();
}
}

View File

@ -3,6 +3,8 @@
namespace SilverStripe\Forms\Tests\GridField; namespace SilverStripe\Forms\Tests\GridField;
use League\Csv\Reader; use League\Csv\Reader;
use LogicException;
use ReflectionMethod;
use SilverStripe\Forms\Tests\GridField\GridFieldExportButtonTest\NoView; use SilverStripe\Forms\Tests\GridField\GridFieldExportButtonTest\NoView;
use SilverStripe\Forms\Tests\GridField\GridFieldExportButtonTest\Team; use SilverStripe\Forms\Tests\GridField\GridFieldExportButtonTest\Team;
use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataList;
@ -12,8 +14,10 @@ use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\GridField\GridFieldConfig; use SilverStripe\Forms\GridField\GridFieldConfig;
use SilverStripe\Forms\GridField\GridFieldExportButton; use SilverStripe\Forms\GridField\GridFieldExportButton;
use SilverStripe\Forms\GridField\GridField; use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldDataColumns;
use SilverStripe\Forms\GridField\GridFieldPaginator; use SilverStripe\Forms\GridField\GridFieldPaginator;
use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\View\ArrayData;
class GridFieldExportButtonTest extends SapphireTest class GridFieldExportButtonTest extends SapphireTest
{ {
@ -155,13 +159,16 @@ class GridFieldExportButtonTest extends SapphireTest
public function testArrayListInput() public function testArrayListInput()
{ {
$button = new GridFieldExportButton(); $button = new GridFieldExportButton();
$columns = new GridFieldDataColumns();
$columns->setDisplayFields(['ID' => 'ID']);
$this->gridField->getConfig()->addComponent($columns);
$this->gridField->getConfig()->addComponent(new GridFieldPaginator()); $this->gridField->getConfig()->addComponent(new GridFieldPaginator());
//Create an ArrayList 1 greater the Paginator's default 15 rows //Create an ArrayList 1 greater the Paginator's default 15 rows
$arrayList = new ArrayList(); $arrayList = new ArrayList();
for ($i = 1; $i <= 16; $i++) { for ($i = 1; $i <= 16; $i++) {
$dataobject = new DataObject(['ID' => $i]); $datum = new ArrayData(['ID' => $i]);
$arrayList->add($dataobject); $arrayList->add($datum);
} }
$this->gridField->setList($arrayList); $this->gridField->setList($arrayList);
@ -192,6 +199,25 @@ class GridFieldExportButtonTest extends SapphireTest
); );
} }
public function testGetExportColumnsForGridFieldThrowsException()
{
$component = new GridFieldExportButton();
$gridField = new GridField('dummy', 'dummy', new ArrayList());
$gridField->getConfig()->removeComponentsByType(GridFieldDataColumns::class);
$modelClass = ArrayData::class;
$gridField->setModelClass($modelClass);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
'Cannot dynamically determine columns. Add a GridFieldDataColumns component to your GridField'
. " or implement a summaryFields() method on $modelClass"
);
$reflectionMethod = new ReflectionMethod($component, 'getExportColumnsForGridField');
$reflectionMethod->setAccessible(true);
$reflectionMethod->invoke($component, $gridField);
}
protected function createReader($string) protected function createReader($string)
{ {
$reader = Reader::createFromString($string); $reader = Reader::createFromString($string);

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Forms\Tests\GridField; namespace SilverStripe\Forms\Tests\GridField;
use LogicException;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
@ -20,6 +21,7 @@ use SilverStripe\Forms\Tests\GridField\GridFieldFilterHeaderTest\TeamGroup;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\View\ArrayData;
class GridFieldFilterHeaderTest extends SapphireTest class GridFieldFilterHeaderTest extends SapphireTest
{ {
@ -226,4 +228,20 @@ class GridFieldFilterHeaderTest extends SapphireTest
$this->assertNull($htmlFragment); $this->assertNull($htmlFragment);
} }
public function testGetDisplayFieldsThrowsException()
{
$component = new GridFieldFilterHeader();
$gridField = new GridField('dummy', 'dummy', new ArrayList());
$modelClass = ArrayData::class;
$gridField->setModelClass($modelClass);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
'Cannot dynamically instantiate SearchContext. Pass the SearchContext to setSearchContext()'
. " or implement a getDefaultSearchContext() method on $modelClass"
);
$component->getSearchContext($gridField);
}
} }

View File

@ -0,0 +1,29 @@
<?php
namespace SilverStripe\Forms\Tests\GridField;
use LogicException;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldLevelup;
use SilverStripe\View\ArrayData;
class GridFieldLevelupTest extends SapphireTest
{
protected $usesDatabase = false;
public function testGetHTMLFragmentsThrowsException()
{
$component = new GridFieldLevelup(0);
$gridField = new GridField('dummy');
$modelClass = ArrayData::class;
$gridField->setModelClass($modelClass);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
GridFieldLevelup::class . " must be used with DataObject subclasses. Found '$modelClass'"
);
$component->getHTMLFragments($gridField);
}
}

View File

@ -2,6 +2,8 @@
namespace SilverStripe\Forms\Tests\GridField; namespace SilverStripe\Forms\Tests\GridField;
use LogicException;
use ReflectionMethod;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
@ -10,7 +12,10 @@ use SilverStripe\Forms\GridField\GridFieldPrintButton;
use SilverStripe\Forms\GridField\GridFieldConfig; use SilverStripe\Forms\GridField\GridFieldConfig;
use SilverStripe\Forms\GridField\GridFieldPaginator; use SilverStripe\Forms\GridField\GridFieldPaginator;
use SilverStripe\Forms\GridField\GridField; use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldDataColumns;
use SilverStripe\Forms\Tests\GridField\GridFieldPrintButtonTest\TestObject; use SilverStripe\Forms\Tests\GridField\GridFieldPrintButtonTest\TestObject;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
class GridFieldPrintButtonTest extends SapphireTest class GridFieldPrintButtonTest extends SapphireTest
{ {
@ -33,21 +38,19 @@ class GridFieldPrintButtonTest extends SapphireTest
public function testLimit() public function testLimit()
{ {
$this->assertEquals(42, $this->getTestableRows()->count()); $this->assertEquals(42, $this->getTestableRows(TestObject::get())->count());
} }
public function testCanViewIsRespected() public function testCanViewIsRespected()
{ {
$orig = TestObject::$canView; $orig = TestObject::$canView;
TestObject::$canView = false; TestObject::$canView = false;
$this->assertEquals(0, $this->getTestableRows()->count()); $this->assertEquals(0, $this->getTestableRows(TestObject::get())->count());
TestObject::$canView = $orig; TestObject::$canView = $orig;
} }
private function getTestableRows() private function getTestableRows($list)
{ {
$list = TestObject::get();
$button = new GridFieldPrintButton(); $button = new GridFieldPrintButton();
$button->setPrintColumns(['Name' => 'My Name']); $button->setPrintColumns(['Name' => 'My Name']);
@ -62,4 +65,50 @@ class GridFieldPrintButtonTest extends SapphireTest
$printData = $button->generatePrintData($gridField); $printData = $button->generatePrintData($gridField);
return $printData->ItemRows; return $printData->ItemRows;
} }
public function testGeneratePrintData()
{
$names = [
'Bob',
'Alice',
'John',
'Jane',
'Sam',
];
$list = new ArrayList();
foreach ($names as $name) {
$list->add(new ArrayData(['Name' => $name]));
}
$rows = $this->getTestableRows($list);
$foundNames = [];
foreach ($rows as $row) {
foreach ($row->ItemRow as $column) {
$foundNames[] = $column->CellString;
}
}
$this->assertSame($names, $foundNames);
}
public function testGetPrintColumnsForGridFieldThrowsException()
{
$component = new GridFieldPrintButton();
$gridField = new GridField('dummy', 'dummy', new ArrayList());
$gridField->getConfig()->removeComponentsByType(GridFieldDataColumns::class);
$modelClass = ArrayData::class;
$gridField->setModelClass($modelClass);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
'Cannot dynamically determine columns. Add a GridFieldDataColumns component to your GridField'
. " or implement a summaryFields() method on $modelClass"
);
$reflectionMethod = new ReflectionMethod($component, 'getPrintColumnsForGridField');
$reflectionMethod->setAccessible(true);
$reflectionMethod->invoke($component, $gridField);
}
} }

View File

@ -0,0 +1,304 @@
<?php
namespace SilverStripe\ORM\Tests\Search;
use ReflectionMethod;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\HiddenField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\Filters\ExactMatchFilter;
use SilverStripe\ORM\Filters\SearchFilter;
use SilverStripe\ORM\Filters\StartsWithFilter;
use SilverStripe\ORM\Search\BasicSearchContext;
use SilverStripe\View\ArrayData;
class BasicSearchContextTest extends SapphireTest
{
protected static $fixture_file = 'BasicSearchContextTest.yml';
protected static $extra_dataobjects = [
SearchContextTest\GeneralSearch::class,
];
private function getList(): ArrayList
{
$data = [
[
'Name' => 'James',
'Email' => 'james@example.com',
'HairColor' => 'brown',
'EyeColor' => 'brown',
],
[
'Name' => 'John',
'Email' => 'john@example.com',
'HairColor' => 'blond',
'EyeColor' => 'blue',
],
[
'Name' => 'Jane',
'Email' => 'jane@example.com',
'HairColor' => 'brown',
'EyeColor' => 'green',
],
[
'Name' => 'Hemi',
'Email' => 'hemi@example.com',
'HairColor' => 'black',
'EyeColor' => 'brown eyes',
],
[
'Name' => 'Sara',
'Email' => 'sara@example.com',
'HairColor' => 'black',
'EyeColor' => 'green',
],
[
'Name' => 'MatchNothing',
'Email' => 'MatchNothing',
'HairColor' => 'MatchNothing',
'EyeColor' => 'MatchNothing',
],
];
$list = new ArrayList();
foreach ($data as $datum) {
$list->add(new ArrayData($datum));
}
return $list;
}
private function getSearchableFields(string $generalField): FieldList
{
return new FieldList([
new HiddenField($generalField),
new TextField('Name'),
new TextField('Email'),
new TextField('HairColor'),
new TextField('EyeColor'),
]);
}
public function testResultSetFilterReturnsExpectedCount()
{
$context = new BasicSearchContext(ArrayData::class);
$results = $context->getQuery(['Name' => ''], existingQuery: $this->getList());
$this->assertEquals(6, $results->Count());
$results = $context->getQuery(['EyeColor' => 'green'], existingQuery: $this->getList());
$this->assertEquals(2, $results->Count());
$results = $context->getQuery(['EyeColor' => 'green', 'HairColor' => 'black'], existingQuery: $this->getList());
$this->assertEquals(1, $results->Count());
}
public function provideApplySearchFilters()
{
$idFilter = new ExactMatchFilter('ID');
$idFilter->setModifiers(['nocase']);
return [
'defaults to PartialMatch' => [
'searchParams' => [
'q' => 'This one gets ignored',
'ID' => 47,
'Name' => 'some search term',
],
'filters' => null,
'expected' => [
'q' => 'This one gets ignored',
'ID:PartialMatch' => 47,
'Name:PartialMatch' => 'some search term',
],
],
'respects custom filters and modifiers' => [
'searchParams' => [
'q' => 'This one gets ignored',
'ID' => 47,
'Name' => 'some search term',
],
'filters' => ['ID' => $idFilter],
'expected' => [
'q' => 'This one gets ignored',
'ID:ExactMatch:nocase' => 47,
'Name:PartialMatch' => 'some search term',
],
],
];
}
/**
* @dataProvider provideApplySearchFilters
*/
public function testApplySearchFilters(array $searchParams, ?array $filters, array $expected)
{
$context = new BasicSearchContext(ArrayData::class);
$reflectionApplySearchFilters = new ReflectionMethod($context, 'applySearchFilters');
$reflectionApplySearchFilters->setAccessible(true);
if ($filters) {
$context->setFilters($filters);
}
$this->assertSame($expected, $reflectionApplySearchFilters->invoke($context, $searchParams));
}
public function provideGetGeneralSearchFilterTerm()
{
return [
'defaults to case-insensitive partial match' => [
'filterType' => null,
'fieldFilter' => null,
'expected' => 'PartialMatch:nocase',
],
'uses default even when config is explicitly "null"' => [
'filterType' => null,
'fieldFilter' => new StartsWithFilter('MyField'),
'expected' => 'PartialMatch:nocase',
],
'uses configuration filter over field-specific filter' => [
'filterType' => ExactMatchFilter::class,
'fieldFilter' => new StartsWithFilter(),
'expected' => 'ExactMatch',
],
'uses field-specific filter if provided and config is empty string' => [
'filterType' => '',
'fieldFilter' => new StartsWithFilter('MyField'),
'expected' => 'StartsWith',
],
];
}
/**
* @dataProvider provideGetGeneralSearchFilterTerm
*/
public function testGetGeneralSearchFilterTerm(?string $filterType, ?SearchFilter $fieldFilter, string $expected)
{
$context = new BasicSearchContext(ArrayData::class);
$reflectionGetGeneralSearchFilterTerm = new ReflectionMethod($context, 'getGeneralSearchFilterTerm');
$reflectionGetGeneralSearchFilterTerm->setAccessible(true);
if ($fieldFilter) {
$context->setFilters(['MyField' => $fieldFilter]);
}
Config::modify()->set(ArrayData::class, 'general_search_field_filter', $filterType);
$this->assertSame($expected, $reflectionGetGeneralSearchFilterTerm->invoke($context, 'MyField'));
}
public function provideGetQuery()
{
// Note that the search TERM is the same for both scenarios,
// but because the search FIELD is different, we get different results.
return [
'search against hair' => [
'searchParams' => [
'HairColor' => 'brown',
],
'expected' => [
'James',
'Jane',
],
],
'search against eyes' => [
'searchParams' => [
'EyeColor' => 'brown',
],
'expected' => [
'James',
'Hemi',
],
],
'search against all' => [
'searchParams' => [
'q' => 'brown',
],
'expected' => [
'James',
'Jane',
'Hemi',
],
],
];
}
/**
* @dataProvider provideGetQuery
*/
public function testGetQuery(array $searchParams, array $expected)
{
$list = $this->getList();
$context = new BasicSearchContext(ArrayData::class);
$context->setFields($this->getSearchableFields(BasicSearchContext::config()->get('general_search_field_name')));
$results = $context->getQuery($searchParams, existingQuery: $list);
$this->assertSame($expected, $results->column('Name'));
}
public function testGeneralSearch()
{
$list = $this->getList();
$generalField = BasicSearchContext::config()->get('general_search_field_name');
$context = new BasicSearchContext(ArrayData::class);
$context->setFields($this->getSearchableFields($generalField));
$results = $context->getQuery([$generalField => 'brown'], existingQuery: $list);
$this->assertSame(['James', 'Jane', 'Hemi'], $results->column('Name'));
$results = $context->getQuery([$generalField => 'b'], existingQuery: $list);
$this->assertSame(['James', 'John', 'Jane', 'Hemi', 'Sara'], $results->column('Name'));
}
public function testGeneralSearchSplitTerms()
{
$list = $this->getList();
$generalField = BasicSearchContext::config()->get('general_search_field_name');
$context = new BasicSearchContext(ArrayData::class);
$context->setFields($this->getSearchableFields($generalField));
// These terms don't exist in a single field in this order on any object, but they do exist in separate fields.
$results = $context->getQuery([$generalField => 'john blue'], existingQuery: $list);
$this->assertSame(['John'], $results->column('Name'));
$results = $context->getQuery([$generalField => 'eyes sara'], existingQuery: $list);
$this->assertSame(['Hemi', 'Sara'], $results->column('Name'));
}
public function testGeneralSearchNoSplitTerms()
{
Config::modify()->set(ArrayData::class, 'general_search_split_terms', false);
$list = $this->getList();
$generalField = BasicSearchContext::config()->get('general_search_field_name');
$context = new BasicSearchContext(ArrayData::class);
$context->setFields($this->getSearchableFields($generalField));
// These terms don't exist in a single field in this order on any object
$results = $context->getQuery([$generalField => 'john blue'], existingQuery: $list);
$this->assertCount(0, $results);
// These terms exist in a single field, but not in this order.
$results = $context->getQuery([$generalField => 'eyes brown'], existingQuery: $list);
$this->assertCount(0, $results);
// These terms exist in a single field in this order.
$results = $context->getQuery([$generalField => 'brown eyes'], existingQuery: $list);
$this->assertSame(['Hemi'], $results->column('Name'));
}
public function testSpecificFieldsCanBeSkipped()
{
$general1 = $this->objFromFixture(SearchContextTest\GeneralSearch::class, 'general1');
$list = new ArrayList();
$list->merge(SearchContextTest\GeneralSearch::get());
$generalField = BasicSearchContext::config()->get('general_search_field_name');
$context = new BasicSearchContext(SearchContextTest\GeneralSearch::class);
// We're searching for a value that DOES exist in a searchable field,
// but that field is set to be skipped by general search.
$results = $context->getQuery([$generalField => $general1->ExcludeThisField], existingQuery: $list);
$this->assertNotEmpty($general1->ExcludeThisField);
$this->assertCount(0, $results);
}
}

View File

@ -0,0 +1,28 @@
SilverStripe\ORM\Tests\Search\SearchContextTest\GeneralSearch:
general0:
Name: General Zero
DoNotUseThisField: omitted
HairColor: blue
ExcludeThisField: excluded
ExactMatchField: Some specific value here
PartialMatchField: A partial match is allowed for this field
MatchAny1: Some match any field
MatchAny2: Another match any field
general1:
Name: General One
DoNotUseThisField: omitted
HairColor: brown
ExcludeThisField: excluded
ExactMatchField: This requires an exact match
PartialMatchField: This explicitly allows partial matches
MatchAny1: first match
MatchAny2: second match
general2:
Name: MatchNothing
DoNotUseThisField: MatchNothing
HairColor: MatchNothing
ExcludeThisField: MatchNothing
ExactMatchField: MatchNothing
PartialMatchField: MatchNothing
MatchAny1: MatchNothing
MatchAny2: MatchNothing

View File

@ -18,6 +18,7 @@ use SilverStripe\ORM\Filters\EndsWithFilter;
use SilverStripe\ORM\Filters\ExactMatchFilter; use SilverStripe\ORM\Filters\ExactMatchFilter;
use SilverStripe\ORM\Filters\PartialMatchFilter; use SilverStripe\ORM\Filters\PartialMatchFilter;
use SilverStripe\ORM\Search\SearchContext; use SilverStripe\ORM\Search\SearchContext;
use SilverStripe\View\ArrayData;
class SearchContextTest extends SapphireTest class SearchContextTest extends SapphireTest
{ {
@ -527,4 +528,18 @@ class SearchContextTest extends SapphireTest
$results = $context->getResults(['PartialMatchField' => 'an']); $results = $context->getResults(['PartialMatchField' => 'an']);
$this->assertCount(1, $results); $this->assertCount(1, $results);
} }
public function testGetSearchFieldsThrowsException()
{
$modelClass = ArrayData::class;
$context = new SearchContext($modelClass);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(
'Cannot dynamically determine search fields. Pass the fields to setFields()'
. " or implement a scaffoldSearchFields() method on {$modelClass}"
);
$context->getSearchFields();
}
} }