AbstractTableLayoutTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Form\Tests;
  11. use Symfony\Component\Form\FormError;
  12. use Symfony\Component\Security\Csrf\CsrfToken;
  13. abstract class AbstractTableLayoutTest extends AbstractLayoutTest
  14. {
  15. public function testRow()
  16. {
  17. $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType');
  18. $form->addError(new FormError('[trans]Error![/trans]'));
  19. $view = $form->createView();
  20. $html = $this->renderRow($view);
  21. $this->assertMatchesXpath($html,
  22. '/tr
  23. [
  24. ./td
  25. [./label[@for="name"]]
  26. /following-sibling::td
  27. [
  28. ./ul
  29. [./li[.="[trans]Error![/trans]"]]
  30. [count(./li)=1]
  31. /following-sibling::input[@id="name"]
  32. ]
  33. ]
  34. '
  35. );
  36. }
  37. public function testLabelIsNotRenderedWhenSetToFalse()
  38. {
  39. $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, array(
  40. 'label' => false,
  41. ));
  42. $html = $this->renderRow($form->createView());
  43. $this->assertMatchesXpath($html,
  44. '/tr
  45. [
  46. ./td
  47. [count(//label)=0]
  48. /following-sibling::td
  49. [./input[@id="name"]]
  50. ]
  51. '
  52. );
  53. }
  54. public function testRepeatedRow()
  55. {
  56. $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType');
  57. $html = $this->renderRow($form->createView());
  58. $this->assertMatchesXpath($html,
  59. '/tr
  60. [
  61. ./td
  62. [./label[@for="name_first"]]
  63. /following-sibling::td
  64. [./input[@id="name_first"]]
  65. ]
  66. /following-sibling::tr
  67. [
  68. ./td
  69. [./label[@for="name_second"]]
  70. /following-sibling::td
  71. [./input[@id="name_second"]]
  72. ]
  73. /following-sibling::tr[@style="display: none"]
  74. [./td[@colspan="2"]/input
  75. [@type="hidden"]
  76. [@id="name__token"]
  77. ]
  78. [count(../tr)=3]
  79. '
  80. );
  81. }
  82. public function testRepeatedRowWithErrors()
  83. {
  84. $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType');
  85. $form->addError(new FormError('[trans]Error![/trans]'));
  86. $view = $form->createView();
  87. $html = $this->renderRow($view);
  88. // The errors of the form are not rendered by intention!
  89. // In practice, repeated fields cannot have errors as all errors
  90. // on them are mapped to the first child.
  91. // (see RepeatedTypeValidatorExtension)
  92. $this->assertMatchesXpath($html,
  93. '/tr
  94. [
  95. ./td
  96. [./label[@for="name_first"]]
  97. /following-sibling::td
  98. [./input[@id="name_first"]]
  99. ]
  100. /following-sibling::tr
  101. [
  102. ./td
  103. [./label[@for="name_second"]]
  104. /following-sibling::td
  105. [./input[@id="name_second"]]
  106. ]
  107. /following-sibling::tr[@style="display: none"]
  108. [./td[@colspan="2"]/input
  109. [@type="hidden"]
  110. [@id="name__token"]
  111. ]
  112. [count(../tr)=3]
  113. '
  114. );
  115. }
  116. public function testButtonRow()
  117. {
  118. $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ButtonType');
  119. $view = $form->createView();
  120. $html = $this->renderRow($view);
  121. $this->assertMatchesXpath($html,
  122. '/tr
  123. [
  124. ./td
  125. [.=""]
  126. /following-sibling::td
  127. [./button[@type="button"][@name="name"]]
  128. ]
  129. [count(//label)=0]
  130. '
  131. );
  132. }
  133. public function testRest()
  134. {
  135. $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType')
  136. ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  137. ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType')
  138. ->add('field3', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  139. ->add('field4', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  140. ->getForm()
  141. ->createView();
  142. // Render field2 row -> does not implicitly call renderWidget because
  143. // it is a repeated field!
  144. $this->renderRow($view['field2']);
  145. // Render field3 widget
  146. $this->renderWidget($view['field3']);
  147. // Rest should only contain field1 and field4
  148. $html = $this->renderRest($view);
  149. $this->assertMatchesXpath($html,
  150. '/tr
  151. [
  152. ./td
  153. [./label[@for="name_field1"]]
  154. /following-sibling::td
  155. [./input[@id="name_field1"]]
  156. ]
  157. /following-sibling::tr
  158. [
  159. ./td
  160. [./label[@for="name_field4"]]
  161. /following-sibling::td
  162. [./input[@id="name_field4"]]
  163. ]
  164. [count(../tr)=3]
  165. [count(..//label)=2]
  166. [count(..//input)=3]
  167. /following-sibling::tr[@style="display: none"]
  168. [./td[@colspan="2"]/input
  169. [@type="hidden"]
  170. [@id="name__token"]
  171. ]
  172. '
  173. );
  174. }
  175. public function testCollection()
  176. {
  177. $form = $this->factory->createNamed('names', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', array('a', 'b'), array(
  178. 'entry_type' => 'Symfony\Component\Form\Extension\Core\Type\TextType',
  179. ));
  180. $this->assertWidgetMatchesXpath($form->createView(), array(),
  181. '/table
  182. [
  183. ./tr[./td/input[@type="text"][@value="a"]]
  184. /following-sibling::tr[./td/input[@type="text"][@value="b"]]
  185. /following-sibling::tr[@style="display: none"][./td[@colspan="2"]/input[@type="hidden"][@id="names__token"]]
  186. ]
  187. [count(./tr[./td/input])=3]
  188. '
  189. );
  190. }
  191. public function testEmptyCollection()
  192. {
  193. $form = $this->factory->createNamed('names', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', array(), array(
  194. 'entry_type' => 'Symfony\Component\Form\Extension\Core\Type\TextType',
  195. ));
  196. $this->assertWidgetMatchesXpath($form->createView(), array(),
  197. '/table
  198. [./tr[@style="display: none"][./td[@colspan="2"]/input[@type="hidden"][@id="names__token"]]]
  199. [count(./tr[./td/input])=1]
  200. '
  201. );
  202. }
  203. public function testForm()
  204. {
  205. $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType')
  206. ->setMethod('PUT')
  207. ->setAction('http://example.com')
  208. ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  209. ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  210. ->getForm()
  211. ->createView();
  212. $html = $this->renderForm($view, array(
  213. 'id' => 'my&id',
  214. 'attr' => array('class' => 'my&class'),
  215. ));
  216. $this->assertMatchesXpath($html,
  217. '/form
  218. [
  219. ./input[@type="hidden"][@name="_method"][@value="PUT"]
  220. /following-sibling::table
  221. [
  222. ./tr
  223. [
  224. ./td
  225. [./label[@for="name_firstName"]]
  226. /following-sibling::td
  227. [./input[@id="name_firstName"]]
  228. ]
  229. /following-sibling::tr
  230. [
  231. ./td
  232. [./label[@for="name_lastName"]]
  233. /following-sibling::td
  234. [./input[@id="name_lastName"]]
  235. ]
  236. /following-sibling::tr[@style="display: none"]
  237. [./td[@colspan="2"]/input
  238. [@type="hidden"]
  239. [@id="name__token"]
  240. ]
  241. ]
  242. [count(.//input)=3]
  243. [@id="my&id"]
  244. [@class="my&class"]
  245. ]
  246. [@method="post"]
  247. [@action="http://example.com"]
  248. [@class="my&class"]
  249. '
  250. );
  251. }
  252. public function testFormWidget()
  253. {
  254. $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType')
  255. ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  256. ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  257. ->getForm()
  258. ->createView();
  259. $this->assertWidgetMatchesXpath($view, array(),
  260. '/table
  261. [
  262. ./tr
  263. [
  264. ./td
  265. [./label[@for="name_firstName"]]
  266. /following-sibling::td
  267. [./input[@id="name_firstName"]]
  268. ]
  269. /following-sibling::tr
  270. [
  271. ./td
  272. [./label[@for="name_lastName"]]
  273. /following-sibling::td
  274. [./input[@id="name_lastName"]]
  275. ]
  276. /following-sibling::tr[@style="display: none"]
  277. [./td[@colspan="2"]/input
  278. [@type="hidden"]
  279. [@id="name__token"]
  280. ]
  281. ]
  282. [count(.//input)=3]
  283. '
  284. );
  285. }
  286. // https://github.com/symfony/symfony/issues/2308
  287. public function testNestedFormError()
  288. {
  289. $form = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType')
  290. ->add($this->factory
  291. ->createNamedBuilder('child', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, array('error_bubbling' => false))
  292. ->add('grandChild', 'Symfony\Component\Form\Extension\Core\Type\FormType')
  293. )
  294. ->getForm();
  295. $form->get('child')->addError(new FormError('[trans]Error![/trans]'));
  296. $this->assertWidgetMatchesXpath($form->createView(), array(),
  297. '/table
  298. [
  299. ./tr/td/ul[./li[.="[trans]Error![/trans]"]]
  300. /following-sibling::table[@id="name_child"]
  301. ]
  302. [count(.//li[.="[trans]Error![/trans]"])=1]
  303. '
  304. );
  305. }
  306. public function testCsrf()
  307. {
  308. $this->csrfTokenManager->expects($this->any())
  309. ->method('getToken')
  310. ->will($this->returnValue(new CsrfToken('token_id', 'foo&bar')));
  311. $form = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType')
  312. ->add($this->factory
  313. // No CSRF protection on nested forms
  314. ->createNamedBuilder('child', 'Symfony\Component\Form\Extension\Core\Type\FormType')
  315. ->add($this->factory->createNamedBuilder('grandchild', 'Symfony\Component\Form\Extension\Core\Type\TextType'))
  316. )
  317. ->getForm();
  318. $this->assertWidgetMatchesXpath($form->createView(), array(),
  319. '/table
  320. [
  321. ./tr[@style="display: none"]
  322. [./td[@colspan="2"]/input
  323. [@type="hidden"]
  324. [@id="name__token"]
  325. ]
  326. ]
  327. [count(.//input[@type="hidden"])=1]
  328. '
  329. );
  330. }
  331. public function testRepeated()
  332. {
  333. $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType', 'foobar', array(
  334. 'type' => 'Symfony\Component\Form\Extension\Core\Type\TextType',
  335. ));
  336. $this->assertWidgetMatchesXpath($form->createView(), array(),
  337. '/table
  338. [
  339. ./tr
  340. [
  341. ./td
  342. [./label[@for="name_first"]]
  343. /following-sibling::td
  344. [./input[@type="text"][@id="name_first"]]
  345. ]
  346. /following-sibling::tr
  347. [
  348. ./td
  349. [./label[@for="name_second"]]
  350. /following-sibling::td
  351. [./input[@type="text"][@id="name_second"]]
  352. ]
  353. /following-sibling::tr[@style="display: none"]
  354. [./td[@colspan="2"]/input
  355. [@type="hidden"]
  356. [@id="name__token"]
  357. ]
  358. ]
  359. [count(.//input)=3]
  360. '
  361. );
  362. }
  363. public function testRepeatedWithCustomOptions()
  364. {
  365. $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RepeatedType', 'foobar', array(
  366. 'type' => 'Symfony\Component\Form\Extension\Core\Type\PasswordType',
  367. 'first_options' => array('label' => 'Test', 'required' => false),
  368. 'second_options' => array('label' => 'Test2'),
  369. ));
  370. $this->assertWidgetMatchesXpath($form->createView(), array(),
  371. '/table
  372. [
  373. ./tr
  374. [
  375. ./td
  376. [./label[@for="name_first"][.="[trans]Test[/trans]"]]
  377. /following-sibling::td
  378. [./input[@type="password"][@id="name_first"][@required="required"]]
  379. ]
  380. /following-sibling::tr
  381. [
  382. ./td
  383. [./label[@for="name_second"][.="[trans]Test2[/trans]"]]
  384. /following-sibling::td
  385. [./input[@type="password"][@id="name_second"][@required="required"]]
  386. ]
  387. /following-sibling::tr[@style="display: none"]
  388. [./td[@colspan="2"]/input
  389. [@type="hidden"]
  390. [@id="name__token"]
  391. ]
  392. ]
  393. [count(.//input)=3]
  394. '
  395. );
  396. }
  397. /**
  398. * The block "_name_child_label" should be overridden in the theme of the
  399. * implemented driver.
  400. */
  401. public function testCollectionRowWithCustomBlock()
  402. {
  403. $collection = array('one', 'two', 'three');
  404. $form = $this->factory->createNamedBuilder('names', 'Symfony\Component\Form\Extension\Core\Type\CollectionType', $collection)
  405. ->getForm();
  406. $this->assertWidgetMatchesXpath($form->createView(), array(),
  407. '/table
  408. [
  409. ./tr[./td/label[.="Custom label: [trans]0[/trans]"]]
  410. /following-sibling::tr[./td/label[.="Custom label: [trans]1[/trans]"]]
  411. /following-sibling::tr[./td/label[.="Custom label: [trans]2[/trans]"]]
  412. ]
  413. '
  414. );
  415. }
  416. public function testFormEndWithRest()
  417. {
  418. $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType')
  419. ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  420. ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  421. ->getForm()
  422. ->createView();
  423. $this->renderWidget($view['field1']);
  424. // Rest should only contain field2
  425. $html = $this->renderEnd($view);
  426. // Insert the start tag, the end tag should be rendered by the helper
  427. // Unfortunately this is not valid HTML, because the surrounding table
  428. // tag is missing. If someone renders a form with table layout
  429. // manually, she should call form_rest() explicitly within the <table>
  430. // tag.
  431. $this->assertMatchesXpath('<form>'.$html,
  432. '/form
  433. [
  434. ./tr
  435. [
  436. ./td
  437. [./label[@for="name_field2"]]
  438. /following-sibling::td
  439. [./input[@id="name_field2"]]
  440. ]
  441. /following-sibling::tr[@style="display: none"]
  442. [./td[@colspan="2"]/input
  443. [@type="hidden"]
  444. [@id="name__token"]
  445. ]
  446. ]
  447. '
  448. );
  449. }
  450. public function testFormEndWithoutRest()
  451. {
  452. $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType')
  453. ->add('field1', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  454. ->add('field2', 'Symfony\Component\Form\Extension\Core\Type\TextType')
  455. ->getForm()
  456. ->createView();
  457. $this->renderWidget($view['field1']);
  458. // Rest should only contain field2, but isn't rendered
  459. $html = $this->renderEnd($view, array('render_rest' => false));
  460. $this->assertEquals('</form>', $html);
  461. }
  462. public function testWidgetContainerAttributes()
  463. {
  464. $form = $this->factory->createNamed('form', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, array(
  465. 'attr' => array('class' => 'foobar', 'data-foo' => 'bar'),
  466. ));
  467. $form->add('text', 'Symfony\Component\Form\Extension\Core\Type\TextType');
  468. $html = $this->renderWidget($form->createView());
  469. // compare plain HTML to check the whitespace
  470. $this->assertContains('<table id="form" class="foobar" data-foo="bar">', $html);
  471. }
  472. public function testWidgetContainerAttributeNameRepeatedIfTrue()
  473. {
  474. $form = $this->factory->createNamed('form', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, array(
  475. 'attr' => array('foo' => true),
  476. ));
  477. $html = $this->renderWidget($form->createView());
  478. // foo="foo"
  479. $this->assertContains('<table id="form" foo="foo">', $html);
  480. }
  481. }