AbstractLayoutTest.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  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\Tests\Component\Form;
  11. use Symfony\Component\Form\FormInterface;
  12. use Symfony\Component\Form\FormError;
  13. use Symfony\Component\Form\TemplateContext;
  14. use Symfony\Component\Form\FormFactory;
  15. use Symfony\Component\Form\CsrfProvider\DefaultCsrfProvider;
  16. use Symfony\Component\Form\Type\Loader\DefaultTypeLoader;
  17. use Symfony\Component\EventDispatcher\EventDispatcher;
  18. abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
  19. {
  20. protected $factory;
  21. protected function setUp()
  22. {
  23. \Locale::setDefault('en');
  24. $dispatcher = new EventDispatcher();
  25. $validator = $this->getMock('Symfony\Component\Validator\ValidatorInterface');
  26. $csrfProvider = new DefaultCsrfProvider('foo');
  27. $storage = new \Symfony\Component\HttpFoundation\File\TemporaryStorage('foo', 1, \sys_get_temp_dir());
  28. $loader = new DefaultTypeLoader($validator, $csrfProvider , $storage);
  29. $this->factory = new FormFactory($loader);
  30. }
  31. protected function assertXpathNodeValue(\DomElement $element, $expression, $nodeValue)
  32. {
  33. $xpath = new \DOMXPath($element->ownerDocument);
  34. $nodeList = $xpath->evaluate($expression);
  35. $this->assertEquals(1, $nodeList->length);
  36. $this->assertEquals($nodeValue, $nodeList->item(0)->nodeValue);
  37. }
  38. protected function assertMatchesXpath($html, $expression, $count = 1)
  39. {
  40. $dom = new \DomDocument('UTF-8');
  41. try {
  42. // Wrap in <root> node so we can load HTML with multiple tags at
  43. // the top level
  44. $dom->loadXml('<root>'.$html.'</root>');
  45. } catch (\Exception $e) {
  46. return $this->fail(sprintf(
  47. "Failed loading HTML:\n\n%s\n\nError: %s",
  48. $html,
  49. $e->getMessage()
  50. ));
  51. }
  52. $xpath = new \DOMXPath($dom);
  53. $nodeList = $xpath->evaluate('/root'.$expression);
  54. if ($nodeList->length != $count) {
  55. $dom->formatOutput = true;
  56. $this->fail(sprintf(
  57. "Failed asserting that \n\n%s\n\nmatches exactly %s. Matches %s in \n\n%s",
  58. $expression,
  59. $count == 1 ? 'once' : $count . ' times',
  60. $nodeList->length == 1 ? 'once' : $nodeList->length . ' times',
  61. // strip away <root> and </root>
  62. substr($dom->saveHTML(), 6, -8)
  63. ));
  64. }
  65. }
  66. protected function assertWidgetMatchesXpath(FormInterface $form, array $vars, $xpath)
  67. {
  68. $html = $this->renderWidget($form, array_merge(array(
  69. 'id' => 'my_id',
  70. 'attr' => array('class' => 'my_class'),
  71. ), $vars));
  72. $xpath = trim($xpath).'
  73. [@id="my_id"]
  74. [@class="my_class"]';
  75. $this->assertMatchesXpath($html, $xpath);
  76. }
  77. abstract protected function renderEnctype(FormInterface $form);
  78. abstract protected function renderLabel(FormInterface $form, $label = null);
  79. abstract protected function renderErrors(FormInterface $form);
  80. abstract protected function renderWidget(FormInterface $form, array $vars = array());
  81. abstract protected function renderRow(FormInterface $form, array $vars = array());
  82. abstract protected function renderRest(FormInterface $form, array $vars = array());
  83. public function testEnctype()
  84. {
  85. $form = $this->factory->createBuilder('form', 'name')
  86. ->add('file', 'file')
  87. ->getForm();
  88. $this->assertEquals('enctype="multipart/form-data"', $this->renderEnctype($form));
  89. }
  90. public function testNoEnctype()
  91. {
  92. $form = $this->factory->createBuilder('form', 'name')
  93. ->add('text', 'text')
  94. ->getForm();
  95. $this->assertEquals('', $this->renderEnctype($form));
  96. }
  97. public function testLabel()
  98. {
  99. $form = $this->factory->create('text', 'name');
  100. $html = $this->renderLabel($form);
  101. $this->assertMatchesXpath($html,
  102. '/label
  103. [@for="name"]
  104. [.="[trans]Name[/trans]"]
  105. '
  106. );
  107. }
  108. public function testLabelWithCustomText()
  109. {
  110. $form = $this->factory->create('text', 'name');
  111. $html = $this->renderLabel($form, 'Custom label');
  112. $this->assertMatchesXpath($html,
  113. '/label
  114. [@for="name"]
  115. [.="[trans]Custom label[/trans]"]
  116. '
  117. );
  118. }
  119. public function testErrors()
  120. {
  121. $form = $this->factory->create('text', 'name');
  122. $form->addError(new FormError('Error 1'));
  123. $form->addError(new FormError('Error 2'));
  124. $html = $this->renderErrors($form);
  125. $this->assertMatchesXpath($html,
  126. '/ul
  127. [
  128. ./li[.="[trans]Error 1[/trans]"]
  129. /following-sibling::li[.="[trans]Error 2[/trans]"]
  130. ]
  131. [count(./li)=2]
  132. '
  133. );
  134. }
  135. public function testCheckedCheckbox()
  136. {
  137. $form = $this->factory->create('checkbox', 'name', array(
  138. 'data' => true,
  139. ));
  140. $this->assertWidgetMatchesXpath($form, array(),
  141. '/input
  142. [@type="checkbox"]
  143. [@name="name"]
  144. [@checked="checked"]
  145. [@value="1"]
  146. '
  147. );
  148. }
  149. public function testCheckedCheckboxWithValue()
  150. {
  151. $form = $this->factory->create('checkbox', 'name', array(
  152. 'value' => 'foobar',
  153. 'data' => true,
  154. ));
  155. $this->assertWidgetMatchesXpath($form, array(),
  156. '/input
  157. [@type="checkbox"]
  158. [@name="name"]
  159. [@checked="checked"]
  160. [@value="foobar"]
  161. '
  162. );
  163. }
  164. public function testUncheckedCheckbox()
  165. {
  166. $form = $this->factory->create('checkbox', 'name', array(
  167. 'data' => false,
  168. ));
  169. $this->assertWidgetMatchesXpath($form, array(),
  170. '/input
  171. [@type="checkbox"]
  172. [@name="name"]
  173. [not(@checked)]
  174. '
  175. );
  176. }
  177. public function testChoice()
  178. {
  179. $form = $this->factory->create('choice', 'name', array(
  180. 'choices' => array('a' => 'A', 'b' => 'B'),
  181. 'data' => 'a',
  182. ));
  183. $this->assertWidgetMatchesXpath($form, array(),
  184. '/select
  185. [@name="name"]
  186. [
  187. ./option[@value="a"][@selected="selected"][.="A"]
  188. /following-sibling::option[@value="b"][not(@selected)][.="B"]
  189. ]
  190. [count(./option)=2]
  191. '
  192. );
  193. }
  194. public function testChoiceWithPreferred()
  195. {
  196. $form = $this->factory->create('choice', 'name', array(
  197. 'choices' => array('a' => 'A', 'b' => 'B'),
  198. 'preferred_choices' => array('b'),
  199. 'data' => 'a',
  200. ));
  201. $this->assertWidgetMatchesXpath($form, array('separator' => '-- sep --'),
  202. '/select
  203. [@name="name"]
  204. [
  205. ./option[@value="b"][not(@selected)][.="B"]
  206. /following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"]
  207. /following-sibling::option[@value="a"][@selected="selected"][.="A"]
  208. ]
  209. [count(./option)=3]
  210. '
  211. );
  212. }
  213. public function testNonRequiredChoice()
  214. {
  215. $form = $this->factory->create('choice', 'name', array(
  216. 'choices' => array('a' => 'A', 'b' => 'B'),
  217. 'required' => false,
  218. 'data' => 'a',
  219. ));
  220. $this->assertWidgetMatchesXpath($form, array(),
  221. '/select
  222. [@name="name"]
  223. [
  224. ./option[@value=""][.=""]
  225. /following-sibling::option[@value="a"][@selected="selected"][.="A"]
  226. /following-sibling::option[@value="b"][not(@selected)][.="B"]
  227. ]
  228. [count(./option)=3]
  229. '
  230. );
  231. }
  232. public function testGroupedChoice()
  233. {
  234. $form = $this->factory->create('choice', 'name', array(
  235. 'choices' => array(
  236. 'Group1' => array('a' => 'A', 'b' => 'B'),
  237. 'Group2' => array('c' => 'C'),
  238. ),
  239. 'data' => 'a',
  240. ));
  241. $this->assertWidgetMatchesXpath($form, array(),
  242. '/select
  243. [@name="name"]
  244. [./optgroup[@label="Group1"]
  245. [
  246. ./option[@value="a"][@selected="selected"][.="A"]
  247. /following-sibling::option[@value="b"][not(@selected)][.="B"]
  248. ]
  249. [count(./option)=2]
  250. ]
  251. [./optgroup[@label="Group2"]
  252. [./option[@value="c"][not(@selected)][.="C"]]
  253. [count(./option)=1]
  254. ]
  255. [count(./optgroup)=2]
  256. '
  257. );
  258. }
  259. public function testCountry()
  260. {
  261. $form = $this->factory->create('country', 'name', array(
  262. 'data' => 'AT',
  263. ));
  264. $this->assertWidgetMatchesXpath($form, array(),
  265. '/select
  266. [@name="name"]
  267. [./option[@value="AT"][@selected="selected"][.="Austria"]]
  268. [count(./option)>200]
  269. '
  270. );
  271. }
  272. public function testCsrf()
  273. {
  274. $form = $this->factory->create('csrf', 'name');
  275. $this->assertWidgetMatchesXpath($form, array(),
  276. '/input
  277. [@type="hidden"]
  278. [string-length(@value)>=40]
  279. '
  280. );
  281. }
  282. public function testDateTime()
  283. {
  284. $form = $this->factory->create('datetime', 'name', array(
  285. 'data' => '2011-02-03 04:05:06',
  286. 'input' => 'string',
  287. 'with_seconds' => false,
  288. ));
  289. $this->assertWidgetMatchesXpath($form, array(),
  290. '/div
  291. [
  292. ./div
  293. [@id="name_date"]
  294. [
  295. ./select
  296. [@id="name_date_month"]
  297. [./option[@value="2"][@selected="selected"]]
  298. /following-sibling::select
  299. [@id="name_date_day"]
  300. [./option[@value="3"][@selected="selected"]]
  301. /following-sibling::select
  302. [@id="name_date_year"]
  303. [./option[@value="2011"][@selected="selected"]]
  304. ]
  305. /following-sibling::div
  306. [@id="name_time"]
  307. [
  308. ./select
  309. [@id="name_time_hour"]
  310. [./option[@value="4"][@selected="selected"]]
  311. /following-sibling::select
  312. [@id="name_time_minute"]
  313. [./option[@value="5"][@selected="selected"]]
  314. ]
  315. ]
  316. [count(.//select)=5]
  317. '
  318. );
  319. }
  320. public function testDateTimeWithSeconds()
  321. {
  322. $form = $this->factory->create('datetime', 'name', array(
  323. 'data' => '2011-02-03 04:05:06',
  324. 'input' => 'string',
  325. 'with_seconds' => true,
  326. ));
  327. $this->assertWidgetMatchesXpath($form, array(),
  328. '/div
  329. [
  330. ./div
  331. [@id="name_date"]
  332. [
  333. ./select
  334. [@id="name_date_month"]
  335. [./option[@value="2"][@selected="selected"]]
  336. /following-sibling::select
  337. [@id="name_date_day"]
  338. [./option[@value="3"][@selected="selected"]]
  339. /following-sibling::select
  340. [@id="name_date_year"]
  341. [./option[@value="2011"][@selected="selected"]]
  342. ]
  343. /following-sibling::div
  344. [@id="name_time"]
  345. [
  346. ./select
  347. [@id="name_time_hour"]
  348. [./option[@value="4"][@selected="selected"]]
  349. /following-sibling::select
  350. [@id="name_time_minute"]
  351. [./option[@value="5"][@selected="selected"]]
  352. /following-sibling::select
  353. [@id="name_time_second"]
  354. [./option[@value="6"][@selected="selected"]]
  355. ]
  356. ]
  357. [count(.//select)=6]
  358. '
  359. );
  360. }
  361. public function testDateChoice()
  362. {
  363. $form = $this->factory->create('date', 'name', array(
  364. 'data' => '2011-02-03',
  365. 'input' => 'string',
  366. 'widget' => 'choice',
  367. ));
  368. $this->assertWidgetMatchesXpath($form, array(),
  369. '/div
  370. [
  371. ./select
  372. [@id="name_month"]
  373. [./option[@value="2"][@selected="selected"]]
  374. /following-sibling::select
  375. [@id="name_day"]
  376. [./option[@value="3"][@selected="selected"]]
  377. /following-sibling::select
  378. [@id="name_year"]
  379. [./option[@value="2011"][@selected="selected"]]
  380. ]
  381. [count(./select)=3]
  382. '
  383. );
  384. }
  385. public function testDateText()
  386. {
  387. $form = $this->factory->create('date', 'name', array(
  388. 'data' => '2011-02-03',
  389. 'input' => 'string',
  390. 'widget' => 'text',
  391. ));
  392. $this->assertWidgetMatchesXpath($form, array(),
  393. '/input
  394. [@type="text"]
  395. [@name="name"]
  396. [@value="Feb 3, 2011"]
  397. '
  398. );
  399. }
  400. public function testFile()
  401. {
  402. $form = $this->factory->create('file', 'name');
  403. $this->assertWidgetMatchesXpath($form, array(),
  404. '/div
  405. [
  406. ./input[@type="file"][@id="name_file"]
  407. /following-sibling::input[@type="hidden"][@id="name_token"]
  408. /following-sibling::input[@type="hidden"][@id="name_name"]
  409. ]
  410. [count(./input)=3]
  411. '
  412. );
  413. }
  414. public function testHidden()
  415. {
  416. $form = $this->factory->create('hidden', 'name', array(
  417. 'data' => 'foobar',
  418. ));
  419. $this->assertWidgetMatchesXpath($form, array(),
  420. '/input
  421. [@type="hidden"]
  422. [@name="name"]
  423. [@value="foobar"]
  424. '
  425. );
  426. }
  427. public function testInteger()
  428. {
  429. $form = $this->factory->create('integer', 'name', array(
  430. 'data' => '123',
  431. ));
  432. $this->assertWidgetMatchesXpath($form, array(),
  433. '/input
  434. [@type="number"]
  435. [@name="name"]
  436. [@value="123"]
  437. '
  438. );
  439. }
  440. public function testLanguage()
  441. {
  442. $form = $this->factory->create('language', 'name', array(
  443. 'data' => 'de',
  444. ));
  445. $this->assertWidgetMatchesXpath($form, array(),
  446. '/select
  447. [@name="name"]
  448. [./option[@value="de"][@selected="selected"][.="German"]]
  449. [count(./option)>200]
  450. '
  451. );
  452. }
  453. public function testLocale()
  454. {
  455. $form = $this->factory->create('locale', 'name', array(
  456. 'data' => 'de_AT',
  457. ));
  458. $this->assertWidgetMatchesXpath($form, array(),
  459. '/select
  460. [@name="name"]
  461. [./option[@value="de_AT"][@selected="selected"][.="German (Austria)"]]
  462. [count(./option)>200]
  463. '
  464. );
  465. }
  466. public function testMoney()
  467. {
  468. $form = $this->factory->create('money', 'name', array(
  469. 'data' => 1234.56,
  470. 'currency' => 'EUR',
  471. ));
  472. $this->assertWidgetMatchesXpath($form, array(),
  473. '/input
  474. [@type="text"]
  475. [@name="name"]
  476. [@value="1234.56"]
  477. [contains(.., "€")]
  478. '
  479. );
  480. }
  481. public function testNumber()
  482. {
  483. $form = $this->factory->create('number', 'name', array(
  484. 'data' => 1234.56,
  485. ));
  486. $this->assertWidgetMatchesXpath($form, array(),
  487. '/input
  488. [@type="text"]
  489. [@name="name"]
  490. [@value="1234.56"]
  491. '
  492. );
  493. }
  494. public function testPassword()
  495. {
  496. $form = $this->factory->create('password', 'name', array(
  497. 'data' => 'Pa$sW0rD',
  498. ));
  499. $this->assertWidgetMatchesXpath($form, array(),
  500. '/input
  501. [@type="password"]
  502. [@name="name"]
  503. [@value=""]
  504. '
  505. );
  506. }
  507. public function testPasswordWithMaxLength()
  508. {
  509. $form = $this->factory->create('password', 'name', array(
  510. 'data' => 'Pa$sW0rD',
  511. 'max_length' => 123,
  512. ));
  513. $this->assertWidgetMatchesXpath($form, array(),
  514. '/input
  515. [@type="password"]
  516. [@name="name"]
  517. [@value=""]
  518. [@maxlength="123"]
  519. '
  520. );
  521. }
  522. public function testPercent()
  523. {
  524. $form = $this->factory->create('percent', 'name', array(
  525. 'data' => 0.1,
  526. ));
  527. $this->assertWidgetMatchesXpath($form, array(),
  528. '/input
  529. [@type="text"]
  530. [@name="name"]
  531. [@value="10"]
  532. [contains(.., "%")]
  533. '
  534. );
  535. }
  536. public function testCheckedRadio()
  537. {
  538. $form = $this->factory->create('radio', 'name', array(
  539. 'data' => true,
  540. ));
  541. $this->assertWidgetMatchesXpath($form, array(),
  542. '/input
  543. [@type="radio"]
  544. [@name="name"]
  545. [@checked="checked"]
  546. [@value=""]
  547. '
  548. );
  549. }
  550. public function testCheckedRadioWithValue()
  551. {
  552. $form = $this->factory->create('radio', 'name', array(
  553. 'data' => true,
  554. 'value' => 'foobar',
  555. ));
  556. $this->assertWidgetMatchesXpath($form, array(),
  557. '/input
  558. [@type="radio"]
  559. [@name="name"]
  560. [@checked="checked"]
  561. [@value="foobar"]
  562. '
  563. );
  564. }
  565. public function testUncheckedRadio()
  566. {
  567. $form = $this->factory->create('radio', 'name', array(
  568. 'data' => false,
  569. ));
  570. $this->assertWidgetMatchesXpath($form, array(),
  571. '/input
  572. [@type="radio"]
  573. [@name="name"]
  574. [not(@checked)]
  575. '
  576. );
  577. }
  578. public function testTextarea()
  579. {
  580. $form = $this->factory->create('textarea', 'name', array(
  581. 'data' => 'foobar',
  582. ));
  583. $this->assertWidgetMatchesXpath($form, array(),
  584. '/textarea
  585. [@name="name"]
  586. [.="foobar"]
  587. '
  588. );
  589. }
  590. public function testText()
  591. {
  592. $form = $this->factory->create('text', 'name', array(
  593. 'data' => 'foobar',
  594. ));
  595. $this->assertWidgetMatchesXpath($form, array(),
  596. '/input
  597. [@type="text"]
  598. [@name="name"]
  599. [@value="foobar"]
  600. [not(@maxlength)]
  601. '
  602. );
  603. }
  604. public function testTextWithMaxLength()
  605. {
  606. $form = $this->factory->create('text', 'name', array(
  607. 'data' => 'foobar',
  608. 'max_length' => 123,
  609. ));
  610. $this->assertWidgetMatchesXpath($form, array(),
  611. '/input
  612. [@type="text"]
  613. [@name="name"]
  614. [@value="foobar"]
  615. [@maxlength="123"]
  616. '
  617. );
  618. }
  619. public function testTime()
  620. {
  621. $form = $this->factory->create('time', 'name', array(
  622. 'data' => '04:05:06',
  623. 'input' => 'string',
  624. 'with_seconds' => false,
  625. ));
  626. $this->assertWidgetMatchesXpath($form, array(),
  627. '/div
  628. [
  629. ./select
  630. [@id="name_hour"]
  631. [./option[@value="4"][@selected="selected"]]
  632. /following-sibling::select
  633. [@id="name_minute"]
  634. [./option[@value="5"][@selected="selected"]]
  635. ]
  636. [count(./select)=2]
  637. '
  638. );
  639. }
  640. public function testTimeWithSeconds()
  641. {
  642. $form = $this->factory->create('time', 'name', array(
  643. 'data' => '04:05:06',
  644. 'input' => 'string',
  645. 'with_seconds' => true,
  646. ));
  647. $this->assertWidgetMatchesXpath($form, array(),
  648. '/div
  649. [
  650. ./select
  651. [@id="name_hour"]
  652. [./option[@value="4"][@selected="selected"]]
  653. /following-sibling::select
  654. [@id="name_minute"]
  655. [./option[@value="5"][@selected="selected"]]
  656. /following-sibling::select
  657. [@id="name_second"]
  658. [./option[@value="6"][@selected="selected"]]
  659. ]
  660. [count(./select)=3]
  661. '
  662. );
  663. }
  664. public function testTimezone()
  665. {
  666. $form = $this->factory->create('timezone', 'name', array(
  667. 'data' => 'Europe/Vienna',
  668. ));
  669. $this->assertWidgetMatchesXpath($form, array(),
  670. '/select
  671. [@name="name"]
  672. [./optgroup
  673. [@label="Europe"]
  674. [./option[@value="Europe/Vienna"][@selected="selected"][.="Vienna"]]
  675. ]
  676. [count(./optgroup)>10]
  677. [count(.//option)>200]
  678. '
  679. );
  680. }
  681. public function testUrl()
  682. {
  683. $form = $this->factory->create('url', 'name', array(
  684. 'data' => 'http://www.google.com',
  685. ));
  686. $this->assertWidgetMatchesXpath($form, array(),
  687. '/input
  688. [@type="url"]
  689. [@name="name"]
  690. [@value="http://www.google.com"]
  691. '
  692. );
  693. }
  694. }