composer update

This commit is contained in:
Oliver Davies 2019-01-24 08:00:03 +00:00
parent f6abc3dce2
commit 71dfaca858
1753 changed files with 45274 additions and 14619 deletions

View file

@ -1,39 +0,0 @@
<?php
namespace Drupal\system\Tests\Ajax;
/**
* Various tests of AJAX behavior.
*
* @group Ajax
*/
class ElementValidationTest extends AjaxTestBase {
/**
* Tries to post an Ajax change to a form that has a validated element.
*
* The drivertext field is Ajax-enabled. An additional field is not, but
* is set to be a required field. In this test the required field is not
* filled in, and we want to see if the activation of the "drivertext"
* Ajax-enabled field fails due to the required field being empty.
*/
public function testAjaxElementValidation() {
$edit = ['drivertext' => t('some dumb text')];
// Post with 'drivertext' as the triggering element.
$this->drupalPostAjaxForm('ajax_validation_test', $edit, 'drivertext');
// Look for a validation failure in the resultant JSON.
$this->assertNoText(t('Error message'), 'No error message in resultant JSON');
$this->assertText('ajax_forms_test_validation_form_callback invoked', 'The correct callback was invoked');
$this->drupalGet('ajax_validation_test');
$edit = ['drivernumber' => 12345];
// Post with 'drivernumber' as the triggering element.
$this->drupalPostAjaxForm('ajax_validation_test', $edit, 'drivernumber');
// Look for a validation failure in the resultant JSON.
$this->assertNoText(t('Error message'), 'No error message in resultant JSON');
$this->assertText('ajax_forms_test_validation_number_form_callback invoked', 'The correct callback was invoked');
}
}

View file

@ -1,65 +0,0 @@
<?php
namespace Drupal\system\Tests\Ajax;
use Drupal\Core\Ajax\DataCommand;
/**
* Tests that form values are properly delivered to AJAX callbacks.
*
* @group Ajax
*/
class FormValuesTest extends AjaxTestBase {
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['access content']));
}
/**
* Submits forms with select and checkbox elements via Ajax.
*/
public function testSimpleAjaxFormValue() {
// Verify form values of a select element.
foreach (['red', 'green', 'blue'] as $item) {
$edit = [
'select' => $item,
];
$commands = $this->drupalPostAjaxForm('ajax_forms_test_get_form', $edit, 'select');
$expected = new DataCommand('#ajax_selected_color', 'form_state_value_select', $item);
$this->assertCommand($commands, $expected->render(), 'Verification of AJAX form values from a selectbox issued with a correct value.');
}
// Verify form values of a checkbox element.
foreach ([FALSE, TRUE] as $item) {
$edit = [
'checkbox' => $item,
];
$commands = $this->drupalPostAjaxForm('ajax_forms_test_get_form', $edit, 'checkbox');
$expected = new DataCommand('#ajax_checkbox_value', 'form_state_value_select', (int) $item);
$this->assertCommand($commands, $expected->render(), 'Verification of AJAX form values from a checkbox issued with a correct value.');
}
// Verify that AJAX elements with invalid callbacks return error code 500.
// Ensure the test error log is empty before these tests.
$this->assertNoErrorsLogged();
// We don't need to check for the X-Drupal-Ajax-Token header with these
// invalid requests.
$this->assertAjaxHeader = FALSE;
foreach (['null', 'empty', 'nonexistent'] as $key) {
$element_name = 'select_' . $key . '_callback';
$edit = [
$element_name => 'red',
];
$commands = $this->drupalPostAjaxForm('ajax_forms_test_get_form', $edit, $element_name);
$this->assertResponse(500);
}
// Switch this back to the default.
$this->assertAjaxHeader = TRUE;
// The exceptions are expected. Do not interpret them as a test failure.
// Not using File API; a potential error must trigger a PHP warning.
unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
}
}

View file

