You are here

form.test in SimpleTest 7

Unit tests for the Drupal Form API.

File

tests/form.test
View source
<?php

/**
 * @file
 * Unit tests for the Drupal Form API.
 */
class FormsTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Required field validation',
      'description' => 'Carriage returns, tabs, and spaces are not valid content for a required field.',
      'group' => 'Form API',
    );
  }

  /**
   * Check several empty values for required forms elements.
   *
   * If the form field is found in form_get_errors() then the test pass.
   */
  function testRequiredFields() {

    // Originates from http://drupal.org/node/117748
    // Sets of empty strings and arrays
    $empty_strings = array(
      '""' => "",
      '"\\n"' => "\n",
      '" "' => " ",
      '"\\t"' => "\t",
      '" \\n\\t "' => " \n\t ",
      '"\\n\\n\\n\\n\\n"' => "\n\n\n\n\n",
    );
    $empty_arrays = array(
      'array()' => array(),
    );
    $elements['textfield']['element'] = array(
      '#title' => $this
        ->randomName(),
      '#type' => 'textfield',
    );
    $elements['textfield']['empty_values'] = $empty_strings;
    $elements['password']['element'] = array(
      '#title' => $this
        ->randomName(),
      '#type' => 'password',
    );
    $elements['password']['empty_values'] = $empty_strings;
    $elements['password_confirm']['element'] = array(
      '#title' => $this
        ->randomName(),
      '#type' => 'password_confirm',
    );
    $elements['password_confirm']['empty_values'] = $empty_strings;
    $elements['textarea']['element'] = array(
      '#title' => $this
        ->randomName(),
      '#type' => 'textarea',
    );
    $elements['textarea']['empty_values'] = $empty_strings;
    $elements['radios']['element'] = array(
      '#title' => $this
        ->randomName(),
      '#type' => 'radios',
      '#options' => array(
        $this
          ->randomName(),
        $this
          ->randomName(),
        $this
          ->randomName(),
      ),
    );
    $elements['radios']['empty_values'] = $empty_arrays;
    $elements['checkboxes']['element'] = array(
      '#title' => $this
        ->randomName(),
      '#type' => 'checkboxes',
      '#options' => array(
        $this
          ->randomName(),
        $this
          ->randomName(),
        $this
          ->randomName(),
      ),
    );
    $elements['checkboxes']['empty_values'] = $empty_arrays;
    $elements['select']['element'] = array(
      '#title' => $this
        ->randomName(),
      '#type' => 'select',
      '#options' => array(
        $this
          ->randomName(),
        $this
          ->randomName(),
        $this
          ->randomName(),
      ),
    );
    $elements['select']['empty_values'] = $empty_strings;
    $elements['file']['element'] = array(
      '#title' => $this
        ->randomName(),
      '#type' => 'file',
    );
    $elements['file']['empty_values'] = $empty_strings;

    // Go through all the elements and all the empty values for them
    foreach ($elements as $type => $data) {
      foreach ($data['empty_values'] as $key => $empty) {
        foreach (array(
          TRUE,
          FALSE,
        ) as $required) {
          $form_id = $this
            ->randomName();
          $form = $form_state = array();
          form_clear_error();
          $form['op'] = array(
            '#type' => 'submit',
            '#value' => t('Submit'),
          );
          $element = $data['element']['#title'];
          $form[$element] = $data['element'];
          $form[$element]['#required'] = $required;
          $form_state['values'][$element] = $empty;
          $form_state['input'] = $form_state['values'];
          $form_state['input']['form_id'] = $form_id;
          $form_state['method'] = 'post';
          drupal_prepare_form($form_id, $form, $form_state);
          drupal_process_form($form_id, $form, $form_state);
          $errors = form_get_errors();
          if ($required) {

            // Make sure we have a form error for this element.
            $this
              ->assertTrue(isset($errors[$element]), "Check empty({$key}) '{$type}' field '{$element}'");
          }
          elseif ($type == 'select') {

            // Select elements are going to have validation errors with empty
            // input, since those are illegal choices. Just make sure the
            // error is not "field is required".
            $this
              ->assertTrue(empty($errors[$element]) || strpos('field is required', $errors[$element]) === FALSE, "Optional '{$type}' field '{$element}' is not treated as a required element");
          }
          else {

            // Make sure there is *no* form error for this element.
            $this
              ->assertTrue(empty($errors[$element]), "Optional '{$type}' field '{$element}' has no errors with empty input");
          }
        }
      }
    }

    // Clear the expected form error messages so they don't appear as exceptions.
    drupal_get_messages();
  }

}

