diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php index 6563339..5424f66 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php @@ -657,17 +657,6 @@ function testRequiredAttribute() { } /** - * Tests error border of multiple fields with same name in a page. - */ - function testMultiFormSameNameErrorClass() { - $this->drupalGet('form-test/double-form'); - $edit = array(); - $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertFieldByXpath('//input[@id="edit-name" and contains(@class, "error")]', NULL, 'Error input form element class found for first element.'); - $this->assertNoFieldByXpath('//input[@id="edit-name--2" and contains(@class, "error")]', NULL, 'No error input form element class found for second element.'); - } - - /** * Tests a form with a form state storing a database connection. */ public function testFormStateDatabaseConnection() { diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/HTMLIdTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/HTMLIdTest.php deleted file mode 100644 index 3d85f7b..0000000 --- a/core/modules/system/lib/Drupal/system/Tests/Form/HTMLIdTest.php +++ /dev/null @@ -1,45 +0,0 @@ - 'Unique HTML IDs', - 'description' => 'Tests functionality of drupal_html_id().', - 'group' => 'Form API', - ); - } - - /** - * Tests that HTML IDs do not get duplicated when form validation fails. - */ - function testHTMLId() { - $this->drupalGet('form-test/double-form'); - $this->assertNoDuplicateIds('There are no duplicate IDs'); - - // Submit second form with empty title. - $edit = array(); - $this->drupalPostForm(NULL, $edit, 'Save', array(), array(), 'form-test-html-id--2'); - $this->assertNoDuplicateIds('There are no duplicate IDs'); - } - -} diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module index a932868..ecdbc76 100644 --- a/core/modules/system/tests/modules/form_test/form_test.module +++ b/core/modules/system/tests/modules/form_test/form_test.module @@ -2057,34 +2057,6 @@ function form_test_required_attribute($form, &$form_state) { } /** - * Menu callback returns two instances of the same form. - * - * @deprecated \Drupal\form_test\Controller\FormTestController::doubleForm() - */ -function form_test_double_form() { - return array( - 'form1' => drupal_get_form('form_test_html_id'), - 'form2' => drupal_get_form('form_test_html_id'), - ); -} - -/** - * Builds a simple form to test duplicate HTML IDs. - */ -function form_test_html_id($form, &$form_state) { - $form['name'] = array( - '#type' => 'textfield', - '#title' => 'name', - '#required' => TRUE, - ); - $form['submit'] = array( - '#type' => 'submit', - '#value' => 'Save', - ); - return $form; -} - -/** * Builds a simple form to test form button classes. * * @deprecated Use \Drupal\form_test\testButtonClass() diff --git a/core/modules/system/tests/modules/form_test/form_test.routing.yml b/core/modules/system/tests/modules/form_test/form_test.routing.yml index de1ddea..a37c9d8 100644 --- a/core/modules/system/tests/modules/form_test/form_test.routing.yml +++ b/core/modules/system/tests/modules/form_test/form_test.routing.yml @@ -77,14 +77,6 @@ form_test.wrapper: requirements: _access: 'TRUE' -form_test.double_form: - path: '/form-test/double-form' - defaults: - _title: 'Double form test' - _content: '\Drupal\form_test\Controller\FormTestController::doubleForm' - requirements: - _access: 'TRUE' - form_test.alter_form: path: '/form-test/alter' defaults: diff --git a/core/modules/system/tests/modules/form_test/lib/Drupal/form_test/Controller/FormTestController.php b/core/modules/system/tests/modules/form_test/lib/Drupal/form_test/Controller/FormTestController.php index 40dd178..e897184 100644 --- a/core/modules/system/tests/modules/form_test/lib/Drupal/form_test/Controller/FormTestController.php +++ b/core/modules/system/tests/modules/form_test/lib/Drupal/form_test/Controller/FormTestController.php @@ -42,11 +42,4 @@ public function wrapperCallback($form_id) { return form_test_wrapper_callback($form_id); } - /** - * @todo Remove form_test_double_form(). - */ - public function doubleForm() { - return form_test_double_form(); - } - } diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index 777920a..0b52a75 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -8,68 +8,17 @@ namespace Drupal\Tests\Core\Form { use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\Core\Form\FormBuilder; use Drupal\Core\Form\FormInterface; -use Drupal\Core\Session\AccountInterface; -use Drupal\Tests\UnitTestCase; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; /** * Tests the form builder. + * + * @group Drupal + * @group Form */ -class FormBuilderTest extends UnitTestCase { - - /** - * The form builder being tested. - * - * @var \Drupal\Core\Form\FormBuilder - */ - protected $formBuilder; - - /** - * The mocked URL generator. - * - * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Routing\UrlGeneratorInterface - */ - protected $urlGenerator; - - /** - * The mocked module handler. - * - * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Extension\ModuleHandlerInterface - */ - protected $moduleHandler; - - /** - * The expirable key value store used by form cache. - * - * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface - */ - protected $formCache; - - /** - * The expirable key value store used by form state cache. - * - * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface - */ - protected $formStateCache; - - /** - * The current user. - * - * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Session\AccountInterface - */ - protected $account; - - /** - * The CSRF token generator. - * - * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Access\CsrfTokenGenerator - */ - protected $csrfToken; +class FormBuilderTest extends FormTestBase { /** * {@inheritdoc} @@ -82,39 +31,6 @@ public static function getInfo() { ); } - public function setUp() { - $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); - - $this->formCache = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface'); - $this->formStateCache = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface'); - $key_value_expirable_factory = $this->getMockBuilder('\Drupal\Core\KeyValueStore\KeyValueExpirableFactory') - ->disableOriginalConstructor() - ->getMock(); - $key_value_expirable_factory->expects($this->any()) - ->method('get') - ->will($this->returnValueMap(array( - array('form', $this->formCache), - array('form_state', $this->formStateCache), - ))); - - $event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); - $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); - $translation_manager = $this->getStringTranslationStub(); - $this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator') - ->disableOriginalConstructor() - ->getMock(); - $http_kernel = $this->getMockBuilder('Drupal\Core\HttpKernel') - ->disableOriginalConstructor() - ->getMock(); - - $this->formBuilder = new TestFormBuilder($this->moduleHandler, $key_value_expirable_factory, $event_dispatcher, $this->urlGenerator, $translation_manager, $this->csrfToken, $http_kernel); - $this->formBuilder->setRequest(new Request()); - - $this->account = $this->getMock('Drupal\Core\Session\AccountInterface'); - $this->formBuilder->setCurrentUser($this->account); - - } - /** * Tests the getFormId() method with a string based form ID. */ @@ -729,147 +645,6 @@ public function testSendResponse() { $this->formBuilder->buildForm($form_arg, $form_state); } - /** - * Provides a mocked form object. - * - * @param string $form_id - * (optional) The form ID to be used. If none is provided, the form will be - * set with no expectation about getFormId(). - * @param mixed $expected_form - * (optional) If provided, the expected form response for buildForm() to - * return. Defaults to NULL. - * @param int $count - * (optional) The number of times the form is expected to be built. Defaults - * to 1. - * - * @return \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Form\FormInterface - * The mocked form object. - */ - protected function getMockForm($form_id, $expected_form = NULL, $count = 1) { - $form = $this->getMock('Drupal\Core\Form\FormInterface'); - $form->expects($this->once()) - ->method('getFormId') - ->will($this->returnValue($form_id)); - - if ($expected_form) { - $form->expects($this->exactly($count)) - ->method('buildForm') - ->will($this->returnValue($expected_form)); - } - return $form; - } - - /** - * Asserts that the expected form structure is found in a form for a given key. - * - * @param array $expected_form - * The expected form structure. - * @param array $actual_form - * The actual form. - * @param string|null $form_key - * (optional) The form key to look in. Otherwise the entire form will be - * compared. - */ - protected function assertFormElement(array $expected_form, array $actual_form, $form_key = NULL) { - $expected_element = $form_key ? $expected_form[$form_key] : $expected_form; - $actual_element = $form_key ? $actual_form[$form_key] : $actual_form; - $this->assertSame(array_intersect_key($expected_element, $actual_element), $expected_element); - } - -} - -/** - * Provides a test form builder class. - */ -class TestFormBuilder extends FormBuilder { - - /** - * {@inheritdoc} - */ - protected function sendResponse(Response $response) { - parent::sendResponse($response); - // Throw an exception instead of exiting. - throw new \Exception('exit'); - } - - /** - * @param \Drupal\Core\Session\AccountInterface $account - */ - public function setCurrentUser(AccountInterface $account) { - $this->currentUser = $account; - } - - /** - * {@inheritdoc} - */ - protected function getElementInfo($type) { - $types['token'] = array( - '#input' => TRUE, - ); - $types['value'] = array( - '#input' => TRUE, - ); - $types['select'] = array( - '#input' => TRUE, - '#multiple' => FALSE, - '#empty_value' => '', - ); - $types['radios'] = array( - '#input' => TRUE, - ); - $types['textfield'] = array( - '#input' => TRUE, - ); - $types['submit'] = array( - '#input' => TRUE, - '#name' => 'op', - '#is_button' => TRUE, - ); - if (!isset($types[$type])) { - $types[$type] = array(); - } - return $types[$type]; - } - - /** - * {@inheritdoc} - */ - protected function drupalInstallationAttempted() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) { - } - - /** - * {@inheritdoc} - */ - protected function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) { - } - - /** - * {@inheritdoc} - */ - protected function drupalHtmlClass($class) { - return $class; - } - - /** - * {@inheritdoc} - */ - protected function drupalHtmlId($id) { - return $id; - } - - /** - * {@inheritdoc} - */ - protected function drupalStaticReset($name = NULL) { - } - } class TestForm implements FormInterface { @@ -892,48 +667,9 @@ public static function create(ContainerInterface $container) { } namespace { - function test_form_id() { - $form['test'] = array( - '#type' => 'textfield', - '#title' => 'Test', - ); - $form['select'] = array( - '#type' => 'select', - '#options' => array( - 'foo' => 'foo', - 'bar' => 'bar', - ), - ); - $form['options'] = array( - '#type' => 'radios', - '#options' => array( - 'foo' => 'foo', - 'bar' => 'bar', - ), - ); - $form['value'] = array( - '#type' => 'value', - '#value' => 'bananas', - ); - $form['actions'] = array( - '#type' => 'actions', - ); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => 'Submit', - ); - return $form; - } function test_form_id_custom_submit(array &$form, array &$form_state) { } - if (!defined('WATCHDOG_ERROR')) { define('WATCHDOG_ERROR', 3); } - if (!function_exists('batch_get')) { - function &batch_get() { - $batch = array(); - return $batch; - } - } } diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php new file mode 100644 index 0000000..2ff4858 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php @@ -0,0 +1,360 @@ +moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); + + $this->formCache = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface'); + $this->formStateCache = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface'); + $this->keyValueExpirableFactory = $this->getMockBuilder('Drupal\Core\KeyValueStore\KeyValueExpirableFactory') + ->disableOriginalConstructor() + ->getMock(); + $this->keyValueExpirableFactory->expects($this->any()) + ->method('get') + ->will($this->returnValueMap(array( + array('form', $this->formCache), + array('form_state', $this->formStateCache), + ))); + + $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); + $this->translationManager = $this->getStringTranslationStub(); + $this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator') + ->disableOriginalConstructor() + ->getMock(); + $this->httpKernel = $this->getMockBuilder('Drupal\Core\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + $this->request = new Request(); + $this->account = $this->getMock('Drupal\Core\Session\AccountInterface'); + + $this->setupFormBuilder(); + } + + /** + * {@inheritdoc} + */ + protected function tearDown() { + $this->formBuilder->drupalStaticReset(); + } + + /** + * Sets up a new form builder object to test. + */ + protected function setupFormBuilder() { + $this->formBuilder = new TestFormBuilder($this->moduleHandler, $this->keyValueExpirableFactory, $this->eventDispatcher, $this->urlGenerator, $this->translationManager, $this->csrfToken, $this->httpKernel); + $this->formBuilder->setRequest($this->request); + $this->formBuilder->setCurrentUser($this->account); + } + + /** + * Provides a mocked form object. + * + * @param string $form_id + * (optional) The form ID to be used. If none is provided, the form will be + * set with no expectation about getFormId(). + * @param mixed $expected_form + * (optional) If provided, the expected form response for buildForm() to + * return. Defaults to NULL. + * @param int $count + * (optional) The number of times the form is expected to be built. Defaults + * to 1. + * + * @return \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Form\FormInterface + * The mocked form object. + */ + protected function getMockForm($form_id, $expected_form = NULL, $count = 1) { + $form = $this->getMock('Drupal\Core\Form\FormInterface'); + $form->expects($this->once()) + ->method('getFormId') + ->will($this->returnValue($form_id)); + + if ($expected_form) { + $form->expects($this->exactly($count)) + ->method('buildForm') + ->will($this->returnValue($expected_form)); + } + return $form; + } + + /** + * Simulates a form submission within a request, bypassing submitForm(). + * + * Calling submitForm() will reset the form builder, if two forms were on the + * same page, they will be submitted simultaneously. + * + * @param string $form_id + * The unique string identifying the form. + * @param \Drupal\Core\Form\FormInterface $form_arg + * The form object. + * @param array $form_state + * An associative array containing the current state of the form. + * + * @return array + * The built form. + */ + protected function simulateFormSubmission($form_id, FormInterface $form_arg, array &$form_state) { + $form_state['build_info']['callback_object'] = $form_arg; + $form_state['build_info']['args'] = array(); + $form_state['input']['op'] = 'Submit'; + $form_state['programmed'] = TRUE; + $form_state['submitted'] = TRUE; + return $this->formBuilder->buildForm($form_id, $form_state); + } + + /** + * Asserts that the expected form structure is found in a form for a given key. + * + * @param array $expected_form + * The expected form structure. + * @param array $actual_form + * The actual form. + * @param string|null $form_key + * (optional) The form key to look in. Otherwise the entire form will be + * compared. + */ + protected function assertFormElement(array $expected_form, array $actual_form, $form_key = NULL) { + $expected_element = $form_key ? $expected_form[$form_key] : $expected_form; + $actual_element = $form_key ? $actual_form[$form_key] : $actual_form; + $this->assertSame(array_intersect_key($expected_element, $actual_element), $expected_element); + } + +} + +/** + * Provides a test form builder class. + */ +class TestFormBuilder extends FormBuilder { + protected static $seenIds = array(); + + /** + * {@inheritdoc} + */ + protected function sendResponse(Response $response) { + parent::sendResponse($response); + // Throw an exception instead of exiting. + throw new \Exception('exit'); + } + + /** + * @param \Drupal\Core\Session\AccountInterface $account + */ + public function setCurrentUser(AccountInterface $account) { + $this->currentUser = $account; + } + + /** + * {@inheritdoc} + */ + protected function getElementInfo($type) { + $types['token'] = array( + '#input' => TRUE, + ); + $types['value'] = array( + '#input' => TRUE, + ); + $types['radios'] = array( + '#input' => TRUE, + ); + $types['textfield'] = array( + '#input' => TRUE, + ); + $types['submit'] = array( + '#input' => TRUE, + '#name' => 'op', + '#is_button' => TRUE, + ); + if (!isset($types[$type])) { + $types[$type] = array(); + } + return $types[$type]; + } + + /** + * {@inheritdoc} + */ + protected function drupalInstallationAttempted() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + protected function menuGetItem() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) { + } + + /** + * {@inheritdoc} + */ + protected function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) { + } + + /** + * {@inheritdoc} + */ + protected function drupalHtmlClass($class) { + return $class; + } + + /** + * {@inheritdoc} + */ + protected function drupalHtmlId($id) { + if (isset(static::$seenIds[$id])) { + $id = $id . '--' . ++static::$seenIds[$id]; + } + else { + static::$seenIds[$id] = 1; + } + return $id; + } + + /** + * {@inheritdoc} + */ + public function drupalStaticReset($name = NULL) { + static::$seenIds = array(); + } + +} + +} + +namespace { + function test_form_id() { + $form['test'] = array( + '#type' => 'textfield', + '#title' => 'Test', + ); + $form['options'] = array( + '#type' => 'radios', + '#options' => array( + 'foo' => 'foo', + 'bar' => 'bar', + ), + ); + $form['value'] = array( + '#type' => 'value', + '#value' => 'bananas', + ); + $form['actions'] = array( + '#type' => 'actions', + ); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => 'Submit', + ); + return $form; + } + if (!function_exists('batch_get')) { + function &batch_get() { + $batch = array(); + return $batch; + } + } +} diff --git a/core/tests/Drupal/Tests/Core/Form/FormValidationTest.php b/core/tests/Drupal/Tests/Core/Form/FormValidationTest.php new file mode 100644 index 0000000..20edd06 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Form/FormValidationTest.php @@ -0,0 +1,78 @@ + 'Form element validation', + 'description' => 'Tests various form element validation mechanisms.', + 'group' => 'Form API', + ); + } + + public function testNoDuplicateErrorsForIdenticalForm() { + $form_id = 'test_form_id'; + $expected_form = $form_id(); + $expected_form['test']['#required'] = TRUE; + + // Mock a form object that will be built three times. + $form_arg = $this->getMockForm($form_id, $expected_form, 3); + + // The first form will have errors. + $form_state = array(); + $this->formBuilder->getFormId($form_arg, $form_state); + $this->simulateFormSubmission($form_id, $form_arg, $form_state); + $errors = $this->formBuilder->getErrors($form_state); + $this->assertNotEmpty($errors['test']); + + // The second form will not have errors. + $form_state = array(); + $this->simulateFormSubmission($form_id, $form_arg, $form_state); + $errors = $this->formBuilder->getErrors($form_state); + $this->assertEmpty($errors); + + // Reset the form builder. + $this->setupFormBuilder(); + + // On a new request, the first form will have errors again. + $form_state = array(); + $this->simulateFormSubmission($form_id, $form_arg, $form_state); + $errors = $this->formBuilder->getErrors($form_state); + $this->assertNotEmpty($errors['test']); + } + + public function testUniqueHtmlId() { + $form_id = 'test_form_id'; + $expected_form = $form_id(); + $expected_form['test']['#required'] = TRUE; + + // Mock a form object that will be built three times. + $form_arg = $this->getMockForm($form_id, $expected_form, 2); + + $form_state = array(); + $this->formBuilder->getFormId($form_arg, $form_state); + $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state); + $this->assertSame($form_id, $form['#id']); + + $form_state = array(); + $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state); + $this->assertSame("$form_id--2", $form['#id']); + } + +}