@ -1,216 +0,0 @@
<?php
namespace Drupal\system\Tests\Ajax;
use Drupal\Core\Ajax\AddCssCommand;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\PrependCommand;
use Drupal\Core\Ajax\SettingsCommand;
use Drupal\Core\Asset\AttachedAssets;
/**
* Performs tests on AJAX framework functions.
*
* @group Ajax
*/
class FrameworkTest extends AjaxTestBase {
/**
* Verifies the Ajax rendering of a command in the settings.
*/
public function testAJAXRender() {
// Verify that settings command is generated if JavaScript settings exist.
$commands = $this->drupalGetAjax('ajax-test/render');
$expected = new SettingsCommand(['ajax' => 'test'], TRUE);
$this->assertCommand($commands, $expected->render(), 'JavaScript settings command is present.');
}
/**
* Tests AjaxResponse::prepare() AJAX commands ordering.
*/
public function testOrder() {
$expected_commands = [];
// Expected commands, in a very specific order.
$asset_resolver = \Drupal::service('asset.resolver');
$css_collection_renderer = \Drupal::service('asset.css.collection_renderer');
$js_collection_renderer = \Drupal::service('asset.js.collection_renderer');
$renderer = \Drupal::service('renderer');
$expected_commands[0] = new SettingsCommand(['ajax' => 'test'], TRUE);
$build['#attached']['library'][] = 'ajax_test/order-css-command';
$assets = AttachedAssets::createFromRenderArray($build);
$css_render_array = $css_collection_renderer->render($asset_resolver->getCssAssets($assets, FALSE));
$expected_commands[1] = new AddCssCommand($renderer->renderRoot($css_render_array));
$build['#attached']['library'][] = 'ajax_test/order-header-js-command';
$build['#attached']['library'][] = 'ajax_test/order-footer-js-command';
$assets = AttachedAssets::createFromRenderArray($build);
list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, FALSE);
$js_header_render_array = $js_collection_renderer->render($js_assets_header);
$js_footer_render_array = $js_collection_renderer->render($js_assets_footer);
$expected_commands[2] = new PrependCommand('head', $js_header_render_array);
$expected_commands[3] = new AppendCommand('body', $js_footer_render_array);
$expected_commands[4] = new HtmlCommand('body', 'Hello, world!');
// Load any page with at least one CSS file, at least one JavaScript file
// and at least one #ajax-powered element. The latter is an assumption of
// drupalPostAjaxForm(), the two former are assumptions of the Ajax
// renderer.
// @todo refactor AJAX Framework + tests to make less assumptions.
$this->drupalGet('ajax_forms_test_lazy_load_form');
// Verify AJAX command order — this should always be the order:
// 1. JavaScript settings
// 2. CSS files
// 3. JavaScript files in the header
// 4. JavaScript files in the footer
// 5. Any other AJAX commands, in whatever order they were added.
$commands = $this->drupalPostAjaxForm(NULL, [], NULL, 'ajax-test/order', [], [], NULL, []);
$this->assertCommand(array_slice($commands, 0, 1), $expected_commands[0]->render(), 'Settings command is first.');
$this->assertCommand(array_slice($commands, 1, 1), $expected_commands[1]->render(), 'CSS command is second (and CSS files are ordered correctly).');
$this->assertCommand(array_slice($commands, 2, 1), $expected_commands[2]->render(), 'Header JS command is third.');
$this->assertCommand(array_slice($commands, 3, 1), $expected_commands[3]->render(), 'Footer JS command is fourth.');
$this->assertCommand(array_slice($commands, 4, 1), $expected_commands[4]->render(), 'HTML command is fifth.');
}
/**
* Tests the behavior of an error alert command.
*/
public function testAJAXRenderError() {
// Verify custom error message.
$edit = [
'message' => 'Custom error message.',
];
$commands = $this->drupalGetAjax('ajax-test/render-error', ['query' => $edit]);
$expected = new AlertCommand($edit['message']);
$this->assertCommand($commands, $expected->render(), 'Custom error message is output.');
}
/**
* Tests that new JavaScript and CSS files are lazy-loaded on an AJAX request.
*/
public function testLazyLoad() {
$asset_resolver = \Drupal::service('asset.resolver');
$css_collection_renderer = \Drupal::service('asset.css.collection_renderer');
$js_collection_renderer = \Drupal::service('asset.js.collection_renderer');
$renderer = \Drupal::service('renderer');
$expected = [
'setting_name' => 'ajax_forms_test_lazy_load_form_submit',
'setting_value' => 'executed',
'library_1' => 'system/admin',
'library_2' => 'system/drupal.system',
];
// Get the base page.
$this->drupalGet('ajax_forms_test_lazy_load_form');
$original_settings = $this->getDrupalSettings();
$original_libraries = explode(',', $original_settings['ajaxPageState']['libraries']);
// Verify that the base page doesn't have the settings and files that are to
// be lazy loaded as part of the next requests.
$this->assertTrue(!isset($original_settings[$expected['setting_name']]), format_string('Page originally lacks the %setting, as expected.', ['%setting' => $expected['setting_name']]));
$this->assertTrue(!in_array($expected['library_1'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', ['%library' => $expected['library_1']]));
$this->assertTrue(!in_array($expected['library_2'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', ['%library' => $expected['library_2']]));
// Calculate the expected CSS and JS.
$assets = new AttachedAssets();
$assets->setLibraries([$expected['library_1']])
->setAlreadyLoadedLibraries($original_libraries);
$css_render_array = $css_collection_renderer->render($asset_resolver->getCssAssets($assets, FALSE));
$expected_css_html = $renderer->renderRoot($css_render_array);
$assets->setLibraries([$expected['library_2']])
->setAlreadyLoadedLibraries($original_libraries);
$js_assets = $asset_resolver->getJsAssets($assets, FALSE)[1];
unset($js_assets['drupalSettings']);
$js_render_array = $js_collection_renderer->render($js_assets);
$expected_js_html = $renderer->renderRoot($js_render_array);
// Submit the AJAX request without triggering files getting added.
$commands = $this->drupalPostAjaxForm(NULL, ['add_files' => FALSE], ['op' => t('Submit')]);
$new_settings = $this->getDrupalSettings();
$new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']);
// Verify the setting was not added when not expected.
$this->assertTrue(!isset($new_settings[$expected['setting_name']]), format_string('Page still lacks the %setting, as expected.', ['%setting' => $expected['setting_name']]));
$this->assertTrue(!in_array($expected['library_1'], $new_libraries), format_string('Page still lacks the %library library, as expected.', ['%library' => $expected['library_1']]));
$this->assertTrue(!in_array($expected['library_2'], $new_libraries), format_string('Page still lacks the %library library, as expected.', ['%library' => $expected['library_2']]));
// Verify a settings command does not add CSS or scripts to drupalSettings
// and no command inserts the corresponding tags on the page.
$found_settings_command = FALSE;
$found_markup_command = FALSE;
foreach ($commands as $command) {
if ($command['command'] == 'settings' && (array_key_exists('css', $command['settings']['ajaxPageState']) || array_key_exists('js', $command['settings']['ajaxPageState']))) {
$found_settings_command = TRUE;
}
if (isset($command['data']) && ($command['data'] == $expected_js_html || $command['data'] == $expected_css_html)) {
$found_markup_command = TRUE;
}
}
$this->assertFalse($found_settings_command, format_string('Page state still lacks the %library_1 and %library_2 libraries, as expected.', ['%library_1' => $expected['library_1'], '%library_2' => $expected['library_2']]));
$this->assertFalse($found_markup_command, format_string('Page still lacks the %library_1 and %library_2 libraries, as expected.', ['%library_1' => $expected['library_1'], '%library_2' => $expected['library_2']]));
// Submit the AJAX request and trigger adding files.
$commands = $this->drupalPostAjaxForm(NULL, ['add_files' => TRUE], ['op' => t('Submit')]);
$new_settings = $this->getDrupalSettings();
$new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']);
// Verify the expected setting was added, both to drupalSettings, and as
// the first AJAX command.
$this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], format_string('Page now has the %setting.', ['%setting' => $expected['setting_name']]));
$expected_command = new SettingsCommand([$expected['setting_name'] => $expected['setting_value']], TRUE);
$this->assertCommand(array_slice($commands, 0, 1), $expected_command->render(), 'The settings command was first.');
// Verify the expected CSS file was added, both to drupalSettings, and as
// the second AJAX command for inclusion into the HTML.
$this->assertTrue(in_array($expected['library_1'], $new_libraries), format_string('Page state now has the %library library.', ['%library' => $expected['library_1']]));
$this->assertCommand(array_slice($commands, 1, 1), ['data' => $expected_css_html], format_string('Page now has the %library library.', ['%library' => $expected['library_1']]));
// Verify the expected JS file was added, both to drupalSettings, and as
// the third AJAX command for inclusion into the HTML. By testing for an
// exact HTML string containing the SCRIPT tag, we also ensure that
// unexpected JavaScript code, such as a jQuery.extend() that would
// potentially clobber rather than properly merge settings, didn't
// accidentally get added.
$this->assertTrue(in_array($expected['library_2'], $new_libraries), format_string('Page state now has the %library library.', ['%library' => $expected['library_2']]));
$this->assertCommand(array_slice($commands, 2, 1), ['data' => $expected_js_html], format_string('Page now has the %library library.', ['%library' => $expected['library_2']]));
}
/**
* Tests that drupalSettings.currentPath is not updated on AJAX requests.
*/
public function testCurrentPathChange() {
$commands = $this->drupalPostAjaxForm('ajax_forms_test_lazy_load_form', ['add_files' => FALSE], ['op' => t('Submit')]);
foreach ($commands as $command) {
if ($command['command'] == 'settings') {
$this->assertFalse(isset($command['settings']['currentPath']), 'Value of drupalSettings.currentPath is not updated after an AJAX request.');
}
}
}
/**
* Tests that overridden CSS files are not added during lazy load.
*/
public function testLazyLoadOverriddenCSS() {
// The test theme overrides js.module.css without an implementation,
// thereby removing it.
\Drupal::service('theme_handler')->install(['test_theme']);
$this->config('system.theme')
->set('default', 'test_theme')
->save();
// This gets the form, and emulates an Ajax submission on it, including
// adding markup to the HEAD and BODY for any lazy loaded JS/CSS files.
$this->drupalPostAjaxForm('ajax_forms_test_lazy_load_form', ['add_files' => TRUE], ['op' => t('Submit')]);
// Verify that the resulting HTML does not load the overridden CSS file.
// We add a "?" to the assertion, because drupalSettings may include
// information about the file; we only really care about whether it appears
// in a LINK or STYLE tag, for which Drupal always adds a query string for
// cache control.
$this->assertNoText('js.module.css?', 'Ajax lazy loading does not add overridden CSS files.');
}
}

View file

@ -1,97 +0,0 @@
<?php
namespace Drupal\system\Tests\Form;
use Drupal\simpletest\WebTestBase;
/**
* Tests that FAPI correctly determines the triggering element.
*
* @group Form
*/
class TriggeringElementTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['form_test'];
/**
* Test the determination of the triggering element when no button
* information is included in the POST data, as is sometimes the case when
* the ENTER key is pressed in a textfield in Internet Explorer.
*/
public function testNoButtonInfoInPost() {
$path = 'form-test/clicked-button';
$edit = [];
$form_html_id = 'form-test-clicked-button';
// Ensure submitting a form with no buttons results in no triggering element
// and the form submit handler not running.
$this->drupalPostForm($path, $edit, NULL, [], [], $form_html_id);
$this->assertText('There is no clicked button.', '$form_state->getTriggeringElement() set to NULL.');
$this->assertNoText('Submit handler for form_test_clicked_button executed.', 'Form submit handler did not execute.');
// Ensure submitting a form with one or more submit buttons results in the
// triggering element being set to the first one the user has access to. An
// argument with 'r' in it indicates a restricted (#access=FALSE) button.
$this->drupalPostForm($path . '/s', $edit, NULL, [], [], $form_html_id);
$this->assertText('The clicked button is button1.', '$form_state->getTriggeringElement() set to only button.');
$this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
$this->drupalPostForm($path . '/s/s', $edit, NULL, [], [], $form_html_id);
$this->assertText('The clicked button is button1.', '$form_state->getTriggeringElement() set to first button.');
$this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
$this->drupalPostForm($path . '/rs/s', $edit, NULL, [], [], $form_html_id);
$this->assertText('The clicked button is button2.', '$form_state->getTriggeringElement() set to first available button.');
$this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
// Ensure submitting a form with buttons of different types results in the
// triggering element being set to the first button, regardless of type. For
// the FAPI 'button' type, this should result in the submit handler not
// executing. The types are 's'(ubmit), 'b'(utton), and 'i'(mage_button).
$this->drupalPostForm($path . '/s/b/i', $edit, NULL, [], [], $form_html_id);
$this->assertText('The clicked button is button1.', '$form_state->getTriggeringElement() set to first button.');
$this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
$this->drupalPostForm($path . '/b/s/i', $edit, NULL, [], [], $form_html_id);
$this->assertText('The clicked button is button1.', '$form_state->getTriggeringElement() set to first button.');
$this->assertNoText('Submit handler for form_test_clicked_button executed.', 'Form submit handler did not execute.');
$this->drupalPostForm($path . '/i/s/b', $edit, NULL, [], [], $form_html_id);
$this->assertText('The clicked button is button1.', '$form_state->getTriggeringElement() set to first button.');
$this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
}
/**
* Test that the triggering element does not get set to a button with
* #access=FALSE.
*/
public function testAttemptAccessControlBypass() {
$path = 'form-test/clicked-button';
$form_html_id = 'form-test-clicked-button';
// Retrieve a form where 'button1' has #access=FALSE and 'button2' doesn't.
$this->drupalGet($path . '/rs/s');
// Submit the form with 'button1=button1' in the POST data, which someone
// trying to get around security safeguards could easily do. We have to do
// a little trickery here, to work around the safeguards in drupalPostForm(): by
// renaming the text field that is in the form to 'button1', we can get the
// data we want into \Drupal::request()->request.
$elements = $this->xpath('//form[@id="' . $form_html_id . '"]//input[@name="text"]');
$elements[0]['name'] = 'button1';
$this->drupalPostForm(NULL, ['button1' => 'button1'], NULL, [], [], $form_html_id);
// Ensure that the triggering element was not set to the restricted button.
// Do this with both a negative and positive assertion, because negative
// assertions alone can be brittle. See testNoButtonInfoInPost() for why the
// triggering element gets set to 'button2'.
$this->assertNoText('The clicked button is button1.', '$form_state->getTriggeringElement() not set to a restricted button.');
$this->assertText('The clicked button is button2.', '$form_state->getTriggeringElement() not set to a restricted button.');
}
}

View file

@ -1,47 +0,0 @@
<?php
namespace Drupal\system\Tests\Session;
use Drupal\simpletest\WebTestBase;
/**
* Tests the stacked session handler functionality.
*
* @group Session
*/
class StackSessionHandlerIntegrationTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['session_test'];
/**
* Tests a request.
*/
public function testRequest() {
$actual_trace = $this->drupalGetAjax('session-test/trace-handler');
$expect_trace = [
['BEGIN', 'test_argument', 'open'],
['BEGIN', NULL, 'open'],
['END', NULL, 'open'],
['END', 'test_argument', 'open'],
['BEGIN', 'test_argument', 'read', $this->sessionId],
['BEGIN', NULL, 'read', $this->sessionId],
['END', NULL, 'read', $this->sessionId],
['END', 'test_argument', 'read', $this->sessionId],
['BEGIN', 'test_argument', 'write', $this->sessionId],
['BEGIN', NULL, 'write', $this->sessionId],
['END', NULL, 'write', $this->sessionId],
['END', 'test_argument', 'write', $this->sessionId],
['BEGIN', 'test_argument', 'close'],
['BEGIN', NULL, 'close'],
['END', NULL, 'close'],
['END', 'test_argument', 'close'],
];
$this->assertEqual($expect_trace, $actual_trace);
}
}

View file

@ -50,7 +50,7 @@ class Callbacks {
*/
public function checkboxCallback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new HtmlCommand('#ajax_checkbox_value', (int) $form_state->getValue('checkbox')));
$response->addCommand(new HtmlCommand('#ajax_checkbox_value', $form_state->getValue('checkbox') ? 'checked' : 'unchecked'));
$response->addCommand(new DataCommand('#ajax_checkbox_value', 'form_state_value_select', (int) $form_state->getValue('checkbox')));
return $response;
}

View file

