├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE.md ├── README.md ├── Rakefile ├── index.js ├── package.json ├── site ├── config │ ├── compass.rb │ └── site.rb ├── site │ ├── images │ │ └── tracing.png │ ├── javascripts │ │ ├── analytics.js │ │ └── prettify.js │ └── stylesheets │ │ └── github.css └── src │ ├── layouts │ └── default.haml │ ├── pages │ ├── binding.haml │ ├── classes.haml │ ├── classmethods.haml │ ├── command.haml │ ├── comparable.haml │ ├── console.haml │ ├── constantscope.haml │ ├── debugging.haml │ ├── decorator.haml │ ├── deferrable.haml │ ├── enumerable.haml │ ├── enumerator.haml │ ├── equality.haml │ ├── forwardable.haml │ ├── hash.haml │ ├── hooks.haml │ ├── index.haml │ ├── inheritance.haml │ ├── interfaces.haml │ ├── introduction.haml │ ├── kernel.haml │ ├── keywords.haml │ ├── license.haml │ ├── linkedlist.haml │ ├── methodchain.haml │ ├── modifyingmodules.haml │ ├── modules.haml │ ├── observable.haml │ ├── packages.haml │ ├── packages │ │ ├── autoload.haml │ │ ├── customloaders.haml │ │ └── introduction.haml │ ├── platforms.haml │ ├── platforms │ │ └── node.haml │ ├── proxies.haml │ ├── range.haml │ ├── reflection.haml │ ├── set.haml │ ├── singletonmethods.haml │ ├── singletons.haml │ ├── stacktrace.haml │ ├── state.haml │ ├── tsort.haml │ └── upgrade.haml │ └── stylesheets │ └── screen.sass ├── source ├── assets │ ├── bullet_go.png │ └── testui.css ├── command.js ├── comparable.js ├── console │ ├── _head.js │ ├── _tail.js │ ├── base.js │ ├── browser.js │ ├── browser_color.js │ ├── config.js │ ├── console.js │ ├── node.js │ ├── phantom.js │ ├── rhino.js │ └── windows.js ├── constant_scope.js ├── core │ ├── _head.js │ ├── _tail.js │ ├── bootstrap.js │ ├── class.js │ ├── interface.js │ ├── kernel.js │ ├── keywords.js │ ├── method.js │ ├── module.js │ ├── singleton.js │ └── utils.js ├── decorator.js ├── deferrable.js ├── dom │ ├── _head.js │ ├── _tail.js │ ├── builder.js │ ├── dom.js │ └── event.js ├── enumerable.js ├── forwardable.js ├── hash.js ├── linked_list.js ├── method_chain.js ├── observable.js ├── package │ ├── _head.js │ ├── _tail.js │ ├── browser.js │ ├── config.js │ ├── dsl.js │ ├── loader.js │ ├── loaders │ │ ├── browser.js │ │ ├── commonjs.js │ │ ├── rhino.js │ │ ├── server.js │ │ ├── wsh.js │ │ └── xulrunner.js │ └── package.js ├── proxy.js ├── range.js ├── set.js ├── stack_trace.js ├── state.js ├── test │ ├── _head.js │ ├── _tail.js │ ├── async_steps.js │ ├── context │ │ ├── context.js │ │ ├── life_cycle.js │ │ ├── shared_behavior.js │ │ ├── suite.js │ │ └── test.js │ ├── coverage.js │ ├── fake_clock.js │ ├── helpers.js │ ├── mocking │ │ ├── dsl.js │ │ ├── matchers.js │ │ ├── parameters.js │ │ └── stub.js │ ├── reporters │ │ ├── browser.js │ │ ├── composite.js │ │ ├── coverage.js │ │ ├── dot.js │ │ ├── error.js │ │ ├── exit_status.js │ │ ├── headless.js │ │ ├── json.js │ │ ├── tap.js │ │ └── test_swarm.js │ ├── runner.js │ ├── ui │ │ ├── browser.js │ │ └── terminal.js │ ├── unit.js │ └── unit │ │ ├── assertion_message.js │ │ ├── assertions.js │ │ ├── error.js │ │ ├── failure.js │ │ ├── observable.js │ │ ├── test_case.js │ │ ├── test_result.js │ │ └── test_suite.js └── tsort.js └── test ├── airenv ├── AIRAliases.js ├── app.xml ├── index.html └── jsclass ├── browser.html ├── console.js ├── examples ├── async.js └── tracing.js ├── fixtures └── common.js ├── phantom.js ├── runner.js ├── specs ├── class_spec.js ├── command_spec.js ├── comparable_spec.js ├── console_spec.js ├── constant_scope_spec.js ├── decorator_spec.js ├── deferrable_spec.js ├── enumerable_spec.js ├── forwardable_spec.js ├── hash_spec.js ├── interface_spec.js ├── kernel_spec.js ├── linked_list_spec.js ├── method_chain_spec.js ├── method_spec.js ├── module_spec.js ├── observable_spec.js ├── package_spec.js ├── proxy_spec.js ├── range_spec.js ├── set_spec.js ├── singleton_spec.js ├── state_spec.js ├── test │ ├── async_steps_spec.js │ ├── context_spec.js │ ├── fake_clock_spec.js │ ├── mocking_spec.js │ ├── test_spec_helpers.js │ └── unit_spec.js └── tsort_spec.js └── xulenv ├── application.ini ├── chrome ├── chrome.manifest └── content │ ├── jsclass │ └── main.xul └── defaults └── preferences └── prefs.js /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | build 3 | node_modules 4 | site/site/stylesheets/screen.css 5 | site/site/*.html 6 | .wake.json 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | 4 | node_js: 5 | - "0.8" 6 | - "0.10" 7 | - "0.12" 8 | - "1" 9 | - "2" 10 | - "3" 11 | - "4" 12 | 13 | before_install: 14 | - '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@~1.4.0' 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `jsclass` 2 | 3 | The `jsclass` git repository is at http://github.com/jcoglan/jsclass. 4 | 5 | To hack on `jsclass` you'll need to be able to build it and run the tests. To 6 | build the library from source, run: 7 | 8 | ``` 9 | $ npm install 10 | $ npm run-script build 11 | ``` 12 | 13 | This will build the project and create files in the `build` directory. 14 | 15 | 16 | ## Running the tests 17 | 18 | Please add tests for any functionality you add to the library. The test files 19 | live in the `test/specs` directory; follow the code conventions you see in the 20 | existing files. 21 | 22 | To run the tests, you need to run several tasks. Make sure all the target server 23 | platforms work: 24 | 25 | ``` 26 | $ JS=(v8 node phantomjs spidermonkey rhino narwhal ringo mongo) 27 | $ for js in "${JS[@]}"; do echo "$js" ; $js test/console.js ; echo $? ; done 28 | ``` 29 | 30 | Some interpreters will skip the tests that use asynchronous APIs. 31 | 32 | Check the tests work in the PhantomJS browser: 33 | 34 | ``` 35 | $ phantomjs test/phantom.js 36 | ``` 37 | 38 | Run the test suite in as many web browsers as you can: 39 | 40 | ``` 41 | $ open test/browser.html 42 | ``` 43 | 44 | For desktop application platforms, run it in XULRunner and AIR: 45 | 46 | ``` 47 | $ xulrunner -app test/xulenv/application.ini 48 | $ adl test/airenv/app.xml 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'httparty' 4 | gem 'nokogiri' 5 | gem 'rake' 6 | 7 | gem 'staticmatic' 8 | gem 'compass', '~> 0.11.0' 9 | gem 'haml', '~> 3.1.0' 10 | gem 'RedCloth', '~> 3.0.0' 11 | gem 'sass', '~> 3.2.0' 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | (The MIT license) 4 | 5 | Copyright (c) 2007-2013 James Coglan and contributors 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | Parts of the Software build on techniques from the following open-source 18 | projects: 19 | 20 | * [Prototype](http://prototypejs.org/), (c) 2005-2010 Sam Stephenson (MIT 21 | license) 22 | * Alex Arnell's [Inheritance](http://www.twologic.com/projects/inheritance/) 23 | library, (c) 2006 Alex Arnell (MIT license) 24 | * [Base](http://dean.edwards.name/weblog/2006/03/base/), (c) 2006-2010 Dean 25 | Edwards (MIT license) 26 | 27 | The Software contains direct translations to JavaScript of these open-source 28 | Ruby libraries: 29 | 30 | * [Ruby standard library modules](http://www.ruby-lang.org/en/), (c) Yukihiro 31 | Matsumoto and contributors (Ruby license) 32 | * [Test::Unit](http://test-unit.rubyforge.org/), (c) 2000-2003 Nathaniel 33 | Talbott (Ruby license) 34 | * [Context](http://github.com/jm/context), (c) 2008 Jeremy McAnally (MIT 35 | license) 36 | * [EventMachine::Deferrable](http://rubyeventmachine.com/), (c) 2006-07 Francis 37 | Cianfrocca (Ruby license) 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 45 | SOFTWARE. 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsclass 2 | 3 | `jsclass` is a portable, modular JavaScript class library, influenced by the 4 | [Ruby](http://ruby-lang.org/ programming) language. It provides a rich set of 5 | tools for building object-oriented JavaScript programs, and is designed to run 6 | on a wide variety of client- and server-side platforms. 7 | 8 | ## Installation 9 | 10 | Download the library from [the website](http://jsclass.jcoglan.com) or from npm: 11 | 12 | ``` 13 | $ npm install jsclass 14 | ``` 15 | 16 | ## Usage 17 | 18 | See [the website](http://jsclass.jcoglan.com) for documentation. 19 | 20 | ## Contributing 21 | 22 | You can find instructions for how to build the library and run the tests in 23 | `CONTRIBUTING.md`. 24 | 25 | ## License 26 | 27 | Copyright 2007-2014 James Coglan, distributed under the MIT license. See 28 | `LICENSE.md` for full details. 29 | 30 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'httparty' 3 | require 'nokogiri' 4 | require 'set' 5 | 6 | def mdc_url(type) 7 | 'http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/' + type 8 | end 9 | 10 | def document(url) 11 | puts "Fetching #{url} ..." 12 | Nokogiri.parse(HTTParty.get(url)) 13 | rescue 14 | document(url) 15 | end 16 | 17 | MDC_URLS = %w(Array Boolean Date EvalError Error Function Math 18 | Number Object RangeError ReferenceError RegExp 19 | String SyntaxError TypeError URIError). 20 | map(&method(:mdc_url)) 21 | 22 | ELEMENT_URL = 'https://developer.mozilla.org/en-US/docs/Web/API/element' 23 | EVENT_URL = 'https://developer.mozilla.org/en-US/docs/Web/API/Event' 24 | STYLE_URL = 'https://developer.mozilla.org/en-US/docs/Web/CSS/Reference' 25 | 26 | class MethodSet < SortedSet 27 | def add_method(link) 28 | name = link.text.strip.gsub(/^.*\.([^\.]+)$/, '\1') 29 | add(name) if name =~ /^[a-z][a-zA-Z0-9\_\$]*$/ 30 | end 31 | 32 | def import(url, selector) 33 | document(url).search(selector).each(&method(:add_method)) 34 | end 35 | end 36 | 37 | namespace :import do 38 | task :method_chain do 39 | methods = MethodSet.new 40 | 41 | MDC_URLS.each { |url| methods.import(url, 'dt a') } 42 | 43 | methods.import(ELEMENT_URL, 'td:first-child a:first-child') 44 | methods.import(EVENT_URL, 'dt a') 45 | methods.import(STYLE_URL, 'li a') 46 | 47 | p methods.entries 48 | end 49 | end 50 | 51 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | cleanup = (typeof JSCLASS_PATH === 'undefined'); 3 | 4 | JSCLASS_PATH = path.dirname(__filename) + '/src'; 5 | module.exports = require(JSCLASS_PATH + '/loader'); 6 | 7 | if (cleanup) delete JSCLASS_PATH; 8 | 9 | -------------------------------------------------------------------------------- /site/config/compass.rb: -------------------------------------------------------------------------------- 1 | require "staticmatic/compass" 2 | 3 | project_type = :staticmatic -------------------------------------------------------------------------------- /site/config/site.rb: -------------------------------------------------------------------------------- 1 | # Default is 3000 2 | # configuration.preview_server_port = 3000 3 | 4 | # Default is localhost 5 | # configuration.preview_server_host = "localhost" 6 | 7 | # Default is true 8 | # When false .html & index.html get stripped off generated urls 9 | # configuration.use_extensions_for_page_links = true 10 | 11 | # Default is an empty hash 12 | # configuration.sass_options = {} 13 | 14 | # Default is an empty hash 15 | # http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#options 16 | # configuration.haml_options = {} -------------------------------------------------------------------------------- /site/site/images/tracing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcoglan/jsclass/7a40c031fe0e04e29b2273a8a4ff6ca049c4bfa2/site/site/images/tracing.png -------------------------------------------------------------------------------- /site/site/javascripts/analytics.js: -------------------------------------------------------------------------------- 1 | var _gaq = _gaq || []; 2 | _gaq.push(['_setAccount', 'UA-873493-4']); 3 | _gaq.push(['_trackPageview']); 4 | 5 | (function() { 6 | var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; 7 | ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 8 | var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); 9 | })(); 10 | -------------------------------------------------------------------------------- /site/site/stylesheets/github.css: -------------------------------------------------------------------------------- 1 | .atn { color:#008080 } 2 | .atv { color:#008080 } 3 | .com { color:#999988 } 4 | .dec { color:#000000; font-weight:bold } 5 | .kwd { color:#000000; font-weight:bold } 6 | .lit { color:#009999 } 7 | .pln { color:#000000 } 8 | .pun { color:#666666 } 9 | .str { color:#dd1144 } 10 | .tag { color:#000080 } 11 | .typ { color:#445588 } 12 | 13 | -------------------------------------------------------------------------------- /site/src/layouts/default.haml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | %html 3 | %head 4 | %meta{'charset' => 'utf-8'} 5 | %title jsclass 6 | = stylesheets 7 | %link{'rel' => 'stylesheet', 'type' => 'text/css', 'href' => 'http://fonts.googleapis.com/css?family=Inconsolata:400,700|Open+Sans:300italic,400italic,700italic,400,300,700'} 8 | %body 9 | 10 | .nav 11 | %h1 12 | = link 'jsclass', '/' 13 | %p.download 14 | = link 'Download v4.0.5', '/assets/JS.Class.4-0-5.zip' 15 | 16 | %h4 Introduction 17 | %ul 18 | %li 19 | = link 'Getting started', '/introduction.html' 20 | %li 21 | = link 'Supported platforms', '/platforms.html' 22 | %li 23 | = link 'Package manager', '/packages.html' 24 | %li 25 | = link 'License & acknowledgements', '/license.html' 26 | 27 | %h4 Community 28 | %ul 29 | %li 30 | = link 'Mailing list', 'http://groups.google.com/group/jsclass-users' 31 | %li 32 | = link 'GitHub repository', 'http://github.com/jcoglan/jsclass' 33 | 34 | %h4 Core reference 35 | %ul 36 | %li 37 | = link 'Creating classes', '/classes.html' 38 | %li 39 | = link 'Using modules', '/modules.html' 40 | %li 41 | = link 'Modifying classes/modules', '/modifyingmodules.html' 42 | %li 43 | = link 'Singleton methods', '/singletonmethods.html' 44 | %li 45 | = link 'Class methods', '/classmethods.html' 46 | %li 47 | = link 'Keyword methods', '/keywords.html' 48 | %li 49 | = link 'Inheritance', '/inheritance.html' 50 | %li 51 | = link 'Method binding', '/binding.html' 52 | %li 53 | = link 'Metaprogramming hooks', '/hooks.html' 54 | %li 55 | = link 'Reflection' 56 | %li 57 | = link 'Debugging support', '/debugging.html' 58 | %li 59 | = link 'The Kernel module', '/kernel.html' 60 | %li 61 | = link 'Equality and hashing', '/equality.html' 62 | %li 63 | = link 'Interfaces' 64 | %li 65 | = link 'Singletons' 66 | 67 | %h4 Standard library 68 | %ul 69 | %li 70 | = link 'Command' 71 | %li 72 | = link 'Comparable' 73 | %li 74 | = link 'Console' 75 | %li 76 | = link 'ConstantScope' 77 | %li 78 | = link 'Decorator' 79 | %li 80 | = link 'Deferrable' 81 | %li 82 | = link 'Enumerable' 83 | %li 84 | = link 'Enumerator' 85 | %li 86 | = link 'Forwardable' 87 | %li 88 | = link 'Hash, OrderedHash', '/hash.html' 89 | %li 90 | = link 'LinkedList', '/linkedlist.html' 91 | %li 92 | = link 'MethodChain' 93 | %li 94 | = link 'Observable' 95 | %li 96 | = link 'Proxy', '/proxies.html' 97 | %li 98 | = link 'Range' 99 | %li 100 | = link 'Set, OrderedSet, SortedSet', '/set.html' 101 | %li 102 | = link 'StackTrace' 103 | %li 104 | = link 'State' 105 | %li 106 | = link 'TSort' 107 | 108 | .content 109 | = yield 110 | 111 | .footer 112 | Copyright © 2007–2014 James Coglan, released under the MIT license 113 | 114 | = javascripts 'prettify' 115 | :plain 116 | 125 | 126 | -------------------------------------------------------------------------------- /site/src/pages/binding.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Method binding 3 | 4 | Ruby has method objects (@Method@ and @UnboundMethod@) for passing references 5 | to an object's methods, so you can call a method without having a reference to 6 | the object. JavaScript has functions and treats them as first-class values, so 7 | you can get a reference to a method and call it later: 8 | 9 |
var rex = new Dog('Rex');
10 |   var spk = rex.speak;    // a reference, we are not calling the method
11 |   spk('biscuits');
12 |   // -> "MY NAME IS AND I LIKE BISCUITS!"
13 | 14 | Where did Rex's name go? The thing is, we've not called @spk@ through the 15 | object @rex@, so @this@ inside the function no longer refers to the right 16 | thing. @jsclass@ gives each object a @method()@ method, that returns a method 17 | by name, bound to its source object. This method is simply a JavaScript 18 | function that you can call on its own and maintain the binding of @this@: 19 | 20 |
var speak = rex.method('speak');
21 |   speak('biscuits');
22 |   // -> "MY NAME IS REX AND I LIKE BISCUITS!"
23 | 24 | You can also do this with class methods, since classes are objects too: 25 | 26 |
var User = new JS.Class({
27 |       extend: {
28 |           create: function(name) {
29 |               return new this(name);
30 |           }
31 |       },
32 |       initialize: function(name) {
33 |           this.username = name;
34 |       }
35 |   });
36 | 
37 |   var u = User.method('create');
38 |   u('James')    // -> {username: 'James'}
39 | 40 | -------------------------------------------------------------------------------- /site/src/pages/classmethods.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Class methods 3 | 4 | In Ruby, modules and classes are just another type of object; they are objects 5 | that are responsible for storing methods and making new objects. Class methods 6 | are methods attached to (and called on) an individual class object, rather 7 | than its instances. They are really just a special case of "singleton methods":/singletonmethods.html. 8 | To add class methods when defining a class, wrap them up in an @extend@ block: 9 | 10 |
var User = new JS.Class({
11 |       extend: {
12 |           find: function(id) {
13 |               // Return a User with id
14 |           },
15 |           create: function(name) {
16 |               return new this(name);
17 |           }
18 |       },
19 |       initialize: function(name) {
20 |           this.username = name;
21 |       }
22 |   });
23 | 24 | We could equally add the methods after the class was created: 25 | 26 |
var User = new JS.Class({
27 |       initialize: function(name) {
28 |           this.username = name;
29 |       }
30 |   });
31 | 
32 |   User.extend({
33 |       find: function(id) {
34 |           // Return a User with id
35 |       },
36 |       create: function(name) {
37 |           return new this(name);
38 |       }
39 |   });
40 | 41 | These two syntaxes apply equally to creating and extending "@Modules@":/modules.html. 42 | Within a class method, the keyword @this@ refers to the class itself - see the 43 | @User.create()@ method: 44 | 45 |
var james = User.create('James');
46 |   james.username    // -> 'James'
47 |   james.klass       // -> User
48 | 49 | When you create a subclass, it will inherit any class methods of its parent, 50 | and you can use @callSuper()@ too: 51 | 52 |
var LoudUser = new JS.Class(User, {
53 |       extend: {
54 |           create: function(name) {
55 |               return this.callSuper(name.toUpperCase());
56 |           }
57 |       }
58 |   });
59 | 
60 |   var me = LoudUser.create('James');
61 |   me.username   // -> 'JAMES'
62 |   me.klass      // -> LoudUser
63 | 
64 |   var you = LoudUser.find(24)   // inherited from User
65 | 66 | Note how @this@, even in @callSuper@ methods, always refers to the same thing 67 | as in the original method call. We get back a @LoudUser@, not a @User@. 68 | 69 | Note that class methods are not the same as Java's static methods. If you want 70 | to call a class method from an instance of the class, you need to get a 71 | reference to the class through the object's @klass@ property. 72 | 73 |
User.define('copy', function() {
74 |       return this.klass.create(this.username);
75 |   });
76 | 77 | -------------------------------------------------------------------------------- /site/src/pages/comparable.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Comparable 3 | 4 | @Comparable@ is a module that helps you manipulate objects that can be ordered 5 | relative to each other. It is designed to work exactly like 6 | "@Comparable@":http://ruby-doc.org/core/classes/Comparable.html in Ruby, only 7 | without the nice method names like @"<="@ that Ruby allows. 8 | 9 |
// In the browser
10 |   JS.require('JS.Comparable', function(Comparable) { ... });
11 | 
12 |   // In CommonJS
13 |   var Comparable = require('jsclass/src/comparable').Comparable;
14 | 15 | To use it, your class must define an instance method called @compareTo@, that 16 | tells it how to compare itself to other objects. As an example, let's create a 17 | class to represent to-do list items. These can be ordered according to their 18 | priority. 19 | 20 |
var TodoItem = new Class({
21 |       include: Comparable,
22 | 
23 |       initialize: function(task, priority) {
24 |           this.task = task;
25 |           this.priority = priority;
26 |       },
27 | 
28 |       // Must return -1 if this object is 'less than' other, +1 if it is
29 |       // 'greater than' other, or 0 if they are equal
30 |       compareTo: function(other) {
31 |           if (this.priority < other.priority) return -1;
32 |           if (this.priority > other.priority) return 1;
33 |           return 0;
34 |       }
35 |   });
36 | 37 | @TodoItem@ now has the following instance methods: 38 | 39 | * @lt(other)@ - returns @true@ iff the receiver is less than @other@ 40 | * @lte(other)@ - returns @true@ iff the receiver is less than or equal to @other@ 41 | * @gt(other)@ - returns @true@ iff the receiver is greater than @other@ 42 | * @gte(other)@ - returns @true@ iff the receiver is greater than or equal to @other@ 43 | * @eq(other)@ - returns @true@ iff the receiver is equal to @other@ 44 | * @between(a,b)@ - returns @true@ iff the receiver is between @a@ and @b@ inclusive 45 | * @compareTo(other)@ - returns @-1@/@0@/@1@ to indicate result of comparison 46 | 47 | @TodoItem@ also has a class method called @compare@ that you should use for 48 | sorting. 49 | 50 | Let's create some items and see how they behave: 51 | 52 |
var items = [
53 |       new TodoItem('Go to work', 5),
54 |       new TodoItem('Buy milk', 3),
55 |       new TodoItem('Pay the rent', 2),
56 |       new TodoItem('Write code', 1),
57 |       new TodoItem('Head down the pub', 4)
58 |   ];
59 | 
60 |   items[2].lt(items[1])   // -> true
61 |   items[0].gte(items[3])  // -> true
62 |   items[4].eq(items[4])   // -> true
63 |   items[1].between(items[3], items[4])  // -> true
64 | 
65 |   items.sort(TodoItem.compare)
66 |   // -> [
67 |   //        {task: 'Write code', ...},
68 |   //        {task: 'Pay the rent', ...},
69 |   //        {task: 'Buy milk', ...},
70 |   //        {task: 'Head down the pub', ...},
71 |   //        {task: 'Go to work', ...}
72 |   //    ]
73 | 74 | -------------------------------------------------------------------------------- /site/src/pages/debugging.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Debugging support 3 | 4 | The 2.1 release introduced support for "WebKit's 5 | @displayName@":http://www.alertdebugging.com/2009/04/29/building-a-better-javascript-profiler-with-webkit/ 6 | property for profiling and debugging JavaScript. Essentially, it is a 7 | workaround for the fact that JavaScript objects and functions do not have 8 | names directly attached to them, and can be referenced by any number of 9 | variables, thus making objects and functions basically anonymous. 10 | 11 | WebKit's profiler and debugger improves the situation by using the 12 | @displayName@ assigned to a @Function@ object if one has been assigned. 13 | @jsclass@ generates display names for methods and inner classes, so long as 14 | you specify a name for the outermost class. Class (and module) names are 15 | optional and are specified using the first argument to the @Class@ and 16 | @Module@ constructors. For example: 17 | 18 |
Foo = new Module('Foo', {
19 |       sleep: function() { /* ... */ },
20 | 
21 |       extend: {
22 |           eatFood: function() { /* ... */ },
23 | 
24 |           InnerClass: new Class({
25 |               haveVisions: function() { /* ... */ }
26 |           })
27 |       }
28 |   });
29 | 30 | The name does not have to be the same as the variable you assign the module 31 | to, although it will probably be more helpful if the names _are_ the same. The 32 | name given is not used for variable assignment, though. 33 | 34 | Given the above definition, we now find @displayName@ set on the methods and 35 | the inner class: 36 | 37 |
Foo.instanceMethod('sleep').displayName
38 |   // -> "Foo#sleep"
39 | 
40 |   Foo.eatFood.displayName
41 |   // -> "Foo.eatFood"
42 | 
43 |   Foo.InnerClass.displayName
44 |   // -> "Foo.InnerClass"
45 | 
46 |   Foo.InnerClass.instanceMethod('haveVisions').displayName
47 |   // -> "Foo.InnerClass#haveVisions"
48 | 49 | Further debugging support is provided by the "@StackTrace@":/stacktrace.html module. 50 | 51 | -------------------------------------------------------------------------------- /site/src/pages/decorator.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Decorator 3 | 4 | The @Decorator@ module gives you a means of implementing "the decorator 5 | pattern":http://en.wikipedia.org/wiki/Decorator_pattern with minimal 6 | boilerplate and code duplication. When creating a decorator class, you only 7 | need to define methods that differ in some way from the methods in the 8 | decorated object (the _component_). This means you don't have to write lots of 9 | forwarding methods by hand, which saves you time, filesize, and reduces code 10 | duplication. 11 | 12 |
// In the browser
13 |   JS.require('JS.Decorator', function(Decorator) { ... });
14 | 
15 |   // In CommonJS
16 |   var Decorator = require('jsclass/src/decorator').Decorator;
17 | 18 | Let's take a quick example: 19 | 20 |
// Basic Bike class. Bikes cost $10 per gear.
21 | 
22 |   var Bike = new Class({
23 |       initialize: function(model, gears) {
24 |           this.model = model;
25 |           this.gears = gears;
26 |       },
27 |       getModel: function() {
28 |           return this.model;
29 |       },
30 |       getPrice: function() {
31 |           return 10 * this.gears;
32 |       },
33 |       applyBrakes: function(force) {
34 |           // slow the bike down...
35 |       }
36 |   });
37 | 
38 |   // Disk brake decorator. Disk brakes add to the price, and make the bike's
39 |   // brakes more powerful.
40 | 
41 |   var DiskBrakeDecorator = new Decorator(Bike, {
42 |       getPrice: function() {
43 |           return this.component.getPrice() + 50;
44 |       },
45 |       applyBrakes: function(force) {
46 |           this.component.applyBrakes(8 * force);
47 |       }
48 |   });
49 | 50 | @DiskBrakeDecorator@ gets versions of all @Bike@'s instance methods that 51 | forward the method call onto the component and return the result. e.g., 52 | @DiskBrakeDecorator@'s @getModel()@ method looks like: 53 | 54 |
getModel: function() {
55 |       return this.component.getModel();
56 |   };
57 | 58 | Any methods you don't redefine in the decorator class will look similar to 59 | this. Let's try our new classes out: 60 | 61 |
var bike = new Bike('Specialized Rock Hopper', 21);
62 |   bike.getPrice()   // -> 210
63 | 
64 |   bike = new DiskBrakeDecorator(bike);
65 |   bike.getPrice()   // -> 260
66 |   bike.getModel()   // -> "Specialized Rock Hopper"
67 | 68 | Within your decorator methods, use @this.component@ to refer to the decorated 69 | object. If a decorator defines new methods, they will be passed through by any 70 | other decorators you wrap an object with. 71 | 72 |
var HornDecorator = new Decorator(Bike, {
73 |       beepHorn: function(noise) {
74 |           return noise.toUpperCase();
75 |       }
76 |   });
77 | 
78 |   var bike = new Bike('Specialized Rock Hopper', 21);
79 | 
80 |   // Let's wrap a HornDecorator with a DiskBrakeDecorator
81 |   bike = new HornDecorator(bike);
82 |   bike = new DiskBrakeDecorator(bike);
83 | 
84 |   bike.beepHorn('beep!')    // -> "BEEP!"
85 | -------------------------------------------------------------------------------- /site/src/pages/deferrable.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Deferrable 3 | 4 | @Deferrable@ is a module you can use to represent "futures or 5 | promises":http://en.wikipedia.org/wiki/Futures_and_promises, objects that 6 | stand in for results that are not yet known. For example, an Ajax request can 7 | be represented as a deferrable, since when it is created the response is not 8 | known, and callbacks must be registered to react to the response when it 9 | arrives. @Deferrable@ provides an API for adding and triggering such callbacks 10 | on these objects. 11 | 12 |
// In the browser
13 |   JS.require('JS.Deferrable', function(Deferrable) { ... });
14 | 
15 |   // In CommonJS
16 |   var Deferrable = require('jsclass/src/deferrable').Deferrable;
17 | 18 | h3. Setting up a deferrable 19 | 20 | A deferrable object's job is to wrap a long-running computation and provide a 21 | means to notify interested parties when the computation completes. Let's take 22 | our Ajax request as an example: @Deferrable@ provides the API for clients to 23 | register callbacks, and our code just needs to call @this.succeed()@ with the 24 | result of the request when it completes. 25 | 26 |
var AjaxRequest = new Class({
27 |       include: Deferrable,
28 | 
29 |       initialize: function(url) {
30 |           var self = this;
31 |           jQuery.get(url, function(response) {
32 |               self.succeed(response);
33 |           });
34 |       }
35 |   });
36 | 37 | Clients can then use this class by instantiating it with a URL and then adding 38 | callbacks. The callbacks will be executed when the class calls its @succeed()@ 39 | method. 40 | 41 |
var request = new AjaxRequest('/index.html');
42 |   request.callback(function(response) {
43 |       // handle response
44 |   });
45 | 46 | Each callback added to a deferrable is only ever executed once, on the next 47 | @succeed()@ call. If the deferrable has already completed when you add a 48 | callback, the callback will be executed immediately with the value of the most 49 | recent @succeed()@ call. 50 | 51 | @Deferrable@ also provides an error handling mechanism based on callbacks. If 52 | you want to be notified of an error, you add a callback using the deferrable 53 | objects's @errback()@ method. Callbacks registered like this will be executed 54 | when the deferrable's @fail()@ method is called. 55 | 56 | The full API provided by @Deferrable@ is as follows. For these methods, 57 | @block@ should be a function and @context@ is an optional argument specifying 58 | the binding of @this@ when @block@ is executed. 59 | 60 | h3. @callback(block, context)@ 61 | 62 | Adds a callback to the object. If the object has already received a 63 | @succeed()@, the callback is immediately executed with the value of the last 64 | @succeed()@ call instead of being added to the object. 65 | 66 | h3. @errback(block, context)@ 67 | 68 | Adds an error callback to the object. If the object has already received a 69 | @fail()@, the callback is immediately executed with the value of the last 70 | @fail()@ call instead of being added to the object. 71 | 72 | h3. @timeout(milliseconds)@ 73 | 74 | Sets a time limit specified by @milliseconds@ to the object. If the object has 75 | not received a @succeed()@ or @fail()@ after the given length of time, then 76 | @fail()@ will be called with a @Deferrable.Timeout@ error. 77 | 78 | h3. @cancelTimeout()@ 79 | 80 | Removes the time limit from the deferrable object. 81 | 82 | h3. @succeed(value1[, value2 ...])@ 83 | 84 | Puts the object in the @success@ state, and executes any attached callbacks 85 | passing in the arguments to the @succeed()@ call. The callbacks are then 86 | detached and will not be executed again. 87 | 88 | h3. @fail(value1[, value2 ...])@ 89 | 90 | Puts the object in the @failed@ state, and executes any attached error 91 | callbacks passing in the arguments to the @succeed()@ call. The callbacks are 92 | then detached and will not be executed again. 93 | -------------------------------------------------------------------------------- /site/src/pages/forwardable.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Forwardable 3 | 4 | What was it the "Gang of Four":http://en.wikipedia.org/wiki/Design_Patterns 5 | said? _Prefer delegation_. Delegation is the act of getting the receiver of a 6 | method call to respond by simply passing that call along to some other object. 7 | @Forwardable@, a port of Ruby's "@Forwardable@ 8 | module":http://ruby-doc.org/core/classes/Forwardable.html, allows you to 9 | easily define instance methods that do just that. 10 | 11 |
// In the browser
12 |   JS.require('JS.Forwardable', function(Forwardable) { ... });
13 | 
14 |   // In CommonJS
15 |   var Forwardable = require('jsclass/src/forward').Forwardable;
16 | 17 | Let's say you have a class that wraps a collection of objects, which it stores 18 | as an array in one of its instance properties. You might implement some 19 | methods for manipulating the array through the class' own interface: 20 | 21 |
var RecordCollection = new Class({
22 |       initialize: function() {
23 |           this.records = [];
24 |       },
25 |       push: function(record) {
26 |           return this.records.push(record);
27 |       },
28 |       shift: function() {
29 |          return this.records.shift();
30 |       }
31 |   });
32 | 33 | Instead, you can @extend@ the class with @Forwardable@, then use 34 | @defineDelegators()@ to create the methods. @defineDelegators@ takes the name 35 | of the instance property to delegate calls to as the first argument, and the 36 | names of the delegated methods as the other arguments: 37 | 38 |
var RecordCollection = new Class({
39 |       extend: Forwardable,
40 | 
41 |       initialize: function() {
42 |           this.records = [];
43 |       }
44 |   });
45 |   RecordCollection.defineDelegators('records', 'push', 'shift');
46 | 
47 |   var recs = new RecordCollection();
48 |   recs.push('The White Stripes - Icky Thump');
49 |   recs.push('Battles - Mirrored');
50 | 
51 |   recs.shift()    // -> "The White Stripes - Icky Thump"
52 | 53 | If you need to define extra class methods, you need to change the notation 54 | slightly: 55 | 56 |
var RecordCollection = new Class({
57 |       extend: [Forwardable, {
58 |           // class methods go here
59 |       }],
60 | 
61 |       initialize: function() {
62 |           this.records = [];
63 |       }
64 |   });
65 | 66 | If you want to give the delegating method a different name, use the 67 | @defineDelegator()@ (singular) method instead: 68 | 69 |
// Add an instance method called 'add' that calls records.push
70 |   RecordCollection.defineDelegator('records', 'push', 'add');
71 | -------------------------------------------------------------------------------- /site/src/pages/hooks.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Metaprogramming hooks 3 | 4 | Ruby defines a few hook methods that you can use to detect when a class is 5 | subclassed or when a module is mixed in. These hooks are called @inherited()@, 6 | @included()@ and @extended()@. 7 | 8 | If a class has a class method called @inherited()@ it will be called whenever 9 | you create a subclass of it: 10 | 11 |
var ChildDetector = new Class({
12 |       extend: {
13 |           inherited: function(klass) {
14 |               // Do stuff with child class
15 |           }
16 |       }
17 |   });
18 | 19 | The hook receives the new child class as an argument. Note that @class@ is a 20 | reserved word in JavaScript and should not be used as a variable name. The 21 | child class will have all its methods in place when @inherited()@ gets called, 22 | so you can use them within your callback function. 23 | 24 | In the same vein, if you @include()@ a module that has a singleton method 25 | called @included@, that method will be called. This effectively allows you to 26 | redefine the meaning of @include@ for individual modules. 27 | 28 | The @extended()@ hook works in much the same way as @included()@, except that 29 | it will be called when the module is used to @extend()@ an object. 30 | 31 |
// This will call MyMod.extended(obj)
32 |   obj.extend(MyMod);
33 | 34 | Again, you can use this to redefine how @extend()@ works with individual 35 | modules, so they can change the behaviour of the objects they extend. 36 | -------------------------------------------------------------------------------- /site/src/pages/index.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. The cross-platform JavaScript class library 3 | 4 | @jsclass@ is a portable, modular JavaScript class library, influenced by the 5 | "Ruby":http://ruby-lang.org/ programming language. It provides a rich set of 6 | tools for building object-oriented JavaScript programs, and is designed to run 7 | on a wide variety of "client- and server-side platforms":/platforms.html. 8 | 9 | In particular, to support the writing of equally portable JavaScript code, it 10 | provides a "package manager":/packages.html and "testing framework":http://jstest.jcoglan.com 11 | that run on all supported platforms. 12 | 13 | h3. Features 14 | 15 | The library provides many of Ruby's powerful features, including: 16 | 17 | * An object system with classes, mixins, and singleton methods 18 | * Late-binding arguments-optional @super@ calls to parent classes and mixins 19 | * @included@, @extended@ and @inherited@ hooks 20 | * Context-preserving method binding 21 | * Reflection APIs for the object system 22 | * Conventions for object equality, comparison, iteration and hashing 23 | * Versions of various standard Ruby modules and data structures 24 | 25 | -------------------------------------------------------------------------------- /site/src/pages/interfaces.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Interfaces 3 | 4 | Though not found in Ruby, I've decided to include @Interface@ support in 5 | @jsclass@. Interfaces are found in Java and can be very useful in JavaScript 6 | when used judiciously. The idea of an interface is that you create a set of 7 | method names with no implementations. You can then insist that objects/classes 8 | implement the named methods; if you require an object to have a certain set of 9 | methods, you can then throw an exception if it does not. 10 | 11 |
// In the browser
12 |   JS.require('JS.Interface', function(Interface) { ... });
13 | 
14 |   // In CommonJS
15 |   var Interface = require('jsclass/src/core').Interface;
16 | 17 | To create an interface, just pass in an array of method names: 18 | 19 |
var IntComparable = new Interface([
20 |       'compareTo', 'lt', 'lte', 'gt', 'gte', 'eq'
21 |   ]);
22 | 
23 |   var IntStateMachine = new Interface([
24 |       'getInitialState', 'changeState'
25 |   ]);
26 | 27 | You can then test any object to find out whether it implements the required 28 | interfaces: 29 | 30 |
Interface.ensure(someObject, IntComparable, IntStateMachine);
31 | 32 | @Interface.ensure()@ tests its first argument against all the supplied 33 | interfaces. If it fails one of the tests, an error is thrown that tells you 34 | the name of the first method found to be missing from the object. 35 | -------------------------------------------------------------------------------- /site/src/pages/introduction.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Getting started with @jsclass@ 3 | 4 | You can use @jsclass@ on any of the "supported platforms":/platforms.html 5 | without any custom configuration. To start using @jsclass@ in your project, 6 | you'll need to download it using the link on the left. 7 | 8 | If you're using @jsclass@ on Node and do not need to support other 9 | environments, you can "install it with @npm@":/platforms/node.html and avoid a 10 | lot of the boilerplate shown below. 11 | 12 | The download contains two directories, @src@ and @min@. Both contain the same 13 | files; @src@ contains the @jsclass@ source code, and @min@ contains a minified 14 | version suitable for production use on the web. Pick which version you want to 15 | use, and copy it into your project. You can then load @jsclass@ into your 16 | pages as follows. 17 | 18 |

