prototype: Reorganize the source tree.

This commit is contained in:
Sam Stephenson 2007-01-18 22:24:27 +00:00
commit bb4d189b37
39 changed files with 6893 additions and 0 deletions

365
CHANGELOG Normal file
View File

@ -0,0 +1,365 @@
*SVN*
* Add test to ensure Content-type header is set for simulated verbs. [sam]
* Fix Content-Type header for HTTP methods simulated with POST not defaulting to application/x-www-form-urlencoded. [Thomas Fuchs]
* Simplify form serialization and add support for fields with the same name as Hash methods. Closes #6649. [Mislav Marohnić]
* Fix attribute selectors for IE. Closes #5170. [Tobie Langel, Andrew Dupont]
* A slew of dom.js improvements. Closes #4217, #6589, #7001. [Tobie]
- Fix Element.getDimensions() for hidden elements, make Element.getHeight() use .getDimensions()
- Add Element.getWidth()
- Make Element.replace() call .toString() on the html argument (alike .update())
- Fix an issue with Element.get/setStyle() and Safari with 'float'
- Add a bunch of missing unit tests
* Fix an issue with Element.setStyle({opacity:''}) setting the opacity to 0 instead of removing the set inline opacity style. [Thomas Fuchs]
* Ensure Ajax.Request's evalResponse is called before onComplete so that onComplete can reference any elements created during the response. Closes #6727. [jonathan]
* Ensure the WEBrick test runner sets the correct Content-Type for tests and fixtures. [sam]
* Form.serialize once again works with non-form elements. This is a temporary change to prevent the Rails link_to_remote regression described in #6898. Prototype 1.5.1 will introduce an API for working with collections of arbitrary form elements. References #6887. Closes #6898. [sam]
* Make selectors match forms that have an element with name="id" correctly, fixes #5759 [mislav]
* Remove support for HTTP authorization in Ajax calls introduced with #6366. Closes #6638 [jmecham]
* Add Enumerable.size() to count elements in an enumerable and the corresponding Array.size() method, fixes #6710 [ZenCocoon]
* Add String.succ() method to allow for String ranges, fixes #6037 [Cory Hudson, mislav]
Examples:
'abcd'.succ(); -> 'abce'
$R('a','d').map(function(char){ return char; }); -> ['a','b','c','d']
* Make Element.scrollTo() correctly consider offsets of parent DOM nodes, fixes #6625 [ohader, savetheclocktower]
* Fix Enumerable.inGroupsOf() to correctly work with fill values that evaluate to false, fixes #6782 [hawk]
* Remove/cleanup redundant $() calls in dom.js, fixes #6817 [Tobie]
* Don't cache files in automatic unit tests, fixes #6218 [voidlock]
* Add $w() to easily create arrays from strings like Ruby's %w, fixes #5682 [glazedginger, brendon.aaron]
* Add Element.toggleClassName() to toggle CSS classes on elements, fixes #6759 [Tobie]
* Make Form.getInputs always return an array for consistency, fixes #6475 [Justin Gehtland, savetheclocktower]
* Make TimedObserver work correctly for SELECT MULTIPLE elements, fixes #6593 [clemos, tdd]
* Fix Template.prototype.evaluate to correctly interpret 0 and false values, add String.interpret() for safely interpreting and converting values to strings, fixes #6675 [hawk]
* Make Element.getStyle() work with camelCased argument, fixes #6686 [Tobie]
* Fix a redundant check in Array.prototype.compact, fixes #4739 [wlodarcz, mislav]
* Fix $() to correctly pass back the results of document.getElementById(), notably returning "null" on elements not found, fixes #6582 [adsmart]
* Change/add assertNull, assertUndefined, assertNullOrUndefined and not-* variants in unittest.js, fixes #6582 [adsmart]
* Cleanup String.prototype.camelize, fix an issue with String.prototype.underscore, fixes #4714, #6685 [Tobie, Thomas Fuchs]
* Add String.prototype.capitalize, which returns a string with the first character in upper case, fixes #6666 [Tobie]
* Make Element.getStyle() and Element.setStyle() handle the CSS 'opacity' property transparently in IE, fixes #6093 [brandon.aaron, Tobie]
* Fix handling of CSS 'float' property for Element.getStyle() and Element.setStyle(), fixes #4160 [Thomas Fuchs, ericf]
* Fix that onComplete would be called twice with synchronous Ajax requests on Safari (provides units tests for #5756) [Thomas Fuchs]
* Fix Form.Field.activate to not select text on buttons in IE, fixes #2653 [sutch, mislav, Thomas Fuchs]
* Fix String.unescapeHTML() on Firefox for strings that are longer than 2048 bytes, fixes #5789 [Paul Moers, Thomas Fuchs]
* Redefine Array.prototype.concat for Opera, as the native implemenation doesn't work correctly [Thomas Fuchs]
* Add unit tests for Function.prototype.bind() [Thomas Fuchs]
* Add String.prototype.underscore and String.prototype.dasherize [Thomas Fuchs]
Examples:
'Hello_World'.dasherize() -> 'Hello-World'
'borderBottomWidth'.underscore() -> 'border_bottom_width'
'borderBottomWidth'.underscore().dasherize() -> 'border-bottom-width'
*1.5.0_rc2* (November 11, 2006)
* Ensure that existing DOM properties take precedence over extended element methods in all browsers. Closes #5115. [Sean Kleinjung, sam]
* Add Element.Methods.readAttribute as a simple wrapper around getAttribute (which isn't a "real" function and doesn't have .apply or .call in Safari and IE). Useful in conjunction with Enumerable.invoke for extracting the values of a custom attribute from a collection of elements. [sam]
Example:
<div id="widgets">
<div class="widget" widget_id="7">...</div>
<div class="widget" widget_id="8">...</div>
<div class="widget" widget_id="9">...</div>
</div>
$$('div.widget').invoke('readAttribute', 'widget_id')
// ["7", "8", "9"]
* Add Element.Methods.immediateDescendants, like $A($(element).childNodes) but without text nodes. [sam]
* More consistency. Closes #6573. [Bob Silva]
* String.prototype.toQueryParams and Hash.prototype.toQueryString now properly serialize arrays as multiple values. Closes #4436. [mislav, altblue, L`OcuS]
* Fix Form.serialize for options with empty values. Closes #5033. [tdd, Thomas Fuchs, sam]
* Add Element.Methods.Simulated for simulating HTMLElement methods in lesser browsers. Add hasAttribute as the first simulated method. [tdd, Thomas Fuchs, sam]
* Add a "retry with throw" button for test error messages. [sam]
* rake test now runs test/unit/*.html by default. Additionally, you can specify individual tests to run with the TESTS environment variable, and you can restrict the tests to particular browsers using the BROWSERS environment variable. [sam]
Examples:
% BROWSERS=safari,firefox rake test
% TESTS=dom rake test
* Element.hasClassName now bypasses the Element.ClassNames API for performance. [sam]
* Pick some low-hanging performance and DRYness fruit. [sam]
- Inline length property accesses in for loops
- Enumerable-ize for loops where it makes sense
- Make better use of Element.Methods and Form.Methods/Form.Element.Methods
* A slew of Ajax improvements. Closes #6366. [mislav, sam]
Public-facing changes include:
- HTTP method can be specified in either lowercase or uppercase, and uppercase is always used when opening the XHR connection
- Added 'encoding' option (for POST) with a default of 'UTF-8'
- Ajax.Request now recognizes all the JavaScript MIME types we're aware of
- PUT body support with the 'postBody' option
- HTTP authentication support with the 'username' and 'password' options
- Query parameters can be passed as a string or as a hash
- Fixed both String.toQueryParams and Hash.toQueryString when handling empty values
- Request headers can now be specified as a hash with the 'requestHeaders' option
* Improve performance of the common case where $ is called with a single argument. Closes #6347. [sam, rvermillion, mislav]
* Fix Object.inspect to correctly distinguish between null and undefined, fixes #5941 [tdd, mislav]
* Don't serialize disabled form elements, fixes #4586 [tdd]
* Make HTML element classes extension mechanism for Safari not throw errors on WebKit beta versions [Thomas Fuchs]
* Add support for using Element.update() with no or a non-string parameter [Thomas Fuchs]
Example:
$('empty_me').update() -> clears the element
$('easy_as').update(123) -> set element content to '123'
* Add unit tests for hashes, fixes #6344 [Tobie Langel]
* Add clone() method to arrays, fixes #6338 [Tobie Langel]
* Backing out of [5194] (Element.clear) because of issues with IE on certain elements, #6051
* Add Element.clear for easily emptying out elements, fixes #6051 [brandon.aaron@gmail.com]
* Enumerable.each now returns the enumerable to allow for method chaining, fixes #6250 [eventualbuddha]
* Make makeClipping and undoClipping always return their element to stay chainable
* Fix an issue with certain Element chain calls not correctly extending elements with Prototype element methods on IE [Thomas Fuchs]
* Add Enumerable.eachSlice and Enumerable.inGroupsOf, fixes #6046 [rails@tddsworld.com, Thomas Fuchs]
Example:
[1,2,3,4,5].inGroupsOf(3) -> [[1,2,3],[4,5,null]]
[1,2,3].inGroupsOf(4,'x') -> [[1,2,3,'x']]
* Complete unit tests for array.js and string.js [Thomas Fuchs]
* Performance improvements for document.getElementsByClassName, including querying with XPath in supported browsers. [Andrew Dupont]
* Make Form.getElements() return elements in the correct order, fix broken Form.serialize return, fixes #4249, #6172 [lars@pinds.com, Thomas Fuchs, john]
* Add various DOM unit tests, fixes #6176, #6177 [tdd]
* Split Form.serialize into Form.serialize and Form.serializeElements. The latter can be used stand-alone to serialize an array of elements you pass in, instead of the entire form [DHH]
* Form.Element.disable() and .enable() will now work correctly, fixes #6034 [dresselm@businesslogic.com]
* Fix IE and Safari issues with Position.positionedOffset, add position.html unit tests, fixes #5621 [renggli@iam.unibe.ch]
* Fix an issue with Element.undoClipping and IE [Thomas Fuchs]
* Element.cleanWhitespace now correctly removes consecutive empty text nodes, fixes #3209 [livier.duroselle@gmail.com]
* Element.extend now does not try to extend text nodes, fixes #4642 [siegfried.puchbauer@gmail.com]
*1.5.0_rc1* (September 4, 2006)
* bindAsEventListener now passes along any provided arguments after the event argument. Closes #5508. [todd.fisher@revolution.com]
* Fix makeClipping and undoClipping with local overflow style values other than visible and hidden, fixes #3672 [Thomas Fuchs]
* Add Element.up, Element.down, Element.previous, and Element.next for easily traversing the DOM. (Inspired by Thomas Fuchs' original implementation of Element.up: http://pastie.caboo.se/7702) [sam]
Examples:
<div id="sidebar"> -> $('nav').up() or $('menu').up('div')
<ul id="nav"> -> $('sidebar').down() or $('sidebar').down('ul') or $('menu').previous()
<li>...</li> -> $('sidebar').down(1) or $('sidebar').down('li')
<li>...</li> -> $('sidebar').down(2) or $('sidebar').down('li', 2) or $('sidebar').down('li').next('li')
<li class="selected">...</li> -> $('sidebar').down('li.selected')
</ul>
<ul id="menu"> -> $('sidebar').down('ul').next()
...
* Refactor $$ and Element.getElementsBySelector into Selector.findChildElements. [sam]
* Add Element.match, which takes a single CSS selector expression and returns true if it matches the element. [sam]
* Add Element.ancestors, Element.descendants, Element.previousSiblings, Element.nextSiblings, and Element.siblings. [sam]
* Add Element.inspect for better DOM debugging. [sam]
* Add an optional boolean argument to String.prototype.inspect which, when true, makes the string double-quoted instead of single-quoted. [sam]
* Add the uniq() method to Arrays, which returns a new Array with duplicates removed, fixes #3810 [secondlife]
* Add stop() method to PeriodicalExecutor, fixes #4801 [Jon Evans]
* Fix issues with Enumerable.any, ObjectRange.toArray, added unit tests, fixes #4419 [miyamuko, Thomas Fuchs]
* Fix two instances of unneccesarily redeclared variables, fixes #5229 [Thomas Fuchs]
* Fix a loop in Element.extend to properly use local variable, fixes #5128 [arrix]
* Add constants for additional keys in Event, fixes #5411, #5795 [simone_b]
* Workaround a DOM API bug in Opera in Position.page(), fixes #2407, #5848 [Thomas Fuchs]
* Remove duplicate definition of Position.clone(), fixes #3765 [Thomas Fuchs]
* Make destructive Element, Form, and Form.Element methods return their first argument, so that multiple calls can be chained together. [sam]
ex. $("sidebar").addClassName("selected").show();
The following methods now return their first argument: Element.toggle, Element.hide, Element.show, Element.remove, Element.update, Element.replace, Element.addClassName, Element.removeClassName, Element.observe, Element.stopObserving, Element.cleanWhitespace, Element.scrollTo, Element.setStyle, Element.makePositioned, Element.undoPositioned, Element.makeClipping, Element.undoClipping, Form.reset, Form.disable, Form.enable, Form.focusFirstElement, Form.Element.focus, Form.Element.select, Form.Element.clear, Form.Element.activate, Form.Element.disable, Form.Element.enable.
* For consistency, Element.toggle, Element.show, Element.hide, Field.clear, and Field.present no longer take an arbitrary number of arguments. [sam]
!! BACKWARDS COMPATIBILITY CHANGE !!
If you have code that looks like this:
Element.show('page', 'sidebar', 'content');
You need to replace it with code like this:
['page', 'sidebar', 'content'].each(Element.show);
* Mix in Form and Form.Element methods to forms and form field elements with $() and $$(). Closes #4448. [Dan Webb, sam]
* Add Object.clone [sam]
* Add Form.Element.disable and Form.Element.enable. Closes #4943. [jan@prima.de]
* Field is now simply an alias for Form.Element. [sam]
* Add Element.Methods.getElementsByClassName and Element.Methods.getElementsBySelector. Closes #4669. [Andrew Dupont, DHH, sam]
* Avoid race condition when stopping an Ajax.PeriodicalUpdater. Closes #4809. [e98cuenc@gmail.com]
* Improve support for synchronous requests. Closes #5916. [sam, jthrom@gmail.com]
* Add serialization and observation support for input type=search. Closes #4096. [rpnielsen@gmail.com]
* Properly decode query components in String.prototype.toQueryParams. Closes #3487. [sam]
* Add Array.prototype.reduce [sam]:
[1, 2].reduce() // [1, 2]
[1].reduce() // 1
[].reduce() // undefined
* Add Object.keys and Object.values [sam]
* Simulate non-GET/POST requests by POSTing with a _method parameter set to the actual verb [DHH]
* Make Element.update() handle TABLE-related elements with the DOM API because of IE's missing .innerHTML property on them [Thomas Fuchs, thx to Rick Olson]
* Sync to script.aculo.us unittest.js library as of 2006/08/29 [Thomas Fuchs]
* Add additional unit tests to test/unit/dom.html for testing Element.update and $().update in various enviroments [Thomas Fuchs]
* Prevent possible exceptions on unloading the page in IE [Thomas Fuchs]
*1.5.0_rc0* (April 5, 2006)
* Modify HTMLElement.prototype and short-circuit Element.extend where possible. Closes #4477. [Thomas Fuchs]
* Only observe window.onunload in IE for Mozilla bfcache support. Closes #3726. [Mike A. Owens]
* Don't redefine Array.prototype.shift. Closes #4138. [leeo]
* Prevent redefining Array.prototype.reverse more than once. Closes #3951. [brettdgibson@gmail.com]
* Fix Enumerable.min/Enumerable.max to recognize the value 0. Closes #3847, #4190. [rubyonrails@brainsick.com, Martin Bialasinski]
* Change Ajax.getTransport to prefer XMLHttpRequest in anticipation of IE 7. Closes #3688. [jschrab@malicstower.org, sam]
* Make Array.prototype.flatten more robust. Closes #3453. [Martin Bialasinski, sam]
* Fix evalScripts from crashing Firefox if script includes 'var'. Closes #3288, #4165. [rahul@ntag.com, sam]
* Scope iterators locally. Closes #4589. [sam]
* Support Insertion.Before/Insertion.After for <tr> elements in IE. Closes #3925. [rails-venkatp@sneakemail.com]
* Add a contentType option for overriding application/x-www-form-urlencoded in Ajax.Request. Closes #3564. [avif@arc90.com, sam]
* Surround values in the X-JSON header in parenthesis for better compatibility with Firefox. Closes #4118. [bigsmoke@gmail.com]
* Fix Form.serialize to properly encode option values in multiple selects in IE. Closes #3291. [rubyonrails@brainsick.com]
* Cache methods added to DOM elements with Element.extend to prevent memory leaks in IE. Closes #4465. [jaen@laborint.com, sam]
* 1.5.0_pre1* (March 26, 2006)
* Add attribute selector support for Selector (and $$). Closes #4368. [devslashnull@gmail.com]
Example:
$$('form#foo input[type=text]').each(function(input) {
input.setStyle({color: 'red'});
});
* Send Accept header containing 'text/javascript, text/html, application/xml, text/xml */*'' to inform Rails that we prefer RJS, but we'll take HTML or XML or whatever if you can't deliver the goods [DHH]
* Make $$ work in IE. Closes #3715. [rubyonrails@brainsick.com]
* Add test/browser.html, which provides a simple object browser for the Prototype source (Firefox/Safari only). [sam]
* Add Element.extend, which mixes Element methods into a single HTML element. This means you can now write $('foo').show() instead of Element.show('foo'). $, $$ and document.getElementsByClassName automatically call Element.extend on any returned elements. [sam]
* Add Element.replace as a cross-browser implementation of the "outerHTML" property. References #3246. [tom@craz8.com]
* Fix Enumerable.zip iterator behavior. [Marcin Kaszynski, sam]
*1.5.0_pre0* (January 18, 2006)
* Add String.prototype.truncate to complement the Action View truncate helper. [sam]
* Add String.prototype.gsub, String.prototype.sub, and String.prototype.scan, all of which take a pattern and an iterator (or a pattern and a replacement template string in the case of gsub and sub). [sam]
* Add a Template class for interpolating named keys from an object in a string. [sam]
* Add the $$ function for finding DOM elements by simple CSS selector strings. [sam]
Example: Find all <img> elements inside <p> elements with class "summary", all inside
the <div> with id "page". Hide each matched <img> tag.
$$('div#page p.summary img').each(Element.hide)
* Add a Selector class for matching elements by single CSS selector tokens. [sam]
* Add Test.Unit.Assertions.assertEnumEqual for comparing Enumerables in tests. [sam]
* Add Element.childOf(element, ancestor) which returns true when element is a child of ancestor. [sam]
* Fix escaping in String.prototype.inspect. [sam]
* Add String.prototype.strip to remove leading and trailing whitespace from a string. [sam]
* Move Prototype to Rails SVN. [sam]
http://dev.rubyonrails.org/svn/rails/spinoffs/prototype/
* Make the console nicer. [sam]
* Prune the source tree. [sam]

16
LICENSE Normal file
View File

@ -0,0 +1,16 @@
Copyright (c) 2005 Sam Stephenson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

55
README Normal file
View File

@ -0,0 +1,55 @@
= Prototype
==== An object-oriented JavaScript framework
Prototype is a JavaScript framework that aims to ease development of dynamic
web applications. It offers a familiar class-style OO framework, extensive
Ajax support, higher-order programming constructs, and easy DOM manipulation.
=== Targeted platforms
Prototype currently targets the following platforms:
* Microsoft Internet Explorer for Windows, version 6.0 and higher
* Mozilla Firefox 1.0/Mozilla 1.7 and higher
* Apple Safari 1.2 and higher
== Using Prototype
To use Prototype in your application, download the latest release from the
Prototype web site (http://prototype.conio.net/) and copy
<tt>dist/prototype.js</tt> to a suitable location. Then include it in your HTML
like so:
<script type="text/javascript" src="/path/to/prototype.js"></script>
=== Building Prototype from source
<tt>prototype.js</tt> is a composite file generated from many source files in
the <tt>src/</tt> directory. To build Prototype, you'll need:
* a copy of the Prototype source tree, either from a distribution tarball or
from the Subversion repository (see below)
* Ruby 1.8.2 or higher (http://www.ruby-lang.org/)
* Rake -- Ruby Make (http://rake.rubyforge.org/)
* RDoc, if your Ruby distribution does not include it
From the root Prototype directory,
* <tt>rake dist</tt> will preprocess the Prototype source using ERB and
generate the composite <tt>dist/prototype.js</tt>.
* <tt>rake package</tt> will create a distribution tarball in the
<tt>pkg/</tt> directory.
== Contributing to Prototype
Check out the Prototype source with
$ svn co http://dev.rubyonrails.org/svn/rails/spinoffs/prototype/
Modify the files in <tt>src/</tt>, add tests in <tt>test/</tt> if possible,
and post a patch to http://dev.rubyonrails.org/newticket (please make sure
to pick "Prototype" as the component).
== Documentation
Please see the Prototype documentation on the script.aculo.us wiki:
http://wiki.script.aculo.us/scriptaculous/show/Prototype

59
Rakefile Normal file
View File

@ -0,0 +1,59 @@
require 'rake'
require 'rake/packagetask'
PROTOTYPE_ROOT = File.expand_path(File.dirname(__FILE__))
PROTOTYPE_SRC_DIR = File.join(PROTOTYPE_ROOT, 'src')
PROTOTYPE_DIST_DIR = File.join(PROTOTYPE_ROOT, 'dist')
PROTOTYPE_PKG_DIR = File.join(PROTOTYPE_ROOT, 'pkg')
PROTOTYPE_VERSION = '1.5.0'
task :default => [:dist, :package, :clean_package_source]
task :dist do
$:.unshift File.join(PROTOTYPE_ROOT, 'lib')
require 'protodoc'
Dir.chdir(PROTOTYPE_SRC_DIR) do
File.open(File.join(PROTOTYPE_DIST_DIR, 'prototype.js'), 'w+') do |dist|
dist << Protodoc::Preprocessor.new('prototype.js')
end
end
end
Rake::PackageTask.new('prototype', PROTOTYPE_VERSION) do |package|
package.need_tar_gz = true
package.package_dir = PROTOTYPE_PKG_DIR
package.package_files.include(
'[A-Z]*',
'dist/prototype.js',
'lib/**',
'src/**',
'test/**'
)
end
task :test => [:dist, :test_units]
require 'test/lib/jstest'
desc "Runs all the JavaScript unit tests and collects the results"
JavaScriptTestTask.new(:test_units) do |t|
tests_to_run = ENV['TESTS'] && ENV['TESTS'].split(',')
browsers_to_test = ENV['BROWSERS'] && ENV['BROWSERS'].split(',')
t.mount("/dist")
t.mount("/test")
Dir["test/unit/*.html"].each do |test_file|
test_file = "/#{test_file}"
test_name = test_file[/.*\/(.+?)\.html/, 1]
t.run(test_file) unless tests_to_run && !tests_to_run.include?(test_name)
end
%w( safari firefox ie konqueror ).each do |browser|
t.browser(browser.to_sym) unless browsers_to_test && !browsers_to_test.include?(browser)
end
end
task :clean_package_source do
rm_rf File.join(PROTOTYPE_PKG_DIR, "prototype-#{PROTOTYPE_VERSION}")
end

36
lib/protodoc.rb Normal file
View File

@ -0,0 +1,36 @@
require 'erb'
class String
def lines
split $/
end
def strip_whitespace_at_line_ends
lines.map {|line| line.gsub(/\s+$/, '')} * $/
end
end
module Protodoc
module Environment
def include(*filenames)
filenames.map {|filename| Preprocessor.new(filename).to_s}.join("\n")
end
end
class Preprocessor
include Environment
def initialize(filename)
@filename = File.expand_path(filename)
@template = ERB.new(IO.read(@filename), nil, '%')
end
def to_s
@template.result(binding).strip_whitespace_at_line_ends
end
end
end
if __FILE__ == $0
print Protodoc::Preprocessor.new(ARGV.first)
end

7
src/HEADER Normal file
View File

@ -0,0 +1,7 @@
/* Prototype JavaScript framework, version <%= PROTOTYPE_VERSION %>
* (c) 2005-2007 Sam Stephenson
*
* Prototype is freely distributable under the terms of an MIT-style license.
* For details, see the Prototype web site: http://prototype.conio.net/
*
/*--------------------------------------------------------------------------*/

313
src/ajax.js Normal file
View File

@ -0,0 +1,313 @@
var Ajax = {
getTransport: function() {
return Try.these(
function() {return new XMLHttpRequest()},
function() {return new ActiveXObject('Msxml2.XMLHTTP')},
function() {return new ActiveXObject('Microsoft.XMLHTTP')}
) || false;
},
activeRequestCount: 0
}
Ajax.Responders = {
responders: [],
_each: function(iterator) {
this.responders._each(iterator);
},
register: function(responder) {
if (!this.include(responder))
this.responders.push(responder);
},
unregister: function(responder) {
this.responders = this.responders.without(responder);
},
dispatch: function(callback, request, transport, json) {
this.each(function(responder) {
if (typeof responder[callback] == 'function') {
try {
responder[callback].apply(responder, [request, transport, json]);
} catch (e) {}
}
});
}
};
Object.extend(Ajax.Responders, Enumerable);
Ajax.Responders.register({
onCreate: function() {
Ajax.activeRequestCount++;
},
onComplete: function() {
Ajax.activeRequestCount--;
}
});
Ajax.Base = function() {};
Ajax.Base.prototype = {
setOptions: function(options) {
this.options = {
method: 'post',
asynchronous: true,
contentType: 'application/x-www-form-urlencoded',
encoding: 'UTF-8',
parameters: ''
}
Object.extend(this.options, options || {});
this.options.method = this.options.method.toLowerCase();
if (typeof this.options.parameters == 'string')
this.options.parameters = this.options.parameters.toQueryParams();
}
}
Ajax.Request = Class.create();
Ajax.Request.Events =
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
_complete: false,
initialize: function(url, options) {
this.transport = Ajax.getTransport();
this.setOptions(options);
this.request(url);
},
request: function(url) {
this.url = url;
this.method = this.options.method;
var params = this.options.parameters;
if (!['get', 'post'].include(this.method)) {
// simulate other verbs over post
params['_method'] = this.method;
this.method = 'post';
}
params = Hash.toQueryString(params);
if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='
// when GET, append parameters to URL
if (this.method == 'get' && params)
this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params;
try {
Ajax.Responders.dispatch('onCreate', this, this.transport);
this.transport.open(this.method.toUpperCase(), this.url,
this.options.asynchronous);
if (this.options.asynchronous)
setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
this.transport.onreadystatechange = this.onStateChange.bind(this);
this.setRequestHeaders();
var body = this.method == 'post' ? (this.options.postBody || params) : null;
this.transport.send(body);
/* Force Firefox to handle ready state 4 for synchronous requests */
if (!this.options.asynchronous && this.transport.overrideMimeType)
this.onStateChange();
}
catch (e) {
this.dispatchException(e);
}
},
onStateChange: function() {
var readyState = this.transport.readyState;
if (readyState > 1 && !((readyState == 4) && this._complete))
this.respondToReadyState(this.transport.readyState);
},
setRequestHeaders: function() {
var headers = {
'X-Requested-With': 'XMLHttpRequest',
'X-Prototype-Version': Prototype.Version,
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
};
if (this.method == 'post') {
headers['Content-type'] = this.options.contentType +
(this.options.encoding ? '; charset=' + this.options.encoding : '');
/* Force "Connection: close" for older Mozilla browsers to work
* around a bug where XMLHttpRequest sends an incorrect
* Content-length header. See Mozilla Bugzilla #246651.
*/
if (this.transport.overrideMimeType &&
(navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
headers['Connection'] = 'close';
}
// user-defined headers
if (typeof this.options.requestHeaders == 'object') {
var extras = this.options.requestHeaders;
if (typeof extras.push == 'function')
for (var i = 0, length = extras.length; i < length; i += 2)
headers[extras[i]] = extras[i+1];
else
$H(extras).each(function(pair) { headers[pair.key] = pair.value });
}
for (var name in headers)
this.transport.setRequestHeader(name, headers[name]);
},
success: function() {
return !this.transport.status
|| (this.transport.status >= 200 && this.transport.status < 300);
},
respondToReadyState: function(readyState) {
var state = Ajax.Request.Events[readyState];
var transport = this.transport, json = this.evalJSON();
if (state == 'Complete') {
try {
this._complete = true;
(this.options['on' + this.transport.status]
|| this.options['on' + (this.success() ? 'Success' : 'Failure')]
|| Prototype.emptyFunction)(transport, json);
} catch (e) {
this.dispatchException(e);
}
if ((this.getHeader('Content-type') || 'text/javascript').strip().
match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
this.evalResponse();
}
try {
(this.options['on' + state] || Prototype.emptyFunction)(transport, json);
Ajax.Responders.dispatch('on' + state, this, transport, json);
} catch (e) {
this.dispatchException(e);
}
if (state == 'Complete') {
// avoid memory leak in MSIE: clean up
this.transport.onreadystatechange = Prototype.emptyFunction;
}
},
getHeader: function(name) {
try {
return this.transport.getResponseHeader(name);
} catch (e) { return null }
},
evalJSON: function() {
try {
var json = this.getHeader('X-JSON');
return json ? eval('(' + json + ')') : null;
} catch (e) { return null }
},
evalResponse: function() {
try {
return eval(this.transport.responseText);
} catch (e) {
this.dispatchException(e);
}
},
dispatchException: function(exception) {
(this.options.onException || Prototype.emptyFunction)(this, exception);
Ajax.Responders.dispatch('onException', this, exception);
}
});
Ajax.Updater = Class.create();
Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
initialize: function(container, url, options) {
this.container = {
success: (container.success || container),
failure: (container.failure || (container.success ? null : container))
}
this.transport = Ajax.getTransport();
this.setOptions(options);
var onComplete = this.options.onComplete || Prototype.emptyFunction;
this.options.onComplete = (function(transport, param) {
this.updateContent();
onComplete(transport, param);
}).bind(this);
this.request(url);
},
updateContent: function() {
var receiver = this.container[this.success() ? 'success' : 'failure'];
var response = this.transport.responseText;
if (!this.options.evalScripts) response = response.stripScripts();
if (receiver = $(receiver)) {
if (this.options.insertion)
new this.options.insertion(receiver, response);
else
receiver.update(response);
}
if (this.success()) {
if (this.onComplete)
setTimeout(this.onComplete.bind(this), 10);
}
}
});
Ajax.PeriodicalUpdater = Class.create();
Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
initialize: function(container, url, options) {
this.setOptions(options);
this.onComplete = this.options.onComplete;
this.frequency = (this.options.frequency || 2);
this.decay = (this.options.decay || 1);
this.updater = {};
this.container = container;
this.url = url;
this.start();
},
start: function() {
this.options.onComplete = this.updateComplete.bind(this);
this.onTimerEvent();
},
stop: function() {
this.updater.options.onComplete = undefined;
clearTimeout(this.timer);
(this.onComplete || Prototype.emptyFunction).apply(this, arguments);
},
updateComplete: function(request) {
if (this.options.decay) {
this.decay = (request.responseText == this.lastText ?
this.decay * this.options.decay : 1);
this.lastText = request.responseText;
}
this.timer = setTimeout(this.onTimerEvent.bind(this),
this.decay * this.frequency * 1000);
},
onTimerEvent: function() {
this.updater = new Ajax.Updater(this.container, this.url, this.options);
}
});

