diff --git a/zephyr/tests/frontend/casperjs/.gitignore b/zephyr/tests/frontend/casperjs/.gitignore new file mode 100644 index 0000000000..f5a9865dfc --- /dev/null +++ b/zephyr/tests/frontend/casperjs/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +*.xml +*.png +/tmp diff --git a/zephyr/tests/frontend/casperjs/.gitmodules b/zephyr/tests/frontend/casperjs/.gitmodules new file mode 100644 index 0000000000..65e230d319 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs"] + path = docs + url = git://github.com/n1k0/casperjs.git diff --git a/zephyr/tests/frontend/casperjs/.jshintconfig b/zephyr/tests/frontend/casperjs/.jshintconfig new file mode 100644 index 0000000000..87f894276c --- /dev/null +++ b/zephyr/tests/frontend/casperjs/.jshintconfig @@ -0,0 +1,24 @@ +{ + "asi": true, + "browser": true, + "debug": true, + "devel": true, + "eqeqeq": true, + "evil": true, + "maxparams": 5, + "maxdepth": 3, + "maxstatements": 15, + "maxcomplexity": 7, + "regexdash": true, + "strict": true, + "sub": true, + "trailing": true, + "undef": true, + + "predef" : [ + "exports", + "phantom", + "require", + "window" + ] +} diff --git a/zephyr/tests/frontend/casperjs/.jshintignore b/zephyr/tests/frontend/casperjs/.jshintignore new file mode 100644 index 0000000000..7efbff6a3a --- /dev/null +++ b/zephyr/tests/frontend/casperjs/.jshintignore @@ -0,0 +1,7 @@ +docs +modules/vendors +modules/events.js +modules/querystring.js +samples +tests/site +tests/testdir diff --git a/zephyr/tests/frontend/casperjs/.travis.yml b/zephyr/tests/frontend/casperjs/.travis.yml new file mode 100644 index 0000000000..375ec62857 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/.travis.yml @@ -0,0 +1,14 @@ +branches: + only: + - master +before_script: + - "phantomjs --version" + - "export PHANTOMJS_EXECUTABLE='phantomjs --local-to-remote-url-access=yes --ignore-ssl-errors=yes'" + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" +script: + - "DISPLAY=:99.0 ./bin/casperjs selftest" +notifications: + irc: + channels: + - "irc.freenode.org#casperjs" diff --git a/zephyr/tests/frontend/casperjs/CHANGELOG.md b/zephyr/tests/frontend/casperjs/CHANGELOG.md new file mode 100644 index 0000000000..107a4f8c30 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/CHANGELOG.md @@ -0,0 +1,298 @@ +CasperJS Changelog +================== + +2012-10-31, v1.0.0-RC4 +---------------------- + +Next version should be 1.0.0 stable. + +- fixed [#261](https://github.com/n1k0/casperjs/issues/261) - Impossible to require CoffeeScript modules +- fixed [#262](https://github.com/n1k0/casperjs/issues/262) - Injecting clientScripts is not working +- fixed [#259](https://github.com/n1k0/casperjs/issues/259) - enhanced `Tester.assertField()` method, which can now tests for other field types than `input`s. +- fixed `Casper.getCurrentUrl()` could misbehave with encoded urls +- added [`Casper.echo()`](http://casperjs.org/api.html#clientutils.echo) to print a message to the casper console from the remote DOM environment +- added [`Casper.waitForText()`](http://casperjs.org/api.html#casper.waitForText) to wait for a given text to be present in page HTML contents +- added [`ClientUtils.getFieldValue()`](http://casperjs.org/api.html#clientutils.getFieldValue) +- Local CoffeeScript version has been upgraded to 1.4.0 + +2012-10-23, v1.0.0-RC3 +---------------------- + +### Important Changes & Caveats + +- the `injector` module is now deprecated, but kept for backward compatibility purpose. +- **BC BREAK**: fixes [#220](https://github.com/n1k0/casperjs/issues/220), [#237](https://github.com/n1k0/casperjs/issues/237) - added a `waitTimeout` options, removed `defaultWaitTimeout` option. +- **BC BREAK** (for the better): fixes [#249](https://github.com/n1k0/casperjs/issues/249) - default timeout functions don't `die()` anymore in tests +- **BC BREAK** (for the better): merged [#188](https://github.com/n1k0/casperjs/issues/188) - Easy access to current response object; + You can now access the current response object as the first parameter of step callbacks: + +```javascript +require('casper').create().start('http://www.google.fr/', function(response) { + require('utils').dump(response); +}).run(); +``` + +That gives: + +``` +$ casperjs dump-headers.js +{ + "contentType": "text/html; charset=UTF-8", + "headers": [ + { + "name": "Date", + "value": "Thu, 18 Oct 2012 08:17:29 GMT" + }, + { + "name": "Expires", + "value": "-1" + }, + // ... lots of other headers + ], + "id": 1, + "redirectURL": null, + "stage": "end", + "status": 200, + "statusText": "OK", + "time": "2012-10-18T08:17:37.068Z", + "url": "http://www.google.fr/" +} +``` + +To fetch a particular header by its name: + +```javascript +require('casper').create().start('http://www.google.fr/', function(response) { + this.echo(response.headers.get('Date')); +}).run(); +``` + +That gives: + +```javascript +$ casperjs dump-single-header.js +Thu, 18 Oct 2012 08:26:34 GMT +``` + +The documentation has been [updated accordingly](http://casperjs.org/api.html#casper.then.callbacks). + +### Bugfixes & enhancements + +- merged [#234](https://github.com/n1k0/casperjs/issues/234) - New Windows Loader written in Batch. Python is no more a requirement for using CasperJS on Windows. New installation instructions are [available](http://casperjs.org/installation.html#windows). +- a new `onWaitTimeout` option has been added, to allow defining a default behavior when a `waitFor*` function times out. +- [Casper.resourceExists()](http://casperjs.org/api.html#casper.resourceExists) and related functions now checks for non HTTP-404 received responses. +- fixed [#167](https://github.com/n1k0/casperjs/issues/167) - fixed opening truncated/uncomplete root urls may give erroneous HTTP statuses +- closes [#205](https://github.com/n1k0/casperjs/issues/205) - [`debugHTML()`](http://casperjs.org/api.html#casper.debugHTML) can have a selector passed; added [`getHTML()`](http://casperjs.org/api.html#casper.getHTML) +- closes [#230](https://github.com/n1k0/casperjs/issues/230) - added [`ClientUtils.getElementsBound()`](http://casperjs.org/api.html#clientutils.getElementsBounds) and [`Casper.getElementsBound()`](http://casperjs.org/api.html#casper.getElementsBounds) +- fixed [#235](https://github.com/n1k0/casperjs/issues/235) - updated `Casper.evaluate()` to use phantomjs >= 1.6 native one. As a consequence, **the `injector` module is marked as deprecated**. +- fixed [#250](https://github.com/n1k0/casperjs/issues/250) - prevent self tests to be run using the standard `casper test` command +- fixed [#254](https://github.com/n1k0/casperjs/issues/254) - fix up one use of qsa, hit when filling forms with missing elements +- [fixed](https://github.com/n1k0/casperjs/commit/ef6c1828c7b64e1cf99b98e27600d0b63308cad3) edge case when current document url couldn't be properly decoded + +2012-10-01, v1.0.0-RC2 +---------------------- + +### Important Changes & Caveats + +- **PhantomJS 1.6 is now the minimal requirement**, PhantomJS 1.7 is supported. +- CasperJS continues to ship with its own implementation of CommonJS' module pattern, due to the way it has to work to offer its own executable. While the implementations are nearly the same, **100% compatibility is not guaranteed**. + +### Bugfixes & enhancements + +- fixed [#119](https://github.com/n1k0/casperjs/issues/119) - `Casper.currentHTTPStatus` now defaults to `null` when resource are loaded using the `file://` protocol +- fixed [#130](https://github.com/n1k0/casperjs/issues/130) - added a `--no-colors` option to the `casper test` command to skip output coloration +- fixed [#153](https://github.com/n1k0/casperjs/issues/153) - erroneous mouse event results when `event.preventDefault()` was used. +- fixed [#164](https://github.com/n1k0/casperjs/issues/164) - ability to force CLI parameters as strings (see [related documentation](http://casperjs.org/cli.html#raw)). +- fixed [#178](https://github.com/n1k0/casperjs/issues/178) - added `Casper.getPageContent()` to access raw page body contents on non-html received content-types. +- fixed [#180](https://github.com/n1k0/casperjs/issues/180) - CasperJS tests are now run against a local HTTP test server. A new `casper selftest` command has been added as well. +- fixed [#189](https://github.com/n1k0/casperjs/issue/189) - fixed invalid XML due to message colorization +- fixed [#197](https://github.com/n1k0/casperjs/pull/197) & [#240](https://github.com/n1k0/casperjs/pull/240/) - Added new tester methods: + * [`assertField`](http://casperjs.org/api.html#tester.assertField) + * [`assertSelectorHasText`](http://casperjs.org/api.html#tester.assertSelectorHasText) + * [`assertSelectorDoesntHaveText`](http://casperjs.org/api.html#tester.assertSelectorDoesntHaveText) + * [`assertVisible`](http://casperjs.org/api.html#tester.assertVisible) + * [`assertNotVisible`](http://casperjs.org/api.html#tester.assertNotVisible) +- fixed [#202](https://github.com/n1k0/casperjs/pull/202) - Fix test status timeouts when running multiple suites +- fixed [#204](https://github.com/n1k0/casperjs/pull/204) - Fix for when the url is changed via javascript +- fixed [#210](https://github.com/n1k0/casperjs/pull/210) - Changed `escape` to `encodeURIComponent` for downloading binaries via POST +- fixed [#216](https://github.com/n1k0/casperjs/pull/216) - Change clientutils to be able to set a global scope +- fixed [#219](https://github.com/n1k0/casperjs/issues/219) - ease chaining of `run()` calls ([more explanations](https://groups.google.com/forum/#!topic/casperjs/jdQ-CrgnUd8)) +- fixed [#222](https://github.com/n1k0/casperjs/pull/222) & [#211](https://github.com/n1k0/casperjs/issues/211) - Change mouse event to include an X + Y value for click position +- fixed [#231](https://github.com/n1k0/casperjs/pull/231) - added `--pre` and `--post` options to the `casperjs test` command to load test files before and after the execution of testsuite +- fixed [#232](https://github.com/n1k0/casperjs/issues/232) - symlink resolution in the ruby version of the `casperjs` executable +- fixed [#236](https://github.com/n1k0/casperjs/issues/236) - fixed `Casper.exit` returned `this` after calling `phantom.exit()` which may caused PhantomJS to hang +- fixed [#252](https://github.com/n1k0/casperjs/issues/252) - better form.fill() error handling +- added [`ClientUtils.getDocumentHeight()`](http://casperjs.org/api.html#clientutils.getDocumentHeight) +- added [`toString()`](http://casperjs.org/api.html#casper.toString) and [`status()`](http://casperjs.org/api.html#casper.status) methods to `Casper` prototype. + +2012-06-26, v1.0.0-RC1 +---------------------- + +### PhantomJS 1.5 & 1.6 + +- fixed [#119](https://github.com/n1k0/casperjs/issues/119) - HTTP status wasn't properly caught +- fixed [#132](https://github.com/n1k0/casperjs/issues/132) - added ability to include js/coffee files using a dedicated option when using the [`casper test` command](http://casperjs.org/testing.html) +- fixed [#140](https://github.com/n1k0/casperjs/issues/140) - `casper test` now resolves local paths urls +- fixed [#148](https://github.com/n1k0/casperjs/issues/148) - [`utils.isWebPage()`](http://casperjs.org/api.html#utils.isWebPage) was broken +- fixed [#149](https://github.com/n1k0/casperjs/issues/149) - [`ClientUtils.fill()`](http://casperjs.org/api.html#casper.fill) was searching elements globally +- fixed [#154](https://github.com/n1k0/casperjs/issues/154) - firing the `change` event after a field value has been set +- fixed [#144](https://github.com/n1k0/casperjs/issues/144) - added a [`safeLogs` option](http://casperjs.org/api.html#casper.options) to blur password values in debug logs. **This option is set to `true` by default.** +- added [`Casper.userAgent()`](http://casperjs.org/api.html#casper.userAgent) to ease a more dynamic setting of user-agent string +- added [`Tester.assertTitleMatch()`](http://casperjs.org/api.html#tester.assertTitleMatch) method +- added [`utils.getPropertyPath()`](http://casperjs.org/api.html#utils.getPropertyPath) +- added [`Casper.captureBase64()`](http://casperjs.org/api.html#casper.captureBase64) for rendering screen captures as base64 strings - closes [#150](https://github.com/n1k0/casperjs/issues/150) +- added [`Casper.reload()`](http://casperjs.org/api.html#casper.reload) +- fixed failed test messages didn't expose the subject correctly +- switched to more standard `.textContent` property to get a node text; this allows a better compatibility of the clientutils bookmarklet with non-webkit browsers +- casper modules now all use [javascript strict mode](http://www.nczonline.net/blog/2012/03/13/its-time-to-start-using-javascript-strict-mode/) + +### PhantomJS >= 1.6 supported features + +- added support of custom headers sending in outgoing request - refs [#137](https://github.com/n1k0/casperjs/issues/137)) +- added support for `prompt()` and `confirm()` - closes [#125](https://github.com/n1k0/casperjs/issues/125) +- fixed [#157](https://github.com/n1k0/casperjs/issues/157) - added support for PhantomJS 1.6 `WebPage#zoomFactor` +- added `url.changed` & `navigation.requested` events - refs [#151](https://github.com/n1k0/casperjs/issues/151) + +2012-06-04, v0.6.10 +------------------- + +- fixed [#73](https://github.com/n1k0/casperjs/issues/73) - `Casper.download()` not working correctly with binaries +- fixed [#129](https://github.com/n1k0/casperjs/issues/129) - Can't put `//` comments in evaluate() function +- closed [#130](https://github.com/n1k0/casperjs/issues/130) - Added a `Dummy` [colorizer](http://casperjs.org/api.html#colorizer) class, in order to disable colors in console output +- fixed [#133](https://github.com/n1k0/casperjs/issues/133) - updated and fixed documentation about [extensibility](http://casperjs.org/extending.html) +- added `Casper.clickLabel()` for clicking on an element found by its `innerText` content + +As a side note, the official website monolithic page has been split across several ones: http://casperjs.org/ + +2012-05-29, v0.6.9 +------------------ + +- **BC BREAK:** PhantomJS 1.5 is now the minimal PhantomJS version supported. +- fixed [#114](https://github.com/n1k0/casperjs/issues/114) - ensured client-side utils are injected before any `evaluate()` call +- merged [#89](https://github.com/n1k0/casperjs/pull/89) - Support for more mouse events (@nrabinowitz) +- [added a new `error` event, better error reporting](https://github.com/n1k0/casperjs/commit/2e6988ae821b3251e063d11ba28af59b0683852a) +- fixed [#117](https://github.com/n1k0/casperjs/issues/117) - `fill()` coulnd't `submit()` a form with a submit input named *submit* +- merged [#122](https://github.com/n1k0/casperjs/pull/122) - allow downloads to be triggered by more than just `GET` requests +- closed [#57](https://github.com/n1k0/casperjs/issues/57) - added context to emitted test events + complete assertion framework refactor +- fixed loaded resources array is now reset adequately [reference discussion](https://groups.google.com/forum/?hl=fr?fromgroups#!topic/casperjs/TCkNzrj1IoA) +- fixed incomplete error message logged when passed an erroneous selector (xpath and css) + +2012-05-20, v0.6.8 +------------------ + +- added support for [XPath selectors](http://casperjs.org/#selectors) +- added `Tester.assertNotEquals()` ([@juliangruber](https://github.com/juliangruber)) +- fixed [#109](https://github.com/n1k0/casperjs/issues/109) - CLI args containing `=` (equals sign) were not being parsed properly + +2012-05-12, v0.6.7 +------------------ + +- fixes [#107](https://github.com/n1k0/casperjs/issues/107): client utils were possibly not yet being injected and available when calling `Capser.base64encode()` from some events +- merged [PR #96](https://github.com/n1k0/casperjs/pull/96): make python launcher use `os.execvp()` instead of `subprocess.Popen()` ([@jart](https://github.com/jart)): + > This patch fixes a bug where casperjs' python launcher process won't pass along kill + > signals to the phantomjs subprocess. This patch works by using an exec system call + > which causes the phantomjs subprocess to completely replace the casperjs parent + > process (while maintaining the same pid). This patch also has the added benefit of + > saving 10 megs or so of memory because the python process is discarded. +- fixes [#109](https://github.com/n1k0/casperjs/issues/109) - CLI args containing `=` (equals sign) were not parsed properly +- fixes [#100](https://github.com/n1k0/casperjs/issues/100) & [#110](https://github.com/n1k0/casperjs/issues/110) - *googlepagination* sample was broken +- merged #103 - added `Tester.assertNotEquals` method (@juliangruber) + +2012-04-27, v0.6.6 +------------------ + +- **BC BREAK:**: moved the `page.initialized` event to where it should have always been, and is now using native phantomjs `onInitialized` event +- fixed [#95](https://github.com/n1k0/casperjs/issues/95) - `Tester.assertSelectorExists` was broken + +2012-03-28, v0.6.5 +------------------ + +- **BC BREAK:** reverted 8347278 (refs [#34](https://github.com/n1k0/casperjs/issues/34) and added a new `clear()` method to *close* a page + You now have to call `casper.clear()` if you want to stop javascript execution within the remote DOM environment. +- **BC BREAK:** removed `fallbackToHref` option handling in `ClientUtils.click()` (refs [#63](https://github.com/n1k0/casperjs/issues/63)) +- `tester.findTestFiles()` now returns results in predictable order +- added `--log-level` and `--direct` options to `casper test` command +- fixed 0.6.4 version number in `bootstrap.js` +- centralized version number to package.json +- ensured compatibility with PhantomJS 1.5 + +2012-02-09, v0.6.4 +------------------ + +- fixed `casperjs` command wasn't passing phantomjs native option in the correct order, resulting them not being taken into account by phantomjs engine: + - fixed [#49](https://github.com/n1k0/casperjs/issues/49) - `casperjs` is not sending `--ssl-ignore-errors` + - fixed [#50](https://github.com/n1k0/casperjs/issues/50) - Cookies not being set when passing `--cookies-file` option +- fixed Python3 compatibility of the `casperjs` executable + +2012-02-05, v0.6.3 +------------------ + +- fixed [#48](https://github.com/n1k0/casperjs/issues/48) - XML Output file doesn't have classpath populated with file name +- refs [#46](https://github.com/n1k0/casperjs/issues/46) - added value details to Tester `fail` event +- new site design, new [domain](http://casperjs.org/), enhanced & updated docs + +2012-01-19, v0.6.2 +------------------ + +- fixed [#41](https://github.com/n1k0/casperjs/issues/41) - injecting casperjs lib crashes `cmd.exe` on Windows 7 +- fixed [#42](https://github.com/n1k0/casperjs/issues/42) - Use file name of test script as 'classname' in JUnit XML report (@mpeltonen) +- fixed [#43](https://github.com/n1k0/casperjs/issues/43) - Exit status not reported back to caller +- suppressed colorized output syntax for windows; was making output hard to read +- added patchy `fs.isWindows()` method +- added `--xunit=` cli option to `$ casperjs test` command for saving xunit results, eg.: + + $ casperjs test tests/suites --xunit=build-result.xml + + +2012-01-16, v0.6.1 +------------------ + +- restablished js-emulated click simulation first, then native QtWebKit + events as a fallback; some real world testing have surprinsingly proven the former being often + more efficient than the latter +- fixed casperjs executable could not handle a `PHANTOMJS_EXECUTABLE` containing spaces +- fixed casper could not be used without the executable [as documented](http://casperjs.org/#faq-executable) +- fixed wrong `debug` log level on `ClientUtils.click()` error; set to `error` + +Please check the [updated documentation](http://casperjs.org). + +2012-01-12, v0.6.0 +------------------ + +- **BC BREAK:** `Casper.click()` now uses native Webkit mouse events instead of previous crazy utopic javascript emulation +- **BC BREAK:** All errors thrown by CasperJS core are of the new `CasperError` type +- **BC BREAK:** removed obsolete `replaceFunctionPlaceholders()` +- *Deprecated*: `Casper.extend()` method has been deprecated; use natural javascript extension mechanisms instead (see samples) +- added `$ casperjs test` command for running split test suites +- `Casper.open()` can now perform HTTP `GET`, `POST`, `PUT`, `DELETE` and `HEAD` operations +- commonjs/nodejs-like module exports implementation +- ported nodejs' `events` module to casperjs; lots of events added, plus some value filtering capabilities +- introduced the `mouse` module to handle native Webkit mouse events +- added support for `RegExp` input in `Casper.resourceExists()` +- added printing of source file path for any uncaught exception printed onto the console +- added an emulation of stack trace printing (but PhantomJS will have to upgrade its javascript engine for it to be fully working though) + +Please check the [updated documentation](http://casperjs.org). + +--- + +2011-12-25, v0.4.2 +------------------ + +- merged PR #30 - Add request method and request data to the `base64encode()` method (@jasonlfunk) +- `casperjs` executable now gracefully exists on KeyboardInterrupt +- added `Casper.download()` method, for downloading any resource and save it onto the filesystem + +--- + +2011-12-21, v0.4.1 +------------------ + +- fixed #31 - replaced bash executable script by a Python one + +--- + +2011-12-20, v0.4.0 +------------------ + +- first numbered version diff --git a/zephyr/tests/frontend/casperjs/CONTRIBUTING.md b/zephyr/tests/frontend/casperjs/CONTRIBUTING.md new file mode 100644 index 0000000000..e92e404732 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/CONTRIBUTING.md @@ -0,0 +1,93 @@ +# Contribution Guide + +*Note: this guide has been heavily inspired by [PhantomJS' one](https://github.com/ariya/phantomjs/blob/master/CONTRIBUTING.md).* + +**This page describes how to contribute changes to the [CasperJS](http://casperjs.org/) project.** + +Please do **not** create a pull request without reading this guide first. Failure to do so may result in the **rejection** of the pull request. + +## For The Impatients + +**Work on a feature branch**. +If your changes need to be modified due to some reviews, it is less clutter to tweak an isolated feature branch and push it again. + +**Create a ticket in the [issue tracker](https://github.com/n1k0/casperjs/issues/)**. +This serves as a placeholder for important feedback, review, or any future updates. **Please ensure searching the bugtracker for an already opened issue matching your case before filing a new issue.** + +In the commit message(s): + +* **Keep the first line short**. Write additional paragraphs if necessary. +* **Reference an opened issue**, by referencing the issue ID prefixed by a `#` and the keyword `refs`, eg. `refs #123` + +Sample commit message: + +> refs #123 - fixed error message formatting +> +> (optional: a short explanation of what the patch actually does) + +**Run tests** + +Run CasperJS' test suite to see you didn't break something: + + $ casperjs selftest + +The result status bar **must be green** before sending your PR. + +## Communicate + +**Improvement and feature request**. If you have an improvement idea, please send an email to the [mailing list](http://groups.google.com/group/casperjs) (preferable than contacting the developers directly) so that other people can give their insights and opinions. This is also important to avoid duplicate work. + +**Help request**. If you're stuck using CasperJS and don't understand how to achieve something, please [ask on the mailing-list](https://groups.google.com/forum/#!forum/casperjs) first. Please don't ask for all the kind people to write your scripts for you. + +**Ensure the issue is related to CasperJS**. Please try to reproduce the issue using plain PhantomJS. If it works with the native PhantomJS API but doesn't with CasperJS, then the issue is probably valid. In the opposite case, please file an issue on [PhantomJS issue tracker](http://code.google.com/p/phantomjs/issues/list). + +**Extending with new API**. Whenever you want to introduce a new API, please send an email to the mailing list along with the link to the issue if any. It may require few iterations to agree on the final API and hence it is important to engage all interested parties as early as possible. + +## Get Ready + +### Use Feature Branch + +To isolate your change, please avoid working on the master branch. Instead, work on a *feature branch* (often also known as *topic branch*). You can create a new branch (example here crash-fix) off the master branch by using: + + git checkout -b crash-fix master + +Refer to your favorite Git tutorial/book for further detailed help. + +Some good practices for the feature branch: + +* Give it a meaningful name instead of, e.g. `prevent-zero-divide` instead of just `fix` +* Make *granular* and *atomic* commits, e.g. do not mix a typo fix with some major refactoring +* Keep one branch for one specific issue. If you need to work on other unrelated issues, create another branch. + +### Write tests + +CasperJS being partly a testing framework, how irrelevant would be to send a pull request with no test? So, please take the time to write and attach tests to your PR. Furthermore, testing with CasperJS is quite [exhaustively documented](http://casperjs.org/testing.html). + +### Run tests! + +This may sound obvious but **don't send pull requests which break the casperjs test suite**. + +To see if your modifications broke the suite, just run: + + $ casperjs selftest + +### Write documentation + +Do you appreciate the [CasperJS documentation](http://casperjs.org/)? I do too. As the documentation contents are managed and generated using Github, Markdown and CasperJS itself, take the time to read the [Documentation Contribution Guide](https://github.com/n1k0/casperjs/blob/gh-pages/README.md#casperjs-documentation) and write the documentation related to your PR whenever applicable. + +**Note:** As the documentation is handled in a [dedicated separated `gh-pages` branch](https://github.com/n1k0/casperjs/tree/gh-pages), you'll have to send a dedicated PR for doc patches. I'm working on a more comfortable solution, but it's no easy task though. + +## Review and Merge + +When your branch is ready, send the pull request. + +While it is not always the case, often it is necessary to improve parts of your code in the branch. This is the actual review process. + +Here is a check list for the review: + +* It does not break the test suite +* There is no typo +* The coding style follows the existing one +* There is a reasonable amount of comment +* The license header is intact +* All examples are still working diff --git a/zephyr/tests/frontend/casperjs/CONTRIBUTORS.md b/zephyr/tests/frontend/casperjs/CONTRIBUTORS.md new file mode 100644 index 0000000000..f17b71190c --- /dev/null +++ b/zephyr/tests/frontend/casperjs/CONTRIBUTORS.md @@ -0,0 +1,46 @@ +# CasperJS contributors + +You can check out the [contribution graphs on github](https://github.com/n1k0/casperjs/graphs/contributors). + +``` +$ git shortlog -s -n + 689 Nicolas Perriault + 14 oncletom + 14 Brikou CARRE + 8 hannyu + 6 Chris Lorenzo + 4 pborreli + 4 nrabinowitz + 3 Andrew Childs + 3 Solomon White + 3 reina.sweet + 2 Reina Sweet + 2 Jason Funk + 2 Michael Geers + 2 Julien Moulin + 2 Donovan Hutchinson + 2 Clochix + 1 Marcel Duran + 1 Mathieu Agopian + 1 Mehdi Kabab + 1 Mikko Peltonen + 1 Pascal Borreli + 1 Rafael + 1 Rafael Garcia + 1 Raphaël Benitte + 1 Andrew de Andrade + 1 Tim Bunce + 1 Victor Yap + 1 alfetopito + 1 Christophe Benz + 1 jean-philippe serafin + 1 Chris Winters + 1 Ben Lowery + 1 Jan Pochyla + 1 Harrison Reiser + 1 Julian Gruber + 1 Justine Tunney + 1 KaroDidi + 1 Leandro Boscariol + 1 Maisons du monde +``` diff --git a/zephyr/tests/frontend/casperjs/LICENSE.md b/zephyr/tests/frontend/casperjs/LICENSE.md new file mode 100644 index 0000000000..5c308cb09f --- /dev/null +++ b/zephyr/tests/frontend/casperjs/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2011-2012 Nicolas Perriault + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/zephyr/tests/frontend/casperjs/README.md b/zephyr/tests/frontend/casperjs/README.md new file mode 100644 index 0000000000..c372c27001 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/README.md @@ -0,0 +1,58 @@ +# CasperJS [![Build Status](https://secure.travis-ci.org/n1k0/casperjs.png)](http://travis-ci.org/n1k0/casperjs) + +CasperJS is a navigation scripting & testing utility for [PhantomJS](http://www.phantomjs.org/). +It eases the process of defining a full navigation scenario and provides useful +high-level functions, methods & syntaxic sugar for doing common tasks such as: + +- defining & ordering [navigation steps](http://casperjs.org/quickstart.html) +- [filling forms](http://casperjs.org/api.html#casper.fill) +- [clicking links](http://casperjs.org/api.html#casper.click) +- [capturing screenshots](http://casperjs.org/api.html#casper.captureSelector) of a page (or an area) +- [making assertions on remote DOM](http://casperjs.org/api.html#tester) +- [logging](http://casperjs.org/logging.html) & [events](http://casperjs.org/events-filters.html) +- [downloading base64](http://casperjs.org/api.html#casper.download) encoded resources, even binary ones +- catching errors and react accordingly +- writing [functional test suites](http://casperjs.org/testing.html), exporting results as JUnit XML (xUnit) + +Browse the [sample examples repository](https://github.com/n1k0/casperjs/tree/master/samples). +Don't hesitate to pull request for any cool example of yours as well! + +**Read the [full documentation](http://casperjs.org/) on casperjs dedicated website.** + +Subscribe to the [project mailing-list](https://groups.google.com/forum/#!forum/casperjs) + +Follow the CasperJS project [on twitter](https://twitter.com/casperjs_org) and [Google+](https://plus.google.com/b/106641872690063476159/). + +## Show me some code! + +Sample test to see if some dropdown can be opened: + +```javascript +casper.start('http://twitter.github.com/bootstrap/javascript.html#dropdowns', function() { + this.test.assertExists('#navbar-example'); + this.click('#dropdowns .nav-pills .dropdown:last-of-type a.dropdown-toggle'); + this.waitUntilVisible('#dropdowns .nav-pills .open', function() { + this.test.pass('Dropdown is open'); + }); +}); + +casper.run(function() { + this.test.done(); +}); +``` + +Run the script: + +![](http://cl.ly/image/112m0F2n162i/Capture%20d%E2%80%99%C3%A9cran%202012-10-19%20%C3%A0%2016.37.15.png) + +## Contributing + +### Contributing code + +Please read the [CONTRIBUTING.md](https://github.com/n1k0/casperjs/blob/master/CONTRIBUTING.md) file contents. + +### Contributing documentation + +CasperJS's documentation is written using the [Markdown format](http://daringfireball.net/projects/markdown/), and hosted on Github thanks to the [Github Pages Feature](http://pages.github.com/). + +To view the source files on github, head to [the gh-pages branch](https://github.com/n1k0/casperjs/tree/gh-pages), and check the [documentation's README](https://github.com/n1k0/casperjs/tree/gh-pages#readme) for further instructions. diff --git a/zephyr/tests/frontend/casperjs/batchbin/casperjs.bat b/zephyr/tests/frontend/casperjs/batchbin/casperjs.bat new file mode 100644 index 0000000000..6f47552d18 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/batchbin/casperjs.bat @@ -0,0 +1,22 @@ +@ECHO OFF +set CASPER_PATH=%~dp0..\ +set CASPER_BIN=%CASPER_PATH%bin\ + +set PHANTOMJS_NATIVE_ARGS=(--cookies-file --config --debug --disk-cache --ignore-ssl-errors --load-images --load-plugins --local-storage-path --local-storage-quota --local-to-remote-url-access --max-disk-cache-size --output-encoding --proxy --proxy-auth --proxy-type --remote-debugger-port --remote-debugger-autorun --script-encoding --web-security) + +set PHANTOM_ARGS= +set CASPER_ARGS= + +:Loop +if "%1"=="" goto Continue + set IS_PHANTOM_ARG=0 + for %%i in %PHANTOMJS_NATIVE_ARGS% do ( + if "%%i"=="%1" set IS_PHANTOM_ARG=1 + ) + if %IS_PHANTOM_ARG%==0 set CASPER_ARGS=%CASPER_ARGS% %1 + if %IS_PHANTOM_ARG%==1 set PHANTOM_ARGS=%PHANTOM_ARGS% %1 +shift +goto Loop +:Continue + +call phantomjs%PHANTOM_ARGS% %CASPER_BIN%bootstrap.js --casper-path=%CASPER_PATH% --cli%CASPER_ARGS% \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/bin/bootstrap.js b/zephyr/tests/frontend/casperjs/bin/bootstrap.js new file mode 100755 index 0000000000..d2816942a6 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/bin/bootstrap.js @@ -0,0 +1,319 @@ +/*! + * Casper is a navigation utility for PhantomJS. + * + * Documentation: http://casperjs.org/ + * Repository: http://github.com/n1k0/casperjs + * + * Copyright (c) 2011-2012 Nicolas Perriault + * + * Part of source code is Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*global console phantom require*/ +/*jshint maxstatements:30 maxcomplexity:10*/ + +if (!phantom) { + console.error('CasperJS needs to be executed in a PhantomJS environment http://phantomjs.org/'); + phantom.exit(1); +} + +if (phantom.version.major === 1 && phantom.version.minor < 6) { + console.error('CasperJS needs at least PhantomJS v1.6.0 or later.'); + phantom.exit(1); +} else { + bootstrap(window); +} + +// Polyfills +if (typeof Function.prototype.bind !== "function") { + Function.prototype.bind = function(scope) { + "use strict"; + var _function = this; + return function() { + return _function.apply(scope, arguments); + }; + }; +} + +/** + * CasperJS ships with its own implementation of CommonJS' require() because + * PhantomJS' native one doesn't allow to specify supplementary, alternative + * lookup directories to fetch modules from. + * + */ +function patchRequire(require, requireDirs) { + "use strict"; + require('webserver'); // force generation of phantomjs' require.cache for the webserver module + var fs = require('fs'); + var phantomBuiltins = ['fs', 'webpage', 'system', 'webserver']; + var phantomRequire = phantom.__orig__require = require; + var requireCache = {}; + function possiblePaths(path, requireDir) { + var dir, paths = []; + if (path[0] === '.') { + paths.push.apply(paths, [ + fs.absolute(path), + fs.absolute(fs.pathJoin(requireDir, path)) + ]); + } else if (path[0] === '/') { + paths.push(path); + } else { + dir = fs.absolute(requireDir); + while (dir !== '' && dir.lastIndexOf(':') !== dir.length - 1) { + paths.push(fs.pathJoin(dir, 'modules', path)); + // nodejs compatibility + paths.push(fs.pathJoin(dir, 'node_modules', path)); + dir = fs.dirname(dir); + } + paths.push(fs.pathJoin(requireDir, 'lib', path)); + paths.push(fs.pathJoin(requireDir, 'modules', path)); + } + return paths; + } + var patchedRequire = function _require(path) { + var i, paths = [], + fileGuesses = [], + file, + module = { + exports: {} + }; + if (phantomBuiltins.indexOf(path) !== -1) { + return phantomRequire(path); + } + requireDirs.forEach(function(requireDir) { + paths = paths.concat(possiblePaths(path, requireDir)); + }); + paths.forEach(function _forEach(testPath) { + fileGuesses.push.apply(fileGuesses, [ + testPath, + testPath + '.js', + testPath + '.coffee', + fs.pathJoin(testPath, 'index.js'), + fs.pathJoin(testPath, 'index.coffee'), + fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'), + fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee') + ]); + }); + file = null; + for (i = 0; i < fileGuesses.length && !file; ++i) { + if (fs.isFile(fileGuesses[i])) { + file = fileGuesses[i]; + } + } + if (!file) { + throw new Error("CasperJS couldn't find module " + path); + } + if (file in requireCache) { + return requireCache[file].exports; + } + var scriptCode = (function getScriptCode(file) { + var scriptCode = fs.read(file); + if (/\.coffee$/i.test(file)) { + /*global CoffeeScript*/ + scriptCode = CoffeeScript.compile(scriptCode); + } + return scriptCode; + })(file); + var fn = new Function('__file__', 'require', 'module', 'exports', scriptCode); + try { + fn(file, _require, module, module.exports); + } catch (e) { + var error = new window.CasperError('__mod_error(' + path + '):: ' + e); + error.file = file; + throw error; + } + requireCache[file] = module; + return module.exports; + }; + patchedRequire.patched = true; + return patchedRequire; +} + +function bootstrap(global) { + "use strict"; + + var phantomArgs = require('system').args; + + /** + * Loads and initialize the CasperJS environment. + */ + phantom.loadCasper = function loadCasper() { + // Patching fs + // TODO: watch for these methods being implemented in official fs module + var fs = (function _fs(fs) { + if (!fs.hasOwnProperty('basename')) { + fs.basename = function basename(path) { + return path.replace(/.*\//, ''); + }; + } + if (!fs.hasOwnProperty('dirname')) { + fs.dirname = function dirname(path) { + return path.replace(/\\/g, '/').replace(/\/[^\/]*$/, ''); + }; + } + if (!fs.hasOwnProperty('isWindows')) { + fs.isWindows = function isWindows() { + var testPath = arguments[0] || this.workingDirectory; + return (/^[a-z]{1,2}:/i).test(testPath) || testPath.indexOf("\\\\") === 0; + }; + } + if (!fs.hasOwnProperty('pathJoin')) { + fs.pathJoin = function pathJoin() { + return Array.prototype.join.call(arguments, this.separator); + }; + } + return fs; + })(require('fs')); + + // casper root path + if (!phantom.casperPath) { + try { + phantom.casperPath = phantom.args.map(function _map(i) { + var match = i.match(/^--casper-path=(.*)/); + if (match) { + return fs.absolute(match[1]); + } + }).filter(function _filter(path) { + return fs.isDirectory(path); + }).pop(); + } catch (e) {} + } + + if (!phantom.casperPath) { + console.error("Couldn't find nor compute phantom.casperPath, exiting."); + phantom.exit(1); + } + + // Embedded, up-to-date, validatable & controlable CoffeeScript + phantom.injectJs(fs.pathJoin(phantom.casperPath, 'modules', 'vendors', 'coffee-script.js')); + + // custom global CasperError + global.CasperError = function CasperError(msg) { + Error.call(this); + this.message = msg; + this.name = 'CasperError'; + }; + + // standard Error prototype inheritance + global.CasperError.prototype = Object.getPrototypeOf(new Error()); + + // CasperJS version, extracted from package.json - see http://semver.org/ + phantom.casperVersion = (function getVersion(path) { + var parts, patchPart, pkg, pkgFile; + var fs = require('fs'); + pkgFile = fs.absolute(fs.pathJoin(path, 'package.json')); + if (!fs.exists(pkgFile)) { + throw new global.CasperError('Cannot find package.json at ' + pkgFile); + } + try { + pkg = JSON.parse(require('fs').read(pkgFile)); + } catch (e) { + throw new global.CasperError('Cannot read package file contents: ' + e); + } + parts = pkg.version.trim().split("."); + if (parts < 3) { + throw new global.CasperError("Invalid version number"); + } + patchPart = parts[2].split('-'); + return { + major: ~~parts[0] || 0, + minor: ~~parts[1] || 0, + patch: ~~patchPart[0] || 0, + ident: patchPart[1] || "", + toString: function toString() { + var version = [this.major, this.minor, this.patch].join('.'); + if (this.ident) { + version = [version, this.ident].join('-'); + } + return version; + } + }; + })(phantom.casperPath); + + // patch require + global.require = patchRequire(global.require, [phantom.casperPath, fs.workingDirectory]); + + // casper cli args + phantom.casperArgs = global.require('cli').parse(phantom.args); + + // loaded status + phantom.casperLoaded = true; + }; + + /** + * Initializes the CasperJS Command Line Interface. + */ + phantom.initCasperCli = function initCasperCli() { + var fs = require("fs"); + + if (!!phantom.casperArgs.options.version) { + console.log(phantom.casperVersion.toString()); + phantom.exit(0); + } else if (phantom.casperArgs.get(0) === "test") { + phantom.casperScript = fs.absolute(fs.pathJoin(phantom.casperPath, 'tests', 'run.js')); + phantom.casperTest = true; + phantom.casperArgs.drop("test"); + } else if (phantom.casperArgs.get(0) === "selftest") { + phantom.casperScript = fs.absolute(fs.pathJoin(phantom.casperPath, 'tests', 'run.js')); + phantom.casperSelfTest = true; + phantom.casperArgs.options.includes = fs.pathJoin(phantom.casperPath, 'tests', 'selftest.js'); + if (phantom.casperArgs.args.length <= 1) { + phantom.casperArgs.args.push(fs.pathJoin(phantom.casperPath, 'tests', 'suites')); + } + phantom.casperArgs.drop("selftest"); + } else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) { + var phantomVersion = [phantom.version.major, phantom.version.minor, phantom.version.patch].join('.'); + var f = require("utils").format; + console.log(f('CasperJS version %s at %s, using PhantomJS version %s', + phantom.casperVersion.toString(), + phantom.casperPath, phantomVersion)); + console.log(fs.read(fs.pathJoin(phantom.casperPath, 'bin', 'usage.txt'))); + phantom.exit(0); + } + + + if (!phantom.casperScript) { + phantom.casperScript = phantom.casperArgs.get(0); + } + + if (phantom.casperScript) { + if (!fs.isFile(phantom.casperScript)) { + console.error('Unable to open file: ' + phantom.casperScript); + phantom.exit(1); + } else { + // filter out the called script name from casper args + phantom.casperArgs.drop(phantom.casperScript); + + // passed casperjs script execution + phantom.injectJs(phantom.casperScript); + } + } + }; + + if (!phantom.casperLoaded) { + phantom.loadCasper(); + } + + if (true === phantom.casperArgs.get('cli')) { + phantom.initCasperCli(); + } +} diff --git a/zephyr/tests/frontend/casperjs/bin/casperjs b/zephyr/tests/frontend/casperjs/bin/casperjs new file mode 100755 index 0000000000..c22baf0f38 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/bin/casperjs @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +import os +import sys + + +def resolve(path): + if os.path.islink(path): + path = os.path.join(os.path.dirname(path), os.readlink(path)) + return resolve(path) + return path + +PHANTOMJS_NATIVE_ARGS = [ + 'cookies-file', + 'config', + 'debug', + 'disk-cache', + 'ignore-ssl-errors', + 'load-images', + 'load-plugins', + 'local-storage-path', + 'local-storage-quota', + 'local-to-remote-url-access', + 'max-disk-cache-size', + 'output-encoding', + 'proxy', + 'proxy-auth', + 'proxy-type', + 'remote-debugger-port', + 'remote-debugger-autorun', + 'script-encoding', + 'web-security', +] +CASPER_ARGS = [] +PHANTOMJS_ARGS = [] + +for arg in sys.argv[1:]: + found = False + for native in PHANTOMJS_NATIVE_ARGS: + if arg.startswith('--%s' % native): + PHANTOMJS_ARGS.append(arg) + found = True + if not found: + CASPER_ARGS.append(arg) + +CASPER_PATH = os.path.abspath(os.path.join(os.path.dirname(resolve(__file__)), '..')) +CASPER_COMMAND = os.environ.get('PHANTOMJS_EXECUTABLE', 'phantomjs').split(' ') +CASPER_COMMAND.extend(PHANTOMJS_ARGS) +CASPER_COMMAND.extend([ + os.path.join(CASPER_PATH, 'bin', 'bootstrap.js'), + '--casper-path=%s' % CASPER_PATH, + '--cli' +]) +CASPER_COMMAND.extend(CASPER_ARGS) + +try: + os.execvp(CASPER_COMMAND[0], CASPER_COMMAND) +except OSError as err: + print(('Fatal: %s; did you install phantomjs?' % err)) + sys.exit(1) diff --git a/zephyr/tests/frontend/casperjs/bin/usage.txt b/zephyr/tests/frontend/casperjs/bin/usage.txt new file mode 100644 index 0000000000..cc94438ae7 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/bin/usage.txt @@ -0,0 +1,10 @@ + +Usage: casperjs [options] script.[js|coffee] [script argument [script argument ...]] + casperjs [options] test [test path [test path ...]] + +Options: + +--help Prints this help +--version Prints out CasperJS version + +Read the docs http://casperjs.org/ \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/casperjs.gemspec b/zephyr/tests/frontend/casperjs/casperjs.gemspec new file mode 100644 index 0000000000..301488431c --- /dev/null +++ b/zephyr/tests/frontend/casperjs/casperjs.gemspec @@ -0,0 +1,23 @@ +# by hannyu + +CASPERJS_VERSION = File.read("package.json")[/version.*:.*"(.*?)"/,1].gsub(/[\-_\+]/,".") + +Gem::Specification.new do |s| + s.name = "casperjs" + s.version = CASPERJS_VERSION + s.homepage = "http://casperjs.org/" + s.authors = ["Nicolas Perriault", ] + s.email = ["nperriault@gmail.com",] + s.description = "CasperJS is a navigation scripting & testing utility for [PhantomJS](http://www.phantomjs.org/). +It eases the process of defining a full navigation scenario and provides useful +high-level functions, methods & syntaxic sugar for doing common tasks." + s.summary = "Navigation scripting & testing utility for PhantomJS" + s.extra_rdoc_files = ["LICENSE.md", "README.md"] + s.files = Dir["LICENSE.md", "README.md", "CHANGELOG.md", "package.json", "casperjs.gemspec", + "bin/bootstrap.js", "bin/usage.txt", "bin/casperjs_python", + "docs/**/*", "modules/**/*", "samples/**/*", "tests/**/*"] + s.bindir = "rubybin" + s.executables = [ "casperjs" ] + s.license = "MIT" + s.requirements = [ "PhantomJS v1.6" ] +end diff --git a/zephyr/tests/frontend/casperjs/modules/casper.js b/zephyr/tests/frontend/casperjs/modules/casper.js new file mode 100644 index 0000000000..1855d08876 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/casper.js @@ -0,0 +1,1874 @@ +/*! + * Casper is a navigation utility for PhantomJS. + * + * Documentation: http://casperjs.org/ + * Repository: http://github.com/n1k0/casperjs + * + * Copyright (c) 2011-2012 Nicolas Perriault + * + * Part of source code is Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*global CasperError console exports phantom require __utils__*/ + +var colorizer = require('colorizer'); +var events = require('events'); +var fs = require('fs'); +var http = require('http'); +var mouse = require('mouse'); +var qs = require('querystring'); +var tester = require('tester'); +var utils = require('utils'); +var f = utils.format; + + +var defaultUserAgent = phantom.defaultPageSettings.userAgent + .replace('PhantomJS', f("CasperJS/%s", phantom.casperVersion) + '+Phantomjs'); + +exports.create = function create(options) { + "use strict"; + return new Casper(options); +}; + +/** + * Shortcut to build an XPath selector object. + * + * @param String expression The XPath expression + * @return Object + * @see http://casperjs.org/selectors.html + */ +function selectXPath(expression) { + "use strict"; + return { + type: 'xpath', + path: expression, + toString: function() { + return this.type + ' selector: ' + this.path; + } + }; +} +exports.selectXPath = selectXPath; + +/** + * Main Casper object. + * + * @param Object options Casper options + */ +var Casper = function Casper(options) { + "use strict"; + /*jshint maxstatements:30*/ + // init & checks + if (!(this instanceof Casper)) { + return new Casper(options); + } + // default options + this.defaults = { + clientScripts: [], + colorizerType: 'Colorizer', + exitOnError: true, + logLevel: "error", + httpStatusHandlers: {}, + safeLogs: true, + onAlert: null, + onDie: null, + onError: null, + onLoadError: null, + onPageInitialized: null, + onResourceReceived: null, + onResourceRequested: null, + onStepComplete: null, + onStepTimeout: function _onStepTimeout(timeout, stepNum) { + this.die("Maximum step execution timeout exceeded for step " + stepNum); + }, + onTimeout: function _onTimeout(timeout) { + this.die(f("Script timeout of %dms reached, exiting.", timeout)); + }, + onWaitTimeout: function _onWaitTimeout(timeout) { + this.die(f("Wait timeout of %dms expired, exiting.", timeout)); + }, + page: null, + pageSettings: { + localToRemoteUrlAccessEnabled: true, + userAgent: defaultUserAgent + }, + stepTimeout: null, + timeout: null, + verbose: false, + waitTimeout: 5000 + }; + // options + this.options = utils.mergeObjects(this.defaults, options); + // properties + this.checker = null; + this.cli = phantom.casperArgs; + this.colorizer = this.getColorizer(); + this.currentResponse = undefined; + this.currentUrl = 'about:blank'; + this.currentHTTPStatus = null; + this.history = []; + this.loadInProgress = false; + this.navigationRequested = false; + this.logFormats = {}; + this.logLevels = ["debug", "info", "warning", "error"]; + this.logStyles = { + debug: 'INFO', + info: 'PARAMETER', + warning: 'COMMENT', + error: 'ERROR' + }; + this.mouse = mouse.create(this); + this.page = null; + this.pendingWait = false; + this.requestUrl = 'about:blank'; + this.resources = []; + this.result = { + log: [], + status: "success", + time: 0 + }; + this.started = false; + this.step = -1; + this.steps = []; + this.test = tester.create(this); + + // init phantomjs error handler + this.initErrorHandler(); + + this.on('error', function(msg, backtrace) { + var c = this.getColorizer(); + var match = /^(.*): __mod_error(.*):: (.*)/.exec(msg); + var notices = []; + if (match && match.length === 4) { + notices.push(' in module ' + match[2]); + notices.push(' NOTICE: errors within modules cannot be backtraced yet.'); + msg = match[3]; + } + console.error(c.colorize(msg, 'RED_BAR', 80)); + notices.forEach(function(notice) { + console.error(c.colorize(notice, 'COMMENT')); + }); + backtrace.forEach(function(item) { + var message = fs.absolute(item.file) + ":" + c.colorize(item.line, "COMMENT"); + if (item['function']) { + message += " in " + c.colorize(item['function'], "PARAMETER"); + } + console.error(" " + message); + }); + }); + + // deprecated feature event handler + this.on('deprecated', function onDeprecated(message) { + this.warn('[deprecated] ' + message); + }); + + // dispatching an event when instance has been constructed + this.emit('init'); +}; + +// Casper class is an EventEmitter +utils.inherits(Casper, events.EventEmitter); + +/** + * Go a step back in browser's history + * + * @return Casper + */ +Casper.prototype.back = function back() { + "use strict"; + this.checkStarted(); + return this.then(function _step() { + this.emit('back'); + this.evaluate(function _evaluate() { + history.back(); + }); + }); +}; + +/** + * Encodes a resource using the base64 algorithm synchronously using + * client-side XMLHttpRequest. + * + * NOTE: we cannot use window.btoa() for some strange reasons here. + * + * @param String url The url to download + * @param String method The method to use, optional: default GET + * @param String data The data to send, optional + * @return string Base64 encoded result + */ +Casper.prototype.base64encode = function base64encode(url, method, data) { + "use strict"; + return this.evaluate(function _evaluate(url, method, data) { + return window.__utils__.getBase64(url, method, data); + }, { url: url, method: method, data: data }); +}; + +/** + * Proxy method for WebPage#render. Adds a clipRect parameter for + * automatically set page clipRect setting values and sets it back once + * done. If the cliprect parameter is omitted, the full page viewport + * area will be rendered. + * + * @param String targetFile A target filename + * @param mixed clipRect An optional clipRect object (optional) + * @return Casper + */ +Casper.prototype.capture = function capture(targetFile, clipRect) { + "use strict"; + /*jshint maxstatements:20*/ + this.checkStarted(); + var previousClipRect; + targetFile = fs.absolute(targetFile); + if (clipRect) { + if (!utils.isClipRect(clipRect)) { + throw new CasperError("clipRect must be a valid ClipRect object."); + } + previousClipRect = this.page.clipRect; + this.page.clipRect = clipRect; + this.log(f("Capturing page to %s with clipRect %s", targetFile, JSON.stringify(clipRect)), "debug"); + } else { + this.log(f("Capturing page to %s", targetFile), "debug"); + } + if (!this.page.render(this.filter('capture.target_filename', targetFile) || targetFile)) { + this.log(f("Failed to save screenshot to %s; please check permissions", targetFile), "error"); + } else { + this.log(f("Capture saved to %s", targetFile), "info"); + this.emit('capture.saved', targetFile); + } + if (previousClipRect) { + this.page.clipRect = previousClipRect; + } + return this; +}; + +/** + * Returns a Base64 representation of a binary image capture of the current + * page, or an area within the page, in a given format. + * + * Supported image formats are `bmp`, `jpg`, `jpeg`, `png`, `ppm`, `tiff`, + * `xbm` and `xpm`. + * + * @param String format The image format + * @param String|Object|undefined selector DOM CSS3/XPath selector or clipRect object (optional) + * @return Casper + */ +Casper.prototype.captureBase64 = function captureBase64(format, area) { + "use strict"; + /*jshint maxstatements:20*/ + this.checkStarted(); + var base64, previousClipRect, formats = ['bmp', 'jpg', 'jpeg', 'png', 'ppm', 'tiff', 'xbm', 'xpm']; + if (formats.indexOf(format.toLowerCase()) === -1) { + throw new CasperError(f('Unsupported format "%s"', format)); + } + if (utils.isClipRect(area)) { + // if area is a clipRect object + this.log(f("Capturing base64 %s representation of %s", format, utils.serialize(area)), "debug"); + previousClipRect = this.page.clipRect; + this.page.clipRect = area; + base64 = this.page.renderBase64(format); + } else if (utils.isValidSelector(area)) { + // if area is a selector string or object + this.log(f("Capturing base64 %s representation of %s", format, area), "debug"); + base64 = this.captureBase64(format, this.getElementBounds(area)); + } else { + // whole page capture + this.log(f("Capturing base64 %s representation of page", format), "debug"); + base64 = this.page.renderBase64(format); + } + if (previousClipRect) { + this.page.clipRect = previousClipRect; + } + return base64; +}; + +/** + * Captures the page area matching the provided selector. + * + * @param String targetFile Target destination file path. + * @param String selector DOM CSS3/XPath selector + * @return Casper + */ +Casper.prototype.captureSelector = function captureSelector(targetFile, selector) { + "use strict"; + return this.capture(targetFile, this.getElementBounds(selector)); +}; + +/** + * Checks for any further navigation step to process. + * + * @param Casper self A self reference + * @param function onComplete An options callback to apply on completion + */ +Casper.prototype.checkStep = function checkStep(self, onComplete) { + "use strict"; + if (self.pendingWait || self.loadInProgress || self.navigationRequested) { + return; + } + var step = self.steps[self.step++]; + if (utils.isFunction(step)) { + self.runStep(step); + } else { + self.result.time = new Date().getTime() - self.startTime; + self.log(f("Done %s steps in %dms", self.steps.length, self.result.time), "info"); + clearInterval(self.checker); + self.step -= 1; + self.emit('run.complete'); + if (utils.isFunction(onComplete)) { + onComplete.call(self, self); + } else { + // default behavior is to exit + self.exit(); + } + } +}; + +/** + * Checks if this instance is started. + * + * @return Boolean + * @throws CasperError + */ +Casper.prototype.checkStarted = function checkStarted() { + "use strict"; + if (!this.started) { + throw new CasperError(f("Casper is not started, can't execute `%s()`", + checkStarted.caller.name)); + } +}; + +/** + * Clears the current page execution environment context. Useful to avoid + * having previously loaded DOM contents being still active (refs #34). + * + * Think of it as a way to stop javascript execution within the remote DOM + * environment. + * + * @return Casper + */ +Casper.prototype.clear = function clear() { + "use strict"; + this.checkStarted(); + this.page.content = ''; + return this; +}; + +/** + * Emulates a click on the element from the provided selector using the mouse + * pointer, if possible. + * + * In case of success, `true` is returned, `false` otherwise. + * + * @param String selector A DOM CSS3 compatible selector + * @return Boolean + */ +Casper.prototype.click = function click(selector) { + "use strict"; + this.checkStarted(); + return this.mouseEvent('click', selector); +}; + +/** + * Emulates a click on the element having `label` as innerText. The first + * element matching this label will be selected, so use with caution. + * + * @param String label Element innerText value + * @param String tag An element tag name (eg. `a` or `button`) (optional) + * @return Boolean + */ +Casper.prototype.clickLabel = function clickLabel(label, tag) { + "use strict"; + this.checkStarted(); + tag = tag || "*"; + var escapedLabel = label.toString().replace(/"/g, '\\"'); + var selector = selectXPath(f('//%s[text()="%s"]', tag, escapedLabel)); + return this.click(selector); +}; + +/** + * Creates a step definition. + * + * @param Function fn The step function to call + * @param Object options Step options + * @return Function The final step function + */ +Casper.prototype.createStep = function createStep(fn, options) { + "use strict"; + if (!utils.isFunction(fn)) { + throw new CasperError("createStep(): a step definition must be a function"); + } + fn.options = utils.isObject(options) ? options : {}; + this.emit('step.created', fn); + return fn; +}; + +/** + * Logs the HTML code of the current page. + * + * @param String selector A DOM CSS3/XPath selector (optional) + * @param Boolean outer Whether to fetch outer HTML contents (default: false) + * @return Casper + */ +Casper.prototype.debugHTML = function debugHTML(selector, outer) { + "use strict"; + this.checkStarted(); + return this.echo(this.getHTML(selector, outer)); +}; + +/** + * Logs the textual contents of the current page. + * + * @return Casper + */ +Casper.prototype.debugPage = function debugPage() { + "use strict"; + this.checkStarted(); + this.echo(this.evaluate(function _evaluate() { + return document.body.textContent || document.body.innerText; + })); + return this; +}; + +/** + * Exit phantom on failure, with a logged error message. + * + * @param String message An optional error message + * @param Number status An optional exit status code (must be > 0) + * @return Casper + */ +Casper.prototype.die = function die(message, status) { + "use strict"; + this.checkStarted(); + this.result.status = "error"; + this.result.time = new Date().getTime() - this.startTime; + if (!utils.isString(message) || !message.length) { + message = "Suite explicitely interrupted without any message given."; + } + this.log(message, "error"); + this.emit('die', message, status); + if (utils.isFunction(this.options.onDie)) { + this.options.onDie.call(this, this, message, status); + } + return this.exit(~~status > 0 ? ~~status : 1); +}; + +/** + * Downloads a resource and saves it on the filesystem. + * + * @param String url The url of the resource to download + * @param String targetPath The destination file path + * @param String method The HTTP method to use (default: GET) + * @param String data Optional data to pass performing the request + * @return Casper + */ +Casper.prototype.download = function download(url, targetPath, method, data) { + "use strict"; + this.checkStarted(); + var cu = require('clientutils').create(utils.mergeObjects({}, this.options)); + try { + fs.write(targetPath, cu.decode(this.base64encode(url, method, data)), 'wb'); + this.emit('downloaded.file', targetPath); + this.log(f("Downloaded and saved resource in %s", targetPath)); + } catch (e) { + this.log(f("Error while downloading %s to %s: %s", url, targetPath, e), "error"); + } + return this; +}; + +/** + * Iterates over the values of a provided array and execute a callback + * for @ item. + * + * @param Array array + * @param Function fn Callback: function(self, item, index) + * @return Casper + */ +Casper.prototype.each = function each(array, fn) { + "use strict"; + if (!utils.isArray(array)) { + this.log("each() only works with arrays", "error"); + return this; + } + (function _each(self) { + array.forEach(function _forEach(item, i) { + fn.call(self, self, item, i); + }); + })(this); + return this; +}; + +/** + * Prints something to stdout. + * + * @param String text A string to echo to stdout + * @param String style An optional style name + * @param Number pad An optional pad value + * @return Casper + */ +Casper.prototype.echo = function echo(text, style, pad) { + "use strict"; + if (!utils.isString(text)) { + try { + text = text.toString(); + } catch (e) { + try { + text = utils.serialize(text); + } catch (e2) { + text = ''; + } + } + } + var message = style ? this.colorizer.colorize(text, style, pad) : text; + console.log(this.filter('echo.message', message) || message); + return this; +}; + +/** + * Evaluates an expression in the page context, a bit like what + * WebPage#evaluate does, but the passed function can also accept + * parameters if a context Object is also passed: + * + * casper.evaluate(function(username, password) { + * document.querySelector('#username').value = username; + * document.querySelector('#password').value = password; + * document.querySelector('#submit').click(); + * }, { + * username: 'Bazoonga', + * password: 'baz00nga' + * }) + * + * FIXME: waiting for a patch of PhantomJS to allow direct passing of + * arguments to the function. + * + * @param Function fn The function to be evaluated within current page DOM + * @param Object context Object containing the parameters to inject into the function + * @return mixed + * @see WebPage#evaluate + */ +Casper.prototype.evaluate = function evaluate(fn, context) { + "use strict"; + this.checkStarted(); + // ensure client utils are always injected + this.injectClientUtils(); + // function context + context = utils.isObject(context) ? context : {}; + // the way this works is kept for BC with older casperjs versions + var args = Object.keys(context).map(function(arg) { + return context[arg]; + }); + return this.page.evaluate.apply(this.page, [fn].concat(args)); +}; + +/** + * Evaluates an expression within the current page DOM and die() if it + * returns false. + * + * @param function fn The expression to evaluate + * @param String message The error message to log + * @param Number status An optional exit status code (must be > 0) + * + * @return Casper + */ +Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message, status) { + "use strict"; + this.checkStarted(); + if (!this.evaluate(fn)) { + return this.die(message, status); + } + return this; +}; + +/** + * Checks if an element matching the provided DOM CSS3/XPath selector exists in + * current page DOM. + * + * @param String selector A DOM CSS3/XPath selector + * @return Boolean + */ +Casper.prototype.exists = function exists(selector) { + "use strict"; + this.checkStarted(); + return this.evaluate(function _evaluate(selector) { + return window.__utils__.exists(selector); + }, { selector: selector }); +}; + +/** + * Exits phantom. + * + * @param Number status Status + * @return Casper + */ +Casper.prototype.exit = function exit(status) { + "use strict"; + this.emit('exit', status); + phantom.exit(status); +}; + +/** + * Fetches plain text contents contained in the DOM element(s) matching a given CSS3/XPath + * selector. + * + * @param String selector A DOM CSS3/XPath selector + * @return String + */ +Casper.prototype.fetchText = function fetchText(selector) { + "use strict"; + this.checkStarted(); + return this.evaluate(function _evaluate(selector) { + return window.__utils__.fetchText(selector); + }, { selector: selector }); +}; + +/** + * Fills a form with provided field values. + * + * @param String selector A DOM CSS3/XPath selector to the target form to fill + * @param Object vals Field values + * @param Boolean submit Submit the form? + */ +Casper.prototype.fill = function fill(selector, vals, submit) { + "use strict"; + this.checkStarted(); + submit = submit === true ? submit : false; + if (!utils.isObject(vals)) { + throw new CasperError("Form values must be provided as an object"); + } + this.emit('fill', selector, vals, submit); + var fillResults = this.evaluate(function _evaluate(selector, values) { + return window.__utils__.fill(selector, values); + }, { + selector: selector, + values: vals + }); + if (!fillResults) { + throw new CasperError("Unable to fill form"); + } else if (fillResults.errors.length > 0) { + throw new CasperError(f('Errors encountered while filling form: %s', + fillResults.errors.join('; '))); + } + // File uploads + if (fillResults.files && fillResults.files.length > 0) { + if (utils.isObject(selector) && selector.type === 'xpath') { + this.warn('Filling file upload fields is currently not supported using ' + + 'XPath selectors; Please use a CSS selector instead.'); + } else { + (function _each(self) { + fillResults.files.forEach(function _forEach(file) { + var fileFieldSelector = [selector, 'input[name="' + file.name + '"]'].join(' '); + self.page.uploadFile(fileFieldSelector, file.path); + }); + })(this); + } + } + // Form submission? + if (submit) { + this.evaluate(function _evaluate(selector) { + var form = window.__utils__.findOne(selector); + var method = (form.getAttribute('method') || "GET").toUpperCase(); + var action = form.getAttribute('action') || "unknown"; + window.__utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info'); + if (typeof form.submit === "function") { + form.submit(); + } else { + // http://www.spiration.co.uk/post/1232/Submit-is-not-a-function + form.submit.click(); + } + }, { selector: selector }); + } +}; + +/** + * Go a step forward in browser's history + * + * @return Casper + */ +Casper.prototype.forward = function forward(then) { + "use strict"; + this.checkStarted(); + return this.then(function _step() { + this.emit('forward'); + this.evaluate(function _evaluate() { + history.forward(); + }); + }); +}; + +/** + * Creates a new Colorizer instance. Sets `Casper.options.type` to change the + * colorizer type name (see the `colorizer` module). + * + * @return Object + */ +Casper.prototype.getColorizer = function getColorizer() { + "use strict"; + return colorizer.create(this.options.colorizerType || 'Colorizer'); +}; + +/** + * Retrieves current page contents, dealing with exotic other content types than HTML. + * + * @return String + */ +Casper.prototype.getPageContent = function getPageContent() { + "use strict"; + this.checkStarted(); + var contentType = utils.getPropertyPath(this, 'currentResponse.contentType'); + if (!utils.isString(contentType)) { + return this.page.content; + } + // for some reason webkit/qtwebkit will always enclose body contents within html tags + var sanitizedHtml = this.evaluate(function checkHtml() { + if (__utils__.findOne('head').childNodes.length === 0 && + __utils__.findOne('body').childNodes.length === 1 && + __utils__.findOne('body pre[style]')) { + return __utils__.findOne('body pre').textContent.trim(); + } + }); + return sanitizedHtml ? sanitizedHtml : this.page.content; +}; + +/** + * Retrieves current document url. + * + * @return String + */ +Casper.prototype.getCurrentUrl = function getCurrentUrl() { + "use strict"; + this.checkStarted(); + var url = this.evaluate(function _evaluate() { + return document.location.href; + }); + try { + return decodeURIComponent(url); + } catch (e) { + /*global unescape*/ + return unescape(url); + } +}; + +/** + * Retrieves the value of an attribute on the first element matching the provided + * DOM CSS3/XPath selector. + * + * @param String selector A DOM CSS3/XPath selector + * @param String attribute The attribute name to lookup + * @return String The requested DOM element attribute value + */ +Casper.prototype.getElementAttribute = Casper.prototype.getElementAttr = function getElementAttr(selector, attribute) { + "use strict"; + this.checkStarted(); + return this.evaluate(function _evaluate(selector, attribute) { + return document.querySelector(selector).getAttribute(attribute); + }, { selector: selector, attribute: attribute }); +}; + +/** + * Retrieves boundaries for a DOM element matching the provided DOM CSS3/XPath selector. + * + * @param String selector A DOM CSS3/XPath selector + * @return Object + */ +Casper.prototype.getElementBounds = function getElementBounds(selector) { + "use strict"; + this.checkStarted(); + if (!this.exists(selector)) { + throw new CasperError("No element matching selector found: " + selector); + } + var clipRect = this.evaluate(function _evaluate(selector) { + return window.__utils__.getElementBounds(selector); + }, { selector: selector }); + if (!utils.isClipRect(clipRect)) { + throw new CasperError('Could not fetch boundaries for element matching selector: ' + selector); + } + return clipRect; +}; + +/** + * Retrieves boundaries for all the DOM elements matching the provided DOM CSS3/XPath selector. + * + * @param String selector A DOM CSS3/XPath selector + * @return Array + */ +Casper.prototype.getElementsBounds = function getElementBounds(selector) { + "use strict"; + this.checkStarted(); + if (!this.exists(selector)) { + throw new CasperError("No element matching selector found: " + selector); + } + return this.evaluate(function _evaluate(selector) { + return window.__utils__.getElementsBounds(selector); + }, { selector: selector }); +}; + +/** + * Retrieves global variable. + * + * @param String name The name of the global variable to retrieve + * @return mixed + */ +Casper.prototype.getGlobal = function getGlobal(name) { + "use strict"; + this.checkStarted(); + var result = this.evaluate(function _evaluate(name) { + var result = {}; + try { + result.value = JSON.stringify(window[name]); + } catch (e) { + var message = f("Unable to JSON encode window.%s: %s", name, e); + window.__utils__.log(message, "error"); + result.error = message; + } + return result; + }, {'name': name}); + if (typeof result !== "object") { + throw new CasperError(f('Could not retrieve global value for "%s"', name)); + } else if ('error' in result) { + throw new CasperError(result.error); + } else if (utils.isString(result.value)) { + return JSON.parse(result.value); + } else { + return undefined; + } +}; + +/** + * Retrieves current HTML code matching the provided CSS3/XPath selector. + * Returns the HTML contents for the whole page if no arg is passed. + * + * @param String selector A DOM CSS3/XPath selector + * @param Boolean outer Whether to fetch outer HTML contents (default: false) + * @return String + */ +Casper.prototype.getHTML = function getHTML(selector, outer) { + "use strict"; + this.checkStarted(); + if (!selector) { + return this.page.content; + } + if (!this.exists(selector)) { + throw new CasperError("No element matching selector found: " + selector); + } + return this.evaluate(function getSelectorHTML(selector, outer) { + var element = __utils__.findOne(selector); + return outer ? element.outerHTML : element.innerHTML; + }, { selector: selector, outer: !!outer }); +}; + +/** + * Retrieves current page title, if any. + * + * @return String + */ +Casper.prototype.getTitle = function getTitle() { + "use strict"; + this.checkStarted(); + return this.evaluate(function _evaluate() { + return document.title; + }); +}; + +/** + * Handles received HTTP resource. + * + * @param Object resource PhantomJS HTTP resource + */ +Casper.prototype.handleReceivedResource = function(resource) { + "use strict"; + if (resource.stage !== "end") { + return; + } + this.resources.push(resource); + if (resource.url !== this.requestUrl) { + return; + } + this.currentHTTPStatus = null; + this.currentResponse = undefined; + if (utils.isHTTPResource(resource)) { + this.currentResponse = resource; + this.currentHTTPStatus = resource.status; + this.emit('http.status.' + resource.status, resource); + if (utils.isObject(this.options.httpStatusHandlers) && + resource.status in this.options.httpStatusHandlers && + utils.isFunction(this.options.httpStatusHandlers[resource.status])) { + this.options.httpStatusHandlers[resource.status].call(this, this, resource); + } + } + this.currentUrl = resource.url; + this.emit('location.changed', resource.url); +}; + +/** + * Initializes PhantomJS error handler. + * + */ +Casper.prototype.initErrorHandler = function initErrorHandler() { + "use strict"; + var casper = this; + phantom.onError = function phantom_onError(msg, backtrace) { + casper.emit('error', msg, backtrace); + if (casper.options.exitOnError === true) { + casper.exit(1); + } + }; +}; + +/** + * Injects configured client scripts. + * + * @return Casper + */ +Casper.prototype.injectClientScripts = function injectClientScripts() { + "use strict"; + this.checkStarted(); + if (!this.options.clientScripts) { + return; + } + if (utils.isString(this.options.clientScripts)) { + this.options.clientScripts = [this.options.clientScripts]; + } + if (!utils.isArray(this.options.clientScripts)) { + throw new CasperError("The clientScripts option must be an array"); + } + this.options.clientScripts.forEach(function _forEach(script) { + if (this.page.injectJs(script)) { + this.log(f('Automatically injected %s client side', script), "debug"); + } else { + this.warn('Failed injecting %s client side', script); + } + }.bind(this)); + return this; +}; + +/** + * Injects Client-side utilities in current page context. + * + */ +Casper.prototype.injectClientUtils = function injectClientUtils() { + "use strict"; + this.checkStarted(); + var clientUtilsInjected = this.page.evaluate(function() { + return typeof window.__utils__ === "object"; + }); + if (true === clientUtilsInjected) { + return; + } + var clientUtilsPath = require('fs').pathJoin(phantom.casperPath, 'modules', 'clientutils.js'); + if (true === this.page.injectJs(clientUtilsPath)) { + this.log("Successfully injected Casper client-side utilities", "debug"); + } else { + this.warn("Failed to inject Casper client-side utilities"); + } + // ClientUtils and Casper shares the same options + // These are not the lines I'm the most proud of in my life, but it works. + /*global __options*/ + this.page.evaluate(function() { + window.__utils__ = new window.ClientUtils(__options); + }.toString().replace('__options', JSON.stringify(this.options))); +}; + +/** + * Logs a message. + * + * @param String message The message to log + * @param String level The log message level (from Casper.logLevels property) + * @param String space Space from where the logged event occurred (default: "phantom") + * @return Casper + */ +Casper.prototype.log = function log(message, level, space) { + "use strict"; + level = level && this.logLevels.indexOf(level) > -1 ? level : "debug"; + space = space ? space : "phantom"; + if (level === "error" && utils.isFunction(this.options.onError)) { + this.options.onError.call(this, this, message, space); + } + if (this.logLevels.indexOf(level) < this.logLevels.indexOf(this.options.logLevel)) { + return this; // skip logging + } + var entry = { + level: level, + space: space, + message: message, + date: new Date().toString() + }; + if (level in this.logFormats && utils.isFunction(this.logFormats[level])) { + message = this.logFormats[level](message, level, space); + } else { + message = f('%s [%s] %s', + this.colorizer.colorize(f('[%s]', level), this.logStyles[level]), + space, + message); + } + if (this.options.verbose) { + this.echo(this.filter('log.message', message) || message); // direct output + } + this.result.log.push(entry); + this.emit('log', entry); + return this; +}; + +/** + * Emulates an event on the element from the provided selector using the mouse + * pointer, if possible. + * + * In case of success, `true` is returned, `false` otherwise. + * + * @param String type Type of event to emulate + * @param String selector A DOM CSS3 compatible selector + * @return Boolean + */ +Casper.prototype.mouseEvent = function mouseEvent(type, selector) { + "use strict"; + this.checkStarted(); + this.log("Mouse event '" + type + "' on selector: " + selector, "debug"); + if (!this.exists(selector)) { + throw new CasperError(f("Cannot dispatch %s event on nonexistent selector: %s", type, selector)); + } + var eventSuccess = this.evaluate(function(type, selector) { + return window.__utils__.mouseEvent(type, selector); + }, { + type: type, + selector: selector + }); + if (!eventSuccess) { + // fallback onto native QtWebKit mouse events + try { + this.mouse.processEvent(type, selector); + } catch (e) { + this.log(f("Couldn't emulate '%s' event on %s: %s", type, selector, e), "error"); + return false; + } + } + return true; +}; + +/** + * Performs an HTTP request, with optional settings. + * + * Available settings are: + * + * - String method: The HTTP method to use + * - Object data: The data to use to perform the request, eg. {foo: 'bar'} + * - Array headers: An array of request headers, eg. [{'Cache-Control': 'max-age=0'}] + * + * @param String location The url to open + * @param Object settings The request settings (optional) + * @return Casper + */ +Casper.prototype.open = function open(location, settings) { + "use strict"; + this.checkStarted(); + // settings validation + if (!settings) { + settings = { + method: "get" + }; + } + if (!utils.isObject(settings)) { + throw new CasperError("open(): request settings must be an Object"); + } + // http method + // taken from https://github.com/ariya/phantomjs/blob/master/src/webpage.cpp#L302 + var methods = ["get", "head", "put", "post", "delete"]; + if (settings.method && (!utils.isString(settings.method) || methods.indexOf(settings.method) === -1)) { + throw new CasperError("open(): settings.method must be part of " + methods.join(', ')); + } + // http data + if (settings.data) { + if (utils.isObject(settings.data)) { // query object + settings.data = qs.encode(settings.data); + } else if (!utils.isString(settings.data)) { + throw new CasperError("open(): invalid request settings data value: " + settings.data); + } + } + // clean location + location = utils.cleanUrl(location); + // current request url + this.requestUrl = this.filter('open.location', location) || location; + // http auth + if (settings.username && settings.password) { + this.setHttpAuth(settings.username, settings.password); + } else { + var httpAuthMatch = location.match(/^https?:\/\/(.+):(.+)@/i); + if (httpAuthMatch) { + var httpAuth = { + username: httpAuthMatch[1], + password: httpAuthMatch[2] + }; + this.setHttpAuth(httpAuth.username, httpAuth.password); + } + } + this.emit('open', this.requestUrl, settings); + this.log(f('opening url: %s, HTTP %s', this.requestUrl, settings.method.toUpperCase()), "debug"); + this.page.openUrl(this.requestUrl, { + operation: settings.method, + data: settings.data, + headers: settings.headers + }, this.page.settings); + this.resources = []; + return this; +}; + +/** + * Reloads current page. + * + * @param Function then a next step function + * @return Casper + */ +Casper.prototype.reload = function reload(then) { + "use strict"; + this.checkStarted(); + // window.location.reload() is broken under phantomjs + this.then(function() { + this.open(this.getCurrentUrl()); + }); + if (utils.isFunction(then)) { + this.then(this.createStep(then)); + } +}; + +/** + * Repeats a step a given number of times. + * + * @param Number times Number of times to repeat step + * @aram function then The step closure + * @return Casper + * @see Casper#then + */ +Casper.prototype.repeat = function repeat(times, then) { + "use strict"; + for (var i = 0; i < times; i++) { + this.then(then); + } + return this; +}; + +/** + * Checks if a given resource was loaded by the remote page. + * + * @param Function/String/RegExp test A test function, string or regular expression. + * In case a string is passed, url matching will be tested. + * @return Boolean + */ +Casper.prototype.resourceExists = function resourceExists(test) { + "use strict"; + this.checkStarted(); + var testFn; + switch (utils.betterTypeOf(test)) { + case "string": + testFn = function _testResourceExists_String(res) { + return res.url.search(test) !== -1 && res.status !== 404; + }; + break; + case "regexp": + testFn = function _testResourceExists_Regexp(res) { + return test.test(res.url) && res.status !== 404; + }; + break; + case "function": + testFn = test; + testFn.name = "_testResourceExists_Function"; + break; + default: + throw new CasperError("Invalid type"); + } + return this.resources.some(testFn); +}; + +/** + * Runs the whole suite of steps. + * + * @param function onComplete an optional callback + * @param Number time an optional amount of milliseconds for interval checking + * @return Casper + */ +Casper.prototype.run = function run(onComplete, time) { + "use strict"; + this.checkStarted(); + if (!this.steps || this.steps.length < 1) { + this.log("No steps defined, aborting", "error"); + return this; + } + this.log(f("Running suite: %d step%s", this.steps.length, this.steps.length > 1 ? "s" : ""), "info"); + this.emit('run.start'); + this.checker = setInterval(this.checkStep, (time ? time: 100), this, onComplete); + return this; +}; + +/** + * Runs a step. + * + * @param Function step + */ +Casper.prototype.runStep = function runStep(step) { + "use strict"; + this.checkStarted(); + var skipLog = utils.isObject(step.options) && step.options.skipLog === true; + var stepInfo = f("Step %d/%d", this.step, this.steps.length); + var stepResult; + if (!skipLog && /^http/.test(this.getCurrentUrl())) { + this.log(stepInfo + f(' %s (HTTP %d)', this.getCurrentUrl(), this.currentHTTPStatus), "info"); + } + if (utils.isNumber(this.options.stepTimeout) && this.options.stepTimeout > 0) { + var stepTimeoutCheckInterval = setInterval(function _check(self, start, stepNum) { + if (new Date().getTime() - start > self.options.stepTimeout) { + if ((self.test.currentSuiteNum + "-" + self.step) === stepNum) { + self.emit('step.timeout'); + if (utils.isFunction(self.options.onStepTimeout)) { + self.options.onStepTimeout.call(self, self.options.onStepTimeout, stepNum); + } + } + clearInterval(stepTimeoutCheckInterval); + } + }, this.options.stepTimeout, this, new Date().getTime(), this.test.currentSuiteNum + "-" + this.step); + } + this.emit('step.start', step); + stepResult = step.call(this, this.currentResponse); + if (utils.isFunction(this.options.onStepComplete)) { + this.options.onStepComplete.call(this, this, stepResult); + } + if (!skipLog) { + this.emit('step.complete', stepResult); + this.log(stepInfo + f(": done in %dms.", new Date().getTime() - this.startTime), "info"); + } +}; + +/** + * Sets HTTP authentication parameters. + * + * @param String username The HTTP_AUTH_USER value + * @param String password The HTTP_AUTH_PW value + * @return Casper + */ +Casper.prototype.setHttpAuth = function setHttpAuth(username, password) { + "use strict"; + this.checkStarted(); + if (!utils.isString(username) || !utils.isString(password)) { + throw new CasperError("Both username and password must be strings"); + } + this.page.settings.userName = username; + this.page.settings.password = password; + this.emit('http.auth', username, password); + this.log("Setting HTTP authentication for user " + username, "info"); + return this; +}; + +/** + * Configures and starts Casper. + * + * @param String location An optional location to open on start + * @param function then Next step function to execute on page loaded (optional) + * @return Casper + */ +Casper.prototype.start = function start(location, then) { + "use strict"; + this.emit('starting'); + this.log('Starting...', "info"); + this.startTime = new Date().getTime(); + this.history = []; + this.steps = []; + this.step = 0; + // Option checks + if (this.logLevels.indexOf(this.options.logLevel) < 0) { + this.log(f("Unknown log level '%d', defaulting to 'warning'", this.options.logLevel), "warning"); + this.options.logLevel = "warning"; + } + // WebPage + if (!utils.isWebPage(this.page)) { + if (utils.isWebPage(this.options.page)) { + this.page = this.options.page; + } else { + this.page = createPage(this); + } + } + this.page.settings = utils.mergeObjects(this.page.settings, this.options.pageSettings); + if (utils.isClipRect(this.options.clipRect)) { + this.page.clipRect = this.options.clipRect; + } + if (utils.isObject(this.options.viewportSize)) { + this.page.viewportSize = this.options.viewportSize; + } + this.started = true; + this.emit('started'); + if (utils.isNumber(this.options.timeout) && this.options.timeout > 0) { + this.log(f("Execution timeout set to %dms", this.options.timeout), "info"); + setTimeout(function _check(self) { + self.emit('timeout'); + if (utils.isFunction(self.options.onTimeout)) { + self.options.onTimeout.call(self, self.options.timeout); + } + }, this.options.timeout, this); + } + if (utils.isString(location) && location.length > 0) { + return this.thenOpen(location, utils.isFunction(then) ? then : this.createStep(function _step() { + this.log("start page is loaded", "debug"); + })); + } + return this; +}; + +/** + * Returns the current status of current instance + * + * @param Boolean asString Export status object as string + * @return Object + */ +Casper.prototype.status = function status(asString) { + "use strict"; + var properties = ['currentHTTPStatus', 'loadInProgress', 'navigationRequested', + 'options', 'pendingWait', 'requestUrl', 'started', 'step', 'url']; + var currentStatus = {}; + properties.forEach(function(property) { + currentStatus[property] = this[property]; + }.bind(this)); + return asString === true ? utils.dump(currentStatus) : currentStatus; +}; + +/** + * Schedules the next step in the navigation process. + * + * @param function step A function to be called as a step + * @return Casper + */ +Casper.prototype.then = function then(step) { + "use strict"; + this.checkStarted(); + if (!utils.isFunction(step)) { + throw new CasperError("You can only define a step as a function"); + } + // check if casper is running + if (this.checker === null) { + // append step to the end of the queue + step.level = 0; + this.steps.push(step); + } else { + // insert substep a level deeper + try { + step.level = this.steps[this.step - 1].level + 1; + } catch (e) { + step.level = 0; + } + var insertIndex = this.step; + while (this.steps[insertIndex] && step.level === this.steps[insertIndex].level) { + insertIndex++; + } + this.steps.splice(insertIndex, 0, step); + } + this.emit('step.added', step); + return this; +}; + +/** + * Adds a new navigation step for clicking on a provided link selector + * and execute an optional next step. + * + * @param String selector A DOM CSS3 compatible selector + * @param Function then Next step function to execute on page loaded (optional) + * @return Casper + * @see Casper#click + * @see Casper#then + */ +Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref) { + "use strict"; + this.checkStarted(); + if (arguments.length > 2) { + this.emit("deprecated", "The thenClick() method does not process the fallbackToHref argument since 0.6"); + } + this.then(function _step() { + this.click(selector); + }); + return utils.isFunction(then) ? this.then(then) : this; +}; + +/** + * Adds a new navigation step to perform code evaluation within the + * current retrieved page DOM. + * + * @param function fn The function to be evaluated within current page DOM + * @param object context Optional function parameters context + * @return Casper + * @see Casper#evaluate + */ +Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) { + "use strict"; + this.checkStarted(); + return this.then(function _step() { + this.evaluate(fn, context); + }); +}; + +/** + * Adds a new navigation step for opening the provided location. + * + * @param String location The URL to load + * @param function then Next step function to execute on page loaded (optional) + * @return Casper + * @see Casper#open + */ +Casper.prototype.thenOpen = function thenOpen(location, settings, then) { + "use strict"; + this.checkStarted(); + if (!(settings && !utils.isFunction(settings))) { + then = settings; + settings = null; + } + this.then(this.createStep(function _step() { + this.open(location, settings); + }, { + skipLog: true + })); + return utils.isFunction(then) ? this.then(then) : this; +}; + +/** + * Adds a new navigation step for opening and evaluate an expression + * against the DOM retrieved from the provided location. + * + * @param String location The url to open + * @param function fn The function to be evaluated within current page DOM + * @param object context Optional function parameters context + * @return Casper + * @see Casper#evaluate + * @see Casper#open + */ +Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn, context) { + "use strict"; + this.checkStarted(); + return this.thenOpen(location).thenEvaluate(fn, context); +}; + +/** + * Returns a string representation of current instance + * + * @return String + */ +Casper.prototype.toString = function toString() { + "use strict"; + return '[object Casper], currently at ' + this.getCurrentUrl(); +}; + +/** + * Sets the user-agent string currently used when requesting urls. + * + * @param String userAgent User agent string + * @return String + */ +Casper.prototype.userAgent = function userAgent(agent) { + "use strict"; + this.checkStarted(); + this.options.pageSettings.userAgent = this.page.settings.userAgent = agent; + return this; +}; + +/** + * Changes the current viewport size. + * + * @param Number width The viewport width, in pixels + * @param Number height The viewport height, in pixels + * @return Casper + */ +Casper.prototype.viewport = function viewport(width, height) { + "use strict"; + this.checkStarted(); + if (!utils.isNumber(width) || !utils.isNumber(height) || width <= 0 || height <= 0) { + throw new CasperError(f("Invalid viewport: %dx%d", width, height)); + } + this.page.viewportSize = { + width: width, + height: height + }; + this.emit('viewport.changed', [width, height]); + return this; +}; + +/** + * Checks if an element matching the provided DOM CSS3/XPath selector is visible + * current page DOM by checking that offsetWidth and offsetHeight are + * both non-zero. + * + * @param String selector A DOM CSS3/XPath selector + * @return Boolean + */ +Casper.prototype.visible = function visible(selector) { + "use strict"; + this.checkStarted(); + return this.evaluate(function _evaluate(selector) { + return window.__utils__.visible(selector); + }, { selector: selector }); +}; + +/** + * Displays a warning message onto the console and logs the event. + * + * @param String message + * @return Casper + */ +Casper.prototype.warn = function warn(message) { + "use strict"; + this.log(message, "warning", "phantom"); + var formatted = f.apply(null, ["⚠  " + message].concat([].slice.call(arguments, 1))); + return this.echo(formatted, 'COMMENT'); +}; + +/** + * Adds a new step that will wait for a given amount of time (expressed + * in milliseconds) before processing an optional next one. + * + * @param Number timeout The max amount of time to wait, in milliseconds + * @param Function then Next step to process (optional) + * @return Casper + */ +Casper.prototype.wait = function wait(timeout, then) { + "use strict"; + this.checkStarted(); + timeout = ~~timeout; + if (timeout < 1) { + this.die("wait() only accepts a positive integer > 0 as a timeout value"); + } + if (then && !utils.isFunction(then)) { + this.die("wait() a step definition must be a function"); + } + return this.then(function _step() { + this.waitStart(); + setTimeout(function _check(self) { + self.log(f("wait() finished waiting for %dms.", timeout), "info"); + if (then) { + then.call(self, self); + } + self.waitDone(); + }, timeout, this); + }); +}; + +Casper.prototype.waitStart = function waitStart() { + "use strict"; + this.emit('wait.start'); + this.pendingWait = true; +}; + +Casper.prototype.waitDone = function waitDone() { + "use strict"; + this.emit('wait.done'); + this.pendingWait = false; +}; + +/** + * Waits until a function returns true to process a next step. + * + * @param Function testFx A function to be evaluated for returning condition satisfecit + * @param Function then The next step to perform (optional) + * @param Function onTimeout A callback function to call on timeout (optional) + * @param Number timeout The max amount of time to wait, in milliseconds (optional) + * @return Casper + */ +Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) { + "use strict"; + this.checkStarted(); + timeout = timeout ? timeout : this.options.waitTimeout; + if (!utils.isFunction(testFx)) { + this.die("waitFor() needs a test function"); + } + if (then && !utils.isFunction(then)) { + this.die("waitFor() next step definition must be a function"); + } + return this.then(function _step() { + this.waitStart(); + var start = new Date().getTime(); + var condition = false; + var interval = setInterval(function _check(self, testFx, timeout, onTimeout) { + if ((new Date().getTime() - start < timeout) && !condition) { + condition = testFx.call(self, self); + } else { + self.waitDone(); + if (!condition) { + self.log("Casper.waitFor() timeout", "warning"); + self.emit('waitFor.timeout'); + var onWaitTimeout = onTimeout ? onTimeout : self.options.onWaitTimeout; + if (!utils.isFunction(onWaitTimeout)) { + throw new CasperError('Invalid timeout function, exiting.'); + } + onWaitTimeout.call(self, timeout); + } else { + self.log(f("waitFor() finished in %dms.", new Date().getTime() - start), "info"); + if (then) { + self.then(then); + } + } + clearInterval(interval); + } + }, 100, this, testFx, timeout, onTimeout); + }); +}; + +/** + * Waits until a given resource is loaded + * + * @param String/Function test A function to test if the resource exists. + * A string will be matched against the resources url. + * @param Function then The next step to perform (optional) + * @param Function onTimeout A callback function to call on timeout (optional) + * @param Number timeout The max amount of time to wait, in milliseconds (optional) + * @return Casper + */ +Casper.prototype.waitForResource = function waitForResource(test, then, onTimeout, timeout) { + "use strict"; + this.checkStarted(); + timeout = timeout ? timeout : this.options.waitTimeout; + return this.waitFor(function _check() { + return this.resourceExists(test); + }, then, onTimeout, timeout); +}; + +/** + * Waits until an element matching the provided DOM CSS3/XPath selector exists in + * remote DOM to process a next step. + * + * @param String selector A DOM CSS3/XPath selector + * @param Function then The next step to perform (optional) + * @param Function onTimeout A callback function to call on timeout (optional) + * @param Number timeout The max amount of time to wait, in milliseconds (optional) + * @return Casper + */ +Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTimeout, timeout) { + "use strict"; + this.checkStarted(); + timeout = timeout ? timeout : this.options.waitTimeout; + return this.waitFor(function _check() { + return this.exists(selector); + }, then, onTimeout, timeout); +}; + +/** + * Waits until the page contains given HTML text. + * + * @param String text Text to wait for + * @param Function then The next step to perform (optional) + * @param Function onTimeout A callback function to call on timeout (optional) + * @param Number timeout The max amount of time to wait, in milliseconds (optional) + * @return Casper + */ +Casper.prototype.waitForText = function(text, then, onTimeout, timeout) { + "use strict"; + this.checkStarted(); + timeout = timeout ? timeout : this.options.waitTimeout; + return this.waitFor(function _check() { + return this.getPageContent().indexOf(text) !== -1; + }, then, onTimeout, timeout); +}; + +/** + * Waits until an element matching the provided DOM CSS3/XPath selector does not + * exist in the remote DOM to process a next step. + * + * @param String selector A DOM CSS3/XPath selector + * @param Function then The next step to perform (optional) + * @param Function onTimeout A callback function to call on timeout (optional) + * @param Number timeout The max amount of time to wait, in milliseconds (optional) + * @return Casper + */ +Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then, onTimeout, timeout) { + "use strict"; + this.checkStarted(); + timeout = timeout ? timeout : this.options.waitTimeout; + return this.waitFor(function _check() { + return !this.exists(selector); + }, then, onTimeout, timeout); +}; + +/** + * Waits until an element matching the provided DOM CSS3/XPath selector is + * visible in the remote DOM to process a next step. + * + * @param String selector A DOM CSS3/XPath selector + * @param Function then The next step to perform (optional) + * @param Function onTimeout A callback function to call on timeout (optional) + * @param Number timeout The max amount of time to wait, in milliseconds (optional) + * @return Casper + */ +Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, onTimeout, timeout) { + "use strict"; + this.checkStarted(); + timeout = timeout ? timeout : this.options.waitTimeout; + return this.waitFor(function _check() { + return this.visible(selector); + }, then, onTimeout, timeout); +}; + +/** + * Waits until an element matching the provided DOM CSS3/XPath selector is no + * longer visible in remote DOM to process a next step. + * + * @param String selector A DOM CSS3/XPath selector + * @param Function then The next step to perform (optional) + * @param Function onTimeout A callback function to call on timeout (optional) + * @param Number timeout The max amount of time to wait, in milliseconds (optional) + * @return Casper + */ +Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, onTimeout, timeout) { + "use strict"; + this.checkStarted(); + timeout = timeout ? timeout : this.options.waitTimeout; + return this.waitFor(function _check() { + return !this.visible(selector); + }, then, onTimeout, timeout); +}; + +/** + * Changes the current page zoom factor. + * + * @param Number factor The zoom factor + * @return Casper + */ +Casper.prototype.zoom = function zoom(factor) { + "use strict"; + this.checkStarted(); + if (!utils.isNumber(factor) || factor <= 0) { + throw new CasperError("Invalid zoom factor: " + factor); + } + this.page.zoomFactor = factor; + return this; +}; + +/** + * Extends Casper's prototype with provided one. + * + * @param Object proto Prototype methods to add to Casper + * @deprecated + * @since 0.6 + */ +Casper.extend = function(proto) { + "use strict"; + this.emit("deprecated", "Casper.extend() has been deprecated since 0.6; check the docs") + if (!utils.isObject(proto)) { + throw new CasperError("extends() only accept objects as prototypes"); + } + utils.mergeObjects(Casper.prototype, proto); +}; + +exports.Casper = Casper; + +/** + * Creates a new WebPage instance for Casper use. + * + * @param Casper casper A Casper instance + * @return WebPage + */ +function createPage(casper) { + "use strict"; + var page = require('webpage').create(); + page.onAlert = function onAlert(message) { + casper.log('[alert] ' + message, "info", "remote"); + casper.emit('remote.alert', message); + if (utils.isFunction(casper.options.onAlert)) { + casper.options.onAlert.call(casper, casper, message); + } + }; + page.onConfirm = function onConfirm(message) { + return casper.filter('page.confirm', message) || true; + }; + page.onConsoleMessage = function onConsoleMessage(msg) { + // client utils casper console message + var consoleTest = /^\[casper\.echo\]\s?(.*)/.exec(msg); + if (consoleTest && consoleTest.length === 2) { + casper.echo(consoleTest[1]); + return; // don't trigger remote.message event for these + } + // client utils log messages + var logLevel = "info", logTest = /^\[casper:(\w+)\]\s?(.*)/.exec(msg); + if (logTest && logTest.length === 3) { + logLevel = logTest[1]; + msg = logTest[2]; + } + casper.log(msg, logLevel, "remote"); + casper.emit('remote.message', msg); + }; + page.onError = function onError(msg, trace) { + casper.emit('page.error', msg, trace); + }; + page.onInitialized = function onInitialized() { + casper.emit('page.initialized', this); + if (utils.isFunction(casper.options.onPageInitialized)) { + this.log("Post-configuring WebPage instance", "debug"); + casper.options.onPageInitialized.call(casper, page); + } + }; + page.onLoadStarted = function onLoadStarted() { + casper.loadInProgress = true; + casper.emit('load.started'); + }; + page.onLoadFinished = function onLoadFinished(status) { + if (status !== "success") { + casper.emit('load.failed', { + status: status, + http_status: casper.currentHTTPStatus, + url: casper.requestUrl + }); + var message = 'Loading resource failed with status=' + status; + if (casper.currentHTTPStatus) { + message += f(' (HTTP %d)', casper.currentHTTPStatus); + } + message += ': ' + casper.requestUrl; + casper.log(message, "warning"); + casper.navigationRequested = false; + if (utils.isFunction(casper.options.onLoadError)) { + casper.options.onLoadError.call(casper, casper, casper.requestUrl, status); + } + } + casper.injectClientScripts(); + // Client-side utils injection + casper.injectClientUtils(); + // history + casper.history.push(casper.getCurrentUrl()); + casper.emit('load.finished', status); + casper.loadInProgress = false; + }; + page.onNavigationRequested = function onNavigationRequested(url, navigationType, navigationLocked, isMainFrame) { + casper.log(f('Navigation requested: url=%s, type=%s, lock=%s, isMainFrame=%s', + url, navigationType, navigationLocked, isMainFrame), "debug"); + if(isMainFrame) { + casper.navigationRequested = true; + } + casper.emit('navigation.requested', url, navigationType, navigationLocked, isMainFrame); + }; + page.onPrompt = function onPrompt(message, value) { + return casper.filter('page.prompt', message, value); + }; + page.onResourceReceived = function onResourceReceived(resource) { + http.augmentResponse(resource); + casper.emit('resource.received', resource); + if (utils.isFunction(casper.options.onResourceReceived)) { + casper.options.onResourceReceived.call(casper, casper, resource); + } + casper.handleReceivedResource(resource); + }; + page.onResourceRequested = function onResourceRequested(request) { + casper.emit('resource.requested', request); + if (utils.isFunction(casper.options.onResourceRequested)) { + casper.options.onResourceRequested.call(casper, casper, request); + } + }; + page.onUrlChanged = function onUrlChanged(url) { + casper.log(f('url changed to "%s"', url), "debug"); + casper.navigationRequested = false; + casper.emit('url.changed', url); + }; + casper.emit('page.created', page); + return page; +} diff --git a/zephyr/tests/frontend/casperjs/modules/cli.js b/zephyr/tests/frontend/casperjs/modules/cli.js new file mode 100644 index 0000000000..52d7404e8d --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/cli.js @@ -0,0 +1,138 @@ +/*! + * Casper is a navigation utility for PhantomJS. + * + * Documentation: http://casperjs.org/ + * Repository: http://github.com/n1k0/casperjs + * + * Copyright (c) 2011-2012 Nicolas Perriault + * + * Part of source code is Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*global CasperError console exports phantom require*/ + +var system = require('system'); +var utils = require('utils'); + +/** + * Extracts, normalize ad organize PhantomJS CLI arguments in a dedicated + * Object. + * + * @param array phantomArgs system.args value + * @return Object + */ +exports.parse = function parse(phantomArgs) { + "use strict"; + var extract = { + args: [], + options: {}, + raw: { + args: [], + options: {} + }, + drop: function drop(what) { + if (utils.isNumber(what)) { + // deleting an arg by its position + this.args = this.args.filter(function _filter(arg, index) { + return index !== what; + }); + } else if (utils.isString(what)) { + // deleting an arg by its value + this.args = this.args.filter(function _filter(arg) { + return arg !== what; + }); + // deleting an option by its name (key) + var self = this; + Object.keys(this.options).forEach(function _forEach(option) { + if (option === what) { + delete self.options[what]; + } + }); + } else { + throw new CasperError("cannot drop argument of type " + typeof what); + } + }, + has: function has(what) { + if (utils.isNumber(what)) { + return what in this.args; + } else if (utils.isString(what)) { + return what in this.options; + } else { + throw new CasperError("Unsupported cli arg tester " + typeof what); + } + }, + get: function get(what) { + if (utils.isNumber(what)) { + return this.args[what]; + } else if (utils.isString(what)) { + return this.options[what]; + } else { + throw new CasperError("Unsupported cli arg getter " + typeof what); + } + } + }; + phantomArgs.forEach(function _forEach(arg) { + if (arg.indexOf('--') === 0) { + // named option + var optionMatch = arg.match(/^--(.*?)=(.*)/i); + if (optionMatch) { + extract.options[optionMatch[1]] = castArgument(optionMatch[2]); + extract.raw.options[optionMatch[1]] = optionMatch[2]; + } else { + // flag + var flagMatch = arg.match(/^--(.*)/); + if (flagMatch) { + extract.options[flagMatch[1]] = extract.raw.options[flagMatch[1]] = true; + } + } + } else { + // positional arg + extract.args.push(castArgument(arg)); + extract.raw.args.push(castArgument(arg)); + } + }); + extract.raw = utils.mergeObjects(extract.raw, { + drop: extract.drop, + has: extract.has, + get: extract.get + }); + return extract; +}; + +/** + * Cast a string argument to its typed equivalent. + * + * @param String arg + * @return Mixed + */ +function castArgument(arg) { + "use strict"; + if (arg.match(/^-?\d+$/)) { + return parseInt(arg, 10); + } else if (arg.match(/^-?\d+\.\d+$/)) { + return parseFloat(arg); + } else if (arg.match(/^(true|false)$/i)) { + return arg.trim().toLowerCase() === "true" ? true : false; + } else { + return arg; + } +} diff --git a/zephyr/tests/frontend/casperjs/modules/clientutils.js b/zephyr/tests/frontend/casperjs/modules/clientutils.js new file mode 100644 index 0000000000..2582186373 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/clientutils.js @@ -0,0 +1,731 @@ +/*! + * Casper is a navigation utility for PhantomJS. + * + * Documentation: http://casperjs.org/ + * Repository: http://github.com/n1k0/casperjs + * + * Copyright (c) 2011-2012 Nicolas Perriault + * + * Part of source code is Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*global console escape exports NodeList window*/ + +(function(exports) { + "use strict"; + + exports.create = function create(options) { + return new this.ClientUtils(options); + }; + + /** + * Casper client-side helpers. + */ + exports.ClientUtils = function ClientUtils(options) { + // private members + var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var BASE64_DECODE_CHARS = new Array( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 + ); + var SUPPORTED_SELECTOR_TYPES = ['css', 'xpath']; + + // public members + this.options = options || {}; + this.options.scope = this.options.scope || document; + /** + * Clicks on the DOM element behind the provided selector. + * + * @param String selector A CSS3 selector to the element to click + * @return Boolean + */ + this.click = function click(selector) { + return this.mouseEvent('click', selector); + }; + + /** + * Decodes a base64 encoded string. Succeeds where window.atob() fails. + * + * @param String str The base64 encoded contents + * @return string + */ + this.decode = function decode(str) { + /*jshint maxstatements:30 maxcomplexity:30 */ + var c1, c2, c3, c4, i = 0, len = str.length, out = ""; + while (i < len) { + do { + c1 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff]; + } while (i < len && c1 === -1); + if (c1 === -1) { + break; + } + do { + c2 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff]; + } while (i < len && c2 === -1); + if (c2 === -1) { + break; + } + out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); + do { + c3 = str.charCodeAt(i++) & 0xff; + if (c3 === 61) + return out; + c3 = BASE64_DECODE_CHARS[c3]; + } while (i < len && c3 === -1); + if (c3 === -1) { + break; + } + out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); + do { + c4 = str.charCodeAt(i++) & 0xff; + if (c4 === 61) { + return out; + } + c4 = BASE64_DECODE_CHARS[c4]; + } while (i < len && c4 === -1); + if (c4 === -1) { + break; + } + out += String.fromCharCode(((c3 & 0x03) << 6) | c4); + } + return out; + }; + + /** + * Echoes something to casper console. + * + * @param String message + * @return + */ + this.echo = function echo(message) { + console.log("[casper.echo] " + message); + }; + + /** + * Base64 encodes a string, even binary ones. Succeeds where + * window.btoa() fails. + * + * @param String str The string content to encode + * @return string + */ + this.encode = function encode(str) { + /*jshint maxstatements:30 */ + var out = "", i = 0, len = str.length, c1, c2, c3; + while (i < len) { + c1 = str.charCodeAt(i++) & 0xff; + if (i === len) { + out += BASE64_ENCODE_CHARS.charAt(c1 >> 2); + out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4); + out += "=="; + break; + } + c2 = str.charCodeAt(i++); + if (i === len) { + out += BASE64_ENCODE_CHARS.charAt(c1 >> 2); + out += BASE64_ENCODE_CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); + out += BASE64_ENCODE_CHARS.charAt((c2 & 0xF) << 2); + out += "="; + break; + } + c3 = str.charCodeAt(i++); + out += BASE64_ENCODE_CHARS.charAt(c1 >> 2); + out += BASE64_ENCODE_CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); + out += BASE64_ENCODE_CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)); + out += BASE64_ENCODE_CHARS.charAt(c3 & 0x3F); + } + return out; + }; + + /** + * Checks if a given DOM element exists in remote page. + * + * @param String selector CSS3 selector + * @return Boolean + */ + this.exists = function exists(selector) { + try { + return this.findAll(selector).length > 0; + } catch (e) { + return false; + } + }; + + /** + * Fetches innerText within the element(s) matching a given CSS3 + * selector. + * + * @param String selector A CSS3 selector + * @return String + */ + this.fetchText = function fetchText(selector) { + var text = '', elements = this.findAll(selector); + if (elements && elements.length) { + Array.prototype.forEach.call(elements, function _forEach(element) { + text += element.textContent || element.innerText; + }); + } + return text; + }; + + /** + * Fills a form with provided field values, and optionnaly submits it. + * + * @param HTMLElement|String form A form element, or a CSS3 selector to a form element + * @param Object vals Field values + * @return Object An object containing setting result for each field, including file uploads + */ + this.fill = function fill(form, vals) { + var out = { + errors: [], + fields: [], + files: [] + }; + if (!(form instanceof HTMLElement) || typeof form === "string") { + this.log("attempting to fetch form element from selector: '" + form + "'", "info"); + try { + form = this.findOne(form); + } catch (e) { + if (e.name === "SYNTAX_ERR") { + out.errors.push("invalid form selector provided: '" + form + "'"); + return out; + } + } + } + if (!form) { + out.errors.push("form not found"); + return out; + } + for (var name in vals) { + if (!vals.hasOwnProperty(name)) { + continue; + } + var field = this.findAll('[name="' + name + '"]', form); + var value = vals[name]; + if (!field || field.length === 0) { + out.errors.push('no field named "' + name + '" in form'); + continue; + } + try { + out.fields[name] = this.setField(field, value); + } catch (err) { + if (err.name === "FileUploadError") { + out.files.push({ + name: name, + path: err.path + }); + } else if(err.name === "FieldNotFound") { + out.errors.push('Form field named "' + name + '" was not found.'); + } else { + out.errors.push(err.toString()); + } + } + } + return out; + }; + + /** + * Finds all DOM elements matching by the provided selector. + * + * @param String selector CSS3 selector + * @param HTMLElement|null scope Element to search child elements within + * @return NodeList|undefined + */ + this.findAll = function findAll(selector, scope) { + scope = scope || this.options.scope; + try { + var pSelector = this.processSelector(selector); + if (pSelector.type === 'xpath') { + return this.getElementsByXPath(pSelector.path); + } else { + return scope.querySelectorAll(pSelector.path); + } + } catch (e) { + this.log('findAll(): invalid selector provided "' + selector + '":' + e, "error"); + } + }; + + /** + * Finds a DOM element by the provided selector. + * + * @param String selector CSS3 selector + * @param HTMLElement|null scope Element to search child elements within + * @return HTMLElement|undefined + */ + this.findOne = function findOne(selector, scope) { + scope = scope || this.options.scope; + try { + var pSelector = this.processSelector(selector); + if (pSelector.type === 'xpath') { + return this.getElementByXPath(pSelector.path); + } else { + return scope.querySelector(pSelector.path); + } + } catch (e) { + this.log('findOne(): invalid selector provided "' + selector + '":' + e, "error"); + } + }; + + /** + * Downloads a resource behind an url and returns its base64-encoded + * contents. + * + * @param String url The resource url + * @param String method The request method, optional (default: GET) + * @param Object data The request data, optional + * @return String Base64 contents string + */ + this.getBase64 = function getBase64(url, method, data) { + return this.encode(this.getBinary(url, method, data)); + }; + + /** + * Retrieves string contents from a binary file behind an url. Silently + * fails but log errors. + * + * @param String url Url. + * @param String method HTTP method. + * @param Object data Request parameters. + * @return String + */ + this.getBinary = function getBinary(url, method, data) { + try { + return this.sendAJAX(url, method, data, false); + } catch (e) { + if (e.name === "NETWORK_ERR" && e.code === 101) { + this.log("getBinary(): Unfortunately, casperjs cannot make cross domain ajax requests", "warning"); + } + this.log("getBinary(): Error while fetching " + url + ": " + e, "error"); + return ""; + } + }; + + /** + * Retrieves total document height. + * http://james.padolsey.com/javascript/get-document-height-cross-browser/ + * + * @return {Number} + */ + this.getDocumentHeight = function getDocumentHeight() { + return Math.max( + Math.max(document.body.scrollHeight, document.documentElement.scrollHeight), + Math.max(document.body.offsetHeight, document.documentElement.offsetHeight), + Math.max(document.body.clientHeight, document.documentElement.clientHeight) + ); + }; + + /** + * Retrieves bounding rect coordinates of the HTML element matching the + * provided CSS3 selector in the following form: + * + * {top: y, left: x, width: w, height:, h} + * + * @param String selector + * @return Object or null + */ + this.getElementBounds = function getElementBounds(selector) { + try { + var clipRect = this.findOne(selector).getBoundingClientRect(); + return { + top: clipRect.top, + left: clipRect.left, + width: clipRect.width, + height: clipRect.height + }; + } catch (e) { + this.log("Unable to fetch bounds for element " + selector, "warning"); + } + }; + + /** + * Retrieves the list of bounding rect coordinates for all the HTML elements matching the + * provided CSS3 selector, in the following form: + * + * [{top: y, left: x, width: w, height:, h}, + * {top: y, left: x, width: w, height:, h}, + * ...] + * + * @param String selector + * @return Array + */ + this.getElementsBounds = function getElementsBounds(selector) { + var elements = this.findAll(selector); + var self = this; + try { + return Array.prototype.map.call(elements, function(element) { + var clipRect = element.getBoundingClientRect(); + return { + top: clipRect.top, + left: clipRect.left, + width: clipRect.width, + height: clipRect.height + }; + }); + } catch (e) { + this.log("Unable to fetch bounds for elements matching " + selector, "warning"); + } + }; + + /** + * Retrieves a single DOM element matching a given XPath expression. + * + * @param String expression The XPath expression + * @return HTMLElement or null + */ + this.getElementByXPath = function getElementByXPath(expression) { + var a = document.evaluate(expression, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + if (a.snapshotLength > 0) { + return a.snapshotItem(0); + } + }; + + /** + * Retrieves all DOM elements matching a given XPath expression. + * + * @param String expression The XPath expression + * @return Array + */ + this.getElementsByXPath = function getElementsByXPath(expression) { + var nodes = []; + var a = document.evaluate(expression, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0; i < a.snapshotLength; i++) { + nodes.push(a.snapshotItem(i)); + } + return nodes; + }; + + /** + * Retrieves the value of a form field. + * + * @param String inputName The for input name attr value + * @return Mixed + */ + this.getFieldValue = function getFieldValue(inputName) { + function getSingleValue(input) { + try { + type = input.getAttribute('type').toLowerCase(); + } catch (e) { + type = 'other'; + } + if (['checkbox', 'radio'].indexOf(type) === -1) { + return input.value; + } + // single checkbox or… radio button (weird, I know) + if (input.hasAttribute('value')) { + return input.checked ? input.getAttribute('value') : undefined; + } + return input.checked; + } + function getMultipleValues(inputs) { + type = inputs[0].getAttribute('type').toLowerCase(); + if (type === 'radio') { + var value; + [].forEach.call(inputs, function(radio) { + value = radio.checked ? radio.value : undefined; + }); + return value; + } else if (type === 'checkbox') { + var values = []; + [].forEach.call(inputs, function(checkbox) { + if (checkbox.checked) { + values.push(checkbox.value); + } + }); + return values; + } + } + var inputs = this.findAll('[name="' + inputName + '"]'), type; + switch (inputs.length) { + case 0: return null; + case 1: return getSingleValue(inputs[0]); + default: return getMultipleValues(inputs); + } + }; + + /** + * Logs a message. Will format the message a way CasperJS will be able + * to log phantomjs side. + * + * @param String message The message to log + * @param String level The log level + */ + this.log = function log(message, level) { + console.log("[casper:" + (level || "debug") + "] " + message); + }; + + /** + * Dispatches a mouse event to the DOM element behind the provided selector. + * + * @param String type Type of event to dispatch + * @param String selector A CSS3 selector to the element to click + * @return Boolean + */ + this.mouseEvent = function mouseEvent(type, selector) { + var elem = this.findOne(selector); + if (!elem) { + this.log("mouseEvent(): Couldn't find any element matching '" + selector + "' selector", "error"); + return false; + } + try { + var evt = document.createEvent("MouseEvents"); + var center_x = 1, center_y = 1; + try { + var pos = elem.getBoundingClientRect(); + center_x = Math.floor((pos.left + pos.right) / 2), + center_y = Math.floor((pos.top + pos.bottom) / 2); + } catch(e) {} + evt.initMouseEvent(type, true, true, window, 1, 1, 1, center_x, center_y, false, false, false, false, 0, elem); + // dispatchEvent return value is false if at least one of the event + // handlers which handled this event called preventDefault; + // so we cannot returns this results as it cannot accurately informs on the status + // of the operation + // let's assume the event has been sent ok it didn't raise any error + elem.dispatchEvent(evt); + return true; + } catch (e) { + this.log("Failed dispatching " + type + "mouse event on " + selector + ": " + e, "error"); + return false; + } + }; + + /** + * Processes a selector input, either as a string or an object. + * + * If passed an object, if must be of the form: + * + * selectorObject = { + * type: <'css' or 'xpath'>, + * path: + * } + * + * @param String|Object selector The selector string or object + * + * @return an object containing 'type' and 'path' keys + */ + this.processSelector = function processSelector(selector) { + var selectorObject = { + toString: function toString() { + return this.type + ' selector: ' + this.path; + } + }; + if (typeof selector === "string") { + // defaults to CSS selector + selectorObject.type = "css"; + selectorObject.path = selector; + return selectorObject; + } else if (typeof selector === "object") { + // validation + if (!selector.hasOwnProperty('type') || !selector.hasOwnProperty('path')) { + throw new Error("Incomplete selector object"); + } else if (SUPPORTED_SELECTOR_TYPES.indexOf(selector.type) === -1) { + throw new Error("Unsupported selector type: " + selector.type); + } + if (!selector.hasOwnProperty('toString')) { + selector.toString = selectorObject.toString; + } + return selector; + } + throw new Error("Unsupported selector type: " + typeof selector); + }; + + /** + * Removes all DOM elements matching a given XPath expression. + * + * @param String expression The XPath expression + * @return Array + */ + this.removeElementsByXPath = function removeElementsByXPath(expression) { + var a = document.evaluate(expression, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0; i < a.snapshotLength; i++) { + a.snapshotItem(i).parentNode.removeChild(a.snapshotItem(i)); + } + }; + + /** + * Performs an AJAX request. + * + * @param String url Url. + * @param String method HTTP method. + * @param Object data Request parameters. + * @param Boolean async Asynchroneous request? (default: false) + * @return String Response text. + */ + this.sendAJAX = function sendAJAX(url, method, data, async) { + var xhr = new XMLHttpRequest(), dataString = ""; + if (typeof method !== "string" || ["GET", "POST"].indexOf(method.toUpperCase()) === -1) { + method = "GET"; + } else { + method = method.toUpperCase(); + } + xhr.open(method, url, !!async); + this.log("getBinary(): Using HTTP method: '" + method + "'", "debug"); + xhr.overrideMimeType("text/plain; charset=x-user-defined"); + if (method === "POST") { + if (typeof data === "object") { + var dataList = []; + for (var k in data) { + dataList.push(encodeURIComponent(k) + "=" + encodeURIComponent(data[k].toString())); + } + dataString = dataList.join('&'); + this.log("sendAJAX(): Using request data: '" + dataString + "'", "debug"); + } else if (typeof data === "string") { + dataString = data; + } + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + } + xhr.send(method === "POST" ? dataString : null); + return xhr.responseText; + }; + + /** + * Sets a field (or a set of fields) value. Fails silently, but log + * error messages. + * + * @param HTMLElement|NodeList field One or more element defining a field + * @param mixed value The field value to set + */ + this.setField = function setField(field, value) { + var logValue, fields, out; + value = logValue = (value || ""); + if (field instanceof NodeList) { + fields = field; + field = fields[0]; + } + if (!(field instanceof HTMLElement)) { + var error = new Error('Invalid field type; only HTMLElement and NodeList are supported'); + error.name = 'FieldNotFound'; + throw error; + } + if (this.options && this.options.safeLogs && field.getAttribute('type') === "password") { + // obfuscate password value + logValue = new Array(value.length + 1).join("*"); + } + this.log('Set "' + field.getAttribute('name') + '" field value to ' + logValue, "debug"); + try { + field.focus(); + } catch (e) { + this.log("Unable to focus() input field " + field.getAttribute('name') + ": " + e, "warning"); + } + var nodeName = field.nodeName.toLowerCase(); + switch (nodeName) { + case "input": + var type = field.getAttribute('type') || "text"; + switch (type.toLowerCase()) { + case "color": + case "date": + case "datetime": + case "datetime-local": + case "email": + case "hidden": + case "month": + case "number": + case "password": + case "range": + case "search": + case "tel": + case "text": + case "time": + case "url": + case "week": + field.value = value; + break; + case "checkbox": + if (fields.length > 1) { + var values = value; + if (!Array.isArray(values)) { + values = [values]; + } + Array.prototype.forEach.call(fields, function _forEach(f) { + f.checked = values.indexOf(f.value) !== -1 ? true : false; + }); + } else { + field.checked = value ? true : false; + } + break; + case "file": + throw { + name: "FileUploadError", + message: "File field must be filled using page.uploadFile", + path: value + }; + case "radio": + if (fields) { + Array.prototype.forEach.call(fields, function _forEach(e) { + e.checked = (e.value === value); + }); + } else { + out = 'Provided radio elements are empty'; + } + break; + default: + out = "Unsupported input field type: " + type; + break; + } + break; + case "select": + case "textarea": + field.value = value; + break; + default: + out = 'Unsupported field type: ' + nodeName; + break; + } + // firing the `change` event + var changeEvent = document.createEvent("HTMLEvents"); + changeEvent.initEvent('change', true, true); + field.dispatchEvent(changeEvent); + // blur the field + try { + field.blur(); + } catch (err) { + this.log("Unable to blur() input field " + field.getAttribute('name') + ": " + err, "warning"); + } + return out; + }; + + /** + * Checks if a given DOM element is visible in remote page. + * + * @param String selector CSS3 selector + * @return Boolean + */ + this.visible = function visible(selector) { + try { + var comp, + el = this.findOne(selector); + + if (el) { + comp = window.getComputedStyle(el, null); + return comp.visibility !== 'hidden' && comp.display !== 'none' && el.offsetHeight > 0 && el.offsetWidth > 0; + } + return false; + } catch (e) { + return false; + } + }; + }; +})(typeof exports === "object" ? exports : window); diff --git a/zephyr/tests/frontend/casperjs/modules/colorizer.js b/zephyr/tests/frontend/casperjs/modules/colorizer.js new file mode 100644 index 0000000000..99d587dcc6 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/colorizer.js @@ -0,0 +1,129 @@ +/*! + * Casper is a navigation utility for PhantomJS. + * + * Documentation: http://casperjs.org/ + * Repository: http://github.com/n1k0/casperjs + * + * Copyright (c) 2011-2012 Nicolas Perriault + * + * Part of source code is Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*global exports console require*/ + +var fs = require('fs'); +var utils = require('utils'); + +exports.create = function create(type) { + "use strict"; + if (!type) { + return; + } + if (!(type in exports)) { + throw new Error(utils.format('Unsupported colorizer type "%s"', type)); + } + return new exports[type](); +}; + +/** + * This is a port of lime colorizer. + * http://trac.symfony-project.org/browser/tools/lime/trunk/lib/lime.php + * + * (c) Fabien Potencier, Symfony project, MIT license + */ +var Colorizer = function Colorizer() { + "use strict"; + var options = { bold: 1, underscore: 4, blink: 5, reverse: 7, conceal: 8 }; + var foreground = { black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37 }; + var background = { black: 40, red: 41, green: 42, yellow: 43, blue: 44, magenta: 45, cyan: 46, white: 47 }; + var styles = { + 'ERROR': { bg: 'red', fg: 'white', bold: true }, + 'INFO': { fg: 'green', bold: true }, + 'TRACE': { fg: 'green', bold: true }, + 'PARAMETER': { fg: 'cyan' }, + 'COMMENT': { fg: 'yellow' }, + 'WARNING': { fg: 'red', bold: true }, + 'GREEN_BAR': { fg: 'white', bg: 'green', bold: true }, + 'RED_BAR': { fg: 'white', bg: 'red', bold: true }, + 'INFO_BAR': { bg: 'cyan', fg: 'white', bold: true } + }; + + /** + * Adds a style to provided text. + * + * @param String text + * @param String styleName + * @return String + */ + this.colorize = function colorize(text, styleName, pad) { + if (fs.isWindows() || !(styleName in styles)) { + return text; + } + return this.format(text, styles[styleName], pad); + }; + + /** + * Formats a text using a style declaration object. + * + * @param String text + * @param Object style + * @return String + */ + this.format = function format(text, style, pad) { + if (fs.isWindows() || !utils.isObject(style)) { + return text; + } + var codes = []; + if (style.fg && foreground[style.fg]) { + codes.push(foreground[style.fg]); + } + if (style.bg && background[style.bg]) { + codes.push(background[style.bg]); + } + for (var option in options) { + if (style[option] === true) { + codes.push(options[option]); + } + } + // pad + if (typeof pad === "number" && text.length < pad) { + text += new Array(pad - text.length + 1).join(' '); + } + return "\u001b[" + codes.join(';') + 'm' + text + "\u001b[0m"; + }; +}; +exports.Colorizer = Colorizer; + +/** + * Dummy colorizer. Does basically nothing. + * + */ +var Dummy = function Dummy() { + "use strict"; + this.colorize = function colorize(text, styleName, pad) { + return text; + }; + this.format = function format(text, style, pad){ + return text; + }; +}; +exports.Dummy = Dummy; diff --git a/zephyr/tests/frontend/casperjs/modules/events.js b/zephyr/tests/frontend/casperjs/modules/events.js new file mode 100644 index 0000000000..8840957bc6 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/events.js @@ -0,0 +1,246 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var isArray = Array.isArray; + +function EventEmitter() { + this._filters = {}; +} +exports.EventEmitter = EventEmitter; + +// By default EventEmitters will print a warning if more than +// 10 listeners are added to it. This is a useful default which +// helps finding memory leaks. +// +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +var defaultMaxListeners = 10; +EventEmitter.prototype.setMaxListeners = function(n) { + if (!this._events) this._events = {}; + this._maxListeners = n; +}; + + +EventEmitter.prototype.emit = function() { + var type = arguments[0]; + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events || !this._events.error || + (isArray(this._events.error) && !this._events.error.length)) + { + if (arguments[1] instanceof Error) { + throw arguments[1]; // Unhandled 'error' event + } else { + throw new CasperError("Uncaught, unspecified 'error' event."); + } + } + } + + if (!this._events) return false; + var handler = this._events[type]; + if (!handler) return false; + + if (typeof handler == 'function') { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + var l = arguments.length; + var args = new Array(l - 1); + for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; + handler.apply(this, args); + } + return true; + + } else if (isArray(handler)) { + var l = arguments.length; + var args = new Array(l - 1); + for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; + + var listeners = handler.slice(); + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + return true; + + } else { + return false; + } +}; + +// EventEmitter is defined in src/node_events.cc +// EventEmitter.prototype.emit() is also defined there. +EventEmitter.prototype.addListener = function(type, listener) { + if ('function' !== typeof listener) { + throw new CasperError('addListener only takes instances of Function'); + } + + if (!this._events) this._events = {}; + + // To avoid recursion in the case that type == "newListeners"! Before + // adding it to the listeners, first emit "newListeners". + this.emit('newListener', type, listener); + + if (!this._events[type]) { + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + } else if (isArray(this._events[type])) { + + // If we've already got an array, just append. + this._events[type].push(listener); + + // Check for listener leak + if (!this._events[type].warned) { + var m; + if (this._maxListeners !== undefined) { + m = this._maxListeners; + } else { + m = defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + console.trace(); + } + } + } else { + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if ('function' !== typeof listener) { + throw new CasperError('.once only takes instances of Function'); + } + + var self = this; + function g() { + self.removeListener(type, g); + listener.apply(this, arguments); + }; + + g.listener = listener; + self.on(type, g); + + return this; +}; + +EventEmitter.prototype.removeListener = function(type, listener) { + if ('function' !== typeof listener) { + throw new CasperError('removeListener only takes instances of Function'); + } + + // does not use listeners(), so no side effect of creating _events[type] + if (!this._events || !this._events[type]) return this; + + var list = this._events[type]; + + if (isArray(list)) { + var position = -1; + for (var i = 0, length = list.length; i < length; i++) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) + { + position = i; + break; + } + } + + if (position < 0) return this; + list.splice(position, 1); + if (list.length == 0) + delete this._events[type]; + } else if (list === listener || + (list.listener && list.listener === listener)) + { + delete this._events[type]; + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + if (arguments.length === 0) { + this._events = {}; + return this; + } + + // does not use listeners(), so no side effect of creating _events[type] + if (type && this._events && this._events[type]) this._events[type] = null; + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + if (!this._events) this._events = {}; + if (!this._events[type]) this._events[type] = []; + if (!isArray(this._events[type])) { + this._events[type] = [this._events[type]]; + } + return this._events[type]; +}; + +// Added for CasperJS: filters a value attached to an event +EventEmitter.prototype.filter = function() { + var type = arguments[0]; + if (!this._filters) { + this._filters = {}; + return; + } + + var filter = this._filters[type]; + if (typeof filter !== 'function') { + return; + } + return filter.apply(this, Array.prototype.splice.call(arguments, 1)); +}; + +EventEmitter.prototype.setFilter = function(type, filterFn) { + if (!this._filters) { + this._filters = {}; + } + if ('function' !== typeof filterFn) { + throw new CasperError('setFilter only takes instances of Function'); + } + if (!this._filters[type]) { + this._filters[type] = filterFn; + return true; + } + // TODO: process multiple filters? in which order? disallow? + return false; +}; diff --git a/zephyr/tests/frontend/casperjs/modules/http.js b/zephyr/tests/frontend/casperjs/modules/http.js new file mode 100644 index 0000000000..33030da321 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/http.js @@ -0,0 +1,69 @@ +/*! + * Casper is a navigation utility for PhantomJS. + * + * Documentation: http://casperjs.org/ + * Repository: http://github.com/n1k0/casperjs + * + * Copyright (c) 2011-2012 Nicolas Perriault + * + * Part of source code is Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +var utils = require('utils'); + +/* + * Building an Array subclass + */ +function responseHeaders(){} +responseHeaders.prototype = []; + +/** + * Retrieves a given header based on its name + * + * @param String name A case-insensitive response header name + * @return mixed A header string or `null` if not found + */ +responseHeaders.prototype.get = function get(name){ + "use strict"; + var headerValue = null; + name = name.toLowerCase(); + this.some(function(header){ + if (header.name.toLowerCase() === name){ + headerValue = header.value; + return true; + } + }); + return headerValue; +}; + +/** + * Augment the response with proper prototypes + * + * @param mixed response Phantom response or undefined (generally with local files) + */ +exports.augmentResponse = function(response) { + "use strict"; + if (!utils.isHTTPResource(response)) { + return; + } + response.headers.__proto__ = responseHeaders.prototype; +}; diff --git a/zephyr/tests/frontend/casperjs/modules/injector.js b/zephyr/tests/frontend/casperjs/modules/injector.js new file mode 100644 index 0000000000..43c004952c --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/injector.js @@ -0,0 +1,97 @@ +/*! + * Casper is a navigation utility for PhantomJS. + * + * Documentation: http://casperjs.org/ + * Repository: http://github.com/n1k0/casperjs + * + * Copyright (c) 2011-2012 Nicolas Perriault + * + * Part of source code is Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*global CasperError console encodeURIComponent escape exports require*/ + +// WARNING: this module is deprecated since CasperJS 1.0.0-RC3 + +var utils = require('utils'); + +exports.create = function create(fn) { + "use strict"; + return new FunctionArgsInjector(fn); +}; + +/** + * Function argument injector. + * + * FIXME: use new Function() instead of eval() + */ +var FunctionArgsInjector = function FunctionArgsInjector(fn) { + "use strict"; + console.error('Warning: the injector module has been deprecated.'); + + if (!utils.isFunction(fn)) { + throw new CasperError("FunctionArgsInjector() can only process functions"); + } + this.fn = fn; + + this.extract = function extract(fn) { + var match = /^function\s?(\w+)?\s?\((.*)\)\s?\{([\s\S]*)\}/i.exec(fn.toString().trim()); + if (match && match.length > 1) { + var args = match[2].split(',').map(function _map(arg) { + return arg.replace(new RegExp(/\/\*+.*\*\//ig), "").trim(); + }).filter(function _filter(arg) { + return arg; + }) || []; + return { + name: match[1] ? match[1].trim() : null, + args: args, + body: match[3] ? match[3].trim() : '' + }; + } + }; + + this.process = function process(values) { + var fnObj = this.extract(this.fn); + if (!utils.isObject(fnObj)) { + throw new CasperError("Unable to process function " + this.fn.toString()); + } + var inject = this.getArgsInjectionString(fnObj.args, values); + var newFn = new Function([inject, fnObj.body].join('\n')); + newFn.name = fnObj.name || ''; + return newFn; + }; + + this.getArgsInjectionString = function getArgsInjectionString(args, values) { + values = typeof values === "object" ? values : {}; + var jsonValues = escape(encodeURIComponent(JSON.stringify(values))); + var inject = [ + 'var __casper_params__ = JSON.parse(decodeURIComponent(unescape(\'' + jsonValues + '\')));' + ]; + args.forEach(function _forEach(arg) { + if (arg in values) { + inject.push('var ' + arg + '=__casper_params__["' + arg + '"];'); + } + }); + return inject.join('\n') + '\n'; + }; +}; +exports.FunctionArgsInjector = FunctionArgsInjector; diff --git a/zephyr/tests/frontend/casperjs/modules/mouse.js b/zephyr/tests/frontend/casperjs/modules/mouse.js new file mode 100644 index 0000000000..93ee2aeae4 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/mouse.js @@ -0,0 +1,110 @@ +/*! + * Casper is a navigation utility for PhantomJS. + * + * Documentation: http://casperjs.org/ + * Repository: http://github.com/n1k0/casperjs + * + * Copyright (c) 2011-2012 Nicolas Perriault + * + * Part of source code is Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*global CasperError exports require*/ + +var utils = require('utils'); + +exports.create = function create(casper) { + "use strict"; + return new Mouse(casper); +}; + +var Mouse = function Mouse(casper) { + "use strict"; + if (!utils.isCasperObject(casper)) { + throw new CasperError('Mouse() needs a Casper instance'); + } + + var slice = Array.prototype.slice; + + var nativeEvents = ['mouseup', 'mousedown', 'click', 'mousemove']; + var emulatedEvents = ['mouseover', 'mouseout']; + var supportedEvents = nativeEvents.concat(emulatedEvents); + + function computeCenter(selector) { + var bounds = casper.getElementBounds(selector); + if (utils.isClipRect(bounds)) { + var x = Math.round(bounds.left + bounds.width / 2); + var y = Math.round(bounds.top + bounds.height / 2); + return [x, y]; + } + } + + function processEvent(type, args) { + if (!utils.isString(type) || supportedEvents.indexOf(type) === -1) { + throw new CasperError('Mouse.processEvent(): Unsupported mouse event type: ' + type); + } + if (emulatedEvents.indexOf(type) > -1) { + casper.log("Mouse.processEvent(): no native fallback for type " + type, "warning"); + } + args = slice.call(args); // cast Arguments -> Array + casper.emit('mouse.' + type.replace('mouse', ''), args); + switch (args.length) { + case 0: + throw new CasperError('Mouse.processEvent(): Too few arguments'); + case 1: + // selector + var selector = args[0]; + casper.page.sendEvent.apply(casper.page, [type].concat(computeCenter(selector))); + break; + case 2: + // coordinates + if (!utils.isNumber(args[0]) || !utils.isNumber(args[1])) { + throw new CasperError('Mouse.processEvent(): No valid coordinates passed: ' + args); + } + casper.page.sendEvent(type, args[0], args[1]); + break; + default: + throw new CasperError('Mouse.processEvent(): Too many arguments'); + } + } + + this.processEvent = function() { + processEvent(arguments[0], slice.call(arguments, 1)); + }; + + this.click = function click() { + processEvent('click', arguments); + }; + + this.down = function down() { + processEvent('mousedown', arguments); + }; + + this.move = function move() { + processEvent('mousemove', arguments); + }; + + this.up = function up() { + processEvent('mouseup', arguments); + }; +}; +exports.Mouse = Mouse; diff --git a/zephyr/tests/frontend/casperjs/modules/querystring.js b/zephyr/tests/frontend/casperjs/modules/querystring.js new file mode 100644 index 0000000000..d34f4338bc --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/querystring.js @@ -0,0 +1,187 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Query String Utilities + +var QueryString = exports; +//var urlDecode = process.binding('http_parser').urlDecode; // phantomjs incompatible + + +// If obj.hasOwnProperty has been overridden, then calling +// obj.hasOwnProperty(prop) will break. +// See: https://github.com/joyent/node/issues/1707 +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + + +function charCode(c) { + return c.charCodeAt(0); +} + + +// a safe fast alternative to decodeURIComponent +QueryString.unescapeBuffer = function(s, decodeSpaces) { + var out = new Buffer(s.length); + var state = 'CHAR'; // states: CHAR, HEX0, HEX1 + var n, m, hexchar; + + for (var inIndex = 0, outIndex = 0; inIndex <= s.length; inIndex++) { + var c = s.charCodeAt(inIndex); + switch (state) { + case 'CHAR': + switch (c) { + case charCode('%'): + n = 0; + m = 0; + state = 'HEX0'; + break; + case charCode('+'): + if (decodeSpaces) c = charCode(' '); + // pass thru + default: + out[outIndex++] = c; + break; + } + break; + + case 'HEX0': + state = 'HEX1'; + hexchar = c; + if (charCode('0') <= c && c <= charCode('9')) { + n = c - charCode('0'); + } else if (charCode('a') <= c && c <= charCode('f')) { + n = c - charCode('a') + 10; + } else if (charCode('A') <= c && c <= charCode('F')) { + n = c - charCode('A') + 10; + } else { + out[outIndex++] = charCode('%'); + out[outIndex++] = c; + state = 'CHAR'; + break; + } + break; + + case 'HEX1': + state = 'CHAR'; + if (charCode('0') <= c && c <= charCode('9')) { + m = c - charCode('0'); + } else if (charCode('a') <= c && c <= charCode('f')) { + m = c - charCode('a') + 10; + } else if (charCode('A') <= c && c <= charCode('F')) { + m = c - charCode('A') + 10; + } else { + out[outIndex++] = charCode('%'); + out[outIndex++] = hexchar; + out[outIndex++] = c; + break; + } + out[outIndex++] = 16 * n + m; + break; + } + } + + // TODO support returning arbitrary buffers. + + return out.slice(0, outIndex - 1); +}; + + +QueryString.unescape = function(s, decodeSpaces) { + return QueryString.unescapeBuffer(s, decodeSpaces).toString(); +}; + + +QueryString.escape = function(str) { + return encodeURIComponent(str); +}; + +var stringifyPrimitive = function(v) { + switch (typeof v) { + case 'string': + return v; + + case 'boolean': + return v ? 'true' : 'false'; + + case 'number': + return isFinite(v) ? v : ''; + + default: + return ''; + } +}; + + +QueryString.stringify = QueryString.encode = function(obj, sep, eq, name) { + sep = sep || '&'; + eq = eq || '='; + obj = (obj === null) ? undefined : obj; + + switch (typeof obj) { + case 'object': + return Object.keys(obj).map(function(k) { + if (Array.isArray(obj[k])) { + return obj[k].map(function(v) { + return QueryString.escape(stringifyPrimitive(k)) + + eq + + QueryString.escape(stringifyPrimitive(v)); + }).join(sep); + } else { + return QueryString.escape(stringifyPrimitive(k)) + + eq + + QueryString.escape(stringifyPrimitive(obj[k])); + } + }).join(sep); + + default: + if (!name) return ''; + return QueryString.escape(stringifyPrimitive(name)) + eq + + QueryString.escape(stringifyPrimitive(obj)); + } +}; + +// Parse a key=val string. +QueryString.parse = QueryString.decode = function(qs, sep, eq) { + sep = sep || '&'; + eq = eq || '='; + var obj = {}; + + if (typeof qs !== 'string' || qs.length === 0) { + return obj; + } + + qs.split(sep).forEach(function(kvp) { + var x = kvp.split(eq); + var k = QueryString.unescape(x[0], true); + var v = QueryString.unescape(x.slice(1).join(eq), true); + + if (!hasOwnProperty(obj, k)) { + obj[k] = v; + } else if (!Array.isArray(obj[k])) { + obj[k] = [obj[k], v]; + } else { + obj[k].push(v); + } + }); + + return obj; +}; diff --git a/zephyr/tests/frontend/casperjs/modules/tester.js b/zephyr/tests/frontend/casperjs/modules/tester.js new file mode 100755 index 0000000000..6b2dc16c62 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/tester.js @@ -0,0 +1,1010 @@ +/*! + * Casper is a navigation utility for PhantomJS. + * + * Documentation: http://casperjs.org/ + * Repository: http://github.com/n1k0/casperjs + * + * Copyright (c) 2011-2012 Nicolas Perriault + * + * Part of source code is Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*global CasperError exports phantom require __utils__*/ + +var fs = require('fs'); +var events = require('events'); +var utils = require('utils'); +var f = utils.format; + +exports.create = function create(casper, options) { + "use strict"; + return new Tester(casper, options); +}; + +/** + * Casper tester: makes assertions, stores test results and display then. + * + * @param Casper casper A valid Casper instance + * @param Object|null options Options object + */ +var Tester = function Tester(casper, options) { + "use strict"; + + if (!utils.isCasperObject(casper)) { + throw new CasperError("Tester needs a Casper instance"); + } + + this.casper = casper; + + this.currentTestFile = null; + this.currentSuiteNum = 0; + this.exporter = require('xunit').create(); + this.loadIncludes = { + includes: [], + pre: [], + post: [] + }; + this.running = false; + this.suites = []; + this.options = utils.mergeObjects({ + failText: "FAIL", // text to use for a successful test + passText: "PASS", // text to use for a failed test + pad: 80 // maximum number of chars for a result line + }, options); + + // properties + this.testResults = { + passed: 0, + failed: 0, + passes: [], + failures: [] + }; + + this.configure(); + + this.on('success', function onSuccess(success) { + this.testResults.passes.push(success); + this.exporter.addSuccess(fs.absolute(success.file), success.message || success.standard); + }); + + this.on('fail', function onFail(failure) { + // export + this.exporter.addFailure( + fs.absolute(failure.file), + failure.message || failure.standard, + failure.standard || "test failed", + failure.type || "unknown" + ); + this.testResults.failures.push(failure); + // special printing + if (failure.type) { + this.comment(' type: ' + failure.type); + } + if (failure.values && Object.keys(failure.values).length > 0) { + for (var name in failure.values) { + var comment = ' ' + name + ': '; + var value = failure.values[name]; + try { + comment += utils.serialize(failure.values[name]); + } catch (e) { + try { + comment += utils.serialize(failure.values[name].toString()); + } catch (e2) { + comment += '(unserializable value)'; + } + } + this.comment(comment); + } + } + }); +}; + +// Tester class is an EventEmitter +utils.inherits(Tester, events.EventEmitter); +exports.Tester = Tester; + +/** + * Asserts that a condition strictly resolves to true. Also returns an + * "assertion object" containing useful informations about the test case + * results. + * + * This method is also used as the base one used for all other `assert*` + * family methods; supplementary informations are then passed using the + * `context` argument. + * + * @param Boolean subject The condition to test + * @param String message Test description + * @param Object|null context Assertion context object (Optional) + * @return Object An assertion result object + */ +Tester.prototype.assert = Tester.prototype.assertTrue = function assert(subject, message, context) { + "use strict"; + return this.processAssertionResult(utils.mergeObjects({ + success: subject === true, + type: "assert", + standard: "Subject is strictly true", + message: message, + file: this.currentTestFile, + values: { + subject: utils.getPropertyPath(context, 'values.subject') || subject + } + }, context || {})); +}; + +/** + * Asserts that two values are strictly equals. + * + * @param Mixed subject The value to test + * @param Mixed expected The expected value + * @param String message Test description (Optional) + * @return Object An assertion result object + */ +Tester.prototype.assertEquals = Tester.prototype.assertEqual = function assertEquals(subject, expected, message) { + "use strict"; + return this.assert(this.testEquals(subject, expected), message, { + type: "assertEquals", + standard: "Subject equals the expected value", + values: { + subject: subject, + expected: expected + } + }); +}; + +/** + * Asserts that two values are strictly not equals. + * + * @param Mixed subject The value to test + * @param Mixed expected The unwanted value + * @param String|null message Test description (Optional) + * @return Object An assertion result object + */ +Tester.prototype.assertNotEquals = function assertNotEquals(subject, shouldnt, message) { + "use strict"; + return this.assert(!this.testEquals(subject, shouldnt), message, { + type: "assertNotEquals", + standard: "Subject doesn't equal what it shouldn't be", + values: { + subject: subject, + shouldnt: shouldnt + } + }); +}; + +/** + * Asserts that a code evaluation in remote DOM resolves to true. + * + * @param Function fn A function to be evaluated in remote DOM + * @param String message Test description + * @param Object params Object containing the parameters to inject into the function (optional) + * @return Object An assertion result object + */ +Tester.prototype.assertEval = Tester.prototype.assertEvaluate = function assertEval(fn, message, params) { + "use strict"; + return this.assert(this.casper.evaluate(fn, params), message, { + type: "assertEval", + standard: "Evaluated function returns true", + values: { + fn: fn, + params: params + } + }); +}; + +/** + * Asserts that the result of a code evaluation in remote DOM equals + * an expected value. + * + * @param Function fn The function to be evaluated in remote DOM + * @param Boolean expected The expected value + * @param String|null message Test description + * @param Object|null params Object containing the parameters to inject into the function (optional) + * @return Object An assertion result object + */ +Tester.prototype.assertEvalEquals = Tester.prototype.assertEvalEqual = function assertEvalEquals(fn, expected, message, params) { + "use strict"; + var subject = this.casper.evaluate(fn, params); + return this.assert(this.testEquals(subject, expected), message, { + type: "assertEvalEquals", + standard: "Evaluated function returns the expected value", + values: { + fn: fn, + params: params, + subject: subject, + expected: expected + } + }); +}; + +/** + * Asserts that a given input field has the provided value. + * + * @param String inputName The name attribute of the input element + * @param String expected The expected value of the input element + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertField = function assertField(inputName, expected, message) { + "use strict"; + var actual = this.casper.evaluate(function(inputName) { + return __utils__.getFieldValue(inputName); + }, { inputName: inputName }); + return this.assert(this.testEquals(actual, expected), message, { + type: 'assertField', + standard: f('"%s" input field has the value "%s"', inputName, expected), + values: { + inputName: inputName, + actual: actual, + expected: expected + } + }); +}; + +/** + * Asserts that an element matching the provided selector expression exists in + * remote DOM. + * + * @param String selector Selector expression + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertExists = Tester.prototype.assertExist = this.assertSelectorExists = Tester.prototype.assertSelectorExist = function assertExists(selector, message) { + "use strict"; + return this.assert(this.casper.exists(selector), message, { + type: "assertExists", + standard: f("Found an element matching: %s", selector), + values: { + selector: selector + } + }); +}; + +/** + * Asserts that an element matching the provided selector expression does not + * exists in remote DOM. + * + * @param String selector Selector expression + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertDoesntExist = Tester.prototype.assertNotExists = function assertDoesntExist(selector, message) { + "use strict"; + return this.assert(!this.casper.exists(selector), message, { + type: "assertDoesntExist", + standard: f("No element found matching selector: %s", selector), + values: { + selector: selector + } + }); +}; + +/** + * Asserts that current HTTP status is the one passed as argument. + * + * @param Number status HTTP status code + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertHttpStatus = function assertHttpStatus(status, message) { + "use strict"; + var currentHTTPStatus = this.casper.currentHTTPStatus; + return this.assert(this.testEquals(this.casper.currentHTTPStatus, status), message, { + type: "assertHttpStatus", + standard: f("HTTP status code is: %s", status), + values: { + current: currentHTTPStatus, + expected: status + } + }); +}; + +/** + * Asserts that a provided string matches a provided RegExp pattern. + * + * @param String subject The string to test + * @param RegExp pattern A RegExp object instance + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertMatch = Tester.prototype.assertMatches = function assertMatch(subject, pattern, message) { + "use strict"; + return this.assert(pattern.test(subject), message, { + type: "assertMatch", + standard: "Subject matches the provided pattern", + values: { + subject: subject, + pattern: pattern.toString() + } + }); +}; + +/** + * Asserts a condition resolves to false. + * + * @param Boolean condition The condition to test + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertNot = function assertNot(condition, message) { + "use strict"; + return this.assert(!condition, message, { + type: "assertNot", + standard: "Subject is falsy", + values: { + condition: condition + } + }); +}; + +/** + * Asserts that a selector expression is not currently visible. + * + * @param String expected selector expression + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertNotVisible = Tester.prototype.assertInvisible = function assertNotVisible(selector, message) { + "use strict"; + return this.assert(!this.casper.visible(selector), message, { + type: "assertVisible", + standard: "Selector is not visible", + values: { + selector: selector + } + }); +}; + +/** + * Asserts that the provided function called with the given parameters + * will raise an exception. + * + * @param Function fn The function to test + * @param Array args The arguments to pass to the function + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertRaises = Tester.prototype.assertRaise = this.assertThrows = function assertRaises(fn, args, message) { + "use strict"; + var context = { + type: "assertRaises", + standard: "Function raises an error" + }; + try { + fn.apply(null, args); + this.assert(false, message, context); + } catch (error) { + this.assert(true, message, utils.mergeObjects(context, { + values: { + error: error + } + })); + } +}; + +/** + * Asserts that the current page has a resource that matches the provided test + * + * @param Function/String test A test function that is called with every response + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertResourceExists = Tester.prototype.assertResourceExist = function assertResourceExists(test, message) { + "use strict"; + return this.assert(this.casper.resourceExists(test), message, { + type: "assertResourceExists", + standard: "Expected resource has been found", + values: { + test: test + } + }); +}; + +/** + * Asserts that given text exists in the document body. + * + * @param String text Text to be found + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertTextExists = Tester.prototype.assertTextExist = function assertTextExists(text, message) { + "use strict"; + var textFound = (this.casper.evaluate(function _evaluate() { + return document.body.textContent || document.body.innerText; + }).indexOf(text) !== -1); + return this.assert(textFound, message, { + type: "assertTextExists", + standard: "Found expected text within the document body", + values: { + text: text + } + }); +}; + +/** + * Asserts that given text exists in the provided selector. + * + * @param String selector Selector expression + * @param String text Text to be found + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertSelectorHasText = function assertSelectorHasText(selector, text, message) { + "use strict"; + var textFound = this.casper.fetchText(selector).indexOf(text) !== -1; + return this.assert(textFound, message, { + type: "assertTextInSelector", + standard: f('Found "%s" within the selector "%s"', text, selector), + values: { + selector: selector, + text: text + } + }); +}; + +/** + * Asserts that given text does not exist in the provided selector. + * + * @param String selector Selector expression + * @param String text Text not to be found + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertSelectorDoesntHaveText = function assertSelectorDoesntHaveText(selector, text, message) { + "use strict"; + var textFound = this.casper.fetchText(selector).indexOf(text) === -1; + return this.assert(textFound, message, { + type: "assertNoTextInSelector", + standard: f('Did not find "%s" within the selector "%s"', text, selector), + values: { + selector: selector, + text: text + } + }); +}; + +/** + * Asserts that title of the remote page equals to the expected one. + * + * @param String expected The expected title string + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertTitle = function assertTitle(expected, message) { + "use strict"; + var currentTitle = this.casper.getTitle(); + return this.assert(this.testEquals(currentTitle, expected), message, { + type: "assertTitle", + standard: f('Page title is: "%s"', expected), + values: { + subject: currentTitle, + expected: expected + } + }); +}; + +/** + * Asserts that title of the remote page matched the provided pattern. + * + * @param RegExp pattern The pattern to test the title against + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertTitleMatch = Tester.prototype.assertTitleMatches = function assertTitleMatch(pattern, message) { + "use strict"; + var currentTitle = this.casper.getTitle(); + return this.assert(pattern.test(currentTitle), message, { + type: "assertTitle", + details: "Page title does not match the provided pattern", + values: { + subject: currentTitle, + pattern: pattern.toString() + } + }); +}; + +/** + * Asserts that the provided subject is of the given type. + * + * @param mixed subject The value to test + * @param String type The javascript type name + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertType = function assertType(subject, type, message) { + "use strict"; + var actual = utils.betterTypeOf(subject); + return this.assert(this.testEquals(actual, type), message, { + type: "assertType", + standard: f('Subject type is: "%s"', type), + values: { + subject: subject, + type: type, + actual: actual + } + }); +}; + +/** + * Asserts that a the current page url matches a given pattern. A pattern may be + * either a RegExp object or a String. The method will test if the URL matches + * the pattern or contains the String. + * + * @param RegExp|String pattern The test pattern + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertUrlMatch = Tester.prototype.assertUrlMatches = function assertUrlMatch(pattern, message) { + "use strict"; + var currentUrl = this.casper.getCurrentUrl(), + patternType = utils.betterTypeOf(pattern), + result; + if (patternType === "regexp") { + result = pattern.test(currentUrl); + } else if (patternType === "string") { + result = currentUrl.indexOf(pattern) !== -1; + } else { + throw new CasperError("assertUrlMatch() only accepts strings or regexps"); + } + return this.assert(result, message, { + type: "assertUrlMatch", + standard: "Current url matches the provided pattern", + values: { + currentUrl: currentUrl, + pattern: pattern.toString() + } + }); +}; + +/** + * Asserts that a selector expression is currently visible. + * + * @param String expected selector expression + * @param String message Test description + * @return Object An assertion result object + */ +Tester.prototype.assertVisible = function assertVisible(selector, message) { + "use strict"; + return this.assert(this.casper.visible(selector), message, { + type: "assertVisible", + standard: "Selector is visible", + values: { + selector: selector + } + }); +}; + +/** + * Prints out a colored bar onto the console. + * + */ +Tester.prototype.bar = function bar(text, style) { + "use strict"; + this.casper.echo(text, style, this.options.pad); +}; + +/** + * Render a colorized output. Basically a proxy method for + * Casper.Colorizer#colorize() + */ +Tester.prototype.colorize = function colorize(message, style) { + "use strict"; + return this.casper.getColorizer().colorize(message, style); +}; + +/** + * Writes a comment-style formatted message to stdout. + * + * @param String message + */ +Tester.prototype.comment = function comment(message) { + "use strict"; + this.casper.echo('# ' + message, 'COMMENT'); +}; + +/** + * Configure casper callbacks for testing purpose. + * + */ +Tester.prototype.configure = function configure() { + "use strict"; + var tester = this; + + // Do not hook casper if we're not testing + if (!phantom.casperTest) { + return; + } + + // specific timeout callbacks + this.casper.options.onStepTimeout = function test_onStepTimeout(timeout, step) { + tester.fail(f("Step timeout occured at step %d (%dms)", step, timeout)); + }; + + this.casper.options.onTimeout = function test_onTimeout(timeout) { + tester.fail(f("Timeout occured (%dms)", timeout)); + }; + + this.casper.options.onWaitTimeout = function test_onWaitTimeout(timeout) { + tester.fail(f("Wait timeout occured (%dms)", timeout)); + }; + + // events + this.casper.on('error', function(msg, backtrace) { + var line = 0; + try { + line = backtrace[0].line; + } catch (e) {} + tester.uncaughtError(msg, tester.currentTestFile, line); + tester.done(); + }); + + this.casper.on('step.error', function onStepError(e) { + tester.uncaughtError(e, tester.currentTestFile); + tester.done(); + }); +}; + +/** + * Declares the current test suite done. + * + */ +Tester.prototype.done = function done() { + "use strict"; + this.emit('test.done'); + this.running = false; +}; + +/** + * Writes an error-style formatted message to stdout. + * + * @param String message + */ +Tester.prototype.error = function error(message) { + "use strict"; + this.casper.echo(message, 'ERROR'); +}; + +/** + * Executes a file, wraping and evaluating its code in an isolated + * environment where only the current `casper` instance is passed. + * + * @param String file Absolute path to some js/coffee file + */ +Tester.prototype.exec = function exec(file) { + "use strict"; + file = this.filter('exec.file', file) || file; + if (!fs.isFile(file) || !utils.isJsFile(file)) { + var e = new CasperError(f("Cannot exec %s: can only exec() files with .js or .coffee extensions", file)); + e.fileName = file; + throw e; + } + this.currentTestFile = file; + phantom.injectJs(file); +}; + +/** + * Adds a failed test entry to the stack. + * + * @param String message + */ +Tester.prototype.fail = function fail(message) { + "use strict"; + return this.assert(false, message, { + type: "fail", + standard: "explicit call to fail()" + }); +}; + +/** + * Recursively finds all test files contained in a given directory. + * + * @param String dir Path to some directory to scan + */ +Tester.prototype.findTestFiles = function findTestFiles(dir) { + "use strict"; + var self = this; + if (!fs.isDirectory(dir)) { + return []; + } + var entries = fs.list(dir).filter(function _filter(entry) { + return entry !== '.' && entry !== '..'; + }).map(function _map(entry) { + return fs.absolute(fs.pathJoin(dir, entry)); + }); + entries.forEach(function _forEach(entry) { + if (fs.isDirectory(entry)) { + entries = entries.concat(self.findTestFiles(entry)); + } + }); + return entries.filter(function _filter(entry) { + return utils.isJsFile(fs.absolute(fs.pathJoin(dir, entry))); + }).sort(); +}; + +/** + * Formats a message to highlight some parts of it. + * + * @param String message + * @param String style + */ +Tester.prototype.formatMessage = function formatMessage(message, style) { + "use strict"; + var parts = /^([a-z0-9_\.]+\(\))(.*)/i.exec(message); + if (!parts) { + return message; + } + return this.colorize(parts[1], 'PARAMETER') + this.colorize(parts[2], style); +}; + +/** + * Retrieves current failure data and all failed cases. + * + * @return Object casedata An object containg information about cases + * @return Number casedata.length The number of failed cases + * @return Array casedata.cases An array of all the failed case objects + */ +Tester.prototype.getFailures = function getFailures() { + "use strict"; + return { + length: this.testResults.failed, + cases: this.testResults.failures + }; +}; + +/** + * Retrieves current passed data and all passed cases. + * + * @return Object casedata An object containg information about cases + * @return Number casedata.length The number of passed cases + * @return Array casedata.cases An array of all the passed case objects + */ +Tester.prototype.getPasses = function getPasses() { + "use strict"; + return { + length: this.testResults.passed, + cases: this.testResults.passes + }; +}; + +/** + * Writes an info-style formatted message to stdout. + * + * @param String message + */ +Tester.prototype.info = function info(message) { + "use strict"; + this.casper.echo(message, 'PARAMETER'); +}; + +/** + * Adds a successful test entry to the stack. + * + * @param String message + */ +Tester.prototype.pass = function pass(message) { + "use strict"; + return this.assert(true, message, { + type: "pass", + standard: "explicit call to pass()" + }); +}; + +/** + * Processes an assertion result by emitting the appropriate event and + * printing result onto the console. + * + * @param Object result An assertion result object + * @return Object The passed assertion result Object + */ +Tester.prototype.processAssertionResult = function processAssertionResult(result) { + "use strict"; + var eventName, style, status; + if (result.success === true) { + eventName = 'success'; + style = 'INFO'; + status = this.options.passText; + this.testResults.passed++; + } else { + eventName = 'fail'; + style = 'RED_BAR'; + status = this.options.failText; + this.testResults.failed++; + } + var message = result.message || result.standard; + this.casper.echo([this.colorize(status, style), this.formatMessage(message)].join(' ')); + this.emit(eventName, result); + return result; +}; + +/** + * Renders a detailed report for each failed test. + * + * @param Array failures + */ +Tester.prototype.renderFailureDetails = function renderFailureDetails(failures) { + "use strict"; + if (failures.length === 0) { + return; + } + this.casper.echo(f("\nDetails for the %d failed test%s:\n", failures.length, failures.length > 1 ? "s" : ""), "PARAMETER"); + failures.forEach(function _forEach(failure) { + var type, message, line; + type = failure.type || "unknown"; + line = ~~failure.line; + message = failure.message; + this.casper.echo(f('In %s:%s', failure.file, line)); + this.casper.echo(f(' %s: %s', type, message || failure.standard || "(no message was entered)"), "COMMENT"); + }); +}; + +/** + * Render tests results, an optionally exit phantomjs. + * + * @param Boolean exit + */ +Tester.prototype.renderResults = function renderResults(exit, status, save) { + "use strict"; + /*jshint maxstatements:20*/ + save = save || this.options.save; + var total = this.testResults.passed + this.testResults.failed, statusText, style, result; + var exitStatus = ~~(status || (this.testResults.failed > 0 ? 1 : 0)); + if (total === 0) { + statusText = this.options.failText; + style = 'RED_BAR'; + result = f("%s Looks like you didn't run any test.", statusText); + } else { + if (this.testResults.failed > 0) { + statusText = this.options.failText; + style = 'RED_BAR'; + } else { + statusText = this.options.passText; + style = 'GREEN_BAR'; + } + result = f('%s %s tests executed, %d passed, %d failed.', + statusText, total, this.testResults.passed, this.testResults.failed); + } + this.casper.echo(result, style, this.options.pad); + if (this.testResults.failed > 0) { + this.renderFailureDetails(this.testResults.failures); + } + if (save) { + this.saveResults(save); + } + if (exit === true) { + this.casper.exit(exitStatus); + } +}; + +/** + * Runs al suites contained in the paths passed as arguments. + * + */ +Tester.prototype.runSuites = function runSuites() { + "use strict"; + var testFiles = [], self = this; + if (arguments.length === 0) { + throw new CasperError("runSuites() needs at least one path argument"); + } + this.loadIncludes.includes.forEach(function _forEachInclude(include) { + phantom.injectJs(include); + }); + + this.loadIncludes.pre.forEach(function _forEachPreTest(preTestFile) { + testFiles = testFiles.concat(preTestFile); + }); + + Array.prototype.forEach.call(arguments, function _forEachArgument(path) { + if (!fs.exists(path)) { + self.bar(f("Path %s doesn't exist", path), "RED_BAR"); + } + if (fs.isDirectory(path)) { + testFiles = testFiles.concat(self.findTestFiles(path)); + } else if (fs.isFile(path)) { + testFiles.push(path); + } + }); + + this.loadIncludes.post.forEach(function _forEachPostTest(postTestFile) { + testFiles = testFiles.concat(postTestFile); + }); + + if (testFiles.length === 0) { + this.bar(f("No test file found in %s, aborting.", Array.prototype.slice.call(arguments)), "RED_BAR"); + this.casper.exit(1); + } + self.currentSuiteNum = 0; + var interval = setInterval(function _check(self) { + if (self.running) { + return; + } + if (self.currentSuiteNum === testFiles.length) { + self.emit('tests.complete'); + clearInterval(interval); + } else { + self.runTest(testFiles[self.currentSuiteNum]); + self.currentSuiteNum++; + } + }, 100, this); +}; + +/** + * Runs a test file + * + */ +Tester.prototype.runTest = function runTest(testFile) { + "use strict"; + this.bar(f('Test file: %s', testFile), 'INFO_BAR'); + this.running = true; // this.running is set back to false with done() + this.exec(testFile); +}; + +/** + * Saves results to file. + * + * @param String filename Target file path. + */ +Tester.prototype.saveResults = function saveResults(filepath) { + "use strict"; + // FIXME: looks like phantomjs has a pb with fs.isWritable https://groups.google.com/forum/#!topic/casperjs/hcUdwgGZOrU + // if (!fs.isWritable(filepath)) { + // throw new CasperError(f('Path %s is not writable.', filepath)); + // } + try { + fs.write(filepath, this.exporter.getXML(), 'w'); + this.casper.echo(f('Result log stored in %s', filepath), 'INFO', 80); + } catch (e) { + this.casper.echo(f('Unable to write results to %s: %s', filepath, e), 'ERROR', 80); + } +}; + +/** + * Tests equality between the two passed arguments. + * + * @param Mixed v1 + * @param Mixed v2 + * @param Boolean + */ +Tester.prototype.testEquals = Tester.prototype.testEqual = function testEquals(v1, v2) { + "use strict"; + return utils.equals(v1, v2); +}; + +/** + * Processes an error caught while running tests contained in a given test + * file. + * + * @param Error|String error The error + * @param String file Test file where the error occurred + * @param Number line Line number (optional) + */ +Tester.prototype.uncaughtError = function uncaughtError(error, file, line) { + "use strict"; + return this.processAssertionResult({ + success: false, + type: "uncaughtError", + file: file, + line: ~~line || "unknown", + message: utils.isObject(error) ? error.message : error, + values: { + error: error + } + }); +}; diff --git a/zephyr/tests/frontend/casperjs/modules/utils.js b/zephyr/tests/frontend/casperjs/modules/utils.js new file mode 100644 index 0000000000..5e0d0f9d57 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/utils.js @@ -0,0 +1,508 @@ +/*! + * Casper is a navigation utility for PhantomJS. + * + * Documentation: http://casperjs.org/ + * Repository: http://github.com/n1k0/casperjs + * + * Copyright (c) 2011-2012 Nicolas Perriault + * + * Part of source code is Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*global CasperError console exports phantom require*/ + +/** + * Provides a better typeof operator equivalent, able to retrieve the array + * type. + * + * @param mixed input + * @return String + * @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/ + */ +function betterTypeOf(input) { + "use strict"; + try { + return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase(); + } catch (e) { + return typeof input; + } +} +exports.betterTypeOf = betterTypeOf; + +/** + * Cleans a passed URL if it lacks a slash at the end when a sole domain is used. + * + * @param String url An HTTP URL + * @return String + */ +function cleanUrl(url) { + "use strict"; + var parts = /(https?):\/\/(.*)/i.exec(url); + if (!parts) { + return url; + } + var protocol = parts[1]; + var subparts = parts[2].split('/'); + if (subparts.length === 1) { + return format("%s://%s/", protocol, subparts[0]); + } + return url; +} +exports.cleanUrl = cleanUrl; + +/** + * Dumps a JSON representation of passed value to the console. Used for + * debugging purpose only. + * + * @param Mixed value + */ +function dump(value) { + "use strict"; + console.log(serialize(value, 4)); +} +exports.dump = dump; + +/** + * Tests equality between the two passed arguments. + * + * @param Mixed v1 + * @param Mixed v2 + * @param Boolean + */ +function equals(v1, v2) { + "use strict"; + if (isFunction(v1)) { + return v1.toString() === v2.toString(); + } + if (v1 instanceof Object) { + if (Object.keys(v1).length !== Object.keys(v2).length) { + return false; + } + for (var k in v1) { + if (!equals(v1[k], v2[k])) { + return false; + } + } + return true; + } + return v1 === v2; +} +exports.equals = equals; + +/** + * Returns the file extension in lower case. + * + * @param String file File path + * @return string + */ +function fileExt(file) { + "use strict"; + try { + return file.split('.').pop().toLowerCase().trim(); + } catch(e) { + return ''; + } +} +exports.fileExt = fileExt; + +/** + * Takes a string and append blanks until the pad value is reached. + * + * @param String text + * @param Number pad Pad value (optional; default: 80) + * @return String + */ +function fillBlanks(text, pad) { + "use strict"; + pad = pad || 80; + if (text.length < pad) { + text += new Array(pad - text.length + 1).join(' '); + } + return text; +} +exports.fillBlanks = fillBlanks; + +/** + * Formats a string with passed parameters. Ported from nodejs `util.format()`. + * + * @return String + */ +function format(f) { + "use strict"; + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(/%[sdj%]/g, function _replace(x) { + if (i >= len) return x; + switch (x) { + case '%s': + return String(args[i++]); + case '%d': + return Number(args[i++]); + case '%j': + return JSON.stringify(args[i++]); + case '%%': + return '%'; + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (x === null || typeof x !== 'object') { + str += ' ' + x; + } else { + str += '[obj]'; + } + } + return str; +} +exports.format = format; + +/** + * Retrieves the value of an Object foreign property using a dot-separated + * path string. + * + * Beware, this function doesn't handle object key names containing a dot. + * + * @param Object obj The source object + * @param String path Dot separated path, eg. "x.y.z" + */ +function getPropertyPath(obj, path) { + "use strict"; + if (!isObject(obj) || !isString(path)) { + return undefined; + } + var value = obj; + path.split('.').forEach(function(property) { + if (typeof value === "object" && property in value) { + value = value[property]; + } else { + value = undefined; + } + }); + return value; +} +exports.getPropertyPath = getPropertyPath; + +/** + * Inherit the prototype methods from one constructor into another. + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +function inherits(ctor, superCtor) { + "use strict"; + ctor.super_ = ctor.__super__ = superCtor; + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); +} +exports.inherits = inherits; + +/** + * Checks if value is a javascript Array + * + * @param mixed value + * @return Boolean + */ +function isArray(value) { + "use strict"; + return Array.isArray(value) || isType(value, "array"); +} +exports.isArray = isArray; + +/** + * Checks if passed argument is an instance of Capser object. + * + * @param mixed value + * @return Boolean + */ +function isCasperObject(value) { + "use strict"; + return value instanceof require('casper').Casper; +} +exports.isCasperObject = isCasperObject; + +/** + * Checks if value is a phantomjs clipRect-compatible object + * + * @param mixed value + * @return Boolean + */ +function isClipRect(value) { + "use strict"; + return isType(value, "cliprect") || ( + isObject(value) && + isNumber(value.top) && isNumber(value.left) && + isNumber(value.width) && isNumber(value.height) + ); +} +exports.isClipRect = isClipRect; + +/** + * Checks if value is a javascript Function + * + * @param mixed value + * @return Boolean + */ +function isFunction(value) { + "use strict"; + return isType(value, "function"); +} +exports.isFunction = isFunction; + +/** + * Checks if passed resource involves an HTTP url. + * + * @param Object resource The PhantomJS HTTP resource object + * @return Boolean + */ +function isHTTPResource(resource) { + "use strict"; + return isObject(resource) && /^http/i.test(resource.url); +} +exports.isHTTPResource = isHTTPResource; + +/** + * Checks if a file is apparently javascript compatible (.js or .coffee). + * + * @param String file Path to the file to test + * @return Boolean + */ +function isJsFile(file) { + "use strict"; + var ext = fileExt(file); + return isString(ext, "string") && ['js', 'coffee'].indexOf(ext) !== -1; +} +exports.isJsFile = isJsFile; + +/** + * Checks if the provided value is null + * + * @return Boolean + */ +function isNull(value) { + "use strict"; + return isType(value, "null"); +} +exports.isNull = isNull; + +/** + * Checks if value is a javascript Number + * + * @param mixed value + * @return Boolean + */ +function isNumber(value) { + "use strict"; + return isType(value, "number"); +} +exports.isNumber = isNumber; + +/** + * Checks if value is a javascript Object + * + * @param mixed value + * @return Boolean + */ +function isObject(value) { + "use strict"; + var objectTypes = ["array", "object", "qtruntimeobject"]; + return objectTypes.indexOf(betterTypeOf(value)) >= 0; +} +exports.isObject = isObject; + +/** + * Checks if value is a javascript String + * + * @param mixed value + * @return Boolean + */ +function isString(value) { + "use strict"; + return isType(value, "string"); +} +exports.isString = isString; + +/** + * Shorthands for checking if a value is of the given type. Can check for + * arrays. + * + * @param mixed what The value to check + * @param String typeName The type name ("string", "number", "function", etc.) + * @return Boolean + */ +function isType(what, typeName) { + "use strict"; + if (typeof typeName !== "string" || !typeName) { + throw new CasperError("You must pass isType() a typeName string"); + } + return betterTypeOf(what).toLowerCase() === typeName.toLowerCase(); +} +exports.isType = isType; + +/** + * Checks if the provided value is undefined + * + * @return Boolean + */ +function isUndefined(value) { + "use strict"; + return isType(value, "undefined"); +} +exports.isUndefined = isUndefined; + +/** + * Checks if value is a valid selector Object. + * + * @param mixed value + * @return Boolean + */ +function isValidSelector(value) { + "use strict"; + if (isString(value)) { + try { + // phantomjs env has a working document object, let's use it + document.querySelector(value); + } catch(e) { + if ('name' in e && e.name === 'SYNTAX_ERR') { + return false; + } + } + return true; + } else if (isObject(value)) { + if (!value.hasOwnProperty('type')) { + return false; + } + if (!value.hasOwnProperty('path')) { + return false; + } + if (['css', 'xpath'].indexOf(value.type) === -1) { + return false; + } + return true; + } + return false; +} +exports.isValidSelector = isValidSelector; + +/** + * Checks if the provided var is a WebPage instance + * + * @param mixed what + * @return Boolean + */ +function isWebPage(what) { + "use strict"; + return betterTypeOf(what) === "qtruntimeobject" && what.objectName === 'WebPage'; +} +exports.isWebPage = isWebPage; + +/** + * Object recursive merging utility. + * + * @param Object origin the origin object + * @param Object add the object to merge data into origin + * @return Object + */ +function mergeObjects(origin, add) { + "use strict"; + for (var p in add) { + try { + if (add[p].constructor === Object) { + origin[p] = mergeObjects(origin[p], add[p]); + } else { + origin[p] = add[p]; + } + } catch(e) { + origin[p] = add[p]; + } + } + return origin; +} +exports.mergeObjects = mergeObjects; + +/** + * Creates an (SG|X)ML node element. + * + * @param String name The node name + * @param Object attributes Optional attributes + * @return HTMLElement + */ +function node(name, attributes) { + "use strict"; + var _node = document.createElement(name); + for (var attrName in attributes) { + var value = attributes[attrName]; + if (attributes.hasOwnProperty(attrName) && isString(attrName)) { + _node.setAttribute(attrName, value); + } + } + return _node; +} +exports.node = node; + +/** + * Serializes a value using JSON. + * + * @param Mixed value + * @return String + */ +function serialize(value, indent) { + "use strict"; + if (isArray(value)) { + value = value.map(function _map(prop) { + return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop; + }); + } + return JSON.stringify(value, null, indent); +} +exports.serialize = serialize; + +/** + * Returns unique values from an array. + * + * Note: ugly code is ugly, but efficient: http://jsperf.com/array-unique2/8 + * + * @param Array array + * @return Array + */ +function unique(array) { + "use strict"; + var o = {}, + r = []; + for (var i = 0, len = array.length; i !== len; i++) { + var d = array[i]; + if (o[d] !== 1) { + o[d] = 1; + r[r.length] = d; + } + } + return r; +} +exports.unique = unique; diff --git a/zephyr/tests/frontend/casperjs/modules/vendors/coffee-script.js b/zephyr/tests/frontend/casperjs/modules/vendors/coffee-script.js new file mode 100644 index 0000000000..66c387f80a --- /dev/null +++ b/zephyr/tests/frontend/casperjs/modules/vendors/coffee-script.js @@ -0,0 +1,8 @@ +/** + * CoffeeScript Compiler v1.4.0 + * http://coffeescript.org + * + * Copyright 2011, Jeremy Ashkenas + * Released under the MIT License + */ +(function(root){var CoffeeScript=function(){function require(a){return require[a]}return require["./helpers"]=new function(){var a=this;((function(){var b,c,d;a.starts=function(a,b,c){return b===a.substr(c,b.length)},a.ends=function(a,b,c){var d;return d=b.length,b===a.substr(a.length-d-(c||0),d)},a.compact=function(a){var b,c,d,e;e=[];for(c=0,d=a.length;c=0)f+=1;else if(j=g[0],t.call(d,j)>=0)f-=1;a+=1}return a-1},a.prototype.removeLeadingNewlines=function(){var a,b,c,d,e;e=this.tokens;for(a=c=0,d=e.length;c=0)?(d.splice(b,1),0):1})},a.prototype.closeOpenCalls=function(){var a,b;return b=function(a,b){var c;return(c=a[0])===")"||c==="CALL_END"||a[0]==="OUTDENT"&&this.tag(b-1)===")"},a=function(a,b){return this.tokens[a[0]==="OUTDENT"?b-1:b][0]="CALL_END"},this.scanTokens(function(c,d){return c[0]==="CALL_START"&&this.detectEnd(d+1,b,a),1})},a.prototype.closeOpenIndexes=function(){var a,b;return b=function(a,b){var c;return(c=a[0])==="]"||c==="INDEX_END"},a=function(a,b){return a[0]="INDEX_END"},this.scanTokens(function(c,d){return c[0]==="INDEX_START"&&this.detectEnd(d+1,b,a),1})},a.prototype.addImplicitBraces=function(){var a,b,c,f,g,i,j,k;return f=[],g=null,k=null,c=!0,i=0,j=0,b=function(a,b){var d,e,f,g,i,m;return i=this.tokens.slice(b+1,+(b+3)+1||9e9),d=i[0],g=i[1],f=i[2],"HERECOMMENT"===(d!=null?d[0]:void 0)?!1:(e=a[0],t.call(l,e)>=0&&(c=!1),(e==="TERMINATOR"||e==="OUTDENT"||t.call(h,e)>=0&&c&&b-j!==1)&&(!k&&this.tag(b-1)!==","||(g!=null?g[0]:void 0)!==":"&&((d!=null?d[0]:void 0)!=="@"||(f!=null?f[0]:void 0)!==":"))||e===","&&d&&(m=d[0])!=="IDENTIFIER"&&m!=="NUMBER"&&m!=="STRING"&&m!=="@"&&m!=="TERMINATOR"&&m!=="OUTDENT")},a=function(a,b){var c;return c=this.generate("}","}",a[2]),this.tokens.splice(b,0,c)},this.scanTokens(function(h,i,m){var n,o,p,q,r,s,u,v;if(u=q=h[0],t.call(e,u)>=0)return f.push([q==="INDENT"&&this.tag(i-1)==="{"?"{":q,i]),1;if(t.call(d,q)>=0)return g=f.pop(),1;if(q!==":"||(n=this.tag(i-2))!==":"&&((v=f[f.length-1])!=null?v[0]:void 0)==="{")return 1;c=!0,j=i+1,f.push(["{"]),o=n==="@"?i-2:i-1;while(this.tag(o-2)==="HERECOMMENT")o-=2;return p=this.tag(o-1),k=!p||t.call(l,p)>=0,s=new String("{"),s.generated=!0,r=this.generate("{",s,h[2]),m.splice(o,0,r),this.detectEnd(i+2,b,a),2})},a.prototype.addImplicitParentheses=function(){var a,b,c,d,e;return c=e=d=!1,b=function(a,b){var c,g,i,j;g=a[0];if(!e&&a.fromThen)return!0;if(g==="IF"||g==="ELSE"||g==="CATCH"||g==="->"||g==="=>"||g==="CLASS")e=!0;if(g==="IF"||g==="ELSE"||g==="SWITCH"||g==="TRY"||g==="=")d=!0;return g!=="."&&g!=="?."&&g!=="::"||this.tag(b-1)!=="OUTDENT"?!a.generated&&this.tag(b-1)!==","&&(t.call(h,g)>=0||g==="INDENT"&&!d)&&(g!=="INDENT"||(i=this.tag(b-2))!=="CLASS"&&i!=="EXTENDS"&&(j=this.tag(b-1),t.call(f,j)<0)&&(!(c=this.tokens[b+1])||!c.generated||c[0]!=="{")):!0},a=function(a,b){return this.tokens.splice(b,0,this.generate("CALL_END",")",a[2]))},this.scanTokens(function(f,h,k){var m,n,o,p,q,r,s,u;q=f[0];if(q==="CLASS"||q==="IF"||q==="FOR"||q==="WHILE")c=!0;return r=k.slice(h-1,+(h+1)+1||9e9),p=r[0],n=r[1],o=r[2],m=!c&&q==="INDENT"&&o&&o.generated&&o[0]==="{"&&p&&(s=p[0],t.call(i,s)>=0),e=!1,d=!1,t.call(l,q)>=0&&(c=!1),p&&!p.spaced&&q==="?"&&(f.call=!0),f.fromThen?1:m||(p!=null?p.spaced:void 0)&&(p.call||(u=p[0],t.call(i,u)>=0))&&(t.call(g,q)>=0||!f.spaced&&!f.newLine&&t.call(j,q)>=0)?(k.splice(h,0,this.generate("CALL_START","(",f[2])),this.detectEnd(h+1,b,a),p[0]==="?"&&(p[0]="FUNC_EXIST"),2):1})},a.prototype.addImplicitIndentation=function(){var a,b,c,d,e;return e=c=d=null,b=function(a,b){var c;return a[1]!==";"&&(c=a[0],t.call(m,c)>=0)&&(a[0]!=="ELSE"||e==="IF"||e==="THEN")},a=function(a,b){return this.tokens.splice(this.tag(b-1)===","?b-1:b,0,d)},this.scanTokens(function(f,g,h){var i,j,k;return i=f[0],i==="TERMINATOR"&&this.tag(g+1)==="THEN"?(h.splice(g,1),0):i==="ELSE"&&this.tag(g-1)!=="OUTDENT"?(h.splice.apply(h,[g,0].concat(u.call(this.indentation(f)))),2):i!=="CATCH"||(j=this.tag(g+2))!=="OUTDENT"&&j!=="TERMINATOR"&&j!=="FINALLY"?t.call(n,i)>=0&&this.tag(g+1)!=="INDENT"&&(i!=="ELSE"||this.tag(g+1)!=="IF")?(e=i,k=this.indentation(f,!0),c=k[0],d=k[1],e==="THEN"&&(c.fromThen=!0),h.splice(g+1,0,c),this.detectEnd(g+2,b,a),i==="THEN"&&h.splice(g,1),1):1:(h.splice.apply(h,[g+2,0].concat(u.call(this.indentation(f)))),4)})},a.prototype.tagPostfixConditionals=function(){var a,b,c;return c=null,b=function(a,b){var c;return(c=a[0])==="TERMINATOR"||c==="INDENT"},a=function(a,b){if(a[0]!=="INDENT"||a.generated&&!a.fromThen)return c[0]="POST_"+c[0]},this.scanTokens(function(d,e){return d[0]!=="IF"?1:(c=d,this.detectEnd(e+1,b,a),1)})},a.prototype.indentation=function(a,b){var c,d;return b==null&&(b=!1),c=["INDENT",2,a[2]],d=["OUTDENT",2,a[2]],b&&(c.generated=d.generated=!0),[c,d]},a.prototype.generate=function(a,b,c){var d;return d=[a,b,c],d.generated=!0,d},a.prototype.tag=function(a){var b;return(b=this.tokens[a])!=null?b[0]:void 0},a}(),b=[["(",")"],["[","]"],["{","}"],["INDENT","OUTDENT"],["CALL_START","CALL_END"],["PARAM_START","PARAM_END"],["INDEX_START","INDEX_END"]],a.INVERSES=k={},e=[],d=[];for(q=0,r=b.length;q","=>","[","(","{","--","++"],j=["+","-"],f=["->","=>","{","[",","],h=["POST_IF","FOR","WHILE","UNTIL","WHEN","BY","LOOP","TERMINATOR"],n=["ELSE","->","=>","TRY","FINALLY","THEN"],m=["TERMINATOR","CATCH","FINALLY","ELSE","OUTDENT","LEADING_WHEN"],l=["TERMINATOR","INDENT","OUTDENT"]})).call(this)},require["./lexer"]=new function(){var a=this;((function(){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X=[].indexOf||function(a){for(var b=0,c=this.length;b=0||X.call(g,c)>=0)&&(j=c.toUpperCase(),j==="WHEN"&&(l=this.tag(),X.call(v,l)>=0)?j="LEADING_WHEN":j==="FOR"?this.seenFor=!0:j==="UNLESS"?j="IF":X.call(O,j)>=0?j="UNARY":X.call(H,j)>=0&&(j!=="INSTANCEOF"&&this.seenFor?(j="FOR"+j,this.seenFor=!1):(j="RELATION",this.value()==="!"&&(this.tokens.pop(),c="!"+c)))),X.call(t,c)>=0&&(b?(j="IDENTIFIER",c=new String(c),c.reserved=!0):X.call(I,c)>=0&&this.error('reserved word "'+c+'"')),b||(X.call(e,c)>=0&&(c=f[c]),j=function(){switch(c){case"!":return"UNARY";case"==":case"!=":return"COMPARE";case"&&":case"||":return"LOGIC";case"true":case"false":return"BOOL";case"break":case"continue":return"STATEMENT";default:return j}}()),this.token(j,c),a&&this.token(":",":"),d.length)):0},a.prototype.numberToken=function(){var a,b,c,d,e;if(!(c=E.exec(this.chunk)))return 0;d=c[0],/^0[BOX]/.test(d)?this.error("radix prefix '"+d+"' must be lowercase"):/E/.test(d)&&!/^0x/.test(d)?this.error("exponential notation '"+d+"' must be indicated with a lowercase 'e'"):/^0\d*[89]/.test(d)?this.error("decimal literal '"+d+"' must not be prefixed with '0'"):/^0\d+/.test(d)&&this.error("octal literal '"+d+"' must be prefixed with '0o'"),b=d.length;if(e=/^0o([0-7]+)/.exec(d))d="0x"+parseInt(e[1],8).toString(16);if(a=/^0b([01]+)/.exec(d))d="0x"+parseInt(a[1],2).toString(16);return this.token("NUMBER",d),b},a.prototype.stringToken=function(){var a,b,c;switch(this.chunk.charAt(0)){case"'":if(!(a=L.exec(this.chunk)))return 0;this.token("STRING",(c=a[0]).replace(A,"\\\n"));break;case'"':if(!(c=this.balancedString(this.chunk,'"')))return 0;0=0)?0:(c=G.exec(this.chunk))?(g=c,c=g[0],e=g[1],a=g[2],e.slice(0,2)==="/*"&&this.error("regular expressions cannot begin with `*`"),e==="//"&&(e="/(?:)/"),this.token("REGEX",""+e+a),c.length):0)},a.prototype.heregexToken=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n;d=a[0],b=a[1],c=a[2];if(0>b.indexOf("#{"))return e=b.replace(o,"").replace(/\//g,"\\/"),e.match(/^\*/)&&this.error("regular expressions cannot begin with `*`"),this.token("REGEX","/"+(e||"(?:)")+"/"+c),d.length;this.token("IDENTIFIER","RegExp"),this.tokens.push(["CALL_START","("]),g=[],k=this.interpolateString(b,{regex:!0});for(i=0,j=k.length;ithis.indent){if(d)return this.indebt=e-this.indent,this.suppressNewlines(),b.length;a=e-this.indent+this.outdebt,this.token("INDENT",a),this.indents.push(a),this.ends.push("OUTDENT"),this.outdebt=this.indebt=0}else this.indebt=0,this.outdentToken(this.indent-e,d);return this.indent=e,b.length},a.prototype.outdentToken=function(a,b){var c,d;while(a>0)d=this.indents.length-1,this.indents[d]===void 0?a=0:this.indents[d]===this.outdebt?(a-=this.outdebt,this.outdebt=0):this.indents[d]=0)&&this.error('reserved word "'+this.value()+"\" can't be assigned");if((h=b[1])==="||"||h==="&&")return b[0]="COMPOUND_ASSIGN",b[1]+="=",f.length}if(f===";")this.seenFor=!1,e="TERMINATOR";else if(X.call(z,f)>=0)e="MATH";else if(X.call(i,f)>=0)e="COMPARE";else if(X.call(j,f)>=0)e="COMPOUND_ASSIGN";else if(X.call(O,f)>=0)e="UNARY";else if(X.call(K,f)>=0)e="SHIFT";else if(X.call(x,f)>=0||f==="?"&&(b!=null?b.spaced:void 0))e="LOGIC";else if(b&&!b.spaced)if(f==="("&&(k=b[0],X.call(c,k)>=0))b[0]==="?"&&(b[0]="FUNC_EXIST"),e="CALL_START";else if(f==="["&&(l=b[0],X.call(q,l)>=0)){e="INDEX_START";switch(b[0]){case"?":b[0]="INDEX_SOAK"}}switch(f){case"(":case"{":case"[":this.ends.push(r[f]);break;case")":case"}":case"]":this.pair(f)}return this.token(e,f),f.length},a.prototype.sanitizeHeredoc=function(a,b){var c,d,e,f,g;e=b.indent,d=b.herecomment;if(d){l.test(a)&&this.error('block comment cannot contain "*/", starting');if(a.indexOf("\n")<=0)return a}else while(f=m.exec(a)){c=f[1];if(e===null||0<(g=c.length)&&gj;d=1<=j?++i:--i){if(c){--c;continue}switch(e=a.charAt(d)){case"\\":++c;continue;case b:h.pop();if(!h.length)return a.slice(0,+d+1||9e9);b=h[h.length-1];continue}b!=="}"||e!=='"'&&e!=="'"?b==="}"&&e==="/"&&(f=n.exec(a.slice(d))||G.exec(a.slice(d)))?c+=f[0].length-1:b==="}"&&e==="{"?h.push(b="}"):b==='"'&&g==="#"&&e==="{"&&h.push(b="}"):h.push(b=e),g=e}return this.error("missing "+h.pop()+", starting")},a.prototype.interpolateString=function(b,c){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;c==null&&(c={}),e=c.heredoc,m=c.regex,o=[],l=0,f=-1;while(j=b.charAt(f+=1)){if(j==="\\"){f+=1;continue}if(j!=="#"||b.charAt(f+1)!=="{"||!(d=this.balancedString(b.slice(f+1),"}")))continue;l1&&(k.unshift(["(","(",this.line]),k.push([")",")",this.line])),o.push(["TOKENS",k])}f+=d.length,l=f+1}f>l&&l1)&&this.token("(","(");for(f=q=0,r=o.length;q|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?\.|\.{2,3})/,P=/^[^\n\S]+/,h=/^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)?$)|^(?:\s*#(?!##[^#]).*)+/,d=/^[-=]>/,B=/^(?:\n[^\n\S]*)+/,L=/^'[^\\']*(?:\\.[^\\']*)*'/,s=/^`[^\\`]*(?:\\.[^\\`]*)*`/,G=/^(\/(?![\s=])[^[\/\n\\]*(?:(?:\\[\s\S]|\[[^\]\n\\]*(?:\\[\s\S][^\]\n\\]*)*])[^[\/\n\\]*)*\/)([imgy]{0,4})(?!\w)/,n=/^\/{3}([\s\S]+?)\/{3}([imgy]{0,4})(?!\w)/,o=/\s+(?:#.*)?/g,A=/\n/g,m=/\n+([^\n\S]*)/g,l=/\*\//,w=/^\s*(?:,|\??\.(?![.\d])|::)/,N=/\s+$/,j=["-=","+=","/=","*=","%=","||=","&&=","?=","<<=",">>=",">>>=","&=","^=","|="],O=["!","~","NEW","TYPEOF","DELETE","DO"],x=["&&","||","&","|","^"],K=["<<",">>",">>>"],i=["==","!=","<",">","<=",">="],z=["*","/","%"],H=["IN","OF","INSTANCEOF"],b=["TRUE","FALSE"],C=["NUMBER","REGEX","BOOL","NULL","UNDEFINED","++","--","]"],D=C.concat(")","}","THIS","IDENTIFIER","STRING"),c=["IDENTIFIER","STRING","REGEX",")","]","}","?","::","@","THIS","SUPER"],q=c.concat("NUMBER","BOOL","NULL","UNDEFINED"),v=["INDENT","OUTDENT","TERMINATOR"]})).call(this)},require["./parser"]=new function(){var a=this,b=function(){var a={trace:function(){},yy:{},symbols_:{error:2,Root:3,Body:4,Block:5,TERMINATOR:6,Line:7,Expression:8,Statement:9,Return:10,Comment:11,STATEMENT:12,Value:13,Invocation:14,Code:15,Operation:16,Assign:17,If:18,Try:19,While:20,For:21,Switch:22,Class:23,Throw:24,INDENT:25,OUTDENT:26,Identifier:27,IDENTIFIER:28,AlphaNumeric:29,NUMBER:30,STRING:31,Literal:32,JS:33,REGEX:34,DEBUGGER:35,UNDEFINED:36,NULL:37,BOOL:38,Assignable:39,"=":40,AssignObj:41,ObjAssignable:42,":":43,ThisProperty:44,RETURN:45,HERECOMMENT:46,PARAM_START:47,ParamList:48,PARAM_END:49,FuncGlyph:50,"->":51,"=>":52,OptComma:53,",":54,Param:55,ParamVar:56,"...":57,Array:58,Object:59,Splat:60,SimpleAssignable:61,Accessor:62,Parenthetical:63,Range:64,This:65,".":66,"?.":67,"::":68,Index:69,INDEX_START:70,IndexValue:71,INDEX_END:72,INDEX_SOAK:73,Slice:74,"{":75,AssignList:76,"}":77,CLASS:78,EXTENDS:79,OptFuncExist:80,Arguments:81,SUPER:82,FUNC_EXIST:83,CALL_START:84,CALL_END:85,ArgList:86,THIS:87,"@":88,"[":89,"]":90,RangeDots:91,"..":92,Arg:93,SimpleArgs:94,TRY:95,Catch:96,FINALLY:97,CATCH:98,THROW:99,"(":100,")":101,WhileSource:102,WHILE:103,WHEN:104,UNTIL:105,Loop:106,LOOP:107,ForBody:108,FOR:109,ForStart:110,ForSource:111,ForVariables:112,OWN:113,ForValue:114,FORIN:115,FOROF:116,BY:117,SWITCH:118,Whens:119,ELSE:120,When:121,LEADING_WHEN:122,IfBlock:123,IF:124,POST_IF:125,UNARY:126,"-":127,"+":128,"--":129,"++":130,"?":131,MATH:132,SHIFT:133,COMPARE:134,LOGIC:135,RELATION:136,COMPOUND_ASSIGN:137,$accept:0,$end:1},terminals_:{2:"error",6:"TERMINATOR",12:"STATEMENT",25:"INDENT",26:"OUTDENT",28:"IDENTIFIER",30:"NUMBER",31:"STRING",33:"JS",34:"REGEX",35:"DEBUGGER",36:"UNDEFINED",37:"NULL",38:"BOOL",40:"=",43:":",45:"RETURN",46:"HERECOMMENT",47:"PARAM_START",49:"PARAM_END",51:"->",52:"=>",54:",",57:"...",66:".",67:"?.",68:"::",70:"INDEX_START",72:"INDEX_END",73:"INDEX_SOAK",75:"{",77:"}",78:"CLASS",79:"EXTENDS",82:"SUPER",83:"FUNC_EXIST",84:"CALL_START",85:"CALL_END",87:"THIS",88:"@",89:"[",90:"]",92:"..",95:"TRY",97:"FINALLY",98:"CATCH",99:"THROW",100:"(",101:")",103:"WHILE",104:"WHEN",105:"UNTIL",107:"LOOP",109:"FOR",113:"OWN",115:"FORIN",116:"FOROF",117:"BY",118:"SWITCH",120:"ELSE",122:"LEADING_WHEN",124:"IF",125:"POST_IF",126:"UNARY",127:"-",128:"+",129:"--",130:"++",131:"?",132:"MATH",133:"SHIFT",134:"COMPARE",135:"LOGIC",136:"RELATION",137:"COMPOUND_ASSIGN"},productions_:[0,[3,0],[3,1],[3,2],[4,1],[4,3],[4,2],[7,1],[7,1],[9,1],[9,1],[9,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[5,2],[5,3],[27,1],[29,1],[29,1],[32,1],[32,1],[32,1],[32,1],[32,1],[32,1],[32,1],[17,3],[17,4],[17,5],[41,1],[41,3],[41,5],[41,1],[42,1],[42,1],[42,1],[10,2],[10,1],[11,1],[15,5],[15,2],[50,1],[50,1],[53,0],[53,1],[48,0],[48,1],[48,3],[48,4],[48,6],[55,1],[55,2],[55,3],[56,1],[56,1],[56,1],[56,1],[60,2],[61,1],[61,2],[61,2],[61,1],[39,1],[39,1],[39,1],[13,1],[13,1],[13,1],[13,1],[13,1],[62,2],[62,2],[62,2],[62,1],[62,1],[69,3],[69,2],[71,1],[71,1],[59,4],[76,0],[76,1],[76,3],[76,4],[76,6],[23,1],[23,2],[23,3],[23,4],[23,2],[23,3],[23,4],[23,5],[14,3],[14,3],[14,1],[14,2],[80,0],[80,1],[81,2],[81,4],[65,1],[65,1],[44,2],[58,2],[58,4],[91,1],[91,1],[64,5],[74,3],[74,2],[74,2],[74,1],[86,1],[86,3],[86,4],[86,4],[86,6],[93,1],[93,1],[94,1],[94,3],[19,2],[19,3],[19,4],[19,5],[96,3],[24,2],[63,3],[63,5],[102,2],[102,4],[102,2],[102,4],[20,2],[20,2],[20,2],[20,1],[106,2],[106,2],[21,2],[21,2],[21,2],[108,2],[108,2],[110,2],[110,3],[114,1],[114,1],[114,1],[114,1],[112,1],[112,3],[111,2],[111,2],[111,4],[111,4],[111,4],[111,6],[111,6],[22,5],[22,7],[22,4],[22,6],[119,1],[119,2],[121,3],[121,4],[123,3],[123,5],[18,1],[18,3],[18,3],[18,3],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,5],[16,3]],performAction:function(b,c,d,e,f,g,h){var i=g.length-1;switch(f){case 1:return this.$=new e.Block;case 2:return this.$=g[i];case 3:return this.$=g[i-1];case 4:this.$=e.Block.wrap([g[i]]);break;case 5:this.$=g[i-2].push(g[i]);break;case 6:this.$=g[i-1];break;case 7:this.$=g[i];break;case 8:this.$=g[i];break;case 9:this.$=g[i];break;case 10:this.$=g[i];break;case 11:this.$=new e.Literal(g[i]);break;case 12:this.$=g[i];break;case 13:this.$=g[i];break;case 14:this.$=g[i];break;case 15:this.$=g[i];break;case 16:this.$=g[i];break;case 17:this.$=g[i];break;case 18:this.$=g[i];break;case 19:this.$=g[i];break;case 20:this.$=g[i];break;case 21:this.$=g[i];break;case 22:this.$=g[i];break;case 23:this.$=g[i];break;case 24:this.$=new e.Block;break;case 25:this.$=g[i-1];break;case 26:this.$=new e.Literal(g[i]);break;case 27:this.$=new e.Literal(g[i]);break;case 28:this.$=new e.Literal(g[i]);break;case 29:this.$=g[i];break;case 30:this.$=new e.Literal(g[i]);break;case 31:this.$=new e.Literal(g[i]);break;case 32:this.$=new e.Literal(g[i]);break;case 33:this.$=new e.Undefined;break;case 34:this.$=new e.Null;break;case 35:this.$=new e.Bool(g[i]);break;case 36:this.$=new e.Assign(g[i-2],g[i]);break;case 37:this.$=new e.Assign(g[i-3],g[i]);break;case 38:this.$=new e.Assign(g[i-4],g[i-1]);break;case 39:this.$=new e.Value(g[i]);break;case 40:this.$=new e.Assign(new e.Value(g[i-2]),g[i],"object");break;case 41:this.$=new e.Assign(new e.Value(g[i-4]),g[i-1],"object");break;case 42:this.$=g[i];break;case 43:this.$=g[i];break;case 44:this.$=g[i];break;case 45:this.$=g[i];break;case 46:this.$=new e.Return(g[i]);break;case 47:this.$=new e.Return;break;case 48:this.$=new e.Comment(g[i]);break;case 49:this.$=new e.Code(g[i-3],g[i],g[i-1]);break;case 50:this.$=new e.Code([],g[i],g[i-1]);break;case 51:this.$="func";break;case 52:this.$="boundfunc";break;case 53:this.$=g[i];break;case 54:this.$=g[i];break;case 55:this.$=[];break;case 56:this.$=[g[i]];break;case 57:this.$=g[i-2].concat(g[i]);break;case 58:this.$=g[i-3].concat(g[i]);break;case 59:this.$=g[i-5].concat(g[i-2]);break;case 60:this.$=new e.Param(g[i]);break;case 61:this.$=new e.Param(g[i-1],null,!0);break;case 62:this.$=new e.Param(g[i-2],g[i]);break;case 63:this.$=g[i];break;case 64:this.$=g[i];break;case 65:this.$=g[i];break;case 66:this.$=g[i];break;case 67:this.$=new e.Splat(g[i-1]);break;case 68:this.$=new e.Value(g[i]);break;case 69:this.$=g[i-1].add(g[i]);break;case 70:this.$=new e.Value(g[i-1],[].concat(g[i]));break;case 71:this.$=g[i];break;case 72:this.$=g[i];break;case 73:this.$=new e.Value(g[i]);break;case 74:this.$=new e.Value(g[i]);break;case 75:this.$=g[i];break;case 76:this.$=new e.Value(g[i]);break;case 77:this.$=new e.Value(g[i]);break;case 78:this.$=new e.Value(g[i]);break;case 79:this.$=g[i];break;case 80:this.$=new e.Access(g[i]);break;case 81:this.$=new e.Access(g[i],"soak");break;case 82:this.$=[new e.Access(new e.Literal("prototype")),new e.Access(g[i])];break;case 83:this.$=new e.Access(new e.Literal("prototype"));break;case 84:this.$=g[i];break;case 85:this.$=g[i-1];break;case 86:this.$=e.extend(g[i],{soak:!0});break;case 87:this.$=new e.Index(g[i]);break;case 88:this.$=new e.Slice(g[i]);break;case 89:this.$=new e.Obj(g[i-2],g[i-3].generated);break;case 90:this.$=[];break;case 91:this.$=[g[i]];break;case 92:this.$=g[i-2].concat(g[i]);break;case 93:this.$=g[i-3].concat(g[i]);break;case 94:this.$=g[i-5].concat(g[i-2]);break;case 95:this.$=new e.Class;break;case 96:this.$=new e.Class(null,null,g[i]);break;case 97:this.$=new e.Class(null,g[i]);break;case 98:this.$=new e.Class(null,g[i-1],g[i]);break;case 99:this.$=new e.Class(g[i]);break;case 100:this.$=new e.Class(g[i-1],null,g[i]);break;case 101:this.$=new e.Class(g[i-2],g[i]);break;case 102:this.$=new e.Class(g[i-3],g[i-1],g[i]);break;case 103:this.$=new e.Call(g[i-2],g[i],g[i-1]);break;case 104:this.$=new e.Call(g[i-2],g[i],g[i-1]);break;case 105:this.$=new e.Call("super",[new e.Splat(new e.Literal("arguments"))]);break;case 106:this.$=new e.Call("super",g[i]);break;case 107:this.$=!1;break;case 108:this.$=!0;break;case 109:this.$=[];break;case 110:this.$=g[i-2];break;case 111:this.$=new e.Value(new e.Literal("this"));break;case 112:this.$=new e.Value(new e.Literal("this"));break;case 113:this.$=new e.Value(new e.Literal("this"),[new e.Access(g[i])],"this");break;case 114:this.$=new e.Arr([]);break;case 115:this.$=new e.Arr(g[i-2]);break;case 116:this.$="inclusive";break;case 117:this.$="exclusive";break;case 118:this.$=new e.Range(g[i-3],g[i-1],g[i-2]);break;case 119:this.$=new e.Range(g[i-2],g[i],g[i-1]);break;case 120:this.$=new e.Range(g[i-1],null,g[i]);break;case 121:this.$=new e.Range(null,g[i],g[i-1]);break;case 122:this.$=new e.Range(null,null,g[i]);break;case 123:this.$=[g[i]];break;case 124:this.$=g[i-2].concat(g[i]);break;case 125:this.$=g[i-3].concat(g[i]);break;case 126:this.$=g[i-2];break;case 127:this.$=g[i-5].concat(g[i-2]);break;case 128:this.$=g[i];break;case 129:this.$=g[i];break;case 130:this.$=g[i];break;case 131:this.$=[].concat(g[i-2],g[i]);break;case 132:this.$=new e.Try(g[i]);break;case 133:this.$=new e.Try(g[i-1],g[i][0],g[i][1]);break;case 134:this.$=new e.Try(g[i-2],null,null,g[i]);break;case 135:this.$=new e.Try(g[i-3],g[i-2][0],g[i-2][1],g[i]);break;case 136:this.$=[g[i-1],g[i]];break;case 137:this.$=new e.Throw(g[i]);break;case 138:this.$=new e.Parens(g[i-1]);break;case 139:this.$=new e.Parens(g[i-2]);break;case 140:this.$=new e.While(g[i]);break;case 141:this.$=new e.While(g[i-2],{guard:g[i]});break;case 142:this.$=new e.While(g[i],{invert:!0});break;case 143:this.$=new e.While(g[i-2],{invert:!0,guard:g[i]});break;case 144:this.$=g[i-1].addBody(g[i]);break;case 145:this.$=g[i].addBody(e.Block.wrap([g[i-1]]));break;case 146:this.$=g[i].addBody(e.Block.wrap([g[i-1]]));break;case 147:this.$=g[i];break;case 148:this.$=(new e.While(new e.Literal("true"))).addBody(g[i]);break;case 149:this.$=(new e.While(new e.Literal("true"))).addBody(e.Block.wrap([g[i]]));break;case 150:this.$=new e.For(g[i-1],g[i]);break;case 151:this.$=new e.For(g[i-1],g[i]);break;case 152:this.$=new e.For(g[i],g[i-1]);break;case 153:this.$={source:new e.Value(g[i])};break;case 154:this.$=function(){return g[i].own=g[i-1].own,g[i].name=g[i-1][0],g[i].index=g[i-1][1],g[i]}();break;case 155:this.$=g[i];break;case 156:this.$=function(){return g[i].own=!0,g[i]}();break;case 157:this.$=g[i];break;case 158:this.$=g[i];break;case 159:this.$=new e.Value(g[i]);break;case 160:this.$=new e.Value(g[i]);break;case 161:this.$=[g[i]];break;case 162:this.$=[g[i-2],g[i]];break;case 163:this.$={source:g[i]};break;case 164:this.$={source:g[i],object:!0};break;case 165:this.$={source:g[i-2],guard:g[i]};break;case 166:this.$={source:g[i-2],guard:g[i],object:!0};break;case 167:this.$={source:g[i-2],step:g[i]};break;case 168:this.$={source:g[i-4],guard:g[i-2],step:g[i]};break;case 169:this.$={source:g[i-4],step:g[i-2],guard:g[i]};break;case 170:this.$=new e.Switch(g[i-3],g[i-1]);break;case 171:this.$=new e.Switch(g[i-5],g[i-3],g[i-1]);break;case 172:this.$=new e.Switch(null,g[i-1]);break;case 173:this.$=new e.Switch(null,g[i-3],g[i-1]);break;case 174:this.$=g[i];break;case 175:this.$=g[i-1].concat(g[i]);break;case 176:this.$=[[g[i-1],g[i]]];break;case 177:this.$=[[g[i-2],g[i-1]]];break;case 178:this.$=new e.If(g[i-1],g[i],{type:g[i-2]});break;case 179:this.$=g[i-4].addElse(new e.If(g[i-1],g[i],{type:g[i-2]}));break;case 180:this.$=g[i];break;case 181:this.$=g[i-2].addElse(g[i]);break;case 182:this.$=new e.If(g[i],e.Block.wrap([g[i-2]]),{type:g[i-1],statement:!0});break;case 183:this.$=new e.If(g[i],e.Block.wrap([g[i-2]]),{type:g[i-1],statement:!0});break;case 184:this.$=new e.Op(g[i-1],g[i]);break;case 185:this.$=new e.Op("-",g[i]);break;case 186:this.$=new e.Op("+",g[i]);break;case 187:this.$=new e.Op("--",g[i]);break;case 188:this.$=new e.Op("++",g[i]);break;case 189:this.$=new e.Op("--",g[i-1],null,!0);break;case 190:this.$=new e.Op("++",g[i-1],null,!0);break;case 191:this.$=new e.Existence(g[i-1]);break;case 192:this.$=new e.Op("+",g[i-2],g[i]);break;case 193:this.$=new e.Op("-",g[i-2],g[i]);break;case 194:this.$=new e.Op(g[i-1],g[i-2],g[i]);break;case 195:this.$=new e.Op(g[i-1],g[i-2],g[i]);break;case 196:this.$=new e.Op(g[i-1],g[i-2],g[i]);break;case 197:this.$=new e.Op(g[i-1],g[i-2],g[i]);break;case 198:this.$=function(){return g[i-1].charAt(0)==="!"?(new e.Op(g[i-1].slice(1),g[i-2],g[i])).invert():new e.Op(g[i-1],g[i-2],g[i])}();break;case 199:this.$=new e.Assign(g[i-2],g[i],g[i-1]);break;case 200:this.$=new e.Assign(g[i-4],g[i-1],g[i-3]);break;case 201:this.$=new e.Extends(g[i-2],g[i])}},table:[{1:[2,1],3:1,4:2,5:3,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,5],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[3]},{1:[2,2],6:[1,74]},{6:[1,75]},{1:[2,4],6:[2,4],26:[2,4],101:[2,4]},{4:77,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[1,76],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,7],6:[2,7],26:[2,7],101:[2,7],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,8],6:[2,8],26:[2,8],101:[2,8],102:90,103:[1,65],105:[1,66],108:91,109:[1,68],110:69,125:[1,89]},{1:[2,12],6:[2,12],25:[2,12],26:[2,12],49:[2,12],54:[2,12],57:[2,12],62:93,66:[1,95],67:[1,96],68:[1,97],69:98,70:[1,99],72:[2,12],73:[1,100],77:[2,12],80:92,83:[1,94],84:[2,107],85:[2,12],90:[2,12],92:[2,12],101:[2,12],103:[2,12],104:[2,12],105:[2,12],109:[2,12],117:[2,12],125:[2,12],127:[2,12],128:[2,12],131:[2,12],132:[2,12],133:[2,12],134:[2,12],135:[2,12],136:[2,12]},{1:[2,13],6:[2,13],25:[2,13],26:[2,13],49:[2,13],54:[2,13],57:[2,13],62:102,66:[1,95],67:[1,96],68:[1,97],69:98,70:[1,99],72:[2,13],73:[1,100],77:[2,13],80:101,83:[1,94],84:[2,107],85:[2,13],90:[2,13],92:[2,13],101:[2,13],103:[2,13],104:[2,13],105:[2,13],109:[2,13],117:[2,13],125:[2,13],127:[2,13],128:[2,13],131:[2,13],132:[2,13],133:[2,13],134:[2,13],135:[2,13],136:[2,13]},{1:[2,14],6:[2,14],25:[2,14],26:[2,14],49:[2,14],54:[2,14],57:[2,14],72:[2,14],77:[2,14],85:[2,14],90:[2,14],92:[2,14],101:[2,14],103:[2,14],104:[2,14],105:[2,14],109:[2,14],117:[2,14],125:[2,14],127:[2,14],128:[2,14],131:[2,14],132:[2,14],133:[2,14],134:[2,14],135:[2,14],136:[2,14]},{1:[2,15],6:[2,15],25:[2,15],26:[2,15],49:[2,15],54:[2,15],57:[2,15],72:[2,15],77:[2,15],85:[2,15],90:[2,15],92:[2,15],101:[2,15],103:[2,15],104:[2,15],105:[2,15],109:[2,15],117:[2,15],125:[2,15],127:[2,15],128:[2,15],131:[2,15],132:[2,15],133:[2,15],134:[2,15],135:[2,15],136:[2,15]},{1:[2,16],6:[2,16],25:[2,16],26:[2,16],49:[2,16],54:[2,16],57:[2,16],72:[2,16],77:[2,16],85:[2,16],90:[2,16],92:[2,16],101:[2,16],103:[2,16],104:[2,16],105:[2,16],109:[2,16],117:[2,16],125:[2,16],127:[2,16],128:[2,16],131:[2,16],132:[2,16],133:[2,16],134:[2,16],135:[2,16],136:[2,16]},{1:[2,17],6:[2,17],25:[2,17],26:[2,17],49:[2,17],54:[2,17],57:[2,17],72:[2,17],77:[2,17],85:[2,17],90:[2,17],92:[2,17],101:[2,17],103:[2,17],104:[2,17],105:[2,17],109:[2,17],117:[2,17],125:[2,17],127:[2,17],128:[2,17],131:[2,17],132:[2,17],133:[2,17],134:[2,17],135:[2,17],136:[2,17]},{1:[2,18],6:[2,18],25:[2,18],26:[2,18],49:[2,18],54:[2,18],57:[2,18],72:[2,18],77:[2,18],85:[2,18],90:[2,18],92:[2,18],101:[2,18],103:[2,18],104:[2,18],105:[2,18],109:[2,18],117:[2,18],125:[2,18],127:[2,18],128:[2,18],131:[2,18],132:[2,18],133:[2,18],134:[2,18],135:[2,18],136:[2,18]},{1:[2,19],6:[2,19],25:[2,19],26:[2,19],49:[2,19],54:[2,19],57:[2,19],72:[2,19],77:[2,19],85:[2,19],90:[2,19],92:[2,19],101:[2,19],103:[2,19],104:[2,19],105:[2,19],109:[2,19],117:[2,19],125:[2,19],127:[2,19],128:[2,19],131:[2,19],132:[2,19],133:[2,19],134:[2,19],135:[2,19],136:[2,19]},{1:[2,20],6:[2,20],25:[2,20],26:[2,20],49:[2,20],54:[2,20],57:[2,20],72:[2,20],77:[2,20],85:[2,20],90:[2,20],92:[2,20],101:[2,20],103:[2,20],104:[2,20],105:[2,20],109:[2,20],117:[2,20],125:[2,20],127:[2,20],128:[2,20],131:[2,20],132:[2,20],133:[2,20],134:[2,20],135:[2,20],136:[2,20]},{1:[2,21],6:[2,21],25:[2,21],26:[2,21],49:[2,21],54:[2,21],57:[2,21],72:[2,21],77:[2,21],85:[2,21],90:[2,21],92:[2,21],101:[2,21],103:[2,21],104:[2,21],105:[2,21],109:[2,21],117:[2,21],125:[2,21],127:[2,21],128:[2,21],131:[2,21],132:[2,21],133:[2,21],134:[2,21],135:[2,21],136:[2,21]},{1:[2,22],6:[2,22],25:[2,22],26:[2,22],49:[2,22],54:[2,22],57:[2,22],72:[2,22],77:[2,22],85:[2,22],90:[2,22],92:[2,22],101:[2,22],103:[2,22],104:[2,22],105:[2,22],109:[2,22],117:[2,22],125:[2,22],127:[2,22],128:[2,22],131:[2,22],132:[2,22],133:[2,22],134:[2,22],135:[2,22],136:[2,22]},{1:[2,23],6:[2,23],25:[2,23],26:[2,23],49:[2,23],54:[2,23],57:[2,23],72:[2,23],77:[2,23],85:[2,23],90:[2,23],92:[2,23],101:[2,23],103:[2,23],104:[2,23],105:[2,23],109:[2,23],117:[2,23],125:[2,23],127:[2,23],128:[2,23],131:[2,23],132:[2,23],133:[2,23],134:[2,23],135:[2,23],136:[2,23]},{1:[2,9],6:[2,9],26:[2,9],101:[2,9],103:[2,9],105:[2,9],109:[2,9],125:[2,9]},{1:[2,10],6:[2,10],26:[2,10],101:[2,10],103:[2,10],105:[2,10],109:[2,10],125:[2,10]},{1:[2,11],6:[2,11],26:[2,11],101:[2,11],103:[2,11],105:[2,11],109:[2,11],125:[2,11]},{1:[2,75],6:[2,75],25:[2,75],26:[2,75],40:[1,103],49:[2,75],54:[2,75],57:[2,75],66:[2,75],67:[2,75],68:[2,75],70:[2,75],72:[2,75],73:[2,75],77:[2,75],83:[2,75],84:[2,75],85:[2,75],90:[2,75],92:[2,75],101:[2,75],103:[2,75],104:[2,75],105:[2,75],109:[2,75],117:[2,75],125:[2,75],127:[2,75],128:[2,75],131:[2,75],132:[2,75],133:[2,75],134:[2,75],135:[2,75],136:[2,75]},{1:[2,76],6:[2,76],25:[2,76],26:[2,76],49:[2,76],54:[2,76],57:[2,76],66:[2,76],67:[2,76],68:[2,76],70:[2,76],72:[2,76],73:[2,76],77:[2,76],83:[2,76],84:[2,76],85:[2,76],90:[2,76],92:[2,76],101:[2,76],103:[2,76],104:[2,76],105:[2,76],109:[2,76],117:[2,76],125:[2,76],127:[2,76],128:[2,76],131:[2,76],132:[2,76],133:[2,76],134:[2,76],135:[2,76],136:[2,76]},{1:[2,77],6:[2,77],25:[2,77],26:[2,77],49:[2,77],54:[2,77],57:[2,77],66:[2,77],67:[2,77],68:[2,77],70:[2,77],72:[2,77],73:[2,77],77:[2,77],83:[2,77],84:[2,77],85:[2,77],90:[2,77],92:[2,77],101:[2,77],103:[2,77],104:[2,77],105:[2,77],109:[2,77],117:[2,77],125:[2,77],127:[2,77],128:[2,77],131:[2,77],132:[2,77],133:[2,77],134:[2,77],135:[2,77],136:[2,77]},{1:[2,78],6:[2,78],25:[2,78],26:[2,78],49:[2,78],54:[2,78],57:[2,78],66:[2,78],67:[2,78],68:[2,78],70:[2,78],72:[2,78],73:[2,78],77:[2,78],83:[2,78],84:[2,78],85:[2,78],90:[2,78],92:[2,78],101:[2,78],103:[2,78],104:[2,78],105:[2,78],109:[2,78],117:[2,78],125:[2,78],127:[2,78],128:[2,78],131:[2,78],132:[2,78],133:[2,78],134:[2,78],135:[2,78],136:[2,78]},{1:[2,79],6:[2,79],25:[2,79],26:[2,79],49:[2,79],54:[2,79],57:[2,79],66:[2,79],67:[2,79],68:[2,79],70:[2,79],72:[2,79],73:[2,79],77:[2,79],83:[2,79],84:[2,79],85:[2,79],90:[2,79],92:[2,79],101:[2,79],103:[2,79],104:[2,79],105:[2,79],109:[2,79],117:[2,79],125:[2,79],127:[2,79],128:[2,79],131:[2,79],132:[2,79],133:[2,79],134:[2,79],135:[2,79],136:[2,79]},{1:[2,105],6:[2,105],25:[2,105],26:[2,105],49:[2,105],54:[2,105],57:[2,105],66:[2,105],67:[2,105],68:[2,105],70:[2,105],72:[2,105],73:[2,105],77:[2,105],81:104,83:[2,105],84:[1,105],85:[2,105],90:[2,105],92:[2,105],101:[2,105],103:[2,105],104:[2,105],105:[2,105],109:[2,105],117:[2,105],125:[2,105],127:[2,105],128:[2,105],131:[2,105],132:[2,105],133:[2,105],134:[2,105],135:[2,105],136:[2,105]},{6:[2,55],25:[2,55],27:109,28:[1,73],44:110,48:106,49:[2,55],54:[2,55],55:107,56:108,58:111,59:112,75:[1,70],88:[1,113],89:[1,114]},{5:115,25:[1,5]},{8:116,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:118,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:119,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{13:121,14:122,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:123,44:63,58:47,59:48,61:120,63:25,64:26,65:27,75:[1,70],82:[1,28],87:[1,58],88:[1,59],89:[1,57],100:[1,56]},{13:121,14:122,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:123,44:63,58:47,59:48,61:124,63:25,64:26,65:27,75:[1,70],82:[1,28],87:[1,58],88:[1,59],89:[1,57],100:[1,56]},{1:[2,72],6:[2,72],25:[2,72],26:[2,72],40:[2,72],49:[2,72],54:[2,72],57:[2,72],66:[2,72],67:[2,72],68:[2,72],70:[2,72],72:[2,72],73:[2,72],77:[2,72],79:[1,128],83:[2,72],84:[2,72],85:[2,72],90:[2,72],92:[2,72],101:[2,72],103:[2,72],104:[2,72],105:[2,72],109:[2,72],117:[2,72],125:[2,72],127:[2,72],128:[2,72],129:[1,125],130:[1,126],131:[2,72],132:[2,72],133:[2,72],134:[2,72],135:[2,72],136:[2,72],137:[1,127]},{1:[2,180],6:[2,180],25:[2,180],26:[2,180],49:[2,180],54:[2,180],57:[2,180],72:[2,180],77:[2,180],85:[2,180],90:[2,180],92:[2,180],101:[2,180],103:[2,180],104:[2,180],105:[2,180],109:[2,180],117:[2,180],120:[1,129],125:[2,180],127:[2,180],128:[2,180],131:[2,180],132:[2,180],133:[2,180],134:[2,180],135:[2,180],136:[2,180]},{5:130,25:[1,5]},{5:131,25:[1,5]},{1:[2,147],6:[2,147],25:[2,147],26:[2,147],49:[2,147],54:[2,147],57:[2,147],72:[2,147],77:[2,147],85:[2,147],90:[2,147],92:[2,147],101:[2,147],103:[2,147],104:[2,147],105:[2,147],109:[2,147],117:[2,147],125:[2,147],127:[2,147],128:[2,147],131:[2,147],132:[2,147],133:[2,147],134:[2,147],135:[2,147],136:[2,147]},{5:132,25:[1,5]},{8:133,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,134],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,95],5:135,6:[2,95],13:121,14:122,25:[1,5],26:[2,95],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:123,44:63,49:[2,95],54:[2,95],57:[2,95],58:47,59:48,61:137,63:25,64:26,65:27,72:[2,95],75:[1,70],77:[2,95],79:[1,136],82:[1,28],85:[2,95],87:[1,58],88:[1,59],89:[1,57],90:[2,95],92:[2,95],100:[1,56],101:[2,95],103:[2,95],104:[2,95],105:[2,95],109:[2,95],117:[2,95],125:[2,95],127:[2,95],128:[2,95],131:[2,95],132:[2,95],133:[2,95],134:[2,95],135:[2,95],136:[2,95]},{8:138,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,47],6:[2,47],8:139,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[2,47],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],101:[2,47],102:39,103:[2,47],105:[2,47],106:40,107:[1,67],108:41,109:[2,47],110:69,118:[1,42],123:37,124:[1,64],125:[2,47],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,48],6:[2,48],25:[2,48],26:[2,48],54:[2,48],77:[2,48],101:[2,48],103:[2,48],105:[2,48],109:[2,48],125:[2,48]},{1:[2,73],6:[2,73],25:[2,73],26:[2,73],40:[2,73],49:[2,73],54:[2,73],57:[2,73],66:[2,73],67:[2,73],68:[2,73],70:[2,73],72:[2,73],73:[2,73],77:[2,73],83:[2,73],84:[2,73],85:[2,73],90:[2,73],92:[2,73],101:[2,73],103:[2,73],104:[2,73],105:[2,73],109:[2,73],117:[2,73],125:[2,73],127:[2,73],128:[2,73],131:[2,73],132:[2,73],133:[2,73],134:[2,73],135:[2,73],136:[2,73]},{1:[2,74],6:[2,74],25:[2,74],26:[2,74],40:[2,74],49:[2,74],54:[2,74],57:[2,74],66:[2,74],67:[2,74],68:[2,74],70:[2,74],72:[2,74],73:[2,74],77:[2,74],83:[2,74],84:[2,74],85:[2,74],90:[2,74],92:[2,74],101:[2,74],103:[2,74],104:[2,74],105:[2,74],109:[2,74],117:[2,74],125:[2,74],127:[2,74],128:[2,74],131:[2,74],132:[2,74],133:[2,74],134:[2,74],135:[2,74],136:[2,74]},{1:[2,29],6:[2,29],25:[2,29],26:[2,29],49:[2,29],54:[2,29],57:[2,29],66:[2,29],67:[2,29],68:[2,29],70:[2,29],72:[2,29],73:[2,29],77:[2,29],83:[2,29],84:[2,29],85:[2,29],90:[2,29],92:[2,29],101:[2,29],103:[2,29],104:[2,29],105:[2,29],109:[2,29],117:[2,29],125:[2,29],127:[2,29],128:[2,29],131:[2,29],132:[2,29],133:[2,29],134:[2,29],135:[2,29],136:[2,29]},{1:[2,30],6:[2,30],25:[2,30],26:[2,30],49:[2,30],54:[2,30],57:[2,30],66:[2,30],67:[2,30],68:[2,30],70:[2,30],72:[2,30],73:[2,30],77:[2,30],83:[2,30],84:[2,30],85:[2,30],90:[2,30],92:[2,30],101:[2,30],103:[2,30],104:[2,30],105:[2,30],109:[2,30],117:[2,30],125:[2,30],127:[2,30],128:[2,30],131:[2,30],132:[2,30],133:[2,30],134:[2,30],135:[2,30],136:[2,30]},{1:[2,31],6:[2,31],25:[2,31],26:[2,31],49:[2,31],54:[2,31],57:[2,31],66:[2,31],67:[2,31],68:[2,31],70:[2,31],72:[2,31],73:[2,31],77:[2,31],83:[2,31],84:[2,31],85:[2,31],90:[2,31],92:[2,31],101:[2,31],103:[2,31],104:[2,31],105:[2,31],109:[2,31],117:[2,31],125:[2,31],127:[2,31],128:[2,31],131:[2,31],132:[2,31],133:[2,31],134:[2,31],135:[2,31],136:[2,31]},{1:[2,32],6:[2,32],25:[2,32],26:[2,32],49:[2,32],54:[2,32],57:[2,32],66:[2,32],67:[2,32],68:[2,32],70:[2,32],72:[2,32],73:[2,32],77:[2,32],83:[2,32],84:[2,32],85:[2,32],90:[2,32],92:[2,32],101:[2,32],103:[2,32],104:[2,32],105:[2,32],109:[2,32],117:[2,32],125:[2,32],127:[2,32],128:[2,32],131:[2,32],132:[2,32],133:[2,32],134:[2,32],135:[2,32],136:[2,32]},{1:[2,33],6:[2,33],25:[2,33],26:[2,33],49:[2,33],54:[2,33],57:[2,33],66:[2,33],67:[2,33],68:[2,33],70:[2,33],72:[2,33],73:[2,33],77:[2,33],83:[2,33],84:[2,33],85:[2,33],90:[2,33],92:[2,33],101:[2,33],103:[2,33],104:[2,33],105:[2,33],109:[2,33],117:[2,33],125:[2,33],127:[2,33],128:[2,33],131:[2,33],132:[2,33],133:[2,33],134:[2,33],135:[2,33],136:[2,33]},{1:[2,34],6:[2,34],25:[2,34],26:[2,34],49:[2,34],54:[2,34],57:[2,34],66:[2,34],67:[2,34],68:[2,34],70:[2,34],72:[2,34],73:[2,34],77:[2,34],83:[2,34],84:[2,34],85:[2,34],90:[2,34],92:[2,34],101:[2,34],103:[2,34],104:[2,34],105:[2,34],109:[2,34],117:[2,34],125:[2,34],127:[2,34],128:[2,34],131:[2,34],132:[2,34],133:[2,34],134:[2,34],135:[2,34],136:[2,34]},{1:[2,35],6:[2,35],25:[2,35],26:[2,35],49:[2,35],54:[2,35],57:[2,35],66:[2,35],67:[2,35],68:[2,35],70:[2,35],72:[2,35],73:[2,35],77:[2,35],83:[2,35],84:[2,35],85:[2,35],90:[2,35],92:[2,35],101:[2,35],103:[2,35],104:[2,35],105:[2,35],109:[2,35],117:[2,35],125:[2,35],127:[2,35],128:[2,35],131:[2,35],132:[2,35],133:[2,35],134:[2,35],135:[2,35],136:[2,35]},{4:140,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,141],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:142,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,146],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],86:144,87:[1,58],88:[1,59],89:[1,57],90:[1,143],93:145,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,111],6:[2,111],25:[2,111],26:[2,111],49:[2,111],54:[2,111],57:[2,111],66:[2,111],67:[2,111],68:[2,111],70:[2,111],72:[2,111],73:[2,111],77:[2,111],83:[2,111],84:[2,111],85:[2,111],90:[2,111],92:[2,111],101:[2,111],103:[2,111],104:[2,111],105:[2,111],109:[2,111],117:[2,111],125:[2,111],127:[2,111],128:[2,111],131:[2,111],132:[2,111],133:[2,111],134:[2,111],135:[2,111],136:[2,111]},{1:[2,112],6:[2,112],25:[2,112],26:[2,112],27:148,28:[1,73],49:[2,112],54:[2,112],57:[2,112],66:[2,112],67:[2,112],68:[2,112],70:[2,112],72:[2,112],73:[2,112],77:[2,112],83:[2,112],84:[2,112],85:[2,112],90:[2,112],92:[2,112],101:[2,112],103:[2,112],104:[2,112],105:[2,112],109:[2,112],117:[2,112],125:[2,112],127:[2,112],128:[2,112],131:[2,112],132:[2,112],133:[2,112],134:[2,112],135:[2,112],136:[2,112]},{25:[2,51]},{25:[2,52]},{1:[2,68],6:[2,68],25:[2,68],26:[2,68],40:[2,68],49:[2,68],54:[2,68],57:[2,68],66:[2,68],67:[2,68],68:[2,68],70:[2,68],72:[2,68],73:[2,68],77:[2,68],79:[2,68],83:[2,68],84:[2,68],85:[2,68],90:[2,68],92:[2,68],101:[2,68],103:[2,68],104:[2,68],105:[2,68],109:[2,68],117:[2,68],125:[2,68],127:[2,68],128:[2,68],129:[2,68],130:[2,68],131:[2,68],132:[2,68],133:[2,68],134:[2,68],135:[2,68],136:[2,68],137:[2,68]},{1:[2,71],6:[2,71],25:[2,71],26:[2,71],40:[2,71],49:[2,71],54:[2,71],57:[2,71],66:[2,71],67:[2,71],68:[2,71],70:[2,71],72:[2,71],73:[2,71],77:[2,71],79:[2,71],83:[2,71],84:[2,71],85:[2,71],90:[2,71],92:[2,71],101:[2,71],103:[2,71],104:[2,71],105:[2,71],109:[2,71],117:[2,71],125:[2,71],127:[2,71],128:[2,71],129:[2,71],130:[2,71],131:[2,71],132:[2,71],133:[2,71],134:[2,71],135:[2,71],136:[2,71],137:[2,71]},{8:149,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:150,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:151,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{5:152,8:153,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,5],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{27:158,28:[1,73],44:159,58:160,59:161,64:154,75:[1,70],88:[1,113],89:[1,57],112:155,113:[1,156],114:157},{111:162,115:[1,163],116:[1,164]},{6:[2,90],11:168,25:[2,90],27:169,28:[1,73],29:170,30:[1,71],31:[1,72],41:166,42:167,44:171,46:[1,46],54:[2,90],76:165,77:[2,90],88:[1,113]},{1:[2,27],6:[2,27],25:[2,27],26:[2,27],43:[2,27],49:[2,27],54:[2,27],57:[2,27],66:[2,27],67:[2,27],68:[2,27],70:[2,27],72:[2,27],73:[2,27],77:[2,27],83:[2,27],84:[2,27],85:[2,27],90:[2,27],92:[2,27],101:[2,27],103:[2,27],104:[2,27],105:[2,27],109:[2,27],117:[2,27],125:[2,27],127:[2,27],128:[2,27],131:[2,27],132:[2,27],133:[2,27],134:[2,27],135:[2,27],136:[2,27]},{1:[2,28],6:[2,28],25:[2,28],26:[2,28],43:[2,28],49:[2,28],54:[2,28],57:[2,28],66:[2,28],67:[2,28],68:[2,28],70:[2,28],72:[2,28],73:[2,28],77:[2,28],83:[2,28],84:[2,28],85:[2,28],90:[2,28],92:[2,28],101:[2,28],103:[2,28],104:[2,28],105:[2,28],109:[2,28],117:[2,28],125:[2,28],127:[2,28],128:[2,28],131:[2,28],132:[2,28],133:[2,28],134:[2,28],135:[2,28],136:[2,28]},{1:[2,26],6:[2,26],25:[2,26],26:[2,26],40:[2,26],43:[2,26],49:[2,26],54:[2,26],57:[2,26],66:[2,26],67:[2,26],68:[2,26],70:[2,26],72:[2,26],73:[2,26],77:[2,26],79:[2,26],83:[2,26],84:[2,26],85:[2,26],90:[2,26],92:[2,26],101:[2,26],103:[2,26],104:[2,26],105:[2,26],109:[2,26],115:[2,26],116:[2,26],117:[2,26],125:[2,26],127:[2,26],128:[2,26],129:[2,26],130:[2,26],131:[2,26],132:[2,26],133:[2,26],134:[2,26],135:[2,26],136:[2,26],137:[2,26]},{1:[2,6],6:[2,6],7:172,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[2,6],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],101:[2,6],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,3]},{1:[2,24],6:[2,24],25:[2,24],26:[2,24],49:[2,24],54:[2,24],57:[2,24],72:[2,24],77:[2,24],85:[2,24],90:[2,24],92:[2,24],97:[2,24],98:[2,24],101:[2,24],103:[2,24],104:[2,24],105:[2,24],109:[2,24],117:[2,24],120:[2,24],122:[2,24],125:[2,24],127:[2,24],128:[2,24],131:[2,24],132:[2,24],133:[2,24],134:[2,24],135:[2,24],136:[2,24]},{6:[1,74],26:[1,173]},{1:[2,191],6:[2,191],25:[2,191],26:[2,191],49:[2,191],54:[2,191],57:[2,191],72:[2,191],77:[2,191],85:[2,191],90:[2,191],92:[2,191],101:[2,191],103:[2,191],104:[2,191],105:[2,191],109:[2,191],117:[2,191],125:[2,191],127:[2,191],128:[2,191],131:[2,191],132:[2,191],133:[2,191],134:[2,191],135:[2,191],136:[2,191]},{8:174,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:175,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:176,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:177,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:178,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:179,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:180,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:181,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,146],6:[2,146],25:[2,146],26:[2,146],49:[2,146],54:[2,146],57:[2,146],72:[2,146],77:[2,146],85:[2,146],90:[2,146],92:[2,146],101:[2,146],103:[2,146],104:[2,146],105:[2,146],109:[2,146],117:[2,146],125:[2,146],127:[2,146],128:[2,146],131:[2,146],132:[2,146],133:[2,146],134:[2,146],135:[2,146],136:[2,146]},{1:[2,151],6:[2,151],25:[2,151],26:[2,151],49:[2,151],54:[2,151],57:[2,151],72:[2,151],77:[2,151],85:[2,151],90:[2,151],92:[2,151],101:[2,151],103:[2,151],104:[2,151],105:[2,151],109:[2,151],117:[2,151],125:[2,151],127:[2,151],128:[2,151],131:[2,151],132:[2,151],133:[2,151],134:[2,151],135:[2,151],136:[2,151]},{8:182,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,145],6:[2,145],25:[2,145],26:[2,145],49:[2,145],54:[2,145],57:[2,145],72:[2,145],77:[2,145],85:[2,145],90:[2,145],92:[2,145],101:[2,145],103:[2,145],104:[2,145],105:[2,145],109:[2,145],117:[2,145],125:[2,145],127:[2,145],128:[2,145],131:[2,145],132:[2,145],133:[2,145],134:[2,145],135:[2,145],136:[2,145]},{1:[2,150],6:[2,150],25:[2,150],26:[2,150],49:[2,150],54:[2,150],57:[2,150],72:[2,150],77:[2,150],85:[2,150],90:[2,150],92:[2,150],101:[2,150],103:[2,150],104:[2,150],105:[2,150],109:[2,150],117:[2,150],125:[2,150],127:[2,150],128:[2,150],131:[2,150],132:[2,150],133:[2,150],134:[2,150],135:[2,150],136:[2,150]},{81:183,84:[1,105]},{1:[2,69],6:[2,69],25:[2,69],26:[2,69],40:[2,69],49:[2,69],54:[2,69],57:[2,69],66:[2,69],67:[2,69],68:[2,69],70:[2,69],72:[2,69],73:[2,69],77:[2,69],79:[2,69],83:[2,69],84:[2,69],85:[2,69],90:[2,69],92:[2,69],101:[2,69],103:[2,69],104:[2,69],105:[2,69],109:[2,69],117:[2,69],125:[2,69],127:[2,69],128:[2,69],129:[2,69],130:[2,69],131:[2,69],132:[2,69],133:[2,69],134:[2,69],135:[2,69],136:[2,69],137:[2,69]},{84:[2,108]},{27:184,28:[1,73]},{27:185,28:[1,73]},{1:[2,83],6:[2,83],25:[2,83],26:[2,83],27:186,28:[1,73],40:[2,83],49:[2,83],54:[2,83],57:[2,83],66:[2,83],67:[2,83],68:[2,83],70:[2,83],72:[2,83],73:[2,83],77:[2,83],79:[2,83],83:[2,83],84:[2,83],85:[2,83],90:[2,83],92:[2,83],101:[2,83],103:[2,83],104:[2,83],105:[2,83],109:[2,83],117:[2,83],125:[2,83],127:[2,83],128:[2,83],129:[2,83],130:[2,83],131:[2,83],132:[2,83],133:[2,83],134:[2,83],135:[2,83],136:[2,83],137:[2,83]},{1:[2,84],6:[2,84],25:[2,84],26:[2,84],40:[2,84],49:[2,84],54:[2,84],57:[2,84],66:[2,84],67:[2,84],68:[2,84],70:[2,84],72:[2,84],73:[2,84],77:[2,84],79:[2,84],83:[2,84],84:[2,84],85:[2,84],90:[2,84],92:[2,84],101:[2,84],103:[2,84],104:[2,84],105:[2,84],109:[2,84],117:[2,84],125:[2,84],127:[2,84],128:[2,84],129:[2,84],130:[2,84],131:[2,84],132:[2,84],133:[2,84],134:[2,84],135:[2,84],136:[2,84],137:[2,84]},{8:188,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],57:[1,192],58:47,59:48,61:36,63:25,64:26,65:27,71:187,74:189,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],91:190,92:[1,191],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{69:193,70:[1,99],73:[1,100]},{81:194,84:[1,105]},{1:[2,70],6:[2,70],25:[2,70],26:[2,70],40:[2,70],49:[2,70],54:[2,70],57:[2,70],66:[2,70],67:[2,70],68:[2,70],70:[2,70],72:[2,70],73:[2,70],77:[2,70],79:[2,70],83:[2,70],84:[2,70],85:[2,70],90:[2,70],92:[2,70],101:[2,70],103:[2,70],104:[2,70],105:[2,70],109:[2,70],117:[2,70],125:[2,70],127:[2,70],128:[2,70],129:[2,70],130:[2,70],131:[2,70],132:[2,70],133:[2,70],134:[2,70],135:[2,70],136:[2,70],137:[2,70]},{6:[1,196],8:195,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,197],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,106],6:[2,106],25:[2,106],26:[2,106],49:[2,106],54:[2,106],57:[2,106],66:[2,106],67:[2,106],68:[2,106],70:[2,106],72:[2,106],73:[2,106],77:[2,106],83:[2,106],84:[2,106],85:[2,106],90:[2,106],92:[2,106],101:[2,106],103:[2,106],104:[2,106],105:[2,106],109:[2,106],117:[2,106],125:[2,106],127:[2,106],128:[2,106],131:[2,106],132:[2,106],133:[2,106],134:[2,106],135:[2,106],136:[2,106]},{8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,146],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],85:[1,198],86:199,87:[1,58],88:[1,59],89:[1,57],93:145,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,53],25:[2,53],49:[1,201],53:203,54:[1,202]},{6:[2,56],25:[2,56],26:[2,56],49:[2,56],54:[2,56]},{6:[2,60],25:[2,60],26:[2,60],40:[1,205],49:[2,60],54:[2,60],57:[1,204]},{6:[2,63],25:[2,63],26:[2,63],40:[2,63],49:[2,63],54:[2,63],57:[2,63]},{6:[2,64],25:[2,64],26:[2,64],40:[2,64],49:[2,64],54:[2,64],57:[2,64]},{6:[2,65],25:[2,65],26:[2,65],40:[2,65],49:[2,65],54:[2,65],57:[2,65]},{6:[2,66],25:[2,66],26:[2,66],40:[2,66],49:[2,66],54:[2,66],57:[2,66]},{27:148,28:[1,73]},{8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,146],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],86:144,87:[1,58],88:[1,59],89:[1,57],90:[1,143],93:145,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,50],6:[2,50],25:[2,50],26:[2,50],49:[2,50],54:[2,50],57:[2,50],72:[2,50],77:[2,50],85:[2,50],90:[2,50],92:[2,50],101:[2,50],103:[2,50],104:[2,50],105:[2,50],109:[2,50],117:[2,50],125:[2,50],127:[2,50],128:[2,50],131:[2,50],132:[2,50],133:[2,50],134:[2,50],135:[2,50],136:[2,50]},{1:[2,184],6:[2,184],25:[2,184],26:[2,184],49:[2,184],54:[2,184],57:[2,184],72:[2,184],77:[2,184],85:[2,184],90:[2,184],92:[2,184],101:[2,184],102:87,103:[2,184],104:[2,184],105:[2,184],108:88,109:[2,184],110:69,117:[2,184],125:[2,184],127:[2,184],128:[2,184],131:[1,78],132:[2,184],133:[2,184],134:[2,184],135:[2,184],136:[2,184]},{102:90,103:[1,65],105:[1,66],108:91,109:[1,68],110:69,125:[1,89]},{1:[2,185],6:[2,185],25:[2,185],26:[2,185],49:[2,185],54:[2,185],57:[2,185],72:[2,185],77:[2,185],85:[2,185],90:[2,185],92:[2,185],101:[2,185],102:87,103:[2,185],104:[2,185],105:[2,185],108:88,109:[2,185],110:69,117:[2,185],125:[2,185],127:[2,185],128:[2,185],131:[1,78],132:[2,185],133:[2,185],134:[2,185],135:[2,185],136:[2,185]},{1:[2,186],6:[2,186],25:[2,186],26:[2,186],49:[2,186],54:[2,186],57:[2,186],72:[2,186],77:[2,186],85:[2,186],90:[2,186],92:[2,186],101:[2,186],102:87,103:[2,186],104:[2,186],105:[2,186],108:88,109:[2,186],110:69,117:[2,186],125:[2,186],127:[2,186],128:[2,186],131:[1,78],132:[2,186],133:[2,186],134:[2,186],135:[2,186],136:[2,186]},{1:[2,187],6:[2,187],25:[2,187],26:[2,187],49:[2,187],54:[2,187],57:[2,187],66:[2,72],67:[2,72],68:[2,72],70:[2,72],72:[2,187],73:[2,72],77:[2,187],83:[2,72],84:[2,72],85:[2,187],90:[2,187],92:[2,187],101:[2,187],103:[2,187],104:[2,187],105:[2,187],109:[2,187],117:[2,187],125:[2,187],127:[2,187],128:[2,187],131:[2,187],132:[2,187],133:[2,187],134:[2,187],135:[2,187],136:[2,187]},{62:93,66:[1,95],67:[1,96],68:[1,97],69:98,70:[1,99],73:[1,100],80:92,83:[1,94],84:[2,107]},{62:102,66:[1,95],67:[1,96],68:[1,97],69:98,70:[1,99],73:[1,100],80:101,83:[1,94],84:[2,107]},{66:[2,75],67:[2,75],68:[2,75],70:[2,75],73:[2,75],83:[2,75],84:[2,75]},{1:[2,188],6:[2,188],25:[2,188],26:[2,188],49:[2,188],54:[2,188],57:[2,188],66:[2,72],67:[2,72],68:[2,72],70:[2,72],72:[2,188],73:[2,72],77:[2,188],83:[2,72],84:[2,72],85:[2,188],90:[2,188],92:[2,188],101:[2,188],103:[2,188],104:[2,188],105:[2,188],109:[2,188],117:[2,188],125:[2,188],127:[2,188],128:[2,188],131:[2,188],132:[2,188],133:[2,188],134:[2,188],135:[2,188],136:[2,188]},{1:[2,189],6:[2,189],25:[2,189],26:[2,189],49:[2,189],54:[2,189],57:[2,189],72:[2,189],77:[2,189],85:[2,189],90:[2,189],92:[2,189],101:[2,189],103:[2,189],104:[2,189],105:[2,189],109:[2,189],117:[2,189],125:[2,189],127:[2,189],128:[2,189],131:[2,189],132:[2,189],133:[2,189],134:[2,189],135:[2,189],136:[2,189]},{1:[2,190],6:[2,190],25:[2,190],26:[2,190],49:[2,190],54:[2,190],57:[2,190],72:[2,190],77:[2,190],85:[2,190],90:[2,190],92:[2,190],101:[2,190],103:[2,190],104:[2,190],105:[2,190],109:[2,190],117:[2,190],125:[2,190],127:[2,190],128:[2,190],131:[2,190],132:[2,190],133:[2,190],134:[2,190],135:[2,190],136:[2,190]},{8:206,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,207],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:208,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{5:209,25:[1,5],124:[1,210]},{1:[2,132],6:[2,132],25:[2,132],26:[2,132],49:[2,132],54:[2,132],57:[2,132],72:[2,132],77:[2,132],85:[2,132],90:[2,132],92:[2,132],96:211,97:[1,212],98:[1,213],101:[2,132],103:[2,132],104:[2,132],105:[2,132],109:[2,132],117:[2,132],125:[2,132],127:[2,132],128:[2,132],131:[2,132],132:[2,132],133:[2,132],134:[2,132],135:[2,132],136:[2,132]},{1:[2,144],6:[2,144],25:[2,144],26:[2,144],49:[2,144],54:[2,144],57:[2,144],72:[2,144],77:[2,144],85:[2,144],90:[2,144],92:[2,144],101:[2,144],103:[2,144],104:[2,144],105:[2,144],109:[2,144],117:[2,144],125:[2,144],127:[2,144],128:[2,144],131:[2,144],132:[2,144],133:[2,144],134:[2,144],135:[2,144],136:[2,144]},{1:[2,152],6:[2,152],25:[2,152],26:[2,152],49:[2,152],54:[2,152],57:[2,152],72:[2,152],77:[2,152],85:[2,152],90:[2,152],92:[2,152],101:[2,152],103:[2,152],104:[2,152],105:[2,152],109:[2,152],117:[2,152],125:[2,152],127:[2,152],128:[2,152],131:[2,152],132:[2,152],133:[2,152],134:[2,152],135:[2,152],136:[2,152]},{25:[1,214],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{119:215,121:216,122:[1,217]},{1:[2,96],6:[2,96],25:[2,96],26:[2,96],49:[2,96],54:[2,96],57:[2,96],72:[2,96],77:[2,96],85:[2,96],90:[2,96],92:[2,96],101:[2,96],103:[2,96],104:[2,96],105:[2,96],109:[2,96],117:[2,96],125:[2,96],127:[2,96],128:[2,96],131:[2,96],132:[2,96],133:[2,96],134:[2,96],135:[2,96],136:[2,96]},{8:218,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,99],5:219,6:[2,99],25:[1,5],26:[2,99],49:[2,99],54:[2,99],57:[2,99],66:[2,72],67:[2,72],68:[2,72],70:[2,72],72:[2,99],73:[2,72],77:[2,99],79:[1,220],83:[2,72],84:[2,72],85:[2,99],90:[2,99],92:[2,99],101:[2,99],103:[2,99],104:[2,99],105:[2,99],109:[2,99],117:[2,99],125:[2,99],127:[2,99],128:[2,99],131:[2,99],132:[2,99],133:[2,99],134:[2,99],135:[2,99],136:[2,99]},{1:[2,137],6:[2,137],25:[2,137],26:[2,137],49:[2,137],54:[2,137],57:[2,137],72:[2,137],77:[2,137],85:[2,137],90:[2,137],92:[2,137],101:[2,137],102:87,103:[2,137],104:[2,137],105:[2,137],108:88,109:[2,137],110:69,117:[2,137],125:[2,137],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,46],6:[2,46],26:[2,46],101:[2,46],102:87,103:[2,46],105:[2,46],108:88,109:[2,46],110:69,125:[2,46],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[1,74],101:[1,221]},{4:222,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,128],25:[2,128],54:[2,128],57:[1,224],90:[2,128],91:223,92:[1,191],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,114],6:[2,114],25:[2,114],26:[2,114],40:[2,114],49:[2,114],54:[2,114],57:[2,114],66:[2,114],67:[2,114],68:[2,114],70:[2,114],72:[2,114],73:[2,114],77:[2,114],83:[2,114],84:[2,114],85:[2,114],90:[2,114],92:[2,114],101:[2,114],103:[2,114],104:[2,114],105:[2,114],109:[2,114],115:[2,114],116:[2,114],117:[2,114],125:[2,114],127:[2,114],128:[2,114],131:[2,114],132:[2,114],133:[2,114],134:[2,114],135:[2,114],136:[2,114]},{6:[2,53],25:[2,53],53:225,54:[1,226],90:[2,53]},{6:[2,123],25:[2,123],26:[2,123],54:[2,123],85:[2,123],90:[2,123]},{8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,146],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],86:227,87:[1,58],88:[1,59],89:[1,57],93:145,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,129],25:[2,129],26:[2,129],54:[2,129],85:[2,129],90:[2,129]},{1:[2,113],6:[2,113],25:[2,113],26:[2,113],40:[2,113],43:[2,113],49:[2,113],54:[2,113],57:[2,113],66:[2,113],67:[2,113],68:[2,113],70:[2,113],72:[2,113],73:[2,113],77:[2,113],79:[2,113],83:[2,113],84:[2,113],85:[2,113],90:[2,113],92:[2,113],101:[2,113],103:[2,113],104:[2,113],105:[2,113],109:[2,113],115:[2,113],116:[2,113],117:[2,113],125:[2,113],127:[2,113],128:[2,113],129:[2,113],130:[2,113],131:[2,113],132:[2,113],133:[2,113],134:[2,113],135:[2,113],136:[2,113],137:[2,113]},{5:228,25:[1,5],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,140],6:[2,140],25:[2,140],26:[2,140],49:[2,140],54:[2,140],57:[2,140],72:[2,140],77:[2,140],85:[2,140],90:[2,140],92:[2,140],101:[2,140],102:87,103:[1,65],104:[1,229],105:[1,66],108:88,109:[1,68],110:69,117:[2,140],125:[2,140],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,142],6:[2,142],25:[2,142],26:[2,142],49:[2,142],54:[2,142],57:[2,142],72:[2,142],77:[2,142],85:[2,142],90:[2,142],92:[2,142],101:[2,142],102:87,103:[1,65],104:[1,230],105:[1,66],108:88,109:[1,68],110:69,117:[2,142],125:[2,142],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,148],6:[2,148],25:[2,148],26:[2,148],49:[2,148],54:[2,148],57:[2,148],72:[2,148],77:[2,148],85:[2,148],90:[2,148],92:[2,148],101:[2,148],103:[2,148],104:[2,148],105:[2,148],109:[2,148],117:[2,148],125:[2,148],127:[2,148],128:[2,148],131:[2,148],132:[2,148],133:[2,148],134:[2,148],135:[2,148],136:[2,148]},{1:[2,149],6:[2,149],25:[2,149],26:[2,149],49:[2,149],54:[2,149],57:[2,149],72:[2,149],77:[2,149],85:[2,149],90:[2,149],92:[2,149],101:[2,149],102:87,103:[1,65],104:[2,149],105:[1,66],108:88,109:[1,68],110:69,117:[2,149],125:[2,149],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,153],6:[2,153],25:[2,153],26:[2,153],49:[2,153],54:[2,153],57:[2,153],72:[2,153],77:[2,153],85:[2,153],90:[2,153],92:[2,153],101:[2,153],103:[2,153],104:[2,153],105:[2,153],109:[2,153],117:[2,153],125:[2,153],127:[2,153],128:[2,153],131:[2,153],132:[2,153],133:[2,153],134:[2,153],135:[2,153],136:[2,153]},{115:[2,155],116:[2,155]},{27:158,28:[1,73],44:159,58:160,59:161,75:[1,70],88:[1,113],89:[1,114],112:231,114:157},{54:[1,232],115:[2,161],116:[2,161]},{54:[2,157],115:[2,157],116:[2,157]},{54:[2,158],115:[2,158],116:[2,158]},{54:[2,159],115:[2,159],116:[2,159]},{54:[2,160],115:[2,160],116:[2,160]},{1:[2,154],6:[2,154],25:[2,154],26:[2,154],49:[2,154],54:[2,154],57:[2,154],72:[2,154],77:[2,154],85:[2,154],90:[2,154],92:[2,154],101:[2,154],103:[2,154],104:[2,154],105:[2,154],109:[2,154],117:[2,154],125:[2,154],127:[2,154],128:[2,154],131:[2,154],132:[2,154],133:[2,154],134:[2,154],135:[2,154],136:[2,154]},{8:233,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:234,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,53],25:[2,53],53:235,54:[1,236],77:[2,53]},{6:[2,91],25:[2,91],26:[2,91],54:[2,91],77:[2,91]},{6:[2,39],25:[2,39],26:[2,39],43:[1,237],54:[2,39],77:[2,39]},{6:[2,42],25:[2,42],26:[2,42],54:[2,42],77:[2,42]},{6:[2,43],25:[2,43],26:[2,43],43:[2,43],54:[2,43],77:[2,43]},{6:[2,44],25:[2,44],26:[2,44],43:[2,44],54:[2,44],77:[2,44]},{6:[2,45],25:[2,45],26:[2,45],43:[2,45],54:[2,45],77:[2,45]},{1:[2,5],6:[2,5],26:[2,5],101:[2,5]},{1:[2,25],6:[2,25],25:[2,25],26:[2,25],49:[2,25],54:[2,25],57:[2,25],72:[2,25],77:[2,25],85:[2,25],90:[2,25],92:[2,25],97:[2,25],98:[2,25],101:[2,25],103:[2,25],104:[2,25],105:[2,25],109:[2,25],117:[2,25],120:[2,25],122:[2,25],125:[2,25],127:[2,25],128:[2,25],131:[2,25],132:[2,25],133:[2,25],134:[2,25],135:[2,25],136:[2,25]},{1:[2,192],6:[2,192],25:[2,192],26:[2,192],49:[2,192],54:[2,192],57:[2,192],72:[2,192],77:[2,192],85:[2,192],90:[2,192],92:[2,192],101:[2,192],102:87,103:[2,192],104:[2,192],105:[2,192],108:88,109:[2,192],110:69,117:[2,192],125:[2,192],127:[2,192],128:[2,192],131:[1,78],132:[1,81],133:[2,192],134:[2,192],135:[2,192],136:[2,192]},{1:[2,193],6:[2,193],25:[2,193],26:[2,193],49:[2,193],54:[2,193],57:[2,193],72:[2,193],77:[2,193],85:[2,193],90:[2,193],92:[2,193],101:[2,193],102:87,103:[2,193],104:[2,193],105:[2,193],108:88,109:[2,193],110:69,117:[2,193],125:[2,193],127:[2,193],128:[2,193],131:[1,78],132:[1,81],133:[2,193],134:[2,193],135:[2,193],136:[2,193]},{1:[2,194],6:[2,194],25:[2,194],26:[2,194],49:[2,194],54:[2,194],57:[2,194],72:[2,194],77:[2,194],85:[2,194],90:[2,194],92:[2,194],101:[2,194],102:87,103:[2,194],104:[2,194],105:[2,194],108:88,109:[2,194],110:69,117:[2,194],125:[2,194],127:[2,194],128:[2,194],131:[1,78],132:[2,194],133:[2,194],134:[2,194],135:[2,194],136:[2,194]},{1:[2,195],6:[2,195],25:[2,195],26:[2,195],49:[2,195],54:[2,195],57:[2,195],72:[2,195],77:[2,195],85:[2,195],90:[2,195],92:[2,195],101:[2,195],102:87,103:[2,195],104:[2,195],105:[2,195],108:88,109:[2,195],110:69,117:[2,195],125:[2,195],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[2,195],134:[2,195],135:[2,195],136:[2,195]},{1:[2,196],6:[2,196],25:[2,196],26:[2,196],49:[2,196],54:[2,196],57:[2,196],72:[2,196],77:[2,196],85:[2,196],90:[2,196],92:[2,196],101:[2,196],102:87,103:[2,196],104:[2,196],105:[2,196],108:88,109:[2,196],110:69,117:[2,196],125:[2,196],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[2,196],135:[2,196],136:[1,85]},{1:[2,197],6:[2,197],25:[2,197],26:[2,197],49:[2,197],54:[2,197],57:[2,197],72:[2,197],77:[2,197],85:[2,197],90:[2,197],92:[2,197],101:[2,197],102:87,103:[2,197],104:[2,197],105:[2,197],108:88,109:[2,197],110:69,117:[2,197],125:[2,197],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[2,197],136:[1,85]},{1:[2,198],6:[2,198],25:[2,198],26:[2,198],49:[2,198],54:[2,198],57:[2,198],72:[2,198],77:[2,198],85:[2,198],90:[2,198],92:[2,198],101:[2,198],102:87,103:[2,198],104:[2,198],105:[2,198],108:88,109:[2,198],110:69,117:[2,198],125:[2,198],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[2,198],135:[2,198],136:[2,198]},{1:[2,183],6:[2,183],25:[2,183],26:[2,183],49:[2,183],54:[2,183],57:[2,183],72:[2,183],77:[2,183],85:[2,183],90:[2,183],92:[2,183],101:[2,183],102:87,103:[1,65],104:[2,183],105:[1,66],108:88,109:[1,68],110:69,117:[2,183],125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,182],6:[2,182],25:[2,182],26:[2,182],49:[2,182],54:[2,182],57:[2,182],72:[2,182],77:[2,182],85:[2,182],90:[2,182],92:[2,182],101:[2,182],102:87,103:[1,65],104:[2,182],105:[1,66],108:88,109:[1,68],110:69,117:[2,182],125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,103],6:[2,103],25:[2,103],26:[2,103],49:[2,103],54:[2,103],57:[2,103],66:[2,103],67:[2,103],68:[2,103],70:[2,103],72:[2,103],73:[2,103],77:[2,103],83:[2,103],84:[2,103],85:[2,103],90:[2,103],92:[2,103],101:[2,103],103:[2,103],104:[2,103],105:[2,103],109:[2,103],117:[2,103],125:[2,103],127:[2,103],128:[2,103],131:[2,103],132:[2,103],133:[2,103],134:[2,103],135:[2,103],136:[2,103]},{1:[2,80],6:[2,80],25:[2,80],26:[2,80],40:[2,80],49:[2,80],54:[2,80],57:[2,80],66:[2,80],67:[2,80],68:[2,80],70:[2,80],72:[2,80],73:[2,80],77:[2,80],79:[2,80],83:[2,80],84:[2,80],85:[2,80],90:[2,80],92:[2,80],101:[2,80],103:[2,80],104:[2,80],105:[2,80],109:[2,80],117:[2,80],125:[2,80],127:[2,80],128:[2,80],129:[2,80],130:[2,80],131:[2,80],132:[2,80],133:[2,80],134:[2,80],135:[2,80],136:[2,80],137:[2,80]},{1:[2,81],6:[2,81],25:[2,81],26:[2,81],40:[2,81],49:[2,81],54:[2,81],57:[2,81],66:[2,81],67:[2,81],68:[2,81],70:[2,81],72:[2,81],73:[2,81],77:[2,81],79:[2,81],83:[2,81],84:[2,81],85:[2,81],90:[2,81],92:[2,81],101:[2,81],103:[2,81],104:[2,81],105:[2,81],109:[2,81],117:[2,81],125:[2,81],127:[2,81],128:[2,81],129:[2,81],130:[2,81],131:[2,81],132:[2,81],133:[2,81],134:[2,81],135:[2,81],136:[2,81],137:[2,81]},{1:[2,82],6:[2,82],25:[2,82],26:[2,82],40:[2,82],49:[2,82],54:[2,82],57:[2,82],66:[2,82],67:[2,82],68:[2,82],70:[2,82],72:[2,82],73:[2,82],77:[2,82],79:[2,82],83:[2,82],84:[2,82],85:[2,82],90:[2,82],92:[2,82],101:[2,82],103:[2,82],104:[2,82],105:[2,82],109:[2,82],117:[2,82],125:[2,82],127:[2,82],128:[2,82],129:[2,82],130:[2,82],131:[2,82],132:[2,82],133:[2,82],134:[2,82],135:[2,82],136:[2,82],137:[2,82]},{72:[1,238]},{57:[1,192],72:[2,87],91:239,92:[1,191],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{72:[2,88]},{8:240,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,72:[2,122],75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{12:[2,116],28:[2,116],30:[2,116],31:[2,116],33:[2,116],34:[2,116],35:[2,116],36:[2,116],37:[2,116],38:[2,116],45:[2,116],46:[2,116],47:[2,116],51:[2,116],52:[2,116],72:[2,116],75:[2,116],78:[2,116],82:[2,116],87:[2,116],88:[2,116],89:[2,116],95:[2,116],99:[2,116],100:[2,116],103:[2,116],105:[2,116],107:[2,116],109:[2,116],118:[2,116],124:[2,116],126:[2,116],127:[2,116],128:[2,116],129:[2,116],130:[2,116]},{12:[2,117],28:[2,117],30:[2,117],31:[2,117],33:[2,117],34:[2,117],35:[2,117],36:[2,117],37:[2,117],38:[2,117],45:[2,117],46:[2,117],47:[2,117],51:[2,117],52:[2,117],72:[2,117],75:[2,117],78:[2,117],82:[2,117],87:[2,117],88:[2,117],89:[2,117],95:[2,117],99:[2,117],100:[2,117],103:[2,117],105:[2,117],107:[2,117],109:[2,117],118:[2,117],124:[2,117],126:[2,117],127:[2,117],128:[2,117],129:[2,117],130:[2,117]},{1:[2,86],6:[2,86],25:[2,86],26:[2,86],40:[2,86],49:[2,86],54:[2,86],57:[2,86],66:[2,86],67:[2,86],68:[2,86],70:[2,86],72:[2,86],73:[2,86],77:[2,86],79:[2,86],83:[2,86],84:[2,86],85:[2,86],90:[2,86],92:[2,86],101:[2,86],103:[2,86],104:[2,86],105:[2,86],109:[2,86],117:[2,86],125:[2,86],127:[2,86],128:[2,86],129:[2,86],130:[2,86],131:[2,86],132:[2,86],133:[2,86],134:[2,86],135:[2,86],136:[2,86],137:[2,86]},{1:[2,104],6:[2,104],25:[2,104],26:[2,104],49:[2,104],54:[2,104],57:[2,104],66:[2,104],67:[2,104],68:[2,104],70:[2,104],72:[2,104],73:[2,104],77:[2,104],83:[2,104],84:[2,104],85:[2,104],90:[2,104],92:[2,104],101:[2,104],103:[2,104],104:[2,104],105:[2,104],109:[2,104],117:[2,104],125:[2,104],127:[2,104],128:[2,104],131:[2,104],132:[2,104],133:[2,104],134:[2,104],135:[2,104],136:[2,104]},{1:[2,36],6:[2,36],25:[2,36],26:[2,36],49:[2,36],54:[2,36],57:[2,36],72:[2,36],77:[2,36],85:[2,36],90:[2,36],92:[2,36],101:[2,36],102:87,103:[2,36],104:[2,36],105:[2,36],108:88,109:[2,36],110:69,117:[2,36],125:[2,36],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{8:241,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:242,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,109],6:[2,109],25:[2,109],26:[2,109],49:[2,109],54:[2,109],57:[2,109],66:[2,109],67:[2,109],68:[2,109],70:[2,109],72:[2,109],73:[2,109],77:[2,109],83:[2,109],84:[2,109],85:[2,109],90:[2,109],92:[2,109],101:[2,109],103:[2,109],104:[2,109],105:[2,109],109:[2,109],117:[2,109],125:[2,109],127:[2,109],128:[2,109],131:[2,109],132:[2,109],133:[2,109],134:[2,109],135:[2,109],136:[2,109]},{6:[2,53],25:[2,53],53:243,54:[1,226],85:[2,53]},{6:[2,128],25:[2,128],26:[2,128],54:[2,128],57:[1,244],85:[2,128],90:[2,128],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{50:245,51:[1,60],52:[1,61]},{6:[2,54],25:[2,54],26:[2,54],27:109,28:[1,73],44:110,55:246,56:108,58:111,59:112,75:[1,70],88:[1,113],89:[1,114]},{6:[1,247],25:[1,248]},{6:[2,61],25:[2,61],26:[2,61],49:[2,61],54:[2,61]},{8:249,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,199],6:[2,199],25:[2,199],26:[2,199],49:[2,199],54:[2,199],57:[2,199],72:[2,199],77:[2,199],85:[2,199],90:[2,199],92:[2,199],101:[2,199],102:87,103:[2,199],104:[2,199],105:[2,199],108:88,109:[2,199],110:69,117:[2,199],125:[2,199],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{8:250,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,201],6:[2,201],25:[2,201],26:[2,201],49:[2,201],54:[2,201],57:[2,201],72:[2,201],77:[2,201],85:[2,201],90:[2,201],92:[2,201],101:[2,201],102:87,103:[2,201],104:[2,201],105:[2,201],108:88,109:[2,201],110:69,117:[2,201],125:[2,201],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,181],6:[2,181],25:[2,181],26:[2,181],49:[2,181],54:[2,181],57:[2,181],72:[2,181],77:[2,181],85:[2,181],90:[2,181],92:[2,181],101:[2,181],103:[2,181],104:[2,181],105:[2,181],109:[2,181],117:[2,181],125:[2,181],127:[2,181],128:[2,181],131:[2,181],132:[2,181],133:[2,181],134:[2,181],135:[2,181],136:[2,181]},{8:251,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,133],6:[2,133],25:[2,133],26:[2,133],49:[2,133],54:[2,133],57:[2,133],72:[2,133],77:[2,133],85:[2,133],90:[2,133],92:[2,133],97:[1,252],101:[2,133],103:[2,133],104:[2,133],105:[2,133],109:[2,133],117:[2,133],125:[2,133],127:[2,133],128:[2,133],131:[2,133],132:[2,133],133:[2,133],134:[2,133],135:[2,133],136:[2,133]},{5:253,25:[1,5]},{27:254,28:[1,73]},{119:255,121:216,122:[1,217]},{26:[1,256],120:[1,257],121:258,122:[1,217]},{26:[2,174],120:[2,174],122:[2,174]},{8:260,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],94:259,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,97],5:261,6:[2,97],25:[1,5],26:[2,97],49:[2,97],54:[2,97],57:[2,97],72:[2,97],77:[2,97],85:[2,97],90:[2,97],92:[2,97],101:[2,97],102:87,103:[1,65],104:[2,97],105:[1,66],108:88,109:[1,68],110:69,117:[2,97],125:[2,97],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,100],6:[2,100],25:[2,100],26:[2,100],49:[2,100],54:[2,100],57:[2,100],72:[2,100],77:[2,100],85:[2,100],90:[2,100],92:[2,100],101:[2,100],103:[2,100],104:[2,100],105:[2,100],109:[2,100],117:[2,100],125:[2,100],127:[2,100],128:[2,100],131:[2,100],132:[2,100],133:[2,100],134:[2,100],135:[2,100],136:[2,100]},{8:262,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,138],6:[2,138],25:[2,138],26:[2,138],49:[2,138],54:[2,138],57:[2,138],66:[2,138],67:[2,138],68:[2,138],70:[2,138],72:[2,138],73:[2,138],77:[2,138],83:[2,138],84:[2,138],85:[2,138],90:[2,138],92:[2,138],101:[2,138],103:[2,138],104:[2,138],105:[2,138],109:[2,138],117:[2,138],125:[2,138],127:[2,138],128:[2,138],131:[2,138],132:[2,138],133:[2,138],134:[2,138],135:[2,138],136:[2,138]},{6:[1,74],26:[1,263]},{8:264,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,67],12:[2,117],25:[2,67],28:[2,117],30:[2,117],31:[2,117],33:[2,117],34:[2,117],35:[2,117],36:[2,117],37:[2,117],38:[2,117],45:[2,117],46:[2,117],47:[2,117],51:[2,117],52:[2,117],54:[2,67],75:[2,117],78:[2,117],82:[2,117],87:[2,117],88:[2,117],89:[2,117],90:[2,67],95:[2,117],99:[2,117],100:[2,117],103:[2,117],105:[2,117],107:[2,117],109:[2,117],118:[2,117],124:[2,117],126:[2,117],127:[2,117],128:[2,117],129:[2,117],130:[2,117]},{6:[1,266],25:[1,267],90:[1,265]},{6:[2,54],8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[2,54],26:[2,54],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],85:[2,54],87:[1,58],88:[1,59],89:[1,57],90:[2,54],93:268,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,53],25:[2,53],26:[2,53],53:269,54:[1,226]},{1:[2,178],6:[2,178],25:[2,178],26:[2,178],49:[2,178],54:[2,178],57:[2,178],72:[2,178],77:[2,178],85:[2,178],90:[2,178],92:[2,178],101:[2,178],103:[2,178],104:[2,178],105:[2,178],109:[2,178],117:[2,178],120:[2,178],125:[2,178],127:[2,178],128:[2,178],131:[2,178],132:[2,178],133:[2,178],134:[2,178],135:[2,178],136:[2,178]},{8:270,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:271,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{115:[2,156],116:[2,156]},{27:158,28:[1,73],44:159,58:160,59:161,75:[1,70],88:[1,113],89:[1,114],114:272},{1:[2,163],6:[2,163],25:[2,163],26:[2,163],49:[2,163],54:[2,163],57:[2,163],72:[2,163],77:[2,163],85:[2,163],90:[2,163],92:[2,163],101:[2,163],102:87,103:[2,163],104:[1,273],105:[2,163],108:88,109:[2,163],110:69,117:[1,274],125:[2,163],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,164],6:[2,164],25:[2,164],26:[2,164],49:[2,164],54:[2,164],57:[2,164],72:[2,164],77:[2,164],85:[2,164],90:[2,164],92:[2,164],101:[2,164],102:87,103:[2,164],104:[1,275],105:[2,164],108:88,109:[2,164],110:69,117:[2,164],125:[2,164],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[1,277],25:[1,278],77:[1,276]},{6:[2,54],11:168,25:[2,54],26:[2,54],27:169,28:[1,73],29:170,30:[1,71],31:[1,72],41:279,42:167,44:171,46:[1,46],77:[2,54],88:[1,113]},{8:280,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,281],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,85],6:[2,85],25:[2,85],26:[2,85],40:[2,85],49:[2,85],54:[2,85],57:[2,85],66:[2,85],67:[2,85],68:[2,85],70:[2,85],72:[2,85],73:[2,85],77:[2,85],79:[2,85],83:[2,85],84:[2,85],85:[2,85],90:[2,85],92:[2,85],101:[2,85],103:[2,85],104:[2,85],105:[2,85],109:[2,85],117:[2,85],125:[2,85],127:[2,85],128:[2,85],129:[2,85],130:[2,85],131:[2,85],132:[2,85],133:[2,85],134:[2,85],135:[2,85],136:[2,85],137:[2,85]},{8:282,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,72:[2,120],75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{72:[2,121],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,37],6:[2,37],25:[2,37],26:[2,37],49:[2,37],54:[2,37],57:[2,37],72:[2,37],77:[2,37],85:[2,37],90:[2,37],92:[2,37],101:[2,37],102:87,103:[2,37],104:[2,37],105:[2,37],108:88,109:[2,37],110:69,117:[2,37],125:[2,37],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{26:[1,283],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[1,266],25:[1,267],85:[1,284]},{6:[2,67],25:[2,67],26:[2,67],54:[2,67],85:[2,67],90:[2,67]},{5:285,25:[1,5]},{6:[2,57],25:[2,57],26:[2,57],49:[2,57],54:[2,57]},{27:109,28:[1,73],44:110,55:286,56:108,58:111,59:112,75:[1,70],88:[1,113],89:[1,114]},{6:[2,55],25:[2,55],26:[2,55],27:109,28:[1,73],44:110,48:287,54:[2,55],55:107,56:108,58:111,59:112,75:[1,70],88:[1,113],89:[1,114]},{6:[2,62],25:[2,62],26:[2,62],49:[2,62],54:[2,62],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{26:[1,288],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{5:289,25:[1,5],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{5:290,25:[1,5]},{1:[2,134],6:[2,134],25:[2,134],26:[2,134],49:[2,134],54:[2,134],57:[2,134],72:[2,134],77:[2,134],85:[2,134],90:[2,134],92:[2,134],101:[2,134],103:[2,134],104:[2,134],105:[2,134],109:[2,134],117:[2,134],125:[2,134],127:[2,134],128:[2,134],131:[2,134],132:[2,134],133:[2,134],134:[2,134],135:[2,134],136:[2,134]},{5:291,25:[1,5]},{26:[1,292],120:[1,293],121:258,122:[1,217]},{1:[2,172],6:[2,172],25:[2,172],26:[2,172],49:[2,172],54:[2,172],57:[2,172],72:[2,172],77:[2,172],85:[2,172],90:[2,172],92:[2,172],101:[2,172],103:[2,172],104:[2,172],105:[2,172],109:[2,172],117:[2,172],125:[2,172],127:[2,172],128:[2,172],131:[2,172],132:[2,172],133:[2,172],134:[2,172],135:[2,172],136:[2,172]},{5:294,25:[1,5]},{26:[2,175],120:[2,175],122:[2,175]},{5:295,25:[1,5],54:[1,296]},{25:[2,130],54:[2,130],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,98],6:[2,98],25:[2,98],26:[2,98],49:[2,98],54:[2,98],57:[2,98],72:[2,98],77:[2,98],85:[2,98],90:[2,98],92:[2,98],101:[2,98],103:[2,98],104:[2,98],105:[2,98],109:[2,98],117:[2,98],125:[2,98],127:[2,98],128:[2,98],131:[2,98],132:[2,98],133:[2,98],134:[2,98],135:[2,98],136:[2,98]},{1:[2,101],5:297,6:[2,101],25:[1,5],26:[2,101],49:[2,101],54:[2,101],57:[2,101],72:[2,101],77:[2,101],85:[2,101],90:[2,101],92:[2,101],101:[2,101],102:87,103:[1,65],104:[2,101],105:[1,66],108:88,109:[1,68],110:69,117:[2,101],125:[2,101],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{101:[1,298]},{90:[1,299],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,115],6:[2,115],25:[2,115],26:[2,115],40:[2,115],49:[2,115],54:[2,115],57:[2,115],66:[2,115],67:[2,115],68:[2,115],70:[2,115],72:[2,115],73:[2,115],77:[2,115],83:[2,115],84:[2,115],85:[2,115],90:[2,115],92:[2,115],101:[2,115],103:[2,115],104:[2,115],105:[2,115],109:[2,115],115:[2,115],116:[2,115],117:[2,115],125:[2,115],127:[2,115],128:[2,115],131:[2,115],132:[2,115],133:[2,115],134:[2,115],135:[2,115],136:[2,115]},{8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],93:300,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,146],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],86:301,87:[1,58],88:[1,59],89:[1,57],93:145,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,124],25:[2,124],26:[2,124],54:[2,124],85:[2,124],90:[2,124]},{6:[1,266],25:[1,267],26:[1,302]},{1:[2,141],6:[2,141],25:[2,141],26:[2,141],49:[2,141],54:[2,141],57:[2,141],72:[2,141],77:[2,141],85:[2,141],90:[2,141],92:[2,141],101:[2,141],102:87,103:[1,65],104:[2,141],105:[1,66],108:88,109:[1,68],110:69,117:[2,141],125:[2,141],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,143],6:[2,143],25:[2,143],26:[2,143],49:[2,143],54:[2,143],57:[2,143],72:[2,143],77:[2,143],85:[2,143],90:[2,143],92:[2,143],101:[2,143],102:87,103:[1,65],104:[2,143],105:[1,66],108:88,109:[1,68],110:69,117:[2,143],125:[2,143],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{115:[2,162],116:[2,162]},{8:303,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:304,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:305,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,89],6:[2,89],25:[2,89],26:[2,89],40:[2,89],49:[2,89],54:[2,89],57:[2,89],66:[2,89],67:[2,89],68:[2,89],70:[2,89],72:[2,89],73:[2,89],77:[2,89],83:[2,89],84:[2,89],85:[2,89],90:[2,89],92:[2,89],101:[2,89],103:[2,89],104:[2,89],105:[2,89],109:[2,89],115:[2,89],116:[2,89],117:[2,89],125:[2,89],127:[2,89],128:[2,89],131:[2,89],132:[2,89],133:[2,89],134:[2,89],135:[2,89],136:[2,89]},{11:168,27:169,28:[1,73],29:170,30:[1,71],31:[1,72],41:306,42:167,44:171,46:[1,46],88:[1,113]},{6:[2,90],11:168,25:[2,90],26:[2,90],27:169,28:[1,73],29:170,30:[1,71],31:[1,72],41:166,42:167,44:171,46:[1,46],54:[2,90],76:307,88:[1,113]},{6:[2,92],25:[2,92],26:[2,92],54:[2,92],77:[2,92]},{6:[2,40],25:[2,40],26:[2,40],54:[2,40],77:[2,40],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{8:308,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{72:[2,119],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,38],6:[2,38],25:[2,38],26:[2,38],49:[2,38],54:[2,38],57:[2,38],72:[2,38],77:[2,38],85:[2,38],90:[2,38],92:[2,38],101:[2,38],103:[2,38],104:[2,38],105:[2,38],109:[2,38],117:[2,38],125:[2,38],127:[2,38],128:[2,38],131:[2,38],132:[2,38],133:[2,38],134:[2,38],135:[2,38],136:[2,38]},{1:[2,110],6:[2,110],25:[2,110],26:[2,110],49:[2,110],54:[2,110],57:[2,110],66:[2,110],67:[2,110],68:[2,110],70:[2,110],72:[2,110],73:[2,110],77:[2,110],83:[2,110],84:[2,110],85:[2,110],90:[2,110],92:[2,110],101:[2,110],103:[2,110],104:[2,110],105:[2,110],109:[2,110],117:[2,110],125:[2,110],127:[2,110],128:[2,110],131:[2,110],132:[2,110],133:[2,110],134:[2,110],135:[2,110],136:[2,110]},{1:[2,49],6:[2,49],25:[2,49],26:[2,49],49:[2,49],54:[2,49],57:[2,49],72:[2,49],77:[2,49],85:[2,49],90:[2,49],92:[2,49],101:[2,49],103:[2,49],104:[2,49],105:[2,49],109:[2,49],117:[2,49],125:[2,49],127:[2,49],128:[2,49],131:[2,49],132:[2,49],133:[2,49],134:[2,49],135:[2,49],136:[2,49]},{6:[2,58],25:[2,58],26:[2,58],49:[2,58],54:[2,58]},{6:[2,53],25:[2,53],26:[2,53],53:309,54:[1,202]},{1:[2,200],6:[2,200],25:[2,200],26:[2,200],49:[2,200],54:[2,200],57:[2,200],72:[2,200],77:[2,200],85:[2,200],90:[2,200],92:[2,200],101:[2,200],103:[2,200],104:[2,200],105:[2,200],109:[2,200],117:[2,200],125:[2,200],127:[2,200],128:[2,200],131:[2,200],132:[2,200],133:[2,200],134:[2,200],135:[2,200],136:[2,200]},{1:[2,179],6:[2,179],25:[2,179],26:[2,179],49:[2,179],54:[2,179],57:[2,179],72:[2,179],77:[2,179],85:[2,179],90:[2,179],92:[2,179],101:[2,179],103:[2,179],104:[2,179],105:[2,179],109:[2,179],117:[2,179],120:[2,179],125:[2,179],127:[2,179],128:[2,179],131:[2,179],132:[2,179],133:[2,179],134:[2,179],135:[2,179],136:[2,179]},{1:[2,135],6:[2,135],25:[2,135],26:[2,135],49:[2,135],54:[2,135],57:[2,135],72:[2,135],77:[2,135],85:[2,135],90:[2,135],92:[2,135],101:[2,135],103:[2,135],104:[2,135],105:[2,135],109:[2,135],117:[2,135],125:[2,135],127:[2,135],128:[2,135],131:[2,135],132:[2,135],133:[2,135],134:[2,135],135:[2,135],136:[2,135]},{1:[2,136],6:[2,136],25:[2,136],26:[2,136],49:[2,136],54:[2,136],57:[2,136],72:[2,136],77:[2,136],85:[2,136],90:[2,136],92:[2,136],97:[2,136],101:[2,136],103:[2,136],104:[2,136],105:[2,136],109:[2,136],117:[2,136],125:[2,136],127:[2,136],128:[2,136],131:[2,136],132:[2,136],133:[2,136],134:[2,136],135:[2,136],136:[2,136]},{1:[2,170],6:[2,170],25:[2,170],26:[2,170],49:[2,170],54:[2,170],57:[2,170],72:[2,170],77:[2,170],85:[2,170],90:[2,170],92:[2,170],101:[2,170],103:[2,170],104:[2,170],105:[2,170],109:[2,170],117:[2,170],125:[2,170],127:[2,170],128:[2,170],131:[2,170],132:[2,170],133:[2,170],134:[2,170],135:[2,170],136:[2,170]},{5:310,25:[1,5]},{26:[1,311]},{6:[1,312],26:[2,176],120:[2,176],122:[2,176]},{8:313,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,102],6:[2,102],25:[2,102],26:[2,102],49:[2,102],54:[2,102],57:[2,102],72:[2,102],77:[2,102],85:[2,102],90:[2,102],92:[2,102],101:[2,102],103:[2,102],104:[2,102],105:[2,102],109:[2,102],117:[2,102],125:[2,102],127:[2,102],128:[2,102],131:[2,102],132:[2,102],133:[2,102],134:[2,102],135:[2,102],136:[2,102]},{1:[2,139],6:[2,139],25:[2,139],26:[2,139],49:[2,139],54:[2,139],57:[2,139],66:[2,139],67:[2,139],68:[2,139],70:[2,139],72:[2,139],73:[2,139],77:[2,139],83:[2,139],84:[2,139],85:[2,139],90:[2,139],92:[2,139],101:[2,139],103:[2,139],104:[2,139],105:[2,139],109:[2,139],117:[2,139],125:[2,139],127:[2,139],128:[2,139],131:[2,139],132:[2,139],133:[2,139],134:[2,139],135:[2,139],136:[2,139]},{1:[2,118],6:[2,118],25:[2,118],26:[2,118],49:[2,118],54:[2,118],57:[2,118],66:[2,118],67:[2,118],68:[2,118],70:[2,118],72:[2,118],73:[2,118],77:[2,118],83:[2,118],84:[2,118],85:[2,118],90:[2,118],92:[2,118],101:[2,118],103:[2,118],104:[2,118],105:[2,118],109:[2,118],117:[2,118],125:[2,118],127:[2,118],128:[2,118],131:[2,118],132:[2,118],133:[2,118],134:[2,118],135:[2,118],136:[2,118]},{6:[2,125],25:[2,125],26:[2,125],54:[2,125],85:[2,125],90:[2,125]},{6:[2,53],25:[2,53],26:[2,53],53:314,54:[1,226]},{6:[2,126],25:[2,126],26:[2,126],54:[2,126],85:[2,126],90:[2,126]},{1:[2,165],6:[2,165],25:[2,165],26:[2,165],49:[2,165],54:[2,165],57:[2,165],72:[2,165],77:[2,165],85:[2,165],90:[2,165],92:[2,165],101:[2,165],102:87,103:[2,165],104:[2,165],105:[2,165],108:88,109:[2,165],110:69,117:[1,315],125:[2,165],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,167],6:[2,167],25:[2,167],26:[2,167],49:[2,167],54:[2,167],57:[2,167],72:[2,167],77:[2,167],85:[2,167],90:[2,167],92:[2,167],101:[2,167],102:87,103:[2,167],104:[1,316],105:[2,167],108:88,109:[2,167],110:69,117:[2,167],125:[2,167],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,166],6:[2,166],25:[2,166],26:[2,166],49:[2,166],54:[2,166],57:[2,166],72:[2,166],77:[2,166],85:[2,166],90:[2,166],92:[2,166],101:[2,166],102:87,103:[2,166],104:[2,166],105:[2,166],108:88,109:[2,166],110:69,117:[2,166],125:[2,166],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[2,93],25:[2,93],26:[2,93],54:[2,93],77:[2,93]},{6:[2,53],25:[2,53],26:[2,53],53:317,54:[1,236]},{26:[1,318],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[1,247],25:[1,248],26:[1,319]},{26:[1,320]},{1:[2,173],6:[2,173],25:[2,173],26:[2,173],49:[2,173],54:[2,173],57:[2,173],72:[2,173],77:[2,173],85:[2,173],90:[2,173],92:[2,173],101:[2,173],103:[2,173],104:[2,173],105:[2,173],109:[2,173],117:[2,173],125:[2,173],127:[2,173],128:[2,173],131:[2,173],132:[2,173],133:[2,173],134:[2,173],135:[2,173],136:[2,173]},{26:[2,177],120:[2,177],122:[2,177]},{25:[2,131],54:[2,131],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[1,266],25:[1,267],26:[1,321]},{8:322,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:323,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[1,277],25:[1,278],26:[1,324]},{6:[2,41],25:[2,41],26:[2,41],54:[2,41],77:[2,41]},{6:[2,59],25:[2,59],26:[2,59],49:[2,59],54:[2,59]},{1:[2,171],6:[2,171],25:[2,171],26:[2,171],49:[2,171],54:[2,171],57:[2,171],72:[2,171],77:[2,171],85:[2,171],90:[2,171],92:[2,171],101:[2,171],103:[2,171],104:[2,171],105:[2,171],109:[2,171],117:[2,171],125:[2,171],127:[2,171],128:[2,171],131:[2,171],132:[2,171],133:[2,171],134:[2,171],135:[2,171],136:[2,171]},{6:[2,127],25:[2,127],26:[2,127],54:[2,127],85:[2,127],90:[2,127]},{1:[2,168],6:[2,168],25:[2,168],26:[2,168],49:[2,168],54:[2,168],57:[2,168],72:[2,168],77:[2,168],85:[2,168],90:[2,168],92:[2,168],101:[2,168],102:87,103:[2,168],104:[2,168],105:[2,168],108:88,109:[2,168],110:69,117:[2,168],125:[2,168],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,169],6:[2,169],25:[2,169],26:[2,169],49:[2,169],54:[2,169],57:[2,169],72:[2,169],77:[2,169],85:[2,169],90:[2,169],92:[2,169],101:[2,169],102:87,103:[2,169],104:[2,169],105:[2,169],108:88,109:[2,169],110:69,117:[2,169],125:[2,169],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[2,94],25:[2,94],26:[2,94],54:[2,94],77:[2,94]}],defaultActions:{60:[2,51],61:[2,52],75:[2,3],94:[2,108],189:[2,88]},parseError:function(b,c){throw new Error(b)},parse:function(b){function o(a){d.length=d.length-2*a,e.length=e.length-a,f.length=f.length-a}function p(){var a;return a=c.lexer.lex()||1,typeof a!="number"&&(a=c.symbols_[a]||a),a}var c=this,d=[0],e=[null],f=[],g=this.table,h="",i=0,j=0,k=0,l=2,m=1;this.lexer.setInput(b),this.lexer.yy=this.yy,this.yy.lexer=this.lexer,typeof this.lexer.yylloc=="undefined"&&(this.lexer.yylloc={});var n=this.lexer.yylloc;f.push(n),typeof this.yy.parseError=="function"&&(this.parseError=this.yy.parseError);var q,r,s,t,u,v,w={},x,y,z,A;for(;;){s=d[d.length-1],this.defaultActions[s]?t=this.defaultActions[s]:(q==null&&(q=p()),t=g[s]&&g[s][q]);if(typeof t=="undefined"||!t.length||!t[0]){if(!k){A=[];for(x in g[s])this.terminals_[x]&&x>2&&A.push("'"+this.terminals_[x]+"'");var B="";this.lexer.showPosition?B="Parse error on line "+(i+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+A.join(", ")+", got '"+this.terminals_[q]+"'":B="Parse error on line "+(i+1)+": Unexpected "+(q==1?"end of input":"'"+(this.terminals_[q]||q)+"'"),this.parseError(B,{text:this.lexer.match,token:this.terminals_[q]||q,line:this.lexer.yylineno,loc:n,expected:A})}if(k==3){if(q==m)throw new Error(B||"Parsing halted.");j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,n=this.lexer.yylloc,q=p()}for(;;){if(l.toString()in g[s])break;if(s==0)throw new Error(B||"Parsing halted.");o(1),s=d[d.length-1]}r=q,q=l,s=d[d.length-1],t=g[s]&&g[s][l],k=3}if(t[0]instanceof Array&&t.length>1)throw new Error("Parse Error: multiple actions possible at state: "+s+", token: "+q);switch(t[0]){case 1:d.push(q),e.push(this.lexer.yytext),f.push(this.lexer.yylloc),d.push(t[1]),q=null,r?(q=r,r=null):(j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,n=this.lexer.yylloc,k>0&&k--);break;case 2:y=this.productions_[t[1]][1],w.$=e[e.length-y],w._$={first_line:f[f.length-(y||1)].first_line,last_line:f[f.length-1].last_line,first_column:f[f.length-(y||1)].first_column,last_column:f[f.length-1].last_column},v=this.performAction.call(w,h,j,i,this.yy,t[1],e,f);if(typeof v!="undefined")return v;y&&(d=d.slice(0,-1*y*2),e=e.slice(0,-1*y),f=f.slice(0,-1*y)),d.push(this.productions_[t[1]][0]),e.push(w.$),f.push(w._$),z=g[d[d.length-2]][d[d.length-1]],d.push(z);break;case 3:return!0}}return!0}};return undefined,a}();typeof require!="undefined"&&typeof a!="undefined"&&(a.parser=b,a.parse=function(){return b.parse.apply(b,arguments)},a.main=function(c){if(!c[1])throw new Error("Usage: "+c[0]+" FILE");if(typeof process!="undefined")var d=require("fs").readFileSync(require("path").join(process.cwd(),c[1]),"utf8");else var e=require("file").path(require("file").cwd()),d=e.join(c[1]).read({charset:"utf-8"});return a.parser.parse(d)},typeof module!="undefined"&&require.main===module&&a.main(typeof process!="undefined"?process.argv.slice(1):require("system").args))},require["./scope"]=new function(){var a=this;((function(){var b,c,d,e;e=require("./helpers"),c=e.extend,d=e.last,a.Scope=b=function(){function a(b,c,d){this.parent=b,this.expressions=c,this.method=d,this.variables=[{name:"arguments",type:"arguments"}],this.positions={},this.parent||(a.root=this)}return a.root=null,a.prototype.add=function(a,b,c){return this.shared&&!c?this.parent.add(a,b,c):Object.prototype.hasOwnProperty.call(this.positions,a)?this.variables[this.positions[a]].type=b:this.positions[a]=this.variables.push({name:a,type:b})-1},a.prototype.namedMethod=function(){return this.method.name||!this.parent?this.method:this.parent.namedMethod()},a.prototype.find=function(a){return this.check(a)?!0:(this.add(a,"var"),!1)},a.prototype.parameter=function(a){if(this.shared&&this.parent.check(a,!0))return;return this.add(a,"param")},a.prototype.check=function(a){var b;return!!(this.type(a)||((b=this.parent)!=null?b.check(a):void 0))},a.prototype.temporary=function(a,b){return a.length>1?"_"+a+(b>1?b-1:""):"_"+(b+parseInt(a,36)).toString(36).replace(/\d/g,"a")},a.prototype.type=function(a){var b,c,d,e;e=this.variables;for(c=0,d=e.length;c1&&a.level>=w?"("+c+")":c)},b.prototype.compileRoot=function(a){var b,c,d,e,f,g;return a.indent=a.bare?"":R,a.scope=new N(null,this,null),a.level=z,this.spaced=!0,e="",a.bare||(f=function(){var a,b,e,f;e=this.expressions,f=[];for(d=a=0,b=e.length;a=u?"(void 0)":"void 0"},b}(e),a.Null=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}return bm(b,a),b.prototype.isAssignable=D,b.prototype.isComplex=D,b.prototype.compileNode=function(){return"null"},b}(e),a.Bool=function(a){function b(a){this.val=a}return bm(b,a),b.prototype.isAssignable=D,b.prototype.isComplex=D,b.prototype.compileNode=function(){return this.val},b}(e),a.Return=K=function(a){function b(a){a&&!a.unwrap().isUndefined&&(this.expression=a)}return bm(b,a),b.prototype.children=["expression"],b.prototype.isStatement=Y,b.prototype.makeReturn=S,b.prototype.jumps=S,b.prototype.compile=function(a,c){var d,e;return d=(e=this.expression)!=null?e.makeReturn():void 0,!d||d instanceof b?b.__super__.compile.call(this,a,c):d.compile(a,c)},b.prototype.compileNode=function(a){return this.tab+("return"+[this.expression?" "+this.expression.compile(a,y):void 0]+";")},b}(e),a.Value=W=function(a){function b(a,c,d){return!c&&a instanceof b?a:(this.base=a,this.properties=c||[],d&&(this[d]=!0),this)}return bm(b,a),b.prototype.children=["base","properties"],b.prototype.add=function(a){return this.properties=this.properties.concat(a),this},b.prototype.hasProperties=function(){return!!this.properties.length},b.prototype.isArray=function(){return!this.properties.length&&this.base instanceof c},b.prototype.isComplex=function(){return this.hasProperties()||this.base.isComplex()},b.prototype.isAssignable=function(){return this.hasProperties()||this.base.isAssignable()},b.prototype.isSimpleNumber=function(){return this.base instanceof A&&L.test(this.base.value)},b.prototype.isString=function(){return this.base instanceof A&&q.test(this.base.value)},b.prototype.isAtomic=function(){var a,b,c,d;d=this.properties.concat(this.base);for(b=0,c=d.length;b"+this.equals],i=n[0],e=n[1],c=this.stepNum?+this.stepNum>0?""+i+" "+this.toVar:""+e+" "+this.toVar:h?(o=[+this.fromNum,+this.toNum],d=o[0],l=o[1],o,d<=l?""+i+" "+l:""+e+" "+l):(b=""+this.fromVar+" <= "+this.toVar,""+b+" ? "+i+" "+this.toVar+" : "+e+" "+this.toVar),k=this.stepVar?""+f+" += "+this.stepVar:h?j?d<=l?"++"+f:"--"+f:d<=l?""+f+"++":""+f+"--":j?""+b+" ? ++"+f+" : --"+f:""+b+" ? "+f+"++ : "+f+"--",j&&(m=""+g+" = "+m),j&&(k=""+g+" = "+k),""+m+"; "+c+"; "+k):this.compileArray(a)},b.prototype.compileArray=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p;if(this.fromNum&&this.toNum&&Math.abs(this.fromNum-this.toNum)<=20)return j=function(){p=[];for(var a=n=+this.fromNum,b=+this.toNum;n<=b?a<=b:a>=b;n<=b?a++:a--)p.push(a);return p}.apply(this),this.exclusive&&j.pop(),"["+j.join(", ")+"]";g=this.tab+R,f=a.scope.freeVariable("i"),k=a.scope.freeVariable("results"),i="\n"+g+k+" = [];",this.fromNum&&this.toNum?(a.index=f,c=this.compileNode(a)):(l=""+f+" = "+this.fromC+(this.toC!==this.toVar?", "+this.toC:""),d=""+this.fromVar+" <= "+this.toVar,c="var "+l+"; "+d+" ? "+f+" <"+this.equals+" "+this.toVar+" : "+f+" >"+this.equals+" "+this.toVar+"; "+d+" ? "+f+"++ : "+f+"--"),h="{ "+k+".push("+f+"); }\n"+g+"return "+k+";\n"+a.indent,e=function(a){return a!=null?a.contains(function(a){return a instanceof A&&a.value==="arguments"&&!a.asKey}):void 0};if(e(this.from)||e(this.to))b=", arguments";return"(function() {"+i+"\n"+g+"for ("+c+")"+h+"}).apply(this"+(b!=null?b:"")+")"},b}(e),a.Slice=O=function(a){function b(a){this.range=a,b.__super__.constructor.call(this)}return bm(b,a),b.prototype.children=["range"],b.prototype.compileNode=function(a){var b,c,d,e,f,g;return g=this.range,e=g.to,c=g.from,d=c&&c.compile(a,y)||"0",b=e&&e.compile(a,y),e&&(!!this.range.exclusive||+b!==-1)&&(f=", "+(this.range.exclusive?b:L.test(b)?""+(+b+1):(b=e.compile(a,u),"+"+b+" + 1 || 9e9"))),".slice("+d+(f||"")+")"},b}(e),a.Obj=E=function(a){function b(a,b){this.generated=b!=null?b:!1,this.objects=this.properties=a||[]}return bm(b,a),b.prototype.children=["properties"],b.prototype.compileNode=function(a){var b,c,e,f,g,h,i,j,l,m,n;l=this.properties;if(!l.length)return this.front?"({})":"{}";if(this.generated)for(m=0,n=l.length;m=0?"[\n"+a.indent+b+"\n"+this.tab+"]":"["+b+"]")):"[]"},b.prototype.assigns=function(a){var b,c,d,e;e=this.objects;for(c=0,d=e.length;c=0)throw SyntaxError("variable name may not be "+a);return a&&(a=o.test(a)&&a)},c.prototype.setContext=function(a){return this.body.traverseChildren(!1,function(b){if(b.classBody)return!1;if(b instanceof A&&b.value==="this")return b.value=a;if(b instanceof j){b.klass=a;if(b.bound)return b.context=a}})},c.prototype.addBoundFunctions=function(a){var c,d,e,f,g,h;if(this.boundFuncs.length){g=this.boundFuncs,h=[];for(e=0,f=g.length;e=0);if(e&&this.context!=="object")throw SyntaxError('variable name may not be "'+f+'"')}return bm(c,a),c.prototype.children=["variable","value"],c.prototype.isStatement=function(a){return(a!=null?a.level:void 0)===z&&this.context!=null&&bn.call(this.context,"?")>=0},c.prototype.assigns=function(a){return this[this.context==="object"?"value":"variable"].assigns(a)},c.prototype.unfoldSoak=function(a){return bh(a,this,"variable")},c.prototype.compileNode=function(a){var b,c,d,e,f,g,h,i,k;if(b=this.variable instanceof W){if(this.variable.isArray()||this.variable.isObject())return this.compilePatternMatch(a);if(this.variable.isSplice())return this.compileSplice(a);if((g=this.context)==="||="||g==="&&="||g==="?=")return this.compileConditional(a)}d=this.variable.compile(a,w);if(!this.context){if(!(f=this.variable.unwrapAll()).isAssignable())throw SyntaxError('"'+this.variable.compile(a)+'" cannot be assigned.');if(typeof f.hasProperties=="function"?!f.hasProperties():!void 0)this.param?a.scope.add(d,"var"):a.scope.find(d)}return this.value instanceof j&&(c=B.exec(d))&&(c[1]&&(this.value.klass=c[1]),this.value.name=(h=(i=(k=c[2])!=null?k:c[3])!=null?i:c[4])!=null?h:c[5]),e=this.value.compile(a,w),this.context==="object"?""+d+": "+e:(e=d+(" "+(this.context||"=")+" ")+e,a.level<=w?e:"("+e+")")},c.prototype.compilePatternMatch=function(a){var d,e,f,g,h,i,j,k,l,m,n,p,q,r,s,u,v,y,B,C,D,E,F,G,J,K,L;s=a.level===z,v=this.value,m=this.variable.base.objects;if(!(n=m.length))return f=v.compile(a),a.level>=x?"("+f+")":f;i=this.variable.isObject();if(s&&n===1&&!((l=m[0])instanceof P)){l instanceof c?(D=l,E=D.variable,h=E.base,l=D.value):l.base instanceof H?(F=(new W(l.unwrapAll())).cacheReference(a),l=F[0],h=F[1]):h=i?l["this"]?l.properties[0].name:l:new A(0),d=o.test(h.unwrap().value||0),v=new W(v),v.properties.push(new(d?b:t)(h));if(G=l.unwrap().value,bn.call(I,G)>=0)throw new SyntaxError("assignment to a reserved word: "+l.compile(a)+" = "+v.compile(a));return(new c(l,v,null,{param:this.param})).compile(a,z)}y=v.compile(a,w),e=[],r=!1;if(!o.test(y)||this.variable.assigns(y))e.push(""+(p=a.scope.freeVariable("ref"))+" = "+y),y=p;for(g=B=0,C=m.length;B=0)throw new SyntaxError("assignment to a reserved word: "+l.compile(a)+" = "+u.compile(a));e.push((new c(l,u,null,{param:this.param,subpattern:!0})).compile(a,w))}return!s&&!this.subpattern&&e.push(y),f=e.join(", "),a.level=0&&(a.isExistentialEquals=!0),(new F(this.context.slice(0,-1),b,new c(d,this.value,"="))).compile(a)},c.prototype.compileSplice=function(a){var b,c,d,e,f,g,h,i,j,k,l,m;return k=this.variable.properties.pop().range,d=k.from,h=k.to,c=k.exclusive,g=this.variable.compile(a),l=(d!=null?d.cache(a,x):void 0)||["0","0"],e=l[0],f=l[1],h?(d!=null?d.isSimpleNumber():void 0)&&h.isSimpleNumber()?(h=+h.compile(a)- +f,c||(h+=1)):(h=h.compile(a,u)+" - "+f,c||(h+=" + 1")):h="9e9",m=this.value.cache(a,w),i=m[0],j=m[1],b="[].splice.apply("+g+", ["+e+", "+h+"].concat("+i+")), "+j,a.level>z?"("+b+")":b},c}(e),a.Code=j=function(a){function b(a,b,c){this.params=a||[],this.body=b||new f,this.bound=c==="boundfunc",this.bound&&(this.context="_this")}return bm(b,a),b.prototype.children=["params","body"],b.prototype.isStatement=function(){return!!this.ctor},b.prototype.jumps=D,b.prototype.compileNode=function(a){var b,e,f,g,h,i,j,k,l,m,n,o,p,q,s,t,v,w,x,y,z,B,C,D,E,G,H,I,J,K,L,M,O;a.scope=new N(a.scope,this.body,this),a.scope.shared=$(a,"sharedScope"),a.indent+=R,delete a.bare,delete a.isExistentialEquals,l=[],e=[],H=this.paramNames();for(s=0,x=H.length;s=u?"("+b+")":b},b.prototype.paramNames=function(){var a,b,c,d,e;a=[],e=this.params;for(c=0,d=e.length;c=0)throw SyntaxError('parameter name "'+a+'" is not allowed')}return bm(b,a),b.prototype.children=["name","value"],b.prototype.compile=function(a){return this.name.compile(a,w)},b.prototype.asReference=function(a){var b;return this.reference?this.reference:(b=this.name,b["this"]?(b=b.properties[0].name,b.value.reserved&&(b=new A(a.scope.freeVariable(b.value)))):b.isComplex()&&(b=new A(a.scope.freeVariable("arg"))),b=new W(b),this.splat&&(b=new P(b)),this.reference=b)},b.prototype.isComplex=function(){return this.name.isComplex()},b.prototype.names=function(a){var b,c,e,f,g,h;a==null&&(a=this.name),b=function(a){var b;return b=a.properties[0].name.value,b.reserved?[]:[b]};if(a instanceof A)return[a.value];if(a instanceof W)return b(a);c=[],h=a.objects;for(f=0,g=h.length;f=c.length)return"";if(c.length===1)return g=c[0].compile(a,w),d?g:""+bi("slice")+".call("+g+")";e=c.slice(i);for(h=k=0,l=e.length;k1?b.expressions.unshift(new r((new H(this.guard)).invert(),new A("continue"))):this.guard&&(b=f.wrap([new r(this.guard,b)]))),b="\n"+b.compile(a,z)+"\n"+this.tab),c=e+this.tab+("while ("+this.condition.compile(a,y)+") {"+b+"}"),this.returns&&(c+="\n"+this.tab+"return "+d+";"),c},b}(e),a.Op=F=function(a){function e(a,c,d,e){if(a==="in")return new s(c,d);if(a==="do")return this.generateDo(c);if(a==="new"){if(c instanceof g&&!c["do"]&&!c.isNew)return c.newInstance();if(c instanceof j&&c.bound||c["do"])c=new H(c)}return this.operator=b[a]||a,this.first=c,this.second=d,this.flip=!!e,this}var b,c;return bm(e,a),b={"==":"===","!=":"!==",of:"in"},c={"!==":"===","===":"!=="},e.prototype.children=["first","second"],e.prototype.isSimpleNumber=D,e.prototype.isUnary=function(){return!this.second},e.prototype.isComplex=function(){var a;return!this.isUnary()||(a=this.operator)!=="+"&&a!=="-"||this.first.isComplex()},e.prototype.isChainable=function(){var a;return(a=this.operator)==="<"||a===">"||a===">="||a==="<="||a==="==="||a==="!=="},e.prototype.invert=function(){var a,b,d,f,g;if(this.isChainable()&&this.first.isChainable()){a=!0,b=this;while(b&&b.operator)a&&(a=b.operator in c),b=b.first;if(!a)return(new H(this)).invert();b=this;while(b&&b.operator)b.invert=!b.invert,b.operator=c[b.operator],b=b.first;return this}return(f=c[this.operator])?(this.operator=f,this.first.unwrap()instanceof e&&this.first.invert(),this):this.second?(new H(this)).invert():this.operator==="!"&&(d=this.first.unwrap())instanceof e&&((g=d.operator)==="!"||g==="in"||g==="instanceof")?d:new e("!",this)},e.prototype.unfoldSoak=function(a){var b;return((b=this.operator)==="++"||b==="--"||b==="delete")&&bh(a,this,"first")},e.prototype.generateDo=function(a){var b,c,e,f,h,i,k,l;f=[],c=a instanceof d&&(h=a.value.unwrap())instanceof j?h:a,l=c.params||[];for(i=0,k=l.length;i=0))throw SyntaxError("prefix increment/decrement may not have eval or arguments operand");return this.isUnary()?this.compileUnary(a):c?this.compileChain(a):this.operator==="?"?this.compileExistence(a):(b=this.first.compile(a,x)+" "+this.operator+" "+this.second.compile(a,x),a.level<=x?b:"("+b+")")},e.prototype.compileChain=function(a){var b,c,d,e;return e=this.first.second.cache(a),this.first.second=e[0],d=e[1],c=this.first.compile(a,x),b=""+c+" "+(this.invert?"&&":"||")+" "+d.compile(a)+" "+this.operator+" "+this.second.compile(a,x),"("+b+")"},e.prototype.compileExistence=function(a){var b,c;return this.first.isComplex()?(c=new A(a.scope.freeVariable("ref")),b=new H(new d(c,this.first))):(b=this.first,c=b),(new r(new l(b),c,{type:"if"})).addElse(this.second).compile(a)},e.prototype.compileUnary=function(a){var b,c,d;if(a.level>=u)return(new H(this)).compile(a);c=[b=this.operator],d=b==="+"||b==="-",(b==="new"||b==="typeof"||b==="delete"||d&&this.first instanceof e&&this.first.operator===b)&&c.push(" ");if(d&&this.first instanceof e||b==="new"&&this.first.isStatement(a))this.first=new H(this.first);return c.push(this.first.compile(a,x)),this.flip&&c.reverse(),c.join("")},e.prototype.toString=function(a){return e.__super__.toString.call(this,a,this.constructor.name+" "+this.operator)},e}(e),a.In=s=function(a){function b(a,b){this.object=a,this.array=b}return bm(b,a),b.prototype.children=["object","array"],b.prototype.invert=C,b.prototype.compileNode=function(a){var b,c,d,e,f;if(this.array instanceof W&&this.array.isArray()){f=this.array.base.objects;for(d=0,e=f.length;d= 0"),d===c?b:(b=d+", "+b,a.level=0)throw SyntaxError('catch variable may not be "'+this.error.value+'"');return a.scope.check(this.error.value)||a.scope.add(this.error.value,"param")," catch"+d+"{\n"+this.recovery.compile(a,z)+"\n"+this.tab+"}"}if(!this.ensure&&!this.recovery)return" catch (_error) {}"}.call(this),c=this.ensure?" finally {\n"+this.ensure.compile(a,z)+"\n"+this.tab+"}":"",""+this.tab+"try {\n"+e+"\n"+this.tab+"}"+(b||"")+c},b}(e),a.Throw=T=function(a){function b(a){this.expression=a}return bm(b,a),b.prototype.children=["expression"],b.prototype.isStatement=Y,b.prototype.jumps=D,b.prototype.makeReturn=S,b.prototype.compileNode=function(a){return this.tab+("throw "+this.expression.compile(a)+";")},b}(e),a.Existence=l=function(a){function b(a){this.expression=a}return bm(b,a),b.prototype.children=["expression"],b.prototype.invert=C,b.prototype.compileNode=function(a){var b,c,d,e;return this.expression.front=this.front,d=this.expression.compile(a,x),o.test(d)&&!a.scope.check(d)?(e=this.negated?["===","||"]:["!==","&&"],b=e[0],c=e[1],d="typeof "+d+" "+b+' "undefined" '+c+" "+d+" "+b+" null"):d=""+d+" "+(this.negated?"==":"!=")+" null",a.level<=v?d:"("+d+")"},b}(e),a.Parens=H=function(a){function b(a){this.body=a}return bm(b,a),b.prototype.children=["body"],b.prototype.unwrap=function(){return this.body},b.prototype.isComplex=function(){return this.body.isComplex()},b.prototype.compileNode=function(a){var b,c,d;return d=this.body.unwrap(),d instanceof W&&d.isAtomic()?(d.front=this.front,d.compile(a)):(c=d.compile(a,y),b=a.level1?b.expressions.unshift(new r((new H(this.guard)).invert(),new A("continue"))):this.guard&&(b=f.wrap([new r(this.guard,b)]))),this.pattern&&b.expressions.unshift(new d(this.name,new A(""+F+"["+l+"]"))),c+=this.pluckDirectCall(a,b),s&&(G="\n"+i+s+";"),this.object&&(e=""+l+" in "+F,this.own&&(h="\n"+i+"if (!"+bi("hasProp")+".call("+F+", "+l+")) continue;")),b=b.compile(bd(a,{indent:i}),z),b&&(b="\n"+b+"\n"),""+c+(u||"")+this.tab+"for ("+e+") {"+h+G+b+this.tab+"}"+(v||"")},b.prototype.pluckDirectCall=function(a,b){var c,e,f,h,i,k,l,m,n,o,p,q,r,s,t;e="",o=b.expressions;for(i=m=0,n=o.length;m=v?"("+d+")":d},b.prototype.unfoldSoak=function(){return this.soak&&this},b}(e),i={wrap:function(a,c,d){var e,h,i,k,l;if(a.jumps())return a;i=new j([],f.wrap([a])),e=[];if((k=a.contains(this.literalArgs))||a.contains(this.literalThis))l=new A(k?"apply":"call"),e=[new A("this")],k&&e.push(new A("arguments")),i=new W(i,[new b(l)]);return i.noReturn=d,h=new g(i,e),c?f.wrap([h]):h},literalArgs:function(a){return a instanceof A&&a.value==="arguments"&&!a.asKey},literalThis:function(a){return a instanceof A&&a.value==="this"&&!a.asKey||a instanceof j&&a.bound||a instanceof g&&a.isSuper}},bh=function(a,b,c){var d;if(!(d=b[c].unfoldSoak(a)))return;return b[c]=d.body,d.body=new W(b),d},V={"extends":function(){return"function(child, parent) { for (var key in parent) { if ("+bi("hasProp")+".call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }"},bind:function(){return"function(fn, me){ return function(){ return fn.apply(me, arguments); }; }"},indexOf:function(){return"[].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }"},hasProp:function(){return"{}.hasOwnProperty"},slice:function(){return"[].slice"}},z=1,y=2,w=3,v=4,x=5,u=6,R=" ",p="[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*",o=RegExp("^"+p+"$"),L=/^[+-]?\d+$/,B=RegExp("^(?:("+p+")\\.prototype(?:\\.("+p+")|\\[(\"(?:[^\\\\\"\\r\\n]|\\\\.)*\"|'(?:[^\\\\'\\r\\n]|\\\\.)*')\\]|\\[(0x[\\da-fA-F]+|\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\]))|("+p+")$"),q=/^['"]/,bi=function(a){var b;return b="__"+a,N.root.assign(b,V[a]()),b},be=function(a,b){return a=a.replace(/\n/g,"$&"+b),a.replace(/\s+$/,"")}})).call(this)},require["./coffee-script"]=new function(){var a=this;((function(){var b,c,d,e,f,g,h,i,j,k,l={}.hasOwnProperty;e=require("fs"),h=require("path"),k=require("./lexer"),b=k.Lexer,c=k.RESERVED,g=require("./parser").parser,j=require("vm"),i=function(a){return a.charCodeAt(0)===65279?a.substring(1):a},require.extensions&&(require.extensions[".coffee"]=function(a,b){var c;return c=d(i(e.readFileSync(b,"utf8")),{filename:b}),a._compile(c,b)}),a.VERSION="1.4.0",a.RESERVED=c,a.helpers=require("./helpers"),a.compile=d=function(b,c){var d,e,h;c==null&&(c={}),h=a.helpers.merge;try{e=g.parse(f.tokenize(b)).compile(c);if(!c.header)return e}catch(i){throw c.filename&&(i.message="In "+c.filename+", "+i.message),i}return d="Generated by CoffeeScript "+this.VERSION,"// "+d+"\n"+e},a.tokens=function(a,b){return f.tokenize(a,b)},a.nodes=function(a,b){return typeof a=="string"?g.parse(f.tokenize(a,b)):g.parse(a)},a.run=function(a,b){var c;return b==null&&(b={}),c=require.main,c.filename=process.argv[1]=b.filename?e.realpathSync(b.filename):".",c.moduleCache&&(c.moduleCache={}),c.paths=require("module")._nodeModulePaths(h.dirname(e.realpathSync(b.filename))),h.extname(c.filename)!==".coffee"||require.extensions?c._compile(d(a,b),c.filename):c._compile(a,c.filename)},a.eval=function(a,b){var c,e,f,g,i,k,m,n,o,p,q,r,s,t;b==null&&(b={});if(!(a=a.trim()))return;e=j.Script;if(e){if(b.sandbox!=null){if(b.sandbox instanceof e.createContext().constructor)m=b.sandbox;else{m=e.createContext(),r=b.sandbox;for(g in r){if(!l.call(r,g))continue;n=r[g],m[g]=n}}m.global=m.root=m.GLOBAL=m}else m=global;m.__filename=b.filename||"eval",m.__dirname=h.dirname(m.__filename);if(m===global&&!m.module&&!m.require){c=require("module"),m.module=q=new c(b.modulename||"eval"),m.require=t=function(a){return c._load(a,q,!0)},q.filename=m.__filename,s=Object.getOwnPropertyNames(require);for(o=0,p=s.length;o + @evaluate (selector) -> + document.querySelector(selector).style.display = "none" + , selector: selector + +casper.start "http://www.bbc.co.uk/", -> + nbLinks = @evaluate -> + return __utils__.findAll('#promo2_carousel_items_items li').length + @echo "#{nbLinks} items founds" + # hide navigation arrows + @hide ".nav_left" + @hide ".nav_right" + @mouse.move "#promo2_carousel" + @waitUntilVisible ".autoplay.nav_pause", -> + @echo "Moving over pause button" + @mouse.move ".autoplay.nav_pause" + @click ".autoplay.nav_pause" + @echo "Clicked on pause button" + @waitUntilVisible ".autoplay.nav_play", -> + @echo "Carousel has been paused" + # hide play button + @hide ".autoplay" + +# Capture carrousel area +next = -> + image = "bbcshot#{currentLink}.png" + images.push image + @echo "Processing image #{currentLink}" + @captureSelector image, '.carousel_viewport' + if currentLink < nbLinks + @click ".carousel_itemList_li[rel='#{currentLink}']" + @wait 1000, -> + @then next + currentLink++ + else + @then buildPage + +# Building resulting page and image +buildPage = -> + @echo "Build result page" + fs = require "fs" + @viewport 624, 400 + pageHtml = "" + images.forEach (image) -> + pageHtml += "
" + pageHtml += "" + fs.write "result.html", pageHtml, 'w' + @thenOpen "file://#{fs.workingDirectory}/result.html", -> + @echo "Resulting image saved to result.png" + @capture "result.png" + +casper.then next + +casper.run() diff --git a/zephyr/tests/frontend/casperjs/samples/bbcshots.js b/zephyr/tests/frontend/casperjs/samples/bbcshots.js new file mode 100644 index 0000000000..dafe6ca3b8 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/bbcshots.js @@ -0,0 +1,80 @@ +/* + * Create a mosaic image from all headline photos on BBC homepage + */ + +var casper = require("casper").create(); +var nbLinks = 0; +var currentLink = 1; +var images = []; +var buildPage, next; + +// helper to hide some element from remote DOM +casper.hide = function(selector) { + this.evaluate(function(selector) { + document.querySelector(selector).style.display = "none"; + }, { + selector: selector + }); +}; + +casper.start("http://www.bbc.co.uk/", function() { + nbLinks = this.evaluate(function() { + return __utils__.findAll('#promo2_carousel_items_items li').length; + }); + this.echo(nbLinks + " items founds"); + // hide navigation arrows + this.hide(".nav_left"); + this.hide(".nav_right"); + this.mouse.move("#promo2_carousel"); + this.waitUntilVisible(".autoplay.nav_pause", function() { + this.echo("Moving over pause button"); + this.mouse.move(".autoplay.nav_pause"); + this.click(".autoplay.nav_pause"); + this.echo("Clicked on pause button"); + this.waitUntilVisible(".autoplay.nav_play", function() { + this.echo("Carousel has been paused"); + // hide play button + this.hide(".autoplay"); + }); + }); +}); + +// Capture carrousel area +next = function() { + var image; + image = "bbcshot" + currentLink + ".png"; + images.push(image); + this.echo("Processing image " + currentLink); + this.captureSelector(image, '.carousel_viewport'); + if (currentLink < nbLinks) { + this.click(".carousel_itemList_li[rel='" + currentLink + "']"); + this.wait(1000, function() { + this.then(next); + currentLink++; + }); + } else { + this.then(buildPage); + } +}; + +// Building resulting page and image +buildPage = function() { + var fs, pageHtml; + this.echo("Build result page"); + fs = require("fs"); + this.viewport(624, 400); + pageHtml = ""; + images.forEach(function(image) { + pageHtml += "
"; + }); + pageHtml += ""; + fs.write("result.html", pageHtml, 'w'); + this.thenOpen("file://" + fs.workingDirectory + "/result.html", function() { + this.echo("Resulting image saved to result.png"); + this.capture("result.png"); + }); +}; + +casper.then(next); + +casper.run(); diff --git a/zephyr/tests/frontend/casperjs/samples/cliplay.coffee b/zephyr/tests/frontend/casperjs/samples/cliplay.coffee new file mode 100644 index 0000000000..802c970f93 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/cliplay.coffee @@ -0,0 +1,19 @@ +casper = require("casper").create() +dump = require("utils").dump + +# removing default options passed by the Python executable +casper.cli.drop "cli" +casper.cli.drop "casper-path" + +if casper.cli.args.length is 0 and Object.keys(casper.cli.options).length is 0 + casper + .echo("Pass some args and options to see how they are handled by CasperJS") + .exit(1) + +casper.echo "Casper CLI passed args:" +dump casper.cli.args + +casper.echo "Casper CLI passed options:" +dump casper.cli.options + +casper.exit() diff --git a/zephyr/tests/frontend/casperjs/samples/cliplay.js b/zephyr/tests/frontend/casperjs/samples/cliplay.js new file mode 100644 index 0000000000..d2557e2a29 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/cliplay.js @@ -0,0 +1,21 @@ +var casper = require("casper").create(); +var dump = require("utils").dump; + +// removing default options passed by the Python executable +casper.cli.drop("cli"); +casper.cli.drop("casper-path"); + +if (casper.cli.args.length === 0 && Object.keys(casper.cli.options).length === 0) { + casper + .echo("Pass some args and options to see how they are handled by CasperJS") + .exit(1) + ; +} + +casper.echo("Casper CLI passed args:"); +dump(casper.cli.args); + +casper.echo("Casper CLI passed options:"); +dump(casper.cli.options); + +casper.exit(); diff --git a/zephyr/tests/frontend/casperjs/samples/customevents.coffee b/zephyr/tests/frontend/casperjs/samples/customevents.coffee new file mode 100644 index 0000000000..84e0e306c6 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/customevents.coffee @@ -0,0 +1,11 @@ +casper = require("casper").create() + +# listening to a custom event +casper.on "google.loaded", (title) -> + @echo "Google page title is #{title}" + +casper.start "http://google.com/", -> + # emitting a custom event + @emit "google.loaded", @getTitle() + +casper.run() diff --git a/zephyr/tests/frontend/casperjs/samples/customevents.js b/zephyr/tests/frontend/casperjs/samples/customevents.js new file mode 100644 index 0000000000..57252a5e0c --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/customevents.js @@ -0,0 +1,13 @@ +var casper = require("casper").create(); + +// listening to a custom event +casper.on("google.loaded", function(title) { + this.echo("Google page title is " + title); +}); + +casper.start("http://google.com/", function() { + // emitting a custom event + this.emit("google.loaded", this.getTitle()); +}); + +casper.run(); diff --git a/zephyr/tests/frontend/casperjs/samples/customlogging.coffee b/zephyr/tests/frontend/casperjs/samples/customlogging.coffee new file mode 100644 index 0000000000..dbc907ba36 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/customlogging.coffee @@ -0,0 +1,33 @@ +### +A basic custom logging implementation. The idea is to (extremely) verbosely +log every received resource. +### + +casper = require("casper").create + ### + Every time a resource is received, a new log entry is added to the stack + at the 'verbose' level. + + @param Object resource A phantomjs resource object + ### + onResourceReceived: (self, resource) -> + infos = [] + props = [ + "url" + "status" + "statusText" + "redirectURL" + "bodySize" + ] + infos.push resource[prop] for prop in props + infos.push "[#{header.name}: #{header.value}]" for header in resource.headers + @log infos.join(", "), "verbose" + verbose: true # we want to see the log printed out to the console + logLevel: "verbose" # of course we want to see logs to our new level :) + +# add a new 'verbose' logging level at the lowest priority +casper.logLevels = ["verbose"].concat casper.logLevels + +# test our new logger with google +casper.start("http://www.google.com/").run -> + @exit() diff --git a/zephyr/tests/frontend/casperjs/samples/customlogging.js b/zephyr/tests/frontend/casperjs/samples/customlogging.js new file mode 100644 index 0000000000..73bfc5004e --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/customlogging.js @@ -0,0 +1,42 @@ +/* + * A basic custom logging implementation. The idea is to (extremely) verbosely + * log every received resource. + */ + +var casper = require("casper").create({ + /* + Every time a resource is received, a new log entry is added to the stack at + the 'verbose' level. + */ + onResourceReceived: function(self, resource) { + var header, infos, prop, props, _i, _j, _len, _len1, _ref; + infos = []; + props = [ + "url", + "status", + "statusText", + "redirectURL", + "bodySize" + ]; + for (_i = 0, _len = props.length; _i < _len; _i++) { + prop = props[_i]; + infos.push(resource[prop]); + } + _ref = resource.headers; + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + header = _ref[_j]; + infos.push("[" + header.name + ": " + header.value + "]"); + } + this.log(infos.join(", "), "verbose"); + }, + verbose: true, + logLevel: "verbose" +}); + +// add a new 'verbose' logging level at the lowest priority +casper.logLevels = ["verbose"].concat(casper.logLevels); + +// test our new logger with google +casper.start("http://www.google.com/").run(function() { + this.exit(); +}); diff --git a/zephyr/tests/frontend/casperjs/samples/download.coffee b/zephyr/tests/frontend/casperjs/samples/download.coffee new file mode 100644 index 0000000000..f05f0166f3 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/download.coffee @@ -0,0 +1,10 @@ +### +Download the google logo image onto the local filesystem +### + +casper = require("casper").create() + +casper.start "http://www.google.fr/", -> + @echo @download "http://www.google.fr/images/srpr/logo3w.png", "logo.png" + +casper.run() diff --git a/zephyr/tests/frontend/casperjs/samples/download.js b/zephyr/tests/frontend/casperjs/samples/download.js new file mode 100644 index 0000000000..8e92278788 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/download.js @@ -0,0 +1,11 @@ +/* + * download the google logo image onto the local filesystem + */ + +var casper = require("casper").create(); + +casper.start("http://www.google.fr/", function() { + this.download("http://www.google.fr/images/srpr/logo3w.png", "logo.png"); +}); + +casper.run(); diff --git a/zephyr/tests/frontend/casperjs/samples/dynamic.coffee b/zephyr/tests/frontend/casperjs/samples/dynamic.coffee new file mode 100644 index 0000000000..b6a22da8d4 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/dynamic.coffee @@ -0,0 +1,60 @@ +casper = require("casper").create + verbose: true + +# The base links array +links = [ + "http://google.com/" + "http://yahoo.com/" + "http://bing.com/" +] + +currentLink = 0; + +# If we don't set a limit, it could go on forever +upTo = ~~casper.cli.get(0) || 10 + +### +Get the links, and add them to the links array +(It could be done all in one step, but it is intentionally splitted) +### +addLinks = (link) -> + @then -> + found = @evaluate searchLinks + @echo "#{found.length} links found on #{link}" + links = links.concat found + +### +Fetch all
elements from the page and return +the ones which contains a href starting with 'http://' +### +searchLinks = -> + filter = Array::filter + map = Array::map + map.call filter.call(document.querySelectorAll("a"), (a) -> + (/^http:\/\/.*/i).test a.getAttribute("href") + ), (a) -> + a.getAttribute "href" + +# Just opens the page and prints the title +start = (link) -> + @start link, -> + @echo "Page title: #{ @getTitle() }" + +# As long as it has a next link, and is under the maximum limit, will keep running +check = -> + if links[currentLink] && currentLink < upTo + @echo "--- Link #{currentLink} ---" + start.call @, links[currentLink] + addLinks.call @, links[currentLink] + currentLink++ + @run check + else + @echo "All done." + @exit() + +casper.start() + +casper.then -> + @echo "Starting" + +casper.run check diff --git a/zephyr/tests/frontend/casperjs/samples/dynamic.js b/zephyr/tests/frontend/casperjs/samples/dynamic.js new file mode 100644 index 0000000000..ff5cb0edea --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/dynamic.js @@ -0,0 +1,65 @@ +var casper = require("casper").create({ + verbose: true +}); + +// The base links array +var links = [ + "http://google.com/", + "http://yahoo.com/", + "http://bing.com/" +]; + +// If we don't set a limit, it could go on forever +var upTo = ~~casper.cli.get(0) || 10; + +var currentLink = 0; + +// Get the links, and add them to the links array +// (It could be done all in one step, but it is intentionally splitted) +function addLinks(link) { + this.then(function() { + var found = this.evaluate(searchLinks); + this.echo(found.length + " links found on " + link); + links = links.concat(found); + }); +} + +// Fetch all elements from the page and return +// the ones which contains a href starting with 'http://' +function searchLinks() { + var filter, map; + filter = Array.prototype.filter; + map = Array.prototype.map; + return map.call(filter.call(document.querySelectorAll("a"), function(a) { + return (/^http:\/\/.*/i).test(a.getAttribute("href")); + }), function(a) { + return a.getAttribute("href"); + }); +} + +// Just opens the page and prints the title +function start(link) { + this.start(link, function() { + this.echo('Page title: ' + this.getTitle()); + }); +} + +// As long as it has a next link, and is under the maximum limit, will keep running +function check() { + if (links[currentLink] && currentLink < upTo) { + this.echo('--- Link ' + currentLink + ' ---'); + start.call(this, links[currentLink]); + addLinks.call(this, links[currentLink]); + currentLink++; + this.run(check); + } else { + this.echo("All done."); + this.exit(); + } +} + +casper.start().then(function() { + this.echo("Starting"); +}); + +casper.run(check); diff --git a/zephyr/tests/frontend/casperjs/samples/each.coffee b/zephyr/tests/frontend/casperjs/samples/each.coffee new file mode 100644 index 0000000000..4d51dae134 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/each.coffee @@ -0,0 +1,14 @@ +casper = require("casper").create() + +links = [ + "http://google.com/" + "http://yahoo.com/" + "http://bing.com/" +] + +casper.start() + +casper.each links, (self, link) -> + @thenOpen link, -> @echo "#{@getTitle()} - #{link}" + +casper.run() diff --git a/zephyr/tests/frontend/casperjs/samples/each.js b/zephyr/tests/frontend/casperjs/samples/each.js new file mode 100644 index 0000000000..e484862400 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/each.js @@ -0,0 +1,17 @@ +var casper = require("casper").create(); + +var links = [ + "http://google.com/", + "http://yahoo.com/", + "http://bing.com/" +]; + +casper.start(); + +casper.each(links, function(self, link) { + this.thenOpen(link, function() { + this.echo(this.getTitle() + " - " + link); + }); +}); + +casper.run(); diff --git a/zephyr/tests/frontend/casperjs/samples/events.coffee b/zephyr/tests/frontend/casperjs/samples/events.coffee new file mode 100644 index 0000000000..a8ef85a976 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/events.coffee @@ -0,0 +1,34 @@ +### +This script will add a custom HTTP status code handler, here for 404 pages. +### + +casper = require("casper").create() + +casper.on "http.status.200", (resource) -> + @echo "#{resource.url} is OK", "INFO" + +casper.on "http.status.301", (resource) -> + @echo "#{resource.url} is permanently redirected", "PARAMETER" + +casper.on "http.status.302", (resource) -> + @echo "#{resource.url} is temporarily redirected", "PARAMETER" + +casper.on "http.status.404", (resource) -> + @echo "#{resource.url} is not found", "COMMENT" + +casper.on "http.status.500", (resource) -> + @echo "#{resource.url} is in error", "ERROR" + +links = [ + "http://google.com/" + "http://www.google.com/" + "http://www.google.com/plop" +] + +casper.start() + +casper.each links, (self, link) -> + self.thenOpen link, -> + @echo "#{link} loaded" + +casper.run() diff --git a/zephyr/tests/frontend/casperjs/samples/events.js b/zephyr/tests/frontend/casperjs/samples/events.js new file mode 100644 index 0000000000..755f6826cf --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/events.js @@ -0,0 +1,41 @@ +/* + * This script will add a custom HTTP status code handler, here for 404 pages. + */ + +var casper = require("casper").create(); + +casper.on("http.status.200", function(resource) { + this.echo(resource.url + " is OK", "INFO"); +}); + +casper.on("http.status.301", function(resource) { + this.echo(resource.url + " is permanently redirected", "PARAMETER"); +}); + +casper.on("http.status.302", function(resource) { + this.echo(resource.url + " is temporarily redirected", "PARAMETER"); +}); + +casper.on("http.status.404", function(resource) { + this.echo(resource.url + " is not found", "COMMENT"); +}); + +casper.on("http.status.500", function(resource) { + this.echo(resource.url + " is in error", "ERROR"); +}); + +var links = [ + "http://google.com/", + "http://www.google.com/", + "http://www.google.com/plop" +]; + +casper.start(); + +casper.each(links, function(self, link) { + self.thenOpen(link, function() { + this.echo(link + " loaded"); + }); +}); + +casper.run(); diff --git a/zephyr/tests/frontend/casperjs/samples/extends.coffee b/zephyr/tests/frontend/casperjs/samples/extends.coffee new file mode 100644 index 0000000000..597b5a2559 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/extends.coffee @@ -0,0 +1,29 @@ +casper = require("casper").create + loadImages: false + logLevel: "debug" + verbose: true + +links = + "http://edition.cnn.com/": 0 + "http://www.nytimes.com/": 0 + "http://www.bbc.co.uk/": 0 + "http://www.guardian.co.uk/": 0 + +fantomas = Object.create(casper) + +fantomas.countLinks = -> + @evaluate -> + __utils__.findAll("a[href]").length + +fantomas.renderJSON = (what) -> + @echo JSON.stringify(what, null, " ") + +fantomas.start() + +Object.keys(links).forEach (url) -> + fantomas.thenOpen url, -> + links[url] = @countLinks() + +fantomas.run -> + @renderJSON(links) + @exit() diff --git a/zephyr/tests/frontend/casperjs/samples/extends.js b/zephyr/tests/frontend/casperjs/samples/extends.js new file mode 100644 index 0000000000..7463634f64 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/extends.js @@ -0,0 +1,37 @@ +var casper = require("casper").create({ + loadImages: false, + logLevel: "debug", + verbose: true +}); + +var links = { + "http://edition.cnn.com/": 0, + "http://www.nytimes.com/": 0, + "http://www.bbc.co.uk/": 0, + "http://www.guardian.co.uk/": 0 +}; + +var fantomas = Object.create(casper); + +fantomas.countLinks = function() { + return this.evaluate(function() { + return __utils__.findAll("a[href]").length; + }); +}; + +fantomas.renderJSON = function(what) { + this.echo(JSON.stringify(what, null, " ")); +}; + +fantomas.start(); + +Object.keys(links).forEach(function(url) { + fantomas.thenOpen(url, function() { + links[url] = this.countLinks(); + }); +}); + +fantomas.run(function() { + this.renderJSON(links); + this.exit(); +}); \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/samples/googlelinks.coffee b/zephyr/tests/frontend/casperjs/samples/googlelinks.coffee new file mode 100644 index 0000000000..b0ce0cda95 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/googlelinks.coffee @@ -0,0 +1,30 @@ +links = [] +casper = require("casper").create() + +getLinks = -> + links = document.querySelectorAll("h3.r a") + Array::map.call links, (e) -> + try + (/url\?q=(.*)&sa=U/).exec(e.getAttribute("href"))[1] + catch e + e.getAttribute "href" + +casper.start "http://google.fr/", -> + # search for 'casperjs' from google form + @fill "form[action=\"/search\"]", q: "casperjs", true + +casper.then -> + # aggregate results for the 'casperjs' search + links = @evaluate(getLinks) + # now search for 'phantomjs' by fillin the form again + @fill "form[action=\"/search\"]", q: "phantomjs", true + +casper.then -> + # aggregate results for the 'phantomjs' search + links = links.concat(@evaluate(getLinks)) + +casper.run -> + # echo results in some pretty fashion + @echo links.length + " links found:" + @echo " - " + links.join("\n - ") + @exit() diff --git a/zephyr/tests/frontend/casperjs/samples/googlelinks.js b/zephyr/tests/frontend/casperjs/samples/googlelinks.js new file mode 100644 index 0000000000..d2f72741a0 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/googlelinks.js @@ -0,0 +1,38 @@ +var links = []; +var casper = require("casper").create(); + +function getLinks() { + var links = document.querySelectorAll("h3.r a"); + return Array.prototype.map.call(links, function(e) { + try { + // google handles redirects hrefs to some script of theirs + return (/url\?q=(.*)&sa=U/).exec(e.getAttribute("href"))[1]; + } catch (err) { + return e.getAttribute("href"); + } + }); +} + +casper.start("http://google.fr/", function() { + // search for 'casperjs' from google form + this.fill('form[action="/search"]', { q: "casperjs" }, true); +}); + +casper.then(function() { + // aggregate results for the 'casperjs' search + links = this.evaluate(getLinks); + // now search for 'phantomjs' by fillin the form again + this.fill('form[action="/search"]', { q: "phantomjs" }, true); +}); + +casper.then(function() { + // aggregate results for the 'phantomjs' search + links = links.concat(this.evaluate(getLinks)); +}); + +casper.run(function() { + // echo results in some pretty fashion + this.echo(links.length + " links found:"); + this.echo(" - " + links.join("\n - ")); + this.exit(); +}); diff --git a/zephyr/tests/frontend/casperjs/samples/googlematch.coffee b/zephyr/tests/frontend/casperjs/samples/googlematch.coffee new file mode 100644 index 0000000000..6841a3f894 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/googlematch.coffee @@ -0,0 +1,47 @@ +### +Takes provided terms passed as arguments and query google for the number of +estimated results each have. + +Usage: + $ casperjs googlematch.coffee nicolas chuck borris + nicolas: 69600000 + chuck: 49500000 + borris: 2370000 + winner is "nicolas" with 69600000 results +### + +casper = require("casper").create verbose: true + +casper.fetchScore = -> + @evaluate -> + result = document.querySelector('#resultStats').innerText + parseInt /Environ ([0-9\s]{1,}).*/.exec(result)[1].replace(/\s/g, '') + +terms = casper.cli.args # terms are passed through command-line arguments + +if terms.length < 2 + casper + .echo("Usage: $ casperjs googlematch.js term1 term2 [term3]...") + .exit(1) + +scores = [] + +casper.echo "Let the match begin between \"#{terms.join '", "'}\"!" + +casper.start "http://google.fr/" + +casper.each terms, (self, term) -> + @then -> @fill 'form[action="/search"]', { q: term }, true + @then -> + score = @fetchScore() + scores.push term: term, score: score + @echo "#{term}: #{score}" + +casper.run -> + if scores.length is 0 + @echo "No result found" + else + scores.sort (a, b) -> b.score - a.score + winner = scores[0] + @echo "Winner is \"" + winner.term + "\" with " + winner.score + " results" + @exit() diff --git a/zephyr/tests/frontend/casperjs/samples/googlematch.js b/zephyr/tests/frontend/casperjs/samples/googlematch.js new file mode 100644 index 0000000000..b92cbdf947 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/googlematch.js @@ -0,0 +1,65 @@ +/* + * Takes provided terms passed as arguments and query google for the number of + * estimated results each have. + * + * Usage: + * $ casperjs googlematch.js nicolas chuck borris + * nicolas: 69600000 + * chuck: 49500000 + * borris: 2370000 + * winner is "nicolas" with 69600000 results + */ + +var casper = require("casper").create({ + verbose: true +}); + +casper.fetchScore = function() { + return this.evaluate(function() { + var result = document.querySelector('#resultStats').innerText; + return parseInt(/Environ ([0-9\s]{1,}).*/.exec(result)[1].replace(/\s/g, ''), 10); + }); +}; + +var terms = casper.cli.args; + +if (terms.length < 2) { + casper + .echo("Usage: $ casperjs googlematch.js term1 term2 [term3]...") + .exit(1) + ; +} + +var scores = []; + +casper.echo("Let the match begin between \"" + (terms.join('", "')) + "\"!"); + +casper.start("http://google.fr/"); + +casper.each(terms, function(casper, term, i) { + this.echo('Fecthing score for ' + term); + this.then(function() { + this.fill('form[action="/search"]', {q: term}, true); + }); + this.then(function() { + var score = this.fetchScore(); + scores.push({ + term: term, + score: score + }); + this.echo(term + ': ' + score); + }); +}); + +casper.run(function() { + if (scores.length === 0) { + this.echo("No result found"); + } else { + scores.sort(function(a, b) { + return b.score - a.score; + }); + var winner = scores[0]; + this.echo("Winner is \"" + winner.term + "\" with " + winner.score + " results"); + } + this.exit(); +}); diff --git a/zephyr/tests/frontend/casperjs/samples/googlepagination.coffee b/zephyr/tests/frontend/casperjs/samples/googlepagination.coffee new file mode 100644 index 0000000000..3cf7190ad1 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/googlepagination.coffee @@ -0,0 +1,40 @@ +### +Capture multiple pages of google search results + +Usage: $ casperjs googlepagination.coffee my search terms + +(all arguments will be used as the query) +### + +casper = require("casper").create() +currentPage = 1 + +if casper.cli.args.length is 0 + casper + .echo("Usage: $ casperjs googlepagination.coffee my search terms") + .exit(1) + +processPage = -> + @echo "capturing page #{currentPage}" + @capture "google-results-p#{currentPage}.png" + + # don't go too far down the rabbit hole + return if currentPage >= 5 + + if @exists "#pnnext" + currentPage++ + @echo "requesting next page: #{currentPage}" + url = @getCurrentUrl() + @thenClick("#pnnext").then -> + @waitFor (-> + url isnt @getCurrentUrl() + ), processPage + else + @echo "that's all, folks." + +casper.start "http://google.fr/", -> + @fill 'form[action="/search"]', q: casper.cli.args.join(" "), true + +casper.then processPage + +casper.run() diff --git a/zephyr/tests/frontend/casperjs/samples/googlepagination.js b/zephyr/tests/frontend/casperjs/samples/googlepagination.js new file mode 100644 index 0000000000..f427d92360 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/googlepagination.js @@ -0,0 +1,51 @@ +/* + * Capture multiple pages of google search results + * + * Usage: $ casperjs googlepagination.coffee my search terms + * + * (all arguments will be used as the query) + */ + +var casper = require("casper").create(); +var currentPage = 1; + +if (casper.cli.args.length === 0) { + casper + .echo("Usage: $ casperjs googlepagination.js my search terms") + .exit(1) + ; +} + +var processPage = function() { + var url; + this.echo("capturing page " + currentPage); + this.capture("google-results-p" + currentPage + ".png"); + + // don't go too far down the rabbit hole + if (currentPage >= 5) { + return; + } + + if (this.exists("#pnnext")) { + currentPage++; + this.echo("requesting next page: " + currentPage); + url = this.getCurrentUrl(); + this.thenClick("#pnnext").then(function() { + this.waitFor(function() { + return url !== this.getCurrentUrl(); + }, processPage); + }); + } else { + this.echo("that's all, folks."); + } +}; + +casper.start("http://google.fr/", function() { + this.fill('form[action="/search"]', { + q: casper.cli.args.join(" ") + }, true); +}); + +casper.then(processPage); + +casper.run(); diff --git a/zephyr/tests/frontend/casperjs/samples/googletesting.coffee b/zephyr/tests/frontend/casperjs/samples/googletesting.coffee new file mode 100644 index 0000000000..f7b5b8c64f --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/googletesting.coffee @@ -0,0 +1,17 @@ +casper = require("casper").create + logLevel: "debug" + +casper.start "http://www.google.fr/", -> + @test.assertTitle "Google", "google homepage title is the one expected" + @test.assertExists 'form[action="/search"]', "main form is found" + @fill 'form[action="/search"]', q: "foo", true + +casper.then -> + @test.assertTitle "foo - Recherche Google", "google title is ok" + @test.assertUrlMatch /q=foo/, "search term has been submitted" + @test.assertEval (-> + __utils__.findAll("h3.r").length >= 10 + ), "google search for \"foo\" retrieves 10 or more results" + +casper.run -> + @test.renderResults true diff --git a/zephyr/tests/frontend/casperjs/samples/googletesting.js b/zephyr/tests/frontend/casperjs/samples/googletesting.js new file mode 100644 index 0000000000..6a9b3026fd --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/googletesting.js @@ -0,0 +1,23 @@ +var casper = require("casper").create({ + logLevel: "debug" +}); + +casper.start("http://www.google.fr/", function() { + this.test.assertTitle("Google", "google homepage title is the one expected"); + this.test.assertExists('form[action="/search"]', "main form is found"); + this.fill('form[action="/search"]', { + q: "foo" + }, true); +}); + +casper.then(function() { + this.test.assertTitle("foo - Recherche Google", "google title is ok"); + this.test.assertUrlMatch(/q=foo/, "search term has been submitted"); + this.test.assertEval((function() { + return __utils__.findAll("h3.r").length >= 10; + }), "google search for \"foo\" retrieves 10 or more results"); +}); + +casper.run(function() { + this.test.renderResults(true); +}); diff --git a/zephyr/tests/frontend/casperjs/samples/logcolor.coffee b/zephyr/tests/frontend/casperjs/samples/logcolor.coffee new file mode 100644 index 0000000000..475d92034b --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/logcolor.coffee @@ -0,0 +1,10 @@ +casper = require("casper").create + verbose: true + logLevel: "debug" + +casper.log "this is a debug message", "debug" +casper.log "and an informative one", "info" +casper.log "and a warning", "warning" +casper.log "and an error", "error" + +casper.exit() diff --git a/zephyr/tests/frontend/casperjs/samples/logcolor.js b/zephyr/tests/frontend/casperjs/samples/logcolor.js new file mode 100644 index 0000000000..370e2be6cb --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/logcolor.js @@ -0,0 +1,11 @@ +var casper = require("casper").create({ + verbose: true, + logLevel: "debug" +}); + +casper.log("this is a debug message", "debug"); +casper.log("and an informative one", "info"); +casper.log("and a warning", "warning"); +casper.log("and an error", "error"); + +casper.exit(); diff --git a/zephyr/tests/frontend/casperjs/samples/metaextract.coffee b/zephyr/tests/frontend/casperjs/samples/metaextract.coffee new file mode 100644 index 0000000000..80afe78a27 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/metaextract.coffee @@ -0,0 +1,23 @@ +casper = require("casper").create() +url = casper.cli.get 0 +metas = [] + +if not url + casper + .echo("Usage: $ casperjs metaextract.coffee ") + .exit 1 + +casper.start url, -> + metas = @evaluate -> + metas = [] + castarray = (arr) -> [].slice.call(arr) + for elem in castarray document.querySelectorAll "meta" + meta = {} + for attr in castarray elem.attributes + meta[attr.name] = attr.value + metas.push meta + metas + +casper.run -> + require("utils").dump metas + this.exit() diff --git a/zephyr/tests/frontend/casperjs/samples/metaextract.js b/zephyr/tests/frontend/casperjs/samples/metaextract.js new file mode 100644 index 0000000000..319c6d2dae --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/metaextract.js @@ -0,0 +1,29 @@ +var casper = require("casper").create(); +var url = casper.cli.get(0); +var metas = []; + +if (!url) { + casper + .echo("Usage: $ casperjs metaextract.js ") + .exit(1) + ; +} + +casper.start(url, function() { + metas = this.evaluate(function() { + var metas = []; + [].forEach.call(document.querySelectorAll("meta"), function(elem) { + var meta = {}; + [].slice.call(elem.attributes).forEach(function(attr) { + meta[attr.name] = attr.value; + }); + metas.push(meta); + }); + return metas; + }); +}); + +casper.run(function() { + require("utils").dump(metas); + this.exit(); +}); diff --git a/zephyr/tests/frontend/casperjs/samples/multirun.coffee b/zephyr/tests/frontend/casperjs/samples/multirun.coffee new file mode 100644 index 0000000000..281ff3928c --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/multirun.coffee @@ -0,0 +1,37 @@ +casper = require("casper").create verbose: true + +countLinks = -> + document.querySelectorAll('a').length + +suites = [ + -> + @echo "Suite 1" + @start "http://google.com/", -> @echo "Page title: #{@getTitle()}" + @then -> @echo "#{@evaluate(countLinks)} links" + -> + @echo "Suite 2" + @start "http://yahoo.com/", -> @echo "Page title: #{@getTitle()}" + @then -> @echo "#{@evaluate(countLinks)} links" + -> + @echo "Suite 3" + @start "http://bing.com/", -> @echo "Page title: #{@getTitle()}" + @then -> @echo "#{@evaluate(countLinks)} links" +] + +casper.start() + +casper.then -> + @echo("Starting") + +currentSuite = 0; + +check = -> + if suites[currentSuite] + suites[currentSuite].call @ + currentSuite++; + casper.run check + else + @echo "All done." + @exit() + +casper.run check diff --git a/zephyr/tests/frontend/casperjs/samples/multirun.js b/zephyr/tests/frontend/casperjs/samples/multirun.js new file mode 100644 index 0000000000..28ac0d1d8d --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/multirun.js @@ -0,0 +1,56 @@ +var casper = require("casper").create({ + verbose: true +}); + +var countLinks = function() { + return document.querySelectorAll('a').length; +}; + +var suites = [ + function() { + this.echo("Suite 1"); + this.start("http://google.com/", function() { + this.echo("Page title: " + (this.getTitle())); + }); + this.then(function() { + this.echo((this.evaluate(countLinks)) + " links"); + }); + }, function() { + this.echo("Suite 2"); + this.start("http://yahoo.com/", function() { + this.echo("Page title: " + (this.getTitle())); + }); + this.then(function() { + this.echo((this.evaluate(countLinks)) + " links"); + }); + }, function() { + this.echo("Suite 3"); + this.start("http://bing.com/", function() { + this.echo("Page title: " + (this.getTitle())); + }); + this.then(function() { + this.echo((this.evaluate(countLinks)) + " links"); + }); + } +]; + +casper.start(); + +casper.then(function() { + this.echo("Starting"); +}); + +var currentSuite = 0; + +var check = function() { + if (suites[currentSuite]) { + suites[currentSuite].call(this); + currentSuite++; + casper.run(check); + } else { + this.echo("All done."); + this.exit(); + } +}; + +casper.run(check); diff --git a/zephyr/tests/frontend/casperjs/samples/screenshot.coffee b/zephyr/tests/frontend/casperjs/samples/screenshot.coffee new file mode 100644 index 0000000000..b5dc69249c --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/screenshot.coffee @@ -0,0 +1,28 @@ +### +This script will capture a screenshot of a twitter account page +Usage: $ casperjs screenshot.coffee +### + +casper = require("casper").create + viewportSize: + width: 1024 + height: 768 + +twitterAccount = casper.cli.get 0 +filename = casper.cli.get 1 + +if not twitterAccount or not filename or not /\.(png|jpg|pdf)$/i.test filename + casper + .echo("Usage: $ casperjs screenshot.coffee ") + .exit(1) + +casper.start "https://twitter.com/#!/#{twitterAccount}", -> + @waitForSelector ".tweet-row", (-> + @captureSelector filename, "html" + @echo "Saved screenshot of #{@getCurrentUrl()} to #{filename}" + ), (-> + @die("Timeout reached. Fail whale?") + @exit() + ), 12000 + +casper.run() diff --git a/zephyr/tests/frontend/casperjs/samples/screenshot.js b/zephyr/tests/frontend/casperjs/samples/screenshot.js new file mode 100644 index 0000000000..d92631f5d7 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/screenshot.js @@ -0,0 +1,33 @@ +/* + * This script will capture a screenshot of a twitter account page + * Usage: $ casperjs screenshot.coffee + */ + +var casper = require("casper").create({ + viewportSize: { + width: 1024, + height: 768 + } +}); + +var twitterAccount = casper.cli.get(0); +var filename = casper.cli.get(1); + +if (!twitterAccount || !filename || !/\.(png|jpg|pdf)$/i.test(filename)) { + casper + .echo("Usage: $ casperjs screenshot.coffee ") + .exit(1) + ; +} + +casper.start("https://twitter.com/#!/" + twitterAccount, function() { + this.waitForSelector(".tweet-row", (function() { + this.captureSelector(filename, "html"); + this.echo("Saved screenshot of " + (this.getCurrentUrl()) + " to " + filename); + }), (function() { + this.die("Timeout reached. Fail whale?"); + this.exit(); + }), 12000); +}); + +casper.run(); diff --git a/zephyr/tests/frontend/casperjs/samples/statushandlers.coffee b/zephyr/tests/frontend/casperjs/samples/statushandlers.coffee new file mode 100644 index 0000000000..6cc9a44a78 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/statushandlers.coffee @@ -0,0 +1,15 @@ +### +This script will add a custom HTTP status code handler, here for 404 pages. +### + +casper = require("casper").create + httpStatusHandlers: + 404: (self, resource) -> + @echo "Resource at #{resource.url} not found (404)", "COMMENT" + verbose: true + +casper.start "http://www.google.com/plop", -> + @echo "Done." + @exit() + +casper.run() diff --git a/zephyr/tests/frontend/casperjs/samples/statushandlers.js b/zephyr/tests/frontend/casperjs/samples/statushandlers.js new file mode 100644 index 0000000000..cfdde27417 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/statushandlers.js @@ -0,0 +1,19 @@ +/* + * This script will add a custom HTTP status code handler, here for 404 pages. + */ + +var casper = require("casper").create({ + httpStatusHandlers: { + 404: function(self, resource) { + this.echo("Resource at " + resource.url + " not found (404)", "COMMENT"); + } + }, + verbose: true +}); + +casper.start("http://www.google.com/plop", function() { + this.echo("Done."); + this.exit(); +}); + +casper.run(); diff --git a/zephyr/tests/frontend/casperjs/samples/steptimeout.coffee b/zephyr/tests/frontend/casperjs/samples/steptimeout.coffee new file mode 100644 index 0000000000..41f05ace25 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/steptimeout.coffee @@ -0,0 +1,37 @@ +failed = [] +start = null +links = [ + "http://google.com/" + "http://akei.com/" + "http://lemonde.fr/" + "http://liberation.fr/" + "http://cdiscount.fr/" +] + +casper = require("casper").create + onStepTimeout: -> + failed.push @requestUrl + @test.fail "#{@requestUrl} loads in less than #{timeout}ms." + +casper.on "load.finished", -> + @echo "#{@requestUrl} loaded in #{new Date() - start}ms", "PARAMETER" + +timeout = ~~casper.cli.get(0) +timeout = 1000 if timeout < 1 +casper.options.stepTimeout = timeout + +casper.echo "Testing with timeout=#{timeout}ms, please be patient." + +casper.start() + +casper.each links, (self, link) -> + @then -> + @test.comment "Loading #{link}" + start = new Date() + @open link + @then -> + if @requestUrl not in failed + @test.pass "#{@requestUrl} loaded in less than #{timeout}ms." + +casper.run -> + @test.renderResults true diff --git a/zephyr/tests/frontend/casperjs/samples/steptimeout.js b/zephyr/tests/frontend/casperjs/samples/steptimeout.js new file mode 100644 index 0000000000..afbb607020 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/steptimeout.js @@ -0,0 +1,45 @@ +var failed = []; +var start = null; +var links = [ + "http://google.com/'", + "http://akei.com/'", + "http://lemonde.fr/'", + "http://liberation.fr/'", + "http://cdiscount.fr/" +]; + +var casper = require("casper").create({ + onStepTimeout: function() { + failed.push(this.requestUrl); + this.test.fail(this.requestUrl + " loads in less than " + timeout + "ms."); + } +}); + +casper.on("load.finished", function() { + this.echo(this.requestUrl + " loaded in " + (new Date() - start) + "ms", "PARAMETER"); +}); + +var timeout = ~~casper.cli.get(0); +casper.options.stepTimeout = timeout > 0 ? timeout : 1000; + +casper.echo("Testing with timeout=" + casper.options.stepTimeout + "ms, please be patient."); + +casper.start(); + +casper.each(links, function(casper, link) { + this.then(function() { + this.test.comment("Loading " + link); + start = new Date(); + this.open(link); + }); + this.then(function() { + var message = this.requestUrl + " loads in less than " + timeout + "ms."; + if (failed.indexOf(this.requestUrl) === -1) { + this.test.pass(message); + } + }); +}); + +casper.run(function() { + this.test.renderResults(true); +}); diff --git a/zephyr/tests/frontend/casperjs/samples/timeout.coffee b/zephyr/tests/frontend/casperjs/samples/timeout.coffee new file mode 100644 index 0000000000..c87d82ad52 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/timeout.coffee @@ -0,0 +1,39 @@ +### +Just a silly game. + +$ casperjs samples/timeout.js 500 +Will google.com load in less than 500ms? +NOPE. + +$ casperjs samples/timeout.js 1000 +Will google.com load in less than 1000ms? +NOPE. + +$ casperjs samples/timeout.js 1500 +Will google.com load in less than 1500ms? +NOPE. + +$ casperjs samples/timeout.js 2000 +Will google.com load in less than 2000ms? +YES! +### + +casper = require("casper").create + onTimeout: -> + @echo "NOPE.", "RED_BAR" + @exit() + +timeout = ~~casper.cli.get 0 +if timeout < 1 + casper + .echo("You must pass a valid timeout value") + .exit(1) + +casper.echo "Will google.com load in less than #{timeout}ms?" +casper.options.timeout = timeout + +casper.start "http://www.google.com/", -> + @echo "YES!", "GREEN_BAR" + @exit() + +casper.run() \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/samples/timeout.js b/zephyr/tests/frontend/casperjs/samples/timeout.js new file mode 100644 index 0000000000..40a14648ed --- /dev/null +++ b/zephyr/tests/frontend/casperjs/samples/timeout.js @@ -0,0 +1,47 @@ +/* + * Just a silly game. + * + * $ casperjs samples/timeout.js 500 + * Will google.com load in less than 500ms? + * NOPE. + * + * $ casperjs samples/timeout.js 1000 + * Will google.com load in less than 1000ms? + * NOPE. + * + * $ casperjs samples/timeout.js 1500 + * Will google.com load in less than 1500ms? + * NOPE. + * + * $ casperjs samples/timeout.js 2000 + * Will google.com load in less than 2000ms? + * YES! + */ + +var casper = require("casper").create({ + onTimeout: function() { + this + .echo("NOPE.", "RED_BAR") + .exit() + ; + } +}); + +var timeout = ~~casper.cli.get(0); + +if (timeout < 1) { + casper + .echo("You must pass a valid timeout value") + .exit(1) + ; +} + +casper.echo("Will google.com load in less than " + timeout + "ms?"); +casper.options.timeout = timeout; + +casper.start("http://www.google.com/", function() { + this.echo("YES!", "GREEN_BAR"); + this.exit(); +}); + +casper.run(); diff --git a/zephyr/tests/frontend/casperjs/tests/run.js b/zephyr/tests/frontend/casperjs/tests/run.js new file mode 100644 index 0000000000..ce024e5773 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/run.js @@ -0,0 +1,103 @@ +/*global phantom*/ + +if (!phantom.casperLoaded) { + console.log('This script must be invoked using the casperjs executable'); + phantom.exit(1); +} + +var fs = require('fs'); +var colorizer = require('colorizer'); +var utils = require('utils'); +var f = utils.format; +var loadIncludes = ['includes', 'pre', 'post']; +var tests = []; +var casper = require('casper').create({ + exitOnError: false +}); + +// local utils +function checkSelfTest(tests) { + "use strict"; + var isCasperTest = false; + tests.forEach(function(test) { + var testDir = fs.absolute(fs.dirname(test)); + if (fs.isDirectory(testDir)) { + if (fs.exists(fs.pathJoin(testDir, '.casper'))) { + isCasperTest = true; + } + } + }); + return isCasperTest; +} + +function checkIncludeFile(include) { + "use strict"; + var absInclude = fs.absolute(include.trim()); + if (!fs.exists(absInclude)) { + casper.warn("%s file not found, can't be included", absInclude); + return; + } + if (!utils.isJsFile(absInclude)) { + casper.warn("%s is not a supported file type, can't be included", absInclude); + return; + } + if (fs.isDirectory(absInclude)) { + casper.warn("%s is a directory, can't be included", absInclude); + return; + } + if (tests.indexOf(include) > -1 || tests.indexOf(absInclude) > -1) { + casper.warn("%s is a test file, can't be included", absInclude); + return; + } + return absInclude; +} + +// parse some options from cli +casper.options.verbose = casper.cli.get('direct') || false; +casper.options.logLevel = casper.cli.get('log-level') || "error"; +if (casper.cli.get('no-colors') === true) { + var cls = 'Dummy'; + casper.options.colorizerType = cls; + casper.colorizer = colorizer.create(cls); +} + +// test paths are passed as args +if (casper.cli.args.length) { + tests = casper.cli.args.filter(function(path) { + "use strict"; + return fs.isFile(path) || fs.isDirectory(path); + }); +} else { + casper.echo('No test path passed, exiting.', 'RED_BAR', 80); + casper.exit(1); +} + +// check for casper selftests +if (!phantom.casperSelfTest && checkSelfTest(tests)) { + casper.warn('To run casper self tests, use the `selftest` command.'); + casper.exit(1); +} + +// includes handling +this.loadIncludes.forEach(function(include){ + "use strict"; + var container; + if (casper.cli.has(include)) { + container = casper.cli.get(include).split(',').map(function(file) { + return checkIncludeFile(file); + }).filter(function(file) { + return utils.isString(file); + }); + + casper.test.loadIncludes[include] = utils.unique(container); + } +}); + +// test suites completion listener +casper.test.on('tests.complete', function() { + "use strict"; + this.renderResults(true, undefined, casper.cli.get('xunit') || undefined); +}); + +// run all the suites +casper.test.runSuites.apply(casper.test, tests); diff --git a/zephyr/tests/frontend/casperjs/tests/sample_modules/csmodule.coffee b/zephyr/tests/frontend/casperjs/tests/sample_modules/csmodule.coffee new file mode 100644 index 0000000000..ee0c5f1097 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/sample_modules/csmodule.coffee @@ -0,0 +1,5 @@ +try + exports.ok = true +catch e + casper.test.fail('error in coffeescript module code: ' + e) + casper.test.done() diff --git a/zephyr/tests/frontend/casperjs/tests/sample_modules/jsmodule.js b/zephyr/tests/frontend/casperjs/tests/sample_modules/jsmodule.js new file mode 100644 index 0000000000..0a1bdaa3ab --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/sample_modules/jsmodule.js @@ -0,0 +1,7 @@ +/*global casper*/ +try { + exports.ok = true; +} catch (e) { + casper.test.fail('error in js module code' + e); + casper.test.done() +} diff --git a/zephyr/tests/frontend/casperjs/tests/selftest.js b/zephyr/tests/frontend/casperjs/tests/selftest.js new file mode 100644 index 0000000000..147d0428ec --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/selftest.js @@ -0,0 +1,53 @@ +/** + * CasperJS local HTTP test server + */ + +/*global phantom casper require*/ + +var colorizer = require('colorizer').create('Colorizer'); +var fs = require('fs'); +var utils = require('utils'); +var server = require('webserver').create(); +var service; +var testServerPort = 54321; + +function info(message) { + "use strict"; + console.log(colorizer.colorize('INFO', 'INFO_BAR') + ' ' + message); +} + +service = server.listen(testServerPort, function(request, response) { + "use strict"; + var requestPath = request.url; + if (requestPath.indexOf('?') !== -1) { + requestPath = request.url.split('?')[0]; + } + var pageFile = fs.pathJoin(phantom.casperPath, requestPath); + if (!fs.exists(pageFile) || !fs.isFile(pageFile)) { + response.statusCode = 404; + console.log(utils.format('Test server url not found: %s (file: %s)', request.url, pageFile), "warning"); + response.write("404 - NOT FOUND"); + } else { + response.statusCode = 200; + response.write(fs.read(pageFile)); + } + response.close(); +}); + +// overriding Casper.open to prefix all test urls +casper.setFilter('open.location', function(location) { + "use strict"; + if (/^file/.test(location)) { + return location; + } + if (!/^http/.test(location)) { + return utils.format('http://localhost:%d/%s', testServerPort, location); + } + return location; +}); + +// test suites completion listener +casper.test.on('tests.complete', function() { + "use strict"; + server.close(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/site/alert.html b/zephyr/tests/frontend/casperjs/tests/site/alert.html new file mode 100644 index 0000000000..6bfea52130 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/alert.html @@ -0,0 +1,10 @@ + + + + + CasperJS test alert + + + + + diff --git a/zephyr/tests/frontend/casperjs/tests/site/click.html b/zephyr/tests/frontend/casperjs/tests/site/click.html new file mode 100644 index 0000000000..ffcb8f564b --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/click.html @@ -0,0 +1,40 @@ + + + + + CasperJS test click + + + test1 + test2 + test3 + test4 + + + diff --git a/zephyr/tests/frontend/casperjs/tests/site/confirm.html b/zephyr/tests/frontend/casperjs/tests/site/confirm.html new file mode 100644 index 0000000000..e667b5387c --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/confirm.html @@ -0,0 +1,12 @@ + + + + + CasperJS test confirm + + + + + diff --git a/zephyr/tests/frontend/casperjs/tests/site/dummy.js b/zephyr/tests/frontend/casperjs/tests/site/dummy.js new file mode 100644 index 0000000000..97fc627ad0 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/dummy.js @@ -0,0 +1 @@ +document.write('foo'); diff --git a/zephyr/tests/frontend/casperjs/tests/site/elementattribute.html b/zephyr/tests/frontend/casperjs/tests/site/elementattribute.html new file mode 100644 index 0000000000..64ccb20001 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/elementattribute.html @@ -0,0 +1,6 @@ + + + +
+ + \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/tests/site/error.html b/zephyr/tests/frontend/casperjs/tests/site/error.html new file mode 100644 index 0000000000..d1e9bc7473 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/error.html @@ -0,0 +1,10 @@ + + + + + CasperJS error test + + + + + diff --git a/zephyr/tests/frontend/casperjs/tests/site/form.html b/zephyr/tests/frontend/casperjs/tests/site/form.html new file mode 100644 index 0000000000..e51ffa32d4 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/form.html @@ -0,0 +1,26 @@ + + + + + CasperJS test form + + +
+ + + + + + + + + + + + +
+ + diff --git a/zephyr/tests/frontend/casperjs/tests/site/global.html b/zephyr/tests/frontend/casperjs/tests/site/global.html new file mode 100644 index 0000000000..1ec80555ec --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/global.html @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/tests/site/index.html b/zephyr/tests/frontend/casperjs/tests/site/index.html new file mode 100644 index 0000000000..c6879266cd --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/index.html @@ -0,0 +1,20 @@ + + + + + CasperJS test index + + + + test + form + + +

Title

+ + + diff --git a/zephyr/tests/frontend/casperjs/tests/site/mouse-events.html b/zephyr/tests/frontend/casperjs/tests/site/mouse-events.html new file mode 100644 index 0000000000..3336751a7a --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/mouse-events.html @@ -0,0 +1,47 @@ + + + + + CasperJS test mouse events + + + test + test + test + test + test + test + test + test + + + diff --git a/zephyr/tests/frontend/casperjs/tests/site/multiple-forms.html b/zephyr/tests/frontend/casperjs/tests/site/multiple-forms.html new file mode 100644 index 0000000000..3e9db353ca --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/multiple-forms.html @@ -0,0 +1,16 @@ + + + + Multiple forms test + + +
+ + +
+
+ + +
+ + diff --git a/zephyr/tests/frontend/casperjs/tests/site/page1.html b/zephyr/tests/frontend/casperjs/tests/site/page1.html new file mode 100644 index 0000000000..c3140e7651 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/page1.html @@ -0,0 +1,8 @@ + + + + + CasperJS test page 1 + + Booh. + \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/tests/site/page2.html b/zephyr/tests/frontend/casperjs/tests/site/page2.html new file mode 100644 index 0000000000..3fe0f6319f --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/page2.html @@ -0,0 +1,8 @@ + + + + + CasperJS test page 2 + + Booh. + \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/tests/site/page3.html b/zephyr/tests/frontend/casperjs/tests/site/page3.html new file mode 100644 index 0000000000..c3140e7651 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/page3.html @@ -0,0 +1,8 @@ + + + + + CasperJS test page 1 + + Booh. + \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/tests/site/prompt.html b/zephyr/tests/frontend/casperjs/tests/site/prompt.html new file mode 100644 index 0000000000..291d86b7db --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/prompt.html @@ -0,0 +1,12 @@ + + + + + CasperJS test prompt + + + + + diff --git a/zephyr/tests/frontend/casperjs/tests/site/resources.html b/zephyr/tests/frontend/casperjs/tests/site/resources.html new file mode 100644 index 0000000000..6c48e43d1b --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/resources.html @@ -0,0 +1,15 @@ + + + + + CasperJS test resource + + + + + + diff --git a/zephyr/tests/frontend/casperjs/tests/site/result.html b/zephyr/tests/frontend/casperjs/tests/site/result.html new file mode 100644 index 0000000000..de4365574e --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/result.html @@ -0,0 +1,11 @@ + + + + + CasperJS test form result + + +

this is the result page

+

Return back home

+ + \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/tests/site/test.html b/zephyr/tests/frontend/casperjs/tests/site/test.html new file mode 100644 index 0000000000..d365c9731f --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/test.html @@ -0,0 +1,10 @@ + + + + + CasperJS test target + + + test form + + \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/tests/site/urls.html b/zephyr/tests/frontend/casperjs/tests/site/urls.html new file mode 100644 index 0000000000..726af187c5 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/urls.html @@ -0,0 +1,14 @@ + + + + + CasperJS url tests + + + + + diff --git a/zephyr/tests/frontend/casperjs/tests/site/visible.html b/zephyr/tests/frontend/casperjs/tests/site/visible.html new file mode 100644 index 0000000000..c638138f05 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/visible.html @@ -0,0 +1,17 @@ + + + + + CasperJS test index + + + + + + + + \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/tests/site/waitFor.html b/zephyr/tests/frontend/casperjs/tests/site/waitFor.html new file mode 100644 index 0000000000..e0dd56ceba --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/waitFor.html @@ -0,0 +1,22 @@ + + + + + waitFor test + + + + + + + \ No newline at end of file diff --git a/zephyr/tests/frontend/casperjs/tests/suites/.casper b/zephyr/tests/frontend/casperjs/tests/suites/.casper new file mode 100644 index 0000000000..e69de29bb2 diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/.casper b/zephyr/tests/frontend/casperjs/tests/suites/casper/.casper new file mode 100644 index 0000000000..e69de29bb2 diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/agent.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/agent.js new file mode 100644 index 0000000000..8de41ac1bd --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/agent.js @@ -0,0 +1,26 @@ +/*global casper*/ +/*jshint strict:false*/ +function testUA(ua, match) { + casper.test.assertMatch( + ua, match, 'Default user agent matches ' + match + ); +} + +function fetchUA(request) { + testUA(request.headers.filter(function(header) { + return header.name === "User-Agent"; + }).pop().value, /plop/); +} + +testUA(casper.options.pageSettings.userAgent, /CasperJS/); + +casper.start(); + +casper.userAgent('plop').on('resource.requested', fetchUA); + +casper.thenOpen('tests/site/index.html'); + +casper.run(function() { + this.removeListener('resource.requested', fetchUA); + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/capture.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/capture.js new file mode 100644 index 0000000000..3310e2d38b --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/capture.js @@ -0,0 +1,33 @@ +/*global casper*/ +/*jshint strict:false*/ +var fs = require('fs'), testFile = '/tmp/__casper_test_capture.png'; + +if (fs.exists(testFile) && fs.isFile(testFile)) { + fs.remove(testFile); +} + +casper.start('tests/site/index.html', function() { + this.viewport(300, 200); + this.test.comment('Casper.capture()'); + this.capture(testFile); + this.test.assert(fs.isFile(testFile), 'Casper.capture() captured a screenshot'); +}); + +if (phantom.version.major === 1 && phantom.version.minor >= 6) { + casper.thenOpen('tests/site/index.html', function() { + this.test.comment('Casper.captureBase64()'); + this.test.assert(this.captureBase64('png').length > 0, + 'Casper.captureBase64() rendered a page capture as base64'); + this.test.assert(this.captureBase64('png', 'ul').length > 0, + 'Casper.captureBase64() rendered a capture from a selector as base64'); + this.test.assert(this.captureBase64('png', {top: 0, left: 0, width: 30, height: 30}).length > 0, + 'Casper.captureBase64() rendered a capture from a clipRect as base64'); + }); +} + +casper.run(function() { + try { + fs.remove(testFile); + } catch(e) {} + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/click.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/click.js new file mode 100644 index 0000000000..f595c7f491 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/click.js @@ -0,0 +1,63 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.start('tests/site/index.html', function() { + this.click('a[href="test.html"]'); +}); + +casper.then(function() { + this.test.comment('Casper.click()'); + this.test.assertTitle('CasperJS test target', 'Casper.click() can click on a link'); +}).thenClick('a', function() { + this.test.comment('Casper.thenClick()'); + this.test.assertTitle('CasperJS test form', 'Casper.thenClick() can click on a link'); +}); + +// onclick variants tests +casper.thenOpen('tests/site/click.html', function() { + this.test.comment('Casper.click()'); + this.test.assert(this.click('#test1'), 'Casper.click() can click an `href="javascript:` link'); + this.test.assert(this.click('#test2'), 'Casper.click() can click an `href="#"` link'); + this.test.assert(this.click('#test3'), 'Casper.click() can click an `onclick=".*; return false"` link'); + this.test.assert(this.click('#test4'), 'Casper.click() can click an unobstrusive js handled link'); + var results = this.getGlobal('results'); + this.test.assert(results.test1, 'Casper.click() has clicked an `href="javascript:` link'); + this.test.assert(results.test2, 'Casper.click() has clicked an `href="#"` link'); + this.test.assert(results.test3, 'Casper.click() has clicked an `onclick=".*; return false"` link'); + this.test.assert(results.test4, 'Casper.click() has clicked an unobstrusive js handled link'); +}); + +// clickLabel tests +casper.thenOpen('tests/site/click.html', function() { + this.test.comment('Casper.clickLabel()'); + this.test.assert(this.clickLabel('test1'), 'Casper.clickLabel() can click an `href="javascript:` link'); + this.test.assert(this.clickLabel('test2'), 'Casper.clickLabel() can click an `href="#"` link'); + this.test.assert(this.clickLabel('test3'), 'Casper.clickLabel() can click an `onclick=".*; return false"` link'); + this.test.assert(this.clickLabel('test4'), 'Casper.clickLabel() can click an unobstrusive js handled link'); + var results = this.getGlobal('results'); + this.test.assert(results.test1, 'Casper.clickLabel() has clicked an `href="javascript:` link'); + this.test.assert(results.test2, 'Casper.clickLabel() has clicked an `href="#"` link'); + this.test.assert(results.test3, 'Casper.clickLabel() has clicked an `onclick=".*; return false"` link'); + this.test.assert(results.test4, 'Casper.clickLabel() has clicked an unobstrusive js handled link'); +}); + +// casper.mouse +casper.then(function() { + this.test.comment('Mouse.down()'); + this.mouse.down(200, 100); + var results = this.getGlobal('results'); + this.test.assertEquals(results.testdown, [200, 100], 'Mouse.down() has pressed button to the specified position'); + + this.test.comment('Mouse.up()'); + this.mouse.up(200, 100); + results = this.getGlobal('results'); + this.test.assertEquals(results.testup, [200, 100], 'Mouse.up() has released button to the specified position'); + + this.test.comment('Mouse.move()'); + this.mouse.move(200, 100); + results = this.getGlobal('results'); + this.test.assertEquals(results.testmove, [200, 100], 'Mouse.move() has moved to the specified position'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/confirm.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/confirm.js new file mode 100644 index 0000000000..de73fe8e28 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/confirm.js @@ -0,0 +1,23 @@ +/*global casper*/ +/*jshint strict:false*/ +// skip this test for phantom versions < 1.5 +if (phantom.version.major === 1 && phantom.version.minor < 6) { + casper.test.comment('Skipped tests, PhantomJS 1.6 required'); + casper.test.done(); +} else { + var received; + + casper.setFilter('page.confirm', function(message) { + received = message; + return true; + }); + + casper.start('tests/site/confirm.html', function() { + this.test.assert(this.getGlobal('confirmed'), 'confirmation received'); + }); + + casper.run(function() { + this.test.assertEquals(received, 'are you sure?', 'confirmation message is ok'); + this.test.done(); + }); +} diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/debug.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/debug.js new file mode 100644 index 0000000000..9cd16e768d --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/debug.js @@ -0,0 +1,10 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.start('tests/site/index.html', function() { + this.test.assertEquals(this.getHTML('ul li'), 'one', 'Casper.getHTML() retrieves inner HTML by default'); + this.test.assertEquals(this.getHTML('ul li', true), '
  • one
  • ', 'Casper.getHTML() can retrieve outer HTML'); +}); + +casper.run(function() { + casper.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/elementattribute.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/elementattribute.js new file mode 100644 index 0000000000..73f67b9d3e --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/elementattribute.js @@ -0,0 +1,10 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.start('tests/site/elementattribute.html', function() { + this.test.comment('Casper.getElementAttribute()'); + this.test.assertEquals(this.getElementAttribute('.testo','data-stuff'), 'beautiful string', 'Casper.getElementAttribute() works as intended'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/encode.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/encode.js new file mode 100644 index 0000000000..f60419ae63 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/encode.js @@ -0,0 +1,24 @@ +/*global casper*/ +/*jshint strict:false*/ +var fs = require('fs'); + +// FIXME: we're using local url scheme until https://github.com/ariya/phantomjs/pull/288 is +// possibly merged +casper.start('file://' + phantom.casperPath + '/tests/site/index.html', function() { + var imageUrl = 'file://' + phantom.casperPath + '/tests/site/images/phantom.png'; + var image = this.base64encode(imageUrl); + + this.test.comment('Casper.base64encode()'); + this.test.assertEquals(image.length, 6160, 'Casper.base64encode() can retrieve base64 contents'); + + this.test.comment('Casper.download()'); + this.download(imageUrl, '__test_logo.png'); + this.test.assert(fs.exists('__test_logo.png'), 'Casper.download() downloads a file'); + if (fs.exists('__test_logo.png')) { + fs.remove('__test_logo.png'); + } +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/evaluate.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/evaluate.js new file mode 100644 index 0000000000..55adde98fe --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/evaluate.js @@ -0,0 +1,35 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.test.comment('Casper.evaluate()'); + +casper.start(); + +var context = { + "_boolean_true": true, + "_boolean_false": false, + "_int_number": 42, + "_float_number": 1337.42, + "_string": "plop! \"Ÿ£$\" 'no'", + "_array": [1, 2, 3], + "_object": {a: 1, b: 2}, + "_function": function(){console.log('ok');} +}; + +var result = casper.evaluate(function(_boolean_true, + _boolean_false, + _int_number, + _float_number, + _string, + _array, + _object, + _function) { + return [].map.call(arguments, function(arg) { + return typeof(arg); + }); +}, context); + +casper.test.assertEquals(result.toString(), + ['boolean', 'boolean', 'number', 'number', 'string', 'object', 'object', 'function'].toString(), + 'Casper.evaluate() handles passed argument context correcly'); + +casper.test.done(); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/events.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/events.js new file mode 100644 index 0000000000..ec3f784a5f --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/events.js @@ -0,0 +1,40 @@ +/*global casper*/ +/*jshint strict:false*/ +// events + +casper.test.comment("events"); + +casper.plopped = false; + +casper.on("plop", function() { + this.plopped = true; +}); + +casper.test.assert(Object.keys(casper._events).some(function(i) { + return i === "plop"; +}), "on() has set an event handler"); + +casper.emit("plop"); + +casper.test.assert(casper.plopped, "emit() emits an event"); + +// filters + +casper.test.comment("filters"); + +casper.foo = 0; +casper.setFilter("test", function(a) { + this.foo = 42; + return a + 1; +}); + +casper.test.assert(Object.keys(casper._filters).some(function(i) { + return i === "test"; +}), "setFilter() has set a filter"); + +casper.test.assertEquals(casper.filter("test", 1), 2, "filter() filters a value"); +casper.test.assertEquals(casper.foo, 42, "filter() applies the correct context"); + +delete casper.foo; + +casper.test.done(); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/exists.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/exists.js new file mode 100644 index 0000000000..9839b70e7e --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/exists.js @@ -0,0 +1,11 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.test.comment('Casper.exists()'); + +casper.start('tests/site/index.html', function() { + this.test.assert(this.exists('a') && !this.exists('chucknorriz'), 'Casper.exists() can check if an element exists'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/fetchtext.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/fetchtext.js new file mode 100644 index 0000000000..ffd072ac94 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/fetchtext.js @@ -0,0 +1,11 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.test.comment('Casper.fetchText()'); + +casper.start('tests/site/index.html', function() { + this.test.assertEquals(this.fetchText('ul li'), 'onetwothree', 'Casper.fetchText() can retrieve text contents'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/flow.coffee b/zephyr/tests/frontend/casperjs/tests/suites/casper/flow.coffee new file mode 100644 index 0000000000..9413b8ec63 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/flow.coffee @@ -0,0 +1,38 @@ +step = 0 + +# testing resources +casper.start "tests/site/resources.html", -> + @test.assertEquals ++step, 1, "step 1" + @wait 400, -> + @test.assertEquals ++step, 2, "step 1.1" + @wait 200, -> + @test.assertEquals ++step, 3, "step 1.1.1" + @wait 200, -> + @test.assertEquals ++step, 4, "step 1.1.1.1" + @then -> + @test.assertEquals ++step, 5, "step 1.1.2.1" + @wait 400, -> + @test.assertEquals ++step, 6, "step 1.2" + +casper.wait 200, -> + @test.assertEquals ++step, 7, "step 2" + +casper.waitForSelector( + '#noneExistingSelector' + -> @test.fail "should run into timeout" + -> @test.assertEquals ++step, 8, "step 3 sucessfully timed out" + 1000 +) +casper.then -> + @test.assertEquals ++step, 9, "step 4" + @wait 300, -> + @test.assertEquals ++step, 10, "step 4.1" + @wait 300, -> + @test.assertEquals ++step, 11, "step 4.1.1" + @wait 100, -> + @test.assertEquals ++step, 12, "step 5.2" + +casper.then -> + @test.assertEquals ++step, 13, "last step" + +casper.run(-> @test.done()) diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/formfill.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/formfill.js new file mode 100644 index 0000000000..c2cc8cf18f --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/formfill.js @@ -0,0 +1,78 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.start('tests/site/form.html', function() { + this.test.comment('Casper.fill()'); + this.fill('form[action="result.html"]', { + email: 'chuck@norris.com', + password: 'chuck', + content: 'Am watching thou', + check: true, + choice: 'no', + topic: 'bar', + file: phantom.libraryPath + '/README.md', + 'checklist[]': ['1', '3'] + }); + this.test.assertEvalEquals(function() { + return document.querySelector('input[name="email"]').value; + }, 'chuck@norris.com', 'Casper.fill() can fill an input[type=text] form field'); + this.test.assertEvalEquals(function() { + return document.querySelector('input[name="password"]').value; + }, 'chuck', 'Casper.fill() can fill an input[type=password] form field'); + this.test.assertEvalEquals(function() { + return document.querySelector('textarea[name="content"]').value; + }, 'Am watching thou', 'Casper.fill() can fill a textarea form field'); + this.test.assertEvalEquals(function() { + return document.querySelector('select[name="topic"]').value; + }, 'bar', 'Casper.fill() can pick a value from a select form field'); + this.test.assertEvalEquals(function() { + return document.querySelector('input[name="check"]').checked; + }, true, 'Casper.fill() can check a form checkbox'); + this.test.assertEvalEquals(function() { + return document.querySelector('input[name="choice"][value="no"]').checked; + }, true, 'Casper.fill() can check a form radio button 1/2'); + this.test.assertEvalEquals(function() { + return document.querySelector('input[name="choice"][value="yes"]').checked; + }, false, 'Casper.fill() can check a form radio button 2/2'); + this.test.assertEvalEquals(function() { + return document.querySelector('input[name="file"]').files.length === 1; + }, true, 'Casper.fill() can select a file to upload'); + this.test.assertEvalEquals(function() { + return (document.querySelector('input[name="checklist[]"][value="1"]').checked && + !document.querySelector('input[name="checklist[]"][value="2"]').checked && + document.querySelector('input[name="checklist[]"][value="3"]').checked); + }, true, 'Casper.fill() can fill a list of checkboxes'); + this.click('input[type="submit"]'); +}); + +casper.then(function() { + this.test.comment('Form submitted'); + this.test.assertUrlMatch(/email=chuck@norris.com/, 'Casper.fill() input[type=email] field was submitted'); + this.test.assertUrlMatch(/password=chuck/, 'Casper.fill() input[type=password] field was submitted'); + this.test.assertUrlMatch(/content=Am\+watching\+thou/, 'Casper.fill() textarea field was submitted'); + this.test.assertUrlMatch(/check=on/, 'Casper.fill() input[type=checkbox] field was submitted'); + this.test.assertUrlMatch(/choice=no/, 'Casper.fill() input[type=radio] field was submitted'); + this.test.assertUrlMatch(/topic=bar/, 'Casper.fill() select field was submitted'); +}); + +casper.thenOpen('tests/site/form.html', function() { + this.test.comment('Unexistent fields'); + this.test.assertRaises(this.fill, ['form[action="result.html"]', { + unexistent: 42 + }, true], 'Casper.fill() raises an exception when unable to fill a form'); +}); + +// multiple forms +casper.thenOpen('tests/site/multiple-forms.html', function() { + this.test.comment('Multiple forms'); + this.fill('form[name="f2"]', { + yo: "ok" + }, true); +}); + +casper.then(function() { + this.test.assertUrlMatch(/\?f=f2&yo=ok$/, 'Casper.fill() handles multiple forms'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/global.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/global.js new file mode 100644 index 0000000000..016dee1e50 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/global.js @@ -0,0 +1,11 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.start('tests/site/global.html', function() { + this.test.comment('Casper.getGlobal()'); + this.test.assertEquals(this.getGlobal('myGlobal'), 'awesome string', 'Casper.getGlobal() can retrieve a remote global variable'); + this.test.assertRaises(this.getGlobal, ['myUnencodableGlobal'], 'Casper.getGlobal() does not fail trying to encode an unencodable global'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/headers.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/headers.js new file mode 100644 index 0000000000..3dc725b68f --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/headers.js @@ -0,0 +1,41 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.test.comment('Casper.headers.get()'); + +var server = require('webserver').create(); +var service = server.listen(8090, function(request, response) { + response.statusCode = 200; + response.headers = { + 'Content-Language': 'en', + 'Content-Type': 'text/html', + 'Date': new Date().toUTCString() + }; + response.write("ok"); + response.close(); +}); + +function dumpHeaders() { + casper.test.comment('Dumping current response headers'); + + casper.currentResponse.headers.forEach(function(header) { + casper.test.comment('- ' + header.name + ': ' + header.value); + }); +} + +// local file:// url +casper.start('file://' + phantom.casperPath + 'tests/site/index.html', function thenLocalPage(response) { + this.test.assertEquals(response, undefined, 'No response available on local page'); +}); + +casper.thenOpen('http://localhost:8090/', function thenLocalhost(response) { + var headers = response.headers; + + this.test.assertEquals(headers.get('Content-Language'), 'en', 'Checking existing header (case sensitive)'); + this.test.assertEquals(headers.get('content-language'), 'en', 'Checking existing header (case insensitive)'); + this.test.assertEquals(headers.get('X-Is-Troll'), null, 'Checking unexisting header'); +}); + +casper.run(function() { + server.close(); + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/history.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/history.js new file mode 100644 index 0000000000..4a9b4926c4 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/history.js @@ -0,0 +1,23 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.start('tests/site/page1.html'); +casper.thenOpen('tests/site/page2.html'); +casper.thenOpen('tests/site/page3.html'); + +casper.back(); +casper.then(function() { + this.test.comment('navigating history backward'); + this.test.assertMatch(this.getCurrentUrl(), /tests\/site\/page2\.html$/, 'Casper.back() can go back an history step'); +}); + +casper.forward(); +casper.then(function() { + this.test.comment('navigating history forward'); + this.test.assertMatch(this.getCurrentUrl(), /tests\/site\/page3\.html$/, 'Casper.forward() can go forward an history step'); +}); + +casper.run(function() { + this.test.assert(this.history.length > 0, 'Casper.history contains urls'); + this.test.assertMatch(this.history[0], /tests\/site\/page1\.html$/, 'Casper.history has the correct first url'); + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/hooks.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/hooks.js new file mode 100644 index 0000000000..19442547a3 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/hooks.js @@ -0,0 +1,43 @@ +/*global casper*/ +/*jshint strict:false*/ +// Dear curious test reader, +// The on* family of methods is considered deprecated since 0.6.0; please use events instead + +// Casper.options.onStepComplete +casper.start('tests/site/index.html', function() { + this.options.onStepComplete = function(self, stepResult) { + this.test.comment('Casper.options.onStepComplete()'); + this.test.assertEquals(stepResult, 'ok', 'Casper.options.onStepComplete() is called on step complete'); + self.options.onStepComplete = null; + }; + return 'ok'; +}); + +// Casper.options.onResourceRequested & Casper.options.onResourceReceived +casper.then(function() { + this.options.onResourceReceived = function(self, resource) { + this.test.comment('Casper.options.onResourceReceived()'); + this.test.assertType(resource, 'object', 'Casper.options.onResourceReceived() retrieve a resource object'); + this.test.assert('status' in resource, 'Casper.options.onResourceReceived() retrieve a valid resource object'); + self.options.onResourceReceived = null; + }; + this.options.onResourceRequested = function(self, request) { + this.test.comment('Casper.options.onResourceRequested()'); + this.test.assertType(request, 'object', 'Casper.options.onResourceRequested() retrieve a request object'); + this.test.assert('method' in request, 'Casper.options.onResourceRequested() retrieve a valid request object'); + self.options.onResourceRequested = null; + }; + this.thenOpen('tests/site/page1.html'); +}); + +// Casper.options.onAlert() +casper.then(function() { + this.options.onAlert = function(self, message) { + self.test.assertEquals(message, 'plop', 'Casper.options.onAlert() can intercept an alert message'); + }; +}); + +casper.run(function() { + this.options.onAlert = null; + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/logging.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/logging.js new file mode 100644 index 0000000000..66dd29e934 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/logging.js @@ -0,0 +1,40 @@ +/*jshint strict:false*/ +/*global casper __utils__*/ +casper.start('tests/site/index.html'); + +var oldLevel = casper.options.logLevel; + +casper.options.logLevel = 'info'; +casper.options.verbose = false; + +casper.test.comment('Casper.log()'); +casper.log('foo', 'info'); +casper.test.assert(casper.result.log.some(function(e) { + return e.message === 'foo' && e.level === 'info'; +}), 'Casper.log() adds a log entry'); + +casper.options.logLevel = oldLevel; +casper.options.verbose = true; + +casper.then(function() { + var oldLevel = casper.options.logLevel; + casper.options.logLevel = 'debug'; + casper.options.verbose = false; + casper.evaluate(function() { + __utils__.log('debug message'); + __utils__.log('info message', 'info'); + }); + this.test.assert(casper.result.log.some(function(e) { + return e.message === 'debug message' && e.level === 'debug' && e.space === 'remote'; + }), 'ClientUtils.log() adds a log entry'); + this.test.assert(casper.result.log.some(function(e) { + return e.message === 'info message' && e.level === 'info' && e.space === 'remote'; + }), 'ClientUtils.log() adds a log entry at a given level'); + casper.options.logLevel = oldLevel; + casper.options.verbose = true; +}); + +casper.run(function() { + this.test.assertEquals(this.result.log.length, 3, 'Casper.log() logged messages'); + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/mouseevents.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/mouseevents.js new file mode 100644 index 0000000000..24b9dbf096 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/mouseevents.js @@ -0,0 +1,29 @@ +/*global casper*/ +/*jshint strict:false maxstatements:99*/ +casper.start('tests/site/mouse-events.html'); + +casper.then(function() { + this.test.comment('CasperUtils.mouseEvent()'); + this.test.assert(this.mouseEvent('mousedown', '#test1'), 'CasperUtils.mouseEvent() can dispatch a mousedown event'); + this.test.assert(this.mouseEvent('mousedown', '#test2'), 'CasperUtils.mouseEvent() can dispatch a mousedown event handled by unobstrusive js'); + this.test.assert(this.mouseEvent('mouseup', '#test3'), 'CasperUtils.mouseEvent() can dispatch a mouseup event'); + this.test.assert(this.mouseEvent('mouseup', '#test4'), 'CasperUtils.mouseEvent() can dispatch a mouseup event handled by unobstrusive js'); + this.test.assert(this.mouseEvent('mouseover', '#test5'), 'CasperUtils.mouseEvent() can dispatch a mouseover event'); + this.test.assert(this.mouseEvent('mouseover', '#test6'), 'CasperUtils.mouseEvent() can dispatch a mouseover event handled by unobstrusive js'); + this.test.assert(this.mouseEvent('mouseout', '#test7'), 'CasperUtils.mouseEvent() can dispatch a mouseout event'); + this.test.assert(this.mouseEvent('mouseout', '#test8'), 'CasperUtils.mouseEvent() can dispatch a mouseout event handled by unobstrusive js'); + + var results = this.getGlobal('results'); + this.test.assert(results.test1, 'CasperUtils.mouseEvent() triggered mousedown'); + this.test.assert(results.test2, 'CasperUtils.mouseEvent() triggered mousedown via unobstrusive js'); + this.test.assert(results.test3, 'CasperUtils.mouseEvent() triggered mouseup'); + this.test.assert(results.test4, 'CasperUtils.mouseEvent() triggered mouseup via unobstrusive js'); + this.test.assert(results.test5, 'CasperUtils.mouseEvent() triggered mouseover'); + this.test.assert(results.test6, 'CasperUtils.mouseEvent() triggered mouseover via unobstrusive js'); + this.test.assert(results.test7, 'CasperUtils.mouseEvent() triggered mouseout'); + this.test.assert(results.test8, 'CasperUtils.mouseEvent() triggered mouseout via unobstrusive js'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/onerror.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/onerror.js new file mode 100644 index 0000000000..3e87db7fdc --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/onerror.js @@ -0,0 +1,21 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.test.comment("page.error event"); + +var error = {}; + +casper.start(); + +casper.on("page.error", function onError(msg, trace) { + error.msg = msg; + error.trace = trace; +}); + +casper.thenOpen('tests/site/error.html', function() { + this.test.assertEquals(error.msg, "ReferenceError: Can't find variable: plop", 'page.error event has been caught OK'); + this.test.assertMatch(error.trace[0].file, /error.html/, 'page.error retrieves correct stack trace'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/open.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/open.js new file mode 100644 index 0000000000..08fc4cfd77 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/open.js @@ -0,0 +1,135 @@ +/*global casper*/ +/*jshint strict:false*/ +var t = casper.test, current = 0, tests = [ + function(settings) { + t.assertEquals(settings, { + method: "get" + }, "Casper.open() used the expected GET settings"); + }, + function(settings) { + t.assertEquals(settings, { + method: "post", + data: "plop=42&chuck=norris" + }, "Casper.open() used the expected POST settings"); + }, + function(settings) { + t.assertEquals(settings, { + method: "put", + data: "plop=42&chuck=norris" + }, "Casper.open() used the expected PUT settings"); + }, + function(settings) { + t.assertEquals(settings, { + method: "get", + username: 'bob', + password: 'sinclar' + }, "Casper.open() used the expected HTTP auth settings"); + }, + function(settings) { + t.assertEquals(settings, { + method: "get" + }, "Casper.thenOpen() used the expected GET settings"); + }, + function(settings) { + t.assertEquals(settings, { + method: "post", + data: "plop=42&chuck=norris" + }, "Casper.thenOpen() used the expected POST settings"); + }, + function(settings) { + t.assertEquals(settings, { + method: "put", + data: "plop=42&chuck=norris" + }, "Casper.thenOpen() used the expected PUT settings"); + }, + function(settings) { + t.assertEquals(settings, { + method: "get", + username: 'bob', + password: 'sinclar' + }, "Casper.thenOpen() used the expected HTTP auth settings"); + }, +]; + +casper.start(); + +casper.on('open', function(url, settings) { + tests[current++](settings); +}); + +// GET +casper.open('tests/site/index.html').then(function() { + t.pass("Casper.open() can open and load a location using GET"); +}); + +// POST +casper.open('tests/site/index.html', { + method: 'post', + data: { + plop: 42, + chuck: 'norris' + } +}).then(function() { + t.pass("Casper.open() can open and load a location using POST"); +}); + +// PUT +casper.open('tests/site/index.html', { + method: 'put', + data: { + plop: 42, + chuck: 'norris' + } +}).then(function() { + t.pass("Casper.open() can open and load a location using PUT"); +}); + +// HTTP Auth +casper.open('tests/site/index.html', { + method: 'get', + username: 'bob', + password: 'sinclar' +}).then(function() { + t.pass("Casper.open() can open and load a location using HTTP auth"); +}); + +// GET with thenOpen +casper.thenOpen('tests/site/index.html').then(function() { + t.pass("Casper.thenOpen() can open and load a location using GET"); +}); + +// POST with thenOpen +casper.thenOpen('tests/site/index.html', { + method: 'post', + data: { + plop: 42, + chuck: 'norris' + } +}, function() { + t.pass("Casper.thenOpen() can open and load a location using POST"); +}); + +// PUT with thenOpen +casper.thenOpen('tests/site/index.html', { + method: 'put', + data: { + plop: 42, + chuck: 'norris' + } +}, function() { + t.pass("Casper.thenOpen() can open and load a location using PUT"); +}); + +// HTTP Auth with thenOpen +casper.thenOpen('tests/site/index.html', { + method: 'get', + username: 'bob', + password: 'sinclar' +}, function() { + t.pass("Casper.thenOpen() can open and load a location using HTTP auth"); +}); + +casper.run(function() { + this.removeAllListeners('open'); + t.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/prompt.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/prompt.js new file mode 100644 index 0000000000..7660777bbf --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/prompt.js @@ -0,0 +1,19 @@ +/*global casper*/ +/*jshint strict:false*/ +// skip this test for phantom versions < 1.5 +if (phantom.version.major === 1 && phantom.version.minor < 6) { + casper.test.comment('Skipped tests, PhantomJS 1.6 required'); + casper.test.done(); +} else { + casper.setFilter('page.prompt', function(message, value) { + return 'Chuck ' + value; + }); + + casper.start('tests/site/prompt.html', function() { + this.test.assertEquals(this.getGlobal('name'), 'Chuck Norris', 'prompted value has been received'); + }); + + casper.run(function() { + this.test.done(); + }); +} diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/resources.coffee b/zephyr/tests/frontend/casperjs/tests/suites/casper/resources.coffee new file mode 100644 index 0000000000..3432ebb33d --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/resources.coffee @@ -0,0 +1,24 @@ +casper.start "tests/site/resources.html", -> + @test.assertEquals @resources.length, 1, "only one resource found" + onTime = -> + @test.assertEquals( + @resources.length + 2 + "two resources found" + ) + @test.assertResourceExists( + /dummy\.js/i + "phantom image found via test RegExp" + ) + @test.assertResourceExists( + (res) -> res.url.match "dummy.js" + "phantom image found via test Function" + ) + @test.assertResourceExists( + "dummy.js" + "phantom image found via test String" + ) + onTimeout = -> @test.fail "waitForResource timeout occured" + @waitForResource "dummy.js", onTime, onTimeout + +casper.run(-> @test.done()) diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/start.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/start.js new file mode 100644 index 0000000000..aa3ba02325 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/start.js @@ -0,0 +1,17 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.test.comment('Casper.start()'); + +casper.start('tests/site/index.html', function() { + this.test.pass('Casper.start() can chain a next step'); + this.test.assertTitle('CasperJS test index', 'Casper.start() opened the passed url'); + this.test.assertEval(function() { + return typeof(__utils__) === "object"; + }, 'Casper.start() injects ClientUtils instance within remote DOM'); +}); + +casper.test.assert(casper.started, 'Casper.start() started'); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/steps.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/steps.js new file mode 100644 index 0000000000..14fab001be --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/steps.js @@ -0,0 +1,34 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.test.comment('Casper.then()'); + +casper.start('tests/site/index.html'); + +var nsteps = casper.steps.length; + +casper.then(function(response) { + this.test.assertTitle('CasperJS test index', 'Casper.then() added a new step'); +}); + +casper.test.assertEquals(casper.steps.length, nsteps + 1, 'Casper.then() can add a new step'); + +casper.test.comment('Casper.thenOpen()'); + +casper.thenOpen('tests/site/test.html'); + +casper.test.assertEquals(casper.steps.length, nsteps + 2, 'Casper.thenOpen() can add a new step'); + +casper.thenOpen('tests/site/test.html', function() { + this.test.assertTitle('CasperJS test target', 'Casper.thenOpen() opened a location and executed a step'); +}); + +casper.test.assertEquals(casper.steps.length, nsteps + 4, 'Casper.thenOpen() can add a new step for opening, plus another step'); + +casper.test.comment('Casper.each()'); +casper.each([1, 2, 3], function(self, item, i) { + self.test.assertEquals(i, item - 1, 'Casper.each() passes a contextualized index'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/urls.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/urls.js new file mode 100644 index 0000000000..880f0125a8 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/urls.js @@ -0,0 +1,22 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.start('tests/site/urls.html', function() { + this.clickLabel('raw unicode', 'a'); +}).then(function() { + this.test.assertHttpStatus(200); + this.test.assertUrlMatches('Forlì', 'Casper.getCurrentUrl() retrieves a raw unicode URL'); + this.clickLabel('escaped', 'a'); +}); + +casper.then(function() { + this.test.assertHttpStatus(200); + this.test.assertUrlMatches('Forlì', 'Casper.getCurrentUrl() retrieves an escaped URL'); + this.clickLabel('uri encoded', 'a'); +}); + +casper.run(function() { + this.test.assertHttpStatus(200); + this.test.assertUrlMatches('Forlì', 'Casper.getCurrentUrl() retrieves a decoded URL'); + this.test.done(); +}); + diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/viewport.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/viewport.js new file mode 100644 index 0000000000..513366bb03 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/viewport.js @@ -0,0 +1,13 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.test.comment('Casper.viewport()'); + +casper.start(); + +casper.viewport(1337, 999); + +casper.test.assertEquals(casper.page.viewportSize.width, 1337, 'Casper.viewport() can change the width of page viewport'); +casper.test.assertEquals(casper.page.viewportSize.height, 999, 'Casper.viewport() can change the height of page viewport'); +casper.test.assertRaises(casper.viewport, ['a', 'b'], 'Casper.viewport() validates viewport size data'); + +casper.test.done(); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/visible.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/visible.js new file mode 100644 index 0000000000..d673eea3b5 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/visible.js @@ -0,0 +1,19 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.start('tests/site/visible.html', function() { + this.test.comment('Casper.visible()'); + this.test.assert(this.visible('#img1'), 'Casper.visible() can detect if an element is visible'); + this.test.assert(!this.visible('#img2'), 'Casper.visible() can detect if an element is invisible'); + this.test.assert(!this.visible('#img3'), 'Casper.visible() can detect if an element is invisible'); + this.waitWhileVisible('#img1', function() { + this.test.comment('Casper.waitWhileVisible()'); + this.test.pass('Casper.waitWhileVisible() can wait while an element is visible'); + }, function() { + this.test.comment('Casper.waitWhileVisible()'); + this.test.fail('Casper.waitWhileVisible() can wait while an element is visible'); + }, 2000); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/wait.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/wait.js new file mode 100644 index 0000000000..74850e963a --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/wait.js @@ -0,0 +1,37 @@ +/*global casper*/ +/*jshint strict:false*/ +var waitStart; + +casper.start('tests/site/index.html', function() { + waitStart = new Date().getTime(); +}); + +casper.wait(1000, function() { + this.test.comment('Casper.wait()'); + this.test.assert(new Date().getTime() - waitStart > 1000, 'Casper.wait() can wait for a given amount of time'); + // Casper.waitFor() + casper.thenOpen('tests/site/waitFor.html', function() { + this.test.comment('Casper.waitFor()'); + this.waitFor(function() { + return this.evaluate(function() { + return document.querySelectorAll('li').length === 4; + }); + }, function() { + this.test.pass('Casper.waitFor() can wait for something to happen'); + }, function() { + this.test.fail('Casper.waitFor() can wait for something to happen'); + }); + }); +}); + +casper.thenOpen('tests/site/waitFor.html').waitForText('
  • four
  • ', function() { + this.test.comment('Casper.waitForText()'); + this.test.pass('Casper.waitForText() can wait for text'); +}, function() { + this.test.comment('Casper.waitForText()'); + this.test.fail('Casper.waitForText() can wait for text'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/casper/xpath.js b/zephyr/tests/frontend/casperjs/tests/suites/casper/xpath.js new file mode 100644 index 0000000000..4a85ace684 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/casper/xpath.js @@ -0,0 +1,34 @@ +/*global casper __utils__*/ +/*jshint strict:false*/ +var x = require('casper').selectXPath; + +casper.test.comment('XPath'); + +casper.start('tests/site/index.html', function() { + this.test.assertExists({ + type: 'xpath', + path: '/html/body/ul/li[2]' + }, 'XPath selector can find an element'); + this.test.assertDoesntExist({ + type: 'xpath', + path: '/html/body/ol/li[2]' + }, 'XPath selector does not retrieve an unexistent element'); + this.test.assertExists(x('/html/body/ul/li[2]'), 'selectXPath() shortcut can find an element as well'); + this.test.assertEvalEquals(function() { + return __utils__.findAll({type: 'xpath', path: '/html/body/ul/li'}).length; + }, 3, 'Correct number of elements are found'); +}); + +casper.thenClick(x('/html/body/a[2]'), function() { + this.test.assertTitle('CasperJS test form', 'Clicking XPath works as expected'); + this.fill(x('/html/body/form'), { + email: 'chuck@norris.com' + }); + this.test.assertEvalEquals(function() { + return document.querySelector('input[name="email"]').value; + }, 'chuck@norris.com', 'Casper.fill() can fill an input[type=text] form field'); +}); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/cli.js b/zephyr/tests/frontend/casperjs/tests/suites/cli.js new file mode 100644 index 0000000000..0a10073926 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/cli.js @@ -0,0 +1,127 @@ +/*global casper*/ +/*jshint strict:false*/ +var cli = require('cli'), t = casper.test; + +t.comment('parse(), get(), has()'); + +(function(parsed) { + // clean + t.assertEquals(parsed.args, [], 'parse() returns expected positional args array'); + t.assertEquals(parsed.options, {}, 'parse() returns expected options object'); + t.assertEquals(parsed.get(0), undefined, 'parse() does not return inexistant positional arg'); + t.assertEquals(parsed.get('blah'), undefined, 'parse() does not return inexistant option'); + t.assert(!parsed.has(0), 'has() checks if an arg is set'); + t.assert(!parsed.has('blah'), 'has() checks if an option is set'); + // raw + t.assertEquals(parsed.raw.args, [], 'parse() returns expected positional args array'); + t.assertEquals(parsed.raw.options, {}, 'parse() returns expected options object'); + t.assertEquals(parsed.raw.get(0), undefined, 'parse() does not return inexistant positional arg'); + t.assertEquals(parsed.raw.get('blah'), undefined, 'parse() does not return inexistant option'); + t.assert(!parsed.raw.has(0), 'has() checks if a raw arg is set'); + t.assert(!parsed.raw.has('blah'), 'has() checks if a raw option is set'); +})(cli.parse([])); + +(function(parsed) { + // clean + t.assertEquals(parsed.args, ['foo', 'bar'], 'parse() returns expected positional args array'); + t.assertEquals(parsed.options, {}, 'parse() returns expected options object'); + t.assertEquals(parsed.get(0), 'foo', 'parse() retrieve first positional arg'); + t.assertEquals(parsed.get(1), 'bar', 'parse() retrieve second positional arg'); + t.assert(parsed.has(0), 'has() checks if an arg is set'); + t.assert(parsed.has(1), 'has() checks if an arg is set'); + t.assert(!parsed.has(2), 'has() checks if an arg is not set'); + // raw + t.assertEquals(parsed.raw.args, ['foo', 'bar'], 'parse() returns expected positional raw args array'); + t.assertEquals(parsed.raw.options, {}, 'parse() returns expected raw options object'); + t.assertEquals(parsed.raw.get(0), 'foo', 'parse() retrieve first positional raw arg'); + t.assertEquals(parsed.raw.get(1), 'bar', 'parse() retrieve second positional raw arg'); + t.assert(parsed.raw.has(0), 'has() checks if a arw arg is set'); + t.assert(parsed.raw.has(1), 'has() checks if a arw arg is set'); + t.assert(!parsed.raw.has(2), 'has() checks if a arw arg is not set'); +})(cli.parse(['foo', 'bar'])); + +(function(parsed) { + // clean + t.assertEquals(parsed.args, [], 'parse() returns expected positional args array'); + t.assertEquals(parsed.options, {foo: 'bar', baz: true}, 'parse() returns expected options object'); + t.assertEquals(parsed.get('foo'), 'bar', 'parse() retrieve an option value'); + t.assert(parsed.get('baz'), 'parse() retrieve boolean option flag'); + t.assert(parsed.has("foo"), 'has() checks if an option is set'); + t.assert(parsed.has("baz"), 'has() checks if an option is set'); + // raw + t.assertEquals(parsed.raw.args, [], 'parse() returns expected positional raw args array'); + t.assertEquals(parsed.raw.options, {foo: 'bar', baz: true}, 'parse() returns expected options raw object'); + t.assertEquals(parsed.raw.get('foo'), 'bar', 'parse() retrieve an option raw value'); + t.assert(parsed.raw.get('baz'), 'parse() retrieve boolean raw option flag'); + t.assert(parsed.raw.has("foo"), 'has() checks if a raw option is set'); + t.assert(parsed.raw.has("baz"), 'has() checks if a raw option is set'); +})(cli.parse(['--foo=bar', '--baz'])); + +(function(parsed) { + // clean + t.assertEquals(parsed.args, [], 'parse() returns expected positional args array'); + t.assertEquals(parsed.options, { '&é"à': "42===42" }, 'parse() returns expected options object'); + t.assertEquals(parsed.get('&é"à'), "42===42", 'parse() handles options with exotic names'); + t.assert(parsed.has('&é"à'), 'has() checks if an option is set'); + // raw + t.assertEquals(parsed.raw.args, [], 'parse() returns expected positional raw args array'); + t.assertEquals(parsed.raw.options, { '&é"à': "42===42" }, 'parse() returns expected options raw object'); + t.assertEquals(parsed.raw.get('&é"à'), "42===42", 'parse() handles raw options with exotic names'); + t.assert(parsed.raw.has('&é"à'), 'has() checks if a raw option is set'); +})(cli.parse(['--&é"à=42===42'])); + +(function(parsed) { + // clean + t.assertEquals(parsed.args, ['foo & bar', 'baz & boz'], 'parse() returns expected positional args array'); + t.assertEquals(parsed.options, { universe: 42, lap: 13.37, chucknorris: true, oops: false }, 'parse() returns expected options object'); + t.assertEquals(parsed.get('universe'), 42, 'parse() can cast a numeric option value'); + t.assertEquals(parsed.get('lap'), 13.37, 'parse() can cast a float option value'); + t.assertType(parsed.get('lap'), "number", 'parse() can cast a boolean value'); + t.assert(parsed.get('chucknorris'), 'parse() can get a flag value by its option name'); + t.assertType(parsed.get('oops'), "boolean", 'parse() can cast a boolean value'); + t.assertEquals(parsed.get('oops'), false, 'parse() can cast a boolean value'); + t.assert(parsed.has(0), 'has() checks if an arg is set'); + t.assert(parsed.has(1), 'has() checks if an arg is set'); + t.assert(parsed.has("universe"), 'has() checks if an option is set'); + t.assert(parsed.has("lap"), 'has() checks if an option is set'); + t.assert(parsed.has("chucknorris"), 'has() checks if an option is set'); + t.assert(parsed.has("oops"), 'has() checks if an option is set'); + + t.comment('drop()'); + + parsed.drop(0); + t.assertEquals(parsed.get(0), 'baz & boz', 'drop() dropped arg'); + parsed.drop("universe"); + t.assert(!parsed.has("universe"), 'drop() dropped option'); + t.assertEquals(parsed.args, ["baz & boz"], 'drop() did not affect other args'); + t.assertEquals(parsed.options, { + lap: 13.37, + chucknorris: true, + oops: false + }, 'drop() did not affect other options'); + + // raw + t.assertEquals(parsed.raw.args, ['foo & bar', 'baz & boz'], 'parse() returns expected positional raw args array'); + t.assertEquals(parsed.raw.options, { universe: "42", lap: "13.37", chucknorris: true, oops: "false" }, 'parse() returns expected options raw object'); + t.assertEquals(parsed.raw.get('universe'), "42", 'parse() does not a raw numeric option value'); + t.assertEquals(parsed.raw.get('lap'), "13.37", 'parse() does not cast a raw float option value'); + t.assertType(parsed.raw.get('lap'), "string", 'parse() does not cast a numeric value'); + t.assert(parsed.raw.get('chucknorris'), 'parse() can get a flag value by its option name'); + t.assertType(parsed.raw.get('oops'), "string", 'parse() can cast a boolean value'); + t.assertEquals(parsed.raw.get('oops'), "false", 'parse() can cast a boolean value'); + + t.comment('drop() for raw'); + + parsed.raw.drop(0); + t.assertEquals(parsed.raw.get(0), 'baz & boz', 'drop() dropped raw arg'); + parsed.raw.drop("universe"); + t.assert(!parsed.raw.has("universe"), 'drop() dropped raw option'); + t.assertEquals(parsed.raw.args, ["baz & boz"], 'drop() did not affect other raw args'); + t.assertEquals(parsed.raw.options, { + lap: "13.37", + chucknorris: true, + oops: "false" + }, 'drop() did not affect other raw options'); +})(cli.parse(['foo & bar', 'baz & boz', '--universe=42', '--lap=13.37', '--chucknorris', '--oops=false'])); + +t.done(); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/clientutils.js b/zephyr/tests/frontend/casperjs/tests/suites/clientutils.js new file mode 100644 index 0000000000..cce9227e6f --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/clientutils.js @@ -0,0 +1,113 @@ +/*global casper*/ +/*jshint strict:false*/ +var fs = require('fs'); +var x = require('casper').selectXPath; + +function fakeDocument(html) { + window.document.body.innerHTML = html; +} + +(function(casper) { + casper.test.comment('ClientUtils.encode()'); + var clientutils = require('clientutils').create(); + var testCases = { + 'an empty string': '', + 'a word': 'plop', + 'a null char': 'a\u0000', + 'an utf8 string': 'ÀÁÃÄÅÇÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðòóôõöùúûüýÿ', + 'song lyrics': ("Voilà l'été, j'aperçois le soleil\n" + + "Les nuages filent et le ciel s'éclaircit\n" + + "Et dans ma tête qui bourdonnent?\n" + + "Les abeilles!"), + 'a file contents': fs.read(phantom.casperPath + '/tests/site/alert.html') + }; + for (var what in testCases) { + var source = testCases[what]; + var encoded = clientutils.encode(source); + casper.test.assertEquals(clientutils.decode(encoded), source, 'ClientUtils.encode() encodes and decodes ' + what); + } +})(casper); + +(function(casper) { + casper.test.comment('ClientUtils.exists()'); + var clientutils = require('clientutils').create(); + fakeDocument(''); + casper.test.assert(clientutils.exists('ul'), 'ClientUtils.exists() checks that an element exist'); + casper.test.assertNot(clientutils.exists('ol'), 'ClientUtils.exists() checks that an element exist'); + casper.test.assert(clientutils.exists('ul.foo li'), 'ClientUtils.exists() checks that an element exist'); + // xpath + casper.test.assert(clientutils.exists(x('//ul')), 'ClientUtils.exists() checks that an element exist using XPath'); + casper.test.assertNot(clientutils.exists(x('//ol')), 'ClientUtils.exists() checks that an element exist using XPath'); + fakeDocument(null); +})(casper); + +(function(casper) { + casper.test.comment('ClientUtils.findAll()'); + var clientutils = require('clientutils').create(); + fakeDocument(''); + casper.test.assertType(clientutils.findAll('li'), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements'); + casper.test.assertEquals(clientutils.findAll('li').length, 2, 'ClientUtils.findAll() can find matching DOM elements'); + casper.test.assertType(clientutils.findAll('ol'), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements'); + casper.test.assertEquals(clientutils.findAll('ol').length, 0, 'ClientUtils.findAll() can find matching DOM elements'); + // scoped + var scope = clientutils.findOne('ul'); + casper.test.assertType(clientutils.findAll('li', scope), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements within a given scope'); + fakeDocument(null); +})(casper); + +(function(casper) { + casper.test.comment('ClientUtils.findOne()'); + var clientutils = require('clientutils').create(); + fakeDocument(''); + casper.test.assertType(clientutils.findOne('ul'), 'htmlulistelement', 'ClientUtils.findOne() can find a matching DOM element'); + casper.test.assertNot(clientutils.findOne('ol'), 'ClientUtils.findOne() can find a matching DOM element'); + // scoped + var scope = clientutils.findOne('ul'); + casper.test.assertType(clientutils.findAll('li', scope), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements within a given scope'); + casper.test.assertEquals(clientutils.findAll('li', scope).length, 2, 'ClientUtils.findAll() can find matching DOM elements within a given scope'); + fakeDocument(null); +})(casper); + +(function(casper) { + casper.test.comment('ClientUtils.processSelector()'); + var clientutils = require('clientutils').create(); + // CSS3 selector + var cssSelector = clientutils.processSelector('html body > ul.foo li'); + casper.test.assertType(cssSelector, 'object', 'ClientUtils.processSelector() can process a CSS3 selector'); + casper.test.assertEquals(cssSelector.type, 'css', 'ClientUtils.processSelector() can process a CSS3 selector'); + casper.test.assertEquals(cssSelector.path, 'html body > ul.foo li', 'ClientUtils.processSelector() can process a CSS3 selector'); + // XPath selector + var xpathSelector = clientutils.processSelector(x('//li[text()="blah"]')); + casper.test.assertType(xpathSelector, 'object', 'ClientUtils.processSelector() can process a XPath selector'); + casper.test.assertEquals(xpathSelector.type, 'xpath', 'ClientUtils.processSelector() can process a XPath selector'); + casper.test.assertEquals(xpathSelector.path, '//li[text()="blah"]', 'ClientUtils.processSelector() can process a XPath selector'); +})(casper); + +(function(casper) { + casper.start(); + // getElementBounds + casper.then(function() { + this.page.content = '
    '; + this.test.assertEquals(this.getElementBounds('#b1'), + { top: 10, left: 11, width: 50, height: 60 }, + 'ClientUtils.getElementBounds() retrieves element boundaries'); + }); + // getElementsBounds + casper.start(); + casper.then(function() { + var html = '
    '; + html += '
    '; + html += '
    '; + html += '
    '; + this.page.content = html; + var bounds = this.getElementsBounds('#boxes div'); + this.test.assertEquals(bounds[0], { top: 10, left: 11, width: 50, height: 60 }, + 'ClientUtils.getElementsBounds() retrieves multiple elements boundaries'); + this.test.assertEquals(bounds[1], { top: 20, left: 21, width: 70, height: 80 }, + 'ClientUtils.getElementsBounds() retrieves multiple elements boundaries'); + }); +})(casper); + +casper.run(function() { + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/coffee.coffee b/zephyr/tests/frontend/casperjs/tests/suites/coffee.coffee new file mode 100644 index 0000000000..fb03260acd --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/coffee.coffee @@ -0,0 +1,19 @@ +"A small subset of the run.js written in coffeescript" + +steps = 0 + +casper.options.onStepComplete = -> steps++ + +casper.start "tests/site/index.html", -> + @test.assertTitle "CasperJS test index", "Casper.start() casper can start itself an open an url" + @test.assertEquals @fetchText("ul li"), "onetwothree", "Casper.fetchText() can retrieves text contents" + @click "a[href=\"test.html\"]" + +casper.then -> + @test.assertTitle "CasperJS test target", "Casper.click() casper can click on a text link" + @click "a[href=\"form.html\"]" + +casper.run -> + @test.assertEquals steps, 3, "Casper.options.onStepComplete() is called on step complete" + @options.onStepComplete = null + @test.done() diff --git a/zephyr/tests/frontend/casperjs/tests/suites/fs.js b/zephyr/tests/frontend/casperjs/tests/suites/fs.js new file mode 100644 index 0000000000..3086a9a9bb --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/fs.js @@ -0,0 +1,38 @@ +/*global casper*/ +/*jshint strict:false*/ +var fs = require('fs'), t = casper.test; + +// Testing added methods +(function() { + t.comment('fs.dirname()'); + var tests = { + '/local/plop/foo.js': '/local/plop', + 'local/plop/foo.js': 'local/plop', + './local/plop/foo.js': './local/plop', + 'c:\\local\\plop\\foo.js': 'c:/local/plop', + 'D:\\local\\plop\\foo.js': 'D:/local/plop', + 'D:\\local\\plop\\': 'D:/local/plop', + 'c:\\': 'c:', + 'c:': 'c:' + }; + for (var testCase in tests) { + t.assertEquals(fs.dirname(testCase), tests[testCase], 'fs.dirname() does its job for ' + testCase); + } +})(); + +(function() { + t.comment('fs.dirname()'); + var tests = { + '/': false, + '/local/plop/foo.js': false, + 'D:\\local\\plop\\': true, + 'c:\\': true, + 'c:': true, + '\\\\Server\\Plop': true + }; + for (var testCase in tests) { + t.assertEquals(fs.isWindows(testCase), tests[testCase], 'fs.isWindows() does its job for ' + testCase); + } +})(); + +t.done(); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/http_status.js b/zephyr/tests/frontend/casperjs/tests/suites/http_status.js new file mode 100644 index 0000000000..a45b05b775 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/http_status.js @@ -0,0 +1,44 @@ +/*global casper*/ +/*jshint strict:false*/ +/** + * Special test server to test for HTTP status codes + * + */ +var server = require('webserver').create(); +var service = server.listen(8090, function (request, response) { + var code = parseInt(/^\/(\d+)$/.exec(request.url)[1], 10); + response.statusCode = code; + response.write(""); + response.close(); +}); +var fs = require("fs"); + +casper.start(); + +// file protocol +casper.thenOpen('file://' + phantom.casperPath + '/tests/site/index.html', function() { + this.test.assertHttpStatus(null, 'file:// protocol does not set a HTTP status'); +}); + +// http protocol +var codes = [100, 101, 102, 118, 200, 201, 202, 203, 204, 205, 206, 207, 210, + 300, 301, 302, 303, 304, 305, 307, 310, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, + 414, 415, 416, 417, 418, 422, 423, 424, 425, 426, 449, 450, + 500, 501, 502, 503, 504, 505, 507, 509]; + +casper.thenOpen('http://google.com').each(codes, function(self, code) { + if (code === 100) { + // HTTP 100 is CONTINUE, so don't expect a terminated response + return; + } + this.thenOpen('http://localhost:8090/' + code, function() { + this.test.assertEquals(this.currentHTTPStatus, code); + this.test.assertHttpStatus(code); + }); +}); + +casper.run(function() { + server.close(); + this.test.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/injector.js b/zephyr/tests/frontend/casperjs/tests/suites/injector.js new file mode 100644 index 0000000000..246bd358da --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/injector.js @@ -0,0 +1,66 @@ +/*global casper*/ +/*jshint strict:false*/ +var t = casper.test; +var createInjector = function(fn, values) { + return require('injector').create(fn, values); +}; +var testFn = function(a, b) { return a + b; }; +var injector = createInjector(testFn); +var extract = injector.extract(testFn); + +t.comment('FunctionArgsInjector.extract()'); +t.assertType(extract, "object", 'FunctionArgsInjector.extract() returns an object'); +t.assertEquals(extract.name, null, 'FunctionArgsInjector.extract() process function name as expected'); +t.assertEquals(extract.body, 'return a + b;', 'FunctionArgsInjector.extract() process function body as expected'); +t.assertEquals(extract.args, ['a', 'b'], 'FunctionArgsInjector.extract() process function args as expected'); + +function Plop(foo, bar) { + return 'foo: ' + foo +', bar: ' + bar; +} +function Plip() { return 'plop'; } +function foo_bar(boz) {} +var gni = function ($bubu_bibi, __popo__) {}; +var gno = function ( arg1, /*plop*/ arg2 ) { }; +function issue129(term) { + // see issue #129 + return term; + // see issue #129 +} +t.assertEquals(injector.extract(Plop), { + name: 'Plop', + args: ['foo', 'bar'], + body: "return 'foo: ' + foo +', bar: ' + bar;" +}, 'FunctionArgsInjector.extract() handles named functions with arguments and body'); +t.assertEquals(injector.extract(Plip), { + name: 'Plip', + args: [], + body: "return 'plop';" +}, 'FunctionArgsInjector.extract() handles functions with no arguments'); +t.assertEquals(injector.extract(foo_bar), { + name: 'foo_bar', + args: ['boz'], + body: "" +}, 'FunctionArgsInjector.extract() handles functions with no body'); +t.assertEquals(injector.extract(gni), { + name: null, + args: ['$bubu_bibi', '__popo__'], + body: "" +}, 'FunctionArgsInjector.extract() handles anonymous functions with complex args passed'); +t.assertEquals(injector.extract(gno), { + name: null, + args: ['arg1', 'arg2'], + body: "" +}, 'FunctionArgsInjector.extract() handles can filter comments in function args'); + +t.comment('FunctionArgsInjector.process()'); +var processed; +eval('processed = ' + injector.process({ a: 1, b: 2 })); + +t.assertType(processed, "function", 'FunctionArgsInjector.process() processed a function'); +t.assertEquals(processed(), 3, 'FunctionArgsInjector.process() processed the function correctly'); + +// Issue #129 +var fnIssue129 = createInjector(issue129).process({term: 'fixed'}); +t.assertEquals(fnIssue129('fixed'), 'fixed', 'FunctionArgsInjector.process() has issue #129 fixed'); + +t.done(); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/require.js b/zephyr/tests/frontend/casperjs/tests/suites/require.js new file mode 100644 index 0000000000..bc0db97077 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/require.js @@ -0,0 +1,23 @@ +/*global casper*/ +/*jshint strict:false*/ +var fs = require('fs'); +var modroot = fs.pathJoin(phantom.casperPath, 'tests', 'sample_modules'); +var jsmod, csmod; + +casper.test.comment('Javascript module loading') +try { + jsmod = require(fs.pathJoin(modroot, 'jsmodule')); + casper.test.assertTrue(jsmod.ok, 'require() patched version can load a js module'); +} catch (e) { + casper.test.fail('require() patched version can load a js module'); +} + +casper.test.comment('CoffeeScript module loading') +try { + csmod = require(fs.pathJoin(modroot, 'csmodule')); + casper.test.assertTrue(csmod.ok, 'require() patched version can load a coffeescript module'); +} catch (e) { + casper.test.fail('require() patched version can load a coffeescript module'); +} + +casper.test.done(); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/tester.js b/zephyr/tests/frontend/casperjs/tests/suites/tester.js new file mode 100644 index 0000000000..2c7881c953 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/tester.js @@ -0,0 +1,152 @@ +/*global casper*/ +/*jshint strict:false maxstatements:99*/ +var fs = require('fs'); +var t = casper.test; + +casper.start(); + +t.comment('Tester.sortFiles()'); +var testDirRoot = fs.pathJoin(phantom.casperPath, 'tests', 'testdir'); +var files = t.findTestFiles(testDirRoot); +var expected = [ + "01_a/abc.js", + "01_a/def.js", + "02_b/abc.js", + "03_a.js", + "03_b.js", + "04/01_init.js", + "04/02_do.js" +].map(function(entry) { + return fs.pathJoin.apply(fs, [testDirRoot].concat(entry.split('/'))); +}); +t.assertEquals(files, expected, 'findTestFiles() find test files and sort them'); + +casper.thenOpen('tests/site/index.html', function() { + t.comment('Tester.assertTextExists()'); + t.assertTextExists('form', 'Tester.assertTextExists() checks that page body contains text'); + + t.comment('Tester.assertSelectorHasText()'); + t.assertSelectorHasText('h1', 'Title', 'Tester.assertSelectorHasText() works as expected'); + + t.comment('Tester.assertSelectorDoesntHaveText()'); + t.assertSelectorDoesntHaveText('h1', 'Subtitle', 'Tester.assertSelectorDoesntHaveText() works as expected'); +}); + +casper.then(function() { + t.comment('Tester.assert()'); + t.assert(true, 'Tester.assert() works as expected'); + + t.comment('Tester.assertNot()'); + t.assertNot(false, 'Tester.assertNot() works as expected'); + + t.comment('Tester.assertEquals()'); + t.assertEquals(true, true, 'Tester.assertEquals() works as expected'); + + t.comment('Tester.assertNotEquals()'); + t.assertNotEquals(true, false, 'Tester.assertNotEquals() works as expected'); + + t.comment('Tester.assertEval()'); + t.assertEval(function() { + return true; + }, 'Tester.assertEval() works as expected'); + + t.comment('Tester.assertEvalEquals()'); + t.assertEvalEquals(function() { + return 42; + }, 42, 'Tester.assertEvalEquals() works as expected'); + + t.comment('Tester.assertExists()'); + t.assertExists('body', 'Tester.assertExists() works as expected'); + + t.comment('Tester.assertDoesntExist()'); + t.assertDoesntExist('foobar', 'Tester.assertDoesntExist() works as expected'); + + t.comment('Tester.assertHttpStatus()'); + // using file:// protocol, HTTP status is always null + t.assertHttpStatus(200, 'Tester.assertHttpStatus() works as expected'); + + t.comment('Tester.assertMatch()'); + t.assertMatch("the lazy dog", /lazy/, 'Tester.assertMatch() works as expected'); + + t.comment('Tester.assertRaises()'); + t.assertRaises(function() { + throw new Error('plop'); + }, [], 'Tester.assertRaises() works as expected'); + + t.comment('Tester.assertResourceExists()'); + t.assertResourceExists(/index\.html/, 'Tester.assertResourceExists() works as expected'); + + t.comment('Tester.assertTitle()'); + t.assertTitle('CasperJS test index', 'Tester.assertTitle() works as expected'); + + t.comment('Tester.assertTitleMatch()'); + t.assertTitleMatch(/test index/, 'Tester.assertTitleMatch() works as expected'); + + t.comment('Tester.assertType()'); + t.assertType("plop", "string", "Tester.assertType() works as expected"); + + t.comment('Tester.assertUrlMatch()'); + t.assertUrlMatch(/index\.html$/, "Tester.assertUrlMatch() works as expected"); + + t.comment('Tester.assertVisible()'); + t.assertVisible('img', 'Tester.assertVisible() works as expected'); + + t.comment('Tester.assertNotVisible()'); + t.assertNotVisible('p#hidden', 'Tester.assertNotVisible() works as expected'); +}); + +casper.thenOpen('tests/site/form.html', function() { + t.comment('Tester.assertField()'); + t.comment('1. Fill inputs'); + var fpath = phantom.libraryPath + '/README.md'; + this.fill('form[action="result.html"]', { + 'email': 'chuck@norris.com', + 'content': 'Am watching thou', + 'check': true, + 'choice': 'no', + 'topic': 'bar', + 'file': fpath, + 'checklist[]': ['1', '3'] + }); + t.assertField('email', 'chuck@norris.com', 'Tester.assertField() works as expected with inputs'); + t.assertField('content', 'Am watching thou', 'Tester.assertField() works as expected with textarea'); + t.assertField('check', true, 'Tester.assertField() works as expected with checkboxes'); + t.assertField('choice', 'no', 'Tester.assertField() works as expected with radios'); + t.assertField('topic', 'bar', 'Tester.assertField() works as expected with selects'); + t.assertField('file', 'C:\\fakepath\\README.md', 'Tester.assertField() works as expected with file inputs'); + t.assertField('checklist[]', ['1', '3'], 'Tester.assertField() works as expected with check lists'); +}); + +casper.reload(function() { + t.comment('2. Unfill inputs'); + this.fill('form[action="result.html"]', { + 'email': '', + 'content': '', + 'check': false, + 'choice': '', + 'topic': '', + 'file': '', + 'checklist[]': [] + }); + t.assertField('email', '', 'Tester.assertField() works as expected with inputs'); + t.assertField('content', '', 'Tester.assertField() works as expected with textarea'); + t.assertField('check', false, 'Tester.assertField() works as expected with checkboxes'); + t.assertField('choice', null, 'Tester.assertField() works as expected with radios'); + t.assertField('topic', 'foo', 'Tester.assertField() works as expected with selects'); + t.assertField('file', '', 'Tester.assertField() works as expected with file inputs'); + t.assertField('checklist[]', [], 'Tester.assertField() works as expected with check lists'); +}); + +casper.then(function() { + t.comment('Tester.getFailures()'); + t.assertEquals(typeof t.getFailures().length, "number", "Tester.getFailures() works as expected"); + + var passCount = t.getPasses().length; + t.comment('Tester.getPasses()'); + t.assertEquals(1, 1, "Rogue assertEquals pass case"); + t.assertEquals(t.getPasses().length, passCount + 1, "Tester.getPasses() works as expected"); +}); + +casper.run(function() { + t.done(); +}); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/utils.js b/zephyr/tests/frontend/casperjs/tests/suites/utils.js new file mode 100644 index 0000000000..d6fbdae1fc --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/utils.js @@ -0,0 +1,275 @@ +/*global casper*/ +/*jshint strict:false maxstatements:99*/ +var utils = require('utils'), + t = casper.test, + x = require('casper').selectXPath; + +t.comment('cleanUrl()'); +(function() { + var testCases = { + 'http://google.com/': 'http://google.com/', + 'http://google.com': 'http://google.com/', + 'http://www.google.com/': 'http://www.google.com/', + 'http://www.google.com/?plop=2': 'http://www.google.com/?plop=2', + 'https://google.com/': 'https://google.com/', + 'https://google.com': 'https://google.com/', + 'https://www.google.com/': 'https://www.google.com/', + 'https://www.google.com/?plop=2': 'https://www.google.com/?plop=2', + 'file:///Users/toto/toto.html': 'file:///Users/toto/toto.html', + '/100': '/100' + }; + for (var testCase in testCases) { + t.assertEquals(utils.cleanUrl(testCase), testCases[testCase], 'cleanUrl() cleans an URL'); + } +})(); + +t.comment('equals()'); +(function() { + t.assert(utils.equals(null, null), 'equals() null equality'); + t.assertNot(utils.equals(null, undefined), 'equals() null vs. undefined inequality'); + t.assert(utils.equals("hi", "hi"), 'equals() string equality'); + t.assertNot(utils.equals("hi", "ih"), 'equals() string inequality'); + t.assert(utils.equals(5, 5), 'equals() number equality'); + t.assertNot(utils.equals("5", 5), 'equals() number equality without implicit cast'); + t.assert(utils.equals(5, 5.0), 'equals() number equality with cast'); + t.assertNot(utils.equals(5, 10), 'equals() number inequality'); + t.assert(utils.equals([], []), 'equals() empty array equality'); + t.assert(utils.equals([1,2], [1,2]), 'equals() array equality'); + t.assert(utils.equals([1,2,[1,2,function(){}]], [1,2,[1,2,function(){}]]), 'equals() complex array equality'); + t.assertNot(utils.equals([1,2,[1,2,function(a){}]], [1,2,[1,2,function(b){}]]), 'equals() complex array inequality'); + t.assertNot(utils.equals([1,2], [2,1]), 'equals() shuffled array inequality'); + t.assertNot(utils.equals([1,2], [1,2,3]), 'equals() array length inequality'); + t.assert(utils.equals({}, {}), 'equals() empty object equality'); + t.assert(utils.equals({a:1,b:2}, {a:1,b:2}), 'equals() object length equality'); + t.assert(utils.equals({a:1,b:2}, {b:2,a:1}), 'equals() shuffled object keys equality'); + t.assertNot(utils.equals({a:1,b:2}, {a:1,b:3}), 'equals() object inequality'); + t.assert(utils.equals({1:{name:"bob",age:28}, 2:{name:"john",age:26}}, {1:{name:"bob",age:28}, 2:{name:"john",age:26}}), 'equals() complex object equality'); + t.assertNot(utils.equals({1:{name:"bob",age:28}, 2:{name:"john",age:26}}, {1:{name:"bob",age:28}, 2:{name:"john",age:27}}), 'equals() complex object inequality'); + t.assert(utils.equals(function(x){return x;}, function(x){return x;}), 'equals() function equality'); + t.assertNot(utils.equals(function(x){return x;}, function(y){return y+2;}), 'equals() function inequality'); + t.assert(utils.equals([{a:1, b:2}, {c:3, d:4}], [{a:1, b:2}, {c:3, d:4}]), 'equals() arrays of objects'); +})(); + +t.comment('fileExt()'); +(function() { + var testCases = { + 'foo.ext': 'ext', + 'FOO.EXT': 'ext', + 'a.ext': 'ext', + '.ext': 'ext', + 'toto.': '', + ' plop.ext ': 'ext' + }; + + for (var testCase in testCases) { + t.assertEquals(utils.fileExt(testCase), testCases[testCase], 'fileExt() extract file extension'); + } +})(); + +t.comment('fillBlanks()'); +(function() { + var testCases = { + 'foo': 'foo ', + ' foo bar ': ' foo bar ', + ' foo bar ': ' foo bar ' + }; + + for (var testCase in testCases) { + t.assertEquals(utils.fillBlanks(testCase, 10), testCases[testCase], 'fillBlanks() fills blanks'); + } +})(); + +t.comment('getPropertyPath()'); +(function() { + var testCases = [ + { + input: utils.getPropertyPath({}, 'a.b.c'), + output: undefined + }, + { + input: utils.getPropertyPath([1, 2, 3], 'a.b.c'), + output: undefined + }, + { + input: utils.getPropertyPath({ a: { b: { c: 1 } }, c: 2 }, 'a.b.c'), + output: 1 + }, + { + input: utils.getPropertyPath({ a: { b: { c: 1 } }, c: 2 }, 'a.b.x'), + output: undefined + }, + { + input: utils.getPropertyPath({ a: { b: { c: 1 } }, c: 2 }, 'a.b'), + output: { c: 1 } + }, + { + input: utils.getPropertyPath({ 'a-b': { 'c-d': 1} }, 'a-b.c-d'), + output: 1 + }, + { + input: utils.getPropertyPath({ 'a.b': { 'c.d': 1} }, 'a.b.c.d'), + output: undefined + } + ]; + testCases.forEach(function(testCase) { + t.assertEquals(testCase.input, testCase.output, 'getPropertyPath() gets a property using a path'); + }); +})(); + +t.comment('isArray()'); +(function() { + t.assertEquals(utils.isArray([]), true, 'isArray() checks for an Array'); + t.assertEquals(utils.isArray({}), false, 'isArray() checks for an Array'); + t.assertEquals(utils.isArray("foo"), false, 'isArray() checks for an Array'); +})(); + +t.comment('isClipRect()'); +(function() { + var testCases = [ + [{}, false], + [{top: 2}, false], + [{top: 2, left: 2, width: 2, height: 2}, true], + [{top: 2, left: 2, height: 2, width: 2}, true], + [{top: 2, left: 2, width: 2, height: new Date()}, false] + ]; + + testCases.forEach(function(testCase) { + t.assertEquals(utils.isClipRect(testCase[0]), testCase[1], 'isClipRect() checks for a ClipRect'); + }); +})(); + +t.comment('isHTTPResource()'); +(function() { + var testCases = [ + [{}, false], + [{url: 'file:///var/www/i.html'}, false], + [{url: 'mailto:plop@plop.com'}, false], + [{url: 'ftp://ftp.plop.com'}, false], + [{url: 'HTTP://plop.com/'}, true], + [{url: 'https://plop.com/'}, true] + ]; + + testCases.forEach(function(testCase) { + t.assertEquals(utils.isHTTPResource(testCase[0]), testCase[1], 'isHTTPResource() checks for an HTTP resource'); + }); +})(); + +t.comment('isObject()'); +(function() { + t.assertEquals(utils.isObject({}), true, 'isObject() checks for an Object'); + t.assertEquals(utils.isObject([]), true, 'isObject() checks for an Object'); + t.assertEquals(utils.isObject(1), false, 'isObject() checks for an Object'); + t.assertEquals(utils.isObject("1"), false, 'isObject() checks for an Object'); + t.assertEquals(utils.isObject(function(){}), false, 'isObject() checks for an Object'); + t.assertEquals(utils.isObject(new Function('return {};')()), true, 'isObject() checks for an Object'); + t.assertEquals(utils.isObject(require('webpage').create()), true, 'isObject() checks for an Object'); + t.assertEquals(utils.isObject(null), false, 'isObject() checks for an Object'); +})(); + +t.comment('isValidSelector()'); +(function() { + t.assertEquals(utils.isValidSelector({}), false, 'isValidSelector() checks for a valid selector'); + t.assertEquals(utils.isValidSelector(""), false, 'isValidSelector() checks for a valid selector'); + t.assertEquals(utils.isValidSelector("a"), true, 'isValidSelector() checks for a valid selector'); + t.assertEquals(utils.isValidSelector('div#plop form[name="form"] input[type="submit"]'), true, 'isValidSelector() checks for a valid selector'); + t.assertEquals(utils.isValidSelector(x('//a')), true, 'isValidSelector() checks for a valid selector'); + t.assertEquals(utils.isValidSelector({ + type: "css", + path: 'div#plop form[name="form"] input[type="submit"]' + }), true, 'isValidSelector() checks for a valid selector'); + t.assertEquals(utils.isValidSelector({ + type: "xpath", + path: '//a' + }), true, 'isValidSelector() checks for a valid selector'); + t.assertEquals(utils.isValidSelector({ + type: "css" + }), false, 'isValidSelector() checks for a valid selector'); + t.assertEquals(utils.isValidSelector({ + type: "xpath" + }), false, 'isValidSelector() checks for a valid selector'); + t.assertEquals(utils.isValidSelector({ + type: "css3", + path: "a" + }), false, 'isValidSelector() checks for a valid selector'); +})(); + +t.comment('isWebPage()'); +(function() { + var pageModule = require('webpage'); + t.assertEquals(utils.isWebPage(pageModule), false, 'isWebPage() checks for a WebPage instance'); + t.assertEquals(utils.isWebPage(pageModule.create()), true, 'isWebPage() checks for a WebPage instance'); + t.assertEquals(utils.isWebPage(null), false, 'isWebPage() checks for a WebPage instance'); +})(); + +t.comment('isJsFile()'); +(function() { + var testCases = { + '': false, + 'toto.png': false, + 'plop': false, + 'gniii.coffee': true, + 'script.js': true + }; + + for (var testCase in testCases) { + t.assertEquals(utils.isJsFile(testCase), testCases[testCase], 'isJsFile() checks for js file'); + } +})(); + +t.comment('mergeObjects()'); +(function() { + var testCases = [ + { + obj1: {a: 1}, obj2: {b: 2}, merged: {a: 1, b: 2} + }, + { + obj1: {}, obj2: {a: 1}, merged: {a: 1} + }, + { + obj1: {a: 1}, obj2: {}, merged: {a: 1} + }, + { + obj1: {a: 1}, obj2: {a: 2}, merged: {a: 2} + }, + { + obj1: {x: 0, double: function(){return this.x*2;}}, + obj2: {triple: function(){return this.x*3;}}, + merged: { + x: 0, + double: function(){return this.x*2;}, + triple: function(){return this.x*3;} + } + } + ]; + + testCases.forEach(function(testCase) { + t.assertEquals(utils.mergeObjects(testCase.obj1, testCase.obj2), testCase.merged, 'mergeObjects() can merge objects'); + }); +})(); + +t.comment('unique()'); +(function() { + var testCases = [ + { + input: [1,2,3], + output: [1,2,3] + }, + { + input: [1,2,3,2,1], + output: [1,2,3] + }, + { + input: ["foo", "bar", "foo"], + output: ["foo", "bar"] + }, + { + input: [], + output: [] + } + ]; + testCases.forEach(function(testCase) { + t.assertEquals(utils.unique(testCase.input), testCase.output, 'unique() computes unique values of an array'); + }); +})(); + +t.done(); diff --git a/zephyr/tests/frontend/casperjs/tests/suites/xunit.js b/zephyr/tests/frontend/casperjs/tests/suites/xunit.js new file mode 100644 index 0000000000..fd8240828a --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/suites/xunit.js @@ -0,0 +1,18 @@ +/*global casper*/ +/*jshint strict:false*/ +casper.test.comment('phantom.Casper.XUnitExporter'); + +var xunit = require('xunit').create(); +xunit.addSuccess('foo', 'bar'); +casper.test.assertMatch(xunit.getXML(), /wrong/, 'XUnitExporter.addFailure() adds a failed testcase'); + +// named classname +xunit = require('xunit').create(); +xunit.addSuccess(require('fs').workingDirectory + '/plop.js', 'It worked'); +casper.test.assertMatch(xunit.getXML(), /