composer update
This commit is contained in:
parent
f6abc3dce2
commit
71dfaca858
1753 changed files with 45274 additions and 14619 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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],
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
|
@ -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.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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('&');
|
||||
$this->assertNoRaw('&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.');
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.');
|
||||
}
|
||||
|
||||
}
|
|
@ -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.');
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
||||
|
|
Reference in a new issue