1: <?php
2:
3: namespace GLFramework\Tests;
4:
5: use Exception;
6: use GLFramework\DatabaseManager;
7: use GLFramework\DBStructure;
8: use GLFramework\Model;
9: use GLFramework\Module\ModuleManager;
10: use GLFramework\Response;
11: use InvalidArgumentException;
12: use GLFramework\Bootstrap;
13: use Symfony\Component\DomCrawler\Crawler;
14: use Symfony\Component\DomCrawler\Form;
15:
16: 17: 18: 19: 20: 21:
22: class TestCase extends \PHPUnit_Framework_TestCase
23: {
24:
25:
26: private $internal_host = "http://localhost";
27: 28: 29:
30: protected $crawler;
31:
32: 33: 34:
35: protected $response;
36:
37: protected $inputs = array();
38:
39: protected $uploads = array();
40:
41: private $redirections = array();
42:
43: private $models_delete = array();
44:
45: private $database;
46:
47: public function requireDatabase()
48: {
49: if($this->database == null)
50: {
51: $this->database = new DatabaseManager();
52:
53: $this->assertTrue($this->database->connect(), "Can not connect to database for testing!");
54:
55: $db2 = new DBStructure();
56: $db2->setDatabaseUpdate();
57: $models = Bootstrap::getSingleton()->getModels();
58: foreach ($models as $model) {
59:
60: $instance = new $model(null);
61: if ($instance instanceof Model) {
62: $diff = $instance->getStructureDifferences();
63: foreach ($diff as $action) {
64: $this->database->exec($action['sql']);
65: }
66:
67: }
68: }
69:
70: }
71: }
72:
73: protected function tearDown()
74: {
75: parent::tearDown();
76: foreach($this->models_delete as $model)
77: {
78: $model->delete();
79: }
80: }
81:
82:
83: public function removeLater($model)
84: {
85: if(!is_array($model))
86: {
87: $model = array($model);
88: }
89: foreach($model as $m)
90: $this->models_delete[] = $m;
91: }
92:
93: 94: 95:
96: public function getDatabase()
97: {
98: return $this->database;
99: }
100:
101:
102: public function visit($url)
103: {
104: return $this->call("GET", $url);
105: }
106:
107: 108: 109: 110: 111:
112: public function makeRequestUsingForm($form, $uploads = array())
113: {
114: $method = $form->getMethod();
115: $uri = $form->getUri();
116: parse_str(http_build_query($form->getValues()), $parameters);
117: return $this->call($method, $uri, $parameters, [], $uploads);
118: }
119:
120: public function call($method, $uri, $params = array(), $cookies = array(), $files = array())
121: {
122: $uri = str_replace($this->internal_host, "", $uri);
123: if(strpos($uri, "/") !== 0) $uri = "/" . $uri;
124: $bs = Bootstrap::getSingleton();
125: $_COOKIE = $cookies;
126: $_REQUEST = $params;
127: $_POST = $params;
128: $_FILES = $this->buildFilesForm($files);
129: $_SERVER['REQUEST_URI'] = $this->internal_host . $uri;
130: $this->response = $bs->run($uri, $method);
131: $this->followRedirects();
132: $this->crawler = new Crawler($this->response->getContent(), $this->internal_host . $uri);
133:
134: return $this;
135: }
136:
137: public function followRedirects()
138: {
139: if($this->response->isRedirect())
140: {
141: $this->redirections[$this->response->getRedirection()] += 1;
142: $this->assertLessThan(10, $this->redirections[$this->response->getRedirection()], "Ciclic redirection: " . $this->response->getRedirection());
143: $this->call('GET', $this->response->getRedirection());
144: }
145: return $this;
146: }
147:
148: public function buildFilesForm($files)
149: {
150: $result = array();
151: if(is_array($files))
152: {
153: foreach($files as $file)
154: {
155: if(is_string($file))
156: {
157: if(file_exists($file))
158: {
159: $this->buildFile(0, $result, $file);
160: }
161: else if($file == "")
162: {
163: $this->buildFile(4, $result);
164: }
165: }
166:
167: }
168: }
169: else{
170: if(file_exists($files))
171: {
172: $this->buildFile(0, $result, $files);
173: }
174: else if($files == "")
175: {
176: $this->buildFile(4, $result);
177: }
178: }
179: return $result;
180: }
181:
182: private function buildFile($error, &$array = array(), $tmp_name = "", $name = "", $type = "", $size = "")
183: {
184: if($tmp_name != "" && $name == "")
185: {
186: $name = basename($tmp_name);
187: }
188: if($tmp_name != "" && $type == "")
189: {
190: $type = mime_content_type($tmp_name);
191: }
192: if($tmp_name != "" && $size == "")
193: {
194: $size = filesize($tmp_name);
195: }
196: if(isset($array['error']))
197: {
198: $array['error'][] = $error;
199: $array['name'][] = $name;
200: $array['tmp_name'][] = $tmp_name;
201: $array['type'][] = $type;
202: $array['size'][] = $size;
203:
204: }
205: else
206: {
207: $array['error'] = $error;
208: $array['name'] = $name;
209: $array['tmp_name'] = $tmp_name;
210: $array['type'] = $type;
211: $array['size'] = $size;
212: }
213: return $array;
214: }
215:
216: public function getContent()
217: {
218: return $this->response->getContent();
219: }
220:
221:
222: public function see($text, $negate = false)
223: {
224: $method = $negate ? 'assertNotRegExp' : 'assertRegExp';
225: $rawPattern = preg_quote($text, '/');
226: $escapedPattern = preg_quote(e($text), '/');
227: $pattern = $rawPattern == $escapedPattern
228: ? $rawPattern : "({$rawPattern}|{$escapedPattern})";
229: $this->$method("/$pattern/i", $this->getContent());
230: return $this;
231: }
232:
233: protected function dontSee($text)
234: {
235: return $this->see($text, true);
236: }
237:
238: protected function seeInElement($element, $text, $negate = false)
239: {
240: $method = $negate ? 'assertNotRegExp' : 'assertRegExp';
241: $rawPattern = preg_quote($text, '/');
242: $escapedPattern = preg_quote(e($text), '/');
243: $content = $this->crawler->filter($element)->html();
244: $pattern = $rawPattern == $escapedPattern
245: ? $rawPattern : "({$rawPattern}|{$escapedPattern})";
246: $this->$method("/$pattern/i", $content);
247: return $this;
248: }
249: 250: 251: 252: 253: 254: 255:
256: public function seeLink($text, $url = null)
257: {
258: $message = "No links were found with expected text [{$text}]";
259: if ($url) {
260: $message .= " and URL [{$url}]";
261: }
262: $this->assertTrue($this->hasLink($text, $url), "{$message}.");
263: return $this;
264: }
265: 266: 267: 268: 269: 270: 271:
272: public function dontSeeLink($text, $url = null)
273: {
274: $message = "A link was found with expected text [{$text}]";
275: if ($url) {
276: $message .= " and URL [{$url}]";
277: }
278: $this->assertFalse($this->hasLink($text, $url), "{$message}.");
279: return $this;
280: }
281:
282: 283: 284: 285: 286: 287: 288:
289: public function seeInField($selector, $expected)
290: {
291: $this->assertSame(
292: $expected, $this->getInputOrTextAreaValue($selector),
293: "The field [{$selector}] does not contain the expected value [{$expected}]."
294: );
295: return $this;
296: }
297: 298: 299: 300: 301: 302: 303:
304: public function dontSeeInField($selector, $value)
305: {
306: $this->assertNotSame(
307: $this->getInputOrTextAreaValue($selector), $value,
308: "The input [{$selector}] should not contain the value [{$value}]."
309: );
310: return $this;
311: }
312: 313: 314: 315: 316: 317:
318: public function seeIsChecked($selector)
319: {
320: $this->assertTrue(
321: $this->isChecked($selector),
322: "The checkbox [{$selector}] is not checked."
323: );
324: return $this;
325: }
326: 327: 328: 329: 330: 331:
332: public function dontSeeIsChecked($selector)
333: {
334: $this->assertFalse(
335: $this->isChecked($selector),
336: "The checkbox [{$selector}] is checked."
337: );
338: return $this;
339: }
340: 341: 342: 343: 344: 345: 346:
347: public function seeIsSelected($selector, $expected)
348: {
349: $this->assertEquals(
350: $expected, $this->getSelectedValue($selector),
351: "The field [{$selector}] does not contain the selected value [{$expected}]."
352: );
353: return $this;
354: }
355: 356: 357: 358: 359: 360: 361:
362: public function dontSeeIsSelected($selector, $value)
363: {
364: $this->assertNotEquals(
365: $value, $this->getSelectedValue($selector),
366: "The field [{$selector}] contains the selected value [{$value}]."
367: );
368: return $this;
369: }
370: 371: 372: 373: 374: 375: 376: 377:
378: protected function getInputOrTextAreaValue($selector)
379: {
380: $field = $this->filterByNameOrId($selector, ['input', 'textarea']);
381: if ($field->count() == 0) {
382: throw new Exception("There are no elements with the name or ID [$selector].");
383: }
384: $element = $field->nodeName();
385: if ($element == 'input') {
386: return $field->attr('value');
387: }
388: if ($element == 'textarea') {
389: return $field->text();
390: }
391: throw new Exception("Given selector [$selector] is not an input or textarea.");
392: }
393: 394: 395: 396: 397: 398: 399: 400:
401: protected function getSelectedValue($selector)
402: {
403: $field = $this->filterByNameOrId($selector);
404: if ($field->count() == 0) {
405: throw new Exception("There are no elements with the name or ID [$selector].");
406: }
407: $element = $field->nodeName();
408: if ($element == 'select') {
409: return $this->getSelectedValueFromSelect($field);
410: }
411: if ($element == 'input') {
412: return $this->getCheckedValueFromRadioGroup($field);
413: }
414: throw new Exception("Given selector [$selector] is not a select or radio group.");
415: }
416: 417: 418: 419: 420: 421: 422: 423:
424: protected function getSelectedValueFromSelect(Crawler $field)
425: {
426: if ($field->nodeName() !== 'select') {
427: throw new Exception('Given element is not a select element.');
428: }
429: foreach ($field->children() as $option) {
430: if ($option->hasAttribute('selected')) {
431: return $option->getAttribute('value');
432: }
433: }
434: return;
435: }
436: 437: 438: 439: 440: 441: 442: 443:
444: protected function getCheckedValueFromRadioGroup(Crawler $radioGroup)
445: {
446: if ($radioGroup->nodeName() !== 'input' || $radioGroup->attr('type') !== 'radio') {
447: throw new Exception('Given element is not a radio button.');
448: }
449: foreach ($radioGroup as $radio) {
450: if ($radio->hasAttribute('checked')) {
451: return $radio->getAttribute('value');
452: }
453: }
454: return;
455: }
456: 457: 458: 459: 460: 461: 462: 463:
464: protected function isChecked($selector)
465: {
466: $checkbox = $this->filterByNameOrId($selector, "input[type='checkbox']");
467: if ($checkbox->count() == 0) {
468: throw new Exception("There are no checkbox elements with the name or ID [$selector].");
469: }
470: return $checkbox->attr('checked') !== null;
471: }
472: 473: 474: 475: 476: 477: 478: 479:
480: protected function click($name)
481: {
482: $link = $this->crawler->selectLink($name);
483: if (! count($link)) {
484: $link = $this->filterByNameOrId($name, 'a');
485: if (! count($link)) {
486: throw new InvalidArgumentException(
487: "Could not find a link with a body, name, or ID attribute of [{$name}]."
488: );
489: }
490: }
491: $this->visit($link->link()->getUri());
492: return $this;
493: }
494:
495:
496: 497: 498: 499: 500: 501: 502:
503: protected function type($text, $element)
504: {
505: return $this->storeInput($element, $text);
506: }
507: 508: 509: 510: 511: 512:
513: protected function check($element)
514: {
515: return $this->storeInput($element, true);
516: }
517: 518: 519: 520: 521: 522:
523: protected function uncheck($element)
524: {
525: return $this->storeInput($element, false);
526: }
527: 528: 529: 530: 531: 532: 533:
534: protected function select($option, $element)
535: {
536: return $this->storeInput($element, $option);
537: }
538: 539: 540: 541: 542: 543: 544:
545: protected function attach($absolutePath, $element)
546: {
547: $this->uploads[$element] = $absolutePath;
548: return $this->storeInput($element, $absolutePath);
549: }
550: 551: 552: 553: 554: 555:
556: protected function press($buttonText)
557: {
558: return $this->submitForm($buttonText, $this->inputs, $this->uploads);
559: }
560: 561: 562: 563: 564: 565: 566: 567:
568: protected function submitForm($buttonText, $inputs = [], $uploads = [])
569: {
570: $this->makeRequestUsingForm($this->fillForm($buttonText, $inputs), $uploads);
571: return $this;
572: }
573: 574: 575: 576: 577: 578: 579:
580: protected function fillForm($buttonText, $inputs = [])
581: {
582: if (! is_string($buttonText)) {
583: $inputs = $buttonText;
584: $buttonText = null;
585: }
586: return $this->getForm($buttonText)->setValues($inputs);
587: }
588: 589: 590: 591: 592: 593: 594: 595:
596: protected function getForm($buttonText = null)
597: {
598: try {
599: if ($buttonText) {
600: return $this->crawler->selectButton($buttonText)->form();
601: }
602: return $this->crawler->filter('form')->form();
603: } catch (InvalidArgumentException $e) {
604: throw new InvalidArgumentException(
605: "Could not find a form that has submit button [{$buttonText}]."
606: );
607: }
608: }
609: 610: 611: 612: 613: 614: 615:
616: protected function storeInput($element, $text)
617: {
618: $this->assertFilterProducesResults($element);
619: $element = str_replace('#', '', $element);
620: $this->inputs[$element] = $text;
621: return $this;
622: }
623: 624: 625: 626: 627: 628: 629: 630:
631: protected function assertFilterProducesResults($filter)
632: {
633: $crawler = $this->filterByNameOrId($filter);
634: if (! count($crawler)) {
635: throw new InvalidArgumentException(
636: "Nothing matched the filter [{$filter}] CSS query provided for [{$this->currentUri}]."
637: );
638: }
639: }
640: 641: 642: 643: 644: 645: 646:
647: protected function filterByNameOrId($name, $elements = '*')
648: {
649: $name = str_replace('#', '', $name);
650: $id = str_replace(['[', ']'], ['\\[', '\\]'], $name);
651: $elements = is_array($elements) ? $elements : [$elements];
652: array_walk($elements, function (&$element) use ($name, $id) {
653: $element = "{$element}#{$id}, {$element}[name='{$name}']";
654: });
655: return $this->crawler->filter(implode(', ', $elements));
656: }
657: }