111
src/array.js Normal file
View File

@ -0,0 +1,111 @@
var $A = Array.from = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) {
return iterable.toArray();
} else {
var results = [];
for (var i = 0, length = iterable.length; i < length; i++)
results.push(iterable[i]);
return results;
}
}
Object.extend(Array.prototype, Enumerable);
if (!Array.prototype._reverse)
Array.prototype._reverse = Array.prototype.reverse;
Object.extend(Array.prototype, {
_each: function(iterator) {
for (var i = 0, length = this.length; i < length; i++)
iterator(this[i]);
},
clear: function() {
this.length = 0;
return this;
},
first: function() {
return this[0];
},
last: function() {
return this[this.length - 1];
},
compact: function() {
return this.select(function(value) {
return value != null;
});
},
flatten: function() {
return this.inject([], function(array, value) {
return array.concat(value && value.constructor == Array ?
value.flatten() : [value]);
});
},
without: function() {
var values = $A(arguments);
return this.select(function(value) {
return !values.include(value);
});
},
indexOf: function(object) {
for (var i = 0, length = this.length; i < length; i++)
if (this[i] == object) return i;
return -1;
},
reverse: function(inline) {
return (inline !== false ? this : this.toArray())._reverse();
},
reduce: function() {
return this.length > 1 ? this : this[0];
},
uniq: function() {
return this.inject([], function(array, value) {
return array.include(value) ? array : array.concat([value]);
});
},
clone: function() {
return [].concat(this);
},
size: function() {
return this.length;
},
inspect: function() {
return '[' + this.map(Object.inspect).join(', ') + ']';
}
});
Array.prototype.toArray = Array.prototype.clone;
function $w(string){
string = string.strip();
return string ? string.split(/\s+/) : [];
}
if(window.opera){
Array.prototype.concat = function(){
var array = [];
for(var i = 0, length = this.length; i < length; i++) array.push(this[i]);
for(var i = 0, length = arguments.length; i < length; i++) {
if(arguments[i].constructor == Array) {
for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
array.push(arguments[i][j]);
} else {
array.push(arguments[i]);
}
}
return array;
}
}

128
src/base.js Normal file
View File

@ -0,0 +1,128 @@
var Class = {
create: function() {
return function() {
this.initialize.apply(this, arguments);
}
}
}
var Abstract = new Object();
Object.extend = function(destination, source) {
for (var property in source) {
destination[property] = source[property];
}
return destination;
}
Object.extend(Object, {
inspect: function(object) {
try {
if (object === undefined) return 'undefined';
if (object === null) return 'null';
return object.inspect ? object.inspect() : object.toString();
} catch (e) {
if (e instanceof RangeError) return '...';
throw e;
}
},
keys: function(object) {
var keys = [];
for (var property in object)
keys.push(property);
return keys;
},
values: function(object) {
var values = [];
for (var property in object)
values.push(object[property]);
return values;
},
clone: function(object) {
return Object.extend({}, object);
}
});
Function.prototype.bind = function() {
var __method = this, args = $A(arguments), object = args.shift();
return function() {
return __method.apply(object, args.concat($A(arguments)));
}
}
Function.prototype.bindAsEventListener = function(object) {
var __method = this, args = $A(arguments), object = args.shift();
return function(event) {
return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments)));
}
}
Object.extend(Number.prototype, {
toColorPart: function() {
var digits = this.toString(16);
if (this < 16) return '0' + digits;
return digits;
},
succ: function() {
return this + 1;
},
times: function(iterator) {
$R(0, this, true).each(iterator);
return this;
}
});
var Try = {
these: function() {
var returnValue;
for (var i = 0, length = arguments.length; i < length; i++) {
var lambda = arguments[i];
try {
returnValue = lambda();
break;
} catch (e) {}
}
return returnValue;
}
}
/*--------------------------------------------------------------------------*/
var PeriodicalExecuter = Class.create();
PeriodicalExecuter.prototype = {
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
this.currentlyExecuting = false;
this.registerCallback();
},
registerCallback: function() {
this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
stop: function() {
if (!this.timer) return;
clearInterval(this.timer);
this.timer = null;
},
onTimerEvent: function() {
if (!this.currentlyExecuting) {
try {
this.currentlyExecuting = true;
this.callback(this);
} finally {
this.currentlyExecuting = false;
}
}
}
}

678
src/dom.js Normal file
View File

@ -0,0 +1,678 @@
function $(element) {
if (arguments.length > 1) {
for (var i = 0, elements = [], length = arguments.length; i < length; i++)
elements.push($(arguments[i]));
return elements;
}
if (typeof element == 'string')
element = document.getElementById(element);
return Element.extend(element);
}
if (Prototype.BrowserFeatures.XPath) {
document._getElementsByXPath = function(expression, parentElement) {
var results = [];
var query = document.evaluate(expression, $(parentElement) || document,
null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0, length = query.snapshotLength; i < length; i++)
results.push(query.snapshotItem(i));
return results;
};
}
document.getElementsByClassName = function(className, parentElement) {
if (Prototype.BrowserFeatures.XPath) {
var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
return document._getElementsByXPath(q, parentElement);
} else {
var children = ($(parentElement) || document.body).getElementsByTagName('*');
var elements = [], child;
for (var i = 0, length = children.length; i < length; i++) {
child = children[i];
if (Element.hasClassName(child, className))
elements.push(Element.extend(child));
}
return elements;
}
};
/*--------------------------------------------------------------------------*/
if (!window.Element)
var Element = new Object();
Element.extend = function(element) {
if (!element || _nativeExtensions || element.nodeType == 3) return element;
if (!element._extended && element.tagName && element != window) {
var methods = Object.clone(Element.Methods), cache = Element.extend.cache;
if (element.tagName == 'FORM')
Object.extend(methods, Form.Methods);
if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
Object.extend(methods, Form.Element.Methods);
Object.extend(methods, Element.Methods.Simulated);
for (var property in methods) {
var value = methods[property];
if (typeof value == 'function' && !(property in element))
element[property] = cache.findOrStore(value);
}
}
element._extended = true;
return element;
};
Element.extend.cache = {
findOrStore: function(value) {
return this[value] = this[value] || function() {
return value.apply(null, [this].concat($A(arguments)));
}
}
};
Element.Methods = {
visible: function(element) {
return $(element).style.display != 'none';
},
toggle: function(element) {
element = $(element);
Element[Element.visible(element) ? 'hide' : 'show'](element);
return element;
},
hide: function(element) {
$(element).style.display = 'none';
return element;
},
show: function(element) {
$(element).style.display = '';
return element;
},
remove: function(element) {
element = $(element);
element.parentNode.removeChild(element);
return element;
},
update: function(element, html) {
html = typeof html == 'undefined' ? '' : html.toString();
$(element).innerHTML = html.stripScripts();
setTimeout(function() {html.evalScripts()}, 10);
return element;
},
replace: function(element, html) {
element = $(element);
html = typeof html == 'undefined' ? '' : html.toString();
if (element.outerHTML) {
element.outerHTML = html.stripScripts();
} else {
var range = element.ownerDocument.createRange();
range.selectNodeContents(element);
element.parentNode.replaceChild(
range.createContextualFragment(html.stripScripts()), element);
}
setTimeout(function() {html.evalScripts()}, 10);
return element;
},
inspect: function(element) {
element = $(element);
var result = '<' + element.tagName.toLowerCase();
$H({'id': 'id', 'className': 'class'}).each(function(pair) {
var property = pair.first(), attribute = pair.last();
var value = (element[property] || '').toString();
if (value) result += ' ' + attribute + '=' + value.inspect(true);
});
return result + '>';
},
recursivelyCollect: function(element, property) {
element = $(element);
var elements = [];
while (element = element[property])
if (element.nodeType == 1)
elements.push(Element.extend(element));
return elements;
},
ancestors: function(element) {
return $(element).recursivelyCollect('parentNode');
},
descendants: function(element) {
return $A($(element).getElementsByTagName('*'));
},
immediateDescendants: function(element) {
if (!(element = $(element).firstChild)) return [];
while (element && element.nodeType != 1) element = element.nextSibling;
if (element) return [element].concat($(element).nextSiblings());
return [];
},
previousSiblings: function(element) {
return $(element).recursivelyCollect('previousSibling');
},
nextSiblings: function(element) {
return $(element).recursivelyCollect('nextSibling');
},
siblings: function(element) {
element = $(element);
return element.previousSiblings().reverse().concat(element.nextSiblings());
},
match: function(element, selector) {
if (typeof selector == 'string')
selector = new Selector(selector);
return selector.match($(element));
},
up: function(element, expression, index) {
return Selector.findElement($(element).ancestors(), expression, index);
},
down: function(element, expression, index) {
return Selector.findElement($(element).descendants(), expression, index);
},
previous: function(element, expression, index) {
return Selector.findElement($(element).previousSiblings(), expression, index);
},
next: function(element, expression, index) {
return Selector.findElement($(element).nextSiblings(), expression, index);
},
getElementsBySelector: function() {
var args = $A(arguments), element = $(args.shift());
return Selector.findChildElements(element, args);
},
getElementsByClassName: function(element, className) {
return document.getElementsByClassName(className, element);
},
readAttribute: function(element, name) {
element = $(element);
if (document.all && !window.opera) {
var t = Element._attributeTranslations;
if (t.values[name]) return t.values[name](element, name);
if (t.names[name]) name = t.names[name];
var attribute = element.attributes[name];
if(attribute) return attribute.nodeValue;
}
return element.getAttribute(name);
},
getHeight: function(element) {
return $(element).getDimensions().height;
},
getWidth: function(element) {
return $(element).getDimensions().width;
},
classNames: function(element) {
return new Element.ClassNames(element);
},
hasClassName: function(element, className) {
if (!(element = $(element))) return;
var elementClassName = element.className;
if (elementClassName.length == 0) return false;
if (elementClassName == className ||
elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
return true;
return false;
},
addClassName: function(element, className) {
if (!(element = $(element))) return;
Element.classNames(element).add(className);
return element;
},
removeClassName: function(element, className) {
if (!(element = $(element))) return;
Element.classNames(element).remove(className);
return element;
},
toggleClassName: function(element, className) {
if (!(element = $(element))) return;
Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
return element;
},
observe: function() {
Event.observe.apply(Event, arguments);
return $A(arguments).first();
},
stopObserving: function() {
Event.stopObserving.apply(Event, arguments);
return $A(arguments).first();
},
// removes whitespace-only text node children
cleanWhitespace: function(element) {
element = $(element);
var node = element.firstChild;
while (node) {
var nextNode = node.nextSibling;
if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
element.removeChild(node);
node = nextNode;
}
return element;
},
empty: function(element) {
return $(element).innerHTML.match(/^\s*$/);
},
descendantOf: function(element, ancestor) {
element = $(element), ancestor = $(ancestor);
while (element = element.parentNode)
if (element == ancestor) return true;
return false;
},
scrollTo: function(element) {
element = $(element);
var pos = Position.cumulativeOffset(element);
window.scrollTo(pos[0], pos[1]);
return element;
},
getStyle: function(element, style) {
element = $(element);
if (['float','cssFloat'].include(style))
style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat');
style = style.camelize();
var value = element.style[style];
if (!value) {
if (document.defaultView && document.defaultView.getComputedStyle) {
var css = document.defaultView.getComputedStyle(element, null);
value = css ? css[style] : null;
} else if (element.currentStyle) {
value = element.currentStyle[style];
}
}
if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none'))
value = element['offset'+style.capitalize()] + 'px';
if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
if (Element.getStyle(element, 'position') == 'static') value = 'auto';
if(style == 'opacity') {
if(value) return parseFloat(value);
if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
if(value[1]) return parseFloat(value[1]) / 100;
return 1.0;
}
return value == 'auto' ? null : value;
},
setStyle: function(element, style) {
element = $(element);
for (var name in style) {
var value = style[name];
if(name == 'opacity') {
if (value == 1) {
value = (/Gecko/.test(navigator.userAgent) &&
!/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0;
if(/MSIE/.test(navigator.userAgent) && !window.opera)
element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
} else if(value == '') {
if(/MSIE/.test(navigator.userAgent) && !window.opera)
element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
} else {
if(value < 0.00001) value = 0;
if(/MSIE/.test(navigator.userAgent) && !window.opera)
element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
'alpha(opacity='+value*100+')';
}
} else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat';
element.style[name.camelize()] = value;
}
return element;
},
getDimensions: function(element) {
element = $(element);
var display = $(element).getStyle('display');
if (display != 'none' && display != null) // Safari bug
return {width: element.offsetWidth, height: element.offsetHeight};
// All *Width and *Height properties give 0 on elements with display none,
// so enable the element temporarily
var els = element.style;
var originalVisibility = els.visibility;
var originalPosition = els.position;
var originalDisplay = els.display;
els.visibility = 'hidden';
els.position = 'absolute';
els.display = 'block';
var originalWidth = element.clientWidth;
var originalHeight = element.clientHeight;
els.display = originalDisplay;
els.position = originalPosition;
els.visibility = originalVisibility;
return {width: originalWidth, height: originalHeight};
},
makePositioned: function(element) {
element = $(element);
var pos = Element.getStyle(element, 'position');
if (pos == 'static' || !pos) {
element._madePositioned = true;
element.style.position = 'relative';
// Opera returns the offset relative to the positioning context, when an
// element is position relative but top and left have not been defined
if (window.opera) {
element.style.top = 0;
element.style.left = 0;
}
}
return element;
},
undoPositioned: function(element) {
element = $(element);
if (element._madePositioned) {
element._madePositioned = undefined;
element.style.position =
element.style.top =
element.style.left =
element.style.bottom =
element.style.right = '';
}
return element;
},
makeClipping: function(element) {
element = $(element);
if (element._overflow) return element;
element._overflow = element.style.overflow || 'auto';
if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
element.style.overflow = 'hidden';
return element;
},
undoClipping: function(element) {
element = $(element);
if (!element._overflow) return element;
element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
element._overflow = null;
return element;
}
};
Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});
Element._attributeTranslations = {};
Element._attributeTranslations.names = {
colspan: "colSpan",
rowspan: "rowSpan",
valign: "vAlign",
datetime: "dateTime",
accesskey: "accessKey",
tabindex: "tabIndex",
enctype: "encType",
maxlength: "maxLength",
readonly: "readOnly",
longdesc: "longDesc"
};
Element._attributeTranslations.values = {
_getAttr: function(element, attribute) {
return element.getAttribute(attribute, 2);
},
_flag: function(element, attribute) {
return $(element).hasAttribute(attribute) ? attribute : null;
},
style: function(element) {
return element.style.cssText.toLowerCase();
},
title: function(element) {
var node = element.getAttributeNode('title');
return node.specified ? node.nodeValue : null;
}
};
Object.extend(Element._attributeTranslations.values, {
href: Element._attributeTranslations.values._getAttr,
src: Element._attributeTranslations.values._getAttr,
disabled: Element._attributeTranslations.values._flag,
checked: Element._attributeTranslations.values._flag,
readonly: Element._attributeTranslations.values._flag,
multiple: Element._attributeTranslations.values._flag
});
Element.Methods.Simulated = {
hasAttribute: function(element, attribute) {
var t = Element._attributeTranslations;
attribute = t.names[attribute] || attribute;
return $(element).getAttributeNode(attribute).specified;
}
};
// IE is missing .innerHTML support for TABLE-related elements
if (document.all && !window.opera){
Element.Methods.update = function(element, html) {
element = $(element);
html = typeof html == 'undefined' ? '' : html.toString();
var tagName = element.tagName.toUpperCase();
if (['THEAD','TBODY','TR','TD'].include(tagName)) {
var div = document.createElement('div');
switch (tagName) {
case 'THEAD':
case 'TBODY':
div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>';
depth = 2;
break;
case 'TR':
div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>';
depth = 3;
break;
case 'TD':
div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>';
depth = 4;
}
$A(element.childNodes).each(function(node){
element.removeChild(node)
});
depth.times(function(){ div = div.firstChild });
$A(div.childNodes).each(
function(node){ element.appendChild(node) });
} else {
element.innerHTML = html.stripScripts();
}
setTimeout(function() {html.evalScripts()}, 10);
return element;
}
};
Object.extend(Element, Element.Methods);
var _nativeExtensions = false;
if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
var className = 'HTML' + tag + 'Element';
if(window[className]) return;
var klass = window[className] = {};
klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
});
Element.addMethods = function(methods) {
Object.extend(Element.Methods, methods || {});
function copy(methods, destination, onlyIfAbsent) {
onlyIfAbsent = onlyIfAbsent || false;
var cache = Element.extend.cache;
for (var property in methods) {
var value = methods[property];
if (!onlyIfAbsent || !(property in destination))
destination[property] = cache.findOrStore(value);
}
}
if (typeof HTMLElement != 'undefined') {
copy(Element.Methods, HTMLElement.prototype);
copy(Element.Methods.Simulated, HTMLElement.prototype, true);
copy(Form.Methods, HTMLFormElement.prototype);
[HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
copy(Form.Element.Methods, klass.prototype);
});
_nativeExtensions = true;
}
}
var Toggle = new Object();
Toggle.display = Element.toggle;
/*--------------------------------------------------------------------------*/
Abstract.Insertion = function(adjacency) {
this.adjacency = adjacency;
}
Abstract.Insertion.prototype = {
initialize: function(element, content) {
this.element = $(element);
this.content = content.stripScripts();
if (this.adjacency && this.element.insertAdjacentHTML) {
try {
this.element.insertAdjacentHTML(this.adjacency, this.content);
} catch (e) {
var tagName = this.element.tagName.toUpperCase();
if (['TBODY', 'TR'].include(tagName)) {
this.insertContent(this.contentFromAnonymousTable());
} else {
throw e;
}
}
} else {
this.range = this.element.ownerDocument.createRange();
if (this.initializeRange) this.initializeRange();
this.insertContent([this.range.createContextualFragment(this.content)]);
}
setTimeout(function() {content.evalScripts()}, 10);
},
contentFromAnonymousTable: function() {
var div = document.createElement('div');
div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
return $A(div.childNodes[0].childNodes[0].childNodes);
}
}
var Insertion = new Object();
Insertion.Before = Class.create();
Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
initializeRange: function() {
this.range.setStartBefore(this.element);
},
insertContent: function(fragments) {
fragments.each((function(fragment) {
this.element.parentNode.insertBefore(fragment, this.element);
}).bind(this));
}
});
Insertion.Top = Class.create();
Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
initializeRange: function() {
this.range.selectNodeContents(this.element);
this.range.collapse(true);
},
insertContent: function(fragments) {
fragments.reverse(false).each((function(fragment) {
this.element.insertBefore(fragment, this.element.firstChild);
}).bind(this));
}
});
Insertion.Bottom = Class.create();
Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
initializeRange: function() {
this.range.selectNodeContents(this.element);
this.range.collapse(this.element);
},
insertContent: function(fragments) {
fragments.each((function(fragment) {
this.element.appendChild(fragment);
}).bind(this));
}
});
Insertion.After = Class.create();
Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
initializeRange: function() {
this.range.setStartAfter(this.element);
},
insertContent: function(fragments) {
fragments.each((function(fragment) {
this.element.parentNode.insertBefore(fragment,
this.element.nextSibling);
}).bind(this));
}
});
/*--------------------------------------------------------------------------*/
Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
initialize: function(element) {
this.element = $(element);
},
_each: function(iterator) {
this.element.className.split(/\s+/).select(function(name) {
return name.length > 0;
})._each(iterator);
},
set: function(className) {
this.element.className = className;
},
add: function(classNameToAdd) {
if (this.include(classNameToAdd)) return;
this.set($A(this).concat(classNameToAdd).join(' '));
},
remove: function(classNameToRemove) {
if (!this.include(classNameToRemove)) return;
this.set($A(this).without(classNameToRemove).join(' '));
},
toString: function() {
return $A(this).join(' ');
}
};
Object.extend(Element.ClassNames.prototype, Enumerable);

