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

@ -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

@ -0,0 +1,234 @@
<?php
namespace Drupal\Tests\system\Functional\Form;
use Drupal\Core\Form\FormState;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the tableselect form element for expected behavior.
*
* @group Form
*/
class ElementsTableSelectTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['form_test'];
/**
* Test the display of checkboxes when #multiple is TRUE.
*/
public function testMultipleTrue() {
$this->drupalGet('form_test/tableselect/multiple-true');
$this->assertSession()->responseNotContains('Empty text.', 'Empty text should not be displayed.');
// Test for the presence of the Select all rows tableheader.
$this->assertNotEmpty($this->xpath('//th[@class="select-all"]'), 'Presence of the "Select all" checkbox.');
$rows = ['row1', 'row2', 'row3'];
foreach ($rows as $row) {
$this->assertNotEmpty($this->xpath('//input[@type="checkbox"]', [$row]), "Checkbox for the value $row.");
}
}
/**
* Test the display of radios when #multiple is FALSE.
*/
public function testMultipleFalse() {
$this->drupalGet('form_test/tableselect/multiple-false');
$this->assertSession()->pageTextNotContains('Empty text.');
// Test for the absence of the Select all rows tableheader.
$this->assertFalse($this->xpath('//th[@class="select-all"]'));
$rows = ['row1', 'row2', 'row3'];
foreach ($rows as $row) {
$this->assertNotEmpty($this->xpath('//input[@type="radio"]', [$row], "Radio button value: $row"));
}
}
/**
* Tests the display when #colspan is set.
*/
public function testTableSelectColSpan() {
$this->drupalGet('form_test/tableselect/colspan');
$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/tr/th');
$this->assertEquals(count($table_head), 4, 'There are four column headers');
// 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->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->assertEquals(count($this->xpath('//tbody/tr[3]/td')), 3, 'There are three cells in row 3.');
}
/**
* Test the display of the #empty text when #options is an empty array.
*/
public function testEmptyText() {
$this->drupalGet('form_test/tableselect/empty-text');
$this->assertSession()->pageTextContains('Empty text.', 'Empty text should be displayed.');
}
/**
* Test the submission of single and multiple values when #multiple is TRUE.
*/
public function testMultipleTrueSubmit() {
// Test a submission with one checkbox checked.
$edit = [];
$edit['tableselect[row1]'] = TRUE;
$this->drupalPostForm('form_test/tableselect/multiple-true', $edit, 'Submit');
$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');
$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.');
}
/**
* Test submission of values when #multiple is FALSE.
*/
public function testMultipleFalseSubmit() {
$edit['tableselect'] = 'row1';
$this->drupalPostForm('form_test/tableselect/multiple-false', $edit, 'Submit');
$this->assertSession()->pageTextContains('Submitted: row1', 'Selected radio button');
}
/**
* Test the #js_select property.
*/
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->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->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->assertFalse($this->xpath('//th[@class="select-all"]'));
$this->drupalGet('form_test/tableselect/advanced-select/multiple-false-advanced-select');
$this->assertFalse($this->xpath('//th[@class="select-all"]'));
}
/**
* Test the whether the option checker gives an error on invalid tableselect values for checkboxes.
*/
public function testMultipleTrueOptionchecker() {
list($header, $options) = _form_test_tableselect_get_data();
$form['tableselect'] = [
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
];
// Test with a valid value.
list(, , $errors) = $this->formSubmitHelper($form, ['tableselect' => ['row1' => 'row1']]);
$this->assertFalse(isset($errors['tableselect']), 'Option checker allows valid values for checkboxes.');
// Test with an invalid value.
list(, , $errors) = $this->formSubmitHelper($form, ['tableselect' => ['non_existing_value' => 'non_existing_value']]);
$this->assertTrue(isset($errors['tableselect']), 'Option checker disallows invalid values for checkboxes.');
}
/**
* Test the whether the option checker gives an error on invalid tableselect values for radios.
*/
public function testMultipleFalseOptionchecker() {
list($header, $options) = _form_test_tableselect_get_data();
$form['tableselect'] = [
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#multiple' => FALSE,
];
// Test with a valid value.
list(, , $errors) = $this->formSubmitHelper($form, ['tableselect' => 'row1']);
$this->assertFalse(isset($errors['tableselect']), 'Option checker allows valid values for radio buttons.');
// Test with an invalid value.
list(, , $errors) = $this->formSubmitHelper($form, ['tableselect' => 'non_existing_value']);
$this->assertTrue(isset($errors['tableselect']), 'Option checker disallows invalid values for radio buttons.');
}
/**
* Helper function for the option check test to submit a form while collecting errors.
*
* @param $form_element
* A form element to test.
* @param $edit
* An array containing post data.
*
* @return
* An array containing the processed form, the form_state and any errors.
*/
private function formSubmitHelper($form, $edit) {
$form_id = $this->randomMachineName();
$form_state = new FormState();
$form['op'] = ['#type' => 'submit', '#value' => t('Submit')];
// The form token CSRF protection should not interfere with this test, so we
// bypass it by setting the token to FALSE.
$form['#token'] = FALSE;
$edit['form_id'] = $form_id;
// Disable page redirect for forms submitted programmatically. This is a
// solution to skip the redirect step (there are no pages, then the redirect
// isn't possible).
$form_state->disableRedirect();
$form_state->setUserInput($edit);
$form_state->setFormObject(new StubForm($form_id, $form));
\Drupal::formBuilder()->prepareForm($form_id, $form, $form_state);
\Drupal::formBuilder()->processForm($form_id, $form, $form_state);
$errors = $form_state->getErrors();
// Clear errors and messages.
\Drupal::messenger()->deleteAll();
$form_state->clearErrors();
// Return the processed form together with form_state and errors
// to allow the caller lowlevel access to the form.
return [$form, $form_state, $errors];
}
}

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

