mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Compare commits
2 Commits
3ea7cf4383
...
2c11426211
Author | SHA1 | Date | |
---|---|---|---|
|
2c11426211 | ||
|
cd8090d247 |
@ -4,6 +4,7 @@ namespace SilverStripe\Dev;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Cookie_Backend;
|
||||
use SilverStripe\Control\Director;
|
||||
@ -214,7 +215,7 @@ class TestSession
|
||||
$formCrawler = $page->filterXPath("//form[@id='$formID']");
|
||||
$form = $formCrawler->form();
|
||||
} catch (InvalidArgumentException $e) {
|
||||
user_error("TestSession::submitForm failed to find the form {$formID}");
|
||||
throw new LogicException("TestSession::submitForm failed to find the form '{$formID}'");
|
||||
}
|
||||
|
||||
foreach ($data as $fieldName => $value) {
|
||||
@ -235,7 +236,7 @@ class TestSession
|
||||
if ($button) {
|
||||
$btnXpath = "//button[@name='$button'] | //input[@name='$button'][@type='button' or @type='submit']";
|
||||
if (!$formCrawler->children()->filterXPath($btnXpath)->count()) {
|
||||
throw new Exception("Can't find button '$button' to submit as part of test.");
|
||||
throw new LogicException("Can't find button '$button' to submit as part of test.");
|
||||
}
|
||||
$values[$button] = true;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Model;
|
||||
|
||||
use SilverStripe\Core\ArrayLib;
|
||||
use InvalidArgumentException;
|
||||
use JsonSerializable;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
@ -16,13 +17,9 @@ use stdClass;
|
||||
* ));
|
||||
* </code>
|
||||
*/
|
||||
class ArrayData extends ModelData
|
||||
class ArrayData extends ModelData implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
* @see ArrayData::_construct()
|
||||
*/
|
||||
protected $array;
|
||||
protected array $array;
|
||||
|
||||
/**
|
||||
* @param object|array $value An associative array, or an object with simple properties.
|
||||
@ -51,10 +48,8 @@ class ArrayData extends ModelData
|
||||
|
||||
/**
|
||||
* Get the source array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toMap()
|
||||
public function toMap(): array
|
||||
{
|
||||
return $this->array;
|
||||
}
|
||||
@ -107,6 +102,11 @@ class ArrayData extends ModelData
|
||||
return !empty($this->array);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an associative array to a simple object
|
||||
*
|
||||
|
@ -1026,10 +1026,9 @@ class SSTemplateParser extends Parser implements TemplateParser
|
||||
'arguments only.', $this);
|
||||
}
|
||||
|
||||
//loop without arguments loops on the current scope
|
||||
// loop without arguments loops on the current scope
|
||||
if ($res['ArgumentCount'] == 0) {
|
||||
$type = ViewLayerData::TYPE_METHOD;
|
||||
$on = "\$scope->locally()->scopeToIntermediateValue('Me', [], '$type')";
|
||||
$on = '$scope->locally()->self()';
|
||||
} else { //loop in the normal way
|
||||
$arg = $res['Arguments'][0];
|
||||
if ($arg['ArgumentMode'] == 'string') {
|
||||
|
@ -4263,10 +4263,9 @@ class SSTemplateParser extends Parser implements TemplateParser
|
||||
'arguments only.', $this);
|
||||
}
|
||||
|
||||
//loop without arguments loops on the current scope
|
||||
// loop without arguments loops on the current scope
|
||||
if ($res['ArgumentCount'] == 0) {
|
||||
$type = ViewLayerData::TYPE_METHOD;
|
||||
$on = "\$scope->locally()->scopeToIntermediateValue('Me', [], '$type')";
|
||||
$on = '$scope->locally()->self()';
|
||||
} else { //loop in the normal way
|
||||
$arg = $res['Arguments'][0];
|
||||
if ($arg['ArgumentMode'] == 'string') {
|
||||
|
@ -376,10 +376,6 @@ class SSViewer_Scope
|
||||
}
|
||||
}
|
||||
|
||||
// if ($retval instanceof DBField) {
|
||||
// $retval = $retval->getValue(); // Workaround because we're still calling obj in ViewLayerData
|
||||
// }
|
||||
|
||||
$this->resetLocalScope();
|
||||
return $retval;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\Model\ModelDataCustomised;
|
||||
use SilverStripe\ORM\FieldType\DBClassName;
|
||||
use Stringable;
|
||||
use Traversable;
|
||||
|
||||
@ -22,6 +23,16 @@ class ViewLayerData implements IteratorAggregate, Stringable
|
||||
|
||||
public const TYPE_ANY = 'any';
|
||||
|
||||
/**
|
||||
* Special variable names that can be used to get metadata about values
|
||||
*/
|
||||
public const META_DATA_NAMES = [
|
||||
// Gets a DBClassName with the class name of $this->data
|
||||
'ClassName',
|
||||
// Returns $this->data
|
||||
'Me',
|
||||
];
|
||||
|
||||
private object $data;
|
||||
|
||||
public function __construct(mixed $data, mixed $source = null, string $name = '')
|
||||
@ -82,7 +93,8 @@ class ViewLayerData implements IteratorAggregate, Stringable
|
||||
{
|
||||
// Note we explicitly DO NOT call count() or exists() on the data here because that would
|
||||
// require fetching the data prematurely which could cause performance issues in extreme cases
|
||||
return isset($this->data->$name)
|
||||
return in_array($name, ViewLayerData::META_DATA_NAMES)
|
||||
|| isset($this->data->$name)
|
||||
|| ClassInfo::hasMethod($this->data, "get$name")
|
||||
|| ClassInfo::hasMethod($this->data, $name);
|
||||
}
|
||||
@ -203,9 +215,22 @@ class ViewLayerData implements IteratorAggregate, Stringable
|
||||
$data->objCacheSet($name, $arguments, $value);
|
||||
}
|
||||
|
||||
if ($value === null && in_array($name, ViewLayerData::META_DATA_NAMES)) {
|
||||
$value = $this->getMetaData($data, $name);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function getMetaData(object $data, string $name): mixed
|
||||
{
|
||||
return match ($name) {
|
||||
'Me' => $data,
|
||||
'ClassName' => DBClassName::create()->setValue(get_class($data)),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
private function callDataMethod(object $data, string $name, array $arguments, bool &$fetchedValue = false): mixed
|
||||
{
|
||||
$hasDynamicMethods = method_exists($data, '__call');
|
||||
|
@ -28,6 +28,7 @@ use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
|
||||
use SilverStripe\View\Exception\MissingTemplateException;
|
||||
use SilverStripe\View\SSTemplateEngine;
|
||||
use SilverStripe\View\ViewLayerData;
|
||||
use stdClass;
|
||||
|
||||
class SSTemplateEngineTest extends SapphireTest
|
||||
{
|
||||
@ -498,23 +499,23 @@ class SSTemplateEngineTest extends SapphireTest
|
||||
{
|
||||
return [
|
||||
[
|
||||
'arg1:0,arg2:"string",arg3:true',
|
||||
'arg0:0,arg1:"string",arg2:true',
|
||||
'$methodWithTypedArguments(0, "string", true).RAW',
|
||||
],
|
||||
[
|
||||
'arg1:false,arg2:"string",arg3:true',
|
||||
'arg0:false,arg1:"string",arg2:true',
|
||||
'$methodWithTypedArguments(false, "string", true).RAW',
|
||||
],
|
||||
[
|
||||
'arg1:null,arg2:"string",arg3:true',
|
||||
'arg0:null,arg1:"string",arg2:true',
|
||||
'$methodWithTypedArguments(null, "string", true).RAW',
|
||||
],
|
||||
[
|
||||
'arg1:"",arg2:"string",arg3:true',
|
||||
'arg0:"",arg1:"string",arg2:true',
|
||||
'$methodWithTypedArguments("", "string", true).RAW',
|
||||
],
|
||||
[
|
||||
'arg1:0,arg2:1,arg3:2',
|
||||
'arg0:0,arg1:1,arg2:2',
|
||||
'$methodWithTypedArguments(0, 1, 2).RAW',
|
||||
],
|
||||
];
|
||||
@ -526,6 +527,62 @@ class SSTemplateEngineTest extends SapphireTest
|
||||
$this->assertEquals($expected, $this->render($template, new TestModelData()));
|
||||
}
|
||||
|
||||
public static function provideEvaluatedArgumentTypes(): array
|
||||
{
|
||||
$stdobj = new stdClass();
|
||||
$stdobj->key = 'value';
|
||||
$scenarios = [
|
||||
'null value' => [
|
||||
'data' => ['Value' => null],
|
||||
'useOverlay' => true,
|
||||
'expected' => 'arg0:null',
|
||||
],
|
||||
'int value' => [
|
||||
'data' => ['Value' => 1],
|
||||
'useOverlay' => true,
|
||||
'expected' => 'arg0:1',
|
||||
],
|
||||
'string value' => [
|
||||
'data' => ['Value' => '1'],
|
||||
'useOverlay' => true,
|
||||
'expected' => 'arg0:"1"',
|
||||
],
|
||||
'boolean true' => [
|
||||
'data' => ['Value' => true],
|
||||
'useOverlay' => true,
|
||||
'expected' => 'arg0:true',
|
||||
],
|
||||
'boolean false' => [
|
||||
'data' => ['Value' => false],
|
||||
'useOverlay' => true,
|
||||
'expected' => 'arg0:false',
|
||||
],
|
||||
'object value' => [
|
||||
'data' => ['Value' => $stdobj],
|
||||
'useOverlay' => true,
|
||||
'expected' => 'arg0:{"key":"value"}',
|
||||
],
|
||||
];
|
||||
foreach ($scenarios as $key => $scenario) {
|
||||
$scenario['useOverlay'] = false;
|
||||
$scenarios[$key . ' no overlay'] = $scenario;
|
||||
}
|
||||
return $scenarios;
|
||||
}
|
||||
|
||||
#[DataProvider('provideEvaluatedArgumentTypes')]
|
||||
public function testEvaluatedArgumentTypes(array $data, bool $useOverlay, string $expected): void
|
||||
{
|
||||
$template = '$methodWithTypedArguments($Value).RAW';
|
||||
$model = new TestModelData();
|
||||
$overlay = $data;
|
||||
if (!$useOverlay) {
|
||||
$model = $model->customise($data);
|
||||
$overlay = [];
|
||||
}
|
||||
$this->assertEquals($expected, $this->render($template, $model, $overlay));
|
||||
}
|
||||
|
||||
public function testObjectDotArguments()
|
||||
{
|
||||
$this->assertEquals(
|
||||
@ -2084,11 +2141,11 @@ after'
|
||||
}
|
||||
|
||||
// Test instance behaviors
|
||||
$this->render($content, null, false);
|
||||
$this->assertFalse(file_exists($cacheFile ?? ''), 'Cache file was created when caching was off');
|
||||
$this->render($content, cache: false);
|
||||
$this->assertFileDoesNotExist($cacheFile, 'Cache file was created when caching was off');
|
||||
|
||||
$this->render($content, null, true);
|
||||
$this->assertTrue(file_exists($cacheFile ?? ''), 'Cache file wasn\'t created when it was meant to');
|
||||
$this->render($content, cache: true);
|
||||
$this->assertFileExists($cacheFile, 'Cache file wasn\'t created when it was meant to');
|
||||
unlink($cacheFile ?? '');
|
||||
}
|
||||
|
||||
@ -2170,14 +2227,14 @@ after'
|
||||
/**
|
||||
* Small helper to render templates from strings
|
||||
*/
|
||||
private function render(string $templateString, mixed $data = null, bool $cacheTemplate = false): string
|
||||
private function render(string $templateString, mixed $data = null, array $overlay = [], bool $cache = false): string
|
||||
{
|
||||
$engine = new SSTemplateEngine();
|
||||
if ($data === null) {
|
||||
$data = new SSTemplateEngineTest\TestFixture();
|
||||
}
|
||||
$data = new ViewLayerData($data);
|
||||
return trim('' . $engine->renderString($templateString, $data, cache: $cacheTemplate));
|
||||
return trim('' . $engine->renderString($templateString, $data, $overlay, $cache));
|
||||
}
|
||||
|
||||
private function _renderWithSourceFileComments($name, $expected)
|
||||
|
@ -29,9 +29,13 @@ class TestModelData extends ModelData implements TestOnly
|
||||
return "arg1:{$arg1},arg2:{$arg2}";
|
||||
}
|
||||
|
||||
public function methodWithTypedArguments($arg1, $arg2, $arg3)
|
||||
public function methodWithTypedArguments(...$args)
|
||||
{
|
||||
return 'arg1:' . json_encode($arg1) . ',arg2:' . json_encode($arg2) . ',arg3:' . json_encode($arg3);
|
||||
$ret = [];
|
||||
foreach ($args as $i => $arg) {
|
||||
$ret[] = "arg$i:" . json_encode($arg);
|
||||
}
|
||||
return implode(',', $ret);
|
||||
}
|
||||
|
||||
public function Type($arg)
|
@ -23,6 +23,7 @@ use SilverStripe\View\Tests\ViewLayerDataTest\StringableObject;
|
||||
use SilverStripe\View\Tests\ViewLayerDataTest\TestFixture;
|
||||
use SilverStripe\View\Tests\ViewLayerDataTest\TestFixtureComplex;
|
||||
use SilverStripe\View\ViewLayerData;
|
||||
use stdClass;
|
||||
use Throwable;
|
||||
|
||||
class ViewLayerDataTest extends SapphireTest
|
||||
@ -727,4 +728,24 @@ class ViewLayerDataTest extends SapphireTest
|
||||
$viewLayerData->MyField;
|
||||
$this->assertSame('some value', $data->objCacheGet('MyField'));
|
||||
}
|
||||
|
||||
public function testSpecialNames(): void
|
||||
{
|
||||
$data = new stdClass;
|
||||
$viewLayerData = new ViewLayerData($data);
|
||||
|
||||
// Metadata values are available when there's nothing in the actual data
|
||||
$this->assertTrue(isset($viewLayerData->ClassName));
|
||||
$this->assertTrue(isset($viewLayerData->Me));
|
||||
$this->assertSame(stdClass::class, $viewLayerData->getRawDataValue('ClassName')->getValue());
|
||||
$this->assertSame($data, $viewLayerData->getRawDataValue('Me'));
|
||||
|
||||
// Metadata values are lower priority than real values in the actual data
|
||||
$data->ClassName = 'some other class';
|
||||
$data->Me = 'something else';
|
||||
$this->assertTrue(isset($viewLayerData->ClassName));
|
||||
$this->assertTrue(isset($viewLayerData->Me));
|
||||
$this->assertSame('some other class', $viewLayerData->getRawDataValue('ClassName'));
|
||||
$this->assertSame('something else', $viewLayerData->getRawDataValue('Me'));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user