202
src/enumerable.js Normal file
View File

@ -0,0 +1,202 @@
var $break = new Object();
var $continue = new Object();
var Enumerable = {
each: function(iterator) {
var index = 0;
try {
this._each(function(value) {
try {
iterator(value, index++);
} catch (e) {
if (e != $continue) throw e;
}
});
} catch (e) {
if (e != $break) throw e;
}
return this;
},
eachSlice: function(number, iterator) {
var index = -number, slices = [], array = this.toArray();
while ((index += number) < array.length)
slices.push(array.slice(index, index+number));
return slices.map(iterator);
},
all: function(iterator) {
var result = true;
this.each(function(value, index) {
result = result && !!(iterator || Prototype.K)(value, index);
if (!result) throw $break;
});
return result;
},
any: function(iterator) {
var result = false;
this.each(function(value, index) {
if (result = !!(iterator || Prototype.K)(value, index))
throw $break;
});
return result;
},
collect: function(iterator) {
var results = [];
this.each(function(value, index) {
results.push((iterator || Prototype.K)(value, index));
});
return results;
},
detect: function(iterator) {
var result;
this.each(function(value, index) {
if (iterator(value, index)) {
result = value;
throw $break;
}
});
return result;
},
findAll: function(iterator) {
var results = [];
this.each(function(value, index) {
if (iterator(value, index))
results.push(value);
});
return results;
},
grep: function(pattern, iterator) {
var results = [];
this.each(function(value, index) {
var stringValue = value.toString();
if (stringValue.match(pattern))
results.push((iterator || Prototype.K)(value, index));
})
return results;
},
include: function(object) {
var found = false;
this.each(function(value) {
if (value == object) {
found = true;
throw $break;
}
});
return found;
},
inGroupsOf: function(number, fillWith) {
fillWith = fillWith === undefined ? null : fillWith;
return this.eachSlice(number, function(slice) {
while(slice.length < number) slice.push(fillWith);
return slice;
});
},
inject: function(memo, iterator) {
this.each(function(value, index) {
memo = iterator(memo, value, index);
});
return memo;
},
invoke: function(method) {
var args = $A(arguments).slice(1);
return this.map(function(value) {
return value[method].apply(value, args);
});
},
max: function(iterator) {
var result;
this.each(function(value, index) {
value = (iterator || Prototype.K)(value, index);
if (result == undefined || value >= result)
result = value;
});
return result;
},
min: function(iterator) {
var result;
this.each(function(value, index) {
value = (iterator || Prototype.K)(value, index);
if (result == undefined || value < result)
result = value;
});
return result;
},
partition: function(iterator) {
var trues = [], falses = [];
this.each(function(value, index) {
((iterator || Prototype.K)(value, index) ?
trues : falses).push(value);
});
return [trues, falses];
},
pluck: function(property) {
var results = [];
this.each(function(value, index) {
results.push(value[property]);
});
return results;
},
reject: function(iterator) {
var results = [];
this.each(function(value, index) {
if (!iterator(value, index))
results.push(value);
});
return results;
},
sortBy: function(iterator) {
return this.map(function(value, index) {
return {value: value, criteria: iterator(value, index)};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}).pluck('value');
},
toArray: function() {
return this.map();
},
zip: function() {
var iterator = Prototype.K, args = $A(arguments);
if (typeof args.last() == 'function')
iterator = args.pop();
var collections = [this].concat(args).map($A);
return this.map(function(value, index) {
return iterator(collections.pluck(index));
});
},
size: function() {
return this.toArray().length;
},
inspect: function() {
return '#<Enumerable:' + this.toArray().inspect() + '>';
}
}
Object.extend(Enumerable, {
map: Enumerable.collect,
find: Enumerable.detect,
select: Enumerable.findAll,
member: Enumerable.include,
entries: Enumerable.toArray
});

114
src/event.js Normal file
View File

@ -0,0 +1,114 @@
if (!window.Event) {
var Event = new Object();
}
Object.extend(Event, {
KEY_BACKSPACE: 8,
KEY_TAB: 9,
KEY_RETURN: 13,
KEY_ESC: 27,
KEY_LEFT: 37,
KEY_UP: 38,
KEY_RIGHT: 39,
KEY_DOWN: 40,
KEY_DELETE: 46,
KEY_HOME: 36,
KEY_END: 35,
KEY_PAGEUP: 33,
KEY_PAGEDOWN: 34,
element: function(event) {
return event.target || event.srcElement;
},
isLeftClick: function(event) {
return (((event.which) && (event.which == 1)) ||
((event.button) && (event.button == 1)));
},
pointerX: function(event) {
return event.pageX || (event.clientX +
(document.documentElement.scrollLeft || document.body.scrollLeft));
},
pointerY: function(event) {
return event.pageY || (event.clientY +
(document.documentElement.scrollTop || document.body.scrollTop));
},
stop: function(event) {
if (event.preventDefault) {
event.preventDefault();
event.stopPropagation();
} else {
event.returnValue = false;
event.cancelBubble = true;
}
},
// find the first node with the given tagName, starting from the
// node the event was triggered on; traverses the DOM upwards
findElement: function(event, tagName) {
var element = Event.element(event);
while (element.parentNode && (!element.tagName ||
(element.tagName.toUpperCase() != tagName.toUpperCase())))
element = element.parentNode;
return element;
},
observers: false,
_observeAndCache: function(element, name, observer, useCapture) {
if (!this.observers) this.observers = [];
if (element.addEventListener) {
this.observers.push([element, name, observer, useCapture]);
element.addEventListener(name, observer, useCapture);
} else if (element.attachEvent) {
this.observers.push([element, name, observer, useCapture]);
element.attachEvent('on' + name, observer);
}
},
unloadCache: function() {
if (!Event.observers) return;
for (var i = 0, length = Event.observers.length; i < length; i++) {
Event.stopObserving.apply(this, Event.observers[i]);
Event.observers[i][0] = null;
}
Event.observers = false;
},
observe: function(element, name, observer, useCapture) {
element = $(element);
useCapture = useCapture || false;
if (name == 'keypress' &&
(navigator.appVersion.match(/Konqueror|Safari|KHTML/)
|| element.attachEvent))
name = 'keydown';
Event._observeAndCache(element, name, observer, useCapture);
},
stopObserving: function(element, name, observer, useCapture) {
element = $(element);
useCapture = useCapture || false;
if (name == 'keypress' &&
(navigator.appVersion.match(/Konqueror|Safari|KHTML/)
|| element.detachEvent))
name = 'keydown';
if (element.removeEventListener) {
element.removeEventListener(name, observer, useCapture);
} else if (element.detachEvent) {
try {
element.detachEvent('on' + name, observer);
} catch (e) {}
}
}
});
/* prevent memory leaks in IE */
if (navigator.appVersion.match(/\bMSIE\b/))
Event.observe(window, 'unload', Event.unloadCache, false);

305
src/form.js Normal file
View File

@ -0,0 +1,305 @@
var Form = {
reset: function(form) {
$(form).reset();
return form;
},
serializeElements: function(elements, getHash) {
var data = elements.inject({}, function(result, element) {
if (!element.disabled && element.name) {
var key = element.name, value = $(element).getValue();
if (value != undefined) {
if (result[key]) {
if (result[key].constructor != Array) result[key] = [result[key]];
result[key].push(value);
}
else result[key] = value;
}
}
return result;
});
return getHash ? data : Hash.toQueryString(data);
}
};
Form.Methods = {
serialize: function(form, getHash) {
return Form.serializeElements(Form.getElements(form), getHash);
},
getElements: function(form) {
return $A($(form).getElementsByTagName('*')).inject([],
function(elements, child) {
if (Form.Element.Serializers[child.tagName.toLowerCase()])
elements.push(Element.extend(child));
return elements;
}
);
},
getInputs: function(form, typeName, name) {
form = $(form);
var inputs = form.getElementsByTagName('input');
if (!typeName && !name) return $A(inputs).map(Element.extend);
for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
var input = inputs[i];
if ((typeName && input.type != typeName) || (name && input.name != name))
continue;
matchingInputs.push(Element.extend(input));
}
return matchingInputs;
},
disable: function(form) {
form = $(form);
form.getElements().each(function(element) {
element.blur();
element.disabled = 'true';
});
return form;
},
enable: function(form) {
form = $(form);
form.getElements().each(function(element) {
element.disabled = '';
});
return form;
},
findFirstElement: function(form) {
return $(form).getElements().find(function(element) {
return element.type != 'hidden' && !element.disabled &&
['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
});
},
focusFirstElement: function(form) {
form = $(form);
form.findFirstElement().activate();
return form;
}
}
Object.extend(Form, Form.Methods);
/*--------------------------------------------------------------------------*/
Form.Element = {
focus: function(element) {
$(element).focus();
return element;
},
select: function(element) {
$(element).select();
return element;
}
}
Form.Element.Methods = {
serialize: function(element) {
element = $(element);
if (!element.disabled && element.name) {
var value = element.getValue();
if (value != undefined) {
var pair = {};
pair[element.name] = value;
return Hash.toQueryString(pair);
}
}
return '';
},
getValue: function(element) {
element = $(element);
var method = element.tagName.toLowerCase();
return Form.Element.Serializers[method](element);
},
clear: function(element) {
$(element).value = '';
return element;
},
present: function(element) {
return $(element).value != '';
},
activate: function(element) {
element = $(element);
element.focus();
if (element.select && ( element.tagName.toLowerCase() != 'input' ||
!['button', 'reset', 'submit'].include(element.type) ) )
element.select();
return element;
},
disable: function(element) {
element = $(element);
element.disabled = true;
return element;
},
enable: function(element) {
element = $(element);
element.blur();
element.disabled = false;
return element;
}
}
Object.extend(Form.Element, Form.Element.Methods);
var Field = Form.Element;
var $F = Form.Element.getValue;
/*--------------------------------------------------------------------------*/
Form.Element.Serializers = {
input: function(element) {
switch (element.type.toLowerCase()) {
case 'checkbox':
case 'radio':
return Form.Element.Serializers.inputSelector(element);
default:
return Form.Element.Serializers.textarea(element);
}
},
inputSelector: function(element) {
return element.checked ? element.value : null;
},
textarea: function(element) {
return element.value;
},
select: function(element) {
return this[element.type == 'select-one' ?
'selectOne' : 'selectMany'](element);
},
selectOne: function(element) {
var index = element.selectedIndex;
return index >= 0 ? this.optionValue(element.options[index]) : null;
},
selectMany: function(element) {
var values, length = element.length;
if (!length) return null;
for (var i = 0, values = []; i < length; i++) {
var opt = element.options[i];
if (opt.selected) values.push(this.optionValue(opt));
}
return values;
},
optionValue: function(opt) {
// extend element because hasAttribute may not be native
return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
}
}
/*--------------------------------------------------------------------------*/
Abstract.TimedObserver = function() {}
Abstract.TimedObserver.prototype = {
initialize: function(element, frequency, callback) {
this.frequency = frequency;
this.element = $(element);
this.callback = callback;
this.lastValue = this.getValue();
this.registerCallback();
},
registerCallback: function() {
setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
onTimerEvent: function() {
var value = this.getValue();
var changed = ('string' == typeof this.lastValue && 'string' == typeof value
? this.lastValue != value : String(this.lastValue) != String(value));
if (changed) {
this.callback(this.element, value);
this.lastValue = value;
}
}
}
Form.Element.Observer = Class.create();
Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
getValue: function() {
return Form.Element.getValue(this.element);
}
});
Form.Observer = Class.create();
Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
getValue: function() {
return Form.serialize(this.element);
}
});
/*--------------------------------------------------------------------------*/
Abstract.EventObserver = function() {}
Abstract.EventObserver.prototype = {
initialize: function(element, callback) {
this.element = $(element);
this.callback = callback;
this.lastValue = this.getValue();
if (this.element.tagName.toLowerCase() == 'form')
this.registerFormCallbacks();
else
this.registerCallback(this.element);
},
onElementEvent: function() {
var value = this.getValue();
if (this.lastValue != value) {
this.callback(this.element, value);
this.lastValue = value;
}
},
registerFormCallbacks: function() {
Form.getElements(this.element).each(this.registerCallback.bind(this));
},
registerCallback: function(element) {
if (element.type) {
switch (element.type.toLowerCase()) {
case 'checkbox':
case 'radio':
Event.observe(element, 'click', this.onElementEvent.bind(this));
break;
default:
Event.observe(element, 'change', this.onElementEvent.bind(this));
break;
}
}
}
}
Form.Element.EventObserver = Class.create();
Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
getValue: function() {
return Form.Element.getValue(this.element);
}
});
Form.EventObserver = Class.create();
Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
getValue: function() {
return Form.serialize(this.element);
}
});

91
src/hash.js Normal file
View File

@ -0,0 +1,91 @@
var Hash = function(obj) {
Object.extend(this, obj || {});
};
Object.extend(Hash, {
toQueryString: function(obj) {
var parts = [];
this.prototype._each.call(obj, function(pair) {
if (!pair.key) return;
if (pair.value && pair.value.constructor == Array) {
var values = pair.value.compact();
if (values.length < 2) pair.value = values.reduce();
else {
key = encodeURIComponent(pair.key);
values.each(function(value) {
value = value != undefined ? encodeURIComponent(value) : '';
parts.push(key + '=' + encodeURIComponent(value));
});
return;
}
}
if (pair.value == undefined) pair[1] = '';
parts.push(pair.map(encodeURIComponent).join('='));
});
return parts.join('&');
}
});
Object.extend(Hash.prototype, Enumerable);
Object.extend(Hash.prototype, {
_each: function(iterator) {
for (var key in this) {
var value = this[key];
if (value && value == Hash.prototype[key]) continue;
var pair = [key, value];
pair.key = key;
pair.value = value;
iterator(pair);
}
},
keys: function() {
return this.pluck('key');
},
values: function() {
return this.pluck('value');
},
merge: function(hash) {
return $H(hash).inject(this, function(mergedHash, pair) {
mergedHash[pair.key] = pair.value;
return mergedHash;
});
},
remove: function() {
var result;
for(var i = 0, length = arguments.length; i < length; i++) {
var value = this[arguments[i]];
if (value !== undefined){
if (result === undefined) result = value;
else {
if (result.constructor != Array) result = [result];
result.push(value)
}
}
delete this[arguments[i]];
}
return result;
},
toQueryString: function() {
return Hash.toQueryString(this);
},
inspect: function() {
return '#<Hash:{' + this.map(function(pair) {
return pair.map(Object.inspect).join(': ');
}).join(', ') + '}>';
}
});
function $H(object) {
if (object && object.constructor == Hash) return object;
return new Hash(object);
};

225
src/position.js Normal file
View File

@ -0,0 +1,225 @@
var Position = {
// set to true if needed, warning: firefox performance problems
// NOT neeeded for page scrolling, only if draggable contained in
// scrollable elements
includeScrollOffsets: false,
// must be called before calling withinIncludingScrolloffset, every time the
// page is scrolled
prepare: function() {
this.deltaX = window.pageXOffset
|| document.documentElement.scrollLeft
|| document.body.scrollLeft
|| 0;
this.deltaY = window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop
|| 0;
},
realOffset: function(element) {
var valueT = 0, valueL = 0;
do {
valueT += element.scrollTop || 0;
valueL += element.scrollLeft || 0;
element = element.parentNode;
} while (element);
return [valueL, valueT];
},
cumulativeOffset: function(element) {
var valueT = 0, valueL = 0;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
element = element.offsetParent;
} while (element);
return [valueL, valueT];
},
positionedOffset: function(element) {
var valueT = 0, valueL = 0;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
element = element.offsetParent;
if (element) {
if(element.tagName=='BODY') break;
var p = Element.getStyle(element, 'position');
if (p == 'relative' || p == 'absolute') break;
}
} while (element);
return [valueL, valueT];
},
offsetParent: function(element) {
if (element.offsetParent) return element.offsetParent;
if (element == document.body) return element;
while ((element = element.parentNode) && element != document.body)
if (Element.getStyle(element, 'position') != 'static')
return element;
return document.body;
},
// caches x/y coordinate pair to use with overlap
within: function(element, x, y) {
if (this.includeScrollOffsets)
return this.withinIncludingScrolloffsets(element, x, y);
this.xcomp = x;
this.ycomp = y;
this.offset = this.cumulativeOffset(element);
return (y >= this.offset[1] &&
y < this.offset[1] + element.offsetHeight &&
x >= this.offset[0] &&
x < this.offset[0] + element.offsetWidth);
},
withinIncludingScrolloffsets: function(element, x, y) {
var offsetcache = this.realOffset(element);
this.xcomp = x + offsetcache[0] - this.deltaX;
this.ycomp = y + offsetcache[1] - this.deltaY;
this.offset = this.cumulativeOffset(element);
return (this.ycomp >= this.offset[1] &&
this.ycomp < this.offset[1] + element.offsetHeight &&
this.xcomp >= this.offset[0] &&
this.xcomp < this.offset[0] + element.offsetWidth);
},
// within must be called directly before
overlap: function(mode, element) {
if (!mode) return 0;
if (mode == 'vertical')
return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
element.offsetHeight;
if (mode == 'horizontal')
return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
element.offsetWidth;
},
page: function(forElement) {
var valueT = 0, valueL = 0;
var element = forElement;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
// Safari fix
if (element.offsetParent==document.body)
if (Element.getStyle(element,'position')=='absolute') break;
} while (element = element.offsetParent);
element = forElement;
do {
if (!window.opera || element.tagName=='BODY') {
valueT -= element.scrollTop || 0;
valueL -= element.scrollLeft || 0;
}
} while (element = element.parentNode);
return [valueL, valueT];
},
clone: function(source, target) {
var options = Object.extend({
setLeft: true,
setTop: true,
setWidth: true,
setHeight: true,
offsetTop: 0,
offsetLeft: 0
}, arguments[2] || {})
// find page position of source
source = $(source);
var p = Position.page(source);
// find coordinate system to use
target = $(target);
var delta = [0, 0];
var parent = null;
// delta [0,0] will do fine with position: fixed elements,
// position:absolute needs offsetParent deltas
if (Element.getStyle(target,'position') == 'absolute') {
parent = Position.offsetParent(target);
delta = Position.page(parent);
}
// correct by body offsets (fixes Safari)
if (parent == document.body) {
delta[0] -= document.body.offsetLeft;
delta[1] -= document.body.offsetTop;
}
// set position
if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
if(options.setWidth) target.style.width = source.offsetWidth + 'px';
if(options.setHeight) target.style.height = source.offsetHeight + 'px';
},
absolutize: function(element) {
element = $(element);
if (element.style.position == 'absolute') return;
Position.prepare();
var offsets = Position.positionedOffset(element);
var top = offsets[1];
var left = offsets[0];
var width = element.clientWidth;
var height = element.clientHeight;
element._originalLeft = left - parseFloat(element.style.left || 0);
element._originalTop = top - parseFloat(element.style.top || 0);
element._originalWidth = element.style.width;
element._originalHeight = element.style.height;
element.style.position = 'absolute';
element.style.top = top + 'px';
element.style.left = left + 'px';
element.style.width = width + 'px';
element.style.height = height + 'px';
},
relativize: function(element) {
element = $(element);
if (element.style.position == 'relative') return;
Position.prepare();
element.style.position = 'relative';
var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
element.style.top = top + 'px';
element.style.left = left + 'px';
element.style.height = element._originalHeight;
element.style.width = element._originalWidth;
}
}
// Safari returns margins on body which is incorrect if the child is absolutely
// positioned. For performance reasons, redefine Position.cumulativeOffset for
// KHTML/WebKit only.
if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
Position.cumulativeOffset = function(element) {
var valueT = 0, valueL = 0;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
if (element.offsetParent == document.body)
if (Element.getStyle(element, 'position') == 'absolute') break;
element = element.offsetParent;
} while (element);
return [valueL, valueT];
}
}