@ -0,0 +1,198 @@
<?php
namespace Drupal\Tests\system\Functional\Form;
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
* caching works right.
*
* The tested form puts data into the storage during the initial form
* construction. These tests verify that there are no duplicate form
* constructions, with and without manual form caching activated. Furthermore
* when a validation error occurs, it makes sure that changed form element
* values are not lost due to a wrong form rebuild.
*
* @group Form
*/
class StorageTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['form_test', 'dblog'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser());
}
/**
* Tests using the form in a usual way.
*/
public function testForm() {
$this->drupalGet('form_test/form-storage');
$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');
$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');
$assert_session->fieldValueEquals('title', 'new');
// After rebuilding, the form has been cached.
$assert_session->pageTextContains('Form constructions: 4');
$this->drupalPostForm(NULL, $edit, 'Save');
$assert_session->pageTextContains('Form constructions: 4');
$assert_session->pageTextContains('Title: new', 'The form storage has stored the values.');
}
/**
* Tests using the form after calling $form_state->setCached().
*/
public function testFormCached() {
$this->drupalGet('form_test/form-storage', ['query' => ['cache' => 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->assertSession()->pageTextContains('Form constructions: 2');
// The second one is for the rebuilding of the form.
$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->assertSession()->fieldValueEquals('title', 'new');
$this->assertSession()->pageTextContains('Form constructions: 4');
$this->drupalPostForm(NULL, $edit, 'Save');
$this->assertSession()->pageTextContains('Form constructions: 4');
$this->assertSession()->pageTextContains('Title: new', 'The form storage has stored the values.');
}
/**
* Tests validation when form storage is used.
*/
public function testValidation() {
$this->drupalPostForm('form_test/form-storage', ['title' => '', 'value' => 'value_is_set'], 'Continue submit');
$this->assertPattern('/value_is_set/', 'The input values have been kept.');
}
/**
* Tests updating cached form storage during form validation.
*
* If form caching is enabled and a form stores data in the form storage, then
* the form storage also has to be updated in case of a validation error in
* the form. This test re-uses the existing form for multi-step tests, but
* triggers a special #element_validate handler to update the form storage
* during form validation, while another, required element in the form
* triggers a form validation error.
*/
public function testCachedFormStorageValidation() {
// Request the form with 'cache' query parameter to enable form caching.
$this->drupalGet('form_test/form-storage', ['query' => ['cache' => 1]]);
// Skip step 1 of the multi-step form, since the first step copies over
// 'title' into form storage, but we want to verify that changes in the form
// storage are updated in the cache during form validation.
$edit = ['title' => 'foo'];
$this->drupalPostForm(NULL, $edit, 'Continue submit');
// In step 2, trigger a validation error for the required 'title' field, and
// post the special 'change_title' value for the 'value' field, which
// conditionally invokes the #element_validate handler to update the form
// storage.
$edit = ['title' => '', 'value' => 'change_title'];
$this->drupalPostForm(NULL, $edit, 'Save');
// At this point, the form storage should contain updated values, but we do
// not see them, because the form has not been rebuilt yet due to the
// 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->assertSession()->pageTextContains("The thing has been changed.", 'The altered form storage value was updated in cache and taken over.');
}
/**
* Verifies that form build-id is regenerated when loading an immutable form
* from the cache.
*/
public function testImmutableForm() {
// 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->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->assertSession()->hiddenFieldValueNotEquals('form_build_id', $buildId);
// Retrieve the new build-id.
$buildIdFields = $this->xpath('//input[@name="form_build_id"]');
$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->assertSession()->hiddenFieldValueEquals('form_build_id', $buildId);
}
/**
* Verify that existing contrib code cannot overwrite immutable form state.
*/
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->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.
$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) 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.
$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

@ -0,0 +1,334 @@
<?php
namespace Drupal\Tests\system\Functional\Routing;
use Drupal\Core\Cache\Cache;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\BrowserTestBase;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Drupal\Core\Url;
/**
* Functional class for the full integrated routing system.
*
* @group Routing
*/
class RouterTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['router_test'];
/**
* Confirms that our FinishResponseSubscriber logic works properly.
*/
public function testFinishResponseSubscriber() {
$renderer_required_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
$expected_cache_contexts = Cache::mergeContexts($renderer_required_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]);
// Confirm that the router can get to a controller.
$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->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']);
// 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->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']);
// 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']);
// 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']));
// 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']));
// 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']);
// 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']);
// 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->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']));
}
/**
* Confirms that multiple routes with the same path do not cause an error.
*/
public function testDuplicateRoutePaths() {
// Tests two routes with exactly the same path. The route with the maximum
// fit and lowest sorting route name will match, regardless of the order the
// routes are declared.
// @see \Drupal\Core\Routing\RouteProvider::getRoutesByPath()
$this->drupalGet('router-test/duplicate-path2');
$this->assertResponse(200);
$this->assertRaw('router_test.two_duplicate1');
// Tests three routes with same the path. One of the routes the path has a
// different case.
$this->drupalGet('router-test/case-sensitive-duplicate-path3');
$this->assertResponse(200);
$this->assertRaw('router_test.case_sensitive_duplicate1');
// While case-insensitive matching works, exact matches are preferred.
$this->drupalGet('router-test/case-sensitive-Duplicate-PATH3');
$this->assertResponse(200);
$this->assertRaw('router_test.case_sensitive_duplicate2');
// Test that case-insensitive matching works, falling back to the first
// route defined.
$this->drupalGet('router-test/case-sensitive-Duplicate-Path3');
$this->assertResponse(200);
$this->assertRaw('router_test.case_sensitive_duplicate1');
}
/**
* Confirms that placeholders in paths work correctly.
*/
public function testControllerPlaceholders() {
// Test with 0 and a random value.
$values = ["0", $this->randomMachineName()];
foreach ($values as $value) {
$this->drupalGet('router_test/test3/' . $value);
$this->assertResponse(200);
$this->assertRaw($value, 'The correct string was returned because the route was successful.');
}
// 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->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
}
/**
* Confirms that default placeholders in paths work correctly.
*/
public function testControllerPlaceholdersDefaultValues() {
$this->drupalGet('router_test/test4');
$this->assertResponse(200);
$this->assertRaw('narf', 'The correct string was returned because the route was successful.');
// 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->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
}
/**
* Confirms that default placeholders in paths work correctly.
*/
public function testControllerPlaceholdersDefaultValuesProvided() {
$this->drupalGet('router_test/test4/barf');
$this->assertResponse(200);
$this->assertRaw('barf', 'The correct string was returned because the route was successful.');
// 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->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
}
/**
* Checks that dynamically defined and altered routes work correctly.
*
* @see \Drupal\router_test\RouteSubscriber
*/
public function testDynamicRoutes() {
// Test the altered route.
$this->drupalGet('router_test/test6');
$this->assertResponse(200);
$this->assertRaw('test5', 'The correct string was returned because the route was successful.');
}
/**
* Checks that a request with text/html response gets rendered as a page.
*/
public function testControllerResolutionPage() {
$this->drupalGet('/router_test/test10');
$this->assertRaw('abcde', 'Correct body was found.');
// 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->assertSession()->responseNotMatches('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
}
/**
* Checks the generate method on the url generator using the front router.
*/
public function testUrlGeneratorFront() {
$front_url = Url::fromRoute('<front>', [], ['absolute' => TRUE]);
// Compare to the site base URL.
$base_url = Url::fromUri('base:/', ['absolute' => TRUE]);
$this->assertIdentical($base_url->toString(), $front_url->toString());
}
/**
* Tests that a page trying to match a path will succeed.
*/
public function testRouterMatching() {
$this->drupalGet('router_test/test14/1');
$this->assertResponse(200);
$this->assertText('User route "entity.user.canonical" was matched.');
// Try to match a route for a non-existent user.
$this->drupalGet('router_test/test14/2');
$this->assertResponse(200);
$this->assertText('Route not matched.');
// Check that very long paths don't cause an error.
$path = 'router_test/test1';
$suffix = '/d/r/u/p/a/l';
for ($i = 0; $i < 10; $i++) {
$path .= $suffix;
$this->drupalGet($path);
$this->assertResponse(404);
}
}
/**
* Tests that a PSR-7 response works.
*/
public function testRouterResponsePsr7() {
$this->drupalGet('/router_test/test23');
$this->assertResponse(200);
$this->assertText('test23');
}
/**
* Tests the user account on the DIC.
*/
public function testUserAccount() {
$account = $this->drupalCreateUser();
$this->drupalLogin($account);
$second_account = $this->drupalCreateUser();
$this->drupalGet('router_test/test12/' . $second_account->id());
$this->assertText($account->getUsername() . ':' . $second_account->getUsername());
$this->assertEqual($account->id(), $this->loggedInUser->id(), 'Ensure that the user was not changed.');
$this->drupalGet('router_test/test13/' . $second_account->id());
$this->assertText($account->getUsername() . ':' . $second_account->getUsername());
$this->assertEqual($account->id(), $this->loggedInUser->id(), 'Ensure that the user was not changed.');
}
/**
* Checks that an ajax request gets rendered as an Ajax response, by mime.
*/
public function testControllerResolutionAjax() {
// This will fail with a JSON parse error if the request is not routed to
// The correct controller.
$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');
$this->assertRaw('abcde', 'Correct body was found.');
}
/**
* Tests that routes no longer exist for a module that has been uninstalled.
*/
public function testRouterUninstallInstall() {
\Drupal::service('module_installer')->uninstall(['router_test']);
\Drupal::service('router.builder')->rebuild();
try {
\Drupal::service('router.route_provider')->getRouteByName('router_test.1');
$this->fail('Route was delete on uninstall.');
}
catch (RouteNotFoundException $e) {
$this->pass('Route was delete on uninstall.');
}
// Install the module again.
\Drupal::service('module_installer')->install(['router_test']);
\Drupal::service('router.builder')->rebuild();
$route = \Drupal::service('router.route_provider')->getRouteByName('router_test.1');
$this->assertNotNull($route, 'Route exists after module installation');
}
/**
* Ensure that multiple leading slashes are redirected.
*/
public function testLeadingSlashes() {
$request = $this->container->get('request_stack')->getCurrentRequest();
$url = $request->getUriForPath('//router_test/test1');
$this->drupalGet($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->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->assertUrl($request->getUriForPath('/router_test/test1') . '?qs=test');
}
}

View file

@ -0,0 +1,138 @@
<?php
namespace Drupal\Tests\system\Functional\Session;
use Drupal\Core\Url;
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 BrowserTestBase {
use BasicAuthTestTrait;
/**
* A test user.
*
* @var \Drupal\user\Entity\User
*/
protected $user;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth', 'session_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a test administrator user.
$this->user = $this->drupalCreateUser(['administer site configuration']);
}
/**
* Check that a basic authentication session does not leak.
*
* Regression test for a bug that caused a session initiated by basic
* authentication to persist over subsequent unauthorized requests.
*/
public function testSessionFromBasicAuthenticationDoesNotLeak() {
// This route is authorized through basic_auth only, not cookie.
$protected_url = Url::fromRoute('session_test.get_session_basic_auth');
// This route is not protected.
$unprotected_url = Url::fromRoute('session_test.get_session_no_auth');
// 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->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($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($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.
$this->drupalGet($protected_url);
$this->assertResponse(401, 'A subsequent request to the same route without basic authentication is not authorized.');
}
/**
* Tests if a session can be initiated through basic authentication.
*/
public function testBasicAuthSession() {
// Set a session value on a request through basic auth.
$test_value = 'alpaca';
$response = $this->basicAuthGet('session-test/set-session/' . $test_value, $this->user->getUsername(), $this->user->pass_raw);
$this->assertSessionData($response, $test_value);
$this->assertResponse(200, 'The request to set a session value was successful.');
// Test that on a subsequent request the session value is still present.
$response = $this->basicAuthGet('session-test/get-session', $this->user->getUsername(), $this->user->pass_raw);
$this->assertSessionData($response, $test_value);
$this->assertResponse(200, 'The request to get a session value was successful.');
}
/**
* Checks the session data returned by the session test routes.
*
* @param string $response
* A response object containing the session values and the user ID.
* @param string $expected
* The expected session value.
*/
protected function assertSessionData($response, $expected) {
$response = json_decode($response, TRUE);
$this->assertEqual(['test_value' => $expected], $response['session'], 'The session data matches the expected value.');
// Check that we are logged in as the correct user.
$this->assertEqual($this->user->id(), $response['user'], 'The correct user is logged in.');
}
/**
* Tests that a session is not started automatically by basic authentication.
*/
public function testBasicAuthNoSession() {
// A route that is authorized through basic_auth only, not cookie.
$no_cookie_url = Url::fromRoute('session_test.get_session_basic_auth');
// A route that is authorized with standard cookie authentication.
$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->getAccountName(), $this->user->passRaw);
$this->assertResponse(200, 'The user is successfully authenticated using 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.
$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->assertNotEmpty($this->getSessionCookies());
}
}

