From d3a4161a945812cecdf83362cde8fcb9dbfd24cd Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 5 Jun 2013 14:59:01 +0200 Subject: [PATCH 1/6] Behat: Backport improved dropdown step handler --- .../Framework/Test/Behaviour/CmsUiContext.php | 98 ++++++++++++------- 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php index 9d263dcc9..f6078ff17 100644 --- a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php +++ b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php @@ -273,55 +273,87 @@ class CmsUiContext extends BehatContext { $field = $this->fixStepArgument($field); $value = $this->fixStepArgument($value); + $nativeField = $this->getSession()->getPage()->findField($field); if($nativeField) { $nativeField->selectOption($value); - } else { - // TODO Allow searching by label - $inputField = $this->getSession()->getPage()->find('xpath', "//*[@name='$field']"); - if(null === $inputField) { - throw new \InvalidArgumentException(sprintf( - 'Chosen.js dropdown named "%s" not found', - $field - )); - } + return; + } + // Given the fuzzy matching, we might get more than one matching field. + $formFields = array(); + + // Find by label + $formField = $this->getSession()->getPage()->findField($field); + if($formField) $formFields[] = $formField; + + // Fall back to finding by title (for dropdowns without a label) + if(!$formFields) { + $formFields = $this->getSession()->getPage()->findAll( + 'xpath', + sprintf( + '//*[self::select][(./@title="%s")]', + $field + ) + ); + } + + // Find by name (incl. hidden fields) + if(!$formFields) { + $formFields = $this->getSession()->getPage()->findAll('xpath', "//*[@name='$field']"); + } + + assertGreaterThan(0, count($formFields), sprintf( + 'Chosen.js dropdown named "%s" not found', + $field + )); + + // Traverse up to field holder + $containers = array(); + foreach($formFields as $formField) { do { - $container = $inputField->getParent(); + $container = $formField->getParent(); $containerClasses = explode(' ', $container->getAttribute('class')); + $containers[] = $container; } while( $container && in_array('field', $containerClasses) && $container->getTagName() != 'form' ); - if(null === $container) throw new \InvalidArgumentException('Chosen.js field container not found'); + } + + assertGreaterThan(0, count($containers), 'Chosen.js field container not found'); - $triggerEl = $container->find('xpath', './/a'); - if(null === $triggerEl) throw new \InvalidArgumentException('Chosen.js link element not found'); - $triggerEl->click(); + // Default to first visible container + $container = $containers[0]; + + // Click on newly expanded list element, indirectly setting the dropdown value + $linkEl = $container->find('xpath', './/a[./@href]'); + assertNotNull($linkEl, 'Chosen.js link element not found'); + $this->getSession()->wait(100); // wait for dropdown overlay to appear + $linkEl->click(); - if(in_array('treedropdown', $containerClasses)) { - // wait for ajax dropdown to load - $this->getSession()->wait( - 5000, - "jQuery('#" . $container->getAttribute('id') . " .treedropdownfield-panel li').length > 0" - ); - } else { - // wait for dropdown overlay to appear - $this->getSession()->wait(100); - } - - $listEl = $container->find('xpath', sprintf('.//li[contains(normalize-space(string(.)), \'%s\')]', $value)); - if(null === $listEl) { - throw new \InvalidArgumentException(sprintf( - 'Chosen.js list element with title "%s" not found', - $value - )); - } - $listEl->find('xpath', './/a')->click(); + if(in_array('treedropdown', $containerClasses)) { + // wait for ajax dropdown to load + $this->getSession()->wait( + 5000, + "jQuery('#" . $container->getAttribute('id') . " .treedropdownfield-panel li').length > 0" + ); + } else { + // wait for dropdown overlay to appear (might be animated) + $this->getSession()->wait(300); } + $listEl = $container->find('xpath', sprintf('.//li[contains(normalize-space(string(.)), \'%s\')]', $value)); + if(null === $listEl) { + throw new \InvalidArgumentException(sprintf( + 'Chosen.js list element with title "%s" not found', + $value + )); + } + + $listEl->find('xpath', './/a')->click(); } /** From 6aae3d7d0581d50a09b21fc07186eaa74c7b21c0 Mon Sep 17 00:00:00 2001 From: Stevie Mayhew Date: Fri, 7 Jun 2013 12:33:57 +1200 Subject: [PATCH 2/6] MINOR: equality check consistency Updated all equality logic checks to use double == for consistency across the page. --- docs/en/reference/templates.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/reference/templates.md b/docs/en/reference/templates.md index 67356261b..d5f2d3959 100644 --- a/docs/en/reference/templates.md +++ b/docs/en/reference/templates.md @@ -146,7 +146,7 @@ quotes, `kipper`, which is a **literal**. If true, the text inside the if-block is output. :::ss - <% if $MyDinner="kipper" %> + <% if $MyDinner=="kipper" %> Yummy, kipper for tea. <% end_if %> @@ -159,7 +159,7 @@ This example shows the use of the `else` option. The markup after `else` is output if the tested condition is *not* true. :::ss - <% if $MyDinner="kipper" %> + <% if $MyDinner=="kipper" %> Yummy, kipper for tea <% else %> I wish I could have kipper :-( @@ -171,9 +171,9 @@ and the markup for that condition is used. If none of the conditions are true, the markup in the `else` clause is used, if that clause is present. :::ss - <% if $MyDinner="quiche" %> + <% if $MyDinner=="quiche" %> Real men don't eat quiche - <% else_if $MyDinner=$YourDinner %> + <% else_if $MyDinner==$YourDinner %> We both have good taste <% else %> Can I have some of your chips? From 71a5615213e31f04c10e82fd6e69453ccdbb5887 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 10 Jun 2013 11:51:35 +0200 Subject: [PATCH 3/6] Test $allowed_actions on controllers with template name=action conventions --- tests/control/ControllerTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/control/ControllerTest.php b/tests/control/ControllerTest.php index a47bbba5a..be5c61ee0 100644 --- a/tests/control/ControllerTest.php +++ b/tests/control/ControllerTest.php @@ -55,6 +55,20 @@ class ControllerTest extends FunctionalTest { 'even if action is unsecured on parent class' ); + $response = $this->get("ControllerTest_AccessSecuredController/templateaction"); + $this->assertEquals(403, $response->getStatusCode(), + 'Access denied on action with $allowed_actions on defining controller, ' . + 'if action is not a method but rather a template discovered by naming convention' + ); + + $this->session()->inst_set('loggedInAs', $adminUser->ID); + $response = $this->get("ControllerTest_AccessSecuredController/templateaction"); + $this->assertEquals(200, $response->getStatusCode(), + 'Access granted for logged in admin on action with $allowed_actions on defining controller, ' . + 'if action is not a method but rather a template discovered by naming convention' + ); + $this->session()->inst_set('loggedInAs', null); + $response = $this->get("ControllerTest_AccessSecuredController/adminonly"); $this->assertEquals(403, $response->getStatusCode(), 'Access denied on action with $allowed_actions on defining controller, ' . @@ -296,6 +310,12 @@ class ControllerTest_AccessSecuredController extends ControllerTest_AccessBaseCo static $allowed_actions = array( "onlysecuredinsubclassaction" => 'ADMIN', "adminonly" => "ADMIN", + // Defined as ControllerTest_templateaction + 'templateaction' => 'ADMIN' + ); + + protected $templates = array( + 'templateaction' => 'ControllerTest_templateaction' ); // Accessible by ADMIN only @@ -315,6 +335,7 @@ class ControllerTest_AccessSecuredController extends ControllerTest_AccessBaseCo public function adminonly() { return "You must be an admin!"; } + } /** From 3b40711b98970a7e0f0b6709fbdc6d3ddd55dea0 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Jun 2013 17:31:28 +0200 Subject: [PATCH 4/6] BUG Resize infinite loops in IE8 (fixes #575) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IE8 gets a bit confused and fires resize events when element dimensions in the DOM are changed as a result of a resize event, causing an infinite loop. Apart from artificially throttling the event, the only solution I've found is to check for actual window dimension changes. http://stackoverflow.com/questions/12366315/window-resize-event-continually-fires-in-ie7?lq=1 This implicitly fixes an issue where TreeDropdownField panel isn't accessible in the "Insert Media" popup, because the resize event happes to be triggered by the popup overlay, and in effect closes the drop down panel right after opening it. Relating to the jQuery UI component, there's a host of issues and discussions around this, but no solution… http://bugs.jquery.com/ticket/4097 http://bugs.jqueryui.com/ticket/4758 http://bugs.jqueryui.com/ticket/4065 http://bugs.jqueryui.com/ticket/7514 http://bugs.jqueryui.com/ticket/8881 https://groups.google.com/forum/?fromgroups#!topic/jquery-ui/fDSvwAKL6Go http://www.mail-archive.com/jquery-ui@googlegroups.com/msg04839.html --- admin/javascript/LeftAndMain.js | 24 +++++++++++++++++++++++- javascript/TreeDropdownField.js | 18 ++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index 09a948267..66a3445f9 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -4,6 +4,25 @@ jQuery.noConflict(); * File: LeftAndMain.js */ (function($) { + + var windowWidth, windowHeight; + $(window).bind('resize.leftandmain', function(e) { + // Entwine's 'fromWindow::onresize' does not trigger on IE8. Use synthetic event. + var cb = function() {$('.cms-container').trigger('windowresize');}; + + // Workaround to avoid IE8 infinite loops when elements are resized as a result of this event + if($.browser.msie && parseInt($.browser.version, 10) < 9) { + var newWindowWidth = $(window).width(), newWindowHeight = $(window).height(); + if(newWindowWidth != windowWidth || newWindowHeight != windowHeight) { + windowWidth = newWindowWidth; + windowHeight = newWindowHeight; + cb(); + } + } else { + cb(); + } + }); + // setup jquery.entwine $.entwine.warningLevel = $.entwine.WARN_LEVEL_BESTPRACTISE; $.entwine('ss', function($) { @@ -133,7 +152,10 @@ jQuery.noConflict(); fromWindow: { onstatechange: function(){ this.handleStateChange(); }, - onresize: function(){ this.redraw(); } + }, + + 'onwindowresize': function() { + this.redraw(); }, 'from .cms-panel': { diff --git a/javascript/TreeDropdownField.js b/javascript/TreeDropdownField.js index 45b5afb5c..659ffe291 100644 --- a/javascript/TreeDropdownField.js +++ b/javascript/TreeDropdownField.js @@ -4,8 +4,22 @@ * On resize of any close the open treedropdownfields * as we'll need to redo with widths */ - $(window).resize(function() { - $('.TreeDropdownField').closePanel(); + var windowWidth, windowHeight; + $(window).bind('resize.treedropdownfield', function() { + // Entwine's 'fromWindow::onresize' does not trigger on IE8. Use synthetic event. + var cb = function() {$('.TreeDropdownField').closePanel();}; + + // Workaround to avoid IE8 infinite loops when elements are resized as a result of this event + if($.browser.msie && parseInt($.browser.version, 10) < 9) { + var newWindowWidth = $(window).width(), newWindowHeight = $(window).height(); + if(newWindowWidth != windowWidth || newWindowHeight != windowHeight) { + windowWidth = newWindowWidth; + windowHeight = newWindowHeight; + cb(); + } + } else { + cb(); + } }); var strings = { From 671b7a0cc7925fc855ec656f4be36edb6429f915 Mon Sep 17 00:00:00 2001 From: CheeseSucker Date: Tue, 18 Jun 2013 15:50:32 +0300 Subject: [PATCH 5/6] Consolidated command line examples Examples were broken into several
 blocks.
---
 docs/en/topics/testing/index.md | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/docs/en/topics/testing/index.md b/docs/en/topics/testing/index.md
index ec29bb2ca..9f2053d4b 100644
--- a/docs/en/topics/testing/index.md
+++ b/docs/en/topics/testing/index.md
@@ -56,16 +56,16 @@ The `phpunit` binary should be used from the root directory of your website.
 
 	# Runs all tests defined in phpunit.xml
 	phpunit
-
+	
 	# Run all tests of a specific module
  	phpunit framework/tests/
-
+	
  	# Run specific tests within a specific module
  	phpunit framework/tests/filesystem
-
+	
  	# Run a specific test
 	phpunit framework/tests/filesystem/FolderTest.php
-
+	
 	# Run tests with optional `$_GET` parameters (you need an empty second argument)
  	phpunit framework/tests '' flush=all
 
@@ -81,16 +81,16 @@ particularly around formatting test output.
 
 	# Run all tests
 	sake dev/tests/all
-
+	
 	# Run all tests of a specific module (comma-separated)
 	sake dev/tests/module/framework,cms
-
+	
 	# Run specific tests (comma-separated)
 	sake dev/tests/FolderTest,OtherTest
-
+	
 	# Run tests with optional `$_GET` parameters
 	sake dev/tests/all flush=all
-
+	
 	# Skip some tests
 	sake dev/tests/all SkipTests=MySkippedTest
 
@@ -187,4 +187,4 @@ understand the problem space and discover suitable APIs for performing specific
 **Behavior Driven Development (BDD):** An extension of the test-driven programming style, where tests are used primarily
 for describing the specification of how code should perform. In practice, there's little or no technical difference - it
 all comes down to language. In BDD, the usual terminology is changed to reflect this change of focus, so *Specification*
-is used in place of *Test Case*, and *should* is used in place of *expect* and *assert*.
\ No newline at end of file
+is used in place of *Test Case*, and *should* is used in place of *expect* and *assert*.

From c5a719389689ff84d1acc587d71edc3ca410494b Mon Sep 17 00:00:00 2001
From: Ingo Schommer 
Date: Wed, 19 Jun 2013 13:42:28 +0200
Subject: [PATCH 6/6] Environment Config: SS_DATABASE_MEMORY

---
 conf/ConfigureFromEnv.php | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/conf/ConfigureFromEnv.php b/conf/ConfigureFromEnv.php
index b081ca694..6d2e7dc28 100644
--- a/conf/ConfigureFromEnv.php
+++ b/conf/ConfigureFromEnv.php
@@ -16,6 +16,8 @@
  *  - SS_DATABASE_SUFFIX:   A suffix to add to the database name.
  *  - SS_DATABASE_PREFIX:   A prefix to add to the database name.
  *  - SS_DATABASE_TIMEZONE: Set the database timezone to something other than the system timezone.
+ *  - SS_DATABASE_MEMORY:   Use in-memory state if possible. Useful for testing, currently only  
+ *                          supported by the SQLite database adapter.
  * 
  * There is one more setting that is intended to be used by people who work on SilverStripe.
  *  - SS_DATABASE_CHOOSE_NAME: Boolean/Int.  If set, then the system will choose a default database name for you if
@@ -104,6 +106,10 @@ if(defined('SS_DATABASE_USERNAME') && defined('SS_DATABASE_PASSWORD')) {
 	// For schema enabled drivers: 
 	if(defined('SS_DATABASE_SCHEMA')) 
 		$databaseConfig["schema"] = SS_DATABASE_SCHEMA; 
+
+	// For SQlite3 memory databases (mainly for testing purposes)
+	if(defined('SS_DATABASE_MEMORY')) 
+		$databaseConfig["memory"] = SS_DATABASE_MEMORY; 
 }
 
 if(defined('SS_SEND_ALL_EMAILS_TO')) {