20
src/prototype.js vendored Normal file
View File

@ -0,0 +1,20 @@
<%= include 'HEADER' %>
var Prototype = {
Version: '<%= PROTOTYPE_VERSION %>',
BrowserFeatures: {
XPath: !!document.evaluate
},
ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
emptyFunction: function() {},
K: function(x) { return x }
}
<%= include 'base.js', 'string.js' %>
<%= include 'enumerable.js', 'array.js', 'hash.js', 'range.js' %>
<%= include 'ajax.js', 'dom.js', 'selector.js', 'form.js', 'event.js', 'position.js' %>
Element.addMethods();

29
src/range.js Normal file
View File

@ -0,0 +1,29 @@
ObjectRange = Class.create();
Object.extend(ObjectRange.prototype, Enumerable);
Object.extend(ObjectRange.prototype, {
initialize: function(start, end, exclusive) {
this.start = start;
this.end = end;
this.exclusive = exclusive;
},
_each: function(iterator) {
var value = this.start;
while (this.include(value)) {
iterator(value);
value = value.succ();
}
},
include: function(value) {
if (value < this.start)
return false;
if (this.exclusive)
return value < this.end;
return value <= this.end;
}
});
var $R = function(start, end, exclusive) {
return new ObjectRange(start, end, exclusive);
}

129
src/selector.js Normal file
View File

@ -0,0 +1,129 @@
var Selector = Class.create();
Selector.prototype = {
initialize: function(expression) {
this.params = {classNames: []};
this.expression = expression.toString().strip();
this.parseExpression();
this.compileMatcher();
},
parseExpression: function() {
function abort(message) { throw 'Parse error in selector: ' + message; }
if (this.expression == '') abort('empty expression');
var params = this.params, expr = this.expression, match, modifier, clause, rest;
while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
params.attributes = params.attributes || [];
params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
expr = match[1];
}
if (expr == '*') return this.params.wildcard = true;
while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
modifier = match[1], clause = match[2], rest = match[3];
switch (modifier) {
case '#': params.id = clause; break;
case '.': params.classNames.push(clause); break;
case '':
case undefined: params.tagName = clause.toUpperCase(); break;
default: abort(expr.inspect());
}
expr = rest;
}
if (expr.length > 0) abort(expr.inspect());
},
buildMatchExpression: function() {
var params = this.params, conditions = [], clause;
if (params.wildcard)
conditions.push('true');
if (clause = params.id)
conditions.push('element.readAttribute("id") == ' + clause.inspect());
if (clause = params.tagName)
conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
if ((clause = params.classNames).length > 0)
for (var i = 0, length = clause.length; i < length; i++)
conditions.push('element.hasClassName(' + clause[i].inspect() + ')');
if (clause = params.attributes) {
clause.each(function(attribute) {
var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
var splitValueBy = function(delimiter) {
return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
}
switch (attribute.operator) {
case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
case '|=': conditions.push(
splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
); break;
case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
case '':
case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
default: throw 'Unknown operator ' + attribute.operator + ' in selector';
}
});
}
return conditions.join(' && ');
},
compileMatcher: function() {
this.match = new Function('element', 'if (!element.tagName) return false; \
element = $(element); \
return ' + this.buildMatchExpression());
},
findElements: function(scope) {
var element;
if (element = $(this.params.id))
if (this.match(element))
if (!scope || Element.childOf(element, scope))
return [element];
scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
var results = [];
for (var i = 0, length = scope.length; i < length; i++)
if (this.match(element = scope[i]))
results.push(Element.extend(element));
return results;
},
toString: function() {
return this.expression;
}
}
Object.extend(Selector, {
matchElements: function(elements, expression) {
var selector = new Selector(expression);
return elements.select(selector.match.bind(selector)).map(Element.extend);
},
findElement: function(elements, expression, index) {
if (typeof expression == 'number') index = expression, expression = false;
return Selector.matchElements(elements, expression || '*')[index || 0];
},
findChildElements: function(element, expressions) {
return expressions.map(function(expression) {
return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
var selector = new Selector(expr);
return results.inject([], function(elements, result) {
return elements.concat(selector.findElements(result || element));
});
});
}).flatten();
}
});
function $$() {
return Selector.findChildElements(document, $A(arguments));
}

170
src/string.js Normal file
View File

@ -0,0 +1,170 @@
String.interpret = function(value){
return value == null ? '' : String(value);
}
Object.extend(String.prototype, {
gsub: function(pattern, replacement) {
var result = '', source = this, match;
replacement = arguments.callee.prepareReplacement(replacement);
while (source.length > 0) {
if (match = source.match(pattern)) {
result += source.slice(0, match.index);
result += String.interpret(replacement(match));
source = source.slice(match.index + match[0].length);
} else {
result += source, source = '';
}
}
return result;
},
sub: function(pattern, replacement, count) {
replacement = this.gsub.prepareReplacement(replacement);
count = count === undefined ? 1 : count;
return this.gsub(pattern, function(match) {
if (--count < 0) return match[0];
return replacement(match);
});
},
scan: function(pattern, iterator) {
this.gsub(pattern, iterator);
return this;
},
truncate: function(length, truncation) {
length = length || 30;
truncation = truncation === undefined ? '...' : truncation;
return this.length > length ?
this.slice(0, length - truncation.length) + truncation : this;
},
strip: function() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
},
stripTags: function() {
return this.replace(/<\/?[^>]+>/gi, '');
},
stripScripts: function() {
return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
},
extractScripts: function() {
var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
return (this.match(matchAll) || []).map(function(scriptTag) {
return (scriptTag.match(matchOne) || ['', ''])[1];
});
},
evalScripts: function() {
return this.extractScripts().map(function(script) { return eval(script) });
},
escapeHTML: function() {
var div = document.createElement('div');
var text = document.createTextNode(this);
div.appendChild(text);
return div.innerHTML;
},
unescapeHTML: function() {
var div = document.createElement('div');
div.innerHTML = this.stripTags();
return div.childNodes[0] ? (div.childNodes.length > 1 ?
$A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) :
div.childNodes[0].nodeValue) : '';
},
toQueryParams: function(separator) {
var match = this.strip().match(/([^?#]*)(#.*)?$/);
if (!match) return {};
return match[1].split(separator || '&').inject({}, function(hash, pair) {
if ((pair = pair.split('='))[0]) {
var name = decodeURIComponent(pair[0]);
var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
if (hash[name] !== undefined) {
if (hash[name].constructor != Array)
hash[name] = [hash[name]];
if (value) hash[name].push(value);
}
else hash[name] = value;
}
return hash;
});
},
toArray: function() {
return this.split('');
},
succ: function() {
return this.slice(0, this.length - 1) +
String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
},
camelize: function() {
var parts = this.split('-'), len = parts.length;
if (len == 1) return parts[0];
var camelized = this.charAt(0) == '-'
? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
: parts[0];
for (var i = 1; i < len; i++)
camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
return camelized;
},
capitalize: function(){
return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
},
underscore: function() {
return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
},
dasherize: function() {
return this.gsub(/_/,'-');
},
inspect: function(useDoubleQuotes) {
var escapedString = this.replace(/\\/g, '\\\\');
if (useDoubleQuotes)
return '"' + escapedString.replace(/"/g, '\\"') + '"';
else
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
}
});
String.prototype.gsub.prepareReplacement = function(replacement) {
if (typeof replacement == 'function') return replacement;
var template = new Template(replacement);
return function(match) { return template.evaluate(match) };
}
String.prototype.parseQuery = String.prototype.toQueryParams;
var Template = Class.create();
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
Template.prototype = {
initialize: function(template, pattern) {
this.template = template.toString();
this.pattern = pattern || Template.Pattern;
},
evaluate: function(object) {
return this.template.gsub(this.pattern, function(match) {
var before = match[1];
if (before == '\\') return match[2];
return before + String.interpret(object[match[3]]);
});
}
}

229
test/browser.html Normal file
View File

@ -0,0 +1,229 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Prototype object browser</title>
<style type="text/css" media="screen">
body {
font-family: Lucida Grande, Verdana, sans-serif;
font-size: 13px;
}
.inspector {
margin: 1%;
float: left;
width: 31%;
border: 1px solid #ccc;
background-color: white;
}
.inspector h2 {
font-size: 13px;
margin: 0;
text-align: center;
padding: 5px;
background-color: #e6e6e6;
border-bottom: 1px solid #999;
}
.inspector ul {
height: 200px;
overflow: auto;
margin: 0;
padding-left: 0;
}
.inspector li {
cursor: pointer;
list-style-type: none;
padding: 2px 5px 2px 30px;
color: #333;
}
.inspector li.selected {
background-color: #888;
color: #fff;
}
.inspector.active li.selected {
background-color: #1a76fd;
color: #fff;
}
#path, #value {
width: 97%;
margin: 1%;
}
#path {
margin-bottom: 0;
border: 1px solid #ccc;
border-bottom: 1px solid #999;
background-color: #e6e6e6;
}
#value {
margin-top: 0;
border: 1px solid #ccc;
border-top: none;
overflow: auto;
}
#path_content, #value_content {
display: block;
padding: 15px 30px 15px 30px;
}
</style>
<script type="text/javascript" src="../dist/prototype.js"></script>
<script type="text/javascript">
var Browser = Class.create();
Browser.prototype = {
initialize: function(element, name, value, options) {
this.element = $(element);
this.name = name;
this.value = value;
this.history = [];
Object.extend(this, options || {});
this.reset();
},
reset: function() {
this.go(this.name, this.value);
},
refresh: function() {
var children = $A(this.element.childNodes),
history = this.history.toArray(),
elements = history.slice(-3).pluck('element');
children.each(function(element) {
if (element && !elements.include(element))
this.element.removeChild(element);
}.bind(this));
children = $A(this.element.childNodes);
elements.each(function(element, index) {
Element.removeClassName(element, 'active');
var child = children[index];
if (!child)
this.element.appendChild(element);
else if (!element.parentNode)
this.element.insertBefore(element, child);
}.bind(this));
this.setTitle();
this.setValue();
},
setTitle: function() {
if (this.titleElement)
this.titleElement.innerHTML =
this.history.pluck('name').invoke('escapeHTML').join('.');
},
setValue: function() {
if (this.valueElement)
this.valueElement.innerHTML =
this.currentValue().escapeHTML() + '&nbsp;';
},
currentValue: function() {
try {
return Object.inspect(this.current());
} catch (e) {
return '(Internal Function)';
}
},
current: function() {
return this.history.last().value;
},
go: function(name, value) {
var from = this.history.last();
this.history.push(new Inspector(this, name, value));
this.refresh();
if (from)
Element.addClassName(from.element, 'active');
}
}
var Inspector = Class.create();
Inspector.prototype = {
initialize: function(browser, name, value) {
this.browser = browser;
this.name = name;
this.value = value;
this.id = 'inspector_' + new Date().getTime();
this.history = this.browser.history.toArray();
this.history.push(this);
this.createElement();
this.populate();
},
properties: function() {
var properties = [];
for (var property in this.value)
properties.push(property);
properties.sort();
return properties;
},
createElement: function() {
var element = document.createElement('div');
element.className = 'inspector';
element.id = this.id;
this.element = element;
var title = document.createElement('h2');
title.innerHTML = this.name.toString().escapeHTML();
this.titleElement = title;
var list = document.createElement('ul');
this.listElement = list;
element.appendChild(title);
element.appendChild(list);
},
populate: function() {
this.properties().each(function(property) {
var li = document.createElement('li');
li.innerHTML = property.toString().escapeHTML();
li.onclick = this.select.bind(this, li);
li._property = property;
this.listElement.appendChild(li);
}.bind(this));
},
select: function(element) {
this.unselect();
Element.addClassName(element, 'selected');
this.selectedProperty = element;
this.browser.history = this.history.toArray();
this.browser.go(element._property, this.value[element._property]);
},
unselect: function() {
if (this.selectedProperty)
Element.removeClassName(this.selectedProperty, 'selected');
this.selectedProperty = null;
}
}
</script>
</head>
<body>
<div id="browser_wrapper">
<div id="browser"></div>
<div style="clear: left"></div>
</div>
<h1 id="path"><span id="path_content"></span></h1>
<pre id="value"><div id="value_content"></div></pre>
<script type="text/javascript">
new Browser('browser', 'window', window, {titleElement: $('path_content'), valueElement: $('value_content')})
</script>
</body>
</html>

110
test/console.html Normal file
View File

@ -0,0 +1,110 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Prototype Console</title>
<script src="../dist/prototype.js" type="text/javascript"></script>
<script type="text/javascript">
Prototype.Console = Class.create();
Prototype.Console.prototype = {
initialize: function(element, form, input) {
this.element = $(element);
this.form = $(form);
this.input = $(input);
this.context = window.eval.bind(window);
this.registerCallbacks();
document.title = 'Prototype Console ' + Prototype.Version;
Field.activate(this.input);
},
registerCallbacks: function() {
Event.observe(this.form, 'submit', function(event) {
this.eval($F(this.input));
this.input.value = '';
Field.activate(this.input);
Event.stop(event);
}.bind(this));
},
log: function(type, message) {
new Insertion.Bottom(this.element,
'<tr class="' + type + '"><td>' +
message.escapeHTML() + '</td></tr>');
Element.scrollTo(this.form);
},
eval: function(expression) {
if (expression.match(/^\s*$/)) return;
try {
this.log('input', expression);
window.$_ = this.context.call(window, expression);
this.log('output', Object.inspect($_));
} catch (e) {
this.log('error', e.toString());
}
},
clear: function() {
this.element.innerHTML = '';
}
}
</script>
<style type="text/css">
body {
margin: 0;
padding: 0;
}
.console {
width: 100%;
border-collapse: collapse;
margin-bottom: 50px;
}
.console td {
padding: 5px;
font-family: monospace;
font-size: 14px;
}
.console tr.input td {
background-color: #eee;
font-weight: bold;
}
.console tr.error td,
.console tr.output td {
color: #333;
border-bottom: 1px solid #ccc;
}
.console tr.error td {
color: #f00;
}
#input-form {
width: 100%;
background-color: #f0f5b8;
border-top: 1px solid #333;
padding: 10px;
position: fixed;
height: 25px;
bottom: 0;
margin: 0;
}
</style>
</head>
<body>
<table class="console">
<tbody id="console">
</tbody>
</table>
<form id="input-form">
<input type="text" size="60" id="input" />
<input type="submit" value="Evaluate" />
</form>
<script type="text/javascript">
window.console = new Prototype.Console('console', 'input-form', 'input');
</script>
</body>
</html>

227
test/lib/jstest.rb Normal file
View File

