diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 1b746e6..61a8bd3 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -1344,4 +1344,42 @@
$target_storage->write($name, $source_storage->read($name));
}
}
+
+ /**
+ * Performs a CSS selection based search on the current page contents.
+ *
+ * Utilizes \PHPUnit_Util_XML::cssSelect to perform a CSS based search of the
+ * contents of the internal browser. The search is relative to the root
+ * element (HTML tag normally) of the page.
+ *
+ * @param string $selector
+ * CSS selector to use in the search.
+ * @param string $contains
+ * (optional) Filter matching nodes to those containing the given content.
+ * Note that due to limitations with \PHPUnit_Util_XML::cssSelect this does
+ * not work when you pass a nested selector such as 'li h2'.
+ * @param mixed $content
+ * (optional) Context to parse for selector. Defaults to $this->content.
+ *
+ * @return array
+ * Array of matching \DOMElement nodes.
+ *
+ * @see \PHPUnit_Util_XML::cssSelect
+ */
+ protected function cssSelect($selector, $contains = TRUE, $content = FALSE) {
+ if (!$content && isset($this->content)) {
+ $content = $this->content;
+ }
+ if (is_array($content)) {
+ $content = array_reduce($content, function ($value, $item) {
+ $value .= $item->ownerDocument->saveHtml($item);
+ return $value;
+ }, '');
+ }
+ if ($nodes = \PHPUnit_Util_XML::cssSelect($selector, $contains, $content, TRUE)) {
+ return $nodes;
+ }
+ return array();
+ }
+
}
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestUnitTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestUnitTest.php
new file mode 100644
index 0000000..8af5204
--- /dev/null
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestUnitTest.php
@@ -0,0 +1,73 @@
+ 'SimpleTest functionality (Unit test)',
+ 'description' => "Tests some methods of Simpletest as unit test.",
+ 'group' => 'SimpleTest'
+ );
+ }
+
+ /**
+ * Tests the cssSelect method.
+ *
+ * @see \PHPUnit_Util_XML::cssSelect()
+ */
+ public function testCssSelect() {
+ $content = '
empty 1
+
+
+ My Subchild
+ My Child
+
+
+ Test Id Text
+
+ classy
';
+
+ $root = $this->cssSelect('div', TRUE, $content);
+ $this->assertEqual(count($root), 3, '3 divs found');
+
+ $root = $this->cssSelect('#test_id', TRUE, $content);
+ $this->assertEqual(count($root), 1, 'ID selector works');
+
+ $child = $this->cssSelect('p', 'My Subchild My Child', $content);
+ $this->assertEqual(count($child), 1, 'Text content found.');
+
+ $child = $this->cssSelect('#test_subchild_id', 'My Subchild', $content);
+ $this->assertEqual(count($child), 1, 'Text match found.');
+
+ $child = $this->cssSelect('#test_subchild_id', 'Wrong text', $content);
+ $this->assertEqual(count($child), 0, 'Text match not found');
+
+ $child = $this->cssSelect('div p', TRUE, $content);
+ $this->assertEqual(count($child), 1, 'Nested selector');
+
+ $child = $this->cssSelect('div > span', TRUE, $content);
+ $this->assertEqual(count($child), 0, 'Nested immediate child selector.');
+
+ $child = $this->cssSelect('div span p', TRUE, $content);
+ $this->assertEqual(count($child), 0, 'Nested selector wrong order');
+
+ $child = $this->cssSelect('div.item', 'classy', $content);
+ $this->assertEqual(count($child), 1, 'Element with class');
+
+ $child = $this->cssSelect('span.item', 'classy', $content);
+ $this->assertEqual(count($child), 0, 'Element with class');
+ }
+
+}
diff --git a/core/modules/tour/lib/Drupal/tour/Tests/TourTest.php b/core/modules/tour/lib/Drupal/tour/Tests/TourTest.php
index db69e18..cb0a44a 100644
--- a/core/modules/tour/lib/Drupal/tour/Tests/TourTest.php
+++ b/core/modules/tour/lib/Drupal/tour/Tests/TourTest.php
@@ -8,12 +8,12 @@
namespace Drupal\tour\Tests;
use Drupal\Core\Language\Language;
-use Drupal\simpletest\WebTestBase;
+use Drupal\tour\Tests\TourTestBase;
/**
* Tests tour functionality.
*/
-class TourTest extends WebTestBase {
+class TourTest extends TourTestBase {
/**
* Modules to enable.
@@ -40,33 +40,27 @@ class TourTest extends WebTestBase {
* Test tour functionality.
*/
public function testTourFunctionality() {
+ $this->loadTours('tour_test');
// Navigate to tour-test-1 and verify the tour_test_1 tip is found with appropriate classes.
$this->drupalGet('tour-test-1');
- $elements = $this->xpath('//li[@data-id=:data_id and @class=:classes and ./h2[contains(., :text)]]', array(
- ':classes' => 'tip-module-tour-test tip-type-text tip-tour-test-1 even last',
- ':data_id' => 'tour-test-1',
- ':text' => 'The first tip',
- ));
+ $tour = $this->getTour('tour-test');
+ $this->hasTipsOnPage($tour);
+
+ $elements = $this->cssSelect('h2', 'The first tip', $this->cssSelect("li.tip-module-tour-test.tip-type-text.tip-tour-test-1.even.last[data-id=tour-test-1]"));
$this->assertEqual(count($elements), 1, 'Found English variant of tip 1.');
- $elements = $this->xpath('//li[@data-id=:data_id and @class=:classes and ./p//a[@href=:href and contains(., :text)]]', array(
- ':classes' => 'tip-module-tour-test tip-type-text tip-tour-test-1 even last',
- ':data_id' => 'tour-test-1',
- ':href' => url('', array('absolute' => TRUE)),
- ':text' => 'Drupal',
- ));
+ // Check for the token replacements done in [site:name]
+ $context = $this->cssSelect('li.tip-type-text[data-id=tour-test-1');
+ $context = $this->cssSelect('p', TRUE, $context);
+ $url = url('', array('absolute' => TRUE));
+
+ $elements = $this->cssSelect('a[href=' . $url . ']', 'Drupal', $context);
$this->assertEqual(count($elements), 1, 'Found Token replacement.');
- $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
- ':data_id' => 'tour-test-2',
- ':text' => 'The quick brown fox',
- ));
+ $elements = $this->cssSelect('h2', 'The quick brown fox', $this->cssSelect("li[data-id=tour-test-2]"));
$this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 2.');
- $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
- ':data_id' => 'tour-test-1',
- ':text' => 'La pioggia cade in spagna',
- ));
+ $elements = $this->cssSelect('h2', 'La pioggia cade in spagna', $this->cssSelect("li[data-id=tour-test-1]"));
$this->assertNotEqual(count($elements), 1, 'Did not find Italian variant of tip 1.');
// Ensure that plugin's work.
@@ -74,33 +68,27 @@ class TourTest extends WebTestBase {
// Navigate to tour-test-2/subpath and verify the tour_test_2 tip is found.
$this->drupalGet('tour-test-2/subpath');
- $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
- ':data_id' => 'tour-test-2',
- ':text' => 'The quick brown fox',
- ));
+ $tour = $this->getTour('tour-test-2');
+ $this->hasTipsOnPage($tour, array('tour-test-2'));
+
+
+ $elements = $this->cssSelect('h2', 'The quick brown fox', $this->cssSelect("li[data-id=tour-test-2]"));
$this->assertEqual(count($elements), 1, 'Found English variant of tip 2.');
- $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
- ':data_id' => 'tour-test-1',
- ':text' => 'The first tip',
- ));
+ $elements = $this->cssSelect('h2', 'The first tip', $this->cssSelect("li[data-id=tour-test-1]"));
$this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 1.');
// Enable Italian language and navigate to it/tour-test1 and verify italian
// version of tip is found.
language_save(new Language(array('langcode' => 'it')));
$this->drupalGet('it/tour-test-1');
+ $tour = $this->getTour('tour-test');
+ $this->hasTipsOnPage($tour);
- $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
- ':data_id' => 'tour-test-1',
- ':text' => 'La pioggia cade in spagna',
- ));
+ $elements = $this->cssSelect('h2', 'La pioggia cade in spagna', $this->cssSelect("li[data-id=tour-test-1]"));
$this->assertEqual(count($elements), 1, 'Found Italian variant of tip 1.');
- $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
- ':data_id' => 'tour-test-1',
- ':text' => 'The first tip',
- ));
+ $elements = $this->cssSelect('h2', 'The quick brown fox', $this->cssSelect("li[data-id=tour-test-2]"));
$this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 1.');
language_save(new Language(array('langcode' => 'en')));
@@ -138,18 +126,12 @@ class TourTest extends WebTestBase {
// Navigate to tour-test-1 and verify the new tip is found.
$this->drupalGet('tour-test-1');
- $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array(
- ':data_id' => 'tour-code-test-1',
- ':text' => 'The rain in spain',
- ));
+ $elements = $this->cssSelect('h2', 'The rain in spain', $this->cssSelect("li[data-id=tour-code-test-1]"));
$this->assertEqual(count($elements), 1, 'Found the required tip markup for tip 4');
// Verify that the weight sorting works by ensuring the lower weight item
// (tip 4) has the 'End tour' button.
- $elements = $this->xpath('//li[@data-id=:data_id and @data-text=:text]', array(
- ':data_id' => 'tour-code-test-1',
- ':text' => 'End tour',
- ));
+ $elements = $this->cssSelect("li[data-id=tour-code-test-1][data-text*=End]");
$this->assertEqual(count($elements), 1, 'Found code tip was weighted last and had "End tour".');
// Test hook_tour_alter().
diff --git a/core/modules/tour/lib/Drupal/tour/Tests/TourTestBase.php b/core/modules/tour/lib/Drupal/tour/Tests/TourTestBase.php
new file mode 100644
index 0000000..7dd7d7e
--- /dev/null
+++ b/core/modules/tour/lib/Drupal/tour/Tests/TourTestBase.php
@@ -0,0 +1,173 @@
+tours = array();
+ $tours = drupal_container()->get('config.storage')->listAll('tour.tour.');
+
+ foreach ($tours as $name) {
+ //$schema = config_typed()->getDefinition($name);
+ $tour = config_typed()->get($name)->getValue();
+ $this->tours[$name] = $tour;
+ }
+ }
+
+ /**
+ * Load tours defined by given module.
+ *
+ * It should be possible to have tours not defined by modules but
+ * added to active config.
+ *
+ * @param string $module
+ */
+ function loadTours($module = NULL) {
+ $this->loadToursAll();
+ $this->active_tours = array_filter($this->tours, function($item) use ($module) {
+ return empty($module) || $item['module'] == $module;
+ });
+ $tours = join(', ', array_keys($this->active_tours));
+ $this->assertTrue(count($this->active_tours), format_string('Found %tours.', array('%tours' => $tours), 'Tours'));
+ $this->verbose(print_r($this->active_tours, TRUE));
+ }
+
+ /**
+ * Returns one of the active tours.
+ *
+ * @param type $id
+ * @return type
+ */
+ function getTour($id) {
+ $tours = array_filter($this->active_tours, function($item) use ($id) {
+ return $item['id'] == $id;
+ });
+ $this->assertEqual(count($tours), 1, "Tour $id found");
+ if (count($tours) == 1) {
+ return array_shift($tours);
+ }
+ }
+
+ /**
+ * Tests for tour on current page.
+ *
+ * If no tour is found there is no tour for the page
+ * or the current user may not see it. Check your access settings.
+ *
+ * @return boolean
+ */
+ function hasTourOnPage() {
+ $path = '//ol[@id="tour"]';
+ $elements = $this->xpath($path);
+ $tour_found = count($elements) == 1;
+ $this->assertTrue($tour_found, "Tour found on page");
+ return $tour_found;
+ }
+
+ /**
+ * Check the current page for the given tour optional filtered by give tip IDs.
+ *
+ * Both the tip as the tip target are checked for.
+ *
+ * TODO:
+ * - order tips by weight? This will help a Tour tester.
+ *
+ * @param array $tour
+ * @param array $tip_ids
+ */
+ function hasTipsOnPage(array $tour, array $tip_ids = array()) {
+ $tour_id = $tour['id'];
+ if (empty($tips_ids)) {
+ $tips_ids = array_keys($tour['tips']);
+ }
+ foreach ($tips_ids as $id) {
+ $tip = $tour['tips'][$id];
+ if (isset($tip['attributes'])) {
+ $attributes = $tip['attributes'];
+ $selector = array();
+ if (isset($attributes['data-id'])) {
+ $selector[':data-id'] = $tip['attributes']['data-id'];
+ $this->tipOnPage($tip['id'], $selector);
+ $this->tipTargetOnPage($tip['id'], $selector);
+ }
+ if (isset($attributes['data-class'])) {
+ $selector[':data-class'] = $tip['attributes']['data-class'];
+ $this->tipOnPage($tip['id'], $selector);
+ $this->tipTargetOnPage($tip['id'], $selector);
+ }
+ if (empty($selector)) {
+ // Modal tip
+ $this->assertTrue(empty($selector), "Modal tip");
+ }
+ elseif (count($selector) > 1) {
+ $this->assertTrue(count($selector) > 1, 'Do not use both data-id and data-class.');
+ }
+ }
+ }
+ }
+
+ function tipOnPage($tip_id, $selector) {
+ //TODO:
+ // Use the #tour selector: fails on '//ol[@id=tour]/li['
+ $path = '//li[';
+ if (isset($selector[':data-id'])) {
+ $path .= '@data-id=:data-id and ';
+ }
+ if (isset($selector[':data-class'])) {
+ // No need to parse path as we select on data-class not on real class.
+ $path .= '@data-class=:data-class and ';
+ }
+ $path .= ' ./h2]';
+ $elements = $this->xpath($path, $selector);
+ $this->assertEqual(count($elements), 1, format_string("Found tip for selection '$path' on '$tip_id' ", $selector));
+ }
+
+ /**
+ * Test for the tip target.
+ *
+ * When the target is not found this means either to page content changed
+ * or the tour selector is wrong.
+ *
+ * @param type $tip_id
+ * @param type $selector
+ */
+ function tipTargetOnPage($tip_id, $selector) {
+ if (isset($selector[':data-id'])) {
+ $path = '//*[' . '@id=:data-id' . ']';
+ $elements = $this->xpath($path, $selector);
+ }
+ else if (isset($selector[':data-class'])) {
+ $path = $selector[':data-class'];
+ // TODO: somehow it seems first item is ALWAYS a class!?!
+ $class = '.' . $path;
+ // Normalize spaces
+ $class = preg_replace('/\s\s+/', ' ', $class);
+ $css_paths = explode(' ', $class);
+ $context = FALSE;
+ while (!empty($css_paths)) {
+ $css_path = array_shift($css_paths);
+ $context = $this->cssSelect($css_path, TRUE, $context);
+ }
+ $elements = $context;
+ }
+ $this->assertEqual(count($elements), 1, format_string("Found target for selection '$path' on '$tip_id' ", $selector));
+ }
+
+}
diff --git a/core/modules/tour/tests/tour_test/config/tour.tour.tour-test-2.yml b/core/modules/tour/tests/tour_test/config/tour.tour.tour-test-2.yml
index 62e5ae8..019d425 100644
--- a/core/modules/tour/tests/tour_test/config/tour.tour.tour-test-2.yml
+++ b/core/modules/tour/tests/tour_test/config/tour.tour.tour-test-2.yml
@@ -12,4 +12,4 @@ tips:
body: Per lo piĆ¹ in pianura.
weight: "2"
attributes:
- data-id: tour-test-2
+ data-id: tour-test-target-2
diff --git a/core/modules/tour/tests/tour_test/config/tour.tour.tour-test.yml b/core/modules/tour/tests/tour_test/config/tour.tour.tour-test.yml
index 9d389ed..ff92728 100644
--- a/core/modules/tour/tests/tour_test/config/tour.tour.tour-test.yml
+++ b/core/modules/tour/tests/tour_test/config/tour.tour.tour-test.yml
@@ -12,7 +12,7 @@ tips:
body: Is [site:name] always the best dressed?
weight: "1"
attributes:
- data-id: tour-test-1
+ data-id: tour-test-target-1
tour-test-3:
id: tour-test-3
plugin: image
@@ -20,4 +20,4 @@ tips:
url: http://local/image.png
weight: "1"
attributes:
- data-id: tour-test-3
+ data-id: tour-test-target-3
diff --git a/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Controller/TourTestController.php b/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Controller/TourTestController.php
index 73b7cde..910ab27 100644
--- a/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Controller/TourTestController.php
+++ b/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Controller/TourTestController.php
@@ -29,14 +29,21 @@ class TourTestController implements ControllerInterface {
'tip-1' => array(
'#type' => 'container',
'#attributes' => array(
- 'id' => 'tour-test-1',
+ 'id' => 'tour-test-target-1',
),
'#children' => t('Where does the rain in Spain fail?'),
),
+ 'burp' => array(
+ '#type' => 'container',
+ '#attributes' => array(
+ 'id' => 'tour-test-target-3',
+ ),
+ '#children' => t('Target for the image plugin'),
+ ),
'tip-4' => array(
'#type' => 'container',
'#attributes' => array(
- 'id' => 'tour-test-4',
+ 'id' => 'tour-test-target-4',
),
'#children' => t('Tip created later?'),
),
@@ -50,7 +57,7 @@ class TourTestController implements ControllerInterface {
return array(
'#type' => 'container',
'#attributes' => array(
- 'id' => 'tour-test-2',
+ 'id' => 'tour-test-target-2',
),
'#children' => t('Pangram example'),
);
diff --git a/core/modules/views_ui/config/tour.tour.views-ui.yml b/core/modules/views_ui/config/tour.tour.views-ui.yml
index 6f2ca9d..981effc 100644
--- a/core/modules/views_ui/config/tour.tour.views-ui.yml
+++ b/core/modules/views_ui/config/tour.tour.views-ui.yml
@@ -4,6 +4,7 @@ label: Views ui
langcode: en
paths:
- admin/structure/views/view/*/edit
+ - admin/structure/views/view/*
tips:
views-ui-active-display:
id: views-ui-active-display
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Tests/TourTest.php b/core/modules/views_ui/lib/Drupal/views_ui/Tests/TourTest.php
new file mode 100644
index 0000000..9c5c739
--- /dev/null
+++ b/core/modules/views_ui/lib/Drupal/views_ui/Tests/TourTest.php
@@ -0,0 +1,59 @@
+ 'Views Tours',
+ 'description' => 'Tests the tours provided by Views UI.',
+ 'group' => 'Tour',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp();
+
+ // Add an admin user will full rights;
+ $this->admin = $this->drupalCreateUser(array('access tour', 'administer views'));
+ }
+
+ /**
+ * Tests that analyze works in general.
+ */
+ function testTourViewEdit() {
+ $this->drupalLogin($this->admin);
+
+ $this->loadTours('views_ui');
+
+ $this->drupalGet('admin/structure/views/view/frontpage/edit');
+ $this->hasTourOnPage();
+ $tour = $this->getTour('views-ui');
+ $this->hasTipsOnPage($tour);
+
+ $this->drupalGet('admin/structure/views/view/frontpage');
+ $tour = $this->getTour('views-ui');
+ $this->hasTipsOnPage($tour);
+
+ }
+
+}