View file

@ -0,0 +1,327 @@
<?php
namespace Drupal\Tests\system\Functional\Session;
use Drupal\Tests\BrowserTestBase;
/**
* Drupal session handling tests.
*
* @group Session
*/
class SessionTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['session_test'];
protected $dumpHeaders = TRUE;
/**
* Tests for \Drupal\Core\Session\WriteSafeSessionHandler::setSessionWritable()
* ::isSessionWritable and \Drupal\Core\Session\SessionManager::regenerate().
*/
public function testSessionSaveRegenerate() {
$session_handler = $this->container->get('session_handler.write_safe');
$this->assertTrue($session_handler->isSessionWritable(), 'session_handler->isSessionWritable() initially returns TRUE.');
$session_handler->setSessionWritable(FALSE);
$this->assertFalse($session_handler->isSessionWritable(), '$session_handler->isSessionWritable() returns FALSE after disabling.');
$session_handler->setSessionWritable(TRUE);
$this->assertTrue($session_handler->isSessionWritable(), '$session_handler->isSessionWritable() returns TRUE after enabling.');
// Test session hardening code from SA-2008-044.
$user = $this->drupalCreateUser();
// Enable sessions.
$this->sessionReset();
// 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.');
// Verify that the session is regenerated if a module calls exit
// in hook_user_login().
$user->name = 'session_test_user';
$user->save();
$this->drupalGet('session-test/id');
$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->getAccountName(),
'pass' => $user->passRaw,
];
$this->drupalPostForm('user/login', $edit, t('Log in'));
$this->drupalGet('user');
$pass = $this->assertText($user->getUsername(), format_string('Found name: %name', ['%name' => $user->getUsername()]), 'User login');
$this->_logged_in = $pass;
$this->drupalGet('session-test/id');
$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.');
}
/**
* Test data persistence via the session_test module callbacks.
*/
public function testDataPersistence() {
$user = $this->drupalCreateUser([]);
// Enable sessions.
$this->sessionReset($user->id());
$this->drupalLogin($user);
$value_1 = $this->randomMachineName();
$this->drupalGet('session-test/set/' . $value_1);
$this->assertText($value_1, 'The session value was stored.', 'Session');
$this->drupalGet('session-test/get');
$this->assertText($value_1, 'Session correctly returned the stored data for an authenticated user.', 'Session');
// Attempt to write over val_1. If drupal_save_session(FALSE) is working.
// 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.
$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();
$this->sessionReset();
$this->drupalGet('session-test/get');
$this->assertNoText($value_1, "After logout, previous user's session data is not available.", 'Session');
// Now try to store some data as an anonymous user.
$value_3 = $this->randomMachineName();
$this->drupalGet('session-test/set/' . $value_3);
$this->assertText($value_3, 'Session data stored for anonymous user.', 'Session');
$this->drupalGet('session-test/get');
$this->assertText($value_3, 'Session correctly returned the stored data for an anonymous user.', 'Session');
// Try to store data when drupal_save_session(FALSE).
$value_4 = $this->randomMachineName();
$this->drupalGet('session-test/no-set/' . $value_4);
$this->assertText($value_4, 'The session value was correctly passed to session-test/no-set.', 'Session');
$this->drupalGet('session-test/get');
$this->assertText($value_3, 'Session data is not saved for drupal_save_session(FALSE).', 'Session');
// Login, the data should persist.
$this->drupalLogin($user);
$this->sessionReset($user->id());
$this->drupalGet('session-test/get');
$this->assertNoText($value_1, 'Session has persisted for an authenticated user after logging out and then back in.', 'Session');
// Change session and create another user.
$user2 = $this->drupalCreateUser([]);
$this->sessionReset($user2->id());
$this->drupalLogin($user2);
}
/**
* Tests storing data in Session() object.
*/
public function testSessionPersistenceOnLogin() {
// Store information via hook_user_login().
$user = $this->drupalCreateUser();
$this->drupalLogin($user);
// Test property added to session object form hook_user_login().
$this->drupalGet('session-test/get-from-session-object');
$this->assertText('foobar', 'Session data is saved in Session() object.', 'Session');
}
/**
* Test that empty anonymous sessions are destroyed.
*/
public function testEmptyAnonymousSession() {
// Disable the dynamic_page_cache module; it'd cause session_test's debug
// output (that is added in
// SessionTestSubscriber::onKernelResponseSessionTest()) to not be added.
$this->container->get('module_installer')->uninstall(['dynamic_page_cache']);
// Verify that no session is automatically created for anonymous user when
// page caching is disabled.
$this->container->get('module_installer')->uninstall(['page_cache']);
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(TRUE);
// The same behavior is expected when caching is enabled.
$this->container->get('module_installer')->install(['page_cache']);
$config = $this->config('system.performance');
$config->set('cache.page.max_age', 300);
$config->save();
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
// @todo Reinstate when REQUEST and RESPONSE events fire for cached pages.
// $this->assertSessionEmpty(TRUE);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
// Start a new session by setting a message.
$this->drupalGet('session-test/set-message');
$this->assertSessionCookie(TRUE);
$this->assertTrue($this->drupalGetHeader('Set-Cookie'), 'New session was started.');
// Display the message, during the same request the session is destroyed
// and the session cookie is unset.
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(FALSE);
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.');
$this->assertText(t('This is a dummy message.'), 'Message was displayed.');
$this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), 'Session cookie was deleted.');
// Verify that session was destroyed.
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
// @todo Reinstate when REQUEST and RESPONSE events fire for cached pages.
// $this->assertSessionEmpty(TRUE);
$this->assertNoText(t('This is a dummy message.'), 'Message was not cached.');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
$this->assertFalse($this->drupalGetHeader('Set-Cookie'), 'New session was not started.');
// Verify that no session is created if drupal_save_session(FALSE) is called.
$this->drupalGet('session-test/set-message-but-dont-save');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(TRUE);
// Verify that no message is displayed.
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
// @todo Reinstate when REQUEST and RESPONSE events fire for cached pages.
// $this->assertSessionEmpty(TRUE);
$this->assertNoText(t('This is a dummy message.'), 'The message was not saved.');
}
/**
* Test that sessions are only saved when necessary.
*/
public function testSessionWrite() {
$user = $this->drupalCreateUser([]);
$this->drupalLogin($user);
$sql = 'SELECT u.access, s.timestamp FROM {users_field_data} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE u.uid = :uid';
$times1 = db_query($sql, [':uid' => $user->id()])->fetchObject();
// Before every request we sleep one second to make sure that if the session
// is saved, its timestamp will change.
// Modify the session.
sleep(1);
$this->drupalGet('session-test/set/foo');
$times2 = db_query($sql, [':uid' => $user->id()])->fetchObject();
$this->assertEqual($times2->access, $times1->access, 'Users table was not updated.');
$this->assertNotEqual($times2->timestamp, $times1->timestamp, 'Sessions table was updated.');
// Write the same value again, i.e. do not modify the session.
sleep(1);
$this->drupalGet('session-test/set/foo');
$times3 = db_query($sql, [':uid' => $user->id()])->fetchObject();
$this->assertEqual($times3->access, $times1->access, 'Users table was not updated.');
$this->assertEqual($times3->timestamp, $times2->timestamp, 'Sessions table was not updated.');
// Do not change the session.
sleep(1);
$this->drupalGet('');
$times4 = db_query($sql, [':uid' => $user->id()])->fetchObject();
$this->assertEqual($times4->access, $times3->access, 'Users table was not updated.');
$this->assertEqual($times4->timestamp, $times3->timestamp, 'Sessions table was not updated.');
// Force updating of users and sessions table once per second.
$settings['settings']['session_write_interval'] = (object) [
'value' => 0,
'required' => TRUE,
];
$this->writeSettings($settings);
$this->drupalGet('');
$times5 = db_query($sql, [':uid' => $user->id()])->fetchObject();
$this->assertNotEqual($times5->access, $times4->access, 'Users table was updated.');
$this->assertNotEqual($times5->timestamp, $times4->timestamp, 'Sessions table was updated.');
}
/**
* Test that empty session IDs are not allowed.
*/
public function testEmptySessionID() {
$user = $this->drupalCreateUser([]);
$this->drupalLogin($user);
$this->drupalGet('session-test/is-logged-in');
$this->assertResponse(200, 'User is logged in.');
// Reset the sid in {sessions} to a blank string. This may exist in the
// wild in some cases, although we normally prevent it from happening.
db_query("UPDATE {sessions} SET sid = '' WHERE uid = :uid", [':uid' => $user->id()]);
// 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->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.
$this->drupalGet('session-test/is-logged-in');
$this->assertResponse(403, 'An empty session ID is not allowed.');
}
/**
* Reset the cookie file so that it refers to the specified user.
*/
public function sessionReset() {
// Close the internal browser.
$this->mink->resetSessions();
$this->loggedInUser = FALSE;
// Change cookie file for user.
$this->drupalGet('session-test/get');
$this->assertResponse(200, 'Session test module is correctly enabled.', 'Session');
}
/**
* Assert whether the SimpleTest browser sent a session cookie.
*/
public function assertSessionCookie($sent) {
if ($sent) {
$this->assertNotEmpty($this->getSessionCookies()->count(), 'Session cookie was sent.');
}
else {
$this->assertEmpty($this->getSessionCookies()->count(), 'Session cookie was not sent.');
}
}
/**
* Assert whether $_SESSION is empty at the beginning of the request.
*/
public function assertSessionEmpty($empty) {
if ($empty) {
$this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '1', 'Session was empty.');
}
else {
$this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', 'Session was not empty.');
}
}
}

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

