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 Exception;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use LogicException;
|
||||||
use SilverStripe\Control\Controller;
|
use SilverStripe\Control\Controller;
|
||||||
use SilverStripe\Control\Cookie_Backend;
|
use SilverStripe\Control\Cookie_Backend;
|
||||||
use SilverStripe\Control\Director;
|
use SilverStripe\Control\Director;
|
||||||
@ -214,7 +215,7 @@ class TestSession
|
|||||||
$formCrawler = $page->filterXPath("//form[@id='$formID']");
|
$formCrawler = $page->filterXPath("//form[@id='$formID']");
|
||||||
$form = $formCrawler->form();
|
$form = $formCrawler->form();
|
||||||
} catch (InvalidArgumentException $e) {
|
} 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) {
|
foreach ($data as $fieldName => $value) {
|
||||||
@ -235,7 +236,7 @@ class TestSession
|
|||||||
if ($button) {
|
if ($button) {
|
||||||
$btnXpath = "//button[@name='$button'] | //input[@name='$button'][@type='button' or @type='submit']";
|
$btnXpath = "//button[@name='$button'] | //input[@name='$button'][@type='button' or @type='submit']";
|
||||||
if (!$formCrawler->children()->filterXPath($btnXpath)->count()) {
|
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;
|
$values[$button] = true;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Model;
|
|||||||
|
|
||||||
use SilverStripe\Core\ArrayLib;
|
use SilverStripe\Core\ArrayLib;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use JsonSerializable;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,13 +17,9 @@ use stdClass;
|
|||||||
* ));
|
* ));
|
||||||
* </code>
|
* </code>
|
||||||
*/
|
*/
|
||||||
class ArrayData extends ModelData
|
class ArrayData extends ModelData implements JsonSerializable
|
||||||
{
|
{
|
||||||
/**
|
protected array $array;
|
||||||
* @var array
|
|
||||||
* @see ArrayData::_construct()
|
|
||||||
*/
|
|
||||||
protected $array;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param object|array $value An associative array, or an object with simple properties.
|
* @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
|
* Get the source array
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function toMap()
|
public function toMap(): array
|
||||||
{
|
{
|
||||||
return $this->array;
|
return $this->array;
|
||||||
}
|
}
|
||||||
@ -107,6 +102,11 @@ class ArrayData extends ModelData
|
|||||||
return !empty($this->array);
|
return !empty($this->array);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
return $this->array;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an associative array to a simple object
|
* Converts an associative array to a simple object
|
||||||
*
|
*
|
||||||
|
@ -1026,10 +1026,9 @@ class SSTemplateParser extends Parser implements TemplateParser
|
|||||||
'arguments only.', $this);
|
'arguments only.', $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
//loop without arguments loops on the current scope
|
// loop without arguments loops on the current scope
|
||||||
if ($res['ArgumentCount'] == 0) {
|
if ($res['ArgumentCount'] == 0) {
|
||||||
$type = ViewLayerData::TYPE_METHOD;
|
$on = '$scope->locally()->self()';
|
||||||
$on = "\$scope->locally()->scopeToIntermediateValue('Me', [], '$type')";
|
|
||||||
} else { //loop in the normal way
|
} else { //loop in the normal way
|
||||||
$arg = $res['Arguments'][0];
|
$arg = $res['Arguments'][0];
|
||||||
if ($arg['ArgumentMode'] == 'string') {
|
if ($arg['ArgumentMode'] == 'string') {
|
||||||
|
@ -4263,10 +4263,9 @@ class SSTemplateParser extends Parser implements TemplateParser
|
|||||||
'arguments only.', $this);
|
'arguments only.', $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
//loop without arguments loops on the current scope
|
// loop without arguments loops on the current scope
|
||||||
if ($res['ArgumentCount'] == 0) {
|
if ($res['ArgumentCount'] == 0) {
|
||||||
$type = ViewLayerData::TYPE_METHOD;
|
$on = '$scope->locally()->self()';
|
||||||
$on = "\$scope->locally()->scopeToIntermediateValue('Me', [], '$type')";
|
|
||||||
} else { //loop in the normal way
|
} else { //loop in the normal way
|
||||||
$arg = $res['Arguments'][0];
|
$arg = $res['Arguments'][0];
|
||||||
if ($arg['ArgumentMode'] == 'string') {
|
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();
|
$this->resetLocalScope();
|
||||||
return $retval;
|
return $retval;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use SilverStripe\Core\ClassInfo;
|
|||||||
use SilverStripe\Core\Injector\Injectable;
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
use SilverStripe\Model\ModelData;
|
use SilverStripe\Model\ModelData;
|
||||||
use SilverStripe\Model\ModelDataCustomised;
|
use SilverStripe\Model\ModelDataCustomised;
|
||||||
|
use SilverStripe\ORM\FieldType\DBClassName;
|
||||||
use Stringable;
|
use Stringable;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
@ -22,6 +23,16 @@ class ViewLayerData implements IteratorAggregate, Stringable
|
|||||||
|
|
||||||
public const TYPE_ANY = 'any';
|
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;
|
private object $data;
|
||||||
|
|
||||||
public function __construct(mixed $data, mixed $source = null, string $name = '')
|
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
|
// 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
|
// 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, "get$name")
|
||||||
|| ClassInfo::hasMethod($this->data, $name);
|
|| ClassInfo::hasMethod($this->data, $name);
|
||||||
}
|
}
|
||||||
@ -203,9 +215,22 @@ class ViewLayerData implements IteratorAggregate, Stringable
|
|||||||
$data->objCacheSet($name, $arguments, $value);
|
$data->objCacheSet($name, $arguments, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($value === null && in_array($name, ViewLayerData::META_DATA_NAMES)) {
|
||||||
|
$value = $this->getMetaData($data, $name);
|
||||||
|
}
|
||||||
|
|
||||||
return $value;
|
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
|
private function callDataMethod(object $data, string $name, array $arguments, bool &$fetchedValue = false): mixed
|
||||||
{
|
{
|
||||||
$hasDynamicMethods = method_exists($data, '__call');
|
$hasDynamicMethods = method_exists($data, '__call');
|
||||||
|
@ -28,6 +28,7 @@ use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
|
|||||||
use SilverStripe\View\Exception\MissingTemplateException;
|
use SilverStripe\View\Exception\MissingTemplateException;
|
||||||
use SilverStripe\View\SSTemplateEngine;
|
use SilverStripe\View\SSTemplateEngine;
|
||||||
use SilverStripe\View\ViewLayerData;
|
use SilverStripe\View\ViewLayerData;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
class SSTemplateEngineTest extends SapphireTest
|
class SSTemplateEngineTest extends SapphireTest
|
||||||
{
|
{
|
||||||
@ -498,23 +499,23 @@ class SSTemplateEngineTest extends SapphireTest
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
'arg1:0,arg2:"string",arg3:true',
|
'arg0:0,arg1:"string",arg2:true',
|
||||||
'$methodWithTypedArguments(0, "string", true).RAW',
|
'$methodWithTypedArguments(0, "string", true).RAW',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'arg1:false,arg2:"string",arg3:true',
|
'arg0:false,arg1:"string",arg2:true',
|
||||||
'$methodWithTypedArguments(false, "string", true).RAW',
|
'$methodWithTypedArguments(false, "string", true).RAW',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'arg1:null,arg2:"string",arg3:true',
|
'arg0:null,arg1:"string",arg2:true',
|
||||||
'$methodWithTypedArguments(null, "string", true).RAW',
|
'$methodWithTypedArguments(null, "string", true).RAW',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'arg1:"",arg2:"string",arg3:true',
|
'arg0:"",arg1:"string",arg2:true',
|
||||||
'$methodWithTypedArguments("", "string", true).RAW',
|
'$methodWithTypedArguments("", "string", true).RAW',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'arg1:0,arg2:1,arg3:2',
|
'arg0:0,arg1:1,arg2:2',
|
||||||
'$methodWithTypedArguments(0, 1, 2).RAW',
|
'$methodWithTypedArguments(0, 1, 2).RAW',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@ -526,6 +527,62 @@ class SSTemplateEngineTest extends SapphireTest
|
|||||||
$this->assertEquals($expected, $this->render($template, new TestModelData()));
|
$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()
|
public function testObjectDotArguments()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
@ -2084,11 +2141,11 @@ after'
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test instance behaviors
|
// Test instance behaviors
|
||||||
$this->render($content, null, false);
|
$this->render($content, cache: false);
|
||||||
$this->assertFalse(file_exists($cacheFile ?? ''), 'Cache file was created when caching was off');
|
$this->assertFileDoesNotExist($cacheFile, 'Cache file was created when caching was off');
|
||||||
|
|
||||||
$this->render($content, null, true);
|
$this->render($content, cache: true);
|
||||||
$this->assertTrue(file_exists($cacheFile ?? ''), 'Cache file wasn\'t created when it was meant to');
|
$this->assertFileExists($cacheFile, 'Cache file wasn\'t created when it was meant to');
|
||||||
unlink($cacheFile ?? '');
|
unlink($cacheFile ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2170,14 +2227,14 @@ after'
|
|||||||
/**
|
/**
|
||||||
* Small helper to render templates from strings
|
* 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();
|
$engine = new SSTemplateEngine();
|
||||||
if ($data === null) {
|
if ($data === null) {
|
||||||
$data = new SSTemplateEngineTest\TestFixture();
|
$data = new SSTemplateEngineTest\TestFixture();
|
||||||
}
|
}
|
||||||
$data = new ViewLayerData($data);
|
$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)
|
private function _renderWithSourceFileComments($name, $expected)
|
||||||
|
@ -29,9 +29,13 @@ class TestModelData extends ModelData implements TestOnly
|
|||||||
return "arg1:{$arg1},arg2:{$arg2}";
|
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)
|
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\TestFixture;
|
||||||
use SilverStripe\View\Tests\ViewLayerDataTest\TestFixtureComplex;
|
use SilverStripe\View\Tests\ViewLayerDataTest\TestFixtureComplex;
|
||||||
use SilverStripe\View\ViewLayerData;
|
use SilverStripe\View\ViewLayerData;
|
||||||
|
use stdClass;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class ViewLayerDataTest extends SapphireTest
|
class ViewLayerDataTest extends SapphireTest
|
||||||
@ -727,4 +728,24 @@ class ViewLayerDataTest extends SapphireTest
|
|||||||
$viewLayerData->MyField;
|
$viewLayerData->MyField;
|
||||||
$this->assertSame('some value', $data->objCacheGet('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