/**
 * Test form type functions for expected behavior.
 */
class FormsTestTypeCase extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Form type-specific tests',
      'description' => 'Test form type functions for expected behavior.',
      'group' => 'Form API',
    );
  }

  /**
   * Test form_type_checkbox_value() function for expected behavior.
   */
  function testFormCheckboxValue() {
    $form['#return_value'] = $return_value = $this
      ->randomName();
    $form['#default_value'] = $default_value = $this
      ->randomName();

    // Element is disabled , and $edit is not empty.
    $form['#disabled'] = TRUE;
    $edit = array(
      1,
    );
    $this
      ->assertEqual(form_type_checkbox_value($form, $edit), $default_value, t('form_type_checkbox_value() returns the default value when #disabled is set.'));

    // Element is not disabled, $edit is not empty.
    unset($form['#disabled']);
    $this
      ->assertEqual(form_type_checkbox_value($form, $edit), $return_value, t('form_type_checkbox_value() returns the return value when #disabled is not set.'));

    // Element is not disabled, $edit is empty.
    $edit = array();
    $this
      ->assertIdentical(form_type_checkbox_value($form, $edit), 0, t('form_type_checkbox_value() returns 0 when #disabled is not set, and $edit is empty.'));

    // $edit is FALSE.
    $edit = FALSE;
    $this
      ->assertNull(form_type_checkbox_value($form, $edit), t('form_type_checkbox_value() returns NULL when $edit is FALSE'));
  }

}

/**
 * Test the tableselect form element for expected behavior.
 */
