Commit 5c4dfd44 authored by Dries's avatar Dries

- Removing and adding files per #885228.

parent bb9bba2d
......@@ -689,6 +689,13 @@ class DrupalWebTestCase extends DrupalTestCase {
*/
protected $plainTextContent;
/**
* The value of the Drupal.settings JavaScript variable for the page currently loaded in the internal browser.
*
* @var Array
*/
protected $drupalSettings;
/**
* The parsed version of the page.
*
......@@ -1698,8 +1705,14 @@ protected function drupalGetAJAX($path, array $options = array(), array $headers
* Note that this is not the Drupal $form_id, but rather the HTML ID of the
* form, which is typically the same thing but with hyphens replacing the
* underscores.
*/
protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL) {
* @param $extra_post
* (optional) A string of additional data to append to the POST submission.
* This can be used to add POST data for which there are no HTML fields, as
* is done by drupalPostAJAX(). This string is literally appended to the
* POST data, so it must already be urlencoded and contain a leading "&"
* (e.g., "&extra_var1=hello+world&extra_var2=you%26me").
*/
protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) {
$submit_matches = FALSE;
$ajax = is_array($submit);
if (isset($path)) {
......@@ -1750,23 +1763,7 @@ protected function drupalPost($path, $edit, $submit, array $options = array(), a
// http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
$post[$key] = urlencode($key) . '=' . urlencode($value);
}
// For AJAX requests, add '_triggering_element_*' and
// 'ajax_html_ids' to the POST data, as ajax.js does.
if ($ajax) {
if (is_array($submit['triggering_element'])) {
// Get the first key/value pair in the array.
$post['_triggering_element_value'] = '_triggering_element_value=' . urlencode(reset($submit['triggering_element']));
$post['_triggering_element_name'] = '_triggering_element_name=' . urlencode(key($submit['triggering_element']));
}
else {
$post['_triggering_element_name'] = '_triggering_element_name=' . urlencode($submit['triggering_element']);
}
foreach ($this->xpath('//*[@id]') as $element) {
$id = (string) $element['id'];
$post[] = urlencode('ajax_html_ids[]') . '=' . urlencode($id);
}
}
$post = implode('&', $post);
$post = implode('&', $post) . $extra_post;
}
$out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers));
// Ensure that any changes to variables in the other thread are picked up.
......@@ -1803,70 +1800,131 @@ protected function drupalPost($path, $edit, $submit, array $options = array(), a
*
* @see ajax.js
*/
protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path = 'system/ajax', array $options = array(), array $headers = array(), $form_html_id = NULL, $ajax_settings = array()) {
protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path = 'system/ajax', array $options = array(), array $headers = array(), $form_html_id = NULL, $ajax_settings = NULL) {
// Get the content of the initial page prior to calling drupalPost(), since
// drupalPost() replaces $this->content.
if (isset($path)) {
$this->drupalGet($path, $options);
}
$content = $this->drupalGetContent();
$return = drupal_json_decode($this->drupalPost(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id));
$content = $this->content;
$drupal_settings = $this->drupalSettings;
// Get the AJAX settings bound to the triggering element.
if (!isset($ajax_settings)) {
if (is_array($triggering_element)) {
$xpath = '//*[@name="' . key($triggering_element) . '" and @value="' . current($triggering_element) . '"]';
}
else {
$xpath = '//*[@name="' . $triggering_element . '"]';
}
if (isset($form_html_id)) {
$xpath = '//form[@id="' . $form_html_id . '"]' . $xpath;
}
$element = $this->xpath($xpath);
$element_id = (string) $element[0]['id'];
$ajax_settings = $drupal_settings['ajax'][$element_id];
}
// Add extra information to the POST data as ajax.js does.
$extra_post = '';
if (isset($ajax_settings['submit'])) {
foreach ($ajax_settings['submit'] as $key => $value) {
$extra_post .= '&' . urlencode($key) . '=' . urlencode($value);
}
}
foreach ($this->xpath('//*[@id]') as $element) {
$id = (string) $element['id'];
$extra_post .= '&' . urlencode('ajax_html_ids[]') . '=' . urlencode($id);
}
// We need $ajax_settings['wrapper'] to perform DOM manipulation.
// Submit the POST request.
$return = drupal_json_decode($this->drupalPost(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id, $extra_post));
// Change the page content by applying the returned commands.
if (!empty($ajax_settings) && !empty($return)) {
// ajax.js applies some defaults to the settings object, so do the same
// for what's used by this function.
$ajax_settings += array(
'method' => 'replaceWith',
);
// DOM can load HTML soup. But, HTML soup can throw warnings, suppress
// them.
@$dom = DOMDocument::loadHTML($content);
foreach ($return as $command) {
// @todo ajax.js can process commands other than 'insert' and can
// process commands that include a 'selector', but these are hard to
// emulate with DOMDocument. For now, we only implement 'insert'
// commands that use $ajax_settings['wrapper'].
if ($command['command'] == 'insert' && !isset($command['selector'])) {
// $dom->getElementById() doesn't work when drupalPostAJAX() is
// invoked multiple times for a page, so use XPath instead. This also
// sets us up for adding support for $command['selector'], though it
// will require transforming a jQuery selector to XPath.
$xpath = new DOMXPath($dom);
$wrapperNode = $xpath->query('//*[@id="' . $ajax_settings['wrapper'] . '"]')->item(0);
if ($wrapperNode) {
// ajax.js adds an enclosing DIV to work around a Safari bug.
$newDom = new DOMDocument();
$newDom->loadHTML('<div>' . $command['data'] . '</div>');
$newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
$method = isset($command['method']) ? $command['method'] : $ajax_settings['method'];
// The "method" is a jQuery DOM manipulation function. Emulate each
// one using PHP's DOMNode API.
switch ($method) {
case 'replaceWith':
$wrapperNode->parentNode->replaceChild($newNode, $wrapperNode);
break;
case 'append':
$wrapperNode->appendChild($newNode);
break;
case 'prepend':
// If no firstChild, insertBefore() falls back to appendChild().
$wrapperNode->insertBefore($newNode, $wrapperNode->firstChild);
break;
case 'before':
$wrapperNode->parentNode->insertBefore($newNode, $wrapperNode);
break;
case 'after':
// If no nextSibling, insertBefore() falls back to appendChild().
$wrapperNode->parentNode->insertBefore($newNode, $wrapperNode->nextSibling);
break;
case 'html':
foreach ($wrapperNode->childNodes as $childNode) {
$wrapperNode->removeChild($childNode);
switch ($command['command']) {
case 'settings':
$drupal_settings = array_merge_recursive($drupal_settings, $command['settings']);
break;
case 'insert':
// @todo ajax.js can process commands that include a 'selector', but
// these are hard to emulate with DOMDocument. For now, we only
// implement 'insert' commands that use $ajax_settings['wrapper'].
if (!isset($command['selector'])) {
// $dom->getElementById() doesn't work when drupalPostAJAX() is
// invoked multiple times for a page, so use XPath instead. This
// also sets us up for adding support for $command['selector'] in
// the future, once we figure out how to transform a jQuery
// selector to XPath.
$xpath = new DOMXPath($dom);
$wrapperNode = $xpath->query('//*[@id="' . $ajax_settings['wrapper'] . '"]')->item(0);
if ($wrapperNode) {
// ajax.js adds an enclosing DIV to work around a Safari bug.
$newDom = new DOMDocument();
$newDom->loadHTML('<div>' . $command['data'] . '</div>');
$newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
$method = isset($command['method']) ? $command['method'] : $ajax_settings['method'];
// The "method" is a jQuery DOM manipulation function. Emulate
// each one using PHP's DOMNode API.
switch ($method) {
case 'replaceWith':
$wrapperNode->parentNode->replaceChild($newNode, $wrapperNode);
break;
case 'append':
$wrapperNode->appendChild($newNode);
break;
case 'prepend':
// If no firstChild, insertBefore() falls back to
// appendChild().
$wrapperNode->insertBefore($newNode, $wrapperNode->firstChild);
break;
case 'before':
$wrapperNode->parentNode->insertBefore($newNode, $wrapperNode);
break;
case 'after':
// If no nextSibling, insertBefore() falls back to
// appendChild().
$wrapperNode->parentNode->insertBefore($newNode, $wrapperNode->nextSibling);
break;
case 'html':
foreach ($wrapperNode->childNodes as $childNode) {
$wrapperNode->removeChild($childNode);
}
$wrapperNode->appendChild($newNode);
break;
}
$wrapperNode->appendChild($newNode);
break;
}
}
}
break;
// @todo Add suitable implementations for these commands in order to
// have full test coverage of what ajax.js can do.
case 'remove':
break;
case 'changed':
break;
case 'css':
break;
case 'data':
break;
case 'restripe':
break;
}
}
$this->drupalSetContent($dom->saveHTML());
$content = $dom->saveHTML();
}
$this->drupalSetContent($content);
$this->drupalSetSettings($drupal_settings);
return $return;
}
......@@ -2397,6 +2455,13 @@ protected function drupalGetContent() {
return $this->content;
}
/**
* Gets the value of the Drupal.settings JavaScript variable for the currently loaded page.
*/
protected function drupalGetSettings() {
return $this->drupalSettings;
}
/**
* Gets an array containing all e-mails sent during this test case.
*
......@@ -2435,6 +2500,17 @@ protected function drupalSetContent($content, $url = 'internal:') {
$this->url = $url;
$this->plainTextContent = FALSE;
$this->elements = FALSE;
$this->drupalSettings = array();
if (preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $content, $matches)) {
$this->drupalSettings = drupal_json_decode($matches[1]);
}
}
/**
* Sets the value of the Drupal.settings JavaScript variable for the currently loaded page.
*/
protected function drupalSetSettings($settings) {
$this->drupalSettings = $settings;
}
/**
......
......@@ -269,24 +269,17 @@ class AJAXMultiFormTestCase extends AJAXTestCase {
// of field items and "add more" button for the multi-valued field within
// each form.
$this->drupalGet('form-test/two-instances-of-same-form');
foreach ($field_xpaths as $form_id => $field_xpath) {
foreach ($field_xpaths as $form_html_id => $field_xpath) {
$this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == 1, t('Found the correct number of field items on the initial page.'));
$this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, t('Found the "add more" button on the initial page.'));
}
$this->assertNoDuplicateIds(t('Initial page contains unique IDs'), 'Other');
// Submit the "add more" button of each form twice. After each corresponding
// page update, ensure the same as above. To successfully implement
// consecutive AJAX submissions, we need to manage $settings as ajax.js
// does for Drupal.settings.
preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $this->content, $matches);
$settings = drupal_json_decode($matches[1]);
foreach ($field_xpaths as $form_id => $field_xpath) {
// page update, ensure the same as above.
foreach ($field_xpaths as $form_html_id => $field_xpath) {
for ($i=0; $i<2; $i++) {
$button = $this->xpath($field_xpath . $button_xpath_suffix);
$button_id = (string) $button[0]['id'];
$commands = $this->drupalPostAJAX(NULL, array(), array($button_name => $button_value), 'system/ajax', array(), array(), $form_id, $settings['ajax'][$button_id]);
$settings = array_merge_recursive($settings, $commands[0]['settings']);
$this->drupalPostAJAX(NULL, array(), array($button_name => $button_value), 'system/ajax', array(), array(), $form_html_id);
$this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == $i+2, t('Found the correct number of field items after an AJAX submission.'));
$this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, t('Found the "add more" button after an AJAX submission.'));
$this->assertNoDuplicateIds(t('Updated page contains unique IDs'), 'Other');
......
......@@ -996,11 +996,7 @@ class FormsRebuildTestCase extends DrupalWebTestCase {
// submission and verify it worked by ensuring the updated page has two text
// field items in the field for which we just added an item.
$this->drupalGet('node/add/page');
preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $this->content, $matches);
$settings = drupal_json_decode($matches[1]);
$button = $this->xpath('//input[@name="field_ajax_test_add_more"]');
$button_id = (string) $button[0]['id'];
$this->drupalPostAJAX(NULL, array(), array('field_ajax_test_add_more' => t('Add another item')), 'system/ajax', array(), array(), 'page-node-form', $settings['ajax'][$button_id]);
$this->drupalPostAJAX(NULL, array(), array('field_ajax_test_add_more' => t('Add another item')), 'system/ajax', array(), array(), 'page-node-form');
$this->assert(count($this->xpath('//div[contains(@class, "field-name-field-ajax-test")]//input[@type="text"]')) == 2, t('AJAX submission succeeded.'));
// Submit the form with the non-AJAX "Save" button, leaving the title field
......
......@@ -13,11 +13,17 @@
function form_test_load_include_menu($form, &$form_state) {
// Submit the form via AJAX. That way the FAPI has to care about including
// the file specified in hook_menu().
$ajax_wrapper_id = drupal_html_id('form-test-load-include-menu-ajax-wrapper');
$form['ajax_wrapper'] = array(
'#markup' => '<div id="' . $ajax_wrapper_id . '"></div>',
);
$form['button'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#submit' => array('form_test_load_include_submit'),
'#ajax' => array(
'wrapper' => $ajax_wrapper_id,
'method' => 'append',
'callback' => 'form_test_load_include_menu_ajax',
),
);
......@@ -32,9 +38,12 @@ function form_test_load_include_submit($form, $form_state) {
}
/**
* Ajax callback for the file inclusion via menu test. We don't need to return
* anything as the messages are added automatically.
* Ajax callback for the file inclusion via menu test.
*/
function form_test_load_include_menu_ajax($form) {
// We don't need to return anything, since #ajax['method'] is 'append', which
// does not remove the original #ajax['wrapper'] element, and status messages
// are automatically added by the AJAX framework as long as there's a wrapper
// element to add them to.
return '';
}
/* $Id$ */
/**
* @file
* RTL styles for administration pages.
*/
/**
* Administration blocks.
*/
div.admin-panel .body {
padding: 0 8px 2px 4px;
}
div.admin .left {
float: right;
margin-left: 0;
......@@ -14,7 +21,6 @@ div.admin .right {
margin-left: 1em;
margin-right: 0;
}
div.admin .expert-link {
text-align: left;
margin-right: 0;
......@@ -23,19 +29,48 @@ div.admin .expert-link {
padding-left: 4px;
}
/**
* Status report.
*/
table.system-status-report th,
table.system-status-report tr.merge-up td {
padding-right: 30px;
}
table.system-status-report th {
background-position: 95% 50%;
}
/**
* Appearance page.
*/
table.screenshot {
margin-left: 1em;
}
.system-themes-list-enabled .theme-selector .screenshot,
.system-themes-list-enabled .theme-selector .no-screenshot {
float: right;
margin: 0 0 0 20px;
}
.system-themes-list-disabled .theme-selector {
float: right;
padding: 20px 0 20px 20px;
}
.theme-selector .operations li {
float: right;
border-right: none;
border-left: 1px solid #cdcdcd;
}
.theme-selector .operations li.last {
padding: 0 0.7em 0 0;
border-left: none;
}
.theme-selector .operations li.first {
padding: 0 0 0 0.7em;
}
/**
* Date and time settings page.
*/
.date-container {
clear: right;
}
......@@ -49,7 +84,7 @@ table.screenshot {
}
/**
* Exposed filters
* Exposed filters.
*/
.exposed-filters .filters {
float: right;
......
/* $Id$ */
/*
** Formatting for administration page
*/
/**
* @file
* Styles for administration pages.
*/
/**
* Administration blocks.
*/
div.admin-panel {
margin: 0;
padding: 5px 5px 15px 5px;
}
div.admin-panel .description {
margin: 0 0 3px;
padding: 2px 0 3px 0;
}
div.admin-panel .body {
padding: 0 4px 2px 8px; /* LTR */
}
div.admin {
padding-top: 15px;
}
div.admin .left {
float: left; /* LTR */
width: 47%;
......@@ -31,13 +32,25 @@ div.admin .right {
width: 47%;
margin-right: 1em; /* LTR */
}
div.admin .expert-link {
text-align: right; /* LTR */
margin-right: 1em; /* LTR */
padding-right: 4px; /* LTR */
}
/**
* Markup generated by theme_system_compact_link().
*/
.compact-link {
margin: 0 0 0.5em 0;
}
/**
* Modules page.
*/
#system-modules div.incompatible {
font-weight: bold;
}
table.package {
width: 100%;
}
......@@ -61,9 +74,27 @@ span.admin-enabled {
span.admin-missing {
color: #f00;
}
a.module-link {
display: block;
padding: 1px 0 1px 20px; /* LTR */
white-space: nowrap;
}
a.module-link-help {
background: url(../../misc/help.png) 0 50% no-repeat; /* LTR */
}
a.module-link-permissions {
background: url(../../misc/permissions.png) 0 50% no-repeat; /* LTR */
}
a.module-link-configure {
background: url(../../misc/configure.png) 0 50% no-repeat; /* LTR */
}
.module-help {
margin-left: 1em; /* LTR */
float: right; /* LTR */
}
/**
* Formatting for status report
* Status report.
*/
table.system-status-report th {
border-bottom: 1px solid #ccc;
......@@ -87,9 +118,19 @@ table.system-status-report tr.warning th {
table.system-status-report tr.ok th {
background-image: url(../../misc/watchdog-ok.png);
}
tr.merge-down,
tr.merge-down td,
tr.merge-down th {
border-bottom-width: 0 !important;
}
tr.merge-up,
tr.merge-up td,
tr.merge-up th {
border-top-width: 0 !important;
}
/**
* Formatting for theme configuration
* Theme settings.
*/
.theme-settings-left {
float: left;
......@@ -104,7 +145,7 @@ table.system-status-report tr.ok th {
}
/**
* Formatting for theme overview
* Appearance page.
*/
table.screenshot {
margin-right: 1em; /* LTR */
......@@ -115,10 +156,91 @@ table.screenshot {
.theme-info p {
margin-top: 0;
}
.system-themes-list {
margin-bottom: 20px;
}
.system-themes-list-disabled {
border-top: 1px solid #cdcdcd;
padding-top: 20px;
}
.system-themes-list h2 {
margin: 0;
}
.theme-selector {
padding-top: 20px;
}
.theme-selector .screenshot,
.theme-selector .no-screenshot {
border: 1px solid #e0e0d8;
padding: 2px;
vertical-align: bottom;
width: 294px;
height: 219px;
line-height: 219px;
text-align: center;
}
.theme-default .screenshot {
border: 1px solid #aaa;
}
.system-themes-list-enabled .theme-selector .screenshot,
.system-themes-list-enabled .theme-selector .no-screenshot {
float: left; /* LTR */
margin: 0 20px 0 0; /* LTR */
}
.system-themes-list-disabled .theme-selector .screenshot,
.system-themes-list-disabled .theme-selector .no-screenshot {
width: 194px;
height: 144px;
line-height: 144px;
}
.theme-selector h3 {
font-weight: normal;
}
.theme-default h3 {
font-weight: bold;
}
.system-themes-list-enabled .theme-selector h3 {
margin-top: 0;
}
.system-themes-list-disabled .theme-selector {
width: 300px;
float: left; /* LTR */
padding: 20px 20px 20px 0; /* LTR */
}
.system-themes-list-enabled .theme-info {
max-width: 940px;
}
.system-themes-list-disabled .theme-info {
min-height: 170px;
}
.theme-selector .incompatible {
margin-top: 10px;