19 |   
20 | 21 | On server-side platforms, you need to set the global variable @JSCLASS_PATH@ 22 | like this: 23 | 24 |
(function() {
25 |       var $ = (typeof global === 'object') ? global : this;
26 |       $.JSCLASS_PATH = 'path/to/jsclass/src';
27 |   })();
28 | 29 | Then use the platform's module loading API to load the @jsclass@ package 30 | loader: 31 | 32 |
// On CommonJS:
33 |   var JS = require('./' + JSCLASS_PATH + '/loader');
34 | 
35 |   // On other platforms, this creates JS as a global:
36 |   load(JSCLASS_PATH + '/loader.js');
37 | 38 | @JSCLASS_PATH@ tells the @loader@ script where you're storing the @jsclass@ 39 | library, and will be interpreted relative to the current working directory. 40 | @loader.js@ is a script that knows how to load packages on any platform, while 41 | @loader-browser.js@ only contains code for loading packages in the browser, 42 | and is thus a little smaller. 43 | 44 | This may look like quite a lot of boilerplate, but once you've done this you 45 | can use a single mechanism to load @jsclass@ components (and any other 46 | components you make) on any platform your code needs to run on. That mechanism 47 | is the @JS.require()@ function. 48 | 49 | All components that are part of the @jsclass@ library can be loaded using the 50 | @JS.require()@ function, passing in the name of the object(s) you want to use 51 | and a callback to run once they're ready. 52 | 53 |
JS.require('JS.Hash', 'JS.Observable', function(Hash, Observable) {
54 |       // ...
55 |   });
56 | 57 | The @JS.require()@ function is aware of dependencies and will load everything 58 | you need to use the objects you want. One some platforms, package loading is 59 | asynchronous, and you should be aware of that when structuring your code. If 60 | an object you want to use is already loaded, @JS.require()@ will not reload 61 | it. 62 | 63 | You're now ready to start using @jsclass@. Dive into the reference 64 | documentation linked on the left of this page, and find out how you can "use 65 | the @jsclass@ package system":/packages.html to manage dependencies in your 66 | own projects. 67 | 68 | -------------------------------------------------------------------------------- /site/src/pages/kernel.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. The Kernel module 3 | 4 | Ruby's @Kernel@ module defines the methods common to all objects. Similarly, 5 | the @Kernel@ module in @jsclass@ defines methods shared by all objects 6 | (including class and module objects). Every object created using @Class@ has 7 | these methods, though they may be overridden depending on the object's type. 8 | 9 | h3. @object.__eigen__()@ 10 | 11 | Returns the @object@'s "metamodule", a module used to store any "singleton methods":/singletonmethods.html 12 | attached to the object. Calling @__eigen__()@ on a class or module returns a 13 | module used to store its class methods. 14 | 15 | h3. @object.enumFor(methodName, *args)@ 16 | 17 | Returns an @Enumerator@ (see "@Enumerable@":/enumerable.html) for the object 18 | using the given @methodName@ and optional arguments. For example, the 19 | @Enumerator@ generated by @Enumerable#forEachCons@ is generated as follows: 20 | 21 |
forEachCons: function(n, block, context) {
22 |       if (!block) return this.enumFor('forEachCons', n);
23 |       // forEachCons implementation details ...
24 |   }
25 | 26 | h3. @object.equals(other)@ 27 | 28 | Returns @true@ iff @object@ and @other@ are the same object. This can be 29 | overridden to provide more meaningful equality tests. If you want to use an 30 | object as a key in a "@Hash@":/hash.html you must also override @Kernel#hash()@ 31 | (see below). 32 | 33 | h3. @object.extend(module)@ 34 | 35 | Adds the methods from @module@ as "singleton methods":/singletonmethods.html 36 | to @object@. 37 | 38 | h3. @object.hash()@ 39 | 40 | Returns a hashcode for the object, which is used by "@Hash@":/hash.html when 41 | storing keys. The default implementation returns a unique hexadecimal number 42 | for each object. If two objects are equal according to the @equals()@ method, 43 | they must both return the same hashcode otherwise they will not work correctly 44 | as keys in a @Hash@ or as members of a "@Set@":/set.html. 45 | 46 | h3. @object.isA(type)@ 47 | 48 | Returns @true@ iff @object@ is an instance of the given @type@, which should 49 | be a class or module. If @type@ is anywhere in @object@'s inheritance 50 | hierarchy this method will return @true@. 51 | 52 | h3. @object.method(name)@ 53 | 54 | Returns a copy of the method with the given name from @object@, as a 55 | standalone function bound to @object@'s instance variables. See "method binding":/binding.html. 56 | 57 | h3. @object.tap(block, context)@ 58 | 59 | Calls the function @block@ in the given (optional) @context@, passing @object@ 60 | as a parameter to @block@, and returns @object@. Useful for inspecting 61 | intermediate values in a long method chain. For example: 62 | 63 |
list                 .tap(function(x) { console.log("original: ", x) })
64 |     .toArray()         .tap(function(x) { console.log("array: ", x) })
65 |     .select(condition) .tap(function(x) { console.log("evens: ", x) })
66 |     .map(square)       .tap(function(x) { console.log("squares: ", x) });
67 | 68 | -------------------------------------------------------------------------------- /site/src/pages/license.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. License 3 | 4 | Copyright © 2007–2014 James Coglan and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | Parts of the Software build on techniques from the following open-source 17 | projects: 18 | 19 | * "Prototype":http://prototypejs.org/, © 2005–2010 Sam Stephenson 20 | (MIT license) 21 | * Alex Arnell's "Inheritance":http://www.twologic.com/projects/inheritance/ 22 | library, © 2006 Alex Arnell (MIT license) 23 | * "Base":http://dean.edwards.name/weblog/2006/03/base/, © 2006–2010 24 | Dean Edwards (MIT license) 25 | 26 | The Software contains direct translations to JavaScript of these open-source 27 | Ruby libraries: 28 | 29 | * "Ruby standard library":http://www.ruby-lang.org/en/ modules, © 30 | Yukihiro Matsumoto and contributors (Ruby license) 31 | * "Test::Unit":http://test-unit.rubyforge.org/, © 2000–2003 32 | Nathaniel Talbott (Ruby license) 33 | * "Context":http://github.com/jm/context, © 2008 Jeremy McAnally (MIT 34 | license) 35 | * "EventMachine::Deferrable":http://rubyeventmachine.com/, © 36 | 2006–07 Francis Cianfrocca (Ruby license) 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 39 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 40 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 41 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 42 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 43 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 44 | SOFTWARE. 45 | 46 | -------------------------------------------------------------------------------- /site/src/pages/modifyingmodules.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Modifying existing classes and modules 3 | 4 | Just like in Ruby, classes and modules are open for modification at any time, 5 | so you can add and change methods in existing classes. All classes and modules 6 | in @jsclass@ have some special methods for modifying the methods they contain. 7 | The following is written to apply to modifying classes, but it applies equally 8 | to modifying modules -- classes are just modules that can be instantiated. 9 | 10 | The first of these methods is called @define()@. This adds a single named 11 | method to the class/module. If you're modifying a class, the method instantly 12 | becomes available in instances of the class, and in its subclasses. 13 | 14 |
Animal.define('sleep', function() {
15 |       return this.name + ' is sleeping';
16 |   });
17 | 
18 |   rex.sleep()
19 |   // -> "Rex is sleeping"
20 | 21 | Note that if the class already has a method with the same name as the one 22 | you're defining, the old method will be overwritten. Methods in parent classes 23 | and in mixins can be accessed using @callSuper()@. 24 | 25 | If you want to create aliases for methods that already exist, use the @alias()@ 26 | method. The new alias goes on the left-hand-side, the existing method name on 27 | the right. 28 | 29 |
Animal.alias({
30 |       talk:   'speak',
31 |       rest:   'sleep'
32 |   });
33 | 34 | The other option available to you is to use @include()@. This method has a 35 | couple of roles; if you supply a "@Module@":/modules.html, the module is mixed 36 | into the class and becomes part of its inheritance tree. If you supply any 37 | other type of object, the methods from the object are copied into the class 38 | itself, overwriting any pre-existing methods with similar names. 39 | 40 |
// Replace Dog's speak method  (#1)
41 |   Dog.include({
42 |       speak: function(stuff) {
43 |           return this.callSuper('lots of ' + stuff) + '!';
44 |       }
45 |   });
46 | 
47 |   rex.speak('cats')
48 |   // -> "My name is Rex and I like lots of cats!"
49 | 
50 |   // Mix in a module, altering the class's ancestry
51 |   // callSuper() in Dog#speak will now call this method
52 |   var Speaker = new Module({
53 |       speak: function(stuff) {
54 |           return 'I can talk about ' + stuff + '!';
55 |       }
56 |   });
57 | 
58 |   Dog.include(Speaker);
59 |   rex.speak('cats')
60 |   // -> "I can talk about lots of cats!!"
61 | 62 | Notice how including @Speaker@ does not overwrite the @speak@ method in the 63 | @Dog@ class (marked @#1@ above). That method is defined in @Dog@ and will 64 | persist until overwritten directly in the @Dog@ class. @Speaker@ merely 65 | injects another method called @speak@ into the inheritance chain for the @Dog@ 66 | class. For more information, refer to the "explanation of Ruby's method lookup 67 | algorithm":/inheritance.html. 68 | -------------------------------------------------------------------------------- /site/src/pages/packages.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Package management 3 | 4 | @jsclass@ comes with a package manager that makes it really easy to load 5 | libraries into your application on demand on all @jsclass@'s "supported 6 | platforms":/platforms.html. You can tell it which file each module in your 7 | application lives in, and what other modules it depends on, and it takes care 8 | of resolving dependencies and loading your code for you. This means your 9 | application code only needs to specify which objects it needs, rather than 10 | which scripts to download in order to run. 11 | 12 | For example, say I want to do something with the "@Console@":/console.html 13 | module. I just need to @require@ it, and supply a function to run once all the 14 | requisite code has loaded: 15 | 16 |
JS.require('JS.Console', function(Console) {
17 |       Console.puts('Hello, world!');
18 |   });
19 | 20 | The package system only loads what is needed to get the objects you want, and 21 | it makes sure that each file is only downloaded once. Where possible, files 22 | are downloaded in parallel to improve performance, but it makes sure 23 | interdependent scripts run in the right order. 24 | 25 | It is designed to be able to load any code from any domain, including from 26 | libraries that have their own package systems. It does not require the files 27 | it loads to follow any particular package format, but it can load files that 28 | export global variables or a CommonJS module. It expects the arguments to 29 | @JS.require()@ to either be the names of global objects, or exported 30 | properties of a CommonJS module. Whichever format the loaded files use, 31 | @JS.require()@ will yield the referenced objects as parameters to the callback 32 | function. 33 | 34 | * "Basic usage examples":/packages/introduction.html 35 | * "Pattern-based loading with @autoload@":/packages/autoload.html 36 | * "Custom loader functions":/packages/customloaders.html 37 | 38 | You can use the "@jsbuild@ command-line 39 | tool":http://github.com/jcoglan/jsbuild to bundle your packages for 40 | production. 41 | 42 | -------------------------------------------------------------------------------- /site/src/pages/packages/autoload.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Short-hand setup using @autoload@ 3 | 4 | As your application grows you may find that your package configuration becomes 5 | repetitive. For example, you may have a set of test scripts that mirror the 6 | set of classes in your application: 7 | 8 |
JS.packages(function() { with(this) {
 9 | 
10 |       file('tests/widget_spec.js')
11 |           .provides('WidgetSpec')
12 |           .requires('MyApp.Widget');
13 | 
14 |       file('tests/blog_post_spec.js')
15 |           .provides('BlogPostSpec')
16 |           .requires('MyApp.BlogPost');
17 | 
18 |       file('tests/users/profile_spec.js')
19 |           .provides('Users.ProfileSpec')
20 |           .requires('MyApp.Users.Profile');
21 |   });
22 | 23 | If you run into this situation you can use the @autoload()@ function to set up 24 | packages for objects whose name matches a certain pattern. For example you 25 | could compress the above configuration like this: 26 | 27 |
JS.packages(function() { with(this) {
28 |       autoload(/^(.*)Spec$/, {from: 'tests', require: 'MyApp.$1'});
29 |   });
30 | 31 | If you @require()@ a package that doesn't have an explicit configuration, the 32 | autoloader will try to figure out where to load it from by matching its name 33 | against the set of patterns it knows about. A naming convention is adopted for 34 | converting object names to paths: dots convert to path separators, and 35 | camelcase names are converted to underscored style. Thus @Users.ProfileSpec@ 36 | becomes @users/profile_spec.js@. 37 | 38 | @autoload()@ expects three parameters. The first is a regex that is used to 39 | match package names. 40 | 41 | The @from@ option is a directory path where packages with that name pattern 42 | live, for example this rule would make the package loader look in 43 | @tests/users/profile_spec.js@ to find the @Users.ProfileSpec@ module. If you 44 | don't like the convention used for turning object names into paths, you can 45 | override it by passing a function as @from@. The function should take an 46 | object name and return a path. 47 | 48 |
JS.packages(function() { with(this) {
49 |       autoload(/^(.*)Spec$/, {
50 |           from: function(name) { return '/modules/' + name + '.js' },
51 |           require: 'MyApp.$1'
52 |       });
53 |   });
54 | 55 | The @require@ option lets you specify an object the package depends on, using 56 | match results from the regex. The above rule would mean that 57 | @Users.ProfileSpec@ has a dependency on @MyApp.Users.Profile@. You can also 58 | pass an array as the @require@ option and each string within will be expaned 59 | in this way. 60 | 61 | -------------------------------------------------------------------------------- /site/src/pages/packages/customloaders.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Custom loader functions 3 | 4 | Some libraries, such as the "Google Ajax 5 | APIs":http://code.google.com/apis/ajax/, have their own systems for loading 6 | code on demand that involve more than simply knowing the path to a script 7 | file. Our package system allows you to specify packages that use a loader 8 | function rather than a path to load themselves; the function should take a 9 | callback and call it when the library in question is done loading. For 10 | example, here's how you'd incorporate Google Maps into your library: 11 | 12 |
JS.packages(function() { with(this) {
13 |       file('http://www.google.com/jsapi?key=MY_GOOGLE_KEY')
14 |           .provides('google.load');
15 | 
16 |       loader(function(cb) { google.load('maps', '2.x', {callback: cb}) })
17 |           .provides('GMap2', 'GClientGeocoder')
18 |           .requires('google.load');
19 |   }});
20 | 21 | The callback (@cb@) is a function generated by the package system that 22 | continues to load and run dependent code once the custom loader has finished 23 | its work. If you don't call @cb@ (or pass it to a function that will call it 24 | for you as above), code that depends on this library will not run. 25 | 26 | @JS.packages@ also provides post-load setup hooks that let you run some code 27 | after a file loads. For example, a strategy for loading 28 | "YUI3":http://developer.yahoo.com/yui/3/ might involve loading the seed file, 29 | creating a new global instance of the library, then using YUI's own loader 30 | functions to load further modules. Some sample code: 31 | 32 |
JS.packages(function() { with(this) {
33 |       file('http://yui.yahooapis.com/3.0.0pr2/build/yui/yui-min.js')
34 |           .setup(function() { window.yui3 = YUI() })
35 |           .provides('YUI', 'yui3');
36 | 
37 |       loader(function(cb) { yui3.use('node', cb) })
38 |           .provides('yui3.Node')
39 |           .requires('yui3');
40 |   }});
41 | 42 | Loader functions can also be used to generate library objects that are 43 | expensive to create without necessarily loading code from external files. Just 44 | remember to call @cb@ yourself when the generated object is ready: 45 | 46 |
JS.packages(function() { with(this) {
47 |       loader(function(cb) {
48 |           window.ChocolateFactory = new WonkaVenture();
49 |           // Perform other expensive setup operations
50 |           cb();
51 |       })
52 |       .provides('ChocolateFactory');
53 |   }});
54 | -------------------------------------------------------------------------------- /site/src/pages/platforms.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Supported platforms 3 | 4 | One of the main goals of @jsclass@ is to help you write programs that work 5 | across a wide variety of JavaScript platforms. It does this by abstracting 6 | some platform-specific details such as "module loading":/packages.html, and by 7 | providing cross-platform "testing tools":http://jstest.jcoglan.com. 8 | 9 | @jsclass@ currently runs without modification on all the following 10 | environments: 11 | 12 | h3. Web browsers 13 | 14 | * "Chrome":http://www.google.com/chrome 15 | * "Firefox":http://www.getfirefox.com/ 16 | * "Internet Explorer":http://www.microsoft.com/windows/internet-explorer/default.aspx 17 | * "Opera":http://www.opera.com/ 18 | * "Safari":http://www.apple.com/safari/ 19 | 20 | h3. Headless DOM environments 21 | 22 | * "PhantomJS":http://www.phantomjs.org/ (both in the browser and the scripting 23 | runtime) 24 | * "SlimerJS":http://slimerjs.org/ 25 | 26 | h3. Server-side platforms 27 | 28 | * "Node.js":http://nodejs.org/ 29 | * "Narwhal":http://narwhaljs.org/ 30 | * "RingoJS":http://ringojs.org/ 31 | 32 | h3. Database shells 33 | 34 | * "MongoDB":http://www.mongodb.org/ 35 | 36 | h3. GUI frameworks 37 | 38 | * "Mozilla XULRunner":https://developer.mozilla.org/en/xulrunner 39 | * "Adobe AIR":http://www.adobe.com/products/air/ 40 | 41 | h3. Shell environments 42 | 43 | * "V8 shell":http://code.google.com/p/v8/ 44 | * "Mozilla Rhino":http://www.mozilla.org/rhino/ 45 | * "Mozilla SpiderMonkey":http://www.mozilla.org/js/spidermonkey/ 46 | * "Windows Script Host":http://msdn.microsoft.com/en-us/library/9bbdkx3k(VS.85).aspx 47 | 48 | -------------------------------------------------------------------------------- /site/src/pages/platforms/node.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Installing with @npm@ 3 | 4 | If you want to use @jsclass@ with Node, there's an npm pacakge you can 5 | install: 6 | 7 |
$ npm install jsclass
8 | 9 | After installing this package, you can either use the 10 | "@JS.require()@":/packages.html API to load components, or use the standard 11 | @require()@ function. 12 | 13 |
// Using JS.require()
14 | 
15 |   var JS = require('jsclass');
16 | 
17 |   JS.require('JS.Set', function(Set) {
18 |       var s = new Set([1,2,3]);
19 |       // ...
20 |   });
21 | 
22 | 
23 |   // Using require()
24 | 
25 |   var Set = require('jsclass/src/set');
26 |   var s = new Set([1,2,3]);
27 | -------------------------------------------------------------------------------- /site/src/pages/proxies.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Proxy 3 | 4 | In general, a proxy is an object that controls access to another object 5 | (referred to as the _subject_). It has exactly the same interface as the 6 | subject and does not modify the subject's behaviour. All it does is forward 7 | method calls onto the subject and it returns the results of such calls. 8 | Proxies are often used to restrict access to a subject, to provide a local 9 | interface to a remote object (such as a web service API), or to allow the 10 | instantiation of memory-intensive objects on demand. 11 | 12 |
// In the browser
13 |   JS.require('JS.Proxy', function(Proxy) { ... });
14 | 
15 |   // In CommonJS
16 |   var Proxy = require('jsclass/src/proxy').Proxy;
17 | 18 | h3. Virtual proxies 19 | 20 | A virtual proxy is an object that acts as a stand-in for its subject. The 21 | subject is not initialized until it is really needed, allowing for 'lazy 22 | instantiation' of objects that are expensive to create. 23 | 24 | @jsclass@ provides a module called @Proxy.Virtual@. This allows you create 25 | proxies for classes without needing to write all the forwarding and 26 | instantiation methods yourself. @Proxy.Virtual@ inspects the proxied class and 27 | automatically creates proxies for all its instance methods. This saves you 28 | time and reduces code duplication. 29 | 30 | Consider the following example: we have a @Dog@ class, which keeps track of 31 | how many times it has been instantiated through its class property @instances@. 32 | We then create a @DogProxy@ class from @Dog@, and instantiate it. At this 33 | point we see that @Dog.instances == 0@. Then we call @rex.bark()@, which 34 | instantiates @rex@'s subject and calls @bark()@ on it. @Dog.instances@ now 35 | equals @1@. Calling further methods on @rex@ will not create any more @Dog@ 36 | instances. 37 | 38 |
var Dog = new Class({
39 |       extend: {
40 |           instances: 0
41 |       },
42 |       initialize: function(name) {
43 |           this.name = name;
44 |           this.klass.instances++;
45 |       },
46 |       bark: function() {
47 |           return this.name + ' says WOOF!';
48 |       }
49 |   });
50 | 
51 |   var DogProxy = new Proxy.Virtual(Dog);
52 | 
53 |   var rex = new DogProxy('Rex');
54 |   Dog.instances   // -> 0
55 | 
56 |   rex.bark()  // -> "Rex says WOOF!"
57 |   Dog.instances   // -> 1
58 | 59 | This pattern is particularly suited to creating parts of a UI that are 60 | initially hidden - you can create proxies for them on page load, but they 61 | won't add any of their HTML to the page until you choose to show them. 62 | -------------------------------------------------------------------------------- /site/src/pages/singletonmethods.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Singleton methods 3 | 4 | In Ruby, singleton methods are methods attached to a single object rather than 5 | defined in a class. If you add a singleton method to a Ruby object, @super@ 6 | refers to the method from the object's class definition. @jsclass@ allows the 7 | same behaviour. Recall our @Animal@ class: 8 | 9 |
var Animal = new Class({
10 |       initialize: function(name) {
11 |           this.name = name;
12 |       },
13 |       speak: function(things) {
14 |           return 'My name is ' + this.name + ' and I like ' + things;
15 |       }
16 |   });
17 | 18 | We can create a new animal and @extend@ it with singleton methods. All 19 | @Class@-derived objects have an @extend()@ method for doing this: 20 | 21 |
var cow = new Animal('Daisy');
22 | 
23 |   cow.extend({
24 |       speak: function(stuff) {
25 |           return 'Mooo! ' + this.callSuper();
26 |       },
27 |       getName: function() {
28 |           return this.name;
29 |       }
30 |   });
31 | 
32 |   cow.getName()   // -> "Daisy"
33 | 
34 |   cow.speak('grass')
35 |       // -> "Mooo! My name is Daisy and I like grass"
36 | 37 | h3. Modules as object extensions 38 | 39 | As well as passing simple objects into the @extend()@ method, you can use 40 | modules. The receiving object will then gain all the methods of the module. If 41 | we extend using "@Observable@":/observable.html, for example: 42 | 43 |
cow.extend(Observable);
44 | 
45 |   cow.addObserver(function() {
46 |       alert('This cow is observable!');
47 |   });
48 | 
49 |   cow.notifyObservers();
50 | 51 | This alerts @"This cow is observable!"@, as expected. Using modules to extend 52 | objects has some interesting inheritance consequences, which are more 53 | thoroughly exposed in "the inheritance article":/inheritance.html. In short, 54 | all singleton methods are stored in a module attached to the object - this is 55 | known as an eigenclass or metaclass in Ruby circles. By using a module to 56 | extend an object, the module is mixed into the eigenclass, making it part of 57 | the inheritance tree. So we can override @notifyObservers()@, for example, to 58 | duplicate every observer added to an object. @callSuper()@ calls out to the 59 | module we used to extend the object. 60 | 61 |
cow.extend({
62 |       notifyObservers: function() {
63 |           this.callSuper();
64 |           this.callSuper();
65 |       }
66 |   });
67 | 
68 |   // alerts "This cow is observable!" twice
69 |   cow.notifyObservers();
70 | 71 | -------------------------------------------------------------------------------- /site/src/pages/singletons.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. Singletons 3 | 4 | A singleton class is one which can only ever have one instance. The concept is 5 | more useful in Java, where you cannot create an object without first creating 6 | a class. JavaScript allows objects without classes (indeed, it has no classes, 7 | only objects) but using @Singleton@ lets you create custom objects from 8 | existing @jsclass@ classes, allowing you to inherit methods, include modules 9 | and use @method()@ et al. 10 | 11 |
// In the browser
12 |   JS.require('JS.Singleton', function(Singleton) { ... });
13 | 
14 |   // In CommonJS
15 |   var Singleton = require('jsclass/src/core').Singleton;
16 | 17 | To create a singleton: 18 | 19 |
var Camel = new Singleton(Animal, {
20 |       fillHumpsWithWater: function() { ... }
21 |   });
22 | 
23 |   // You can call instance methods...
24 |   Camel.speak('the desert');    // from Animal
25 |   Camel.fillHumpsWithWater();
26 | 
27 |   var s = Camel.method('speak');
28 |   s('drinking');
29 | 
30 |   Camel.klass.superclass    // -> Animal
31 | 32 | @Singleton@ just creates a class with the arguments you give it, immediately 33 | instantiates this new class and returns the instance. You can access the class 34 | through @Camel.klass@ as shown above. 35 | 36 | -------------------------------------------------------------------------------- /site/src/pages/stacktrace.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. StackTrace 3 | 4 | @StackTrace@ is a module you can use to inspect what an application is doing 5 | internally while it runs. It provides an interface for monitoring method 6 | calls, which you can use to build monitoring and debugging tools. 7 | 8 |
// In the browser
 9 |   JS.require('JS.StackTrace', function(StackTrace) { ... });
10 | 
11 |   // In CommonJS
12 |   var StackTrace = require('jsclass/src/stack_trace').StackTrace;
13 | 14 | The @StackTrace@ module supports the "@Observable@":/observable.html interface 15 | for monitoring what the stack is doing: 16 | 17 |
StackTrace.addObserver(monitor);
18 | 19 | @monitor@ should be an object that responds to the @update()@ method. This 20 | method takes two arguments: an event name, and an event object. So, the object 21 | should look something like this: 22 | 23 |
monitor = {
24 |       update: function(event, data) {
25 |           if (event === 'call') // ...
26 |       }
27 |   };
28 | 29 | There are three types of event, which tell you when a function is called, when 30 | a function returns, and when an error is thrown. 31 | 32 | h3. @call@ event 33 | 34 | The @call@ event fires when a function is called. The @data@ object in this 35 | case represents the data surrounding the method call. It has the following 36 | properties: 37 | 38 | * @object@ - The object receiving the method call 39 | * @method@ - The @Method@ object for the current method call 40 | * @env@ - The @Class@ or @Module@ where the method is being executed 41 | * @args@ - An @Array@ of the arguments to the method call 42 | * @leaf@ - Boolean indicating whether the call is a leaf; it's a leaf if no 43 | other method calls are logged while it is running. This is always @true@ 44 | when a method is first called. 45 | 46 | h3. @return@ event 47 | 48 | The @return@ event fires when a function returns. The @data@ object is the 49 | same object that's passed to the @call@ event, with one extra property: 50 | 51 | * @result@ - The return value of the method call 52 | 53 | h3. @error@ event 54 | 55 | This event fires when an exception is thrown. The @data@ object is just the 56 | error that was raised. 57 | 58 | h3. Enabling tracing 59 | 60 | Since tracing incurs a performance cost, @jsclass@ does not trace anything by 61 | default. When you want to trace a module or class, you pass a list of the 62 | modules you want to trace to @Method.trace()@, and use @Method.untrace()@ to 63 | stop tracing them. 64 | 65 |
Method.trace([Hash, Range]);
66 | 67 | h3. Call stack logging 68 | 69 | There is a logger you can use to print the call stack to the 70 | "@Console@":/console.html. To use it, just pass a list of modules to trace 71 | and a function to @Method.tracing()@. This enables tracing for the given 72 | modules, runs the function, then disables tracing again. 73 | 74 |
Method.tracing([Hash], function() {
75 |       var hash = new OrderedHash(['foo', 4, 'bar', 5]);
76 |       hash.hasKey('foo');
77 |   });
78 | 79 | !/images/tracing.png! 80 | -------------------------------------------------------------------------------- /site/src/pages/tsort.haml: -------------------------------------------------------------------------------- 1 | :textile 2 | h2. TSort 3 | 4 | @TSort@ is a JavaScript version of Ruby's @TSort@ module, which provides 5 | "topological sort":http://en.wikipedia.org/wiki/Topological_sorting capability 6 | to data structures. 7 | 8 |
// In the browser
 9 |   JS.require('JS.TSort', function(TSort) { ... });
10 | 
11 |   // In CommonJS
12 |   var TSort = require('jsclass/src/tsort').TSort;
13 | 14 | The canonical example of this is determining how a set of 15 | dependent tasks should be sorted such that each task comes after those it 16 | depends on in the list. One way to represent this information may be as a task 17 | table: 18 | 19 |
var tasks = new Tasks({
20 |       'eat breakfast': ['serve'],
21 |       'serve':         ['cook'],
22 |       'cook':          ['buy bacon', 'buy eggs'],
23 |       'buy bacon':     [],
24 |       'buy eggs':      []
25 |   });
26 | 27 | (This example is borrowed from the excellent "Getting to know the Ruby 28 | standard 29 | library":http://endofline.wordpress.com/2010/12/22/ruby-standard-library-tsort/ 30 | blog.) 31 | 32 | This table represents each task involved in serving breakfast. The tasks are 33 | the keys in the table, and each task maps to the list of tasks which must be 34 | done immediately before it. We want to to sort all our tasks into a linear 35 | list so we can process them in the correct order, and @TSort@ lets us do that. 36 | 37 | To use @TSort@, our @Tasks@ class must implement two methods. @tsortEachNode@ 38 | must take a callback function and context and yield each task in the set to 39 | the callback. @tsortEachChild@ must accept an individual task and yield each 40 | of its direct children. We can implement this simply: 41 | 42 |
var Tasks = new Class({
43 |       include: TSort,
44 | 
45 |       initialize: function(table) {
46 |           this.table = table;
47 |       },
48 | 
49 |       tsortEachNode: function(block, context) {
50 |           for (var task in this.table) {
51 |               if (this.table.hasOwnProperty(task))
52 |                   block.call(context, task);
53 |           }
54 |       },
55 | 
56 |       tsortEachChild: function(task, block, context) {
57 |           var tasks = this.table[task];
58 |           for (var i = 0, n = tasks.length; i < n; i++)
59 |               block.call(context, tasks[i]);
60 |       }
61 |   });
62 | 63 | Once we've told @TSort@ how to traverse our list of tasks, it can sort the 64 | dependent tasks out for us: 65 | 66 |
tasks.tsort()
67 |   // -> ['buy bacon', 'buy eggs', 'cook', 'serve', 'eat breakfast']
68 | 69 | We now have a flat list of the tasks and can iterate over them in order, 70 | knowing that each task will have all its dependencies run before it in the 71 | list. 72 | 73 | Note that @TSort@ sorts based on the how objects are related to each other in 74 | a graph, not by how they compare using "comparison":/comparable.html methods. 75 | -------------------------------------------------------------------------------- /site/src/stylesheets/screen.sass: -------------------------------------------------------------------------------- 1 | body 2 | background: #fff 3 | border-top: 2em solid #95c3f0 4 | color: #30303c 5 | font: 300 18px/1.4 Open Sans, FreeSans, Helvetica, Arial, sans-serif 6 | margin: 0 7 | padding: 0 8 | 9 | .nav 10 | float: left 11 | margin: 2em 12 | 13 | h1 14 | font-size: 2em 15 | font-weight: 400 16 | margin: 0 17 | 18 | a 19 | color: #30303c 20 | 21 | h4, li 22 | font-size: 90% 23 | 24 | .content 25 | margin: 2em 0 3em 20em 26 | width: 40em 27 | 28 | h2 29 | font-size: 1.8em 30 | font-weight: 300 31 | margin: 0 0 1em 32 | 33 | h3 34 | font-size: 1em 35 | font-weight: bold 36 | margin: 2.8em 0 1.4em 37 | 38 | p, ul, pre 39 | margin: 1.4em 0 40 | 41 | ul 42 | margin: 1em 0 0 2em 43 | padding: 0 44 | list-style: circle outside 45 | 46 | ul ul 47 | font-size: 0.9em 48 | margin-top: 0 49 | 50 | a 51 | color: #3094c0 52 | text-decoration: none 53 | 54 | a:hover 55 | text-decoration: underline 56 | 57 | pre, code 58 | font-family: Inconsolata, Monaco, Lucida Console, Courier New, monospace 59 | 60 | pre, blockquote 61 | border-left: 1em solid #f0f0f0 62 | font-size: 0.9em 63 | margin-left: 0 64 | padding-left: 2em 65 | 66 | .footer 67 | border-top: 1px solid #d0d0d0 68 | clear: both 69 | margin: 2em 0 0 2em 70 | padding: 1em 0 2em 18em 71 | width: 40em 72 | 73 | -------------------------------------------------------------------------------- /source/assets/bullet_go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcoglan/jsclass/7a40c031fe0e04e29b2273a8a4ff6ca049c4bfa2/source/assets/bullet_go.png -------------------------------------------------------------------------------- /source/command.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS, 4 | 5 | Enumerable = js.Enumerable || require('./enumerable').Enumerable, 6 | Observable = js.Observable || require('./observable').Observable; 7 | 8 | if (E) exports.JS = exports; 9 | factory(js, Enumerable, Observable, E ? exports : js); 10 | 11 | })(function(JS, Enumerable, Observable, exports) { 12 | 'use strict'; 13 | 14 | var Command = new JS.Class('Command', { 15 | initialize: function(functions) { 16 | if (typeof functions === 'function') 17 | functions = {execute: functions}; 18 | this._functions = functions; 19 | this._stack = this._functions.stack || null; 20 | }, 21 | 22 | execute: function(push) { 23 | if (this._stack) this._stack._restart(); 24 | var exec = this._functions.execute; 25 | if (exec) exec.apply(this); 26 | if (this._stack && push !== false) this._stack.push(this); 27 | }, 28 | 29 | undo: function() { 30 | var exec = this._functions.undo; 31 | if (exec) exec.apply(this); 32 | }, 33 | 34 | extend: { 35 | Stack: new JS.Class({ 36 | include: [Observable || {}, Enumerable || {}], 37 | 38 | initialize: function(options) { 39 | options = options || {}; 40 | this._redo = options.redo || null; 41 | this.clear(); 42 | }, 43 | 44 | forEach: function(block, context) { 45 | if (!block) return this.enumFor('forEach'); 46 | block = Enumerable.toFn(block); 47 | 48 | for (var i = 0, n = this._stack.length; i < n; i++) { 49 | if (this._stack[i] !== undefined) 50 | block.call(context, this._stack[i], i); 51 | } 52 | return this; 53 | }, 54 | 55 | clear: function() { 56 | this._stack = []; 57 | this.length = this.pointer = 0; 58 | }, 59 | 60 | _restart: function() { 61 | if (this.pointer === 0 && this._redo && this._redo.execute) 62 | this._redo.execute(); 63 | }, 64 | 65 | push: function(command) { 66 | this._stack.splice(this.pointer, this.length); 67 | this._stack.push(command); 68 | this.length = this.pointer = this._stack.length; 69 | if (this.notifyObservers) this.notifyObservers(this); 70 | }, 71 | 72 | stepTo: function(position) { 73 | if (position < 0 || position > this.length) return; 74 | var i, n; 75 | 76 | switch (true) { 77 | case position > this.pointer : 78 | for (i = this.pointer, n = position; i < n; i++) 79 | this._stack[i].execute(false); 80 | break; 81 | 82 | case position < this.pointer : 83 | if (this._redo && this._redo.execute) { 84 | this._redo.execute(); 85 | for (i = 0, n = position; i < n; i++) 86 | this._stack[i].execute(false); 87 | } else { 88 | for (i = 0, n = this.pointer - position; i < n; i++) 89 | this._stack[this.pointer - i - 1].undo(); 90 | } 91 | break; 92 | } 93 | this.pointer = position; 94 | if (this.notifyObservers) this.notifyObservers(this); 95 | }, 96 | 97 | undo: function() { 98 | this.stepTo(this.pointer - 1); 99 | }, 100 | 101 | redo: function() { 102 | this.stepTo(this.pointer + 1); 103 | } 104 | }) 105 | } 106 | }); 107 | 108 | exports.Command = Command; 109 | }); 110 | 111 | -------------------------------------------------------------------------------- /source/comparable.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS; 4 | 5 | if (E) exports.JS = exports; 6 | factory(js, E ? exports : js); 7 | 8 | })(function(JS, exports) { 9 | 'use strict'; 10 | 11 | var Comparable = new JS.Module('Comparable', { 12 | extend: { 13 | ClassMethods: new JS.Module({ 14 | compare: function(one, another) { 15 | return one.compareTo(another); 16 | } 17 | }), 18 | 19 | included: function(base) { 20 | base.extend(this.ClassMethods); 21 | } 22 | }, 23 | 24 | lt: function(other) { 25 | return this.compareTo(other) < 0; 26 | }, 27 | 28 | lte: function(other) { 29 | return this.compareTo(other) < 1; 30 | }, 31 | 32 | gt: function(other) { 33 | return this.compareTo(other) > 0; 34 | }, 35 | 36 | gte: function(other) { 37 | return this.compareTo(other) > -1; 38 | }, 39 | 40 | eq: function(other) { 41 | return this.compareTo(other) === 0; 42 | }, 43 | 44 | between: function(a, b) { 45 | return this.gte(a) && this.lte(b); 46 | } 47 | }); 48 | 49 | exports.Comparable = Comparable; 50 | }); 51 | 52 | -------------------------------------------------------------------------------- /source/console/_head.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS, 4 | 5 | Enumerable = js.Enumerable || require('./enumerable').Enumerable; 6 | 7 | if (E) exports.JS = exports; 8 | factory(js, Enumerable, E ? exports : js); 9 | 10 | })(function(JS, Enumerable, exports) { 11 | 'use strict'; 12 | 13 | -------------------------------------------------------------------------------- /source/console/_tail.js: -------------------------------------------------------------------------------- 1 | exports.Console = Console; 2 | }); 3 | 4 | -------------------------------------------------------------------------------- /source/console/base.js: -------------------------------------------------------------------------------- 1 | Console.extend({ 2 | Base: new JS.Class({ 3 | __buffer__: '', 4 | __format__: '', 5 | 6 | backtraceFilter: function() { 7 | if (typeof version === 'function' && version() > 100) { 8 | return /.*/; 9 | } else { 10 | return null; 11 | } 12 | }, 13 | 14 | coloring: function() { 15 | return !this.envvar(Console.NO_COLOR); 16 | }, 17 | 18 | echo: function(string) { 19 | if (typeof console !== 'undefined') return console.log(string); 20 | if (typeof print === 'function') return print(string); 21 | }, 22 | 23 | envvar: function(name) { 24 | return null; 25 | }, 26 | 27 | exit: function(status) { 28 | if (typeof system === 'object' && system.exit) system.exit(status); 29 | if (typeof quit === 'function') quit(status); 30 | }, 31 | 32 | format: function(type, name, args) { 33 | if (!this.coloring()) return; 34 | var escape = Console.ESCAPE_CODES[type][name]; 35 | 36 | for (var i = 0, n = args.length; i < n; i++) 37 | escape = escape.replace('%' + (i+1), args[i]); 38 | 39 | this.__format__ += Console.escape(escape); 40 | }, 41 | 42 | flushFormat: function() { 43 | var format = this.__format__; 44 | this.__format__ = ''; 45 | return format; 46 | }, 47 | 48 | getDimensions: function() { 49 | var width = this.envvar('COLUMNS') || Console.DEFAULT_WIDTH, 50 | height = this.envvar('ROWS') || Console.DEFAULT_HEIGHT; 51 | 52 | return [parseInt(width, 10), parseInt(height, 10)]; 53 | }, 54 | 55 | print: function(string) { 56 | var coloring = this.coloring(), 57 | width = this.getDimensions()[0], 58 | esc = Console.escape, 59 | length, prefix, line; 60 | 61 | while (string.length > 0) { 62 | length = this.__buffer__.length; 63 | prefix = (length > 0 && coloring) ? esc('1F') + esc((length + 1) + 'G') : ''; 64 | line = string.substr(0, width - length); 65 | 66 | this.__buffer__ += line; 67 | 68 | if (coloring) this.echo(prefix + this.flushFormat() + line); 69 | 70 | if (this.__buffer__.length === width) { 71 | if (!coloring) this.echo(this.__buffer__); 72 | this.__buffer__ = ''; 73 | } 74 | string = string.substr(width - length); 75 | } 76 | }, 77 | 78 | puts: function(string) { 79 | var coloring = this.coloring(), 80 | esc = Console.escape, 81 | length = this.__buffer__.length, 82 | prefix = (length > 0 && coloring) ? esc('1F') + esc((length + 1) + 'G') : this.__buffer__; 83 | 84 | this.echo(prefix + this.flushFormat() + string); 85 | this.__buffer__ = ''; 86 | } 87 | }) 88 | }); 89 | 90 | -------------------------------------------------------------------------------- /source/console/browser.js: -------------------------------------------------------------------------------- 1 | Console.extend({ 2 | Browser: new JS.Class(Console.Base, { 3 | backtraceFilter: function() { 4 | return new RegExp(window.location.href.replace(/(\/[^\/]+)/g, '($1)?') + '/?', 'g'); 5 | }, 6 | 7 | coloring: function() { 8 | if (this.envvar(Console.NO_COLOR)) return false; 9 | return Console.AIR; 10 | }, 11 | 12 | echo: function(string) { 13 | if (window.runtime) return window.runtime.trace(string); 14 | if (window.console) return console.log(string); 15 | alert(string); 16 | }, 17 | 18 | envvar: function(name) { 19 | return window[name] || null; 20 | }, 21 | 22 | getDimensions: function() { 23 | if (Console.AIR) return this.callSuper(); 24 | return [1024, 1]; 25 | } 26 | }) 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /source/console/browser_color.js: -------------------------------------------------------------------------------- 1 | Console.extend({ 2 | BrowserColor: new JS.Class(Console.Browser, { 3 | COLORS: { 4 | green: 'limegreen' 5 | }, 6 | 7 | __queue__: [], 8 | __state__: null, 9 | 10 | format: function(type, name) { 11 | name = name.replace(/^bg/, ''); 12 | 13 | var state = JS.extend({}, this.__state__ || {}), 14 | color = this.COLORS[name] || name, 15 | no = /^no/.test(name); 16 | 17 | if (type === 'reset') 18 | state = null; 19 | else if (no) 20 | delete state[type]; 21 | else if (type === 'weight') 22 | state.weight = 'font-weight: ' + name; 23 | else if (type === 'style') 24 | state.style = 'font-style: ' + name; 25 | else if (type === 'underline') 26 | state.underline = 'text-decoration: underline'; 27 | else if (type === 'color') 28 | state.color = 'color: ' + color; 29 | else if (type === 'background') 30 | state.background = 'background-color: ' + color; 31 | else 32 | state = undefined; 33 | 34 | if (state !== undefined) { 35 | this.__state__ = state; 36 | this.__queue__.push(state); 37 | } 38 | }, 39 | 40 | print: function(string) { 41 | this.__queue__.push(string) 42 | }, 43 | 44 | puts: function(string) { 45 | this.print(string); 46 | var buffer = '', formats = [], item; 47 | while ((item = this.__queue__.shift()) !== undefined) { 48 | if (typeof item === 'string') { 49 | if (this.__state__) { 50 | buffer += '%c' + item; 51 | formats.push(this._serialize(this.__state__)); 52 | } else { 53 | buffer += item; 54 | } 55 | } else { 56 | this.__state__ = item; 57 | } 58 | } 59 | console.log.apply(console, [buffer].concat(formats)); 60 | }, 61 | 62 | _serialize: function(state) { 63 | var rules = []; 64 | for (var key in state) rules.push(state[key]); 65 | return rules.join('; '); 66 | } 67 | }) 68 | }); 69 | 70 | -------------------------------------------------------------------------------- /source/console/config.js: -------------------------------------------------------------------------------- 1 | Console.BROWSER = (typeof window !== 'undefined'); 2 | Console.NODE = (typeof process === 'object') && !Console.BROWSER; 3 | Console.PHANTOM = (typeof phantom !== 'undefined'); 4 | Console.AIR = (Console.BROWSER && typeof runtime !== 'undefined'); 5 | Console.RHINO = (typeof java !== 'undefined' && typeof java.lang !== 'undefined'); 6 | Console.WSH = (typeof WScript !== 'undefined'); 7 | 8 | var useColor = false, ua; 9 | if (Console.BROWSER) { 10 | ua = navigator.userAgent; 11 | if (window.console && /Chrome/.test(ua)) 12 | useColor = true; 13 | } 14 | 15 | if (Console.PHANTOM) Console.adapter = new Console.Phantom(); 16 | else if (useColor) Console.adapter = new Console.BrowserColor(); 17 | else if (Console.BROWSER) Console.adapter = new Console.Browser(); 18 | else if (Console.NODE) Console.adapter = new Console.Node(); 19 | else if (Console.RHINO) Console.adapter = new Console.Rhino(); 20 | else if (Console.WSH) Console.adapter = new Console.Windows(); 21 | else Console.adapter = new Console.Base(); 22 | 23 | for (var type in Console.ESCAPE_CODES) { 24 | for (var key in Console.ESCAPE_CODES[type]) (function(type, key) { 25 | Console.define(key, function() { 26 | Console.adapter.format(type, key, arguments); 27 | }); 28 | })(type, key); 29 | } 30 | 31 | Console.extend(Console); 32 | 33 | -------------------------------------------------------------------------------- /source/console/node.js: -------------------------------------------------------------------------------- 1 | Console.extend({ 2 | Node: new JS.Class(Console.Base, { 3 | backtraceFilter: function() { 4 | return new RegExp(process.cwd() + '/', 'g'); 5 | }, 6 | 7 | coloring: function() { 8 | return !this.envvar(Console.NO_COLOR) && require('tty').isatty(1); 9 | }, 10 | 11 | envvar: function(name) { 12 | return process.env[name] || null; 13 | }, 14 | 15 | exit: function(status) { 16 | process.exit(status); 17 | }, 18 | 19 | getDimensions: function() { 20 | var width, height, dims; 21 | if (process.stdout.getWindowSize) { 22 | dims = process.stdout.getWindowSize(); 23 | width = dims[0]; 24 | height = dims[1]; 25 | } else { 26 | dims = process.binding('stdio').getWindowSize(); 27 | width = dims[1]; 28 | height = dims[0]; 29 | } 30 | return [width, height]; 31 | }, 32 | 33 | print: function(string) { 34 | process.stdout.write(this.flushFormat() + string); 35 | }, 36 | 37 | puts: function(string) { 38 | console.log(this.flushFormat() + string); 39 | } 40 | }) 41 | }); 42 | 43 | -------------------------------------------------------------------------------- /source/console/phantom.js: -------------------------------------------------------------------------------- 1 | Console.extend({ 2 | Phantom: new JS.Class(Console.Base, { 3 | echo: function(string) { 4 | console.log(string); 5 | }, 6 | 7 | envvar: function(name) { 8 | return require('system').env[name] || null; 9 | }, 10 | 11 | exit: function(status) { 12 | phantom.exit(status); 13 | } 14 | }) 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /source/console/rhino.js: -------------------------------------------------------------------------------- 1 | Console.extend({ 2 | Rhino: new JS.Class(Console.Base, { 3 | backtraceFilter: function() { 4 | return new RegExp(java.lang.System.getProperty('user.dir') + '/', 'g'); 5 | }, 6 | 7 | envvar: function(name) { 8 | var env = java.lang.System.getenv(); 9 | return env.get(name) || null; 10 | }, 11 | 12 | getDimensions: function() { 13 | var proc = java.lang.Runtime.getRuntime().exec(['sh', '-c', 'stty -a < /dev/tty']), 14 | is = proc.getInputStream(), 15 | bite = 0, 16 | out = '', 17 | width, height; 18 | 19 | while (bite >= 0) { 20 | bite = is.read(); 21 | if (bite >= 0) out += String.fromCharCode(bite); 22 | } 23 | 24 | var match = out.match(/rows\s+(\d+);\s+columns\s+(\d+)/); 25 | if (!match) return this._dimCache || this.callSuper(); 26 | 27 | return this._dimCache = [parseInt(match[2], 10), parseInt(match[1], 10)]; 28 | }, 29 | 30 | print: function(string) { 31 | java.lang.System.out.print(this.flushFormat() + string); 32 | }, 33 | 34 | puts: function(string) { 35 | java.lang.System.out.println(this.flushFormat() + string); 36 | } 37 | }) 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /source/console/windows.js: -------------------------------------------------------------------------------- 1 | Console.extend({ 2 | Windows: new JS.Class(Console.Base, { 3 | coloring: function() { 4 | return false; 5 | }, 6 | 7 | echo: function(string) { 8 | WScript.Echo(string); 9 | }, 10 | 11 | exit: function(status) { 12 | WScript.Quit(status); 13 | } 14 | }) 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /source/constant_scope.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS; 4 | 5 | if (E) exports.JS = exports; 6 | factory(js, E ? exports : js); 7 | 8 | })(function(JS, exports) { 9 | 'use strict'; 10 | 11 | var ConstantScope = new JS.Module('ConstantScope', { 12 | extend: { 13 | included: function(base) { 14 | base.__consts__ = new JS.Module(); 15 | base.extend(this.ClassMethods); 16 | base.__eigen__().extend(this.ClassMethods); 17 | 18 | base.include(base.__consts__); 19 | base.extend(base.__consts__); 20 | 21 | base.include(this.extract(base.__fns__)); 22 | base.extend(this.extract(base.__eigen__().__fns__)); 23 | }, 24 | 25 | ClassMethods: new JS.Module({ 26 | define: function(name, callable) { 27 | var constants = this.__consts__ || this.__tgt__.__consts__; 28 | 29 | if (/^[A-Z]/.test(name)) 30 | constants.define(name, callable); 31 | else 32 | this.callSuper(); 33 | 34 | if (JS.isType(callable, JS.Module)) { 35 | callable.include(ConstantScope); 36 | callable.__consts__.include(constants); 37 | } 38 | } 39 | }), 40 | 41 | extract: function(methods, base) { 42 | var constants = {}, key, object; 43 | for (key in methods) { 44 | if (!/^[A-Z]/.test(key)) continue; 45 | 46 | object = methods[key]; 47 | constants[key] = object; 48 | delete methods[key]; 49 | } 50 | return constants; 51 | } 52 | } 53 | }); 54 | 55 | exports.ConstantScope = ConstantScope; 56 | }); 57 | 58 | -------------------------------------------------------------------------------- /source/core/_head.js: -------------------------------------------------------------------------------- 1 | var JS = (typeof this.JS === 'undefined') ? {} : this.JS; 2 | 3 | (function(factory) { 4 | var $ = (typeof this.global === 'object') ? this.global : this, 5 | E = (typeof exports === 'object'); 6 | 7 | if (E) { 8 | exports.JS = exports; 9 | JS = exports; 10 | } else { 11 | $.JS = JS; 12 | } 13 | factory($, JS); 14 | 15 | })(function(global, exports) { 16 | 'use strict'; 17 | 18 | -------------------------------------------------------------------------------- /source/core/_tail.js: -------------------------------------------------------------------------------- 1 | JS.extend(exports, JS); 2 | if (global.JS) JS.extend(global.JS, JS); 3 | }); 4 | 5 | -------------------------------------------------------------------------------- /source/core/bootstrap.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var methodsFromPrototype = function(klass) { 3 | var methods = {}, 4 | proto = klass.prototype; 5 | 6 | for (var field in proto) { 7 | if (!proto.hasOwnProperty(field)) continue; 8 | methods[field] = JS.Method.create(klass, field, proto[field]); 9 | } 10 | return methods; 11 | }; 12 | 13 | var classify = function(name, parentName) { 14 | var klass = JS[name], 15 | parent = JS[parentName]; 16 | 17 | klass.__inc__ = []; 18 | klass.__dep__ = []; 19 | klass.__fns__ = methodsFromPrototype(klass); 20 | klass.__tgt__ = klass.prototype; 21 | 22 | klass.prototype.constructor = 23 | klass.prototype.klass = klass; 24 | 25 | JS.extend(klass, JS.Class.prototype); 26 | klass.include(parent || JS.Kernel); 27 | klass.setName(name); 28 | 29 | klass.constructor = klass.klass = JS.Class; 30 | }; 31 | 32 | classify('Method'); 33 | classify('Module'); 34 | classify('Class', 'Module'); 35 | 36 | var eigen = JS.Kernel.instanceMethod('__eigen__'); 37 | 38 | eigen.call(JS.Method).resolve(); 39 | eigen.call(JS.Module).resolve(); 40 | eigen.call(JS.Class).include(JS.Module.__meta__); 41 | })(); 42 | 43 | JS.NotImplementedError = new JS.Class('NotImplementedError', Error); 44 | 45 | -------------------------------------------------------------------------------- /source/core/class.js: -------------------------------------------------------------------------------- 1 | JS.Class = JS.makeClass(JS.Module); 2 | 3 | JS.extend(JS.Class.prototype, { 4 | initialize: function(name, parent, methods, options) { 5 | if (typeof name !== 'string') { 6 | options = arguments[2]; 7 | methods = arguments[1]; 8 | parent = arguments[0]; 9 | name = undefined; 10 | } 11 | if (typeof parent !== 'function') { 12 | options = methods; 13 | methods = parent; 14 | parent = Object; 15 | } 16 | JS.Module.prototype.initialize.call(this, name); 17 | options = options || {}; 18 | 19 | var klass = JS.makeClass(parent); 20 | JS.extend(klass, this); 21 | 22 | klass.prototype.constructor = 23 | klass.prototype.klass = klass; 24 | 25 | klass.__eigen__().include(parent.__meta__, {_resolve: options._resolve}); 26 | klass.setName(name); 27 | 28 | klass.__tgt__ = klass.prototype; 29 | 30 | var parentModule = (parent === Object) 31 | ? {} 32 | : (parent.__fns__ ? parent : new JS.Module(parent.prototype, {_resolve: false})); 33 | 34 | klass.include(JS.Kernel, {_resolve: false}) 35 | .include(parentModule, {_resolve: false}) 36 | .include(methods, {_resolve: false}); 37 | 38 | if (options._resolve !== false) klass.resolve(); 39 | 40 | if (typeof parent.inherited === 'function') 41 | parent.inherited(klass); 42 | 43 | return klass; 44 | } 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /source/core/interface.js: -------------------------------------------------------------------------------- 1 | JS.Interface = new JS.Class('Interface', { 2 | initialize: function(methods) { 3 | this.test = function(object, returnName) { 4 | var n = methods.length; 5 | while (n--) { 6 | if (typeof object[methods[n]] !== 'function') 7 | return returnName ? methods[n] : false; 8 | } 9 | return true; 10 | }; 11 | }, 12 | 13 | extend: { 14 | ensure: function() { 15 | var args = JS.array(arguments), object = args.shift(), face, result; 16 | while (face = args.shift()) { 17 | result = face.test(object, true); 18 | if (result !== true) throw new Error('object does not implement ' + result + '()'); 19 | } 20 | } 21 | } 22 | }); 23 | 24 | -------------------------------------------------------------------------------- /source/core/kernel.js: -------------------------------------------------------------------------------- 1 | JS.Kernel = new JS.Module('Kernel', { 2 | __eigen__: function() { 3 | if (this.__meta__) return this.__meta__; 4 | var name = this.toString() + '.'; 5 | this.__meta__ = new JS.Module(name, null, {_target: this}); 6 | return this.__meta__.include(this.klass, {_resolve: false}); 7 | }, 8 | 9 | equals: function(other) { 10 | return this === other; 11 | }, 12 | 13 | extend: function(module, options) { 14 | var resolve = (options || {})._resolve; 15 | this.__eigen__().include(module, {_extended: this, _resolve: resolve}); 16 | return this; 17 | }, 18 | 19 | hash: function() { 20 | return JS.Kernel.hashFor(this); 21 | }, 22 | 23 | isA: function(module) { 24 | return (typeof module === 'function' && this instanceof module) || 25 | this.__eigen__().includes(module); 26 | }, 27 | 28 | method: function(name) { 29 | var cache = this.__mct__ = this.__mct__ || {}, 30 | value = cache[name], 31 | field = this[name]; 32 | 33 | if (typeof field !== 'function') return field; 34 | if (value && field === value._value) return value._bound; 35 | 36 | var bound = JS.bind(field, this); 37 | cache[name] = {_value: field, _bound: bound}; 38 | return bound; 39 | }, 40 | 41 | methods: function() { 42 | return this.__eigen__().instanceMethods(); 43 | }, 44 | 45 | tap: function(block, context) { 46 | block.call(context, this); 47 | return this; 48 | }, 49 | 50 | toString: function() { 51 | if (this.displayName) return this.displayName; 52 | var name = this.klass.displayName || this.klass.toString(); 53 | return '#<' + name + ':' + this.hash() + '>'; 54 | } 55 | }); 56 | 57 | (function() { 58 | var id = 1; 59 | 60 | JS.Kernel.hashFor = function(object) { 61 | if (object.__hash__ !== undefined) return object.__hash__; 62 | object.__hash__ = (new JS.Date().getTime() + id).toString(16); 63 | id += 1; 64 | return object.__hash__; 65 | }; 66 | })(); 67 | 68 | -------------------------------------------------------------------------------- /source/core/keywords.js: -------------------------------------------------------------------------------- 1 | JS.Method.keyword('callSuper', function(method, env, receiver, args) { 2 | var methods = env.lookup(method.name), 3 | stackIndex = methods.length - 1, 4 | params = JS.array(args); 5 | 6 | if (stackIndex === 0) return undefined; 7 | 8 | var _super = function() { 9 | var i = arguments.length; 10 | while (i--) params[i] = arguments[i]; 11 | 12 | stackIndex -= 1; 13 | if (stackIndex === 0) delete receiver.callSuper; 14 | var returnValue = methods[stackIndex].apply(receiver, params); 15 | receiver.callSuper = _super; 16 | stackIndex += 1; 17 | 18 | return returnValue; 19 | }; 20 | 21 | return _super; 22 | }); 23 | 24 | JS.Method.keyword('blockGiven', function(method, env, receiver, args) { 25 | var block = Array.prototype.slice.call(args, method.arity), 26 | hasBlock = (typeof block[0] === 'function'); 27 | 28 | return function() { return hasBlock }; 29 | }); 30 | 31 | JS.Method.keyword('yieldWith', function(method, env, receiver, args) { 32 | var block = Array.prototype.slice.call(args, method.arity); 33 | 34 | return function() { 35 | if (typeof block[0] !== 'function') return; 36 | return block[0].apply(block[1] || null, arguments); 37 | }; 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /source/core/singleton.js: -------------------------------------------------------------------------------- 1 | JS.Singleton = new JS.Class('Singleton', { 2 | initialize: function(name, parent, methods) { 3 | return new (new JS.Class(name, parent, methods)); 4 | } 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /source/core/utils.js: -------------------------------------------------------------------------------- 1 | var JS = {ENV: global}; 2 | 3 | JS.END_WITHOUT_DOT = /([^\.])$/; 4 | 5 | JS.array = function(enumerable) { 6 | var array = [], i = enumerable.length; 7 | while (i--) array[i] = enumerable[i]; 8 | return array; 9 | }; 10 | 11 | JS.bind = function(method, object) { 12 | return function() { 13 | return method.apply(object, arguments); 14 | }; 15 | }; 16 | 17 | JS.Date = JS.ENV.Date; 18 | 19 | JS.extend = function(destination, source, overwrite) { 20 | if (!destination || !source) return destination; 21 | for (var field in source) { 22 | if (destination[field] === source[field]) continue; 23 | if (overwrite === false && destination.hasOwnProperty(field)) continue; 24 | destination[field] = source[field]; 25 | } 26 | return destination; 27 | }; 28 | 29 | JS.indexOf = function(list, item) { 30 | if (list.indexOf) return list.indexOf(item); 31 | var i = list.length; 32 | while (i--) { 33 | if (list[i] === item) return i; 34 | } 35 | return -1; 36 | }; 37 | 38 | JS.isType = function(object, type) { 39 | if (typeof type === 'string') 40 | return typeof object === type; 41 | 42 | if (object === null || object === undefined) 43 | return false; 44 | 45 | return (typeof type === 'function' && object instanceof type) || 46 | (object.isA && object.isA(type)) || 47 | object.constructor === type; 48 | }; 49 | 50 | JS.makeBridge = function(parent) { 51 | var bridge = function() {}; 52 | bridge.prototype = parent.prototype; 53 | return new bridge(); 54 | }; 55 | 56 | JS.makeClass = function(parent) { 57 | parent = parent || Object; 58 | 59 | var constructor = function() { 60 | return this.initialize 61 | ? this.initialize.apply(this, arguments) || this 62 | : this; 63 | }; 64 | constructor.prototype = JS.makeBridge(parent); 65 | 66 | constructor.superclass = parent; 67 | 68 | constructor.subclasses = []; 69 | if (parent.subclasses) parent.subclasses.push(constructor); 70 | 71 | return constructor; 72 | }; 73 | 74 | JS.match = function(category, object) { 75 | if (object === undefined) return false; 76 | return typeof category.test === 'function' 77 | ? category.test(object) 78 | : category.match(object); 79 | }; 80 | 81 | -------------------------------------------------------------------------------- /source/decorator.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS; 4 | 5 | if (E) exports.JS = exports; 6 | factory(js, E ? exports : js); 7 | 8 | })(function(JS, exports) { 9 | 'use strict'; 10 | 11 | var Decorator = new JS.Class('Decorator', { 12 | initialize: function(decoree, methods) { 13 | var decorator = new JS.Class(), 14 | delegators = {}, 15 | method, func; 16 | 17 | for (method in decoree.prototype) { 18 | func = decoree.prototype[method]; 19 | if (typeof func === 'function' && func !== decoree) func = this.klass.delegate(method); 20 | delegators[method] = func; 21 | } 22 | 23 | decorator.include(new JS.Module(delegators), {_resolve: false}); 24 | decorator.include(this.klass.InstanceMethods, {_resolve: false}); 25 | decorator.include(methods); 26 | return decorator; 27 | }, 28 | 29 | extend: { 30 | delegate: function(name) { 31 | return function() { 32 | return this.component[name].apply(this.component, arguments); 33 | }; 34 | }, 35 | 36 | InstanceMethods: new JS.Module({ 37 | initialize: function(component) { 38 | this.component = component; 39 | this.klass = this.constructor = component.klass; 40 | var method, func; 41 | for (method in component) { 42 | if (this[method]) continue; 43 | func = component[method]; 44 | if (typeof func === 'function') func = Decorator.delegate(method); 45 | this[method] = func; 46 | } 47 | }, 48 | 49 | extend: function(source) { 50 | this.component.extend(source); 51 | var method, func; 52 | for (method in source) { 53 | func = source[method]; 54 | if (typeof func === 'function') func = Decorator.delegate(method); 55 | this[method] = func; 56 | } 57 | } 58 | }) 59 | } 60 | }); 61 | 62 | exports.Decorator = Decorator; 63 | }); 64 | 65 | -------------------------------------------------------------------------------- /source/deferrable.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS; 4 | 5 | if (E) exports.JS = exports; 6 | factory(js, E ? exports : js); 7 | 8 | })(function(JS, exports) { 9 | 'use strict'; 10 | 11 | var Deferrable = new JS.Module('Deferrable', { 12 | extend: { 13 | Timeout: new JS.Class(Error) 14 | }, 15 | 16 | callback: function(block, context) { 17 | if (this.__deferredStatus__ === 'success') 18 | return block.apply(context, this.__deferredValue__); 19 | 20 | if (this.__deferredStatus__ === 'failure') 21 | return; 22 | 23 | this.__callbacks__ = this.__callbacks__ || []; 24 | this.__callbacks__.push([block, context]); 25 | }, 26 | 27 | errback: function(block, context) { 28 | if (this.__deferredStatus__ === 'failure') 29 | return block.apply(context, this.__deferredValue__); 30 | 31 | if (this.__deferredStatus__ === 'success') 32 | return; 33 | 34 | this.__errbacks__ = this.__errbacks__ || []; 35 | this.__errbacks__.push([block, context]); 36 | }, 37 | 38 | timeout: function(milliseconds) { 39 | this.cancelTimeout(); 40 | var self = this, error = new Deferrable.Timeout(); 41 | this.__timeout__ = JS.ENV.setTimeout(function() { self.fail(error) }, milliseconds); 42 | }, 43 | 44 | cancelTimeout: function() { 45 | if (!this.__timeout__) return; 46 | JS.ENV.clearTimeout(this.__timeout__); 47 | delete this.__timeout__; 48 | }, 49 | 50 | setDeferredStatus: function(status, args) { 51 | this.__deferredStatus__ = status; 52 | this.__deferredValue__ = args; 53 | 54 | this.cancelTimeout(); 55 | 56 | switch (status) { 57 | case 'success': 58 | if (!this.__callbacks__) return; 59 | var callback; 60 | while (callback = this.__callbacks__.pop()) 61 | callback[0].apply(callback[1], args); 62 | break; 63 | 64 | case 'failure': 65 | if (!this.__errbacks__) return; 66 | var errback; 67 | while (errback = this.__errbacks__.pop()) 68 | errback[0].apply(errback[1], args); 69 | break; 70 | } 71 | }, 72 | 73 | succeed: function() { 74 | return this.setDeferredStatus('success', arguments); 75 | }, 76 | 77 | fail: function() { 78 | return this.setDeferredStatus('failure', arguments); 79 | } 80 | }); 81 | 82 | exports.Deferrable = Deferrable; 83 | }); 84 | 85 | -------------------------------------------------------------------------------- /source/dom/_head.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS; 4 | 5 | if (E) exports.JS = exports; 6 | factory(js, E ? exports : js); 7 | 8 | })(function(JS, exports) { 9 | 'use strict'; 10 | 11 | -------------------------------------------------------------------------------- /source/dom/_tail.js: -------------------------------------------------------------------------------- 1 | exports.DOM = DOM; 2 | }); 3 | 4 | -------------------------------------------------------------------------------- /source/dom/builder.js: -------------------------------------------------------------------------------- 1 | DOM.Builder = new JS.Class('DOM.Builder', { 2 | extend: { 3 | addElement: function(name) { 4 | this.define(name, function() { 5 | return this.makeElement(name, arguments); 6 | }); 7 | DOM[name] = function() { 8 | return new DOM.Builder().makeElement(name, arguments); 9 | }; 10 | }, 11 | 12 | addElements: function(list) { 13 | var i = list.length; 14 | while (i--) this.addElement(list[i]); 15 | } 16 | }, 17 | 18 | initialize: function(parent) { 19 | this._parentNode = parent; 20 | }, 21 | 22 | makeElement: function(name, children) { 23 | var element, child, attribute; 24 | if ( document.createElementNS ) { 25 | // That makes possible to mix HTML within SVG or XUL. 26 | element = document.createElementNS('http://www.w3.org/1999/xhtml', name); 27 | } else { 28 | element = document.createElement(name); 29 | } 30 | for (var i = 0, n = children.length; i < n; i++) { 31 | child = children[i]; 32 | if (typeof child === 'function') { 33 | child(new this.klass(element)); 34 | } else if (JS.isType(child, 'string')) { 35 | element.appendChild(document.createTextNode(child)); 36 | } else { 37 | for (attribute in child) 38 | element[attribute] = child[attribute]; 39 | } 40 | } 41 | if (this._parentNode) this._parentNode.appendChild(element); 42 | return element; 43 | }, 44 | 45 | concat: function(text) { 46 | if (!this._parentNode) return; 47 | this._parentNode.appendChild(document.createTextNode(text)); 48 | } 49 | }); 50 | 51 | DOM.Builder.addElements([ 52 | 'a', 'abbr', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 53 | 'base', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 54 | 'cite', 'code', 'col', 'colgroup', 'command', 'datalist', 'dd', 'del', 55 | 'details', 'device', 'dfn', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 56 | 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 57 | 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 58 | 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'map', 'mark', 59 | 'marquee', 'menu', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 60 | 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 61 | 'rt', 'ruby', 'samp', 'script', 'section', 'select', 'small', 'source', 62 | 'span', 'strong', 'style', 'sub', 'sup', 'summary', 'table', 'tbody', 'td', 63 | 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'ul', 64 | 'var', 'video', 'wbr' 65 | ]); 66 | 67 | -------------------------------------------------------------------------------- /source/dom/dom.js: -------------------------------------------------------------------------------- 1 | var DOM = { 2 | ELEMENT_NODE: 1, 3 | ATTRIBUTE_NODE: 2, 4 | TEXT_NODE: 3, 5 | CDATA_SECTION_NODE: 4, 6 | ENTITY_REFERENCE_NODE: 5, 7 | ENTITY_NODE: 6, 8 | PROCESSING_INSTRUCTION_NODE: 7, 9 | COMMENT_NODE: 8, 10 | DOCUMENT_NODE: 9, 11 | DOCUMENT_TYPE_NODE: 10, 12 | DOCUMENT_FRAGMENT_NODE: 11, 13 | NOTATION_NODE: 12, 14 | 15 | ENV: this, 16 | 17 | toggleClass: function(node, className) { 18 | if (this.hasClass(node, className)) this.removeClass(node, className); 19 | else this.addClass(node, className); 20 | }, 21 | 22 | hasClass: function(node, className) { 23 | var classes = node.className.split(/\s+/); 24 | return JS.indexOf(classes, className) >= 0; 25 | }, 26 | 27 | addClass: function(node, className) { 28 | if (this.hasClass(node, className)) return; 29 | node.className = node.className + ' ' + className; 30 | }, 31 | 32 | removeClass: function(node, className) { 33 | var pattern = new RegExp('\\b' + className + '\\b\\s*', 'g'); 34 | node.className = node.className.replace(pattern, ''); 35 | } 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /source/dom/event.js: -------------------------------------------------------------------------------- 1 | DOM.Event = { 2 | _registry: [], 3 | 4 | on: function(element, eventName, callback, context) { 5 | if (element === undefined) return; 6 | 7 | if (element !== DOM.ENV && 8 | element.nodeType !== DOM.ELEMENT_NODE && 9 | element.nodeType !== DOM.DOCUMENT_NODE) 10 | return; 11 | 12 | var wrapped = function() { callback.call(context, element) }; 13 | 14 | if (element.addEventListener) 15 | element.addEventListener(eventName, wrapped, false); 16 | else if (element.attachEvent) 17 | element.attachEvent('on' + eventName, wrapped); 18 | 19 | this._registry.push({ 20 | _element: element, 21 | _type: eventName, 22 | _callback: callback, 23 | _context: context, 24 | _handler: wrapped 25 | }); 26 | }, 27 | 28 | detach: function(element, eventName, callback, context) { 29 | var i = this._registry.length, register; 30 | while (i--) { 31 | register = this._registry[i]; 32 | 33 | if ((element && element !== register._element) || 34 | (eventName && eventName !== register._type) || 35 | (callback && callback !== register._callback) || 36 | (context && context !== register._context)) 37 | continue; 38 | 39 | if (register._element.removeEventListener) 40 | register._element.removeEventListener(register._type, register._handler, false); 41 | else if (register._element.detachEvent) 42 | register._element.detachEvent('on' + register._type, register._handler); 43 | 44 | this._registry.splice(i,1); 45 | register = null; 46 | } 47 | } 48 | }; 49 | 50 | DOM.Event.on(DOM.ENV, 'unload', DOM.Event.detach, DOM.Event); 51 | 52 | -------------------------------------------------------------------------------- /source/forwardable.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS; 4 | 5 | if (E) exports.JS = exports; 6 | factory(js, E ? exports : js); 7 | 8 | })(function(JS, exports) { 9 | 'use strict'; 10 | 11 | var Forwardable = new JS.Module('Forwardable', { 12 | defineDelegator: function(subject, method, alias, resolve) { 13 | alias = alias || method; 14 | this.define(alias, function() { 15 | var object = this[subject], 16 | property = object[method]; 17 | 18 | return (typeof property === 'function') 19 | ? property.apply(object, arguments) 20 | : property; 21 | }, {_resolve: resolve !== false}); 22 | }, 23 | 24 | defineDelegators: function() { 25 | var methods = JS.array(arguments), 26 | subject = methods.shift(), 27 | i = methods.length; 28 | 29 | while (i--) this.defineDelegator(subject, methods[i], methods[i], false); 30 | this.resolve(); 31 | } 32 | }); 33 | 34 | exports.Forwardable = Forwardable; 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /source/observable.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS; 4 | 5 | if (E) exports.JS = exports; 6 | factory(js, E ? exports : js); 7 | 8 | })(function(JS, exports) { 9 | 'use strict'; 10 | 11 | var Observable = new JS.Module('Observable', { 12 | extend: { 13 | DEFAULT_METHOD: 'update' 14 | }, 15 | 16 | addObserver: function(observer, context) { 17 | (this.__observers__ = this.__observers__ || []).push({_block: observer, _context: context}); 18 | }, 19 | 20 | removeObserver: function(observer, context) { 21 | this.__observers__ = this.__observers__ || []; 22 | context = context; 23 | var i = this.countObservers(); 24 | while (i--) { 25 | if (this.__observers__[i]._block === observer && this.__observers__[i]._context === context) { 26 | this.__observers__.splice(i,1); 27 | return; 28 | } 29 | } 30 | }, 31 | 32 | removeObservers: function() { 33 | this.__observers__ = []; 34 | }, 35 | 36 | countObservers: function() { 37 | return (this.__observers__ = this.__observers__ || []).length; 38 | }, 39 | 40 | notifyObservers: function() { 41 | if (!this.isChanged()) return; 42 | var i = this.countObservers(), observer, block, context; 43 | while (i--) { 44 | observer = this.__observers__[i]; 45 | block = observer._block; 46 | context = observer._context; 47 | if (typeof block === 'function') block.apply(context, arguments); 48 | else block[context || Observable.DEFAULT_METHOD].apply(block, arguments); 49 | } 50 | }, 51 | 52 | setChanged: function(state) { 53 | this.__changed__ = !(state === false); 54 | }, 55 | 56 | isChanged: function() { 57 | if (this.__changed__ === undefined) this.__changed__ = true; 58 | return !!this.__changed__; 59 | } 60 | }); 61 | 62 | Observable.alias({ 63 | subscribe: 'addObserver', 64 | unsubscribe: 'removeObserver' 65 | }, true); 66 | 67 | exports.Observable = Observable; 68 | }); 69 | 70 | -------------------------------------------------------------------------------- /source/package/_head.js: -------------------------------------------------------------------------------- 1 | var JS = (typeof this.JS === 'undefined') ? {} : this.JS; 2 | JS.Date = Date; 3 | 4 | (function(factory) { 5 | var $ = (typeof this.global === 'object') ? this.global : this, 6 | E = (typeof exports === 'object'); 7 | 8 | if (E) { 9 | exports.JS = exports; 10 | JS = exports; 11 | } else { 12 | $.JS = JS; 13 | } 14 | factory($, JS); 15 | 16 | })(function(global, exports) { 17 | 'use strict'; 18 | 19 | -------------------------------------------------------------------------------- /source/package/_tail.js: -------------------------------------------------------------------------------- 1 | exports.Package = Package; 2 | exports.Packages = exports.packages = packages; 3 | exports.DSL = DSL; 4 | }); 5 | 6 | -------------------------------------------------------------------------------- /source/package/browser.js: -------------------------------------------------------------------------------- 1 | Package.loader = Package.BrowserLoader; 2 | 3 | -------------------------------------------------------------------------------- /source/package/dsl.js: -------------------------------------------------------------------------------- 1 | var DSL = { 2 | __FILE__: function() { 3 | return Package.loader.__FILE__(); 4 | }, 5 | 6 | pkg: function(name, path) { 7 | var pkg = path 8 | ? Package._getByPath(path) 9 | : Package._getByName(name); 10 | pkg.provides(name); 11 | return pkg; 12 | }, 13 | 14 | file: function(filename) { 15 | var files = [], i = arguments.length; 16 | while (i--) files[i] = resolve(arguments[i]); 17 | return Package._getByPath.apply(Package, files); 18 | }, 19 | 20 | load: function(path, fireCallbacks) { 21 | Package.loader.loadFile(path, fireCallbacks); 22 | }, 23 | 24 | autoload: function(pattern, options) { 25 | Package._autoload(pattern, options); 26 | } 27 | }; 28 | 29 | DSL.files = DSL.file; 30 | DSL.loader = DSL.file; 31 | 32 | var packages = function(declaration) { 33 | declaration.call(DSL); 34 | }; 35 | 36 | var parseLoadArgs = function(args) { 37 | var files = [], i = 0; 38 | 39 | while (typeof args[i] === 'string'){ 40 | files.push(args[i]); 41 | i += 1; 42 | } 43 | 44 | return {files: files, callback: args[i], context: args[i+1]}; 45 | }; 46 | 47 | exports.load = function(path, callback) { 48 | var args = parseLoadArgs(arguments), 49 | n = args.files.length; 50 | 51 | var loadNext = function(index) { 52 | if (index === n) return args.callback.call(args.context); 53 | Package.loader.loadFile(args.files[index], function() { 54 | loadNext(index + 1); 55 | }); 56 | }; 57 | loadNext(0); 58 | }; 59 | 60 | exports.require = function() { 61 | var args = parseLoadArgs(arguments); 62 | 63 | Package.when({complete: args.files}, function(objects) { 64 | if (!args.callback) return; 65 | args.callback.apply(args.context, objects && objects.complete); 66 | }); 67 | 68 | return this; 69 | }; 70 | 71 | -------------------------------------------------------------------------------- /source/package/loader.js: -------------------------------------------------------------------------------- 1 | var candidates = [ Package.XULRunnerLoader, 2 | Package.RhinoLoader, 3 | Package.BrowserLoader, 4 | Package.CommonJSLoader, 5 | Package.ServerLoader, 6 | Package.WshLoader ], 7 | 8 | n = candidates.length, 9 | i, candidate; 10 | 11 | for (i = 0; i < n; i++) { 12 | candidate = candidates[i]; 13 | if (candidate.usable()) { 14 | Package.loader = candidate; 15 | if (candidate.setup) candidate.setup(); 16 | break; 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /source/package/loaders/browser.js: -------------------------------------------------------------------------------- 1 | Package.BrowserLoader = { 2 | HOST_REGEX: /^(https?\:)?\/\/[^\/]+/i, 3 | 4 | usable: function() { 5 | return !!Package._getObject('window.document.getElementsByTagName') && 6 | typeof phantom === 'undefined'; 7 | }, 8 | 9 | __FILE__: function() { 10 | var scripts = document.getElementsByTagName('script'), 11 | src = scripts[scripts.length - 1].src, 12 | url = window.location.href; 13 | 14 | if (/^\w+\:\/+/.test(src)) return src; 15 | if (/^\//.test(src)) return window.location.origin + src; 16 | return url.replace(/[^\/]*$/g, '') + src; 17 | }, 18 | 19 | cacheBust: function(path) { 20 | if (exports.cache !== false) return path; 21 | var token = new JS.Date().getTime(); 22 | return path + (/\?/.test(path) ? '&' : '?') + token; 23 | }, 24 | 25 | fetch: function(path) { 26 | var originalPath = path; 27 | path = this.cacheBust(path); 28 | 29 | this.HOST = this.HOST || this.HOST_REGEX.exec(window.location.href); 30 | var host = this.HOST_REGEX.exec(path); 31 | 32 | if (!this.HOST || (host && host[0] !== this.HOST[0])) return null; 33 | Package.log('[FETCH] ' + path); 34 | 35 | var source = new Package.Deferred(), 36 | self = this, 37 | xhr = window.ActiveXObject 38 | ? new ActiveXObject('Microsoft.XMLHTTP') 39 | : new XMLHttpRequest(); 40 | 41 | xhr.open('GET', path, true); 42 | xhr.onreadystatechange = function() { 43 | if (xhr.readyState !== 4) return; 44 | xhr.onreadystatechange = self._K; 45 | source.succeed(xhr.responseText + '\n//@ sourceURL=' + originalPath); 46 | xhr = null; 47 | }; 48 | xhr.send(null); 49 | return source; 50 | }, 51 | 52 | loadFile: function(path, fireCallbacks, source) { 53 | if (!source) path = this.cacheBust(path); 54 | 55 | var self = this, 56 | head = document.getElementsByTagName('head')[0], 57 | script = document.createElement('script'); 58 | 59 | script.type = 'text/javascript'; 60 | 61 | if (source) 62 | return source.callback(function(code) { 63 | Package.log('[EXEC] ' + path); 64 | var execute = new Function('code', 'eval(code)'); 65 | execute(code); 66 | fireCallbacks(); 67 | }); 68 | 69 | Package.log('[LOAD] ' + path); 70 | script.src = path; 71 | 72 | script.onload = script.onreadystatechange = function() { 73 | var state = script.readyState, status = script.status; 74 | if ( !state || state === 'loaded' || state === 'complete' || 75 | (state === 4 && status === 200) ) { 76 | fireCallbacks(); 77 | script.onload = script.onreadystatechange = self._K; 78 | head = null; 79 | script = null; 80 | } 81 | }; 82 | head.appendChild(script); 83 | }, 84 | 85 | loadStyle: function(path) { 86 | var link = document.createElement('link'); 87 | link.rel = 'stylesheet'; 88 | link.type = 'text/css'; 89 | link.href = this.cacheBust(path); 90 | 91 | document.getElementsByTagName('head')[0].appendChild(link); 92 | }, 93 | 94 | _K: function() {} 95 | }; 96 | 97 | -------------------------------------------------------------------------------- /source/package/loaders/commonjs.js: -------------------------------------------------------------------------------- 1 | Package.CommonJSLoader = { 2 | usable: function() { 3 | return typeof require === 'function' && 4 | typeof exports === 'object'; 5 | }, 6 | 7 | __FILE__: function() { 8 | return this._currentPath; 9 | }, 10 | 11 | loadFile: function(path, fireCallbacks) { 12 | var file, module; 13 | 14 | if (typeof process !== 'undefined') { 15 | module = path.replace(/\.[^\.]+$/g, ''); 16 | file = require('path').resolve(module); 17 | } 18 | else if (typeof phantom !== 'undefined') { 19 | file = phantom.libraryPath.replace(/\/$/, '') + '/' + 20 | path.replace(/^\//, ''); 21 | } 22 | 23 | this._currentPath = file + '.js'; 24 | var module = require(file); 25 | fireCallbacks(module); 26 | 27 | return module; 28 | } 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /source/package/loaders/rhino.js: -------------------------------------------------------------------------------- 1 | Package.RhinoLoader = { 2 | usable: function() { 3 | return typeof java === 'object' && 4 | typeof require === 'function'; 5 | }, 6 | 7 | __FILE__: function() { 8 | return this._currentPath; 9 | }, 10 | 11 | loadFile: function(path, fireCallbacks) { 12 | var cwd = java.lang.System.getProperty('user.dir'), 13 | module = path.replace(/\.[^\.]+$/g, ''); 14 | 15 | var requirePath = new java.io.File(cwd, module).toString(); 16 | this._currentPath = requirePath + '.js'; 17 | var module = require(requirePath); 18 | fireCallbacks(module); 19 | 20 | return module; 21 | } 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /source/package/loaders/server.js: -------------------------------------------------------------------------------- 1 | Package.ServerLoader = { 2 | usable: function() { 3 | return typeof Package._getObject('load') === 'function' && 4 | typeof Package._getObject('version') === 'function'; 5 | }, 6 | 7 | __FILE__: function() { 8 | return this._currentPath; 9 | }, 10 | 11 | loadFile: function(path, fireCallbacks) { 12 | this._currentPath = path; 13 | load(path); 14 | fireCallbacks(); 15 | } 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /source/package/loaders/wsh.js: -------------------------------------------------------------------------------- 1 | Package.WshLoader = { 2 | usable: function() { 3 | return !!Package._getObject('ActiveXObject') && 4 | !!Package._getObject('WScript'); 5 | }, 6 | 7 | __FILE__: function() { 8 | return this._currentPath; 9 | }, 10 | 11 | loadFile: function(path, fireCallbacks) { 12 | this._currentPath = path; 13 | var fso = new ActiveXObject('Scripting.FileSystemObject'), file, runner; 14 | try { 15 | file = fso.OpenTextFile(path); 16 | runner = function() { eval(file.ReadAll()) }; 17 | runner(); 18 | fireCallbacks(); 19 | } finally { 20 | try { if (file) file.Close() } catch (e) {} 21 | } 22 | } 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /source/package/loaders/xulrunner.js: -------------------------------------------------------------------------------- 1 | Package.XULRunnerLoader = { 2 | jsloader: '@mozilla.org/moz/jssubscript-loader;1', 3 | cssservice: '@mozilla.org/content/style-sheet-service;1', 4 | ioservice: '@mozilla.org/network/io-service;1', 5 | 6 | usable: function() { 7 | try { 8 | var CC = (Components || {}).classes; 9 | return !!(CC && CC[this.jsloader] && CC[this.jsloader].getService); 10 | } catch(e) { 11 | return false; 12 | } 13 | }, 14 | 15 | setup: function() { 16 | var Cc = Components.classes, Ci = Components.interfaces; 17 | this.ssl = Cc[this.jsloader].getService(Ci.mozIJSSubScriptLoader); 18 | this.sss = Cc[this.cssservice].getService(Ci.nsIStyleSheetService); 19 | this.ios = Cc[this.ioservice].getService(Ci.nsIIOService); 20 | }, 21 | 22 | loadFile: function(path, fireCallbacks) { 23 | Package.log('[LOAD] ' + path); 24 | 25 | this.ssl.loadSubScript(path); 26 | fireCallbacks(); 27 | }, 28 | 29 | loadStyle: function(path) { 30 | var uri = this.ios.newURI(path, null, null); 31 | this.sss.loadAndRegisterSheet(uri, this.sss.USER_SHEET); 32 | } 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /source/proxy.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS; 4 | 5 | if (E) exports.JS = exports; 6 | factory(js, E ? exports : js); 7 | 8 | })(function(JS, exports) { 9 | 'use strict'; 10 | 11 | var Proxy = new JS.Module('Proxy', { 12 | extend: { 13 | Virtual: new JS.Class({ 14 | initialize: function(klass) { 15 | var bridge = function() {}, 16 | proxy = new JS.Class(), 17 | delegators = {}, 18 | method, func; 19 | 20 | bridge.prototype = klass.prototype; 21 | 22 | for (method in klass.prototype) { 23 | func = klass.prototype[method]; 24 | if (typeof func === 'function' && func !== klass) func = this.klass.forward(method); 25 | delegators[method] = func; 26 | } 27 | 28 | proxy.include({ 29 | initialize: function() { 30 | var args = arguments, 31 | subject = null; 32 | 33 | this.__getSubject__ = function() { 34 | subject = new bridge; 35 | klass.apply(subject, args); 36 | return (this.__getSubject__ = function() { return subject; })(); 37 | }; 38 | }, 39 | klass: klass, 40 | constructor: klass 41 | }, {_resolve: false}); 42 | 43 | proxy.include(new JS.Module(delegators), {_resolve: false}); 44 | proxy.include(this.klass.InstanceMethods); 45 | return proxy; 46 | }, 47 | 48 | extend: { 49 | forward: function(name) { 50 | return function() { 51 | var subject = this.__getSubject__(); 52 | return subject[name].apply(subject, arguments); 53 | }; 54 | }, 55 | 56 | InstanceMethods: new JS.Module({ 57 | extend: function(source) { 58 | this.__getSubject__().extend(source); 59 | var method, func; 60 | for (method in source) { 61 | func = source[method]; 62 | if (typeof func === 'function') func = Proxy.Virtual.forward(method); 63 | this[method] = func; 64 | } 65 | } 66 | }) 67 | } 68 | }) 69 | } 70 | }); 71 | 72 | exports.Proxy = Proxy; 73 | }); 74 | 75 | -------------------------------------------------------------------------------- /source/state.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS; 4 | 5 | if (E) exports.JS = exports; 6 | factory(js, E ? exports : js); 7 | 8 | })(function(JS, exports) { 9 | 'use strict'; 10 | 11 | var State = new JS.Module('State', { 12 | __getState__: function(state) { 13 | if (typeof state === 'object') return state; 14 | if (typeof state === 'string') return (this.states || {})[state]; 15 | return {}; 16 | }, 17 | 18 | setState: function(state) { 19 | this.__state__ = this.__getState__(state); 20 | State.addMethods(this.__state__, this.klass); 21 | }, 22 | 23 | inState: function() { 24 | var i = arguments.length; 25 | while (i--) { 26 | if (this.__state__ === this.__getState__(arguments[i])) return true; 27 | } 28 | return false; 29 | }, 30 | 31 | extend: { 32 | ClassMethods: new JS.Module({ 33 | states: function(block) { 34 | this.define('states', State.buildCollection(this, block)); 35 | } 36 | }), 37 | 38 | included: function(klass) { 39 | klass.extend(this.ClassMethods); 40 | }, 41 | 42 | stub: function() { return this; }, 43 | 44 | buildStubs: function(stubs, collection, states) { 45 | var state, method; 46 | for (state in states) { 47 | collection[state] = {}; 48 | for (method in states[state]) stubs[method] = this.stub; 49 | } 50 | }, 51 | 52 | findStates: function(collections, name) { 53 | var i = collections.length, results = []; 54 | while (i--) { 55 | if (collections[i].hasOwnProperty(name)) 56 | results.push(collections[i][name]); 57 | } 58 | return results; 59 | }, 60 | 61 | buildCollection: function(module, states) { 62 | var stubs = {}, 63 | collection = {}, 64 | superstates = module.lookup('states'), 65 | state, klass, methods, name, mixins, i, n; 66 | 67 | this.buildStubs(stubs, collection, states); 68 | 69 | for (i = 0, n = superstates.length; i < n; i++) 70 | this.buildStubs(stubs, collection, superstates[i]); 71 | 72 | for (state in collection) { 73 | klass = new JS.Class(states[state]); 74 | mixins = this.findStates(superstates, state); 75 | 76 | i = mixins.length; 77 | while (i--) { 78 | if (mixins[i]) klass.include(mixins[i].klass); 79 | } 80 | 81 | methods = {}; 82 | for (name in stubs) { 83 | if (!klass.prototype[name]) methods[name] = stubs[name]; 84 | } 85 | klass.include(methods); 86 | collection[state] = new klass; 87 | } 88 | if (module.__tgt__) this.addMethods(stubs, module.__tgt__.klass); 89 | return collection; 90 | }, 91 | 92 | addMethods: function(state, klass) { 93 | if (!klass) return; 94 | 95 | var methods = {}, 96 | proto = klass.prototype, 97 | method; 98 | 99 | for (method in state) { 100 | if (proto[method]) continue; 101 | klass.define(method, this.wrapped(method)); 102 | } 103 | }, 104 | 105 | wrapped: function(method) { 106 | return function() { 107 | var func = (this.__state__ || {})[method]; 108 | return func ? func.apply(this, arguments) : this; 109 | }; 110 | } 111 | } 112 | }); 113 | 114 | exports.State = State; 115 | }); 116 | 117 | -------------------------------------------------------------------------------- /source/test/_head.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS, 4 | 5 | Console = js.Console || require('./console').Console, 6 | DOM = js.DOM || require('./dom').DOM, 7 | Enumerable = js.Enumerable || require('./enumerable').Enumerable, 8 | SortedSet = js.SortedSet || require('./set').SortedSet, 9 | Range = js.Range || require('./range').Range, 10 | Hash = js.Hash || require('./hash').Hash, 11 | MethodChain = js.MethodChain || require('./method_chain').MethodChain, 12 | Comparable = js.Comparable || require('./comparable').Comparable, 13 | StackTrace = js.StackTrace || require('./stack_trace').StackTrace; 14 | 15 | if (E) exports.JS = exports; 16 | factory(js, Console, DOM, Enumerable, SortedSet, Range, Hash, MethodChain, Comparable, StackTrace, E ? exports : js); 17 | 18 | })(function(JS, Console, DOM, Enumerable, SortedSet, Range, Hash, MethodChain, Comparable, StackTrace, exports) { 19 | 'use strict'; 20 | 21 | -------------------------------------------------------------------------------- /source/test/_tail.js: -------------------------------------------------------------------------------- 1 | exports.Test = Test; 2 | }); 3 | 4 | -------------------------------------------------------------------------------- /source/test/async_steps.js: -------------------------------------------------------------------------------- 1 | Test.extend({ 2 | AsyncSteps: new JS.Class(JS.Module, { 3 | define: function(name, method) { 4 | this.callSuper(name, function() { 5 | var args = [name, method].concat(JS.array(arguments)); 6 | this.__enqueue__(args); 7 | }); 8 | }, 9 | 10 | included: function(klass) { 11 | klass.include(Test.AsyncSteps.Sync); 12 | if (!klass.blockTransform) return; 13 | 14 | klass.extend({ 15 | blockTransform: function(block) { 16 | return function(resume) { 17 | this.exec(block, function(error) { 18 | this.sync(function() { resume(error) }); 19 | }); 20 | }; 21 | } 22 | }); 23 | }, 24 | 25 | extend: { 26 | Sync: new JS.Module({ 27 | __enqueue__: function(args) { 28 | this.__stepQueue__ = this.__stepQueue__ || []; 29 | this.__stepQueue__.push(args); 30 | if (this.__runningSteps__) return; 31 | this.__runningSteps__ = true; 32 | 33 | var setTimeout = Test.FakeClock.REAL.setTimeout; 34 | setTimeout(this.method('__runNextStep__'), 1); 35 | }, 36 | 37 | __runNextStep__: function(error) { 38 | if (typeof error === 'object' && error !== null) return this.addError(error); 39 | 40 | var step = this.__stepQueue__.shift(), n; 41 | 42 | if (!step) { 43 | this.__runningSteps__ = false; 44 | if (!this.__stepCallbacks__) return; 45 | 46 | n = this.__stepCallbacks__.length; 47 | while (n--) this.__stepCallbacks__.shift().call(this); 48 | 49 | return; 50 | } 51 | 52 | var methodName = step.shift(), 53 | method = step.shift(), 54 | parameters = step.slice(), 55 | block = function() { method.apply(this, parameters) }; 56 | 57 | parameters[method.length - 1] = this.method('__runNextStep__'); 58 | if (!this.exec) return block.call(this); 59 | this.exec(block, function() {}, this.method('__endSteps__')); 60 | }, 61 | 62 | __endSteps__: function(error) { 63 | Test.Unit.TestCase.processError(this, error); 64 | this.__stepQueue__ = []; 65 | this.__runNextStep__(); 66 | }, 67 | 68 | addError: function() { 69 | this.callSuper(); 70 | this.__endSteps__(); 71 | }, 72 | 73 | sync: function(callback) { 74 | if (!this.__runningSteps__) return callback.call(this); 75 | this.__stepCallbacks__ = this.__stepCallbacks__ || []; 76 | this.__stepCallbacks__.push(callback); 77 | } 78 | }) 79 | } 80 | }), 81 | 82 | asyncSteps: function(methods) { 83 | return new this.AsyncSteps(methods); 84 | } 85 | }); 86 | 87 | -------------------------------------------------------------------------------- /source/test/context/context.js: -------------------------------------------------------------------------------- 1 | Test.extend({ 2 | Context: new JS.Module({ 3 | extend: { 4 | included: function(base) { 5 | base.extend(Test.Context.Context, {_resolve: false}); 6 | base.include(Test.Context.LifeCycle, {_resolve: false}); 7 | base.extend(Test.Context.Test, {_resolve: false}); 8 | base.include(Console); 9 | }, 10 | 11 | Context: new JS.Module({ 12 | context: function(name, block) { 13 | var klass = new JS.Class(name.toString(), this, {}, {_resolve: false}); 14 | klass.__eigen__().resolve(); 15 | block.call(klass); 16 | return klass; 17 | }, 18 | 19 | cover: function(module) { 20 | var logger = new Test.Coverage(module); 21 | this.before_all_callbacks.push(logger.method('attach')); 22 | this.after_all_callbacks.push(logger.method('detach')); 23 | Test.Unit.TestCase.reports.push(logger); 24 | } 25 | }) 26 | } 27 | }), 28 | 29 | describe: function(name, block) { 30 | var klass = new JS.Class(name.toString(), Test.Unit.TestCase, {}, {_resolve: false}); 31 | klass.include(Test.Context, {_resolve: false}); 32 | klass.__eigen__().resolve(); 33 | 34 | block.call(klass); 35 | return klass; 36 | } 37 | }); 38 | 39 | Test.Context.Context.alias({describe: 'context'}); 40 | 41 | Test.extend({ 42 | context: Test.describe 43 | }); 44 | 45 | -------------------------------------------------------------------------------- /source/test/context/life_cycle.js: -------------------------------------------------------------------------------- 1 | Test.Context.LifeCycle = new JS.Module({ 2 | extend: { 3 | included: function(base) { 4 | base.extend(this.ClassMethods); 5 | 6 | base.before_all_callbacks = []; 7 | base.before_each_callbacks = []; 8 | base.after_all_callbacks = []; 9 | base.after_each_callbacks = []; 10 | base.before_should_callbacks = {}; 11 | 12 | base.extend({ 13 | inherited: function(child) { 14 | this.callSuper(); 15 | child.before_all_callbacks = []; 16 | child.before_each_callbacks = []; 17 | child.after_all_callbacks = []; 18 | child.after_each_callbacks = []; 19 | child.before_should_callbacks = {}; 20 | } 21 | }); 22 | }, 23 | 24 | ClassMethods: new JS.Module({ 25 | blockTransform: function(block) { 26 | return block; 27 | }, 28 | 29 | before: function(period, block) { 30 | if (block === undefined) { 31 | block = period; 32 | period = 'each'; 33 | } 34 | 35 | this['before_' + (period + '_') + 'callbacks'].push(this.blockTransform(block)); 36 | }, 37 | 38 | after: function(period, block) { 39 | if (block === undefined) { 40 | block = period; 41 | period = 'each'; 42 | } 43 | 44 | this['after_' + (period + '_') + 'callbacks'].push(this.blockTransform(block)); 45 | }, 46 | 47 | gatherCallbacks: function(callbackType, period) { 48 | var outerCallbacks = (typeof this.superclass.gatherCallbacks === 'function') 49 | ? this.superclass.gatherCallbacks(callbackType, period) 50 | : []; 51 | 52 | var mine = this[callbackType + '_' + (period + '_') + 'callbacks']; 53 | 54 | return (callbackType === 'before') 55 | ? outerCallbacks.concat(mine) 56 | : mine.concat(outerCallbacks); 57 | } 58 | }) 59 | }, 60 | 61 | setup: function(resume) { 62 | if (this.klass.before_should_callbacks[this._methodName]) 63 | this.klass.before_should_callbacks[this._methodName].call(this); 64 | 65 | this.runCallbacks('before', 'each', resume); 66 | }, 67 | 68 | teardown: function(resume) { 69 | this.runCallbacks('after', 'each', resume); 70 | }, 71 | 72 | runCallbacks: function(callbackType, period, continuation) { 73 | var callbacks = this.klass.gatherCallbacks(callbackType, period); 74 | 75 | Test.Unit.TestSuite.forEach(callbacks, function(callback, resume) { 76 | this.exec(callback, resume, continuation); 77 | }, continuation, this); 78 | }, 79 | 80 | runAllCallbacks: function(callbackType, continuation, context) { 81 | var previousIvars = this.instanceVariables(); 82 | this.runCallbacks(callbackType, 'all', function() { 83 | 84 | var ivars = this.instanceVariables().inject({}, function(hash, ivar) { 85 | if (previousIvars.member(ivar)) return hash; 86 | hash[ivar] = this[ivar]; 87 | return hash; 88 | }, this); 89 | 90 | if (continuation) continuation.call(context, ivars); 91 | }); 92 | }, 93 | 94 | setValuesFromCallbacks: function(values) { 95 | for (var key in values) 96 | this[key] = values[key]; 97 | }, 98 | 99 | instanceVariables: function() { 100 | var ivars = []; 101 | for (var key in this) { 102 | if (this.hasOwnProperty(key)) ivars.push(key); 103 | } 104 | return new Enumerable.Collection(ivars); 105 | } 106 | }); 107 | 108 | (function() { 109 | var m = Test.Context.LifeCycle.ClassMethods.method('instanceMethod'); 110 | 111 | Test.Context.LifeCycle.ClassMethods.include({ 112 | setup: m('before'), 113 | teardown: m('after') 114 | }); 115 | })(); 116 | 117 | -------------------------------------------------------------------------------- /source/test/context/shared_behavior.js: -------------------------------------------------------------------------------- 1 | Test.Context.extend({ 2 | SharedBehavior: new JS.Class(JS.Module, { 3 | extend: { 4 | createFromBehavior: function(beh) { 5 | var mod = new this(); 6 | mod._behavior = beh; 7 | return mod; 8 | }, 9 | 10 | moduleName: function(name) { 11 | return name.toLowerCase() 12 | .replace(/[\s:',\.~;!#=\(\)&]+/g, '_') 13 | .replace(/\/(.?)/g, function(m,a) { return '.' + a.toUpperCase() }) 14 | .replace(/(?:^|_)(.)/g, function(m,a) { return a.toUpperCase() }); 15 | } 16 | }, 17 | 18 | included: function(arg) { 19 | this._behavior.call(arg); 20 | } 21 | }) 22 | }); 23 | 24 | Test.Unit.TestCase.extend({ 25 | shared: function(name, block) { 26 | name = Test.Context.SharedBehavior.moduleName(name); 27 | JS.ENV[name] = Test.Context.SharedBehavior.createFromBehavior(block); 28 | }, 29 | 30 | use: function(sharedName) { 31 | if (JS.isType(sharedName, Test.Context.SharedBehavior) || 32 | JS.isType(sharedName, JS.Module)) 33 | this.include(sharedName); 34 | 35 | else if (JS.isType(sharedName, 'string')) { 36 | var name = Test.Context.SharedBehavior.moduleName(sharedName), 37 | beh = JS.ENV[name]; 38 | 39 | if (!beh) throw new Error('Could not find example group named "' + sharedName + '"'); 40 | this.include(beh); 41 | } 42 | } 43 | }); 44 | 45 | (function() { 46 | var alias = function(method, aliases) { 47 | var extension = {}; 48 | for (var i = 0, n = aliases.length; i < n; i++) 49 | extension[aliases[i]] = Test.Unit.TestCase[method]; 50 | Test.Unit.TestCase.extend(extension); 51 | }; 52 | 53 | alias('shared', ['sharedBehavior', 'shareAs', 'shareBehaviorAs', 'sharedExamplesFor']); 54 | alias('use', ['uses', 'itShouldBehaveLike', 'behavesLike', 'usesExamplesFrom']); 55 | })(); 56 | 57 | -------------------------------------------------------------------------------- /source/test/context/suite.js: -------------------------------------------------------------------------------- 1 | Test.Unit.TestSuite.include({ 2 | run: function(result, continuation, callback, context) { 3 | if (this._metadata.fullName) 4 | callback.call(context, this.klass.STARTED, this); 5 | 6 | var withIvars = function(ivarsFromCallback) { 7 | this.forEach(function(test, resume) { 8 | if (ivarsFromCallback && test.setValuesFromCallbacks) 9 | test.setValuesFromCallbacks(ivarsFromCallback); 10 | 11 | test.run(result, resume, callback, context); 12 | 13 | }, function() { 14 | var afterCallbacks = function() { 15 | if (this._metadata.fullName) 16 | callback.call(context, this.klass.FINISHED, this); 17 | 18 | continuation.call(context); 19 | }; 20 | if (ivarsFromCallback && first.runAllCallbacks) 21 | first.runAllCallbacks('after', afterCallbacks, this); 22 | else 23 | afterCallbacks.call(this); 24 | 25 | }, this); 26 | }; 27 | 28 | var first = this._tests[0], ivarsFromCallback = null; 29 | 30 | if (first && first.runAllCallbacks) 31 | first.runAllCallbacks('before', withIvars, this); 32 | else 33 | withIvars.call(this, null); 34 | } 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /source/test/context/test.js: -------------------------------------------------------------------------------- 1 | Test.Context.Test = new JS.Module({ 2 | test: function(name, opts, block) { 3 | var testName = 'test: ' + name; 4 | 5 | if (JS.indexOf(this.instanceMethods(false), testName) >= 0) 6 | throw new Error(testName + ' is already defined in ' + this.displayName); 7 | 8 | opts = opts || {}; 9 | 10 | if (typeof opts === 'function') { 11 | block = opts; 12 | } else { 13 | if (opts.before !== undefined) 14 | this.before_should_callbacks[testName] = opts.before; 15 | } 16 | 17 | this.define(testName, this.blockTransform(block), {_resolve: false}); 18 | }, 19 | 20 | beforeTest: function(name, block) { 21 | this.it(name, {before: block}, function() {}); 22 | } 23 | }); 24 | 25 | Test.Context.Test.alias({ 26 | it: 'test', 27 | should: 'test', 28 | tests: 'test', 29 | beforeIt: 'beforeTest', 30 | beforeShould: 'beforeTest', 31 | beforeTests: 'beforeTest' 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /source/test/coverage.js: -------------------------------------------------------------------------------- 1 | Test.extend({ 2 | Coverage: new JS.Class({ 3 | initialize: function(module) { 4 | this._module = module; 5 | this._methods = new Hash([]); 6 | 7 | var storeMethods = function(module) { 8 | var methods = module.instanceMethods(false), 9 | i = methods.length; 10 | while (i--) this._methods.store(module.instanceMethod(methods[i]), 0); 11 | }; 12 | storeMethods.call(this, module); 13 | storeMethods.call(this, module.__eigen__()); 14 | }, 15 | 16 | attach: function() { 17 | var module = this._module; 18 | StackTrace.addObserver(this); 19 | JS.Method.trace([module, module.__eigen__()]); 20 | }, 21 | 22 | detach: function() { 23 | var module = this._module; 24 | JS.Method.untrace([module, module.__eigen__()]); 25 | StackTrace.removeObserver(this); 26 | }, 27 | 28 | update: function(event, frame) { 29 | if (event !== 'call') return; 30 | var pair = this._methods.assoc(frame.method); 31 | if (pair) pair.setValue(pair.value + 1); 32 | }, 33 | 34 | report: function() { 35 | var methods = this._methods.entries().sort(function(a,b) { 36 | return b.value - a.value; 37 | }); 38 | var covered = this._methods.all(function(pair) { return pair.value > 0 }); 39 | 40 | this.printTable(methods, function(row, i) { 41 | if (row[1] === 0) return ['bgred', 'white']; 42 | return (i % 2 === 0) ? ['bold'] : []; 43 | }); 44 | return covered; 45 | }, 46 | 47 | printTable: function(table, formatter) { 48 | var widths = [], 49 | table = [['Method', 'Calls']].concat(table), 50 | C = Console, 51 | i = table.length, 52 | j, string; 53 | 54 | while (i--) { 55 | j = table[i].length; 56 | while (j--) { 57 | widths[j] = widths[j] || 0; 58 | string = (table[i][j] === undefined ? '' : table[i][j]).toString(); 59 | widths[j] = Math.max(string.length, widths[j]); 60 | } 61 | } 62 | 63 | var divider = '+', j = widths.length; 64 | while (j--) divider = '+' + this.repeat('-', widths[j] + 2) + divider; 65 | divider = ' ' + divider; 66 | C.reset(); 67 | C.puts(); 68 | C.puts(divider); 69 | 70 | var printRow = function(row, format) { 71 | var data = table[row]; 72 | C.reset(); 73 | C.print(' '); 74 | for (var i = 0, n = data.length; i < n; i++) { 75 | C.reset(); 76 | C.print('|'); 77 | C.consoleFormat.apply(C, format); 78 | C.print(' ' + this.pad(data[i], widths[i]) + ' '); 79 | } 80 | C.reset(); 81 | C.puts('|'); 82 | }; 83 | printRow.call(this, 0, ['bold']); 84 | C.reset(); 85 | C.puts(divider); 86 | 87 | for (var i = 1, n = table.length; i < n; i++) { 88 | var format = formatter ? formatter(table[i], i) : []; 89 | printRow.call(this, i, format); 90 | } 91 | C.reset(); 92 | C.puts(divider); 93 | }, 94 | 95 | pad: function(string, width) { 96 | string = (string === undefined ? '' : string).toString(); 97 | return string + this.repeat(' ', width - string.length); 98 | }, 99 | 100 | repeat: function(string, n) { 101 | var result = ''; 102 | while (n--) result += string; 103 | return result; 104 | } 105 | }) 106 | }); 107 | -------------------------------------------------------------------------------- /source/test/fake_clock.js: -------------------------------------------------------------------------------- 1 | Test.extend({ 2 | FakeClock: new JS.Module({ 3 | extend: { 4 | API: new JS.Singleton({ 5 | METHODS: ['Date', 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'], 6 | 7 | stub: function() { 8 | var mocking = Test.Mocking, 9 | methods = this.METHODS, 10 | i = methods.length; 11 | 12 | Test.FakeClock.reset(); 13 | 14 | while (i--) { 15 | if (methods[i] === 'Date') 16 | mocking.stub('new', methods[i], Test.FakeClock.method(methods[i])); 17 | else 18 | mocking.stub(methods[i], Test.FakeClock.method(methods[i])); 19 | } 20 | 21 | Date.now = Test.FakeClock.REAL.Date.now; 22 | }, 23 | 24 | reset: function() { 25 | return Test.FakeClock.reset(); 26 | }, 27 | 28 | tick: function(milliseconds) { 29 | return Test.FakeClock.tick(milliseconds); 30 | } 31 | }), 32 | 33 | REAL: {}, 34 | 35 | Schedule: new JS.Class(SortedSet, { 36 | nextScheduledAt: function(time) { 37 | return this.find(function(timeout) { return timeout.time <= time }); 38 | } 39 | }), 40 | 41 | Timeout: new JS.Class({ 42 | include: Comparable, 43 | 44 | initialize: function(callback, interval, repeat) { 45 | this.callback = callback; 46 | this.interval = interval; 47 | this.repeat = repeat; 48 | }, 49 | 50 | compareTo: function(other) { 51 | return this.time - other.time; 52 | }, 53 | 54 | toString: function() { 55 | return (this.repeat ? 'Interval' : 'Timeout') + 56 | '(' + this.interval + ')' + 57 | ':' + this.time; 58 | } 59 | }), 60 | 61 | reset: function() { 62 | this._currentTime = new Date().getTime(); 63 | this._callTime = this._currentTime; 64 | this._schedule = new this.Schedule(); 65 | }, 66 | 67 | tick: function(milliseconds) { 68 | this._currentTime += milliseconds; 69 | var timeout; 70 | while (timeout = this._schedule.nextScheduledAt(this._currentTime)) 71 | this._run(timeout); 72 | this._callTime = this._currentTime; 73 | }, 74 | 75 | _run: function(timeout) { 76 | this._callTime = timeout.time; 77 | timeout.callback(); 78 | 79 | if (timeout.repeat) { 80 | timeout.time += timeout.interval; 81 | this._schedule.rebuild(); 82 | } else { 83 | this.clearTimeout(timeout); 84 | } 85 | }, 86 | 87 | _timer: function(callback, milliseconds, repeat) { 88 | var timeout = new this.Timeout(callback, milliseconds, repeat); 89 | timeout.time = this._callTime + milliseconds; 90 | this._schedule.add(timeout); 91 | return timeout; 92 | }, 93 | 94 | Date: function() { 95 | var date = new Test.FakeClock.REAL.Date(); 96 | date.setTime(this._callTime); 97 | return date; 98 | }, 99 | 100 | setTimeout: function(callback, milliseconds) { 101 | return this._timer(callback, milliseconds, false); 102 | }, 103 | 104 | setInterval: function(callback, milliseconds) { 105 | return this._timer(callback, milliseconds, true); 106 | }, 107 | 108 | clearTimeout: function(timeout) { 109 | this._schedule.remove(timeout) 110 | }, 111 | 112 | clearInterval: function(timeout) { 113 | this._schedule.remove(timeout); 114 | } 115 | } 116 | }) 117 | }); 118 | 119 | Test.FakeClock.include({ 120 | clock: Test.FakeClock.API 121 | }); 122 | 123 | (function() { 124 | var methods = Test.FakeClock.API.METHODS, 125 | i = methods.length; 126 | 127 | while (i--) Test.FakeClock.REAL[methods[i]] = JS.ENV[methods[i]]; 128 | })(); 129 | 130 | -------------------------------------------------------------------------------- /source/test/helpers.js: -------------------------------------------------------------------------------- 1 | Test.extend({ 2 | Helpers: new JS.Module({ 3 | $R: function(start, end) { 4 | return new Range(start, end); 5 | }, 6 | 7 | $w: function(string) { 8 | return string.split(/\s+/); 9 | }, 10 | 11 | forEach: function(list, block, context) { 12 | for (var i = 0, n = list.length; i < n; i++) { 13 | block.call(context, list[i], i); 14 | } 15 | }, 16 | 17 | its: function() { 18 | return new MethodChain(); 19 | }, 20 | 21 | map: function(list, block, context) { 22 | return new Enumerable.Collection(list).map(block, context) 23 | }, 24 | 25 | repeat: function(n, block, context) { 26 | while (n--) block.call(context); 27 | } 28 | }) 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /source/test/mocking/dsl.js: -------------------------------------------------------------------------------- 1 | Test.Mocking.extend({ 2 | DSL: new JS.Module({ 3 | stub: function() { 4 | return Test.Mocking.stub.apply(Test.Mocking, arguments); 5 | }, 6 | 7 | expect: function() { 8 | var stub = Test.Mocking.stub.apply(Test.Mocking, arguments); 9 | stub.expected(); 10 | this.addAssertion(); 11 | return stub; 12 | }, 13 | 14 | anything: function() { 15 | return new Test.Mocking.Anything(); 16 | }, 17 | 18 | anyArgs: function() { 19 | return new Test.Mocking.AnyArgs(); 20 | }, 21 | 22 | instanceOf: function(type) { 23 | return new Test.Mocking.InstanceOf(type); 24 | }, 25 | 26 | match: function(type) { 27 | return new Test.Mocking.Matcher(type); 28 | }, 29 | 30 | arrayIncluding: function() { 31 | return new Test.Mocking.ArrayIncluding(arguments); 32 | }, 33 | 34 | objectIncluding: function(elements) { 35 | return new Test.Mocking.ObjectIncluding(elements); 36 | } 37 | }) 38 | }); 39 | 40 | Test.Unit.TestCase.include(Test.Mocking.DSL); 41 | Test.Unit.mocking = Test.Mocking; 42 | 43 | -------------------------------------------------------------------------------- /source/test/mocking/matchers.js: -------------------------------------------------------------------------------- 1 | Test.Mocking.extend({ 2 | Anything: new JS.Class({ 3 | equals: function() { return true }, 4 | toString: function() { return 'anything()' } 5 | }), 6 | 7 | AnyArgs: new JS.Class({ 8 | equals: function() { return Enumerable.ALL_EQUAL }, 9 | toString: function() { return 'anyArgs()' } 10 | }), 11 | 12 | ArrayIncluding: new JS.Class({ 13 | initialize: function(elements) { 14 | this._elements = Array.prototype.slice.call(elements); 15 | }, 16 | 17 | equals: function(array) { 18 | if (!JS.isType(array, Array)) return false; 19 | var i = this._elements.length, j; 20 | loop: while (i--) { 21 | j = array.length; 22 | while (j--) { 23 | if (Enumerable.areEqual(this._elements[i], array[j])) 24 | continue loop; 25 | } 26 | return false; 27 | } 28 | return true; 29 | }, 30 | 31 | toString: function() { 32 | var name = Console.convert(this._elements).replace(/^\[/, '').replace(/\]$/, ''); 33 | return 'arrayIncluding(' + name + ')'; 34 | } 35 | }), 36 | 37 | ObjectIncluding: new JS.Class({ 38 | initialize: function(elements) { 39 | this._elements = elements; 40 | }, 41 | 42 | equals: function(object) { 43 | if (!JS.isType(object, Object)) return false; 44 | for (var key in this._elements) { 45 | if (!Enumerable.areEqual(this._elements[key], object[key])) 46 | return false; 47 | } 48 | return true; 49 | }, 50 | 51 | toString: function() { 52 | var name = Console.convert(this._elements); 53 | return 'objectIncluding(' + name + ')'; 54 | } 55 | }), 56 | 57 | InstanceOf: new JS.Class({ 58 | initialize: function(type) { 59 | this._type = type; 60 | }, 61 | 62 | equals: function(object) { 63 | return JS.isType(object, this._type); 64 | }, 65 | 66 | toString: function() { 67 | var name = Console.convert(this._type); 68 | return 'instanceOf(' + name + ')'; 69 | } 70 | }), 71 | 72 | Matcher: new JS.Class({ 73 | initialize: function(type) { 74 | this._type = type; 75 | }, 76 | 77 | equals: function(object) { 78 | return JS.match(this._type, object); 79 | }, 80 | 81 | toString: function() { 82 | var name = Console.convert(this._type); 83 | return 'match(' + name + ')'; 84 | } 85 | }) 86 | }); 87 | 88 | -------------------------------------------------------------------------------- /source/test/reporters/composite.js: -------------------------------------------------------------------------------- 1 | Test.Reporters.extend({ 2 | Composite: new JS.Class({ 3 | initialize: function(reporters) { 4 | this._reporters = reporters || []; 5 | this._queue = []; 6 | this._pointer = 0; 7 | }, 8 | 9 | addReporter: function(reporter) { 10 | if (!reporter) return; 11 | this._reporters.push(reporter); 12 | }, 13 | 14 | removeReporter: function(reporter) { 15 | var index = JS.indexOf(this._reporters, reporter); 16 | if (index >= 0) this._reporters.splice(index, 1); 17 | }, 18 | 19 | flush: function() { 20 | var queue = this._queue, method, event, i, n, fn; 21 | while (queue[this._pointer] !== undefined) { 22 | method = queue[this._pointer][0]; 23 | event = queue[this._pointer][1]; 24 | for (i = 0, n = this._reporters.length; i < n; i++) { 25 | fn = this._reporters[i][method]; 26 | if (fn) fn.call(this._reporters[i], event); 27 | } 28 | this._pointer += 1; 29 | } 30 | } 31 | }) 32 | }); 33 | 34 | (function() { 35 | var methods = Test.Reporters.METHODS, 36 | n = methods.length; 37 | 38 | while (n--) 39 | (function(i) { 40 | var method = methods[i]; 41 | Test.Reporters.Composite.define(method, function(event) { 42 | this._queue[event.eventId] = [method, event]; 43 | this.flush(); 44 | }); 45 | })(n); 46 | })(); 47 | 48 | -------------------------------------------------------------------------------- /source/test/reporters/coverage.js: -------------------------------------------------------------------------------- 1 | Test.Reporters.extend({ 2 | Coverage: new JS.Class({ 3 | include: Console, 4 | 5 | startSuite: function(event) {}, 6 | 7 | startContext: function(event) {}, 8 | 9 | startTest: function(event) {}, 10 | 11 | addFault: function(event) {}, 12 | 13 | endTest: function(event) {}, 14 | 15 | endContext: function(event) {}, 16 | 17 | update: function(event) {}, 18 | 19 | endSuite: function(event) { 20 | var reports = Test.Unit.TestCase.reports; 21 | for (var i = 0, n = reports.length; i < n; i++) { 22 | this.reset(); 23 | this.puts(''); 24 | reports[i].report(); 25 | } 26 | } 27 | }) 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /source/test/reporters/dot.js: -------------------------------------------------------------------------------- 1 | Test.Reporters.extend({ 2 | Dot: new JS.Class(Test.Reporters.Error, { 3 | SYMBOLS: { 4 | failure: 'F', 5 | error: 'E' 6 | }, 7 | 8 | startTest: function(event) { 9 | this._outputFault = false; 10 | }, 11 | 12 | addFault: function(event) { 13 | this._faults.push(event); 14 | if (this._outputFault) return; 15 | this._outputFault = true; 16 | this.consoleFormat('bold', 'red'); 17 | this.print(this.SYMBOLS[event.error.type]); 18 | this.reset(); 19 | }, 20 | 21 | endTest: function(event) { 22 | if (this._outputFault) return; 23 | this.consoleFormat('green'); 24 | this.print('.'); 25 | this.reset(); 26 | }, 27 | 28 | endSuite: function(event) { 29 | this.puts('\n'); 30 | 31 | for (var i = 0, n = this._faults.length; i < n; i++) 32 | this._printFault(i + 1, this._faults[i]); 33 | 34 | this._printSummary(event); 35 | } 36 | }) 37 | }); 38 | 39 | Test.Reporters.register('dot', Test.Reporters.Dot); 40 | 41 | -------------------------------------------------------------------------------- /source/test/reporters/error.js: -------------------------------------------------------------------------------- 1 | Test.Reporters.extend({ 2 | Error: new JS.Class({ 3 | include: Console, 4 | 5 | NAMES: { 6 | failure: 'Failure', 7 | error: 'Error' 8 | }, 9 | 10 | startSuite: function(event) { 11 | this._faults = []; 12 | this._start = event.timestamp; 13 | 14 | this.consoleFormat('bold'); 15 | this.puts('Loaded suite: ' + event.children.join(', ')); 16 | this.reset(); 17 | this.puts(''); 18 | }, 19 | 20 | startContext: function(event) {}, 21 | 22 | startTest: function(event) {}, 23 | 24 | addFault: function(event) { 25 | this._faults.push(event); 26 | this._printFault(this._faults.length, event); 27 | }, 28 | 29 | update: function(event) {}, 30 | 31 | endTest: function(event) {}, 32 | 33 | endContext: function(event) {}, 34 | 35 | endSuite: function(event) { 36 | this._printSummary(event); 37 | }, 38 | 39 | _printFault: function(index, fault) { 40 | this.consoleFormat('bold', 'red'); 41 | this.puts(index + ') ' + this.NAMES[fault.error.type] + ': ' + fault.test.fullName); 42 | this.reset(); 43 | this.puts(fault.error.message); 44 | if (fault.error.backtrace) { 45 | this.grey(); 46 | this.puts(fault.error.backtrace); 47 | } 48 | this.reset(); 49 | this.puts(''); 50 | }, 51 | 52 | _printSummary: function(event) { 53 | var runtime = (event.timestamp - this._start) / 1000; 54 | this.reset(); 55 | this.puts('Finished in ' + runtime + ' seconds'); 56 | 57 | var color = event.passed ? 'green' : 'red'; 58 | this.consoleFormat(color); 59 | this.puts(this._plural(event.tests, 'test') + ', ' + 60 | this._plural(event.assertions, 'assertion') + ', ' + 61 | this._plural(event.failures, 'failure') + ', ' + 62 | this._plural(event.errors, 'error')); 63 | this.reset(); 64 | this.puts(''); 65 | }, 66 | 67 | _plural: function(number, noun) { 68 | return number + ' ' + noun + (number === 1 ? '' : 's'); 69 | } 70 | }) 71 | }); 72 | 73 | Test.Reporters.register('error', Test.Reporters.Error); 74 | 75 | -------------------------------------------------------------------------------- /source/test/reporters/exit_status.js: -------------------------------------------------------------------------------- 1 | Test.Reporters.extend({ 2 | ExitStatus: new JS.Class({ 3 | startSuite: function(event) {}, 4 | 5 | startContext: function(event) {}, 6 | 7 | startTest: function(event) {}, 8 | 9 | addFault: function(event) {}, 10 | 11 | endTest: function(event) {}, 12 | 13 | endContext: function(event) {}, 14 | 15 | update: function(event) {}, 16 | 17 | endSuite: function(event) { 18 | Console.exit(event.passed ? 0 : 1); 19 | } 20 | }) 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /source/test/reporters/headless.js: -------------------------------------------------------------------------------- 1 | // http://phantomjs.org/ 2 | // http://slimerjs.org/ 3 | 4 | Test.Reporters.extend({ 5 | Headless: new JS.Class({ 6 | extend: { 7 | UA: /\b(PhantomJS|SlimerJS)\b/ 8 | }, 9 | 10 | initialize: function(options) { 11 | this._options = options || {}; 12 | 13 | var format = Console.envvar('FORMAT'); 14 | 15 | if (Console.envvar('TAP')) format = format || 'tap'; 16 | this._options.format = this._options.format || format; 17 | 18 | var R = Test.Reporters, 19 | Printer = R.get(this._options.format) || R.Dot, 20 | reporter = new R.Composite(); 21 | 22 | reporter.addReporter(new Printer(options)); 23 | reporter.addReporter(new R.ExitStatus()); 24 | 25 | this._reader = new R.JSON.Reader(reporter); 26 | }, 27 | 28 | open: function(url) { 29 | var page = (typeof WebPage === 'function') ? new WebPage() : require('webpage').create(), 30 | self = this; 31 | 32 | page.onConsoleMessage = function(message) { 33 | if (!self._reader.read(message)) console.log(message); 34 | }; 35 | page.open(url); 36 | return page; 37 | } 38 | }) 39 | }); 40 | 41 | -------------------------------------------------------------------------------- /source/test/reporters/json.js: -------------------------------------------------------------------------------- 1 | Test.Reporters.extend({ 2 | JSON: new JS.Class({ 3 | include: Console, 4 | 5 | _log: function(eventName, data) { 6 | if (!JS.ENV.JSON) return; 7 | this.puts(JSON.stringify({jstest: [eventName, data]})); 8 | }, 9 | 10 | extend: { 11 | create: function() { 12 | if (!JS.ENV.navigator) return; 13 | if (Test.Reporters.Headless.UA.test(navigator.userAgent)) return new this(); 14 | }, 15 | 16 | Reader: new JS.Class({ 17 | initialize: function(reporter) { 18 | this._reporter = new Test.Reporters.Composite([reporter]); 19 | }, 20 | 21 | read: function(message) { 22 | if (!JS.ENV.JSON) return false; 23 | try { 24 | var data = JSON.parse(message), 25 | payload = data.jstest, 26 | method = payload[0], 27 | event = payload[1]; 28 | 29 | this._reporter[method](event); 30 | return true; 31 | } 32 | catch (e) { 33 | return false; 34 | } 35 | } 36 | }) 37 | } 38 | }) 39 | }); 40 | 41 | (function() { 42 | var methods = Test.Reporters.METHODS, 43 | n = methods.length; 44 | 45 | while (n--) 46 | (function(i) { 47 | var method = methods[i]; 48 | Test.Reporters.JSON.define(method, function(event) { 49 | this._log(method, event); 50 | }); 51 | })(n); 52 | })(); 53 | 54 | Test.Reporters.register('json', Test.Reporters.JSON); 55 | 56 | -------------------------------------------------------------------------------- /source/test/reporters/tap.js: -------------------------------------------------------------------------------- 1 | Test.Reporters.extend({ 2 | TAP: new JS.Class({ 3 | extend: { 4 | HOSTNAME: 'testling', 5 | 6 | create: function(options) { 7 | if (!JS.ENV.location) return; 8 | var parts = location.hostname.split('.'); 9 | if (JS.indexOf(parts, this.HOSTNAME) >= 0) return new this(options); 10 | } 11 | }, 12 | 13 | include: Console, 14 | 15 | startSuite: function(event) { 16 | this._testId = 0; 17 | this.puts('1..' + event.size); 18 | }, 19 | 20 | startContext: function(event) {}, 21 | 22 | startTest: function(event) { 23 | this._testPassed = true; 24 | this._faults = []; 25 | }, 26 | 27 | addFault: function(event) { 28 | this._testPassed = false; 29 | this._faults.push(event); 30 | }, 31 | 32 | endTest: function(event) { 33 | var line = this._testPassed ? 'ok' : 'not ok'; 34 | line += ' ' + ++this._testId + ' - ' + this._format(event.fullName); 35 | this.puts(line); 36 | 37 | var fault, message, parts, j, m; 38 | for (var i = 0, n = this._faults.length; i < n; i++) { 39 | fault = this._faults[i]; 40 | var message = fault.error.message; 41 | if (fault.error.backtrace) message += '\n' + fault.error.backtrace; 42 | parts = message.split(/[\r\n]/); 43 | for (j = 0, m = parts.length; j < m; j++) 44 | this.puts(' ' + parts[j]); 45 | } 46 | }, 47 | 48 | endContext: function(event) {}, 49 | 50 | update: function(event) {}, 51 | 52 | endSuite: function(event) {}, 53 | 54 | _format: function(string) { 55 | return string.replace(/[\s\t\r\n]+/g, ' '); 56 | } 57 | }) 58 | }); 59 | 60 | Test.Reporters.register('tap', Test.Reporters.TAP); 61 | 62 | -------------------------------------------------------------------------------- /source/test/reporters/test_swarm.js: -------------------------------------------------------------------------------- 1 | // https://github.com/jquery/testswarm 2 | 3 | Test.Reporters.extend({ 4 | TestSwarm: new JS.Class({ 5 | extend: { 6 | create: function(options, browser) { 7 | if (JS.ENV.TestSwarm) return new this(options, browser); 8 | } 9 | }, 10 | 11 | initialize: function(options, browserReporter) { 12 | this._browserReporter = browserReporter; 13 | 14 | TestSwarm.serialize = function() { 15 | return browserReporter.serialize(); 16 | }; 17 | }, 18 | 19 | startSuite: function(event) {}, 20 | 21 | startContext: function(event) {}, 22 | 23 | startTest: function(event) {}, 24 | 25 | addFault: function(event) {}, 26 | 27 | endTest: function(event) { 28 | TestSwarm.heartbeat(); 29 | }, 30 | 31 | endContext: function(event) {}, 32 | 33 | update: function(event) {}, 34 | 35 | endSuite: function(event) { 36 | TestSwarm.submit({ 37 | fail: event.failures, 38 | error: event.errors, 39 | total: event.tests 40 | }); 41 | } 42 | }) 43 | }); 44 | 45 | Test.Reporters.register('testswarm', Test.Reporters.TestSwarm); 46 | 47 | -------------------------------------------------------------------------------- /source/test/ui/browser.js: -------------------------------------------------------------------------------- 1 | Test.UI.extend({ 2 | Browser: new JS.Class({ 3 | getOptions: function() { 4 | var qs = (location.search || '').replace(/^\?/, ''), 5 | pairs = qs.split('&'), 6 | options = {}, 7 | parts, key, value; 8 | 9 | for (var i = 0, n = pairs.length; i < n; i++) { 10 | parts = pairs[i].split('='); 11 | key = decodeURIComponent(parts[0]); 12 | value = decodeURIComponent(parts[1]); 13 | 14 | if (/\[\]$/.test(parts[0])) { 15 | key = key.replace(/\[\]$/, ''); 16 | if (!(options[key] instanceof Array)) options[key] = []; 17 | options[key].push(value); 18 | } else { 19 | options[key] = value; 20 | } 21 | } 22 | 23 | if (options.test) 24 | options.test = [].concat(options.test); 25 | else 26 | options.test = []; 27 | 28 | return options; 29 | }, 30 | 31 | getReporters: function(options) { 32 | var reporters = [], 33 | R = Test.Reporters, 34 | reg = R._registry, 35 | browser = new R.Browser(options), 36 | reporter; 37 | 38 | reporters.push(new R.Coverage()); 39 | reporters.push(browser); 40 | 41 | for (var name in reg) { 42 | reporter = reg[name] && reg[name].create && reg[name].create(options, browser); 43 | if (reporter) reporters.push(reporter); 44 | } 45 | 46 | return reporters; 47 | } 48 | }) 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /source/test/ui/terminal.js: -------------------------------------------------------------------------------- 1 | Test.UI.extend({ 2 | Terminal: new JS.Class({ 3 | getOptions: function() { 4 | var options = {}, 5 | format = Console.envvar('FORMAT'), 6 | test = Console.envvar('TEST'); 7 | 8 | if (Console.envvar('TAP')) options.format = 'tap'; 9 | 10 | if (format) options.format = format; 11 | if (test) options.test = [test]; 12 | 13 | delete options.argv; 14 | options.test = options.test || []; 15 | return options; 16 | }, 17 | 18 | getReporters: function(options) { 19 | var R = Test.Reporters, 20 | Printer = R.get(options.format) || R.Dot; 21 | 22 | return [ 23 | new R.Coverage(options), 24 | new Printer(options), 25 | new R.ExitStatus(options) 26 | ]; 27 | } 28 | }) 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /source/test/unit.js: -------------------------------------------------------------------------------- 1 | var Test = new JS.Module('Test', { 2 | extend: { 3 | asyncTimeout: 5, 4 | 5 | filter: function(objects, suffix) { 6 | return Test.Runner.filter(objects, suffix); 7 | }, 8 | 9 | Reporters: new JS.Module({ 10 | extend: { 11 | METHODS: ['startSuite', 'startContext', 'startTest', 12 | 'update', 'addFault', 13 | 'endTest', 'endContext', 'endSuite'], 14 | 15 | _registry: {}, 16 | 17 | register: function(name, klass) { 18 | this._registry[name] = klass; 19 | }, 20 | 21 | get: function(name) { 22 | if (!name) return null; 23 | return this._registry[name] || null; 24 | } 25 | } 26 | }), 27 | 28 | UI: new JS.Module({}), 29 | Unit: new JS.Module({}) 30 | } 31 | }); 32 | 33 | -------------------------------------------------------------------------------- /source/test/unit/assertion_message.js: -------------------------------------------------------------------------------- 1 | Test.Unit.extend({ 2 | AssertionMessage: new JS.Class({ 3 | extend: { 4 | Literal: new JS.Class({ 5 | initialize: function(value) { 6 | this._value = value; 7 | this.toString = this.inspect; 8 | }, 9 | 10 | inspect: function() { 11 | return this._value.toString(); 12 | } 13 | }), 14 | 15 | literal: function(value) { 16 | return new this.Literal(value); 17 | }, 18 | 19 | Template: new JS.Class({ 20 | extend: { 21 | create: function(string) { 22 | var parts = string ? string.match(/\(\?\)|(?=[^\\])\?|(?:(?!\(\?\))(?:\\\?|[^\?]))+/g) : []; 23 | return new this(parts); 24 | } 25 | }, 26 | 27 | initialize: function(parts) { 28 | this._parts = new Enumerable.Collection(parts); 29 | this.count = this._parts.findAll(function(e) { return e === '?' || e === '(?)' }).length; 30 | }, 31 | 32 | result: function(parameters) { 33 | if (parameters.length !== this.count) throw 'The number of parameters does not match the number of substitutions'; 34 | var params = JS.array(parameters); 35 | return this._parts.collect(function(e) { 36 | if (e === '(?)') return params.shift().replace(/^\[/, '(').replace(/\]$/, ')'); 37 | if (e === '?') return params.shift(); 38 | return e.replace(/\\\?/g, '?'); 39 | }).join(''); 40 | } 41 | }) 42 | }, 43 | 44 | initialize: function(head, template, parameters) { 45 | this._head = head; 46 | this._templateString = template; 47 | this._parameters = new Enumerable.Collection(parameters); 48 | }, 49 | 50 | template: function() { 51 | return this._template = this._template || this.klass.Template.create(this._templateString); 52 | }, 53 | 54 | toString: function() { 55 | var messageParts = [], head, tail; 56 | if (this._head) messageParts.push(this._head); 57 | tail = this.template().result(this._parameters.collect(function(e) { 58 | return Console.convert(e); 59 | }, this)); 60 | if (tail !== '') messageParts.push(tail); 61 | return messageParts.join('\n'); 62 | } 63 | }) 64 | }); 65 | 66 | -------------------------------------------------------------------------------- /source/test/unit/error.js: -------------------------------------------------------------------------------- 1 | Test.Unit.extend({ 2 | Error: new JS.Class({ 3 | initialize: function(testCase, exception) { 4 | if (typeof exception === 'string') 5 | exception = new Error(exception); 6 | 7 | this._testCase = testCase; 8 | this._exception = exception; 9 | }, 10 | 11 | metadata: function() { 12 | return { 13 | test: this.testMetadata(), 14 | error: this.errorMetadata() 15 | } 16 | }, 17 | 18 | testMetadata: function() { 19 | return this._testCase.metadata(); 20 | }, 21 | 22 | errorMetadata: function() { 23 | return { 24 | type: 'error', 25 | message: this._exception.name + ': ' + this._exception.message, 26 | backtrace: Console.filterBacktrace(this._exception.stack) 27 | }; 28 | } 29 | }) 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /source/test/unit/failure.js: -------------------------------------------------------------------------------- 1 | Test.Unit.extend({ 2 | Failure: new JS.Class({ 3 | initialize: function(testCase, message) { 4 | this._testCase = testCase; 5 | this._message = message; 6 | }, 7 | 8 | metadata: function() { 9 | return { 10 | test: this.testMetadata(), 11 | error: this.errorMetadata() 12 | } 13 | }, 14 | 15 | testMetadata: function() { 16 | return this._testCase.metadata(); 17 | }, 18 | 19 | errorMetadata: function() { 20 | return { 21 | type: 'failure', 22 | message: this._message 23 | }; 24 | } 25 | }) 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /source/test/unit/observable.js: -------------------------------------------------------------------------------- 1 | Test.Unit.extend({ 2 | Observable: new JS.Module({ 3 | addListener: function(channelName, block, context) { 4 | if (block === undefined) throw new Error('No callback was passed as a listener'); 5 | 6 | this.channels()[channelName] = this.channels()[channelName] || []; 7 | this.channels()[channelName].push([block, context]); 8 | 9 | return block; 10 | }, 11 | 12 | removeListener: function(channelName, block, context) { 13 | var channel = this.channels()[channelName]; 14 | if (!channel) return; 15 | 16 | var i = channel.length; 17 | while (i--) { 18 | if (channel[i][0] === block) { 19 | channel.splice(i,1); 20 | return block; 21 | } 22 | } 23 | return null; 24 | }, 25 | 26 | notifyListeners: function(channelName, args) { 27 | var args = JS.array(arguments), 28 | channelName = args.shift(), 29 | channel = this.channels()[channelName]; 30 | 31 | if (!channel) return 0; 32 | 33 | for (var i = 0, n = channel.length; i < n; i++) 34 | channel[i][0].apply(channel[i][1] || null, args); 35 | 36 | return channel.length; 37 | }, 38 | 39 | channels: function() { 40 | return this.__channels__ = this.__channels__ || []; 41 | } 42 | }) 43 | }); 44 | 45 | -------------------------------------------------------------------------------- /source/test/unit/test_result.js: -------------------------------------------------------------------------------- 1 | Test.Unit.extend({ 2 | TestResult: new JS.Class({ 3 | include: Test.Unit.Observable, 4 | 5 | extend: { 6 | CHANGED: 'Test.Unit.TestResult.CHANGED', 7 | FAULT: 'Test.Unit.TestResult.FAULT' 8 | }, 9 | 10 | initialize: function() { 11 | this._runCount = this._assertionCount = 0; 12 | this._failures = []; 13 | this._errors = []; 14 | }, 15 | 16 | addRun: function() { 17 | this._runCount += 1; 18 | this.notifyListeners(this.klass.CHANGED, this); 19 | }, 20 | 21 | addFailure: function(failure) { 22 | this._failures.push(failure); 23 | this.notifyListeners(this.klass.FAULT, failure); 24 | this.notifyListeners(this.klass.CHANGED, this); 25 | }, 26 | 27 | addError: function(error) { 28 | this._errors.push(error); 29 | this.notifyListeners(this.klass.FAULT, error); 30 | this.notifyListeners(this.klass.CHANGED, this); 31 | }, 32 | 33 | addAssertion: function() { 34 | this._assertionCount += 1; 35 | this.notifyListeners(this.klass.CHANGED, this); 36 | }, 37 | 38 | passed: function() { 39 | return this._failures.length === 0 && this._errors.length === 0; 40 | }, 41 | 42 | runCount: function() { 43 | return this._runCount; 44 | }, 45 | 46 | assertionCount: function() { 47 | return this._assertionCount; 48 | }, 49 | 50 | failureCount: function() { 51 | return this._failures.length; 52 | }, 53 | 54 | errorCount: function() { 55 | return this._errors.length; 56 | }, 57 | 58 | metadata: function() { 59 | return { 60 | passed: this.passed(), 61 | tests: this.runCount(), 62 | assertions: this.assertionCount(), 63 | failures: this.failureCount(), 64 | errors: this.errorCount() 65 | }; 66 | } 67 | }) 68 | }); 69 | 70 | -------------------------------------------------------------------------------- /source/test/unit/test_suite.js: -------------------------------------------------------------------------------- 1 | Test.Unit.extend({ 2 | TestSuite: new JS.Class({ 3 | include: Enumerable, 4 | 5 | extend: { 6 | STARTED: 'Test.Unit.TestSuite.STARTED', 7 | FINISHED: 'Test.Unit.TestSuite.FINISHED', 8 | 9 | forEach: function(tests, block, continuation, context) { 10 | var looping = false, 11 | pinged = false, 12 | n = tests.length, 13 | i = -1, 14 | breakTime = new JS.Date().getTime(), 15 | setTimeout = Test.FakeClock.REAL.setTimeout; 16 | 17 | var ping = function() { 18 | pinged = true; 19 | var time = new JS.Date().getTime(); 20 | 21 | if (Console.BROWSER && (time - breakTime) > 1000) { 22 | breakTime = time; 23 | looping = false; 24 | setTimeout(iterate, 0); 25 | } 26 | else if (!looping) { 27 | looping = true; 28 | while (looping) iterate(); 29 | } 30 | }; 31 | 32 | var iterate = function() { 33 | i += 1; 34 | if (i === n) { 35 | looping = false; 36 | return continuation && continuation.call(context); 37 | } 38 | pinged = false; 39 | block.call(context, tests[i], ping); 40 | if (!pinged) looping = false; 41 | }; 42 | 43 | ping(); 44 | } 45 | }, 46 | 47 | initialize: function(metadata, tests) { 48 | this._metadata = metadata; 49 | this._tests = tests; 50 | }, 51 | 52 | forEach: function(block, continuation, context) { 53 | this.klass.forEach(this._tests, block, continuation, context); 54 | }, 55 | 56 | run: function(result, continuation, callback, context) { 57 | if (this._metadata.fullName) 58 | callback.call(context, this.klass.STARTED, this); 59 | 60 | this.forEach(function(test, resume) { 61 | test.run(result, resume, callback, context) 62 | }, function() { 63 | if (this._metadata.fullName) 64 | callback.call(context, this.klass.FINISHED, this); 65 | 66 | continuation.call(context); 67 | }, this); 68 | }, 69 | 70 | size: function() { 71 | if (this._size !== undefined) return this._size; 72 | var totalSize = 0, i = this._tests.length; 73 | while (i--) totalSize += this._tests[i].size(); 74 | return this._size = totalSize; 75 | }, 76 | 77 | empty: function() { 78 | return this._tests.length === 0; 79 | }, 80 | 81 | metadata: function(root) { 82 | var data = JS.extend({size: this.size()}, this._metadata); 83 | if (root) { 84 | delete data.fullName; 85 | delete data.shortName; 86 | delete data.context; 87 | } 88 | return data; 89 | } 90 | }) 91 | }); 92 | 93 | -------------------------------------------------------------------------------- /source/tsort.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | var E = (typeof exports === 'object'), 3 | js = (typeof JS === 'undefined') ? require('./core') : JS, 4 | 5 | Hash = js.Hash || require('./hash').Hash; 6 | 7 | if (E) exports.JS = exports; 8 | factory(js, Hash, E ? exports : js); 9 | 10 | })(function(JS, Hash, exports) { 11 | 'use strict'; 12 | 13 | var TSort = new JS.Module('TSort', { 14 | extend: { 15 | Cyclic: new JS.Class(Error) 16 | }, 17 | 18 | tsort: function() { 19 | var result = []; 20 | this.tsortEach(result.push, result); 21 | return result; 22 | }, 23 | 24 | tsortEach: function(block, context) { 25 | this.eachStronglyConnectedComponent(function(component) { 26 | if (component.length === 1) 27 | block.call(context, component[0]); 28 | else 29 | throw new TSort.Cyclic('topological sort failed: ' + component.toString()); 30 | }); 31 | }, 32 | 33 | stronglyConnectedComponents: function() { 34 | var result = []; 35 | this.eachStronglyConnectedComponent(result.push, result); 36 | return result; 37 | }, 38 | 39 | eachStronglyConnectedComponent: function(block, context) { 40 | var idMap = new Hash(), 41 | stack = []; 42 | 43 | this.tsortEachNode(function(node) { 44 | if (idMap.hasKey(node)) return; 45 | this.eachStronglyConnectedComponentFrom(node, idMap, stack, function(child) { 46 | block.call(context, child); 47 | }); 48 | }, this); 49 | }, 50 | 51 | eachStronglyConnectedComponentFrom: function(node, idMap, stack, block, context) { 52 | var nodeId = idMap.size, 53 | stackLength = stack.length, 54 | minimumId = nodeId, 55 | component, i; 56 | 57 | idMap.store(node, nodeId); 58 | stack.push(node); 59 | 60 | this.tsortEachChild(node, function(child) { 61 | if (idMap.hasKey(child)) { 62 | var childId = idMap.get(child); 63 | if (child !== undefined && childId < minimumId) minimumId = childId; 64 | } else { 65 | var subMinimumId = this.eachStronglyConnectedComponentFrom(child, idMap, stack, block, context); 66 | if (subMinimumId < minimumId) minimumId = subMinimumId; 67 | } 68 | }, this); 69 | 70 | if (nodeId === minimumId) { 71 | component = stack.splice(stackLength, stack.length - stackLength); 72 | i = component.length; 73 | while (i--) idMap.store(component[i], undefined); 74 | block.call(context, component); 75 | } 76 | 77 | return minimumId; 78 | }, 79 | 80 | tsortEachNode: function() { 81 | throw new JS.NotImplementedError('tsortEachNode'); 82 | }, 83 | 84 | tsortEachChild: function() { 85 | throw new JS.NotImplementedError('tsortEachChild'); 86 | } 87 | }); 88 | 89 | exports.TSort = TSort; 90 | }); 91 | 92 | -------------------------------------------------------------------------------- /test/airenv/app.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | JSClass 4 | 3.0 5 | JSClass 6 | 7 | index.html 8 | true 9 | 720 10 | 800 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/airenv/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JS.Class test runner 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/airenv/jsclass: -------------------------------------------------------------------------------- 1 | ../.. -------------------------------------------------------------------------------- /test/browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JS.Class test runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/console.js: -------------------------------------------------------------------------------- 1 | if (this.ActiveXObject) load = function(path) { 2 | var fso = new ActiveXObject('Scripting.FileSystemObject'), file, runner; 3 | try { 4 | file = fso.OpenTextFile(path); 5 | runner = function() { eval(file.ReadAll()) }; 6 | runner(); 7 | } finally { 8 | try { if (file) file.Close() } catch (e) {} 9 | } 10 | }; 11 | 12 | (function() { 13 | var $ = (typeof global === 'object') ? global : this, 14 | path = $.JSCLASS_PATH = 'build/src/'; 15 | 16 | if (typeof phantom !== 'undefined') { 17 | $.JSCLASS_PATH = '../' + $.JSCLASS_PATH; 18 | $.CWD = '..'; 19 | } 20 | 21 | if (typeof require === 'function') { 22 | $.JS = require('../' + path + 'loader'); 23 | require('./runner'); 24 | } else { 25 | load(path + 'loader.js'); 26 | load('test/runner.js'); 27 | } 28 | })(); 29 | 30 | -------------------------------------------------------------------------------- /test/examples/async.js: -------------------------------------------------------------------------------- 1 | JSCLASS_PATH = "build/min/" 2 | var JS = require("../../" + JSCLASS_PATH + "loader") 3 | 4 | JS.require("JS.Test", "JS.MethodChain", function(Test, MC) { 5 | 6 | Test.describe("Asynchronous testing", function() { with(this) { 7 | describe("with a simple test", function() { with(this) { 8 | it("allows async assertions", function(resume) { with(this) { 9 | setTimeout(function() { 10 | resume(function() { assert(false) }) 11 | }, 1000) 12 | }}) 13 | }}) 14 | 15 | describe("with nested resume blocks", function() { with(this) { 16 | define("wait", function(resume, block) { with(this) { 17 | setTimeout(function() { resume(block) }, 1000) 18 | }}) 19 | 20 | it("keeps running until you use a resume block with no continuation", function(resume) { with(this) { 21 | var startTime = new Date().getTime(); 22 | 23 | wait(resume, function(resume) { 24 | assert(true) 25 | wait(resume, function(resume) { 26 | assert(true) 27 | wait(resume, function() { 28 | var endTime = new Date().getTime(); 29 | assertInDelta( 4, endTime - startTime, 0.1 ) 30 | }) 31 | }) 32 | }) 33 | }}) 34 | }}) 35 | 36 | describe("with an async before block", function() { with(this) { 37 | before(function(resume) { with(this) { 38 | var self = this 39 | setTimeout(function() { 40 | self.value = 2 41 | resume() 42 | }, 1000); 43 | }}) 44 | 45 | it("waits for the before block to resume", function() { with(this) { 46 | assertEqual( 2, value ) 47 | }}) 48 | 49 | describe("with another nested block", function() { with(this) { 50 | before(function(resume) { with(this) { 51 | var self = this 52 | setTimeout(function() { 53 | self.value *= 4 54 | resume() 55 | }, 500) 56 | }}) 57 | 58 | it("runs both before blocks sequentially", function() { with(this) { 59 | assertEqual( 80, this.value ) 60 | }}) 61 | }}) 62 | }}) 63 | 64 | describe("with an async before all block", function() { with(this) { 65 | before("all", function(resume) { with(this) { 66 | var self = this 67 | setTimeout(function() { 68 | self.value = 20 69 | resume() 70 | }, 1000); 71 | }}) 72 | 73 | it("waits for the before all block to resume", function() { with(this) { 74 | assertEqual( 2, value ) 75 | }}) 76 | 77 | describe("with another nested all block", function() { with(this) { 78 | before("all", function(resume) { with(this) { 79 | var self = this 80 | setTimeout(function() { 81 | self.value *= 4 82 | resume() 83 | }, 500) 84 | }}) 85 | 86 | it("runs both before all blocks sequentially", function() { with(this) { 87 | assertEqual( 8, value ) 88 | }}) 89 | }}) 90 | }}) 91 | }}) 92 | 93 | Test.autorun() 94 | }) 95 | 96 | -------------------------------------------------------------------------------- /test/examples/tracing.js: -------------------------------------------------------------------------------- 1 | if (this.ActiveXObject) load = function(path) { 2 | var fso = new ActiveXObject('Scripting.FileSystemObject'), file, runner; 3 | try { 4 | file = fso.OpenTextFile(path); 5 | runner = function() { eval(file.ReadAll()) }; 6 | runner(); 7 | } finally { 8 | try { if (file) file.Close() } catch (e) {} 9 | } 10 | }; 11 | 12 | (function() { 13 | var $ = (typeof this.global === 'object') ? this.global : this 14 | $.JSCLASS_PATH = 'build/src/' 15 | })() 16 | 17 | if (typeof require === 'function') { 18 | var JS = require('../../' + JSCLASS_PATH + 'loader') 19 | } else { 20 | load(JSCLASS_PATH + 'loader.js') 21 | } 22 | 23 | Foo = {} 24 | 25 | JS.require('JS.Class', 'JS.Method', 'JS.Console', 'JS.Hash', 'JS.OrderedHash', 'JS.TSort', 26 | function(Class, Method, Console, Hash, OrderedHash, TSort) { 27 | Tasks = new Class({ 28 | include: TSort, 29 | 30 | initialize: function(table) { 31 | this.table = table; 32 | }, 33 | 34 | tsortEachNode: function(block, context) { 35 | for (var task in this.table) { 36 | if (this.table.hasOwnProperty(task)) 37 | block.call(context, task); 38 | } 39 | }, 40 | 41 | tsortEachChild: function(task, block, context) { 42 | var tasks = this.table[task]; 43 | for (var i = 0, n = tasks.length; i < n; i++) 44 | block.call(context, tasks[i]); 45 | } 46 | }) 47 | 48 | var tasks = new Tasks({ 49 | 'eat breakfast': ['serve'], 50 | 'serve': ['cook'], 51 | 'cook': ['buy bacon', 'buy eggs'], 52 | 'buy bacon': [], 53 | 'buy eggs': [] 54 | }) 55 | 56 | var hash = new OrderedHash(['foo', 4, 'bar', 5]) 57 | 58 | Method.tracing([Hash, TSort], function() { 59 | tasks.tsort() 60 | hash.hasKey('foo') 61 | Console.puts(Console.nameOf(Foo)) 62 | hash.select(function() { throw new Error('fail') }) 63 | }) 64 | 65 | hash.hasKey('something') 66 | }) 67 | 68 | -------------------------------------------------------------------------------- /test/fixtures/common.js: -------------------------------------------------------------------------------- 1 | Common = {name: 'CommonJS module'}; 2 | HTTP = {name: 'CommonJS HTTP lib'}; 3 | 4 | if (typeof exports === 'object') { 5 | exports.Common = Common; 6 | exports.HTTP = HTTP; 7 | } 8 | -------------------------------------------------------------------------------- /test/phantom.js: -------------------------------------------------------------------------------- 1 | JSCLASS_PATH = '../build/src' 2 | var pkg = require(JSCLASS_PATH + '/loader') 3 | 4 | pkg.require('JS.Test', function(Test) { 5 | var reporter = new Test.Reporters.Headless() 6 | reporter.open('test/browser.html') 7 | }) 8 | 9 | -------------------------------------------------------------------------------- /test/runner.js: -------------------------------------------------------------------------------- 1 | JS.ENV.CWD = (typeof CWD === 'undefined') ? '.' : CWD 2 | 3 | JS.cache = false 4 | if (JS.ENV.JS_DEBUG) JS.debug = true 5 | 6 | JS.packages(function() { with(this) { 7 | autoload(/^(.*)Spec$/, {from: CWD + '/test/specs', require: ['JS.$1']}) 8 | 9 | pkg('Test.UnitSpec').requires('JS.Set', 'JS.Observable') 10 | pkg('ClassSpec').requires('ModuleSpec') 11 | 12 | file(CWD + '/test/specs/test/test_spec_helpers.js').provides('TestSpecHelpers') 13 | 14 | pkg('Test.UnitSpec').requires('TestSpecHelpers') 15 | pkg('Test.MockingSpec').requires('TestSpecHelpers') 16 | }}) 17 | 18 | JS.require('JS', 'JS.Test', function(js, Test) { 19 | js.extend(JS, js) 20 | JS.Test = Test 21 | 22 | var specs = [ 'Test.UnitSpec', 23 | 'Test.ContextSpec', 24 | 'Test.MockingSpec', 25 | 'Test.FakeClockSpec', 26 | 'Test.AsyncStepsSpec', 27 | 'ModuleSpec', 28 | 'ClassSpec', 29 | 'MethodSpec', 30 | 'KernelSpec', 31 | 'SingletonSpec', 32 | 'InterfaceSpec', 33 | 'CommandSpec', 34 | 'ComparableSpec', 35 | 'ConsoleSpec', 36 | 'ConstantScopeSpec', 37 | 'DecoratorSpec', 38 | 'EnumerableSpec', 39 | 'ForwardableSpec', 40 | 'HashSpec', 41 | 'LinkedListSpec', 42 | 'MethodChainSpec', 43 | 'DeferrableSpec', 44 | 'ObservableSpec', 45 | 'PackageSpec', 46 | 'ProxySpec', 47 | 'RangeSpec', 48 | 'SetSpec', 49 | 'StateSpec', 50 | 'TSortSpec' ] 51 | 52 | specs = Test.filter(specs, 'Spec') 53 | specs.push(function() { Test.autorun() }) 54 | JS.require.apply(JS, specs) 55 | }) 56 | 57 | -------------------------------------------------------------------------------- /test/specs/comparable_spec.js: -------------------------------------------------------------------------------- 1 | JS.require('JS.Comparable', function(Comparable) { 2 | 3 | JS.ENV.ComparableSpec = JS.Test.describe(Comparable, function() { with(this) { 4 | include(JS.Test.Helpers) 5 | 6 | define("TodoItem", new JS.Class({ 7 | include: Comparable, 8 | initialize: function(position, task) { 9 | this.position = position; 10 | this.task = task || ""; 11 | }, 12 | compareTo: function(other) { 13 | if (this.position < other.position) 14 | return -1; 15 | else if (this.position > other.position) 16 | return 1; 17 | else 18 | return 0; 19 | } 20 | })) 21 | 22 | describe("sorting", function() { with(this) { 23 | before(function() { with(this) { 24 | this.todos = map([8,2,7,5,3,7,6], function(id) { return new TodoItem(id) }) 25 | }}) 26 | 27 | it("uses the #compareTo method to sort", function() { with(this) { 28 | todos.sort(TodoItem.compare) 29 | assertEqual( [2,3,5,6,7,7,8], map(todos, 'position') ) 30 | }}) 31 | }}) 32 | 33 | describe("#lt", function() { with(this) { 34 | it("returns true if A < B", function() { with(this) { 35 | assert( new TodoItem(1).lt(new TodoItem(2)) ) 36 | }}) 37 | 38 | it("returns false if A = B", function() { with(this) { 39 | assert( !new TodoItem(2).lt(new TodoItem(2)) ) 40 | }}) 41 | 42 | it("returns false if A > B", function() { with(this) { 43 | assert( !new TodoItem(3).lt(new TodoItem(2)) ) 44 | }}) 45 | }}) 46 | 47 | describe("#lte", function() { with(this) { 48 | it("returns true if A < B", function() { with(this) { 49 | assert( new TodoItem(1).lte(new TodoItem(2)) ) 50 | }}) 51 | 52 | it("returns true if A = B", function() { with(this) { 53 | assert( new TodoItem(2).lte(new TodoItem(2)) ) 54 | }}) 55 | 56 | it("returns false if A > B", function() { with(this) { 57 | assert( !new TodoItem(3).lte(new TodoItem(2)) ) 58 | }}) 59 | }}) 60 | 61 | describe("#eq", function() { with(this) { 62 | it("returns false if A < B", function() { with(this) { 63 | assert( !new TodoItem(1).eq(new TodoItem(2)) ) 64 | }}) 65 | 66 | it("returns true if A = B", function() { with(this) { 67 | assert( new TodoItem(2).eq(new TodoItem(2)) ) 68 | }}) 69 | 70 | it("returns false if A > B", function() { with(this) { 71 | assert( !new TodoItem(3).eq(new TodoItem(2)) ) 72 | }}) 73 | }}) 74 | 75 | describe("#gt", function() { with(this) { 76 | it("returns false if A < B", function() { with(this) { 77 | assert( !new TodoItem(1).gt(new TodoItem(2)) ) 78 | }}) 79 | 80 | it("returns false if A = B", function() { with(this) { 81 | assert( !new TodoItem(2).gt(new TodoItem(2)) ) 82 | }}) 83 | 84 | it("returns true if A > B", function() { with(this) { 85 | assert( new TodoItem(3).gt(new TodoItem(2)) ) 86 | }}) 87 | }}) 88 | 89 | describe("#gte", function() { with(this) { 90 | it("returns false if A < B", function() { with(this) { 91 | assert( !new TodoItem(1).gte(new TodoItem(2)) ) 92 | }}) 93 | 94 | it("returns true if A = B", function() { with(this) { 95 | assert( new TodoItem(2).gte(new TodoItem(2)) ) 96 | }}) 97 | 98 | it("returns true if A > B", function() { with(this) { 99 | assert( new TodoItem(3).gte(new TodoItem(2)) ) 100 | }}) 101 | }}) 102 | }}) 103 | 104 | }) 105 | 106 | -------------------------------------------------------------------------------- /test/specs/console_spec.js: -------------------------------------------------------------------------------- 1 | JS.require('JS.Console', function(Console) { 2 | 3 | JS.ENV.ConsoleSpec = JS.Test.describe(Console, function() { with(this) { 4 | describe("convert", function() { with(this) { 5 | it("strigifies undefined", function() { with(this) { 6 | assertEqual( "undefined", Console.convert(undefined) ) 7 | }}) 8 | 9 | it("strigifies null", function() { with(this) { 10 | assertEqual( "null", Console.convert(null) ) 11 | }}) 12 | 13 | it("strigifies booleans", function() { with(this) { 14 | assertEqual( "true", Console.convert(true) ) 15 | assertEqual( "false", Console.convert(false) ) 16 | }}) 17 | 18 | it("strigifies numbers", function() { with(this) { 19 | assertEqual( "0", Console.convert(0) ) 20 | assertEqual( "5", Console.convert(5) ) 21 | }}) 22 | 23 | it("stringifies strings", function() { with(this) { 24 | assertEqual( '""', Console.convert("") ) 25 | assertEqual( '"hi"', Console.convert("hi") ) 26 | }}) 27 | 28 | it("strigifies arrays", function() { with(this) { 29 | assertEqual( "[]", Console.convert([]) ) 30 | assertEqual( "[ 1, 2, 3 ]", Console.convert([1,2,3]) ) 31 | }}) 32 | 33 | it("strigifies circular arrays", function() { with(this) { 34 | var a = [1,2] 35 | a.push(a) 36 | assertEqual( "[ 1, 2, #circular ]", Console.convert(a) ) 37 | }}) 38 | 39 | it("strigifies objects", function() { with(this) { 40 | assertEqual( "{}", Console.convert({}) ) 41 | assertEqual( "{ \"foo\": \"bar\" }", Console.convert({foo: "bar"}) ) 42 | }}) 43 | 44 | it("strigifies recursive objects", function() { with(this) { 45 | assertEqual( "{ \"foo\": { \"bar\": [ 1, 2, 3 ] } }", Console.convert({foo: {bar: [1,2,3]}}) ) 46 | }}) 47 | 48 | it("strigifies circular objects", function() { with(this) { 49 | var o = {foo: "bar"} 50 | o.bar = o 51 | assertEqual( "{ \"bar\": #circular, \"foo\": \"bar\" }", Console.convert(o) ) 52 | }}) 53 | 54 | it("strigifies DOM nodes", function() { with(this) { 55 | var node = { 56 | nodeType: 0, 57 | toString: function() { return "[object HTMLFormElement]" } 58 | } 59 | assertEqual( "[object HTMLFormElement]", Console.convert(node) ) 60 | }}) 61 | }}) 62 | }}) 63 | 64 | }) 65 | 66 | -------------------------------------------------------------------------------- /test/specs/decorator_spec.js: -------------------------------------------------------------------------------- 1 | JS.require('JS.Decorator', function(Decorator) { 2 | 3 | JS.ENV.DecoratorSpec = JS.Test.describe(Decorator, function() { with(this) { 4 | var Bicycle = new JS.Class({ 5 | initialize: function(model, gears) { 6 | this.model = model; 7 | this.gears = gears; 8 | }, 9 | getModel: function() { 10 | return this.model; 11 | }, 12 | getPrice: function() { 13 | return 10 * this.gears; 14 | } 15 | }); 16 | 17 | var HeadlightDecorator = new Decorator(Bicycle, { 18 | getPrice: function() { 19 | return 5 + this.component.getPrice(); 20 | } 21 | }); 22 | 23 | var PedalsDecorator = new Decorator(Bicycle, { 24 | getPrice: function() { 25 | return 24 + this.component.getPrice(); 26 | }, 27 | rotatePedals: function() { 28 | return 'Turning the pedals'; 29 | } 30 | }); 31 | 32 | define("Bicycle", Bicycle) 33 | define("HeadlightDecorator", HeadlightDecorator) 34 | define("PedalsDecorator", PedalsDecorator) 35 | 36 | before(function() { with(this) { 37 | this.bicycle = new Bicycle("Trek", 24) 38 | this.withHeadlights = new HeadlightDecorator(bicycle) 39 | this.withPedals = new PedalsDecorator(bicycle) 40 | this.withBoth = new HeadlightDecorator(withPedals) 41 | }}) 42 | 43 | it("creates classes", function() { with(this) { 44 | assertKindOf( JS.Class, HeadlightDecorator ) 45 | }}) 46 | 47 | it("generates objects of the decorated type", function() { with(this) { 48 | assertKindOf( Bicycle, withHeadlights ) 49 | assertKindOf( Bicycle, withBoth ) 50 | }}) 51 | 52 | it("generates the same API of the decorated class", function() { with(this) { 53 | assertRespondTo( withHeadlights, "getModel" ) 54 | assertRespondTo( withHeadlights, "getPrice" ) 55 | }}) 56 | 57 | it("adds methods specified in the decorating class", function() { with(this) { 58 | assertRespondTo( withPedals, "rotatePedals" ) 59 | assertEqual( "Turning the pedals", withPedals.rotatePedals() ) 60 | }}) 61 | 62 | it("passes undefined method calls down to the component", function() { with(this) { 63 | assertEqual( "Trek", withHeadlights.getModel() ) 64 | assertEqual( "Trek", withPedals.getModel() ) 65 | }}) 66 | 67 | it("allows decorators to call down to the decoree using this.component", function() { with(this) { 68 | assertEqual( 240, bicycle.getPrice() ) 69 | assertEqual( 245, withHeadlights.getPrice() ) 70 | assertEqual( 264, withPedals.getPrice() ) 71 | }}) 72 | 73 | it("allows decorators to be composed", function() { with(this) { 74 | assertEqual( 269, withBoth.getPrice() ) 75 | }}) 76 | 77 | it("allows decorators to wrap any object", function() { with(this) { 78 | var subject = { 79 | getPrice: function() { return 50 }, 80 | getSizes: function() { return ['S', 'M', 'L', 'XL'] } 81 | } 82 | var decorated = new PedalsDecorator(subject) 83 | assertEqual( 74, decorated.getPrice() ) 84 | assertEqual( decorated.getSizes(), subject.getSizes() ) 85 | }}) 86 | }}) 87 | 88 | }) 89 | 90 | -------------------------------------------------------------------------------- /test/specs/forwardable_spec.js: -------------------------------------------------------------------------------- 1 | JS.require('JS.Forwardable', function(Forwardable) { 2 | 3 | JS.ENV.ForwardableSpec = JS.Test.describe(Forwardable, function() { with(this) { 4 | define("Subject", new JS.Class({ 5 | initialize: function() { 6 | this.name = "something"; 7 | }, 8 | setName: function(name) { 9 | this.name = name; 10 | }, 11 | getName: function() { 12 | return this.name; 13 | }, 14 | multiply: function(a,b) { 15 | return a*b; 16 | } 17 | })) 18 | 19 | define("forwardableClass", function() { with(this) { 20 | return new JS.Class({extend: Forwardable, 21 | initialize: function() { 22 | this.subject = new Subject() 23 | } 24 | }) 25 | }}) 26 | 27 | before(function() { with(this) { 28 | this.forwarderClass = forwardableClass() 29 | this.forwarder = new forwarderClass() 30 | }}) 31 | 32 | describe("#defineDelegator", function() { with(this) { 33 | it("defines a forwarding method", function() { with(this) { 34 | forwarderClass.defineDelegator("subject", "getName") 35 | assertRespondTo( forwarder, "getName" ) 36 | assertEqual( "something", forwarder.getName() ) 37 | }}) 38 | 39 | it("passes arguments through when forwarding calls", function() { with(this) { 40 | forwarderClass.defineDelegator("subject", "multiply") 41 | assertEqual( 20, forwarder.multiply(4,5) ) 42 | }}) 43 | 44 | it("uses the named property as the forwarding target", function() { with(this) { 45 | forwarder.target = {getName: function() { return "target name" }} 46 | forwarderClass.defineDelegator("target", "getName") 47 | assertEqual( "target name", forwarder.getName() ) 48 | }}) 49 | 50 | it("defines a forwarding method under a different name", function() { with(this) { 51 | forwarderClass.defineDelegator("subject", "getName", "subjectName") 52 | assertRespondTo( forwarder, "subjectName" ) 53 | assertEqual( "something", forwarder.subjectName() ) 54 | }}) 55 | 56 | it("defines forwarding methods for mutators", function() { with(this) { 57 | forwarderClass.defineDelegator("subject", "getName") 58 | forwarderClass.defineDelegator("subject", "setName") 59 | forwarder.setName("nothing") 60 | assertEqual( "nothing", forwarder.getName() ) 61 | }}) 62 | }}) 63 | 64 | describe("#defineDelegators", function() { with(this) { 65 | it("defines multiple forwarding methods", function() { with(this) { 66 | forwarderClass.defineDelegators("subject", "getName", "setName") 67 | forwarder.setName("nothing") 68 | assertEqual( "nothing", forwarder.getName() ) 69 | }}) 70 | }}) 71 | }}) 72 | 73 | }) 74 | 75 | -------------------------------------------------------------------------------- /test/specs/interface_spec.js: -------------------------------------------------------------------------------- 1 | JS.ENV.InterfaceSpec = JS.Test.describe(JS.Interface, function() { with(this) { 2 | before(function() { with(this) { 3 | this.face = new JS.Interface(["foo", "bar"]) 4 | }}) 5 | 6 | describe("#test", function() { with(this) { 7 | it("returns true iff the object implements all the methods", function() { with(this) { 8 | assert( face.test({foo: function() {}, bar: function() {}}) ) 9 | }}) 10 | 11 | it("returns false iff one of the names does not map to a function", function() { with(this) { 12 | assert( !face.test({foo: function() {}, bar: true}) ) 13 | assert( !face.test({foo: true, bar: function() {}}) ) 14 | }}) 15 | 16 | it("returns false iff one of the names is missing", function() { with(this) { 17 | assert( !face.test({foo: function() {}}) ) 18 | assert( !face.test({bar: function() {}}) ) 19 | }}) 20 | }}) 21 | 22 | describe(".ensure", function() { with(this) { 23 | it("passes iff the object implements all the methods", function() { with(this) { 24 | assertNothingThrown(function() { 25 | face.test({foo: function() {}, bar: function() {}}) 26 | }) 27 | }}) 28 | 29 | it("throws an error iff one of the names does not map to a function", function() { with(this) { 30 | assertThrows(Error, function() { 31 | JS.Interface.ensure({foo: function() {}, bar: true}, face) 32 | }) 33 | assertThrows(Error, function() { 34 | JS.Interface.ensure({foo: true, bar: function() {}}, face) 35 | }) 36 | }}) 37 | 38 | it("throws an error iff one of the names is missing", function() { with(this) { 39 | assertThrows(Error, function() { 40 | JS.Interface.ensure({foo: function() {}}, face) 41 | }) 42 | assertThrows(Error, function() { 43 | JS.Interface.ensure({bar: function() {}}, face) 44 | }) 45 | }}) 46 | 47 | it("throws an error iff the object does not fully implement one of the interfaces", function() { with(this) { 48 | assertThrows(Error, function() { 49 | JS.Interface.ensure({foo: function() {}}, new JS.Interface(["foo"]), new JS.Interface(["bar"])) 50 | }) 51 | assertThrows(Error, function() { 52 | JS.Interface.ensure({bar: function() {}}, new JS.Interface(["foo"]), new JS.Interface(["bar"])) 53 | }) 54 | }}) 55 | }}) 56 | }}) 57 | 58 | -------------------------------------------------------------------------------- /test/specs/method_spec.js: -------------------------------------------------------------------------------- 1 | JS.ENV.MethodSpec = JS.Test.describe(JS.Method, function() { with(this) { 2 | before(function() { with(this) { 3 | this.callable = function(a,b) { return "something" } 4 | this.theModule = new JS.Module({ im_a_method: callable }) 5 | this.theMethod = theModule.instanceMethod("im_a_method") 6 | }}) 7 | 8 | it("should be bootstrapped properly", function() { with(this) { 9 | assertKindOf( JS.Class, JS.Method ) 10 | assertKindOf( JS.Module, JS.Method ) 11 | assertKindOf( JS.Kernel, JS.Method ) 12 | assertEqual( "Method", JS.Method.displayName ) 13 | }}) 14 | 15 | describe("#module", function() { with(this) { 16 | it("refers to the module hosting the method", function() { with(this) { 17 | assertEqual( theModule, theMethod.module ) 18 | }}) 19 | }}) 20 | 21 | describe("#name", function() { with(this) { 22 | it("returns the name of the method", function() { with(this) { 23 | assertEqual( "im_a_method", theMethod.name ) 24 | }}) 25 | }}) 26 | 27 | describe("#callable", function() { with(this) { 28 | it("refers to the JavaScript function the method represents", function() { with(this) { 29 | assertEqual( callable, theMethod.callable ) 30 | }}) 31 | }}) 32 | 33 | describe("#arity", function() { with(this) { 34 | it("gives the number of arguments the method accepts", function() { with(this) { 35 | assertEqual( 2, theMethod.arity ) 36 | }}) 37 | }}) 38 | 39 | describe("#contains", function() { with(this) { 40 | it("returns true if the method's source includes the word", function() { with(this) { 41 | assert( theMethod.contains("return") ) 42 | assert( theMethod.contains("something") ) 43 | }}) 44 | 45 | it("returns false if the method's source does not include the word", function() { with(this) { 46 | assert( !theMethod.contains("nothing") ) 47 | }}) 48 | }}) 49 | }}) 50 | 51 | -------------------------------------------------------------------------------- /test/specs/proxy_spec.js: -------------------------------------------------------------------------------- 1 | JS.require('JS.Proxy', function(Proxy) { 2 | 3 | JS.ENV.ProxySpec = JS.Test.describe(Proxy, function() { with(this) { 4 | describe(Proxy.Virtual, function() { with(this) { 5 | before(function() { with(this) { 6 | this.instances = 0 7 | 8 | this.Subject = new JS.Class({ 9 | initialize: function(name, age) { 10 | instances += 1 11 | this.name = name 12 | this.age = age 13 | }, 14 | 15 | setName: function(a, b) { 16 | this.name = a + " " + b 17 | }, 18 | 19 | getName: function() { return this.name }, 20 | 21 | getAge: function() { return this.age } 22 | }) 23 | 24 | this.Proxy = new Proxy.Virtual(Subject) 25 | this.proxyInstance = new Proxy("jcoglan", 26) 26 | }}) 27 | 28 | it("creates classes", function() { with(this) { 29 | assertKindOf( JS.Class, Proxy ) 30 | }}) 31 | 32 | it("does not instantiate the wrapped class immediately", function() { with(this) { 33 | assertEqual( 0, instances ) 34 | }}) 35 | 36 | it("instantiates the wrapped class when a method is called", function() { with(this) { 37 | proxyInstance.getName() 38 | assertEqual( 1, instances ) 39 | }}) 40 | 41 | it("instantiates the wrapped class once per proxy instance", function() { with(this) { 42 | proxyInstance.getName() 43 | proxyInstance.getName() 44 | assertEqual( 1, instances ) 45 | new Proxy("bart", 10).getName() 46 | assertEqual( 2, instances ) 47 | }}) 48 | 49 | it("passes constructor arguments down to the subject", function() { with(this) { 50 | assertEqual( "jcoglan", proxyInstance.getName() ) 51 | assertEqual( 26, proxyInstance.getAge() ) 52 | }}) 53 | 54 | it("passes method arguments down to the subject", function() { with(this) { 55 | proxyInstance.setName("some", "words") 56 | assertEqual( "some words", proxyInstance.getName() ) 57 | }}) 58 | 59 | describe("a singleton method", function() { with(this) { 60 | before(function() { with(this) { 61 | proxyInstance.extend({ 62 | newMethod: function() { return this.name.toUpperCase() } 63 | }) 64 | }}) 65 | 66 | it("can access the subject's instance variables", function() { with(this) { 67 | assertEqual( "JCOGLAN", proxyInstance.newMethod() ) 68 | }}) 69 | }}) 70 | 71 | describe("a singleton method that calls super", function() { with(this) { 72 | before(function() { with(this) { 73 | proxyInstance.extend({ 74 | getAge: function() { return this.callSuper() * 2 } 75 | }) 76 | }}) 77 | 78 | it("calls the subject's implementation of the method", function() { with(this) { 79 | assertEqual( 52, proxyInstance.getAge() ) 80 | }}) 81 | }}) 82 | }}) 83 | }}) 84 | 85 | }) 86 | 87 | -------------------------------------------------------------------------------- /test/specs/singleton_spec.js: -------------------------------------------------------------------------------- 1 | JS.ENV.SingletonSpec = JS.Test.describe(JS.Singleton, function() { with(this) { 2 | before(function() { with(this) { 3 | this.mixin = new JS.Module() 4 | this.parent = new JS.Class() 5 | }}) 6 | 7 | describe("with no ancestors", function() { with(this) { 8 | before(function() { with(this) { 9 | this.singleton = new JS.Singleton({ foo: function() { return "foo" } }) 10 | }}) 11 | 12 | it("creates an object that inherits from Kernel", function() { with(this) { 13 | assertEqual( [JS.Kernel, singleton.klass, singleton.__eigen__()], 14 | singleton.__eigen__().ancestors() ) 15 | }}) 16 | 17 | it("creates an object with the right methods", function() { with(this) { 18 | assertEqual( "foo", singleton.foo() ) 19 | }}) 20 | }}) 21 | 22 | describe("with a parent class", function() { with(this) { 23 | before(function() { with(this) { 24 | this.singleton = new JS.Singleton(parent) 25 | }}) 26 | 27 | it("creates an object that inherits from the parent", function() { with(this) { 28 | assertEqual( [JS.Kernel, parent, singleton.klass, singleton.__eigen__()], 29 | singleton.__eigen__().ancestors() ) 30 | }}) 31 | }}) 32 | 33 | describe("with a mixin", function() { with(this) { 34 | before(function() { with(this) { 35 | this.singleton = new JS.Singleton({ include: mixin }) 36 | }}) 37 | 38 | it("creates an object that inherits from the mixin", function() { with(this) { 39 | assertEqual( [JS.Kernel, mixin, singleton.klass, singleton.__eigen__()], 40 | singleton.__eigen__().ancestors() ) 41 | }}) 42 | }}) 43 | }}) 44 | 45 | -------------------------------------------------------------------------------- /test/specs/test/test_spec_helpers.js: -------------------------------------------------------------------------------- 1 | JS.ENV.TestSpecHelpers = new JS.Module({ 2 | suite: function(tests) { 3 | return new JS.Class("TestedSuite", JS.Test.Unit.TestCase, tests).suite() 4 | }, 5 | 6 | createTestEnvironment: function() { 7 | this.result = new JS.Test.Unit.TestResult() 8 | this.faults = [] 9 | this.result.addListener(JS.Test.Unit.TestResult.FAULT, this.faults.push, this.faults) 10 | }, 11 | 12 | runTests: function(tests, resume) { 13 | if (tests) this.testcase = this.suite(tests) 14 | JS.Test.showStack = false 15 | this.testcase.run(this.result, function() { 16 | if (resume) resume() 17 | JS.Test.showStack = true 18 | }, function() {}) 19 | }, 20 | 21 | assertTestResult: function(runs, assertions, failures, errors) { with(this) { 22 | __wrapAssertion__(function() { with(this) { 23 | assertEqual( runs, result.runCount(), "Incorrect run count" ) 24 | assertEqual( assertions, result.assertionCount(), "Incorrect assertion count" ) 25 | assertEqual( failures, result.failureCount(), "Incorrect failure count" ) 26 | assertEqual( errors, result.errorCount(), "Incorrect error count" ) 27 | 28 | assertEqual( failures + errors, faults.length ) 29 | }}) 30 | }}, 31 | 32 | assertMessage: function(index, message) { with(this) { 33 | if (typeof index === "string") { 34 | message = index 35 | index = 1 36 | } 37 | 38 | var f = faults[index-1], 39 | t = f.testMetadata(), 40 | e = f.errorMetadata(); 41 | 42 | assertEqual( message, (e.type === "failure" ? "Failure" : "Error") + ":\n" + 43 | t.shortName + "(" + t.context.pop() + "):\n" + 44 | e.message ) 45 | }} 46 | }) 47 | 48 | -------------------------------------------------------------------------------- /test/specs/tsort_spec.js: -------------------------------------------------------------------------------- 1 | JS.require('JS.TSort', 'JS.Hash', function(TSort, Hash) { 2 | 3 | JS.ENV.TSortSpec = JS.Test.describe(TSort, function() { with(this) { 4 | before(function() { with(this) { 5 | this.Hash = new JS.Class(Hash, { 6 | include: TSort, 7 | 8 | tsortEachNode: function(block, context) { 9 | return this.forEachKey(block, context) 10 | }, 11 | 12 | tsortEachChild: function(node, block, context) { 13 | var list = this.fetch(node); 14 | for (var i = 0, n = list.length; i < n; i++) 15 | block.call(context, list[i]); 16 | } 17 | }) 18 | }}) 19 | 20 | describe("with primitive data", function() { with(this) { 21 | describe("with no cycles", function() { with(this) { 22 | before(function() { with(this) { 23 | this.hash = new Hash([ 1, [2,3], 24 | 2, [3], 25 | 3, [], 26 | 4, [] ]) 27 | }}) 28 | 29 | it("sorts the elements topologically", function() { with(this) { 30 | assertEqual( [3,2,1,4], hash.tsort() ) 31 | }}) 32 | 33 | it("identifies strongly connected nodes", function() { with(this) { 34 | assertEqual( [[3],[2],[1],[4]], hash.stronglyConnectedComponents() ) 35 | }}) 36 | }}) 37 | 38 | describe("when there are cycles", function() { with(this) { 39 | before(function() { with(this) { 40 | this.hash = new Hash([ 1, [2,3,4], 41 | 2, [3], 42 | 3, [2], 43 | 4, [1] ]) 44 | }}) 45 | 46 | it("raises an error", function() { with(this) { 47 | assertThrows(TSort.Cyclic, function() { hash.tsort() }) 48 | }}) 49 | 50 | it("identifies strongly connected nodes", function() { with(this) { 51 | assertEqual( [[2,3],[1,4]], hash.stronglyConnectedComponents() ) 52 | }}) 53 | }}) 54 | }}) 55 | 56 | describe("with object data", function() { with(this) { 57 | include(JS.Test.Helpers) 58 | 59 | before(function() { with(this) { 60 | this.TodoItem = new JS.Class("TodoItem", { 61 | initialize: function(priority) { 62 | this.priority = priority 63 | }, 64 | equals: function(other) { 65 | return this.priority == other.priority 66 | }, 67 | hash: function() { 68 | return this.priority 69 | } 70 | }) 71 | }}) 72 | 73 | describe("with no cycles", function() { with(this) { 74 | before(function() { with(this) { 75 | this.hash = new Hash([ new TodoItem(1), [new TodoItem(2),new TodoItem(3)], 76 | new TodoItem(2), [new TodoItem(3)], 77 | new TodoItem(3), [], 78 | new TodoItem(4), [] ]) 79 | }}) 80 | 81 | it("sorts the elements topologically", function() { with(this) { 82 | assertEqual( [3,2,1,4], map(hash.tsort(), "priority") ) 83 | }}) 84 | 85 | it("identifies strongly connected nodes", function() { with(this) { 86 | assertEqual( [[new TodoItem(3)],[new TodoItem(2)],[new TodoItem(1)],[new TodoItem(4)]], 87 | hash.stronglyConnectedComponents() ) 88 | }}) 89 | }}) 90 | }}) 91 | }}) 92 | 93 | }) 94 | 95 | -------------------------------------------------------------------------------- /test/xulenv/application.ini: -------------------------------------------------------------------------------- 1 | [App] 2 | Vendor=JSClass 3 | Name=XULTestEnvironment 4 | Version=0.1 5 | BuildID=20101002 6 | Copyright=Copyright (c) 2010 Aurélio A. Heckert 7 | ID=xultestenv@jsclass.jcoglan.com 8 | 9 | [Gecko] 10 | MinVersion=1.8 11 | MaxVersion=1.9.2.* 12 | 13 | -------------------------------------------------------------------------------- /test/xulenv/chrome/chrome.manifest: -------------------------------------------------------------------------------- 1 | content xultestenv file:content/ 2 | -------------------------------------------------------------------------------- /test/xulenv/chrome/content/jsclass: -------------------------------------------------------------------------------- 1 | ../../../.. -------------------------------------------------------------------------------- /test/xulenv/chrome/content/main.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/xulenv/defaults/preferences/prefs.js: -------------------------------------------------------------------------------- 1 | pref("toolkit.defaultChromeURI", "chrome://xultestenv/content/main.xul"); 2 | --------------------------------------------------------------------------------