class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Tableselect form element type test',
      'description' => 'Test the tableselect element for expected behavior',
      'group' => 'Form API',
    );
  }
  function setUp() {
    parent::setUp('form_test');
  }

  /**
   * Test the display of checkboxes when #multiple is TRUE.
   */
  function testMultipleTrue() {
    $this
      ->drupalGet('form_test/tableselect/multiple-true');
    $this
      ->assertNoText(t('Empty text.'), t('Empty text should not be displayed.'));

    // Test for the presence of the Select all rows tableheader.
    $this
      ->assertFieldByXPath('//th[@class="select-all"]', NULL, t('Presence of the "Select all" checkbox.'));
    $rows = array(
      'row1',
      'row2',
      'row3',
    );
    foreach ($rows as $row) {
      $this
        ->assertFieldByXPath('//input[@type="checkbox"]', $row, t('Checkbox for value @row.', array(
        '@row' => $row,
      )));
    }
  }

  /**
   * Test the display of radios when #multiple is FALSE.
   */
  function testMultipleFalse() {
    $this
      ->drupalGet('form_test/tableselect/multiple-false');
    $this
      ->assertNoText(t('Empty text.'), t('Empty text should not be displayed.'));

    // Test for the absence of the Select all rows tableheader.
    $this
      ->assertNoFieldByXPath('//th[@class="select-all"]', '', t('Absence of the "Select all" checkbox.'));
    $rows = array(
      'row1',
      'row2',
      'row3',
    );
    foreach ($rows as $row) {
      $this
        ->assertFieldByXPath('//input[@type="radio"]', $row, t('Radio button for value @row.', array(
        '@row' => $row,
      )));
    }
  }

  /**
   * Test the display of the #empty text when #options is an empty array.
   */
  function testEmptyText() {
    $this
      ->drupalGet('form_test/tableselect/empty-text');
    $this
      ->assertText(t('Empty text.'), t('Empty text should be displayed.'));
  }

  /**
   * Test the submission of single and multiple values when #multiple is TRUE.
   */
  function testMultipleTrueSubmit() {

    // Test a submission with one checkbox checked.
    $edit = array();
    $edit['tableselect[row1]'] = TRUE;
    $this
      ->drupalPost('form_test/tableselect/multiple-true', $edit, 'Submit');
    $this
      ->assertText(t('Submitted: row1 = row1'), t('Checked checkbox row1'));
    $this
      ->assertText(t('Submitted: row2 = 0'), t('Unchecked checkbox row2.'));
    $this
      ->assertText(t('Submitted: row3 = 0'), t('Unchecked checkbox row3.'));

    // Test a submission with multiple checkboxes checked.
    $edit['tableselect[row1]'] = TRUE;
    $edit['tableselect[row3]'] = TRUE;
    $this
      ->drupalPost('form_test/tableselect/multiple-true', $edit, 'Submit');
    $this
      ->assertText(t('Submitted: row1 = row1'), t('Checked checkbox row1.'));
    $this
      ->assertText(t('Submitted: row2 = 0'), t('Unchecked checkbox row2.'));
    $this
      ->assertText(t('Submitted: row3 = row3'), t('Checked checkbox row3.'));
  }

  /**
   * Test submission of values when #multiple is FALSE.
   */
  function testMultipleFalseSubmit() {
    $edit['tableselect'] = 'row1';
    $this
      ->drupalPost('form_test/tableselect/multiple-false', $edit, 'Submit');
    $this
      ->assertText(t('Submitted: row1'), t('Selected radio button'));
  }

  /**
   * Test the #js_select property.
   */
  function testAdvancedSelect() {

    // When #multiple = TRUE a Select all checkbox should be displayed by default.
    $this
      ->drupalGet('form_test/tableselect/advanced-select/multiple-true-default');
    $this
      ->assertFieldByXPath('//th[@class="select-all"]', NULL, t('Display a "Select all" checkbox by default when #multiple is TRUE.'));

    // When #js_select is set to FALSE, a "Select all" checkbox should not be displayed.
    $this
      ->drupalGet('form_test/tableselect/advanced-select/multiple-true-no-advanced-select');
    $this
      ->assertNoFieldByXPath('//th[@class="select-all"]', NULL, t('Do not display a "Select all" checkbox when #js_select is FALSE.'));

    // A "Select all" checkbox never makes sense when #multiple = FALSE, regardless of the value of #js_select.
    $this
      ->drupalGet('form_test/tableselect/advanced-select/multiple-false-default');
    $this
      ->assertNoFieldByXPath('//th[@class="select-all"]', NULL, t('Do not display a "Select all" checkbox when #multiple is FALSE.'));
    $this
      ->drupalGet('form_test/tableselect/advanced-select/multiple-false-advanced-select');
    $this
      ->assertNoFieldByXPath('//th[@class="select-all"]', NULL, t('Do not display a "Select all" checkbox when #multiple is FALSE, even when #js_select is TRUE.'));
  }

  /**
   * Test the whether the option checker gives an error on invalid tableselect values for checkboxes.
   */
  function testMultipleTrueOptionchecker() {
    list($header, $options) = _form_test_tableselect_get_data();
    $form['tableselect'] = array(
      '#type' => 'tableselect',
      '#header' => $header,
      '#options' => $options,
    );

    // Test with a valid value.
    list($processed_form, $form_state, $errors) = $this
      ->formSubmitHelper($form, array(
      'tableselect' => 'row1',
    ));
    $this
      ->assertFalse(isset($errors['tableselect']), t('Option checker allows valid values for checkboxes.'));

    // Test with an invalid value.
    list($processed_form, $form_state, $errors) = $this
      ->formSubmitHelper($form, array(
      'tableselect' => 'non_existing_value',
    ));
    $this
      ->assertTrue(isset($errors['tableselect']), t('Option checker disallows invalid values for checkboxes.'));
  }

  /**
   * Test the whether the option checker gives an error on invalid tableselect values for radios.
   */
  function testMultipleFalseOptionchecker() {
    list($header, $options) = _form_test_tableselect_get_data();
    $form['tableselect'] = array(
      '#type' => 'tableselect',
      '#header' => $header,
      '#options' => $options,
      '#multiple' => FALSE,
    );

    // Test with a valid value.
    list($processed_form, $form_state, $errors) = $this
      ->formSubmitHelper($form, array(
      'tableselect' => 'row1',
    ));
    $this
      ->assertFalse(isset($errors['tableselect']), t('Option checker allows valid values for radio buttons.'));

    // Test with an invalid value.
    list($processed_form, $form_state, $errors) = $this
      ->formSubmitHelper($form, array(
      'tableselect' => 'non_existing_value',
    ));
    $this
      ->assertTrue(isset($errors['tableselect']), t('Option checker disallows invalid values for radio buttons.'));
  }

  /**
   * Helper function for the option check test to submit a form while collecting errors.
   *
   * @param $form_element
   *   A form element to test.
   * @param $edit
   *   An array containing post data.
   *
   * @return
   *   An array containing the processed form, the form_state and any errors.
   */
  private function formSubmitHelper($form_element, $edit) {
    $form_id = $this
      ->randomName();
    $form_state = form_state_defaults();
    $form = array();
    $form = array_merge($form, $form_element);
    $form['op'] = array(
      '#type' => 'submit',
      '#value' => t('Submit'),
    );
    $form_state['input'] = $edit;
    $form_state['input']['form_id'] = $form_id;
    drupal_prepare_form($form_id, $form, $form_state);
    drupal_process_form($form_id, $form, $form_state);
    $errors = form_get_errors();

    // Clear errors and messages.
    drupal_get_messages();
    form_clear_error();

    // Return the processed form together with form_state and errors
    // to allow the caller lowlevel access to the form.
    return array(
      $form,
      $form_state,
      $errors,
    );
  }

}

