Newer
Older
<?php
/**
* @file
* Unit tests for the Drupal Form API.
*/
class FormsTestCase extends DrupalWebTestCase {
Angie Byron
committed
public static function getInfo() {
return array(
'name' => 'Form element validation',
'description' => 'Tests various form element validation mechanisms.',
'group' => 'Form API',
);
}
function setUp() {
parent::setUp(array('form_test', 'file'));
$filtered_html_format = array(
'format' => 'filtered_html',
'name' => 'Filtered HTML',
);
$filtered_html_format = (object) $filtered_html_format;
filter_format_save($filtered_html_format);
$filtered_html_permission = filter_permission_name($filtered_html_format);
user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array($filtered_html_permission));
}
/**
* Check several empty values for required forms elements.
*
* Carriage returns, tabs, spaces, and unchecked checkbox elements are not
* valid content for a required field.
*
* 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());
$empty_checkbox = array(NULL);
Dries Buytaert
committed
$elements['textfield']['element'] = array('#title' => $this->randomName(), '#type' => 'textfield');
$elements['textfield']['empty_values'] = $empty_strings;
$elements['telephone']['element'] = array('#title' => $this->randomName(), '#type' => 'tel');
$elements['telephone']['empty_values'] = $empty_strings;
catch
committed
$elements['url']['element'] = array('#title' => $this->randomName(), '#type' => 'url');
$elements['url']['empty_values'] = $empty_strings;
Dries Buytaert
committed
$elements['password']['element'] = array('#title' => $this->randomName(), '#type' => 'password');
$elements['password']['empty_values'] = $empty_strings;
Dries Buytaert
committed
$elements['password_confirm']['element'] = array('#title' => $this->randomName(), '#type' => 'password_confirm');
// Provide empty values for both password fields.
foreach ($empty_strings as $key => $value) {
$elements['password_confirm']['empty_values'][$key] = array('pass1' => $value, 'pass2' => $value);
}
Dries Buytaert
committed
$elements['textarea']['element'] = array('#title' => $this->randomName(), '#type' => 'textarea');
$elements['textarea']['empty_values'] = $empty_strings;
Angie Byron
committed
$elements['radios']['element'] = array('#title' => $this->randomName(), '#type' => 'radios', '#options' => array('' => t('None'), $this->randomName(), $this->randomName(), $this->randomName()));
$elements['radios']['empty_values'] = $empty_arrays;
$elements['checkbox']['element'] = array('#title' => $this->randomName(), '#type' => 'checkbox', '#required' => TRUE, '#title' => $this->randomName());
$elements['checkbox']['empty_values'] = $empty_checkbox;
Dries Buytaert
committed
$elements['checkboxes']['element'] = array('#title' => $this->randomName(), '#type' => 'checkboxes', '#options' => array($this->randomName(), $this->randomName(), $this->randomName()));
$elements['checkboxes']['empty_values'] = $empty_arrays;
Angie Byron
committed
$elements['select']['element'] = array('#title' => $this->randomName(), '#type' => 'select', '#options' => array('' => t('None'), $this->randomName(), $this->randomName(), $this->randomName()));
$elements['select']['empty_values'] = $empty_strings;
Dries Buytaert
committed
$elements['file']['element'] = array('#title' => $this->randomName(), '#type' => 'file');
$elements['file']['empty_values'] = $empty_strings;
Angie Byron
committed
// Regular expression to find the expected marker on required elements.
Dries Buytaert
committed
$required_marker_preg = '@<label.*<abbr class="form-required" title="This field is required\.">\*</abbr></label>@';
Angie Byron
committed
// Go through all the elements and all the empty values for them.
foreach ($elements as $type => $data) {
foreach ($data['empty_values'] as $key => $empty) {
Dries Buytaert
committed
foreach (array(TRUE, FALSE) as $required) {
$form_id = $this->randomName();
Dries Buytaert
committed
$form = array();
$form_state = form_state_defaults();
Dries Buytaert
committed
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['input'][$element] = $empty;
Dries Buytaert
committed
$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();
Angie Byron
committed
// Form elements of type 'radios' throw all sorts of PHP notices
// when you try to render them like this, so we ignore those for
// testing the required marker.
// @todo Fix this work-around (http://drupal.org/node/588438).
$form_output = ($type == 'radios') ? '' : drupal_render($form);
Dries Buytaert
committed
if ($required) {
// Make sure we have a form error for this element.
$this->assertTrue(isset($errors[$element]), "Check empty($key) '$type' field '$element'");
Angie Byron
committed
if (!empty($form_output)) {
// Make sure the form element is marked as required.
$this->assertTrue(preg_match($required_marker_preg, $form_output), "Required '$type' field is marked as required");
}
Dries Buytaert
committed
}
else {
Angie Byron
committed
if (!empty($form_output)) {
// Make sure the form element is *not* marked as required.
$this->assertFalse(preg_match($required_marker_preg, $form_output), "Optional '$type' field is not marked as required");
}
if ($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");
}
Dries Buytaert
committed
}
}
}
}
// Clear the expected form error messages so they don't appear as exceptions.
drupal_get_messages();
}
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**
* Tests validation for required checkbox, select, and radio elements.
*
* Submits a test form containing several types of form elements. The form
* is submitted twice, first without values for required fields and then
* with values. Each submission is checked for relevant error messages.
*
* @see form_test_validate_required_form()
*/
function testRequiredCheckboxesRadio() {
$form = $form_state = array();
$form = form_test_validate_required_form($form, $form_state);
// Attempt to submit the form with no required fields set.
$edit = array();
$this->drupalPost('form-test/validate-required', $edit, 'Submit');
// The only error messages that should appear are the relevant 'required'
// messages for each field.
$expected = array();
foreach (array('textfield', 'checkboxes', 'select', 'radios') as $key) {
$expected[] = t('!name field is required.', array('!name' => $form[$key]['#title']));
}
// Check the page for error messages.
$errors = $this->xpath('//div[contains(@class, "error")]//li');
foreach ($errors as $error) {
$expected_key = array_search($error[0], $expected);
// If the error message is not one of the expected messages, fail.
if ($expected_key === FALSE) {
$this->fail(format_string("Unexpected error message: @error", array('@error' => $error[0])));
}
// Remove the expected message from the list once it is found.
else {
unset($expected[$expected_key]);
}
}
// Fail if any expected messages were not found.
foreach ($expected as $not_found) {
$this->fail(format_string("Found error message: @error", array('@error' => $not_found)));
}
// Verify that input elements are still empty.
$this->assertFieldByName('textfield', '');
$this->assertNoFieldChecked('edit-checkboxes-foo');
$this->assertNoFieldChecked('edit-checkboxes-bar');
$this->assertOptionSelected('edit-select', '');
$this->assertNoFieldChecked('edit-radios-foo');
$this->assertNoFieldChecked('edit-radios-bar');
$this->assertNoFieldChecked('edit-radios-optional-foo');
$this->assertNoFieldChecked('edit-radios-optional-bar');
// Submit again with required fields set and verify that there are no
// error messages.
$edit = array(
'textfield' => $this->randomString(),
'checkboxes[foo]' => TRUE,
'select' => 'foo',
'radios' => 'bar',
);
$this->drupalPost(NULL, $edit, 'Submit');
$this->assertNoFieldByXpath('//div[contains(@class, "error")]', FALSE, 'No error message is displayed when all required fields are filled.');
$this->assertRaw("The form_test_validate_required_form form was submitted successfully.", 'Validation form submitted successfully.');
}
* Test default value handling for checkboxes.
*
Dries Buytaert
committed
* @see _form_test_checkbox()
function testCheckboxProcessing() {
// First, try to submit without the required checkbox.
$edit = array();
$this->drupalPost('form-test/checkbox', $edit, t('Submit'));
$this->assertRaw(t('!name field is required.', array('!name' => 'required_checkbox')), t('A required checkbox is actually mandatory'));
// Now try to submit the form correctly.
Dries Buytaert
committed
$values = drupal_json_decode($this->drupalPost(NULL, array('required_checkbox' => 1), t('Submit')));
$expected_values = array(
'disabled_checkbox_on' => 'disabled_checkbox_on',
'disabled_checkbox_off' => '',
'checkbox_on' => 'checkbox_on',
'checkbox_off' => '',
'zero_checkbox_on' => '0',
'zero_checkbox_off' => '',
);
foreach ($expected_values as $widget => $expected_value) {
$this->assertEqual($values[$widget], $expected_value, t('Checkbox %widget returns expected value (expected: %expected, got: %value)', array(
'%widget' => var_export($widget, TRUE),
'%expected' => var_export($expected_value, TRUE),
'%value' => var_export($values[$widget], TRUE),
)));
}
Angie Byron
committed
Dries Buytaert
committed
/**
* Tests validation of #type 'select' elements.
*/
function testSelect() {
$form = $form_state = array();
$form = form_test_select($form, $form_state);
$error = '!name field is required.';
$this->drupalGet('form-test/select');
// Posting without any values should throw validation errors.
$this->drupalPost(NULL, array(), 'Submit');
$this->assertNoText(t($error, array('!name' => $form['select']['#title'])));
Dries Buytaert
committed
$this->assertNoText(t($error, array('!name' => $form['select_required']['#title'])));
$this->assertNoText(t($error, array('!name' => $form['select_optional']['#title'])));
Dries Buytaert
committed
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
$this->assertNoText(t($error, array('!name' => $form['empty_value']['#title'])));
$this->assertNoText(t($error, array('!name' => $form['empty_value_one']['#title'])));
$this->assertText(t($error, array('!name' => $form['no_default']['#title'])));
$this->assertNoText(t($error, array('!name' => $form['no_default_optional']['#title'])));
$this->assertText(t($error, array('!name' => $form['no_default_empty_option']['#title'])));
$this->assertNoText(t($error, array('!name' => $form['no_default_empty_option_optional']['#title'])));
$this->assertText(t($error, array('!name' => $form['no_default_empty_value']['#title'])));
$this->assertText(t($error, array('!name' => $form['no_default_empty_value_one']['#title'])));
$this->assertNoText(t($error, array('!name' => $form['no_default_empty_value_optional']['#title'])));
$this->assertNoText(t($error, array('!name' => $form['multiple']['#title'])));
$this->assertNoText(t($error, array('!name' => $form['multiple_no_default']['#title'])));
$this->assertText(t($error, array('!name' => $form['multiple_no_default_required']['#title'])));
// Post values for required fields.
$edit = array(
'no_default' => 'three',
'no_default_empty_option' => 'three',
'no_default_empty_value' => 'three',
'no_default_empty_value_one' => 'three',
'multiple_no_default_required[]' => 'three',
);
$this->drupalPost(NULL, $edit, 'Submit');
$values = drupal_json_decode($this->drupalGetContent());
// Verify expected values.
$expected = array(
'select' => 'one',
'empty_value' => 'one',
'empty_value_one' => 'one',
'no_default' => 'three',
Dries Buytaert
committed
'no_default_optional' => 'one',
'no_default_optional_empty_value' => '',
Dries Buytaert
committed
'no_default_empty_option' => 'three',
'no_default_empty_option_optional' => '',
'no_default_empty_value' => 'three',
'no_default_empty_value_one' => 'three',
'no_default_empty_value_optional' => 0,
'multiple' => array('two' => 'two'),
'multiple_no_default' => array(),
'multiple_no_default_required' => array('three' => 'three'),
);
foreach ($expected as $key => $value) {
$this->assertIdentical($values[$key], $value, t('@name: @actual is equal to @expected.', array(
'@name' => $key,
'@actual' => var_export($values[$key], TRUE),
'@expected' => var_export($value, TRUE),
)));
}
}
Angie Byron
committed
/**
* Test handling of disabled elements.
*
Dries Buytaert
committed
* @see _form_test_disabled_elements()
Angie Byron
committed
*/
function testDisabledElements() {
Dries Buytaert
committed
// Get the raw form in its original state.
Angie Byron
committed
$form_state = array();
$form = _form_test_disabled_elements(array(), $form_state);
Dries Buytaert
committed
// Build a submission that tries to hijack the form by submitting input for
// elements that are disabled.
$edit = array();
Angie Byron
committed
foreach (element_children($form) as $key) {
Dries Buytaert
committed
if (isset($form[$key]['#test_hijack_value'])) {
if (is_array($form[$key]['#test_hijack_value'])) {
foreach ($form[$key]['#test_hijack_value'] as $subkey => $value) {
$edit[$key . '[' . $subkey . ']'] = $value;
}
}
else {
$edit[$key] = $form[$key]['#test_hijack_value'];
}
}
}
Angie Byron
committed
Dries Buytaert
committed
// Submit the form with no input, as the browser does for disabled elements,
// and fetch the $form_state['values'] that is passed to the submit handler.
$this->drupalPost('form-test/disabled-elements', array(), t('Submit'));
$returned_values['normal'] = drupal_json_decode($this->content);
// Do the same with input, as could happen if JavaScript un-disables an
// element. drupalPost() emulates a browser by not submitting input for
// disabled elements, so we need to un-disable those elements first.
$this->drupalGet('form-test/disabled-elements');
Angie Byron
committed
$disabled_elements = array();
Dries Buytaert
committed
foreach ($this->xpath('//*[@disabled]') as $element) {
Angie Byron
committed
$disabled_elements[] = (string) $element['name'];
Dries Buytaert
committed
unset($element['disabled']);
}
Angie Byron
committed
// All the elements should be marked as disabled, including the ones below
// the disabled container.
catch
committed
$this->assertEqual(count($disabled_elements), 36, 'The correct elements have the disabled property in the HTML code.');
Angie Byron
committed
Dries Buytaert
committed
$this->drupalPost(NULL, $edit, t('Submit'));
$returned_values['hijacked'] = drupal_json_decode($this->content);
// Ensure that the returned values match the form's default values in both
// cases.
foreach ($returned_values as $type => $values) {
Angie Byron
committed
$this->assertFormValuesDefault($values, $form);
}
}
Dries Buytaert
committed
Angie Byron
committed
/**
* Assert that the values submitted to a form matches the default values of the elements.
*/
function assertFormValuesDefault($values, $form) {
foreach (element_children($form) as $key) {
if (isset($form[$key]['#default_value'])) {
Dries Buytaert
committed
if (isset($form[$key]['#expected_value'])) {
$expected_value = $form[$key]['#expected_value'];
}
else {
$expected_value = $form[$key]['#default_value'];
}
Angie Byron
committed
if ($key == 'checkboxes_multiple') {
// Checkboxes values are not filtered out.
$values[$key] = array_filter($values[$key]);
Angie Byron
committed
}
$this->assertIdentical($expected_value, $values[$key], t('Default value for %type: expected %expected, returned %returned.', array('%type' => $key, '%expected' => var_export($expected_value, TRUE), '%returned' => var_export($values[$key], TRUE))));
Angie Byron
committed
}
Angie Byron
committed
// Recurse children.
$this->assertFormValuesDefault($values, $form[$key]);
Angie Byron
committed
}
}
Dries Buytaert
committed
Dries Buytaert
committed
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
/**
* Verify markup for disabled form elements.
*
* @see _form_test_disabled_elements()
*/
function testDisabledMarkup() {
$this->drupalGet('form-test/disabled-elements');
$form_state = array();
$form = _form_test_disabled_elements(array(), $form_state);
$type_map = array(
'textarea' => 'textarea',
'select' => 'select',
'weight' => 'select',
'date' => 'select',
);
foreach ($form as $name => $item) {
// Skip special #types.
if (!isset($item['#type']) || in_array($item['#type'], array('hidden', 'text_format'))) {
continue;
}
// Setup XPath and CSS class depending on #type.
if (in_array($item['#type'], array('image_button', 'button', 'submit'))) {
$path = "//!type[contains(@class, :div-class) and @value=:value]";
$class = 'form-button-disabled';
}
else {
// starts-with() required for checkboxes.
$path = "//div[contains(@class, :div-class)]/descendant::!type[starts-with(@name, :name)]";
$class = 'form-disabled';
}
// Replace DOM element name in $path according to #type.
$type = 'input';
if (isset($type_map[$item['#type']])) {
$type = $type_map[$item['#type']];
}
$path = strtr($path, array('!type' => $type));
// Verify that the element exists.
$element = $this->xpath($path, array(
':name' => check_plain($name),
':div-class' => $class,
':value' => isset($item['#value']) ? $item['#value'] : '',
));
$this->assertTrue(isset($element[0]), t('Disabled form element class found for #type %type.', array('%type' => $item['#type'])));
Dries Buytaert
committed
}
// Verify special element #type text-format.
$element = $this->xpath('//div[contains(@class, :div-class)]/descendant::textarea[@name=:name]', array(
':name' => 'text_format[value]',
':div-class' => 'form-disabled',
));
$this->assertTrue(isset($element[0]), t('Disabled form element class found for #type %type.', array('%type' => 'text_format[value]')));
Dries Buytaert
committed
$element = $this->xpath('//div[contains(@class, :div-class)]/descendant::select[@name=:name]', array(
':name' => 'text_format[format]',
':div-class' => 'form-disabled',
));
$this->assertTrue(isset($element[0]), t('Disabled form element class found for #type %type.', array('%type' => 'text_format[format]')));
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
* Test Form API protections against input forgery.
*
* @see _form_test_input_forgery()
*/
function testInputForgery() {
$this->drupalGet('form-test/input-forgery');
$checkbox = $this->xpath('//input[@name="checkboxes[two]"]');
$checkbox[0]['value'] = 'FORGERY';
$this->drupalPost(NULL, array('checkboxes[one]' => TRUE, 'checkboxes[two]' => TRUE), t('Submit'));
$this->assertText('An illegal choice has been detected.', t('Input forgery was detected.'));
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
* Tests building and processing of core form elements.
*/
class FormElementTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Element processing',
'description' => 'Tests building and processing of core form elements.',
'group' => 'Form API',
);
}
function setUp() {
parent::setUp(array('form_test'));
}
Dries Buytaert
committed
/**
catch
committed
* Tests placeholder text for elements that support placeholders.
Dries Buytaert
committed
*/
function testPlaceHolderText() {
$this->drupalGet('form-test/placeholder-text');
$expected = 'placeholder-text';
catch
committed
// Test to make sure non-textarea elements have the proper placeholder text.
catch
committed
foreach (array('textfield', 'tel', 'url', 'password', 'email') as $type) {
Dries Buytaert
committed
$element = $this->xpath('//input[@id=:id and @placeholder=:expected]', array(
':id' => 'edit-' . $type,
':expected' => $expected,
));
$this->assertTrue(!empty($element), t('Placeholder text placed in @type.', array('@type' => $type)));
}
// Test to make sure textarea has the proper placeholder text.
$element = $this->xpath('//textarea[@id=:id and @placeholder=:expected]', array(
':id' => 'edit-textarea',
':expected' => $expected,
));
$this->assertTrue(!empty($element), t('Placeholder text placed in textarea.'));
}
Dries Buytaert
committed
/**
* Tests expansion of #options for #type checkboxes and radios.
*/
function testOptions() {
$this->drupalGet('form-test/checkboxes-radios');
// Verify that all options appear in their defined order.
foreach (array('checkbox', 'radio') as $type) {
$elements = $this->xpath('//input[@type=:type]', array(':type' => $type));
Angie Byron
committed
$expected_values = array('0', 'foo', '1', 'bar', '>');
Dries Buytaert
committed
foreach ($elements as $element) {
$expected = array_shift($expected_values);
$this->assertIdentical((string) $element['value'], $expected);
}
}
// Enable customized option sub-elements.
$this->drupalGet('form-test/checkboxes-radios/customize');
// Verify that all options appear in their defined order, taking a custom
// #weight into account.
foreach (array('checkbox', 'radio') as $type) {
$elements = $this->xpath('//input[@type=:type]', array(':type' => $type));
Angie Byron
committed
$expected_values = array('0', 'foo', 'bar', '>', '1');
Dries Buytaert
committed
foreach ($elements as $element) {
$expected = array_shift($expected_values);
$this->assertIdentical((string) $element['value'], $expected);
}
}
// Verify that custom #description properties are output.
foreach (array('checkboxes', 'radios') as $type) {
Angie Byron
committed
$elements = $this->xpath('//input[@id=:id]/following-sibling::div[@class=:class]', array(
':id' => 'edit-' . $type . '-foo',
Dries Buytaert
committed
':class' => 'description',
));
$this->assertTrue(count($elements), t('Custom %type option description found.', array(
'%type' => $type,
)));
}
}
}
/**
* Test form alter hooks.
*/
class FormAlterTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Form alter hooks',
'description' => 'Tests hook_form_alter() and hook_form_FORM_ID_alter().',
'group' => 'Form API',
);
}
function setUp() {
parent::setUp(array('block', 'form_test'));
}
/**
* Tests execution order of hook_form_alter() and hook_form_FORM_ID_alter().
*/
function testExecutionOrder() {
$this->drupalGet('form-test/alter');
// Ensure that the order is first by module, then for a given module, the
// id-specific one after the generic one.
$expected = array(
'block_form_form_test_alter_form_alter() executed.',
'form_test_form_alter() executed.',
'form_test_form_form_test_alter_form_alter() executed.',
'system_form_form_test_alter_form_alter() executed.',
);
$content = preg_replace('/\s+/', ' ', filter_xss($this->content, array()));
$this->assert(strpos($content, implode(' ', $expected)) !== FALSE, t('Form alter hooks executed in the expected order.'));
}
}
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
/**
* Test form validation handlers.
*/
class FormValidationTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Form validation handlers',
'description' => 'Tests form processing and alteration via form validation handlers.',
'group' => 'Form API',
);
}
function setUp() {
parent::setUp('form_test');
}
/**
* Tests form alterations by #element_validate, #validate, and form_set_value().
*/
function testValidate() {
$this->drupalGet('form-test/validate');
// Verify that #element_validate handlers can alter the form and submitted
// form values.
$edit = array(
'name' => 'element_validate',
);
$this->drupalPost(NULL, $edit, 'Save');
$this->assertFieldByName('name', '#value changed by #element_validate', t('Form element #value was altered.'));
$this->assertText('Name value: value changed by form_set_value() in #element_validate', t('Form element value in $form_state was altered.'));
// Verify that #validate handlers can alter the form and submitted
// form values.
$edit = array(
'name' => 'validate',
);
$this->drupalPost(NULL, $edit, 'Save');
$this->assertFieldByName('name', '#value changed by #validate', t('Form element #value was altered.'));
$this->assertText('Name value: value changed by form_set_value() in #validate', t('Form element value in $form_state was altered.'));
// Verify that #element_validate handlers can make form elements
// inaccessible, but values persist.
$edit = array(
'name' => 'element_validate_access',
);
$this->drupalPost(NULL, $edit, 'Save');
$this->assertNoFieldByName('name', t('Form element was hidden.'));
$this->assertText('Name value: element_validate_access', t('Value for inaccessible form element exists.'));
// Verify that value for inaccessible form element persists.
$this->drupalPost(NULL, array(), 'Save');
$this->assertNoFieldByName('name', t('Form element was hidden.'));
$this->assertText('Name value: element_validate_access', t('Value for inaccessible form element exists.'));
}
Angie Byron
committed
/**
* Tests partial form validation through #limit_validation_errors.
*/
function testValidateLimitErrors() {
'test' => 'invalid',
'test_numeric_index[0]' => 'invalid',
'test_substring[foo]' => 'invalid',
);
Angie Byron
committed
$path = 'form-test/limit-validation-errors';
// Submit the form by pressing the 'Partial validate' button (uses
// #limit_validation_errors) and ensure that the title field is not
// validated, but the #element_validate handler for the 'test' field
// is triggered.
Angie Byron
committed
$this->drupalPost($path, $edit, t('Partial validate'));
$this->assertNoText(t('!name field is required.', array('!name' => 'Title')));
$this->assertText('Test element is invalid');
// Edge case of #limit_validation_errors containing numeric indexes: same
// thing with the 'Partial validate (numeric index)' button and the
// 'test_numeric_index' field.
$this->drupalPost($path, $edit, t('Partial validate (numeric index)'));
$this->assertNoText(t('!name field is required.', array('!name' => 'Title')));
$this->assertText('Test (numeric index) element is invalid');
// Ensure something like 'foobar' isn't considered "inside" 'foo'.
$this->drupalPost($path, $edit, t('Partial validate (substring)'));
$this->assertNoText(t('!name field is required.', array('!name' => 'Title')));
$this->assertText('Test (substring) foo element is invalid');
Angie Byron
committed
// Ensure not validated values are not available to submit handlers.
$this->drupalPost($path, array('title' => '', 'test' => 'valid'), t('Partial validate'));
$this->assertText('Only validated values appear in the form values.');
Angie Byron
committed
// Now test full form validation and ensure that the #element_validate
// handler is still triggered.
$this->drupalPost($path, $edit, t('Full validate'));
$this->assertText(t('!name field is required.', array('!name' => 'Title')));
$this->assertText('Test element is invalid');
}
}
Dries Buytaert
committed
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
/**
* Test form element labels, required markers and associated output.
*/
class FormsElementsLabelsTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Form element and label output test',
'description' => 'Test form element labels, required markers and associated output.',
'group' => 'Form API',
);
}
function setUp() {
parent::setUp('form_test');
}
/**
* Test form elements, labels, title attibutes and required marks output
* correctly and have the correct label option class if needed.
*/
function testFormLabels() {
$this->drupalGet('form_test/form-labels');
// Check that the checkbox/radio processing is not interfering with
// basic placement.
$elements = $this->xpath('//input[@id="edit-form-checkboxes-test-third-checkbox"]/following-sibling::label[@for="edit-form-checkboxes-test-third-checkbox" and @class="option"]');
$this->assertTrue(isset($elements[0]), t("Label follows field and label option class correct for regular checkboxes."));
Dries Buytaert
committed
// Make sure the label is rendered for checkboxes.
$elements = $this->xpath('//input[@id="edit-form-checkboxes-test-0"]/following-sibling::label[@for="edit-form-checkboxes-test-0" and @class="option"]');
$this->assertTrue(isset($elements[0]), t("Label 0 found checkbox."));
Dries Buytaert
committed
$elements = $this->xpath('//input[@id="edit-form-radios-test-second-radio"]/following-sibling::label[@for="edit-form-radios-test-second-radio" and @class="option"]');
$this->assertTrue(isset($elements[0]), t("Label follows field and label option class correct for regular radios."));
Dries Buytaert
committed
// Make sure the label is rendered for radios.
$elements = $this->xpath('//input[@id="edit-form-radios-test-0"]/following-sibling::label[@for="edit-form-radios-test-0" and @class="option"]');
$this->assertTrue(isset($elements[0]), t("Label 0 found radios."));
Dries Buytaert
committed
// Exercise various defaults for checkboxes and modifications to ensure
// appropriate override and correct behaviour.
$elements = $this->xpath('//input[@id="edit-form-checkbox-test"]/following-sibling::label[@for="edit-form-checkbox-test" and @class="option"]');
$this->assertTrue(isset($elements[0]), t("Label follows field and label option class correct for a checkbox by default."));
Dries Buytaert
committed
// Exercise various defaults for textboxes and modifications to ensure
// appropriate override and correct behaviour.
Dries Buytaert
committed
$elements = $this->xpath('//label[@for="edit-form-textfield-test-title-and-required"]/child::abbr[@class="form-required"]/parent::*/following-sibling::input[@id="edit-form-textfield-test-title-and-required"]');
$this->assertTrue(isset($elements[0]), t("Label precedes textfield, with required marker inside label."));
Dries Buytaert
committed
Dries Buytaert
committed
$elements = $this->xpath('//input[@id="edit-form-textfield-test-no-title-required"]/preceding-sibling::label[@for="edit-form-textfield-test-no-title-required"]/abbr[@class="form-required"]');
$this->assertTrue(isset($elements[0]), t("Label tag with required marker precedes required textfield with no title."));
Dries Buytaert
committed
Angie Byron
committed
$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"]');
$this->assertTrue(isset($elements[0]), t("Label preceding field and label class is element-invisible."));
Angie Byron
committed
Dries Buytaert
committed
$elements = $this->xpath('//input[@id="edit-form-textfield-test-title"]/preceding-sibling::abbr[@class="form-required"]');
$this->assertFalse(isset($elements[0]), t("No required marker on non-required field."));
Dries Buytaert
committed
$elements = $this->xpath('//input[@id="edit-form-textfield-test-title-after"]/following-sibling::label[@for="edit-form-textfield-test-title-after" and @class="option"]');
$this->assertTrue(isset($elements[0]), t("Label after field and label option class correct for text field."));
Dries Buytaert
committed
$elements = $this->xpath('//label[@for="edit-form-textfield-test-title-no-show"]');
$this->assertFalse(isset($elements[0]), t("No label tag when title set not to display."));
Dries Buytaert
committed
// Check #field_prefix and #field_suffix placement.
$elements = $this->xpath('//span[@class="field-prefix"]/following-sibling::div[@id="edit-form-radios-test"]');
$this->assertTrue(isset($elements[0]), t("Properly placed the #field_prefix element after the label and before the field."));
Dries Buytaert
committed
$elements = $this->xpath('//span[@class="field-suffix"]/preceding-sibling::div[@id="edit-form-radios-test"]');
$this->assertTrue(isset($elements[0]), t("Properly places the #field_suffix element immediately after the form field."));
Dries Buytaert
committed
// Check #prefix and #suffix placement.
$elements = $this->xpath('//div[@id="form-test-textfield-title-prefix"]/following-sibling::div[contains(@class, \'form-item-form-textfield-test-title\')]');
$this->assertTrue(isset($elements[0]), t("Properly places the #prefix element before the form item."));
Dries Buytaert
committed
$elements = $this->xpath('//div[@id="form-test-textfield-title-suffix"]/preceding-sibling::div[contains(@class, \'form-item-form-textfield-test-title\')]');
$this->assertTrue(isset($elements[0]), t("Properly places the #suffix element before the form item."));
// Check that required checkboxes with #title_display attribute have
// a description but no title is displayed.
$this->assertFieldByXPath('//div[contains(@class, "form-item-form-checkboxes-test-title-display-attribute")]/div[contains(@class, "description")]', NULL, 'Description displayed for #title_display attribute.');
$this->assertNoFieldByXPath('//label[@for="edit-form-checkboxes-test-title-display-attribute"]', NULL, 'No title displayed for #title_display attribute.');
Dries Buytaert
committed
}
}
Angie Byron
committed
/**
* Test the tableselect form element for expected behavior.
*/
class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
Angie Byron
committed
public static function getInfo() {
Angie Byron
committed
return array(
'name' => 'Tableselect form element type test',
'description' => 'Test the tableselect element for expected behavior',
'group' => 'Form API',
Angie Byron
committed
);
}
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.'));
Angie Byron
committed
// Test for the presence of the Select all rows tableheader.
$this->assertFieldByXPath('//th[@class="select-all"]', NULL, t('Presence of the "Select all" checkbox.'));
Angie Byron
committed
$rows = array('row1', 'row2', 'row3');
foreach ($rows as $row) {
$this->assertFieldByXPath('//input[@type="checkbox"]', $row, t('Checkbox for value @row.', array('@row' => $row)));
Angie Byron
committed
}
}
/**
* 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.'));
Angie Byron
committed
// Test for the absence of the Select all rows tableheader.
$this->assertNoFieldByXPath('//th[@class="select-all"]', '', t('Absence of the "Select all" checkbox.'));
Angie Byron
committed
$rows = array('row1', 'row2', 'row3');
foreach ($rows as $row) {
$this->assertFieldByXPath('//input[@type="radio"]', $row, t('Radio button for value @row.', array('@row' => $row)));
Angie Byron
committed
}
}
/**
* 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.'));
Angie Byron
committed
}
/**
* 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.'));
Angie Byron
committed
// 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.'));
Angie Byron
committed
}
/**
* 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'));
Angie Byron
committed
}
/**
* 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.'));
Angie Byron
committed
// 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.'));
Angie Byron
committed
// 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.'));
Angie Byron
committed
$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.'));
Angie Byron
committed
}
/**
* 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' => array('row1' => 'row1')));
$this->assertFalse(isset($errors['tableselect']), t('Option checker allows valid values for checkboxes.'));
Angie Byron
committed
// Test with an invalid value.
list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => array('non_existing_value' => 'non_existing_value')));
$this->assertTrue(isset($errors['tableselect']), t('Option checker disallows invalid values for checkboxes.'));
Angie Byron
committed
}
/**
* 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.'));
Angie Byron
committed
// 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.'));
Angie Byron
committed
}
/**
* Helper function for the option check test to submit a form while collecting errors.
*
* @param $form_element
* A form element to test.
* @param $edit
* An array containing post data.
*
* @return
* An array containing the processed form, the form_state and any errors.
*/
private function formSubmitHelper($form, $edit) {
Angie Byron
committed
$form_id = $this->randomName();
Dries Buytaert
committed
$form_state = form_state_defaults();
Angie Byron
committed
$form['op'] = array('#type' => 'submit', '#value' => t('Submit'));
Dries Buytaert
committed
$form_state['input'] = $edit;
$form_state['input']['form_id'] = $form_id;
Angie Byron
committed
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();
Dries Buytaert
committed
form_clear_error();
Angie Byron
committed
// 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 the vertical_tabs form element for expected behavior.
*/
class FormsElementsVerticalTabsFunctionalTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Vertical tabs form element type test',
'description' => 'Test the vertical_tabs element for expected behavior',
'group' => 'Form API',
);
}
function setUp() {
parent::setUp('form_test');
}
/**
* Ensures that vertical-tabs.js is included before collapse.js.
*
* Otherwise, collapse.js adds "SHOW" or "HIDE" labels to the tabs.
*/
function testJavaScriptOrdering() {
$this->drupalGet('form_test/vertical-tabs');
Nate Lampton
committed
$position1 = strpos($this->content, 'core/misc/vertical-tabs.js');
$position2 = strpos($this->content, 'core/misc/collapse.js');
$this->assertTrue($position1 !== FALSE && $position2 !== FALSE && $position1 < $position2, t('vertical-tabs.js is included before collapse.js'));
}
}
Dries Buytaert
committed
/**
* 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() {
Dries Buytaert
committed
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',