├── .gitignore ├── .gitmodules ├── CHANGELOG ├── LICENSE ├── README.markdown ├── Rakefile ├── dist └── .gitignore ├── doc_assets └── images │ ├── header-logo-small.png │ └── header-stripe-small.png ├── ext └── update_helper │ ├── prototype_update_helper.html │ ├── prototype_update_helper.js │ └── update_helper.js ├── src ├── constants.yml ├── prototype.js └── prototype │ ├── ajax.js │ ├── ajax │ ├── ajax.js │ ├── base.js │ ├── periodical_updater.js │ ├── request.js │ ├── responders.js │ ├── response.js │ └── updater.js │ ├── deprecated.js │ ├── dom.js │ ├── dom │ ├── dom.js │ ├── event.js │ ├── form.js │ ├── layout.js │ └── selector.js │ ├── lang.js │ ├── lang │ ├── array.js │ ├── class.js │ ├── enumerable.js │ ├── function.js │ ├── hash.js │ ├── number.js │ ├── object.js │ ├── periodical_executer.js │ ├── range.js │ ├── regexp.js │ ├── string.js │ └── template.js │ └── prototype.js ├── test ├── browser.html ├── console.html ├── functional │ └── event.html └── unit │ ├── browsers.sample.yml │ ├── config.ru │ ├── fixtures │ ├── content.html │ ├── data.json │ ├── empty.html │ ├── hello.js │ ├── iframe.html │ └── logo.gif │ ├── phantomjs │ ├── core-extensions.js │ └── mocha-phantomjs.js │ ├── runner.rb │ ├── server.rb │ ├── static │ ├── css │ │ └── mocha.css │ └── js │ │ ├── assertions.js │ │ ├── mocha.js │ │ ├── proclaim.js │ │ └── test_helpers.js │ ├── tests │ ├── ajax.test.js │ ├── array.test.js │ ├── base.test.js │ ├── class.test.js │ ├── date.test.js │ ├── dom.test.js │ ├── element_mixins.test.js │ ├── enumerable.test.js │ ├── event.test.js │ ├── event_handler.test.js │ ├── form.test.js │ ├── function.test.js │ ├── hash.test.js │ ├── layout.test.js │ ├── number.test.js │ ├── object.test.js │ ├── periodical_executer.test.js │ ├── position.test.js │ ├── prototype.test.js │ ├── range.test.js │ ├── regexp.test.js │ ├── selector.test.js │ ├── selector_engine.test.js │ └── string.test.js │ └── views │ ├── layout.erb │ ├── tests.erb │ └── tests │ ├── ajax.erb │ ├── array.erb │ ├── dom.erb │ ├── element_mixins.erb │ ├── enumerable.erb │ ├── event.erb │ ├── event_handler.erb │ ├── form.erb │ ├── layout.erb │ ├── object.erb │ ├── position.erb │ ├── selector.erb │ └── selector_engine.erb └── vendor ├── legacy_selector ├── repository │ └── legacy_selector.js └── selector_engine.js ├── nwmatcher └── selector_engine.js ├── sizzle └── selector_engine.js └── slick └── selector_engine.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | pkg 3 | test/unit/tmp/* 4 | test/unit/browsers.yml 5 | doc 6 | tmp 7 | *.pdoc.yaml -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/caja_builder"] 2 | path = vendor/caja_builder 3 | url = git://github.com/tobie/unittest_js_caja_builder.git 4 | [submodule "vendor/pdoc"] 5 | path = vendor/pdoc 6 | url = git://github.com/tobie/pdoc.git 7 | [submodule "vendor/sprockets"] 8 | path = vendor/sprockets 9 | url = git://github.com/sstephenson/sprockets.git 10 | 11 | 12 | [submodule "vendor/nwmatcher/repository"] 13 | path = vendor/nwmatcher/repository 14 | url = git://github.com/dperini/nwmatcher.git 15 | [submodule "vendor/sizzle/repository"] 16 | path = vendor/sizzle/repository 17 | url = git://github.com/jquery/sizzle.git 18 | [submodule "vendor/slick/repository"] 19 | path = vendor/slick/repository 20 | url = git://github.com/mootools/slick.git 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2010 Sam Stephenson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 14 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 15 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 16 | SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Prototype 2 | ========= 3 | 4 | #### An object-oriented JavaScript framework #### 5 | 6 | Prototype is a JavaScript framework that aims to ease development of dynamic 7 | web applications. It offers a familiar class-style OO framework, extensive 8 | Ajax support, higher-order programming constructs, and easy DOM manipulation. 9 | 10 | ### Targeted platforms ### 11 | 12 | Prototype currently targets the following platforms: 13 | 14 | * Microsoft Internet Explorer for Windows, version 6.0 and higher 15 | * Mozilla Firefox 1.5 and higher 16 | * Apple Safari 2.0.4 and higher 17 | * Opera 9.25 and higher 18 | * Chrome 1.0 and higher 19 | 20 | Using Prototype 21 | --------------- 22 | 23 | To use Prototype in your application, download the latest release from the 24 | Prototype web site () and copy 25 | `dist/prototype.js` to a suitable location. Then include it in your HTML 26 | like so: 27 | 28 | 29 | 30 | ### Building Prototype from source ### 31 | 32 | `prototype.js` is a composite file generated from many source files in 33 | the `src/` directory. To build Prototype, you'll need: 34 | 35 | * a copy of the Prototype source tree, either from a distribution tarball or 36 | from the Git repository (see below) 37 | * Ruby 1.8.2 or higher () 38 | * Rake--Ruby Make () 39 | * RDoc, if your Ruby distribution does not include it 40 | 41 | From the root Prototype directory: 42 | 43 | * `rake dist` will preprocess the Prototype source using Sprockets and 44 | generate the composite `dist/prototype.js` 45 | * `rake package` will create a distribution tarball in the 46 | `pkg/` directory 47 | 48 | Contributing to Prototype 49 | ------------------------- 50 | 51 | Check out the Prototype source with 52 | 53 | $ git clone git://github.com/sstephenson/prototype.git 54 | $ cd prototype 55 | $ git submodule init 56 | $ git submodule update vendor/sprockets vendor/pdoc vendor/unittest_js 57 | 58 | Find out how to contribute: . 59 | 60 | Documentation 61 | ------------- 62 | 63 | Please see the online Prototype API: . 64 | -------------------------------------------------------------------------------- /dist/.gitignore: -------------------------------------------------------------------------------- 1 | prototype.js 2 | prototype_update_helper.js -------------------------------------------------------------------------------- /doc_assets/images/header-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prototypejs/prototype/dee2f7d8611248abce81287e1be4156011953c90/doc_assets/images/header-logo-small.png -------------------------------------------------------------------------------- /doc_assets/images/header-stripe-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prototypejs/prototype/dee2f7d8611248abce81287e1be4156011953c90/doc_assets/images/header-stripe-small.png -------------------------------------------------------------------------------- /ext/update_helper/update_helper.js: -------------------------------------------------------------------------------- 1 | /* Update Helper (c) 2008-2009 Tobie Langel 2 | * 3 | * Requires Prototype >= 1.6.0 4 | * 5 | * Update Helper is distributable under the same terms as Prototype 6 | * (MIT-style license). For details, see the Prototype web site: 7 | * http://www.prototypejs.org/ 8 | * 9 | *--------------------------------------------------------------------------*/ 10 | 11 | var UpdateHelper = Class.create({ 12 | logLevel: 0, 13 | MessageTemplate: new Template('Update Helper: #{message}\n#{stack}'), 14 | Regexp: new RegExp("@" + window.location.protocol + ".*?\\d+\\n", "g"), 15 | 16 | initialize: function(deprecatedMethods) { 17 | var notify = function(message, type) { 18 | this.notify(message, type); 19 | }.bind(this); // Late binding to simplify testing. 20 | 21 | deprecatedMethods.each(function(d) { 22 | var condition = d.condition, 23 | type = d.type || 'info', 24 | message = d.message, 25 | namespace = d.namespace, 26 | method = d.methodName; 27 | 28 | namespace[method] = (namespace[method] || function() {}).wrap(function(proceed) { 29 | var args = $A(arguments).splice(1); 30 | if (!condition || condition.apply(this, args)) notify(message, type); 31 | return proceed.apply(proceed, args); 32 | }); 33 | }); 34 | Element.addMethods(); 35 | }, 36 | 37 | notify: function(message, type) { 38 | switch(type) { 39 | case 'info': 40 | if (this.logLevel > UpdateHelper.Info) return false; 41 | case 'warn': 42 | if (this.logLevel > UpdateHelper.Warn) return false; 43 | default: 44 | if (this.logLevel > UpdateHelper.Error) return false; 45 | } 46 | this.log(this.MessageTemplate.evaluate({ 47 | message: message, 48 | stack: this.getStack() 49 | }), type); 50 | return true; 51 | }, 52 | 53 | getStack: function() { 54 | try { 55 | throw new Error("stack"); 56 | } catch(e) { 57 | var match = (e.stack || '').match(this.Regexp); 58 | if (match) { 59 | return match.reject(function(path) { 60 | return (/(prototype|unittest|update_helper)\.js/).test(path); 61 | }).join("\n"); 62 | } else { return ''; } 63 | } 64 | }, 65 | 66 | log: function(message, type) { 67 | if (type == 'error') console.error(message); 68 | else if (type == 'warn') console.warn(message); 69 | else console.log(message); 70 | } 71 | }); 72 | 73 | Object.extend(UpdateHelper, { 74 | Info: 0, 75 | Warn: 1, 76 | Error: 2 77 | }); 78 | 79 | -------------------------------------------------------------------------------- /src/constants.yml: -------------------------------------------------------------------------------- 1 | PROTOTYPE_VERSION: 1.7.3 2 | -------------------------------------------------------------------------------- /src/prototype.js: -------------------------------------------------------------------------------- 1 | //= compat 2 | //= require "./prototype/prototype" 3 | //= require "./prototype/lang" 4 | //= require "./prototype/ajax" 5 | //= require "./prototype/dom" 6 | //= require "./prototype/deprecated" 7 | -------------------------------------------------------------------------------- /src/prototype/ajax.js: -------------------------------------------------------------------------------- 1 | //= compat 2 | //= require "ajax/ajax" 3 | //= require "ajax/responders" 4 | //= require "ajax/base" 5 | //= require "ajax/request" 6 | //= require "ajax/response" 7 | //= require "ajax/updater" 8 | //= require "ajax/periodical_updater" 9 | 10 | /** 11 | * == Ajax == 12 | * 13 | * Prototype's APIs around the `XmlHttpRequest` object. 14 | * 15 | * The Prototype framework enables you to deal with Ajax calls in a manner that 16 | * is both easy and compatible with all modern browsers. 17 | * 18 | * Actual requests are made by creating instances of [[Ajax.Request]]. 19 | * 20 | * ##### Request headers 21 | * 22 | * The following headers are sent with all Ajax requests (and can be 23 | * overridden with the `requestHeaders` option described below): 24 | * 25 | * * `X-Requested-With` is set to `XMLHttpRequest`. 26 | * * `X-Prototype-Version` is set to Prototype's current version (e.g., 27 | * `<%= PROTOTYPE_VERSION %>`). 28 | * * `Accept` is set to `text/javascript, text/html, application/xml, 29 | * text/xml, * / *` 30 | * * `Content-type` is automatically determined based on the `contentType` 31 | * and `encoding` options. 32 | * 33 | * ##### Ajax options 34 | * 35 | * All Ajax classes share a common set of _options_ and _callbacks_. 36 | * Callbacks are called at various points in the life-cycle of a request, and 37 | * always feature the same list of arguments. 38 | * 39 | * ##### Common options 40 | * 41 | * * `asynchronous` ([[Boolean]]; default `true`): Determines whether 42 | * `XMLHttpRequest` is used asynchronously or not. Synchronous usage is 43 | * **strongly discouraged** — it halts all script execution for the 44 | * duration of the request _and_ blocks the browser UI. 45 | * * `body` ([[String]]): Specific contents for the request body on a 46 | * method that sends a payload (like `post` or `put`). If it is not 47 | * provided, the contents of the `parameters` option will be used instead. 48 | * * `contentType` ([[String]]; default `application/x-www-form-urlencoded`): 49 | * The `Content-type` header for your request. Change this header if you 50 | * want to send data in another format (like XML). 51 | * * `encoding` ([[String]]; default `UTF-8`): The encoding for the contents 52 | * of your request. It is best left as-is, but should weird encoding issues 53 | * arise, you may have to tweak this. 54 | * * `method` ([[String]]; default `post`): The HTTP method to use for the 55 | * request. The other common possibility is `get`. Abiding by Rails 56 | * conventions, Prototype also reacts to other HTTP verbs (such as `put` and 57 | * `delete`) by submitting via `post` and adding a extra `_method` parameter 58 | * with the originally-requested method. 59 | * * `parameters` ([[String]]): The parameters for the request, which will be 60 | * encoded into the URL for a `get` method, or into the request body for the 61 | * other methods. This can be provided either as a URL-encoded string, a 62 | * [[Hash]], or a plain [[Object]]. 63 | * * `postBody` ([[String]]): An alias for `body` that is provided for 64 | * backward-compatibility. Its use is discouraged. 65 | * * `requestHeaders` ([[Object]]): A set of key-value pairs, with properties 66 | * representing header names. 67 | * * `evalJS` ([[Boolean]] | [[String]]; default `true`): Automatically `eval`s 68 | * the content of [[Ajax.Response#responseText]] if the `Content-type` returned 69 | * by the server is set to one of `text/javascript`, `application/ecmascript` 70 | * (matches expression `(text|application)\/(x-)?(java|ecma)script`). 71 | * If the request doesn't obey same-origin policy, the content is not evaluated. 72 | * If you need to force evalutation, pass `'force'`. To prevent it altogether, 73 | * pass `false`. 74 | * * `evalJSON` ([[Boolean]] | [[String]]; default `true`): Automatically `eval`s 75 | * the content of [[Ajax.Response#responseText]] and populates 76 | * [[Ajax.Response#responseJSON]] with it if the `Content-type` returned by 77 | * the server is set to `application/json`. If the request doesn't obey 78 | * same-origin policy, the content is sanitized before evaluation. If you 79 | * need to force evalutation, pass `'force'`. To prevent it altogether, pass 80 | * `false`. 81 | * * `sanitizeJSON` ([[Boolean]]; default is `false` for same-origin requests, 82 | * `true` otherwise): Sanitizes the contents of 83 | * [[Ajax.Response#responseText]] before evaluating it. 84 | * 85 | * ##### Common callbacks 86 | * 87 | * When used on individual instances, all callbacks (except `onException`) are 88 | * invoked with two parameters: the [[Ajax.Response]] object and the result of 89 | * evaluating the `X-JSON` response header, if any (can be `null`). 90 | * 91 | * For another way of describing their chronological order and which callbacks 92 | * are mutually exclusive, see [[Ajax.Request]]. 93 | * 94 | * * `onCreate`: Triggered when the [[Ajax.Request]] object is initialized. 95 | * This is _after_ the parameters and the URL have been processed, but 96 | * _before_ opening the connection via the XHR object. 97 | * * `onUninitialized` (*Not guaranteed*): Invoked just after the XHR object 98 | * is created. 99 | * * `onLoading` (*Not guaranteed*): Triggered when the underlying XHR object 100 | * is being setup, and its connection opened. 101 | * * `onLoaded` (*Not guaranteed*): Triggered once the underlying XHR object 102 | * is setup, the connection is open, and it is ready to send its actual 103 | * request. 104 | * * `onInteractive` (*Not guaranteed*): Triggered whenever the requester 105 | * receives a part of the response (but not the final part), should it 106 | * be sent in several packets. 107 | * * `onSuccess`: Invoked when a request completes and its status code is 108 | * `undefined` or belongs in the `2xy` family. This is skipped if a 109 | * code-specific callback is defined (e.g., `on200`), and happens _before_ 110 | * `onComplete`. 111 | * * `onFailure`: Invoked when a request completes and its status code exists 112 | * but _is not_ in the `2xy` family. This is skipped if a code-specific 113 | * callback is defined (e.g. `on403`), and happens _before_ `onComplete`. 114 | * * `onXYZ` (_with `XYZ` representing any HTTP status code_): Invoked just 115 | * after the response is complete _if_ the status code is the exact code 116 | * used in the callback name. _Prevents_ execution of `onSuccess` and 117 | * `onFailure`. Happens _before_ `onComplete`. 118 | * * `onException`: Triggered whenever an XHR error arises. Has a custom 119 | * signature: the first argument is the requester (i.e. an [[Ajax.Request]] 120 | * instance), and the second is the exception object. The default 121 | * `onException` handler simply re-throws the caught exception. (If you 122 | * want exceptions to be silently ignored, pass in an empty function to 123 | * `onException`.) 124 | * * `onComplete`: Triggered at the _very end_ of a request's life-cycle, after 125 | * the request completes, status-specific callbacks are called, and possible 126 | * automatic behaviors are processed. Guaranteed to run regardless of what 127 | * happened during the request. 128 | * 129 | **/ 130 | -------------------------------------------------------------------------------- /src/prototype/ajax/ajax.js: -------------------------------------------------------------------------------- 1 | /** section: Ajax 2 | * Ajax 3 | **/ 4 | 5 | var Ajax = { 6 | getTransport: function() { 7 | return new XMLHttpRequest(); 8 | }, 9 | 10 | /** 11 | * Ajax.activeRequestCount -> Number 12 | * 13 | * Represents the number of active XHR requests triggered through 14 | * [[Ajax.Request]], [[Ajax.Updater]], or [[Ajax.PeriodicalUpdater]]. 15 | **/ 16 | activeRequestCount: 0 17 | }; 18 | -------------------------------------------------------------------------------- /src/prototype/ajax/base.js: -------------------------------------------------------------------------------- 1 | // Abstract class; does not need documentation. 2 | Ajax.Base = Class.create({ 3 | initialize: function(options) { 4 | this.options = { 5 | method: 'post', 6 | asynchronous: true, 7 | contentType: 'application/x-www-form-urlencoded', 8 | encoding: 'UTF-8', 9 | parameters: '', 10 | evalJSON: true, 11 | evalJS: true, 12 | onException: function (request, error) { 13 | throw error; 14 | } 15 | }; 16 | Object.extend(this.options, options || { }); 17 | 18 | this.options.method = this.options.method.toLowerCase(); 19 | 20 | if (Object.isHash(this.options.parameters)) 21 | this.options.parameters = this.options.parameters.toObject(); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/prototype/ajax/periodical_updater.js: -------------------------------------------------------------------------------- 1 | /** section: Ajax 2 | * class Ajax.PeriodicalUpdater 3 | * 4 | * Periodically performs an Ajax request and updates a container's contents 5 | * based on the response text. 6 | * 7 | * [[Ajax.PeriodicalUpdater]] behaves like [[Ajax.Updater]], but performs the 8 | * update at a prescribed interval, rather than only once. (Note that it is 9 | * _not_ a subclass of [[Ajax.Updater]]; it's a wrapper around it.) 10 | * 11 | * This class addresses the common need of periodical update, as required by 12 | * all sorts of "polling" mechanisms (e.g., an online chatroom or an online 13 | * mail client). 14 | * 15 | * The basic idea is to run a regular [[Ajax.Updater]] at regular intervals, 16 | * keeping track of the response text so it can (optionally) react to 17 | * receiving the exact same response consecutively. 18 | * 19 | * ##### Additional options 20 | * 21 | * [[Ajax.PeriodicalUpdater]] features all the common options and callbacks 22 | * described in the [[Ajax section]] — _plus_ those added by 23 | * [[Ajax.Updater]]. 24 | * 25 | * It also provides two new options: 26 | * 27 | * * `frequency` ([[Number]]; default is `2`): How long, in seconds, to wait 28 | * between the end of one request and the beginning of the next. 29 | * * `decay` ([[Number]]; default is `1`): The rate at which the `frequency` 30 | * grows when the response received is _exactly_ the same as the previous. 31 | * The default of `1` means `frequency` will never grow; override the 32 | * default if a stale response implies it's worthwhile to poll less often. 33 | * If `decay` is set to `2`, for instance, `frequency` will double 34 | * (2 seconds, 4 seconds, 8 seconds...) each consecutive time the result 35 | * is the same; when the result is different once again, `frequency` will 36 | * revert to its original value. 37 | * 38 | * ##### Disabling and re-enabling a [[Ajax.PeriodicalUpdater]] 39 | * 40 | * You can hit the brakes on a running [[Ajax.PeriodicalUpdater]] by calling 41 | * [[Ajax.PeriodicalUpdater#stop]]. If you wish to re-enable it later, call 42 | * [[Ajax.PeriodicalUpdater#start]]. 43 | * 44 | **/ 45 | 46 | Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { 47 | /** 48 | * new Ajax.PeriodicalUpdater(container, url[, options]) 49 | * - container (String | Element): The DOM element whose contents to update 50 | * as a result of the Ajax request. Can be a DOM node or a string that 51 | * identifies a node's ID. 52 | * - url (String): The URL to fetch. When the _same-origin_ policy is in 53 | * effect (as it is in most cases), `url` **must** be a relative URL or an 54 | * absolute URL that starts with a slash (i.e., it must not begin with 55 | * `http`). 56 | * - options (Object): Configuration for the request. See the 57 | * [[Ajax section]] for more information. 58 | * 59 | * Creates a new [[Ajax.PeriodicalUpdater]]. 60 | * 61 | * Periodically performs an AJAX request and updates a container's contents 62 | * based on the response text. Offers a mechanism for "decay," which lets it 63 | * trigger at widening intervals while the response is unchanged. 64 | * 65 | * This object addresses the common need of periodical update, which is used 66 | * by all sorts of "polling" mechanisms (e.g. in an online chatroom or an 67 | * online mail client). 68 | * 69 | * The basic idea is to run a regular [[Ajax.Updater]] at 70 | * regular intervals, monitoring changes in the response text if the `decay` 71 | * option (see below) is active. 72 | * 73 | * ##### Additional options 74 | * 75 | * [[Ajax.PeriodicalUpdater]] features all the common options and callbacks 76 | * (see the [[Ajax section]] for more information), plus those added by 77 | * [[Ajax.Updater]]. It also provides two new options that deal with the 78 | * original period, and its decay rate (how Rocket Scientist does that make 79 | * us sound, uh?!). 80 | * 81 | * 82 | * 83 | * 84 | * 85 | * 86 | * 87 | * 88 | * 89 | * 90 | * 91 | * 92 | * 93 | * 98 | * 99 | * 100 | * 101 | * 102 | * 111 | * 112 | * 113 | *
OptionDefaultDescription
frequency2Okay, this is not a frequency (e.g 0.5Hz), but a period (i.e. a number of seconds). 94 | * Don't kill me, I didn't write this one! This is the minimum interval at which AJAX 95 | * requests are made. You don't want to make it too short (otherwise you may very well 96 | * end up with multiple requests in parallel, if they take longer to process and return), 97 | * but you technically can provide a number below one, e.g. 0.75 second.
decay1This controls the rate at which the request interval grows when the response is 103 | * unchanged. It is used as a multiplier on the current period (which starts at the original 104 | * value of the frequency parameter). Every time a request returns an unchanged 105 | * response text, the current period is multiplied by the decay. Therefore, the default 106 | * value means regular requests (no change of interval). Values higher than one will 107 | * yield growing intervals. Values below one are dangerous: the longer the response text 108 | * stays the same, the more often you'll check, until the interval is so short your browser 109 | * is left with no other choice than suicide. Note that, as soon as the response text 110 | * does change, the current period resets to the original one.
114 | * 115 | * To better understand decay, here is a small sequence of calls from the 116 | * following example: 117 | * 118 | * new Ajax.PeriodicalUpdater('items', '/items', { 119 | * method: 'get', frequency: 3, decay: 2 120 | * }); 121 | * 122 | * 123 | * 124 | * 125 | * 126 | * 127 | * 128 | * 129 | * 130 | * 131 | * 132 | * 133 | * 134 | * 135 | * 136 | * 137 | * 138 | * 139 | * 140 | * 141 | * 142 | * 143 | * 144 | * 145 | * 146 | * 147 | * 148 | * 149 | * 150 | * 151 | * 152 | * 153 | * 154 | * 155 | * 156 | * 157 | * 158 | * 159 | * 160 | * 162 | * 163 | * 164 | * 165 | * 166 | * 167 | * 168 | * 169 | * 170 | * 171 | * 172 | * 173 | * 174 | * 175 | * 176 | * 177 | * 178 | * 179 | * 180 | * 181 | * 182 | * 183 | * 184 | * 185 | * 186 | * 187 | * 188 | * 189 | * 190 | * 191 | *
Call#When?Decay beforeResponse changed?Decay afterNext periodComments
100:002n/a13Response is deemed changed, since there is no prior response to compare to!
200:031yes13Response did change again: we "reset" to 1, which was already the decay.
300:061no26Response didn't change: decay augments by the decay option factor: 161 | * we're waiting longer now…
400:122no412Still no change, doubling again.
500:244no824Jesus, is this thing going to change or what?
600:488yes13Ah, finally! Resetting decay to 1, and therefore using the original period.
192 | * 193 | * ##### Disabling and re-enabling a [[Ajax.PeriodicalUpdater]] 194 | * 195 | * You can pull the brake on a running [[Ajax.PeriodicalUpdater]] by simply 196 | * calling its `stop` method. If you wish to re-enable it later, just call 197 | * its `start` method. Both take no argument. 198 | * 199 | * ##### Beware! Not a specialization! 200 | * 201 | * [[Ajax.PeriodicalUpdater]] is not a specialization of [[Ajax.Updater]], 202 | * despite its name. When using it, do not expect to be able to use methods 203 | * normally provided by [[Ajax.Request]] and "inherited" by [[Ajax.Updater]], 204 | * such as `evalJSON` or `getHeader`. Also the `onComplete` callback is 205 | * hijacked to be used for update management, so if you wish to be notified 206 | * of every successful request, use `onSuccess` instead (beware: it will get 207 | * called *before* the update is performed). 208 | **/ 209 | initialize: function($super, container, url, options) { 210 | $super(options); 211 | this.onComplete = this.options.onComplete; 212 | 213 | this.frequency = (this.options.frequency || 2); 214 | this.decay = (this.options.decay || 1); 215 | 216 | this.updater = { }; 217 | this.container = container; 218 | this.url = url; 219 | 220 | this.start(); 221 | }, 222 | 223 | /** 224 | * Ajax.PeriodicalUpdater#start() -> undefined 225 | * 226 | * Starts the periodical updater (if it had previously been stopped with 227 | * [[Ajax.PeriodicalUpdater#stop]]). 228 | **/ 229 | start: function() { 230 | this.options.onComplete = this.updateComplete.bind(this); 231 | this.onTimerEvent(); 232 | }, 233 | 234 | /** 235 | * Ajax.PeriodicalUpdater#stop() -> undefined 236 | * 237 | * Stops the periodical updater. 238 | * 239 | * Also calls the `onComplete` callback, if one has been defined. 240 | **/ 241 | stop: function() { 242 | this.updater.options.onComplete = undefined; 243 | clearTimeout(this.timer); 244 | (this.onComplete || Prototype.emptyFunction).apply(this, arguments); 245 | }, 246 | 247 | updateComplete: function(response) { 248 | if (this.options.decay) { 249 | this.decay = (response.responseText == this.lastText ? 250 | this.decay * this.options.decay : 1); 251 | 252 | this.lastText = response.responseText; 253 | } 254 | this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); 255 | }, 256 | 257 | onTimerEvent: function() { 258 | this.updater = new Ajax.Updater(this.container, this.url, this.options); 259 | } 260 | }); 261 | -------------------------------------------------------------------------------- /src/prototype/ajax/responders.js: -------------------------------------------------------------------------------- 1 | /** section: Ajax 2 | * Ajax.Responders 3 | * includes Enumerable 4 | * 5 | * A repository of global listeners notified about every step of 6 | * Prototype-based Ajax requests. 7 | * 8 | * Sometimes, you need to provide generic behaviors over all Ajax operations 9 | * happening on the page (through [[Ajax.Request]], [[Ajax.Updater]] or 10 | * [[Ajax.PeriodicalUpdater]]). 11 | * 12 | * For instance, you might want to automatically show an indicator when an 13 | * Ajax request is ongoing, and hide it when none are. You may well want to 14 | * factor out exception handling as well, logging those somewhere on the page 15 | * in a custom fashion. The possibilities are myriad. 16 | * 17 | * To achieve this, Prototype provides `Ajax.Responders`, which lets you 18 | * register (and, if you wish, unregister later) _responders_, which are 19 | * objects with specially-named methods. These names come from a set of 20 | * general callbacks corresponding to different points in time (or outcomes) 21 | * of an Ajax request's life cycle. 22 | * 23 | * For instance, Prototype automatically registers a responder that maintains 24 | * a nifty variable: [[Ajax.activeRequestCount]]. This represents, at a given 25 | * time, the number of currently active Ajax requests — by monitoring their 26 | * `onCreate` and `onComplete` events. The code for this is fairly simple: 27 | * 28 | * Ajax.Responders.register({ 29 | * onCreate: function() { 30 | * Ajax.activeRequestCount++; 31 | * }, 32 | * onComplete: function() { 33 | * Ajax.activeRequestCount--; 34 | * } 35 | * }); 36 | * 37 | * ##### Responder callbacks 38 | * 39 | * The callbacks for responders are similar to the callbacks described in 40 | * the [[Ajax section]], but take a different signature. They're invoked with 41 | * three parameters: the requester object (i.e., the corresponding "instance" 42 | * of [[Ajax.Request]]), the `XMLHttpRequest` object, and the result of 43 | * evaluating the `X-JSON` response header, if any (can be `null`). They also 44 | * execute in the context of the responder, bound to the `this` reference. 45 | * 46 | * * `onCreate`: Triggered whenever a requester object from the `Ajax` 47 | * namespace is created, after its parameters are adjusted and before its 48 | * XHR connection is opened. This takes *two* arguments: the requester 49 | * object and the underlying XHR object. 50 | * * `onUninitialized` (*Not guaranteed*): Invoked just after the XHR object 51 | * is created. 52 | * * `onLoading` (*Not guaranteed*): Triggered when the underlying XHR object 53 | * is being setup, and its connection opened. 54 | * * `onLoaded` (*Not guaranteed*): Triggered once the underlying XHR object 55 | * is setup, the connection is open, and it is ready to send its actual 56 | * request. 57 | * * `onInteractive` (*Not guaranteed*): Triggered whenever the requester 58 | * receives a part of the response (but not the final part), should it 59 | * be sent in several packets. 60 | * * `onException`: Triggered whenever an XHR error arises. Has a custom 61 | * signature: the first argument is the requester (i.e. an [[Ajax.Request]] 62 | * instance), and the second is the exception object. 63 | * * `onComplete`: Triggered at the _very end_ of a request's life-cycle, after 64 | * the request completes, status-specific callbacks are called, and possible 65 | * automatic behaviors are processed. Guaranteed to run regardless of what 66 | * happened during the request. 67 | **/ 68 | 69 | Ajax.Responders = { 70 | responders: [], 71 | 72 | _each: function(iterator, context) { 73 | this.responders._each(iterator, context); 74 | }, 75 | 76 | /** 77 | * Ajax.Responders.register(responder) -> undefined 78 | * - responder (Object): A list of functions with keys corresponding to the 79 | * names of possible callbacks. 80 | * 81 | * Add a group of responders to all Ajax requests. 82 | **/ 83 | register: function(responder) { 84 | if (!this.include(responder)) 85 | this.responders.push(responder); 86 | }, 87 | 88 | /** 89 | * Ajax.Responders.unregister(responder) -> undefined 90 | * - responder (Object): A list of functions with keys corresponding to the 91 | * names of possible callbacks. 92 | * 93 | * Remove a previously-added group of responders. 94 | * 95 | * As always, unregistering something requires you to use the very same 96 | * object you used at registration. If you plan to use `unregister`, be sure 97 | * to assign your responder to a _variable_ before passing it into 98 | * [[Ajax.Responders#register]] — don't pass it an object literal. 99 | **/ 100 | unregister: function(responder) { 101 | this.responders = this.responders.without(responder); 102 | }, 103 | 104 | dispatch: function(callback, request, transport, json) { 105 | this.each(function(responder) { 106 | if (Object.isFunction(responder[callback])) { 107 | try { 108 | responder[callback].apply(responder, [request, transport, json]); 109 | } catch (e) { } 110 | } 111 | }); 112 | } 113 | }; 114 | 115 | Object.extend(Ajax.Responders, Enumerable); 116 | 117 | Ajax.Responders.register({ 118 | onCreate: function() { Ajax.activeRequestCount++ }, 119 | onComplete: function() { Ajax.activeRequestCount-- } 120 | }); 121 | -------------------------------------------------------------------------------- /src/prototype/ajax/response.js: -------------------------------------------------------------------------------- 1 | /** section: Ajax 2 | * class Ajax.Response 3 | * 4 | * A wrapper class around `XmlHttpRequest` for dealing with HTTP responses 5 | * of Ajax requests. 6 | * 7 | * An instance of [[Ajax.Response]] is passed as the first argument of all Ajax 8 | * requests' callbacks. You _will not_ need to create instances of 9 | * [[Ajax.Response]] yourself. 10 | **/ 11 | 12 | /** 13 | * Ajax.Response#readyState -> Number 14 | * 15 | * A [[Number]] corresponding to the request's current state. 16 | * 17 | * `0` : `"Uninitialized"`
18 | * `1` : `"Loading"`
19 | * `2` : `"Loaded"`
20 | * `3` : `"Interactive"`
21 | * `4` : `"Complete"` 22 | **/ 23 | 24 | /** 25 | * Ajax.Response#responseText -> String 26 | * 27 | * The text body of the response. 28 | **/ 29 | 30 | /** 31 | * Ajax.Response#responseXML -> document | null 32 | * 33 | * The XML body of the response if the `Content-type` of the request is set 34 | * to `application/xml`; `null` otherwise. 35 | **/ 36 | 37 | /** 38 | * Ajax.Response#responseJSON -> Object | Array | null 39 | * 40 | * The JSON body of the response if the `Content-type` of the request is set 41 | * to `application/json`; `null` otherwise. 42 | **/ 43 | 44 | /** 45 | * Ajax.Response#headerJSON -> Object | Array | null 46 | * 47 | * Auto-evaluated content of the `X-JSON` header if present; `null` otherwise. 48 | * This is useful to transfer _small_ amounts of data. 49 | **/ 50 | 51 | /** 52 | * Ajax.Response#request -> Ajax.Request | Ajax.Updater 53 | * 54 | * The request object itself (an instance of [[Ajax.Request]] or 55 | * [[Ajax.Updater]]). 56 | **/ 57 | 58 | /** 59 | * Ajax.Response#transport -> XmlHttpRequest 60 | * 61 | * The native `XmlHttpRequest` object itself. 62 | **/ 63 | 64 | Ajax.Response = Class.create({ 65 | // Don't document the constructor; should never be manually instantiated. 66 | initialize: function(request){ 67 | this.request = request; 68 | var transport = this.transport = request.transport, 69 | readyState = this.readyState = transport.readyState; 70 | 71 | if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { 72 | this.status = this.getStatus(); 73 | this.statusText = this.getStatusText(); 74 | this.responseText = String.interpret(transport.responseText); 75 | this.headerJSON = this._getHeaderJSON(); 76 | } 77 | 78 | if (readyState == 4) { 79 | var xml = transport.responseXML; 80 | this.responseXML = Object.isUndefined(xml) ? null : xml; 81 | this.responseJSON = this._getResponseJSON(); 82 | } 83 | }, 84 | 85 | /** 86 | * Ajax.Response#status -> Number 87 | * 88 | * The HTTP status code sent by the server. 89 | **/ 90 | status: 0, 91 | 92 | /** 93 | * Ajax.Response#statusText -> String 94 | * 95 | * The HTTP status text sent by the server. 96 | **/ 97 | statusText: '', 98 | 99 | getStatus: Ajax.Request.prototype.getStatus, 100 | 101 | getStatusText: function() { 102 | try { 103 | return this.transport.statusText || ''; 104 | } catch (e) { return '' } 105 | }, 106 | 107 | /** 108 | * Ajax.Response#getHeader(name) -> String | null 109 | * 110 | * See [[Ajax.Request#getHeader]]. 111 | **/ 112 | getHeader: Ajax.Request.prototype.getHeader, 113 | 114 | /** 115 | * Ajax.Response#getAllHeaders() -> String | null 116 | * 117 | * Returns a [[String]] containing all headers separated by line breaks. 118 | * _Does not_ throw errors if no headers are present the way its native 119 | * counterpart does. 120 | **/ 121 | getAllHeaders: function() { 122 | try { 123 | return this.getAllResponseHeaders(); 124 | } catch (e) { return null } 125 | }, 126 | 127 | /** 128 | * Ajax.Response#getResponseHeader(name) -> String 129 | * 130 | * Returns the value of the requested header if present; throws an error 131 | * otherwise. This is just a wrapper around the `XmlHttpRequest` method of 132 | * the same name. Prefer it's shorter counterpart: 133 | * [[Ajax.Response#getHeader]]. 134 | **/ 135 | getResponseHeader: function(name) { 136 | return this.transport.getResponseHeader(name); 137 | }, 138 | 139 | /** 140 | * Ajax.Response#getAllResponseHeaders() -> String 141 | * 142 | * Returns a [[String]] containing all headers separated by line breaks; throws 143 | * an error if no headers exist. This is just a wrapper around the 144 | * `XmlHttpRequest` method of the same name. Prefer it's shorter counterpart: 145 | * [[Ajax.Response#getAllHeaders]]. 146 | **/ 147 | getAllResponseHeaders: function() { 148 | return this.transport.getAllResponseHeaders(); 149 | }, 150 | 151 | _getHeaderJSON: function() { 152 | var json = this.getHeader('X-JSON'); 153 | if (!json) return null; 154 | 155 | try { 156 | // Browsers expect HTTP headers to be ASCII and nothing else. Running 157 | // them through `decodeURIComponent` processes them with the page's 158 | // specified encoding. 159 | json = decodeURIComponent(escape(json)); 160 | } catch(e) { 161 | // Except Chrome doesn't seem to need this, and calling 162 | // `decodeURIComponent` on text that's already in the proper encoding 163 | // will throw a `URIError`. The ugly solution is to assume that a 164 | // `URIError` raised here signifies that the text is, in fact, already 165 | // in the correct encoding, and treat the failure as a good sign. 166 | // 167 | // This is ugly, but so too is sending extended characters in an HTTP 168 | // header with no spec to back you up. 169 | } 170 | 171 | try { 172 | return json.evalJSON(this.request.options.sanitizeJSON || 173 | !this.request.isSameOrigin()); 174 | } catch (e) { 175 | this.request.dispatchException(e); 176 | } 177 | }, 178 | 179 | _getResponseJSON: function() { 180 | var options = this.request.options; 181 | if (!options.evalJSON || (options.evalJSON != 'force' && 182 | !(this.getHeader('Content-type') || '').include('application/json')) || 183 | this.responseText.blank()) 184 | return null; 185 | try { 186 | return this.responseText.evalJSON(options.sanitizeJSON || 187 | !this.request.isSameOrigin()); 188 | } catch (e) { 189 | this.request.dispatchException(e); 190 | } 191 | } 192 | }); 193 | -------------------------------------------------------------------------------- /src/prototype/ajax/updater.js: -------------------------------------------------------------------------------- 1 | /** section: Ajax 2 | * class Ajax.Updater < Ajax.Request 3 | * 4 | * A class that performs an Ajax request and updates a container's contents 5 | * with the contents of the response. 6 | * 7 | * [[Ajax.Updater]] is a subclass of [[Ajax.Request]] built for a common 8 | * use-case. 9 | * 10 | * ##### Example 11 | * 12 | * new Ajax.Updater('items', '/items', { 13 | * parameters: { text: $F('text') } 14 | * }); 15 | * 16 | * This example will make a request to the URL `/items` (with the given 17 | * parameters); it will then replace the contents of the element with the ID 18 | * of `items` with whatever response it receives. 19 | * 20 | * ##### Callbacks 21 | * 22 | * [[Ajax.Updater]] supports all the callbacks listed in the [[Ajax section]]. 23 | * Note that the `onComplete` callback will be invoked **after** the element 24 | * is updated. 25 | * 26 | * ##### Additional options 27 | * 28 | * [[Ajax.Updater]] has some options of its own apart from the common options 29 | * described in the [[Ajax section]]: 30 | * 31 | * * `evalScripts` ([[Boolean]]; defaults to `false`): Whether ` 80 | 217 | 218 | 219 |
220 |
221 |
222 |
223 |

224 |
225 | 228 | 229 | -------------------------------------------------------------------------------- /test/console.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Prototype Console 6 | 7 | 52 | 96 | 97 | 98 | 99 | 100 | 101 |
102 |
103 | 104 | 105 |
106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /test/unit/browsers.sample.yml: -------------------------------------------------------------------------------- 1 | # 2 | # If the test runner doesn't know where your browsers are, you can define 3 | # them here. 4 | # 5 | 6 | browsers: 7 | firefox: C:\Program Files\Mozilla Firefox\firefox.exe 8 | opera: C:\Program Files\Opera\launcher.exe 9 | chrome: C:\Program Files\Google\Chrome\Application\chrome.exe 10 | -------------------------------------------------------------------------------- /test/unit/config.ru: -------------------------------------------------------------------------------- 1 | require './server' 2 | run UnitTests -------------------------------------------------------------------------------- /test/unit/fixtures/content.html: -------------------------------------------------------------------------------- 1 | Pack my box with five dozen liquor jugs! Oh, how quickly daft jumping zebras vex... -------------------------------------------------------------------------------- /test/unit/fixtures/data.json: -------------------------------------------------------------------------------- 1 | {"test": 123} -------------------------------------------------------------------------------- /test/unit/fixtures/empty.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prototypejs/prototype/dee2f7d8611248abce81287e1be4156011953c90/test/unit/fixtures/empty.html -------------------------------------------------------------------------------- /test/unit/fixtures/hello.js: -------------------------------------------------------------------------------- 1 | $("content").update("

Hello world!

"); 2 | -------------------------------------------------------------------------------- /test/unit/fixtures/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 |

visible

10 |

hidden

11 | 12 | 13 | -------------------------------------------------------------------------------- /test/unit/fixtures/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prototypejs/prototype/dee2f7d8611248abce81287e1be4156011953c90/test/unit/fixtures/logo.gif -------------------------------------------------------------------------------- /test/unit/phantomjs/core-extensions.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * PhantomJS Runners for Mocha 3 | * https://github.com/metaskills/mocha-phantomjs/ 4 | * 5 | * Copyright (c) 2012 Ken Collins 6 | * Released under the MIT license 7 | * http://github.com/metaskills/mocha-phantomjs/blob/master/MIT-LICENSE 8 | * 9 | */ 10 | 11 | (function(){ 12 | 13 | // A shim for non ES5 supporting browsers, like PhantomJS. Lovingly inspired by: 14 | // http://www.angrycoding.com/2011/09/to-bind-or-not-to-bind-that-is-in.html 15 | if (!('bind' in Function.prototype)) { 16 | Function.prototype.bind = function() { 17 | var funcObj = this; 18 | var extraArgs = Array.prototype.slice.call(arguments); 19 | var thisObj = extraArgs.shift(); 20 | return function() { 21 | return funcObj.apply(thisObj, extraArgs.concat(Array.prototype.slice.call(arguments))); 22 | }; 23 | }; 24 | } 25 | 26 | // Mocha needs a process.stdout.write in order to change the cursor position. 27 | Mocha.process = Mocha.process || {}; 28 | Mocha.process.stdout = Mocha.process.stdout || process.stdout; 29 | Mocha.process.stdout.write = function(s) { window.callPhantom({"Mocha.process.stdout.write":s}); } 30 | 31 | // Mocha needs the formating feature of console.log so copy node's format function and 32 | // monkey-patch it into place. This code is copied from node's, links copyright applies. 33 | // https://github.com/joyent/node/blob/master/lib/util.js 34 | console.format = function(f) { 35 | if (typeof f !== 'string') { 36 | var objects = []; 37 | for (var i = 0; i < arguments.length; i++) { 38 | objects.push(JSON.stringify(arguments[i])); 39 | } 40 | return objects.join(' '); 41 | } 42 | var i = 1; 43 | var args = arguments; 44 | var len = args.length; 45 | var str = String(f).replace(/%[sdj%]/g, function(x) { 46 | if (x === '%%') return '%'; 47 | if (i >= len) return x; 48 | switch (x) { 49 | case '%s': return String(args[i++]); 50 | case '%d': return Number(args[i++]); 51 | case '%j': return JSON.stringify(args[i++]); 52 | default: 53 | return x; 54 | } 55 | }); 56 | for (var x = args[i]; i < len; x = args[++i]) { 57 | if (x === null || typeof x !== 'object') { 58 | str += ' ' + x; 59 | } else { 60 | str += ' ' + JSON.stringify(x); 61 | } 62 | } 63 | return str; 64 | }; 65 | var origError = console.error; 66 | console.error = function(){ origError.call(console, console.format.apply(console, arguments)); }; 67 | var origLog = console.log; 68 | console.log = function(){ origLog.call(console, console.format.apply(console, arguments)); }; 69 | 70 | })(); 71 | -------------------------------------------------------------------------------- /test/unit/phantomjs/mocha-phantomjs.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * PhantomJS Runners for Mocha 3 | * https://github.com/metaskills/mocha-phantomjs/ 4 | * 5 | * Copyright (c) 2012 Ken Collins 6 | * Released under the MIT license 7 | * http://github.com/metaskills/mocha-phantomjs/blob/master/MIT-LICENSE 8 | * 9 | */ 10 | 11 | // Generated by CoffeeScript 1.7.1 12 | (function() { 13 | var Reporter, USAGE, config, mocha, reporter, system, webpage, 14 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 15 | 16 | system = require('system'); 17 | 18 | webpage = require('webpage'); 19 | 20 | USAGE = "Usage: phantomjs mocha-phantomjs.coffee URL REPORTER [CONFIG]"; 21 | 22 | Reporter = (function() { 23 | function Reporter(reporter, config) { 24 | this.reporter = reporter; 25 | this.config = config; 26 | this.checkStarted = __bind(this.checkStarted, this); 27 | this.waitForRunMocha = __bind(this.waitForRunMocha, this); 28 | this.waitForInitMocha = __bind(this.waitForInitMocha, this); 29 | this.waitForMocha = __bind(this.waitForMocha, this); 30 | this.url = system.args[1]; 31 | this.columns = parseInt(system.env.COLUMNS || 75) * .75 | 0; 32 | this.mochaStarted = false; 33 | this.mochaStartWait = this.config.timeout || 6000; 34 | this.startTime = Date.now(); 35 | if (!this.url) { 36 | this.fail(USAGE); 37 | } 38 | } 39 | 40 | Reporter.prototype.run = function() { 41 | this.initPage(); 42 | return this.loadPage(); 43 | }; 44 | 45 | Reporter.prototype.customizeMocha = function(options) { 46 | return Mocha.reporters.Base.window.width = options.columns; 47 | }; 48 | 49 | Reporter.prototype.customizeOptions = function() { 50 | return { 51 | columns: this.columns 52 | }; 53 | }; 54 | 55 | Reporter.prototype.fail = function(msg, errno) { 56 | if (msg) { 57 | console.log(msg); 58 | } 59 | return phantom.exit(errno || 1); 60 | }; 61 | 62 | Reporter.prototype.finish = function() { 63 | return phantom.exit(this.page.evaluate(function() { 64 | return mochaPhantomJS.failures; 65 | })); 66 | }; 67 | 68 | Reporter.prototype.initPage = function() { 69 | var cookie, _i, _len, _ref; 70 | this.page = webpage.create({ 71 | settings: this.config.settings 72 | }); 73 | if (this.config.headers) { 74 | this.page.customHeaders = this.config.headers; 75 | } 76 | _ref = this.config.cookies || []; 77 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 78 | cookie = _ref[_i]; 79 | this.page.addCookie(cookie); 80 | } 81 | if (this.config.viewportSize) { 82 | this.page.viewportSize = this.config.viewportSize; 83 | } 84 | this.page.onConsoleMessage = function(msg) { 85 | return system.stdout.writeLine(msg); 86 | }; 87 | this.page.onError = (function(_this) { 88 | return function(msg, traces) { 89 | var file, index, line, _j, _len1, _ref1; 90 | if (_this.page.evaluate(function() { 91 | return window.onerror != null; 92 | })) { 93 | return; 94 | } 95 | for (index = _j = 0, _len1 = traces.length; _j < _len1; index = ++_j) { 96 | _ref1 = traces[index], line = _ref1.line, file = _ref1.file; 97 | traces[index] = " " + file + ":" + line; 98 | } 99 | return _this.fail("" + msg + "\n\n" + (traces.join('\n'))); 100 | }; 101 | })(this); 102 | return this.page.onInitialized = (function(_this) { 103 | return function() { 104 | return _this.page.evaluate(function(env) { 105 | return window.mochaPhantomJS = { 106 | env: env, 107 | failures: 0, 108 | ended: false, 109 | started: false, 110 | run: function() { 111 | mochaPhantomJS.started = true; 112 | return window.callPhantom({ 113 | 'mochaPhantomJS.run': true 114 | }); 115 | } 116 | }; 117 | }, system.env); 118 | }; 119 | })(this); 120 | }; 121 | 122 | Reporter.prototype.loadPage = function() { 123 | this.page.open(this.url); 124 | this.page.onLoadFinished = (function(_this) { 125 | return function(status) { 126 | _this.page.onLoadFinished = function() {}; 127 | if (status !== 'success') { 128 | _this.onLoadFailed(); 129 | } 130 | return _this.waitForInitMocha(); 131 | }; 132 | })(this); 133 | return this.page.onCallback = (function(_this) { 134 | return function(data) { 135 | if (data.hasOwnProperty('Mocha.process.stdout.write')) { 136 | system.stdout.write(data['Mocha.process.stdout.write']); 137 | } else if (data.hasOwnProperty('mochaPhantomJS.run')) { 138 | if (_this.injectJS()) { 139 | _this.waitForRunMocha(); 140 | } 141 | } 142 | return true; 143 | }; 144 | })(this); 145 | }; 146 | 147 | Reporter.prototype.onLoadFailed = function() { 148 | return this.fail("Failed to load the page. Check the url: " + this.url); 149 | }; 150 | 151 | Reporter.prototype.injectJS = function() { 152 | if (this.page.evaluate(function() { 153 | return window.mocha != null; 154 | })) { 155 | this.page.injectJs('core-extensions.js'); 156 | this.page.evaluate(this.customizeMocha, this.customizeOptions()); 157 | return true; 158 | } else { 159 | this.fail("Failed to find mocha on the page."); 160 | return false; 161 | } 162 | }; 163 | 164 | Reporter.prototype.runMocha = function() { 165 | if (this.config.useColors === false) { 166 | this.page.evaluate(function() { 167 | return Mocha.reporters.Base.useColors = false; 168 | }); 169 | } 170 | this.page.evaluate(this.runner, this.reporter); 171 | this.mochaStarted = this.page.evaluate(function() { 172 | return mochaPhantomJS.runner || false; 173 | }); 174 | if (this.mochaStarted) { 175 | this.mochaRunAt = new Date().getTime(); 176 | return this.waitForMocha(); 177 | } else { 178 | return this.fail("Failed to start mocha."); 179 | } 180 | }; 181 | 182 | Reporter.prototype.waitForMocha = function() { 183 | var ended; 184 | ended = this.page.evaluate(function() { 185 | return mochaPhantomJS.ended; 186 | }); 187 | if (ended) { 188 | return this.finish(); 189 | } else { 190 | return setTimeout(this.waitForMocha, 100); 191 | } 192 | }; 193 | 194 | Reporter.prototype.waitForInitMocha = function() { 195 | if (!this.checkStarted()) { 196 | return setTimeout(this.waitForInitMocha, 100); 197 | } 198 | }; 199 | 200 | Reporter.prototype.waitForRunMocha = function() { 201 | if (this.checkStarted()) { 202 | return this.runMocha(); 203 | } else { 204 | return setTimeout(this.waitForRunMocha, 100); 205 | } 206 | }; 207 | 208 | Reporter.prototype.checkStarted = function() { 209 | var started; 210 | started = this.page.evaluate(function() { 211 | return mochaPhantomJS.started; 212 | }); 213 | if (!started && this.mochaStartWait && this.startTime + this.mochaStartWait < Date.now()) { 214 | this.fail("Failed to start mocha: Init timeout", 255); 215 | } 216 | return started; 217 | }; 218 | 219 | Reporter.prototype.runner = function(reporter) { 220 | var cleanup, error, _ref, _ref1; 221 | try { 222 | mocha.setup({ 223 | reporter: reporter 224 | }); 225 | mochaPhantomJS.runner = mocha.run(); 226 | if (mochaPhantomJS.runner) { 227 | cleanup = function() { 228 | mochaPhantomJS.failures = mochaPhantomJS.runner.failures; 229 | return mochaPhantomJS.ended = true; 230 | }; 231 | if ((_ref = mochaPhantomJS.runner) != null ? (_ref1 = _ref.stats) != null ? _ref1.end : void 0 : void 0) { 232 | return cleanup(); 233 | } else { 234 | return mochaPhantomJS.runner.on('end', cleanup); 235 | } 236 | } 237 | } catch (_error) { 238 | error = _error; 239 | return false; 240 | } 241 | }; 242 | 243 | return Reporter; 244 | 245 | })(); 246 | 247 | if (phantom.version.major !== 1 || phantom.version.minor < 9) { 248 | console.log('mocha-phantomjs requires PhantomJS > 1.9.1'); 249 | phantom.exit(-1); 250 | } 251 | 252 | reporter = system.args[2] || 'spec'; 253 | 254 | config = JSON.parse(system.args[3] || '{}'); 255 | 256 | mocha = new Reporter(reporter, config); 257 | 258 | mocha.run(); 259 | 260 | }).call(this); 261 | -------------------------------------------------------------------------------- /test/unit/server.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'pathname' 3 | require 'json' 4 | 5 | class UnitTests < Sinatra::Application 6 | 7 | PWD = Pathname.new( File.expand_path( File.dirname(__FILE__) ) ) 8 | 9 | UNIQUE_ASSET_STRING = Time.new.to_i 10 | 11 | set :root, PWD 12 | set :public_folder, PWD.join('static') 13 | 14 | # Suppress logging. 15 | set :logging, false 16 | set :server_settings, { :AccessLog => [] } 17 | 18 | # By default, the server is only reachable locally. We change this so that 19 | # we can start the server on one machine and then run tests from another. 20 | set :bind, '0.0.0.0' 21 | 22 | PATH_TO_PROTOTYPE = PWD.join('..', '..', 'dist', 'prototype.js') 23 | 24 | unless PATH_TO_PROTOTYPE.file? 25 | raise "You must run `rake dist` before starting the server." 26 | end 27 | 28 | PATH_TO_TEST_JS = PWD.join('tests') 29 | PATH_TO_FIXTURES = PWD.join('fixtures') 30 | 31 | SUITES = [] 32 | 33 | PATH_TO_TEST_JS.each_entry do |e| 34 | next if e.directory? 35 | basename = e.basename('.*').to_s 36 | next if basename.start_with?('.') 37 | SUITES << basename.sub('.test', '') 38 | end 39 | 40 | SUITES_WITH_VIEWS = [] 41 | 42 | PWD.join('views', 'tests').each_entry do |e| 43 | next if e.directory? 44 | basename = e.basename('.*').to_s 45 | SUITES_WITH_VIEWS << basename 46 | end 47 | 48 | after do 49 | headers({ 50 | 'X-UA-Compatible' => 'IE=edge', 51 | 'Cache-Control' => 'no-cache, no-store, must-revalidate', 52 | 'Pragma' => 'no-cache', 53 | 'Expires' => '0' 54 | }) 55 | end 56 | 57 | # The '/inspect' endpoint should be available regardless of HTTP method. 58 | def self.handle_inspect(url, &block) 59 | %w{get post put delete patch options head}.each do |verb| 60 | self.send(verb, url, &block) 61 | end 62 | end 63 | 64 | def self.get_or_post(url, &block) 65 | get(url, &block) 66 | post(url, &block) 67 | end 68 | 69 | get '/test' do 70 | redirect to('/test/') 71 | end 72 | 73 | get '/test/:names/' do 74 | redirect to("/test/#{params[:names]}") 75 | end 76 | 77 | # /test/ will run all tests; 78 | # /test/foo,bar will run just "foo" and "bar" tests. 79 | get '/test/:names?' do 80 | names = params[:names] 81 | @suites = names.nil? ? SUITES : names.split(/,/).uniq 82 | @unique_asset_string = UNIQUE_ASSET_STRING.to_s 83 | erb :tests, :locals => { :suites => @suites } 84 | end 85 | 86 | # Will read from disk each time. No server restart necessary when the 87 | # distributable is updated. 88 | get '/prototype.js' do 89 | content_type 'text/javascript' 90 | send_file PATH_TO_PROTOTYPE 91 | end 92 | 93 | 94 | # We don't put either of these in the /static directory because 95 | # (a) they should be more prominent in the directory structure; 96 | # (b) they should never, ever get cached, and we want to enforce that 97 | # aggressively. 98 | get '/js/tests/:filename' do 99 | filename = params[:filename] 100 | path = PATH_TO_TEST_JS.join(filename) 101 | if path.file? 102 | content_type 'text/javascript' 103 | send_file PATH_TO_TEST_JS.join(filename) 104 | else 105 | status 404 106 | end 107 | end 108 | 109 | get '/fixtures/:filename' do 110 | filename = params[:filename] 111 | send_file PATH_TO_FIXTURES.join(filename) 112 | end 113 | 114 | 115 | # Routes for Ajax tests 116 | 117 | handle_inspect '/inspect' do 118 | response = { 119 | :headers => request_headers(request.env), 120 | :method => request.request_method, 121 | :body => request.body.read 122 | } 123 | 124 | content_type 'application/json' 125 | JSON.dump(response) 126 | end 127 | 128 | get '/response' do 129 | header_params = {} 130 | params.each do |k, v| 131 | v = v.gsub(/[\r\n]/, '') 132 | header_params[k] = v 133 | end 134 | headers(header_params) 135 | 136 | if params[:'Content-Type'] 137 | content_type params[:'Content-Type'].strip 138 | else 139 | content_type 'application/json' 140 | end 141 | 142 | params[:responseBody] || "" 143 | end 144 | 145 | # Collect all the headers that were sent with a request. (This is harder than 146 | # it seems because of how Rack normalizes headers.) 147 | def request_headers(env) 148 | results = {} 149 | 150 | env.each do |k, v| 151 | next unless k.start_with?('HTTP_') || k == 'CONTENT_TYPE' 152 | key = k.sub('HTTP_', '').gsub('_', '-').downcase 153 | results[key] = v 154 | end 155 | 156 | results 157 | end 158 | 159 | 160 | not_found do 161 | "File not found." 162 | end 163 | 164 | 165 | helpers do 166 | 167 | def suite_has_html?(suite) 168 | SUITES_WITH_VIEWS.include?(suite) 169 | end 170 | 171 | end 172 | 173 | # run! if app_file == $0 174 | end -------------------------------------------------------------------------------- /test/unit/static/css/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin:0; 5 | } 6 | 7 | #mocha { 8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | margin: 60px 50px; 10 | } 11 | 12 | #mocha ul, #mocha li { 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | #mocha ul { 18 | list-style: none; 19 | } 20 | 21 | #mocha h1, #mocha h2 { 22 | margin: 0; 23 | } 24 | 25 | #mocha h1 { 26 | margin-top: 15px; 27 | font-size: 1em; 28 | font-weight: 200; 29 | } 30 | 31 | #mocha h1 a { 32 | text-decoration: none; 33 | color: inherit; 34 | } 35 | 36 | #mocha h1 a:hover { 37 | text-decoration: underline; 38 | } 39 | 40 | #mocha .suite .suite h1 { 41 | margin-top: 0; 42 | font-size: .8em; 43 | } 44 | 45 | #mocha .hidden { 46 | display: none; 47 | } 48 | 49 | #mocha h2 { 50 | font-size: 12px; 51 | font-weight: normal; 52 | cursor: pointer; 53 | } 54 | 55 | #mocha .suite { 56 | margin-left: 15px; 57 | } 58 | 59 | #mocha .test { 60 | margin-left: 15px; 61 | overflow: hidden; 62 | } 63 | 64 | #mocha .test.pending:hover h2::after { 65 | content: '(pending)'; 66 | font-family: arial, sans-serif; 67 | } 68 | 69 | #mocha .test.pass.medium .duration { 70 | background: #C09853; 71 | } 72 | 73 | #mocha .test.pass.slow .duration { 74 | background: #B94A48; 75 | } 76 | 77 | #mocha .test.pass::before { 78 | content: '✓'; 79 | font-size: 12px; 80 | display: block; 81 | float: left; 82 | margin-right: 5px; 83 | color: #00d6b2; 84 | } 85 | 86 | #mocha .test.pass .duration { 87 | font-size: 9px; 88 | margin-left: 5px; 89 | padding: 2px 5px; 90 | color: white; 91 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 92 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 93 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -webkit-border-radius: 5px; 95 | -moz-border-radius: 5px; 96 | -ms-border-radius: 5px; 97 | -o-border-radius: 5px; 98 | border-radius: 5px; 99 | } 100 | 101 | #mocha .test.pass.fast .duration { 102 | display: none; 103 | } 104 | 105 | #mocha .test.pending { 106 | color: #0b97c4; 107 | } 108 | 109 | #mocha .test.pending::before { 110 | content: '◦'; 111 | color: #0b97c4; 112 | } 113 | 114 | #mocha .test.fail { 115 | color: #c00; 116 | } 117 | 118 | #mocha .test.fail pre { 119 | color: black; 120 | } 121 | 122 | #mocha .test.fail::before { 123 | content: '✖'; 124 | font-size: 12px; 125 | display: block; 126 | float: left; 127 | margin-right: 5px; 128 | color: #c00; 129 | } 130 | 131 | #mocha .test pre.error { 132 | color: #c00; 133 | max-height: 300px; 134 | overflow: auto; 135 | } 136 | 137 | #mocha .test pre { 138 | display: block; 139 | float: left; 140 | clear: left; 141 | font: 12px/1.5 monaco, monospace; 142 | margin: 5px; 143 | padding: 15px; 144 | border: 1px solid #eee; 145 | border-bottom-color: #ddd; 146 | -webkit-border-radius: 3px; 147 | -webkit-box-shadow: 0 1px 3px #eee; 148 | -moz-border-radius: 3px; 149 | -moz-box-shadow: 0 1px 3px #eee; 150 | } 151 | 152 | #mocha .test h2 { 153 | position: relative; 154 | } 155 | 156 | #mocha .test a.replay { 157 | position: absolute; 158 | top: 3px; 159 | right: 0; 160 | text-decoration: none; 161 | vertical-align: middle; 162 | display: block; 163 | width: 15px; 164 | height: 15px; 165 | line-height: 15px; 166 | text-align: center; 167 | background: #eee; 168 | font-size: 15px; 169 | -moz-border-radius: 15px; 170 | border-radius: 15px; 171 | -webkit-transition: opacity 200ms; 172 | -moz-transition: opacity 200ms; 173 | transition: opacity 200ms; 174 | opacity: 0.3; 175 | color: #888; 176 | } 177 | 178 | #mocha .test:hover a.replay { 179 | opacity: 1; 180 | } 181 | 182 | #mocha-report.pass .test.fail { 183 | display: none; 184 | } 185 | 186 | #mocha-report.fail .test.pass { 187 | display: none; 188 | } 189 | 190 | #mocha-report.pending .test.pass 191 | #mocha-report.pending .test.fail, { 192 | display: none; 193 | } 194 | #mocha-report.pending .test.pass.pending { 195 | display: block; 196 | } 197 | 198 | #mocha-error { 199 | color: #c00; 200 | font-size: 1.5em; 201 | font-weight: 100; 202 | letter-spacing: 1px; 203 | } 204 | 205 | #mocha-stats { 206 | position: fixed; 207 | top: 15px; 208 | right: 10px; 209 | font-size: 12px; 210 | margin: 0; 211 | color: #888; 212 | z-index: 1; 213 | } 214 | 215 | #mocha-stats .progress { 216 | float: right; 217 | padding-top: 0; 218 | } 219 | 220 | #mocha-stats em { 221 | color: black; 222 | } 223 | 224 | #mocha-stats a { 225 | text-decoration: none; 226 | color: inherit; 227 | } 228 | 229 | #mocha-stats a:hover { 230 | border-bottom: 1px solid #eee; 231 | } 232 | 233 | #mocha-stats li { 234 | display: inline-block; 235 | margin: 0 5px; 236 | list-style: none; 237 | padding-top: 11px; 238 | } 239 | 240 | #mocha-stats canvas { 241 | width: 40px; 242 | height: 40px; 243 | } 244 | 245 | #mocha code .comment { color: #ddd } 246 | #mocha code .init { color: #2F6FAD } 247 | #mocha code .string { color: #5890AD } 248 | #mocha code .keyword { color: #8A6343 } 249 | #mocha code .number { color: #2F6FAD } 250 | 251 | @media screen and (max-device-width: 480px) { 252 | #mocha { 253 | margin: 60px 0px; 254 | } 255 | 256 | #mocha #stats { 257 | position: absolute; 258 | } 259 | } -------------------------------------------------------------------------------- /test/unit/tests/base.test.js: -------------------------------------------------------------------------------- 1 | 2 | suite('Base', function () { 3 | 4 | this.name = 'base'; 5 | 6 | test('Browser detection', function () { 7 | 8 | var results = $H(Prototype.Browser).map(function (engine) { 9 | return engine; 10 | }).partition(function (engine) { 11 | return engine[1] === true; 12 | }); 13 | var trues = results[0], falses = results[1]; 14 | 15 | info('User agent string is: ' + navigator.userAgent); 16 | 17 | // It's OK for there to be two true values if we're on MobileSafari, 18 | // since it's also a WebKit browser. 19 | if (Prototype.Browser.MobileSafari) { 20 | assert(trues.size() === 2, 'MobileSafari should also identify as WebKit.'); 21 | } else { 22 | assert(trues.size() === 0 || trues.size() === 1, 23 | 'There should be only one or no browser detected.'); 24 | } 25 | 26 | // we should have definite trues or falses here 27 | trues.each(function(result) { 28 | assert(result[1] === true); 29 | }, this); 30 | falses.each(function(result) { 31 | assert(result[1] === false); 32 | }, this); 33 | 34 | var ua = navigator.userAgent; 35 | 36 | if (ua.indexOf('AppleWebKit/') > -1) { 37 | info('Running on WebKit'); 38 | assert(Prototype.Browser.WebKit); 39 | } 40 | 41 | if (Object.prototype.toString.call(window.opera) === '[object Opera]') { 42 | info('Running on Opera'); 43 | assert(Prototype.Browser.Opera); 44 | } 45 | 46 | if (ua.indexOf('MSIE') > -1) { 47 | info('Running on IE'); 48 | assert(Prototype.Browser.IE); 49 | } 50 | 51 | if (ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1) { 52 | info('Running on Gecko'); 53 | assert(Prototype.Browser.Gecko); 54 | } 55 | }); 56 | 57 | }); -------------------------------------------------------------------------------- /test/unit/tests/class.test.js: -------------------------------------------------------------------------------- 1 | // base class 2 | var Animal = Class.create({ 3 | initialize: function(name) { 4 | this.name = name; 5 | }, 6 | name: "", 7 | eat: function() { 8 | return this.say("Yum!"); 9 | }, 10 | say: function(message) { 11 | return this.name + ": " + message; 12 | } 13 | }); 14 | 15 | // subclass that augments a method 16 | var Cat = Class.create(Animal, { 17 | eat: function($super, food) { 18 | if (food instanceof Mouse) return $super(); 19 | else return this.say("Yuk! I only eat mice."); 20 | } 21 | }); 22 | 23 | // empty subclass 24 | var Mouse = Class.create(Animal, {}); 25 | 26 | //mixins 27 | var Sellable = { 28 | getValue: function(pricePerKilo) { 29 | return this.weight * pricePerKilo; 30 | }, 31 | 32 | inspect: function() { 33 | return '#'.interpolate(this); 34 | } 35 | }; 36 | 37 | var Reproduceable = { 38 | reproduce: function(partner) { 39 | if (partner.constructor != this.constructor || partner.sex == this.sex) 40 | return null; 41 | var weight = this.weight / 10, sex = Math.random(1).round() ? 'male' : 'female'; 42 | return new this.constructor('baby', weight, sex); 43 | } 44 | }; 45 | 46 | // base class with mixin 47 | var Plant = Class.create(Sellable, { 48 | initialize: function(name, weight) { 49 | this.name = name; 50 | this.weight = weight; 51 | }, 52 | 53 | inspect: function() { 54 | return '#'.interpolate(this); 55 | } 56 | }); 57 | 58 | // subclass with mixin 59 | var Dog = Class.create(Animal, Reproduceable, { 60 | initialize: function($super, name, weight, sex) { 61 | this.weight = weight; 62 | this.sex = sex; 63 | $super(name); 64 | } 65 | }); 66 | 67 | // subclass with mixins 68 | var Ox = Class.create(Animal, Sellable, Reproduceable, { 69 | initialize: function($super, name, weight, sex) { 70 | this.weight = weight; 71 | this.sex = sex; 72 | $super(name); 73 | }, 74 | 75 | eat: function(food) { 76 | if (food instanceof Plant) 77 | this.weight += food.weight; 78 | }, 79 | 80 | inspect: function() { 81 | return '#'.interpolate(this); 82 | } 83 | }); 84 | 85 | 86 | 87 | 88 | suite("Class", function () { 89 | this.name = 'class'; 90 | 91 | test('create', function () { 92 | assert(Object.isFunction(Animal), 'Animal is not a constructor'); 93 | }); 94 | 95 | test('instantiation', function () { 96 | var pet = new Animal('Nibbles'); 97 | assert.equal('Nibbles', pet.name, 'property not initialized'); 98 | assert.equal('Nibbles: Hi!', pet.say('Hi!')); 99 | assert.equal(Animal, pet.constructor, 'bad constructor reference'); 100 | assert.isUndefined(pet.superclass); 101 | 102 | var Empty = Class.create(); 103 | assert.equal('object', typeof new Empty); 104 | }); 105 | 106 | test('inheritance', function () { 107 | var tom = new Cat('Tom'); 108 | assert.equal(Cat, tom.constructor, 'bad constructor reference'); 109 | assert.equal(Animal, tom.constructor.superclass, 'bad superclass reference'); 110 | assert.equal('Tom', tom.name); 111 | assert.equal('Tom: meow', tom.say('meow')); 112 | assert.equal('Tom: Yuk! I only eat mice.', tom.eat(new Animal)); 113 | }); 114 | 115 | test('superclass method call', function () { 116 | var tom = new Cat('Tom'); 117 | assert.equal('Tom: Yum!', tom.eat(new Mouse)); 118 | 119 | var Dodo = Class.create(Animal, { 120 | initialize: function ($super, name) { 121 | $super(name); 122 | this.extinct = true; 123 | }, 124 | 125 | say: function ($super, message) { 126 | return $super(message) + " honk honk"; 127 | } 128 | }); 129 | 130 | var gonzo = new Dodo('Gonzo'); 131 | assert.equal('Gonzo', gonzo.name); 132 | assert(gonzo.extinct, 'Dodo birds should be extinct'); 133 | assert.equal('Gonzo: hello honk honk', gonzo.say('hello')); 134 | }); 135 | 136 | test('addMethods', function () { 137 | var tom = new Cat('Tom'); 138 | var jerry = new Mouse('Jerry'); 139 | 140 | Animal.addMethods({ 141 | sleep: function () { 142 | return this.say('ZZZ'); 143 | } 144 | }); 145 | 146 | Mouse.addMethods({ 147 | sleep: function ($super) { 148 | return $super() + " ... no, can't sleep! Gotta steal cheese!"; 149 | }, 150 | 151 | escape: function (cat) { 152 | return this.say('(from a mousehole) Take that, ' + cat.name + '!'); 153 | } 154 | }); 155 | 156 | assert.equal('Tom: ZZZ', tom.sleep(), 157 | 'added instance method not available to subclass'); 158 | assert.equal("Jerry: ZZZ ... no, can't sleep! Gotta steal cheese!", 159 | jerry.sleep()); 160 | assert.equal("Jerry: (from a mousehole) Take that, Tom!", 161 | jerry.escape(tom)); 162 | 163 | // Ensure that a method has not propagated _up_ the prototype chain. 164 | assert.isUndefined(tom.escape); 165 | assert.isUndefined(new Animal().escape); 166 | 167 | Animal.addMethods({ 168 | sleep: function () { 169 | return this.say('zZzZ'); 170 | } 171 | }); 172 | 173 | assert.equal("Jerry: zZzZ ... no, can't sleep! Gotta steal cheese!", 174 | jerry.sleep()); 175 | 176 | }); 177 | 178 | test('base class with mixin', function () { 179 | var grass = new Plant('grass', 3); 180 | assert.respondsTo('getValue', grass); 181 | assert.equal('#', grass.inspect()); 182 | }); 183 | 184 | test('subclass with mixin', function () { 185 | var snoopy = new Dog('Snoopy', 12, 'male'); 186 | assert.respondsTo('reproduce', snoopy); 187 | }); 188 | 189 | test('subclass with mixins', function () { 190 | var cow = new Ox('cow', 400, 'female'); 191 | assert.equal('#', cow.inspect()); 192 | assert.respondsTo('reproduce', cow); 193 | assert.respondsTo('getValue', cow); 194 | }); 195 | 196 | test('class with toString and valueOf methods', function () { 197 | var Foo = Class.create({ 198 | toString: function() { return "toString"; }, 199 | valueOf: function() { return "valueOf"; } 200 | }); 201 | 202 | var Bar = Class.create(Foo, { 203 | valueOf: function() { return "myValueOf"; } 204 | }); 205 | 206 | var Parent = Class.create({ 207 | m1: function(){ return 'm1'; }, 208 | m2: function(){ return 'm2'; } 209 | }); 210 | var Child = Class.create(Parent, { 211 | m1: function($super) { return 'm1 child'; }, 212 | m2: function($super) { return 'm2 child'; } 213 | }); 214 | 215 | assert(new Child().m1.toString().indexOf('m1 child') > -1); 216 | 217 | assert.equal("toString", new Foo().toString()); 218 | assert.equal("valueOf", new Foo().valueOf() ); 219 | assert.equal("toString", new Bar().toString()); 220 | assert.equal("myValueOf", new Bar().valueOf() ); 221 | }); 222 | 223 | }); -------------------------------------------------------------------------------- /test/unit/tests/date.test.js: -------------------------------------------------------------------------------- 1 | suite('Date', function () { 2 | this.name = 'date'; 3 | 4 | test('#toJSON', function () { 5 | assert.match( 6 | new Date(Date.UTC(1970, 0, 1)).toJSON(), 7 | /^1970-01-01T00:00:00(\.000)?Z$/ 8 | ); 9 | }); 10 | 11 | test('#toISOString', function () { 12 | assert.match( 13 | new Date(Date.UTC(1970, 0, 1)).toISOString(), 14 | /^1970-01-01T00:00:00(\.000)?Z$/ 15 | ); 16 | }); 17 | 18 | }); -------------------------------------------------------------------------------- /test/unit/tests/element_mixins.test.js: -------------------------------------------------------------------------------- 1 | Form.Element.Methods.coffee = Prototype.K; 2 | Element.addMethods(); 3 | 4 | suite('Element mixins', function () { 5 | this.name = 'element_mixins'; 6 | 7 | test('input', function () { 8 | assert($("input").present != null); 9 | assert(typeof $("input").present == 'function'); 10 | assert($("input").select != null); 11 | assert.respondsTo('present', Form.Element); 12 | assert.respondsTo('present', Form.Element.Methods); 13 | assert.respondsTo('coffee', $('input')); 14 | assert.strictEqual(Prototype.K, Form.Element.coffee); 15 | assert.strictEqual(Prototype.K, Form.Element.Methods.coffee); 16 | }); 17 | 18 | test('form', function () { 19 | assert($("form").reset != null); 20 | assert($("form").getInputs().length == 2); 21 | }); 22 | 23 | test('event', function () { 24 | assert($("form").observe != null); 25 | // Can't really test this one with TestUnit... 26 | $('form').observe("submit", function(e) { 27 | alert("yeah!"); 28 | Event.stop(e); 29 | }); 30 | }); 31 | 32 | test('collections', function () { 33 | assert($$("input").all(function(input) { 34 | return (input.focus != null); 35 | })); 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /test/unit/tests/event_handler.test.js: -------------------------------------------------------------------------------- 1 | 2 | function handle(selector, callback) { 3 | if (!callback) { 4 | callback = selector; 5 | selector = false; 6 | } 7 | return new Event.Handler("event-handler-container", "test:event", selector, callback); 8 | } 9 | 10 | var handler; 11 | 12 | suite('Event.Handler', function () { 13 | this.name = 'event_handler'; 14 | 15 | teardown(function () { 16 | try { 17 | handler.stop(); 18 | } catch (e) { 19 | } finally { 20 | delete handler; 21 | } 22 | }); 23 | 24 | test('handlers do nothing if #start has not been called', function () { 25 | var fired = false; 26 | handler = handle(function() { fired = true; }); 27 | 28 | $("event-handler-container").fire("test:event"); 29 | assert(!fired); 30 | }); 31 | 32 | test('handlers are fired when #start is called', function () { 33 | var fired = false; 34 | handler = handle(function() { fired = true; }); 35 | 36 | handler.start(); 37 | assert(!fired); 38 | $("event-handler-container").fire("test:event"); 39 | assert(fired); 40 | }); 41 | 42 | test('handlers do not fire after starting, then stopping', function () { 43 | var fired = 0; 44 | handler = handle(function() { fired++; }); 45 | 46 | handler.start(); 47 | assert.equal(0, fired); 48 | $("event-handler-container").fire("test:event"); 49 | assert.equal(1, fired); 50 | handler.stop(); 51 | $("event-handler-container").fire("test:event"); 52 | assert.equal(1, fired); 53 | }); 54 | 55 | test('handlers without selectors pass the target element to callbacks', function () { 56 | var span = $("event-handler-container").down("span"); 57 | handler = handle(function(event, element) { 58 | assert.equal(span, element); 59 | }.bind(this)); 60 | 61 | handler.start(); 62 | span.fire("test:event"); 63 | }); 64 | 65 | test('handlers with selectors pass the matched element to callbacks', function () { 66 | var link = $("event-handler-container").down("a"), span = link.down("span"); 67 | handler = handle("a", function(event, element) { 68 | assert.equal(link, element); 69 | }.bind(this)); 70 | 71 | handler.start(); 72 | span.fire("test:event"); 73 | }); 74 | 75 | test('handlers with selectors do not call the callback if no matching element is called', function () { 76 | var paragraph = $("event-handler-container").down("p", 1), fired = false; 77 | handler = handle("a", function(event, element) { fired = true; }); 78 | 79 | handler.start(); 80 | paragraph.fire("test:event"); 81 | assert(!fired); 82 | }); 83 | 84 | test('handler callbacks are bound to the original element', function () { 85 | var span = $("event-handler-container").down("span"), element; 86 | handler = handle(function() { element = this; }); 87 | 88 | handler.start(); 89 | span.fire("test:event"); 90 | assert.equal($("event-handler-container"), element); 91 | }); 92 | 93 | test('calling start multiple times does not install multiple observers', function () { 94 | var fired = 0; 95 | handler = handle(function() { fired++; }); 96 | 97 | handler.start(); 98 | handler.start(); 99 | $("event-handler-container").fire("test:event"); 100 | assert.equal(1, fired); 101 | }); 102 | 103 | }); 104 | -------------------------------------------------------------------------------- /test/unit/tests/form.test.js: -------------------------------------------------------------------------------- 1 | 2 | suite('Form', function () { 3 | this.timeout(5000); 4 | this.name = 'form'; 5 | 6 | setup(function () { 7 | // $$('div.form-tests form').invoke('reset'); 8 | $$('div.form-tests form').each(function (f) { 9 | Form.reset(f); 10 | }); 11 | // for some reason, hidden value does not reset 12 | $('form-test-bigform')['tf_hidden'].value = ''; 13 | }); 14 | 15 | test('$F', function () { 16 | assert.equal("4", $F('input_enabled')); 17 | }); 18 | 19 | test('reset', function () { 20 | assert(!Object.isUndefined(Form.reset('form-test-form').reset)); 21 | }); 22 | 23 | test('Element.EventObserver', function () { 24 | var callbackCounter = 0; 25 | var observer = new Form.Element.EventObserver('input_enabled', function () { 26 | callbackCounter++; 27 | }); 28 | 29 | assert.equal(0, callbackCounter, 'callbacks should be 0'); 30 | 31 | $('input_enabled').value = 'boo!'; 32 | 33 | // Can't test the event directly, so we simulate it. 34 | observer.onElementEvent(); 35 | 36 | assert.equal(1, callbackCounter, 'callbacks should be 1'); 37 | }); 38 | 39 | test('Element.Observer', function (done) { 40 | var timedCounter = 0; 41 | 42 | // FIRST: Test a regular field. 43 | var observer = new Form.Element.Observer('input_enabled', 0.5, function () { 44 | ++timedCounter; 45 | }); 46 | 47 | assert.equal(0, timedCounter); 48 | 49 | // Test it doesn't change on first check. 50 | setTimeout(function () { 51 | assert.equal(0, timedCounter); 52 | 53 | // Change it, ensure it hasn't changed immediately. 54 | $('input_enabled').value = 'yowza!'; 55 | assert.equal(0, timedCounter); 56 | 57 | // Ensure it changes on next check, but not again on the next. 58 | setTimeout(function () { 59 | assert.equal(1, timedCounter); 60 | setTimeout(function () { 61 | assert.equal(1, timedCounter); 62 | done(); 63 | 64 | }, 550); 65 | }, 550); 66 | }, 550); 67 | }); 68 | 69 | test('Element.Observer with multi-select', function (done) { 70 | // SECOND: Test a multiple-select. 71 | [1, 2, 3].each(function (index) { 72 | $('multiSel1_opt' + index).selected = (1 === index); 73 | }); 74 | 75 | var timedCounter = 0; 76 | var observer = new Form.Element.Observer('multiSel1', 0.5, function () { 77 | ++timedCounter; 78 | }); 79 | 80 | assert.equal(0, timedCounter); 81 | 82 | // Test it doesn't change on first check. 83 | setTimeout(function () { 84 | assert.equal(0, timedCounter, 'before we change it'); 85 | 86 | // Change it, ensure it hasn't changed immediately. 87 | // NOTE: it is important that the 3rd be re-selected, for the 88 | // serialize form to obtain the expected value :-) 89 | $('multiSel1_opt3').selected = true; 90 | assert.equal(0, timedCounter, "while we're changing it"); 91 | 92 | // Ensure it changes on next check, but not again on the next. 93 | setTimeout(function () { 94 | assert.equal(1, timedCounter, "just after we've changed it"); 95 | setTimeout(function () { 96 | assert.equal(1, timedCounter, "long after we've changed it"); 97 | done(); 98 | }, 550); 99 | }, 550); 100 | }, 550); 101 | }); 102 | 103 | test('Observer', function (done) { 104 | var timedCounter = 0; 105 | 106 | // Should work the same way as Form.Element.Observer; 107 | var observer = new Form.Observer('form-test-form', 0.5, function (form, value) { 108 | ++timedCounter; 109 | }); 110 | 111 | assert.equal(0, timedCounter); 112 | 113 | // Test it doesn't change on first check. 114 | wait(550, done, function () { 115 | assert.equal(0, timedCounter, 'callbacks should be 0'); 116 | 117 | // Change it, ensure it hasn't changed immediately. 118 | $('input_enabled').value = 'yowza!'; 119 | assert.equal(0, timedCounter, 'callbacks should be 0 still'); 120 | 121 | wait(550, done, function () { 122 | assert.equal(1, timedCounter, 'callbacks should be 1'); 123 | 124 | wait(550, done, function () { 125 | assert.equal(1, timedCounter, 'callbacks should be 1 still'); 126 | observer.stop(); 127 | done(); 128 | }); 129 | }); 130 | }); 131 | 132 | }); 133 | 134 | test('enabling forms', function () { 135 | var form = $('form-test-bigform'); 136 | var input1 = $('dummy_disabled'); 137 | var input2 = $('focus_text'); 138 | 139 | assert.disabled(input1); 140 | assert.enabled(input2); 141 | 142 | form.disable(); 143 | assert.disabled(input1, input2); 144 | 145 | form.enable(); 146 | assert.enabled(input1, input2); 147 | 148 | input1.disable(); 149 | assert.disabled(input1); 150 | 151 | // Non-form elements 152 | var fieldset = $('selects_fieldset'); 153 | var fields = fieldset.immediateDescendants(); 154 | 155 | fields.each(function (select) { assert.enabled(select); }); 156 | 157 | Form.disable(fieldset); 158 | fields.each(function (select) { assert.disabled(select); }); 159 | 160 | Form.enable(fieldset); 161 | fields.each(function (select) { assert.enabled(select); }); 162 | }); 163 | 164 | test('enabling elements', function () { 165 | var field = $('input_disabled'); 166 | field.enable(); 167 | assert.enabled(field); 168 | field.disable(); 169 | assert.disabled(field); 170 | 171 | field = $('input_enabled'); 172 | assert.enabled(field); 173 | field.disable(); 174 | assert.disabled(field); 175 | field.enable(); 176 | assert.enabled(field); 177 | }); 178 | 179 | test('activating forms', function () { 180 | function getSelection (element) { 181 | try { 182 | if (typeof element.selectionStart === 'number') { 183 | return element.value.substring(element.selectionStart, 184 | element.selectionEnd); 185 | } else if (document.selection && document.selection.createRange) { 186 | return document.selection.createRange().text; 187 | } 188 | } catch (e) { 189 | return null; 190 | } 191 | } 192 | 193 | var element = Form.findFirstElement('form-test-bigform'); 194 | assert.equal('submit', element.id, 195 | 'Form.findFirstElement should skip disabled elements'); 196 | 197 | Form.focusFirstElement('form-test-bigform'); 198 | assert.equal($('submit'), document.activeElement, 'active element'); 199 | if (document.selection) { 200 | assert.equal('', getSelection(element), 201 | "shouldn't select text on buttons"); 202 | } 203 | 204 | element = $('focus_text'); 205 | assert.equal('', getSelection(element), 206 | "shouldn't select text on buttons"); 207 | 208 | element.activate(); 209 | assert.equal('Hello', getSelection(element), 210 | "Form.Element.activate should select text on text fields"); 211 | 212 | assert.nothingRaised(function () { 213 | $('form_focus_hidden').focusFirstElement(); 214 | }, "Form.Element.activate shouldn't raise an exception when the form or field is hidden"); 215 | 216 | assert.nothingRaised(function () { 217 | $('form_empty').focusFirstElement(); 218 | }, "Form.focusFirstElement shouldn't raise an exception when the form has no fields"); 219 | 220 | }); 221 | 222 | test('getElements', function () { 223 | var elements = Form.getElements('various'); 224 | var names = $w('tf_selectOne tf_textarea tf_checkbox tf_selectMany tf_text tf_radio tf_hidden tf_password tf_button'); 225 | 226 | assert.enumEqual(names, elements.pluck('name')); 227 | }); 228 | 229 | test('getInputs', function () { 230 | var form = $('form-test-form'); 231 | [form.getInputs(), Form.getInputs(form)].each(function (inputs) { 232 | assert.equal(inputs.length, 5); 233 | assert.isInstanceOf(inputs, Array); 234 | assert(inputs.all(function (input) { return input.tagName === 'INPUT'; })); 235 | }); 236 | }); 237 | 238 | test('findFirstElement', function () { 239 | assert.equal($('ffe_checkbox'), $('ffe').findFirstElement()); 240 | assert.equal($('ffe_ti_submit'), $('ffe_ti').findFirstElement()); 241 | assert.equal($('ffe_ti2_checkbox'), $('ffe_ti2').findFirstElement()); 242 | }); 243 | 244 | test('serialize', function () { 245 | // Form is initially empty. 246 | var form = $('form-test-bigform'); 247 | var expected = { 248 | tf_selectOne: '', 249 | tf_textarea: '', 250 | tf_text: '', 251 | tf_hidden: '', 252 | tf_password: '', 253 | tf_button: '' 254 | }; 255 | 256 | assert.deepEqual(expected, Form.serialize('various', true)); 257 | 258 | // set up some stuff 259 | form['tf_selectOne'].selectedIndex = 1; 260 | form['tf_textarea'].value = "boo hoo!"; 261 | form['tf_text'].value = "123öäü"; 262 | form['tf_hidden'].value = "moo%hoo&test"; 263 | form['tf_password'].value = 'sekrit code'; 264 | form['tf_button'].value = 'foo bar'; 265 | form['tf_checkbox'].checked = true; 266 | form['tf_radio'].checked = true; 267 | 268 | expected = { 269 | tf_selectOne: 1, tf_textarea: "boo hoo!", 270 | tf_text: "123öäü", 271 | tf_hidden: "moo%hoo&test", 272 | tf_password: 'sekrit code', 273 | tf_button: 'foo bar', 274 | tf_checkbox: 'on', 275 | tf_radio: 'on' 276 | }; 277 | 278 | // return params 279 | assert.deepEqual(expected, Form.serialize('various', true), 280 | "test the whole form (as a hash)"); 281 | 282 | // return string 283 | assert.deepEqual( 284 | Object.toQueryString(expected).split('&').sort(), 285 | Form.serialize('various').split('&').sort(), 286 | "test the whole form (as a string)" 287 | ); 288 | 289 | 290 | }); 291 | 292 | }); -------------------------------------------------------------------------------- /test/unit/tests/function.test.js: -------------------------------------------------------------------------------- 1 | var arg1 = 1; 2 | var arg2 = 2; 3 | var arg3 = 3; 4 | function TestObj() { }; 5 | TestObj.prototype.assertingEventHandler = 6 | function(event, assertEvent, assert1, assert2, assert3, a1, a2, a3) { 7 | assertEvent(event); 8 | assert1(a1); 9 | assert2(a2); 10 | assert3(a3); 11 | }; 12 | 13 | var globalBindTest = null; 14 | 15 | /// 16 | 17 | var undefined; 18 | 19 | suite('Function', function () { 20 | 21 | this.name = 'function'; 22 | 23 | test('#argumentNames', function () { 24 | assert.enumEqual([], (function() {}).argumentNames()); 25 | assert.enumEqual(["one"], (function(one) {}).argumentNames()); 26 | assert.enumEqual(["one", "two", "three"], (function(one, two, three) {}).argumentNames()); 27 | assert.enumEqual(["one", "two", "three"], (function( one , two 28 | , three ) {}).argumentNames()); 29 | assert.equal("$super", (function($super) {}).argumentNames().first()); 30 | function named1() {}; 31 | assert.enumEqual([], named1.argumentNames()); 32 | function named2(one) {}; 33 | assert.enumEqual(["one"], named2.argumentNames()); 34 | function named3(one, two, three) {}; 35 | assert.enumEqual(["one", "two", "three"], named3.argumentNames()); 36 | function named4(one, 37 | two, 38 | 39 | three) {} 40 | assert.enumEqual($w('one two three'), named4.argumentNames()); 41 | function named5(/*foo*/ foo, /* bar */ bar, /*****/ baz) {} 42 | assert.enumEqual($w("foo bar baz"), named5.argumentNames()); 43 | function named6( 44 | /*foo*/ foo, 45 | /**/bar, 46 | /* baz */ /* baz */ baz, 47 | // Skip a line just to screw with the regex... 48 | /* thud */ thud) {} 49 | assert.enumEqual($w("foo bar baz thud"), named6.argumentNames()); 50 | }); 51 | 52 | test('#bind', function () { 53 | function methodWithoutArguments() { return this.hi; } 54 | function methodWithArguments() { return this.hi + ',' + $A(arguments).join(','); } 55 | function methodReturningContext() { return this; } 56 | var func = Prototype.emptyFunction; 57 | var u; 58 | 59 | // We used to test that `bind` without a `context` argument simply 60 | // returns the original function, but this contradicts the ES5 spec. 61 | 62 | assert.notStrictEqual(func, func.bind(null)); 63 | 64 | assert.equal('without', methodWithoutArguments.bind({ hi: 'without' })()); 65 | assert.equal('with,arg1,arg2', methodWithArguments.bind({ hi: 'with' })('arg1','arg2')); 66 | assert.equal('withBindArgs,arg1,arg2', 67 | methodWithArguments.bind({ hi: 'withBindArgs' }, 'arg1', 'arg2')()); 68 | assert.equal('withBindArgsAndArgs,arg1,arg2,arg3,arg4', 69 | methodWithArguments.bind({ hi: 'withBindArgsAndArgs' }, 'arg1', 'arg2')('arg3', 'arg4')); 70 | 71 | assert.equal(window, methodReturningContext.bind(null)(), 'null has window as its context'); 72 | assert.equal(window, methodReturningContext.bind(u)(), 'undefined has window as its context'); 73 | assert.equal('', methodReturningContext.bind('')(), 'other falsy values should not have window as their context'); 74 | 75 | 76 | // Ensure that bound functions ignore their `context` when used as 77 | // constructors. Taken from example at: 78 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind 79 | function Point(x, y) { 80 | this.x = x; 81 | this.y = y; 82 | } 83 | 84 | Point.prototype.toString = function() { 85 | return this.x + "," + this.y; 86 | }; 87 | 88 | var p = new Point(1, 2); 89 | p.toString(); // "1,2" 90 | 91 | var emptyObj = {}; 92 | var YAxisPoint = Point.bind(emptyObj, 0 /* x */); 93 | 94 | var axisPoint = new YAxisPoint(5); 95 | axisPoint.toString(); // "0,5" 96 | 97 | assert.equal("0,5", axisPoint.toString(), 98 | "bound constructor should ignore context and curry properly"); 99 | 100 | assert(axisPoint instanceof Point, 101 | "should be an instance of Point"); 102 | assert(axisPoint instanceof YAxisPoint, 103 | "should be an instance of YAxisPoint"); 104 | }); 105 | 106 | test('#curry', function () { 107 | var split = function(delimiter, string) { return string.split(delimiter); }; 108 | var splitOnColons = split.curry(":"); 109 | assert.notStrictEqual(split, splitOnColons); 110 | assert.enumEqual(split(":", "0:1:2:3:4:5"), splitOnColons("0:1:2:3:4:5")); 111 | assert.strictEqual(split, split.curry()); 112 | }); 113 | 114 | test('#delay', function (done) { 115 | window.delayed = undefined; 116 | var delayedFunction = function() { window.delayed = true; }; 117 | var delayedFunctionWithArgs = function() { window.delayedWithArgs = $A(arguments).join(' '); }; 118 | delayedFunction.delay(0.8); 119 | delayedFunctionWithArgs.delay(0.8, 'hello', 'world'); 120 | assert.isUndefined(window.delayed); 121 | wait(1000, done, function() { 122 | assert(window.delayed); 123 | assert.equal('hello world', window.delayedWithArgs); 124 | done(); 125 | }); 126 | }); 127 | 128 | test('#wrap', function () { 129 | function sayHello(){ 130 | return 'hello world'; 131 | }; 132 | 133 | assert.equal('HELLO WORLD', sayHello.wrap(function(proceed) { 134 | return proceed().toUpperCase(); 135 | })()); 136 | 137 | var temp = String.prototype.capitalize; 138 | String.prototype.capitalize = String.prototype.capitalize.wrap(function(proceed, eachWord) { 139 | if(eachWord && this.include(' ')) return this.split(' ').map(function(str){ 140 | return str.capitalize(); 141 | }).join(' '); 142 | return proceed(); 143 | }); 144 | assert.equal('Hello world', 'hello world'.capitalize()); 145 | assert.equal('Hello World', 'hello world'.capitalize(true)); 146 | assert.equal('Hello', 'hello'.capitalize()); 147 | String.prototype.capitalize = temp; 148 | }); 149 | 150 | test('#defer', function (done) { 151 | window.deferred = undefined; 152 | var deferredFunction = function() { window.deferred = true; }; 153 | deferredFunction.defer(); 154 | assert.isUndefined(window.deferred); 155 | wait(50, done, function() { 156 | assert(window.deferred); 157 | 158 | window.deferredValue = 0; 159 | var deferredFunction2 = function(arg) { window.deferredValue = arg; }; 160 | deferredFunction2.defer('test'); 161 | wait(50, done, function() { 162 | assert.equal('test', window.deferredValue); 163 | 164 | window.deferBoundProperly = false; 165 | 166 | var obj = { foo: 'bar' }; 167 | var func = function() { 168 | if ('foo' in this) window.deferBoundProperly = true; 169 | }; 170 | 171 | func.bind(obj).defer(); 172 | 173 | wait(50, done, function() { 174 | assert(window.deferBoundProperly, 175 | "deferred bound functions should preserve original scope"); 176 | 177 | window.deferBoundProperlyOnString = false; 178 | var str = "" 179 | 180 | str.evalScripts.bind(str).defer(); 181 | 182 | wait(50, done, function() { 183 | assert(window.deferBoundProperlyOnString); 184 | done(); 185 | }); 186 | }); 187 | }); 188 | }); 189 | }); 190 | 191 | test('#methodize', function () { 192 | var Foo = { bar: function(baz) { return baz; } }; 193 | var baz = { quux: Foo.bar.methodize() }; 194 | 195 | assert.equal(Foo.bar.methodize(), baz.quux); 196 | assert.equal(baz, Foo.bar(baz)); 197 | assert.equal(baz, baz.quux()); 198 | }); 199 | 200 | test('#bindAsEventListener', function () { 201 | for (var i = 0; i < 10; ++i){ 202 | var div = document.createElement('div'); 203 | div.setAttribute('id','test-'+i); 204 | document.body.appendChild(div); 205 | var tobj = new TestObj(); 206 | var eventTest = { test: true }; 207 | var call = tobj.assertingEventHandler.bindAsEventListener(tobj, 208 | assert.equal.bind(assert, eventTest), 209 | assert.equal.bind(assert, arg1), 210 | assert.equal.bind(assert, arg2), 211 | assert.equal.bind(assert, arg3), arg1, arg2, arg3 ); 212 | call(eventTest); 213 | } 214 | }); 215 | 216 | }); 217 | -------------------------------------------------------------------------------- /test/unit/tests/hash.test.js: -------------------------------------------------------------------------------- 1 | Fixtures.Hash = { 2 | one: { a: 'A#' }, 3 | 4 | many: { 5 | a: 'A', 6 | b: 'B', 7 | c: 'C', 8 | d: 'D#' 9 | }, 10 | 11 | functions: { 12 | quad: function(n) { return n*n; }, 13 | plus: function(n) { return n+n; } 14 | }, 15 | 16 | multiple: { color: $w('r g b') }, 17 | multiple_nil: { color: ['r', null, 'g', undefined, 0] }, 18 | multiple_all_nil: { color: [null, undefined] }, 19 | multiple_empty: { color: [] }, 20 | multiple_special: { 'stuff[]': $w('$ a ;') }, 21 | 22 | value_undefined: { a:"b", c:undefined }, 23 | value_null: { a:"b", c:null }, 24 | value_zero: { a:"b", c:0 } 25 | }; 26 | 27 | 28 | /// 29 | 30 | 31 | suite('Hash', function () { 32 | this.name = 'hash'; 33 | 34 | test('#set', function () { 35 | var h = $H({a: 'A'}); 36 | 37 | assert.equal('B', h.set('b', 'B')); 38 | assert.hashEqual({a: 'A', b: 'B'}, h); 39 | 40 | assert.isUndefined(h.set('c')); 41 | assert.hashEqual({a: 'A', b: 'B', c: undefined}, h); 42 | }); 43 | 44 | test('#get', function () { 45 | var h = $H({a: 'A'}); 46 | assert.equal('A', h.get('a')); 47 | assert.isUndefined(h.a); 48 | assert.isUndefined($H({}).get('a')); 49 | 50 | assert.isUndefined($H({}).get('toString')); 51 | assert.isUndefined($H({}).get('constructor')); 52 | }); 53 | 54 | test('#unset', function () { 55 | var hash = $H(Fixtures.Hash.many); 56 | assert.equal('B', hash.unset('b')); 57 | assert.hashEqual({a:'A', c: 'C', d:'D#'}, hash); 58 | assert.isUndefined(hash.unset('z')); 59 | assert.hashEqual({a:'A', c: 'C', d:'D#'}, hash); 60 | // not equivalent to Hash#remove 61 | assert.equal('A', hash.unset('a', 'c')); 62 | assert.hashEqual({c: 'C', d:'D#'}, hash); 63 | }); 64 | 65 | test('#toObject', function () { 66 | var hash = $H(Fixtures.Hash.many), object = hash.toObject(); 67 | assert.isInstanceOf(object, Object); 68 | assert.hashEqual(Fixtures.Hash.many, object); 69 | assert.notStrictEqual(Fixtures.Hash.many, object); 70 | hash.set('foo', 'bar'); 71 | assert.hashNotEqual(object, hash.toObject()); 72 | }); 73 | 74 | test('new Hash', function () { 75 | var object = Object.clone(Fixtures.Hash.one); 76 | var h = new Hash(object), h2 = $H(object); 77 | assert.isInstanceOf(h, Hash); 78 | assert.isInstanceOf(h2, Hash); 79 | 80 | assert.hashEqual({}, new Hash()); 81 | assert.hashEqual(object, h); 82 | assert.hashEqual(object, h2); 83 | 84 | h.set('foo', 'bar'); 85 | assert.hashNotEqual(object, h); 86 | 87 | var clone = $H(h); 88 | assert.isInstanceOf(clone, Hash); 89 | assert.hashEqual(h, clone); 90 | h.set('foo', 'foo'); 91 | assert.hashNotEqual(h, clone); 92 | assert.strictEqual($H, Hash.from); 93 | }); 94 | 95 | test('#keys', function () { 96 | assert.enumEqual([], $H({}).keys()); 97 | assert.enumEqual(['a'], $H(Fixtures.Hash.one).keys()); 98 | assert.enumEqual($w('a b c d'), $H(Fixtures.Hash.many).keys().sort()); 99 | assert.enumEqual($w('plus quad'), $H(Fixtures.Hash.functions).keys().sort()); 100 | }); 101 | 102 | test('#values', function () { 103 | assert.enumEqual([], $H({}).values()); 104 | assert.enumEqual(['A#'], $H(Fixtures.Hash.one).values()); 105 | assert.enumEqual($w('A B C D#'), $H(Fixtures.Hash.many).values().sort()); 106 | assert.enumEqual($w('function function'), 107 | $H(Fixtures.Hash.functions).values().map(function(i){ return typeof i; })); 108 | assert.equal(9, $H(Fixtures.Hash.functions).get('quad')(3)); 109 | assert.equal(6, $H(Fixtures.Hash.functions).get('plus')(3)); 110 | }); 111 | 112 | test('#index', function () { 113 | assert.isUndefined($H().index('foo')); 114 | 115 | assert('a', $H(Fixtures.Hash.one).index('A#')); 116 | assert('a', $H(Fixtures.Hash.many).index('A')); 117 | assert.isUndefined($H(Fixtures.Hash.many).index('Z')); 118 | 119 | var hash = $H({a:1,b:'2',c:1}); 120 | assert(['a','c'].include(hash.index(1))); 121 | assert.isUndefined(hash.index('1')); 122 | }); 123 | 124 | test('#merge', function () { 125 | var h = $H(Fixtures.Hash.many); 126 | assert.notStrictEqual(h, h.merge()); 127 | assert.notStrictEqual(h, h.merge({})); 128 | assert.isInstanceOf(h.merge(), Hash); 129 | assert.isInstanceOf(h.merge({}), Hash); 130 | assert.hashEqual(h, h.merge()); 131 | assert.hashEqual(h, h.merge({})); 132 | assert.hashEqual(h, h.merge($H())); 133 | assert.hashEqual({a:'A', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.merge({aaa: 'AAA'})); 134 | assert.hashEqual({a:'A#', b:'B', c:'C', d:'D#' }, h.merge(Fixtures.Hash.one)); 135 | }); 136 | 137 | test('#update', function () { 138 | var h = $H(Fixtures.Hash.many); 139 | assert.strictEqual(h, h.update()); 140 | assert.strictEqual(h, h.update({})); 141 | assert.hashEqual(h, h.update()); 142 | assert.hashEqual(h, h.update({})); 143 | assert.hashEqual(h, h.update($H())); 144 | assert.hashEqual({a:'A', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.update({aaa: 'AAA'})); 145 | assert.hashEqual({a:'A#', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.update(Fixtures.Hash.one)); 146 | }); 147 | 148 | test('#toQueryString', function () { 149 | assert.equal('', $H({}).toQueryString()); 150 | assert.equal('a%23=A', $H({'a#': 'A'}).toQueryString()); 151 | assert.equal('a=A%23', $H(Fixtures.Hash.one).toQueryString()); 152 | assert.equal('a=A&b=B&c=C&d=D%23', $H(Fixtures.Hash.many).toQueryString()); 153 | assert.equal("a=b&c", $H(Fixtures.Hash.value_undefined).toQueryString()); 154 | assert.equal("a=b&c", $H("a=b&c".toQueryParams()).toQueryString()); 155 | assert.equal("a=b+d&c", $H("a=b+d&c".toQueryParams()).toQueryString()); 156 | assert.equal("a=b&c=", $H(Fixtures.Hash.value_null).toQueryString()); 157 | assert.equal("a=b&c=0", $H(Fixtures.Hash.value_zero).toQueryString()); 158 | assert.equal("color=r&color=g&color=b", $H(Fixtures.Hash.multiple).toQueryString()); 159 | assert.equal("color=r&color=&color=g&color&color=0", $H(Fixtures.Hash.multiple_nil).toQueryString()); 160 | assert.equal("color=&color", $H(Fixtures.Hash.multiple_all_nil).toQueryString()); 161 | assert.equal("", $H(Fixtures.Hash.multiple_empty).toQueryString()); 162 | assert.equal("", $H({foo: {}, bar: {}}).toQueryString()); 163 | assert.equal("stuff%5B%5D=%24&stuff%5B%5D=a&stuff%5B%5D=%3B", $H(Fixtures.Hash.multiple_special).toQueryString()); 164 | assert.hashEqual(Fixtures.Hash.multiple_special, $H(Fixtures.Hash.multiple_special).toQueryString().toQueryParams()); 165 | assert.strictEqual(Object.toQueryString, Hash.toQueryString); 166 | 167 | // Serializing newlines and spaces is weird. See: 168 | // http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.13.4.1 169 | var complex = "an arbitrary line\n\'something in single quotes followed by a newline\'\r\n" + 170 | "and more text eventually"; 171 | var queryString = $H({ val: complex }).toQueryString(); 172 | var expected = "val=an+arbitrary+line%0D%0A'something+in+single+quotes+followed+by+a+" + 173 | "newline'%0D%0Aand+more+text+eventually"; 174 | assert.equal(expected, queryString, "newlines and spaces should be properly encoded"); 175 | }); 176 | 177 | test('#inspect', function () { 178 | assert.equal('#', $H({}).inspect()); 179 | assert.equal("#", $H(Fixtures.Hash.one).inspect()); 180 | assert.equal("#", $H(Fixtures.Hash.many).inspect()); 181 | }); 182 | 183 | test('#clone', function () { 184 | var h = $H(Fixtures.Hash.many); 185 | assert.hashEqual(h, h.clone()); 186 | assert.isInstanceOf(h.clone(), Hash); 187 | assert.notStrictEqual(h, h.clone()); 188 | }); 189 | 190 | test('#toJSON', function () { 191 | assert.equal('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', 192 | Object.toJSON({b: [undefined, false, true, undefined], c: {a: 'hello!'}})); 193 | }); 194 | 195 | test('ability to contain any key', function () { 196 | var h = $H({ _each: 'E', map: 'M', keys: 'K', pluck: 'P', unset: 'U' }); 197 | assert.enumEqual($w('_each keys map pluck unset'), h.keys().sort()); 198 | assert.equal('U', h.unset('unset')); 199 | assert.hashEqual({ _each: 'E', map: 'M', keys: 'K', pluck: 'P' }, h); 200 | }); 201 | 202 | test('#toTemplateReplacements', function () { 203 | var template = new Template("#{a} #{b}"), hash = $H({ a: "hello", b: "world" }); 204 | assert.equal("hello world", template.evaluate(hash.toObject())); 205 | assert.equal("hello world", template.evaluate(hash)); 206 | assert.equal("hello", "#{a}".interpolate(hash)); 207 | }); 208 | 209 | test("don't iterate over shadowed properties", function () { 210 | // redundant now that object is systematically cloned. 211 | var FooMaker = function(value) { 212 | this.key = value; 213 | }; 214 | FooMaker.prototype.key = 'foo'; 215 | var foo = new FooMaker('bar'); 216 | assert.equal("key=bar", new Hash(foo).toQueryString()); 217 | assert.equal("key=bar", new Hash(new Hash(foo)).toQueryString()); 218 | }); 219 | 220 | test('#each', function () { 221 | var h = $H({a:1, b:2}); 222 | var result = []; 223 | h.each(function(kv, i){ 224 | result.push(i); 225 | }); 226 | assert.enumEqual([0,1], result); 227 | }); 228 | 229 | 230 | }); 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /test/unit/tests/number.test.js: -------------------------------------------------------------------------------- 1 | 2 | suite('Number', function () { 3 | this.name = 'number'; 4 | 5 | test('math methods', function () { 6 | assert.equal(1, (0.9).round()); 7 | assert.equal(-2, (-1.9).floor()); 8 | assert.equal(-1, (-1.9).ceil()); 9 | 10 | $w('abs floor round ceil').each(function(method) { 11 | assert.equal(Math[method](Math.PI), Math.PI[method]()); 12 | }, this); 13 | }); 14 | 15 | test('#toColorPart', function () { 16 | assert.equal('00', (0).toColorPart()); 17 | assert.equal('0a', (10).toColorPart()); 18 | assert.equal('ff', (255).toColorPart()); 19 | }); 20 | 21 | test('#toPaddedString', function () { 22 | assert.equal('00', (0).toPaddedString(2, 16)); 23 | assert.equal('0a', (10).toPaddedString(2, 16)); 24 | assert.equal('ff', (255).toPaddedString(2, 16)); 25 | assert.equal('000', (0).toPaddedString(3)); 26 | assert.equal('010', (10).toPaddedString(3)); 27 | assert.equal('100', (100).toPaddedString(3)); 28 | assert.equal('1000', (1000).toPaddedString(3)); 29 | }); 30 | 31 | test('#times', function () { 32 | var results = []; 33 | (5).times(function(i) { results.push(i); }); 34 | assert.enumEqual($R(0, 4), results); 35 | 36 | results = []; 37 | (5).times(function(i) { results.push(i * this.i); }, { i: 2 }); 38 | assert.enumEqual([0, 2, 4, 6, 8], results); 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /test/unit/tests/object.test.js: -------------------------------------------------------------------------------- 1 | var Person = function(name){ 2 | this.name = name; 3 | }; 4 | 5 | Person.prototype.toJSON = function() { 6 | return '-' + this.name; 7 | }; 8 | 9 | /// 10 | 11 | suite('Object', function () { 12 | this.name = 'object'; 13 | 14 | test('.extend', function () { 15 | var object = {foo: 'foo', bar: [1, 2, 3]}; 16 | assert.strictEqual(object, Object.extend(object)); 17 | assert.hashEqual({foo: 'foo', bar: [1, 2, 3]}, object); 18 | assert.strictEqual(object, Object.extend(object, {bla: 123})); 19 | assert.hashEqual({foo: 'foo', bar: [1, 2, 3], bla: 123}, object); 20 | assert.hashEqual({foo: 'foo', bar: [1, 2, 3], bla: null}, 21 | Object.extend(object, {bla: null})); 22 | }); 23 | 24 | test('.toQueryString', function () { 25 | assert.equal('a=A&b=B&c=C&d=D%23', Object.toQueryString({a: 'A', b: 'B', c: 'C', d: 'D#'})); 26 | }); 27 | 28 | test('.clone', function () { 29 | var object = {foo: 'foo', bar: [1, 2, 3]}; 30 | assert.notStrictEqual(object, Object.clone(object)); 31 | assert.hashEqual(object, Object.clone(object)); 32 | assert.hashEqual({}, Object.clone()); 33 | var clone = Object.clone(object); 34 | delete clone.bar; 35 | assert.hashEqual({foo: 'foo'}, clone, 36 | "Optimizing Object.clone perf using prototyping doesn't allow properties to be deleted."); 37 | }); 38 | 39 | test('.keys', function () { 40 | assert.enumEqual([], Object.keys({})); 41 | assert.enumEqual(['bar', 'foo'], Object.keys({foo: 'foo', bar: 'bar'}).sort()); 42 | function Foo() { this.bar = 'bar'; } 43 | Foo.prototype.foo = 'foo'; 44 | assert.enumEqual(['bar'], Object.keys(new Foo())); 45 | assert.raise('TypeError', function(){ Object.keys(); }); 46 | 47 | var obj = { 48 | foo: 'bar', 49 | baz: 'thud', 50 | toString: function() { return '1'; }, 51 | valueOf: function() { return 1; } 52 | }; 53 | 54 | assert.equal(4, Object.keys(obj).length, 'DontEnum properties should be included in Object.keys'); 55 | }); 56 | 57 | test('.inspect', function () { 58 | assert.equal('undefined', Object.inspect()); 59 | assert.equal('undefined', Object.inspect(undefined)); 60 | assert.equal('null', Object.inspect(null)); 61 | assert.equal("'foo\\\\b\\\'ar'", Object.inspect('foo\\b\'ar')); 62 | assert.equal('[]', Object.inspect([])); 63 | assert.nothingRaised(function() { Object.inspect(window.Node); }); 64 | }); 65 | 66 | test('.toJSON', function () { 67 | assert.isUndefined(Object.toJSON(undefined)); 68 | assert.isUndefined(Object.toJSON(Prototype.K)); 69 | assert.equal('\"\"', Object.toJSON('')); 70 | assert.equal('\"test\"', Object.toJSON('test')); 71 | assert.equal('null', Object.toJSON(Number.NaN)); 72 | assert.equal('0', Object.toJSON(0)); 73 | assert.equal('-293', Object.toJSON(-293)); 74 | assert.equal('[]', Object.toJSON([])); 75 | assert.equal('[\"a\"]', Object.toJSON(['a'])); 76 | assert.equal('[\"a\",1]', Object.toJSON(['a', 1])); 77 | assert.equal('[\"a\",{\"b\":null}]', Object.toJSON(['a', {'b': null}])); 78 | assert.equal('{\"a\":\"hello!\"}', Object.toJSON({a: 'hello!'})); 79 | assert.equal('{}', Object.toJSON({})); 80 | assert.equal('{}', Object.toJSON({a: undefined, b: undefined, c: Prototype.K})); 81 | assert.equal('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', 82 | Object.toJSON({'b': [undefined, false, true, undefined], c: {a: 'hello!'}})); 83 | assert.equal('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', 84 | Object.toJSON($H({'b': [undefined, false, true, undefined], c: {a: 'hello!'}}))); 85 | assert.equal('true', Object.toJSON(true)); 86 | assert.equal('false', Object.toJSON(false)); 87 | assert.equal('null', Object.toJSON(null)); 88 | var sam = new Person('sam'); 89 | assert.equal('"-sam"', Object.toJSON(sam)); 90 | }); 91 | 92 | test('.toHTML', function () { 93 | assert.strictEqual('', Object.toHTML()); 94 | assert.strictEqual('', Object.toHTML('')); 95 | assert.strictEqual('', Object.toHTML(null)); 96 | assert.strictEqual('0', Object.toHTML(0)); 97 | assert.strictEqual('123', Object.toHTML(123)); 98 | assert.equal('hello world', Object.toHTML('hello world')); 99 | assert.equal('hello world', Object.toHTML({toHTML: function() { return 'hello world'; }})); 100 | }); 101 | 102 | test('.isArray', function () { 103 | assert(Object.isArray([])); 104 | assert(Object.isArray([0])); 105 | assert(Object.isArray([0, 1])); 106 | assert(!Object.isArray({})); 107 | assert(!Object.isArray($('object-test-list').childNodes)); 108 | assert(!Object.isArray()); 109 | assert(!Object.isArray('')); 110 | assert(!Object.isArray('foo')); 111 | assert(!Object.isArray(0)); 112 | assert(!Object.isArray(1)); 113 | assert(!Object.isArray(null)); 114 | assert(!Object.isArray(true)); 115 | assert(!Object.isArray(false)); 116 | assert(!Object.isArray(undefined)); 117 | }); 118 | 119 | test('.isHash', function () { 120 | assert(Object.isHash($H())); 121 | assert(Object.isHash(new Hash())); 122 | assert(!Object.isHash({})); 123 | assert(!Object.isHash(null)); 124 | assert(!Object.isHash()); 125 | assert(!Object.isHash('')); 126 | assert(!Object.isHash(2)); 127 | assert(!Object.isHash(false)); 128 | assert(!Object.isHash(true)); 129 | assert(!Object.isHash([])); 130 | }); 131 | 132 | test('.isElement', function () { 133 | assert(Object.isElement(document.createElement('div'))); 134 | assert(Object.isElement(new Element('div'))); 135 | assert(Object.isElement($('object-test'))); 136 | assert(!Object.isElement(document.createTextNode('bla'))); 137 | 138 | // falsy variables should not mess up return value type 139 | assert.strictEqual(false, Object.isElement(0)); 140 | assert.strictEqual(false, Object.isElement('')); 141 | assert.strictEqual(false, Object.isElement(NaN)); 142 | assert.strictEqual(false, Object.isElement(null)); 143 | assert.strictEqual(false, Object.isElement(undefined)); 144 | }); 145 | 146 | test('.isFunction', function () { 147 | assert(Object.isFunction(function() { })); 148 | assert(Object.isFunction(Class.create())); 149 | 150 | assert(!Object.isFunction("a string")); 151 | assert(!Object.isFunction($(document.createElement('div')))); 152 | assert(!Object.isFunction([])); 153 | assert(!Object.isFunction({})); 154 | assert(!Object.isFunction(0)); 155 | assert(!Object.isFunction(false)); 156 | assert(!Object.isFunction(undefined)); 157 | assert(!Object.isFunction(/xyz/), 'regular expressions are not functions'); 158 | }); 159 | 160 | test('.isString', function () { 161 | assert(!Object.isString(function() { })); 162 | assert(Object.isString("a string")); 163 | assert(Object.isString(new String("a string"))); 164 | assert(!Object.isString(0)); 165 | assert(!Object.isString([])); 166 | assert(!Object.isString({})); 167 | assert(!Object.isString(false)); 168 | assert(!Object.isString(undefined)); 169 | assert(!Object.isString(document), 'host objects should return false rather than throw exceptions'); 170 | }); 171 | 172 | test('.isNumber', function () { 173 | assert(Object.isNumber(0)); 174 | assert(Object.isNumber(1.0)); 175 | assert(Object.isNumber(new Number(0))); 176 | assert(Object.isNumber(new Number(1.0))); 177 | assert(!Object.isNumber(function() { })); 178 | assert(!Object.isNumber({ test: function() { return 3; } })); 179 | assert(!Object.isNumber("a string")); 180 | assert(!Object.isNumber([])); 181 | assert(!Object.isNumber({})); 182 | assert(!Object.isNumber(false)); 183 | assert(!Object.isNumber(undefined)); 184 | assert(!Object.isNumber(document), 'host objects should return false rather than throw exceptions'); 185 | }); 186 | 187 | test('.isDate', function () { 188 | var d = new Date(); 189 | assert(Object.isDate(d), 'constructor with no arguments'); 190 | assert(Object.isDate(new Date(0)), 'constructor with milliseconds'); 191 | assert(Object.isDate(new Date(1995, 11, 17)), 'constructor with Y, M, D'); 192 | assert(Object.isDate(new Date(1995, 11, 17, 3, 24, 0)), 'constructor with Y, M, D, H, M, S'); 193 | assert(Object.isDate(new Date(Date.parse("Dec 25, 1995"))), 'constructor with result of Date.parse'); 194 | 195 | assert(!Object.isDate(d.valueOf()), 'Date#valueOf returns a number'); 196 | assert(!Object.isDate(function() { })); 197 | assert(!Object.isDate(0)); 198 | assert(!Object.isDate("a string")); 199 | assert(!Object.isDate([])); 200 | assert(!Object.isDate({})); 201 | assert(!Object.isDate(false)); 202 | assert(!Object.isDate(undefined)); 203 | assert(!Object.isDate(document), 'host objects should return false rather than throw exceptions'); 204 | }); 205 | 206 | test('.isUndefined', function () { 207 | assert(Object.isUndefined(undefined)); 208 | assert(!Object.isUndefined(null)); 209 | assert(!Object.isUndefined(false)); 210 | assert(!Object.isUndefined(0)); 211 | assert(!Object.isUndefined("")); 212 | assert(!Object.isUndefined(function() { })); 213 | assert(!Object.isUndefined([])); 214 | assert(!Object.isUndefined({})); 215 | }); 216 | 217 | test('should not extend Object.prototype', function () { 218 | // for-in is supported with objects 219 | var iterations = 0, obj = { a: 1, b: 2, c: 3 }, property; 220 | for (property in obj) iterations++; 221 | assert.equal(3, iterations); 222 | 223 | // for-in is not supported with arrays 224 | iterations = 0; 225 | var arr = [1,2,3]; 226 | for (property in arr) iterations++; 227 | assert(iterations > 3); 228 | }); 229 | 230 | 231 | }); 232 | -------------------------------------------------------------------------------- /test/unit/tests/periodical_executer.test.js: -------------------------------------------------------------------------------- 1 | 2 | suite('PeriodicalExecuter', function () { 3 | this.name = 'periodical_executer'; 4 | 5 | test('#stop', function (done) { 6 | var peEventCount = 0; 7 | function peEventFired(pe) { 8 | if (++peEventCount > 2) pe.stop(); 9 | } 10 | 11 | // peEventFired will stop the PeriodicalExecuter after 3 callbacks 12 | new PeriodicalExecuter(peEventFired, 0.05); 13 | 14 | wait(600, done, function() { 15 | assert.equal(3, peEventCount); 16 | done(); 17 | }); 18 | }); 19 | 20 | test('#onTimerEvent', function () { 21 | var pe = { 22 | onTimerEvent: PeriodicalExecuter.prototype.onTimerEvent, 23 | execute: function() { 24 | assert(pe.currentlyExecuting); 25 | } 26 | }; 27 | 28 | pe.onTimerEvent(); 29 | assert(!pe.currentlyExecuting); 30 | 31 | pe.execute = function() { 32 | assert(pe.currentlyExecuting); 33 | throw new Error(); 34 | }; 35 | assert.raise('Error', pe.onTimerEvent.bind(pe)); 36 | assert(!pe.currentlyExecuting); 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /test/unit/tests/position.test.js: -------------------------------------------------------------------------------- 1 | 2 | suite('Position', function () { 3 | this.name = 'position'; 4 | 5 | setup(function () { 6 | scrollTo(0, 0); 7 | Position.prepare(); 8 | Position.includeScrollOffsets = false; 9 | }); 10 | 11 | teardown(function () { 12 | scrollTo(0, 0); 13 | Position.prepare(); 14 | Position.includeScrollOffsets = false; 15 | }); 16 | 17 | test('.prepare', function () { 18 | Position.prepare(); 19 | assert.equal(0, Position.deltaX); 20 | assert.equal(0, Position.deltaY); 21 | scrollTo(20, 30); 22 | Position.prepare(); 23 | assert.equal(20, Position.deltaX); 24 | assert.equal(30, Position.deltaY); 25 | }); 26 | 27 | test('.within', function () { 28 | [true, false].each(function(withScrollOffsets) { 29 | Position.includeScrollOffsets = withScrollOffsets; 30 | assert(!Position.within($('position_test_body_absolute'), 9, 9), 'outside left/top'); 31 | assert(Position.within($('position_test_body_absolute'), 10, 10), 'left/top corner'); 32 | assert(Position.within($('position_test_body_absolute'), 10, 19), 'left/bottom corner'); 33 | assert(!Position.within($('position_test_body_absolute'), 10, 20), 'outside bottom'); 34 | }, this); 35 | 36 | scrollTo(20, 30); 37 | Position.prepare(); 38 | Position.includeScrollOffsets = true; 39 | assert(!Position.within($('position_test_body_absolute'), 9, 9), 'outside left/top'); 40 | assert(Position.within($('position_test_body_absolute'), 10, 10), 'left/top corner'); 41 | assert(Position.within($('position_test_body_absolute'), 10, 19), 'left/bottom corner'); 42 | assert(!Position.within($('position_test_body_absolute'), 10, 20), 'outside bottom'); 43 | }); 44 | 45 | 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/tests/prototype.test.js: -------------------------------------------------------------------------------- 1 | 2 | suite('Prototype', function () { 3 | this.name = 'prototype'; 4 | 5 | test('browser detection', function () { 6 | var results = $H(Prototype.Browser).map(function(engine){ 7 | return engine; 8 | }).partition(function(engine){ 9 | return engine[1] === true; 10 | }); 11 | var trues = results[0], falses = results[1]; 12 | 13 | var ua = navigator.userAgent; 14 | 15 | info('User agent string is: ' + ua); 16 | 17 | // It's OK for there to be two true values if we're on MobileSafari, 18 | // since it's also a WebKit browser. 19 | if (Prototype.Browser.MobileSafari) { 20 | assert(trues.size() === 2, 'MobileSafari should also identify as WebKit.'); 21 | } else { 22 | assert(trues.size() === 0 || trues.size() === 1, 23 | 'There should be only one or no browser detected.'); 24 | } 25 | 26 | // we should have definite trues or falses here 27 | trues.each(function(result) { 28 | assert(result[1] === true); 29 | }, this); 30 | falses.each(function(result) { 31 | assert(result[1] === false); 32 | }, this); 33 | 34 | if (ua.indexOf('AppleWebKit/') > -1) { 35 | info('Running on WebKit'); 36 | assert(Prototype.Browser.WebKit); 37 | } 38 | 39 | if (!!window.opera) { 40 | info('Running on Opera'); 41 | assert(Prototype.Browser.Opera); 42 | } 43 | 44 | if (ua.indexOf('MSIE') > -1 && !window.opera) { 45 | info('Running on IE'); 46 | assert(Prototype.Browser.IE); 47 | } 48 | 49 | if (ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') == -1) { 50 | info('Running on Gecko'); 51 | assert(Prototype.Browser.Gecko); 52 | } 53 | }); 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /test/unit/tests/range.test.js: -------------------------------------------------------------------------------- 1 | 2 | suite('Range', function () { 3 | this.name = 'range'; 4 | 5 | test('#include', function () { 6 | assert(!$R(0, 0, true).include(0)); 7 | assert($R(0, 0, false).include(0)); 8 | 9 | assert($R(0, 5, true).include(0)); 10 | assert($R(0, 5, true).include(4)); 11 | assert(!$R(0, 5, true).include(5)); 12 | 13 | assert($R(0, 5, false).include(0)); 14 | assert($R(0, 5, false).include(5)); 15 | assert(!$R(0, 5, false).include(6)); 16 | }); 17 | 18 | test('#each', function () { 19 | var results = []; 20 | $R(0, 0, true).each(function(value) { 21 | results.push(value); 22 | }); 23 | 24 | assert.enumEqual([], results); 25 | 26 | results = []; 27 | $R(0, 3, false).each(function(value) { 28 | results.push(value); 29 | }); 30 | 31 | assert.enumEqual([0, 1, 2, 3], results); 32 | 33 | results = []; 34 | $R(2, 4, true).each(function(value, index) { 35 | results.push(index); 36 | }); 37 | assert.enumEqual([0, 1], results); 38 | }); 39 | 40 | test('#any', function () { 41 | assert(!$R(1, 1, true).any()); 42 | assert($R(0, 3, false).any(function(value) { 43 | return value == 3; 44 | })); 45 | }); 46 | 47 | test('#all', function () { 48 | assert($R(1, 1, true).all()); 49 | assert($R(0, 3, false).all(function(value) { 50 | return value <= 3; 51 | })); 52 | }); 53 | 54 | test('#toArray', function () { 55 | assert.enumEqual([], $R(0, 0, true).toArray()); 56 | assert.enumEqual([0], $R(0, 0, false).toArray()); 57 | assert.enumEqual([0], $R(0, 1, true).toArray()); 58 | assert.enumEqual([0, 1], $R(0, 1, false).toArray()); 59 | assert.enumEqual([-3, -2, -1, 0, 1, 2], $R(-3, 3, true).toArray()); 60 | assert.enumEqual([-3, -2, -1, 0, 1, 2, 3], $R(-3, 3, false).toArray()); 61 | }); 62 | 63 | test('defaults to inclusive', function () { 64 | assert.enumEqual($R(-3,3), $R(-3,3,false)); 65 | }); 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /test/unit/tests/regexp.test.js: -------------------------------------------------------------------------------- 1 | 2 | suite('RegExp', function () { 3 | this.name = 'regexp'; 4 | 5 | test('#escape', function () { 6 | assert.equal('word', RegExp.escape('word')); 7 | assert.equal('\\/slashes\\/', RegExp.escape('/slashes/')); 8 | assert.equal('\\\\backslashes\\\\', RegExp.escape('\\backslashes\\')); 9 | assert.equal('\\\\border of word', RegExp.escape('\\border of word')); 10 | 11 | assert.equal('\\(\\?\\:non-capturing\\)', RegExp.escape('(?:non-capturing)')); 12 | assert.equal('non-capturing', new RegExp(RegExp.escape('(?:') + '([^)]+)').exec('(?:non-capturing)')[1]); 13 | 14 | assert.equal('\\(\\?\\=positive-lookahead\\)', RegExp.escape('(?=positive-lookahead)')); 15 | assert.equal('positive-lookahead', new RegExp(RegExp.escape('(?=') + '([^)]+)').exec('(?=positive-lookahead)')[1]); 16 | 17 | assert.equal('\\(\\?<\\=positive-lookbehind\\)', RegExp.escape('(?<=positive-lookbehind)')); 18 | assert.equal('positive-lookbehind', new RegExp(RegExp.escape('(?<=') + '([^)]+)').exec('(?<=positive-lookbehind)')[1]); 19 | 20 | assert.equal('\\(\\?\\!negative-lookahead\\)', RegExp.escape('(?!negative-lookahead)')); 21 | assert.equal('negative-lookahead', new RegExp(RegExp.escape('(?!') + '([^)]+)').exec('(?!negative-lookahead)')[1]); 22 | 23 | assert.equal('\\(\\?<\\!negative-lookbehind\\)', RegExp.escape('(?', new RegExp(RegExp.escape('
')).exec('
')[0]); 30 | 31 | assert.equal('false', RegExp.escape(false)); 32 | assert.equal('undefined', RegExp.escape()); 33 | assert.equal('null', RegExp.escape(null)); 34 | assert.equal('42', RegExp.escape(42)); 35 | 36 | assert.equal('\\\\n\\\\r\\\\t', RegExp.escape('\\n\\r\\t')); 37 | assert.equal('\n\r\t', RegExp.escape('\n\r\t')); 38 | assert.equal('\\{5,2\\}', RegExp.escape('{5,2}')); 39 | 40 | assert.equal( 41 | '\\/\\(\\[\\.\\*\\+\\?\\^\\=\\!\\:\\$\\{\\}\\(\\)\\|\\[\\\\\\]\\\\\\\/\\\\\\\\\\]\\)\\/g', 42 | RegExp.escape('/([.*+?^=!:${}()|[\\]\\/\\\\])/g') 43 | ); 44 | }); 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /test/unit/tests/selector_engine.test.js: -------------------------------------------------------------------------------- 1 | 2 | suite('Selector engine', function () { 3 | this.name = 'selector_engine'; 4 | 5 | test('.engine', function () { 6 | assert(Prototype.Selector.engine); 7 | }); 8 | 9 | test('.select', function () { 10 | var elements = Prototype.Selector.select('.test_class'); 11 | 12 | assert(Object.isArray(elements)); 13 | assert.equal(2, elements.length); 14 | assert.equal('div_parent', elements[0].id); 15 | assert.equal('div_child', elements[1].id); 16 | }); 17 | 18 | test('.select (with context)', function () { 19 | var elements = Prototype.Selector.select('.test_class', $('div_parent')); 20 | 21 | assert(Object.isArray(elements)); 22 | assert.equal(1, elements.length); 23 | assert.equal('div_child', elements[0].id); 24 | }); 25 | 26 | test('.select (with empty result set)', function () { 27 | var elements = Prototype.Selector.select('.non_existent'); 28 | 29 | assert(Object.isArray(elements)); 30 | assert.equal(0, elements.length); 31 | }); 32 | 33 | test('.match', function () { 34 | var element = $('div_parent'); 35 | 36 | assert.equal(true, Prototype.Selector.match(element, '.test_class')); 37 | assert.equal(false, Prototype.Selector.match(element, '.non_existent')); 38 | }); 39 | 40 | test('.find', function () { 41 | var elements = document.getElementsByTagName('*'), 42 | expression = '.test_class'; 43 | assert.equal('div_parent', Prototype.Selector.find(elements, expression).id); 44 | assert.equal('div_child', Prototype.Selector.find(elements, expression, 1).id); 45 | }); 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 54 | 55 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 78 | 79 | 83 | 84 | <% @suites.each do |suite| %> 85 | 86 | <% end %> 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 | 96 |
97 | 98 |
99 | <%= yield %> 100 |
101 | 102 | 122 | 123 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /test/unit/views/tests.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% @suites.each do |suite| %> 4 | <% if suite_has_html?(suite) %> 5 |
6 | <%= erb(:"tests/#{suite}", :locals => { :name => suite }) %> 7 |
8 | <% end %> 9 | <% end %> -------------------------------------------------------------------------------- /test/unit/views/tests/ajax.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /test/unit/views/tests/array.erb: -------------------------------------------------------------------------------- 1 |
22
2 | -------------------------------------------------------------------------------- /test/unit/views/tests/element_mixins.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /test/unit/views/tests/enumerable.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | -------------------------------------------------------------------------------- /test/unit/views/tests/event.erb: -------------------------------------------------------------------------------- 1 | 4 |
5 | -------------------------------------------------------------------------------- /test/unit/views/tests/event_handler.erb: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /test/unit/views/tests/form.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
This is not a form element
5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |
31 | 36 | 41 | 46 | 47 |
48 | 53 | 58 | 63 |
64 |
65 | 66 |
67 | 68 | 69 | 70 | 71 | 72 |
This is not a form element
73 | 74 | 75 | 76 | 77 |
78 |
79 | 80 | 83 | 84 |
85 | 86 |
87 | 88 |
89 | 90 | 91 |
92 |
93 |

94 | 95 | 96 |
97 | 98 |
99 |

100 | 101 | 102 | 103 |
104 | 105 |
106 |

107 | 108 | 109 | 110 |
111 | 112 |
113 | 114 | 120 | 121 | 124 | 125 |
126 | 127 | 128 |
129 | 130 |
131 | 132 | 133 | 140 | 146 | 147 |
148 |
149 | -------------------------------------------------------------------------------- /test/unit/views/tests/object.erb: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 4 |
  • 5 |
  • 6 |
-------------------------------------------------------------------------------- /test/unit/views/tests/position.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
testtest
7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /test/unit/views/tests/selector.erb: -------------------------------------------------------------------------------- 1 | 89 | -------------------------------------------------------------------------------- /test/unit/views/tests/selector_engine.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
-------------------------------------------------------------------------------- /vendor/legacy_selector/selector_engine.js: -------------------------------------------------------------------------------- 1 | //= require "repository/legacy_selector" 2 | 3 | ;(function(engine) { 4 | function select(selector, scope) { 5 | return engine.findChildElements(scope || document, [selector]); 6 | } 7 | 8 | function match(element, selector) { 9 | return !!engine.findElement([element], selector); 10 | } 11 | 12 | Prototype.Selector.engine = engine; 13 | Prototype.Selector.select = select; 14 | Prototype.Selector.match = match; 15 | })(Prototype.LegacySelector); 16 | -------------------------------------------------------------------------------- /vendor/nwmatcher/selector_engine.js: -------------------------------------------------------------------------------- 1 | Prototype._original_property = window.NW; 2 | //= require "repository/src/nwmatcher" 3 | 4 | ;(function(engine) { 5 | function select(selector, scope) { 6 | return engine.select(selector, scope); 7 | } 8 | 9 | Prototype.Selector.engine = engine; 10 | Prototype.Selector.select = select; 11 | Prototype.Selector.match = engine.match; 12 | })(NW.Dom); 13 | 14 | // Restore globals. 15 | window.NW = Prototype._original_property; 16 | delete Prototype._original_property; 17 | -------------------------------------------------------------------------------- /vendor/sizzle/selector_engine.js: -------------------------------------------------------------------------------- 1 | Prototype._original_property = window.Sizzle; 2 | 3 | ;(function () { 4 | function fakeDefine(fn) { 5 | Prototype._actual_sizzle = fn(); 6 | } 7 | fakeDefine.amd = true; 8 | 9 | if (typeof define !== 'undefined' && define.amd) { 10 | // RequireJS is loaded. We need to pretend to be `define` while Sizzle 11 | // runs. 12 | Prototype._original_define = define; 13 | Prototype._actual_sizzle = null; 14 | window.define = fakeDefine; 15 | } 16 | })(); 17 | 18 | //= require "repository/dist/sizzle" 19 | 20 | ;(function() { 21 | if (typeof Sizzle !== 'undefined') { 22 | // Sizzle was properly defined. 23 | return; 24 | } 25 | 26 | if (typeof define !== 'undefined' && define.amd) { 27 | // RequireJS. 28 | // We should find Sizzle where we put it. And we need to restore the original `define`. 29 | window.Sizzle = Prototype._actual_sizzle; 30 | window.define = Prototype._original_define; 31 | delete Prototype._actual_sizzle; 32 | delete Prototype._original_define; 33 | // TODO: Should we make our own `define` call here? 34 | } else if (typeof module !== 'undefined' && module.exports) { 35 | // Sizzle saw that it's in a CommonJS environment and attached itself to 36 | // `module.exports` instead. 37 | window.Sizzle = module.exports; 38 | // Reset `module.exports`. 39 | module.exports = {}; 40 | } 41 | })(); 42 | 43 | ;(function(engine) { 44 | function select(selector, scope) { 45 | return engine(selector, scope || document); 46 | } 47 | 48 | function match(element, selector) { 49 | return engine.matches(selector, [element]).length == 1; 50 | } 51 | 52 | Prototype.Selector.engine = engine; 53 | Prototype.Selector.select = select; 54 | Prototype.Selector.match = match; 55 | })(window.Sizzle); 56 | 57 | // Restore globals. 58 | window.Sizzle = Prototype._original_property; 59 | delete Prototype._original_property; 60 | -------------------------------------------------------------------------------- /vendor/slick/selector_engine.js: -------------------------------------------------------------------------------- 1 | Prototype._original_property = window.Slick; 2 | //= require "repository/Source/Slick.Parser.js" 3 | //= require "repository/Source/Slick.Finder.js" 4 | 5 | ;(function(engine) { 6 | function select(selector, scope) { 7 | return engine.search(scope || document, selector); 8 | } 9 | 10 | Prototype.Selector.engine = engine; 11 | Prototype.Selector.select = select; 12 | Prototype.Selector.match = engine.match; 13 | })(Slick); 14 | 15 | // Restore globals. 16 | window.Slick = Prototype._original_property; 17 | delete Prototype._original_property; 18 | --------------------------------------------------------------------------------