/**
 * Test using drupal_form_submit in a batch.
 */
class FormAPITestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Drupal Execute and Batch API',
      'description' => 'Tests the compatibility of drupal_form_submit and the Batch API',
      'group' => 'Form API',
    );
  }

  /**
   * Check that we can run drupal_form_submit during a batch.
   */
  function testDrupalFormSubmitInBatch() {

    // Our test is going to modify the following variable.
    variable_set('form_test_mock_submit', 'initial_state');

    // This is a page that sets a batch, which calls drupal_form_submit, which
    // modifies the variable we set up above.
    $this
      ->drupalGet('form_test/drupal_form_submit_batch_api');

    // If the drupal_form_submit call executed correctly our test variable will be
    // set to 'form_submitted'.
    $this
      ->assertEqual('form_submitted', variable_get('form_test_mock_submit', 'initial_state'), t('Check drupal_form_submit called submit handlers when running in a batch'));

    // Clean our variable up.
    variable_del('form_test_mock_submit');
  }
  function setUp() {
    parent::setUp('form_test');
  }

}

/**
 * Test the form storage on a multistep form.
 *
 * The tested form puts data into the storage during the initial form
 * construction. These tests verify that there are no duplicate form
 * constructions, with and without manual form caching activiated. Furthermore
 * when a validation error occurs, it makes sure that changed form element
 * values aren't lost due to a wrong form rebuild.
 */
class FormsFormStorageTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Multistep form using form storage',
      'description' => 'Tests a multistep form using form storage and makes sure validation and caching works right.',
      'group' => 'Form API',
    );
  }
  function setUp() {
    parent::setUp('form_test');
  }

  /**
   * Tests using the form in a usual way.
   */
  function testForm() {
    $user = $this
      ->drupalCreateUser(array(
      'access content',
    ));
    $this
      ->drupalLogin($user);
    $this
      ->drupalPost('form_test/form-storage', array(
      'title' => 'new',
      'value' => 'value_is_set',
    ), 'Continue');
    $this
      ->assertText('Form constructions: 2', t('The form has been constructed two times till now.'));
    $this
      ->drupalPost(NULL, array(), 'Save');
    $this
      ->assertText('Form constructions: 3', t('The form has been constructed three times till now.'));
    $this
      ->assertText('Title: new', t('The form storage has stored the values.'));
  }

  /**
   * Tests using the form with an activated #cache property.
   */
  function testFormCached() {
    $user = $this
      ->drupalCreateUser(array(
      'access content',
    ));
    $this
      ->drupalLogin($user);
    $this
      ->drupalPost('form_test/form-storage', array(
      'title' => 'new',
      'value' => 'value_is_set',
    ), 'Continue', array(
      'query' => array(
        'cache' => 1,
      ),
    ));
    $this
      ->assertText('Form constructions: 1', t('The form has been constructed one time till now.'));
    $this
      ->drupalPost(NULL, array(), 'Save', array(
      'query' => array(
        'cache' => 1,
      ),
    ));
    $this
      ->assertText('Form constructions: 2', t('The form has been constructed two times till now.'));
    $this
      ->assertText('Title: new', t('The form storage has stored the values.'));
  }

  /**
   * Tests validation when form storage is used.
   */
  function testValidation() {
    $user = $this
      ->drupalCreateUser(array(
      'access content',
    ));
    $this
      ->drupalLogin($user);
    $this
      ->drupalPost('form_test/form-storage', array(
      'title' => '',
      'value' => 'value_is_set',
    ), 'Continue');
    $this
      ->assertPattern('/value_is_set/', t("The input values have been kept."));
  }

}