@ -0,0 +1,171 @@
<?php
namespace Drupal\Tests\system\Functional\System;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Tests\BrowserTestBase;
/**
* Performs tests on the Drupal error and exception handler.
*
* @group system
*/
class ErrorHandlerTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['error_test'];
/**
* Test the error handler.
*/
public function testErrorHandler() {
$config = $this->config('system.logging');
$error_notice = [
'%type' => 'Notice',
'@message' => 'Undefined variable: bananas',
'%function' => 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()',
'%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
];
$error_warning = [
'%type' => 'Warning',
'@message' => 'Division by zero',
'%function' => 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()',
'%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
];
$error_user_notice = [
'%type' => 'User warning',
'@message' => 'Drupal & awesome',
'%function' => 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()',
'%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
];
// Set error reporting to display verbose notices.
$this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
$this->drupalGet('error-test/generate-warnings');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertErrorMessage($error_notice);
$this->assertErrorMessage($error_warning);
$this->assertErrorMessage($error_user_notice);
$this->assertRaw('<pre class="backtrace">', 'Found pre element with backtrace class.');
// Ensure we are escaping but not double escaping.
$this->assertRaw('&amp;');
$this->assertNoRaw('&amp;amp;');
// Set error reporting to display verbose notices.
$this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
// Set error reporting to collect notices.
$config->set('error_level', ERROR_REPORTING_DISPLAY_ALL)->save();
$this->drupalGet('error-test/generate-warnings');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertErrorMessage($error_notice);
$this->assertErrorMessage($error_warning);
$this->assertErrorMessage($error_user_notice);
$this->assertNoRaw('<pre class="backtrace">', 'Did not find pre element with backtrace class.');
// Set error reporting to not collect notices.
$config->set('error_level', ERROR_REPORTING_DISPLAY_SOME)->save();
$this->drupalGet('error-test/generate-warnings');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertNoErrorMessage($error_notice);
$this->assertErrorMessage($error_warning);
$this->assertErrorMessage($error_user_notice);
$this->assertNoRaw('<pre class="backtrace">', 'Did not find pre element with backtrace class.');
// Set error reporting to not show any errors.
$config->set('error_level', ERROR_REPORTING_HIDE)->save();
$this->drupalGet('error-test/generate-warnings');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertNoErrorMessage($error_notice);
$this->assertNoErrorMessage($error_warning);
$this->assertNoErrorMessage($error_user_notice);
$this->assertNoMessages();
$this->assertNoRaw('<pre class="backtrace">', 'Did not find pre element with backtrace class.');
}
/**
* Test the exception handler.
*/
public function testExceptionHandler() {
$error_exception = [
'%type' => 'Exception',
'@message' => 'Drupal & awesome',
'%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerException()',
'%line' => 56,
'%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
];
$error_pdo_exception = [
'%type' => 'DatabaseExceptionWrapper',
'@message' => 'SELECT * FROM bananas_are_awesome',
'%function' => 'Drupal\error_test\Controller\ErrorTestController->triggerPDOException()',
'%line' => 64,
'%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
];
$error_renderer_exception = [
'%type' => 'Exception',
'@message' => 'This is an exception that occurs during rendering',
'%function' => 'Drupal\error_test\Controller\ErrorTestController->Drupal\error_test\Controller\{closure}()',
'%line' => 82,
'%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
];
$this->drupalGet('error-test/trigger-exception');
$this->assertSession()->statusCodeEquals(500);
$this->assertErrorMessage($error_exception);
$this->drupalGet('error-test/trigger-pdo-exception');
$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));
$this->assertText($error_pdo_exception['@message'], format_string('Found @message in error page.', $error_pdo_exception));
$error_details = format_string('in %function (line ', $error_pdo_exception);
$this->assertRaw($error_details, format_string("Found '@message' in error page.", ['@message' => $error_details]));
$this->drupalGet('error-test/trigger-renderer-exception');
$this->assertSession()->statusCodeEquals(500);
$this->assertErrorMessage($error_renderer_exception);
// Disable error reporting, ensure that 5xx responses are not cached.
$this->config('system.logging')
->set('error_level', ERROR_REPORTING_HIDE)
->save();
$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->assertSession()->statusCodeEquals(500);
$this->assertNoErrorMessage($error_exception);
}
/**
* Helper function: assert that the error message is found.
*/
public function assertErrorMessage(array $error) {
$message = new FormattableMarkup('%type: @message in %function (line ', $error);
$this->assertRaw($message, format_string('Found error message: @message.', ['@message' => $message]));
}
/**
* Helper function: assert that the error message is not found.
*/
public function assertNoErrorMessage(array $error) {
$message = new FormattableMarkup('%type: @message in %function (line ', $error);
$this->assertNoRaw($message, format_string('Did not find error message: @message.', ['@message' => $message]));
}
/**
* Asserts that no messages are printed onto the page.
*
* @return bool
* TRUE, if there are no messages.
*/
protected function assertNoMessages() {
return $this->assertFalse($this->xpath('//div[contains(@class, "messages")]'), 'Ensures that also no messages div exists, which proves that no messages were generated by the error handler, not even an empty one.');
}
}

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

@ -0,0 +1,121 @@
<?php
namespace Drupal\Tests\system\FunctionalJavascript\Form;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests functionality of \Drupal\Core\Form\FormBuilderInterface::rebuildForm().
*
* @group Form
* @todo Add tests for other aspects of form rebuilding.
*/
class RebuildTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'form_test'];
/**
* A user for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
$this->webUser = $this->drupalCreateUser(['access content']);
$this->drupalLogin($this->webUser);
}
/**
* Tests that a form's action is retained after an Ajax submission.
*
* The 'action' attribute of a form should not change after an Ajax submission
* 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([
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'text',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
])->save();
FieldConfig::create([
'field_name' => $field_name,
'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.
$this->webUser = $this->drupalCreateUser(['create page content']);
$this->drupalLogin($this->webUser);
// Get the form for adding a 'page' node. Submit an "add another item" Ajax
// 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');
$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 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 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->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->assertEquals(1, count($forms));
// Strip query params off the action before asserting.
$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';
}