@ -65,7 +65,7 @@ class AjaxFormsTestSimpleForm extends FormBase {
$form['select_' . $key . '_callback'] = [
'#type' => 'select',
'#title' => $this->t('Test %key callbacks', ['%key' => $key]),
'#options' => ['red' => 'red'],
'#options' => ['red' => 'red', 'green' => 'green'],
'#ajax' => ['callback' => $value],
];
}

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\form_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Form constructor for testing #type 'file' elements.
*
* @internal
*/
class FormTestFileForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'form_test_file';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['file'] = [
'#type' => 'file',
'#multiple' => TRUE,
'#attributes' => [
'class' => ['cagatio'],
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
}

View file

@ -0,0 +1,158 @@
<?php
namespace Drupal\Tests\system\Functional\Ajax;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Ajax\AddCssCommand;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\PrependCommand;
use Drupal\Core\Ajax\SettingsCommand;
use Drupal\Core\Asset\AttachedAssets;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Tests\BrowserTestBase;
/**
* Performs tests on AJAX framework functions.
*
* @group Ajax
*/
class FrameworkTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'ajax_test', 'ajax_forms_test'];
/**
* Verifies the Ajax rendering of a command in the settings.
*/
public function testAJAXRender() {
// Verify that settings command is generated if JavaScript settings exist.
$commands = $this->drupalGetAjax('ajax-test/render');
$expected = new SettingsCommand(['ajax' => 'test'], TRUE);
$this->assertCommand($commands, $expected->render(), 'JavaScript settings command is present.');
}
/**
* Tests AjaxResponse::prepare() AJAX commands ordering.
*/
public function testOrder() {
$expected_commands = [];
// Expected commands, in a very specific order.
$asset_resolver = \Drupal::service('asset.resolver');
$css_collection_renderer = \Drupal::service('asset.css.collection_renderer');
$js_collection_renderer = \Drupal::service('asset.js.collection_renderer');
$renderer = \Drupal::service('renderer');
$build['#attached']['library'][] = 'ajax_test/order-css-command';
$assets = AttachedAssets::createFromRenderArray($build);
$css_render_array = $css_collection_renderer->render($asset_resolver->getCssAssets($assets, FALSE));
$expected_commands[1] = new AddCssCommand($renderer->renderRoot($css_render_array));
$build['#attached']['library'][] = 'ajax_test/order-header-js-command';
$build['#attached']['library'][] = 'ajax_test/order-footer-js-command';
$assets = AttachedAssets::createFromRenderArray($build);
list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, FALSE);
$js_header_render_array = $js_collection_renderer->render($js_assets_header);
$js_footer_render_array = $js_collection_renderer->render($js_assets_footer);
$expected_commands[2] = new PrependCommand('head', $js_header_render_array);
$expected_commands[3] = new AppendCommand('body', $js_footer_render_array);
$expected_commands[4] = new HtmlCommand('body', 'Hello, world!');
// Load any page with at least one CSS file, at least one JavaScript file
// and at least one #ajax-powered element. The latter is an assumption of
// drupalPostAjaxForm(), the two former are assumptions of the Ajax
// renderer.
// @todo refactor AJAX Framework + tests to make less assumptions.
$this->drupalGet('ajax_forms_test_lazy_load_form');
// Verify AJAX command order — this should always be the order:
// 1. CSS files
// 2. JavaScript files in the header
// 3. JavaScript files in the footer
// 4. Any other AJAX commands, in whatever order they were added.
$commands = $this->drupalGetAjax('ajax-test/order');
$this->assertCommand(array_slice($commands, 0, 1), $expected_commands[1]->render());
$this->assertCommand(array_slice($commands, 1, 1), $expected_commands[2]->render());
$this->assertCommand(array_slice($commands, 2, 1), $expected_commands[3]->render());
$this->assertCommand(array_slice($commands, 3, 1), $expected_commands[4]->render());
}
/**
* Tests the behavior of an error alert command.
*/
public function testAJAXRenderError() {
// Verify custom error message.
$edit = [
'message' => 'Custom error message.',
];
$commands = $this->drupalGetAjax('ajax-test/render-error', ['query' => $edit]);
$expected = new AlertCommand($edit['message']);
$this->assertCommand($commands, $expected->render(), 'Custom error message is output.');
}
/**
* Asserts the array of Ajax commands contains the searched command.
*
* An AjaxResponse object stores an array of Ajax commands. This array
* sometimes includes commands automatically provided by the framework in
* addition to commands returned by a particular controller. During testing,
* we're usually interested that a particular command is present, and don't
* care whether other commands precede or follow the one we're interested in.
* Additionally, the command we're interested in may include additional data
* that we're not interested in. Therefore, this function simply asserts that
* one of the commands in $haystack contains all of the keys and values in
* $needle. Furthermore, if $needle contains a 'settings' key with an array
* value, we simply assert that all keys and values within that array are
* present in the command we're checking, and do not consider it a failure if
* the actual command contains additional settings that aren't part of
* $needle.
*
* @param $haystack
* An array of rendered Ajax commands returned by the server.
* @param $needle
* Array of info we're expecting in one of those commands.
*/
protected function assertCommand($haystack, $needle) {
$found = FALSE;
foreach ($haystack as $command) {
// If the command has additional settings that we're not testing for, do
// not consider that a failure.
if (isset($command['settings']) && is_array($command['settings']) && isset($needle['settings']) && is_array($needle['settings'])) {
$command['settings'] = array_intersect_key($command['settings'], $needle['settings']);
}
// If the command has additional data that we're not testing for, do not
// consider that a failure. Also, == instead of ===, because we don't
// require the key/value pairs to be in any particular order
// (http://php.net/manual/language.operators.array.php).
if (array_intersect_key($command, $needle) == $needle) {
$found = TRUE;
break;
}
}
$this->assertTrue($found);
}
/**
* Requests a path or URL in drupal_ajax format and JSON-decodes the response.
*
* @param \Drupal\Core\Url|string $path
* Drupal path or URL to request from.
* @param array $options
* Array of URL options.
* @param array $headers
* Array of headers.
*
* @return array
* Decoded JSON.
*/
protected function drupalGetAjax($path, array $options = [], array $headers = []) {
$headers[] = 'X-Requested-With: XMLHttpRequest';
if (!isset($options['query'][MainContentViewSubscriber::WRAPPER_FORMAT])) {
$options['query'][MainContentViewSubscriber::WRAPPER_FORMAT] = 'drupal_ajax';
}
return Json::decode($this->drupalGet($path, $options, $headers));
}
}

View file