@ -0,0 +1,227 @@
require 'rake/tasklib'
require 'thread'
require 'webrick'
class Browser
def supported?; true; end
def setup ; end
def open(url) ; end
def teardown ; end
def host
require 'rbconfig'
Config::CONFIG['host']
end
def macos?
host.include?('darwin')
end
def windows?
host.include?('mswin')
end
def linux?
host.include?('linux')
end
def applescript(script)
raise "Can't run AppleScript on #{host}" unless macos?
system "osascript -e '#{script}' 2>&1 >/dev/null"
end
end
class FirefoxBrowser < Browser
def initialize(path='c:\Program Files\Mozilla Firefox\firefox.exe')
@path = path
end
def visit(url)
applescript('tell application "Firefox" to Get URL "' + url + '"') if macos?
system("#{@path} #{url}") if windows?
system("firefox #{url}") if linux?
end
def to_s
"Firefox"
end
end
class SafariBrowser < Browser
def supported?
macos?
end
def setup
applescript('tell application "Safari" to make new document')
end
def visit(url)
applescript('tell application "Safari" to set URL of front document to "' + url + '"')
end
def teardown
#applescript('tell application "Safari" to close front document')
end
def to_s
"Safari"
end
end
class IEBrowser < Browser
def initialize(path='C:\Program Files\Internet Explorer\IEXPLORE.EXE')
@path = path
end
def setup
if windows?
puts %{
MAJOR ANNOYANCE on Windows.
You have to shut down the Internet Explorer manually after each test
for the script to proceed.
Any suggestions on fixing this is GREATLY appreaciated!
Thank you for your understanding.
}
end
end
def supported?
windows?
end
def visit(url)
system("#{@path} #{url}") if windows?
end
def to_s
"Internet Explorer"
end
end
class KonquerorBrowser < Browser
def supported?
linux?
end
def visit(url)
system("kfmclient openURL #{url}")
end
def to_s
"Konqueror"
end
end
# shut up, webrick :-)
class ::WEBrick::HTTPServer
def access_log(config, req, res)
# nop
end
end
class ::WEBrick::BasicLog
def log(level, data)
# nop
end
end
class NonCachingFileHandler < WEBrick::HTTPServlet::FileHandler
def do_GET(req, res)
super
res['Content-Type'] = case req.path
when /\.js$/ then 'text/javascript'
when /\.html$/ then 'text/html'
when /\.css$/ then 'text/css'
else 'text/plain'
end
res['ETag'] = nil
res['Last-Modified'] = Time.now + 100**4
res['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
res['Pragma'] = 'no-cache'
res['Expires'] = Time.now - 100**4
end
end
class JavaScriptTestTask < ::Rake::TaskLib
def initialize(name=:test)
@name = name
@tests = []
@browsers = []
@queue = Queue.new
result = []
@server = WEBrick::HTTPServer.new(:Port => 4711) # TODO: make port configurable
@server.mount_proc("/results") do |req, res|
@queue.push(req.query['result'])
res.body = "OK"
end
@server.mount_proc("/content-type") do |req, res|
res.body = req["content-type"]
end
yield self if block_given?
define
end
def define
task @name do
trap("INT") { @server.shutdown }
t = Thread.new { @server.start }
# run all combinations of browsers and tests
@browsers.each do |browser|
if browser.supported?
browser.setup
@tests.each do |test|
browser.visit("http://localhost:4711#{test}?resultsURL=http://localhost:4711/results&t=" + ("%.6f" % Time.now.to_f))
result = @queue.pop
puts "#{test} on #{browser}: #{result}"
end
browser.teardown
else
puts "Skipping #{browser}, not supported on this OS"
end
browser.teardown
end
@server.shutdown
t.join
end
end
def mount(path, dir=nil)
dir = Dir.pwd + path unless dir
# don't cache anything in our tests
@server.mount(path, NonCachingFileHandler, dir)
end
# test should be specified as a url
def run(test)
@tests<<test
end
def browser(browser)
browser =
case(browser)
when :firefox
FirefoxBrowser.new
when :safari
SafariBrowser.new
when :ie
IEBrowser.new
when :konqueror
KonquerorBrowser.new
else
browser
end
@browsers<<browser
end
end

497
test/lib/unittest.js Normal file
View File

@ -0,0 +1,497 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// experimental, Firefox-only
Event.simulateMouse = function(element, eventName) {
var options = Object.extend({
pointerX: 0,
pointerY: 0,
buttons: 0
}, arguments[2] || {});
var oEvent = document.createEvent("MouseEvents");
oEvent.initMouseEvent(eventName, true, true, document.defaultView,
options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
false, false, false, false, 0, $(element));
if(this.mark) Element.remove(this.mark);
this.mark = document.createElement('div');
this.mark.appendChild(document.createTextNode(" "));
document.body.appendChild(this.mark);
this.mark.style.position = 'absolute';
this.mark.style.top = options.pointerY + "px";
this.mark.style.left = options.pointerX + "px";
this.mark.style.width = "5px";
this.mark.style.height = "5px;";
this.mark.style.borderTop = "1px solid red;"
this.mark.style.borderLeft = "1px solid red;"
if(this.step)
alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
$(element).dispatchEvent(oEvent);
};
// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
// You need to downgrade to 1.0.4 for now to get this working
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
Event.simulateKey = function(element, eventName) {
var options = Object.extend({
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
keyCode: 0,
charCode: 0
}, arguments[2] || {});
var oEvent = document.createEvent("KeyEvents");
oEvent.initKeyEvent(eventName, true, true, window,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
options.keyCode, options.charCode );
$(element).dispatchEvent(oEvent);
};
Event.simulateKeys = function(element, command) {
for(var i=0; i<command.length; i++) {
Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
}
};
var Test = {}
Test.Unit = {};
// security exception workaround
Test.Unit.inspect = Object.inspect;
Test.Unit.Logger = Class.create();
Test.Unit.Logger.prototype = {
initialize: function(log) {
this.log = $(log);
if (this.log) {
this._createLogTable();
}
},
start: function(testName) {
if (!this.log) return;
this.testName = testName;
this.lastLogLine = document.createElement('tr');
this.statusCell = document.createElement('td');
this.nameCell = document.createElement('td');
this.nameCell.appendChild(document.createTextNode(testName));
this.messageCell = document.createElement('td');
this.lastLogLine.appendChild(this.statusCell);
this.lastLogLine.appendChild(this.nameCell);
this.lastLogLine.appendChild(this.messageCell);
this.loglines.appendChild(this.lastLogLine);
},
finish: function(status, summary) {
if (!this.log) return;
this.lastLogLine.className = status;
this.statusCell.innerHTML = status;
this.messageCell.innerHTML = this._toHTML(summary);
},
message: function(message) {
if (!this.log) return;
this.messageCell.innerHTML = this._toHTML(message);
},
summary: function(summary) {
if (!this.log) return;
this.logsummary.innerHTML = this._toHTML(summary);
},
_createLogTable: function() {
this.log.innerHTML =
'<div id="logsummary"></div>' +
'<table id="logtable">' +
'<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
'<tbody id="loglines"></tbody>' +
'</table>';
this.logsummary = $('logsummary')
this.loglines = $('loglines');
},
_toHTML: function(txt) {
return txt.escapeHTML().replace(/\n/g,"<br/>");
}
}
Test.Unit.Runner = Class.create();
Test.Unit.Runner.prototype = {
initialize: function(testcases) {
this.options = Object.extend({
testLog: 'testlog'
}, arguments[1] || {});
this.options.resultsURL = this.parseResultsURLQueryParameter();
if (this.options.testLog) {
this.options.testLog = $(this.options.testLog) || null;
}
if(this.options.tests) {
this.tests = [];
for(var i = 0; i < this.options.tests.length; i++) {
if(/^test/.test(this.options.tests[i])) {
this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
}
}
} else {
if (this.options.test) {
this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
} else {
this.tests = [];
for(var testcase in testcases) {
if(/^test/.test(testcase)) {
this.tests.push(new Test.Unit.Testcase(testcase, testcases[testcase], testcases["setup"], testcases["teardown"]));
}
}
}
}
this.currentTest = 0;
this.logger = new Test.Unit.Logger(this.options.testLog);
setTimeout(this.runTests.bind(this), 1000);
},
parseResultsURLQueryParameter: function() {
return window.location.search.parseQuery()["resultsURL"];
},
// Returns:
// "ERROR" if there was an error,
// "FAILURE" if there was a failure, or
// "SUCCESS" if there was neither
getResult: function() {
var hasFailure = false;
for(var i=0;i<this.tests.length;i++) {
if (this.tests[i].errors > 0) {
return "ERROR";
}
if (this.tests[i].failures > 0) {
hasFailure = true;
}
}
if (hasFailure) {
return "FAILURE";
} else {
return "SUCCESS";
}
},
postResults: function() {
if (this.options.resultsURL) {
new Ajax.Request(this.options.resultsURL,
{ method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
}
},
runTests: function() {
var test = this.tests[this.currentTest];
if (!test) {
// finished!
this.postResults();
this.logger.summary(this.summary());
return;
}
if(!test.isWaiting) {
this.logger.start(test.name);
}
test.run();
if(test.isWaiting) {
this.logger.message("Waiting for " + test.timeToWait + "ms");
setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
} else {
this.logger.finish(test.status(), test.summary());
var actionButtons = test.actionButtons();
if (actionButtons)
$(this.logger.lastLogLine).down('td', 2).appendChild(actionButtons);
this.currentTest++;
// tail recursive, hopefully the browser will skip the stackframe
this.runTests();
}
},
summary: function() {
var assertions = 0;
var failures = 0;
var errors = 0;
var messages = [];
for(var i=0;i<this.tests.length;i++) {
assertions += this.tests[i].assertions;
failures += this.tests[i].failures;
errors += this.tests[i].errors;
}
return (
this.tests.length + " tests, " +
assertions + " assertions, " +
failures + " failures, " +
errors + " errors");
}
}
Test.Unit.Assertions = Class.create();
Test.Unit.Assertions.prototype = {
initialize: function() {
this.assertions = 0;
this.failures = 0;
this.errors = 0;
this.messages = [];
this.actions = {};
},
summary: function() {
return (
this.assertions + " assertions, " +
this.failures + " failures, " +
this.errors + " errors" + "\n" +
this.messages.join("\n"));
},
actionButtons: function() {
if (!Object.keys(this.actions).any()) return false;
var div = $(document.createElement("div"));
div.addClassName("action_buttons");
for (var title in this.actions) {
var button = $(document.createElement("input"));
button.value = title;
button.type = "button";
button.observe("click", this.actions[title]);
div.appendChild(button);
}
return div;
},
pass: function() {
this.assertions++;
},
fail: function(message) {
this.failures++;
this.messages.push("Failure: " + message);
},
info: function(message) {
this.messages.push("Info: " + message);
},
error: function(error, test) {
this.errors++;
this.actions['retry with throw'] = function() { test.run(true) };
this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) + ")");
},
status: function() {
if (this.failures > 0) return 'failed';
if (this.errors > 0) return 'error';
return 'passed';
},
assert: function(expression) {
var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
try { expression ? this.pass() :
this.fail(message); }
catch(e) { this.error(e); }
},
assertEqual: function(expected, actual) {
var message = arguments[2] || "assertEqual";
try { (expected == actual) ? this.pass() :
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
'", actual "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertEnumEqual: function(expected, actual) {
var message = arguments[2] || "assertEnumEqual";
try { $A(expected).length == $A(actual).length &&
expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
', actual ' + Test.Unit.inspect(actual)); }
catch(e) { this.error(e); }
},
assertNotEqual: function(expected, actual) {
var message = arguments[2] || "assertNotEqual";
try { (expected != actual) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertIdentical: function(expected, actual) {
var message = arguments[2] || "assertIdentical";
try { (expected === actual) ? this.pass() :
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
'", actual "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertNotIdentical: function(expected, actual) {
var message = arguments[2] || "assertNotIdentical";
try { !(expected === actual) ? this.pass() :
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
'", actual "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertNull: function(obj) {
var message = arguments[1] || 'assertNull'
try { (obj===null) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
catch(e) { this.error(e); }
},
assertNotNull: function(obj) {
var message = arguments[1] || 'assertNotNull'
try { (obj!==null) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
catch(e) { this.error(e); }
},
assertUndefined: function(obj) {
var message = arguments[1] || 'assertUndefined'
try { (typeof obj=="undefined") ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
catch(e) { this.error(e); }
},
assertNotUndefined: function(obj) {
var message = arguments[1] || 'assertNotUndefined'
try { (typeof obj != "undefined") ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
catch(e) { this.error(e); }
},
assertNullOrUndefined: function(obj){
var message = arguments[1] || 'assertNullOrUndefined'
try { (obj==null) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
catch(e) { this.error(e); }
},
assertNotNullOrUndefined: function(obj){
var message = arguments[1] || 'assertNotNullOrUndefined'
try { (obj!=null) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
catch(e) { this.error(e); }
},
assertMatch: function(expected, actual) {
var message = arguments[2] || 'assertMatch';
var regex = new RegExp(expected);
try { (regex.exec(actual)) ? this.pass() :
this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertHidden: function(element) {
var message = arguments[1] || 'assertHidden';
this.assertEqual("none", element.style.display, message);
},
assertNotNull: function(object) {
var message = arguments[1] || 'assertNotNull';
this.assert(object != null, message);
},
assertInstanceOf: function(expected, actual) {
var message = arguments[2] || 'assertInstanceOf';
try {
(actual instanceof expected) ? this.pass() :
this.fail(message + ": object was not an instance of the expected type"); }
catch(e) { this.error(e); }
},
assertNotInstanceOf: function(expected, actual) {
var message = arguments[2] || 'assertNotInstanceOf';
try {
!(actual instanceof expected) ? this.pass() :
this.fail(message + ": object was an instance of the not expected type"); }
catch(e) { this.error(e); }
},
assertRespondsTo: function(method, obj) {
var message = arguments[2] || 'assertRespondsTo';
try {
(obj[method] && typeof obj[method] == 'function') ? this.pass() :
this.fail(message + ": object doesn't respond to [" + method + "]"); }
catch(e) { this.error(e); }
},
assertRaise: function(exceptionName, method) {
var message = arguments[2] || 'assertRaise';
try {
method();
this.fail(message + ": exception expected but none was raised"); }
catch(e) {
(e.name==exceptionName) ? this.pass() : this.error(e);
}
},
assertNothingRaised: function(method) {
var message = arguments[1] || 'assertNothingRaised';
try {
method();
this.pass();
} catch (e) {
this.fail(message + ": " + e.toString());
}
},
_isVisible: function(element) {
element = $(element);
if(!element.parentNode) return true;
this.assertNotNull(element);
if(element.style && Element.getStyle(element, 'display') == 'none')
return false;
return this._isVisible(element.parentNode);
},
assertNotVisible: function(element) {
this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
},
assertVisible: function(element) {
this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
},
assertElementsMatch: function() {
var expressions = $A(arguments), elements = $A(expressions.shift());
if (elements.length != expressions.length) {
this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
return false;
}
elements.zip(expressions).all(function(pair, index) {
var element = $(pair.first()), expression = pair.last();
if (element.match(expression)) return true;
this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
}.bind(this)) && this.pass();
},
assertElementMatches: function(element, expression) {
this.assertElementsMatch([element], expression);
},
benchmark: function(operation, iterations) {
var startAt = new Date();
(iterations || 1).times(operation);
var timeTaken = ((new Date())-startAt);
this.info((arguments[2] || 'Operation') + ' finished ' +
iterations + ' iterations in ' + (timeTaken/1000)+'s' );
return timeTaken;
}
}
Test.Unit.Testcase = Class.create();
Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
initialize: function(name, test, setup, teardown) {
Test.Unit.Assertions.prototype.initialize.bind(this)();
this.name = name;
this.test = test || function() {};
this.setup = setup || function() {};
this.teardown = teardown || function() {};
this.isWaiting = false;
this.timeToWait = 1000;
},
wait: function(time, nextPart) {
this.isWaiting = true;
this.test = nextPart;
this.timeToWait = time;
},
run: function(rethrow) {
try {
try {
if (!this.isWaiting) this.setup.bind(this)();
this.isWaiting = false;
this.test.bind(this)();
} finally {
if(!this.isWaiting) {
this.teardown.bind(this)();
}
}
}
catch(e) {
if (rethrow) throw e;
this.error(e, this);
}
}
});

49
test/test.css Normal file
View File

@ -0,0 +1,49 @@
body, div, p, h1, h2, h3, ul, ol, span, a, table, td, form, img, li {
font-family: sans-serif;
}
body {
font-size:0.8em;
}
#log {
padding-bottom: 1em;
border-bottom: 2px solid #000;
margin-bottom: 2em;
}
#logsummary {
margin-bottom: 1em;
padding: 1ex;
border: 1px solid #000;
font-weight: bold;
}
#logtable {
width:100%;
border-collapse: collapse;
border: 1px dotted #666;
}
#logtable td, #logtable th {
text-align: left;
padding: 3px 8px;
border: 1px dotted #666;
}
#logtable .passed {
background-color: #cfc;
}
#logtable .failed, #logtable .error {
background-color: #fcc;
}
#logtable td div.action_buttons {
display: inline;
}
#logtable td div.action_buttons input {
margin: 0 5px;
font-size: 10px;
}

174
test/unit/ajax.html Normal file
View File

@ -0,0 +1,174 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of utility functions in ajax.js
</p>
<!-- Log output -->
<div id="testlog"> </div>
<div id="content"></div>
<div id="content2" style="color:red"></div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
var responderCounter = 0;
new Test.Unit.Runner({
setup: function(){
$('content').update('');
$('content2').update('');
},
teardown: function(){
// hack to cleanup responders
Ajax.Responders.responders = [Ajax.Responders.responders[0]];
},
testSynchronousRequest: function() {with(this) {
assertEqual("", $("content").innerHTML);
assertEqual(0, Ajax.activeRequestCount);
new Ajax.Request("fixtures/hello.js", {
asynchronous: false,
method: 'GET',
onComplete: function(response) { eval(response.responseText) }
});
assertEqual(0, Ajax.activeRequestCount);
var h2 = $("content").firstChild;
assertEqual("Hello world!", h2.innerHTML);
}},
testAsynchronousRequest: function() {with(this) {
assertEqual("", $("content").innerHTML);
new Ajax.Request("fixtures/hello.js", {
asynchronous: true,
method: 'get',
onComplete: function(response) { eval(response.responseText) }
});
wait(1000,function(){
var h2 = $("content").firstChild;
assertEqual("Hello world!", h2.innerHTML);
});
}},
testUpdater: function() {with(this) {
assertEqual("", $("content").innerHTML);
new Ajax.Updater("content", "fixtures/content.html", { method:'get' });
// lowercase comparison because of MSIE which presents HTML tags in uppercase
var sentence = ("Pack my box with <em>five dozen</em> liquor jugs! " +
"Oh, how <strong>quickly</strong> daft jumping zebras vex...").toLowerCase();
wait(1000,function(){
assertEqual(sentence, $("content").innerHTML.strip().toLowerCase());
$('content').update('');
assertEqual("", $("content").innerHTML);
new Ajax.Updater({ success:"content", failure:"content2" },
"fixtures/content.html", { method:'get', parameters:{ pet:'monkey' } });
new Ajax.Updater("", "fixtures/content.html", { method:'get', parameters:"pet=monkey" });
wait(1000,function(){
assertEqual(sentence, $("content").innerHTML.strip().toLowerCase());
assertEqual("", $("content2").innerHTML);
});
});
}},
testResponders: function(){with(this) {
// check for internal responder
assertEqual(1, Ajax.Responders.responders.length);
var dummyResponder = {
onComplete: function(req){ /* dummy */ }
};
Ajax.Responders.register(dummyResponder);
assertEqual(2, Ajax.Responders.responders.length);
// don't add twice
Ajax.Responders.register(dummyResponder);
assertEqual(2, Ajax.Responders.responders.length);
Ajax.Responders.unregister(dummyResponder);
assertEqual(1, Ajax.Responders.responders.length);
var responder = {
onCreate: function(req){ responderCounter++ },
onLoading: function(req){ responderCounter++ },
onComplete: function(req){ responderCounter++ }
};
Ajax.Responders.register(responder);
assertEqual(0, responderCounter);
assertEqual(0, Ajax.activeRequestCount);
new Ajax.Request("fixtures/content.html", { method:'get', parameters:"pet=monkey" });
assertEqual(1, responderCounter);
assertEqual(1, Ajax.activeRequestCount);
wait(1000,function(){
assertEqual(3, responderCounter);
assertEqual(0, Ajax.activeRequestCount);
});
}},
testEvalResponseShouldBeCalledBeforeOnComplete: function() {with(this) {
assertEqual("", $("content").innerHTML);
assertEqual(0, Ajax.activeRequestCount);
new Ajax.Request("fixtures/hello.js", {
asynchronous: false,
method: 'GET',
onComplete: function(response) { assertNotEqual("", $("content").innerHTML) }
});
assertEqual(0, Ajax.activeRequestCount);
var h2 = $("content").firstChild;
assertEqual("Hello world!", h2.innerHTML);
}},
testContentTypeSetForSimulatedVerbs: function() {with(this) {
var isRunningFromRake = true;
new Ajax.Request('/content-type', {
method: 'put',
contentType: 'application/bogus',
asynchronous: false,
onFailure: function() {
isRunningFromRake = false;
},
onComplete: function(response) {
if (isRunningFromRake)
assertEqual('application/bogus; charset=UTF-8', response.responseText);
}
});
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>

152
test/unit/array.html Normal file
View File

@ -0,0 +1,152 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of the Array.prototype extensions
</p>
<!-- Log output -->
<div id="testlog"> </div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
var globalArgsTest = 'nothing to see here';
new Test.Unit.Runner({
testToArrayOnArguments: function(){ with(this) {
function toArrayOnArguments(){
globalArgsTest = $A(arguments);
}
toArrayOnArguments();
assertEnumEqual([], globalArgsTest);
toArrayOnArguments('foo');
assertEnumEqual(['foo'], globalArgsTest);
toArrayOnArguments('foo','bar');
assertEnumEqual(['foo','bar'], globalArgsTest);
}},
testClear: function(){ with(this) {
assertEnumEqual([], [].clear());
assertEnumEqual([], [1].clear());
assertEnumEqual([], [1,2].clear());
}},
testClone: function(){ with(this) {
assertEnumEqual([], [].clone());
assertEnumEqual([1], [1].clone());
assertEnumEqual([1,2], [1,2].clone());
assertEnumEqual([0,1,2], [0,1,2].clone());
var a = [0,1,2];
var b = a;
assertIdentical(a, b);
b = a.clone();
assertNotIdentical(a, b);
}},
testFirst: function(){ with(this) {
assertUndefined([].first());
assertEqual(1, [1].first());
assertEqual(1, [1,2].first());
}},
testLast: function(){ with(this) {
assertUndefined([].last());
assertEqual(1, [1].last());
assertEqual(2, [1,2].last());
}},
testCompact: function(){ with(this) {
assertEnumEqual([], [].compact());
assertEnumEqual([1,2,3], [1,2,3].compact());
assertEnumEqual([0,1,2,3], [0,null,1,2,undefined,3].compact());
assertEnumEqual([1,2,3], [null,1,2,3,null].compact());
}},
testFlatten: function(){ with(this) {
assertEnumEqual([], [].flatten());
assertEnumEqual([1,2,3], [1,2,3].flatten());
assertEnumEqual([1,2,3], [1,[[[2,3]]]].flatten());
assertEnumEqual([1,2,3], [[1],[2],[3]].flatten());
assertEnumEqual([1,2,3], [[[[[[[1]]]]]],2,3].flatten());
}},
testIndexOf: function(){ with(this) {
assertEqual(-1, [].indexOf(1));
assertEqual(-1, [0].indexOf(1));
assertEqual(0, [1].indexOf(1));
assertEqual(1, [0,1,2].indexOf(1));
}},
testInspect: function(){ with(this) {
assertEqual('[]',[].inspect());
assertEqual('[1]',[1].inspect());
assertEqual('[\'a\']',['a'].inspect());
assertEqual('[\'a\', 1]',['a',1].inspect());
}},
testReduce: function(){ with(this) {
assertUndefined([].reduce());
assertNull([null].reduce());
assertEqual(1, [1].reduce());
assertEnumEqual([1,2,3], [1,2,3].reduce());
assertEnumEqual([1,null,3], [1,null,3].reduce());
}},
testReverse: function(){ with(this) {
assertEnumEqual([], [].reverse());
assertEnumEqual([1], [1].reverse());
assertEnumEqual([2,1], [1,2].reverse());
assertEnumEqual([3,2,1], [1,2,3].reverse());
}},
testSize: function(){ with(this) {
assertEqual(4, [0, 1, 2, 3].size());
assertEqual(0, [].size());
}},
testUniq: function(){ with(this) {
assertEnumEqual([1], [1, 1, 1].uniq());
assertEnumEqual([1], [1].uniq());
assertEnumEqual([], [].uniq());
assertEnumEqual([0, 1, 2, 3], [0, 1, 2, 2, 3, 0, 2].uniq());
}},
testWithout: function(){ with(this) {
assertEnumEqual([], [].without(0));
assertEnumEqual([], [0].without(0));
assertEnumEqual([1], [0,1].without(0));
assertEnumEqual([1,2], [0,1,2].without(0));
}},
test$w: function(){ with(this) {
assertEnumEqual(['a', 'b', 'c', 'd'], $w('a b c d'));
assertEnumEqual([], $w(' '));
assertEnumEqual(['a'], $w('a'));
assertEnumEqual(['a'], $w('a '));
assertEnumEqual(['a'], $w(' a'));
assertEnumEqual(['a', 'b', 'c', 'd'], $w(' a b\nc\t\nd\n'));
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>

133
test/unit/base.html Normal file
View File

@ -0,0 +1,133 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of utility functions in base.js
</p>
<!-- Log output -->
<div id="testlog"> </div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
var peEventCount = 0;
// peEventFired will stop the PeriodicalExecuter after 3 callbacks
function peEventFired(pe) {
if (++peEventCount>2) {
pe.stop();
}
}
var arg1 = 1;
var arg2 = 2;
var arg3 = 3;
function TestObj(){}
TestObj.prototype.assertingEventHandler =
function( event, assertEvent, assert1, assert2, assert3, a1, a2, a3 ){
assertEvent(event);
assert1(a1);
assert2(a2);
assert3(a3);
}
var globalBindTest = null;
new Test.Unit.Runner({
testFunctionBind: function() { with(this) {
function methodWithoutArguments(){
globalBindTest = this.hi;
}
function methodWithArguments(){
globalBindTest = this.hi + ',' + $A(arguments).join(',');
}
function methodWithBindArguments(){
globalBindTest = this.hi + ',' + $A(arguments).join(',');
}
function methodWithBindArgumentsAndArguments(){
globalBindTest = this.hi + ',' + $A(arguments).join(',');
}
methodWithoutArguments.bind({hi:'without'})();
assertEqual('without', globalBindTest);
methodWithArguments.bind({hi:'with'})('arg1','arg2');
assertEqual('with,arg1,arg2', globalBindTest);
methodWithBindArguments.bind({hi:'withBindArgs'},'arg1','arg2')();
assertEqual('withBindArgs,arg1,arg2', globalBindTest);
methodWithBindArgumentsAndArguments.bind({hi:'withBindArgsAndArgs'},'arg1','arg2')('arg3','arg4');
assertEqual('withBindArgsAndArgs,arg1,arg2,arg3,arg4', globalBindTest);
}},
testObjectInspect: function() { with(this) {
assertEqual('undefined', Object.inspect());
assertEqual('undefined', Object.inspect(undefined));
assertEqual('null', Object.inspect(null));
assertEqual("'foo\\\\b\\\'ar'", Object.inspect('foo\\b\'ar'));
assertEqual('[]', Object.inspect([]));
}},
// sanity check
testDoesntExtendObjectPrototype: function() {with(this) {
// for-in is supported with objects
var iterations = 0, obj = { a: 1, b: 2, c: 3 };
for(property in obj) iterations++;
assertEqual(3, iterations);
// for-in is not supported with arrays
iterations = 0;
var arr = [1,2,3];
for(property in arr) iterations++;
assert(iterations > 3);
}},
testPeriodicalExecuterStop: function() {with(this) {
new PeriodicalExecuter(peEventFired, 0.1);
wait(1000, function() {
assertEqual(3, peEventCount);
});
}},
testBindAsEventListener: function() {
for( var i = 0; i < 10; ++i ){
var div = document.createElement('div');
div.setAttribute('id','test-'+i);
document.body.appendChild(div);
var tobj = new TestObj();
var eventTest = {test:true};
var call = tobj.assertingEventHandler.bindAsEventListener(tobj,
this.assertEqual.bind(this,eventTest),
this.assertEqual.bind(this,arg1),
this.assertEqual.bind(this,arg2),
this.assertEqual.bind(this,arg3), arg1, arg2, arg3 );
call(eventTest);
}
}
}, 'testlog');
// ]]>
</script>
</body>
</html>

809
test/unit/dom.html Normal file
View File

@ -0,0 +1,809 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
#style_test_1 { cursor: pointer; font-size:12px;}
div.style-test { margin-left: 1px }
#not_floating_style { float: none }
#floating_style { float: left }
#op2 { opacity:0.5;filter:alpha(opacity=50)progid:DXImageTransform.Microsoft.Blur(strength=10);}
#scroll_test_1 {
margin: 10px;
padding: 10px;
position: relative;
}
#scroll_test_2 {
position: absolute;
left: 10px;
top: 10px;
}
#dimensions-visible,
#dimensions-display-none,
#dimensions-visible-pos-rel,
#dimensions-display-none-pos-rel,
#dimensions-visible-pos-abs,
#dimensions-display-none-pos-abs {
font-size: 10px;
height: 10em;
width: 20em;
}
#dimensions-visible-pos-abs,
#dimensions-display-none-pos-abs {
position: absolute;
top: 15px;
left: 15px;
}
#dimensions-visible-pos-rel,
#dimensions-display-none-pos-rel {
position: relative;
top: 15px;
left: 15px;
}
#dimensions-display-none, #imensions-display-none-pos-rel, #dimensions-display-none-pos-abs {
display: none;
}
#dimensions-table, #dimensions-tbody, #dimensions-tr, #dimensions-td {
font-size: 10px;
margin: 0;
padding: 0;
border: 0;
border-spacing: 0;
height: 10em;
width: 20em;
}
/* for scroll test on really big screens */
body {
height: 40000px;
}
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of functions in dom.js
</p>
<!-- Log output -->
<div id="scroll_test_1">
<p id="scroll_test_2">Scroll test</p>
</div>
<div id="testlog"> </div>
<div id="test-visible">visible</div>
<div id="test-hidden" style="display:none;">hidden</div>
<div id="test-toggle-visible">visible</div>
<div id="test-toggle-hidden" style="display:none;">hidden</div>
<div id="test-hide-visible">visible</div>
<div id="test-hide-hidden" style="display:none;">hidden</div>
<div id="test-show-visible">visible</div>
<div id="test-show-hidden" style="display:none;">hidden</div>
<div id="removable-container"><div id="removable"></div></div>
<div>
<table>
<tbody id="table">
<tr>
<td>Data</td>
</tr>
<tr>
<td id="a_cell">First Row</td>
</tr>
<tr id="second_row">
<td>Second Row</td>
</tr>
</tbody>
</table>
</div>
<div id="table-container-to-replace">
<table>
<tbody id="table-to-replace">
<tr>
<td>Data</td>
</tr>
<tr>
<td id="a_cell-to-replace">First Row</td>
</tr>
<tr id="second_row-to-replace">
<td>Second Row</td>
</tr>
</tbody>
</table>
</div>
<p class="test">Test paragraph outside of container</p>
<div id="container">
<p class="test" id="intended">Test paragraph 1 inside of container</p>
<p class="test">Test paragraph 2 inside of container</p>
<p class="test">Test paragraph 3 inside of container</p>
<p class="test">Test paragraph 4 inside of container</p>
</div>
<div id="testdiv">to be updated</div>
<div id="testdiv-replace-container-1"><div id="testdiv-replace-1"></div></div>
<div id="testdiv-replace-container-2"><div id="testdiv-replace-2"></div></div>
<div id="testdiv-replace-container-3"><div id="testdiv-replace-3"></div></div>
<div id="testdiv-replace-container-4"><div id="testdiv-replace-4"></div></div>
<div id="testdiv-replace-container-5"><div id="testdiv-replace-5"></div></div>
<div id="element_with_visible_overflow" style="overflow:visible">V</div>
<div id="element_with_hidden_overflow" style="overflow:hidden">H</div>
<div id="element_with_scroll_overflow" style="overflow:scroll">S</div>
<div id="element_extend_test"> </div>
<div id="test_whitespace"> <span> </span>
<div><div></div> </div><span> </span>
</div>
<div id="nav_tests_isolator">
<div id="nav_test_first_sibling"></div>
<div></div>
<p id="nav_test_p" class="test"></p>
<span id="nav_test_prev_sibling"></span>
<ul id="navigation_test" style="display: none">
<li class="first"><em>A</em></li>
<li><em class="dim">B</em></li>
<li id="navigation_test_c">
<em>C</em>
<ul>
<li><em class="dim">E</em></li>
<li id="navigation_test_f"><em>F</em></li>
</ul>
</li>
<li class="last"><em>D</em></li>
</ul>
<div id="navigation_test_next_sibling">
<!-- -->
</div>
<p></p>
</div>
<div id="class_names">
<p class="A"></p>
<ul class="A B" id="class_names_ul">
<li class="C"></li>
<li class="A C"></li>
</ul>
<div class="B C D"></div>
</div>
<div id="style_test_1" style="display:none;"></div>
<div id="style_test_2" class="style-test" style="font-size:11px;"></div>
<div id="style_test_3">blah</div>
<div id="custom_attributes">
<div foo="1" bar="2"></div>
<div foo="2"></div>
</div>
<a id="attributes_with_issues_1" href="test.html" accesskey="L" tabindex="50" title="a link"></a>
<a id="attributes_with_issues_2" href="" accesskey="" tabindex="" title=""></a>
<a id="attributes_with_issues_3"></a>
<div id="dom_attribute_precedence">
<form>
<input type="submit" id="update" />
</form>
</div>
<div id="not_floating_none">NFN</div>
<div id="not_floating_inline" style="float:none">NFI</div>
<div id="not_floating_style">NFS</div>
<div id="floating_inline" style="float:left">FI</div>
<div id="floating_style">FS</div>
<!-- Test Element opacity functions -->
<img id="op1" alt="op2" src="fixtures/logo.gif" style="opacity:0.5;filter:alpha(opacity=50)" />
<img id="op2" alt="op2" src="fixtures/logo.gif"/>
<img id="op3" alt="op3" src="fixtures/logo.gif"/>
<img id="op4-ie" alt="op3" src="fixtures/logo.gif" style="filter:alpha(opacity=30)" />
<div id="dimensions-visible"></div>
<div id="dimensions-display-none"></div>
<div id="dimensions-visible-pos-rel"></div>
<div id="dimensions-display-none-pos-rel"></div>
<div id="dimensions-visible-pos-abs"></div>
<div id="dimensions-display-none-pos-abs"></div>
<table border="0" cellspacing="0" cellpadding="0" id="dimensions-table">
<tbody id="dimensions-tbody">
<tr id="dimensions-tr">
<td id="dimensions-td">Data</td>
</tr>
</tbody>
</table>
<p id="test-empty"></p>
<p id="test-empty-but-contains-whitespace">
</p>
<p id="test-full">content</p>
<div id="ancestor"><div id="child"><div><div id="great-grand-child"></div></div></div></div>
<div id="not-in-the-family"></div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
var testVar = 'to be updated';
new Test.Unit.Runner({
testDollarFunction: function() { with(this) {
assertUndefined($());
assertNull(document.getElementById('noWayThisIDExists'));
assertNull($('noWayThisIDExists'));
assertIdentical(document.getElementById('testdiv'), $('testdiv'));
assertEnumEqual([ $('testdiv'), $('container') ], $('testdiv', 'container'));
assertEnumEqual([ $('testdiv'), undefined, $('container') ],
$('testdiv', 'noWayThisIDExists', 'container'));
var elt = $('testdiv');
assertIdentical(elt, $(elt));
assertRespondsTo('hide', elt);
}},
testGetElementsByClassName: function() {with(this) {
assertElementsMatch(document.getElementsByClassName('A'), 'p.A', 'ul#class_names_ul.A', 'li.A.C');
assertElementsMatch(document.getElementsByClassName('A', 'class_names_ul'), 'li.A.C');
assertElementsMatch(document.getElementsByClassName('B', 'class_names'), 'ul#class_names_ul.A.B', 'div.B.C.D');
assertElementsMatch(document.getElementsByClassName('B', 'class_names_ul'));
}},
testInsertWithTR: function() {with(this) {
new Insertion.After('second_row', '<tr id="third_row"><td>Third Row</td></tr>');
assert($('second_row').childOf('table'));
}},
testElementVisible: function(){with(this) {
assertNotEqual('none', $('test-visible').style.display);
assertEqual('none', $('test-hidden').style.display);
}},
testElementToggle: function(){with(this) {
$('test-toggle-visible').toggle();
assert(!$('test-toggle-visible').visible());
$('test-toggle-visible').toggle();
assert($('test-toggle-visible').visible());
$('test-toggle-hidden').toggle();
assert($('test-toggle-hidden').visible());
$('test-toggle-hidden').toggle();
assert(!$('test-toggle-hidden').visible());
}},
testElementShow: function(){with(this) {
$('test-show-visible').show();
assert($('test-show-visible').visible());
$('test-show-hidden').show();
assert($('test-show-hidden').visible());
}},
testElementHide: function(){with(this) {
$('test-hide-visible').hide();
assert(!$('test-hide-visible').visible());
$('test-hide-hidden').hide();
assert(!$('test-hide-hidden').visible());
}},
testElementRemove: function(){with(this) {
$('removable').remove();
assert($('removable-container').empty());
}},
testElementUpdate: function() {with(this) {
$('testdiv').update('hello from div!');
assertEqual('hello from div!', $('testdiv').innerHTML);
Element.update('testdiv', 'another hello from div!');
assertEqual('another hello from div!', $('testdiv').innerHTML);
Element.update('testdiv', 123);
assertEqual('123', $('testdiv').innerHTML)
Element.update('testdiv');
assertEqual('', $('testdiv').innerHTML)
}},
testElementUpdateWithScript: function() {with(this) {
$('testdiv').update('hello from div!<script>testVar="hello!"</'+'script>');
assertEqual('hello from div!',$('testdiv').innerHTML);
wait(100,function(){
assertEqual('hello!',testVar);
Element.update('testdiv','another hello from div!\n<script>testVar="another hello!"</'+'script>\nhere it goes');
// note: IE normalizes whitespace (like line breaks) to single spaces, thus the match test
assertMatch(/^another hello from div!\s+here it goes$/,$('testdiv').innerHTML);
wait(100,function(){
assertEqual('another hello!',testVar);
});
});
}},
testElementUpdateInTableRow: function() {with(this) {
$('second_row').update('<td id="i_am_a_td">test</td>');
assertEqual('test',$('i_am_a_td').innerHTML);
Element.update('second_row','<td id="i_am_a_td">another <span>test</span></td>');
assertEqual('another <span>test</span>',$('i_am_a_td').innerHTML.toLowerCase());
}},
testElementUpdateInTableCell: function() {with(this) {
Element.update('a_cell','another <span>test</span>');
assertEqual('another <span>test</span>',$('a_cell').innerHTML.toLowerCase());
}},
testElementUpdateInTable: function() {with(this) {
Element.update('table','<tr><td>boo!</td></tr>');
assertMatch(/^<tr>\s*<td>boo!<\/td><\/tr>$/,$('table').innerHTML.toLowerCase());
}},
testElementReplace: function() {with(this) {
$('testdiv-replace-1').replace('hello from div!');
assertEqual('hello from div!', $('testdiv-replace-container-1').innerHTML);
$('testdiv-replace-2').replace(123);
assertEqual('123', $('testdiv-replace-container-2').innerHTML)
$('testdiv-replace-3').replace();
assertEqual('', $('testdiv-replace-container-3').innerHTML)
}},
testElementReplaceWithScript: function() {with(this) {
$('testdiv-replace-4').replace('hello from div!<script>testVarReplace="hello!"</'+'script>');
assertEqual('hello from div!', $('testdiv-replace-container-4').innerHTML);
wait(100,function(){
assertEqual('hello!',testVarReplace);
$('testdiv-replace-5').replace('another hello from div!\n<script>testVarReplace="another hello!"</'+'script>\nhere it goes');
// note: IE normalizes whitespace (like line breaks) to single spaces, thus the match test
assertMatch(/^another hello from div!\s+here it goes$/,$('testdiv-replace-container-5').innerHTML);
wait(100,function(){
assertEqual('another hello!',testVarReplace);
});
});
}},
testElementSelectorMethod: function() {with(this) {
var testSelector = $('container').getElementsBySelector('p.test');
assertEqual(testSelector.length, 4);
assertEqual(testSelector[0], $('intended'));
assertEqual(testSelector[0], $$('#container p.test')[0]);
}},
testElementClassNameMethod: function() {with(this) {
var testClassNames = $('container').getElementsByClassName('test');
var testSelector = $('container').getElementsBySelector('p.test');
assertEqual(testClassNames[0], $('intended'));
assertEqual(testClassNames.length, 4);
assertEqual(testSelector[3], testClassNames[3]);
assertEqual(testClassNames.length, testSelector.length);
}},
testElementAncestors: function() {with(this) {
var ancestors = $('navigation_test_f').ancestors();
assertElementsMatch(ancestors, 'ul', 'li', 'ul#navigation_test',
'div#nav_tests_isolator', 'body', 'html');
assertElementsMatch(ancestors.last().ancestors());
}},
testElementDescendants: function() {with(this) {
assertElementsMatch($('navigation_test').descendants(),
'li', 'em', 'li', 'em.dim', 'li', 'em', 'ul', 'li',
'em.dim', 'li#navigation_test_f', 'em', 'li', 'em');
assertElementsMatch($('navigation_test_f').descendants(), 'em');
}},
testElementImmediateDescendants: function() {with(this) {
assertElementsMatch($('navigation_test').immediateDescendants(),
'li.first', 'li', 'li#navigation_test_c', 'li.last');
assertNotEqual(0, $('navigation_test_next_sibling').childNodes.length);
assertEnumEqual([], $('navigation_test_next_sibling').immediateDescendants());
}},
testElementPreviousSiblings: function() {with(this) {
assertElementsMatch($('navigation_test').previousSiblings(),
'span#nav_test_prev_sibling', 'p.test', 'div', 'div#nav_test_first_sibling');
assertElementsMatch($('navigation_test_f').previousSiblings(), 'li');
}},
testElementNextSiblings: function() {with(this) {
assertElementsMatch($('navigation_test').nextSiblings(),
'div#navigation_test_next_sibling', 'p');
assertElementsMatch($('navigation_test_f').nextSiblings());
}},
testElementSiblings: function() {with(this) {
assertElementsMatch($('navigation_test').siblings(),
'div#nav_test_first_sibling', 'div', 'p.test',
'span#nav_test_prev_sibling', 'div#navigation_test_next_sibling', 'p');
}},
testElementUp: function() {with(this) {
var element = $('navigation_test_f');
assertElementMatches(element.up(), 'ul');
assertElementMatches(element.up(0), 'ul');
assertElementMatches(element.up(1), 'li');
assertElementMatches(element.up(2), 'ul#navigation_test');
assertElementsMatch(element.up('li').siblings(), 'li.first', 'li', 'li.last');
assertElementMatches(element.up('ul', 1), 'ul#navigation_test');
assertEqual(undefined, element.up('garbage'));
assertEqual(undefined, element.up(6));
}},
testElementDown: function() {with(this) {
var element = $('navigation_test');
assertElementMatches(element.down(), 'li.first');
assertElementMatches(element.down(0), 'li.first');
assertElementMatches(element.down(1), 'em');
assertElementMatches(element.down('li', 5), 'li.last');
assertElementMatches(element.down('ul').down('li', 1), 'li#navigation_test_f');
}},
testElementPrevious: function() {with(this) {
var element = $('navigation_test').down('li.last');
assertElementMatches(element.previous(), 'li#navigation_test_c');
assertElementMatches(element.previous(1), 'li');
assertElementMatches(element.previous('.first'), 'li.first');
assertEqual(undefined, element.previous(3));
assertEqual(undefined, $('navigation_test').down().previous());
}},
testElementNext: function() {with(this) {
var element = $('navigation_test').down('li.first');
assertElementMatches(element.next(), 'li');
assertElementMatches(element.next(1), 'li#navigation_test_c');
assertElementMatches(element.next(2), 'li.last');
assertElementMatches(element.next('.last'), 'li.last');
assertEqual(undefined, element.next(3));
assertEqual(undefined, element.next(2).next());
}},
testElementInspect: function() {with(this) {
assertEqual('<ul id="navigation_test">', $('navigation_test').inspect());
assertEqual('<li class="first">', $('navigation_test').down().inspect());
assertEqual('<em>', $('navigation_test').down(1).inspect());
}},
testElementMakeClipping: function() {with(this) {
var chained = Element.extend(document.createElement('DIV'));
assertEqual(chained, chained.makeClipping());
assertEqual(chained, chained.makeClipping());
assertEqual(chained, chained.makeClipping().makeClipping());
assertEqual(chained, chained.undoClipping());
assertEqual(chained, chained.undoClipping());
assertEqual(chained, chained.undoClipping().makeClipping());
['hidden','visible','scroll'].each( function(overflowValue) {
var element = $('element_with_'+overflowValue+'_overflow');
assertEqual(overflowValue, element.getStyle('overflow'));
element.makeClipping();
assertEqual('hidden', element.getStyle('overflow'));
element.undoClipping();
assertEqual(overflowValue, element.getStyle('overflow'));
});
}},
testElementExtend: function() {with(this) {
var element = $('element_extend_test');
assertRespondsTo('show', element);
var XHTML_TAGS = $w(
'a abbr acronym address applet area '+
'b bdo big blockquote br button caption '+
'cite code col colgroup dd del dfn div dl dt '+
'em fieldset form h1 h2 h3 h4 h5 h6 hr '+
'i iframe img input ins kbd label legend li '+
'map object ol optgroup option p param pre q samp '+
'script select small span strong style sub sup '+
'table tbody td textarea tfoot th thead tr tt ul var');
XHTML_TAGS.each(function(tag) {
var element = document.createElement(tag);
assertEqual(element, Element.extend(element));
assertRespondsTo('show', element);
});
[null,'','a','aa'].each(function(content) {
var textnode = document.createTextNode(content);
assertEqual(textnode, Element.extend(textnode));
assert(typeof textnode['show'] == 'undefined');
});
}},
testElementCleanWhitespace: function() {with(this) {
Element.cleanWhitespace("test_whitespace");
assertEqual(3, $("test_whitespace").childNodes.length);
assertEqual(1, $("test_whitespace").firstChild.nodeType);
assertEqual('SPAN', $("test_whitespace").firstChild.tagName);
assertEqual(1, $("test_whitespace").firstChild.nextSibling.nodeType);
assertEqual('DIV', $("test_whitespace").firstChild.nextSibling.tagName);
assertEqual(1, $("test_whitespace").firstChild.nextSibling.nextSibling.nodeType);
assertEqual('SPAN', $("test_whitespace").firstChild.nextSibling.nextSibling.tagName);
var element = document.createElement('DIV');
element.appendChild(document.createTextNode(''));
element.appendChild(document.createTextNode(''));
assertEqual(2, element.childNodes.length);
Element.cleanWhitespace(element);
assertEqual(0, element.childNodes.length);
}},
testElementEmpty: function() {with(this) {
assert($('test-empty').empty());
assert($('test-empty-but-contains-whitespace').empty());
assert(!$('test-full').empty());
}},
testDescendantOf: function() {with(this) {
assert($('child').descendantOf('ancestor'));
assert($('child').descendantOf($('ancestor')));
assert($('great-grand-child').descendantOf('ancestor'));
assert(!$('great-grand-child').descendantOf('not-in-the-family'));
}},
testChildOf: function() {with(this) {
assert($('child').childOf('ancestor'));
assert($('child').childOf($('ancestor')));
assert($('great-grand-child').childOf('ancestor'));
assert(!$('great-grand-child').childOf('not-in-the-family'));
assertIdentical(Element.Methods.childOf, Element.Methods.descendantOf);
}},
testElementSetStyle: function() { with(this) {
Element.setStyle('style_test_3',{ 'left': '2px' });
assertEqual('2px', $('style_test_3').style.left);
Element.setStyle('style_test_3',{ marginTop: '1px' });
assertEqual('1px', $('style_test_3').style.marginTop);
$('style_test_3').setStyle({ 'margin-top': '2px', left: '-1px' });
assertEqual('-1px', $('style_test_3').style.left);
assertEqual('2px', $('style_test_3').style.marginTop);
assertEqual('none', $('style_test_3').getStyle('float'));
$('style_test_3').setStyle({ 'float': 'left' });
assertEqual('left', $('style_test_3').getStyle('float'));
$('style_test_3').setStyle({ cssFloat: 'none' });
assertEqual('none', $('style_test_3').getStyle('float'));
assertEqual(1, $('style_test_3').getStyle('opacity'));
$('style_test_3').setStyle({ opacity: 0.5 });
assertEqual(0.5, $('style_test_3').getStyle('opacity'));
// should remove opacity
$('style_test_3').setStyle({ opacity: '' });
assertEqual(1, $('style_test_3').getStyle('opacity'));
}},
testElementGetStyle: function() { with(this) {
assertEqual("none",
$('style_test_1').getStyle('display'));
// not displayed, so "null" ("auto" is tranlated to "null")
assertNull(Element.getStyle('style_test_1','width'));
$('style_test_1').show();
// from id rule
assertEqual("pointer",
Element.getStyle('style_test_1','cursor'));
assertEqual("block",
Element.getStyle('style_test_2','display'));
// we should always get something for width (if displayed)
// firefox and safari automatically send the correct value,
// IE is special-cased to do the same
assertEqual($('style_test_2').offsetWidth+'px', Element.getStyle('style_test_2','width'));
assertEqual("static",Element.getStyle('style_test_1','position'));
// from style
assertEqual("11px",
Element.getStyle('style_test_2','font-size'));
// from class
assertEqual("1px",
Element.getStyle('style_test_2','margin-left'));
['not_floating_none','not_floating_style','not_floating_inline'].each(function(element){
assertEqual('none', $(element).getStyle('float'));
assertEqual('none', $(element).getStyle('cssFloat'));
});
['floating_style','floating_inline'].each(function(element){
assertEqual('left', $(element).getStyle('float'));
assertEqual('left', $(element).getStyle('cssFloat'));
});
assertEqual(0.5, $('op1').getStyle('opacity'));
assertEqual(0.5, $('op2').getStyle('opacity'));
assertEqual(1.0, $('op3').getStyle('opacity'));
$('op1').setStyle({opacity: '0.3'});
$('op2').setStyle({opacity: '0.3'});
$('op3').setStyle({opacity: '0.3'});
assertEqual(0.3, $('op1').getStyle('opacity'));
assertEqual(0.3, $('op2').getStyle('opacity'));
assertEqual(0.3, $('op3').getStyle('opacity'));
if(navigator.appVersion.match(/MSIE/)) {
assertEqual('alpha(opacity=30)', $('op1').getStyle('filter'));
assertEqual('progid:DXImageTransform.Microsoft.Blur(strength=10)alpha(opacity=30)', $('op2').getStyle('filter'));
assertEqual('alpha(opacity=30)', $('op3').getStyle('filter'));
assertEqual(0.3, $('op4-ie').getStyle('opacity'));
}
// verify that value is stil found when using camelized
// strings (function previously used getPropertyValue()
// which expected non-camelized strings)
assertEqual("12px", $('style_test_1').getStyle('fontSize'));
}},
testElementReadAttribute: function() {with(this) {
assertEqual('test.html' , $('attributes_with_issues_1').readAttribute('href'));
assertEqual('L' , $('attributes_with_issues_1').readAttribute('accesskey'));
assertEqual('50' , $('attributes_with_issues_1').readAttribute('tabindex'));
assertEqual('a link' , $('attributes_with_issues_1').readAttribute('title'));
['href', 'accesskey', 'accesskey', 'title'].each(function(attr){
assertEqual('' , $('attributes_with_issues_2').readAttribute(attr));
});
var elements = $('custom_attributes').immediateDescendants();
assertEnumEqual(['1', '2'], elements.invoke('readAttribute', 'foo'));
assertEnumEqual(['2', null], elements.invoke('readAttribute', 'bar'));
}},
testElementGetHeight: function() {with(this) {
assertIdentical(100, $('dimensions-visible').getHeight());
assertIdentical(100, $('dimensions-display-none').getHeight());
}},
testElementGetWidth: function() {with(this) {
assertIdentical(200, $('dimensions-visible').getWidth());
assertIdentical(200, $('dimensions-display-none').getWidth());
}},
testElementGetDimensions: function() {with(this) {
assertIdentical(100, $('dimensions-visible').getDimensions().height);
assertIdentical(200, $('dimensions-visible').getDimensions().width);
assertIdentical(100, $('dimensions-display-none').getDimensions().height);
assertIdentical(200, $('dimensions-display-none').getDimensions().width);
assertIdentical(100, $('dimensions-visible-pos-rel').getDimensions().height);
assertIdentical(200, $('dimensions-visible-pos-rel').getDimensions().width);
assertIdentical(100, $('dimensions-display-none-pos-rel').getDimensions().height);
assertIdentical(200, $('dimensions-display-none-pos-rel').getDimensions().width);
assertIdentical(100, $('dimensions-visible-pos-abs').getDimensions().height);
assertIdentical(200, $('dimensions-visible-pos-abs').getDimensions().width);
assertIdentical(100, $('dimensions-display-none-pos-abs').getDimensions().height);
assertIdentical(200, $('dimensions-display-none-pos-abs').getDimensions().width);
$('dimensions-td').hide();
assertIdentical(100, $('dimensions-td').getDimensions().height);
assertIdentical(200, $('dimensions-td').getDimensions().width);
$('dimensions-td').show();
$('dimensions-tr').hide();
assertIdentical(100, $('dimensions-tr').getDimensions().height);
assertIdentical(200, $('dimensions-tr').getDimensions().width);
$('dimensions-tr').show();
$('dimensions-table').hide();
assertIdentical(100, $('dimensions-table').getDimensions().height);
assertIdentical(200, $('dimensions-table').getDimensions().width);
}},
testDOMAttributesHavePrecedenceOverExtendedElementMethods: function() {with(this) {
assertNothingRaised(function() { $('dom_attribute_precedence').down('form') });
assertEqual($('dom_attribute_precedence').down('input'), $('dom_attribute_precedence').down('form').update);
}},
testClassNames: function() {with(this) {
assertEnumEqual([], $('class_names').classNames());
assertEnumEqual(['A'], $('class_names').down().classNames());
assertEnumEqual(['A', 'B'], $('class_names_ul').classNames());
}},
testHasClassName: function() {with(this) {
assert(!$('class_names').hasClassName('does_not_exist'));
assert($('class_names').down().hasClassName('A'));
assert(!$('class_names').down().hasClassName('does_not_exist'));
assert($('class_names_ul').hasClassName('A'));
assert($('class_names_ul').hasClassName('B'));
assert(!$('class_names_ul').hasClassName('does_not_exist'));
}},
testAddClassName: function() {with(this) {
$('class_names').addClassName('added_className');
assertEnumEqual(['added_className'], $('class_names').classNames());
$('class_names').addClassName('added_className'); // verify that className cannot be added twice.
assertEnumEqual(['added_className'], $('class_names').classNames());
$('class_names').addClassName('another_added_className');
assertEnumEqual(['added_className', 'another_added_className'], $('class_names').classNames());
}},
testRemoveClassName: function() {with(this) {
$('class_names').removeClassName('added_className');
assertEnumEqual(['another_added_className'], $('class_names').classNames());
$('class_names').removeClassName('added_className'); // verify that removing a non existent className is safe.
assertEnumEqual(['another_added_className'], $('class_names').classNames());
$('class_names').removeClassName('another_added_className');
assertEnumEqual([], $('class_names').classNames());
}},
testToggleClassName: function() {with(this) {
$('class_names').toggleClassName('toggled_className');
assertEnumEqual(['toggled_className'], $('class_names').classNames());
$('class_names').toggleClassName('toggled_className');
assertEnumEqual([], $('class_names').classNames());
$('class_names_ul').toggleClassName('toggled_className');
assertEnumEqual(['A', 'B', 'toggled_className'], $('class_names_ul').classNames());
$('class_names_ul').toggleClassName('toggled_className');
assertEnumEqual(['A', 'B'], $('class_names_ul').classNames());
}},
testElementScrollTo: function() {with(this) {
var elem = $('scroll_test_2');
Element.scrollTo('scroll_test_2');
assertEqual(Position.page(elem)[1], 0);
window.scrollTo(0, 0);
elem.scrollTo();
assertEqual(Position.page(elem)[1], 0);
window.scrollTo(0, 0);
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>

View File

@ -0,0 +1,65 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of extra methods mixed in to elements with $() and $$().
</p>
<!-- Log output -->
<div id="testlog"> </div>
<h2>Test Form Elements</h2>
<form id="form">
<input type="text" id="input" value="4" />
<input type="submit" />
</form>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
new Test.Unit.Runner({
testInput: function() {with(this) {
assert($("input").present != null);
assert(typeof $("input").present == 'function');
assert($("input").select != null);
}},
testForm: function() {with(this) {
assert($("form").reset != null);
assert($("form").getInputs().length == 2);
}},
testEvent: function() {with(this) {
assert($("form").observe != null)
// Can't really test this one with TestUnit...
$('form').observe("submit", function(e) {
alert("yeah!");
Event.stop(e);
});
}},
testCollections: function() {with(this) {
assert($$("input").all(function(input) {
return (input.focus != null);
}));
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>

289
test/unit/enumerable.html Normal file
View File

@ -0,0 +1,289 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of utility functions in enumerable.js
</p>
<!-- Log output -->
<div id="testlog"> </div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
var Fixtures = {
People: [
{name: 'Sam Stephenson', nickname: 'sam-'},
{name: 'Marcel Molina Jr.', nickname: 'noradio'},
{name: 'Scott Barron', nickname: 'htonl'},
{name: 'Nicholas Seckar', nickname: 'Ulysses'}
],
Nicknames: $w('sam- noradio htonl Ulysses'),
Primes: [
1, 2, 3, 5, 7, 11, 13, 17, 19, 23,
29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
71, 73, 79, 83, 89, 97
],
Z: []
};
for (var i = 1; i <= 100; i++)
Fixtures.Z.push(i);
function prime(value) {
for (var i = 2; i < value; i++)
if (value % i == 0) return false;
return true;
}
new Test.Unit.Runner({
testEachBreak: function() {with(this) {
var result = 0;
[1, 2, 3].each(function(value) {
if ((result = value) == 2) throw $break;
});
assertEqual(2, result);
}},
testEachContinue: function() {with(this) {
var results = [];
[1, 2, 3].each(function(value) {
if (value == 2) throw $continue;
results.push(value);
});
assertEqual('1, 3', results.join(', '));
}},
testEachChaining: function() {with(this) {
assertEqual(Fixtures.Primes, Fixtures.Primes.each(Prototype.emptyFunction));
assertEqual(3, [1, 2, 3].each(Prototype.emptyFunction).length);
}},
testAny: function() {with(this) {
assert(!([].any()));
assert([true, true, true].any());
assert([true, false, false].any());
assert(![false, false, false].any());
assert([1, 2, 3, 4, 5].any(function(value) {
return value > 3;
}));
assert(![1, 2, 3, 4, 5].any(function(value) {
return value > 10;
}));
}},
testAll: function() {with(this) {
assert([].all());
assert([true, true, true].all());
assert(![true, false, false].all());
assert(![false, false, false].all());
assert([1, 2, 3, 4, 5].all(function(value) {
return value > 0;
}));
assert(![1, 2, 3, 4, 5].all(function(value) {
return value > 3;
}));
}},
testCollect: function() {with(this) {
assertEqual(Fixtures.Nicknames.join(', '),
Fixtures.People.collect(function(person) {
return person.nickname;
}).join(", "));
assertEqual(26, Fixtures.Primes.map().length);
}},
testDetect: function() {with(this) {
assertEqual('Marcel Molina Jr.',
Fixtures.People.detect(function(person) {
return person.nickname.match(/no/);
}).name);
}},
testEachSlice: function() {with(this) {
assertEnumEqual([], [].eachSlice(2));
assertEqual(1, [1].eachSlice(1).length);
assertEnumEqual([1], [1].eachSlice(1)[0]);
assertEqual(2, [1,2,3].eachSlice(2).length);
assertEnumEqual(
[3, 2, 1, 11, 7, 5, 19, 17, 13, 31, 29, 23, 43, 41, 37, 59, 53, 47, 71, 67, 61, 83, 79, 73, 97, 89],
Fixtures.Primes.eachSlice( 3, function(slice){ return slice.reverse() }).flatten()
);
}},
testEachWithIndex: function() {with(this) {
var nicknames = [], indexes = [];
Fixtures.People.each(function(person, index) {
nicknames.push(person.nickname);
indexes.push(index);
});
assertEqual(Fixtures.Nicknames.join(', '),
nicknames.join(', '));
assertEqual('0, 1, 2, 3', indexes.join(', '));
}},
testFindAll: function() {with(this) {
assertEqual(Fixtures.Primes.join(', '),
Fixtures.Z.findAll(prime).join(', '));
}},
testGrep: function() {with(this) {
assertEqual('noradio, htonl',
Fixtures.Nicknames.grep(/o/).join(", "));
assertEqual('NORADIO, HTONL',
Fixtures.Nicknames.grep(/o/, function(nickname) {
return nickname.toUpperCase();
}).join(", "))
}},
testInclude: function() {with(this) {
assert(Fixtures.Nicknames.include('sam-'));
assert(Fixtures.Nicknames.include('noradio'));
assert(Fixtures.Nicknames.include('htonl'));
assert(Fixtures.Nicknames.include('Ulysses'));
assert(!Fixtures.Nicknames.include('gmosx'));
}},
testInGroupsOf: function() { with(this) {
assertEnumEqual([], [].inGroupsOf(3));
var arr = [1, 2, 3, 4, 5, 6].inGroupsOf(3);
assertEqual(2, arr.length);
assertEnumEqual([1, 2, 3], arr[0]);
assertEnumEqual([4, 5, 6], arr[1]);
arr = [1, 2, 3, 4, 5, 6].inGroupsOf(4);
assertEqual(2, arr.length);
assertEnumEqual([1, 2, 3, 4], arr[0]);
assertEnumEqual([5, 6, null, null], arr[1]);
arr = [1, 2, 3].inGroupsOf(4,'x');
assertEqual(1, arr.length);
assertEnumEqual([1, 2, 3, 'x'], arr[0]);
assertEnumEqual([1,2,3,'a'], [1,2,3].inGroupsOf(2, 'a').flatten());
arr = [1, 2, 3].inGroupsOf(5, '');
assertEqual(1, arr.length);
assertEnumEqual([1, 2, 3, '', ''], arr[0]);
assertEnumEqual([1,2,3,0], [1,2,3].inGroupsOf(2, 0).flatten());
assertEnumEqual([1,2,3,false], [1,2,3].inGroupsOf(2, false).flatten());
}},
testInject: function() {with(this) {
assertEqual(1061,
Fixtures.Primes.inject(0, function(sum, value) {
return sum + value;
}));
}},
testInvoke: function() {with(this) {
var result = [[2, 1, 3], [6, 5, 4]].invoke('sort');
assertEqual(2, result.length);
assertEqual('1, 2, 3', result[0].join(', '));
assertEqual('4, 5, 6', result[1].join(', '));
result = result.invoke('invoke', 'toString', 2);
assertEqual('1, 10, 11', result[0].join(', '));
assertEqual('100, 101, 110', result[1].join(', '));
}},
testMax: function() {with(this) {
assertEqual(100, Fixtures.Z.max());
assertEqual(97, Fixtures.Primes.max());
assertEqual(2, [ -9, -8, -7, -6, -4, -3, -2, 0, -1, 2 ].max());
assertEqual('sam-', Fixtures.Nicknames.max()); // ?s > ?U
}},
testMin: function() {with(this) {
assertEqual(1, Fixtures.Z.min());
assertEqual(0, [ 1, 2, 3, 4, 5, 6, 7, 8, 0, 9 ].min());
assertEqual('Ulysses', Fixtures.Nicknames.min()); // ?U < ?h
}},
testPartition: function() {with(this) {
var result = Fixtures.People.partition(function(person) {
return person.name.length < 15;
}).invoke('pluck', 'nickname');
assertEqual(2, result.length);
assertEqual('sam-, htonl', result[0].join(', '));
assertEqual('noradio, Ulysses', result[1].join(', '));
}},
testPluck: function() {with(this) {
assertEqual(Fixtures.Nicknames.join(', '),
Fixtures.People.pluck('nickname').join(', '));
}},
testReject: function() {with(this) {
assertEqual(0,
Fixtures.Nicknames.reject(Prototype.K).length);
assertEqual('sam-, noradio, htonl',
Fixtures.Nicknames.reject(function(nickname) {
return nickname != nickname.toLowerCase();
}).join(', '));
}},
testSortBy: function() {with(this) {
assertEqual('htonl, noradio, sam-, Ulysses',
Fixtures.People.sortBy(function(value) {
return value.nickname.toLowerCase();
}).pluck('nickname').join(', '));
}},
testToArray: function() {with(this) {
var result = Fixtures.People.toArray();
assert(result != Fixtures.People); // they're different objects...
assertEqual(Fixtures.Nicknames.join(', '),
result.pluck('nickname').join(', ')); // but the values are the same
}},
testZip: function() {with(this) {
var result = [1, 2, 3].zip([4, 5, 6], [7, 8, 9]);
assertEqual('[[1, 4, 7], [2, 5, 8], [3, 6, 9]]', result.inspect());
result = [1, 2, 3].zip([4, 5, 6], [7, 8, 9], function(array) { return array.reverse() });
assertEqual('[[7, 4, 1], [8, 5, 2], [9, 6, 3]]', result.inspect());
}},
testSize: function() {with(this) {
assertEqual(4, Fixtures.People.size());
assertEqual(4, Fixtures.Nicknames.size());
assertEqual(26, Fixtures.Primes.size());
assertEqual(0, [].size());
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>

View File

@ -0,0 +1 @@
Pack my box with <em>five dozen</em> liquor jugs! Oh, how <strong>quickly</strong> daft jumping zebras vex...

View File

@ -0,0 +1 @@
$("content").update("<H2>Hello world!</H2>");

BIN
test/unit/fixtures/logo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

280
test/unit/form.html Normal file
View File

@ -0,0 +1,280 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of utility functions in form.js
</p>
<!-- Log output -->
<div id="testlog"> </div>
<form id="form">
<input type="text" name="val1" id="input_enabled" value="4" />
<div>This is not a form element</div>
<input type="text" name="val2" id="input_disabled" disabled="disabled" value="5" />
<input type="submit" />
</form>
<div id="form_wrapper">
<form id="form_selects">
<select name="vu">
<option value="1" selected="selected">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<select id="multiSel1" name="vm[]" multiple="multiple">
<option id="multiSel1_opt1" value="1" selected="selected">One</option>
<option id="multiSel1_opt2" value="2">Two</option>
<option id="multiSel1_opt3" value="3" selected="selected">Three</option>
</select>
<select name="nvu">
<option selected="selected">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<fieldset id="form_fieldset">
<select name="nvm[]" multiple="multiple">
<option selected="selected">One</option>
<option>Two</option>
<option selected="selected">Three</option>
</select>
<select name="evu">
<option value="" selected="selected">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<select name="evm[]" multiple="multiple">
<option value="" selected="selected">One</option>
<option>Two</option>
<option selected="selected">Three</option>
</select>
</fieldset>
</form>
</div>
<form id="form_getelements">
<select id="tf_selectOne" name="tf_selectOne"><option></option><option>1</option></select>
<textarea id="tf_textarea" name="tf_textarea"></textarea>
<input type="checkbox" id="tf_checkbox" name="tf_checkbox" value="on" />
<select id="tf_selectMany" name="tf_selectMany" multiple="multiple"></select>
<input type="text" id="tf_text" name="tf_text" />
<div>This is not a form element</div>
<input type="radio" id="tf_radio" name="tf_radio" value="on" />
<input type="hidden" id="tf_hidden" name="tf_hidden" />
<input type="password" id="tf_password" name="tf_password" />
</form>
<form id="form_focus">
<input type="text" name="focus_disabled" id="focus_disabled" disabled="disabled"/>
<input type="submit" name="focus_submit" id="focus_submit" />
<input type="button" name="focus_button" id="focus_button" value="button" />
<input type="reset" name="focus_reset" id="focus_reset" />
<input type="text" name="focus_text" id="focus_text" value="Hello" />
</form>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
var callbackCounter = 0;
var timedCounter = 0;
new Test.Unit.Runner({
// Make sure to set defaults in the test forms, as some browsers override this
// with previously entered values on page reload
setup: function(){ with(this) {
$('input_enabled').value = '4';
$('input_disabled').value = '5';
$('tf_selectOne').selectedIndex = 0;
$('tf_textarea').value = '';
$('tf_text').value = '';
$('tf_hidden').value = '';
$('tf_password').value = '';
$('tf_checkbox').checked = false;
$('tf_radio').checked = false;
}},
testFormElementEventObserver: function(){ with(this) {
var observer = new Form.Element.EventObserver('input_enabled', function(){
callbackCounter++;
});
assertEqual(0, callbackCounter);
$('input_enabled').value = 'boo!';
observer.onElementEvent(); // can't test the event directly, simulating
assertEqual(1, callbackCounter);
}},
testFormElementObserver: function(){ with(this) {
// First part: regular field
var observer = new Form.Element.Observer('input_enabled', 0.5, function() {
++timedCounter;
});
// Test it's unchanged yet
assertEqual(0, timedCounter);
// Test it doesn't change on first check
wait(550, function() {
assertEqual(0, timedCounter);
// Change, test it doesn't immediately change
$('input_enabled').value = 'yowza!';
assertEqual(0, timedCounter);
// Test it changes on next check, but not again on the next
wait(550, function() {
assertEqual(1, timedCounter);
wait(550, function() {
assertEqual(1, timedCounter);
});
});
});
// Second part: multiple-select! -- Fails before patch in #6593.
[1, 2, 3].each(function(index) {
$('multiSel1_opt' + index).selected = (1 == index);
});
timedCounter = 0;
observer = new Form.Element.Observer('multiSel1', 0.5, function() {
++timedCounter;
});
// Test it's unchanged yet
assertEqual(0, timedCounter);
// Test it doesn't change on first check
wait(550, function() {
assertEqual(0, timedCounter);
// Change, test it doesn't immediately change
// NOTE: it is important that the 3rd be re-selected, for the
// serialize form to obtain the expected value :-)
$('multiSel1_opt3').selected = true;
assertEqual(0, timedCounter);
// Test it changes on next check, but not again on the next
wait(550, function() {
assertEqual(1, timedCounter);
wait(550, function() {
assertEqual(1, timedCounter);
});
});
});
}},
testFormElementEnabling: function(){ with(this) {
assert($('input_disabled').disabled);
$('input_disabled').enable();
assert(!$('input_disabled').disabled);
$('input_disabled').disable();
assert($('input_disabled').disabled);
assert(!$('input_enabled').disabled);
$('input_enabled').disable();
assert($('input_enabled').disabled);
$('input_enabled').enable();
assert(!$('input_enabled').disabled);
}},
// due to the lack of a DOM hasFocus() API method,
// we're simulating things here a little bit
testFormActivating: function(){ with(this) {
// Firefox, IE, and Safari 2+
function getSelection(element){
try {
if (typeof element.selectionStart == 'number') {
return element.value.substring(element.selectionStart, element.selectionEnd);
} else if (document.selection && document.selection.createRange) {
return document.selection.createRange().text;
}
}
catch(e){ return null }
}
// Form.focusFirstElement shouldn't focus disabled elements
var element = Form.findFirstElement('form_focus');
assertEqual('focus_submit',element.id);
// Test IE doesn't select text on buttons
Form.focusFirstElement('form_focus');
if(document.selection) assertEqual('', getSelection(element));
// Form.Field.activate shouldn't select text on buttons
element = $('focus_text');
assertEqual('', getSelection(element));
// Form.Field.activate should select text on text input elements
element.activate();
assertEqual('Hello', getSelection(element));
}},
testFormGetElements: function() {with(this) {
var formElements = $('form_getelements').getElements();
assertEqual(8, formElements.length);
assertEqual('tf_selectOne', formElements[0].id);
assertEqual('tf_textarea', formElements[1].id);
assertEqual('tf_checkbox', formElements[2].id);
assertEqual('tf_selectMany', formElements[3].id);
assertEqual('tf_text', formElements[4].id);
assertEqual('tf_radio', formElements[5].id);
assertEqual('tf_hidden', formElements[6].id);
assertEqual('tf_password', formElements[7].id);
}},
testFormGetInputs: function() {with(this){
var form = $('form_getelements'), formInputs = Form.getInputs(form);
assertEqual(formInputs.length, 5);
assert(formInputs instanceof Array);
assert(formInputs.all(function(input) { return (input.tagName == "INPUT"); }));
var formInputs2 = form.getInputs();
assertEqual(formInputs2.length, 5);
assert(formInputs2 instanceof Array);
assert(formInputs2.all(function(input) { return (input.tagName == "INPUT"); }));
}},
testFormSerialize: function() {with(this){
assertEqual('tf_selectOne=&tf_textarea=&tf_text=&tf_hidden=&tf_password=',
Form.serialize('form_getelements'));
$('tf_selectOne').selectedIndex = 1;
$('tf_textarea').value = "boo hoo!";
$('tf_text').value = "123öäü";
$('tf_hidden').value = "moo%hoo&test";
$('tf_password').value = 'sekrit code';
$('tf_checkbox').checked = true;
$('tf_radio').checked = true;
assertEqual(
'tf_selectOne=1&tf_textarea=boo%20hoo!&tf_checkbox=on&tf_text=123%C3%B6%C3%A4%C3%BC&'+
'tf_radio=on&tf_hidden=moo%25hoo%26test&tf_password=sekrit%20code',
Form.serialize('form_getelements'));
// Checks that disabled element is not included in serialized form.
$('input_enabled').enable();
assertEqual('val1=4', Form.serialize('form'));
// Checks that select-related serializations work just fine
assertEqual('vu=1&vm%5B%5D=1&vm%5B%5D=3&nvu=One&nvm%5B%5D=One&nvm%5B%5D=Three&evu=&evm%5B%5D=&evm%5B%5D=Three', Form.serialize('form_selects'));
}},
testFormSerializeWorksWithNonFormElements: function() {with(this) {
assertEqual('nvm%5B%5D=One&nvm%5B%5D=Three&evu=&evm%5B%5D=&evm%5B%5D=Three', Form.serialize('form_fieldset'));
assertEqual('vu=1&vm%5B%5D=1&vm%5B%5D=3&nvu=One&nvm%5B%5D=One&nvm%5B%5D=Three&evu=&evm%5B%5D=&evm%5B%5D=Three', Form.serialize('form_wrapper'));
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>

119
test/unit/hash.html Normal file
View File

@ -0,0 +1,119 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of the Hash.prototype extensions
</p>
<!-- Log output -->
<div id="testlog"> </div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
var Fixtures = {
one: { a: 'A#' },
many: {
a: 'A',
b: 'B',
c: 'C',
d: 'D#'
},
functions: {
alpha: 'foo',
beta: function(n) { return n+1; }
},
multiple: { color: $w('r g b') },
multiple_nil: { color: ['r', null, 'g', undefined, 0] },
multiple_all_nil: { color: [null, undefined] },
multiple_empty: { color: [] },
value_undefined: { a:"b", c:undefined },
value_null: { a:"b", c:null },
value_zero: { a:"b", c:0 },
dangerous: {
_each: 'E',
map: 'M',
keys: 'K',
values: 'V',
collect: 'C',
inject: 'I'
}
};
new Test.Unit.Runner({
testKeys: function(){ with(this) {
assertEnumEqual([], $H({}).keys())
assertEnumEqual(['a'], $H(Fixtures.one).keys())
assertEnumEqual($w('a b c d'), $H(Fixtures.many).keys())
assertEnumEqual($w('alpha beta'), $H(Fixtures.functions).keys())
}},
testValues: function(){ with(this) {
assertEnumEqual([], $H({}).values())
assertEnumEqual(['A#'], $H(Fixtures.one).values())
assertEnumEqual($w('A B C D#'), $H(Fixtures.many).values())
assertEqual('function', typeof $H(Fixtures.functions).values()[1])
assertEqual(2, $H(Fixtures.functions).beta(1))
}},
testMerge: function(){ with(this) {
assertEqual($H(Fixtures.many).inspect(), $H(Fixtures.many).merge().inspect());
assertEqual($H(Fixtures.many).inspect(), $H(Fixtures.many).merge({}).inspect());
assertEqual("#<Hash:{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D#', 'aaa': 'AAA'}>", $H(Fixtures.many).merge({aaa: 'AAA'}).inspect());
assertEqual("#<Hash:{'a': 'A#', 'b': 'B', 'c': 'C', 'd': 'D#'}>", $H(Fixtures.many).merge(Fixtures.one).inspect());
}},
testRemove: function(){ with(this) {
var hash = $H(Fixtures.many)
var values = hash.remove('b', 'c')
assertEnumEqual($w('a d'), hash.keys())
assertEnumEqual($w('B C'), values)
}},
testToQueryString: function(){ with(this) {
assertEqual('', $H({}).toQueryString())
assertEqual('a=A%23', $H(Fixtures.one).toQueryString())
assertEqual('a=A&b=B&c=C&d=D%23', $H(Fixtures.many).toQueryString())
assertEqual("a=b&c=", $H(Fixtures.value_undefined).toQueryString())
assertEqual("a=b&c=", $H(Fixtures.value_null).toQueryString())
assertEqual("a=b&c=0", $H(Fixtures.value_zero).toQueryString())
assertEqual("color=r&color=g&color=b", $H(Fixtures.multiple).toQueryString())
assertEqual("color=r&color=g&color=0", $H(Fixtures.multiple_nil).toQueryString())
assertEqual("color=", $H(Fixtures.multiple_all_nil).toQueryString())
assertEqual("color=", $H(Fixtures.multiple_empty).toQueryString())
assertEqual("_each=E&map=M&keys=K&values=V&collect=C&inject=I", Hash.toQueryString(Fixtures.dangerous))
}},
testInspect: function(){ with(this) {
assertEqual('#<Hash:{}>', $H({}).inspect());
assertEqual("#<Hash:{'a': 'A#'}>", $H(Fixtures.one).inspect());
assertEqual("#<Hash:{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D#'}>", $H(Fixtures.many).inspect());
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>

118
test/unit/position.html Normal file
View File

@ -0,0 +1,118 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<div id="ensure_scrollbars" style="width:10000px; height:10000px; position:absolute" > </div>
<h1>Prototype Unit test file</h1>
<p>
Test of functions in position.js
</p>
<!-- Log output -->
<div id="testlog"> </div>
<div id="body_absolute" style="position: absolute; top: 10px; left: 10px">
<div id="absolute_absolute" style="position: absolute; top: 10px; left:10px"> </div>
<div id="absolute_relative" style="position: relative; top: 10px; left:10px">
<div style="height:10px">test<span id="inline">test</span></div>
<div id="absolute_relative_undefined"> </div>
</div>
</div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
var testVar = 'to be updated';
new Test.Unit.Runner({
setup: function() {
scrollTo(0,0);
Position.prepare();
Position.includeScrollOffsets = false;
},
teardown: function() {
scrollTo(0,0);
Position.prepare();
Position.includeScrollOffsets = false;
},
testPrepare: function() {with(this) {
Position.prepare();
assertEqual(0, Position.deltaX);
assertEqual(0, Position.deltaY);
scrollTo(20,30);
Position.prepare();
assertEqual(20, Position.deltaX);
assertEqual(30, Position.deltaY);
}},
testPositionedOffset: function() {with(this) {
assertEnumEqual([10,10],
Position.positionedOffset($('body_absolute')));
assertEnumEqual([10,10],
Position.positionedOffset($('absolute_absolute')));
assertEnumEqual([10,10],
Position.positionedOffset($('absolute_relative')));
assertEnumEqual([0,10],
Position.positionedOffset($('absolute_relative_undefined')));
}},
testPage: function() {with(this) {
assertEnumEqual([10,10],
Position.page($('body_absolute')));
assertEnumEqual([20,20],
Position.page($('absolute_absolute')));
assertEnumEqual([20,20],
Position.page($('absolute_relative')));
assertEnumEqual([20,30],
Position.page($('absolute_relative_undefined')));
}},
testOffsetParent: function() {with(this) {
assertEqual('body_absolute', Position.offsetParent($('absolute_absolute')).id);
assertEqual('body_absolute', Position.offsetParent($('absolute_relative')).id);
assertEqual('absolute_relative', Position.offsetParent($('inline')).id);
assertEqual('absolute_relative', Position.offsetParent($('absolute_relative_undefined')).id);
}},
testWithin: function() {with(this) {
[true, false].each(function(withScrollOffsets) {
Position.includeScrollOffsets = withScrollOffsets;
assert(!Position.within($('body_absolute'), 9, 9), 'outside left/top');
assert(Position.within($('body_absolute'), 10, 10), 'left/top corner');
assert(Position.within($('body_absolute'), 10, 19), 'left/bottom corner');
assert(!Position.within($('body_absolute'), 10, 20), 'outside bottom');
});
scrollTo(20,30);
Position.prepare();
Position.includeScrollOffsets = true;
assert(!Position.within($('body_absolute'), 9, 9), 'outside left/top');
assert(Position.within($('body_absolute'), 10, 10), 'left/top corner');
assert(Position.within($('body_absolute'), 10, 19), 'left/bottom corner');
assert(!Position.within($('body_absolute'), 10, 20), 'outside bottom');
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>

93
test/unit/range.html Normal file
View File

@ -0,0 +1,93 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of utility functions in range.js
</p>
<!-- Log output -->
<div id="testlog"> </div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
new Test.Unit.Runner({
testInclude: function() {with(this) {
assert(!$R(0, 0, true).include(0));
assert($R(0, 0, false).include(0));
assert($R(0, 5, true).include(0));
assert($R(0, 5, true).include(4));
assert(!$R(0, 5, true).include(5));
assert($R(0, 5, false).include(0));
assert($R(0, 5, false).include(5));
assert(!$R(0, 5, false).include(6));
}},
testEach: function() {with(this) {
var results = [];
$R(0, 0, true).each(function(value) {
results.push(value);
});
assertEnumEqual([], results);
results = [];
$R(0, 3, false).each(function(value) {
results.push(value);
});
assertEnumEqual([0, 1, 2, 3], results);
}},
testAny: function() {with(this) {
assert(!$R(1, 1, true).any());
assert($R(0, 3, false).any(function(value) {
return value == 3;
}));
}},
testAll: function() {with(this) {
assert($R(1, 1, true).all());
assert($R(0, 3, false).all(function(value) {
return value <= 3;
}));
}},
testToArray: function() {with(this) {
assertEnumEqual([], $R(0, 0, true).toArray());
assertEnumEqual([0], $R(0, 0, false).toArray());
assertEnumEqual([0], $R(0, 1, true).toArray());
assertEnumEqual([0, 1], $R(0, 1, false).toArray());
assertEnumEqual([-3, -2, -1, 0, 1, 2], $R(-3, 3, true).toArray());
assertEnumEqual([-3, -2, -1, 0, 1, 2, 3], $R(-3, 3, false).toArray());
}},
testDefaultsToNotExclusive: function() {with(this) {
assertEnumEqual(
$R(-3,3), $R(-3,3,false)
);
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>

157
test/unit/selector.html Normal file
View File

@ -0,0 +1,157 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of utility functions in selector.js
</p>
<div id="fixtures" style="display: none">
<h1 class="title">Some title <span>here</span></h1>
<p id="p" class="first summary">
<strong id="strong">This</strong> is a short blurb
<a id="link_1" class="first internal" href="#">with a link</a> or
<a id="link_2" class="internal highlight" href="#"><em id="em">two</em></a>.
Or <cite id="with_title" title="hello world!">three</cite>.
</p>
<ul id="list">
<li id="item_1" class="first"><a id="link_3" href="#" class="external"><span id="span">Another link</span></a></li>
<li id="item_2">Some text</li>
<li id="item_3" xml:lang="es-us" class="">Otra cosa</li>
</ul>
<!-- this form has a field with the name 'id',
therefore its ID property won't be 'troubleForm': -->
<form id="troubleForm"><input type="hidden" name="id" /></form>
</div>
<!-- Log output -->
<div id="testlog"> </div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
new Test.Unit.Runner({
testSelectorWithTagName: function() {with(this) {
assertEnumEqual($A(document.getElementsByTagName('li')), $$('li'));
assertEnumEqual([$('strong')], $$('strong'));
assertEnumEqual([], $$('nonexistent'));
assertEnumEqual($A(document.getElementsByTagName('*')), $$('*'));
}},
testSelectorWithId: function() {with(this) {
assertEnumEqual([$('fixtures')], $$('#fixtures'));
assertEnumEqual([], $$('#nonexistent'));
assertEnumEqual([$('troubleForm')], $$('#troubleForm'));
}},
testSelectorWithClassName: function() {with(this) {
assertEnumEqual($('p', 'link_1', 'item_1'), $$('.first'));
assertEnumEqual([], $$('.second'));
}},
testSelectorWithTagNameAndId: function() {with(this) {
assertEnumEqual([$('strong')], $$('strong#strong'));
assertEnumEqual([], $$('p#strong'));
}},
testSelectorWithTagNameAndClassName: function() {with(this) {
assertEnumEqual($('link_1', 'link_2'), $$('a.internal'));
assertEnumEqual([$('link_2')], $$('a.internal.highlight'));
assertEnumEqual([$('link_2')], $$('a.highlight.internal'));
assertEnumEqual([], $$('a.highlight.internal.nonexistent'));
}},
testSelectorWithIdAndClassName: function() {with(this) {
assertEnumEqual([$('link_2')], $$('#link_2.internal'));
assertEnumEqual([$('link_2')], $$('.internal#link_2'));
assertEnumEqual([$('link_2')], $$('#link_2.internal.highlight'));
assertEnumEqual([], $$('#link_2.internal.nonexistent'));
}},
testSelectorWithTagNameAndIdAndClassName: function() {with(this) {
assertEnumEqual([$('link_2')], $$('a#link_2.internal'));
assertEnumEqual([$('link_2')], $$('a.internal#link_2'));
assertEnumEqual([$('item_1')], $$('li#item_1.first'));
assertEnumEqual([], $$('li#item_1.nonexistent'));
assertEnumEqual([], $$('li#item_1.first.nonexistent'));
}},
test$$MatchesAncestryWithTokensSeparatedByWhitespace: function() {with(this) {
assertEnumEqual($('em', 'span'), $$('#fixtures a *'));
assertEnumEqual([$('p')], $$('div#fixtures p'));
}},
test$$CombinesResultsWhenMultipleExpressionsArePassed: function() {with(this) {
assertEnumEqual($('link_1', 'link_2', 'item_1', 'item_2', 'item_3'), $$('#p a', ' ul#list li '));
}},
testSelectorWithTagNameAndAttributeExistence: function() {with(this) {
assertEnumEqual($$('#fixtures h1'), $$('h1[class]'));
assertEnumEqual($$('#fixtures h1'), $$('h1[CLASS]'));
assertEnumEqual([$('item_3')], $$('li#item_3[class]'));
}},
testSelectorWithTagNameAndSpecificAttributeValue: function() {with(this) {
assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href="#"]'));
assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href=#]'));
}},
testSelectorWithTagNameAndWhitespaceTokenizedAttributeValue: function() {with(this) {
assertEnumEqual($('link_1', 'link_2'), $$('a[class~="internal"]'));
assertEnumEqual($('link_1', 'link_2'), $$('a[class~=internal]'));
}},
testSelectorWithUniversalAndHyphenTokenizedAttributeValue: function() {with(this) {
assertEnumEqual([$('item_3')], $$('*[xml:lang|="es"]'));
assertEnumEqual([$('item_3')], $$('*[xml:lang|="ES"]'));
}},
testSelectorWithTagNameAndNegatedAttributeValue: function() {with(this) {
assertEnumEqual([], $$('a[href!=#]'));
}},
test$$WithNestedAttributeSelectors: function() {with(this) {
assertEnumEqual([$('strong')], $$('div[style] p[id] strong'));
}},
testSelectorWithMultipleConditions: function() {with(this) {
assertEnumEqual([$('link_3')], $$('a[class~=external][href="#"]'));
assertEnumEqual([], $$('a[class~=external][href!="#"]'));
}},
testSelectorMatchElements: function() {with(this) {
assertElementsMatch(Selector.matchElements($('list').descendants(), 'li'), '#item_1', '#item_2', '#item_3');
assertElementsMatch(Selector.matchElements($('fixtures').descendants(), 'a.internal'), '#link_1', '#link_2');
assertEnumEqual([], Selector.matchElements($('fixtures').descendants(), 'p.last'));
}},
testSelectorFindElement: function() {with(this) {
assertElementMatches(Selector.findElement($('list').descendants(), 'li'), 'li#item_1.first');
assertElementMatches(Selector.findElement($('list').descendants(), 'li', 1), 'li#item_2');
assertElementMatches(Selector.findElement($('list').descendants(), 'li#item_3'), 'li');
assertEqual(undefined, Selector.findElement($('list').descendants(), 'em'));
}},
testSelectorWithSpaceInAttributeValue: function() {with(this) {
assertEnumEqual([$('with_title')], $$('cite[title="hello world!"]'));
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>

337
test/unit/string.html Normal file
View File

@ -0,0 +1,337 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../test.css" type="text/css" />
<style type="text/css" media="screen">
/* <![CDATA[ */
#testcss1 { font-size:11px; color: #f00; }
#testcss2 { font-size:12px; color: #0f0; display: none; }
/* ]]> */
</style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
Test of utility functions in string.js
</p>
<!-- Log output -->
<div id="testlog"> </div>
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
var evalScriptsCounter = 0,
largeTextEscaped = '&lt;span&gt;test&lt;/span&gt;',
largeTextUnescaped = '<span>test</span>';
(2048).times(function(){
largeTextEscaped += ' ABC';
largeTextUnescaped += ' ABC';
});
new Test.Unit.Runner({
testInterpret: function(){with(this) {
assertIdentical('true', String.interpret(true));
assertIdentical('123', String.interpret(123));
assertIdentical('foo bar', String.interpret('foo bar'));
assertIdentical(
'object string',
String.interpret({ toString: function(){ return 'object string' } }));
assertIdentical('0', String.interpret(0));
assertIdentical('false', String.interpret(false));
assertIdentical('', String.interpret(undefined));
assertIdentical('', String.interpret(null));
assertIdentical('', String.interpret(''));
}},
testGsubWithReplacementFunction: function() {with(this) {
var source = 'foo boo boz';
assertEqual('Foo Boo BoZ',
source.gsub(/[^o]+/, function(match) {
return match[0].toUpperCase()
}));
assertEqual('f2 b2 b1z',
source.gsub(/o+/, function(match) {
return match[0].length;
}));
assertEqual('f0 b0 b1z',
source.gsub(/o+/, function(match) {
return match[0].length % 2;
}));
}},
testGsubWithReplacementString: function() {with(this) {
var source = 'foo boo boz';
assertEqual('foobooboz',
source.gsub(/\s+/, ''));
assertEqual(' z',
source.gsub(/(.)(o+)/, ''));
}},
testGsubWithReplacementTemplateString: function() {with(this) {
var source = 'foo boo boz';
assertEqual('-oo-#{1}- -oo-#{1}- -o-#{1}-z',
source.gsub(/(.)(o+)/, '-#{2}-\\#{1}-'));
assertEqual('-foo-f- -boo-b- -bo-b-z',
source.gsub(/(.)(o+)/, '-#{0}-#{1}-'));
assertEqual('-oo-f- -oo-b- -o-b-z',
source.gsub(/(.)(o+)/, '-#{2}-#{1}-'));
assertEqual(' z',
source.gsub(/(.)(o+)/, '#{3}'));
}},
testSubWithReplacementFunction: function() {with(this) {
var source = 'foo boo boz';
assertEqual('Foo boo boz',
source.sub(/[^o]+/, function(match) {
return match[0].toUpperCase()
}), 1);
assertEqual('Foo Boo boz',
source.sub(/[^o]+/, function(match) {
return match[0].toUpperCase()
}, 2), 2);
assertEqual(source,
source.sub(/[^o]+/, function(match) {
return match[0].toUpperCase()
}, 0), 0);
assertEqual(source,
source.sub(/[^o]+/, function(match) {
return match[0].toUpperCase()
}, -1), -1);
}},
testSubWithReplacementString: function() {with(this) {
var source = 'foo boo boz';
assertEqual('oo boo boz',
source.sub(/[^o]+/, ''));
assertEqual('oooo boz',
source.sub(/[^o]+/, '', 2));
assertEqual('-f-oo boo boz',
source.sub(/[^o]+/, '-#{0}-'));
assertEqual('-f-oo- b-oo boz',
source.sub(/[^o]+/, '-#{0}-', 2));
}},
testScan: function() {with(this) {
var source = 'foo boo boz', results = [];
source.scan(/[o]+/, function(match) {
results.push(match[0].length);
});
assertEnumEqual([2, 2, 1], results);
assertEqual(source, source.scan(/x/, fail));
}},
testToArray: function() {with(this) {
assertEnumEqual([],''.toArray());
assertEnumEqual(['a'],'a'.toArray());
assertEnumEqual(['a','b'],'ab'.toArray());
assertEnumEqual(['f','o','o'],'foo'.toArray());
}},
/*
Note that camelize() differs from its Rails counterpart,
as it is optimized for dealing with JavaScript object
properties in conjunction with CSS property names:
- Looks for dashes, not underscores
- CamelCases first word if there is a front dash
*/
testCamelize: function() {with(this) {
assertEqual('', ''.camelize());
assertEqual('', '-'.camelize());
assertEqual('foo', 'foo'.camelize());
assertEqual('foo_bar', 'foo_bar'.camelize());
assertEqual('FooBar', '-foo-bar'.camelize());
assertEqual('FooBar', 'FooBar'.camelize());
assertEqual('fooBar', 'foo-bar'.camelize());
assertEqual('borderBottomWidth', 'border-bottom-width'.camelize());
assertEqual('classNameTest','class-name-test'.camelize());
assertEqual('classNameTest','className-test'.camelize());
assertEqual('classNameTest','class-nameTest'.camelize());
/* benchmark(function(){
'class-name-test'.camelize();
},10000); */
}},
testCapitalize: function() {with(this) {
assertEqual('',''.capitalize());
assertEqual('Ä','ä'.capitalize());
assertEqual('A','A'.capitalize());
assertEqual('Hello','hello'.capitalize());
assertEqual('Hello','HELLO'.capitalize());
assertEqual('Hello','Hello'.capitalize());
assertEqual('Hello world','hello WORLD'.capitalize());
}},
testUnderscore: function() {with(this) {
assertEqual('', ''.underscore());
assertEqual('_', '-'.underscore());
assertEqual('foo', 'foo'.underscore());
assertEqual('foo', 'Foo'.underscore());
assertEqual('foo_bar', 'foo_bar'.underscore());
assertEqual('border_bottom', 'borderBottom'.underscore());
assertEqual('border_bottom_width', 'borderBottomWidth'.underscore());
assertEqual('border_bottom_width', 'border-Bottom-Width'.underscore());
}},
testDasherize: function() {with(this) {
assertEqual('', ''.dasherize());
assertEqual('foo', 'foo'.dasherize());
assertEqual('Foo', 'Foo'.dasherize());
assertEqual('foo-bar', 'foo-bar'.dasherize());
assertEqual('border-bottom-width', 'border_bottom_width'.dasherize());
}},
testTruncate: function() {with(this) {
var source = 'foo boo boz foo boo boz foo boo boz foo boo boz';
assertEqual(source, source.truncate(source.length));
assertEqual('foo boo boz foo boo boz foo...', source.truncate(0));
assertEqual('fo...', source.truncate(5));
assertEqual('foo b', source.truncate(5, ''));
}},
testStrip: function() {with(this) {
assertEqual('hello world', ' hello world '.strip());
assertEqual('hello world', 'hello world'.strip());
assertEqual('hello \n world', ' hello \n world '.strip());
assertEqual('', ' '.strip());
}},
testStripTags: function() {with(this) {
assertEqual('hello world', 'hello world'.stripTags());
assertEqual('hello world', 'hello <span>world</span>'.stripTags());
assertEqual('hello world', '<a href="#" onclick="moo!">hello</a> world'.stripTags());
assertEqual('hello world', 'h<b><em>e</em></b>l<i>l</i>o w<span class="moo" id="x"><b>o</b></span>rld'.stripTags());
}},
testStripScripts: function() {with(this) {
assertEqual('foo bar', 'foo bar'.stripScripts());
assertEqual('foo bar', ('foo <script>boo();<'+'/script>bar').stripScripts());
assertEqual('foo bar', ('foo <script type="text/javascript">boo();\nmoo();<'+'/script>bar').stripScripts());
}},
testExtractScripts: function() {with(this) {
assertEnumEqual([], 'foo bar'.extractScripts());
assertEnumEqual(['boo();'], ('foo <script>boo();<'+'/script>bar').extractScripts());
assertEnumEqual(['boo();','boo();\nmoo();'],
('foo <script>boo();<'+'/script><script type="text/javascript">boo();\nmoo();<'+'/script>bar').extractScripts());
assertEnumEqual(['boo();','boo();\nmoo();'],
('foo <script>boo();<'+'/script>blub\nblub<script type="text/javascript">boo();\nmoo();<'+'/script>bar').extractScripts());
}},
testEvalScripts: function() {with(this) {
assertEqual(0, evalScriptsCounter);
('foo <script>evalScriptsCounter++<'+'/script>bar').evalScripts();
assertEqual(1, evalScriptsCounter);
var stringWithScripts = '';
(3).times(function(){ stringWithScripts += 'foo <script>evalScriptsCounter++<'+'/script>bar' });
stringWithScripts.evalScripts();
assertEqual(4, evalScriptsCounter);
}},
testEscapeHTML: function() {with(this) {
assertEqual('foo bar', 'foo bar'.escapeHTML());
assertEqual('foo &lt;span&gt;bar&lt;/span&gt;', 'foo <span>bar</span>'.escapeHTML());
assertEqual('foo ß bar', 'foo ß bar'.escapeHTML());
assertEqual('a&lt;a href="blah"&gt;blub&lt;/a&gt;b&lt;span&gt;&lt;div&gt;&lt;/div&gt;&lt;/span&gt;cdef&lt;strong&gt;!!!!&lt;/strong&gt;g',
'a<a href="blah">blub</a>b<span><div></div></span>cdef<strong>!!!!</strong>g'.escapeHTML());
assertEqual(largeTextEscaped, largeTextUnescaped.escapeHTML());
}},
testUnescapeHTML: function() {with(this) {
assertEqual('foo bar', 'foo bar'.unescapeHTML());
assertEqual('foo <span>bar</span>', 'foo &lt;span&gt;bar&lt;/span&gt;'.unescapeHTML());
assertEqual('foo ß bar', 'foo ß bar'.unescapeHTML());
assertEqual('a<a href="blah">blub</a>b<span><div></div></span>cdef<strong>!!!!</strong>g',
'a&lt;a href="blah"&gt;blub&lt;/a&gt;b&lt;span&gt;&lt;div&gt;&lt;/div&gt;&lt;/span&gt;cdef&lt;strong&gt;!!!!&lt;/strong&gt;g'.unescapeHTML());
assertEqual(largeTextUnescaped, largeTextEscaped.unescapeHTML());
}},
testTemplateEvaluation: function() {with(this) {
var source = '<tr><td>#{name}</td><td>#{age}</td></tr>';
var person = {name: 'Sam', age: 21};
var template = new Template(source);
assertEqual('<tr><td>Sam</td><td>21</td></tr>',
template.evaluate(person));
assertEqual('<tr><td></td><td></td></tr>',
template.evaluate({}));
}},
testTemplateEvaluationWithFalses: function() {with(this) {
var source = '<tr><td>#{zero}</td><td>#{false_}</td><td>#{undef}</td><td>#{null_}</td><td>#{empty}</td></tr>';
var falses = {zero:0, false_:false, undef:undefined, null_:null, empty:""};
var template = new Template(source);
assertEqual('<tr><td>0</td><td>false</td><td></td><td></td><td></td></tr>',
template.evaluate(falses));
}},
testToQueryParams: function() {with(this) {
assertEnumEqual([], Object.keys(''.toQueryParams()));
assertEnumEqual([], Object.keys('foo?'.toQueryParams()));
assertEnumEqual(['a', 'b'], Object.keys('foo?a&b'.toQueryParams()));
assertEnumEqual(['a', 'b'], Object.keys('foo?a&b#fragment'.toQueryParams()));
assertEnumEqual(['a', 'b'], Object.keys('a;b'.toQueryParams(';')));
var result = 'a'.toQueryParams();
assertEqual(undefined, result['a']);
assert(result.hasOwnProperty('a'));
result = 'a&b=c'.toQueryParams();
assertEqual(undefined, result['a']);
assert(result.hasOwnProperty('a'));
assertEqual('c', result['b']);
result = 'a%20b=c&d=e%20f&g=h'.toQueryParams();
assertEqual('c', result['a b']);
assertEqual('e f', result['d']);
assertEqual('h', result['g']);
result = 'color=r&color=g&color=b'.toQueryParams();
assertEnumEqual(['r', 'g', 'b'], result['color']);
assertEnumEqual(['r', 'b'], 'c=r&c=&c=b'.toQueryParams()['c']);
assertEqual('blue', 'c=&c=blue'.toQueryParams()['c']);
assertEqual('blue', 'c=blue&c='.toQueryParams()['c']);
}},
testInspect: function() {with(this) {
assertEqual('\'\'', ''.inspect());
assertEqual('\'test\'', 'test'.inspect());
assertEqual('\'test \\\'test\\\' "test"\'', 'test \'test\' "test"'.inspect());
}},
testSucc: function() {with(this) {
assertEqual('b', 'a'.succ());
assertEqual('B', 'A'.succ());
assertEqual('1', '0'.succ());
assertEqual('abce', 'abcd'.succ());
assertEqual('{', 'z'.succ());
assertEqual(':', '9'.succ());
}}
}, 'testlog');
// ]]>
</script>
</body>
</html>