diff --git a/api/src/Etemplate.php b/api/src/Etemplate.php index bdc60e06e9..d2c9d4a9d5 100644 --- a/api/src/Etemplate.php +++ b/api/src/Etemplate.php @@ -368,7 +368,15 @@ class Etemplate extends Etemplate\Widget\Template //error_log(__METHOD__."(,".array2string($content).')'); //error_log(' validated='.array2string($validated)); - $content = ExecMethod(self::$request->method, self::complete_array_merge(self::$request->preserv, $validated)); + if(is_callable(self::$request->method)) + { + call_user_func(self::$request->method,self::complete_array_merge(self::$request->preserv, $validated)); + } + else + { + // Deprecated, but may still be needed + $content = ExecMethod(self::$request->method, self::complete_array_merge(self::$request->preserv, $validated)); + } $tcontent = is_array($content) ? $content : self::complete_array_merge(self::$request->preserv, $validated); diff --git a/api/src/Etemplate/Widget/test/TemplateTest.php b/api/src/Etemplate/Widget/test/TemplateTest.php index c147f4ded0..fd28b91740 100644 --- a/api/src/Etemplate/Widget/test/TemplateTest.php +++ b/api/src/Etemplate/Widget/test/TemplateTest.php @@ -22,12 +22,53 @@ require_once realpath(__DIR__.'/../../test/WidgetBaseTest.php'); class TemplateTest extends \EGroupware\Api\Etemplate\WidgetBaseTest { /** - * Test instanciation of a template + * Test instanciation of template from a file */ - public function testInstance() + public function testSimpleInstance() { - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); + static $name = 'api.prompt'; + + $template = Template::instance($name); + $this->assertInstanceOf(Template::class, $template); } + + /** + * Test instanciating nested template + * + */ + public function testNestedInstanciation() + { + static $template = 'api.nested'; + + $template = Template::instance($template, 'test'); + $this->assertInstanceOf(Template::class, $template); + + // Check for the sub-child to see if the nested template was loaded + $this->assertInstanceOf(\EGroupware\Api\Etemplate\Widget::class, $template->getElementById('sub_child')); + + // Check that it's not just making things up + $this->assertNull($template->getElementById('not_existing')); + } + + + /** + * Test that we can instanciate a sub-template from a file, once the file + * is in the cache + * + * @depends testNestedInstanciation + */ + public function testSubTemplate() + { + // No file matches this, but it was loaded and cached in the previous test + static $template = 'api.nested.sub_template'; + $template = Template::instance($template, 'test'); + $this->assertInstanceOf(Template::class, $template); + + // Check for the sub-child to see if the template was loaded + $this->assertInstanceOf(\EGroupware\Api\Etemplate\Widget::class, $template->getElementById('sub_child')); + + // Check that it's not just making things up + $this->assertNull($template->getElementById('not_existing')); + } + } diff --git a/api/src/Etemplate/test/WidgetBaseTest.php b/api/src/Etemplate/test/WidgetBaseTest.php index 3da03e1a93..5ff2e558ae 100644 --- a/api/src/Etemplate/test/WidgetBaseTest.php +++ b/api/src/Etemplate/test/WidgetBaseTest.php @@ -12,9 +12,14 @@ namespace EGroupware\Api\Etemplate; +use Egroupware\Api\Etemplate; + // test base providing Egw environment, since we need the DB require_once realpath(__DIR__.'/../../test/LoggedInTest.php'); +// Store request in the session, file access probably won't work due to permissions +\EGroupware\Api\Etemplate\Request::$request_class = 'EGroupware\Api\Etemplate\Request\Session'; + /** * Base class for all widget tests doing needed setup so the tests can run, and * providing common utilities. @@ -24,6 +29,16 @@ require_once realpath(__DIR__.'/../../test/LoggedInTest.php'); */ abstract class WidgetBaseTest extends \EGroupware\Api\LoggedInTest { + /** + * We use our own callback for results of executing, here we store the results + * until returned + * + * @var Array + */ + protected static $mocked_exec_result = array(); + + protected $ajax_response = null; + public static function setUpBeforeClass() { parent::setUpBeforeClass(); @@ -34,23 +49,89 @@ abstract class WidgetBaseTest extends \EGroupware\Api\LoggedInTest { new \EGroupware\Api\Etemplate(); } + public function setUp() + { + // Mock AJAX response + $this->ajax_response = $this->mock_ajax_response(); + } + public function tearDown() + { + // Clean up AJAX response + $this->ajax_response->initResponseArray(); + $ref = new \ReflectionProperty('\\EGroupware\\Api\\Json\\Response', 'response'); + $ref->setAccessible(true); + $ref->setValue(null, null); + } + + /** + * Use a known, public callback so we can hook into it, if needed + */ + public static function public_callback($content) + { + static::$mocked_exec_result = $content; + } + /** * Mocks what is needed to fake a call to exec, and catch its output. * The resulting array of information, which would normally be sent to the * client as JSON, is returned for evaluation. * - * @param String $method + * @param Etemplate $etemplate * @param array $content * @param array $sel_options * @param array $readonlys * @param array $preserv */ - protected function mockedExec(\EGroupware\Api\Etemplate $etemplate, $method,array $content,array $sel_options=null,array $readonlys=null,array $preserv=null) + protected function mockedExec(Etemplate $etemplate, array $content,array $sel_options=null,array $readonlys=null,array $preserv=null) + { + ob_start(); + + // Exec the template + $etemplate->exec(__CLASS__ . '::public_callback', $content, $sel_options, $readonlys, $preserv, 4); + $result = $this->ajax_response->returnResult(); + + // Store & clean the request + //$etemplate->destroy_request(); + + ob_end_clean(); + + return $result; + } + + protected function mockedRoundTrip(\EGroupware\Api\Etemplate $etemplate, array $content,array $sel_options=null,array $readonlys=null,array $preserv=null) + { + + $result = $this->mockedExec($etemplate, $content, $sel_options, $readonlys, $preserv); + + // Check for the load + $data = array(); + foreach($result as $command) + { + if($command['type'] == 'et2_load') + { + $data = $command['data']; + break; + } + } + + Etemplate::ajax_process_content($data['data']['etemplate_exec_id'], $data['data']['content'], false); + + $content = static::$mocked_exec_result; + static::$mocked_exec_result = array(); + + return $content; + } + + /** + * Mock the ajax response and override it so it doesn't try to send headers, + * which generates errors since PHPUnit has already done that + */ + protected function mock_ajax_response() { $response = $this->getMockBuilder('\\EGroupware\\Api\\Json\\Response') ->disableOriginalConstructor() ->setMethods(['get'/*,'generic'*/]) - ->getMock($etemplate); + ->getMock(); // Replace protected self reference with mock object $ref = new \ReflectionProperty('\\EGroupware\\Api\\Json\\Response', 'response'); $ref->setAccessible(true); @@ -62,19 +143,6 @@ abstract class WidgetBaseTest extends \EGroupware\Api\LoggedInTest { // Don't send headers, like the real one does return self::$response; }); - - ob_start(); - - // Exec the template - $etemplate->exec($method, $content, $sel_options, $readonlys, $preserv, 4); - $result = $response->returnResult(); - - // Clean json response - $response->initResponseArray(); - $ref->setValue(null, null); - - ob_end_clean(); - - return $result; + return $response; } } diff --git a/api/src/test/EtemplateTest.php b/api/src/test/EtemplateTest.php index 52f3927c49..330d7616bf 100644 --- a/api/src/test/EtemplateTest.php +++ b/api/src/test/EtemplateTest.php @@ -31,6 +31,10 @@ class EtemplateTest extends Etemplate\WidgetBaseTest { */ const TEST_TEMPLATE = 'api.prompt'; + protected $content = array('value' => 'test content'); + protected $sel_options = array(array('value' => 0, 'label' => 'label')); + protected $readonlys = array('value' => true); + /** * Test reading xml files * @@ -48,7 +52,7 @@ class EtemplateTest extends Etemplate\WidgetBaseTest { $this->assertEquals(true, $etemplate->read(static::TEST_TEMPLATE)); // This loads and parses - $result = $this->mockedExec($etemplate, '',array()); + $result = $this->mockedExec($etemplate, array()); // Look for the load and match the template name foreach($result as $command) @@ -74,7 +78,7 @@ class EtemplateTest extends Etemplate\WidgetBaseTest { // Change the target DOM ID $etemplate->set_dom_id('test_id'); - $result = $this->mockedExec($etemplate, '',array()); + $result = $this->mockedExec($etemplate, array()); // Check for the load foreach($result as $command) @@ -92,10 +96,6 @@ class EtemplateTest extends Etemplate\WidgetBaseTest { */ public function testExec() { - $content = array('id' => 'value'); - $sel_options = array(array('value' => 0, 'label' => 'label')); - $readonlys = array('id' => true); - // Templates must be in the correct templates directory - use one from API $etemplate = new Etemplate(); $etemplate->read(static::TEST_TEMPLATE); @@ -103,7 +103,7 @@ class EtemplateTest extends Etemplate\WidgetBaseTest { // Change the target DOM ID $etemplate->set_dom_id('test_id'); - $result = $this->mockedExec($etemplate, '',$content, $sel_options, $readonlys); + $result = $this->mockedExec($etemplate, $this->content, $this->sel_options, $this->readonlys); // Check for the load $data = array(); @@ -116,8 +116,103 @@ class EtemplateTest extends Etemplate\WidgetBaseTest { } } - $this->assertArraySubset($content, $data['data']['content'], false, 'Content does not match'); - $this->assertArraySubset($sel_options, $data['data']['sel_options'], false, 'Select options do not match'); - $this->assertArraySubset($readonlys, $data['data']['readonlys'], false, 'Readonlys does not match'); + $this->assertArraySubset($this->content, $data['data']['content'], false, 'Content does not match'); + $this->assertArraySubset($this->sel_options, $data['data']['sel_options'], false, 'Select options do not match'); + $this->assertArraySubset($this->readonlys, $data['data']['readonlys'], false, 'Readonlys does not match'); + } + + /** + * Test that data passed in is passed back + * + * In this case, since there's one input widget and we're passing it's value, and + * we're not passing anything extra and no preserve, it should be the same. + * + * @depends testExec + */ + public function testRoundTrip() + { + // Templates must be in the correct templates directory - use one from API + $etemplate = new Etemplate(); + $etemplate->read(static::TEST_TEMPLATE); + + $this->readonlys['value'] = false; + + $result = $this->mockedRoundTrip($etemplate, $this->content, $this->sel_options, $this->readonlys); + + $this->assertEquals($this->content, $result); + } + + /** + * Simple test of a read-only widget + * + * The value is passed in, but does not come back + * + * @depends testExec + */ + public function testSimpleReadonly() + { + // Templates must be in the correct templates directory - use one from API + $etemplate = new Etemplate(); + $etemplate->read(static::TEST_TEMPLATE); + + $this->readonlys['value'] = true; + + $result = $this->mockedRoundTrip($etemplate, $this->content, $this->sel_options, $this->readonlys); + + // The only input widget is readonly, expect an empty array + $this->assertEquals(array(), $result); + } + + /** + * Simple test of preserve + * + * The value is passed in, and comes back, even if the widget is readonly, + * or if there is no matching widget. + * + * @depends testExec + */ + public function testArbitraryPreserve() + { + // Templates must be in the correct templates directory - use one from API + $etemplate = new Etemplate(); + $etemplate->read(static::TEST_TEMPLATE); + + $this->readonlys['value'] = true; + + $preserve = array('arbitrary' => 'value'); + $result = $this->mockedRoundTrip($etemplate, $this->content, $this->sel_options, $this->readonlys, $preserve); + + // The only input widget is readonly, expect preserve back + $this->assertEquals($preserve, $result); + + // Now try with widget + $this->readonlys['value'] = false; + + $result = $this->mockedRoundTrip($etemplate, $this->content, $this->sel_options, $this->readonlys, $preserve); + + // The only input widget is readonly, expect preserve + content back + $this->assertArraySubset($this->content, $result); + $this->assertArraySubset($preserve, $result); + } + + public function testReadonlyPreserve() + { + $etemplate = new Etemplate(); + $etemplate->read(static::TEST_TEMPLATE); + + $this->readonlys['value'] = true; + $preserve['value'] = 'preserved_value'; + + $result = $this->mockedRoundTrip($etemplate, $this->content, $this->sel_options, $this->readonlys, $preserve); + + // The only input widget is readonly, expect preserve back, not content + $this->assertEquals($preserve['value'], $result['value']); + + $this->readonlys['value'] = false; + $result = $this->mockedRoundTrip($etemplate, $this->content, $this->sel_options, $this->readonlys, $preserve); + + // The only input widget is editable, expect content back, not preserve + $this->assertEquals($this->content['value'], $result['value']); + } } diff --git a/api/templates/test/nested.xet b/api/templates/test/nested.xet new file mode 100644 index 0000000000..93b38722b2 --- /dev/null +++ b/api/templates/test/nested.xet @@ -0,0 +1,12 @@ + + + + + +