@ -1,17 +1,16 @@
<?php
namespace Drupal\system\Tests\Form;
namespace Drupal\Tests\system\Functional\Form;
use Drupal\Core\Form\FormState;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\system\Functional\Form\StubForm;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the tableselect form element for expected behavior.
*
* @group Form
*/
class ElementsTableSelectTest extends WebTestBase {
class ElementsTableSelectTest extends BrowserTestBase {
/**
* Modules to enable.
@ -27,35 +26,14 @@ class ElementsTableSelectTest extends WebTestBase {
$this->drupalGet('form_test/tableselect/multiple-true');
$this->assertNoText(t('Empty text.'), 'Empty text should not be displayed.');
$this->assertSession()->responseNotContains('Empty text.', 'Empty text should not be displayed.');
// Test for the presence of the Select all rows tableheader.
$this->assertFieldByXPath('//th[@class="select-all"]', NULL, 'Presence of the "Select all" checkbox.');
$this->assertNotEmpty($this->xpath('//th[@class="select-all"]'), 'Presence of the "Select all" checkbox.');
$rows = ['row1', 'row2', 'row3'];
foreach ($rows as $row) {
$this->assertFieldByXPath('//input[@type="checkbox"]', $row, format_string('Checkbox for value @row.', ['@row' => $row]));
}
}
/**
* Test the presence of ajax functionality for all options.
*/
public function testAjax() {
$rows = ['row1', 'row2', 'row3'];
// Test checkboxes (#multiple == TRUE).
foreach ($rows as $row) {
$element = 'tableselect[' . $row . ']';
$edit = [$element => TRUE];
$result = $this->drupalPostAjaxForm('form_test/tableselect/multiple-true', $edit, $element);
$this->assertFalse(empty($result), t('Ajax triggers on checkbox for @row.', ['@row' => $row]));
}
// Test radios (#multiple == FALSE).
$element = 'tableselect';
foreach ($rows as $row) {
$edit = [$element => $row];
$result = $this->drupalPostAjaxForm('form_test/tableselect/multiple-false', $edit, $element);
$this->assertFalse(empty($result), t('Ajax triggers on radio for @row.', ['@row' => $row]));
$this->assertNotEmpty($this->xpath('//input[@type="checkbox"]', [$row]), "Checkbox for the value $row.");
}
}
@ -65,40 +43,39 @@ class ElementsTableSelectTest extends WebTestBase {
public function testMultipleFalse() {
$this->drupalGet('form_test/tableselect/multiple-false');
$this->assertNoText(t('Empty text.'), 'Empty text should not be displayed.');
$this->assertSession()->pageTextNotContains('Empty text.');
// Test for the absence of the Select all rows tableheader.
$this->assertNoFieldByXPath('//th[@class="select-all"]', '', 'Absence of the "Select all" checkbox.');
$this->assertFalse($this->xpath('//th[@class="select-all"]'));
$rows = ['row1', 'row2', 'row3'];
foreach ($rows as $row) {
$this->assertFieldByXPath('//input[@type="radio"]', $row, format_string('Radio button for value @row.', ['@row' => $row]));
$this->assertNotEmpty($this->xpath('//input[@type="radio"]', [$row], "Radio button value: $row"));
}
}
/**
* Tests the display when #colspan is set.
*/
public function testTableselectColSpan() {
public function testTableSelectColSpan() {
$this->drupalGet('form_test/tableselect/colspan');
$this->assertText(t('Three'), 'Presence of the third column');
$this->assertNoText(t('Four'), 'Absence of a fourth column');
$this->assertSession()->pageTextContains('Three', 'Presence of the third column');
$this->assertSession()->pageTextNotContains('Four', 'Absence of a fourth column');
// There should be three labeled column headers and 1 for the input.
$table_head = $this->xpath('//thead');
$this->assertEqual(count($table_head[0]->tr->th), 4, 'There are four column headers');
$table_head = $this->xpath('//thead/tr/th');
$this->assertEquals(count($table_head), 4, 'There are four column headers');
$table_body = $this->xpath('//tbody');
// The first two body rows should each have 5 table cells: One for the
// radio, one cell in the first column, one cell in the second column,
// and two cells in the third column which has colspan 2.
for ($i = 0; $i <= 1; $i++) {
$this->assertEqual(count($table_body[0]->tr[$i]->td), 5, format_string('There are five cells in row @row.', ['@row' => $i]));
$this->assertEquals(count($this->xpath('//tbody/tr[' . ($i + 1) . ']/td')), 5, 'There are five cells in row ' . $i);
}
// The third row should have 3 cells, one for the radio, one spanning the
// first and second column, and a third in column 3 (which has colspan 3).
$this->assertEqual(count($table_body[0]->tr[2]->td), 3, 'There are three cells in row 3.');
$this->assertEquals(count($this->xpath('//tbody/tr[3]/td')), 3, 'There are three cells in row 3.');
}
/**
@ -106,7 +83,7 @@ class ElementsTableSelectTest extends WebTestBase {
*/
public function testEmptyText() {
$this->drupalGet('form_test/tableselect/empty-text');
$this->assertText(t('Empty text.'), 'Empty text should be displayed.');
$this->assertSession()->pageTextContains('Empty text.', 'Empty text should be displayed.');
}
/**
@ -119,18 +96,19 @@ class ElementsTableSelectTest extends WebTestBase {
$edit['tableselect[row1]'] = TRUE;
$this->drupalPostForm('form_test/tableselect/multiple-true', $edit, 'Submit');
$this->assertText(t('Submitted: row1 = row1'), 'Checked checkbox row1');
$this->assertText(t('Submitted: row2 = 0'), 'Unchecked checkbox row2.');
$this->assertText(t('Submitted: row3 = 0'), 'Unchecked checkbox row3.');
$assert_session = $this->assertSession();
$assert_session->pageTextContains('Submitted: row1 = row1', 'Checked checkbox row1');
$assert_session->pageTextContains('Submitted: row2 = 0', 'Unchecked checkbox row2.');
$assert_session->pageTextContains('Submitted: row3 = 0', 'Unchecked checkbox row3.');
// Test a submission with multiple checkboxes checked.
$edit['tableselect[row1]'] = TRUE;
$edit['tableselect[row3]'] = TRUE;
$this->drupalPostForm('form_test/tableselect/multiple-true', $edit, 'Submit');
$this->assertText(t('Submitted: row1 = row1'), 'Checked checkbox row1.');
$this->assertText(t('Submitted: row2 = 0'), 'Unchecked checkbox row2.');
$this->assertText(t('Submitted: row3 = row3'), 'Checked checkbox row3.');
$assert_session->pageTextContains('Submitted: row1 = row1', 'Checked checkbox row1.');
$assert_session->pageTextContains('Submitted: row2 = 0', 'Unchecked checkbox row2.');
$assert_session->pageTextContains('Submitted: row3 = row3', 'Checked checkbox row3.');
}
@ -140,7 +118,7 @@ class ElementsTableSelectTest extends WebTestBase {
public function testMultipleFalseSubmit() {
$edit['tableselect'] = 'row1';
$this->drupalPostForm('form_test/tableselect/multiple-false', $edit, 'Submit');
$this->assertText(t('Submitted: row1'), 'Selected radio button');
$this->assertSession()->pageTextContains('Submitted: row1', 'Selected radio button');
}
/**
@ -149,18 +127,18 @@ class ElementsTableSelectTest extends WebTestBase {
public function testAdvancedSelect() {
// When #multiple = TRUE a Select all checkbox should be displayed by default.
$this->drupalGet('form_test/tableselect/advanced-select/multiple-true-default');
$this->assertFieldByXPath('//th[@class="select-all"]', NULL, 'Display a "Select all" checkbox by default when #multiple is TRUE.');
$this->xpath('//th[@class="select-all"]');
// When #js_select is set to FALSE, a "Select all" checkbox should not be displayed.
$this->drupalGet('form_test/tableselect/advanced-select/multiple-true-no-advanced-select');
$this->assertNoFieldByXPath('//th[@class="select-all"]', NULL, 'Do not display a "Select all" checkbox when #js_select is FALSE.');
$this->assertFalse($this->xpath('//th[@class="select-all"]'));
// A "Select all" checkbox never makes sense when #multiple = FALSE, regardless of the value of #js_select.
$this->drupalGet('form_test/tableselect/advanced-select/multiple-false-default');
$this->assertNoFieldByXPath('//th[@class="select-all"]', NULL, 'Do not display a "Select all" checkbox when #multiple is FALSE.');
$this->assertFalse($this->xpath('//th[@class="select-all"]'));
$this->drupalGet('form_test/tableselect/advanced-select/multiple-false-advanced-select');
$this->assertNoFieldByXPath('//th[@class="select-all"]', NULL, 'Do not display a "Select all" checkbox when #multiple is FALSE, even when #js_select is TRUE.');
$this->assertFalse($this->xpath('//th[@class="select-all"]'));
}
/**

View file

@ -0,0 +1,61 @@
<?php
namespace Drupal\Tests\system\Functional\Form;
use Drupal\Tests\BrowserTestBase;
/**
* Tests functionality of \Drupal\Core\Form\FormBuilderInterface::rebuildForm().
*
* @group Form
*/
class RebuildTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'form_test'];
/**
* A user for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
$this->webUser = $this->drupalCreateUser(['access content']);
$this->drupalLogin($this->webUser);
}
/**
* Tests preservation of values.
*/
public function testRebuildPreservesValues() {
$edit = [
'checkbox_1_default_off' => TRUE,
'checkbox_1_default_on' => FALSE,
'text_1' => 'foo',
];
$this->drupalPostForm('form-test/form-rebuild-preserve-values', $edit, 'Add more');
$assert_session = $this->assertSession();
// Verify that initial elements retained their submitted values.
$assert_session->checkboxChecked('edit-checkbox-1-default-off');
$assert_session->checkboxNotChecked('edit-checkbox-1-default-on');
$assert_session->fieldValueEquals('edit-text-1', 'foo');
// Verify that newly added elements were initialized with their default values.
$assert_session->checkboxChecked('edit-checkbox-2-default-on');
$assert_session->checkboxNotChecked('edit-checkbox-2-default-off');
$assert_session->fieldValueEquals('edit-text-2', 'DEFAULT 2');
}
}

View file

@ -1,8 +1,10 @@
<?php
namespace Drupal\system\Tests\Form;
namespace Drupal\Tests\system\Functional\Form;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Database\Database;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Tests\BrowserTestBase;
/**
* Tests a multistep form using form storage and makes sure validation and
@ -16,7 +18,7 @@ use Drupal\simpletest\WebTestBase;
*
* @group Form
*/
class StorageTest extends WebTestBase {
class StorageTest extends BrowserTestBase {
/**
* Modules to enable.
@ -25,6 +27,9 @@ class StorageTest extends WebTestBase {
*/
public static $modules = ['form_test', 'dblog'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
@ -36,25 +41,27 @@ class StorageTest extends WebTestBase {
*/
public function testForm() {
$this->drupalGet('form_test/form-storage');
$this->assertText('Form constructions: 1');
$assert_session = $this->assertSession();
$assert_session->pageTextContains('Form constructions: 1');
$edit = ['title' => 'new', 'value' => 'value_is_set'];
// Use form rebuilding triggered by a submit button.
$this->drupalPostForm(NULL, $edit, 'Continue submit');
$this->assertText('Form constructions: 2');
$this->assertText('Form constructions: 3');
$assert_session->pageTextContains('Form constructions: 2');
$assert_session->pageTextContains('Form constructions: 3');
// Reset the form to the values of the storage, using a form rebuild
// triggered by button of type button.
$this->drupalPostForm(NULL, ['title' => 'changed'], 'Reset');
$this->assertFieldByName('title', 'new', 'Values have been reset.');
$assert_session->fieldValueEquals('title', 'new');
// After rebuilding, the form has been cached.
$this->assertText('Form constructions: 4');
$assert_session->pageTextContains('Form constructions: 4');
$this->drupalPostForm(NULL, $edit, 'Save');
$this->assertText('Form constructions: 4');
$this->assertText('Title: new', 'The form storage has stored the values.');
$assert_session->pageTextContains('Form constructions: 4');
$assert_session->pageTextContains('Title: new', 'The form storage has stored the values.');
}
/**
@ -62,26 +69,26 @@ class StorageTest extends WebTestBase {
*/
public function testFormCached() {
$this->drupalGet('form_test/form-storage', ['query' => ['cache' => 1]]);
$this->assertText('Form constructions: 1');
$this->assertSession()->pageTextContains('Form constructions: 1');
$edit = ['title' => 'new', 'value' => 'value_is_set'];
// Use form rebuilding triggered by a submit button.
$this->drupalPostForm(NULL, $edit, 'Continue submit');
// The first one is for the building of the form.
$this->assertText('Form constructions: 2');
$this->assertSession()->pageTextContains('Form constructions: 2');
// The second one is for the rebuilding of the form.
$this->assertText('Form constructions: 3');
$this->assertSession()->pageTextContains('Form constructions: 3');
// Reset the form to the values of the storage, using a form rebuild
// triggered by button of type button.
$this->drupalPostForm(NULL, ['title' => 'changed'], 'Reset');
$this->assertFieldByName('title', 'new', 'Values have been reset.');
$this->assertText('Form constructions: 4');
$this->assertSession()->fieldValueEquals('title', 'new');
$this->assertSession()->pageTextContains('Form constructions: 4');
$this->drupalPostForm(NULL, $edit, 'Save');
$this->assertText('Form constructions: 4');
$this->assertText('Title: new', 'The form storage has stored the values.');
$this->assertSession()->pageTextContains('Form constructions: 4');
$this->assertSession()->pageTextContains('Title: new', 'The form storage has stored the values.');
}
/**
@ -124,7 +131,7 @@ class StorageTest extends WebTestBase {
// validation error. Post again and verify that the rebuilt form contains
// the values of the updated form storage.
$this->drupalPostForm(NULL, ['title' => 'foo', 'value' => 'bar'], 'Save');
$this->assertText("The thing has been changed.", 'The altered form storage value was updated in cache and taken over.');
$this->assertSession()->pageTextContains("The thing has been changed.", 'The altered form storage value was updated in cache and taken over.');
}
/**
@ -135,27 +142,27 @@ class StorageTest extends WebTestBase {
// Request the form with 'cache' query parameter to enable form caching.
$this->drupalGet('form_test/form-storage', ['query' => ['cache' => 1, 'immutable' => 1]]);
$buildIdFields = $this->xpath('//input[@name="form_build_id"]');
$this->assertEqual(count($buildIdFields), 1, 'One form build id field on the page');
$buildId = (string) $buildIdFields[0]['value'];
$this->assertEquals(count($buildIdFields), 1, 'One form build id field on the page');
$buildId = $buildIdFields[0]->getValue();
// Trigger validation error by submitting an empty title.
$edit = ['title' => ''];
$this->drupalPostForm(NULL, $edit, 'Continue submit');
// Verify that the build-id did change.
$this->assertNoFieldByName('form_build_id', $buildId, 'Build id changes when form validation fails');
$this->assertSession()->hiddenFieldValueNotEquals('form_build_id', $buildId);
// Retrieve the new build-id.
$buildIdFields = $this->xpath('//input[@name="form_build_id"]');
$this->assertEqual(count($buildIdFields), 1, 'One form build id field on the page');
$buildId = (string) $buildIdFields[0]['value'];
$this->assertEquals(count($buildIdFields), 1, 'One form build id field on the page');
$buildId = (string) $buildIdFields[0]->getValue();
// Trigger validation error by again submitting an empty title.
$edit = ['title' => ''];
$this->drupalPostForm(NULL, $edit, 'Continue submit');
// Verify that the build-id does not change the second time.
$this->assertFieldByName('form_build_id', $buildId, 'Build id remains the same when form validation fails subsequently');
$this->assertSession()->hiddenFieldValueEquals('form_build_id', $buildId);
}
/**
@ -164,25 +171,28 @@ class StorageTest extends WebTestBase {
public function testImmutableFormLegacyProtection() {
$this->drupalGet('form_test/form-storage', ['query' => ['cache' => 1, 'immutable' => 1]]);
$build_id_fields = $this->xpath('//input[@name="form_build_id"]');
$this->assertEqual(count($build_id_fields), 1, 'One form build id field on the page');
$build_id = (string) $build_id_fields[0]['value'];
$this->assertEquals(count($build_id_fields), 1, 'One form build id field on the page');
$build_id = $build_id_fields[0]->getValue();
// Try to poison the form cache.
$original = $this->drupalGetAjax('form-test/form-storage-legacy/' . $build_id);
$this->assertEqual($original['form']['#build_id_old'], $build_id, 'Original build_id was recorded');
$this->assertNotEqual($original['form']['#build_id'], $build_id, 'New build_id was generated');
$response = $this->drupalGet('form-test/form-storage-legacy/' . $build_id, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']], ['X-Requested-With: XMLHttpRequest']);
$original = json_decode($response, TRUE);
$this->assertEquals($original['form']['#build_id_old'], $build_id, 'Original build_id was recorded');
$this->assertNotEquals($original['form']['#build_id'], $build_id, 'New build_id was generated');
// Assert that a watchdog message was logged by
// \Drupal::formBuilder()->setCache().
$status = (bool) db_query_range('SELECT 1 FROM {watchdog} WHERE message = :message', 0, 1, [':message' => 'Form build-id mismatch detected while attempting to store a form in the cache.']);
$this->assert($status, 'A watchdog message was logged by \Drupal::formBuilder()->setCache');
$status = (bool) Database::getConnection()->queryRange('SELECT 1 FROM {watchdog} WHERE message = :message', 0, 1, [':message' => 'Form build-id mismatch detected while attempting to store a form in the cache.']);
$this->assertTrue($status, 'A watchdog message was logged by \Drupal::formBuilder()->setCache');
// Ensure that the form state was not poisoned by the preceding call.
$original = $this->drupalGetAjax('form-test/form-storage-legacy/' . $build_id);
$this->assertEqual($original['form']['#build_id_old'], $build_id, 'Original build_id was recorded');
$this->assertNotEqual($original['form']['#build_id'], $build_id, 'New build_id was generated');
$this->assert(empty($original['form']['#poisoned']), 'Original form structure was preserved');
$this->assert(empty($original['form_state']['poisoned']), 'Original form state was preserved');
$response = $this->drupalGet('form-test/form-storage-legacy/' . $build_id, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']], ['X-Requested-With: XMLHttpRequest']);
$original = json_decode($response, TRUE);
$this->assertEquals($original['form']['#build_id_old'], $build_id, 'Original build_id was recorded');
$this->assertNotEquals($original['form']['#build_id'], $build_id, 'New build_id was generated');
$this->assertTrue(empty($original['form']['#poisoned']), 'Original form structure was preserved');
$this->assertTrue(empty($original['form_state']['poisoned']), 'Original form state was preserved');
}
}

View file

@ -107,6 +107,32 @@ class MailTest extends BrowserTestBase {
$this->assertEquals('Drépal this is a very long test sentence to te <simpletest@example.com>', Unicode::mimeHeaderDecode($sent_message['headers']['From']), 'From header is correctly encoded.');
$this->assertFalse(isset($sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.');
$this->assertFalse(isset($sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.');
// Test RFC-2822 rules are respected for 'display-name' component of
// 'From:' header. Specials characters are not allowed, so randomly add one
// of them to the site name and check the string is wrapped in quotes. Also
// hardcode some double-quotes and backslash to validate these are escaped
// properly too.
$specials = '()<>[]:;@\,."';
$site_name = 'Drupal' . $specials[rand(0, strlen($specials) - 1)] . ' "si\te"';
$this->config('system.site')->set('name', $site_name)->save();
// Send an email and check that the From-header contains the site name
// within double-quotes. Also make sure double-quotes and "\" are escaped.
\Drupal::service('plugin.manager.mail')->mail('simpletest', 'from_test', 'from_test@example.com', $language);
$captured_emails = \Drupal::state()->get('system.test_mail_collector');
$sent_message = end($captured_emails);
$escaped_site_name = str_replace(['\\', '"'], ['\\\\', '\\"'], $site_name);
$this->assertEquals('"' . $escaped_site_name . '" <simpletest@example.com>', $sent_message['headers']['From'], 'From header is correctly quoted.');
// Make sure display-name is not quoted nor escaped if part on an encoding.
$site_name = 'Drépal, "si\te"';
$this->config('system.site')->set('name', $site_name)->save();
// Send an email and check that the From-header contains the site name.
\Drupal::service('plugin.manager.mail')->mail('simpletest', 'from_test', 'from_test@example.com', $language);
$captured_emails = \Drupal::state()->get('system.test_mail_collector');
$sent_message = end($captured_emails);
$this->assertEquals('=?UTF-8?B?RHLDqXBhbCwgInNpXHRlIg==?= <simpletest@example.com>', $sent_message['headers']['From'], 'From header is correctly encoded.');
$this->assertEquals($site_name . ' <simpletest@example.com>', Unicode::mimeHeaderDecode($sent_message['headers']['From']), 'From header is correctly encoded.');
}
/**

View file

@ -1,11 +1,11 @@
<?php
namespace Drupal\system\Tests\Routing;
namespace Drupal\Tests\system\Functional\Routing;
use Drupal\Core\Cache\Cache;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Drupal\Core\Url;
@ -14,7 +14,7 @@ use Drupal\Core\Url;
*
* @group Routing
*/
class RouterTest extends WebTestBase {
class RouterTest extends BrowserTestBase {
/**
* Modules to enable.
@ -34,69 +34,72 @@ class RouterTest extends WebTestBase {
$this->drupalGet('router_test/test1');
$this->assertRaw('test1', 'The correct string was returned because the route was successful.');
// Check expected headers from FinishResponseSubscriber.
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-ua-compatible'], 'IE=edge');
$this->assertEqual($headers['content-language'], 'en');
$this->assertEqual($headers['x-content-type-options'], 'nosniff');
$this->assertEqual($headers['x-frame-options'], 'SAMEORIGIN');
$headers = $this->getSession()->getResponseHeaders();
$this->assertEquals($headers['X-UA-Compatible'], ['IE=edge']);
$this->assertEquals($headers['Content-language'], ['en']);
$this->assertEquals($headers['X-Content-Type-Options'], ['nosniff']);
$this->assertEquals($headers['X-Frame-Options'], ['SAMEORIGIN']);
$this->drupalGet('router_test/test2');
$this->assertRaw('test2', 'The correct string was returned because the route was successful.');
// Check expected headers from FinishResponseSubscriber.
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', $expected_cache_contexts));
$this->assertEqual($headers['x-drupal-cache-tags'], 'config:user.role.anonymous http_response rendered');
$this->assertEqual($headers['X-Drupal-Cache-Contexts'], [implode(' ', $expected_cache_contexts)]);
$this->assertEqual($headers['X-Drupal-Cache-Tags'], ['config:user.role.anonymous http_response rendered']);
// Confirm that the page wrapping is being added, so we're not getting a
// raw body returned.
$this->assertRaw('</html>', 'Page markup was found.');
// In some instances, the subrequest handling may get confused and render
// a page inception style. This test verifies that is not happening.
$this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
$this->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
// Confirm that route-level access check's cacheability is applied to the
// X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags headers.
// 1. controller result: render array, globally cacheable route access.
$this->drupalGet('router_test/test18');
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url'])));
$this->assertEqual($headers['x-drupal-cache-tags'], 'config:user.role.anonymous foo http_response rendered');
$this->assertEqual($headers['X-Drupal-Cache-Contexts'], [implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url']))]);
$this->assertEqual($headers['X-Drupal-Cache-Tags'], ['config:user.role.anonymous foo http_response rendered']);
// 2. controller result: render array, per-role cacheable route access.
$this->drupalGet('router_test/test19');
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url', 'user.roles'])));
$this->assertEqual($headers['x-drupal-cache-tags'], 'config:user.role.anonymous foo http_response rendered');
$this->assertEqual($headers['X-Drupal-Cache-Contexts'], [implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url', 'user.roles']))]);
$this->assertEqual($headers['X-Drupal-Cache-Tags'], ['config:user.role.anonymous foo http_response rendered']);
// 3. controller result: Response object, globally cacheable route access.
$this->drupalGet('router_test/test1');
$headers = $this->drupalGetHeaders();
$this->assertFalse(isset($headers['x-drupal-cache-contexts']));
$this->assertFalse(isset($headers['x-drupal-cache-tags']));
$this->assertFalse(isset($headers['X-Drupal-Cache-Contexts']));
$this->assertFalse(isset($headers['X-Drupal-Cache-Tags']));
// 4. controller result: Response object, per-role cacheable route access.
$this->drupalGet('router_test/test20');
$headers = $this->drupalGetHeaders();
$this->assertFalse(isset($headers['x-drupal-cache-contexts']));
$this->assertFalse(isset($headers['x-drupal-cache-tags']));
$this->assertFalse(isset($headers['X-Drupal-Cache-Contexts']));
$this->assertFalse(isset($headers['X-Drupal-Cache-Tags']));
// 5. controller result: CacheableResponse object, globally cacheable route access.
$this->drupalGet('router_test/test21');
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-drupal-cache-contexts'], '');
$this->assertEqual($headers['x-drupal-cache-tags'], 'http_response');
$this->assertEqual($headers['X-Drupal-Cache-Contexts'], ['']);
$this->assertEqual($headers['X-Drupal-Cache-Tags'], ['http_response']);
// 6. controller result: CacheableResponse object, per-role cacheable route access.
$this->drupalGet('router_test/test22');
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-drupal-cache-contexts'], 'user.roles');
$this->assertEqual($headers['x-drupal-cache-tags'], 'http_response');
$this->assertEqual($headers['X-Drupal-Cache-Contexts'], ['user.roles']);
$this->assertEqual($headers['X-Drupal-Cache-Tags'], ['http_response']);
// Finally, verify that the X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags
// headers are not sent when their container parameter is set to FALSE.
$this->drupalGet('router_test/test18');
$headers = $this->drupalGetHeaders();
$this->assertTrue(isset($headers['x-drupal-cache-contexts']));
$this->assertTrue(isset($headers['x-drupal-cache-tags']));
$this->setHttpResponseDebugCacheabilityHeaders(FALSE);
$this->assertTrue(isset($headers['X-Drupal-Cache-Contexts']));
$this->assertTrue(isset($headers['X-Drupal-Cache-Tags']));
$this->setContainerParameter('http.response.debug_cacheability_headers', FALSE);
$this->rebuildContainer();
$this->resetAll();
$this->drupalGet('router_test/test18');
$headers = $this->drupalGetHeaders();
$this->assertFalse(isset($headers['x-drupal-cache-contexts']));
$this->assertFalse(isset($headers['x-drupal-cache-tags']));
$this->assertFalse(isset($headers['X-Drupal-Cache-Contexts']));
$this->assertFalse(isset($headers['X-Drupal-Cache-Tags']));
}
/**
@ -145,7 +148,7 @@ class RouterTest extends WebTestBase {
// In some instances, the subrequest handling may get confused and render
// a page inception style. This test verifies that is not happening.
$this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
$this->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
}
/**
@ -162,7 +165,7 @@ class RouterTest extends WebTestBase {
// In some instances, the subrequest handling may get confused and render
// a page inception style. This test verifies that is not happening.
$this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
$this->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
}
/**
@ -179,7 +182,7 @@ class RouterTest extends WebTestBase {
// In some instances, the subrequest handling may get confused and render
// a page inception style. This test verifies that is not happening.
$this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
$this->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
}
/**
@ -208,7 +211,7 @@ class RouterTest extends WebTestBase {
// In some instances, the subrequest handling may get confused and render
// a page inception style. This test verifies that is not happening.
$this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
$this->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
}
/**
@ -277,7 +280,9 @@ class RouterTest extends WebTestBase {
public function testControllerResolutionAjax() {
// This will fail with a JSON parse error if the request is not routed to
// The correct controller.
$this->drupalGetAjax('/router_test/test10');
$options['query'][MainContentViewSubscriber::WRAPPER_FORMAT] = 'drupal_ajax';
$headers[] = 'X-Requested-With: XMLHttpRequest';
$this->drupalGet('/router_test/test10', $options, $headers);
$this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/json', 'Correct mime content type was returned');
@ -311,21 +316,18 @@ class RouterTest extends WebTestBase {
$request = $this->container->get('request_stack')->getCurrentRequest();
$url = $request->getUriForPath('//router_test/test1');
$this->drupalGet($url);
$this->assertEqual(1, $this->redirectCount, $url . " redirected to " . $this->url);
$this->assertUrl($request->getUriForPath('/router_test/test1'));
// It should not matter how many leading slashes are used and query strings
// should be preserved.
$url = $request->getUriForPath('/////////////////////////////////////////////////router_test/test1') . '?qs=test';
$this->drupalGet($url);
$this->assertEqual(1, $this->redirectCount, $url . " redirected to " . $this->url);
$this->assertUrl($request->getUriForPath('/router_test/test1') . '?qs=test');
// Ensure that external URLs in destination query params are not redirected
// to.
$url = $request->getUriForPath('/////////////////////////////////////////////////router_test/test1') . '?qs=test&destination=http://www.example.com%5c@drupal8alt.test';
$this->drupalGet($url);
$this->assertEqual(1, $this->redirectCount, $url . " redirected to " . $this->url);
$this->assertUrl($request->getUriForPath('/router_test/test1') . '?qs=test');
}

View file

@ -1,17 +1,17 @@
<?php
namespace Drupal\system\Tests\Session;
namespace Drupal\Tests\system\Functional\Session;
use Drupal\Core\Url;
use Drupal\basic_auth\Tests\BasicAuthTestTrait;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\basic_auth\Traits\BasicAuthTestTrait;
use Drupal\Tests\BrowserTestBase;
/**
* Tests if sessions are correctly handled when a user authenticates.
*
* @group Session
*/
class SessionAuthenticationTest extends WebTestBase {
class SessionAuthenticationTest extends BrowserTestBase {
use BasicAuthTestTrait;
@ -52,20 +52,22 @@ class SessionAuthenticationTest extends WebTestBase {
// Test that the route is not accessible as an anonymous user.
$this->drupalGet($protected_url);
$session = $this->getSession();
$this->assertResponse(401, 'An anonymous user cannot access a route protected with basic authentication.');
// We should be able to access the route with basic authentication.
$this->basicAuthGet($protected_url, $this->user->getUsername(), $this->user->pass_raw);
$this->basicAuthGet($protected_url, $this->user->getAccountName(), $this->user->passRaw);
$this->assertResponse(200, 'A route protected with basic authentication can be accessed by an authenticated user.');
// Check that the correct user is logged in.
$this->assertEqual($this->user->id(), json_decode($this->getRawContent())->user, 'The correct user is authenticated on a route with basic authentication.');
$this->assertEqual($this->user->id(), json_decode($session->getPage()->getContent())->user, 'The correct user is authenticated on a route with basic authentication.');
$session->restart();
// If we now try to access a page without basic authentication then we
// should no longer be logged in.
$this->drupalGet($unprotected_url);
$this->assertResponse(200, 'An unprotected route can be accessed without basic authentication.');
$this->assertFalse(json_decode($this->getRawContent())->user, 'The user is no longer authenticated after visiting a page without basic authentication.');
$this->assertFalse(json_decode($session->getPage()->getContent())->user, 'The user is no longer authenticated after visiting a page without basic authentication.');
// If we access the protected page again without basic authentication we
// should get 401 Unauthorized.
@ -113,20 +115,24 @@ class SessionAuthenticationTest extends WebTestBase {
$no_cookie_url = Url::fromRoute('session_test.get_session_basic_auth');
// A route that is authorized with standard cookie authentication.
$cookie_url = '<front>';
$cookie_url = 'user/login';
// If we authenticate with a third party authentication system then no
// session cookie should be set, the third party system is responsible for
// sustaining the session.
$this->basicAuthGet($no_cookie_url, $this->user->getUsername(), $this->user->pass_raw);
$this->basicAuthGet($no_cookie_url, $this->user->getAccountName(), $this->user->passRaw);
$this->assertResponse(200, 'The user is successfully authenticated using basic authentication.');
$this->assertFalse($this->drupalGetHeader('set-cookie', TRUE), 'No cookie is set on a route protected with basic authentication.');
$this->assertEmpty($this->getSessionCookies());
// Mink stores some information in the session that breaks the next check if
// not reset.
$this->getSession()->restart();
// On the other hand, authenticating using Cookie sets a cookie.
$edit = ['name' => $this->user->getUsername(), 'pass' => $this->user->pass_raw];
$this->drupalGet($cookie_url);
$this->assertEmpty($this->getSessionCookies());
$edit = ['name' => $this->user->getAccountName(), 'pass' => $this->user->passRaw];
$this->drupalPostForm($cookie_url, $edit, t('Log in'));
$this->assertResponse(200, 'The user is successfully authenticated using cookie authentication.');
$this->assertTrue($this->drupalGetHeader('set-cookie', TRUE), 'A cookie is set on a route protected with cookie authentication.');
$this->assertNotEmpty($this->getSessionCookies());
}
}

View file

@ -1,15 +1,15 @@
<?php
namespace Drupal\system\Tests\Session;
namespace Drupal\Tests\system\Functional\Session;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
/**
* Drupal session handling tests.
*
* @group Session
*/
class SessionTest extends WebTestBase {
class SessionTest extends BrowserTestBase {
/**
* Modules to enable.
@ -36,12 +36,15 @@ class SessionTest extends WebTestBase {
$user = $this->drupalCreateUser();
// Enable sessions.
$this->sessionReset($user->id());
$this->sessionReset();
// Make sure the session cookie is set as HttpOnly.
$this->drupalLogin($user);
// Make sure the session cookie is set as HttpOnly. We can only test this in
// the header, with the test setup
// \GuzzleHttp\Cookie\SetCookie::getHttpOnly() always returns FALSE.
// Start a new session by setting a message.
$this->drupalGet('session-test/set-message');
$this->assertSessionCookie(TRUE);
$this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as HttpOnly.');
$this->drupalLogout();
// Verify that the session is regenerated if a module calls exit
// in hook_user_login().
@ -49,15 +52,15 @@ class SessionTest extends WebTestBase {
$user->save();
$this->drupalGet('session-test/id');
$matches = [];
preg_match('/\s*session_id:(.*)\n/', $this->getRawContent(), $matches);
preg_match('/\s*session_id:(.*)\n/', $this->getSession()->getPage()->getContent(), $matches);
$this->assertTrue(!empty($matches[1]), 'Found session ID before logging in.');
$original_session = $matches[1];
// We cannot use $this->drupalLogin($user); because we exit in
// session_test_user_login() which breaks a normal assertion.
$edit = [
'name' => $user->getUsername(),
'pass' => $user->pass_raw,
'name' => $user->getAccountName(),
'pass' => $user->passRaw,
];
$this->drupalPostForm('user/login', $edit, t('Log in'));
$this->drupalGet('user');
@ -66,7 +69,7 @@ class SessionTest extends WebTestBase {
$this->drupalGet('session-test/id');
$matches = [];
preg_match('/\s*session_id:(.*)\n/', $this->getRawContent(), $matches);
preg_match('/\s*session_id:(.*)\n/', $this->getSession()->getPage()->getContent(), $matches);
$this->assertTrue(!empty($matches[1]), 'Found session ID after logging in.');
$this->assertTrue($matches[1] != $original_session, 'Session ID changed after login.');
}
@ -91,14 +94,22 @@ class SessionTest extends WebTestBase {
// properly, val_1 will still be set.
$value_2 = $this->randomMachineName();
$this->drupalGet('session-test/no-set/' . $value_2);
$session = $this->getSession();
$this->assertText($value_2, 'The session value was correctly passed to session-test/no-set.', 'Session');
$this->drupalGet('session-test/get');
$this->assertText($value_1, 'Session data is not saved for drupal_save_session(FALSE).', 'Session');
// Switch browser cookie to anonymous user, then back to user 1.
$this->sessionReset();
$this->sessionReset($user->id());
$session_cookie_name = $this->getSessionName();
$session_cookie_value = $session->getCookie($session_cookie_name);
$session->restart();
$this->initFrontPage();
// Session restart always resets all the cookies by design, so we need to
// add the old session cookie again.
$session->setCookie($session_cookie_name, $session_cookie_value);
$this->drupalGet('session-test/get');
$this->assertText($value_1, 'Session data persists through browser close.', 'Session');
$this->mink->setDefaultSessionName('default');
// Logout the user and make sure the stored value no longer persists.
$this->drupalLogout();
@ -242,8 +253,6 @@ class SessionTest extends WebTestBase {
$this->assertEqual($times4->timestamp, $times3->timestamp, 'Sessions table was not updated.');
// Force updating of users and sessions table once per second.
$this->settingsSet('session_write_interval', 0);
// Write that value also into the test settings.php file.
$settings['settings']['session_write_interval'] = (object) [
'value' => 0,
'required' => TRUE,
@ -270,8 +279,7 @@ class SessionTest extends WebTestBase {
// Send a blank sid in the session cookie, and the session should no longer
// be valid. Closing the curl handler will stop the previous session ID
// from persisting.
$this->curlClose();
$this->additionalCurlOptions[CURLOPT_COOKIE] = rawurlencode($this->getSessionName()) . '=;';
$this->mink->resetSessions();
$this->drupalGet('session-test/id-from-cookie');
$this->assertRaw("session_id:\n", 'Session ID is blank as sent from cookie header.');
// Assert that we have an anonymous session now.
@ -281,19 +289,13 @@ class SessionTest extends WebTestBase {
/**
* Reset the cookie file so that it refers to the specified user.
*
* @param $uid
* User id to set as the active session.
*/
public function sessionReset($uid = 0) {
public function sessionReset() {
// Close the internal browser.
$this->curlClose();
$this->mink->resetSessions();
$this->loggedInUser = FALSE;
// Change cookie file for user.
$this->cookieFile = \Drupal::service('stream_wrapper_manager')->getViaScheme('temporary')->getDirectoryPath() . '/cookie.' . $uid . '.txt';
$this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile;
$this->additionalCurlOptions[CURLOPT_COOKIESESSION] = TRUE;
$this->drupalGet('session-test/get');
$this->assertResponse(200, 'Session test module is correctly enabled.', 'Session');
}
@ -303,10 +305,10 @@ class SessionTest extends WebTestBase {
*/
public function assertSessionCookie($sent) {
if ($sent) {
$this->assertNotNull($this->sessionId, 'Session cookie was sent.');
$this->assertNotEmpty($this->getSessionCookies()->count(), 'Session cookie was sent.');
}
else {
$this->assertNull($this->sessionId, 'Session cookie was not sent.');
$this->assertEmpty($this->getSessionCookies()->count(), 'Session cookie was not sent.');
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\Tests\system\Functional\Session;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the stacked session handler functionality.
*
* @group Session
*/
class StackSessionHandlerIntegrationTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['session_test'];
/**
* Tests a request.
*/
public function testRequest() {
$options['query'][MainContentViewSubscriber::WRAPPER_FORMAT] = 'drupal_ajax';
$headers[] = 'X-Requested-With: XMLHttpRequest';
$actual_trace = json_decode($this->drupalGet('session-test/trace-handler', $options, $headers));
$sessionId = $this->getSessionCookies()->getCookieByName($this->getSessionName())->getValue();
$expect_trace = [
['BEGIN', 'test_argument', 'open'],
['BEGIN', NULL, 'open'],
['END', NULL, 'open'],
['END', 'test_argument', 'open'],
['BEGIN', 'test_argument', 'read', $sessionId],
['BEGIN', NULL, 'read', $sessionId],
['END', NULL, 'read', $sessionId],
['END', 'test_argument', 'read', $sessionId],
['BEGIN', 'test_argument', 'write', $sessionId],
['BEGIN', NULL, 'write', $sessionId],
['END', NULL, 'write', $sessionId],
['END', 'test_argument', 'write', $sessionId],
['BEGIN', 'test_argument', 'close'],
['BEGIN', NULL, 'close'],
['END', NULL, 'close'],
['END', 'test_argument', 'close'],
];
$this->assertEqual($expect_trace, $actual_trace);
}
}

View file

@ -1,16 +1,16 @@
<?php
namespace Drupal\system\Tests\System;
namespace Drupal\Tests\system\Functional\System;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
/**
* Performs tests on the Drupal error and exception handler.
*
* @group system
*/
class ErrorHandlerTest extends WebTestBase {
class ErrorHandlerTest extends BrowserTestBase {
/**
* Modules to enable.
@ -42,17 +42,6 @@ class ErrorHandlerTest extends WebTestBase {
'%function' => 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()',
'%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
];
$fatal_error = [
'%type' => 'Recoverable fatal error',
'%function' => 'Drupal\error_test\Controller\ErrorTestController->Drupal\error_test\Controller\{closure}()',
'@message' => 'Argument 1 passed to Drupal\error_test\Controller\ErrorTestController::Drupal\error_test\Controller\{closure}() must be of the type array, string given, called in ' . \Drupal::root() . '/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php on line 62 and defined',
];
if (version_compare(PHP_VERSION, '7.0.0-dev') >= 0) {
// In PHP 7, instead of a recoverable fatal error we get a TypeError.
$fatal_error['%type'] = 'TypeError';
// The error message also changes in PHP 7.
$fatal_error['@message'] = 'Argument 1 passed to Drupal\error_test\Controller\ErrorTestController::Drupal\error_test\Controller\{closure}() must be of the type array, string given, called in ' . \Drupal::root() . '/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php on line 62';
}
// Set error reporting to display verbose notices.
$this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
@ -68,25 +57,6 @@ class ErrorHandlerTest extends WebTestBase {
// Set error reporting to display verbose notices.
$this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
$this->drupalGet('error-test/generate-fatals');
$this->assertResponse(500, 'Received expected HTTP status code.');
$this->assertErrorMessage($fatal_error);
$this->assertRaw('<pre class="backtrace">', 'Found pre element with backtrace class.');
// Ensure we are escaping but not double escaping.
$this->assertRaw('&#039;');
$this->assertNoRaw('&amp;#039;');
// Remove the recoverable fatal error from the assertions, it's wanted here.
// Ensure that we just remove this one recoverable fatal error (in PHP 7 this
// is a TypeError).
foreach ($this->assertions as $key => $assertion) {
if (in_array($assertion['message_group'], ['Recoverable fatal error', 'TypeError']) && strpos($assertion['message'], 'Argument 1 passed to Drupal\error_test\Controller\ErrorTestController::Drupal\error_test\Controller\{closure}() must be of the type array, string given, called in') !== FALSE) {
unset($this->assertions[$key]);
$this->deleteAssert($assertion['message_id']);
}
}
// Drop the single exception.
$this->results['#exception']--;
// Set error reporting to collect notices.
$config->set('error_level', ERROR_REPORTING_DISPLAY_ALL)->save();
@ -96,7 +66,6 @@ class ErrorHandlerTest extends WebTestBase {
$this->assertErrorMessage($error_warning);
$this->assertErrorMessage($error_user_notice);
$this->assertNoRaw('<pre class="backtrace">', 'Did not find pre element with backtrace class.');
$this->assertErrorLogged($fatal_error['@message']);
// Set error reporting to not collect notices.
$config->set('error_level', ERROR_REPORTING_DISPLAY_SOME)->save();
@ -122,9 +91,6 @@ class ErrorHandlerTest extends WebTestBase {
* Test the exception handler.
*/
public function testExceptionHandler() {
// Ensure the test error log is empty before these tests.
$this->assertNoErrorsLogged();
$error_exception = [
'%type' => 'Exception',
'@message' => 'Drupal & awesome',
@ -148,11 +114,11 @@ class ErrorHandlerTest extends WebTestBase {
];
$this->drupalGet('error-test/trigger-exception');
$this->assertTrue(strpos($this->drupalGetHeader(':status'), '500 Service unavailable (with message)'), 'Received expected HTTP status line.');
$this->assertSession()->statusCodeEquals(500);
$this->assertErrorMessage($error_exception);
$this->drupalGet('error-test/trigger-pdo-exception');
$this->assertTrue(strpos($this->drupalGetHeader(':status'), '500 Service unavailable (with message)'), 'Received expected HTTP status line.');
$this->assertSession()->statusCodeEquals(500);
// We cannot use assertErrorMessage() since the exact error reported
// varies from database to database. Check that the SQL string is displayed.
$this->assertText($error_pdo_exception['%type'], format_string('Found %type in error page.', $error_pdo_exception));
@ -161,7 +127,7 @@ class ErrorHandlerTest extends WebTestBase {
$this->assertRaw($error_details, format_string("Found '@message' in error page.", ['@message' => $error_details]));
$this->drupalGet('error-test/trigger-renderer-exception');
$this->assertTrue(strpos($this->drupalGetHeader(':status'), '500 Service unavailable (with message)'), 'Received expected HTTP status line.');
$this->assertSession()->statusCodeEquals(500);
$this->assertErrorMessage($error_renderer_exception);
// Disable error reporting, ensure that 5xx responses are not cached.
@ -172,12 +138,8 @@ class ErrorHandlerTest extends WebTestBase {
$this->drupalGet('error-test/trigger-exception');
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
$this->assertIdentical(strpos($this->drupalGetHeader('Cache-Control'), 'public'), FALSE, 'Received expected HTTP status line.');
$this->assertTrue(strpos($this->drupalGetHeader(':status'), '500 Service unavailable (with message)'), 'Received expected HTTP status line.');
$this->assertSession()->statusCodeEquals(500);
$this->assertNoErrorMessage($error_exception);
// The exceptions are expected. Do not interpret them as a test failure.
// Not using File API; a potential error must trigger a PHP warning.
unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
}
/**

View file

@ -94,7 +94,11 @@ class ThemeTest extends BrowserTestBase {
$config->set('css.preprocess', 0);
$config->save();
$this->drupalGet('theme-test/suggestion');
$this->assertNoText('js.module.css', 'The theme\'s .info.yml file is able to override a module CSS file from being added to the page.');
// We add a "?" to the assertion, because drupalSettings may include
// information about the file; we only really care about whether it appears
// in a LINK or STYLE tag, for which Drupal always adds a query string for
// cache control.
$this->assertSession()->responseNotContains('js.module.css?');
// Also test with aggregation enabled, simply ensuring no PHP errors are
// triggered during drupal_build_css_cache() when a source file doesn't

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\Tests\system\FunctionalJavascript\Form;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests the tableselect form element for expected behavior.
*
* @group Form
*/
class ElementsTableSelectTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['form_test'];
/**
* Test the presence of ajax functionality for all options.
*/
public function testAjax() {
// Test checkboxes (#multiple == TRUE).
$this->drupalGet('form_test/tableselect/multiple-true');
$session = $this->getSession();
$page = $session->getPage();
for ($i = 1; $i <= 3; $i++) {
$row = 'row' . $i;
$page->hasUncheckedField($row);
$page->checkField($row);
$this->assertSession()->assertWaitOnAjaxRequest();
// Check current row and previous rows are checked.
for ($j = 1; $j <= $i; $j++) {
$other_row = 'row' . $j;
$page->hasCheckedField($other_row);
}
}
// Test radios (#multiple == FALSE).
$this->drupalGet('form_test/tableselect/multiple-false');
for ($i = 1; $i <= 3; $i++) {
$row = 'input[value="row' . $i . '"]';
$page->hasUncheckedField($row);
$this->click($row);
$this->assertSession()->assertWaitOnAjaxRequest();
$page->hasCheckedField($row);
// Check other rows are not checked
for ($j = 1; $j <= 3; $j++) {
if ($j == $i) {
continue;
}
$other_row = 'edit-tableselect-row' . $j;
$page->hasUncheckedField($other_row);
}
}
}
}

View file

@ -1,12 +1,12 @@
<?php
namespace Drupal\system\Tests\Form;
namespace Drupal\Tests\system\FunctionalJavascript\Form;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\simpletest\WebTestBase;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests functionality of \Drupal\Core\Form\FormBuilderInterface::rebuildForm().
@ -14,14 +14,12 @@ use Drupal\field\Entity\FieldStorageConfig;
* @group Form
* @todo Add tests for other aspects of form rebuilding.
*/
class RebuildTest extends WebTestBase {
class RebuildTest extends WebDriverTestBase {
/**
* Modules to enable.
*
* @var array
* {@inheritdoc}
*/
public static $modules = ['node', 'form_test'];
protected static $modules = ['node', 'form_test'];
/**
* A user for testing.
@ -30,6 +28,9 @@ class RebuildTest extends WebTestBase {
*/
protected $webUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
@ -39,28 +40,6 @@ class RebuildTest extends WebTestBase {
$this->drupalLogin($this->webUser);
}
/**
* Tests preservation of values.
*/
public function testRebuildPreservesValues() {
$edit = [
'checkbox_1_default_off' => TRUE,
'checkbox_1_default_on' => FALSE,
'text_1' => 'foo',
];
$this->drupalPostForm('form-test/form-rebuild-preserve-values', $edit, 'Add more');
// Verify that initial elements retained their submitted values.
$this->assertFieldChecked('edit-checkbox-1-default-off', 'A submitted checked checkbox retained its checked state during a rebuild.');
$this->assertNoFieldChecked('edit-checkbox-1-default-on', 'A submitted unchecked checkbox retained its unchecked state during a rebuild.');
$this->assertFieldById('edit-text-1', 'foo', 'A textfield retained its submitted value during a rebuild.');
// Verify that newly added elements were initialized with their default values.
$this->assertFieldChecked('edit-checkbox-2-default-on', 'A newly added checkbox was initialized with a default checked state.');
$this->assertNoFieldChecked('edit-checkbox-2-default-off', 'A newly added checkbox was initialized with a default unchecked state.');
$this->assertFieldById('edit-text-2', 'DEFAULT 2', 'A newly added textfield was initialized with its default value.');
}
/**
* Tests that a form's action is retained after an Ajax submission.
*
@ -68,6 +47,7 @@ class RebuildTest extends WebTestBase {
* followed by a non-Ajax submission, which triggers a validation error.
*/
public function testPreserveFormActionAfterAJAX() {
$page = $this->getSession()->getPage();
// Create a multi-valued field for 'page' nodes to use for Ajax testing.
$field_name = 'field_ajax_test';
FieldStorageConfig::create([
@ -81,8 +61,26 @@ class RebuildTest extends WebTestBase {
'entity_type' => 'node',
'bundle' => 'page',
])->save();
// Also create a file field to test server side validation error.
$field_file_name = 'field_file_test';
FieldStorageConfig::create([
'field_name' => $field_file_name,
'entity_type' => 'node',
'type' => 'file',
'cardinality' => 1,
])->save();
FieldConfig::create([
'field_name' => $field_file_name,
'entity_type' => 'node',
'bundle' => 'page',
'label' => 'Test file',
'required' => TRUE,
])->save();
entity_get_form_display('node', 'page', 'default')
->setComponent($field_name, ['type' => 'text_textfield'])
->setComponent($field_file_name, ['type' => 'file_generic'])
->save();
// Log in a user who can create 'page' nodes.
@ -93,27 +91,31 @@ class RebuildTest extends WebTestBase {
// 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');
$this->drupalPostAjaxForm(NULL, [], ['field_ajax_test_add_more' => t('Add another item')], NULL, [], [], 'node-page-form');
$this->assert(count($this->xpath('//div[contains(@class, "field--name-field-ajax-test")]//input[@type="text"]')) == 2, 'AJAX submission succeeded.');
$page->find('css', '[value="Add another item"]')->click();
$this->assertSession()->assertWaitOnAjaxRequest();
$this->assertTrue(count($this->xpath('//div[contains(@class, "field--name-field-ajax-test")]//input[@type="text"]')) == 2, 'AJAX submission succeeded.');
// Submit the form with the non-Ajax "Save" button, leaving the title field
// Submit the form with the non-Ajax "Save" button, leaving the file field
// blank to trigger a validation error, and ensure that a validation error
// occurred, because this test is for testing what happens when a form is
// re-rendered without being re-built, which is what happens when there's
// a validation error.
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertText('Title field is required.', 'Non-AJAX submission correctly triggered a validation error.');
// a server side validation error.
$edit = [
'title[0][value]' => $this->randomString(),
];
$this->drupalPostForm(NULL, $edit, 'Save');
$this->assertSession()->pageTextContains('Test file field is required.', 'Non-AJAX submission correctly triggered a validation error.');
// Ensure that the form contains two items in the multi-valued field, so we
// know we're testing a form that was correctly retrieved from cache.
$this->assert(count($this->xpath('//form[contains(@id, "node-page-form")]//div[contains(@class, "js-form-item-field-ajax-test")]//input[@type="text"]')) == 2, 'Form retained its state from cache.');
$this->assertTrue(count($this->xpath('//form[contains(@id, "node-page-form")]//div[contains(@class, "js-form-item-field-ajax-test")]//input[@type="text"]')) == 2, 'Form retained its state from cache.');
// Ensure that the form's action is correct.
$forms = $this->xpath('//form[contains(@class, "node-page-form")]');
$this->assertEqual(1, count($forms));
$this->assertEquals(1, count($forms));
// Strip query params off the action before asserting.
$url = parse_url($forms[0]['action'])['path'];
$this->assertEqual(Url::fromRoute('node.add', ['node_type' => 'page'])->toString(), $url);
$url = parse_url($forms[0]->getAttribute('action'))['path'];
$this->assertEquals(Url::fromRoute('node.add', ['node_type' => 'page'])->toString(), $url);
}
}

View file

@ -0,0 +1,111 @@
<?php
namespace Drupal\Tests\system\FunctionalJavascript\Form;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests that FAPI correctly determines the triggering element.
*
* @group Form
*/
class TriggeringElementTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['form_test'];
/**
* Tests the the triggering element when no button information is included.
*
* Test the determination of the triggering element when no button
* information is included in the POST data, as is sometimes the case when
* the ENTER key is pressed in a textfield in Internet Explorer.
*/
public function testNoButtonInfoInPost() {
$path = '/form-test/clicked-button';
$form_html_id = 'form-test-clicked-button';
// Ensure submitting a form with no buttons results in no triggering element
// and the form submit handler not running.
$this->drupalGet($path);
$assert_session = $this->assertSession();
$this->getSession()->getDriver()->submitForm('//form[@id="' . $form_html_id . '"]');
$assert_session->pageTextContains('There is no clicked button.');
$assert_session->pageTextNotContains('Submit handler for form_test_clicked_button executed.');
// Ensure submitting a form with one or more submit buttons results in the
// triggering element being set to the first one the user has access to. An
// argument with 'r' in it indicates a restricted (#access=FALSE) button.
$this->drupalGet($path . '/s');
$this->getSession()->getDriver()->submitForm('//form[@id="' . $form_html_id . '"]');
$assert_session->pageTextContains('The clicked button is button1.');
$assert_session->pageTextContains('Submit handler for form_test_clicked_button executed.');
$this->drupalGet($path . '/s/s');
$this->getSession()->getDriver()->submitForm('//form[@id="' . $form_html_id . '"]');
$assert_session->pageTextContains('The clicked button is button1.');
$assert_session->pageTextContains('Submit handler for form_test_clicked_button executed.');
$this->drupalGet($path . '/rs/s');
$this->getSession()->getDriver()->submitForm('//form[@id="' . $form_html_id . '"]');
$assert_session->pageTextContains('The clicked button is button2.');
$assert_session->pageTextContains('Submit handler for form_test_clicked_button executed.');
// Ensure submitting a form with buttons of different types results in the
// triggering element being set to the first button, regardless of type. For
// the FAPI 'button' type, this should result in the submit handler not
// executing. The types are 's'(ubmit), 'b'(utton), and 'i'(mage_button).
$this->drupalGet($path . '/s/b/i');
$this->getSession()->getDriver()->submitForm('//form[@id="' . $form_html_id . '"]');
$assert_session->pageTextContains('The clicked button is button1.');
$assert_session->pageTextContains('Submit handler for form_test_clicked_button executed.');
$this->drupalGet($path . '/b/s/i');
$this->getSession()->getDriver()->submitForm('//form[@id="' . $form_html_id . '"]');
$assert_session->pageTextContains('The clicked button is button1.');
$assert_session->pageTextNotContains('Submit handler for form_test_clicked_button executed.');
$this->drupalGet($path . '/i/s/b');
$this->getSession()->getDriver()->submitForm('//form[@id="' . $form_html_id . '"]');
$assert_session->pageTextContains('The clicked button is button1.');
$assert_session->pageTextContains('Submit handler for form_test_clicked_button executed.');
}
/**
* Tests attempts to bypass access control.
*
* Test that the triggering element does not get set to a button with
* #access=FALSE.
*/
public function testAttemptAccessControlBypass() {
$path = 'form-test/clicked-button';
$form_html_id = 'form-test-clicked-button';
// Retrieve a form where 'button1' has #access=FALSE and 'button2' doesn't.
$this->drupalGet($path . '/rs/s');
// Submit the form with 'button1=button1' in the POST data, which someone
// trying to get around security safeguards could easily do. We have to do
// a little trickery here, to work around the safeguards in drupalPostForm()
// by renaming the text field and value that is in the form to 'button1',
// we can get the data we want into \Drupal::request()->request.
$page = $this->getSession()->getPage();
$input = $page->find('css', 'input[name="text"]');
$this->assertNotNull($input, 'text input located.');
$input->setValue('name', 'button1');
$input->setValue('value', 'button1');
$this->xpath('//form[@id="' . $form_html_id . '"]//input[@type="submit"]')[0]->click();
// Ensure that the triggering element was not set to the restricted button.
// Do this with both a negative and positive assertion, because negative
// assertions alone can be brittle. See testNoButtonInfoInPost() for why the
// triggering element gets set to 'button2'.
$this->assertSession()->pageTextNotContains('The clicked button is button1.');
$this->assertSession()->pageTextContains('The clicked button is button2.');
}
}

View file

@ -0,0 +1,122 @@
<?php
namespace Drupal\Tests\system\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests the off-canvas dialog functionality.
*
* @group system
*/
class FrameworkTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'ajax_test', 'ajax_forms_test'];
/**
* Tests that new JavaScript and CSS files are lazy-loaded on an AJAX request.
*/
public function testLazyLoad() {
$expected = [
'setting_name' => 'ajax_forms_test_lazy_load_form_submit',
'setting_value' => 'executed',
'library_1' => 'system/admin',
'library_2' => 'system/drupal.system',
];
// Get the base page.
$this->drupalGet('ajax_forms_test_lazy_load_form');
$page = $this->getSession()->getPage();
$assert = $this->assertSession();
$original_settings = $this->getDrupalSettings();
$original_libraries = explode(',', $original_settings['ajaxPageState']['libraries']);
// Verify that the base page doesn't have the settings and files that are to
// be lazy loaded as part of the next requests.
$this->assertTrue(!isset($original_settings[$expected['setting_name']]), format_string('Page originally lacks the %setting, as expected.', ['%setting' => $expected['setting_name']]));
$this->assertTrue(!in_array($expected['library_1'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', ['%library' => $expected['library_1']]));
$this->assertTrue(!in_array($expected['library_2'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', ['%library' => $expected['library_2']]));
// Submit the AJAX request without triggering files getting added.
$page->pressButton('Submit');
$assert->assertWaitOnAjaxRequest();
$new_settings = $this->getDrupalSettings();
$new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']);
// Verify the setting was not added when not expected.
$this->assertTrue(!isset($new_settings[$expected['setting_name']]), format_string('Page still lacks the %setting, as expected.', ['%setting' => $expected['setting_name']]));
$this->assertTrue(!in_array($expected['library_1'], $new_libraries), format_string('Page still lacks the %library library, as expected.', ['%library' => $expected['library_1']]));
$this->assertTrue(!in_array($expected['library_2'], $new_libraries), format_string('Page still lacks the %library library, as expected.', ['%library' => $expected['library_2']]));
// Submit the AJAX request and trigger adding files.
$page->checkField('add_files');
$page->pressButton('Submit');
$assert->assertWaitOnAjaxRequest();
$new_settings = $this->getDrupalSettings();
$new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']);
// Verify the expected setting was added, both to drupalSettings, and as
// the first AJAX command.
$this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], format_string('Page now has the %setting.', ['%setting' => $expected['setting_name']]));
// Verify the expected CSS file was added, both to drupalSettings, and as
// the second AJAX command for inclusion into the HTML.
$this->assertTrue(in_array($expected['library_1'], $new_libraries), format_string('Page state now has the %library library.', ['%library' => $expected['library_1']]));
// Verify the expected JS file was added, both to drupalSettings, and as
// the third AJAX command for inclusion into the HTML. By testing for an
// exact HTML string containing the SCRIPT tag, we also ensure that
// unexpected JavaScript code, such as a jQuery.extend() that would
// potentially clobber rather than properly merge settings, didn't
// accidentally get added.
$this->assertTrue(in_array($expected['library_2'], $new_libraries), format_string('Page state now has the %library library.', ['%library' => $expected['library_2']]));
}
/**
* Tests that drupalSettings.currentPath is not updated on AJAX requests.
*/
public function testCurrentPathChange() {
$this->drupalGet('ajax_forms_test_lazy_load_form');
$page = $this->getSession()->getPage();
$assert = $this->assertSession();
$old_settings = $this->getDrupalSettings();
$page->pressButton('Submit');
$assert->assertWaitOnAjaxRequest();
$new_settings = $this->getDrupalSettings();
$this->assertEquals($old_settings['path']['currentPath'], $new_settings['path']['currentPath']);
}
/**
* Tests that overridden CSS files are not added during lazy load.
*/
public function testLazyLoadOverriddenCSS() {
// The test theme overrides js.module.css without an implementation,
// thereby removing it.
\Drupal::service('theme_handler')->install(['test_theme']);
$this->config('system.theme')
->set('default', 'test_theme')
->save();
// This gets the form, and does an Ajax submission on it.
$this->drupalGet('ajax_forms_test_lazy_load_form');
$page = $this->getSession()->getPage();
$assert = $this->assertSession();
$page->checkField('add_files');
$page->pressButton('Submit');
$assert->assertWaitOnAjaxRequest();
// Verify that the resulting HTML does not load the overridden CSS file.
// We add a "?" to the assertion, because drupalSettings may include
// information about the file; we only really care about whether it appears
// in a LINK or STYLE tag, for which Drupal always adds a query string for
// cache control.
$assert->responseNotContains('js.module.css?', 'Ajax lazy loading does not add overridden CSS files.');
}
}

View file

@ -233,11 +233,11 @@ class ModuleHandlerTest extends KernelTestBase {
$result = $this->moduleInstaller()->uninstall([$non_dependency]);
$this->assertTrue($result, 'ModuleInstaller::uninstall() returns TRUE.');
$this->assertFalse($this->moduleHandler()->moduleExists($non_dependency));
$this->assertEquals(drupal_get_installed_schema_version($non_dependency), SCHEMA_UNINSTALLED, "$dependency module was uninstalled.");
$this->assertEquals(drupal_get_installed_schema_version($non_dependency), SCHEMA_UNINSTALLED, "$non_dependency module was uninstalled.");
// Verify that the installation profile itself was not uninstalled.
$uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order') ?: [];
$this->assertContains($non_dependency, $uninstalled_modules, "$dependency module is in the list of uninstalled modules.");
$this->assertContains($non_dependency, $uninstalled_modules, "$non_dependency module is in the list of uninstalled modules.");
$this->assertNotContains($profile, $uninstalled_modules, 'The installation profile is not in the list of uninstalled modules.');
// Try uninstalling the required module.

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\Tests\system\Kernel\Form;
use Drupal\form_test\Form\FormTestFileForm;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests for the 'file' form element.
*
* @group Form
*/
class FileElementTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['form_test'];
/**
* Tests that file elements are built and processed correctly.
*/
public function testFileElement() {
$form = $this->container->get('form_builder')
->getForm(FormTestFileForm::class);
$this->assertSame('file', $form['file']['#type']);
$this->assertTrue($form['file']['#multiple']);
$this->assertContains('cagatio', $form['file']['#attributes']['class']);
}
}

View file

@ -16,7 +16,7 @@ base theme: classy
core: 8.x
logo: images/logo2.svg
stylesheets-remove:
- '@system/css/js.module.css'
- '@stable/css/system/components/js.module.css'
libraries:
- test_theme/global-styling
libraries-override:

View file

@ -10,3 +10,9 @@ test_theme_settings.settings:
sequence:
type: integer
label: 'fids'
multi_file:
type: sequence
label: 'Multiple file field with all file extensions'
sequence:
type: integer
label: 'fids'

View file

@ -24,6 +24,17 @@ function test_theme_settings_form_system_theme_settings_alter(&$form, FormStateI
],
];
$form['multi_file'] = [
'#type' => 'managed_file',
'#title' => t('Multiple file field with all file extensions'),
'#multiple' => TRUE,
'#default_value' => theme_get_setting('multi_file'),
'#upload_location' => 'public://test',
'#upload_validators' => [
'file_validate_extensions' => [],
],
];
$form['#submit'][] = 'test_theme_settings_form_system_theme_settings_submit';
}