/**
 * Test wrapper form callbacks.
 */
class FormsFormWrapperTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Form wrapper callback',
      'description' => 'Tests form wrapper callbacks to pass a prebuilt form to form builder functions.',
      'group' => 'Form API',
    );
  }
  function setUp() {
    parent::setUp('form_test');
  }

  /**
   * Tests using the form in a usual way.
   */
  function testWrapperCallback() {
    $this
      ->drupalGet('form_test/wrapper-callback');
    $this
      ->assertText('Form wrapper callback element output.', t('The form contains form wrapper elements.'));
    $this
      ->assertText('Form builder element output.', t('The form contains form builder elements.'));
  }

}

/**
 * Test $form_state clearance.
 */
class FormStateValuesCleanTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Form state values clearance',
      'description' => 'Test proper removal of submitted form values using form_state_values_clean().',
      'group' => 'Form API',
    );
  }
  function setUp() {
    parent::setUp('form_test');
  }

  /**
   * Tests form_state_values_clean().
   */
  function testFormStateValuesClean() {
    $this
      ->drupalPost('form_test/form-state-values-clean', array(), t('Submit'));
    $values = json_decode($this->content, TRUE);

    // Setup the expected result.
    $result = array(
      'beer' => 1000,
      'baz' => array(
        'beer' => 2000,
      ),
    );

    // Verify that all internal Form API elements were removed.
    $this
      ->assertFalse(isset($values['form_id']), t('%element was removed.', array(
      '%element' => 'form_id',
    )));
    $this
      ->assertFalse(isset($values['form_token']), t('%element was removed.', array(
      '%element' => 'form_token',
    )));
    $this
      ->assertFalse(isset($values['form_build_id']), t('%element was removed.', array(
      '%element' => 'form_build_id',
    )));
    $this
      ->assertFalse(isset($values['op']), t('%element was removed.', array(
      '%element' => 'op',
    )));

    // Verify that all buttons were removed.
    $this
      ->assertFalse(isset($values['foo']), t('%element was removed.', array(
      '%element' => 'foo',
    )));
    $this
      ->assertFalse(isset($values['bar']), t('%element was removed.', array(
      '%element' => 'bar',
    )));
    $this
      ->assertFalse(isset($values['baz']['foo']), t('%element was removed.', array(
      '%element' => 'foo',
    )));
    $this
      ->assertFalse(isset($values['baz']['baz']), t('%element was removed.', array(
      '%element' => 'baz',
    )));

    // Verify that nested form value still exists.
    $this
      ->assertTrue(isset($values['baz']['beer']), t('Nested form value still exists.'));

    // Verify that actual form values equal resulting form values.
    $this
      ->assertEqual($values, $result, t('Expected form values equal actual form values.'));
  }

}

Classes

Namesort descending Description
FormAPITestCase Test using drupal_form_submit in a batch.
FormsElementsTableSelectFunctionalTest Test the tableselect form element for expected behavior.
FormsFormStorageTestCase Test the form storage on a multistep form.
FormsFormWrapperTestCase Test wrapper form callbacks.
FormStateValuesCleanTestCase Test $form_state clearance.
FormsTestCase @file Unit tests for the Drupal Form API.
FormsTestTypeCase Test form type functions for expected behavior.