cms/drupal/modules/simpletest/tests/form.test
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Unit tests for the Drupal Form API.
       
     6  */
       
     7 
       
     8 class FormsTestCase extends DrupalWebTestCase {
       
     9 
       
    10   public static function getInfo() {
       
    11     return array(
       
    12       'name' => 'Form element validation',
       
    13       'description' => 'Tests various form element validation mechanisms.',
       
    14       'group' => 'Form API',
       
    15     );
       
    16   }
       
    17 
       
    18   function setUp() {
       
    19     parent::setUp('form_test');
       
    20   }
       
    21 
       
    22   /**
       
    23    * Check several empty values for required forms elements.
       
    24    *
       
    25    * Carriage returns, tabs, spaces, and unchecked checkbox elements are not
       
    26    * valid content for a required field.
       
    27    *
       
    28    * If the form field is found in form_get_errors() then the test pass.
       
    29    */
       
    30   function testRequiredFields() {
       
    31     // Originates from http://drupal.org/node/117748
       
    32     // Sets of empty strings and arrays.
       
    33     $empty_strings = array('""' => "", '"\n"' => "\n", '" "' => " ", '"\t"' => "\t", '" \n\t "' => " \n\t ", '"\n\n\n\n\n"' => "\n\n\n\n\n");
       
    34     $empty_arrays = array('array()' => array());
       
    35     $empty_checkbox = array(NULL);
       
    36 
       
    37     $elements['textfield']['element'] = array('#title' => $this->randomName(), '#type' => 'textfield');
       
    38     $elements['textfield']['empty_values'] = $empty_strings;
       
    39 
       
    40     $elements['password']['element'] = array('#title' => $this->randomName(), '#type' => 'password');
       
    41     $elements['password']['empty_values'] = $empty_strings;
       
    42 
       
    43     $elements['password_confirm']['element'] = array('#title' => $this->randomName(), '#type' => 'password_confirm');
       
    44     // Provide empty values for both password fields.
       
    45     foreach ($empty_strings as $key => $value) {
       
    46       $elements['password_confirm']['empty_values'][$key] = array('pass1' => $value, 'pass2' => $value);
       
    47     }
       
    48 
       
    49     $elements['textarea']['element'] = array('#title' => $this->randomName(), '#type' => 'textarea');
       
    50     $elements['textarea']['empty_values'] = $empty_strings;
       
    51 
       
    52     $elements['radios']['element'] = array('#title' => $this->randomName(), '#type' => 'radios', '#options' => array('' => t('None'), $this->randomName(), $this->randomName(), $this->randomName()));
       
    53     $elements['radios']['empty_values'] = $empty_arrays;
       
    54 
       
    55     $elements['checkbox']['element'] = array('#title' => $this->randomName(), '#type' => 'checkbox', '#required' => TRUE);
       
    56     $elements['checkbox']['empty_values'] = $empty_checkbox;
       
    57 
       
    58     $elements['checkboxes']['element'] = array('#title' => $this->randomName(), '#type' => 'checkboxes', '#options' => array($this->randomName(), $this->randomName(), $this->randomName()));
       
    59     $elements['checkboxes']['empty_values'] = $empty_arrays;
       
    60 
       
    61     $elements['select']['element'] = array('#title' => $this->randomName(), '#type' => 'select', '#options' => array('' => t('None'), $this->randomName(), $this->randomName(), $this->randomName()));
       
    62     $elements['select']['empty_values'] = $empty_strings;
       
    63 
       
    64     $elements['file']['element'] = array('#title' => $this->randomName(), '#type' => 'file');
       
    65     $elements['file']['empty_values'] = $empty_strings;
       
    66 
       
    67     // Regular expression to find the expected marker on required elements.
       
    68     $required_marker_preg = '@<label.*<span class="form-required" title="This field is required\.">\*</span></label>@';
       
    69 
       
    70     // Go through all the elements and all the empty values for them.
       
    71     foreach ($elements as $type => $data) {
       
    72       foreach ($data['empty_values'] as $key => $empty) {
       
    73         foreach (array(TRUE, FALSE) as $required) {
       
    74           $form_id = $this->randomName();
       
    75           $form = array();
       
    76           $form_state = form_state_defaults();
       
    77           form_clear_error();
       
    78           $form['op'] = array('#type' => 'submit', '#value' => t('Submit'));
       
    79           $element = $data['element']['#title'];
       
    80           $form[$element] = $data['element'];
       
    81           $form[$element]['#required'] = $required;
       
    82           $form_state['input'][$element] = $empty;
       
    83           $form_state['input']['form_id'] = $form_id;
       
    84           $form_state['method'] = 'post';
       
    85 
       
    86           // The form token CSRF protection should not interfere with this test,
       
    87           // so we bypass it by marking this test form as programmed.
       
    88           $form_state['programmed'] = TRUE;
       
    89           drupal_prepare_form($form_id, $form, $form_state);
       
    90           drupal_process_form($form_id, $form, $form_state);
       
    91           $errors = form_get_errors();
       
    92           // Form elements of type 'radios' throw all sorts of PHP notices
       
    93           // when you try to render them like this, so we ignore those for
       
    94           // testing the required marker.
       
    95           // @todo Fix this work-around (http://drupal.org/node/588438).
       
    96           $form_output = ($type == 'radios') ? '' : drupal_render($form);
       
    97           if ($required) {
       
    98             // Make sure we have a form error for this element.
       
    99             $this->assertTrue(isset($errors[$element]), "Check empty($key) '$type' field '$element'");
       
   100             if (!empty($form_output)) {
       
   101               // Make sure the form element is marked as required.
       
   102               $this->assertTrue(preg_match($required_marker_preg, $form_output), "Required '$type' field is marked as required");
       
   103             }
       
   104           }
       
   105           else {
       
   106             if (!empty($form_output)) {
       
   107               // Make sure the form element is *not* marked as required.
       
   108               $this->assertFalse(preg_match($required_marker_preg, $form_output), "Optional '$type' field is not marked as required");
       
   109             }
       
   110             if ($type == 'select') {
       
   111               // Select elements are going to have validation errors with empty
       
   112               // input, since those are illegal choices. Just make sure the
       
   113               // error is not "field is required".
       
   114               $this->assertTrue((empty($errors[$element]) || strpos('field is required', $errors[$element]) === FALSE), "Optional '$type' field '$element' is not treated as a required element");
       
   115             }
       
   116             else {
       
   117               // Make sure there is *no* form error for this element.
       
   118               $this->assertTrue(empty($errors[$element]), "Optional '$type' field '$element' has no errors with empty input");
       
   119             }
       
   120           }
       
   121         }
       
   122       }
       
   123     }
       
   124     // Clear the expected form error messages so they don't appear as exceptions.
       
   125     drupal_get_messages();
       
   126   }
       
   127 
       
   128   /**
       
   129    * Tests validation for required checkbox, select, and radio elements.
       
   130    *
       
   131    * Submits a test form containing several types of form elements. The form
       
   132    * is submitted twice, first without values for required fields and then
       
   133    * with values. Each submission is checked for relevant error messages.
       
   134    *
       
   135    * @see form_test_validate_required_form()
       
   136    */
       
   137   function testRequiredCheckboxesRadio() {
       
   138     $form = $form_state = array();
       
   139     $form = form_test_validate_required_form($form, $form_state);
       
   140 
       
   141     // Attempt to submit the form with no required fields set.
       
   142     $edit = array();
       
   143     $this->drupalPost('form-test/validate-required', $edit, 'Submit');
       
   144 
       
   145     // The only error messages that should appear are the relevant 'required'
       
   146     // messages for each field.
       
   147     $expected = array();
       
   148     foreach (array('textfield', 'checkboxes', 'select', 'radios') as $key) {
       
   149       $expected[] = t('!name field is required.', array('!name' => $form[$key]['#title']));
       
   150     }
       
   151 
       
   152     // Check the page for error messages.
       
   153     $errors = $this->xpath('//div[contains(@class, "error")]//li');
       
   154     foreach ($errors as $error) {
       
   155       $expected_key = array_search($error[0], $expected);
       
   156       // If the error message is not one of the expected messages, fail.
       
   157       if ($expected_key === FALSE) {
       
   158         $this->fail(format_string("Unexpected error message: @error", array('@error' => $error[0])));
       
   159       }
       
   160       // Remove the expected message from the list once it is found.
       
   161       else {
       
   162         unset($expected[$expected_key]);
       
   163       }
       
   164     }
       
   165 
       
   166     // Fail if any expected messages were not found.
       
   167     foreach ($expected as $not_found) {
       
   168       $this->fail(format_string("Found error message: @error", array('@error' => $not_found)));
       
   169     }
       
   170 
       
   171     // Verify that input elements are still empty.
       
   172     $this->assertFieldByName('textfield', '');
       
   173     $this->assertNoFieldChecked('edit-checkboxes-foo');
       
   174     $this->assertNoFieldChecked('edit-checkboxes-bar');
       
   175     $this->assertOptionSelected('edit-select', '');
       
   176     $this->assertNoFieldChecked('edit-radios-foo');
       
   177     $this->assertNoFieldChecked('edit-radios-bar');
       
   178     $this->assertNoFieldChecked('edit-radios-optional-foo');
       
   179     $this->assertNoFieldChecked('edit-radios-optional-bar');
       
   180     $this->assertNoFieldChecked('edit-radios-optional-default-value-false-foo');
       
   181     $this->assertNoFieldChecked('edit-radios-optional-default-value-false-bar');
       
   182 
       
   183     // Submit again with required fields set and verify that there are no
       
   184     // error messages.
       
   185     $edit = array(
       
   186       'textfield' => $this->randomString(),
       
   187       'checkboxes[foo]' => TRUE,
       
   188       'select' => 'foo',
       
   189       'radios' => 'bar',
       
   190     );
       
   191     $this->drupalPost(NULL, $edit, 'Submit');
       
   192     $this->assertNoFieldByXpath('//div[contains(@class, "error")]', FALSE, 'No error message is displayed when all required fields are filled.');
       
   193     $this->assertRaw("The form_test_validate_required_form form was submitted successfully.", 'Validation form submitted successfully.');
       
   194   }
       
   195 
       
   196   /**
       
   197    * Tests validation for required textfield element without title.
       
   198    *
       
   199    * Submits a test form containing a textfield form elements without title.
       
   200    * The form is submitted twice, first without value for the required field
       
   201    * and then with value. Each submission is checked for relevant error
       
   202    * messages.
       
   203    *
       
   204    * @see form_test_validate_required_form_no_title()
       
   205    */
       
   206   function testRequiredTextfieldNoTitle() {
       
   207     $form = $form_state = array();
       
   208     $form = form_test_validate_required_form_no_title($form, $form_state);
       
   209 
       
   210     // Attempt to submit the form with no required field set.
       
   211     $edit = array();
       
   212     $this->drupalPost('form-test/validate-required-no-title', $edit, 'Submit');
       
   213     $this->assertNoRaw("The form_test_validate_required_form_no_title form was submitted successfully.", 'Validation form submitted successfully.');
       
   214 
       
   215     // Check the page for the error class on the textfield.
       
   216     $this->assertFieldByXPath('//input[contains(@class, "error")]', FALSE, 'Error input form element class found.');
       
   217 
       
   218     // Submit again with required fields set and verify that there are no
       
   219     // error messages.
       
   220     $edit = array(
       
   221       'textfield' => $this->randomString(),
       
   222     );
       
   223     $this->drupalPost(NULL, $edit, 'Submit');
       
   224     $this->assertNoFieldByXpath('//input[contains(@class, "error")]', FALSE, 'No error input form element class found.');
       
   225     $this->assertRaw("The form_test_validate_required_form_no_title form was submitted successfully.", 'Validation form submitted successfully.');
       
   226   }
       
   227 
       
   228   /**
       
   229    * Test default value handling for checkboxes.
       
   230    *
       
   231    * @see _form_test_checkbox()
       
   232    */
       
   233   function testCheckboxProcessing() {
       
   234     // First, try to submit without the required checkbox.
       
   235     $edit = array();
       
   236     $this->drupalPost('form-test/checkbox', $edit, t('Submit'));
       
   237     $this->assertRaw(t('!name field is required.', array('!name' => 'required_checkbox')), 'A required checkbox is actually mandatory');
       
   238 
       
   239     // Now try to submit the form correctly.
       
   240     $values = drupal_json_decode($this->drupalPost(NULL, array('required_checkbox' => 1), t('Submit')));
       
   241     $expected_values = array(
       
   242       'disabled_checkbox_on' => 'disabled_checkbox_on',
       
   243       'disabled_checkbox_off' => '',
       
   244       'checkbox_on' => 'checkbox_on',
       
   245       'checkbox_off' => '',
       
   246       'zero_checkbox_on' => '0',
       
   247       'zero_checkbox_off' => '',
       
   248     );
       
   249     foreach ($expected_values as $widget => $expected_value) {
       
   250       $this->assertEqual($values[$widget], $expected_value, format_string('Checkbox %widget returns expected value (expected: %expected, got: %value)', array(
       
   251         '%widget' => var_export($widget, TRUE),
       
   252         '%expected' => var_export($expected_value, TRUE),
       
   253         '%value' => var_export($values[$widget], TRUE),
       
   254       )));
       
   255     }
       
   256   }
       
   257 
       
   258   /**
       
   259    * Tests validation of #type 'select' elements.
       
   260    */
       
   261   function testSelect() {
       
   262     $form = $form_state = array();
       
   263     $form = form_test_select($form, $form_state);
       
   264     $error = '!name field is required.';
       
   265     $this->drupalGet('form-test/select');
       
   266 
       
   267     // Posting without any values should throw validation errors.
       
   268     $this->drupalPost(NULL, array(), 'Submit');
       
   269     $this->assertNoText(t($error, array('!name' => $form['select']['#title'])));
       
   270     $this->assertNoText(t($error, array('!name' => $form['select_required']['#title'])));
       
   271     $this->assertNoText(t($error, array('!name' => $form['select_optional']['#title'])));
       
   272     $this->assertNoText(t($error, array('!name' => $form['empty_value']['#title'])));
       
   273     $this->assertNoText(t($error, array('!name' => $form['empty_value_one']['#title'])));
       
   274     $this->assertText(t($error, array('!name' => $form['no_default']['#title'])));
       
   275     $this->assertNoText(t($error, array('!name' => $form['no_default_optional']['#title'])));
       
   276     $this->assertText(t($error, array('!name' => $form['no_default_empty_option']['#title'])));
       
   277     $this->assertNoText(t($error, array('!name' => $form['no_default_empty_option_optional']['#title'])));
       
   278     $this->assertText(t($error, array('!name' => $form['no_default_empty_value']['#title'])));
       
   279     $this->assertText(t($error, array('!name' => $form['no_default_empty_value_one']['#title'])));
       
   280     $this->assertNoText(t($error, array('!name' => $form['no_default_empty_value_optional']['#title'])));
       
   281     $this->assertNoText(t($error, array('!name' => $form['multiple']['#title'])));
       
   282     $this->assertNoText(t($error, array('!name' => $form['multiple_no_default']['#title'])));
       
   283     $this->assertText(t($error, array('!name' => $form['multiple_no_default_required']['#title'])));
       
   284 
       
   285     // Post values for required fields.
       
   286     $edit = array(
       
   287       'no_default' => 'three',
       
   288       'no_default_empty_option' => 'three',
       
   289       'no_default_empty_value' => 'three',
       
   290       'no_default_empty_value_one' => 'three',
       
   291       'multiple_no_default_required[]' => 'three',
       
   292     );
       
   293     $this->drupalPost(NULL, $edit, 'Submit');
       
   294     $values = drupal_json_decode($this->drupalGetContent());
       
   295 
       
   296     // Verify expected values.
       
   297     $expected = array(
       
   298       'select' => 'one',
       
   299       'empty_value' => 'one',
       
   300       'empty_value_one' => 'one',
       
   301       'no_default' => 'three',
       
   302       'no_default_optional' => 'one',
       
   303       'no_default_optional_empty_value' => '',
       
   304       'no_default_empty_option' => 'three',
       
   305       'no_default_empty_option_optional' => '',
       
   306       'no_default_empty_value' => 'three',
       
   307       'no_default_empty_value_one' => 'three',
       
   308       'no_default_empty_value_optional' => 0,
       
   309       'multiple' => array('two' => 'two'),
       
   310       'multiple_no_default' => array(),
       
   311       'multiple_no_default_required' => array('three' => 'three'),
       
   312     );
       
   313     foreach ($expected as $key => $value) {
       
   314       $this->assertIdentical($values[$key], $value, format_string('@name: @actual is equal to @expected.', array(
       
   315         '@name' => $key,
       
   316         '@actual' => var_export($values[$key], TRUE),
       
   317         '@expected' => var_export($value, TRUE),
       
   318       )));
       
   319     }
       
   320   }
       
   321 
       
   322   /**
       
   323    * Test handling of disabled elements.
       
   324    *
       
   325    * @see _form_test_disabled_elements()
       
   326    */
       
   327   function testDisabledElements() {
       
   328     // Get the raw form in its original state.
       
   329     $form_state = array();
       
   330     $form = _form_test_disabled_elements(array(), $form_state);
       
   331 
       
   332     // Build a submission that tries to hijack the form by submitting input for
       
   333     // elements that are disabled.
       
   334     $edit = array();
       
   335     foreach (element_children($form) as $key) {
       
   336       if (isset($form[$key]['#test_hijack_value'])) {
       
   337         if (is_array($form[$key]['#test_hijack_value'])) {
       
   338           foreach ($form[$key]['#test_hijack_value'] as $subkey => $value) {
       
   339             $edit[$key . '[' . $subkey . ']'] = $value;
       
   340           }
       
   341         }
       
   342         else {
       
   343           $edit[$key] = $form[$key]['#test_hijack_value'];
       
   344         }
       
   345       }
       
   346     }
       
   347 
       
   348     // Submit the form with no input, as the browser does for disabled elements,
       
   349     // and fetch the $form_state['values'] that is passed to the submit handler.
       
   350     $this->drupalPost('form-test/disabled-elements', array(), t('Submit'));
       
   351     $returned_values['normal'] = drupal_json_decode($this->content);
       
   352 
       
   353     // Do the same with input, as could happen if JavaScript un-disables an
       
   354     // element. drupalPost() emulates a browser by not submitting input for
       
   355     // disabled elements, so we need to un-disable those elements first.
       
   356     $this->drupalGet('form-test/disabled-elements');
       
   357     $disabled_elements = array();
       
   358     foreach ($this->xpath('//*[@disabled]') as $element) {
       
   359       $disabled_elements[] = (string) $element['name'];
       
   360       unset($element['disabled']);
       
   361     }
       
   362 
       
   363     // All the elements should be marked as disabled, including the ones below
       
   364     // the disabled container.
       
   365     $this->assertEqual(count($disabled_elements), 32, 'The correct elements have the disabled property in the HTML code.');
       
   366 
       
   367     $this->drupalPost(NULL, $edit, t('Submit'));
       
   368     $returned_values['hijacked'] = drupal_json_decode($this->content);
       
   369 
       
   370     // Ensure that the returned values match the form's default values in both
       
   371     // cases.
       
   372     foreach ($returned_values as $type => $values) {
       
   373       $this->assertFormValuesDefault($values, $form);
       
   374     }
       
   375   }
       
   376 
       
   377   /**
       
   378    * Assert that the values submitted to a form matches the default values of the elements.
       
   379    */
       
   380   function assertFormValuesDefault($values, $form) {
       
   381     foreach (element_children($form) as $key) {
       
   382       if (isset($form[$key]['#default_value'])) {
       
   383         if (isset($form[$key]['#expected_value'])) {
       
   384           $expected_value = $form[$key]['#expected_value'];
       
   385         }
       
   386         else {
       
   387           $expected_value = $form[$key]['#default_value'];
       
   388         }
       
   389 
       
   390         if ($key == 'checkboxes_multiple') {
       
   391           // Checkboxes values are not filtered out.
       
   392           $values[$key] = array_filter($values[$key]);
       
   393         }
       
   394         $this->assertIdentical($expected_value, $values[$key], format_string('Default value for %type: expected %expected, returned %returned.', array('%type' => $key, '%expected' => var_export($expected_value, TRUE), '%returned' => var_export($values[$key], TRUE))));
       
   395       }
       
   396 
       
   397       // Recurse children.
       
   398       $this->assertFormValuesDefault($values, $form[$key]);
       
   399     }
       
   400   }
       
   401 
       
   402   /**
       
   403    * Verify markup for disabled form elements.
       
   404    *
       
   405    * @see _form_test_disabled_elements()
       
   406    */
       
   407   function testDisabledMarkup() {
       
   408     $this->drupalGet('form-test/disabled-elements');
       
   409     $form_state = array();
       
   410     $form = _form_test_disabled_elements(array(), $form_state);
       
   411     $type_map = array(
       
   412       'textarea' => 'textarea',
       
   413       'select' => 'select',
       
   414       'weight' => 'select',
       
   415       'date' => 'select',
       
   416     );
       
   417 
       
   418     foreach ($form as $name => $item) {
       
   419       // Skip special #types.
       
   420       if (!isset($item['#type']) || in_array($item['#type'], array('hidden', 'text_format'))) {
       
   421         continue;
       
   422       }
       
   423       // Setup XPath and CSS class depending on #type.
       
   424       if (in_array($item['#type'], array('image_button', 'button', 'submit'))) {
       
   425         $path = "//!type[contains(@class, :div-class) and @value=:value]";
       
   426         $class = 'form-button-disabled';
       
   427       }
       
   428       else {
       
   429         // starts-with() required for checkboxes.
       
   430         $path = "//div[contains(@class, :div-class)]/descendant::!type[starts-with(@name, :name)]";
       
   431         $class = 'form-disabled';
       
   432       }
       
   433       // Replace DOM element name in $path according to #type.
       
   434       $type = 'input';
       
   435       if (isset($type_map[$item['#type']])) {
       
   436         $type = $type_map[$item['#type']];
       
   437       }
       
   438       $path = strtr($path, array('!type' => $type));
       
   439       // Verify that the element exists.
       
   440       $element = $this->xpath($path, array(
       
   441         ':name' => check_plain($name),
       
   442         ':div-class' => $class,
       
   443         ':value' => isset($item['#value']) ? $item['#value'] : '',
       
   444       ));
       
   445       $this->assertTrue(isset($element[0]), format_string('Disabled form element class found for #type %type.', array('%type' => $item['#type'])));
       
   446     }
       
   447 
       
   448     // Verify special element #type text-format.
       
   449     $element = $this->xpath('//div[contains(@class, :div-class)]/descendant::textarea[@name=:name]', array(
       
   450       ':name' => 'text_format[value]',
       
   451       ':div-class' => 'form-disabled',
       
   452     ));
       
   453     $this->assertTrue(isset($element[0]), format_string('Disabled form element class found for #type %type.', array('%type' => 'text_format[value]')));
       
   454     $element = $this->xpath('//div[contains(@class, :div-class)]/descendant::select[@name=:name]', array(
       
   455       ':name' => 'text_format[format]',
       
   456       ':div-class' => 'form-disabled',
       
   457     ));
       
   458     $this->assertTrue(isset($element[0]), format_string('Disabled form element class found for #type %type.', array('%type' => 'text_format[format]')));
       
   459   }
       
   460 
       
   461   /**
       
   462    * Test Form API protections against input forgery.
       
   463    *
       
   464    * @see _form_test_input_forgery()
       
   465    */
       
   466   function testInputForgery() {
       
   467     $this->drupalGet('form-test/input-forgery');
       
   468     $checkbox = $this->xpath('//input[@name="checkboxes[two]"]');
       
   469     $checkbox[0]['value'] = 'FORGERY';
       
   470     $this->drupalPost(NULL, array('checkboxes[one]' => TRUE, 'checkboxes[two]' => TRUE), t('Submit'));
       
   471     $this->assertText('An illegal choice has been detected.', 'Input forgery was detected.');
       
   472   }
       
   473 
       
   474   /**
       
   475    * Tests that submitted values are converted to scalar strings for textfields.
       
   476    */
       
   477   public function testTextfieldStringValue() {
       
   478     // Check multivalued submissions.
       
   479     $multivalue = array('evil' => 'multivalue', 'not so' => 'good');
       
   480     $this->checkFormValue('textfield', $multivalue, '');
       
   481     $this->checkFormValue('password', $multivalue, '');
       
   482     $this->checkFormValue('textarea', $multivalue, '');
       
   483     $this->checkFormValue('machine_name', $multivalue, '');
       
   484     $this->checkFormValue('password_confirm', $multivalue, array('pass1' => '', 'pass2' => ''));
       
   485     // Check integer submissions.
       
   486     $integer = 5;
       
   487     $string = '5';
       
   488     $this->checkFormValue('textfield', $integer, $string);
       
   489     $this->checkFormValue('password', $integer, $string);
       
   490     $this->checkFormValue('textarea', $integer, $string);
       
   491     $this->checkFormValue('machine_name', $integer, $string);
       
   492     $this->checkFormValue('password_confirm', array('pass1' => $integer, 'pass2' => $integer), array('pass1' => $string, 'pass2' => $string));
       
   493     // Check that invalid array keys are ignored for password confirm elements.
       
   494     $this->checkFormValue('password_confirm', array('pass1' => 'test', 'pass2' => 'test', 'extra' => 'invalid'), array('pass1' => 'test', 'pass2' => 'test'));
       
   495   }
       
   496 
       
   497   /**
       
   498    * Checks that a given form input value is sanitized to the expected result.
       
   499    *
       
   500    * @param string $element_type
       
   501    *   The form element type. Example: textfield.
       
   502    * @param mixed $input_value
       
   503    *   The submitted user input value for the form element.
       
   504    * @param mixed $expected_value
       
   505    *   The sanitized result value in the form state after calling
       
   506    *   form_builder().
       
   507    */
       
   508   protected function checkFormValue($element_type, $input_value, $expected_value) {
       
   509     $form_id = $this->randomName();
       
   510     $form = array();
       
   511     $form_state = form_state_defaults();
       
   512     $form['op'] = array('#type' => 'submit', '#value' => t('Submit'));
       
   513     $form[$element_type] = array(
       
   514       '#type' => $element_type,
       
   515       '#title' => 'test',
       
   516     );
       
   517 
       
   518     $form_state['input'][$element_type] = $input_value;
       
   519     $form_state['input']['form_id'] = $form_id;
       
   520     $form_state['method'] = 'post';
       
   521     $form_state['values'] = array();
       
   522     drupal_prepare_form($form_id, $form, $form_state);
       
   523 
       
   524     // This is the main function we want to test: it is responsible for
       
   525     // populating user supplied $form_state['input'] to sanitized
       
   526     // $form_state['values'].
       
   527     form_builder($form_id, $form, $form_state);
       
   528 
       
   529     $this->assertIdentical($form_state['values'][$element_type], $expected_value, format_string('Form submission for the "@element_type" element type has been correctly sanitized.', array('@element_type' => $element_type)));
       
   530   }
       
   531 }
       
   532 
       
   533 /**
       
   534  * Tests building and processing of core form elements.
       
   535  */
       
   536 class FormElementTestCase extends DrupalWebTestCase {
       
   537   protected $profile = 'testing';
       
   538 
       
   539   public static function getInfo() {
       
   540     return array(
       
   541       'name' => 'Element processing',
       
   542       'description' => 'Tests building and processing of core form elements.',
       
   543       'group' => 'Form API',
       
   544     );
       
   545   }
       
   546 
       
   547   function setUp() {
       
   548     parent::setUp(array('form_test'));
       
   549   }
       
   550 
       
   551   /**
       
   552    * Tests expansion of #options for #type checkboxes and radios.
       
   553    */
       
   554   function testOptions() {
       
   555     $this->drupalGet('form-test/checkboxes-radios');
       
   556 
       
   557     // Verify that all options appear in their defined order.
       
   558     foreach (array('checkbox', 'radio') as $type) {
       
   559       $elements = $this->xpath('//input[@type=:type]', array(':type' => $type));
       
   560       $expected_values = array('0', 'foo', '1', 'bar', '>');
       
   561       foreach ($elements as $element) {
       
   562         $expected = array_shift($expected_values);
       
   563         $this->assertIdentical((string) $element['value'], $expected);
       
   564       }
       
   565     }
       
   566 
       
   567     // Enable customized option sub-elements.
       
   568     $this->drupalGet('form-test/checkboxes-radios/customize');
       
   569 
       
   570     // Verify that all options appear in their defined order, taking a custom
       
   571     // #weight into account.
       
   572     foreach (array('checkbox', 'radio') as $type) {
       
   573       $elements = $this->xpath('//input[@type=:type]', array(':type' => $type));
       
   574       $expected_values = array('0', 'foo', 'bar', '>', '1');
       
   575       foreach ($elements as $element) {
       
   576         $expected = array_shift($expected_values);
       
   577         $this->assertIdentical((string) $element['value'], $expected);
       
   578       }
       
   579     }
       
   580     // Verify that custom #description properties are output.
       
   581     foreach (array('checkboxes', 'radios') as $type) {
       
   582       $elements = $this->xpath('//input[@id=:id]/following-sibling::div[@class=:class]', array(
       
   583         ':id' => 'edit-' . $type . '-foo',
       
   584         ':class' => 'description',
       
   585       ));
       
   586       $this->assertTrue(count($elements), format_string('Custom %type option description found.', array(
       
   587         '%type' => $type,
       
   588       )));
       
   589     }
       
   590   }
       
   591 }
       
   592 
       
   593 /**
       
   594  * Test form alter hooks.
       
   595  */
       
   596 class FormAlterTestCase extends DrupalWebTestCase {
       
   597   public static function getInfo() {
       
   598     return array(
       
   599       'name' => 'Form alter hooks',
       
   600       'description' => 'Tests hook_form_alter() and hook_form_FORM_ID_alter().',
       
   601       'group' => 'Form API',
       
   602     );
       
   603   }
       
   604 
       
   605   function setUp() {
       
   606     parent::setUp('form_test');
       
   607   }
       
   608 
       
   609   /**
       
   610    * Tests execution order of hook_form_alter() and hook_form_FORM_ID_alter().
       
   611    */
       
   612   function testExecutionOrder() {
       
   613     $this->drupalGet('form-test/alter');
       
   614     // Ensure that the order is first by module, then for a given module, the
       
   615     // id-specific one after the generic one.
       
   616     $expected = array(
       
   617       'block_form_form_test_alter_form_alter() executed.',
       
   618       'form_test_form_alter() executed.',
       
   619       'form_test_form_form_test_alter_form_alter() executed.',
       
   620       'system_form_form_test_alter_form_alter() executed.',
       
   621     );
       
   622     $content = preg_replace('/\s+/', ' ', filter_xss($this->content, array()));
       
   623     $this->assert(strpos($content, implode(' ', $expected)) !== FALSE, 'Form alter hooks executed in the expected order.');
       
   624   }
       
   625 }
       
   626 
       
   627 /**
       
   628  * Test form validation handlers.
       
   629  */
       
   630 class FormValidationTestCase extends DrupalWebTestCase {
       
   631   public static function getInfo() {
       
   632     return array(
       
   633       'name' => 'Form validation handlers',
       
   634       'description' => 'Tests form processing and alteration via form validation handlers.',
       
   635       'group' => 'Form API',
       
   636     );
       
   637   }
       
   638 
       
   639   function setUp() {
       
   640     parent::setUp('form_test');
       
   641   }
       
   642 
       
   643   /**
       
   644    * Tests form alterations by #element_validate, #validate, and form_set_value().
       
   645    */
       
   646   function testValidate() {
       
   647     $this->drupalGet('form-test/validate');
       
   648     // Verify that #element_validate handlers can alter the form and submitted
       
   649     // form values.
       
   650     $edit = array(
       
   651       'name' => 'element_validate',
       
   652     );
       
   653     $this->drupalPost(NULL, $edit, 'Save');
       
   654     $this->assertFieldByName('name', '#value changed by #element_validate', 'Form element #value was altered.');
       
   655     $this->assertText('Name value: value changed by form_set_value() in #element_validate', 'Form element value in $form_state was altered.');
       
   656 
       
   657     // Verify that #validate handlers can alter the form and submitted
       
   658     // form values.
       
   659     $edit = array(
       
   660       'name' => 'validate',
       
   661     );
       
   662     $this->drupalPost(NULL, $edit, 'Save');
       
   663     $this->assertFieldByName('name', '#value changed by #validate', 'Form element #value was altered.');
       
   664     $this->assertText('Name value: value changed by form_set_value() in #validate', 'Form element value in $form_state was altered.');
       
   665 
       
   666     // Verify that #element_validate handlers can make form elements
       
   667     // inaccessible, but values persist.
       
   668     $edit = array(
       
   669       'name' => 'element_validate_access',
       
   670     );
       
   671     $this->drupalPost(NULL, $edit, 'Save');
       
   672     $this->assertNoFieldByName('name', 'Form element was hidden.');
       
   673     $this->assertText('Name value: element_validate_access', 'Value for inaccessible form element exists.');
       
   674 
       
   675     // Verify that value for inaccessible form element persists.
       
   676     $this->drupalPost(NULL, array(), 'Save');
       
   677     $this->assertNoFieldByName('name', 'Form element was hidden.');
       
   678     $this->assertText('Name value: element_validate_access', 'Value for inaccessible form element exists.');
       
   679 
       
   680     // Verify that #validate handlers don't run if the CSRF token is invalid.
       
   681     $this->drupalLogin($this->drupalCreateUser());
       
   682     $this->drupalGet('form-test/validate');
       
   683     $edit = array(
       
   684       'name' => 'validate',
       
   685       'form_token' => 'invalid token'
       
   686     );
       
   687     $this->drupalPost(NULL, $edit, 'Save');
       
   688     $this->assertNoFieldByName('name', '#value changed by #validate', 'Form element #value was not altered.');
       
   689     $this->assertNoText('Name value: value changed by form_set_value() in #validate', 'Form element value in $form_state was not altered.');
       
   690     $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
       
   691   }
       
   692 
       
   693   /**
       
   694    * Tests that a form with a disabled CSRF token can be validated.
       
   695    */
       
   696   function testDisabledToken() {
       
   697     $this->drupalPost('form-test/validate-no-token', array(), 'Save');
       
   698     $this->assertText('The form_test_validate_no_token form has been submitted successfully.');
       
   699   }
       
   700 
       
   701   /**
       
   702    * Tests partial form validation through #limit_validation_errors.
       
   703    */
       
   704   function testValidateLimitErrors() {
       
   705     $edit = array(
       
   706       'test' => 'invalid',
       
   707       'test_numeric_index[0]' => 'invalid',
       
   708       'test_substring[foo]' => 'invalid',
       
   709     );
       
   710     $path = 'form-test/limit-validation-errors';
       
   711 
       
   712     // Submit the form by pressing the 'Partial validate' button (uses
       
   713     // #limit_validation_errors) and ensure that the title field is not
       
   714     // validated, but the #element_validate handler for the 'test' field
       
   715     // is triggered.
       
   716     $this->drupalPost($path, $edit, t('Partial validate'));
       
   717     $this->assertNoText(t('!name field is required.', array('!name' => 'Title')));
       
   718     $this->assertText('Test element is invalid');
       
   719 
       
   720     // Edge case of #limit_validation_errors containing numeric indexes: same
       
   721     // thing with the 'Partial validate (numeric index)' button and the
       
   722     // 'test_numeric_index' field.
       
   723     $this->drupalPost($path, $edit, t('Partial validate (numeric index)'));
       
   724     $this->assertNoText(t('!name field is required.', array('!name' => 'Title')));
       
   725     $this->assertText('Test (numeric index) element is invalid');
       
   726 
       
   727     // Ensure something like 'foobar' isn't considered "inside" 'foo'.
       
   728     $this->drupalPost($path, $edit, t('Partial validate (substring)'));
       
   729     $this->assertNoText(t('!name field is required.', array('!name' => 'Title')));
       
   730     $this->assertText('Test (substring) foo element is invalid');
       
   731 
       
   732     // Ensure not validated values are not available to submit handlers.
       
   733     $this->drupalPost($path, array('title' => '', 'test' => 'valid'), t('Partial validate'));
       
   734     $this->assertText('Only validated values appear in the form values.');
       
   735 
       
   736     // Now test full form validation and ensure that the #element_validate
       
   737     // handler is still triggered.
       
   738     $this->drupalPost($path, $edit, t('Full validate'));
       
   739     $this->assertText(t('!name field is required.', array('!name' => 'Title')));
       
   740     $this->assertText('Test element is invalid');
       
   741   }
       
   742 
       
   743   /**
       
   744    *  Tests error border of multiple fields with same name in a page.
       
   745    */
       
   746   function testMultiFormSameNameErrorClass() {
       
   747     $this->drupalGet('form-test/double-form');
       
   748     $edit = array();
       
   749     $this->drupalPost(NULL, $edit, t('Save'));
       
   750     $this->assertFieldByXpath('//input[@id="edit-name" and contains(@class, "error")]', NULL, 'Error input form element class found for first element.');
       
   751     $this->assertNoFieldByXpath('//input[@id="edit-name--2" and contains(@class, "error")]', NULL, 'No error input form element class found for second element.');
       
   752   }
       
   753 }
       
   754 
       
   755 /**
       
   756  * Test form element labels, required markers and associated output.
       
   757  */
       
   758 class FormsElementsLabelsTestCase extends DrupalWebTestCase {
       
   759 
       
   760   public static function getInfo() {
       
   761     return array(
       
   762       'name' => 'Form element and label output test',
       
   763       'description' => 'Test form element labels, required markers and associated output.',
       
   764       'group' => 'Form API',
       
   765     );
       
   766   }
       
   767 
       
   768   function setUp() {
       
   769     parent::setUp('form_test');
       
   770   }
       
   771 
       
   772   /**
       
   773    * Test form elements, labels, title attibutes and required marks output
       
   774    * correctly and have the correct label option class if needed.
       
   775    */
       
   776   function testFormLabels() {
       
   777     $this->drupalGet('form_test/form-labels');
       
   778 
       
   779     // Check that the checkbox/radio processing is not interfering with
       
   780     // basic placement.
       
   781     $elements = $this->xpath('//input[@id="edit-form-checkboxes-test-third-checkbox"]/following-sibling::label[@for="edit-form-checkboxes-test-third-checkbox" and @class="option"]');
       
   782     $this->assertTrue(isset($elements[0]), "Label follows field and label option class correct for regular checkboxes.");
       
   783 
       
   784     // Make sure the label is rendered for checkboxes.
       
   785     $elements = $this->xpath('//input[@id="edit-form-checkboxes-test-0"]/following-sibling::label[@for="edit-form-checkboxes-test-0" and @class="option"]');
       
   786     $this->assertTrue(isset($elements[0]), "Label 0 found checkbox.");
       
   787 
       
   788     $elements = $this->xpath('//input[@id="edit-form-radios-test-second-radio"]/following-sibling::label[@for="edit-form-radios-test-second-radio" and @class="option"]');
       
   789     $this->assertTrue(isset($elements[0]), "Label follows field and label option class correct for regular radios.");
       
   790 
       
   791     // Make sure the label is rendered for radios.
       
   792     $elements = $this->xpath('//input[@id="edit-form-radios-test-0"]/following-sibling::label[@for="edit-form-radios-test-0" and @class="option"]');
       
   793     $this->assertTrue(isset($elements[0]), "Label 0 found radios.");
       
   794 
       
   795     // Exercise various defaults for checkboxes and modifications to ensure
       
   796     // appropriate override and correct behavior.
       
   797     $elements = $this->xpath('//input[@id="edit-form-checkbox-test"]/following-sibling::label[@for="edit-form-checkbox-test" and @class="option"]');
       
   798     $this->assertTrue(isset($elements[0]), "Label follows field and label option class correct for a checkbox by default.");
       
   799 
       
   800     // Exercise various defaults for textboxes and modifications to ensure
       
   801     // appropriate override and correct behavior.
       
   802     $elements = $this->xpath('//label[@for="edit-form-textfield-test-title-and-required"]/child::span[@class="form-required"]/parent::*/following-sibling::input[@id="edit-form-textfield-test-title-and-required"]');
       
   803     $this->assertTrue(isset($elements[0]), "Label precedes textfield, with required marker inside label.");
       
   804 
       
   805     $elements = $this->xpath('//input[@id="edit-form-textfield-test-no-title-required"]/preceding-sibling::label[@for="edit-form-textfield-test-no-title-required"]/span[@class="form-required"]');
       
   806     $this->assertTrue(isset($elements[0]), "Label tag with required marker precedes required textfield with no title.");
       
   807 
       
   808     $elements = $this->xpath('//input[@id="edit-form-textfield-test-title-invisible"]/preceding-sibling::label[@for="edit-form-textfield-test-title-invisible" and @class="element-invisible"]');
       
   809     $this->assertTrue(isset($elements[0]), "Label preceding field and label class is element-invisible.");
       
   810 
       
   811     $elements = $this->xpath('//input[@id="edit-form-textfield-test-title"]/preceding-sibling::span[@class="form-required"]');
       
   812     $this->assertFalse(isset($elements[0]), "No required marker on non-required field.");
       
   813 
       
   814     $elements = $this->xpath('//input[@id="edit-form-textfield-test-title-after"]/following-sibling::label[@for="edit-form-textfield-test-title-after" and @class="option"]');
       
   815     $this->assertTrue(isset($elements[0]), "Label after field and label option class correct for text field.");
       
   816 
       
   817     $elements = $this->xpath('//label[@for="edit-form-textfield-test-title-no-show"]');
       
   818     $this->assertFalse(isset($elements[0]), "No label tag when title set not to display.");
       
   819 
       
   820     // Check #field_prefix and #field_suffix placement.
       
   821     $elements = $this->xpath('//span[@class="field-prefix"]/following-sibling::div[@id="edit-form-radios-test"]');
       
   822     $this->assertTrue(isset($elements[0]), "Properly placed the #field_prefix element after the label and before the field.");
       
   823 
       
   824     $elements = $this->xpath('//span[@class="field-suffix"]/preceding-sibling::div[@id="edit-form-radios-test"]');
       
   825     $this->assertTrue(isset($elements[0]), "Properly places the #field_suffix element immediately after the form field.");
       
   826 
       
   827     // Check #prefix and #suffix placement.
       
   828     $elements = $this->xpath('//div[@id="form-test-textfield-title-prefix"]/following-sibling::div[contains(@class, \'form-item-form-textfield-test-title\')]');
       
   829     $this->assertTrue(isset($elements[0]), "Properly places the #prefix element before the form item.");
       
   830 
       
   831     $elements = $this->xpath('//div[@id="form-test-textfield-title-suffix"]/preceding-sibling::div[contains(@class, \'form-item-form-textfield-test-title\')]');
       
   832     $this->assertTrue(isset($elements[0]), "Properly places the #suffix element before the form item.");
       
   833 
       
   834     // Check title attribute for radios and checkboxes.
       
   835     $elements = $this->xpath('//div[@id="edit-form-checkboxes-title-attribute"]');
       
   836     $this->assertEqual($elements[0]['title'], 'Checkboxes test' . ' (' . t('Required') . ')', 'Title attribute found.');
       
   837     $elements = $this->xpath('//div[@id="edit-form-radios-title-attribute"]');
       
   838     $this->assertEqual($elements[0]['title'], 'Radios test' . ' (' . t('Required') . ')', 'Title attribute found.');
       
   839   }
       
   840 }
       
   841 
       
   842 /**
       
   843  * Test the tableselect form element for expected behavior.
       
   844  */
       
   845 class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
       
   846 
       
   847   public static function getInfo() {
       
   848     return array(
       
   849       'name' => 'Tableselect form element type test',
       
   850       'description' => 'Test the tableselect element for expected behavior',
       
   851       'group' => 'Form API',
       
   852     );
       
   853   }
       
   854 
       
   855   function setUp() {
       
   856     parent::setUp('form_test');
       
   857   }
       
   858 
       
   859 
       
   860   /**
       
   861    * Test the display of checkboxes when #multiple is TRUE.
       
   862    */
       
   863   function testMultipleTrue() {
       
   864 
       
   865     $this->drupalGet('form_test/tableselect/multiple-true');
       
   866 
       
   867     $this->assertNoText(t('Empty text.'), 'Empty text should not be displayed.');
       
   868 
       
   869     // Test for the presence of the Select all rows tableheader.
       
   870     $this->assertFieldByXPath('//th[@class="select-all"]', NULL, 'Presence of the "Select all" checkbox.');
       
   871 
       
   872     $rows = array('row1', 'row2', 'row3');
       
   873     foreach ($rows as $row) {
       
   874       $this->assertFieldByXPath('//input[@type="checkbox"]', $row, format_string('Checkbox for value @row.', array('@row' => $row)));
       
   875     }
       
   876   }
       
   877 
       
   878   /**
       
   879    * Test the display of radios when #multiple is FALSE.
       
   880    */
       
   881   function testMultipleFalse() {
       
   882     $this->drupalGet('form_test/tableselect/multiple-false');
       
   883 
       
   884     $this->assertNoText(t('Empty text.'), 'Empty text should not be displayed.');
       
   885 
       
   886     // Test for the absence of the Select all rows tableheader.
       
   887     $this->assertNoFieldByXPath('//th[@class="select-all"]', '', 'Absence of the "Select all" checkbox.');
       
   888 
       
   889     $rows = array('row1', 'row2', 'row3');
       
   890     foreach ($rows as $row) {
       
   891       $this->assertFieldByXPath('//input[@type="radio"]', $row, format_string('Radio button for value @row.', array('@row' => $row)));
       
   892     }
       
   893   }
       
   894 
       
   895   /**
       
   896    * Test the display of the #empty text when #options is an empty array.
       
   897    */
       
   898   function testEmptyText() {
       
   899     $this->drupalGet('form_test/tableselect/empty-text');
       
   900     $this->assertText(t('Empty text.'), 'Empty text should be displayed.');
       
   901   }
       
   902 
       
   903   /**
       
   904    * Test the submission of single and multiple values when #multiple is TRUE.
       
   905    */
       
   906   function testMultipleTrueSubmit() {
       
   907 
       
   908     // Test a submission with one checkbox checked.
       
   909     $edit = array();
       
   910     $edit['tableselect[row1]'] = TRUE;
       
   911     $this->drupalPost('form_test/tableselect/multiple-true', $edit, 'Submit');
       
   912 
       
   913     $this->assertText(t('Submitted: row1 = row1'), 'Checked checkbox row1');
       
   914     $this->assertText(t('Submitted: row2 = 0'), 'Unchecked checkbox row2.');
       
   915     $this->assertText(t('Submitted: row3 = 0'), 'Unchecked checkbox row3.');
       
   916 
       
   917     // Test a submission with multiple checkboxes checked.
       
   918     $edit['tableselect[row1]'] = TRUE;
       
   919     $edit['tableselect[row3]'] = TRUE;
       
   920     $this->drupalPost('form_test/tableselect/multiple-true', $edit, 'Submit');
       
   921 
       
   922     $this->assertText(t('Submitted: row1 = row1'), 'Checked checkbox row1.');
       
   923     $this->assertText(t('Submitted: row2 = 0'), 'Unchecked checkbox row2.');
       
   924     $this->assertText(t('Submitted: row3 = row3'), 'Checked checkbox row3.');
       
   925 
       
   926   }
       
   927 
       
   928   /**
       
   929    * Test submission of values when #multiple is FALSE.
       
   930    */
       
   931   function testMultipleFalseSubmit() {
       
   932     $edit['tableselect'] = 'row1';
       
   933     $this->drupalPost('form_test/tableselect/multiple-false', $edit, 'Submit');
       
   934     $this->assertText(t('Submitted: row1'), 'Selected radio button');
       
   935   }
       
   936 
       
   937   /**
       
   938    * Test the #js_select property.
       
   939    */
       
   940   function testAdvancedSelect() {
       
   941     // When #multiple = TRUE a Select all checkbox should be displayed by default.
       
   942     $this->drupalGet('form_test/tableselect/advanced-select/multiple-true-default');
       
   943     $this->assertFieldByXPath('//th[@class="select-all"]', NULL, 'Display a "Select all" checkbox by default when #multiple is TRUE.');
       
   944 
       
   945     // When #js_select is set to FALSE, a "Select all" checkbox should not be displayed.
       
   946     $this->drupalGet('form_test/tableselect/advanced-select/multiple-true-no-advanced-select');
       
   947     $this->assertNoFieldByXPath('//th[@class="select-all"]', NULL, 'Do not display a "Select all" checkbox when #js_select is FALSE.');
       
   948 
       
   949     // A "Select all" checkbox never makes sense when #multiple = FALSE, regardless of the value of #js_select.
       
   950     $this->drupalGet('form_test/tableselect/advanced-select/multiple-false-default');
       
   951     $this->assertNoFieldByXPath('//th[@class="select-all"]', NULL, 'Do not display a "Select all" checkbox when #multiple is FALSE.');
       
   952 
       
   953     $this->drupalGet('form_test/tableselect/advanced-select/multiple-false-advanced-select');
       
   954     $this->assertNoFieldByXPath('//th[@class="select-all"]', NULL, 'Do not display a "Select all" checkbox when #multiple is FALSE, even when #js_select is TRUE.');
       
   955   }
       
   956 
       
   957 
       
   958   /**
       
   959    * Test the whether the option checker gives an error on invalid tableselect values for checkboxes.
       
   960    */
       
   961   function testMultipleTrueOptionchecker() {
       
   962 
       
   963     list($header, $options) = _form_test_tableselect_get_data();
       
   964 
       
   965     $form['tableselect'] = array(
       
   966       '#type' => 'tableselect',
       
   967       '#header' => $header,
       
   968       '#options' => $options,
       
   969     );
       
   970 
       
   971     // Test with a valid value.
       
   972     list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => array('row1' => 'row1')));
       
   973     $this->assertFalse(isset($errors['tableselect']), 'Option checker allows valid values for checkboxes.');
       
   974 
       
   975     // Test with an invalid value.
       
   976     list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => array('non_existing_value' => 'non_existing_value')));
       
   977     $this->assertTrue(isset($errors['tableselect']), 'Option checker disallows invalid values for checkboxes.');
       
   978 
       
   979   }
       
   980 
       
   981 
       
   982   /**
       
   983    * Test the whether the option checker gives an error on invalid tableselect values for radios.
       
   984    */
       
   985   function testMultipleFalseOptionchecker() {
       
   986 
       
   987     list($header, $options) = _form_test_tableselect_get_data();
       
   988 
       
   989     $form['tableselect'] = array(
       
   990       '#type' => 'tableselect',
       
   991       '#header' => $header,
       
   992       '#options' => $options,
       
   993       '#multiple' => FALSE,
       
   994     );
       
   995 
       
   996     // Test with a valid value.
       
   997     list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => 'row1'));
       
   998     $this->assertFalse(isset($errors['tableselect']), 'Option checker allows valid values for radio buttons.');
       
   999 
       
  1000     // Test with an invalid value.
       
  1001     list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => 'non_existing_value'));
       
  1002     $this->assertTrue(isset($errors['tableselect']), 'Option checker disallows invalid values for radio buttons.');
       
  1003   }
       
  1004 
       
  1005   /**
       
  1006    * Test presence of ajax functionality
       
  1007    */
       
  1008   function testAjax() {
       
  1009     $rows = array('row1', 'row2', 'row3');
       
  1010     // Test checkboxes (#multiple == TRUE).
       
  1011     foreach ($rows as $row) {
       
  1012       $element = 'tableselect[' . $row . ']';
       
  1013       $edit = array($element => TRUE);
       
  1014       $result = $this->drupalPostAJAX('form_test/tableselect/multiple-true', $edit, $element);
       
  1015       $this->assertFalse(empty($result), t('Ajax triggers on checkbox for @row.', array('@row' => $row)));
       
  1016     }
       
  1017     // Test radios (#multiple == FALSE).
       
  1018     $element = 'tableselect';
       
  1019     foreach ($rows as $row) {
       
  1020       $edit = array($element => $row);
       
  1021       $result = $this->drupalPostAjax('form_test/tableselect/multiple-false', $edit, $element);
       
  1022       $this->assertFalse(empty($result), t('Ajax triggers on radio for @row.', array('@row' => $row)));
       
  1023     }
       
  1024   }
       
  1025 
       
  1026   /**
       
  1027    * Helper function for the option check test to submit a form while collecting errors.
       
  1028    *
       
  1029    * @param $form_element
       
  1030    *   A form element to test.
       
  1031    * @param $edit
       
  1032    *   An array containing post data.
       
  1033    *
       
  1034    * @return
       
  1035    *   An array containing the processed form, the form_state and any errors.
       
  1036    */
       
  1037   private function formSubmitHelper($form, $edit) {
       
  1038     $form_id = $this->randomName();
       
  1039     $form_state = form_state_defaults();
       
  1040 
       
  1041     $form['op'] = array('#type' => 'submit', '#value' => t('Submit'));
       
  1042 
       
  1043     $form_state['input'] = $edit;
       
  1044     $form_state['input']['form_id'] = $form_id;
       
  1045 
       
  1046     // The form token CSRF protection should not interfere with this test,
       
  1047     // so we bypass it by marking this test form as programmed.
       
  1048     $form_state['programmed'] = TRUE;
       
  1049 
       
  1050     drupal_prepare_form($form_id, $form, $form_state);
       
  1051 
       
  1052     drupal_process_form($form_id, $form, $form_state);
       
  1053 
       
  1054     $errors = form_get_errors();
       
  1055 
       
  1056     // Clear errors and messages.
       
  1057     drupal_get_messages();
       
  1058     form_clear_error();
       
  1059 
       
  1060     // Return the processed form together with form_state and errors
       
  1061     // to allow the caller lowlevel access to the form.
       
  1062     return array($form, $form_state, $errors);
       
  1063   }
       
  1064 
       
  1065 }
       
  1066 
       
  1067 /**
       
  1068  * Test the vertical_tabs form element for expected behavior.
       
  1069  */
       
  1070 class FormsElementsVerticalTabsFunctionalTest extends DrupalWebTestCase {
       
  1071 
       
  1072   public static function getInfo() {
       
  1073     return array(
       
  1074       'name' => 'Vertical tabs form element type test',
       
  1075       'description' => 'Test the vertical_tabs element for expected behavior',
       
  1076       'group' => 'Form API',
       
  1077     );
       
  1078   }
       
  1079 
       
  1080   function setUp() {
       
  1081     parent::setUp('form_test');
       
  1082   }
       
  1083 
       
  1084   /**
       
  1085    * Ensures that vertical-tabs.js is included before collapse.js.
       
  1086    *
       
  1087    * Otherwise, collapse.js adds "SHOW" or "HIDE" labels to the tabs.
       
  1088    */
       
  1089   function testJavaScriptOrdering() {
       
  1090     $this->drupalGet('form_test/vertical-tabs');
       
  1091     $position1 = strpos($this->content, 'misc/vertical-tabs.js');
       
  1092     $position2 = strpos($this->content, 'misc/collapse.js');
       
  1093     $this->assertTrue($position1 !== FALSE && $position2 !== FALSE && $position1 < $position2, 'vertical-tabs.js is included before collapse.js');
       
  1094   }
       
  1095 }
       
  1096 
       
  1097 /**
       
  1098  * Test the form storage on a multistep form.
       
  1099  *
       
  1100  * The tested form puts data into the storage during the initial form
       
  1101  * construction. These tests verify that there are no duplicate form
       
  1102  * constructions, with and without manual form caching activiated. Furthermore
       
  1103  * when a validation error occurs, it makes sure that changed form element
       
  1104  * values aren't lost due to a wrong form rebuild.
       
  1105  */
       
  1106 class FormsFormStorageTestCase extends DrupalWebTestCase {
       
  1107 
       
  1108   public static function getInfo() {
       
  1109     return array(
       
  1110       'name'  => 'Multistep form using form storage',
       
  1111       'description'  => 'Tests a multistep form using form storage and makes sure validation and caching works right.',
       
  1112       'group' => 'Form API',
       
  1113     );
       
  1114   }
       
  1115 
       
  1116   function setUp() {
       
  1117     parent::setUp('form_test');
       
  1118 
       
  1119     $this->web_user = $this->drupalCreateUser(array('access content'));
       
  1120     $this->drupalLogin($this->web_user);
       
  1121   }
       
  1122 
       
  1123   /**
       
  1124    * Tests using the form in a usual way.
       
  1125    */
       
  1126   function testForm() {
       
  1127     $this->drupalGet('form_test/form-storage');
       
  1128     $this->assertText('Form constructions: 1');
       
  1129 
       
  1130     $edit = array('title' => 'new', 'value' => 'value_is_set');
       
  1131 
       
  1132     // Use form rebuilding triggered by a submit button.
       
  1133     $this->drupalPost(NULL, $edit, 'Continue submit');
       
  1134     $this->assertText('Form constructions: 2');
       
  1135     $this->assertText('Form constructions: 3');
       
  1136 
       
  1137     // Reset the form to the values of the storage, using a form rebuild
       
  1138     // triggered by button of type button.
       
  1139     $this->drupalPost(NULL, array('title' => 'changed'), 'Reset');
       
  1140     $this->assertFieldByName('title', 'new', 'Values have been resetted.');
       
  1141     // After rebuilding, the form has been cached.
       
  1142     $this->assertText('Form constructions: 4');
       
  1143 
       
  1144     $this->drupalPost(NULL, $edit, 'Save');
       
  1145     $this->assertText('Form constructions: 4');
       
  1146     $this->assertText('Title: new', 'The form storage has stored the values.');
       
  1147   }
       
  1148 
       
  1149   /**
       
  1150    * Tests using the form with an activated $form_state['cache'] property.
       
  1151    */
       
  1152   function testFormCached() {
       
  1153     $this->drupalGet('form_test/form-storage', array('query' => array('cache' => 1)));
       
  1154     $this->assertText('Form constructions: 1');
       
  1155 
       
  1156     $edit = array('title' => 'new', 'value' => 'value_is_set');
       
  1157 
       
  1158     // Use form rebuilding triggered by a submit button.
       
  1159     $this->drupalPost(NULL, $edit, 'Continue submit');
       
  1160     $this->assertText('Form constructions: 2');
       
  1161 
       
  1162     // Reset the form to the values of the storage, using a form rebuild
       
  1163     // triggered by button of type button.
       
  1164     $this->drupalPost(NULL, array('title' => 'changed'), 'Reset');
       
  1165     $this->assertFieldByName('title', 'new', 'Values have been resetted.');
       
  1166     $this->assertText('Form constructions: 3');
       
  1167 
       
  1168     $this->drupalPost(NULL, $edit, 'Save');
       
  1169     $this->assertText('Form constructions: 3');
       
  1170     $this->assertText('Title: new', 'The form storage has stored the values.');
       
  1171   }
       
  1172 
       
  1173   /**
       
  1174    * Tests validation when form storage is used.
       
  1175    */
       
  1176   function testValidation() {
       
  1177     $this->drupalPost('form_test/form-storage', array('title' => '', 'value' => 'value_is_set'), 'Continue submit');
       
  1178     $this->assertPattern('/value_is_set/', 'The input values have been kept.');
       
  1179   }
       
  1180 
       
  1181   /**
       
  1182    * Tests updating cached form storage during form validation.
       
  1183    *
       
  1184    * If form caching is enabled and a form stores data in the form storage, then
       
  1185    * the form storage also has to be updated in case of a validation error in
       
  1186    * the form. This test re-uses the existing form for multi-step tests, but
       
  1187    * triggers a special #element_validate handler to update the form storage
       
  1188    * during form validation, while another, required element in the form
       
  1189    * triggers a form validation error.
       
  1190    */
       
  1191   function testCachedFormStorageValidation() {
       
  1192     // Request the form with 'cache' query parameter to enable form caching.
       
  1193     $this->drupalGet('form_test/form-storage', array('query' => array('cache' => 1)));
       
  1194 
       
  1195     // Skip step 1 of the multi-step form, since the first step copies over
       
  1196     // 'title' into form storage, but we want to verify that changes in the form
       
  1197     // storage are updated in the cache during form validation.
       
  1198     $edit = array('title' => 'foo');
       
  1199     $this->drupalPost(NULL, $edit, 'Continue submit');
       
  1200 
       
  1201     // In step 2, trigger a validation error for the required 'title' field, and
       
  1202     // post the special 'change_title' value for the 'value' field, which
       
  1203     // conditionally invokes the #element_validate handler to update the form
       
  1204     // storage.
       
  1205     $edit = array('title' => '', 'value' => 'change_title');
       
  1206     $this->drupalPost(NULL, $edit, 'Save');
       
  1207 
       
  1208     // At this point, the form storage should contain updated values, but we do
       
  1209     // not see them, because the form has not been rebuilt yet due to the
       
  1210     // validation error. Post again and verify that the rebuilt form contains
       
  1211     // the values of the updated form storage.
       
  1212     $this->drupalPost(NULL, array('title' => 'foo', 'value' => 'bar'), 'Save');
       
  1213     $this->assertText("The thing has been changed.", 'The altered form storage value was updated in cache and taken over.');
       
  1214   }
       
  1215 
       
  1216   /**
       
  1217    * Tests a form using form state without using 'storage' to pass data from the
       
  1218    * constructor to a submit handler. The data has to persist even when caching
       
  1219    * gets activated, what may happen when a modules alter the form and adds
       
  1220    * #ajax properties.
       
  1221    */
       
  1222   function testFormStatePersist() {
       
  1223     // Test the form one time with caching activated and one time without.
       
  1224     $run_options = array(
       
  1225       array(),
       
  1226       array('query' => array('cache' => 1)),
       
  1227     );
       
  1228     foreach ($run_options as $options) {
       
  1229       $this->drupalPost('form-test/state-persist', array(), t('Submit'), $options);
       
  1230       // The submit handler outputs the value in $form_state, assert it's there.
       
  1231       $this->assertText('State persisted.');
       
  1232 
       
  1233       // Test it again, but first trigger a validation error, then test.
       
  1234       $this->drupalPost('form-test/state-persist', array('title' => ''), t('Submit'), $options);
       
  1235       $this->assertText(t('!name field is required.', array('!name' => 'title')));
       
  1236       // Submit the form again triggering no validation error.
       
  1237       $this->drupalPost(NULL, array('title' => 'foo'), t('Submit'), $options);
       
  1238       $this->assertText('State persisted.');
       
  1239 
       
  1240       // Now post to the rebuilt form and verify it's still there afterwards.
       
  1241       $this->drupalPost(NULL, array('title' => 'bar'), t('Submit'), $options);
       
  1242       $this->assertText('State persisted.');
       
  1243     }
       
  1244   }
       
  1245 
       
  1246   /**
       
  1247    * Verify that the form build-id remains the same when validation errors
       
  1248    * occur on a mutable form.
       
  1249    */
       
  1250   function testMutableForm() {
       
  1251     // Request the form with 'cache' query parameter to enable form caching.
       
  1252     $this->drupalGet('form_test/form-storage', array('query' => array('cache' => 1)));
       
  1253     $buildIdFields = $this->xpath('//input[@name="form_build_id"]');
       
  1254     $this->assertEqual(count($buildIdFields), 1, 'One form build id field on the page');
       
  1255     $buildId = (string) $buildIdFields[0]['value'];
       
  1256 
       
  1257     // Trigger validation error by submitting an empty title.
       
  1258     $edit = array('title' => '');
       
  1259     $this->drupalPost(NULL, $edit, 'Continue submit');
       
  1260 
       
  1261     // Verify that the build-id did not change.
       
  1262     $this->assertFieldByName('form_build_id', $buildId, 'Build id remains the same when form validation fails');
       
  1263   }
       
  1264 
       
  1265   /**
       
  1266    * Verifies that form build-id is regenerated when loading an immutable form
       
  1267    * from the cache.
       
  1268    */
       
  1269   function testImmutableForm() {
       
  1270     // Request the form with 'cache' query parameter to enable form caching.
       
  1271     $this->drupalGet('form_test/form-storage', array('query' => array('cache' => 1, 'immutable' => 1)));
       
  1272     $buildIdFields = $this->xpath('//input[@name="form_build_id"]');
       
  1273     $this->assertEqual(count($buildIdFields), 1, 'One form build id field on the page');
       
  1274     $buildId = (string) $buildIdFields[0]['value'];
       
  1275 
       
  1276     // Trigger validation error by submitting an empty title.
       
  1277     $edit = array('title' => '');
       
  1278     $this->drupalPost(NULL, $edit, 'Continue submit');
       
  1279 
       
  1280     // Verify that the build-id did change.
       
  1281     $this->assertNoFieldByName('form_build_id', $buildId, 'Build id changes when form validation fails');
       
  1282 
       
  1283     // Retrieve the new build-id.
       
  1284     $buildIdFields = $this->xpath('//input[@name="form_build_id"]');
       
  1285     $this->assertEqual(count($buildIdFields), 1, 'One form build id field on the page');
       
  1286     $buildId = (string) $buildIdFields[0]['value'];
       
  1287 
       
  1288     // Trigger validation error by again submitting an empty title.
       
  1289     $edit = array('title' => '');
       
  1290     $this->drupalPost(NULL, $edit, 'Continue submit');
       
  1291 
       
  1292     // Verify that the build-id does not change the second time.
       
  1293     $this->assertFieldByName('form_build_id', $buildId, 'Build id remains the same when form validation fails subsequently');
       
  1294   }
       
  1295 
       
  1296   /**
       
  1297    * Verify that existing contrib code cannot overwrite immutable form state.
       
  1298    */
       
  1299   public function testImmutableFormLegacyProtection() {
       
  1300     $this->drupalGet('form_test/form-storage', array('query' => array('cache' => 1, 'immutable' => 1)));
       
  1301     $build_id_fields = $this->xpath('//input[@name="form_build_id"]');
       
  1302     $this->assertEqual(count($build_id_fields), 1, 'One form build id field on the page');
       
  1303     $build_id = (string) $build_id_fields[0]['value'];
       
  1304 
       
  1305     // Try to poison the form cache.
       
  1306     $original = $this->drupalGetAJAX('form_test/form-storage-legacy/' . $build_id);
       
  1307     $this->assertEqual($original['form']['#build_id_old'], $build_id, 'Original build_id was recorded');
       
  1308     $this->assertNotEqual($original['form']['#build_id'], $build_id, 'New build_id was generated');
       
  1309 
       
  1310     // Assert that a watchdog message was logged by form_set_cache.
       
  1311     $status = (bool) db_query_range('SELECT 1 FROM {watchdog} WHERE message = :message', 0, 1, array(':message' => 'Form build-id mismatch detected while attempting to store a form in the cache.'));
       
  1312     $this->assert($status, 'A watchdog message was logged by form_set_cache');
       
  1313 
       
  1314     // Ensure that the form state was not poisoned by the preceeding call.
       
  1315     $original = $this->drupalGetAJAX('form_test/form-storage-legacy/' . $build_id);
       
  1316     $this->assertEqual($original['form']['#build_id_old'], $build_id, 'Original build_id was recorded');
       
  1317     $this->assertNotEqual($original['form']['#build_id'], $build_id, 'New build_id was generated');
       
  1318     $this->assert(empty($original['form']['#poisoned']), 'Original form structure was preserved');
       
  1319     $this->assert(empty($original['form_state']['poisoned']), 'Original form state was preserved');
       
  1320   }
       
  1321 }
       
  1322 
       
  1323 /**
       
  1324  * Test the form storage when page caching for anonymous users is turned on.
       
  1325  */
       
  1326 class FormsFormStoragePageCacheTestCase extends DrupalWebTestCase {
       
  1327   protected $profile = 'testing';
       
  1328 
       
  1329   public static function getInfo() {
       
  1330     return array(
       
  1331       'name'  => 'Forms using form storage on cached pages',
       
  1332       'description'  => 'Tests a form using form storage and makes sure validation and caching works when page caching for anonymous users is turned on.',
       
  1333       'group' => 'Form API',
       
  1334     );
       
  1335   }
       
  1336 
       
  1337   public function setUp() {
       
  1338     parent::setUp('form_test');
       
  1339 
       
  1340     variable_set('cache', TRUE);
       
  1341   }
       
  1342 
       
  1343   /**
       
  1344    * Return the build id of the current form.
       
  1345    */
       
  1346   protected function getFormBuildId() {
       
  1347     $build_id_fields = $this->xpath('//input[@name="form_build_id"]');
       
  1348     $this->assertEqual(count($build_id_fields), 1, 'One form build id field on the page');
       
  1349     return (string) $build_id_fields[0]['value'];
       
  1350   }
       
  1351 
       
  1352   /**
       
  1353    * Build-id is regenerated when validating cached form.
       
  1354    */
       
  1355   public function testValidateFormStorageOnCachedPage() {
       
  1356     $this->drupalGet('form_test/form-storage-page-cache');
       
  1357     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
       
  1358     $this->assertText('No old build id', 'No old build id on the page');
       
  1359     $build_id_initial = $this->getFormBuildId();
       
  1360 
       
  1361     // Trigger validation error by submitting an empty title.
       
  1362     $edit = array('title' => '');
       
  1363     $this->drupalPost(NULL, $edit, 'Save');
       
  1364     $this->assertText($build_id_initial, 'Old build id on the page');
       
  1365     $build_id_first_validation = $this->getFormBuildId();
       
  1366     $this->assertNotEqual($build_id_initial, $build_id_first_validation, 'Build id changes when form validation fails');
       
  1367 
       
  1368     // Trigger validation error by again submitting an empty title.
       
  1369     $edit = array('title' => '');
       
  1370     $this->drupalPost(NULL, $edit, 'Save');
       
  1371     $this->assertText('No old build id', 'No old build id on the page');
       
  1372     $build_id_second_validation = $this->getFormBuildId();
       
  1373     $this->assertEqual($build_id_first_validation, $build_id_second_validation, 'Build id remains the same when form validation fails subsequently');
       
  1374 
       
  1375     // Repeat the test sequence but this time with a page loaded from the cache.
       
  1376     $this->drupalGet('form_test/form-storage-page-cache');
       
  1377     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
       
  1378     $this->assertText('No old build id', 'No old build id on the page');
       
  1379     $build_id_from_cache_initial = $this->getFormBuildId();
       
  1380     $this->assertEqual($build_id_initial, $build_id_from_cache_initial, 'Build id is the same as on the first request');
       
  1381 
       
  1382     // Trigger validation error by submitting an empty title.
       
  1383     $edit = array('title' => '');
       
  1384     $this->drupalPost(NULL, $edit, 'Save');
       
  1385     $this->assertText($build_id_initial, 'Old build id is initial build id');
       
  1386     $build_id_from_cache_first_validation = $this->getFormBuildId();
       
  1387     $this->assertNotEqual($build_id_initial, $build_id_from_cache_first_validation, 'Build id changes when form validation fails');
       
  1388     $this->assertNotEqual($build_id_first_validation, $build_id_from_cache_first_validation, 'Build id from first user is not reused');
       
  1389 
       
  1390     // Trigger validation error by again submitting an empty title.
       
  1391     $edit = array('title' => '');
       
  1392     $this->drupalPost(NULL, $edit, 'Save');
       
  1393     $this->assertText('No old build id', 'No old build id on the page');
       
  1394     $build_id_from_cache_second_validation = $this->getFormBuildId();
       
  1395     $this->assertEqual($build_id_from_cache_first_validation, $build_id_from_cache_second_validation, 'Build id remains the same when form validation fails subsequently');
       
  1396   }
       
  1397 
       
  1398   /**
       
  1399    * Build-id is regenerated when rebuilding cached form.
       
  1400    */
       
  1401   public function testRebuildFormStorageOnCachedPage() {
       
  1402     $this->drupalGet('form_test/form-storage-page-cache');
       
  1403     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
       
  1404     $this->assertText('No old build id', 'No old build id on the page');
       
  1405     $build_id_initial = $this->getFormBuildId();
       
  1406 
       
  1407     // Trigger rebuild, should regenerate build id.
       
  1408     $edit = array('title' => 'something');
       
  1409     $this->drupalPost(NULL, $edit, 'Rebuild');
       
  1410     $this->assertText($build_id_initial, 'Initial build id as old build id on the page');
       
  1411     $build_id_first_rebuild = $this->getFormBuildId();
       
  1412     $this->assertNotEqual($build_id_initial, $build_id_first_rebuild, 'Build id changes on first rebuild.');
       
  1413 
       
  1414     // Trigger subsequent rebuild, should regenerate the build id again.
       
  1415     $edit = array('title' => 'something');
       
  1416     $this->drupalPost(NULL, $edit, 'Rebuild');
       
  1417     $this->assertText($build_id_first_rebuild, 'First build id as old build id on the page');
       
  1418     $build_id_second_rebuild = $this->getFormBuildId();
       
  1419     $this->assertNotEqual($build_id_first_rebuild, $build_id_second_rebuild, 'Build id changes on second rebuild.');
       
  1420   }
       
  1421 }
       
  1422 
       
  1423 /**
       
  1424  * Test wrapper form callbacks.
       
  1425  */
       
  1426 class FormsFormWrapperTestCase extends DrupalWebTestCase {
       
  1427   public static function getInfo() {
       
  1428     return array(
       
  1429       'name' => 'Form wrapper callback',
       
  1430       'description' => 'Tests form wrapper callbacks to pass a prebuilt form to form builder functions.',
       
  1431       'group' => 'Form API',
       
  1432     );
       
  1433   }
       
  1434 
       
  1435   function setUp() {
       
  1436     parent::setUp('form_test');
       
  1437   }
       
  1438 
       
  1439   /**
       
  1440    * Tests using the form in a usual way.
       
  1441    */
       
  1442   function testWrapperCallback() {
       
  1443     $this->drupalGet('form_test/wrapper-callback');
       
  1444     $this->assertText('Form wrapper callback element output.', 'The form contains form wrapper elements.');
       
  1445     $this->assertText('Form builder element output.', 'The form contains form builder elements.');
       
  1446   }
       
  1447 }
       
  1448 
       
  1449 /**
       
  1450  * Test $form_state clearance.
       
  1451  */
       
  1452 class FormStateValuesCleanTestCase extends DrupalWebTestCase {
       
  1453   public static function getInfo() {
       
  1454     return array(
       
  1455       'name' => 'Form state values clearance',
       
  1456       'description' => 'Test proper removal of submitted form values using form_state_values_clean().',
       
  1457       'group' => 'Form API',
       
  1458     );
       
  1459   }
       
  1460 
       
  1461   function setUp() {
       
  1462     parent::setUp('form_test');
       
  1463   }
       
  1464 
       
  1465   /**
       
  1466    * Tests form_state_values_clean().
       
  1467    */
       
  1468   function testFormStateValuesClean() {
       
  1469     $values = drupal_json_decode($this->drupalPost('form_test/form-state-values-clean', array(), t('Submit')));
       
  1470 
       
  1471     // Setup the expected result.
       
  1472     $result = array(
       
  1473       'beer' => 1000,
       
  1474       'baz' => array('beer' => 2000),
       
  1475     );
       
  1476 
       
  1477     // Verify that all internal Form API elements were removed.
       
  1478     $this->assertFalse(isset($values['form_id']), format_string('%element was removed.', array('%element' => 'form_id')));
       
  1479     $this->assertFalse(isset($values['form_token']), format_string('%element was removed.', array('%element' => 'form_token')));
       
  1480     $this->assertFalse(isset($values['form_build_id']), format_string('%element was removed.', array('%element' => 'form_build_id')));
       
  1481     $this->assertFalse(isset($values['op']), format_string('%element was removed.', array('%element' => 'op')));
       
  1482 
       
  1483     // Verify that all buttons were removed.
       
  1484     $this->assertFalse(isset($values['foo']), format_string('%element was removed.', array('%element' => 'foo')));
       
  1485     $this->assertFalse(isset($values['bar']), format_string('%element was removed.', array('%element' => 'bar')));
       
  1486     $this->assertFalse(isset($values['baz']['foo']), format_string('%element was removed.', array('%element' => 'foo')));
       
  1487     $this->assertFalse(isset($values['baz']['baz']), format_string('%element was removed.', array('%element' => 'baz')));
       
  1488 
       
  1489     // Verify that nested form value still exists.
       
  1490     $this->assertTrue(isset($values['baz']['beer']), 'Nested form value still exists.');
       
  1491 
       
  1492     // Verify that actual form values equal resulting form values.
       
  1493     $this->assertEqual($values, $result, 'Expected form values equal actual form values.');
       
  1494   }
       
  1495 }
       
  1496 
       
  1497 /**
       
  1498  * Tests $form_state clearance with form elements having buttons.
       
  1499  */
       
  1500 class FormStateValuesCleanAdvancedTestCase extends DrupalWebTestCase {
       
  1501   /**
       
  1502    * An image file path for uploading.
       
  1503    */
       
  1504   protected $image;
       
  1505 
       
  1506   public static function getInfo() {
       
  1507     return array(
       
  1508       'name' => 'Form state values clearance (advanced)',
       
  1509       'description' => 'Test proper removal of submitted form values using form_state_values_clean() when having forms with elements containing buttons like "managed_file".',
       
  1510       'group' => 'Form API',
       
  1511     );
       
  1512   }
       
  1513 
       
  1514   function setUp() {
       
  1515     parent::setUp('form_test');
       
  1516   }
       
  1517 
       
  1518   /**
       
  1519    * Tests form_state_values_clean().
       
  1520    */
       
  1521   function testFormStateValuesCleanAdvanced() {
       
  1522 
       
  1523     // Get an image for uploading.
       
  1524     $image_files = $this->drupalGetTestFiles('image');
       
  1525     $this->image = current($image_files);
       
  1526 
       
  1527     // Check if the physical file is there.
       
  1528     $this->assertTrue(is_file($this->image->uri), "The image file we're going to upload exists.");
       
  1529 
       
  1530     // "Browse" for the desired file.
       
  1531     $edit = array('files[image]' => drupal_realpath($this->image->uri));
       
  1532 
       
  1533     // Post the form.
       
  1534     $this->drupalPost('form_test/form-state-values-clean-advanced', $edit, t('Submit'));
       
  1535 
       
  1536     // Expecting a 200 HTTP code.
       
  1537     $this->assertResponse(200, 'Received a 200 response for posted test file.');
       
  1538     $this->assertRaw(t('You WIN!'), 'Found the success message.');
       
  1539   }
       
  1540 }
       
  1541 
       
  1542 /**
       
  1543  * Tests form rebuilding.
       
  1544  *
       
  1545  * @todo Add tests for other aspects of form rebuilding.
       
  1546  */
       
  1547 class FormsRebuildTestCase extends DrupalWebTestCase {
       
  1548   public static function getInfo() {
       
  1549     return array(
       
  1550       'name' => 'Form rebuilding',
       
  1551       'description' => 'Tests functionality of drupal_rebuild_form().',
       
  1552       'group' => 'Form API',
       
  1553     );
       
  1554   }
       
  1555 
       
  1556   function setUp() {
       
  1557     parent::setUp('form_test');
       
  1558 
       
  1559     $this->web_user = $this->drupalCreateUser(array('access content'));
       
  1560     $this->drupalLogin($this->web_user);
       
  1561   }
       
  1562 
       
  1563   /**
       
  1564    * Tests preservation of values.
       
  1565    */
       
  1566   function testRebuildPreservesValues() {
       
  1567     $edit = array(
       
  1568       'checkbox_1_default_off' => TRUE,
       
  1569       'checkbox_1_default_on' => FALSE,
       
  1570       'text_1' => 'foo',
       
  1571     );
       
  1572     $this->drupalPost('form-test/form-rebuild-preserve-values', $edit, 'Add more');
       
  1573 
       
  1574     // Verify that initial elements retained their submitted values.
       
  1575     $this->assertFieldChecked('edit-checkbox-1-default-off', 'A submitted checked checkbox retained its checked state during a rebuild.');
       
  1576     $this->assertNoFieldChecked('edit-checkbox-1-default-on', 'A submitted unchecked checkbox retained its unchecked state during a rebuild.');
       
  1577     $this->assertFieldById('edit-text-1', 'foo', 'A textfield retained its submitted value during a rebuild.');
       
  1578 
       
  1579     // Verify that newly added elements were initialized with their default values.
       
  1580     $this->assertFieldChecked('edit-checkbox-2-default-on', 'A newly added checkbox was initialized with a default checked state.');
       
  1581     $this->assertNoFieldChecked('edit-checkbox-2-default-off', 'A newly added checkbox was initialized with a default unchecked state.');
       
  1582     $this->assertFieldById('edit-text-2', 'DEFAULT 2', 'A newly added textfield was initialized with its default value.');
       
  1583   }
       
  1584 
       
  1585   /**
       
  1586    * Tests that a form's action is retained after an Ajax submission.
       
  1587    *
       
  1588    * The 'action' attribute of a form should not change after an Ajax submission
       
  1589    * followed by a non-Ajax submission, which triggers a validation error.
       
  1590    */
       
  1591   function testPreserveFormActionAfterAJAX() {
       
  1592     // Create a multi-valued field for 'page' nodes to use for Ajax testing.
       
  1593     $field_name = 'field_ajax_test';
       
  1594     $field = array(
       
  1595       'field_name' => $field_name,
       
  1596       'type' => 'text',
       
  1597       'cardinality' => FIELD_CARDINALITY_UNLIMITED,
       
  1598     );
       
  1599     field_create_field($field);
       
  1600     $instance = array(
       
  1601       'field_name' => $field_name,
       
  1602       'entity_type' => 'node',
       
  1603       'bundle' => 'page',
       
  1604     );
       
  1605     field_create_instance($instance);
       
  1606 
       
  1607     // Log in a user who can create 'page' nodes.
       
  1608     $this->web_user = $this->drupalCreateUser(array('create page content'));
       
  1609     $this->drupalLogin($this->web_user);
       
  1610 
       
  1611     // Get the form for adding a 'page' node. Submit an "add another item" Ajax
       
  1612     // submission and verify it worked by ensuring the updated page has two text
       
  1613     // field items in the field for which we just added an item.
       
  1614     $this->drupalGet('node/add/page');
       
  1615     $this->drupalPostAJAX(NULL, array(), array('field_ajax_test_add_more' => t('Add another item')), 'system/ajax', array(), array(), 'page-node-form');
       
  1616     $this->assert(count($this->xpath('//div[contains(@class, "field-name-field-ajax-test")]//input[@type="text"]')) == 2, 'AJAX submission succeeded.');
       
  1617 
       
  1618     // Submit the form with the non-Ajax "Save" button, leaving the title field
       
  1619     // blank to trigger a validation error, and ensure that a validation error
       
  1620     // occurred, because this test is for testing what happens when a form is
       
  1621     // re-rendered without being re-built, which is what happens when there's
       
  1622     // a validation error.
       
  1623     $this->drupalPost(NULL, array(), t('Save'));
       
  1624     $this->assertText('Title field is required.', 'Non-AJAX submission correctly triggered a validation error.');
       
  1625 
       
  1626     // Ensure that the form contains two items in the multi-valued field, so we
       
  1627     // know we're testing a form that was correctly retrieved from cache.
       
  1628     $this->assert(count($this->xpath('//form[contains(@id, "page-node-form")]//div[contains(@class, "form-item-field-ajax-test")]//input[@type="text"]')) == 2, 'Form retained its state from cache.');
       
  1629 
       
  1630     // Ensure that the form's action is correct.
       
  1631     $forms = $this->xpath('//form[contains(@class, "node-page-form")]');
       
  1632     $this->assert(count($forms) == 1 && $forms[0]['action'] == url('node/add/page'), 'Re-rendered form contains the correct action value.');
       
  1633   }
       
  1634 }
       
  1635 
       
  1636 /**
       
  1637  * Tests form redirection.
       
  1638  */
       
  1639 class FormsRedirectTestCase extends DrupalWebTestCase {
       
  1640 
       
  1641   public static function getInfo() {
       
  1642     return array(
       
  1643       'name' => 'Form redirecting',
       
  1644       'description' => 'Tests functionality of drupal_redirect_form().',
       
  1645       'group' => 'Form API',
       
  1646     );
       
  1647   }
       
  1648 
       
  1649   function setUp() {
       
  1650     parent::setUp(array('form_test'));
       
  1651   }
       
  1652 
       
  1653   /**
       
  1654    * Tests form redirection.
       
  1655    */
       
  1656   function testRedirect() {
       
  1657     $path = 'form-test/redirect';
       
  1658     $options = array('query' => array('foo' => 'bar'));
       
  1659     $options['absolute'] = TRUE;
       
  1660 
       
  1661     // Test basic redirection.
       
  1662     $edit = array(
       
  1663       'redirection' => TRUE,
       
  1664       'destination' => $this->randomName(),
       
  1665     );
       
  1666     $this->drupalPost($path, $edit, t('Submit'));
       
  1667     $this->assertUrl($edit['destination'], array(), 'Basic redirection works.');
       
  1668 
       
  1669 
       
  1670     // Test without redirection.
       
  1671     $edit = array(
       
  1672       'redirection' => FALSE,
       
  1673     );
       
  1674     $this->drupalPost($path, $edit, t('Submit'));
       
  1675     $this->assertUrl($path, array(), 'When redirect is set to FALSE, there should be no redirection.');
       
  1676 
       
  1677     // Test redirection with query parameters.
       
  1678     $edit = array(
       
  1679       'redirection' => TRUE,
       
  1680       'destination' => $this->randomName(),
       
  1681     );
       
  1682     $this->drupalPost($path, $edit, t('Submit'), $options);
       
  1683     $this->assertUrl($edit['destination'], array(), 'Redirection with query parameters works.');
       
  1684 
       
  1685     // Test without redirection but with query parameters.
       
  1686     $edit = array(
       
  1687       'redirection' => FALSE,
       
  1688     );
       
  1689     $this->drupalPost($path, $edit, t('Submit'), $options);
       
  1690     $this->assertUrl($path, $options, 'When redirect is set to FALSE, there should be no redirection, and the query parameters should be passed along.');
       
  1691 
       
  1692     // Test redirection back to the original path.
       
  1693     $edit = array(
       
  1694       'redirection' => TRUE,
       
  1695       'destination' => '',
       
  1696     );
       
  1697     $this->drupalPost($path, $edit, t('Submit'));
       
  1698     $this->assertUrl($path, array(), 'When using an empty redirection string, there should be no redirection.');
       
  1699 
       
  1700     // Test redirection back to the original path with query parameters.
       
  1701     $edit = array(
       
  1702       'redirection' => TRUE,
       
  1703       'destination' => '',
       
  1704     );
       
  1705     $this->drupalPost($path, $edit, t('Submit'), $options);
       
  1706     $this->assertUrl($path, $options, 'When using an empty redirection string, there should be no redirection, and the query parameters should be passed along.');
       
  1707   }
       
  1708 
       
  1709 }
       
  1710 
       
  1711 /**
       
  1712  * Test the programmatic form submission behavior.
       
  1713  */
       
  1714 class FormsProgrammaticTestCase extends DrupalWebTestCase {
       
  1715 
       
  1716   public static function getInfo() {
       
  1717     return array(
       
  1718       'name' => 'Programmatic form submissions',
       
  1719       'description' => 'Test the programmatic form submission behavior.',
       
  1720       'group' => 'Form API',
       
  1721     );
       
  1722   }
       
  1723 
       
  1724   function setUp() {
       
  1725     parent::setUp('form_test');
       
  1726   }
       
  1727 
       
  1728   /**
       
  1729    * Test the programmatic form submission workflow.
       
  1730    */
       
  1731   function testSubmissionWorkflow() {
       
  1732     // Backup the current batch status and reset it to avoid conflicts while
       
  1733     // processing the dummy form submit handler.
       
  1734     $current_batch = $batch =& batch_get();
       
  1735     $batch = array();
       
  1736 
       
  1737     // Test that a programmatic form submission is rejected when a required
       
  1738     // textfield is omitted and correctly processed when it is provided.
       
  1739     $this->submitForm(array(), FALSE);
       
  1740     $this->submitForm(array('textfield' => 'test 1'), TRUE);
       
  1741     $this->submitForm(array(), FALSE);
       
  1742     $this->submitForm(array('textfield' => 'test 2'), TRUE);
       
  1743 
       
  1744     // Test that a programmatic form submission can turn on and off checkboxes
       
  1745     // which are, by default, checked.
       
  1746     $this->submitForm(array('textfield' => 'dummy value', 'checkboxes' => array(1 => 1, 2 => 2)), TRUE);
       
  1747     $this->submitForm(array('textfield' => 'dummy value', 'checkboxes' => array(1 => 1, 2 => NULL)), TRUE);
       
  1748     $this->submitForm(array('textfield' => 'dummy value', 'checkboxes' => array(1 => NULL, 2 => 2)), TRUE);
       
  1749     $this->submitForm(array('textfield' => 'dummy value', 'checkboxes' => array(1 => NULL, 2 => NULL)), TRUE);
       
  1750 
       
  1751     // Test that a programmatic form submission can successfully submit values
       
  1752     // even for fields where the #access property is FALSE.
       
  1753     $this->submitForm(array('textfield' => 'dummy value', 'textfield_no_access' => 'test value'), TRUE);
       
  1754     // Test that #access is respected for programmatic form submissions when
       
  1755     // requested to do so.
       
  1756     $submitted_values = array('textfield' => 'dummy value', 'textfield_no_access' => 'test value');
       
  1757     $expected_values = array('textfield' => 'dummy value', 'textfield_no_access' => 'default value');
       
  1758     $form_state = array('programmed_bypass_access_check' => FALSE);
       
  1759     $this->submitForm($submitted_values, TRUE, $expected_values, $form_state);
       
  1760 
       
  1761     // Test that a programmatic form submission can correctly click a button
       
  1762     // that limits validation errors based on user input. Since we do not
       
  1763     // submit any values for "textfield" here and the textfield is required, we
       
  1764     // only expect form validation to pass when validation is limited to a
       
  1765     // different field.
       
  1766     $this->submitForm(array('op' => 'Submit with limited validation', 'field_to_validate' => 'all'), FALSE);
       
  1767     $this->submitForm(array('op' => 'Submit with limited validation', 'field_to_validate' => 'textfield'), FALSE);
       
  1768     $this->submitForm(array('op' => 'Submit with limited validation', 'field_to_validate' => 'field_to_validate'), TRUE);
       
  1769 
       
  1770     // Restore the current batch status.
       
  1771     $batch = $current_batch;
       
  1772   }
       
  1773 
       
  1774   /**
       
  1775    * Helper function used to programmatically submit the form defined in
       
  1776    * form_test.module with the given values.
       
  1777    *
       
  1778    * @param $values
       
  1779    *   An array of field values to be submitted.
       
  1780    * @param $valid_input
       
  1781    *   A boolean indicating whether or not the form submission is expected to
       
  1782    *   be valid.
       
  1783    * @param $expected_values
       
  1784    *   (Optional) An array of field values that are expected to be stored by
       
  1785    *   the form submit handler. If not set, the submitted $values are assumed
       
  1786    *   to also be the expected stored values.
       
  1787    * @param $form_state
       
  1788    *   (Optional) A keyed array containing the state of the form, to be sent in
       
  1789    *   the call to drupal_form_submit(). The $values parameter is added to
       
  1790    *   $form_state['values'] by default, if it is not already set.
       
  1791    */
       
  1792   private function submitForm($values, $valid_input, $expected_values = NULL, $form_state = array()) {
       
  1793     // Programmatically submit the given values.
       
  1794     $form_state += array('values' => $values);
       
  1795     drupal_form_submit('form_test_programmatic_form', $form_state);
       
  1796 
       
  1797     // Check that the form returns an error when expected, and vice versa.
       
  1798     $errors = form_get_errors();
       
  1799     $valid_form = empty($errors);
       
  1800     $args = array(
       
  1801       '%values' => print_r($values, TRUE),
       
  1802       '%errors' => $valid_form ? t('None') : implode(' ', $errors),
       
  1803     );
       
  1804     $this->assertTrue($valid_input == $valid_form, format_string('Input values: %values<br/>Validation handler errors: %errors', $args));
       
  1805 
       
  1806     // We check submitted values only if we have a valid input.
       
  1807     if ($valid_input) {
       
  1808       // By fetching the values from $form_state['storage'] we ensure that the
       
  1809       // submission handler was properly executed.
       
  1810       $stored_values = $form_state['storage']['programmatic_form_submit'];
       
  1811       if (!isset($expected_values)) {
       
  1812         $expected_values = $values;
       
  1813       }
       
  1814       foreach ($expected_values as $key => $value) {
       
  1815         $this->assertTrue(isset($stored_values[$key]) && $stored_values[$key] == $value, format_string('Submission handler correctly executed: %stored_key is %stored_value', array('%stored_key' => $key, '%stored_value' => print_r($value, TRUE))));
       
  1816       }
       
  1817     }
       
  1818   }
       
  1819 }
       
  1820 
       
  1821 /**
       
  1822  * Test that FAPI correctly determines $form_state['triggering_element'].
       
  1823  */
       
  1824 class FormsTriggeringElementTestCase extends DrupalWebTestCase {
       
  1825 
       
  1826   public static function getInfo() {
       
  1827     return array(
       
  1828       'name' => 'Form triggering element determination',
       
  1829       'description' => 'Test the determination of $form_state[\'triggering_element\'].',
       
  1830       'group' => 'Form API',
       
  1831     );
       
  1832   }
       
  1833 
       
  1834   function setUp() {
       
  1835     parent::setUp('form_test');
       
  1836   }
       
  1837 
       
  1838   /**
       
  1839    * Test the determination of $form_state['triggering_element'] when no button
       
  1840    * information is included in the POST data, as is sometimes the case when
       
  1841    * the ENTER key is pressed in a textfield in Internet Explorer.
       
  1842    */
       
  1843   function testNoButtonInfoInPost() {
       
  1844     $path = 'form-test/clicked-button';
       
  1845     $edit = array();
       
  1846     $form_html_id = 'form-test-clicked-button';
       
  1847 
       
  1848     // Ensure submitting a form with no buttons results in no
       
  1849     // $form_state['triggering_element'] and the form submit handler not
       
  1850     // running.
       
  1851     $this->drupalPost($path, $edit, NULL, array(), array(), $form_html_id);
       
  1852     $this->assertText('There is no clicked button.', '$form_state[\'triggering_element\'] set to NULL.');
       
  1853     $this->assertNoText('Submit handler for form_test_clicked_button executed.', 'Form submit handler did not execute.');
       
  1854 
       
  1855     // Ensure submitting a form with one or more submit buttons results in
       
  1856     // $form_state['triggering_element'] being set to the first one the user has
       
  1857     // access to. An argument with 'r' in it indicates a restricted
       
  1858     // (#access=FALSE) button.
       
  1859     $this->drupalPost($path . '/s', $edit, NULL, array(), array(), $form_html_id);
       
  1860     $this->assertText('The clicked button is button1.', '$form_state[\'triggering_element\'] set to only button.');
       
  1861     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
       
  1862 
       
  1863     $this->drupalPost($path . '/s/s', $edit, NULL, array(), array(), $form_html_id);
       
  1864     $this->assertText('The clicked button is button1.', '$form_state[\'triggering_element\'] set to first button.');
       
  1865     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
       
  1866 
       
  1867     $this->drupalPost($path . '/rs/s', $edit, NULL, array(), array(), $form_html_id);
       
  1868     $this->assertText('The clicked button is button2.', '$form_state[\'triggering_element\'] set to first available button.');
       
  1869     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
       
  1870 
       
  1871     // Ensure submitting a form with buttons of different types results in
       
  1872     // $form_state['triggering_element'] being set to the first button,
       
  1873     // regardless of type. For the FAPI 'button' type, this should result in the
       
  1874     // submit handler not executing. The types are 's'(ubmit), 'b'(utton), and
       
  1875     // 'i'(mage_button).
       
  1876     $this->drupalPost($path . '/s/b/i', $edit, NULL, array(), array(), $form_html_id);
       
  1877     $this->assertText('The clicked button is button1.', '$form_state[\'triggering_element\'] set to first button.');
       
  1878     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
       
  1879 
       
  1880     $this->drupalPost($path . '/b/s/i', $edit, NULL, array(), array(), $form_html_id);
       
  1881     $this->assertText('The clicked button is button1.', '$form_state[\'triggering_element\'] set to first button.');
       
  1882     $this->assertNoText('Submit handler for form_test_clicked_button executed.', 'Form submit handler did not execute.');
       
  1883 
       
  1884     $this->drupalPost($path . '/i/s/b', $edit, NULL, array(), array(), $form_html_id);
       
  1885     $this->assertText('The clicked button is button1.', '$form_state[\'triggering_element\'] set to first button.');
       
  1886     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
       
  1887   }
       
  1888 
       
  1889   /**
       
  1890    * Test that $form_state['triggering_element'] does not get set to a button
       
  1891    * with #access=FALSE.
       
  1892    */
       
  1893   function testAttemptAccessControlBypass() {
       
  1894     $path = 'form-test/clicked-button';
       
  1895     $form_html_id = 'form-test-clicked-button';
       
  1896 
       
  1897     // Retrieve a form where 'button1' has #access=FALSE and 'button2' doesn't.
       
  1898     $this->drupalGet($path . '/rs/s');
       
  1899 
       
  1900     // Submit the form with 'button1=button1' in the POST data, which someone
       
  1901     // trying to get around security safeguards could easily do. We have to do
       
  1902     // a little trickery here, to work around the safeguards in drupalPost(): by
       
  1903     // renaming the text field that is in the form to 'button1', we can get the
       
  1904     // data we want into $_POST.
       
  1905     $elements = $this->xpath('//form[@id="' . $form_html_id . '"]//input[@name="text"]');
       
  1906     $elements[0]['name'] = 'button1';
       
  1907     $this->drupalPost(NULL, array('button1' => 'button1'), NULL, array(), array(), $form_html_id);
       
  1908 
       
  1909     // Ensure that $form_state['triggering_element'] was not set to the
       
  1910     // restricted button. Do this with both a negative and positive assertion,
       
  1911     // because negative assertions alone can be brittle. See
       
  1912     // testNoButtonInfoInPost() for why the triggering element gets set to
       
  1913     // 'button2'.
       
  1914     $this->assertNoText('The clicked button is button1.', '$form_state[\'triggering_element\'] not set to a restricted button.');
       
  1915     $this->assertText('The clicked button is button2.', '$form_state[\'triggering_element\'] not set to a restricted button.');
       
  1916   }
       
  1917 }
       
  1918 
       
  1919 /**
       
  1920  * Tests rebuilding of arbitrary forms by altering them.
       
  1921  */
       
  1922 class FormsArbitraryRebuildTestCase extends DrupalWebTestCase {
       
  1923   public static function getInfo() {
       
  1924     return array(
       
  1925       'name' => 'Rebuild arbitrary forms',
       
  1926       'description' => 'Tests altering forms to be rebuilt so there are multiple steps.',
       
  1927       'group' => 'Form API',
       
  1928     );
       
  1929   }
       
  1930 
       
  1931   function setUp() {
       
  1932     parent::setUp('form_test');
       
  1933     // Auto-create a field for testing.
       
  1934     $field = array(
       
  1935       'field_name' => 'test_multiple',
       
  1936       'type' => 'text',
       
  1937       'cardinality' => -1,
       
  1938       'translatable' => FALSE,
       
  1939     );
       
  1940     field_create_field($field);
       
  1941 
       
  1942     $instance = array(
       
  1943       'entity_type' => 'node',
       
  1944       'field_name' => 'test_multiple',
       
  1945       'bundle' => 'page',
       
  1946       'label' => 'Test a multiple valued field',
       
  1947       'widget' => array(
       
  1948         'type' => 'text_textfield',
       
  1949         'weight' => 0,
       
  1950       ),
       
  1951     );
       
  1952     field_create_instance($instance);
       
  1953     variable_set('user_register', USER_REGISTER_VISITORS);
       
  1954   }
       
  1955 
       
  1956   /**
       
  1957    * Tests a basic rebuild with the user registration form.
       
  1958    */
       
  1959   function testUserRegistrationRebuild() {
       
  1960     $edit = array(
       
  1961       'name' => 'foo',
       
  1962       'mail' => 'bar@example.com',
       
  1963     );
       
  1964     $this->drupalPost('user/register', $edit, 'Rebuild');
       
  1965     $this->assertText('Form rebuilt.');
       
  1966     $this->assertFieldByName('name', 'foo', 'Entered user name has been kept.');
       
  1967     $this->assertFieldByName('mail', 'bar@example.com', 'Entered mail address has been kept.');
       
  1968   }
       
  1969 
       
  1970   /**
       
  1971    * Tests a rebuild caused by a multiple value field.
       
  1972    */
       
  1973   function testUserRegistrationMultipleField() {
       
  1974     $edit = array(
       
  1975       'name' => 'foo',
       
  1976       'mail' => 'bar@example.com',
       
  1977     );
       
  1978     $this->drupalPost('user/register', $edit, t('Add another item'), array('query' => array('field' => TRUE)));
       
  1979     $this->assertText('Test a multiple valued field', 'Form has been rebuilt.');
       
  1980     $this->assertFieldByName('name', 'foo', 'Entered user name has been kept.');
       
  1981     $this->assertFieldByName('mail', 'bar@example.com', 'Entered mail address has been kept.');
       
  1982   }
       
  1983 }
       
  1984 
       
  1985 /**
       
  1986  * Tests form API file inclusion.
       
  1987  */
       
  1988 class FormsFileInclusionTestCase extends DrupalWebTestCase {
       
  1989 
       
  1990   public static function getInfo() {
       
  1991     return array(
       
  1992       'name' => 'Form API file inclusion',
       
  1993       'description' => 'Tests form API file inclusion.',
       
  1994       'group' => 'Form API',
       
  1995     );
       
  1996   }
       
  1997 
       
  1998   function setUp() {
       
  1999     parent::setUp('form_test');
       
  2000   }
       
  2001 
       
  2002   /**
       
  2003    * Tests loading an include specified in hook_menu().
       
  2004    */
       
  2005   function testLoadMenuInclude() {
       
  2006     $this->drupalPostAJAX('form-test/load-include-menu', array(), array('op' => t('Save')), 'system/ajax', array(), array(), 'form-test-load-include-menu');
       
  2007     $this->assertText('Submit callback called.');
       
  2008   }
       
  2009 
       
  2010   /**
       
  2011    * Tests loading a custom specified inlcude.
       
  2012    */
       
  2013   function testLoadCustomInclude() {
       
  2014     $this->drupalPost('form-test/load-include-custom', array(), t('Save'));
       
  2015     $this->assertText('Submit callback called.');
       
  2016   }
       
  2017 }
       
  2018 
       
  2019 /**
       
  2020  * Tests checkbox element.
       
  2021  */
       
  2022 class FormCheckboxTestCase extends DrupalWebTestCase {
       
  2023 
       
  2024   public static function getInfo() {
       
  2025     return array(
       
  2026       'name' => 'Form API checkbox',
       
  2027       'description' => 'Tests form API checkbox handling of various combinations of #default_value and #return_value.',
       
  2028       'group' => 'Form API',
       
  2029     );
       
  2030   }
       
  2031 
       
  2032   function setUp() {
       
  2033     parent::setUp('form_test');
       
  2034   }
       
  2035 
       
  2036   function testFormCheckbox() {
       
  2037     // Ensure that the checked state is determined and rendered correctly for
       
  2038     // tricky combinations of default and return values.
       
  2039     foreach (array(FALSE, NULL, TRUE, 0, '0', '', 1, '1', 'foobar', '1foobar') as $default_value) {
       
  2040       // Only values that can be used for array indeces are supported for
       
  2041       // #return_value, with the exception of integer 0, which is not supported.
       
  2042       // @see form_process_checkbox().
       
  2043       foreach (array('0', '', 1, '1', 'foobar', '1foobar') as $return_value) {
       
  2044         $form_array = drupal_get_form('form_test_checkbox_type_juggling', $default_value, $return_value);
       
  2045         $form = drupal_render($form_array);
       
  2046         if ($default_value === TRUE) {
       
  2047           $checked = TRUE;
       
  2048         }
       
  2049         elseif ($return_value === '0') {
       
  2050           $checked = ($default_value === '0');
       
  2051         }
       
  2052         elseif ($return_value === '') {
       
  2053           $checked = ($default_value === '');
       
  2054         }
       
  2055         elseif ($return_value === 1 || $return_value === '1') {
       
  2056           $checked = ($default_value === 1 || $default_value === '1');
       
  2057         }
       
  2058         elseif ($return_value === 'foobar') {
       
  2059           $checked = ($default_value === 'foobar');
       
  2060         }
       
  2061         elseif ($return_value === '1foobar') {
       
  2062           $checked = ($default_value === '1foobar');
       
  2063         }
       
  2064         $checked_in_html = strpos($form, 'checked') !== FALSE;
       
  2065         $message = format_string('#default_value is %default_value #return_value is %return_value.', array('%default_value' => var_export($default_value, TRUE), '%return_value' => var_export($return_value, TRUE)));
       
  2066         $this->assertIdentical($checked, $checked_in_html, $message);
       
  2067       }
       
  2068     }
       
  2069 
       
  2070     // Ensure that $form_state['values'] is populated correctly for a checkboxes
       
  2071     // group that includes a 0-indexed array of options.
       
  2072     $results = json_decode($this->drupalPost('form-test/checkboxes-zero', array(), 'Save'));
       
  2073     $this->assertIdentical($results->checkbox_off, array(0, 0, 0), 'All three in checkbox_off are zeroes: off.');
       
  2074     $this->assertIdentical($results->checkbox_zero_default, array('0', 0, 0), 'The first choice is on in checkbox_zero_default');
       
  2075     $this->assertIdentical($results->checkbox_string_zero_default, array('0', 0, 0), 'The first choice is on in checkbox_string_zero_default');
       
  2076     $edit = array('checkbox_off[0]' => '0');
       
  2077     $results = json_decode($this->drupalPost('form-test/checkboxes-zero', $edit, 'Save'));
       
  2078     $this->assertIdentical($results->checkbox_off, array('0', 0, 0), 'The first choice is on in checkbox_off but the rest is not');
       
  2079 
       
  2080     // Ensure that each checkbox is rendered correctly for a checkboxes group
       
  2081     // that includes a 0-indexed array of options.
       
  2082     $this->drupalPost('form-test/checkboxes-zero/0', array(), 'Save');
       
  2083     $checkboxes = $this->xpath('//input[@type="checkbox"]');
       
  2084     foreach ($checkboxes as $checkbox) {
       
  2085       $checked = isset($checkbox['checked']);
       
  2086       $name = (string) $checkbox['name'];
       
  2087       $this->assertIdentical($checked, $name == 'checkbox_zero_default[0]' || $name == 'checkbox_string_zero_default[0]', format_string('Checkbox %name correctly checked', array('%name' => $name)));
       
  2088     }
       
  2089     $edit = array('checkbox_off[0]' => '0');
       
  2090     $this->drupalPost('form-test/checkboxes-zero/0', $edit, 'Save');
       
  2091     $checkboxes = $this->xpath('//input[@type="checkbox"]');
       
  2092     foreach ($checkboxes as $checkbox) {
       
  2093       $checked = isset($checkbox['checked']);
       
  2094       $name = (string) $checkbox['name'];
       
  2095       $this->assertIdentical($checked, $name == 'checkbox_off[0]' || $name == 'checkbox_zero_default[0]' || $name == 'checkbox_string_zero_default[0]', format_string('Checkbox %name correctly checked', array('%name' => $name)));
       
  2096     }
       
  2097   }
       
  2098 }
       
  2099 
       
  2100 /**
       
  2101  * Tests uniqueness of generated HTML IDs.
       
  2102  */
       
  2103 class HTMLIdTestCase extends DrupalWebTestCase {
       
  2104 
       
  2105   public static function getInfo() {
       
  2106     return array(
       
  2107       'name' => 'Unique HTML IDs',
       
  2108       'description' => 'Tests functionality of drupal_html_id().',
       
  2109       'group' => 'Form API',
       
  2110     );
       
  2111   }
       
  2112 
       
  2113   function setUp() {
       
  2114     parent::setUp('form_test');
       
  2115   }
       
  2116 
       
  2117   /**
       
  2118    * Tests that HTML IDs do not get duplicated when form validation fails.
       
  2119    */
       
  2120   function testHTMLId() {
       
  2121     $this->drupalGet('form-test/double-form');
       
  2122     $this->assertNoDuplicateIds('There are no duplicate IDs');
       
  2123 
       
  2124     // Submit second form with empty title.
       
  2125     $edit = array();
       
  2126     $this->drupalPost(NULL, $edit, 'Save', array(), array(), 'form-test-html-id--2');
       
  2127     $this->assertNoDuplicateIds('There are no duplicate IDs');
       
  2128   }
       
  2129 }
       
  2130 
       
  2131 /**
       
  2132  * Tests for form textarea.
       
  2133  */
       
  2134 class FormTextareaTestCase extends DrupalUnitTestCase {
       
  2135 
       
  2136   public static function getInfo() {
       
  2137     return array(
       
  2138       'name' => 'Form textarea',
       
  2139       'description' => 'Tests form textarea related functions.',
       
  2140       'group' => 'Form API',
       
  2141     );
       
  2142   }
       
  2143 
       
  2144   /**
       
  2145    * Tests that textarea value is properly set.
       
  2146    */
       
  2147   public function testValueCallback() {
       
  2148     $element = array();
       
  2149     $form_state = array();
       
  2150     $test_cases = array(
       
  2151       array(NULL, FALSE),
       
  2152       array(NULL, NULL),
       
  2153       array('', array('test')),
       
  2154       array('test', 'test'),
       
  2155       array('123', 123),
       
  2156     );
       
  2157     foreach ($test_cases as $test_case) {
       
  2158       list($expected, $input) = $test_case;
       
  2159       $this->assertIdentical($expected, form_type_textarea_value($element, $input, $form_state));
       
  2160     }
       
  2161   }
       
  2162 }