From 47d067a16be3b84b6e781a16ba94c30f365beb9c Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Sun, 18 Nov 2018 23:56:22 +0000 Subject: [PATCH] Issue #2900291 by martin107, Lendude, RytoEX, vijaycs85, borisson_, jibran, dawehner: Form: Convert system functional tests to phpunit Part 2 --- .../src/Tests/Form/TriggeringElementTest.php | 97 --------------- .../Form/ElementsTableSelectTest.php | 80 +++++-------- .../tests/src/Functional/Form/RebuildTest.php | 61 ++++++++++ .../src/Functional}/Form/StorageTest.php | 79 +++++++------ .../Form/ElementsTableSelectTest.php | 58 +++++++++ .../Form/RebuildTest.php | 80 +++++++------ .../Form/TriggeringElementTest.php | 111 ++++++++++++++++++ 7 files changed, 344 insertions(+), 222 deletions(-) delete mode 100644 core/modules/system/src/Tests/Form/TriggeringElementTest.php rename core/modules/system/{src/Tests => tests/src/Functional}/Form/ElementsTableSelectTest.php (65%) create mode 100644 core/modules/system/tests/src/Functional/Form/RebuildTest.php rename core/modules/system/{src/Tests => tests/src/Functional}/Form/StorageTest.php (64%) create mode 100644 core/modules/system/tests/src/FunctionalJavascript/Form/ElementsTableSelectTest.php rename core/modules/system/{src/Tests => tests/src/FunctionalJavascript}/Form/RebuildTest.php (53%) create mode 100644 core/modules/system/tests/src/FunctionalJavascript/Form/TriggeringElementTest.php diff --git a/core/modules/system/src/Tests/Form/TriggeringElementTest.php b/core/modules/system/src/Tests/Form/TriggeringElementTest.php deleted file mode 100644 index fb59cb5e8ac..00000000000 --- a/core/modules/system/src/Tests/Form/TriggeringElementTest.php +++ /dev/null @@ -1,97 +0,0 @@ -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.'); - } - -} diff --git a/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php b/core/modules/system/tests/src/Functional/Form/ElementsTableSelectTest.php similarity index 65% rename from core/modules/system/src/Tests/Form/ElementsTableSelectTest.php rename to core/modules/system/tests/src/Functional/Form/ElementsTableSelectTest.php index 030526eef9f..f377c0abf60 100644 --- a/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php +++ b/core/modules/system/tests/src/Functional/Form/ElementsTableSelectTest.php @@ -1,17 +1,16 @@ 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"]')); } /** diff --git a/core/modules/system/tests/src/Functional/Form/RebuildTest.php b/core/modules/system/tests/src/Functional/Form/RebuildTest.php new file mode 100644 index 00000000000..3e951e2e802 --- /dev/null +++ b/core/modules/system/tests/src/Functional/Form/RebuildTest.php @@ -0,0 +1,61 @@ +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'); + } + +} diff --git a/core/modules/system/src/Tests/Form/StorageTest.php b/core/modules/system/tests/src/Functional/Form/StorageTest.php similarity index 64% rename from core/modules/system/src/Tests/Form/StorageTest.php rename to core/modules/system/tests/src/Functional/Form/StorageTest.php index 12a797d6d7d..95969fde5a2 100644 --- a/core/modules/system/src/Tests/Form/StorageTest.php +++ b/core/modules/system/tests/src/Functional/Form/StorageTest.php @@ -1,9 +1,10 @@ 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'); } } diff --git a/core/modules/system/tests/src/FunctionalJavascript/Form/ElementsTableSelectTest.php b/core/modules/system/tests/src/FunctionalJavascript/Form/ElementsTableSelectTest.php new file mode 100644 index 00000000000..b565bfd7438 --- /dev/null +++ b/core/modules/system/tests/src/FunctionalJavascript/Form/ElementsTableSelectTest.php @@ -0,0 +1,58 @@ +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); + } + } + } + +} diff --git a/core/modules/system/src/Tests/Form/RebuildTest.php b/core/modules/system/tests/src/FunctionalJavascript/Form/RebuildTest.php similarity index 53% rename from core/modules/system/src/Tests/Form/RebuildTest.php rename to core/modules/system/tests/src/FunctionalJavascript/Form/RebuildTest.php index 2e138bdfb32..caf52094d57 100644 --- a/core/modules/system/src/Tests/Form/RebuildTest.php +++ b/core/modules/system/tests/src/FunctionalJavascript/Form/RebuildTest.php @@ -1,12 +1,12 @@ 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); } } diff --git a/core/modules/system/tests/src/FunctionalJavascript/Form/TriggeringElementTest.php b/core/modules/system/tests/src/FunctionalJavascript/Form/TriggeringElementTest.php new file mode 100644 index 00000000000..41733ec46e3 --- /dev/null +++ b/core/modules/system/tests/src/FunctionalJavascript/Form/TriggeringElementTest.php @@ -0,0 +1,111 @@ +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.'); + } + +}