Issue #2900291 by martin107, Lendude, RytoEX, vijaycs85, borisson_, jibran, dawehner: Form: Convert system functional tests to phpunit Part 2

8.7.x
Alex Pott 2018-11-18 23:56:22 +00:00
parent 9df150e1ab
commit 47d067a16b
No known key found for this signature in database
GPG Key ID: 31905460D4A69276
7 changed files with 344 additions and 222 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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