├── .gitignore
├── .travis.yml
├── App
├── .htaccess
├── Config
│ ├── codeception-local-sample.php
│ └── codeception.php
├── Functions
│ └── helpers.php
├── Lib
│ ├── Codeception.php
│ ├── Site.php
│ └── Test.php
├── Routes
│ ├── executable.route.php
│ ├── logs.route.php
│ ├── run.route.php
│ └── webception.route.php
├── Screenshots
│ ├── webception-no-executable.png
│ ├── webception-ready.png
│ └── webception.gif
├── Templates
│ ├── _cache
│ │ └── .gitignore
│ ├── dashboard.html
│ ├── error.php
│ └── layout
│ │ └── master.html
├── Tests
│ ├── _bootstrap.php
│ ├── _config
│ │ ├── codeception_executable_fail.php
│ │ ├── codeception_fail.php
│ │ ├── codeception_log_fail.php
│ │ ├── codeception_log_fail.yml
│ │ ├── codeception_pass.php
│ │ ├── codeception_pass.yml
│ │ └── codeception_yml_fail.php
│ ├── _data
│ │ └── dump.sql
│ ├── _helpers
│ │ ├── CodeGuy.php
│ │ ├── CodeHelper.php
│ │ ├── TestGuy.php
│ │ ├── TestHelper.php
│ │ ├── WebGuy.php
│ │ ├── WebHelper.php
│ │ └── _generated
│ │ │ ├── CodeGuyActions.php
│ │ │ ├── TestGuyActions.php
│ │ │ └── WebGuyActions.php
│ ├── _log
│ │ └── .gitignore
│ ├── acceptance.suite.yml
│ ├── acceptance
│ │ ├── CheckAJAXWhenExecutableFailsCept.php
│ │ ├── CheckAJAXWhenExecutablePassesCept.php
│ │ ├── CheckAJAXWhenLogFailsCept.php
│ │ ├── CheckAJAXWhenLogPassesCept.php
│ │ ├── RunFailTestCept.php
│ │ ├── RunNonExistentTestCept.php
│ │ ├── RunPassingTestCept.php
│ │ ├── TheTestThatFailsCept.php
│ │ ├── TheTestThatPassesCept.php
│ │ ├── WebGuy.php
│ │ └── _bootstrap.php
│ ├── functional.suite.yml
│ ├── functional
│ │ ├── TestGuy.php
│ │ └── _bootstrap.php
│ ├── unit.suite.yml
│ └── unit
│ │ ├── CodeGuy.php
│ │ ├── CodeceptionClassTestsTest.php
│ │ ├── SiteClassTestsTest.php
│ │ ├── WebceptionClassTestsTest.php
│ │ └── _bootstrap.php
└── bootstrap.php
├── CHANGELOG.md
├── LICENSE
├── README.md
├── TODO.md
├── codeception.yml
├── composer.json
├── composer.lock
├── public
├── .htaccess
├── css
│ ├── app.css
│ ├── foundation.css
│ ├── foundation.min.css
│ ├── jquery.jscrollpane.css
│ └── normalize.css
├── index.php
└── js
│ ├── app.js
│ ├── libs
│ ├── angular.min.js
│ ├── foundation.min.js
│ ├── foundation
│ │ ├── foundation.abide.js
│ │ ├── foundation.accordion.js
│ │ ├── foundation.alert.js
│ │ ├── foundation.clearing.js
│ │ ├── foundation.dropdown.js
│ │ ├── foundation.interchange.js
│ │ ├── foundation.joyride.js
│ │ ├── foundation.js
│ │ ├── foundation.magellan.js
│ │ ├── foundation.offcanvas.js
│ │ ├── foundation.orbit.js
│ │ ├── foundation.reveal.js
│ │ ├── foundation.tab.js
│ │ ├── foundation.tooltip.js
│ │ └── foundation.topbar.js
│ ├── jquery.js
│ ├── modernizr.js
│ └── sprintf.js
│ └── vendor
│ ├── custom.modernizr.js
│ ├── fastclick.js
│ ├── jquery.autocomplete.js
│ ├── jquery.cookie.js
│ ├── jquery.js
│ └── placeholder.js
└── travis_dependencies.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.phar
2 | vendor
3 | package/codecept.phar
4 | App/Config/codeception-local.php
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | sudo: false
3 |
4 | php:
5 | - 7.1
6 | - 7.2
7 |
8 | before_script:
9 | - sh -e /etc/init.d/xvfb start
10 | - export DISPLAY=:99.0
11 | - sleep 4
12 |
13 | - curl http://getcomposer.org/installer | php
14 | - php composer.phar install --prefer-source
15 |
16 | - chmod a+x vendor/bin/codecept
17 |
18 | - chmod -R 777 App/Tests/_log
19 | - chmod -R 777 App/Templates/_cache
20 |
21 | - chmod a+x travis_dependencies.sh
22 | - ./travis_dependencies.sh
23 | script: "vendor/bin/codecept run --skip-group=web-interface-only unit"
24 |
--------------------------------------------------------------------------------
/App/.htaccess:
--------------------------------------------------------------------------------
1 | Deny from all
--------------------------------------------------------------------------------
/App/Config/codeception-local-sample.php:
--------------------------------------------------------------------------------
1 | array(
20 | // Add your own sites here
21 | ),
22 | );
23 |
--------------------------------------------------------------------------------
/App/Config/codeception.php:
--------------------------------------------------------------------------------
1 | array(
39 | 'Webception' => dirname(__FILE__) .DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'..'
40 | .DIRECTORY_SEPARATOR.'codeception.yml',
41 | ),
42 |
43 | /*
44 | |--------------------------------------------------------------------------
45 | | Execute Codeception as a PHP command
46 | |--------------------------------------------------------------------------
47 | */
48 | 'run_php' => TRUE,
49 |
50 | /*
51 | |--------------------------------------------------------------------------
52 | | Codeception Executable
53 | |--------------------------------------------------------------------------
54 | |
55 | | Codeception is installed as a dependancy of Webception via Composer.
56 | |
57 | | You might need to set 'sudo chmod a+x vendor/bin/codecept' to allow Apache
58 | | to execute the Codeception executable.
59 | |
60 | */
61 |
62 | 'executable' =>
63 | dirname(__FILE__) .
64 | DIRECTORY_SEPARATOR.'..'.
65 | DIRECTORY_SEPARATOR.'..'.
66 | DIRECTORY_SEPARATOR.'vendor'.
67 | DIRECTORY_SEPARATOR.'codeception'.
68 | DIRECTORY_SEPARATOR.'codeception'.
69 | DIRECTORY_SEPARATOR.'codecept',
70 |
71 |
72 | /*
73 | |--------------------------------------------------------------------------
74 | | You get to decide which type of tests get included.
75 | |--------------------------------------------------------------------------
76 | */
77 |
78 | 'tests' => array(
79 | 'acceptance' => TRUE,
80 | 'functional' => TRUE,
81 | 'unit' => TRUE,
82 | ),
83 |
84 | /*
85 | |--------------------------------------------------------------------------
86 | | When we scan for the tests, we need to ignore the following files.
87 | |--------------------------------------------------------------------------
88 | */
89 |
90 | 'ignore' => array(
91 | 'WebGuy.php',
92 | 'TestGuy.php',
93 | 'CodeGuy.php',
94 | 'AcceptanceTester.php',
95 | 'FunctionalTester.php',
96 | 'UnitTester.php',
97 | '_bootstrap.php',
98 | '.DS_Store',
99 | ),
100 |
101 | /*
102 | |--------------------------------------------------------------------------
103 | | Setting the location as the current file helps with offering information
104 | | about where this configuration file sits on the server.
105 | |--------------------------------------------------------------------------
106 | */
107 |
108 | 'location' => __FILE__,
109 |
110 | /*
111 | |--------------------------------------------------------------------------
112 | | Setting a Directory seperator in the configuration.
113 | | @todo Implement config driven seperator inplace of DIRECTORY_SEPERATOR
114 | |--------------------------------------------------------------------------
115 | */
116 | 'DS' => DIRECTORY_SEPARATOR,
117 |
118 | /*
119 | |--------------------------------------------------------------------------
120 | | Setting whether to pass additional run commands
121 | |--------------------------------------------------------------------------
122 | */
123 | 'debug' => FALSE,
124 | 'steps' => TRUE,
125 | ), $localConfig);
126 |
--------------------------------------------------------------------------------
/App/Functions/helpers.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | if (! function_exists('run_terminal_command')) {
13 |
14 | /**
15 | * Run a terminal command.
16 | *
17 | * @param string $command
18 | * @return array Each array entry is a line of output from running the command.
19 | */
20 | function run_terminal_command($command)
21 | {
22 | $output = array();
23 |
24 | $spec = array(
25 | 0 => array("pipe", "r"), // stdin is a pipe that the child will read from
26 | 1 => array("pipe", "w"), // stdout is a pipe that the child will write to
27 | 2 => array("pipe", "w") // stderr is a pipe that the child will write to
28 | );
29 |
30 | flush();
31 |
32 | $process = proc_open($command, $spec, $pipes, realpath('./'), (!empty($_ENV) ? $_ENV : NULL));
33 |
34 | if (is_resource($process)) {
35 |
36 | while ($line = fgets($pipes[1])) {
37 |
38 | // Trim any line breaks and white space
39 | $line = trim(preg_replace("/\r|\n/", "", $line));
40 |
41 | // If the line has content, add to the output log.
42 | if (! empty($line))
43 | $output[] = $line;
44 |
45 | flush();
46 | }
47 | }
48 |
49 | return $output;
50 | }
51 |
52 | }
53 |
54 | if (! function_exists('get_webception_config')) {
55 |
56 | /**
57 | * Decide on which Webception configuration file to load
58 | * based on the 'test' query string parameter.
59 | *
60 | * If the test config is not found, it falls back to the default file.
61 | *
62 | * @param object $app Slim's App object.
63 | * @return array Array of the application config.
64 | */
65 | function get_webception_config($app)
66 | {
67 | $config = FALSE;
68 | $test_type = $app->request()->params('test');
69 | $webception_config = $app->config('webception');
70 |
71 | // If the test query string parameter is set,
72 | // a test config will be loaded.
73 | if ($test_type !== NULL) {
74 |
75 | // Sanitize the test type.
76 | $test_type = trim(strtolower(remove_file_extension($test_type)));
77 |
78 | // Filter the test type into the test string.
79 | $test_config = sprintf($webception_config['test'], $test_type);
80 |
81 | // Load the config if it can be found
82 | if (file_exists($test_config))
83 | $config = require_once($test_config);
84 | }
85 |
86 | if ($config == FALSE)
87 | $config = require_once($webception_config['config']);
88 |
89 | return $config;
90 | }
91 | }
92 |
93 | if (! function_exists('get_route_param')) {
94 |
95 | /**
96 | * Given the Application, and a given name of a parameter
97 | * return the value of the parameter.
98 | *
99 | * @param object $app
100 | * @param string $param Name of route
101 | * @return value if found or false.
102 | */
103 | function get_route_param($app, $param)
104 | {
105 | $route = $app->router()->getCurrentRoute();
106 |
107 | $params = $route->getParams();
108 |
109 | if (isset($params[$param]))
110 | return $params[$param];
111 |
112 | return FALSE;
113 | }
114 |
115 | }
116 |
117 | if (! function_exists('camel_to_sentance')) {
118 |
119 | /**
120 | * Take a camel cased string and turn it into a word seperated sentance.
121 | * e.g. 'ThisIsASentance' would turn into 'This Is A Sentance'
122 | *
123 | * @param string $string
124 | * @return string
125 | */
126 | function camel_to_sentance($string)
127 | {
128 | return trim(preg_replace('/(?!^)[A-Z]{2,}(?=[A-Z][a-z])|[A-Z][a-z]/', ' $0', $string));
129 | }
130 |
131 | }
132 |
133 | if (! function_exists('remove_file_extension')) {
134 |
135 | /**
136 | * Given a file name, remove any file extension from the string.
137 | *
138 | * @param string $string
139 | * @return string
140 | */
141 | function remove_file_extension($string)
142 | {
143 | return preg_replace("/\\.[^.\\s]{3,4}$/", "", $string);
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/App/Lib/Site.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | class Site
13 | {
14 | /**
15 | * List of all sites set in the Codeception config.
16 | *
17 | * @var array
18 | */
19 | private $sites = array();
20 |
21 | /**
22 | * Hash value of the current site.
23 | *
24 | * @var false if not set; string if set.
25 | */
26 | private $hash = FALSE;
27 |
28 | /**
29 | * On construct, prepare the site details and the chosen site.
30 | *
31 | * @param array $config
32 | * @param string $hash
33 | */
34 | public function __construct($sites = array())
35 | {
36 | // Filter the sites by creating unique hashes.
37 | $this->sites = $this->prepare($sites);
38 | }
39 |
40 | /**
41 | * Set the current site.
42 | *
43 | * @param string if set, false if not.
44 | */
45 | public function set($hash=FALSE)
46 | {
47 | // If hash matched in sites, set hash.
48 | if (isset($this->sites[$hash])) {
49 | $this->hash = $hash;
50 | } elseif ($hash == FALSE && sizeof($this->sites) > 0) {
51 | // If no site set, but sites available,
52 | // pick the first in the list.
53 | reset($this->sites);
54 | $this->hash = key($this->sites);
55 | } else {
56 | // If no site found or none available,
57 | // set as false.
58 | $this->hash = FALSE;
59 | }
60 | }
61 |
62 | /**
63 | * Return the name of the current site.
64 | *
65 | * @return string
66 | */
67 | public function getName()
68 | {
69 | return $this->get('name');
70 | }
71 |
72 | /**
73 | * Return the full path to the Codeception.yml for the current site.
74 | *
75 | * @return string
76 | */
77 | public function getConfig()
78 | {
79 | return $this->get('path');
80 | }
81 |
82 | /**
83 | * Return just the path of the Codeception.yml file for the current site.
84 | *
85 | * @return string
86 | */
87 | public function getConfigPath()
88 | {
89 | $path = $this->get('path');
90 |
91 | return ($path !== FALSE) ?
92 | dirname($path) . '/' : FALSE;
93 | }
94 |
95 | /**
96 | * Return just the filename of the configuration file for the current site.
97 | *
98 | * Usually returns 'Codeception.yml'.
99 | *
100 | * @return string
101 | */
102 | public function getConfigFile()
103 | {
104 | $path = $this->get('path');
105 |
106 | return ($path !== FALSE) ?
107 | basename($path) : FALSE;
108 | }
109 |
110 | /**
111 | * Getter for site details.
112 | *
113 | * @param string $value Name of the required field.
114 | * @return string (or FALSE if $value not set)
115 | */
116 | public function get($value)
117 | {
118 | return ($this->hash !== FALSE) && isset($this->sites[$this->hash][$value])
119 | ? $this->sites[$this->hash][$value] : FALSE;
120 | }
121 |
122 | /**
123 | * Return the hash of the current site.
124 | *
125 | * @return string (or FALSE if not set)
126 | */
127 | public function getHash()
128 | {
129 | return $this->hash;
130 | }
131 |
132 | /**
133 | * Return full list of sites.
134 | *
135 | * @return array
136 | */
137 | public function getSites()
138 | {
139 | return $this->sites;
140 | }
141 |
142 | /**
143 | * Given a list of sites, prepare a unique hash
144 | * and tidy up the path to the Codeception configuration.
145 | *
146 | * @param array $sites List of sites set in Codeception.php configuration.
147 | * @return array $filtered
148 | */
149 | public function prepare($sites = array())
150 | {
151 | $filtered = array();
152 |
153 | foreach ($sites as $name => $path) {
154 | $filtered[md5($name)] = array(
155 | 'name' => $name,
156 | 'path' => realpath(dirname($path)) .'/' . basename($path)
157 | );
158 | }
159 |
160 | return $filtered;
161 | }
162 |
163 | /**
164 | * Confirm if the Site details are valid.
165 | *
166 | * It counts that Sites are set and a hash was decided on __construct().
167 | *
168 | * @return boolean
169 | */
170 | public function ready()
171 | {
172 | return sizeof($this->sites) > 0 && $this->hash !== FALSE;
173 | }
174 |
175 | /**
176 | * Confirm that Site class has more than one option available.
177 | *
178 | * This is used on the Webception front-end to decide
179 | * if a dropdown is required to swap sites.
180 | *
181 | * @return boolean Checks if details are ready and more than one site.
182 | */
183 | public function hasChoices()
184 | {
185 | return $this->ready() && sizeof($this->sites) > 1;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/App/Lib/Test.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | class Test
13 | {
14 | /**
15 | * MD5 of the file name
16 | *
17 | * @var string
18 | */
19 | private $hash;
20 |
21 | /**
22 | * Filename of the test.
23 | *
24 | * @var string
25 | */
26 | private $filename;
27 |
28 | /**
29 | * Readable version of the filename
30 | *
31 | * @var string
32 | */
33 | private $title;
34 |
35 | /**
36 | * The file object.
37 | *
38 | * @var SplFileInfo
39 | */
40 | private $file;
41 |
42 | /**
43 | * Test Type: Functional, Acceptance or Unit.
44 | *
45 | * @var string
46 | */
47 | private $type;
48 |
49 | /**
50 | * Log of the test result
51 | *
52 | * @var array Each array entry is a console log line.
53 | */
54 | private $log = array();
55 |
56 | /**
57 | * Result of running the test.
58 | *
59 | * @var bool
60 | */
61 | private $passed = FALSE;
62 |
63 | /**
64 | * Possible test states
65 | */
66 | const STATE_PASSED = 'passed';
67 | const STATE_FAILED = 'failed';
68 | const STATE_ERROR = 'error';
69 | const STATE_READY = 'ready';
70 |
71 | /**
72 | * List of responses that can occur from Codeception.
73 | *
74 | * Using these, we scan the result when log is added to the test.
75 | *
76 | * @var array
77 | */
78 | private $responses = array(
79 | 'timeout' => 'Operation timed out after',
80 | 'writeable' => 'Path for logs is not writable',
81 | 'passed' => array(
82 | 'PASSED', // Functional & Acceptance Test
83 | 'OK \(' // Unit Test
84 | ),
85 | 'failed' => 'FAILURES',
86 | );
87 |
88 | /**
89 | * On Test __construct, the passed matches are turned into a regex.
90 | *
91 | * @var string
92 | */
93 | private $passed_regex;
94 |
95 | /**
96 | * Colour tags from Codeception's coloured output.
97 | *
98 | * @var array
99 | */
100 | private $colour_codes = array(
101 | "[37;45m",
102 | "[2K",
103 | "[1m",
104 | "[0m",
105 | "[30;42m",
106 | "[37;41m",
107 | "[33m",
108 | "[36m",
109 | "[35;1m",
110 | "-",
111 | );
112 |
113 | /**
114 | * File extensions to remove from the output.
115 | *
116 | * @var array
117 | */
118 | private $filtered_files = array(
119 | 'Cept.php',
120 | 'Cest.php',
121 | 'Test.php',
122 | );
123 |
124 | public function __construct()
125 | {
126 | // Declare the regex string containing all the responses that
127 | // can indicate that as a passed test.
128 | $this->passed_regex = implode('|', $this->responses['passed']);
129 |
130 | // maybe there will be any more failures? Then we are going to need this
131 | $this->failure_regex = $this->responses['failed'];
132 | }
133 |
134 | /**
135 | * Initialization of the Test
136 | *
137 | * @param string $type Type of Test
138 | * @param object $file File for the Test
139 | */
140 | public function init($type, $file)
141 | {
142 | $filename = $this->filterFileName($file->getFileName());
143 | $posTypePath = strpos($file->getPathname(), DIRECTORY_SEPARATOR.$type.DIRECTORY_SEPARATOR)
144 | + strlen(DIRECTORY_SEPARATOR.$type.DIRECTORY_SEPARATOR);
145 |
146 | $this->hash = $this->makeHash($type . $filename);
147 | $this->title = $this->filterTitle($filename);
148 | $this->filename = substr($file->getPathname(), $posTypePath);
149 | $this->file = $file;
150 | $this->type = $type;
151 | $this->state = self::STATE_READY; // Not used yet.
152 | }
153 |
154 | /**
155 | * Filter out content from a title any to improve readability of the test name
156 | *
157 | * @param string $filename
158 | * @return string
159 | */
160 | public function filterFileName($filename)
161 | {
162 | return str_ireplace($this->filtered_files, '', $filename);
163 | }
164 |
165 | /**
166 | * Generate a unique hash for the test.
167 | *
168 | * @param string $string
169 | * @return string MD5 of $string
170 | */
171 | public function makeHash($string)
172 | {
173 | return md5($string);
174 | }
175 |
176 | /**
177 | * Turn a "CamelCasedString" into "Camel Cased String".
178 | * This is to improve readability of the test list.
179 | *
180 | * @param string $title
181 | * @return string
182 | */
183 | public function filterTitle($title)
184 | {
185 | return camel_to_sentance($title);
186 | }
187 |
188 | /**
189 | * Get the Test title
190 | *
191 | * @return string
192 | */
193 | public function getTitle()
194 | {
195 | return $this->title;
196 | }
197 |
198 | /**
199 | * Get the Test Hash
200 | *
201 | * The hash is the Test title that's been md5'd.
202 | *
203 | * @return string
204 | */
205 | public function getHash()
206 | {
207 | return $this->hash;
208 | }
209 |
210 | /**
211 | * Get the Test type
212 | *
213 | * @return string
214 | */
215 | public function getType()
216 | {
217 | return $this->type;
218 | }
219 |
220 | /**
221 | * Get the file Filename
222 | *
223 | * @return string
224 | */
225 | public function getFilename()
226 | {
227 | return $this->filename;
228 | }
229 |
230 | /**
231 | * Set if the test has been passed.
232 | *
233 | * @return boolean
234 | */
235 | public function setPassed()
236 | {
237 | $this->passed = TRUE;
238 | }
239 |
240 | /**
241 | * Sets passed to false if test fails
242 | */
243 | public function setFailed()
244 | {
245 | $this->passed = false;
246 | }
247 |
248 | /**
249 | * Return if the test was run and passed
250 | *
251 | * @return boolean
252 | */
253 | public function passed()
254 | {
255 | return $this->passed;
256 | }
257 |
258 | /**
259 | * Return if the test was successfully run.
260 | *
261 | * This is deteremined by simply checking the length of the log.
262 | *
263 | * @return boolean
264 | */
265 | public function ran()
266 | {
267 | return sizeof($this->log) > 0;
268 | }
269 |
270 | /**
271 | * Get the Test state based on if the test has run or passed.
272 | *
273 | * @return boolean
274 | */
275 | public function getState()
276 | {
277 | return ($this->passed() ? self::STATE_PASSED :
278 | ($this->ran() ? self::STATE_FAILED : self::STATE_ERROR));
279 | }
280 |
281 | /**
282 | * Add a new line entry to the Test log.
283 | *
284 | * Also check the log line may indicate if the Test has passed.
285 | *
286 | * @param String $line
287 | */
288 | public function setLog($lines = array())
289 | {
290 | $failed = false;
291 | foreach ($lines as $line) {
292 |
293 | if ($this->checkLogForTestPass($line) && $failed==false)
294 | $this->setPassed();
295 |
296 | if ($this->checkLogForTestFailure($line)) {
297 | $this->setFailed();
298 | $failed = true;
299 | }
300 |
301 | // Filter the line of any junk and add to the log.
302 | $this->log[] = $this->filterLog($line);
303 | }
304 | }
305 |
306 | /**
307 | * Return the log as a HTML string.
308 | *
309 | * @param $format Split the array into HTML with linebreaks or return as-is if false.
310 | * @return HTML/Array
311 | */
312 | public function getLog($format = TRUE)
313 | {
314 | return $format ? implode($this->log, PHP_EOL) : $this->log;
315 | }
316 |
317 | /**
318 | * Filter out junk content from a log line.
319 | *
320 | * @param String $line
321 | */
322 | private function filterLog($line)
323 | {
324 | return str_replace($this->colour_codes, '', $line);
325 | }
326 |
327 | /**
328 | * Check if it contains any text that indiciates that the test has passed.
329 | *
330 | * @param string $line
331 | * @return boolean
332 | */
333 | public function checkLogForTestPass($line)
334 | {
335 | return count(preg_grep("/({$this->passed_regex})/", array($line))) > 0;
336 | }
337 |
338 | /**
339 | * Checks if line contains failure regex
340 | *
341 | * @param string $line
342 | * @return bool
343 | */
344 | public function checkLogForTestFailure($line)
345 | {
346 | return count(preg_grep("/({$this->failure_regex})/", array($line))) > 0;
347 | }
348 |
349 | /**
350 | * Reset a Test back to default.
351 | */
352 | public function reset()
353 | {
354 | $this->log = array();
355 | $this->passed = FALSE;
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/App/Routes/executable.route.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | /*
13 | |--------------------------------------------------------------------------
14 | | Route: Codeception Check
15 | |--------------------------------------------------------------------------
16 | |
17 | | This route returns an AJAX response to confirm if Codeception is
18 | | executable by Webception.
19 | |
20 | */
21 |
22 | $app->get('/executable', function () use ($app) {
23 | $codeception = $app->codeception;
24 |
25 | $response = $codeception->checkExecutable(
26 | $codeception->config['executable'],
27 | $codeception->config['location']
28 | );
29 |
30 | $http_status = $response['ready'] ? 200 : 500;
31 | $app_response = $app->response();
32 | $app_response['Content-Type'] = 'application/json';
33 | $app_response->status($http_status);
34 | $app_response->body(json_encode($response));
35 | });
36 |
--------------------------------------------------------------------------------
/App/Routes/logs.route.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | /*
13 | |--------------------------------------------------------------------------
14 | | Route: Log Check
15 | |--------------------------------------------------------------------------
16 | |
17 | | This route returns an AJAX response to confirm if Codeception's Log folder is
18 | | writeable by Webception.
19 | |
20 | */
21 |
22 | $app->get('/logs', function () use ($app) {
23 |
24 | $codeception = $app->codeception;
25 |
26 | // There's the potential the codeception config isn't setup,
27 | // so, set it to a default of NULL and we can still warn there's a problem.
28 | $log_path = isset($codeception->config['paths']['log']) ?
29 | $codeception->config['paths']['log'] : NULL;
30 |
31 | $response = $codeception->checkWriteable(
32 | $log_path,
33 | $codeception->site->getConfig()
34 | );
35 |
36 | $http_status = $response['ready'] ? 200 : 500;
37 |
38 | $app_response = $app->response();
39 | $app_response['Content-Type'] = 'application/json';
40 | $app_response->status($http_status);
41 | $app_response->body(json_encode($response));
42 | });
43 |
--------------------------------------------------------------------------------
/App/Routes/run.route.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | /*
13 | |--------------------------------------------------------------------------
14 | | Route: Test Runner
15 | |--------------------------------------------------------------------------
16 | |
17 | | Given a test type (acceptance, functional etc) and a hash,
18 | | load all the tests, find the test and then run it.
19 | |
20 | | The route is called via AJAX and the return repsonse is JSON.
21 | |
22 | */
23 |
24 | $app->get('/run/:type/:hash', function ($type, $hash) use ($app) {
25 |
26 | $response = $app->codeception->getRunResponse($type, $hash);
27 | $http_status = $response['run'] ? 200 : 500;
28 |
29 | $app_response = $app->response();
30 | $app_response['Content-Type'] = 'application/json';
31 | $app_response->status($http_status);
32 | $app_response->body(json_encode($response));
33 |
34 | })->name('run');
35 |
--------------------------------------------------------------------------------
/App/Routes/webception.route.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | /*
13 | |--------------------------------------------------------------------------
14 | | Route: Dashboard
15 | |--------------------------------------------------------------------------
16 | |
17 | | The dashboard is the homepage of Webception. It loads all the
18 | | configuration and shows what tests are available to run.
19 | |
20 | */
21 |
22 | $app->get('/', function ($site = null) use ($app) {
23 |
24 | $tests = FALSE;
25 | $test_count = 0;
26 | $webception = $app->config('webception');
27 | $codeception = $app->codeception;
28 | $environments = array();
29 |
30 | if ($codeception->ready()) {
31 | $tests = $codeception->getTests();
32 | $test_count = $codeception->getTestTally();
33 | if (isset($codeception->config['env'])) {
34 | $environments = $codeception->config['env'];
35 | }
36 | }
37 |
38 | $app->render('dashboard.html', array(
39 | 'name' => $app->getName(),
40 | 'webception' => $webception,
41 | 'codeception' => $codeception,
42 | 'tests' => $tests,
43 | 'test_count' => $test_count,
44 | 'environments'=> $environments
45 | ));
46 | });
47 |
--------------------------------------------------------------------------------
/App/Screenshots/webception-no-executable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayhealey/Webception/de070806bbaacf3d1bdd690224d189da93af0b70/App/Screenshots/webception-no-executable.png
--------------------------------------------------------------------------------
/App/Screenshots/webception-ready.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayhealey/Webception/de070806bbaacf3d1bdd690224d189da93af0b70/App/Screenshots/webception-ready.png
--------------------------------------------------------------------------------
/App/Screenshots/webception.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jayhealey/Webception/de070806bbaacf3d1bdd690224d189da93af0b70/App/Screenshots/webception.gif
--------------------------------------------------------------------------------
/App/Templates/_cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/App/Templates/dashboard.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/master.html" %}
2 |
3 | {% block content %}
4 |
5 | {% if codeception.ready() %}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {% for type, files in tests %}
36 |
37 |
38 |
39 |
40 |
41 |
{{ type|capitalize }} Tests ({{ files|length }} available)
42 |
43 | {% if attribute(environments, type) is defined %}
44 | {% if attribute(environments, type)|length > 0 %}
45 |
46 |
47 |
Environments:
48 |
49 | {% for env in attribute(environments, type) %}
50 |
51 |
52 |
55 |
56 | {% endfor %}
57 |
58 |
59 |
60 |
61 | {% endif %}
62 | {% endif %}
63 |
64 |
76 |
77 | {% for row in files|batch(2, '') %}
78 |
79 |
80 |
81 | {% for file in row %}
82 |
83 | {% if file %}
84 |
85 |
86 |
87 |
99 |
100 |
101 | {% endif %}
102 |
103 | {% endfor %}
104 |
105 |
106 |
107 | {% endfor %}
108 |
109 | {# end: for row in files|batch(2, '') #}
110 |
111 |
112 |
113 | {% endfor %}
114 |
115 |
116 |
117 |
118 |
119 |
Console Results
120 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | {% endif %}
148 |
149 | {% endblock %}
150 |
--------------------------------------------------------------------------------
/App/Templates/error.php:
--------------------------------------------------------------------------------
1 |
Oh snap
2 |
--------------------------------------------------------------------------------
/App/Templates/layout/master.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ webception.name|striptags }} | Dashboard
7 |
8 |
9 |
10 |
11 |
12 |
13 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | {{ webception.name|raw }}
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | {% if not codeception.ready() %}
64 |
65 |
66 | Codeception config could not be fully loaded.
67 |
68 | {% if codeception.site.ready() %}
69 |
70 | The configuration for {{ codeception.site.getName() }} is currently set as:
71 |
72 |
73 | - {{ codeception.site.getConfig() }}
74 |
75 |
76 | You can set the location of the configuration in:
77 |
78 |
79 | - {{ codeception.config.location }}
80 |
81 | {% else %}
82 |
83 | It appears there are no settings in there at all. It may be worth re-downloading it from the {{ webception.name|raw }} repo.
84 |
85 |
86 | You can set the location of the Codeception configuration in the following:
87 |
88 |
89 | - {{ codeception.config.location }}
90 |
91 | {% endif %}
92 |
93 | Pro-tip: Once you've set that, just reload the page.
94 |
95 |
96 |
97 | {% endif %}
98 |
99 |
100 |
{{ random(["It appears we have a problem...", "Oh no, I don't believe it!", "What's wrong this time?", "Ah, that ain't right!"]) }}
101 |
102 |
105 |
106 | You can set the location of the config in the following file:
107 |
108 |
111 |
112 | Pro-tip: Once you've fixed that, just reload the page.
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | {% block content %}{% endblock %}
122 |
123 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
143 |
144 |
--------------------------------------------------------------------------------
/App/Tests/_bootstrap.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | // Load Silex's Autoloader, so Codeception tests can load Webceptions library.
13 | require dirname(__FILE__) . '/../../vendor/autoload.php';
14 |
15 | // Load Webceptions custom bootstrap for helper functions.
16 | require dirname(__FILE__) . '/../bootstrap.php';
17 |
--------------------------------------------------------------------------------
/App/Tests/_config/codeception_executable_fail.php:
--------------------------------------------------------------------------------
1 | array(
24 |
25 | 'Webception' => dirname(__FILE__) .'/../../codeception_pass.yml',
26 |
27 | ),
28 |
29 | /*
30 | |--------------------------------------------------------------------------
31 | | Dummy Codeception executable.
32 | |--------------------------------------------------------------------------
33 | */
34 |
35 | 'executable' => dirname(__FILE__) .'/../../../vendor/bin/codeceptlol',
36 |
37 | /*
38 | |--------------------------------------------------------------------------
39 | | You get to decide which type of tests get included.
40 | |--------------------------------------------------------------------------
41 | */
42 |
43 | 'tests' => array(
44 | 'acceptance' => TRUE,
45 | 'functional' => TRUE,
46 | 'unit' => TRUE,
47 | ),
48 |
49 | /*
50 | |--------------------------------------------------------------------------
51 | | When we scan for the tests, we need to ignore the following files.
52 | |--------------------------------------------------------------------------
53 | */
54 |
55 | 'ignore' => array(
56 | 'WebGuy.php',
57 | 'TestGuy.php',
58 | 'CodeGuy.php',
59 | '_bootstrap.php',
60 | ),
61 |
62 | 'location' => __FILE__,
63 |
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Setting a Directory seperator in the configuration.
67 | | @todo Implement config driven seperator inplace of DIRECTORY_SEPERATOR
68 | |--------------------------------------------------------------------------
69 | */
70 | 'DS' => DIRECTORY_SEPARATOR,
71 | );
72 |
--------------------------------------------------------------------------------
/App/Tests/_config/codeception_fail.php:
--------------------------------------------------------------------------------
1 | array(),
18 | 'location' => __FILE__,
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Setting a Directory seperator in the configuration.
23 | | @todo Implement config driven seperator inplace of DIRECTORY_SEPERATOR
24 | |--------------------------------------------------------------------------
25 | */
26 | 'DS' => DIRECTORY_SEPARATOR,
27 | );
28 |
--------------------------------------------------------------------------------
/App/Tests/_config/codeception_log_fail.php:
--------------------------------------------------------------------------------
1 | array(
24 |
25 | 'Webception' => dirname(__FILE__) .'/codeception_log_fail.yml',
26 |
27 | ),
28 |
29 | /*
30 | |--------------------------------------------------------------------------
31 | | Dummy Codeception executable.
32 | |--------------------------------------------------------------------------
33 | */
34 |
35 | 'executable' => '/usr/bin/codecept',
36 |
37 | /*
38 | |--------------------------------------------------------------------------
39 | | You get to decide which type of tests get included.
40 | |--------------------------------------------------------------------------
41 | */
42 |
43 | 'tests' => array(
44 | 'acceptance' => TRUE,
45 | 'functional' => TRUE,
46 | 'unit' => TRUE,
47 | ),
48 |
49 | /*
50 | |--------------------------------------------------------------------------
51 | | When we scan for the tests, we need to ignore the following files.
52 | |--------------------------------------------------------------------------
53 | */
54 |
55 | 'ignore' => array(
56 | 'WebGuy.php',
57 | 'TestGuy.php',
58 | 'CodeGuy.php',
59 | '_bootstrap.php',
60 | ),
61 |
62 | 'location' => __FILE__,
63 |
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Setting a Directory seperator in the configuration.
67 | | @todo Implement config driven seperator inplace of DIRECTORY_SEPERATOR
68 | |--------------------------------------------------------------------------
69 | */
70 | 'DS' => DIRECTORY_SEPARATOR,
71 | );
72 |
--------------------------------------------------------------------------------
/App/Tests/_config/codeception_log_fail.yml:
--------------------------------------------------------------------------------
1 | paths:
2 | tests: ../
3 | log: ../_log_not_real
4 | data: ../_data
5 | helpers: ../_helpers
6 | settings:
7 | bootstrap: _bootstrap.php
8 | suite_class: \PHPUnit_Framework_TestSuite
9 | colors: false
10 | memory_limit: 1024M
11 | log: true
12 | modules:
13 | config:
14 | Db:
15 | dsn: ''
16 | user: ''
17 | password: ''
18 | dump: ../_data/dump.sql
19 |
--------------------------------------------------------------------------------
/App/Tests/_config/codeception_pass.php:
--------------------------------------------------------------------------------
1 | array(
24 |
25 | 'Webception' => dirname(__FILE__) .'/codeception_pass.yml',
26 | 'Another Webception' => dirname(__FILE__) .'/codeception_pass.yml',
27 |
28 | ),
29 |
30 | /*
31 | |--------------------------------------------------------------------------
32 | | Dummy Codeception executable (that I use in testing)
33 | |--------------------------------------------------------------------------
34 | */
35 |
36 | 'executable' => dirname(__FILE__) .'/../../../vendor/bin/codecept',
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | You get to decide which type of tests get included.
41 | |--------------------------------------------------------------------------
42 | */
43 |
44 | 'tests' => array(
45 | 'acceptance' => TRUE,
46 | 'functional' => TRUE,
47 | 'unit' => TRUE,
48 | ),
49 |
50 | /*
51 | |--------------------------------------------------------------------------
52 | | When we scan for the tests, we need to ignore the following files.
53 | |--------------------------------------------------------------------------
54 | */
55 |
56 | 'ignore' => array(
57 | 'WebGuy.php',
58 | 'TestGuy.php',
59 | 'CodeGuy.php',
60 | '_bootstrap.php',
61 | ),
62 |
63 | 'location' => __FILE__,
64 |
65 | /*
66 | |--------------------------------------------------------------------------
67 | | Setting a Directory seperator in the configuration.
68 | | @todo Implement config driven seperator inplace of DIRECTORY_SEPERATOR
69 | |--------------------------------------------------------------------------
70 | */
71 | 'DS' => DIRECTORY_SEPARATOR,
72 | );
73 |
--------------------------------------------------------------------------------
/App/Tests/_config/codeception_pass.yml:
--------------------------------------------------------------------------------
1 | paths:
2 | tests: ../
3 | log: ../_log
4 | data: ../_data
5 | helpers: ../_helpers
6 | settings:
7 | bootstrap: _bootstrap.php
8 | suite_class: \PHPUnit_Framework_TestSuite
9 | colors: false
10 | memory_limit: 1024M
11 | log: true
12 | modules:
13 | config:
14 | Db:
15 | dsn: ''
16 | user: ''
17 | password: ''
18 | dump: ../_data/dump.sql
19 |
--------------------------------------------------------------------------------
/App/Tests/_config/codeception_yml_fail.php:
--------------------------------------------------------------------------------
1 | array(
24 |
25 | 'Webception' => dirname(__FILE__) .'/../../not-real-codeception.yml',
26 |
27 | ),
28 |
29 | /*
30 | |--------------------------------------------------------------------------
31 | | Dummy Codeception executable.
32 | |--------------------------------------------------------------------------
33 | */
34 |
35 | 'executable' => dirname(__FILE__) .'/../../../vendor/bin/codecept',
36 |
37 | /*
38 | |--------------------------------------------------------------------------
39 | | You get to decide which type of tests get included.
40 | |--------------------------------------------------------------------------
41 | */
42 |
43 | 'tests' => array(
44 | 'acceptance' => TRUE,
45 | 'functional' => TRUE,
46 | 'unit' => TRUE,
47 | ),
48 |
49 | /*
50 | |--------------------------------------------------------------------------
51 | | When we scan for the tests, we need to ignore the following files.
52 | |--------------------------------------------------------------------------
53 | */
54 |
55 | 'ignore' => array(
56 | 'WebGuy.php',
57 | 'TestGuy.php',
58 | 'CodeGuy.php',
59 | '_bootstrap.php',
60 | ),
61 |
62 | 'location' => __FILE__,
63 |
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Setting a Directory seperator in the configuration.
67 | | @todo Implement config driven seperator inplace of DIRECTORY_SEPERATOR
68 | |--------------------------------------------------------------------------
69 | */
70 | 'DS' => DIRECTORY_SEPARATOR,
71 | );
72 |
--------------------------------------------------------------------------------
/App/Tests/_data/dump.sql:
--------------------------------------------------------------------------------
1 | /* Replace this file with actual dump of your database */
--------------------------------------------------------------------------------
/App/Tests/_helpers/CodeGuy.php:
--------------------------------------------------------------------------------
1 | getScenario()->runStep(new \Codeception\Step\Condition('amInPath', func_get_args()));
30 | }
31 |
32 |
33 | /**
34 | * [!] Method is generated. Documentation taken from corresponding module.
35 | *
36 | * Opens a file and stores it's content.
37 | *
38 | * Usage:
39 | *
40 | * ``` php
41 | * openFile('composer.json');
43 | * $I->seeInThisFile('codeception/codeception');
44 | * ?>
45 | * ```
46 | *
47 | * @param $filename
48 | * @see \Codeception\Module\Filesystem::openFile()
49 | */
50 | public function openFile($filename) {
51 | return $this->getScenario()->runStep(new \Codeception\Step\Action('openFile', func_get_args()));
52 | }
53 |
54 |
55 | /**
56 | * [!] Method is generated. Documentation taken from corresponding module.
57 | *
58 | * Deletes a file
59 | *
60 | * ``` php
61 | * deleteFile('composer.lock');
63 | * ?>
64 | * ```
65 | *
66 | * @param $filename
67 | * @see \Codeception\Module\Filesystem::deleteFile()
68 | */
69 | public function deleteFile($filename) {
70 | return $this->getScenario()->runStep(new \Codeception\Step\Action('deleteFile', func_get_args()));
71 | }
72 |
73 |
74 | /**
75 | * [!] Method is generated. Documentation taken from corresponding module.
76 | *
77 | * Deletes directory with all subdirectories
78 | *
79 | * ``` php
80 | * deleteDir('vendor');
82 | * ?>
83 | * ```
84 | *
85 | * @param $dirname
86 | * @see \Codeception\Module\Filesystem::deleteDir()
87 | */
88 | public function deleteDir($dirname) {
89 | return $this->getScenario()->runStep(new \Codeception\Step\Action('deleteDir', func_get_args()));
90 | }
91 |
92 |
93 | /**
94 | * [!] Method is generated. Documentation taken from corresponding module.
95 | *
96 | * Copies directory with all contents
97 | *
98 | * ``` php
99 | * copyDir('vendor','old_vendor');
101 | * ?>
102 | * ```
103 | *
104 | * @param $src
105 | * @param $dst
106 | * @see \Codeception\Module\Filesystem::copyDir()
107 | */
108 | public function copyDir($src, $dst) {
109 | return $this->getScenario()->runStep(new \Codeception\Step\Action('copyDir', func_get_args()));
110 | }
111 |
112 |
113 | /**
114 | * [!] Method is generated. Documentation taken from corresponding module.
115 | *
116 | * Checks If opened file has `text` in it.
117 | *
118 | * Usage:
119 | *
120 | * ``` php
121 | * openFile('composer.json');
123 | * $I->seeInThisFile('codeception/codeception');
124 | * ?>
125 | * ```
126 | *
127 | * @param $text
128 | * Conditional Assertion: Test won't be stopped on fail
129 | * @see \Codeception\Module\Filesystem::seeInThisFile()
130 | */
131 | public function canSeeInThisFile($text) {
132 | return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInThisFile', func_get_args()));
133 | }
134 | /**
135 | * [!] Method is generated. Documentation taken from corresponding module.
136 | *
137 | * Checks If opened file has `text` in it.
138 | *
139 | * Usage:
140 | *
141 | * ``` php
142 | * openFile('composer.json');
144 | * $I->seeInThisFile('codeception/codeception');
145 | * ?>
146 | * ```
147 | *
148 | * @param $text
149 | * @see \Codeception\Module\Filesystem::seeInThisFile()
150 | */
151 | public function seeInThisFile($text) {
152 | return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInThisFile', func_get_args()));
153 | }
154 |
155 |
156 | /**
157 | * [!] Method is generated. Documentation taken from corresponding module.
158 | *
159 | * Checks If opened file has the `number` of new lines.
160 | *
161 | * Usage:
162 | *
163 | * ``` php
164 | * openFile('composer.json');
166 | * $I->seeNumberNewLines(5);
167 | * ?>
168 | * ```
169 | *
170 | * @param int $number New lines
171 | * Conditional Assertion: Test won't be stopped on fail
172 | * @see \Codeception\Module\Filesystem::seeNumberNewLines()
173 | */
174 | public function canSeeNumberNewLines($number) {
175 | return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNumberNewLines', func_get_args()));
176 | }
177 | /**
178 | * [!] Method is generated. Documentation taken from corresponding module.
179 | *
180 | * Checks If opened file has the `number` of new lines.
181 | *
182 | * Usage:
183 | *
184 | * ``` php
185 | * openFile('composer.json');
187 | * $I->seeNumberNewLines(5);
188 | * ?>
189 | * ```
190 | *
191 | * @param int $number New lines
192 | * @see \Codeception\Module\Filesystem::seeNumberNewLines()
193 | */
194 | public function seeNumberNewLines($number) {
195 | return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNumberNewLines', func_get_args()));
196 | }
197 |
198 |
199 | /**
200 | * [!] Method is generated. Documentation taken from corresponding module.
201 | *
202 | * Checks the strict matching of file contents.
203 | * Unlike `seeInThisFile` will fail if file has something more than expected lines.
204 | * Better to use with HEREDOC strings.
205 | * Matching is done after removing "\r" chars from file content.
206 | *
207 | * ``` php
208 | * openFile('process.pid');
210 | * $I->seeFileContentsEqual('3192');
211 | * ?>
212 | * ```
213 | *
214 | * @param $text
215 | * Conditional Assertion: Test won't be stopped on fail
216 | * @see \Codeception\Module\Filesystem::seeFileContentsEqual()
217 | */
218 | public function canSeeFileContentsEqual($text) {
219 | return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeFileContentsEqual', func_get_args()));
220 | }
221 | /**
222 | * [!] Method is generated. Documentation taken from corresponding module.
223 | *
224 | * Checks the strict matching of file contents.
225 | * Unlike `seeInThisFile` will fail if file has something more than expected lines.
226 | * Better to use with HEREDOC strings.
227 | * Matching is done after removing "\r" chars from file content.
228 | *
229 | * ``` php
230 | * openFile('process.pid');
232 | * $I->seeFileContentsEqual('3192');
233 | * ?>
234 | * ```
235 | *
236 | * @param $text
237 | * @see \Codeception\Module\Filesystem::seeFileContentsEqual()
238 | */
239 | public function seeFileContentsEqual($text) {
240 | return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeFileContentsEqual', func_get_args()));
241 | }
242 |
243 |
244 | /**
245 | * [!] Method is generated. Documentation taken from corresponding module.
246 | *
247 | * Checks If opened file doesn't contain `text` in it
248 | *
249 | * ``` php
250 | * openFile('composer.json');
252 | * $I->dontSeeInThisFile('codeception/codeception');
253 | * ?>
254 | * ```
255 | *
256 | * @param $text
257 | * Conditional Assertion: Test won't be stopped on fail
258 | * @see \Codeception\Module\Filesystem::dontSeeInThisFile()
259 | */
260 | public function cantSeeInThisFile($text) {
261 | return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInThisFile', func_get_args()));
262 | }
263 | /**
264 | * [!] Method is generated. Documentation taken from corresponding module.
265 | *
266 | * Checks If opened file doesn't contain `text` in it
267 | *
268 | * ``` php
269 | * openFile('composer.json');
271 | * $I->dontSeeInThisFile('codeception/codeception');
272 | * ?>
273 | * ```
274 | *
275 | * @param $text
276 | * @see \Codeception\Module\Filesystem::dontSeeInThisFile()
277 | */
278 | public function dontSeeInThisFile($text) {
279 | return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeInThisFile', func_get_args()));
280 | }
281 |
282 |
283 | /**
284 | * [!] Method is generated. Documentation taken from corresponding module.
285 | *
286 | * Deletes a file
287 | * @see \Codeception\Module\Filesystem::deleteThisFile()
288 | */
289 | public function deleteThisFile() {
290 | return $this->getScenario()->runStep(new \Codeception\Step\Action('deleteThisFile', func_get_args()));
291 | }
292 |
293 |
294 | /**
295 | * [!] Method is generated. Documentation taken from corresponding module.
296 | *
297 | * Checks if file exists in path.
298 | * Opens a file when it's exists
299 | *
300 | * ``` php
301 | * seeFileFound('UserModel.php','app/models');
303 | * ?>
304 | * ```
305 | *
306 | * @param $filename
307 | * @param string $path
308 | * Conditional Assertion: Test won't be stopped on fail
309 | * @see \Codeception\Module\Filesystem::seeFileFound()
310 | */
311 | public function canSeeFileFound($filename, $path = null) {
312 | return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeFileFound', func_get_args()));
313 | }
314 | /**
315 | * [!] Method is generated. Documentation taken from corresponding module.
316 | *
317 | * Checks if file exists in path.
318 | * Opens a file when it's exists
319 | *
320 | * ``` php
321 | * seeFileFound('UserModel.php','app/models');
323 | * ?>
324 | * ```
325 | *
326 | * @param $filename
327 | * @param string $path
328 | * @see \Codeception\Module\Filesystem::seeFileFound()
329 | */
330 | public function seeFileFound($filename, $path = null) {
331 | return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeFileFound', func_get_args()));
332 | }
333 |
334 |
335 | /**
336 | * [!] Method is generated. Documentation taken from corresponding module.
337 | *
338 | * Checks if file does not exist in path
339 | *
340 | * @param $filename
341 | * @param string $path
342 | * Conditional Assertion: Test won't be stopped on fail
343 | * @see \Codeception\Module\Filesystem::dontSeeFileFound()
344 | */
345 | public function cantSeeFileFound($filename, $path = null) {
346 | return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeFileFound', func_get_args()));
347 | }
348 | /**
349 | * [!] Method is generated. Documentation taken from corresponding module.
350 | *
351 | * Checks if file does not exist in path
352 | *
353 | * @param $filename
354 | * @param string $path
355 | * @see \Codeception\Module\Filesystem::dontSeeFileFound()
356 | */
357 | public function dontSeeFileFound($filename, $path = null) {
358 | return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeFileFound', func_get_args()));
359 | }
360 |
361 |
362 | /**
363 | * [!] Method is generated. Documentation taken from corresponding module.
364 | *
365 | * Erases directory contents
366 | *
367 | * ``` php
368 | * cleanDir('logs');
370 | * ?>
371 | * ```
372 | *
373 | * @param $dirname
374 | * @see \Codeception\Module\Filesystem::cleanDir()
375 | */
376 | public function cleanDir($dirname) {
377 | return $this->getScenario()->runStep(new \Codeception\Step\Action('cleanDir', func_get_args()));
378 | }
379 |
380 |
381 | /**
382 | * [!] Method is generated. Documentation taken from corresponding module.
383 | *
384 | * Saves contents to file
385 | *
386 | * @param $filename
387 | * @param $contents
388 | * @see \Codeception\Module\Filesystem::writeToFile()
389 | */
390 | public function writeToFile($filename, $contents) {
391 | return $this->getScenario()->runStep(new \Codeception\Step\Action('writeToFile', func_get_args()));
392 | }
393 | }
394 |
--------------------------------------------------------------------------------
/App/Tests/_log/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/App/Tests/acceptance.suite.yml:
--------------------------------------------------------------------------------
1 | # Codeception Test Suite Configuration
2 |
3 | # suite for acceptance tests.
4 | # perform tests in browser using the Selenium-like tools.
5 | # powered by Mink (http://mink.behat.org).
6 | # (tip: that's what your customer will see).
7 | # (tip: test your ajax and javascript by one of Mink drivers).
8 |
9 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
10 |
11 | class_name: WebGuy
12 | modules:
13 | enabled:
14 | - Asserts
15 | - PhpBrowser
16 | - WebHelper
17 | - REST
18 | config:
19 | REST:
20 | depends: PhpBrowser
21 | url: 'http://webception/'
22 | timeout: 90
23 | depends: PhpBrowser
24 | PhpBrowser:
25 | url: 'http://webception/'
26 | curl:
27 | CURLOPT_RETURNTRANSFER: true
28 | CURLOPT_FOLLOWLOCATION: true
29 |
--------------------------------------------------------------------------------
/App/Tests/acceptance/CheckAJAXWhenExecutableFailsCept.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | $I = new WebGuy($scenario);
13 | $I->wantTo('check AJAX call when executable fails');
14 | $I->sendGET('executable?test=executable_fail');
15 | $I->seeResponseContainsJson(array(
16 | 'ready' => false,
17 | 'error' => "The Codeception executable could not be found.",
18 | ));
19 |
20 |
--------------------------------------------------------------------------------
/App/Tests/acceptance/CheckAJAXWhenExecutablePassesCept.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | $I = new WebGuy($scenario);
13 | $I->wantTo('check AJAX call when executable passes.');
14 | $I->sendGET('executable?test=pass');
15 | $I->seeResponseContainsJson(array(
16 | 'ready' => true,
17 | ));
18 |
--------------------------------------------------------------------------------
/App/Tests/acceptance/CheckAJAXWhenLogFailsCept.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | $I = new WebGuy($scenario);
13 | $I->wantTo('check AJAX call when the Codeception log check fails.');
14 | $I->sendGET('logs?test=log_fail');
15 | $I->seeResponseContainsJson(array(
16 | 'ready' => false,
17 | 'error' => "The Codeception Log directory does not exist. Please check the following path exists:",
18 | ));
19 |
--------------------------------------------------------------------------------
/App/Tests/acceptance/CheckAJAXWhenLogPassesCept.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | $I = new WebGuy($scenario);
13 | $I->wantTo('check AJAX call when the Codeception log check passes.');
14 | $I->sendGET('logs?test=pass');
15 | $I->seeResponseContainsJson(array(
16 | 'ready' => true,
17 | ));
18 |
--------------------------------------------------------------------------------
/App/Tests/acceptance/RunFailTestCept.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | $I = new WebGuy($scenario);
13 | $I->wantTo('run a failing test');
14 | $I->sendGET('run/acceptance/'. md5('acceptance'.'TheTestThatFails'));
15 | $I->seeResponseContainsJson(array(
16 | 'run' => true,
17 | 'passed' => false,
18 | 'state' => 'failed',
19 | ));
--------------------------------------------------------------------------------
/App/Tests/acceptance/RunNonExistentTestCept.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | $I = new WebGuy($scenario);
13 | $I->wantTo('run test that does not exist to confirm the response');
14 | $I->sendGET('run/lol/fake-test-id');
15 | $I->seeResponseContainsJson(array(
16 | 'message' => 'The test could not be found.',
17 | 'run' => false,
18 | 'passed' => false,
19 | 'state' => 'error',
20 | 'log' => NULL,
21 | ));
22 |
--------------------------------------------------------------------------------
/App/Tests/acceptance/RunPassingTestCept.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | $I = new WebGuy($scenario);
13 | $I->wantTo('run a passing test');
14 | $I->sendGET('run/acceptance/'. md5('acceptance' . 'TheTestThatPasses'));
15 | $I->seeResponseContainsJson(array(
16 | 'message' => NULL,
17 | 'run' => true,
18 | 'passed' => true,
19 | 'state' => 'passed',
20 | ));
21 |
--------------------------------------------------------------------------------
/App/Tests/acceptance/TheTestThatFailsCept.php:
--------------------------------------------------------------------------------
1 |
18 | *
19 | * For the full copyright and license information, please view the LICENSE
20 | * file that was distributed with this source code.
21 | */
22 |
23 | // This test is used as a false positive when testing Webception. This test is used
24 | // to test a failed test via the Webception RUN command.
25 |
26 | $I = new WebGuy($scenario);
27 | $I->wantTo('fail at passing');
28 |
29 | $I->assertTrue(FALSE);
30 |
--------------------------------------------------------------------------------
/App/Tests/acceptance/TheTestThatPassesCept.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | // This test is used as a positive when testing Webception. This test is used
12 | // to test a failed test via the Webception RUN command.
13 |
14 | $I = new WebGuy($scenario);
15 | $I->wantTo('do nothing and pass!');
16 |
--------------------------------------------------------------------------------
/App/Tests/acceptance/_bootstrap.php:
--------------------------------------------------------------------------------
1 | scenario->runStep(new \Codeception\Step\Condition('amInPath', func_get_args()));
40 | }
41 |
42 |
43 | /**
44 | * [!] Method is generated. Documentation taken from corresponding module.
45 | *
46 | * Opens a file and stores it's content.
47 | *
48 | * Usage:
49 | *
50 | * ``` php
51 | * openFile('composer.json');
53 | * $I->seeInThisFile('codeception/codeception');
54 | * ?>
55 | * ```
56 | *
57 | * @param $filename
58 | * @see \Codeception\Module\Filesystem::openFile()
59 | */
60 | public function openFile($filename) {
61 | return $this->scenario->runStep(new \Codeception\Step\Action('openFile', func_get_args()));
62 | }
63 |
64 |
65 | /**
66 | * [!] Method is generated. Documentation taken from corresponding module.
67 | *
68 | * Deletes a file
69 | *
70 | * ``` php
71 | * deleteFile('composer.lock');
73 | * ?>
74 | * ```
75 | *
76 | * @param $filename
77 | * @see \Codeception\Module\Filesystem::deleteFile()
78 | */
79 | public function deleteFile($filename) {
80 | return $this->scenario->runStep(new \Codeception\Step\Action('deleteFile', func_get_args()));
81 | }
82 |
83 |
84 | /**
85 | * [!] Method is generated. Documentation taken from corresponding module.
86 | *
87 | * Deletes directory with all subdirectories
88 | *
89 | * ``` php
90 | * deleteDir('vendor');
92 | * ?>
93 | * ```
94 | *
95 | * @param $dirname
96 | * @see \Codeception\Module\Filesystem::deleteDir()
97 | */
98 | public function deleteDir($dirname) {
99 | return $this->scenario->runStep(new \Codeception\Step\Action('deleteDir', func_get_args()));
100 | }
101 |
102 |
103 | /**
104 | * [!] Method is generated. Documentation taken from corresponding module.
105 | *
106 | * Copies directory with all contents
107 | *
108 | * ``` php
109 | * copyDir('vendor','old_vendor');
111 | * ?>
112 | * ```
113 | *
114 | * @param $src
115 | * @param $dst
116 | * @see \Codeception\Module\Filesystem::copyDir()
117 | */
118 | public function copyDir($src, $dst) {
119 | return $this->scenario->runStep(new \Codeception\Step\Action('copyDir', func_get_args()));
120 | }
121 |
122 |
123 | /**
124 | * [!] Method is generated. Documentation taken from corresponding module.
125 | *
126 | * Checks If opened file has `text` in it.
127 | *
128 | * Usage:
129 | *
130 | * ``` php
131 | * openFile('composer.json');
133 | * $I->seeInThisFile('codeception/codeception');
134 | * ?>
135 | * ```
136 | *
137 | * @param $text
138 | * Conditional Assertion: Test won't be stopped on fail
139 | * @see \Codeception\Module\Filesystem::seeInThisFile()
140 | */
141 | public function canSeeInThisFile($text) {
142 | return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeInThisFile', func_get_args()));
143 | }
144 | /**
145 | * [!] Method is generated. Documentation taken from corresponding module.
146 | *
147 | * Checks If opened file has `text` in it.
148 | *
149 | * Usage:
150 | *
151 | * ``` php
152 | * openFile('composer.json');
154 | * $I->seeInThisFile('codeception/codeception');
155 | * ?>
156 | * ```
157 | *
158 | * @param $text
159 | * @see \Codeception\Module\Filesystem::seeInThisFile()
160 | */
161 | public function seeInThisFile($text) {
162 | return $this->scenario->runStep(new \Codeception\Step\Assertion('seeInThisFile', func_get_args()));
163 | }
164 |
165 |
166 | /**
167 | * [!] Method is generated. Documentation taken from corresponding module.
168 | *
169 | * Checks the strict matching of file contents.
170 | * Unlike `seeInThisFile` will fail if file has something more than expected lines.
171 | * Better to use with HEREDOC strings.
172 | * Matching is done after removing "\r" chars from file content.
173 | *
174 | * ``` php
175 | * openFile('process.pid');
177 | * $I->seeFileContentsEqual('3192');
178 | * ?>
179 | * ```
180 | *
181 | * @param $text
182 | * Conditional Assertion: Test won't be stopped on fail
183 | * @see \Codeception\Module\Filesystem::seeFileContentsEqual()
184 | */
185 | public function canSeeFileContentsEqual($text) {
186 | return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeFileContentsEqual', func_get_args()));
187 | }
188 | /**
189 | * [!] Method is generated. Documentation taken from corresponding module.
190 | *
191 | * Checks the strict matching of file contents.
192 | * Unlike `seeInThisFile` will fail if file has something more than expected lines.
193 | * Better to use with HEREDOC strings.
194 | * Matching is done after removing "\r" chars from file content.
195 | *
196 | * ``` php
197 | * openFile('process.pid');
199 | * $I->seeFileContentsEqual('3192');
200 | * ?>
201 | * ```
202 | *
203 | * @param $text
204 | * @see \Codeception\Module\Filesystem::seeFileContentsEqual()
205 | */
206 | public function seeFileContentsEqual($text) {
207 | return $this->scenario->runStep(new \Codeception\Step\Assertion('seeFileContentsEqual', func_get_args()));
208 | }
209 |
210 |
211 | /**
212 | * [!] Method is generated. Documentation taken from corresponding module.
213 | *
214 | * Checks If opened file doesn't contain `text` in it
215 | *
216 | * ``` php
217 | * openFile('composer.json');
219 | * $I->dontSeeInThisFile('codeception/codeception');
220 | * ?>
221 | * ```
222 | *
223 | * @param $text
224 | * Conditional Assertion: Test won't be stopped on fail
225 | * @see \Codeception\Module\Filesystem::dontSeeInThisFile()
226 | */
227 | public function cantSeeInThisFile($text) {
228 | return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInThisFile', func_get_args()));
229 | }
230 | /**
231 | * [!] Method is generated. Documentation taken from corresponding module.
232 | *
233 | * Checks If opened file doesn't contain `text` in it
234 | *
235 | * ``` php
236 | * openFile('composer.json');
238 | * $I->dontSeeInThisFile('codeception/codeception');
239 | * ?>
240 | * ```
241 | *
242 | * @param $text
243 | * @see \Codeception\Module\Filesystem::dontSeeInThisFile()
244 | */
245 | public function dontSeeInThisFile($text) {
246 | return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeInThisFile', func_get_args()));
247 | }
248 |
249 |
250 | /**
251 | * [!] Method is generated. Documentation taken from corresponding module.
252 | *
253 | * Deletes a file
254 | * @see \Codeception\Module\Filesystem::deleteThisFile()
255 | */
256 | public function deleteThisFile() {
257 | return $this->scenario->runStep(new \Codeception\Step\Action('deleteThisFile', func_get_args()));
258 | }
259 |
260 |
261 | /**
262 | * [!] Method is generated. Documentation taken from corresponding module.
263 | *
264 | * Checks if file exists in path.
265 | * Opens a file when it's exists
266 | *
267 | * ``` php
268 | * seeFileFound('UserModel.php','app/models');
270 | * ?>
271 | * ```
272 | *
273 | * @param $filename
274 | * @param string $path
275 | * Conditional Assertion: Test won't be stopped on fail
276 | * @see \Codeception\Module\Filesystem::seeFileFound()
277 | */
278 | public function canSeeFileFound($filename, $path = null) {
279 | return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeFileFound', func_get_args()));
280 | }
281 | /**
282 | * [!] Method is generated. Documentation taken from corresponding module.
283 | *
284 | * Checks if file exists in path.
285 | * Opens a file when it's exists
286 | *
287 | * ``` php
288 | * seeFileFound('UserModel.php','app/models');
290 | * ?>
291 | * ```
292 | *
293 | * @param $filename
294 | * @param string $path
295 | * @see \Codeception\Module\Filesystem::seeFileFound()
296 | */
297 | public function seeFileFound($filename, $path = null) {
298 | return $this->scenario->runStep(new \Codeception\Step\Assertion('seeFileFound', func_get_args()));
299 | }
300 |
301 |
302 | /**
303 | * [!] Method is generated. Documentation taken from corresponding module.
304 | *
305 | * Checks if file does not exists in path
306 | *
307 | * @param $filename
308 | * @param string $path
309 | * Conditional Assertion: Test won't be stopped on fail
310 | * @see \Codeception\Module\Filesystem::dontSeeFileFound()
311 | */
312 | public function cantSeeFileFound($filename, $path = null) {
313 | return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeFileFound', func_get_args()));
314 | }
315 | /**
316 | * [!] Method is generated. Documentation taken from corresponding module.
317 | *
318 | * Checks if file does not exists in path
319 | *
320 | * @param $filename
321 | * @param string $path
322 | * @see \Codeception\Module\Filesystem::dontSeeFileFound()
323 | */
324 | public function dontSeeFileFound($filename, $path = null) {
325 | return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeFileFound', func_get_args()));
326 | }
327 |
328 |
329 | /**
330 | * [!] Method is generated. Documentation taken from corresponding module.
331 | *
332 | * Erases directory contents
333 | *
334 | * ``` php
335 | * cleanDir('logs');
337 | * ?>
338 | * ```
339 | *
340 | * @param $dirname
341 | * @see \Codeception\Module\Filesystem::cleanDir()
342 | */
343 | public function cleanDir($dirname) {
344 | return $this->scenario->runStep(new \Codeception\Step\Action('cleanDir', func_get_args()));
345 | }
346 |
347 |
348 | /**
349 | * [!] Method is generated. Documentation taken from corresponding module.
350 | *
351 | * Saves contents to file
352 | *
353 | * @param $filename
354 | * @param $contents
355 | * @see \Codeception\Module\Filesystem::writeToFile()
356 | */
357 | public function writeToFile($filename, $contents) {
358 | return $this->scenario->runStep(new \Codeception\Step\Action('writeToFile', func_get_args()));
359 | }
360 | }
361 |
--------------------------------------------------------------------------------
/App/Tests/functional/_bootstrap.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | class CodeceptionClassTestsTest extends \Codeception\TestCase\Test
13 | {
14 | /**
15 | * @var \CodeGuy
16 | */
17 | protected $codeGuy;
18 |
19 | private $codeception = FALSE;
20 | private $config = FALSE;
21 |
22 | private $test_filename = 'WebceptionTestClassTest.php';
23 | private $test_file;
24 | private $pass_filename = 'TheTestThatPassesCept.php';
25 | private $fail_filename = 'TheTestThatFailsCept';
26 | private $test;
27 | private $type = 'unit';
28 | private $site_name = 'Webception';
29 |
30 | protected function _before()
31 | {
32 | // Load the Codeception config
33 | $this->config = require(dirname(__FILE__) . '/../_config/codeception_pass.php');
34 |
35 | // Load up a working Site class
36 | $this->site = new \App\Lib\Site($this->config['sites']);
37 |
38 | $this->site->set(md5($this->site_name));
39 |
40 | // Load up Codeception class
41 | $this->codeception = new \App\Lib\Codeception($this->config, $this->site);
42 |
43 | // Load up a blank test
44 | $this->test = new \App\Lib\Test();
45 |
46 | // Load up the current test file as an example test.
47 | $this->test_file = new SplFileInfo($this->test_filename);
48 | }
49 |
50 | protected function _after()
51 | {
52 | }
53 |
54 | /**
55 | * Test the Codeception class without passing in a configuration
56 | *
57 | */
58 | public function testCodeceptionWithNoConfig()
59 | {
60 | $codeception = new \App\Lib\Codeception();
61 | $this->assertFalse($codeception->ready());
62 | }
63 |
64 | /**
65 | * Test the Codeception class with configuration where
66 | * is completely empty.
67 | */
68 | public function testCodeceptionWithEmptyConfig()
69 | {
70 | $config = require(dirname(__FILE__) . '/../_config/codeception_fail.php');
71 |
72 | $site = new \App\Lib\Site($config['sites']);
73 | $site->set(md5($this->site_name));
74 | $codeception = new \App\Lib\Codeception($config, $site);
75 | $this->assertFalse($codeception->ready());
76 | }
77 |
78 | public function testCodeceptionWithInvalidExecutable()
79 | {
80 | $config = require(dirname(__FILE__) . '/../_config/codeception_executable_fail.php');
81 | $site = new \App\Lib\Site($this->config['sites']);
82 | $site->set(md5($this->site_name));
83 | $codeception = new \App\Lib\Codeception($config, $site);
84 | $response = $codeception->checkExecutable($config['executable'], $config['location']);
85 | $this->assertFalse($response['ready']);
86 | }
87 |
88 | public function testCodeceptionWithValidConfig()
89 | {
90 | $codeception = $this->codeception;
91 | $config = $this->config;
92 | $this->assertTrue($codeception->ready());
93 |
94 | $response = $codeception->checkExecutable($config['executable'], $config['location']);
95 | $this->assertTrue($response['ready']);
96 | }
97 |
98 | public function testCodeceptionCommandPath()
99 | {
100 | $codeception = $this->codeception;
101 | $config = $this->config;
102 |
103 | $params = array(
104 | $this->config['executable'], // Codeception Executable
105 | "run", // Command to Codeception
106 | "--no-colors", // Forcing Codeception to not use colors, if enabled in codeception.yml
107 | "--config=\"{$codeception->site->getConfig()}\"", // Full path & file of Codeception
108 | $this->type, // Test Type (Acceptance, Unit, Functional)
109 | $this->test_filename, // Filename of the Codeception test
110 | "2>&1" // Added to force output of running executable to be streamed out
111 | );
112 |
113 | $mock_command = implode(' ', $params);
114 | $codeception_command = $codeception->getCommandPath($this->type, $this->test_filename);
115 |
116 | $this->assertEquals($mock_command, $codeception_command);
117 | }
118 |
119 | /**
120 | * Test the adding and getting of tests when they're loaded.
121 | */
122 | public function testAddingAndGettingTests()
123 | {
124 | $test = $this->test;
125 | $codeception = $this->codeception;
126 | $tally_before = $codeception->getTestTally();
127 | $type = 'awesome'; // Fake type. So we don't clash with the loaded tests.
128 |
129 | // Initialize the test as per usual
130 | $test->init($type, $this->test_file);
131 |
132 | // Check the adding tally works
133 | $codeception->addTest($test);
134 | $tally_after = $codeception->getTestTally();
135 | $this->assertGreaterThan($tally_before, $tally_after);
136 |
137 | // Check the test can be recieved again
138 | $test_back = $codeception->getTest($type, $test->getHash());
139 | $this->assertEquals($test_back->getTitle(), $test->getTitle());
140 | }
141 |
142 | /**
143 | * Testing the getRunResponse used in the run route for a test that wasn't found.
144 | */
145 | public function testResponseForNotFoundTest()
146 | {
147 | $codeception = $this->codeception;
148 | $response = $codeception->getRunResponse('lol', md5("notgoingtofindthis"));
149 |
150 | $this->assertFalse($response['run']);
151 | $this->assertFalse($response['passed']);
152 | $this->assertEquals($response['message'], 'The test could not be found.');
153 | $this->assertNull($response['log']);
154 | $this->assertEquals($response['state'], 'error');
155 | }
156 |
157 | /**
158 | * Testing the run() function on the Test class with a passing test.
159 | */
160 | public function testTheTestThatPasses()
161 | {
162 | $codeception = $this->codeception;
163 |
164 | // Get the test that we know fails.
165 | $test = $codeception->getTest('acceptance', md5("acceptanceTheTestThatPasses"));
166 |
167 | // Confirm the test hasn't been or passed yet
168 | $this->assertFalse($test->ran());
169 | $this->assertFalse($test->passed());
170 |
171 | // Run the test, which generates the log and analyes the log as it gets added.
172 | $test = $codeception->run($test);
173 |
174 | // Confirm the test was run & passed!
175 | $this->assertTrue($test->ran());
176 | $this->assertTrue($test->passed());
177 | }
178 |
179 | /**
180 | * Testing the getRunResponse used in the run route for a passing test.
181 | */
182 | public function testResponseForTheTestThatPasses()
183 | {
184 | $codeception = $this->codeception;
185 | $response = $codeception->getRunResponse('acceptance', md5("acceptanceTheTestThatPasses"));
186 |
187 | $this->assertTrue($response['run']);
188 | $this->assertTrue($response['passed']);
189 | $this->assertNull($response['message']);
190 | $this->assertNotNull($response['log']);
191 | $this->assertEquals($response['state'], 'passed');
192 | }
193 |
194 | /**
195 | * Testing the run() function on the Test class with a failing test.
196 | */
197 | public function testTheTestThatFails()
198 | {
199 | $codeception = $this->codeception;
200 |
201 | // Get the test that we know fails.
202 | $test = $codeception->getTest('acceptance', md5("acceptanceTheTestThatFails"));
203 |
204 | // Confirm the test hasn't been or passed yet
205 | $this->assertFalse($test->ran());
206 | $this->assertFalse($test->passed());
207 |
208 | // Run the test, which generates the log and analyes the log as it gets added.
209 | $test = $codeception->run($test);
210 |
211 | // Confirm the test was run but it's not passed
212 | $this->assertTrue($test->ran());
213 | $this->assertFalse($test->passed());
214 | }
215 |
216 | /**
217 | * Testing the getRunResponse used in the run route for a failing test.
218 | */
219 | public function testResponseForTheTestThatFails()
220 | {
221 | $codeception = $this->codeception;
222 | $response = $codeception->getRunResponse('acceptance', md5("acceptanceTheTestThatFails"));
223 |
224 | $this->assertTrue($response['run']);
225 | $this->assertFalse($response['passed']);
226 | $this->assertNull($response['message']);
227 | $this->assertNotNull($response['log']);
228 | $this->assertEquals($response['state'], 'failed');
229 | }
230 |
231 | }
232 |
--------------------------------------------------------------------------------
/App/Tests/unit/SiteClassTestsTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | class SiteClassTestsTest extends \Codeception\TestCase\Test
13 | {
14 | /**
15 | * @var \CodeGuy
16 | */
17 | protected $codeGuy;
18 |
19 | private $config = FALSE;
20 | private $site = FALSE;
21 | private $site_1 = 'Webception';
22 | private $site_2 = 'Another Webception';
23 | private $multiple_sites = array();
24 | private $single_site = array();
25 |
26 | protected function _before()
27 | {
28 | // Default Site List
29 | $this->multiple_sites = array(
30 | 'Webception' => dirname(__FILE__) .'/../_config/codeception_pass.yml',
31 | 'Another Webception' => dirname(__FILE__) .'/../_config/codeception_pass.yml'
32 | );
33 |
34 | $this->single_site = array(
35 | 'Another Webception' => dirname(__FILE__) .'/../_config/codeception_pass.yml',
36 | );
37 |
38 | // Load the Codeception config
39 | $this->config = require(dirname(__FILE__) . '/../_config/codeception_pass.php');
40 |
41 | // Set the hashes!
42 | $this->hash_1 = md5($this->site_1);
43 | $this->hash_2 = md5($this->site_2);
44 | }
45 |
46 | protected function _after()
47 | {
48 | }
49 |
50 | public function testSettingNoSites()
51 | {
52 | $site = new \App\Lib\Site();
53 | $this->assertFalse($site->ready());
54 | $this->assertFalse($site->getHash());
55 | $this->assertFalse($site->hasChoices());
56 | $this->assertFalse($site->getName());
57 | $this->assertFalse($site->getConfig());
58 | $this->assertFalse($site->getConfigPath());
59 | $this->assertFalse($site->getConfigFile());
60 | $this->assertEquals(count($site->getSites()), 0);
61 | $this->assertEquals($site->getSites(), array());
62 | }
63 |
64 | public function testSettingSites()
65 | {
66 | $site = new \App\Lib\Site($this->config['sites']);
67 | $this->assertEquals(count($site->getSites()), 2);
68 | $filtered = $site->prepare($this->multiple_sites);
69 | $this->assertEquals($filtered, $site->getSites());
70 | }
71 |
72 | public function testSettingInvalidSite()
73 | {
74 | $site = new \App\Lib\Site($this->config['sites']);
75 | $this->assertFalse($site->ready());
76 | $site->set(md5('junk-site'));
77 | $this->assertFalse($site->ready());
78 | $this->assertFalse($site->getHash());
79 | $this->assertFalse($site->hasChoices());
80 | $this->assertFalse($site->getName());
81 | $this->assertFalse($site->getConfig());
82 | $this->assertFalse($site->getConfigPath());
83 | $this->assertFalse($site->getConfigFile());
84 | $this->assertEquals(count($site->getSites()), 2);
85 | }
86 |
87 | public function testSettingMultipleValidSites()
88 | {
89 | $site = new \App\Lib\Site($this->config['sites']);
90 |
91 | $filtered = $site->prepare($this->multiple_sites);
92 |
93 | $this->assertEquals(count($site->getSites()), 2);
94 |
95 | $this->assertFalse($site->hasChoices());
96 | $this->assertFalse($site->ready());
97 |
98 | // Set to first site.
99 | $site->set($this->hash_1);
100 | $this->assertTrue($site->ready());
101 | $this->assertEquals($site->getName(), $this->site_1);
102 | $this->assertEquals($this->hash_1, $site->getHash());
103 | $this->assertEquals($filtered[$this->hash_1]['path'], $site->getConfig());
104 | $this->assertEquals(basename($filtered[$this->hash_1]['path']), $site->getConfigFile());
105 | $this->assertEquals(dirname($filtered[$this->hash_1]['path']) .'/', $site->getConfigPath());
106 |
107 | // Swap the site over!
108 | $site->set($this->hash_2);
109 | $this->assertTrue($site->ready());
110 | $this->assertNotEquals($site->getName(), $this->site_1);
111 | $this->assertEquals($site->getName(), $this->site_2);
112 | $this->assertEquals($this->hash_2, $site->getHash());
113 | $this->assertEquals($filtered[$this->hash_2]['path'], $site->getConfig());
114 | $this->assertEquals(basename($filtered[$this->hash_2]['path']), $site->getConfigFile());
115 | $this->assertEquals(dirname($filtered[$this->hash_2]['path']) .'/', $site->getConfigPath());
116 | $this->assertEquals(count($site->getSites()), 2);
117 |
118 | // Sites are set and more than one site available
119 | $this->assertTrue($site->hasChoices());
120 |
121 | }
122 |
123 | public function testSettingSingleValidSites()
124 | {
125 | $sites = $this->config['sites'];
126 |
127 | // Remove the first site
128 | unset($sites[$this->site_1]);
129 |
130 | $site = new \App\Lib\Site($sites);
131 |
132 | $filtered = $site->prepare($this->single_site);
133 |
134 | // Confirm the single set has been set
135 | $this->assertEquals($filtered, $site->getSites());
136 | $this->assertEquals(count($site->getSites()), 1);
137 | $this->assertFalse($site->hasChoices());
138 | $this->assertFalse($site->ready());
139 |
140 | // Set to first site.
141 | $site->set($this->hash_2);
142 | $this->assertTrue($site->ready());
143 | $this->assertEquals($site->getName(), $this->site_2);
144 | $this->assertEquals($this->hash_2, $site->getHash());
145 | $this->assertEquals($filtered[$this->hash_2]['path'], $site->getConfig());
146 | $this->assertEquals(basename($filtered[$this->hash_2]['path']), $site->getConfigFile());
147 | $this->assertEquals(dirname($filtered[$this->hash_2]['path']) .'/', $site->getConfigPath());
148 |
149 | // Check there's only one site but we don't have choices
150 | $this->assertEquals(count($site->getSites()), 1);
151 | $this->assertFalse($site->hasChoices());
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/App/Tests/unit/WebceptionClassTestsTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | class WebceptionClassTestsTest extends \Codeception\TestCase\Test
13 | {
14 | /**
15 | * @var \CodeGuy
16 | */
17 | protected $codeGuy;
18 |
19 | /**
20 | * Test Object
21 | *
22 | * @var boolean
23 | */
24 | private $test;
25 |
26 | /**
27 | * Test File
28 | *
29 | * @var boolean
30 | */
31 | private $test_file;
32 |
33 | private $type = 'unit';
34 | private $filter = array();
35 |
36 | protected function _before()
37 | {
38 | $this->test = new \App\Lib\Test();
39 |
40 | // Load up the current test file as an example test.
41 | $this->test_file = new SplFileInfo(__FILE__);
42 | }
43 |
44 | protected function _after()
45 | {
46 | }
47 |
48 | public function testDefaultSettings()
49 | {
50 | $test = $this->test;
51 | $this->assertFalse($test->passed());
52 | $this->assertFalse($test->ran());
53 | $this->assertEquals(sizeof($test->getLog(FALSE)), 0);
54 | }
55 |
56 | public function testSettingOfTestData()
57 | {
58 | $test = $this->test;
59 | $test_file = $this->test_file;
60 |
61 | // Mock how the test object handles the test data.
62 | $filename = $test->filterFileName($test_file->getFileName());
63 | $hash = $test->makeHash($this->type . $filename);
64 | $title = $test->filterTitle($filename);
65 |
66 | // Initialize the test as per usual
67 | $test->init($this->type, $test_file);
68 |
69 | // Confirm the mocked test data is filtered and set properly in the test object
70 | $this->assertEquals($hash, $test->getHash());
71 | $this->assertEquals($title, $test->getTitle());
72 | $this->assertEquals($this->type, $test->getType());
73 | $this->assertEquals($test_file->getFileName(), $test->getFilename());
74 |
75 | // Confirm a new test has not passed (as it's done nothing)
76 | $this->assertEquals(false, $test->passed());
77 |
78 | // Falsify a passed test
79 | $test->setPassed();
80 | $this->assertEquals(true, $test->passed());
81 | }
82 |
83 | public function testLog()
84 | {
85 | $test = $this->test;
86 | $test_file = $this->test_file;
87 |
88 | $test->init($this->type, $test_file);
89 |
90 | // Confirm the log is empty
91 | $this->assertFalse($test->ran());
92 | $this->assertEquals(sizeof($test->getLog(FALSE)), 0);
93 |
94 | // Add a line to the log
95 | $log_line = 'Example Log Line';
96 | $test->setLog(array('Example Log Line'));
97 |
98 | // Confirm the item was added
99 | $log = $test->getLog(FALSE);
100 | $this->assertEquals($log[0], $log_line);
101 | $this->assertEquals(sizeof($test->getLog(FALSE)), 1);
102 | $this->assertTrue($test->ran());
103 |
104 | // Reset the test
105 | $test->reset();
106 |
107 | // Confirm the log is empty
108 | $this->assertFalse($test->ran());
109 | $this->assertEquals(sizeof($test->getLog(FALSE)), 0);
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/App/Tests/unit/_bootstrap.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | // Load helpers functions
13 | include_once 'Functions/helpers.php';
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.1.2 (2016-08-22)
2 | - Updated CodeCeption to version 2
3 | - Updated all other libraries
4 | - Added support for CodeCeption environments
5 |
6 | ## 0.1.1 (2015-11-27)
7 | - Windows Support
8 | - Improved error handling (a few better error messages)
9 | - Added ability to have local config outside of version control
10 |
11 | ## 0.1.0 (2014-01-07)
12 |
13 | **Initial Release Featured**
14 |
15 | - Run Codeception tests (Acceptance, Unit & Functional) via a Web interface.
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2014 James Healey
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Webception
2 |
3 | [](https://gitter.im/jayhealey/Webception?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 |
5 | #### Web Interface for running [Codeception](http://codeception.com) tests.
6 |
7 | Built with [Slim PHP framework](http://www.slimframework.com/) and [Foundation CSS framework](http://foundation.zurb.com/).
8 |
9 | ------------
10 |
11 | **What does it do?**
12 |
13 | Webception is a deployable web-application that allows you to run all your Codeception tests in the browser.
14 |
15 | You can access multiple test suites and decide which tests to include in a run. It allows you start, stop and restart the process whilst watching the test results in the Console.
16 |
17 | **What does it look like?**
18 |
19 | I'm glad you asked...
20 |
21 |

22 |
23 | **What's the ideal usage?**
24 |
25 | If you're a web developer, it's likely you run a staging server that hosts work in progress for your clients.
26 |
27 | Webception's ideal setup would be on a sub-domain on your staging server (`webception.your-staging-domain.com`) so it could have access to all of your test suites.
28 |
29 | **What it will it do?**
30 |
31 | Webception is a work in progress. Check out the [roadmap](#roadmap) for short-term goals.
32 |
33 | Check out how to [contribute](#contribute) if you want to get involved.
34 |
35 | ------------
36 |
37 | ## Requirements
38 |
39 | A web-server running PHP 5.3.0+ and [Composer](http://getcomposer.org/download/). Codeception will be installed via Composer.
40 |
41 | ------------
42 |
43 | ## Installation
44 |
45 | Out of the box, Webception is configured to run it's own Codeception tests.
46 |
47 | You'll need [Composer](http://getcomposer.org/download/) to be installed and the Codeception executable and logs directory need full read/write permissions.
48 |
49 | To configure Webception for your needs (i.e. to tun your own tests) copy `App/Config/codeception-local-sample.php` to `App/Config/codeception-local.php` and override settings from `App/Config/codeception.php`. It's here where you add references to the `codeception.yml` configurations.
50 |
51 | Also note Webception's `codeception.yml` is setup to use `http://webception:80` as it's host. Change this to be whatever host and port you decide to run Webception on.
52 |
53 | ### 1. Deploy Webception
54 |
55 | You can either install Webception using Composer:
56 |
57 | `composer create-project jayhealey/webception --stability=dev`
58 |
59 | Or [downloaded Webception](https://github.com/jayhealey/Webception/archive/master.zip) and unzip it. Once you've unzipped it, you need to install the Composer dependancies with:
60 |
61 | `composer install`
62 |
63 | Now you can do the following:
64 |
65 | 1. Ensure Codeception has permissions:
66 |
67 | `sudo chmod a+x vendor/bin/codecept`
68 |
69 | 2. Set permissions so Codeception can write out the log files:
70 |
71 | `sudo chmod -R 777 App/Tests/_log`
72 |
73 | 3. Set permissions so Slim PHP can write to the template cache:
74 |
75 | `sudo chmod -R 777 App/Templates/_cache`
76 |
77 | 4. Point your new server to the `public` path of where you unzipped Webception.
78 |
79 | You'll now be able to load Webception in your browser.
80 |
81 | If there are any issues Webception will do it's best to tell what you need to do.
82 |
83 | ### 2. Customise the Webception configuration
84 |
85 | There are a few configuration files you can play with in `/App/Config/codeception.php`.
86 |
87 | #### Adding your own tests to Webception
88 |
89 | You can add as many Codeception test suites as you need by adding to the `sites` array:
90 |
91 | ```
92 | 'sites' => array(
93 | 'Webception' => dirname(__FILE__) .'/../../codeception.yml',
94 | ),
95 | ```
96 | Put them in order you want to see in the dropdown. And if you only have one entry, you won't see the dropdown.
97 |
98 | Feel free to remove/replace the `Webception` entry with one of your own suites.
99 |
100 | If you have more than one site in the configuration, you can use the site picker on the top-left of Webception to swap between test suites.
101 |
102 | **And remember**: it's important you set `sudo chmod -R 777 /path/to/logs` on the log directories declared in your `codeception.yml` configurations. If you don't, Webception will fail to run the tests.
103 |
104 | *Note*: You may experience issues using `$_SERVER['DOCUMENT_ROOT']` to define the configuration path. It may be best to put the absolute path to your application root or a relative path using `dirname(__FILE__)`.
105 |
106 | ### 3. Run your tests!
107 |
108 | If you've configured everything correctly, Webception will show all your available tests. Just click **START** to run everything!
109 |
110 | That's it! **Happy Testing!**
111 |
112 | ------------
113 |
114 |
115 | ## Want to Contribute?
116 | There's a few ways you can get in touch:
117 |
118 | * **Chat on Twitter**. Follow [@WebceptionApp](https://www.twitter.com/WebceptionApp) for release updates or follow [@JayHealey](https://www.twitter.com/JayHealey) for everything else.
119 |
120 | * **Post bugs, issues, feature requests** via [GitHub Issues](https://github.com/jayhealey/webception/issues).
121 |
122 | * **Pull & Fork** on [GitHub](https://github.com/jayhealey/Webception/pulls) if you want to get your hands dirty. Please ensure that any new features you add have assosciated tests with them, and where possible point yuor feature request at the appropriate version (as per the [Roadmap](https://github.com/jayhealey/Webception/wiki))
123 |
124 | And **please let me know** if you use Webception. I'm keen to understand how you'd *like* to use it and if there's anything you'd like to see in future releases.
125 |
126 | I'm open to any feedback on how to improve Webception. From tips on SlimPHP, to how best to improve the Codeception handling to improving the UI. I'd be happy to hear it!
127 |
128 | ------------
129 |
130 | ## Infrequently Asked Questions (IAQs)
131 |
132 | **Why would I use Webception?**
133 |
134 | The aim of Webception is to open the test suites up to anyone involved in a web-development project. This could be a team leader, another developer (who might not be a PHP developer), client manager or even the client.
135 |
136 | The plan is to grow the tool to be a worthwhile part of your process. Potentially integrating CI tools or part of a bug reporting process.
137 |
138 | And selfishly, I couldn't find anything else that acted as a web interface for Codeception, so it was a problem worth solving.
139 |
140 | **Is Webception made by the same people as Codeception?**
141 |
142 | No. It's completely un-official. It's not affiliated or sponsored in anyway by the people who built Codeception.
143 |
144 | So, raise all issues about Webception on the Webception [GitHub Issues](https://github.com/jayhealey/Webception/issues) page.
145 |
146 | ------------
147 |
148 |
149 | ## Roadmap
150 |
151 | * **Automated/Interactive Setup**: I'd like to replace the manual setup with an interactive installation that asks for the Codeception test suites whilst verifying the details as you enter them. You'll should also be able to add/remove test suites via the app instead of modifying configuration files. It's possible to find all available `codeception.yml` files which would help automate installation.
152 |
153 | * **Logs and Screenshots**: When Codeception runs, it creates HTML snapshots and screenshots of where a test fails. It'd be useful for Webception to copy those files across and make them accessible via the console.
154 |
155 | * **Security**: At the moment, you can just secure the installation with .htaccess - but it might be worth adding built-in security via a Slim module.
156 |
157 | * **Exposed Unit Tests**: Unit tests contain multiple tests in a single file, so it'd be nice to scan Unit tests to expose them - and then allow the ability to run each of these tests individually (is that even possible in Codeception?).
158 |
159 | * **More Webception Tests**: It feels fitting that an application that runs tests should be drowning in tests. So, there'll be more of them in future.
160 |
161 | There's also the [TODO](TODO.md) list which contains a list of things I'd like to improve.
162 |
163 | If you have any ideas or issues, jump on [GitHub Issues](https://github.com/jayhealey/Webception/issues) or [@WebceptionApp](https://www.twitter.com/WebceptionApp) on Twitter.
164 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 |
2 | # TODO
3 |
4 | On top of the general feedback, bug fixes and roadmap there's a few other things that need work.
5 |
6 | * **Test Results**: There's no interaction/link between the test list and the results in the console. I might remove the console and put the resolves in a hover-modal instead.
7 |
8 | * **Improved Error Handling**: The UI for showing errors is a bit shaky. It could be better, which would be fixed as part of...
9 |
10 | * **Re-implement the Javascript in AngularJS**: Whilst the current Javascript is straight forward for the current feature-set, it likely won't scale as it grows. So, hopefully re-implementing it in AngularJS will likely solve that problem.
11 |
12 | * **Branding Webception**. The CSS for Webpcetion is pure Foundation with no changes, so it's very basic. I'd like to eventually brand it a little more.
13 |
--------------------------------------------------------------------------------
/codeception.yml:
--------------------------------------------------------------------------------
1 | paths:
2 | tests: App/Tests
3 | log: App/Tests/_log
4 | data: App/Tests/_data
5 | helpers: App/Tests/_helpers
6 | settings:
7 | bootstrap: _bootstrap.php
8 | suite_class: \PHPUnit_Framework_TestSuite
9 | colors: false
10 | memory_limit: 1024M
11 | log: true
12 | modules:
13 | config:
14 | Db:
15 | dsn: ''
16 | user: ''
17 | password: ''
18 | dump: App/Tests/_data/dump.sql
19 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jayhealey/webception",
3 | "description": "Web Interface for running Codeception tests.",
4 | "keywords": ["codeception", "webception", "testing", "php", "web interface"],
5 | "homepage" : "https://github.com/jayhealey/Webception",
6 | "authors": [
7 | {
8 | "name": "James Healey",
9 | "email": "jayhealey@gmail.com",
10 | "homepage": "http://twitter.com/jayhealey",
11 | "role": "Developer"
12 | }
13 | ],
14 | "license": "MIT",
15 | "require": {
16 | "php": ">=7.1",
17 | "slim/slim": "2.*",
18 | "slim/views": "0.1.*",
19 | "twig/twig": "~1.13",
20 | "codeception/codeception": "2.*",
21 | "symfony/yaml": "2.5.x-dev"
22 | },
23 | "autoload": {
24 | "psr-0": {
25 | "App": ""
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 | RewriteEngine On
2 |
3 | # Some hosts may require you to use the `RewriteBase` directive.
4 | # If you need to use the `RewriteBase` directive, it should be the
5 | # absolute physical path to the directory that contains this htaccess file.
6 | #
7 | # RewriteBase /
8 |
9 | RewriteCond %{REQUEST_FILENAME} !-f
10 | RewriteRule ^ index.php [QSA,L]
--------------------------------------------------------------------------------
/public/css/app.css:
--------------------------------------------------------------------------------
1 | .console { height: 350px; overflow: scroll;}
--------------------------------------------------------------------------------
/public/css/jquery.jscrollpane.css:
--------------------------------------------------------------------------------
1 | /*
2 | * CSS Styles that are needed by jScrollPane for it to operate correctly.
3 | *
4 | * Include this stylesheet in your site or copy and paste the styles below into your stylesheet - jScrollPane
5 | * may not operate correctly without them.
6 | */
7 | .scroll-pane
8 | {
9 | height: 300px;
10 | overflow: auto;
11 | }
12 | .jspContainer
13 | {
14 | overflow: hidden;
15 | position: relative;
16 | }
17 |
18 | .jspPane
19 | {
20 | position: absolute;
21 | }
22 |
23 | .jspVerticalBar
24 | {
25 | position: absolute;
26 | top: 0;
27 | right: 0;
28 | width: 16px;
29 | height: 100%;
30 | background: red;
31 | }
32 |
33 | .jspHorizontalBar
34 | {
35 | position: absolute;
36 | bottom: 0;
37 | left: 0;
38 | width: 100%;
39 | height: 16px;
40 | background: red;
41 | }
42 |
43 | .jspCap
44 | {
45 | display: none;
46 | }
47 |
48 | .jspHorizontalBar .jspCap
49 | {
50 | float: left;
51 | }
52 |
53 | .jspTrack
54 | {
55 | background: #dde;
56 | position: relative;
57 | }
58 |
59 | .jspDrag
60 | {
61 | background: #bbd;
62 | position: relative;
63 | top: 0;
64 | left: 0;
65 | cursor: pointer;
66 | }
67 |
68 | .jspHorizontalBar .jspTrack,
69 | .jspHorizontalBar .jspDrag
70 | {
71 | float: left;
72 | height: 100%;
73 | }
74 |
75 | .jspArrow
76 | {
77 | background: #50506d;
78 | text-indent: -20000px;
79 | display: block;
80 | cursor: pointer;
81 | padding: 0;
82 | margin: 0;
83 | }
84 |
85 | .jspArrow.jspDisabled
86 | {
87 | cursor: default;
88 | background: #80808d;
89 | }
90 |
91 | .jspVerticalBar .jspArrow
92 | {
93 | height: 16px;
94 | }
95 |
96 | .jspHorizontalBar .jspArrow
97 | {
98 | width: 16px;
99 | float: left;
100 | height: 100%;
101 | }
102 |
103 | .jspVerticalBar .jspArrow:focus
104 | {
105 | outline: none;
106 | }
107 |
108 | .jspCorner
109 | {
110 | background: #eeeef4;
111 | float: left;
112 | height: 100%;
113 | }
114 |
115 | /* Yuk! CSS Hack for IE6 3 pixel bug :( */
116 | * html .jspCorner
117 | {
118 | margin: 0 -3px 0 0;
119 | }
--------------------------------------------------------------------------------
/public/css/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v2.1.2 | MIT License | git.io/normalize */
2 |
3 | /* ==========================================================================
4 | HTML5 display definitions
5 | ========================================================================== */
6 |
7 | /**
8 | * Correct `block` display not defined in IE 8/9.
9 | */
10 |
11 | article,
12 | aside,
13 | details,
14 | figcaption,
15 | figure,
16 | footer,
17 | header,
18 | hgroup,
19 | main,
20 | nav,
21 | section,
22 | summary {
23 | display: block;
24 | }
25 |
26 | /**
27 | * Correct `inline-block` display not defined in IE 8/9.
28 | */
29 |
30 | audio,
31 | canvas,
32 | video {
33 | display: inline-block;
34 | }
35 |
36 | /**
37 | * Prevent modern browsers from displaying `audio` without controls.
38 | * Remove excess height in iOS 5 devices.
39 | */
40 |
41 | audio:not([controls]) {
42 | display: none;
43 | height: 0;
44 | }
45 |
46 | /**
47 | * Address `[hidden]` styling not present in IE 8/9.
48 | * Hide the `template` element in IE, Safari, and Firefox < 22.
49 | */
50 |
51 | [hidden],
52 | template {
53 | display: none;
54 | }
55 |
56 | script {
57 | display: none !important;
58 | }
59 |
60 | /* ==========================================================================
61 | Base
62 | ========================================================================== */
63 |
64 | /**
65 | * 1. Set default font family to sans-serif.
66 | * 2. Prevent iOS text size adjust after orientation change, without disabling
67 | * user zoom.
68 | */
69 |
70 | html {
71 | font-family: sans-serif; /* 1 */
72 | -ms-text-size-adjust: 100%; /* 2 */
73 | -webkit-text-size-adjust: 100%; /* 2 */
74 | }
75 |
76 | /**
77 | * Remove default margin.
78 | */
79 |
80 | body {
81 | margin: 0;
82 | }
83 |
84 | /* ==========================================================================
85 | Links
86 | ========================================================================== */
87 |
88 | /**
89 | * Remove the gray background color from active links in IE 10.
90 | */
91 |
92 | a {
93 | background: transparent;
94 | }
95 |
96 | /**
97 | * Address `outline` inconsistency between Chrome and other browsers.
98 | */
99 |
100 | a:focus {
101 | outline: thin dotted;
102 | }
103 |
104 | /**
105 | * Improve readability when focused and also mouse hovered in all browsers.
106 | */
107 |
108 | a:active,
109 | a:hover {
110 | outline: 0;
111 | }
112 |
113 | /* ==========================================================================
114 | Typography
115 | ========================================================================== */
116 |
117 | /**
118 | * Address variable `h1` font-size and margin within `section` and `article`
119 | * contexts in Firefox 4+, Safari 5, and Chrome.
120 | */
121 |
122 | h1 {
123 | font-size: 2em;
124 | margin: 0.67em 0;
125 | }
126 |
127 | /**
128 | * Address styling not present in IE 8/9, Safari 5, and Chrome.
129 | */
130 |
131 | abbr[title] {
132 | border-bottom: 1px dotted;
133 | }
134 |
135 | /**
136 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
137 | */
138 |
139 | b,
140 | strong {
141 | font-weight: bold;
142 | }
143 |
144 | /**
145 | * Address styling not present in Safari 5 and Chrome.
146 | */
147 |
148 | dfn {
149 | font-style: italic;
150 | }
151 |
152 | /**
153 | * Address differences between Firefox and other browsers.
154 | */
155 |
156 | hr {
157 | -moz-box-sizing: content-box;
158 | box-sizing: content-box;
159 | height: 0;
160 | }
161 |
162 | /**
163 | * Address styling not present in IE 8/9.
164 | */
165 |
166 | mark {
167 | background: #ff0;
168 | color: #000;
169 | }
170 |
171 | /**
172 | * Correct font family set oddly in Safari 5 and Chrome.
173 | */
174 |
175 | code,
176 | kbd,
177 | pre,
178 | samp {
179 | font-family: monospace, serif;
180 | font-size: 1em;
181 | }
182 |
183 | /**
184 | * Improve readability of pre-formatted text in all browsers.
185 | */
186 |
187 | pre {
188 | white-space: pre-wrap;
189 | }
190 |
191 | /**
192 | * Set consistent quote types.
193 | */
194 |
195 | q {
196 | quotes: "\201C" "\201D" "\2018" "\2019";
197 | }
198 |
199 | /**
200 | * Address inconsistent and variable font size in all browsers.
201 | */
202 |
203 | small {
204 | font-size: 80%;
205 | }
206 |
207 | /**
208 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
209 | */
210 |
211 | sub,
212 | sup {
213 | font-size: 75%;
214 | line-height: 0;
215 | position: relative;
216 | vertical-align: baseline;
217 | }
218 |
219 | sup {
220 | top: -0.5em;
221 | }
222 |
223 | sub {
224 | bottom: -0.25em;
225 | }
226 |
227 | /* ==========================================================================
228 | Embedded content
229 | ========================================================================== */
230 |
231 | /**
232 | * Remove border when inside `a` element in IE 8/9.
233 | */
234 |
235 | img {
236 | border: 0;
237 | }
238 |
239 | /**
240 | * Correct overflow displayed oddly in IE 9.
241 | */
242 |
243 | svg:not(:root) {
244 | overflow: hidden;
245 | }
246 |
247 | /* ==========================================================================
248 | Figures
249 | ========================================================================== */
250 |
251 | /**
252 | * Address margin not present in IE 8/9 and Safari 5.
253 | */
254 |
255 | figure {
256 | margin: 0;
257 | }
258 |
259 | /* ==========================================================================
260 | Forms
261 | ========================================================================== */
262 |
263 | /**
264 | * Define consistent border, margin, and padding.
265 | */
266 |
267 | fieldset {
268 | border: 1px solid #c0c0c0;
269 | margin: 0 2px;
270 | padding: 0.35em 0.625em 0.75em;
271 | }
272 |
273 | /**
274 | * 1. Correct `color` not being inherited in IE 8/9.
275 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
276 | */
277 |
278 | legend {
279 | border: 0; /* 1 */
280 | padding: 0; /* 2 */
281 | }
282 |
283 | /**
284 | * 1. Correct font family not being inherited in all browsers.
285 | * 2. Correct font size not being inherited in all browsers.
286 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
287 | */
288 |
289 | button,
290 | input,
291 | select,
292 | textarea {
293 | font-family: inherit; /* 1 */
294 | font-size: 100%; /* 2 */
295 | margin: 0; /* 3 */
296 | }
297 |
298 | /**
299 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
300 | * the UA stylesheet.
301 | */
302 |
303 | button,
304 | input {
305 | line-height: normal;
306 | }
307 |
308 | /**
309 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
310 | * All other form control elements do not inherit `text-transform` values.
311 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
312 | * Correct `select` style inheritance in Firefox 4+ and Opera.
313 | */
314 |
315 | button,
316 | select {
317 | text-transform: none;
318 | }
319 |
320 | /**
321 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
322 | * and `video` controls.
323 | * 2. Correct inability to style clickable `input` types in iOS.
324 | * 3. Improve usability and consistency of cursor style between image-type
325 | * `input` and others.
326 | */
327 |
328 | button,
329 | html input[type="button"], /* 1 */
330 | input[type="reset"],
331 | input[type="submit"] {
332 | -webkit-appearance: button; /* 2 */
333 | cursor: pointer; /* 3 */
334 | }
335 |
336 | /**
337 | * Re-set default cursor for disabled elements.
338 | */
339 |
340 | button[disabled],
341 | html input[disabled] {
342 | cursor: default;
343 | }
344 |
345 | /**
346 | * 1. Address box sizing set to `content-box` in IE 8/9.
347 | * 2. Remove excess padding in IE 8/9.
348 | */
349 |
350 | input[type="checkbox"],
351 | input[type="radio"] {
352 | box-sizing: border-box; /* 1 */
353 | padding: 0; /* 2 */
354 | }
355 |
356 | /**
357 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
358 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
359 | * (include `-moz` to future-proof).
360 | */
361 |
362 | input[type="search"] {
363 | -webkit-appearance: textfield; /* 1 */
364 | -moz-box-sizing: content-box;
365 | -webkit-box-sizing: content-box; /* 2 */
366 | box-sizing: content-box;
367 | }
368 |
369 | /**
370 | * Remove inner padding and search cancel button in Safari 5 and Chrome
371 | * on OS X.
372 | */
373 |
374 | input[type="search"]::-webkit-search-cancel-button,
375 | input[type="search"]::-webkit-search-decoration {
376 | -webkit-appearance: none;
377 | }
378 |
379 | /**
380 | * Remove inner padding and border in Firefox 4+.
381 | */
382 |
383 | button::-moz-focus-inner,
384 | input::-moz-focus-inner {
385 | border: 0;
386 | padding: 0;
387 | }
388 |
389 | /**
390 | * 1. Remove default vertical scrollbar in IE 8/9.
391 | * 2. Improve readability and alignment in all browsers.
392 | */
393 |
394 | textarea {
395 | overflow: auto; /* 1 */
396 | vertical-align: top; /* 2 */
397 | }
398 |
399 | /* ==========================================================================
400 | Tables
401 | ========================================================================== */
402 |
403 | /**
404 | * Remove most spacing between table cells.
405 | */
406 |
407 | table {
408 | border-collapse: collapse;
409 | border-spacing: 0;
410 | }
411 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | /*
14 | |--------------------------------------------------------------------------
15 | | Application Bootstrap
16 | |--------------------------------------------------------------------------
17 | */
18 |
19 | session_cache_limiter(false);
20 | session_start();
21 |
22 | define('HASH', 'site_session');
23 | define('RP', '../');
24 | require(RP . 'vendor/autoload.php');
25 | require(RP . 'App/bootstrap.php');
26 |
27 | /*
28 | |--------------------------------------------------------------------------
29 | | Slim Bootstrap
30 | |--------------------------------------------------------------------------
31 | */
32 |
33 | $app = new \Slim\Slim(array(
34 |
35 | /*
36 | |--------------------------------------------------------------------------
37 | | Webception Settings
38 | |--------------------------------------------------------------------------
39 | */
40 |
41 | 'webception' => array(
42 | 'version' => '0.1.0',
43 | 'name' => '
Webception',
44 | 'repo' => 'https://github.com/jayhealey/webception',
45 | 'twitter' => '@WebceptionApp',
46 | 'config' => RP . 'App/Config/codeception.php',
47 | 'test' => RP . 'App/Tests/_config/codeception_%s.php',
48 | ),
49 |
50 | /*
51 | |--------------------------------------------------------------------------
52 | | Application Settings
53 | |--------------------------------------------------------------------------
54 | */
55 |
56 | 'templates.path' => RP . 'App/Templates',
57 |
58 | ));
59 |
60 | /*
61 | |--------------------------------------------------------------------------
62 | | Load the configuration
63 | |--------------------------------------------------------------------------
64 | */
65 |
66 | $config = get_webception_config($app);
67 |
68 | /*
69 | |--------------------------------------------------------------------------
70 | | Sites Class
71 | |--------------------------------------------------------------------------
72 | */
73 |
74 | $app->container->singleton('site', function () use ($app, $config) {
75 |
76 | $hash_in_querystring = $app->request()->get('hash');
77 |
78 | // If the site is being changed (via query string), use that.
79 | // If a site is in session, use that.
80 | // Otherwise, set as false and the Site() class will pull the first site it can find.
81 |
82 | $hash = FALSE;
83 |
84 | if (! is_null($hash_in_querystring) && $hash_in_querystring !== FALSE)
85 | $hash = $hash_in_querystring;
86 | elseif (isset($_SESSION[HASH]))
87 | $hash = $_SESSION[HASH];
88 |
89 | // Setup the site class with all the available sites from the
90 | // Codeception configuration
91 | $site = new \App\Lib\Site($config['sites']);
92 |
93 | // Set the current site
94 | $site->set($hash);
95 |
96 | // Update the users session to use the chosen site
97 | $_SESSION[HASH] = $site->getHash();
98 |
99 | return $site;
100 | });
101 |
102 | /*
103 | |--------------------------------------------------------------------------
104 | | Codeception Class
105 | |--------------------------------------------------------------------------
106 | */
107 |
108 | $app->container->singleton('codeception', function () use ($config, $app) {
109 | return new \App\Lib\Codeception($config, $app->site);
110 | });
111 |
112 | /*
113 | |--------------------------------------------------------------------------
114 | | Load Routes
115 | |--------------------------------------------------------------------------
116 | */
117 |
118 | foreach (glob(RP . 'App/Routes/*.route.php') as $route)
119 | require_once($route);
120 |
121 | /*
122 | |--------------------------------------------------------------------------
123 | | Setup Twig for Templates
124 | |--------------------------------------------------------------------------
125 | */
126 |
127 | $app->view(new \Slim\Views\Twig());
128 | $app->view->parserOptions = array(
129 | 'charset' => 'utf-8',
130 | 'cache' => realpath(RP .'App/Templates/_cache'),
131 | 'auto_reload' => true,
132 | 'strict_variables' => false,
133 | 'autoescape' => true
134 | );
135 | $app->view->parserExtensions = array(new \Slim\Views\TwigExtension());
136 |
137 | /*
138 | |--------------------------------------------------------------------------
139 | | Error Handler
140 | |--------------------------------------------------------------------------
141 | */
142 |
143 | $app->error(function (\Exception $e) use ($app) {
144 | $app->render('error.php');
145 | });
146 |
147 | $app->run();
148 |
--------------------------------------------------------------------------------
/public/js/libs/foundation/foundation.abide.js:
--------------------------------------------------------------------------------
1 | ;(function ($, window, document, undefined) {
2 | 'use strict';
3 |
4 | Foundation.libs.abide = {
5 | name : 'abide',
6 |
7 | version : '5.0.0',
8 |
9 | settings : {
10 | focus_on_invalid : true,
11 | timeout : 1000,
12 | patterns : {
13 | alpha: /[a-zA-Z]+/,
14 | alpha_numeric : /[a-zA-Z0-9]+/,
15 | integer: /-?\d+/,
16 | number: /-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?/,
17 |
18 | // generic password: upper-case, lower-case, number/special character, and min 8 characters
19 | password : /(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/,
20 |
21 | // amex, visa, diners
22 | card : /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/,
23 | cvv : /^([0-9]){3,4}$/,
24 |
25 | // http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
26 | email : /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
27 |
28 | url: /(https?|ftp|file|ssh):\/\/(((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?/,
29 | // abc.de
30 | domain: /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$/,
31 |
32 | datetime: /([0-2][0-9]{3})\-([0-1][0-9])\-([0-3][0-9])T([0-5][0-9])\:([0-5][0-9])\:([0-5][0-9])(Z|([\-\+]([0-1][0-9])\:00))/,
33 | // YYYY-MM-DD
34 | date: /(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))/,
35 | // HH:MM:SS
36 | time : /(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}/,
37 | dateISO: /\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/,
38 | // MM/DD/YYYY
39 | month_day_year : /(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.](19|20)\d\d/,
40 |
41 | // #FFF or #FFFFFF
42 | color: /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/
43 | }
44 | },
45 |
46 | timer : null,
47 |
48 | init : function (scope, method, options) {
49 | this.bindings(method, options);
50 | },
51 |
52 | events : function (scope) {
53 | var self = this,
54 | form = $(scope).attr('novalidate', 'novalidate'),
55 | settings = form.data('abide-init');
56 |
57 | form
58 | .off('.abide')
59 | .on('submit.fndtn.abide validate.fndtn.abide', function (e) {
60 | var is_ajax = /ajax/i.test($(this).attr('data-abide'));
61 | return self.validate($(this).find('input, textarea, select').get(), e, is_ajax);
62 | })
63 | .find('input, textarea, select')
64 | .off('.abide')
65 | .on('blur.fndtn.abide change.fndtn.abide', function (e) {
66 | self.validate([this], e);
67 | })
68 | .on('keydown.fndtn.abide', function (e) {
69 | var settings = $(this).closest('form').data('abide-init');
70 | clearTimeout(self.timer);
71 | self.timer = setTimeout(function () {
72 | self.validate([this], e);
73 | }.bind(this), settings.timeout);
74 | });
75 | },
76 |
77 | validate : function (els, e, is_ajax) {
78 | var validations = this.parse_patterns(els),
79 | validation_count = validations.length,
80 | form = $(els[0]).closest('form'),
81 | submit_event = /submit/.test(e.type);
82 |
83 | for (var i=0; i < validation_count; i++) {
84 | if (!validations[i] && (submit_event || is_ajax)) {
85 | if (this.settings.focus_on_invalid) els[i].focus();
86 | form.trigger('invalid');
87 | $(els[i]).closest('form').attr('data-invalid', '');
88 | return false;
89 | }
90 | }
91 |
92 | if (submit_event || is_ajax) {
93 | form.trigger('valid');
94 | }
95 |
96 | form.removeAttr('data-invalid');
97 |
98 | if (is_ajax) return false;
99 |
100 | return true;
101 | },
102 |
103 | parse_patterns : function (els) {
104 | var count = els.length,
105 | el_patterns = [];
106 |
107 | for (var i = count - 1; i >= 0; i--) {
108 | el_patterns.push(this.pattern(els[i]));
109 | }
110 |
111 | return this.check_validation_and_apply_styles(el_patterns);
112 | },
113 |
114 | pattern : function (el) {
115 | var type = el.getAttribute('type'),
116 | required = typeof el.getAttribute('required') === 'string';
117 |
118 | if (this.settings.patterns.hasOwnProperty(type)) {
119 | return [el, this.settings.patterns[type], required];
120 | }
121 |
122 | var pattern = el.getAttribute('pattern') || '';
123 |
124 | if (this.settings.patterns.hasOwnProperty(pattern) && pattern.length > 0) {
125 | return [el, this.settings.patterns[pattern], required];
126 | } else if (pattern.length > 0) {
127 | return [el, new RegExp(pattern), required];
128 | }
129 |
130 | pattern = /.*/;
131 |
132 | return [el, pattern, required];
133 | },
134 |
135 | check_validation_and_apply_styles : function (el_patterns) {
136 | var count = el_patterns.length,
137 | validations = [];
138 |
139 | for (var i = count - 1; i >= 0; i--) {
140 | var el = el_patterns[i][0],
141 | required = el_patterns[i][2],
142 | value = el.value,
143 | is_equal = el.getAttribute('data-equalto'),
144 | is_radio = el.type === "radio",
145 | valid_length = (required) ? (el.value.length > 0) : true;
146 |
147 | if (is_radio && required) {
148 | validations.push(this.valid_radio(el, required));
149 | } else if (is_equal && required) {
150 | validations.push(this.valid_equal(el, required));
151 | } else {
152 | if (el_patterns[i][1].test(value) && valid_length ||
153 | !required && el.value.length < 1) {
154 | $(el).removeAttr('data-invalid').parent().removeClass('error');
155 | validations.push(true);
156 | } else {
157 | $(el).attr('data-invalid', '').parent().addClass('error');
158 | validations.push(false);
159 | }
160 | }
161 | }
162 |
163 | return validations;
164 | },
165 |
166 | valid_radio : function (el, required) {
167 | var name = el.getAttribute('name'),
168 | group = document.getElementsByName(name),
169 | count = group.length,
170 | valid = false;
171 |
172 | for (var i=0; i < count; i++) {
173 | if (group[i].checked) valid = true;
174 | }
175 |
176 | for (var i=0; i < count; i++) {
177 | if (valid) {
178 | $(group[i]).removeAttr('data-invalid').parent().removeClass('error');
179 | } else {
180 | $(group[i]).attr('data-invalid', '').parent().addClass('error');
181 | }
182 | }
183 |
184 | return valid;
185 | },
186 |
187 | valid_equal: function(el, required) {
188 | var from = document.getElementById(el.getAttribute('data-equalto')).value,
189 | to = el.value,
190 | valid = (from === to);
191 |
192 | if (valid) {
193 | $(el).removeAttr('data-invalid').parent().removeClass('error');
194 | } else {
195 | $(el).attr('data-invalid', '').parent().addClass('error');
196 | }
197 |
198 | return valid;
199 | }
200 | };
201 | }(jQuery, this, this.document));
202 |
--------------------------------------------------------------------------------
/public/js/libs/foundation/foundation.accordion.js:
--------------------------------------------------------------------------------
1 | ;(function ($, window, document, undefined) {
2 | 'use strict';
3 |
4 | Foundation.libs.accordion = {
5 | name : 'accordion',
6 |
7 | version : '5.0.1',
8 |
9 | settings : {
10 | active_class: 'active',
11 | toggleable: true
12 | },
13 |
14 | init : function (scope, method, options) {
15 | this.bindings(method, options);
16 | },
17 |
18 | events : function () {
19 | $(this.scope).off('.accordion').on('click.fndtn.accordion', '[data-accordion] > dd > a', function (e) {
20 | var accordion = $(this).parent(),
21 | target = $('#' + this.href.split('#')[1]),
22 | siblings = $('> dd > .content', target.closest('[data-accordion]')),
23 | settings = accordion.parent().data('accordion-init'),
24 | active = $('> dd > .content.' + settings.active_class, accordion.parent());
25 |
26 | e.preventDefault();
27 |
28 | if (active[0] == target[0] && settings.toggleable) {
29 | return target.toggleClass(settings.active_class);
30 | }
31 |
32 | siblings.removeClass(settings.active_class);
33 | target.addClass(settings.active_class);
34 | });
35 | },
36 |
37 | off : function () {},
38 |
39 | reflow : function () {}
40 | };
41 | }(jQuery, this, this.document));
42 |
--------------------------------------------------------------------------------
/public/js/libs/foundation/foundation.alert.js:
--------------------------------------------------------------------------------
1 | ;(function ($, window, document, undefined) {
2 | 'use strict';
3 |
4 | Foundation.libs.alert = {
5 | name : 'alert',
6 |
7 | version : '5.0.0',
8 |
9 | settings : {
10 | animation: 'fadeOut',
11 | speed: 300, // fade out speed
12 | callback: function (){}
13 | },
14 |
15 | init : function (scope, method, options) {
16 | this.bindings(method, options);
17 | },
18 |
19 | events : function () {
20 | $(this.scope).off('.alert').on('click.fndtn.alert', '[data-alert] a.close', function (e) {
21 | var alertBox = $(this).closest("[data-alert]"),
22 | settings = alertBox.data('alert-init');
23 |
24 | e.preventDefault();
25 | alertBox[settings.animation](settings.speed, function () {
26 | $(this).trigger('closed').remove();
27 | settings.callback();
28 | });
29 | });
30 | },
31 |
32 | reflow : function () {}
33 | };
34 | }(jQuery, this, this.document));
35 |
--------------------------------------------------------------------------------
/public/js/libs/foundation/foundation.dropdown.js:
--------------------------------------------------------------------------------
1 | ;(function ($, window, document, undefined) {
2 | 'use strict';
3 |
4 | Foundation.libs.dropdown = {
5 | name : 'dropdown',
6 |
7 | version : '5.0.0',
8 |
9 | settings : {
10 | active_class: 'open',
11 | is_hover: false,
12 | opened: function(){},
13 | closed: function(){}
14 | },
15 |
16 | init : function (scope, method, options) {
17 | Foundation.inherit(this, 'throttle');
18 |
19 | this.bindings(method, options);
20 | },
21 |
22 | events : function (scope) {
23 | var self = this;
24 |
25 | $(this.scope)
26 | .off('.dropdown')
27 | .on('click.fndtn.dropdown', '[data-dropdown]', function (e) {
28 | var settings = $(this).data('dropdown-init');
29 | e.preventDefault();
30 |
31 | if (!settings.is_hover || Modernizr.touch) self.toggle($(this));
32 | })
33 | .on('mouseenter.fndtn.dropdown', '[data-dropdown], [data-dropdown-content]', function (e) {
34 | var $this = $(this);
35 | clearTimeout(self.timeout);
36 |
37 | if ($this.data('dropdown')) {
38 | var dropdown = $('#' + $this.data('dropdown')),
39 | target = $this;
40 | } else {
41 | var dropdown = $this;
42 | target = $("[data-dropdown='" + dropdown.attr('id') + "']");
43 | }
44 |
45 | var settings = target.data('dropdown-init');
46 | if (settings.is_hover) self.open.apply(self, [dropdown, target]);
47 | })
48 | .on('mouseleave.fndtn.dropdown', '[data-dropdown], [data-dropdown-content]', function (e) {
49 | var $this = $(this);
50 | self.timeout = setTimeout(function () {
51 | if ($this.data('dropdown')) {
52 | var settings = $this.data('dropdown-init');
53 | if (settings.is_hover) self.close.call(self, $('#' + $this.data('dropdown')));
54 | } else {
55 | var target = $('[data-dropdown="' + $(this).attr('id') + '"]'),
56 | settings = target.data('dropdown-init');
57 | if (settings.is_hover) self.close.call(self, $this);
58 | }
59 | }.bind(this), 150);
60 | })
61 | .on('click.fndtn.dropdown', function (e) {
62 | var parent = $(e.target).closest('[data-dropdown-content]');
63 |
64 | if ($(e.target).data('dropdown') || $(e.target).parent().data('dropdown')) {
65 | return;
66 | }
67 | if (!($(e.target).data('revealId')) &&
68 | (parent.length > 0 && ($(e.target).is('[data-dropdown-content]') ||
69 | $.contains(parent.first()[0], e.target)))) {
70 | e.stopPropagation();
71 | return;
72 | }
73 |
74 | self.close.call(self, $('[data-dropdown-content]'));
75 | })
76 | .on('opened.fndtn.dropdown', '[data-dropdown-content]', this.settings.opened)
77 | .on('closed.fndtn.dropdown', '[data-dropdown-content]', this.settings.closed);
78 |
79 | $(window)
80 | .off('.dropdown')
81 | .on('resize.fndtn.dropdown', self.throttle(function () {
82 | self.resize.call(self);
83 | }, 50)).trigger('resize');
84 | },
85 |
86 | close: function (dropdown) {
87 | var self = this;
88 | dropdown.each(function () {
89 | if ($(this).hasClass(self.settings.active_class)) {
90 | $(this)
91 | .css(Foundation.rtl ? 'right':'left', '-99999px')
92 | .removeClass(self.settings.active_class);
93 | $(this).trigger('closed');
94 | }
95 | });
96 | },
97 |
98 | open: function (dropdown, target) {
99 | this
100 | .css(dropdown
101 | .addClass(this.settings.active_class), target);
102 | dropdown.trigger('opened');
103 | },
104 |
105 | toggle : function (target) {
106 | var dropdown = $('#' + target.data('dropdown'));
107 | if (dropdown.length === 0) {
108 | // No dropdown found, not continuing
109 | return;
110 | }
111 |
112 | this.close.call(this, $('[data-dropdown-content]').not(dropdown));
113 |
114 | if (dropdown.hasClass(this.settings.active_class)) {
115 | this.close.call(this, dropdown);
116 | } else {
117 | this.close.call(this, $('[data-dropdown-content]'))
118 | this.open.call(this, dropdown, target);
119 | }
120 | },
121 |
122 | resize : function () {
123 | var dropdown = $('[data-dropdown-content].open'),
124 | target = $("[data-dropdown='" + dropdown.attr('id') + "']");
125 |
126 | if (dropdown.length && target.length) {
127 | this.css(dropdown, target);
128 | }
129 | },
130 |
131 | css : function (dropdown, target) {
132 | var offset_parent = dropdown.offsetParent(),
133 | position = target.offset();
134 |
135 | position.top -= offset_parent.offset().top;
136 | position.left -= offset_parent.offset().left;
137 |
138 | if (this.small()) {
139 | dropdown.css({
140 | position : 'absolute',
141 | width: '95%',
142 | 'max-width': 'none',
143 | top: position.top + target.outerHeight()
144 | });
145 | dropdown.css(Foundation.rtl ? 'right':'left', '2.5%');
146 | } else {
147 | if (!Foundation.rtl && $(window).width() > dropdown.outerWidth() + target.offset().left) {
148 | var left = position.left;
149 | if (dropdown.hasClass('right')) {
150 | dropdown.removeClass('right');
151 | }
152 | } else {
153 | if (!dropdown.hasClass('right')) {
154 | dropdown.addClass('right');
155 | }
156 | var left = position.left - (dropdown.outerWidth() - target.outerWidth());
157 | }
158 |
159 | dropdown.attr('style', '').css({
160 | position : 'absolute',
161 | top: position.top + target.outerHeight(),
162 | left: left
163 | });
164 | }
165 |
166 | return dropdown;
167 | },
168 |
169 | small : function () {
170 | return matchMedia(Foundation.media_queries.small).matches &&
171 | !matchMedia(Foundation.media_queries.medium).matches;
172 | },
173 |
174 | off: function () {
175 | $(this.scope).off('.fndtn.dropdown');
176 | $('html, body').off('.fndtn.dropdown');
177 | $(window).off('.fndtn.dropdown');
178 | $('[data-dropdown-content]').off('.fndtn.dropdown');
179 | this.settings.init = false;
180 | },
181 |
182 | reflow : function () {}
183 | };
184 | }(jQuery, this, this.document));
185 |
--------------------------------------------------------------------------------
/public/js/libs/foundation/foundation.interchange.js:
--------------------------------------------------------------------------------
1 | ;(function ($, window, document, undefined) {
2 | 'use strict';
3 |
4 | Foundation.libs.interchange = {
5 | name : 'interchange',
6 |
7 | version : '5.0.0',
8 |
9 | cache : {},
10 |
11 | images_loaded : false,
12 | nodes_loaded : false,
13 |
14 | settings : {
15 | load_attr : 'interchange',
16 |
17 | named_queries : {
18 | 'default' : Foundation.media_queries.small,
19 | small : Foundation.media_queries.small,
20 | medium : Foundation.media_queries.medium,
21 | large : Foundation.media_queries.large,
22 | xlarge : Foundation.media_queries.xlarge,
23 | xxlarge: Foundation.media_queries.xxlarge,
24 | landscape : 'only screen and (orientation: landscape)',
25 | portrait : 'only screen and (orientation: portrait)',
26 | retina : 'only screen and (-webkit-min-device-pixel-ratio: 2),' +
27 | 'only screen and (min--moz-device-pixel-ratio: 2),' +
28 | 'only screen and (-o-min-device-pixel-ratio: 2/1),' +
29 | 'only screen and (min-device-pixel-ratio: 2),' +
30 | 'only screen and (min-resolution: 192dpi),' +
31 | 'only screen and (min-resolution: 2dppx)'
32 | },
33 |
34 | directives : {
35 | replace: function (el, path, trigger) {
36 | // The trigger argument, if called within the directive, fires
37 | // an event named after the directive on the element, passing
38 | // any parameters along to the event that you pass to trigger.
39 | //
40 | // ex. trigger(), trigger([a, b, c]), or trigger(a, b, c)
41 | //
42 | // This allows you to bind a callback like so:
43 | // $('#interchangeContainer').on('replace', function (e, a, b, c) {
44 | // console.log($(this).html(), a, b, c);
45 | // });
46 |
47 | if (/IMG/.test(el[0].nodeName)) {
48 | var orig_path = el[0].src;
49 |
50 | if (new RegExp(path, 'i').test(orig_path)) return;
51 |
52 | el[0].src = path;
53 |
54 | return trigger(el[0].src);
55 | }
56 | var last_path = el.data('interchange-last-path');
57 |
58 | if (last_path == path) return;
59 |
60 | return $.get(path, function (response) {
61 | el.html(response);
62 | el.data('interchange-last-path', path);
63 | trigger();
64 | });
65 |
66 | }
67 | }
68 | },
69 |
70 | init : function (scope, method, options) {
71 | Foundation.inherit(this, 'throttle');
72 |
73 | this.data_attr = 'data-' + this.settings.load_attr;
74 |
75 | this.bindings(method, options);
76 | this.load('images');
77 | this.load('nodes');
78 | },
79 |
80 | events : function () {
81 | var self = this;
82 |
83 | $(window)
84 | .off('.interchange')
85 | .on('resize.fndtn.interchange', self.throttle(function () {
86 | self.resize.call(self);
87 | }, 50));
88 |
89 | return this;
90 | },
91 |
92 | resize : function () {
93 | var cache = this.cache;
94 |
95 | if(!this.images_loaded || !this.nodes_loaded) {
96 | setTimeout($.proxy(this.resize, this), 50);
97 | return;
98 | }
99 |
100 | for (var uuid in cache) {
101 | if (cache.hasOwnProperty(uuid)) {
102 | var passed = this.results(uuid, cache[uuid]);
103 |
104 | if (passed) {
105 | this.settings.directives[passed
106 | .scenario[1]](passed.el, passed.scenario[0], function () {
107 | if (arguments[0] instanceof Array) {
108 | var args = arguments[0];
109 | } else {
110 | var args = Array.prototype.slice.call(arguments, 0);
111 | }
112 |
113 | passed.el.trigger(passed.scenario[1], args);
114 | });
115 | }
116 | }
117 | }
118 |
119 | },
120 |
121 | results : function (uuid, scenarios) {
122 | var count = scenarios.length;
123 |
124 | if (count > 0) {
125 | var el = this.S('[data-uuid="' + uuid + '"]');
126 |
127 | for (var i = count - 1; i >= 0; i--) {
128 | var mq, rule = scenarios[i][2];
129 | if (this.settings.named_queries.hasOwnProperty(rule)) {
130 | mq = matchMedia(this.settings.named_queries[rule]);
131 | } else {
132 | mq = matchMedia(rule);
133 | }
134 | if (mq.matches) {
135 | return {el: el, scenario: scenarios[i]};
136 | }
137 | }
138 | }
139 |
140 | return false;
141 | },
142 |
143 | load : function (type, force_update) {
144 | if (typeof this['cached_' + type] === 'undefined' || force_update) {
145 | this['update_' + type]();
146 | }
147 |
148 | return this['cached_' + type];
149 | },
150 |
151 | update_images : function () {
152 | var images = this.S('img[' + this.data_attr + ']'),
153 | count = images.length,
154 | loaded_count = 0,
155 | data_attr = this.data_attr;
156 |
157 | this.cache = {};
158 | this.cached_images = [];
159 | this.images_loaded = (count === 0);
160 |
161 | for (var i = count - 1; i >= 0; i--) {
162 | loaded_count++;
163 | if (images[i]) {
164 | var str = images[i].getAttribute(data_attr) || '';
165 |
166 | if (str.length > 0) {
167 | this.cached_images.push(images[i]);
168 | }
169 | }
170 |
171 | if(loaded_count === count) {
172 | this.images_loaded = true;
173 | this.enhance('images');
174 | }
175 | }
176 |
177 | return this;
178 | },
179 |
180 | update_nodes : function () {
181 | var nodes = this.S('[' + this.data_attr + ']:not(img)'),
182 | count = nodes.length,
183 | loaded_count = 0,
184 | data_attr = this.data_attr;
185 |
186 | this.cached_nodes = [];
187 | // Set nodes_loaded to true if there are no nodes
188 | // this.nodes_loaded = false;
189 | this.nodes_loaded = (count === 0);
190 |
191 |
192 | for (var i = count - 1; i >= 0; i--) {
193 | loaded_count++;
194 | var str = nodes[i].getAttribute(data_attr) || '';
195 |
196 | if (str.length > 0) {
197 | this.cached_nodes.push(nodes[i]);
198 | }
199 |
200 | if(loaded_count === count) {
201 | this.nodes_loaded = true;
202 | this.enhance('nodes');
203 | }
204 | }
205 |
206 | return this;
207 | },
208 |
209 | enhance : function (type) {
210 | var count = this['cached_' + type].length;
211 |
212 | for (var i = count - 1; i >= 0; i--) {
213 | this.object($(this['cached_' + type][i]));
214 | }
215 |
216 | return $(window).trigger('resize');
217 | },
218 |
219 | parse_params : function (path, directive, mq) {
220 | return [this.trim(path), this.convert_directive(directive), this.trim(mq)];
221 | },
222 |
223 | convert_directive : function (directive) {
224 | var trimmed = this.trim(directive);
225 |
226 | if (trimmed.length > 0) {
227 | return trimmed;
228 | }
229 |
230 | return 'replace';
231 | },
232 |
233 | object : function(el) {
234 | var raw_arr = this.parse_data_attr(el),
235 | scenarios = [], count = raw_arr.length;
236 |
237 | if (count > 0) {
238 | for (var i = count - 1; i >= 0; i--) {
239 | var split = raw_arr[i].split(/\((.*?)(\))$/);
240 |
241 | if (split.length > 1) {
242 | var cached_split = split[0].split(','),
243 | params = this.parse_params(cached_split[0],
244 | cached_split[1], split[1]);
245 |
246 | scenarios.push(params);
247 | }
248 | }
249 | }
250 |
251 | return this.store(el, scenarios);
252 | },
253 |
254 | uuid : function (separator) {
255 | var delim = separator || "-";
256 |
257 | function S4() {
258 | return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
259 | }
260 |
261 | return (S4() + S4() + delim + S4() + delim + S4()
262 | + delim + S4() + delim + S4() + S4() + S4());
263 | },
264 |
265 | store : function (el, scenarios) {
266 | var uuid = this.uuid(),
267 | current_uuid = el.data('uuid');
268 |
269 | if (current_uuid) return this.cache[current_uuid];
270 |
271 | el.attr('data-uuid', uuid);
272 |
273 | return this.cache[uuid] = scenarios;
274 | },
275 |
276 | trim : function(str) {
277 | if (typeof str === 'string') {
278 | return $.trim(str);
279 | }
280 |
281 | return str;
282 | },
283 |
284 | parse_data_attr : function (el) {
285 | var raw = el.data(this.settings.load_attr).split(/\[(.*?)\]/),
286 | count = raw.length, output = [];
287 |
288 | for (var i = count - 1; i >= 0; i--) {
289 | if (raw[i].replace(/[\W\d]+/, '').length > 4) {
290 | output.push(raw[i]);
291 | }
292 | }
293 |
294 | return output;
295 | },
296 |
297 | reflow : function () {
298 | this.load('images', true);
299 | this.load('nodes', true);
300 | }
301 |
302 | };
303 |
304 | }(jQuery, this, this.document));
305 |
--------------------------------------------------------------------------------
/public/js/libs/foundation/foundation.magellan.js:
--------------------------------------------------------------------------------
1 | ;(function ($, window, document, undefined) {
2 | 'use strict';
3 |
4 | Foundation.libs.magellan = {
5 | name : 'magellan',
6 |
7 | version : '5.0.0',
8 |
9 | settings : {
10 | active_class: 'active',
11 | threshold: 0
12 | },
13 |
14 | init : function (scope, method, options) {
15 | this.fixed_magellan = $("[data-magellan-expedition]");
16 | this.set_threshold();
17 | this.last_destination = $('[data-magellan-destination]').last();
18 | this.events();
19 | },
20 |
21 | events : function () {
22 | var self = this;
23 |
24 | $(this.scope)
25 | .off('.magellan')
26 | .on('arrival.fndtn.magellan', '[data-magellan-arrival]', function (e) {
27 | var $destination = $(this),
28 | $expedition = $destination.closest('[data-magellan-expedition]'),
29 | active_class = $expedition.attr('data-magellan-active-class')
30 | || self.settings.active_class;
31 |
32 | $destination
33 | .closest('[data-magellan-expedition]')
34 | .find('[data-magellan-arrival]')
35 | .not($destination)
36 | .removeClass(active_class);
37 | $destination.addClass(active_class);
38 | });
39 |
40 | this.fixed_magellan
41 | .off('.magellan')
42 | .on('update-position.fndtn.magellan', function() {
43 | var $el = $(this);
44 | })
45 | .trigger('update-position');
46 |
47 | $(window)
48 | .off('.magellan')
49 | .on('resize.fndtn.magellan', function() {
50 | this.fixed_magellan.trigger('update-position');
51 | }.bind(this))
52 | .on('scroll.fndtn.magellan', function() {
53 | var windowScrollTop = $(window).scrollTop();
54 | self.fixed_magellan.each(function() {
55 | var $expedition = $(this);
56 | if (typeof $expedition.data('magellan-top-offset') === 'undefined') {
57 | $expedition.data('magellan-top-offset', $expedition.offset().top);
58 | }
59 | if (typeof $expedition.data('magellan-fixed-position') === 'undefined') {
60 | $expedition.data('magellan-fixed-position', false);
61 | }
62 | var fixed_position = (windowScrollTop + self.settings.threshold) > $expedition.data("magellan-top-offset");
63 | var attr = $expedition.attr('data-magellan-top-offset');
64 |
65 | if ($expedition.data("magellan-fixed-position") != fixed_position) {
66 | $expedition.data("magellan-fixed-position", fixed_position);
67 | if (fixed_position) {
68 | $expedition.addClass('fixed');
69 | $expedition.css({position:"fixed", top:0});
70 | } else {
71 | $expedition.removeClass('fixed');
72 | $expedition.css({position:"", top:""});
73 | }
74 | if (fixed_position && typeof attr != 'undefined' && attr != false) {
75 | $expedition.css({position:"fixed", top:attr + "px"});
76 | }
77 | }
78 | });
79 | });
80 |
81 |
82 | if (this.last_destination.length > 0) {
83 | $(window).on('scroll.fndtn.magellan', function (e) {
84 | var windowScrollTop = $(window).scrollTop(),
85 | scrolltopPlusHeight = windowScrollTop + $(window).height(),
86 | lastDestinationTop = Math.ceil(self.last_destination.offset().top);
87 |
88 | $('[data-magellan-destination]').each(function () {
89 | var $destination = $(this),
90 | destination_name = $destination.attr('data-magellan-destination'),
91 | topOffset = $destination.offset().top - $destination.outerHeight(true) - windowScrollTop;
92 | if (topOffset <= self.settings.threshold) {
93 | $("[data-magellan-arrival='" + destination_name + "']").trigger('arrival');
94 | }
95 | // In large screens we may hit the bottom of the page and dont reach the top of the last magellan-destination, so lets force it
96 | if (scrolltopPlusHeight >= $(self.scope).height() && lastDestinationTop > windowScrollTop && lastDestinationTop < scrolltopPlusHeight) {
97 | $('[data-magellan-arrival]').last().trigger('arrival');
98 | }
99 | });
100 | });
101 | }
102 | },
103 |
104 | set_threshold : function () {
105 | if (typeof this.settings.threshold !== 'number') {
106 | this.settings.threshold = (this.fixed_magellan.length > 0) ?
107 | this.fixed_magellan.outerHeight(true) : 0;
108 | }
109 | },
110 |
111 | off : function () {
112 | $(this.scope).off('.fndtn.magellan');
113 | $(window).off('.fndtn.magellan');
114 | },
115 |
116 | reflow : function () {}
117 | };
118 | }(jQuery, this, this.document));
119 |
--------------------------------------------------------------------------------
/public/js/libs/foundation/foundation.offcanvas.js:
--------------------------------------------------------------------------------
1 | ;(function ($, window, document, undefined) {
2 | 'use strict';
3 |
4 | Foundation.libs.offcanvas = {
5 | name : 'offcanvas',
6 |
7 | version : '5.0.0',
8 |
9 | settings : {},
10 |
11 | init : function (scope, method, options) {
12 | this.events();
13 | },
14 |
15 | events : function () {
16 | $(this.scope).off('.offcanvas')
17 | .on('click.fndtn.offcanvas', '.left-off-canvas-toggle', function (e) {
18 | e.preventDefault();
19 | $(this).closest('.off-canvas-wrap').toggleClass('move-right');
20 | })
21 | .on('click.fndtn.offcanvas', '.exit-off-canvas', function (e) {
22 | e.preventDefault();
23 | $(".off-canvas-wrap").removeClass("move-right");
24 | })
25 | .on('click.fndtn.offcanvas', '.right-off-canvas-toggle', function (e) {
26 | e.preventDefault();
27 | $(this).closest(".off-canvas-wrap").toggleClass("move-left");
28 | })
29 | .on('click.fndtn.offcanvas', '.exit-off-canvas', function (e) {
30 | e.preventDefault();
31 | $(".off-canvas-wrap").removeClass("move-left");
32 | });
33 | },
34 |
35 | reflow : function () {}
36 | };
37 | }(jQuery, this, this.document));
38 |
--------------------------------------------------------------------------------
/public/js/libs/foundation/foundation.reveal.js:
--------------------------------------------------------------------------------
1 | ;(function ($, window, document, undefined) {
2 | 'use strict';
3 |
4 | Foundation.libs.reveal = {
5 | name : 'reveal',
6 |
7 | version : '5.0.0',
8 |
9 | locked : false,
10 |
11 | settings : {
12 | animation: 'fadeAndPop',
13 | animation_speed: 250,
14 | close_on_background_click: true,
15 | close_on_esc: true,
16 | dismiss_modal_class: 'close-reveal-modal',
17 | bg_class: 'reveal-modal-bg',
18 | open: function(){},
19 | opened: function(){},
20 | close: function(){},
21 | closed: function(){},
22 | bg : $('.reveal-modal-bg'),
23 | css : {
24 | open : {
25 | 'opacity': 0,
26 | 'visibility': 'visible',
27 | 'display' : 'block'
28 | },
29 | close : {
30 | 'opacity': 1,
31 | 'visibility': 'hidden',
32 | 'display': 'none'
33 | }
34 | }
35 | },
36 |
37 | init : function (scope, method, options) {
38 | Foundation.inherit(this, 'delay');
39 |
40 | this.bindings(method, options);
41 | },
42 |
43 | events : function (scope) {
44 | var self = this;
45 |
46 | $('[data-reveal-id]', this.scope)
47 | .off('.reveal')
48 | .on('click.fndtn.reveal', function (e) {
49 | e.preventDefault();
50 |
51 | if (!self.locked) {
52 | var element = $(this),
53 | ajax = element.data('reveal-ajax');
54 |
55 | self.locked = true;
56 |
57 | if (typeof ajax === 'undefined') {
58 | self.open.call(self, element);
59 | } else {
60 | var url = ajax === true ? element.attr('href') : ajax;
61 |
62 | self.open.call(self, element, {url: url});
63 | }
64 | }
65 | });
66 |
67 | $(this.scope)
68 | .off('.reveal')
69 | .on('click.fndtn.reveal', this.close_targets(), function (e) {
70 |
71 | e.preventDefault();
72 |
73 | if (!self.locked) {
74 | var settings = $('[data-reveal].open').data('reveal-init'),
75 | bg_clicked = $(e.target)[0] === $('.' + settings.bg_class)[0];
76 |
77 | if (bg_clicked && !settings.close_on_background_click) {
78 | return;
79 | }
80 |
81 | self.locked = true;
82 | self.close.call(self, bg_clicked ? $('[data-reveal].open') : $(this).closest('[data-reveal]'));
83 | }
84 | });
85 |
86 | if($('[data-reveal]', this.scope).length > 0) {
87 | $(this.scope)
88 | // .off('.reveal')
89 | .on('open.fndtn.reveal', this.settings.open)
90 | .on('opened.fndtn.reveal', this.settings.opened)
91 | .on('opened.fndtn.reveal', this.open_video)
92 | .on('close.fndtn.reveal', this.settings.close)
93 | .on('closed.fndtn.reveal', this.settings.closed)
94 | .on('closed.fndtn.reveal', this.close_video);
95 | } else {
96 | $(this.scope)
97 | // .off('.reveal')
98 | .on('open.fndtn.reveal', '[data-reveal]', this.settings.open)
99 | .on('opened.fndtn.reveal', '[data-reveal]', this.settings.opened)
100 | .on('opened.fndtn.reveal', '[data-reveal]', this.open_video)
101 | .on('close.fndtn.reveal', '[data-reveal]', this.settings.close)
102 | .on('closed.fndtn.reveal', '[data-reveal]', this.settings.closed)
103 | .on('closed.fndtn.reveal', '[data-reveal]', this.close_video);
104 | }
105 |
106 | $('body').on('keyup.fndtn.reveal', function ( event ) {
107 | var open_modal = $('[data-reveal].open'),
108 | settings = open_modal.data('reveal-init');
109 | if ( event.which === 27 && settings.close_on_esc) { // 27 is the keycode for the Escape key
110 | open_modal.foundation('reveal', 'close');
111 | }
112 | });
113 |
114 | return true;
115 | },
116 |
117 | open : function (target, ajax_settings) {
118 | if (target) {
119 | if (typeof target.selector !== 'undefined') {
120 | var modal = $('#' + target.data('reveal-id'));
121 | } else {
122 | var modal = $(this.scope);
123 |
124 | ajax_settings = target;
125 | }
126 | } else {
127 | var modal = $(this.scope);
128 | }
129 |
130 | if (!modal.hasClass('open')) {
131 | var open_modal = $('[data-reveal].open');
132 |
133 | if (typeof modal.data('css-top') === 'undefined') {
134 | modal.data('css-top', parseInt(modal.css('top'), 10))
135 | .data('offset', this.cache_offset(modal));
136 | }
137 |
138 | modal.trigger('open');
139 |
140 | if (open_modal.length < 1) {
141 | this.toggle_bg();
142 | }
143 |
144 | if (typeof ajax_settings === 'undefined' || !ajax_settings.url) {
145 | this.hide(open_modal, this.settings.css.close);
146 | this.show(modal, this.settings.css.open);
147 | } else {
148 | var self = this,
149 | old_success = typeof ajax_settings.success !== 'undefined' ? ajax_settings.success : null;
150 |
151 | $.extend(ajax_settings, {
152 | success: function (data, textStatus, jqXHR) {
153 | if ( $.isFunction(old_success) ) {
154 | old_success(data, textStatus, jqXHR);
155 | }
156 |
157 | modal.html(data);
158 | $(modal).foundation('section', 'reflow');
159 |
160 | self.hide(open_modal, self.settings.css.close);
161 | self.show(modal, self.settings.css.open);
162 | }
163 | });
164 |
165 | $.ajax(ajax_settings);
166 | }
167 | }
168 | },
169 |
170 | close : function (modal) {
171 |
172 | var modal = modal && modal.length ? modal : $(this.scope),
173 | open_modals = $('[data-reveal].open');
174 |
175 | if (open_modals.length > 0) {
176 | this.locked = true;
177 | modal.trigger('close');
178 | this.toggle_bg();
179 | this.hide(open_modals, this.settings.css.close);
180 | }
181 | },
182 |
183 | close_targets : function () {
184 | var base = '.' + this.settings.dismiss_modal_class;
185 |
186 | if (this.settings.close_on_background_click) {
187 | return base + ', .' + this.settings.bg_class;
188 | }
189 |
190 | return base;
191 | },
192 |
193 | toggle_bg : function () {
194 | if ($('.' + this.settings.bg_class).length === 0) {
195 | this.settings.bg = $('
', {'class': this.settings.bg_class})
196 | .appendTo('body');
197 | }
198 |
199 | if (this.settings.bg.filter(':visible').length > 0) {
200 | this.hide(this.settings.bg);
201 | } else {
202 | this.show(this.settings.bg);
203 | }
204 | },
205 |
206 | show : function (el, css) {
207 | // is modal
208 | if (css) {
209 | if (el.parent('body').length === 0) {
210 | var placeholder = el.wrap('
').parent();
211 | el.on('closed.fndtn.reveal.wrapped', function() {
212 | el.detach().appendTo(placeholder);
213 | el.unwrap().unbind('closed.fndtn.reveal.wrapped');
214 | });
215 |
216 | el.detach().appendTo('body');
217 | }
218 |
219 | if (/pop/i.test(this.settings.animation)) {
220 | css.top = $(window).scrollTop() - el.data('offset') + 'px';
221 | var end_css = {
222 | top: $(window).scrollTop() + el.data('css-top') + 'px',
223 | opacity: 1
224 | };
225 |
226 | return this.delay(function () {
227 | return el
228 | .css(css)
229 | .animate(end_css, this.settings.animation_speed, 'linear', function () {
230 | this.locked = false;
231 | el.trigger('opened');
232 | }.bind(this))
233 | .addClass('open');
234 | }.bind(this), this.settings.animation_speed / 2);
235 | }
236 |
237 | if (/fade/i.test(this.settings.animation)) {
238 | var end_css = {opacity: 1};
239 |
240 | return this.delay(function () {
241 | return el
242 | .css(css)
243 | .animate(end_css, this.settings.animation_speed, 'linear', function () {
244 | this.locked = false;
245 | el.trigger('opened');
246 | }.bind(this))
247 | .addClass('open');
248 | }.bind(this), this.settings.animation_speed / 2);
249 | }
250 |
251 | return el.css(css).show().css({opacity: 1}).addClass('open').trigger('opened');
252 | }
253 |
254 | // should we animate the background?
255 | if (/fade/i.test(this.settings.animation)) {
256 | return el.fadeIn(this.settings.animation_speed / 2);
257 | }
258 |
259 | return el.show();
260 | },
261 |
262 | hide : function (el, css) {
263 | // is modal
264 | if (css) {
265 | if (/pop/i.test(this.settings.animation)) {
266 | var end_css = {
267 | top: - $(window).scrollTop() - el.data('offset') + 'px',
268 | opacity: 0
269 | };
270 |
271 | return this.delay(function () {
272 | return el
273 | .animate(end_css, this.settings.animation_speed, 'linear', function () {
274 | this.locked = false;
275 | el.css(css).trigger('closed');
276 | }.bind(this))
277 | .removeClass('open');
278 | }.bind(this), this.settings.animation_speed / 2);
279 | }
280 |
281 | if (/fade/i.test(this.settings.animation)) {
282 | var end_css = {opacity: 0};
283 |
284 | return this.delay(function () {
285 | return el
286 | .animate(end_css, this.settings.animation_speed, 'linear', function () {
287 | this.locked = false;
288 | el.css(css).trigger('closed');
289 | }.bind(this))
290 | .removeClass('open');
291 | }.bind(this), this.settings.animation_speed / 2);
292 | }
293 |
294 | return el.hide().css(css).removeClass('open').trigger('closed');
295 | }
296 |
297 | // should we animate the background?
298 | if (/fade/i.test(this.settings.animation)) {
299 | return el.fadeOut(this.settings.animation_speed / 2);
300 | }
301 |
302 | return el.hide();
303 | },
304 |
305 | close_video : function (e) {
306 | var video = $(this).find('.flex-video'),
307 | iframe = video.find('iframe');
308 |
309 | if (iframe.length > 0) {
310 | iframe.attr('data-src', iframe[0].src);
311 | iframe.attr('src', 'about:blank');
312 | video.hide();
313 | }
314 | },
315 |
316 | open_video : function (e) {
317 | var video = $(this).find('.flex-video'),
318 | iframe = video.find('iframe');
319 |
320 | if (iframe.length > 0) {
321 | var data_src = iframe.attr('data-src');
322 | if (typeof data_src === 'string') {
323 | iframe[0].src = iframe.attr('data-src');
324 | } else {
325 | var src = iframe[0].src;
326 | iframe[0].src = undefined;
327 | iframe[0].src = src;
328 | }
329 | video.show();
330 | }
331 | },
332 |
333 | cache_offset : function (modal) {
334 | var offset = modal.show().height() + parseInt(modal.css('top'), 10);
335 |
336 | modal.hide();
337 |
338 | return offset;
339 | },
340 |
341 | off : function () {
342 | $(this.scope).off('.fndtn.reveal');
343 | },
344 |
345 | reflow : function () {}
346 | };
347 | }(jQuery, this, this.document));
348 |
--------------------------------------------------------------------------------
/public/js/libs/foundation/foundation.tab.js:
--------------------------------------------------------------------------------
1 | /*jslint unparam: true, browser: true, indent: 2 */
2 | ;(function ($, window, document, undefined) {
3 | 'use strict';
4 |
5 | Foundation.libs.tab = {
6 | name : 'tab',
7 |
8 | version : '5.0.1',
9 |
10 | settings : {
11 | active_class: 'active'
12 | },
13 |
14 | init : function (scope, method, options) {
15 | this.bindings(method, options);
16 | },
17 |
18 | events : function () {
19 | $(this.scope).off('.tab').on('click.fndtn.tab', '[data-tab] > dd > a', function (e) {
20 | e.preventDefault();
21 |
22 | var tab = $(this).parent(),
23 | target = $('#' + this.href.split('#')[1]),
24 | siblings = tab.siblings(),
25 | settings = tab.closest('[data-tab]').data('tab-init');
26 |
27 | tab.addClass(settings.active_class);
28 | siblings.removeClass(settings.active_class);
29 | target.siblings().removeClass(settings.active_class).end().addClass(settings.active_class);
30 | });
31 | },
32 |
33 | off : function () {},
34 |
35 | reflow : function () {}
36 | };
37 | }(jQuery, this, this.document));
38 |
--------------------------------------------------------------------------------
/public/js/libs/foundation/foundation.tooltip.js:
--------------------------------------------------------------------------------
1 | ;(function ($, window, document, undefined) {
2 | 'use strict';
3 |
4 | Foundation.libs.tooltip = {
5 | name : 'tooltip',
6 |
7 | version : '5.0.0',
8 |
9 | settings : {
10 | additional_inheritable_classes : [],
11 | tooltip_class : '.tooltip',
12 | append_to: 'body',
13 | touch_close_text: 'Tap To Close',
14 | disable_for_touch: false,
15 | tip_template : function (selector, content) {
16 | return '
' + content + '';
19 | }
20 | },
21 |
22 | cache : {},
23 |
24 | init : function (scope, method, options) {
25 | this.bindings(method, options);
26 | },
27 |
28 | events : function () {
29 | var self = this;
30 |
31 | if (Modernizr.touch) {
32 | $(this.scope)
33 | .off('.tooltip')
34 | .on('click.fndtn.tooltip touchstart.fndtn.tooltip touchend.fndtn.tooltip',
35 | '[data-tooltip]', function (e) {
36 | var settings = $.extend({}, self.settings, self.data_options($(this)));
37 | if (!settings.disable_for_touch) {
38 | e.preventDefault();
39 | $(settings.tooltip_class).hide();
40 | self.showOrCreateTip($(this));
41 | }
42 | })
43 | .on('click.fndtn.tooltip touchstart.fndtn.tooltip touchend.fndtn.tooltip',
44 | this.settings.tooltip_class, function (e) {
45 | e.preventDefault();
46 | $(this).fadeOut(150);
47 | });
48 | } else {
49 | $(this.scope)
50 | .off('.tooltip')
51 | .on('mouseenter.fndtn.tooltip mouseleave.fndtn.tooltip',
52 | '[data-tooltip]', function (e) {
53 | var $this = $(this);
54 |
55 | if (/enter|over/i.test(e.type)) {
56 | self.showOrCreateTip($this);
57 | } else if (e.type === 'mouseout' || e.type === 'mouseleave') {
58 | self.hide($this);
59 | }
60 | });
61 | }
62 | },
63 |
64 | showOrCreateTip : function ($target) {
65 | var $tip = this.getTip($target);
66 |
67 | if ($tip && $tip.length > 0) {
68 | return this.show($target);
69 | }
70 |
71 | return this.create($target);
72 | },
73 |
74 | getTip : function ($target) {
75 | var selector = this.selector($target),
76 | tip = null;
77 |
78 | if (selector) {
79 | tip = $('span[data-selector="' + selector + '"]' + this.settings.tooltip_class);
80 | }
81 |
82 | return (typeof tip === 'object') ? tip : false;
83 | },
84 |
85 | selector : function ($target) {
86 | var id = $target.attr('id'),
87 | dataSelector = $target.attr('data-tooltip') || $target.attr('data-selector');
88 |
89 | if ((id && id.length < 1 || !id) && typeof dataSelector != 'string') {
90 | dataSelector = 'tooltip' + Math.random().toString(36).substring(7);
91 | $target.attr('data-selector', dataSelector);
92 | }
93 |
94 | return (id && id.length > 0) ? id : dataSelector;
95 | },
96 |
97 | create : function ($target) {
98 | var $tip = $(this.settings.tip_template(this.selector($target), $('
').html($target.attr('title')).html())),
99 | classes = this.inheritable_classes($target);
100 |
101 | $tip.addClass(classes).appendTo(this.settings.append_to);
102 | if (Modernizr.touch) {
103 | $tip.append('
'+this.settings.touch_close_text+'');
104 | }
105 | $target.removeAttr('title').attr('title','');
106 | this.show($target);
107 | },
108 |
109 | reposition : function (target, tip, classes) {
110 | var width, nub, nubHeight, nubWidth, column, objPos;
111 |
112 | tip.css('visibility', 'hidden').show();
113 |
114 | width = target.data('width');
115 | nub = tip.children('.nub');
116 | nubHeight = nub.outerHeight();
117 | nubWidth = nub.outerHeight();
118 |
119 | objPos = function (obj, top, right, bottom, left, width) {
120 | return obj.css({
121 | 'top' : (top) ? top : 'auto',
122 | 'bottom' : (bottom) ? bottom : 'auto',
123 | 'left' : (left) ? left : 'auto',
124 | 'right' : (right) ? right : 'auto',
125 | 'width' : (width) ? width : 'auto'
126 | }).end();
127 | };
128 |
129 | objPos(tip, (target.offset().top + target.outerHeight() + 10), 'auto', 'auto', target.offset().left, width);
130 |
131 | if (this.small()) {
132 | objPos(tip, (target.offset().top + target.outerHeight() + 10), 'auto', 'auto', 12.5, $(this.scope).width());
133 | tip.addClass('tip-override');
134 | objPos(nub, -nubHeight, 'auto', 'auto', target.offset().left);
135 | } else {
136 | var left = target.offset().left;
137 | if (Foundation.rtl) {
138 | left = target.offset().left + target.offset().width - tip.outerWidth();
139 | }
140 | objPos(tip, (target.offset().top + target.outerHeight() + 10), 'auto', 'auto', left, width);
141 | tip.removeClass('tip-override');
142 | if (classes && classes.indexOf('tip-top') > -1) {
143 | objPos(tip, (target.offset().top - tip.outerHeight()), 'auto', 'auto', left, width)
144 | .removeClass('tip-override');
145 | } else if (classes && classes.indexOf('tip-left') > -1) {
146 | objPos(tip, (target.offset().top + (target.outerHeight() / 2) - nubHeight*2.5), 'auto', 'auto', (target.offset().left - tip.outerWidth() - nubHeight), width)
147 | .removeClass('tip-override');
148 | } else if (classes && classes.indexOf('tip-right') > -1) {
149 | objPos(tip, (target.offset().top + (target.outerHeight() / 2) - nubHeight*2.5), 'auto', 'auto', (target.offset().left + target.outerWidth() + nubHeight), width)
150 | .removeClass('tip-override');
151 | }
152 | }
153 |
154 | tip.css('visibility', 'visible').hide();
155 | },
156 |
157 | small : function () {
158 | return matchMedia(Foundation.media_queries.small).matches;
159 | },
160 |
161 | inheritable_classes : function (target) {
162 | var inheritables = ['tip-top', 'tip-left', 'tip-bottom', 'tip-right', 'noradius'].concat(this.settings.additional_inheritable_classes),
163 | classes = target.attr('class'),
164 | filtered = classes ? $.map(classes.split(' '), function (el, i) {
165 | if ($.inArray(el, inheritables) !== -1) {
166 | return el;
167 | }
168 | }).join(' ') : '';
169 |
170 | return $.trim(filtered);
171 | },
172 |
173 | show : function ($target) {
174 | var $tip = this.getTip($target);
175 |
176 | this.reposition($target, $tip, $target.attr('class'));
177 | $tip.fadeIn(150);
178 | },
179 |
180 | hide : function ($target) {
181 | var $tip = this.getTip($target);
182 |
183 | $tip.fadeOut(150);
184 | },
185 |
186 | // deprecate reload
187 | reload : function () {
188 | var $self = $(this);
189 |
190 | return ($self.data('fndtn-tooltips')) ? $self.foundationTooltips('destroy').foundationTooltips('init') : $self.foundationTooltips('init');
191 | },
192 |
193 | off : function () {
194 | $(this.scope).off('.fndtn.tooltip');
195 | $(this.settings.tooltip_class).each(function (i) {
196 | $('[data-tooltip]').get(i).attr('title', $(this).text());
197 | }).remove();
198 | },
199 |
200 | reflow : function () {}
201 | };
202 | }(jQuery, this, this.document));
203 |
--------------------------------------------------------------------------------
/public/js/libs/modernizr.js:
--------------------------------------------------------------------------------
1 | /* Modernizr 2.6.2 (Custom Build) | MIT & BSD
2 | * Build: http://modernizr.com/download/#-inlinesvg-svg-svgclippaths-touch-shiv-mq-cssclasses-teststyles-prefixes-ie8compat-load
3 | */
4 | ;window.Modernizr=function(a,b,c){function y(a){j.cssText=a}function z(a,b){return y(m.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n={svg:"http://www.w3.org/2000/svg"},o={},p={},q={},r=[],s=r.slice,t,u=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},v=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return u("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=s.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(s.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(s.call(arguments)))};return e}),o.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:u(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},o.svg=function(){return!!b.createElementNS&&!!b.createElementNS(n.svg,"svg").createSVGRect},o.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="
",(a.firstChild&&a.firstChild.namespaceURI)==n.svg},o.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(l.call(b.createElementNS(n.svg,"clipPath")))};for(var D in o)x(o,D)&&(t=D.toLowerCase(),e[t]=o[D](),r.push((e[t]?"":"no-")+t));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e
",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=m,e.mq=v,e.testStyles=u,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+r.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f | 3 clause BSD license */(function(e){function r(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}function i(e,t){for(var n=[];t>0;n[--t]=e);return n.join("")}var t=function(){return t.cache.hasOwnProperty(arguments[0])||(t.cache[arguments[0]]=t.parse(arguments[0])),t.format.call(null,t.cache[arguments[0]],arguments)};t.format=function(e,n){var s=1,o=e.length,u="",a,f=[],l,c,h,p,d,v;for(l=0;l>>=0;break;case"x":a=a.toString(16);break;case"X":a=a.toString(16).toUpperCase()}a=/[def]/.test(h[8])&&h[3]&&a>=0?"+"+a:a,d=h[4]?h[4]=="0"?"0":h[4].charAt(1):" ",v=h[6]-String(a).length,p=h[6]?i(d,v):"",f.push(h[5]?a+p:p+a)}}return f.join("")},t.cache={},t.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push("%");else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw"[sprintf] huh?";if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw"[sprintf] huh?";s.push(u[1]);while((o=o.substring(u[0].length))!=="")if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw"[sprintf] huh?";s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw"[sprintf] mixing positional and named placeholders is not (yet) supported";r.push(n)}t=t.substring(n[0].length)}return r};var n=function(e,n,r){return r=n.slice(0),r.splice(0,0,e),t.apply(null,r)};e.sprintf=t,e.vsprintf=n})(typeof exports!="undefined"?exports:window);
--------------------------------------------------------------------------------
/public/js/vendor/custom.modernizr.js:
--------------------------------------------------------------------------------
1 | /* Modernizr 2.6.2 (Custom Build) | MIT & BSD
2 | * Build: http://modernizr.com/download/#-inlinesvg-svg-svgclippaths-touch-shiv-mq-cssclasses-teststyles-prefixes-ie8compat-load
3 | */
4 | ;window.Modernizr=function(a,b,c){function y(a){j.cssText=a}function z(a,b){return y(m.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n={svg:"http://www.w3.org/2000/svg"},o={},p={},q={},r=[],s=r.slice,t,u=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},v=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return u("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=s.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(s.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(s.call(arguments)))};return e}),o.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:u(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},o.svg=function(){return!!b.createElementNS&&!!b.createElementNS(n.svg,"svg").createSVGRect},o.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==n.svg},o.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(l.call(b.createElementNS(n.svg,"clipPath")))};for(var D in o)x(o,D)&&(t=D.toLowerCase(),e[t]=o[D](),r.push((e[t]?"":"no-")+t));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=m,e.mq=v,e.testStyles=u,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+r.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f
12 | DocumentRoot $WEBROOT
13 |
14 | Options FollowSymLinks
15 | AllowOverride All
16 |
17 |
18 | Options Indexes FollowSymLinks MultiViews
19 | AllowOverride All
20 | Order allow,deny
21 | allow from all
22 |
23 |
24 | # Configure PHP as CGI
25 | ScriptAlias /local-bin $CGIROOT
26 | DirectoryIndex index.php index.html
27 | AddType application/x-httpd-php5 .php
28 | Action application/x-httpd-php5 '/local-bin/php-cgi'
29 |
30 | " | sudo tee /etc/apache2/sites-available/default > /dev/null
31 | cat /etc/apache2/sites-available/default
32 |
33 | sudo a2enmod rewrite
34 | sudo a2enmod actions
35 | sudo service apache2 restart
36 |
37 | # Configure custom domain
38 | echo "127.0.0.1 webception" | sudo tee --append /etc/hosts
39 |
40 | echo "TRAVIS_PHP_VERSION: $TRAVIS_PHP_VERSION"
--------------------------------------------------------------------------------