├── .gitignore
├── LICENSE
├── README.md
├── lineman
├── .gitignore
├── .npmignore
├── .travis.yml
├── Gruntfile.js
├── README.md
├── app
│ ├── css
│ │ └── style.css
│ ├── img
│ │ └── .gitkeep
│ ├── js
│ │ ├── .gitkeep
│ │ └── problems.coffee
│ ├── pages
│ │ └── index.us
│ ├── static
│ │ └── favicon.ico
│ └── templates
│ │ └── problems.us
├── config
│ ├── application.js
│ ├── files.js
│ ├── lineman.js
│ ├── server.js
│ └── spec.json
├── package.json
├── spec
│ ├── helpers
│ │ ├── helper.js
│ │ ├── jasmine-fixture.js
│ │ ├── jasmine-given.js
│ │ ├── jasmine-only.js
│ │ └── jasmine-stealth.js
│ └── problems-spec.coffee
├── tasks
│ └── .gitkeep
└── vendor
│ ├── css
│ └── .gitkeep
│ ├── img
│ └── .gitkeep
│ └── js
│ ├── jquery.js
│ └── underscore.js
├── node
├── .gitignore
├── Gruntfile.coffee
├── README.md
├── app.coffee
├── lib
│ └── .keep
├── main.js
├── package.json
└── spec
│ ├── e2e
│ ├── .gitkeep
│ ├── get-problem-spec.coffee
│ └── helpers
│ │ ├── globalize-underscore.coffee
│ │ ├── request-methods.coffee
│ │ └── start-server.coffee
│ └── unit
│ ├── .gitkeep
│ └── helpers
│ └── require-subject.coffee
└── standalone
├── .gitignore
├── README.md
├── index.html
├── lib
└── jasmine-1.3.1
│ ├── MIT.LICENSE
│ ├── jasmine-html.js
│ ├── jasmine.css
│ └── jasmine.js
├── main.js
├── package.json
├── spec
├── calculator-spec.coffee
├── helpers
│ ├── helper.js
│ ├── jasmine-given.js
│ ├── jasmine-only.js
│ └── jasmine-stealth.js
└── roman-numeral-converter-spec.coffee
├── src
└── roman-numeral-converter.coffee
└── vendor
├── coffee-script.js
└── underscore.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Justin Searls
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Real-world JavaScript Testing
2 |
3 | ## Schedule Overview
4 |
5 | Time | Topic
6 | --- | ---
7 | 1:00-1:30 | Intro: background, goals, first steps
8 | 1:30-2:15 | Exercise: Simple tests, simple test runner
9 | 2:15-2:30 | Discussion: types of tests
10 | 2:30-2:45 | Break
11 | 2:45-3:45 | Exercise: Node.js web service, Grunt-based integration & unit test runner
12 | 3:45-4:00 | Discussion: Intro to lineman, testem, browser testing
13 | 4:00-5:00 | Exercise: Lineman.js web app, Testem test runner
14 |
15 | ### Intro
16 |
17 | Goal: set expectations and provide a context for the day
18 |
19 | * Set agenda, goals, and constraints
20 | * Solicit requests from attendees of concepts to emphasize or cover ad hoc
21 | * Review vanilla Jasmine API briefly
22 | * Review the jasmine-given CoffeeScript API
23 |
24 | ### Student Exercise: Standalone test runner
25 |
26 | Goal: practice the rhythm and rules of red-green-refactor; understand how simple
27 | Jasmine really is (as a script you drop into a plain HTML page)
28 |
29 | * Provide a pre-packaged standalone runner with CoffeeScript script tag support added
30 | * Pair off and do the Roman Numeral Kata in a 30 minute time box
31 | * Do a short retrospective afterward
32 |
33 |
62 |
63 | ### Discuss outside-in TDD, introduce Node.js, demo
64 |
65 | Goal: discuss how to use TDD to break a large problem down. Use a Node.js example
66 | to demo outside-in TDD in a familiar domain (HTTP controller actions) to Rubyists
67 |
68 | * Explain the [limitations of basic TDD](http://blog.testdouble.com/posts/2014-01-25-the-failures-of-intro-to-tdd.html)
69 | and introduce concepts from GOOS and a reductionist approach
70 | * Introduce Node.js and basic necessary concepts (Common JS packages, etc.)
71 | * Demo grunt, grunt-jasmine-bundle, node-sandboxed-module to break down a large problem into a small one
72 |
73 | ### Student Exercise: Math Quiz Web Service
74 |
75 | Goal: practice outside-in TDD, gain familiarity with Node and one way to write
76 | tests in Node.
77 |
78 | * Students start with a failing integration test
79 | * Starting with an express.js app, break down a nice object model that creates
80 | arithmetic quiz questions and accepts solutions
81 |
82 | Rules of the game:
83 |
84 | * Practice outside-in TDD to break the problem down into multiple units,
85 | multiple files.
86 | * Create a "GET /problem" route, which will generate a new ID, create a random
87 | (easy) arithmetic problem (e.g. "10 ÷ 2") and store it with that ID in memory,
88 | return a structured object back that a user interface could work with
89 | * Create a "GET /problem/:id" route by which a problem can be revisited or shared
90 | * Create a "POST /solution" which takes a problem ID and an answer, responding
91 | with a 422 if the solution is incorrect & a 202 if the solution is correct
92 |
93 | ### Discuss Lineman, Testem, and the tension between TDD and frameworks
94 |
95 | Goal: Familiarize the class with the front-end web tooling we recommend for fat-client
96 | JavaScript applications, set expectations about practicing TDD in the presence of
97 | an application framework
98 |
99 | * Discuss Client/Server separation, why we built Lineman to make that easy to
100 | accomplish
101 | * Demonstrate Testem and how it's totally awesome.
102 | * Illustrate how application frameworks and TDD both try to solve the same problem:
103 | how to prevent code from becoming an unreasonable mess.
104 | * Identify some of the tensions that inevitably arise from attempting TDD in a large framework
105 | * Discuss creating scar tissue for application frameworks and wrappers for third-party dependencies
106 |
107 | ### Student Exercise: Math Quiz Web User Interface
108 |
109 | Goal: Practice some of the more challenging tactics useful when testing DOM interactions, with less of an emphasis on TDD's design benefits.
110 |
111 | Rules of the game:
112 |
113 | * No application frameworks (e.g. Angular, Backbone, Ember), but heavy convenience
114 | libraries like jQuery are okay
115 | * TDD a web interface to the math quiz service that we started in the previous
116 | exercise: render a problem and a response form, POST attempted solutions, give
117 | the user feedback, and move on to new problems
118 | * Use the tactics we learned when tackling legacy jQuery to aggressively push new
119 | abstractions with outside-in TDD, building scar tissue between the application and
120 | the browser
121 |
122 | [Note: the rule above not to use an application framework does not indicate a
123 | negative opinion about application frameworks, but rather that they pose a number
124 | of additional time-consuming challenges when practicing TDD, which we'll discuss
125 | as a group.]
126 |
127 |
135 |
--------------------------------------------------------------------------------
/lineman/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | #ignore node_modules, as the node project is not "deployed" per se: http://www.mikealrogers.com/posts/nodemodules-in-git.html
4 | /node_modules
5 |
6 | /dist
7 | /generated
8 |
9 | .sass-cache
10 |
--------------------------------------------------------------------------------
/lineman/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | #ignore node_modules, as the node project is not "deployed" per se: http://www.mikealrogers.com/posts/nodemodules-in-git.html
4 | /node_modules
5 |
6 | /dist
7 | /generated
8 |
9 | .sass-cache
10 |
--------------------------------------------------------------------------------
/lineman/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 0.10
4 | script: "lineman spec-ci"
5 |
--------------------------------------------------------------------------------
/lineman/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*global module:false*/
2 | module.exports = function(grunt) {
3 | require('./config/lineman').config.grunt.run(grunt);
4 | };
5 |
--------------------------------------------------------------------------------
/lineman/README.md:
--------------------------------------------------------------------------------
1 | # example Lineman project
2 |
3 | Every [Lineman](http://linemanjs.com) app ships with some sensible Jasmine testing defaults.
4 |
5 | ## Getting Lineman installed
6 |
7 | It's recommended you have Lineman installed globally:
8 |
9 | ```
10 | $ npm install -g lineman
11 | $ lineman build #<-- will build the project from a local binary
12 | ```
13 |
14 | Optionally, you may also install and run Lineman locally:
15 |
16 | ```
17 | $ npm install lineman
18 | $ ./node_modules/.bin/lineman build #<-- will build the project from a local binary
19 | ```
20 |
21 | ## Running specs in development
22 |
23 | The lineman TDD workflow requires two long-running processes, so we recommend you run in two separate terminal sessions during development.
24 |
25 | In the first terminal:
26 |
27 | ```
28 | $ lineman run
29 | ```
30 |
31 | Which will spin up a server on [http://localhost:8000](http://localhost:8000) and watch for any meaningful file changes in the project.
32 |
33 | In the second terminal:
34 |
35 | ```
36 | $ lineman spec
37 | ```
38 |
39 | This will start an interactive [testem](https://github.com/airportyh/testem) session. Virtually any browser can attach to testem by navigating to [http://localhost:7357/](http://localhost:7357/). The interactive shell will aggregate each browser's results in tabs (which are navigable with the arrow keys).
40 |
41 | While `lineman run` will watch for file changes to your project, `lineman spec` is configured to trigger a new test run whenever your compiled/concatenated JavaScript sources or specs change.
42 |
43 | ## Running specs in CI
44 |
45 | To run specs in a single-shell, non-interactive, headless environment, as you would for continuous integration, you'll first need [PhantomJS](http://phantomjs.org/download.html) built and on your `PATH`, then run:
46 |
47 | ```
48 | $ lineman spec-ci
49 | ```
50 |
51 | This will first do a full build of your project and then run the tests with testem's CI mode against PhantomJS and report the results in a [TAP](http://en.wikipedia.org/wiki/Test_Anything_Protocol) format.
52 |
53 |
54 |
--------------------------------------------------------------------------------
/lineman/app/css/style.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/lineman/app/css/style.css
--------------------------------------------------------------------------------
/lineman/app/img/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/lineman/app/img/.gitkeep
--------------------------------------------------------------------------------
/lineman/app/js/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/lineman/app/js/.gitkeep
--------------------------------------------------------------------------------
/lineman/app/js/problems.coffee:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/lineman/app/js/problems.coffee
--------------------------------------------------------------------------------
/lineman/app/pages/index.us:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= pkg.name %>
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lineman/app/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/lineman/app/static/favicon.ico
--------------------------------------------------------------------------------
/lineman/app/templates/problems.us:
--------------------------------------------------------------------------------
1 |
2 |
Get new problem
3 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/lineman/config/application.js:
--------------------------------------------------------------------------------
1 | /* Exports a function which returns an object that overrides the default &
2 | * plugin grunt configuration object.
3 | *
4 | * You can familiarize yourself with Lineman's defaults by checking out:
5 | *
6 | * - https://github.com/linemanjs/lineman/blob/master/config/application.coffee
7 | * - https://github.com/linemanjs/lineman/blob/master/config/plugins
8 | *
9 | * You can also ask Lineman's about config from the command line:
10 | *
11 | * $ lineman config #=> to print the entire config
12 | * $ lineman config concat.js #=> to see the JS config for the concat task.
13 | */
14 | module.exports = function(lineman) {
15 | //Override application configuration here. Common examples follow in the comments.
16 | return {
17 |
18 | // API Proxying
19 | //
20 | // During development, you'll likely want to make XHR (AJAX) requests to an API on the same
21 | // port as your lineman development server. By enabling the API proxy and setting the port, all
22 | // requests for paths that don't match a static asset in ./generated will be forwarded to
23 | // whatever service might be running on the specified port.
24 | //
25 | server: {
26 | apiProxy: {
27 | enabled: true,
28 | host: 'localhost',
29 | port: 8080
30 | }
31 | }
32 |
33 | // Sass
34 | //
35 | // Lineman supports Sass via grunt-contrib-sass, which requires you first
36 | // have Ruby installed as well as the `sass` gem. To enable it, comment out the
37 | // following line:
38 | //
39 | // enableSass: false
40 |
41 | // Asset Fingerprints
42 | //
43 | // Lineman can fingerprint your static assets by appending a hash to the filename
44 | // and logging a manifest of logical-to-hashed filenames in dist/assets.json
45 | // via grunt-asset-fingerprint
46 | //
47 | // enableAssetFingerprint: false
48 |
49 | };
50 | };
51 |
--------------------------------------------------------------------------------
/lineman/config/files.js:
--------------------------------------------------------------------------------
1 | /* Exports a function which returns an object that overrides the default &
2 | * plugin file patterns (used widely through the app configuration)
3 | *
4 | * To see the default definitions for Lineman's file paths and globs, see:
5 | *
6 | * - https://github.com/linemanjs/lineman/blob/master/config/files.coffee
7 | */
8 | module.exports = function(lineman) {
9 | //Override file patterns here
10 | return {
11 |
12 | // As an example, to override the file patterns for
13 | // the order in which to load third party JS libs:
14 | //
15 | // js: {
16 | // vendor: [
17 | // "vendor/js/underscore.js",
18 | // "vendor/js/**/*.js"
19 | // ]
20 | // }
21 |
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/lineman/config/lineman.js:
--------------------------------------------------------------------------------
1 | module.exports = require(process.env['LINEMAN_MAIN']);
2 |
--------------------------------------------------------------------------------
/lineman/config/server.js:
--------------------------------------------------------------------------------
1 | /* Define custom server-side HTTP routes for lineman's development server
2 | * These might be as simple as stubbing a little JSON to
3 | * facilitate development of code that interacts with an HTTP service
4 | * (presumably, mirroring one that will be reachable in a live environment).
5 | *
6 | * It's important to remember that any custom endpoints defined here
7 | * will only be available in development, as lineman only builds
8 | * static assets, it can't run server-side code.
9 | *
10 | * This file can be very useful for rapid prototyping or even organically
11 | * defining a spec based on the needs of the client code that emerge.
12 | *
13 | */
14 |
15 | module.exports = {
16 | drawRoutes: function(app) {
17 | // app.get('/api/greeting/:message', function(req, res){
18 | // res.json({ message: "OK, "+req.params.message });
19 | // });
20 | }
21 | };
--------------------------------------------------------------------------------
/lineman/config/spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework" : "jasmine",
3 | "launch_in_dev" : ["Chrome"],
4 | "launch_in_ci" : ["PhantomJS"],
5 | "src_files" : [
6 | "generated/js/app.js",
7 | "generated/js/spec.js"
8 | ]
9 | }
--------------------------------------------------------------------------------
/lineman/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "title": "An HTML/JS/CSS app",
4 | "version": "0.0.1",
5 | "private": true,
6 | "author": {
7 | "name": "John Doe",
8 | "company": "ACME, Inc."
9 | },
10 | "devDependencies": {
11 | "coffee-script": "~1.6.3",
12 | "foreman": "^1.3.0",
13 | "lineman": ">= 0.24.3",
14 | "mocha": "^2.2.4",
15 | "mocha-given": "^0.1.3",
16 | "testium": "^2.6.0"
17 | },
18 | "scripts": {
19 | "start": "lineman run",
20 | "test": "lineman spec-ci"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lineman/spec/helpers/helper.js:
--------------------------------------------------------------------------------
1 | var root = this;
2 |
3 | root.context = root.describe;
4 | root.xcontext = root.xdescribe;
--------------------------------------------------------------------------------
/lineman/spec/helpers/jasmine-fixture.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | jasmine-fixture 1.0.5
4 | Makes injecting HTML snippets into the DOM easy & clean!
5 | site: https://github.com/searls/jasmine-fixture
6 | */
7 |
8 |
9 | (function() {
10 | var createHTMLBlock;
11 |
12 | (function($) {
13 | var jasmineFixture, originalAffix, originalInject, originalJasmineFixture, root, _;
14 | root = this;
15 | originalJasmineFixture = root.jasmineFixture;
16 | originalInject = root.inject;
17 | originalAffix = root.affix;
18 | _ = function(list) {
19 | return {
20 | inject: function(iterator, memo) {
21 | var item, _i, _len, _results;
22 | _results = [];
23 | for (_i = 0, _len = list.length; _i < _len; _i++) {
24 | item = list[_i];
25 | _results.push(memo = iterator(memo, item));
26 | }
27 | return _results;
28 | }
29 | };
30 | };
31 | root.jasmineFixture = function($) {
32 | var $whatsTheRootOf, applyAttributes, defaultConfiguration, defaults, init, injectContents, isReady, isString, itLooksLikeHtml, rootId, tidyUp;
33 | $.fn.affix = root.affix = function(selectorOptions) {
34 | var $top;
35 | $top = null;
36 | _(selectorOptions.split(/[ ](?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) {
37 | var $el;
38 | if (elementSelector === ">") {
39 | return $parent;
40 | }
41 | $el = createHTMLBlock($, elementSelector).appendTo($parent);
42 | $top || ($top = $el);
43 | return $el;
44 | }, $whatsTheRootOf(this));
45 | return $top;
46 | };
47 | $whatsTheRootOf = function(that) {
48 | if (that.jquery != null) {
49 | return that;
50 | } else if ($('#jasmine_content').length > 0) {
51 | return $('#jasmine_content');
52 | } else {
53 | return $('
').appendTo('body');
54 | }
55 | };
56 | afterEach(function() {
57 | return $('#jasmine_content').remove();
58 | });
59 | isReady = false;
60 | rootId = "specContainer";
61 | defaultConfiguration = {
62 | el: "div",
63 | cssClass: "",
64 | id: "",
65 | text: "",
66 | html: "",
67 | defaultAttribute: "class",
68 | attrs: {}
69 | };
70 | defaults = $.extend({}, defaultConfiguration);
71 | $.jasmine = {
72 | inject: function(arg, context) {
73 | var $toInject, config, parent;
74 | if (isReady !== true) {
75 | init();
76 | }
77 | parent = (context ? context : $("#" + rootId));
78 | $toInject = void 0;
79 | if (itLooksLikeHtml(arg)) {
80 | $toInject = $(arg);
81 | } else {
82 | config = $.extend({}, defaults, arg, {
83 | userString: arg
84 | });
85 | $toInject = $("<" + config.el + ">" + config.el + ">");
86 | applyAttributes($toInject, config);
87 | injectContents($toInject, config);
88 | }
89 | return $toInject.appendTo(parent);
90 | },
91 | configure: function(config) {
92 | return $.extend(defaults, config);
93 | },
94 | restoreDefaults: function() {
95 | return defaults = $.extend({}, defaultConfiguration);
96 | },
97 | noConflict: function() {
98 | root.jasmineFixture = originalJasmineFixture;
99 | root.inject = originalInject;
100 | root.affix = originalAffix;
101 | return this;
102 | }
103 | };
104 | $.fn.inject = function(html) {
105 | return $.jasmine.inject(html, $(this));
106 | };
107 | applyAttributes = function($html, config) {
108 | var attrs, key, _results;
109 | attrs = $.extend({}, {
110 | id: config.id,
111 | "class": config["class"] || config.cssClass
112 | }, config.attrs);
113 | if (isString(config.userString)) {
114 | attrs[config.defaultAttribute] = config.userString;
115 | }
116 | _results = [];
117 | for (key in attrs) {
118 | if (attrs[key]) {
119 | _results.push($html.attr(key, attrs[key]));
120 | } else {
121 | _results.push(void 0);
122 | }
123 | }
124 | return _results;
125 | };
126 | injectContents = function($el, config) {
127 | if (config.text && config.html) {
128 | throw "Error: because they conflict, you may only configure inject() to set `html` or `text`, not both! \n\nHTML was: " + config.html + " \n\n Text was: " + config.text;
129 | } else if (config.text) {
130 | return $el.text(config.text);
131 | } else {
132 | if (config.html) {
133 | return $el.html(config.html);
134 | }
135 | }
136 | };
137 | itLooksLikeHtml = function(arg) {
138 | return isString(arg) && arg.indexOf("<") !== -1;
139 | };
140 | isString = function(arg) {
141 | return arg && arg.constructor === String;
142 | };
143 | init = function() {
144 | $("body").append("
");
145 | return isReady = true;
146 | };
147 | tidyUp = function() {
148 | $("#" + rootId).remove();
149 | return isReady = false;
150 | };
151 | $(function($) {
152 | return init();
153 | });
154 | afterEach(function() {
155 | return tidyUp();
156 | });
157 | return $.jasmine;
158 | };
159 | if ($) {
160 | jasmineFixture = root.jasmineFixture($);
161 | return root.inject = root.inject || jasmineFixture.inject;
162 | }
163 | })(window.jQuery);
164 |
165 | createHTMLBlock = (function() {
166 | var bindData, bindEvents, parseAttributes, parseClasses, parseContents, parseEnclosure, parseReferences, parseVariableScope, regAttr, regAttrDfn, regAttrs, regCBrace, regClass, regClasses, regData, regDatas, regEvent, regEvents, regExclamation, regId, regReference, regTag, regTagNotContent, regZenTagDfn;
167 | createHTMLBlock = function($, ZenObject, data, functions, indexes) {
168 | var ZenCode, arr, block, blockAttrs, blockClasses, blockHTML, blockId, blockTag, blocks, el, el2, els, forScope, indexName, inner, len, obj, origZenCode, paren, result, ret, zc, zo;
169 | if ($.isPlainObject(ZenObject)) {
170 | ZenCode = ZenObject.main;
171 | } else {
172 | ZenCode = ZenObject;
173 | ZenObject = {
174 | main: ZenCode
175 | };
176 | }
177 | origZenCode = ZenCode;
178 | if (indexes === undefined) {
179 | indexes = {};
180 | }
181 | if (ZenCode.charAt(0) === "!" || $.isArray(data)) {
182 | if ($.isArray(data)) {
183 | forScope = ZenCode;
184 | } else {
185 | obj = parseEnclosure(ZenCode, "!");
186 | obj = obj.substring(obj.indexOf(":") + 1, obj.length - 1);
187 | forScope = parseVariableScope(ZenCode);
188 | }
189 | while (forScope.charAt(0) === "@") {
190 | forScope = parseVariableScope("!for:!" + parseReferences(forScope, ZenObject));
191 | }
192 | zo = ZenObject;
193 | zo.main = forScope;
194 | el = $();
195 | if (ZenCode.substring(0, 5) === "!for:" || $.isArray(data)) {
196 | if (!$.isArray(data) && obj.indexOf(":") > 0) {
197 | indexName = obj.substring(0, obj.indexOf(":"));
198 | obj = obj.substr(obj.indexOf(":") + 1);
199 | }
200 | arr = ($.isArray(data) ? data : data[obj]);
201 | zc = zo.main;
202 | if ($.isArray(arr) || $.isPlainObject(arr)) {
203 | $.map(arr, function(value, index) {
204 | var next;
205 | zo.main = zc;
206 | if (indexName !== undefined) {
207 | indexes[indexName] = index;
208 | }
209 | if (!$.isPlainObject(value)) {
210 | value = {
211 | value: value
212 | };
213 | }
214 | next = createHTMLBlock($, zo, value, functions, indexes);
215 | if (el.length !== 0) {
216 | return $.each(next, function(index, value) {
217 | return el.push(value);
218 | });
219 | }
220 | });
221 | }
222 | if (!$.isArray(data)) {
223 | ZenCode = ZenCode.substr(obj.length + 6 + forScope.length);
224 | } else {
225 | ZenCode = "";
226 | }
227 | } else if (ZenCode.substring(0, 4) === "!if:") {
228 | result = parseContents("!" + obj + "!", data, indexes);
229 | if (result !== "undefined" || result !== "false" || result !== "") {
230 | el = createHTMLBlock($, zo, data, functions, indexes);
231 | }
232 | ZenCode = ZenCode.substr(obj.length + 5 + forScope.length);
233 | }
234 | ZenObject.main = ZenCode;
235 | } else if (ZenCode.charAt(0) === "(") {
236 | paren = parseEnclosure(ZenCode, "(", ")");
237 | inner = paren.substring(1, paren.length - 1);
238 | ZenCode = ZenCode.substr(paren.length);
239 | zo = ZenObject;
240 | zo.main = inner;
241 | el = createHTMLBlock($, zo, data, functions, indexes);
242 | } else {
243 | blocks = ZenCode.match(regZenTagDfn);
244 | block = blocks[0];
245 | if (block.length === 0) {
246 | return "";
247 | }
248 | if (block.indexOf("@") >= 0) {
249 | ZenCode = parseReferences(ZenCode, ZenObject);
250 | zo = ZenObject;
251 | zo.main = ZenCode;
252 | return createHTMLBlock($, zo, data, functions, indexes);
253 | }
254 | block = parseContents(block, data, indexes);
255 | blockClasses = parseClasses($, block);
256 | if (regId.test(block)) {
257 | blockId = regId.exec(block)[1];
258 | }
259 | blockAttrs = parseAttributes(block, data);
260 | blockTag = (block.charAt(0) === "{" ? "span" : "div");
261 | if (ZenCode.charAt(0) !== "#" && ZenCode.charAt(0) !== "." && ZenCode.charAt(0) !== "{") {
262 | blockTag = regTag.exec(block)[1];
263 | }
264 | if (block.search(regCBrace) !== -1) {
265 | blockHTML = block.match(regCBrace)[1];
266 | }
267 | blockAttrs = $.extend(blockAttrs, {
268 | id: blockId,
269 | "class": blockClasses,
270 | html: blockHTML
271 | });
272 | el = $("<" + blockTag + ">", blockAttrs);
273 | el.attr(blockAttrs);
274 | el = bindEvents(block, el, functions);
275 | el = bindData(block, el, data);
276 | ZenCode = ZenCode.substr(blocks[0].length);
277 | ZenObject.main = ZenCode;
278 | }
279 | if (ZenCode.length > 0) {
280 | if (ZenCode.charAt(0) === ">") {
281 | if (ZenCode.charAt(1) === "(") {
282 | zc = parseEnclosure(ZenCode.substr(1), "(", ")");
283 | ZenCode = ZenCode.substr(zc.length + 1);
284 | } else if (ZenCode.charAt(1) === "!") {
285 | obj = parseEnclosure(ZenCode.substr(1), "!");
286 | forScope = parseVariableScope(ZenCode.substr(1));
287 | zc = obj + forScope;
288 | ZenCode = ZenCode.substr(zc.length + 1);
289 | } else {
290 | len = Math.max(ZenCode.indexOf("+"), ZenCode.length);
291 | zc = ZenCode.substring(1, len);
292 | ZenCode = ZenCode.substr(len);
293 | }
294 | zo = ZenObject;
295 | zo.main = zc;
296 | els = $(createHTMLBlock($, zo, data, functions, indexes));
297 | els.appendTo(el);
298 | }
299 | if (ZenCode.charAt(0) === "+") {
300 | zo = ZenObject;
301 | zo.main = ZenCode.substr(1);
302 | el2 = createHTMLBlock($, zo, data, functions, indexes);
303 | $.each(el2, function(index, value) {
304 | return el.push(value);
305 | });
306 | }
307 | }
308 | ret = el;
309 | return ret;
310 | };
311 | bindData = function(ZenCode, el, data) {
312 | var datas, i, split;
313 | if (ZenCode.search(regDatas) === 0) {
314 | return el;
315 | }
316 | datas = ZenCode.match(regDatas);
317 | if (datas === null) {
318 | return el;
319 | }
320 | i = 0;
321 | while (i < datas.length) {
322 | split = regData.exec(datas[i]);
323 | if (split[3] === undefined) {
324 | $(el).data(split[1], data[split[1]]);
325 | } else {
326 | $(el).data(split[1], data[split[3]]);
327 | }
328 | i++;
329 | }
330 | return el;
331 | };
332 | bindEvents = function(ZenCode, el, functions) {
333 | var bindings, fn, i, split;
334 | if (ZenCode.search(regEvents) === 0) {
335 | return el;
336 | }
337 | bindings = ZenCode.match(regEvents);
338 | if (bindings === null) {
339 | return el;
340 | }
341 | i = 0;
342 | while (i < bindings.length) {
343 | split = regEvent.exec(bindings[i]);
344 | if (split[2] === undefined) {
345 | fn = functions[split[1]];
346 | } else {
347 | fn = functions[split[2]];
348 | }
349 | $(el).bind(split[1], fn);
350 | i++;
351 | }
352 | return el;
353 | };
354 | parseAttributes = function(ZenBlock, data) {
355 | var attrStrs, attrs, i, parts;
356 | if (ZenBlock.search(regAttrDfn) === -1) {
357 | return undefined;
358 | }
359 | attrStrs = ZenBlock.match(regAttrDfn);
360 | attrs = {};
361 | i = 0;
362 | while (i < attrStrs.length) {
363 | parts = regAttr.exec(attrStrs[i]);
364 | attrs[parts[1]] = "";
365 | if (parts[3] !== undefined) {
366 | attrs[parts[1]] = parseContents(parts[3], data);
367 | }
368 | i++;
369 | }
370 | return attrs;
371 | };
372 | parseClasses = function($, ZenBlock) {
373 | var classes, clsString, i;
374 | ZenBlock = ZenBlock.match(regTagNotContent)[0];
375 | if (ZenBlock.search(regClasses) === -1) {
376 | return undefined;
377 | }
378 | classes = ZenBlock.match(regClasses);
379 | clsString = "";
380 | i = 0;
381 | while (i < classes.length) {
382 | clsString += " " + regClass.exec(classes[i])[1];
383 | i++;
384 | }
385 | return $.trim(clsString);
386 | };
387 | parseContents = function(ZenBlock, data, indexes) {
388 | var html;
389 | if (indexes === undefined) {
390 | indexes = {};
391 | }
392 | html = ZenBlock;
393 | if (data === undefined) {
394 | return html;
395 | }
396 | while (regExclamation.test(html)) {
397 | html = html.replace(regExclamation, function(str, str2) {
398 | var begChar, fn, val;
399 | begChar = "";
400 | if (str.indexOf("!for:") > 0 || str.indexOf("!if:") > 0) {
401 | return str;
402 | }
403 | if (str.charAt(0) !== "!") {
404 | begChar = str.charAt(0);
405 | str = str.substring(2, str.length - 1);
406 | }
407 | fn = new Function("data", "indexes", "var r=undefined;" + "with(data){try{r=" + str + ";}catch(e){}}" + "with(indexes){try{if(r===undefined)r=" + str + ";}catch(e){}}" + "return r;");
408 | val = unescape(fn(data, indexes));
409 | return begChar + val;
410 | });
411 | }
412 | html = html.replace(/\\./g, function(str) {
413 | return str.charAt(1);
414 | });
415 | return unescape(html);
416 | };
417 | parseEnclosure = function(ZenCode, open, close, count) {
418 | var index, ret;
419 | if (close === undefined) {
420 | close = open;
421 | }
422 | index = 1;
423 | if (count === undefined) {
424 | count = (ZenCode.charAt(0) === open ? 1 : 0);
425 | }
426 | if (count === 0) {
427 | return;
428 | }
429 | while (count > 0 && index < ZenCode.length) {
430 | if (ZenCode.charAt(index) === close && ZenCode.charAt(index - 1) !== "\\") {
431 | count--;
432 | } else {
433 | if (ZenCode.charAt(index) === open && ZenCode.charAt(index - 1) !== "\\") {
434 | count++;
435 | }
436 | }
437 | index++;
438 | }
439 | ret = ZenCode.substring(0, index);
440 | return ret;
441 | };
442 | parseReferences = function(ZenCode, ZenObject) {
443 | ZenCode = ZenCode.replace(regReference, function(str) {
444 | var fn;
445 | str = str.substr(1);
446 | fn = new Function("objs", "var r=\"\";" + "with(objs){try{" + "r=" + str + ";" + "}catch(e){}}" + "return r;");
447 | return fn(ZenObject, parseReferences);
448 | });
449 | return ZenCode;
450 | };
451 | parseVariableScope = function(ZenCode) {
452 | var forCode, rest, tag;
453 | if (ZenCode.substring(0, 5) !== "!for:" && ZenCode.substring(0, 4) !== "!if:") {
454 | return undefined;
455 | }
456 | forCode = parseEnclosure(ZenCode, "!");
457 | ZenCode = ZenCode.substr(forCode.length);
458 | if (ZenCode.charAt(0) === "(") {
459 | return parseEnclosure(ZenCode, "(", ")");
460 | }
461 | tag = ZenCode.match(regZenTagDfn)[0];
462 | ZenCode = ZenCode.substr(tag.length);
463 | if (ZenCode.length === 0 || ZenCode.charAt(0) === "+") {
464 | return tag;
465 | } else if (ZenCode.charAt(0) === ">") {
466 | rest = "";
467 | rest = parseEnclosure(ZenCode.substr(1), "(", ")", 1);
468 | return tag + ">" + rest;
469 | }
470 | return undefined;
471 | };
472 | regZenTagDfn = /([#\.\@]?[\w-]+|\[([\w-!?=:"']+(="([^"]|\\")+")? {0,})+\]|\~[\w$]+=[\w$]+|&[\w$]+(=[\w$]+)?|[#\.\@]?!([^!]|\\!)+!){0,}(\{([^\}]|\\\})+\})?/i;
473 | regTag = /(\w+)/i;
474 | regId = /#([\w-!]+)/i;
475 | regTagNotContent = /((([#\.]?[\w-]+)?(\[([\w!]+(="([^"]|\\")+")? {0,})+\])?)+)/i;
476 | regClasses = /(\.[\w-]+)/g;
477 | regClass = /\.([\w-]+)/i;
478 | regReference = /(@[\w$_][\w$_\d]+)/i;
479 | regAttrDfn = /(\[([\w-!]+(="?([^"]|\\")+"?)? {0,})+\])/ig;
480 | regAttrs = /([\w-!]+(="([^"]|\\")+")?)/g;
481 | regAttr = /([\w-!]+)(="?(([^"\]]|\\")+)"?)?/i;
482 | regCBrace = /\{(([^\}]|\\\})+)\}/i;
483 | regExclamation = /(?:([^\\]|^))!([^!]|\\!)+!/g;
484 | regEvents = /\~[\w$]+(=[\w$]+)?/g;
485 | regEvent = /\~([\w$]+)=([\w$]+)/i;
486 | regDatas = /&[\w$]+(=[\w$]+)?/g;
487 | regData = /&([\w$]+)(=([\w$]+))?/i;
488 | return createHTMLBlock;
489 | })();
490 |
491 | }).call(this);
492 |
--------------------------------------------------------------------------------
/lineman/spec/helpers/jasmine-given.js:
--------------------------------------------------------------------------------
1 | /* jasmine-given - 2.6.0
2 | * Adds a Given-When-Then DSL to jasmine as an alternative style for specs
3 | * https://github.com/searls/jasmine-given
4 | */
5 | /* jasmine-matcher-wrapper - 0.0.2
6 | * Wraps Jasmine 1.x matchers for use with Jasmine 2
7 | * https://github.com/testdouble/jasmine-matcher-wrapper
8 | */
9 | (function() {
10 | var __slice = [].slice,
11 | __hasProp = {}.hasOwnProperty;
12 |
13 | (function(jasmine) {
14 | var comparatorFor;
15 | if (jasmine == null) {
16 | return typeof console !== "undefined" && console !== null ? console.warn("jasmine was not found. Skipping jasmine-matcher-wrapper. Verify your script load order.") : void 0;
17 | }
18 | if (jasmine.matcherWrapper != null) {
19 | return;
20 | }
21 | comparatorFor = function(matcher, isNot) {
22 | return function() {
23 | var actual, context, message, params, pass, _ref;
24 | actual = arguments[0], params = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
25 | context = {
26 | actual: actual,
27 | isNot: isNot
28 | };
29 | pass = matcher.apply(context, params);
30 | if (isNot) {
31 | pass = !pass;
32 | }
33 | if (!pass) {
34 | message = (_ref = context.message) != null ? _ref.apply(context, params) : void 0;
35 | }
36 | return {
37 | pass: pass,
38 | message: message
39 | };
40 | };
41 | };
42 | return jasmine.matcherWrapper = {
43 | wrap: function(matchers) {
44 | var matcher, name, wrappedMatchers;
45 | if (jasmine.addMatchers == null) {
46 | return matchers;
47 | }
48 | wrappedMatchers = {};
49 | for (name in matchers) {
50 | if (!__hasProp.call(matchers, name)) continue;
51 | matcher = matchers[name];
52 | wrappedMatchers[name] = function() {
53 | return {
54 | compare: comparatorFor(matcher, false),
55 | negativeCompare: comparatorFor(matcher, true)
56 | };
57 | };
58 | }
59 | return wrappedMatchers;
60 | }
61 | };
62 | })(jasmine || getJasmineRequireObj());
63 |
64 | }).call(this);
65 |
66 | (function() {
67 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
68 |
69 | (function(jasmine) {
70 | var Waterfall, additionalInsightsForErrorMessage, apparentReferenceError, attemptedEquality, comparisonInsight, currentSpec, declareJasmineSpec, deepEqualsNotice, doneWrapperFor, evalInContextOfSpec, finalStatementFrom, getBlock, invariantList, mostRecentExpectations, mostRecentlyUsed, o, root, stringifyExpectation, wasComparison, whenList, wrapAsExpectations;
71 | mostRecentlyUsed = null;
72 | root = this;
73 | currentSpec = null;
74 | beforeEach(function() {
75 | return currentSpec = this;
76 | });
77 | root.Given = function() {
78 | mostRecentlyUsed = root.Given;
79 | return beforeEach(getBlock(arguments));
80 | };
81 | whenList = [];
82 | root.When = function() {
83 | var b;
84 | mostRecentlyUsed = root.When;
85 | b = getBlock(arguments);
86 | beforeEach(function() {
87 | return whenList.push(b);
88 | });
89 | return afterEach(function() {
90 | return whenList.pop();
91 | });
92 | };
93 | invariantList = [];
94 | root.Invariant = function() {
95 | var invariantBehavior;
96 | mostRecentlyUsed = root.Invariant;
97 | invariantBehavior = getBlock(arguments);
98 | beforeEach(function() {
99 | return invariantList.push(invariantBehavior);
100 | });
101 | return afterEach(function() {
102 | return invariantList.pop();
103 | });
104 | };
105 | getBlock = function(thing) {
106 | var assignResultTo, setupFunction;
107 | setupFunction = o(thing).firstThat(function(arg) {
108 | return o(arg).isFunction();
109 | });
110 | assignResultTo = o(thing).firstThat(function(arg) {
111 | return o(arg).isString();
112 | });
113 | return doneWrapperFor(setupFunction, function(done) {
114 | var context, result;
115 | context = currentSpec;
116 | result = setupFunction.call(context, done);
117 | if (assignResultTo) {
118 | if (!context[assignResultTo]) {
119 | return context[assignResultTo] = result;
120 | } else {
121 | throw new Error("Unfortunately, the variable '" + assignResultTo + "' is already assigned to: " + context[assignResultTo]);
122 | }
123 | }
124 | });
125 | };
126 | mostRecentExpectations = null;
127 | declareJasmineSpec = function(specArgs, itFunction) {
128 | var expectationFunction, expectations, label;
129 | if (itFunction == null) {
130 | itFunction = it;
131 | }
132 | label = o(specArgs).firstThat(function(arg) {
133 | return o(arg).isString();
134 | });
135 | expectationFunction = o(specArgs).firstThat(function(arg) {
136 | return o(arg).isFunction();
137 | });
138 | mostRecentlyUsed = root.subsequentThen;
139 | mostRecentExpectations = expectations = [expectationFunction];
140 | itFunction("then " + (label != null ? label : stringifyExpectation(expectations)), function(jasmineDone) {
141 | var userCommands;
142 | userCommands = [].concat(whenList, invariantList, wrapAsExpectations(expectations));
143 | return new Waterfall(userCommands, jasmineDone).flow();
144 | });
145 | return {
146 | Then: subsequentThen,
147 | And: subsequentThen
148 | };
149 | };
150 | wrapAsExpectations = function(expectations) {
151 | var expectation, i, _i, _len, _results;
152 | _results = [];
153 | for (i = _i = 0, _len = expectations.length; _i < _len; i = ++_i) {
154 | expectation = expectations[i];
155 | _results.push((function(expectation, i) {
156 | return doneWrapperFor(expectation, function(maybeDone) {
157 | return expect(expectation).not.toHaveReturnedFalseFromThen(currentSpec, i + 1, maybeDone);
158 | });
159 | })(expectation, i));
160 | }
161 | return _results;
162 | };
163 | doneWrapperFor = function(func, toWrap) {
164 | if (func.length === 0) {
165 | return function() {
166 | return toWrap();
167 | };
168 | } else {
169 | return function(done) {
170 | return toWrap(done);
171 | };
172 | }
173 | };
174 | root.Then = function() {
175 | return declareJasmineSpec(arguments);
176 | };
177 | root.Then.only = function() {
178 | return declareJasmineSpec(arguments, it.only);
179 | };
180 | root.subsequentThen = function(additionalExpectation) {
181 | mostRecentExpectations.push(additionalExpectation);
182 | return this;
183 | };
184 | mostRecentlyUsed = root.Given;
185 | root.And = function() {
186 | return mostRecentlyUsed.apply(this, jasmine.util.argsToArray(arguments));
187 | };
188 | o = function(thing) {
189 | return {
190 | isFunction: function() {
191 | return Object.prototype.toString.call(thing) === "[object Function]";
192 | },
193 | isString: function() {
194 | return Object.prototype.toString.call(thing) === "[object String]";
195 | },
196 | firstThat: function(test) {
197 | var i;
198 | i = 0;
199 | while (i < thing.length) {
200 | if (test(thing[i]) === true) {
201 | return thing[i];
202 | }
203 | i++;
204 | }
205 | return void 0;
206 | }
207 | };
208 | };
209 | jasmine._given = {
210 | matchers: {
211 | toHaveReturnedFalseFromThen: function(context, n, done) {
212 | var e, exception, result;
213 | result = false;
214 | exception = void 0;
215 | try {
216 | result = this.actual.call(context, done);
217 | } catch (_error) {
218 | e = _error;
219 | exception = e;
220 | }
221 | this.message = function() {
222 | var msg, stringyExpectation;
223 | stringyExpectation = stringifyExpectation(this.actual);
224 | msg = "Then clause" + (n > 1 ? " #" + n : "") + " `" + stringyExpectation + "` failed by ";
225 | if (exception) {
226 | msg += "throwing: " + exception.toString();
227 | } else {
228 | msg += "returning false";
229 | }
230 | msg += additionalInsightsForErrorMessage(stringyExpectation);
231 | return msg;
232 | };
233 | return result === false;
234 | }
235 | }
236 | };
237 | stringifyExpectation = function(expectation) {
238 | var matches;
239 | matches = expectation.toString().replace(/\n/g, '').match(/function\s?\(.*\)\s?{\s*(return\s+)?(.*?)(;)?\s*}/i);
240 | if (matches && matches.length >= 3) {
241 | return matches[2].replace(/\s+/g, ' ');
242 | } else {
243 | return "";
244 | }
245 | };
246 | additionalInsightsForErrorMessage = function(expectationString) {
247 | var comparison, expectation;
248 | expectation = finalStatementFrom(expectationString);
249 | if (comparison = wasComparison(expectation)) {
250 | return comparisonInsight(expectation, comparison);
251 | } else {
252 | return "";
253 | }
254 | };
255 | finalStatementFrom = function(expectationString) {
256 | var multiStatement;
257 | if (multiStatement = expectationString.match(/.*return (.*)/)) {
258 | return multiStatement[multiStatement.length - 1];
259 | } else {
260 | return expectationString;
261 | }
262 | };
263 | wasComparison = function(expectation) {
264 | var comparator, comparison, left, right, s;
265 | if (comparison = expectation.match(/(.*) (===|!==|==|!=|>|>=|<|<=) (.*)/)) {
266 | s = comparison[0], left = comparison[1], comparator = comparison[2], right = comparison[3];
267 | return {
268 | left: left,
269 | comparator: comparator,
270 | right: right
271 | };
272 | }
273 | };
274 | comparisonInsight = function(expectation, comparison) {
275 | var left, msg, right;
276 | left = evalInContextOfSpec(comparison.left);
277 | right = evalInContextOfSpec(comparison.right);
278 | if (apparentReferenceError(left) && apparentReferenceError(right)) {
279 | return "";
280 | }
281 | msg = "\n\nThis comparison was detected:\n " + expectation + "\n " + left + " " + comparison.comparator + " " + right;
282 | if (attemptedEquality(left, right, comparison.comparator)) {
283 | msg += "\n\n" + (deepEqualsNotice(comparison.left, comparison.right));
284 | }
285 | return msg;
286 | };
287 | apparentReferenceError = function(result) {
288 | return /^";
299 | }
300 | };
301 | attemptedEquality = function(left, right, comparator) {
302 | var _ref;
303 | if (!(comparator === "==" || comparator === "===")) {
304 | return false;
305 | }
306 | if (((_ref = jasmine.matchersUtil) != null ? _ref.equals : void 0) != null) {
307 | return jasmine.matchersUtil.equals(left, right);
308 | } else {
309 | return jasmine.getEnv().equals_(left, right);
310 | }
311 | };
312 | deepEqualsNotice = function(left, right) {
313 | return "However, these items are deeply equal! Try an expectation like this instead:\n expect(" + left + ").toEqual(" + right + ")";
314 | };
315 | Waterfall = (function() {
316 | function Waterfall(functions, finalCallback) {
317 | var func, _i, _len, _ref;
318 | if (functions == null) {
319 | functions = [];
320 | }
321 | this.flow = __bind(this.flow, this);
322 | this.invokeFinalCallbackIfNecessary = __bind(this.invokeFinalCallbackIfNecessary, this);
323 | this.asyncTaskCompleted = __bind(this.asyncTaskCompleted, this);
324 | this.functions = functions.slice(0);
325 | this.finalCallback = finalCallback;
326 | this.asyncCount = 0;
327 | _ref = this.functions;
328 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
329 | func = _ref[_i];
330 | if (func.length > 0) {
331 | this.asyncCount += 1;
332 | }
333 | }
334 | }
335 |
336 | Waterfall.prototype.asyncTaskCompleted = function() {
337 | this.asyncCount -= 1;
338 | return this.flow();
339 | };
340 |
341 | Waterfall.prototype.invokeFinalCallbackIfNecessary = function() {
342 | if (this.asyncCount === 0) {
343 | if (typeof this.finalCallback === "function") {
344 | this.finalCallback();
345 | }
346 | return this.finalCallback = void 0;
347 | }
348 | };
349 |
350 | Waterfall.prototype.flow = function() {
351 | var func;
352 | if (this.functions.length === 0) {
353 | return this.invokeFinalCallbackIfNecessary();
354 | }
355 | func = this.functions.shift();
356 | if (func.length > 0) {
357 | return func(this.asyncTaskCompleted);
358 | } else {
359 | func();
360 | return this.flow();
361 | }
362 | };
363 |
364 | return Waterfall;
365 |
366 | })();
367 | return beforeEach(function() {
368 | if (jasmine.addMatchers != null) {
369 | return jasmine.addMatchers(jasmine.matcherWrapper.wrap(jasmine._given.matchers));
370 | } else {
371 | return this.addMatchers(jasmine._given.matchers);
372 | }
373 | });
374 | })(jasmine);
375 |
376 | }).call(this);
377 |
--------------------------------------------------------------------------------
/lineman/spec/helpers/jasmine-only.js:
--------------------------------------------------------------------------------
1 | /* jasmine-only - 0.1.0
2 | * Exclusivity spec helpers for jasmine: `describe.only` and `it.only`
3 | * https://github.com/davemo/jasmine-only
4 | */
5 | (function() {
6 | var __hasProp = {}.hasOwnProperty,
7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
8 |
9 | (function(jasmine) {
10 | var describeOnly, env, itOnly, root;
11 |
12 | root = this;
13 | env = jasmine.getEnv();
14 | describeOnly = function(description, specDefinitions) {
15 | var suite;
16 |
17 | suite = new jasmine.Suite(this, description, null, this.currentSuite);
18 | suite.exclusive_ = 1;
19 | this.exclusive_ = Math.max(this.exclusive_, 1);
20 | return this.describe_(suite, specDefinitions);
21 | };
22 | itOnly = function(description, func) {
23 | var spec;
24 |
25 | spec = this.it(description, func);
26 | spec.exclusive_ = 2;
27 | this.exclusive_ = 2;
28 | return spec;
29 | };
30 | env.exclusive_ = 0;
31 | env.describe = function(description, specDefinitions) {
32 | var suite;
33 |
34 | suite = new jasmine.Suite(this, description, null, this.currentSuite);
35 | return this.describe_(suite, specDefinitions);
36 | };
37 | env.describe_ = function(suite, specDefinitions) {
38 | var declarationError, e, parentSuite;
39 |
40 | parentSuite = this.currentSuite;
41 | if (parentSuite) {
42 | parentSuite.add(suite);
43 | } else {
44 | this.currentRunner_.add(suite);
45 | }
46 | this.currentSuite = suite;
47 | declarationError = null;
48 | try {
49 | specDefinitions.call(suite);
50 | } catch (_error) {
51 | e = _error;
52 | declarationError = e;
53 | }
54 | if (declarationError) {
55 | this.it("encountered a declaration exception", function() {
56 | throw declarationError;
57 | });
58 | }
59 | this.currentSuite = parentSuite;
60 | return suite;
61 | };
62 | env.specFilter = function(spec) {
63 | return this.exclusive_ <= spec.exclusive_;
64 | };
65 | env.describe.only = function() {
66 | return describeOnly.apply(env, arguments);
67 | };
68 | env.it.only = function() {
69 | return itOnly.apply(env, arguments);
70 | };
71 | root.describe.only = function(description, specDefinitions) {
72 | return env.describe.only(description, specDefinitions);
73 | };
74 | root.it.only = function(description, func) {
75 | return env.it.only(description, func);
76 | };
77 | root.iit = root.it.only;
78 | root.ddescribe = root.describe.only;
79 | jasmine.Spec = (function(_super) {
80 | __extends(Spec, _super);
81 |
82 | function Spec(env, suite, description) {
83 | this.exclusive_ = suite.exclusive_;
84 | Spec.__super__.constructor.call(this, env, suite, description);
85 | }
86 |
87 | return Spec;
88 |
89 | })(jasmine.Spec);
90 | return jasmine.Suite = (function(_super) {
91 | __extends(Suite, _super);
92 |
93 | function Suite(env, suite, specDefinitions, parentSuite) {
94 | this.exclusive_ = parentSuite && parentSuite.exclusive_ || 0;
95 | Suite.__super__.constructor.call(this, env, suite, specDefinitions, parentSuite);
96 | }
97 |
98 | return Suite;
99 |
100 | })(jasmine.Suite);
101 | })(jasmine);
102 |
103 | }).call(this);
104 |
--------------------------------------------------------------------------------
/lineman/spec/helpers/jasmine-stealth.js:
--------------------------------------------------------------------------------
1 | /* jasmine-stealth - 0.0.15
2 | * Makes Jasmine spies a bit more robust
3 | * https://github.com/searls/jasmine-stealth
4 | */
5 | (function() {
6 | var __hasProp = {}.hasOwnProperty,
7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
8 |
9 | (function() {
10 | var Captor, fake, root, stubChainer, unfakes, whatToDoWhenTheSpyGetsCalled, _;
11 | root = this;
12 | _ = function(obj) {
13 | return {
14 | each: function(iterator) {
15 | var item, _i, _len, _results;
16 | _results = [];
17 | for (_i = 0, _len = obj.length; _i < _len; _i++) {
18 | item = obj[_i];
19 | _results.push(iterator(item));
20 | }
21 | return _results;
22 | },
23 | isFunction: function() {
24 | return Object.prototype.toString.call(obj) === "[object Function]";
25 | },
26 | isString: function() {
27 | return Object.prototype.toString.call(obj) === "[object String]";
28 | }
29 | };
30 | };
31 | root.spyOnConstructor = function(owner, classToFake, methodsToSpy) {
32 | var fakeClass, spies;
33 | if (methodsToSpy == null) {
34 | methodsToSpy = [];
35 | }
36 | if (_(methodsToSpy).isString()) {
37 | methodsToSpy = [methodsToSpy];
38 | }
39 | spies = {
40 | constructor: jasmine.createSpy("" + classToFake + "'s constructor")
41 | };
42 | fakeClass = (function() {
43 | function _Class() {
44 | spies.constructor.apply(this, arguments);
45 | }
46 |
47 | return _Class;
48 |
49 | })();
50 | _(methodsToSpy).each(function(methodName) {
51 | spies[methodName] = jasmine.createSpy("" + classToFake + "#" + methodName);
52 | return fakeClass.prototype[methodName] = function() {
53 | return spies[methodName].apply(this, arguments);
54 | };
55 | });
56 | fake(owner, classToFake, fakeClass);
57 | return spies;
58 | };
59 | unfakes = [];
60 | afterEach(function() {
61 | _(unfakes).each(function(u) {
62 | return u();
63 | });
64 | return unfakes = [];
65 | });
66 | fake = function(owner, thingToFake, newThing) {
67 | var originalThing;
68 | originalThing = owner[thingToFake];
69 | owner[thingToFake] = newThing;
70 | return unfakes.push(function() {
71 | return owner[thingToFake] = originalThing;
72 | });
73 | };
74 | root.stubFor = root.spyOn;
75 | jasmine.createStub = jasmine.createSpy;
76 | jasmine.createStubObj = function(baseName, stubbings) {
77 | var name, obj, stubbing;
78 | if (stubbings.constructor === Array) {
79 | return jasmine.createSpyObj(baseName, stubbings);
80 | } else {
81 | obj = {};
82 | for (name in stubbings) {
83 | stubbing = stubbings[name];
84 | obj[name] = jasmine.createSpy(baseName + "." + name);
85 | if (_(stubbing).isFunction()) {
86 | obj[name].andCallFake(stubbing);
87 | } else {
88 | obj[name].andReturn(stubbing);
89 | }
90 | }
91 | return obj;
92 | }
93 | };
94 | whatToDoWhenTheSpyGetsCalled = function(spy) {
95 | var matchesStub, priorPlan;
96 | matchesStub = function(stubbing, args, context) {
97 | switch (stubbing.type) {
98 | case "args":
99 | return jasmine.getEnv().equals_(stubbing.ifThis, jasmine.util.argsToArray(args));
100 | case "context":
101 | return jasmine.getEnv().equals_(stubbing.ifThis, context);
102 | }
103 | };
104 | priorPlan = spy.plan;
105 | return spy.andCallFake(function() {
106 | var i, stubbing;
107 | i = 0;
108 | while (i < spy._stealth_stubbings.length) {
109 | stubbing = spy._stealth_stubbings[i];
110 | if (matchesStub(stubbing, arguments, this)) {
111 | if (stubbing.satisfaction === "callFake") {
112 | return stubbing.thenThat.apply(stubbing, arguments);
113 | } else {
114 | return stubbing.thenThat;
115 | }
116 | }
117 | i++;
118 | }
119 | return priorPlan.apply(spy, arguments);
120 | });
121 | };
122 | jasmine.Spy.prototype.whenContext = function(context) {
123 | var spy;
124 | spy = this;
125 | spy._stealth_stubbings || (spy._stealth_stubbings = []);
126 | whatToDoWhenTheSpyGetsCalled(spy);
127 | return stubChainer(spy, "context", context);
128 | };
129 | jasmine.Spy.prototype.when = function() {
130 | var ifThis, spy;
131 | spy = this;
132 | ifThis = jasmine.util.argsToArray(arguments);
133 | spy._stealth_stubbings || (spy._stealth_stubbings = []);
134 | whatToDoWhenTheSpyGetsCalled(spy);
135 | return stubChainer(spy, "args", ifThis);
136 | };
137 | stubChainer = function(spy, type, ifThis) {
138 | var addStubbing;
139 | addStubbing = function(satisfaction) {
140 | return function(thenThat) {
141 | spy._stealth_stubbings.push({
142 | type: type,
143 | ifThis: ifThis,
144 | satisfaction: satisfaction,
145 | thenThat: thenThat
146 | });
147 | return spy;
148 | };
149 | };
150 | return {
151 | thenReturn: addStubbing("return"),
152 | thenCallFake: addStubbing("callFake")
153 | };
154 | };
155 | jasmine.Spy.prototype.mostRecentCallThat = function(callThat, context) {
156 | var i;
157 | i = this.calls.length - 1;
158 | while (i >= 0) {
159 | if (callThat.call(context || this, this.calls[i]) === true) {
160 | return this.calls[i];
161 | }
162 | i--;
163 | }
164 | };
165 | jasmine.Matchers.ArgThat = (function(_super) {
166 | __extends(ArgThat, _super);
167 |
168 | function ArgThat(matcher) {
169 | this.matcher = matcher;
170 | }
171 |
172 | ArgThat.prototype.jasmineMatches = function(actual) {
173 | return this.matcher(actual);
174 | };
175 |
176 | return ArgThat;
177 |
178 | })(jasmine.Matchers.Any);
179 | jasmine.Matchers.ArgThat.prototype.matches = jasmine.Matchers.ArgThat.prototype.jasmineMatches;
180 | jasmine.argThat = function(expected) {
181 | return new jasmine.Matchers.ArgThat(expected);
182 | };
183 | jasmine.Matchers.Capture = (function(_super) {
184 | __extends(Capture, _super);
185 |
186 | function Capture(captor) {
187 | this.captor = captor;
188 | }
189 |
190 | Capture.prototype.jasmineMatches = function(actual) {
191 | this.captor.value = actual;
192 | return true;
193 | };
194 |
195 | return Capture;
196 |
197 | })(jasmine.Matchers.Any);
198 | jasmine.Matchers.Capture.prototype.matches = jasmine.Matchers.Capture.prototype.jasmineMatches;
199 | Captor = (function() {
200 | function Captor() {}
201 |
202 | Captor.prototype.capture = function() {
203 | return new jasmine.Matchers.Capture(this);
204 | };
205 |
206 | return Captor;
207 |
208 | })();
209 | return jasmine.captor = function() {
210 | return new Captor();
211 | };
212 | })();
213 |
214 | }).call(this);
215 |
--------------------------------------------------------------------------------
/lineman/spec/problems-spec.coffee:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/lineman/spec/problems-spec.coffee
--------------------------------------------------------------------------------
/lineman/tasks/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/lineman/tasks/.gitkeep
--------------------------------------------------------------------------------
/lineman/vendor/css/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/lineman/vendor/css/.gitkeep
--------------------------------------------------------------------------------
/lineman/vendor/img/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/lineman/vendor/img/.gitkeep
--------------------------------------------------------------------------------
/lineman/vendor/js/underscore.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.4.4
2 | // ===================
3 |
4 | // > http://underscorejs.org
5 | // > (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
6 | // > Underscore may be freely distributed under the MIT license.
7 |
8 | // Baseline setup
9 | // --------------
10 | (function() {
11 |
12 | // Establish the root object, `window` in the browser, or `global` on the server.
13 | var root = this;
14 |
15 | // Save the previous value of the `_` variable.
16 | var previousUnderscore = root._;
17 |
18 | // Establish the object that gets returned to break out of a loop iteration.
19 | var breaker = {};
20 |
21 | // Save bytes in the minified (but not gzipped) version:
22 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
23 |
24 | // Create quick reference variables for speed access to core prototypes.
25 | var push = ArrayProto.push,
26 | slice = ArrayProto.slice,
27 | concat = ArrayProto.concat,
28 | toString = ObjProto.toString,
29 | hasOwnProperty = ObjProto.hasOwnProperty;
30 |
31 | // All **ECMAScript 5** native function implementations that we hope to use
32 | // are declared here.
33 | var
34 | nativeForEach = ArrayProto.forEach,
35 | nativeMap = ArrayProto.map,
36 | nativeReduce = ArrayProto.reduce,
37 | nativeReduceRight = ArrayProto.reduceRight,
38 | nativeFilter = ArrayProto.filter,
39 | nativeEvery = ArrayProto.every,
40 | nativeSome = ArrayProto.some,
41 | nativeIndexOf = ArrayProto.indexOf,
42 | nativeLastIndexOf = ArrayProto.lastIndexOf,
43 | nativeIsArray = Array.isArray,
44 | nativeKeys = Object.keys,
45 | nativeBind = FuncProto.bind;
46 |
47 | // Create a safe reference to the Underscore object for use below.
48 | var _ = function(obj) {
49 | if (obj instanceof _) return obj;
50 | if (!(this instanceof _)) return new _(obj);
51 | this._wrapped = obj;
52 | };
53 |
54 | // Export the Underscore object for **Node.js**, with
55 | // backwards-compatibility for the old `require()` API. If we're in
56 | // the browser, add `_` as a global object via a string identifier,
57 | // for Closure Compiler "advanced" mode.
58 | if (typeof exports !== 'undefined') {
59 | if (typeof module !== 'undefined' && module.exports) {
60 | exports = module.exports = _;
61 | }
62 | exports._ = _;
63 | } else {
64 | root._ = _;
65 | }
66 |
67 | // Current version.
68 | _.VERSION = '1.4.4';
69 |
70 | // Collection Functions
71 | // --------------------
72 |
73 | // The cornerstone, an `each` implementation, aka `forEach`.
74 | // Handles objects with the built-in `forEach`, arrays, and raw objects.
75 | // Delegates to **ECMAScript 5**'s native `forEach` if available.
76 | var each = _.each = _.forEach = function(obj, iterator, context) {
77 | if (obj == null) return;
78 | if (nativeForEach && obj.forEach === nativeForEach) {
79 | obj.forEach(iterator, context);
80 | } else if (obj.length === +obj.length) {
81 | for (var i = 0, l = obj.length; i < l; i++) {
82 | if (iterator.call(context, obj[i], i, obj) === breaker) return;
83 | }
84 | } else {
85 | for (var key in obj) {
86 | if (_.has(obj, key)) {
87 | if (iterator.call(context, obj[key], key, obj) === breaker) return;
88 | }
89 | }
90 | }
91 | };
92 |
93 | // Return the results of applying the iterator to each element.
94 | // Delegates to **ECMAScript 5**'s native `map` if available.
95 | _.map = _.collect = function(obj, iterator, context) {
96 | var results = [];
97 | if (obj == null) return results;
98 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
99 | each(obj, function(value, index, list) {
100 | results[results.length] = iterator.call(context, value, index, list);
101 | });
102 | return results;
103 | };
104 |
105 | var reduceError = 'Reduce of empty array with no initial value';
106 |
107 | // **Reduce** builds up a single result from a list of values, aka `inject`,
108 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
109 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
110 | var initial = arguments.length > 2;
111 | if (obj == null) obj = [];
112 | if (nativeReduce && obj.reduce === nativeReduce) {
113 | if (context) iterator = _.bind(iterator, context);
114 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
115 | }
116 | each(obj, function(value, index, list) {
117 | if (!initial) {
118 | memo = value;
119 | initial = true;
120 | } else {
121 | memo = iterator.call(context, memo, value, index, list);
122 | }
123 | });
124 | if (!initial) throw new TypeError(reduceError);
125 | return memo;
126 | };
127 |
128 | // The right-associative version of reduce, also known as `foldr`.
129 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
130 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
131 | var initial = arguments.length > 2;
132 | if (obj == null) obj = [];
133 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
134 | if (context) iterator = _.bind(iterator, context);
135 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
136 | }
137 | var length = obj.length;
138 | if (length !== +length) {
139 | var keys = _.keys(obj);
140 | length = keys.length;
141 | }
142 | each(obj, function(value, index, list) {
143 | index = keys ? keys[--length] : --length;
144 | if (!initial) {
145 | memo = obj[index];
146 | initial = true;
147 | } else {
148 | memo = iterator.call(context, memo, obj[index], index, list);
149 | }
150 | });
151 | if (!initial) throw new TypeError(reduceError);
152 | return memo;
153 | };
154 |
155 | // Return the first value which passes a truth test. Aliased as `detect`.
156 | _.find = _.detect = function(obj, iterator, context) {
157 | var result;
158 | any(obj, function(value, index, list) {
159 | if (iterator.call(context, value, index, list)) {
160 | result = value;
161 | return true;
162 | }
163 | });
164 | return result;
165 | };
166 |
167 | // Return all the elements that pass a truth test.
168 | // Delegates to **ECMAScript 5**'s native `filter` if available.
169 | // Aliased as `select`.
170 | _.filter = _.select = function(obj, iterator, context) {
171 | var results = [];
172 | if (obj == null) return results;
173 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
174 | each(obj, function(value, index, list) {
175 | if (iterator.call(context, value, index, list)) results[results.length] = value;
176 | });
177 | return results;
178 | };
179 |
180 | // Return all the elements for which a truth test fails.
181 | _.reject = function(obj, iterator, context) {
182 | return _.filter(obj, function(value, index, list) {
183 | return !iterator.call(context, value, index, list);
184 | }, context);
185 | };
186 |
187 | // Determine whether all of the elements match a truth test.
188 | // Delegates to **ECMAScript 5**'s native `every` if available.
189 | // Aliased as `all`.
190 | _.every = _.all = function(obj, iterator, context) {
191 | iterator || (iterator = _.identity);
192 | var result = true;
193 | if (obj == null) return result;
194 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
195 | each(obj, function(value, index, list) {
196 | if (!(result = result && iterator.call(context, value, index, list))) return breaker;
197 | });
198 | return !!result;
199 | };
200 |
201 | // Determine if at least one element in the object matches a truth test.
202 | // Delegates to **ECMAScript 5**'s native `some` if available.
203 | // Aliased as `any`.
204 | var any = _.some = _.any = function(obj, iterator, context) {
205 | iterator || (iterator = _.identity);
206 | var result = false;
207 | if (obj == null) return result;
208 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
209 | each(obj, function(value, index, list) {
210 | if (result || (result = iterator.call(context, value, index, list))) return breaker;
211 | });
212 | return !!result;
213 | };
214 |
215 | // Determine if the array or object contains a given value (using `===`).
216 | // Aliased as `include`.
217 | _.contains = _.include = function(obj, target) {
218 | if (obj == null) return false;
219 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
220 | return any(obj, function(value) {
221 | return value === target;
222 | });
223 | };
224 |
225 | // Invoke a method (with arguments) on every item in a collection.
226 | _.invoke = function(obj, method) {
227 | var args = slice.call(arguments, 2);
228 | var isFunc = _.isFunction(method);
229 | return _.map(obj, function(value) {
230 | return (isFunc ? method : value[method]).apply(value, args);
231 | });
232 | };
233 |
234 | // Convenience version of a common use case of `map`: fetching a property.
235 | _.pluck = function(obj, key) {
236 | return _.map(obj, function(value){ return value[key]; });
237 | };
238 |
239 | // Convenience version of a common use case of `filter`: selecting only objects
240 | // containing specific `key:value` pairs.
241 | _.where = function(obj, attrs, first) {
242 | if (_.isEmpty(attrs)) return first ? null : [];
243 | return _[first ? 'find' : 'filter'](obj, function(value) {
244 | for (var key in attrs) {
245 | if (attrs[key] !== value[key]) return false;
246 | }
247 | return true;
248 | });
249 | };
250 |
251 | // Convenience version of a common use case of `find`: getting the first object
252 | // containing specific `key:value` pairs.
253 | _.findWhere = function(obj, attrs) {
254 | return _.where(obj, attrs, true);
255 | };
256 |
257 | // Return the maximum element or (element-based computation).
258 | // Can't optimize arrays of integers longer than 65,535 elements.
259 | // See: https://bugs.webkit.org/show_bug.cgi?id=80797
260 | _.max = function(obj, iterator, context) {
261 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
262 | return Math.max.apply(Math, obj);
263 | }
264 | if (!iterator && _.isEmpty(obj)) return -Infinity;
265 | var result = {computed : -Infinity, value: -Infinity};
266 | each(obj, function(value, index, list) {
267 | var computed = iterator ? iterator.call(context, value, index, list) : value;
268 | computed >= result.computed && (result = {value : value, computed : computed});
269 | });
270 | return result.value;
271 | };
272 |
273 | // Return the minimum element (or element-based computation).
274 | _.min = function(obj, iterator, context) {
275 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
276 | return Math.min.apply(Math, obj);
277 | }
278 | if (!iterator && _.isEmpty(obj)) return Infinity;
279 | var result = {computed : Infinity, value: Infinity};
280 | each(obj, function(value, index, list) {
281 | var computed = iterator ? iterator.call(context, value, index, list) : value;
282 | computed < result.computed && (result = {value : value, computed : computed});
283 | });
284 | return result.value;
285 | };
286 |
287 | // Shuffle an array.
288 | _.shuffle = function(obj) {
289 | var rand;
290 | var index = 0;
291 | var shuffled = [];
292 | each(obj, function(value) {
293 | rand = _.random(index++);
294 | shuffled[index - 1] = shuffled[rand];
295 | shuffled[rand] = value;
296 | });
297 | return shuffled;
298 | };
299 |
300 | // An internal function to generate lookup iterators.
301 | var lookupIterator = function(value) {
302 | return _.isFunction(value) ? value : function(obj){ return obj[value]; };
303 | };
304 |
305 | // Sort the object's values by a criterion produced by an iterator.
306 | _.sortBy = function(obj, value, context) {
307 | var iterator = lookupIterator(value);
308 | return _.pluck(_.map(obj, function(value, index, list) {
309 | return {
310 | value : value,
311 | index : index,
312 | criteria : iterator.call(context, value, index, list)
313 | };
314 | }).sort(function(left, right) {
315 | var a = left.criteria;
316 | var b = right.criteria;
317 | if (a !== b) {
318 | if (a > b || a === void 0) return 1;
319 | if (a < b || b === void 0) return -1;
320 | }
321 | return left.index < right.index ? -1 : 1;
322 | }), 'value');
323 | };
324 |
325 | // An internal function used for aggregate "group by" operations.
326 | var group = function(obj, value, context, behavior) {
327 | var result = {};
328 | var iterator = lookupIterator(value || _.identity);
329 | each(obj, function(value, index) {
330 | var key = iterator.call(context, value, index, obj);
331 | behavior(result, key, value);
332 | });
333 | return result;
334 | };
335 |
336 | // Groups the object's values by a criterion. Pass either a string attribute
337 | // to group by, or a function that returns the criterion.
338 | _.groupBy = function(obj, value, context) {
339 | return group(obj, value, context, function(result, key, value) {
340 | (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
341 | });
342 | };
343 |
344 | // Counts instances of an object that group by a certain criterion. Pass
345 | // either a string attribute to count by, or a function that returns the
346 | // criterion.
347 | _.countBy = function(obj, value, context) {
348 | return group(obj, value, context, function(result, key) {
349 | if (!_.has(result, key)) result[key] = 0;
350 | result[key]++;
351 | });
352 | };
353 |
354 | // Use a comparator function to figure out the smallest index at which
355 | // an object should be inserted so as to maintain order. Uses binary search.
356 | _.sortedIndex = function(array, obj, iterator, context) {
357 | iterator = iterator == null ? _.identity : lookupIterator(iterator);
358 | var value = iterator.call(context, obj);
359 | var low = 0, high = array.length;
360 | while (low < high) {
361 | var mid = (low + high) >>> 1;
362 | iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
363 | }
364 | return low;
365 | };
366 |
367 | // Safely convert anything iterable into a real, live array.
368 | _.toArray = function(obj) {
369 | if (!obj) return [];
370 | if (_.isArray(obj)) return slice.call(obj);
371 | if (obj.length === +obj.length) return _.map(obj, _.identity);
372 | return _.values(obj);
373 | };
374 |
375 | // Return the number of elements in an object.
376 | _.size = function(obj) {
377 | if (obj == null) return 0;
378 | return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
379 | };
380 |
381 | // Array Functions
382 | // ---------------
383 |
384 | // Get the first element of an array. Passing **n** will return the first N
385 | // values in the array. Aliased as `head` and `take`. The **guard** check
386 | // allows it to work with `_.map`.
387 | _.first = _.head = _.take = function(array, n, guard) {
388 | if (array == null) return void 0;
389 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
390 | };
391 |
392 | // Returns everything but the last entry of the array. Especially useful on
393 | // the arguments object. Passing **n** will return all the values in
394 | // the array, excluding the last N. The **guard** check allows it to work with
395 | // `_.map`.
396 | _.initial = function(array, n, guard) {
397 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
398 | };
399 |
400 | // Get the last element of an array. Passing **n** will return the last N
401 | // values in the array. The **guard** check allows it to work with `_.map`.
402 | _.last = function(array, n, guard) {
403 | if (array == null) return void 0;
404 | if ((n != null) && !guard) {
405 | return slice.call(array, Math.max(array.length - n, 0));
406 | } else {
407 | return array[array.length - 1];
408 | }
409 | };
410 |
411 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
412 | // Especially useful on the arguments object. Passing an **n** will return
413 | // the rest N values in the array. The **guard**
414 | // check allows it to work with `_.map`.
415 | _.rest = _.tail = _.drop = function(array, n, guard) {
416 | return slice.call(array, (n == null) || guard ? 1 : n);
417 | };
418 |
419 | // Trim out all falsy values from an array.
420 | _.compact = function(array) {
421 | return _.filter(array, _.identity);
422 | };
423 |
424 | // Internal implementation of a recursive `flatten` function.
425 | var flatten = function(input, shallow, output) {
426 | each(input, function(value) {
427 | if (_.isArray(value)) {
428 | shallow ? push.apply(output, value) : flatten(value, shallow, output);
429 | } else {
430 | output.push(value);
431 | }
432 | });
433 | return output;
434 | };
435 |
436 | // Return a completely flattened version of an array.
437 | _.flatten = function(array, shallow) {
438 | return flatten(array, shallow, []);
439 | };
440 |
441 | // Return a version of the array that does not contain the specified value(s).
442 | _.without = function(array) {
443 | return _.difference(array, slice.call(arguments, 1));
444 | };
445 |
446 | // Produce a duplicate-free version of the array. If the array has already
447 | // been sorted, you have the option of using a faster algorithm.
448 | // Aliased as `unique`.
449 | _.uniq = _.unique = function(array, isSorted, iterator, context) {
450 | if (_.isFunction(isSorted)) {
451 | context = iterator;
452 | iterator = isSorted;
453 | isSorted = false;
454 | }
455 | var initial = iterator ? _.map(array, iterator, context) : array;
456 | var results = [];
457 | var seen = [];
458 | each(initial, function(value, index) {
459 | if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
460 | seen.push(value);
461 | results.push(array[index]);
462 | }
463 | });
464 | return results;
465 | };
466 |
467 | // Produce an array that contains the union: each distinct element from all of
468 | // the passed-in arrays.
469 | _.union = function() {
470 | return _.uniq(concat.apply(ArrayProto, arguments));
471 | };
472 |
473 | // Produce an array that contains every item shared between all the
474 | // passed-in arrays.
475 | _.intersection = function(array) {
476 | var rest = slice.call(arguments, 1);
477 | return _.filter(_.uniq(array), function(item) {
478 | return _.every(rest, function(other) {
479 | return _.indexOf(other, item) >= 0;
480 | });
481 | });
482 | };
483 |
484 | // Take the difference between one array and a number of other arrays.
485 | // Only the elements present in just the first array will remain.
486 | _.difference = function(array) {
487 | var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
488 | return _.filter(array, function(value){ return !_.contains(rest, value); });
489 | };
490 |
491 | // Zip together multiple lists into a single array -- elements that share
492 | // an index go together.
493 | _.zip = function() {
494 | var args = slice.call(arguments);
495 | var length = _.max(_.pluck(args, 'length'));
496 | var results = new Array(length);
497 | for (var i = 0; i < length; i++) {
498 | results[i] = _.pluck(args, "" + i);
499 | }
500 | return results;
501 | };
502 |
503 | // Converts lists into objects. Pass either a single array of `[key, value]`
504 | // pairs, or two parallel arrays of the same length -- one of keys, and one of
505 | // the corresponding values.
506 | _.object = function(list, values) {
507 | if (list == null) return {};
508 | var result = {};
509 | for (var i = 0, l = list.length; i < l; i++) {
510 | if (values) {
511 | result[list[i]] = values[i];
512 | } else {
513 | result[list[i][0]] = list[i][1];
514 | }
515 | }
516 | return result;
517 | };
518 |
519 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
520 | // we need this function. Return the position of the first occurrence of an
521 | // item in an array, or -1 if the item is not included in the array.
522 | // Delegates to **ECMAScript 5**'s native `indexOf` if available.
523 | // If the array is large and already in sort order, pass `true`
524 | // for **isSorted** to use binary search.
525 | _.indexOf = function(array, item, isSorted) {
526 | if (array == null) return -1;
527 | var i = 0, l = array.length;
528 | if (isSorted) {
529 | if (typeof isSorted == 'number') {
530 | i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
531 | } else {
532 | i = _.sortedIndex(array, item);
533 | return array[i] === item ? i : -1;
534 | }
535 | }
536 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
537 | for (; i < l; i++) if (array[i] === item) return i;
538 | return -1;
539 | };
540 |
541 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
542 | _.lastIndexOf = function(array, item, from) {
543 | if (array == null) return -1;
544 | var hasIndex = from != null;
545 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
546 | return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
547 | }
548 | var i = (hasIndex ? from : array.length);
549 | while (i--) if (array[i] === item) return i;
550 | return -1;
551 | };
552 |
553 | // Generate an integer Array containing an arithmetic progression. A port of
554 | // the native Python `range()` function. See
555 | // [the Python documentation](http://docs.python.org/library/functions.html#range).
556 | _.range = function(start, stop, step) {
557 | if (arguments.length <= 1) {
558 | stop = start || 0;
559 | start = 0;
560 | }
561 | step = arguments[2] || 1;
562 |
563 | var len = Math.max(Math.ceil((stop - start) / step), 0);
564 | var idx = 0;
565 | var range = new Array(len);
566 |
567 | while(idx < len) {
568 | range[idx++] = start;
569 | start += step;
570 | }
571 |
572 | return range;
573 | };
574 |
575 | // Function (ahem) Functions
576 | // ------------------
577 |
578 | // Create a function bound to a given object (assigning `this`, and arguments,
579 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
580 | // available.
581 | _.bind = function(func, context) {
582 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
583 | var args = slice.call(arguments, 2);
584 | return function() {
585 | return func.apply(context, args.concat(slice.call(arguments)));
586 | };
587 | };
588 |
589 | // Partially apply a function by creating a version that has had some of its
590 | // arguments pre-filled, without changing its dynamic `this` context.
591 | _.partial = function(func) {
592 | var args = slice.call(arguments, 1);
593 | return function() {
594 | return func.apply(this, args.concat(slice.call(arguments)));
595 | };
596 | };
597 |
598 | // Bind all of an object's methods to that object. Useful for ensuring that
599 | // all callbacks defined on an object belong to it.
600 | _.bindAll = function(obj) {
601 | var funcs = slice.call(arguments, 1);
602 | if (funcs.length === 0) funcs = _.functions(obj);
603 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
604 | return obj;
605 | };
606 |
607 | // Memoize an expensive function by storing its results.
608 | _.memoize = function(func, hasher) {
609 | var memo = {};
610 | hasher || (hasher = _.identity);
611 | return function() {
612 | var key = hasher.apply(this, arguments);
613 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
614 | };
615 | };
616 |
617 | // Delays a function for the given number of milliseconds, and then calls
618 | // it with the arguments supplied.
619 | _.delay = function(func, wait) {
620 | var args = slice.call(arguments, 2);
621 | return setTimeout(function(){ return func.apply(null, args); }, wait);
622 | };
623 |
624 | // Defers a function, scheduling it to run after the current call stack has
625 | // cleared.
626 | _.defer = function(func) {
627 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
628 | };
629 |
630 | // Returns a function, that, when invoked, will only be triggered at most once
631 | // during a given window of time.
632 | _.throttle = function(func, wait) {
633 | var context, args, timeout, result;
634 | var previous = 0;
635 | var later = function() {
636 | previous = new Date;
637 | timeout = null;
638 | result = func.apply(context, args);
639 | };
640 | return function() {
641 | var now = new Date;
642 | var remaining = wait - (now - previous);
643 | context = this;
644 | args = arguments;
645 | if (remaining <= 0) {
646 | clearTimeout(timeout);
647 | timeout = null;
648 | previous = now;
649 | result = func.apply(context, args);
650 | } else if (!timeout) {
651 | timeout = setTimeout(later, remaining);
652 | }
653 | return result;
654 | };
655 | };
656 |
657 | // Returns a function, that, as long as it continues to be invoked, will not
658 | // be triggered. The function will be called after it stops being called for
659 | // N milliseconds. If `immediate` is passed, trigger the function on the
660 | // leading edge, instead of the trailing.
661 | _.debounce = function(func, wait, immediate) {
662 | var timeout, result;
663 | return function() {
664 | var context = this, args = arguments;
665 | var later = function() {
666 | timeout = null;
667 | if (!immediate) result = func.apply(context, args);
668 | };
669 | var callNow = immediate && !timeout;
670 | clearTimeout(timeout);
671 | timeout = setTimeout(later, wait);
672 | if (callNow) result = func.apply(context, args);
673 | return result;
674 | };
675 | };
676 |
677 | // Returns a function that will be executed at most one time, no matter how
678 | // often you call it. Useful for lazy initialization.
679 | _.once = function(func) {
680 | var ran = false, memo;
681 | return function() {
682 | if (ran) return memo;
683 | ran = true;
684 | memo = func.apply(this, arguments);
685 | func = null;
686 | return memo;
687 | };
688 | };
689 |
690 | // Returns the first function passed as an argument to the second,
691 | // allowing you to adjust arguments, run code before and after, and
692 | // conditionally execute the original function.
693 | _.wrap = function(func, wrapper) {
694 | return function() {
695 | var args = [func];
696 | push.apply(args, arguments);
697 | return wrapper.apply(this, args);
698 | };
699 | };
700 |
701 | // Returns a function that is the composition of a list of functions, each
702 | // consuming the return value of the function that follows.
703 | _.compose = function() {
704 | var funcs = arguments;
705 | return function() {
706 | var args = arguments;
707 | for (var i = funcs.length - 1; i >= 0; i--) {
708 | args = [funcs[i].apply(this, args)];
709 | }
710 | return args[0];
711 | };
712 | };
713 |
714 | // Returns a function that will only be executed after being called N times.
715 | _.after = function(times, func) {
716 | if (times <= 0) return func();
717 | return function() {
718 | if (--times < 1) {
719 | return func.apply(this, arguments);
720 | }
721 | };
722 | };
723 |
724 | // Object Functions
725 | // ----------------
726 |
727 | // Retrieve the names of an object's properties.
728 | // Delegates to **ECMAScript 5**'s native `Object.keys`
729 | _.keys = nativeKeys || function(obj) {
730 | if (obj !== Object(obj)) throw new TypeError('Invalid object');
731 | var keys = [];
732 | for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
733 | return keys;
734 | };
735 |
736 | // Retrieve the values of an object's properties.
737 | _.values = function(obj) {
738 | var values = [];
739 | for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
740 | return values;
741 | };
742 |
743 | // Convert an object into a list of `[key, value]` pairs.
744 | _.pairs = function(obj) {
745 | var pairs = [];
746 | for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
747 | return pairs;
748 | };
749 |
750 | // Invert the keys and values of an object. The values must be serializable.
751 | _.invert = function(obj) {
752 | var result = {};
753 | for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
754 | return result;
755 | };
756 |
757 | // Return a sorted list of the function names available on the object.
758 | // Aliased as `methods`
759 | _.functions = _.methods = function(obj) {
760 | var names = [];
761 | for (var key in obj) {
762 | if (_.isFunction(obj[key])) names.push(key);
763 | }
764 | return names.sort();
765 | };
766 |
767 | // Extend a given object with all the properties in passed-in object(s).
768 | _.extend = function(obj) {
769 | each(slice.call(arguments, 1), function(source) {
770 | if (source) {
771 | for (var prop in source) {
772 | obj[prop] = source[prop];
773 | }
774 | }
775 | });
776 | return obj;
777 | };
778 |
779 | // Return a copy of the object only containing the whitelisted properties.
780 | _.pick = function(obj) {
781 | var copy = {};
782 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
783 | each(keys, function(key) {
784 | if (key in obj) copy[key] = obj[key];
785 | });
786 | return copy;
787 | };
788 |
789 | // Return a copy of the object without the blacklisted properties.
790 | _.omit = function(obj) {
791 | var copy = {};
792 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
793 | for (var key in obj) {
794 | if (!_.contains(keys, key)) copy[key] = obj[key];
795 | }
796 | return copy;
797 | };
798 |
799 | // Fill in a given object with default properties.
800 | _.defaults = function(obj) {
801 | each(slice.call(arguments, 1), function(source) {
802 | if (source) {
803 | for (var prop in source) {
804 | if (obj[prop] == null) obj[prop] = source[prop];
805 | }
806 | }
807 | });
808 | return obj;
809 | };
810 |
811 | // Create a (shallow-cloned) duplicate of an object.
812 | _.clone = function(obj) {
813 | if (!_.isObject(obj)) return obj;
814 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
815 | };
816 |
817 | // Invokes interceptor with the obj, and then returns obj.
818 | // The primary purpose of this method is to "tap into" a method chain, in
819 | // order to perform operations on intermediate results within the chain.
820 | _.tap = function(obj, interceptor) {
821 | interceptor(obj);
822 | return obj;
823 | };
824 |
825 | // Internal recursive comparison function for `isEqual`.
826 | var eq = function(a, b, aStack, bStack) {
827 | // Identical objects are equal. `0 === -0`, but they aren't identical.
828 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
829 | if (a === b) return a !== 0 || 1 / a == 1 / b;
830 | // A strict comparison is necessary because `null == undefined`.
831 | if (a == null || b == null) return a === b;
832 | // Unwrap any wrapped objects.
833 | if (a instanceof _) a = a._wrapped;
834 | if (b instanceof _) b = b._wrapped;
835 | // Compare `[[Class]]` names.
836 | var className = toString.call(a);
837 | if (className != toString.call(b)) return false;
838 | switch (className) {
839 | // Strings, numbers, dates, and booleans are compared by value.
840 | case '[object String]':
841 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
842 | // equivalent to `new String("5")`.
843 | return a == String(b);
844 | case '[object Number]':
845 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
846 | // other numeric values.
847 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
848 | case '[object Date]':
849 | case '[object Boolean]':
850 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their
851 | // millisecond representations. Note that invalid dates with millisecond representations
852 | // of `NaN` are not equivalent.
853 | return +a == +b;
854 | // RegExps are compared by their source patterns and flags.
855 | case '[object RegExp]':
856 | return a.source == b.source &&
857 | a.global == b.global &&
858 | a.multiline == b.multiline &&
859 | a.ignoreCase == b.ignoreCase;
860 | }
861 | if (typeof a != 'object' || typeof b != 'object') return false;
862 | // Assume equality for cyclic structures. The algorithm for detecting cyclic
863 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
864 | var length = aStack.length;
865 | while (length--) {
866 | // Linear search. Performance is inversely proportional to the number of
867 | // unique nested structures.
868 | if (aStack[length] == a) return bStack[length] == b;
869 | }
870 | // Add the first object to the stack of traversed objects.
871 | aStack.push(a);
872 | bStack.push(b);
873 | var size = 0, result = true;
874 | // Recursively compare objects and arrays.
875 | if (className == '[object Array]') {
876 | // Compare array lengths to determine if a deep comparison is necessary.
877 | size = a.length;
878 | result = size == b.length;
879 | if (result) {
880 | // Deep compare the contents, ignoring non-numeric properties.
881 | while (size--) {
882 | if (!(result = eq(a[size], b[size], aStack, bStack))) break;
883 | }
884 | }
885 | } else {
886 | // Objects with different constructors are not equivalent, but `Object`s
887 | // from different frames are.
888 | var aCtor = a.constructor, bCtor = b.constructor;
889 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
890 | _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
891 | return false;
892 | }
893 | // Deep compare objects.
894 | for (var key in a) {
895 | if (_.has(a, key)) {
896 | // Count the expected number of properties.
897 | size++;
898 | // Deep compare each member.
899 | if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
900 | }
901 | }
902 | // Ensure that both objects contain the same number of properties.
903 | if (result) {
904 | for (key in b) {
905 | if (_.has(b, key) && !(size--)) break;
906 | }
907 | result = !size;
908 | }
909 | }
910 | // Remove the first object from the stack of traversed objects.
911 | aStack.pop();
912 | bStack.pop();
913 | return result;
914 | };
915 |
916 | // Perform a deep comparison to check if two objects are equal.
917 | _.isEqual = function(a, b) {
918 | return eq(a, b, [], []);
919 | };
920 |
921 | // Is a given array, string, or object empty?
922 | // An "empty" object has no enumerable own-properties.
923 | _.isEmpty = function(obj) {
924 | if (obj == null) return true;
925 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
926 | for (var key in obj) if (_.has(obj, key)) return false;
927 | return true;
928 | };
929 |
930 | // Is a given value a DOM element?
931 | _.isElement = function(obj) {
932 | return !!(obj && obj.nodeType === 1);
933 | };
934 |
935 | // Is a given value an array?
936 | // Delegates to ECMA5's native Array.isArray
937 | _.isArray = nativeIsArray || function(obj) {
938 | return toString.call(obj) == '[object Array]';
939 | };
940 |
941 | // Is a given variable an object?
942 | _.isObject = function(obj) {
943 | return obj === Object(obj);
944 | };
945 |
946 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
947 | each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
948 | _['is' + name] = function(obj) {
949 | return toString.call(obj) == '[object ' + name + ']';
950 | };
951 | });
952 |
953 | // Define a fallback version of the method in browsers (ahem, IE), where
954 | // there isn't any inspectable "Arguments" type.
955 | if (!_.isArguments(arguments)) {
956 | _.isArguments = function(obj) {
957 | return !!(obj && _.has(obj, 'callee'));
958 | };
959 | }
960 |
961 | // Optimize `isFunction` if appropriate.
962 | if (typeof (/./) !== 'function') {
963 | _.isFunction = function(obj) {
964 | return typeof obj === 'function';
965 | };
966 | }
967 |
968 | // Is a given object a finite number?
969 | _.isFinite = function(obj) {
970 | return isFinite(obj) && !isNaN(parseFloat(obj));
971 | };
972 |
973 | // Is the given value `NaN`? (NaN is the only number which does not equal itself).
974 | _.isNaN = function(obj) {
975 | return _.isNumber(obj) && obj != +obj;
976 | };
977 |
978 | // Is a given value a boolean?
979 | _.isBoolean = function(obj) {
980 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
981 | };
982 |
983 | // Is a given value equal to null?
984 | _.isNull = function(obj) {
985 | return obj === null;
986 | };
987 |
988 | // Is a given variable undefined?
989 | _.isUndefined = function(obj) {
990 | return obj === void 0;
991 | };
992 |
993 | // Shortcut function for checking if an object has a given property directly
994 | // on itself (in other words, not on a prototype).
995 | _.has = function(obj, key) {
996 | return hasOwnProperty.call(obj, key);
997 | };
998 |
999 | // Utility Functions
1000 | // -----------------
1001 |
1002 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
1003 | // previous owner. Returns a reference to the Underscore object.
1004 | _.noConflict = function() {
1005 | root._ = previousUnderscore;
1006 | return this;
1007 | };
1008 |
1009 | // Keep the identity function around for default iterators.
1010 | _.identity = function(value) {
1011 | return value;
1012 | };
1013 |
1014 | // Run a function **n** times.
1015 | _.times = function(n, iterator, context) {
1016 | var accum = Array(n);
1017 | for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
1018 | return accum;
1019 | };
1020 |
1021 | // Return a random integer between min and max (inclusive).
1022 | _.random = function(min, max) {
1023 | if (max == null) {
1024 | max = min;
1025 | min = 0;
1026 | }
1027 | return min + Math.floor(Math.random() * (max - min + 1));
1028 | };
1029 |
1030 | // List of HTML entities for escaping.
1031 | var entityMap = {
1032 | escape: {
1033 | '&': '&',
1034 | '<': '<',
1035 | '>': '>',
1036 | '"': '"',
1037 | "'": ''',
1038 | '/': '/'
1039 | }
1040 | };
1041 | entityMap.unescape = _.invert(entityMap.escape);
1042 |
1043 | // Regexes containing the keys and values listed immediately above.
1044 | var entityRegexes = {
1045 | escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
1046 | unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
1047 | };
1048 |
1049 | // Functions for escaping and unescaping strings to/from HTML interpolation.
1050 | _.each(['escape', 'unescape'], function(method) {
1051 | _[method] = function(string) {
1052 | if (string == null) return '';
1053 | return ('' + string).replace(entityRegexes[method], function(match) {
1054 | return entityMap[method][match];
1055 | });
1056 | };
1057 | });
1058 |
1059 | // If the value of the named property is a function then invoke it;
1060 | // otherwise, return it.
1061 | _.result = function(object, property) {
1062 | if (object == null) return null;
1063 | var value = object[property];
1064 | return _.isFunction(value) ? value.call(object) : value;
1065 | };
1066 |
1067 | // Add your own custom functions to the Underscore object.
1068 | _.mixin = function(obj) {
1069 | each(_.functions(obj), function(name){
1070 | var func = _[name] = obj[name];
1071 | _.prototype[name] = function() {
1072 | var args = [this._wrapped];
1073 | push.apply(args, arguments);
1074 | return result.call(this, func.apply(_, args));
1075 | };
1076 | });
1077 | };
1078 |
1079 | // Generate a unique integer id (unique within the entire client session).
1080 | // Useful for temporary DOM ids.
1081 | var idCounter = 0;
1082 | _.uniqueId = function(prefix) {
1083 | var id = ++idCounter + '';
1084 | return prefix ? prefix + id : id;
1085 | };
1086 |
1087 | // By default, Underscore uses ERB-style template delimiters, change the
1088 | // following template settings to use alternative delimiters.
1089 | _.templateSettings = {
1090 | evaluate : /<%([\s\S]+?)%>/g,
1091 | interpolate : /<%=([\s\S]+?)%>/g,
1092 | escape : /<%-([\s\S]+?)%>/g
1093 | };
1094 |
1095 | // When customizing `templateSettings`, if you don't want to define an
1096 | // interpolation, evaluation or escaping regex, we need one that is
1097 | // guaranteed not to match.
1098 | var noMatch = /(.)^/;
1099 |
1100 | // Certain characters need to be escaped so that they can be put into a
1101 | // string literal.
1102 | var escapes = {
1103 | "'": "'",
1104 | '\\': '\\',
1105 | '\r': 'r',
1106 | '\n': 'n',
1107 | '\t': 't',
1108 | '\u2028': 'u2028',
1109 | '\u2029': 'u2029'
1110 | };
1111 |
1112 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
1113 |
1114 | // JavaScript micro-templating, similar to John Resig's implementation.
1115 | // Underscore templating handles arbitrary delimiters, preserves whitespace,
1116 | // and correctly escapes quotes within interpolated code.
1117 | _.template = function(text, data, settings) {
1118 | var render;
1119 | settings = _.defaults({}, settings, _.templateSettings);
1120 |
1121 | // Combine delimiters into one regular expression via alternation.
1122 | var matcher = new RegExp([
1123 | (settings.escape || noMatch).source,
1124 | (settings.interpolate || noMatch).source,
1125 | (settings.evaluate || noMatch).source
1126 | ].join('|') + '|$', 'g');
1127 |
1128 | // Compile the template source, escaping string literals appropriately.
1129 | var index = 0;
1130 | var source = "__p+='";
1131 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
1132 | source += text.slice(index, offset)
1133 | .replace(escaper, function(match) { return '\\' + escapes[match]; });
1134 |
1135 | if (escape) {
1136 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
1137 | }
1138 | if (interpolate) {
1139 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
1140 | }
1141 | if (evaluate) {
1142 | source += "';\n" + evaluate + "\n__p+='";
1143 | }
1144 | index = offset + match.length;
1145 | return match;
1146 | });
1147 | source += "';\n";
1148 |
1149 | // If a variable is not specified, place data values in local scope.
1150 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
1151 |
1152 | source = "var __t,__p='',__j=Array.prototype.join," +
1153 | "print=function(){__p+=__j.call(arguments,'');};\n" +
1154 | source + "return __p;\n";
1155 |
1156 | try {
1157 | render = new Function(settings.variable || 'obj', '_', source);
1158 | } catch (e) {
1159 | e.source = source;
1160 | throw e;
1161 | }
1162 |
1163 | if (data) return render(data, _);
1164 | var template = function(data) {
1165 | return render.call(this, data, _);
1166 | };
1167 |
1168 | // Provide the compiled function source as a convenience for precompilation.
1169 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
1170 |
1171 | return template;
1172 | };
1173 |
1174 | // Add a "chain" function, which will delegate to the wrapper.
1175 | _.chain = function(obj) {
1176 | return _(obj).chain();
1177 | };
1178 |
1179 | // OOP
1180 | // ---------------
1181 | // If Underscore is called as a function, it returns a wrapped object that
1182 | // can be used OO-style. This wrapper holds altered versions of all the
1183 | // underscore functions. Wrapped objects may be chained.
1184 |
1185 | // Helper function to continue chaining intermediate results.
1186 | var result = function(obj) {
1187 | return this._chain ? _(obj).chain() : obj;
1188 | };
1189 |
1190 | // Add all of the Underscore functions to the wrapper object.
1191 | _.mixin(_);
1192 |
1193 | // Add all mutator Array functions to the wrapper.
1194 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1195 | var method = ArrayProto[name];
1196 | _.prototype[name] = function() {
1197 | var obj = this._wrapped;
1198 | method.apply(obj, arguments);
1199 | if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
1200 | return result.call(this, obj);
1201 | };
1202 | });
1203 |
1204 | // Add all accessor Array functions to the wrapper.
1205 | each(['concat', 'join', 'slice'], function(name) {
1206 | var method = ArrayProto[name];
1207 | _.prototype[name] = function() {
1208 | return result.call(this, method.apply(this._wrapped, arguments));
1209 | };
1210 | });
1211 |
1212 | _.extend(_.prototype, {
1213 |
1214 | // Start chaining a wrapped Underscore object.
1215 | chain: function() {
1216 | this._chain = true;
1217 | return this;
1218 | },
1219 |
1220 | // Extracts the result from a wrapped and chained object.
1221 | value: function() {
1222 | return this._wrapped;
1223 | }
1224 |
1225 | });
1226 |
1227 | }).call(this);
1228 |
--------------------------------------------------------------------------------
/node/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | #ignore node_modules, as the node project is not "deployed" per se: http://www.mikealrogers.com/posts/nodemodules-in-git.html
4 | /node_modules
5 |
6 | /dist
7 | /generated
8 |
9 | .sass-cache
10 |
--------------------------------------------------------------------------------
/node/Gruntfile.coffee:
--------------------------------------------------------------------------------
1 | #global module:false
2 |
3 | module.exports = (grunt) ->
4 | grunt.loadNpmTasks('grunt-jasmine-bundle')
5 |
6 | grunt.initConfig
7 | spec:
8 | unit:
9 | options:
10 | helpers: "spec/unit/helpers/**/*.{js,coffee}"
11 | specs: "spec/unit/**/*.{js,coffee}"
12 | minijasminenode:
13 | showColors: true
14 |
15 | e2e:
16 | options:
17 | helpers: "spec/e2e/helpers/**/*.{js,coffee}"
18 | specs: "spec/e2e/**/*.{js,coffee}"
19 | minijasminenode:
20 | showColors: true
21 | defaultTimeoutInterval: 10000
22 |
23 | grunt.registerTask("default", ["spec:unit", "spec:e2e"])
24 |
--------------------------------------------------------------------------------
/node/README.md:
--------------------------------------------------------------------------------
1 | # example Node project with grunt & grunt-jasmine-bundle
2 |
3 | To get started, install your dependencies:
4 |
5 | ```
6 | $ npm install
7 | ```
8 |
9 | ## Running the server
10 |
11 | The server can be started by running:
12 |
13 | ```
14 | $ npm start
15 | ```
16 |
17 | This will start a server up at [http://localhost:8080](http://localhost:8080).
18 |
19 | ## Install Grunt Command Line Tool
20 |
21 | Install grunt and grunt-cli globally.
22 |
23 | ```
24 | $ npm install -g grunt
25 | $ npm install -g grunt-cli
26 | ```
27 |
28 | ## Running tests
29 |
30 | The project has two test suites, one for isolated unit tests and one for end-to-end ("e2e") integration tests. You can run either of them with:
31 |
32 | ```
33 | $ grunt spec:unit
34 | ```
35 |
36 | or
37 |
38 | ```
39 | $ grunt spec:e2e
40 | ```
41 |
42 | Simply executing `grunt` will run both suites in succession.
43 |
44 |
--------------------------------------------------------------------------------
/node/app.coffee:
--------------------------------------------------------------------------------
1 | express = require("express")
2 |
3 | app = express()
4 |
5 | app.use(express.bodyParser())
6 |
7 | app.get "/", (req, res, err) ->
8 | res.send 200
9 |
10 | app.get "/problem", (req, res, err) ->
11 | res.send(501)
12 |
13 | app.get "/problem/:id", (req, res, err) ->
14 | res.send(501)
15 |
16 | app.post "/solution", (req, res, err) ->
17 | res.send(501)
18 |
19 | module.exports =
20 | start: (quiet) ->
21 | @server = app.listen 8080, ->
22 | console.log("Now accepting requests at http://localhost:8080") unless quiet?
23 |
24 | stop: ->
25 | @server?.close()
26 |
--------------------------------------------------------------------------------
/node/lib/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/node/lib/.keep
--------------------------------------------------------------------------------
/node/main.js:
--------------------------------------------------------------------------------
1 | require("coffee-script");
2 | require("./app").start();
3 |
4 |
--------------------------------------------------------------------------------
/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-tdd-example",
3 | "version": "0.0.1",
4 | "description": "An example project for unit and integration testing under node with jasmine",
5 | "scripts": {
6 | },
7 | "main": "main.js",
8 | "author": "",
9 | "scripts": {
10 | "start": "node main.js",
11 | "test": "grunt spec:unit spec:e2e"
12 | },
13 | "license": "BSD-2-Clause",
14 | "dependencies": {
15 | "express": "~3.3",
16 | "coffee-script": "~1.6.3",
17 | "lodash": "~0.9"
18 | },
19 | "devDependencies": {
20 | "grunt-jasmine-bundle": "~0.1.4",
21 | "grunt": "~0.4.2",
22 | "sandboxed-module": "~0.3.0",
23 | "request": "~2.27.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/node/spec/e2e/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/node/spec/e2e/.gitkeep
--------------------------------------------------------------------------------
/node/spec/e2e/get-problem-spec.coffee:
--------------------------------------------------------------------------------
1 | describe "GET /problem", ->
2 | When (done) -> GET "/problem", done, (err, res) =>
3 | @result = res
4 | @json = res.json
5 | Then -> @result.statusCode == 200
6 | And -> expect(@json.id).toEqual(jasmine.any(Number))
7 | And -> @json.operands.left >= 0 && @json.operands.left <= 100
8 | And -> @json.operands.right >= 0 && @json.operands.right <= 100
9 | And -> _(["*", "+", "-", "/"]).include(@json.operator)
10 | And -> @json.description == "#{@json.operands.left} #{@json.operator} #{@json.operands.right}"
11 |
12 | describe "GET /problem/:id", ->
13 | Given (done) -> GET "/problem", done, (err, res) => @problem = res.json
14 | When (done) -> GET "/problem/#{@problem.id}", done, (err, res) => @result = res
15 | Then -> expect(@result.json).toEqual(@problem)
16 |
17 | describe "POST /solution", ->
18 | Given (done) -> GET "/problem", done, (err, res) => @problem = res.json
19 |
20 | context "a correct answer", ->
21 | Given -> @solution =
22 | problemId: @problem.id
23 | answer: eval(@problem.description)
24 | When (done) -> POST "/solution", @solution, done, (err, res) => @result = res
25 | Then -> @result.statusCode == 202
26 |
27 | context "an incorrect answer", ->
28 | Given -> @solution =
29 | problemId: @problem.id
30 | answer: eval(@problem.description) + 1
31 | When (done) -> POST "/solution", @solution, done, (err, res) => @result = res
32 | Then -> @result.statusCode == 422
33 |
--------------------------------------------------------------------------------
/node/spec/e2e/helpers/globalize-underscore.coffee:
--------------------------------------------------------------------------------
1 | global._ = require('lodash')
2 |
--------------------------------------------------------------------------------
/node/spec/e2e/helpers/request-methods.coffee:
--------------------------------------------------------------------------------
1 | request = require("request")
2 |
3 | global.GET = (path, done, callback) ->
4 | request.get "http://localhost:8080#{path}", (err, res) ->
5 | try
6 | res.json = JSON.parse(res.body)
7 | catch e
8 |
9 | callback(err, res)
10 | done()
11 |
12 | global.POST = (path, data, done, callback) ->
13 | request.post
14 | url: "http://localhost:8080#{path}"
15 | json: data
16 | ,
17 | (err, res) ->
18 | callback(err, res)
19 | done()
20 |
--------------------------------------------------------------------------------
/node/spec/e2e/helpers/start-server.coffee:
--------------------------------------------------------------------------------
1 | app = require("./../../../app")
2 |
3 | beforeEach -> app.start("quietly")
4 | afterEach -> app.stop()
5 |
--------------------------------------------------------------------------------
/node/spec/unit/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/node/spec/unit/.gitkeep
--------------------------------------------------------------------------------
/node/spec/unit/helpers/require-subject.coffee:
--------------------------------------------------------------------------------
1 | SandboxedModule = require('sandboxed-module')
2 |
3 | global.requireSubject = (path, requires) ->
4 | SandboxedModule.require("./../../../#{path}", {requires})
5 |
--------------------------------------------------------------------------------
/standalone/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
--------------------------------------------------------------------------------
/standalone/README.md:
--------------------------------------------------------------------------------
1 | # example Standalone jasmine project
2 |
3 | This project includes Jasmine itself, several helper scripts, and an HTML "test runner"
4 | file that allow you to run Jasmine tests without any additional tooling.
5 |
6 | Because opening a URL in a browser from a `file://` protocol raises security
7 | errors in most modern browsers, these files can be statically hosted using an
8 | included Express.js application.
9 |
10 | To run your tests, first install [Node.js](http://nodejs.org), then:
11 |
12 | ```
13 | $ npm install
14 | $ npm start
15 | ```
16 |
17 | Navigate to [http://localhost:8081](http://localhost:8081) to run your tests.
18 |
--------------------------------------------------------------------------------
/standalone/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Jasmine Spec Runner
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/standalone/lib/jasmine-1.3.1/MIT.LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008-2011 Pivotal Labs
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/standalone/lib/jasmine-1.3.1/jasmine-html.js:
--------------------------------------------------------------------------------
1 | jasmine.HtmlReporterHelpers = {};
2 |
3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
4 | var el = document.createElement(type);
5 |
6 | for (var i = 2; i < arguments.length; i++) {
7 | var child = arguments[i];
8 |
9 | if (typeof child === 'string') {
10 | el.appendChild(document.createTextNode(child));
11 | } else {
12 | if (child) {
13 | el.appendChild(child);
14 | }
15 | }
16 | }
17 |
18 | for (var attr in attrs) {
19 | if (attr == "className") {
20 | el[attr] = attrs[attr];
21 | } else {
22 | el.setAttribute(attr, attrs[attr]);
23 | }
24 | }
25 |
26 | return el;
27 | };
28 |
29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
30 | var results = child.results();
31 | var status = results.passed() ? 'passed' : 'failed';
32 | if (results.skipped) {
33 | status = 'skipped';
34 | }
35 |
36 | return status;
37 | };
38 |
39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
40 | var parentDiv = this.dom.summary;
41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
42 | var parent = child[parentSuite];
43 |
44 | if (parent) {
45 | if (typeof this.views.suites[parent.id] == 'undefined') {
46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
47 | }
48 | parentDiv = this.views.suites[parent.id].element;
49 | }
50 |
51 | parentDiv.appendChild(childElement);
52 | };
53 |
54 |
55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
56 | for(var fn in jasmine.HtmlReporterHelpers) {
57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
58 | }
59 | };
60 |
61 | jasmine.HtmlReporter = function(_doc) {
62 | var self = this;
63 | var doc = _doc || window.document;
64 |
65 | var reporterView;
66 |
67 | var dom = {};
68 |
69 | // Jasmine Reporter Public Interface
70 | self.logRunningSpecs = false;
71 |
72 | self.reportRunnerStarting = function(runner) {
73 | var specs = runner.specs() || [];
74 |
75 | if (specs.length == 0) {
76 | return;
77 | }
78 |
79 | createReporterDom(runner.env.versionString());
80 | doc.body.appendChild(dom.reporter);
81 | setExceptionHandling();
82 |
83 | reporterView = new jasmine.HtmlReporter.ReporterView(dom);
84 | reporterView.addSpecs(specs, self.specFilter);
85 | };
86 |
87 | self.reportRunnerResults = function(runner) {
88 | reporterView && reporterView.complete();
89 | };
90 |
91 | self.reportSuiteResults = function(suite) {
92 | reporterView.suiteComplete(suite);
93 | };
94 |
95 | self.reportSpecStarting = function(spec) {
96 | if (self.logRunningSpecs) {
97 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
98 | }
99 | };
100 |
101 | self.reportSpecResults = function(spec) {
102 | reporterView.specComplete(spec);
103 | };
104 |
105 | self.log = function() {
106 | var console = jasmine.getGlobal().console;
107 | if (console && console.log) {
108 | if (console.log.apply) {
109 | console.log.apply(console, arguments);
110 | } else {
111 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
112 | }
113 | }
114 | };
115 |
116 | self.specFilter = function(spec) {
117 | if (!focusedSpecName()) {
118 | return true;
119 | }
120 |
121 | return spec.getFullName().indexOf(focusedSpecName()) === 0;
122 | };
123 |
124 | return self;
125 |
126 | function focusedSpecName() {
127 | var specName;
128 |
129 | (function memoizeFocusedSpec() {
130 | if (specName) {
131 | return;
132 | }
133 |
134 | var paramMap = [];
135 | var params = jasmine.HtmlReporter.parameters(doc);
136 |
137 | for (var i = 0; i < params.length; i++) {
138 | var p = params[i].split('=');
139 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
140 | }
141 |
142 | specName = paramMap.spec;
143 | })();
144 |
145 | return specName;
146 | }
147 |
148 | function createReporterDom(version) {
149 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
150 | dom.banner = self.createDom('div', { className: 'banner' },
151 | self.createDom('span', { className: 'title' }, "Jasmine "),
152 | self.createDom('span', { className: 'version' }, version)),
153 |
154 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
155 | dom.alert = self.createDom('div', {className: 'alert'},
156 | self.createDom('span', { className: 'exceptions' },
157 | self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
158 | self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
159 | dom.results = self.createDom('div', {className: 'results'},
160 | dom.summary = self.createDom('div', { className: 'summary' }),
161 | dom.details = self.createDom('div', { id: 'details' }))
162 | );
163 | }
164 |
165 | function noTryCatch() {
166 | return window.location.search.match(/catch=false/);
167 | }
168 |
169 | function searchWithCatch() {
170 | var params = jasmine.HtmlReporter.parameters(window.document);
171 | var removed = false;
172 | var i = 0;
173 |
174 | while (!removed && i < params.length) {
175 | if (params[i].match(/catch=/)) {
176 | params.splice(i, 1);
177 | removed = true;
178 | }
179 | i++;
180 | }
181 | if (jasmine.CATCH_EXCEPTIONS) {
182 | params.push("catch=false");
183 | }
184 |
185 | return params.join("&");
186 | }
187 |
188 | function setExceptionHandling() {
189 | var chxCatch = document.getElementById('no_try_catch');
190 |
191 | if (noTryCatch()) {
192 | chxCatch.setAttribute('checked', true);
193 | jasmine.CATCH_EXCEPTIONS = false;
194 | }
195 | chxCatch.onclick = function() {
196 | window.location.search = searchWithCatch();
197 | };
198 | }
199 | };
200 | jasmine.HtmlReporter.parameters = function(doc) {
201 | var paramStr = doc.location.search.substring(1);
202 | var params = [];
203 |
204 | if (paramStr.length > 0) {
205 | params = paramStr.split('&');
206 | }
207 | return params;
208 | }
209 | jasmine.HtmlReporter.sectionLink = function(sectionName) {
210 | var link = '?';
211 | var params = [];
212 |
213 | if (sectionName) {
214 | params.push('spec=' + encodeURIComponent(sectionName));
215 | }
216 | if (!jasmine.CATCH_EXCEPTIONS) {
217 | params.push("catch=false");
218 | }
219 | if (params.length > 0) {
220 | link += params.join("&");
221 | }
222 |
223 | return link;
224 | };
225 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
226 | jasmine.HtmlReporter.ReporterView = function(dom) {
227 | this.startedAt = new Date();
228 | this.runningSpecCount = 0;
229 | this.completeSpecCount = 0;
230 | this.passedCount = 0;
231 | this.failedCount = 0;
232 | this.skippedCount = 0;
233 |
234 | this.createResultsMenu = function() {
235 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
236 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
237 | ' | ',
238 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
239 |
240 | this.summaryMenuItem.onclick = function() {
241 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
242 | };
243 |
244 | this.detailsMenuItem.onclick = function() {
245 | showDetails();
246 | };
247 | };
248 |
249 | this.addSpecs = function(specs, specFilter) {
250 | this.totalSpecCount = specs.length;
251 |
252 | this.views = {
253 | specs: {},
254 | suites: {}
255 | };
256 |
257 | for (var i = 0; i < specs.length; i++) {
258 | var spec = specs[i];
259 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
260 | if (specFilter(spec)) {
261 | this.runningSpecCount++;
262 | }
263 | }
264 | };
265 |
266 | this.specComplete = function(spec) {
267 | this.completeSpecCount++;
268 |
269 | if (isUndefined(this.views.specs[spec.id])) {
270 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
271 | }
272 |
273 | var specView = this.views.specs[spec.id];
274 |
275 | switch (specView.status()) {
276 | case 'passed':
277 | this.passedCount++;
278 | break;
279 |
280 | case 'failed':
281 | this.failedCount++;
282 | break;
283 |
284 | case 'skipped':
285 | this.skippedCount++;
286 | break;
287 | }
288 |
289 | specView.refresh();
290 | this.refresh();
291 | };
292 |
293 | this.suiteComplete = function(suite) {
294 | var suiteView = this.views.suites[suite.id];
295 | if (isUndefined(suiteView)) {
296 | return;
297 | }
298 | suiteView.refresh();
299 | };
300 |
301 | this.refresh = function() {
302 |
303 | if (isUndefined(this.resultsMenu)) {
304 | this.createResultsMenu();
305 | }
306 |
307 | // currently running UI
308 | if (isUndefined(this.runningAlert)) {
309 | this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
310 | dom.alert.appendChild(this.runningAlert);
311 | }
312 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
313 |
314 | // skipped specs UI
315 | if (isUndefined(this.skippedAlert)) {
316 | this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
317 | }
318 |
319 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
320 |
321 | if (this.skippedCount === 1 && isDefined(dom.alert)) {
322 | dom.alert.appendChild(this.skippedAlert);
323 | }
324 |
325 | // passing specs UI
326 | if (isUndefined(this.passedAlert)) {
327 | this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
328 | }
329 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
330 |
331 | // failing specs UI
332 | if (isUndefined(this.failedAlert)) {
333 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
334 | }
335 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
336 |
337 | if (this.failedCount === 1 && isDefined(dom.alert)) {
338 | dom.alert.appendChild(this.failedAlert);
339 | dom.alert.appendChild(this.resultsMenu);
340 | }
341 |
342 | // summary info
343 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
344 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
345 | };
346 |
347 | this.complete = function() {
348 | dom.alert.removeChild(this.runningAlert);
349 |
350 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
351 |
352 | if (this.failedCount === 0) {
353 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
354 | } else {
355 | showDetails();
356 | }
357 |
358 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
359 | };
360 |
361 | return this;
362 |
363 | function showDetails() {
364 | if (dom.reporter.className.search(/showDetails/) === -1) {
365 | dom.reporter.className += " showDetails";
366 | }
367 | }
368 |
369 | function isUndefined(obj) {
370 | return typeof obj === 'undefined';
371 | }
372 |
373 | function isDefined(obj) {
374 | return !isUndefined(obj);
375 | }
376 |
377 | function specPluralizedFor(count) {
378 | var str = count + " spec";
379 | if (count > 1) {
380 | str += "s"
381 | }
382 | return str;
383 | }
384 |
385 | };
386 |
387 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
388 |
389 |
390 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
391 | this.spec = spec;
392 | this.dom = dom;
393 | this.views = views;
394 |
395 | this.symbol = this.createDom('li', { className: 'pending' });
396 | this.dom.symbolSummary.appendChild(this.symbol);
397 |
398 | this.summary = this.createDom('div', { className: 'specSummary' },
399 | this.createDom('a', {
400 | className: 'description',
401 | href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
402 | title: this.spec.getFullName()
403 | }, this.spec.description)
404 | );
405 |
406 | this.detail = this.createDom('div', { className: 'specDetail' },
407 | this.createDom('a', {
408 | className: 'description',
409 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
410 | title: this.spec.getFullName()
411 | }, this.spec.getFullName())
412 | );
413 | };
414 |
415 | jasmine.HtmlReporter.SpecView.prototype.status = function() {
416 | return this.getSpecStatus(this.spec);
417 | };
418 |
419 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
420 | this.symbol.className = this.status();
421 |
422 | switch (this.status()) {
423 | case 'skipped':
424 | break;
425 |
426 | case 'passed':
427 | this.appendSummaryToSuiteDiv();
428 | break;
429 |
430 | case 'failed':
431 | this.appendSummaryToSuiteDiv();
432 | this.appendFailureDetail();
433 | break;
434 | }
435 | };
436 |
437 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
438 | this.summary.className += ' ' + this.status();
439 | this.appendToSummary(this.spec, this.summary);
440 | };
441 |
442 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
443 | this.detail.className += ' ' + this.status();
444 |
445 | var resultItems = this.spec.results().getItems();
446 | var messagesDiv = this.createDom('div', { className: 'messages' });
447 |
448 | for (var i = 0; i < resultItems.length; i++) {
449 | var result = resultItems[i];
450 |
451 | if (result.type == 'log') {
452 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
453 | } else if (result.type == 'expect' && result.passed && !result.passed()) {
454 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
455 |
456 | if (result.trace.stack) {
457 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
458 | }
459 | }
460 | }
461 |
462 | if (messagesDiv.childNodes.length > 0) {
463 | this.detail.appendChild(messagesDiv);
464 | this.dom.details.appendChild(this.detail);
465 | }
466 | };
467 |
468 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
469 | this.suite = suite;
470 | this.dom = dom;
471 | this.views = views;
472 |
473 | this.element = this.createDom('div', { className: 'suite' },
474 | this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
475 | );
476 |
477 | this.appendToSummary(this.suite, this.element);
478 | };
479 |
480 | jasmine.HtmlReporter.SuiteView.prototype.status = function() {
481 | return this.getSpecStatus(this.suite);
482 | };
483 |
484 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
485 | this.element.className += " " + this.status();
486 | };
487 |
488 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
489 |
490 | /* @deprecated Use jasmine.HtmlReporter instead
491 | */
492 | jasmine.TrivialReporter = function(doc) {
493 | this.document = doc || document;
494 | this.suiteDivs = {};
495 | this.logRunningSpecs = false;
496 | };
497 |
498 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
499 | var el = document.createElement(type);
500 |
501 | for (var i = 2; i < arguments.length; i++) {
502 | var child = arguments[i];
503 |
504 | if (typeof child === 'string') {
505 | el.appendChild(document.createTextNode(child));
506 | } else {
507 | if (child) { el.appendChild(child); }
508 | }
509 | }
510 |
511 | for (var attr in attrs) {
512 | if (attr == "className") {
513 | el[attr] = attrs[attr];
514 | } else {
515 | el.setAttribute(attr, attrs[attr]);
516 | }
517 | }
518 |
519 | return el;
520 | };
521 |
522 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
523 | var showPassed, showSkipped;
524 |
525 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
526 | this.createDom('div', { className: 'banner' },
527 | this.createDom('div', { className: 'logo' },
528 | this.createDom('span', { className: 'title' }, "Jasmine"),
529 | this.createDom('span', { className: 'version' }, runner.env.versionString())),
530 | this.createDom('div', { className: 'options' },
531 | "Show ",
532 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
533 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
534 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
535 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
536 | )
537 | ),
538 |
539 | this.runnerDiv = this.createDom('div', { className: 'runner running' },
540 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
541 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
542 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
543 | );
544 |
545 | this.document.body.appendChild(this.outerDiv);
546 |
547 | var suites = runner.suites();
548 | for (var i = 0; i < suites.length; i++) {
549 | var suite = suites[i];
550 | var suiteDiv = this.createDom('div', { className: 'suite' },
551 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
552 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
553 | this.suiteDivs[suite.id] = suiteDiv;
554 | var parentDiv = this.outerDiv;
555 | if (suite.parentSuite) {
556 | parentDiv = this.suiteDivs[suite.parentSuite.id];
557 | }
558 | parentDiv.appendChild(suiteDiv);
559 | }
560 |
561 | this.startedAt = new Date();
562 |
563 | var self = this;
564 | showPassed.onclick = function(evt) {
565 | if (showPassed.checked) {
566 | self.outerDiv.className += ' show-passed';
567 | } else {
568 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
569 | }
570 | };
571 |
572 | showSkipped.onclick = function(evt) {
573 | if (showSkipped.checked) {
574 | self.outerDiv.className += ' show-skipped';
575 | } else {
576 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
577 | }
578 | };
579 | };
580 |
581 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
582 | var results = runner.results();
583 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
584 | this.runnerDiv.setAttribute("class", className);
585 | //do it twice for IE
586 | this.runnerDiv.setAttribute("className", className);
587 | var specs = runner.specs();
588 | var specCount = 0;
589 | for (var i = 0; i < specs.length; i++) {
590 | if (this.specFilter(specs[i])) {
591 | specCount++;
592 | }
593 | }
594 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
595 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
596 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
597 |
598 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
599 | };
600 |
601 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
602 | var results = suite.results();
603 | var status = results.passed() ? 'passed' : 'failed';
604 | if (results.totalCount === 0) { // todo: change this to check results.skipped
605 | status = 'skipped';
606 | }
607 | this.suiteDivs[suite.id].className += " " + status;
608 | };
609 |
610 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
611 | if (this.logRunningSpecs) {
612 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
613 | }
614 | };
615 |
616 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
617 | var results = spec.results();
618 | var status = results.passed() ? 'passed' : 'failed';
619 | if (results.skipped) {
620 | status = 'skipped';
621 | }
622 | var specDiv = this.createDom('div', { className: 'spec ' + status },
623 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
624 | this.createDom('a', {
625 | className: 'description',
626 | href: '?spec=' + encodeURIComponent(spec.getFullName()),
627 | title: spec.getFullName()
628 | }, spec.description));
629 |
630 |
631 | var resultItems = results.getItems();
632 | var messagesDiv = this.createDom('div', { className: 'messages' });
633 | for (var i = 0; i < resultItems.length; i++) {
634 | var result = resultItems[i];
635 |
636 | if (result.type == 'log') {
637 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
638 | } else if (result.type == 'expect' && result.passed && !result.passed()) {
639 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
640 |
641 | if (result.trace.stack) {
642 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
643 | }
644 | }
645 | }
646 |
647 | if (messagesDiv.childNodes.length > 0) {
648 | specDiv.appendChild(messagesDiv);
649 | }
650 |
651 | this.suiteDivs[spec.suite.id].appendChild(specDiv);
652 | };
653 |
654 | jasmine.TrivialReporter.prototype.log = function() {
655 | var console = jasmine.getGlobal().console;
656 | if (console && console.log) {
657 | if (console.log.apply) {
658 | console.log.apply(console, arguments);
659 | } else {
660 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
661 | }
662 | }
663 | };
664 |
665 | jasmine.TrivialReporter.prototype.getLocation = function() {
666 | return this.document.location;
667 | };
668 |
669 | jasmine.TrivialReporter.prototype.specFilter = function(spec) {
670 | var paramMap = {};
671 | var params = this.getLocation().search.substring(1).split('&');
672 | for (var i = 0; i < params.length; i++) {
673 | var p = params[i].split('=');
674 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
675 | }
676 |
677 | if (!paramMap.spec) {
678 | return true;
679 | }
680 | return spec.getFullName().indexOf(paramMap.spec) === 0;
681 | };
682 |
--------------------------------------------------------------------------------
/standalone/lib/jasmine-1.3.1/jasmine.css:
--------------------------------------------------------------------------------
1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
2 |
3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
4 | #HTMLReporter a { text-decoration: none; }
5 | #HTMLReporter a:hover { text-decoration: underline; }
6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; }
9 | #HTMLReporter .version { color: #aaaaaa; }
10 | #HTMLReporter .banner { margin-top: 14px; }
11 | #HTMLReporter .duration { color: #aaaaaa; float: right; }
12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; }
15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; }
17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; }
21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
24 | #HTMLReporter .runningAlert { background-color: #666666; }
25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; }
26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; }
27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
28 | #HTMLReporter .passingAlert { background-color: #a6b779; }
29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
30 | #HTMLReporter .failingAlert { background-color: #cf867e; }
31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; }
32 | #HTMLReporter .results { margin-top: 14px; }
33 | #HTMLReporter #details { display: none; }
34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
38 | #HTMLReporter.showDetails .summary { display: none; }
39 | #HTMLReporter.showDetails #details { display: block; }
40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
41 | #HTMLReporter .summary { margin-top: 14px; }
42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; }
45 | #HTMLReporter .description + .suite { margin-top: 0; }
46 | #HTMLReporter .suite { margin-top: 14px; }
47 | #HTMLReporter .suite a { color: #333333; }
48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; }
49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
51 | #HTMLReporter .resultMessage span.result { display: block; }
52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
53 |
54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; }
56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
61 | #TrivialReporter .runner.running { background-color: yellow; }
62 | #TrivialReporter .options { text-align: right; font-size: .8em; }
63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
64 | #TrivialReporter .suite .suite { margin: 5px; }
65 | #TrivialReporter .suite.passed { background-color: #dfd; }
66 | #TrivialReporter .suite.failed { background-color: #fdd; }
67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
71 | #TrivialReporter .spec.skipped { background-color: #bbb; }
72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
73 | #TrivialReporter .passed { background-color: #cfc; display: none; }
74 | #TrivialReporter .failed { background-color: #fbb; }
75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
77 | #TrivialReporter .resultMessage .mismatch { color: black; }
78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; }
82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
83 |
--------------------------------------------------------------------------------
/standalone/main.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 |
3 | app = express();
4 | app.use(express.static(__dirname));
5 |
6 | app.listen(8081, function(){
7 | console.log("Run your tests at: http://localhost:8081");
8 | });
9 |
--------------------------------------------------------------------------------
/standalone/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "standalone-tdd-example",
3 | "version": "0.0.1",
4 | "description": "An example project trying out the standalone Jasmine test runner",
5 | "main": "main.js",
6 | "author": "",
7 | "scripts": {
8 | "start": "node main.js"
9 | },
10 | "license": "BSD-2-Clause",
11 | "dependencies": {
12 | "express": "~3.3"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/standalone/spec/calculator-spec.coffee:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/standalone/spec/calculator-spec.coffee
--------------------------------------------------------------------------------
/standalone/spec/helpers/helper.js:
--------------------------------------------------------------------------------
1 | var root = this;
2 |
3 | root.context = root.describe;
4 | root.xcontext = root.xdescribe;
--------------------------------------------------------------------------------
/standalone/spec/helpers/jasmine-given.js:
--------------------------------------------------------------------------------
1 | /* jasmine-given - 2.6.0
2 | * Adds a Given-When-Then DSL to jasmine as an alternative style for specs
3 | * https://github.com/searls/jasmine-given
4 | */
5 | /* jasmine-matcher-wrapper - 0.0.2
6 | * Wraps Jasmine 1.x matchers for use with Jasmine 2
7 | * https://github.com/testdouble/jasmine-matcher-wrapper
8 | */
9 | (function() {
10 | var __slice = [].slice,
11 | __hasProp = {}.hasOwnProperty;
12 |
13 | (function(jasmine) {
14 | var comparatorFor;
15 | if (jasmine == null) {
16 | return typeof console !== "undefined" && console !== null ? console.warn("jasmine was not found. Skipping jasmine-matcher-wrapper. Verify your script load order.") : void 0;
17 | }
18 | if (jasmine.matcherWrapper != null) {
19 | return;
20 | }
21 | comparatorFor = function(matcher, isNot) {
22 | return function() {
23 | var actual, context, message, params, pass, _ref;
24 | actual = arguments[0], params = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
25 | context = {
26 | actual: actual,
27 | isNot: isNot
28 | };
29 | pass = matcher.apply(context, params);
30 | if (isNot) {
31 | pass = !pass;
32 | }
33 | if (!pass) {
34 | message = (_ref = context.message) != null ? _ref.apply(context, params) : void 0;
35 | }
36 | return {
37 | pass: pass,
38 | message: message
39 | };
40 | };
41 | };
42 | return jasmine.matcherWrapper = {
43 | wrap: function(matchers) {
44 | var matcher, name, wrappedMatchers;
45 | if (jasmine.addMatchers == null) {
46 | return matchers;
47 | }
48 | wrappedMatchers = {};
49 | for (name in matchers) {
50 | if (!__hasProp.call(matchers, name)) continue;
51 | matcher = matchers[name];
52 | wrappedMatchers[name] = function() {
53 | return {
54 | compare: comparatorFor(matcher, false),
55 | negativeCompare: comparatorFor(matcher, true)
56 | };
57 | };
58 | }
59 | return wrappedMatchers;
60 | }
61 | };
62 | })(jasmine || getJasmineRequireObj());
63 |
64 | }).call(this);
65 |
66 | (function() {
67 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
68 |
69 | (function(jasmine) {
70 | var Waterfall, additionalInsightsForErrorMessage, apparentReferenceError, attemptedEquality, comparisonInsight, currentSpec, declareJasmineSpec, deepEqualsNotice, doneWrapperFor, evalInContextOfSpec, finalStatementFrom, getBlock, invariantList, mostRecentExpectations, mostRecentlyUsed, o, root, stringifyExpectation, wasComparison, whenList, wrapAsExpectations;
71 | mostRecentlyUsed = null;
72 | root = this;
73 | currentSpec = null;
74 | beforeEach(function() {
75 | return currentSpec = this;
76 | });
77 | root.Given = function() {
78 | mostRecentlyUsed = root.Given;
79 | return beforeEach(getBlock(arguments));
80 | };
81 | whenList = [];
82 | root.When = function() {
83 | var b;
84 | mostRecentlyUsed = root.When;
85 | b = getBlock(arguments);
86 | beforeEach(function() {
87 | return whenList.push(b);
88 | });
89 | return afterEach(function() {
90 | return whenList.pop();
91 | });
92 | };
93 | invariantList = [];
94 | root.Invariant = function() {
95 | var invariantBehavior;
96 | mostRecentlyUsed = root.Invariant;
97 | invariantBehavior = getBlock(arguments);
98 | beforeEach(function() {
99 | return invariantList.push(invariantBehavior);
100 | });
101 | return afterEach(function() {
102 | return invariantList.pop();
103 | });
104 | };
105 | getBlock = function(thing) {
106 | var assignResultTo, setupFunction;
107 | setupFunction = o(thing).firstThat(function(arg) {
108 | return o(arg).isFunction();
109 | });
110 | assignResultTo = o(thing).firstThat(function(arg) {
111 | return o(arg).isString();
112 | });
113 | return doneWrapperFor(setupFunction, function(done) {
114 | var context, result;
115 | context = currentSpec;
116 | result = setupFunction.call(context, done);
117 | if (assignResultTo) {
118 | if (!context[assignResultTo]) {
119 | return context[assignResultTo] = result;
120 | } else {
121 | throw new Error("Unfortunately, the variable '" + assignResultTo + "' is already assigned to: " + context[assignResultTo]);
122 | }
123 | }
124 | });
125 | };
126 | mostRecentExpectations = null;
127 | declareJasmineSpec = function(specArgs, itFunction) {
128 | var expectationFunction, expectations, label;
129 | if (itFunction == null) {
130 | itFunction = it;
131 | }
132 | label = o(specArgs).firstThat(function(arg) {
133 | return o(arg).isString();
134 | });
135 | expectationFunction = o(specArgs).firstThat(function(arg) {
136 | return o(arg).isFunction();
137 | });
138 | mostRecentlyUsed = root.subsequentThen;
139 | mostRecentExpectations = expectations = [expectationFunction];
140 | itFunction("then " + (label != null ? label : stringifyExpectation(expectations)), function(jasmineDone) {
141 | var userCommands;
142 | userCommands = [].concat(whenList, invariantList, wrapAsExpectations(expectations));
143 | return new Waterfall(userCommands, jasmineDone).flow();
144 | });
145 | return {
146 | Then: subsequentThen,
147 | And: subsequentThen
148 | };
149 | };
150 | wrapAsExpectations = function(expectations) {
151 | var expectation, i, _i, _len, _results;
152 | _results = [];
153 | for (i = _i = 0, _len = expectations.length; _i < _len; i = ++_i) {
154 | expectation = expectations[i];
155 | _results.push((function(expectation, i) {
156 | return doneWrapperFor(expectation, function(maybeDone) {
157 | return expect(expectation).not.toHaveReturnedFalseFromThen(currentSpec, i + 1, maybeDone);
158 | });
159 | })(expectation, i));
160 | }
161 | return _results;
162 | };
163 | doneWrapperFor = function(func, toWrap) {
164 | if (func.length === 0) {
165 | return function() {
166 | return toWrap();
167 | };
168 | } else {
169 | return function(done) {
170 | return toWrap(done);
171 | };
172 | }
173 | };
174 | root.Then = function() {
175 | return declareJasmineSpec(arguments);
176 | };
177 | root.Then.only = function() {
178 | return declareJasmineSpec(arguments, it.only);
179 | };
180 | root.subsequentThen = function(additionalExpectation) {
181 | mostRecentExpectations.push(additionalExpectation);
182 | return this;
183 | };
184 | mostRecentlyUsed = root.Given;
185 | root.And = function() {
186 | return mostRecentlyUsed.apply(this, jasmine.util.argsToArray(arguments));
187 | };
188 | o = function(thing) {
189 | return {
190 | isFunction: function() {
191 | return Object.prototype.toString.call(thing) === "[object Function]";
192 | },
193 | isString: function() {
194 | return Object.prototype.toString.call(thing) === "[object String]";
195 | },
196 | firstThat: function(test) {
197 | var i;
198 | i = 0;
199 | while (i < thing.length) {
200 | if (test(thing[i]) === true) {
201 | return thing[i];
202 | }
203 | i++;
204 | }
205 | return void 0;
206 | }
207 | };
208 | };
209 | jasmine._given = {
210 | matchers: {
211 | toHaveReturnedFalseFromThen: function(context, n, done) {
212 | var e, exception, result;
213 | result = false;
214 | exception = void 0;
215 | try {
216 | result = this.actual.call(context, done);
217 | } catch (_error) {
218 | e = _error;
219 | exception = e;
220 | }
221 | this.message = function() {
222 | var msg, stringyExpectation;
223 | stringyExpectation = stringifyExpectation(this.actual);
224 | msg = "Then clause" + (n > 1 ? " #" + n : "") + " `" + stringyExpectation + "` failed by ";
225 | if (exception) {
226 | msg += "throwing: " + exception.toString();
227 | } else {
228 | msg += "returning false";
229 | }
230 | msg += additionalInsightsForErrorMessage(stringyExpectation);
231 | return msg;
232 | };
233 | return result === false;
234 | }
235 | }
236 | };
237 | stringifyExpectation = function(expectation) {
238 | var matches;
239 | matches = expectation.toString().replace(/\n/g, '').match(/function\s?\(.*\)\s?{\s*(return\s+)?(.*?)(;)?\s*}/i);
240 | if (matches && matches.length >= 3) {
241 | return matches[2].replace(/\s+/g, ' ');
242 | } else {
243 | return "";
244 | }
245 | };
246 | additionalInsightsForErrorMessage = function(expectationString) {
247 | var comparison, expectation;
248 | expectation = finalStatementFrom(expectationString);
249 | if (comparison = wasComparison(expectation)) {
250 | return comparisonInsight(expectation, comparison);
251 | } else {
252 | return "";
253 | }
254 | };
255 | finalStatementFrom = function(expectationString) {
256 | var multiStatement;
257 | if (multiStatement = expectationString.match(/.*return (.*)/)) {
258 | return multiStatement[multiStatement.length - 1];
259 | } else {
260 | return expectationString;
261 | }
262 | };
263 | wasComparison = function(expectation) {
264 | var comparator, comparison, left, right, s;
265 | if (comparison = expectation.match(/(.*) (===|!==|==|!=|>|>=|<|<=) (.*)/)) {
266 | s = comparison[0], left = comparison[1], comparator = comparison[2], right = comparison[3];
267 | return {
268 | left: left,
269 | comparator: comparator,
270 | right: right
271 | };
272 | }
273 | };
274 | comparisonInsight = function(expectation, comparison) {
275 | var left, msg, right;
276 | left = evalInContextOfSpec(comparison.left);
277 | right = evalInContextOfSpec(comparison.right);
278 | if (apparentReferenceError(left) && apparentReferenceError(right)) {
279 | return "";
280 | }
281 | msg = "\n\nThis comparison was detected:\n " + expectation + "\n " + left + " " + comparison.comparator + " " + right;
282 | if (attemptedEquality(left, right, comparison.comparator)) {
283 | msg += "\n\n" + (deepEqualsNotice(comparison.left, comparison.right));
284 | }
285 | return msg;
286 | };
287 | apparentReferenceError = function(result) {
288 | return /^";
299 | }
300 | };
301 | attemptedEquality = function(left, right, comparator) {
302 | var _ref;
303 | if (!(comparator === "==" || comparator === "===")) {
304 | return false;
305 | }
306 | if (((_ref = jasmine.matchersUtil) != null ? _ref.equals : void 0) != null) {
307 | return jasmine.matchersUtil.equals(left, right);
308 | } else {
309 | return jasmine.getEnv().equals_(left, right);
310 | }
311 | };
312 | deepEqualsNotice = function(left, right) {
313 | return "However, these items are deeply equal! Try an expectation like this instead:\n expect(" + left + ").toEqual(" + right + ")";
314 | };
315 | Waterfall = (function() {
316 | function Waterfall(functions, finalCallback) {
317 | var func, _i, _len, _ref;
318 | if (functions == null) {
319 | functions = [];
320 | }
321 | this.flow = __bind(this.flow, this);
322 | this.invokeFinalCallbackIfNecessary = __bind(this.invokeFinalCallbackIfNecessary, this);
323 | this.asyncTaskCompleted = __bind(this.asyncTaskCompleted, this);
324 | this.functions = functions.slice(0);
325 | this.finalCallback = finalCallback;
326 | this.asyncCount = 0;
327 | _ref = this.functions;
328 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
329 | func = _ref[_i];
330 | if (func.length > 0) {
331 | this.asyncCount += 1;
332 | }
333 | }
334 | }
335 |
336 | Waterfall.prototype.asyncTaskCompleted = function() {
337 | this.asyncCount -= 1;
338 | return this.flow();
339 | };
340 |
341 | Waterfall.prototype.invokeFinalCallbackIfNecessary = function() {
342 | if (this.asyncCount === 0) {
343 | if (typeof this.finalCallback === "function") {
344 | this.finalCallback();
345 | }
346 | return this.finalCallback = void 0;
347 | }
348 | };
349 |
350 | Waterfall.prototype.flow = function() {
351 | var func;
352 | if (this.functions.length === 0) {
353 | return this.invokeFinalCallbackIfNecessary();
354 | }
355 | func = this.functions.shift();
356 | if (func.length > 0) {
357 | return func(this.asyncTaskCompleted);
358 | } else {
359 | func();
360 | return this.flow();
361 | }
362 | };
363 |
364 | return Waterfall;
365 |
366 | })();
367 | return beforeEach(function() {
368 | if (jasmine.addMatchers != null) {
369 | return jasmine.addMatchers(jasmine.matcherWrapper.wrap(jasmine._given.matchers));
370 | } else {
371 | return this.addMatchers(jasmine._given.matchers);
372 | }
373 | });
374 | })(jasmine);
375 |
376 | }).call(this);
377 |
--------------------------------------------------------------------------------
/standalone/spec/helpers/jasmine-only.js:
--------------------------------------------------------------------------------
1 | /* jasmine-only - 0.1.0
2 | * Exclusivity spec helpers for jasmine: `describe.only` and `it.only`
3 | * https://github.com/davemo/jasmine-only
4 | */
5 | (function() {
6 | var __hasProp = {}.hasOwnProperty,
7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
8 |
9 | (function(jasmine) {
10 | var describeOnly, env, itOnly, root;
11 |
12 | root = this;
13 | env = jasmine.getEnv();
14 | describeOnly = function(description, specDefinitions) {
15 | var suite;
16 |
17 | suite = new jasmine.Suite(this, description, null, this.currentSuite);
18 | suite.exclusive_ = 1;
19 | this.exclusive_ = Math.max(this.exclusive_, 1);
20 | return this.describe_(suite, specDefinitions);
21 | };
22 | itOnly = function(description, func) {
23 | var spec;
24 |
25 | spec = this.it(description, func);
26 | spec.exclusive_ = 2;
27 | this.exclusive_ = 2;
28 | return spec;
29 | };
30 | env.exclusive_ = 0;
31 | env.describe = function(description, specDefinitions) {
32 | var suite;
33 |
34 | suite = new jasmine.Suite(this, description, null, this.currentSuite);
35 | return this.describe_(suite, specDefinitions);
36 | };
37 | env.describe_ = function(suite, specDefinitions) {
38 | var declarationError, e, parentSuite;
39 |
40 | parentSuite = this.currentSuite;
41 | if (parentSuite) {
42 | parentSuite.add(suite);
43 | } else {
44 | this.currentRunner_.add(suite);
45 | }
46 | this.currentSuite = suite;
47 | declarationError = null;
48 | try {
49 | specDefinitions.call(suite);
50 | } catch (_error) {
51 | e = _error;
52 | declarationError = e;
53 | }
54 | if (declarationError) {
55 | this.it("encountered a declaration exception", function() {
56 | throw declarationError;
57 | });
58 | }
59 | this.currentSuite = parentSuite;
60 | return suite;
61 | };
62 | env.specFilter = function(spec) {
63 | return this.exclusive_ <= spec.exclusive_;
64 | };
65 | env.describe.only = function() {
66 | return describeOnly.apply(env, arguments);
67 | };
68 | env.it.only = function() {
69 | return itOnly.apply(env, arguments);
70 | };
71 | root.describe.only = function(description, specDefinitions) {
72 | return env.describe.only(description, specDefinitions);
73 | };
74 | root.it.only = function(description, func) {
75 | return env.it.only(description, func);
76 | };
77 | root.iit = root.it.only;
78 | root.ddescribe = root.describe.only;
79 | jasmine.Spec = (function(_super) {
80 | __extends(Spec, _super);
81 |
82 | function Spec(env, suite, description) {
83 | this.exclusive_ = suite.exclusive_;
84 | Spec.__super__.constructor.call(this, env, suite, description);
85 | }
86 |
87 | return Spec;
88 |
89 | })(jasmine.Spec);
90 | return jasmine.Suite = (function(_super) {
91 | __extends(Suite, _super);
92 |
93 | function Suite(env, suite, specDefinitions, parentSuite) {
94 | this.exclusive_ = parentSuite && parentSuite.exclusive_ || 0;
95 | Suite.__super__.constructor.call(this, env, suite, specDefinitions, parentSuite);
96 | }
97 |
98 | return Suite;
99 |
100 | })(jasmine.Suite);
101 | })(jasmine);
102 |
103 | }).call(this);
104 |
--------------------------------------------------------------------------------
/standalone/spec/helpers/jasmine-stealth.js:
--------------------------------------------------------------------------------
1 | /* jasmine-stealth - 0.0.15
2 | * Makes Jasmine spies a bit more robust
3 | * https://github.com/searls/jasmine-stealth
4 | */
5 | (function() {
6 | var __hasProp = {}.hasOwnProperty,
7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
8 |
9 | (function() {
10 | var Captor, fake, root, stubChainer, unfakes, whatToDoWhenTheSpyGetsCalled, _;
11 | root = this;
12 | _ = function(obj) {
13 | return {
14 | each: function(iterator) {
15 | var item, _i, _len, _results;
16 | _results = [];
17 | for (_i = 0, _len = obj.length; _i < _len; _i++) {
18 | item = obj[_i];
19 | _results.push(iterator(item));
20 | }
21 | return _results;
22 | },
23 | isFunction: function() {
24 | return Object.prototype.toString.call(obj) === "[object Function]";
25 | },
26 | isString: function() {
27 | return Object.prototype.toString.call(obj) === "[object String]";
28 | }
29 | };
30 | };
31 | root.spyOnConstructor = function(owner, classToFake, methodsToSpy) {
32 | var fakeClass, spies;
33 | if (methodsToSpy == null) {
34 | methodsToSpy = [];
35 | }
36 | if (_(methodsToSpy).isString()) {
37 | methodsToSpy = [methodsToSpy];
38 | }
39 | spies = {
40 | constructor: jasmine.createSpy("" + classToFake + "'s constructor")
41 | };
42 | fakeClass = (function() {
43 | function _Class() {
44 | spies.constructor.apply(this, arguments);
45 | }
46 |
47 | return _Class;
48 |
49 | })();
50 | _(methodsToSpy).each(function(methodName) {
51 | spies[methodName] = jasmine.createSpy("" + classToFake + "#" + methodName);
52 | return fakeClass.prototype[methodName] = function() {
53 | return spies[methodName].apply(this, arguments);
54 | };
55 | });
56 | fake(owner, classToFake, fakeClass);
57 | return spies;
58 | };
59 | unfakes = [];
60 | afterEach(function() {
61 | _(unfakes).each(function(u) {
62 | return u();
63 | });
64 | return unfakes = [];
65 | });
66 | fake = function(owner, thingToFake, newThing) {
67 | var originalThing;
68 | originalThing = owner[thingToFake];
69 | owner[thingToFake] = newThing;
70 | return unfakes.push(function() {
71 | return owner[thingToFake] = originalThing;
72 | });
73 | };
74 | root.stubFor = root.spyOn;
75 | jasmine.createStub = jasmine.createSpy;
76 | jasmine.createStubObj = function(baseName, stubbings) {
77 | var name, obj, stubbing;
78 | if (stubbings.constructor === Array) {
79 | return jasmine.createSpyObj(baseName, stubbings);
80 | } else {
81 | obj = {};
82 | for (name in stubbings) {
83 | stubbing = stubbings[name];
84 | obj[name] = jasmine.createSpy(baseName + "." + name);
85 | if (_(stubbing).isFunction()) {
86 | obj[name].andCallFake(stubbing);
87 | } else {
88 | obj[name].andReturn(stubbing);
89 | }
90 | }
91 | return obj;
92 | }
93 | };
94 | whatToDoWhenTheSpyGetsCalled = function(spy) {
95 | var matchesStub, priorPlan;
96 | matchesStub = function(stubbing, args, context) {
97 | switch (stubbing.type) {
98 | case "args":
99 | return jasmine.getEnv().equals_(stubbing.ifThis, jasmine.util.argsToArray(args));
100 | case "context":
101 | return jasmine.getEnv().equals_(stubbing.ifThis, context);
102 | }
103 | };
104 | priorPlan = spy.plan;
105 | return spy.andCallFake(function() {
106 | var i, stubbing;
107 | i = 0;
108 | while (i < spy._stealth_stubbings.length) {
109 | stubbing = spy._stealth_stubbings[i];
110 | if (matchesStub(stubbing, arguments, this)) {
111 | if (stubbing.satisfaction === "callFake") {
112 | return stubbing.thenThat.apply(stubbing, arguments);
113 | } else {
114 | return stubbing.thenThat;
115 | }
116 | }
117 | i++;
118 | }
119 | return priorPlan.apply(spy, arguments);
120 | });
121 | };
122 | jasmine.Spy.prototype.whenContext = function(context) {
123 | var spy;
124 | spy = this;
125 | spy._stealth_stubbings || (spy._stealth_stubbings = []);
126 | whatToDoWhenTheSpyGetsCalled(spy);
127 | return stubChainer(spy, "context", context);
128 | };
129 | jasmine.Spy.prototype.when = function() {
130 | var ifThis, spy;
131 | spy = this;
132 | ifThis = jasmine.util.argsToArray(arguments);
133 | spy._stealth_stubbings || (spy._stealth_stubbings = []);
134 | whatToDoWhenTheSpyGetsCalled(spy);
135 | return stubChainer(spy, "args", ifThis);
136 | };
137 | stubChainer = function(spy, type, ifThis) {
138 | var addStubbing;
139 | addStubbing = function(satisfaction) {
140 | return function(thenThat) {
141 | spy._stealth_stubbings.push({
142 | type: type,
143 | ifThis: ifThis,
144 | satisfaction: satisfaction,
145 | thenThat: thenThat
146 | });
147 | return spy;
148 | };
149 | };
150 | return {
151 | thenReturn: addStubbing("return"),
152 | thenCallFake: addStubbing("callFake")
153 | };
154 | };
155 | jasmine.Spy.prototype.mostRecentCallThat = function(callThat, context) {
156 | var i;
157 | i = this.calls.length - 1;
158 | while (i >= 0) {
159 | if (callThat.call(context || this, this.calls[i]) === true) {
160 | return this.calls[i];
161 | }
162 | i--;
163 | }
164 | };
165 | jasmine.Matchers.ArgThat = (function(_super) {
166 | __extends(ArgThat, _super);
167 |
168 | function ArgThat(matcher) {
169 | this.matcher = matcher;
170 | }
171 |
172 | ArgThat.prototype.jasmineMatches = function(actual) {
173 | return this.matcher(actual);
174 | };
175 |
176 | return ArgThat;
177 |
178 | })(jasmine.Matchers.Any);
179 | jasmine.Matchers.ArgThat.prototype.matches = jasmine.Matchers.ArgThat.prototype.jasmineMatches;
180 | jasmine.argThat = function(expected) {
181 | return new jasmine.Matchers.ArgThat(expected);
182 | };
183 | jasmine.Matchers.Capture = (function(_super) {
184 | __extends(Capture, _super);
185 |
186 | function Capture(captor) {
187 | this.captor = captor;
188 | }
189 |
190 | Capture.prototype.jasmineMatches = function(actual) {
191 | this.captor.value = actual;
192 | return true;
193 | };
194 |
195 | return Capture;
196 |
197 | })(jasmine.Matchers.Any);
198 | jasmine.Matchers.Capture.prototype.matches = jasmine.Matchers.Capture.prototype.jasmineMatches;
199 | Captor = (function() {
200 | function Captor() {}
201 |
202 | Captor.prototype.capture = function() {
203 | return new jasmine.Matchers.Capture(this);
204 | };
205 |
206 | return Captor;
207 |
208 | })();
209 | return jasmine.captor = function() {
210 | return new Captor();
211 | };
212 | })();
213 |
214 | }).call(this);
215 |
--------------------------------------------------------------------------------
/standalone/spec/roman-numeral-converter-spec.coffee:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/standalone/spec/roman-numeral-converter-spec.coffee
--------------------------------------------------------------------------------
/standalone/src/roman-numeral-converter.coffee:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/real-world-testing/6370d14f47a46220f328b177746600eb53410fdd/standalone/src/roman-numeral-converter.coffee
--------------------------------------------------------------------------------
/standalone/vendor/underscore.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.4.4
2 | // ===================
3 |
4 | // > http://underscorejs.org
5 | // > (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
6 | // > Underscore may be freely distributed under the MIT license.
7 |
8 | // Baseline setup
9 | // --------------
10 | (function() {
11 |
12 | // Establish the root object, `window` in the browser, or `global` on the server.
13 | var root = this;
14 |
15 | // Save the previous value of the `_` variable.
16 | var previousUnderscore = root._;
17 |
18 | // Establish the object that gets returned to break out of a loop iteration.
19 | var breaker = {};
20 |
21 | // Save bytes in the minified (but not gzipped) version:
22 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
23 |
24 | // Create quick reference variables for speed access to core prototypes.
25 | var push = ArrayProto.push,
26 | slice = ArrayProto.slice,
27 | concat = ArrayProto.concat,
28 | toString = ObjProto.toString,
29 | hasOwnProperty = ObjProto.hasOwnProperty;
30 |
31 | // All **ECMAScript 5** native function implementations that we hope to use
32 | // are declared here.
33 | var
34 | nativeForEach = ArrayProto.forEach,
35 | nativeMap = ArrayProto.map,
36 | nativeReduce = ArrayProto.reduce,
37 | nativeReduceRight = ArrayProto.reduceRight,
38 | nativeFilter = ArrayProto.filter,
39 | nativeEvery = ArrayProto.every,
40 | nativeSome = ArrayProto.some,
41 | nativeIndexOf = ArrayProto.indexOf,
42 | nativeLastIndexOf = ArrayProto.lastIndexOf,
43 | nativeIsArray = Array.isArray,
44 | nativeKeys = Object.keys,
45 | nativeBind = FuncProto.bind;
46 |
47 | // Create a safe reference to the Underscore object for use below.
48 | var _ = function(obj) {
49 | if (obj instanceof _) return obj;
50 | if (!(this instanceof _)) return new _(obj);
51 | this._wrapped = obj;
52 | };
53 |
54 | // Export the Underscore object for **Node.js**, with
55 | // backwards-compatibility for the old `require()` API. If we're in
56 | // the browser, add `_` as a global object via a string identifier,
57 | // for Closure Compiler "advanced" mode.
58 | if (typeof exports !== 'undefined') {
59 | if (typeof module !== 'undefined' && module.exports) {
60 | exports = module.exports = _;
61 | }
62 | exports._ = _;
63 | } else {
64 | root._ = _;
65 | }
66 |
67 | // Current version.
68 | _.VERSION = '1.4.4';
69 |
70 | // Collection Functions
71 | // --------------------
72 |
73 | // The cornerstone, an `each` implementation, aka `forEach`.
74 | // Handles objects with the built-in `forEach`, arrays, and raw objects.
75 | // Delegates to **ECMAScript 5**'s native `forEach` if available.
76 | var each = _.each = _.forEach = function(obj, iterator, context) {
77 | if (obj == null) return;
78 | if (nativeForEach && obj.forEach === nativeForEach) {
79 | obj.forEach(iterator, context);
80 | } else if (obj.length === +obj.length) {
81 | for (var i = 0, l = obj.length; i < l; i++) {
82 | if (iterator.call(context, obj[i], i, obj) === breaker) return;
83 | }
84 | } else {
85 | for (var key in obj) {
86 | if (_.has(obj, key)) {
87 | if (iterator.call(context, obj[key], key, obj) === breaker) return;
88 | }
89 | }
90 | }
91 | };
92 |
93 | // Return the results of applying the iterator to each element.
94 | // Delegates to **ECMAScript 5**'s native `map` if available.
95 | _.map = _.collect = function(obj, iterator, context) {
96 | var results = [];
97 | if (obj == null) return results;
98 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
99 | each(obj, function(value, index, list) {
100 | results[results.length] = iterator.call(context, value, index, list);
101 | });
102 | return results;
103 | };
104 |
105 | var reduceError = 'Reduce of empty array with no initial value';
106 |
107 | // **Reduce** builds up a single result from a list of values, aka `inject`,
108 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
109 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
110 | var initial = arguments.length > 2;
111 | if (obj == null) obj = [];
112 | if (nativeReduce && obj.reduce === nativeReduce) {
113 | if (context) iterator = _.bind(iterator, context);
114 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
115 | }
116 | each(obj, function(value, index, list) {
117 | if (!initial) {
118 | memo = value;
119 | initial = true;
120 | } else {
121 | memo = iterator.call(context, memo, value, index, list);
122 | }
123 | });
124 | if (!initial) throw new TypeError(reduceError);
125 | return memo;
126 | };
127 |
128 | // The right-associative version of reduce, also known as `foldr`.
129 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
130 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
131 | var initial = arguments.length > 2;
132 | if (obj == null) obj = [];
133 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
134 | if (context) iterator = _.bind(iterator, context);
135 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
136 | }
137 | var length = obj.length;
138 | if (length !== +length) {
139 | var keys = _.keys(obj);
140 | length = keys.length;
141 | }
142 | each(obj, function(value, index, list) {
143 | index = keys ? keys[--length] : --length;
144 | if (!initial) {
145 | memo = obj[index];
146 | initial = true;
147 | } else {
148 | memo = iterator.call(context, memo, obj[index], index, list);
149 | }
150 | });
151 | if (!initial) throw new TypeError(reduceError);
152 | return memo;
153 | };
154 |
155 | // Return the first value which passes a truth test. Aliased as `detect`.
156 | _.find = _.detect = function(obj, iterator, context) {
157 | var result;
158 | any(obj, function(value, index, list) {
159 | if (iterator.call(context, value, index, list)) {
160 | result = value;
161 | return true;
162 | }
163 | });
164 | return result;
165 | };
166 |
167 | // Return all the elements that pass a truth test.
168 | // Delegates to **ECMAScript 5**'s native `filter` if available.
169 | // Aliased as `select`.
170 | _.filter = _.select = function(obj, iterator, context) {
171 | var results = [];
172 | if (obj == null) return results;
173 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
174 | each(obj, function(value, index, list) {
175 | if (iterator.call(context, value, index, list)) results[results.length] = value;
176 | });
177 | return results;
178 | };
179 |
180 | // Return all the elements for which a truth test fails.
181 | _.reject = function(obj, iterator, context) {
182 | return _.filter(obj, function(value, index, list) {
183 | return !iterator.call(context, value, index, list);
184 | }, context);
185 | };
186 |
187 | // Determine whether all of the elements match a truth test.
188 | // Delegates to **ECMAScript 5**'s native `every` if available.
189 | // Aliased as `all`.
190 | _.every = _.all = function(obj, iterator, context) {
191 | iterator || (iterator = _.identity);
192 | var result = true;
193 | if (obj == null) return result;
194 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
195 | each(obj, function(value, index, list) {
196 | if (!(result = result && iterator.call(context, value, index, list))) return breaker;
197 | });
198 | return !!result;
199 | };
200 |
201 | // Determine if at least one element in the object matches a truth test.
202 | // Delegates to **ECMAScript 5**'s native `some` if available.
203 | // Aliased as `any`.
204 | var any = _.some = _.any = function(obj, iterator, context) {
205 | iterator || (iterator = _.identity);
206 | var result = false;
207 | if (obj == null) return result;
208 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
209 | each(obj, function(value, index, list) {
210 | if (result || (result = iterator.call(context, value, index, list))) return breaker;
211 | });
212 | return !!result;
213 | };
214 |
215 | // Determine if the array or object contains a given value (using `===`).
216 | // Aliased as `include`.
217 | _.contains = _.include = function(obj, target) {
218 | if (obj == null) return false;
219 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
220 | return any(obj, function(value) {
221 | return value === target;
222 | });
223 | };
224 |
225 | // Invoke a method (with arguments) on every item in a collection.
226 | _.invoke = function(obj, method) {
227 | var args = slice.call(arguments, 2);
228 | var isFunc = _.isFunction(method);
229 | return _.map(obj, function(value) {
230 | return (isFunc ? method : value[method]).apply(value, args);
231 | });
232 | };
233 |
234 | // Convenience version of a common use case of `map`: fetching a property.
235 | _.pluck = function(obj, key) {
236 | return _.map(obj, function(value){ return value[key]; });
237 | };
238 |
239 | // Convenience version of a common use case of `filter`: selecting only objects
240 | // containing specific `key:value` pairs.
241 | _.where = function(obj, attrs, first) {
242 | if (_.isEmpty(attrs)) return first ? null : [];
243 | return _[first ? 'find' : 'filter'](obj, function(value) {
244 | for (var key in attrs) {
245 | if (attrs[key] !== value[key]) return false;
246 | }
247 | return true;
248 | });
249 | };
250 |
251 | // Convenience version of a common use case of `find`: getting the first object
252 | // containing specific `key:value` pairs.
253 | _.findWhere = function(obj, attrs) {
254 | return _.where(obj, attrs, true);
255 | };
256 |
257 | // Return the maximum element or (element-based computation).
258 | // Can't optimize arrays of integers longer than 65,535 elements.
259 | // See: https://bugs.webkit.org/show_bug.cgi?id=80797
260 | _.max = function(obj, iterator, context) {
261 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
262 | return Math.max.apply(Math, obj);
263 | }
264 | if (!iterator && _.isEmpty(obj)) return -Infinity;
265 | var result = {computed : -Infinity, value: -Infinity};
266 | each(obj, function(value, index, list) {
267 | var computed = iterator ? iterator.call(context, value, index, list) : value;
268 | computed >= result.computed && (result = {value : value, computed : computed});
269 | });
270 | return result.value;
271 | };
272 |
273 | // Return the minimum element (or element-based computation).
274 | _.min = function(obj, iterator, context) {
275 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
276 | return Math.min.apply(Math, obj);
277 | }
278 | if (!iterator && _.isEmpty(obj)) return Infinity;
279 | var result = {computed : Infinity, value: Infinity};
280 | each(obj, function(value, index, list) {
281 | var computed = iterator ? iterator.call(context, value, index, list) : value;
282 | computed < result.computed && (result = {value : value, computed : computed});
283 | });
284 | return result.value;
285 | };
286 |
287 | // Shuffle an array.
288 | _.shuffle = function(obj) {
289 | var rand;
290 | var index = 0;
291 | var shuffled = [];
292 | each(obj, function(value) {
293 | rand = _.random(index++);
294 | shuffled[index - 1] = shuffled[rand];
295 | shuffled[rand] = value;
296 | });
297 | return shuffled;
298 | };
299 |
300 | // An internal function to generate lookup iterators.
301 | var lookupIterator = function(value) {
302 | return _.isFunction(value) ? value : function(obj){ return obj[value]; };
303 | };
304 |
305 | // Sort the object's values by a criterion produced by an iterator.
306 | _.sortBy = function(obj, value, context) {
307 | var iterator = lookupIterator(value);
308 | return _.pluck(_.map(obj, function(value, index, list) {
309 | return {
310 | value : value,
311 | index : index,
312 | criteria : iterator.call(context, value, index, list)
313 | };
314 | }).sort(function(left, right) {
315 | var a = left.criteria;
316 | var b = right.criteria;
317 | if (a !== b) {
318 | if (a > b || a === void 0) return 1;
319 | if (a < b || b === void 0) return -1;
320 | }
321 | return left.index < right.index ? -1 : 1;
322 | }), 'value');
323 | };
324 |
325 | // An internal function used for aggregate "group by" operations.
326 | var group = function(obj, value, context, behavior) {
327 | var result = {};
328 | var iterator = lookupIterator(value || _.identity);
329 | each(obj, function(value, index) {
330 | var key = iterator.call(context, value, index, obj);
331 | behavior(result, key, value);
332 | });
333 | return result;
334 | };
335 |
336 | // Groups the object's values by a criterion. Pass either a string attribute
337 | // to group by, or a function that returns the criterion.
338 | _.groupBy = function(obj, value, context) {
339 | return group(obj, value, context, function(result, key, value) {
340 | (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
341 | });
342 | };
343 |
344 | // Counts instances of an object that group by a certain criterion. Pass
345 | // either a string attribute to count by, or a function that returns the
346 | // criterion.
347 | _.countBy = function(obj, value, context) {
348 | return group(obj, value, context, function(result, key) {
349 | if (!_.has(result, key)) result[key] = 0;
350 | result[key]++;
351 | });
352 | };
353 |
354 | // Use a comparator function to figure out the smallest index at which
355 | // an object should be inserted so as to maintain order. Uses binary search.
356 | _.sortedIndex = function(array, obj, iterator, context) {
357 | iterator = iterator == null ? _.identity : lookupIterator(iterator);
358 | var value = iterator.call(context, obj);
359 | var low = 0, high = array.length;
360 | while (low < high) {
361 | var mid = (low + high) >>> 1;
362 | iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
363 | }
364 | return low;
365 | };
366 |
367 | // Safely convert anything iterable into a real, live array.
368 | _.toArray = function(obj) {
369 | if (!obj) return [];
370 | if (_.isArray(obj)) return slice.call(obj);
371 | if (obj.length === +obj.length) return _.map(obj, _.identity);
372 | return _.values(obj);
373 | };
374 |
375 | // Return the number of elements in an object.
376 | _.size = function(obj) {
377 | if (obj == null) return 0;
378 | return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
379 | };
380 |
381 | // Array Functions
382 | // ---------------
383 |
384 | // Get the first element of an array. Passing **n** will return the first N
385 | // values in the array. Aliased as `head` and `take`. The **guard** check
386 | // allows it to work with `_.map`.
387 | _.first = _.head = _.take = function(array, n, guard) {
388 | if (array == null) return void 0;
389 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
390 | };
391 |
392 | // Returns everything but the last entry of the array. Especially useful on
393 | // the arguments object. Passing **n** will return all the values in
394 | // the array, excluding the last N. The **guard** check allows it to work with
395 | // `_.map`.
396 | _.initial = function(array, n, guard) {
397 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
398 | };
399 |
400 | // Get the last element of an array. Passing **n** will return the last N
401 | // values in the array. The **guard** check allows it to work with `_.map`.
402 | _.last = function(array, n, guard) {
403 | if (array == null) return void 0;
404 | if ((n != null) && !guard) {
405 | return slice.call(array, Math.max(array.length - n, 0));
406 | } else {
407 | return array[array.length - 1];
408 | }
409 | };
410 |
411 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
412 | // Especially useful on the arguments object. Passing an **n** will return
413 | // the rest N values in the array. The **guard**
414 | // check allows it to work with `_.map`.
415 | _.rest = _.tail = _.drop = function(array, n, guard) {
416 | return slice.call(array, (n == null) || guard ? 1 : n);
417 | };
418 |
419 | // Trim out all falsy values from an array.
420 | _.compact = function(array) {
421 | return _.filter(array, _.identity);
422 | };
423 |
424 | // Internal implementation of a recursive `flatten` function.
425 | var flatten = function(input, shallow, output) {
426 | each(input, function(value) {
427 | if (_.isArray(value)) {
428 | shallow ? push.apply(output, value) : flatten(value, shallow, output);
429 | } else {
430 | output.push(value);
431 | }
432 | });
433 | return output;
434 | };
435 |
436 | // Return a completely flattened version of an array.
437 | _.flatten = function(array, shallow) {
438 | return flatten(array, shallow, []);
439 | };
440 |
441 | // Return a version of the array that does not contain the specified value(s).
442 | _.without = function(array) {
443 | return _.difference(array, slice.call(arguments, 1));
444 | };
445 |
446 | // Produce a duplicate-free version of the array. If the array has already
447 | // been sorted, you have the option of using a faster algorithm.
448 | // Aliased as `unique`.
449 | _.uniq = _.unique = function(array, isSorted, iterator, context) {
450 | if (_.isFunction(isSorted)) {
451 | context = iterator;
452 | iterator = isSorted;
453 | isSorted = false;
454 | }
455 | var initial = iterator ? _.map(array, iterator, context) : array;
456 | var results = [];
457 | var seen = [];
458 | each(initial, function(value, index) {
459 | if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
460 | seen.push(value);
461 | results.push(array[index]);
462 | }
463 | });
464 | return results;
465 | };
466 |
467 | // Produce an array that contains the union: each distinct element from all of
468 | // the passed-in arrays.
469 | _.union = function() {
470 | return _.uniq(concat.apply(ArrayProto, arguments));
471 | };
472 |
473 | // Produce an array that contains every item shared between all the
474 | // passed-in arrays.
475 | _.intersection = function(array) {
476 | var rest = slice.call(arguments, 1);
477 | return _.filter(_.uniq(array), function(item) {
478 | return _.every(rest, function(other) {
479 | return _.indexOf(other, item) >= 0;
480 | });
481 | });
482 | };
483 |
484 | // Take the difference between one array and a number of other arrays.
485 | // Only the elements present in just the first array will remain.
486 | _.difference = function(array) {
487 | var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
488 | return _.filter(array, function(value){ return !_.contains(rest, value); });
489 | };
490 |
491 | // Zip together multiple lists into a single array -- elements that share
492 | // an index go together.
493 | _.zip = function() {
494 | var args = slice.call(arguments);
495 | var length = _.max(_.pluck(args, 'length'));
496 | var results = new Array(length);
497 | for (var i = 0; i < length; i++) {
498 | results[i] = _.pluck(args, "" + i);
499 | }
500 | return results;
501 | };
502 |
503 | // Converts lists into objects. Pass either a single array of `[key, value]`
504 | // pairs, or two parallel arrays of the same length -- one of keys, and one of
505 | // the corresponding values.
506 | _.object = function(list, values) {
507 | if (list == null) return {};
508 | var result = {};
509 | for (var i = 0, l = list.length; i < l; i++) {
510 | if (values) {
511 | result[list[i]] = values[i];
512 | } else {
513 | result[list[i][0]] = list[i][1];
514 | }
515 | }
516 | return result;
517 | };
518 |
519 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
520 | // we need this function. Return the position of the first occurrence of an
521 | // item in an array, or -1 if the item is not included in the array.
522 | // Delegates to **ECMAScript 5**'s native `indexOf` if available.
523 | // If the array is large and already in sort order, pass `true`
524 | // for **isSorted** to use binary search.
525 | _.indexOf = function(array, item, isSorted) {
526 | if (array == null) return -1;
527 | var i = 0, l = array.length;
528 | if (isSorted) {
529 | if (typeof isSorted == 'number') {
530 | i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
531 | } else {
532 | i = _.sortedIndex(array, item);
533 | return array[i] === item ? i : -1;
534 | }
535 | }
536 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
537 | for (; i < l; i++) if (array[i] === item) return i;
538 | return -1;
539 | };
540 |
541 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
542 | _.lastIndexOf = function(array, item, from) {
543 | if (array == null) return -1;
544 | var hasIndex = from != null;
545 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
546 | return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
547 | }
548 | var i = (hasIndex ? from : array.length);
549 | while (i--) if (array[i] === item) return i;
550 | return -1;
551 | };
552 |
553 | // Generate an integer Array containing an arithmetic progression. A port of
554 | // the native Python `range()` function. See
555 | // [the Python documentation](http://docs.python.org/library/functions.html#range).
556 | _.range = function(start, stop, step) {
557 | if (arguments.length <= 1) {
558 | stop = start || 0;
559 | start = 0;
560 | }
561 | step = arguments[2] || 1;
562 |
563 | var len = Math.max(Math.ceil((stop - start) / step), 0);
564 | var idx = 0;
565 | var range = new Array(len);
566 |
567 | while(idx < len) {
568 | range[idx++] = start;
569 | start += step;
570 | }
571 |
572 | return range;
573 | };
574 |
575 | // Function (ahem) Functions
576 | // ------------------
577 |
578 | // Create a function bound to a given object (assigning `this`, and arguments,
579 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
580 | // available.
581 | _.bind = function(func, context) {
582 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
583 | var args = slice.call(arguments, 2);
584 | return function() {
585 | return func.apply(context, args.concat(slice.call(arguments)));
586 | };
587 | };
588 |
589 | // Partially apply a function by creating a version that has had some of its
590 | // arguments pre-filled, without changing its dynamic `this` context.
591 | _.partial = function(func) {
592 | var args = slice.call(arguments, 1);
593 | return function() {
594 | return func.apply(this, args.concat(slice.call(arguments)));
595 | };
596 | };
597 |
598 | // Bind all of an object's methods to that object. Useful for ensuring that
599 | // all callbacks defined on an object belong to it.
600 | _.bindAll = function(obj) {
601 | var funcs = slice.call(arguments, 1);
602 | if (funcs.length === 0) funcs = _.functions(obj);
603 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
604 | return obj;
605 | };
606 |
607 | // Memoize an expensive function by storing its results.
608 | _.memoize = function(func, hasher) {
609 | var memo = {};
610 | hasher || (hasher = _.identity);
611 | return function() {
612 | var key = hasher.apply(this, arguments);
613 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
614 | };
615 | };
616 |
617 | // Delays a function for the given number of milliseconds, and then calls
618 | // it with the arguments supplied.
619 | _.delay = function(func, wait) {
620 | var args = slice.call(arguments, 2);
621 | return setTimeout(function(){ return func.apply(null, args); }, wait);
622 | };
623 |
624 | // Defers a function, scheduling it to run after the current call stack has
625 | // cleared.
626 | _.defer = function(func) {
627 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
628 | };
629 |
630 | // Returns a function, that, when invoked, will only be triggered at most once
631 | // during a given window of time.
632 | _.throttle = function(func, wait) {
633 | var context, args, timeout, result;
634 | var previous = 0;
635 | var later = function() {
636 | previous = new Date;
637 | timeout = null;
638 | result = func.apply(context, args);
639 | };
640 | return function() {
641 | var now = new Date;
642 | var remaining = wait - (now - previous);
643 | context = this;
644 | args = arguments;
645 | if (remaining <= 0) {
646 | clearTimeout(timeout);
647 | timeout = null;
648 | previous = now;
649 | result = func.apply(context, args);
650 | } else if (!timeout) {
651 | timeout = setTimeout(later, remaining);
652 | }
653 | return result;
654 | };
655 | };
656 |
657 | // Returns a function, that, as long as it continues to be invoked, will not
658 | // be triggered. The function will be called after it stops being called for
659 | // N milliseconds. If `immediate` is passed, trigger the function on the
660 | // leading edge, instead of the trailing.
661 | _.debounce = function(func, wait, immediate) {
662 | var timeout, result;
663 | return function() {
664 | var context = this, args = arguments;
665 | var later = function() {
666 | timeout = null;
667 | if (!immediate) result = func.apply(context, args);
668 | };
669 | var callNow = immediate && !timeout;
670 | clearTimeout(timeout);
671 | timeout = setTimeout(later, wait);
672 | if (callNow) result = func.apply(context, args);
673 | return result;
674 | };
675 | };
676 |
677 | // Returns a function that will be executed at most one time, no matter how
678 | // often you call it. Useful for lazy initialization.
679 | _.once = function(func) {
680 | var ran = false, memo;
681 | return function() {
682 | if (ran) return memo;
683 | ran = true;
684 | memo = func.apply(this, arguments);
685 | func = null;
686 | return memo;
687 | };
688 | };
689 |
690 | // Returns the first function passed as an argument to the second,
691 | // allowing you to adjust arguments, run code before and after, and
692 | // conditionally execute the original function.
693 | _.wrap = function(func, wrapper) {
694 | return function() {
695 | var args = [func];
696 | push.apply(args, arguments);
697 | return wrapper.apply(this, args);
698 | };
699 | };
700 |
701 | // Returns a function that is the composition of a list of functions, each
702 | // consuming the return value of the function that follows.
703 | _.compose = function() {
704 | var funcs = arguments;
705 | return function() {
706 | var args = arguments;
707 | for (var i = funcs.length - 1; i >= 0; i--) {
708 | args = [funcs[i].apply(this, args)];
709 | }
710 | return args[0];
711 | };
712 | };
713 |
714 | // Returns a function that will only be executed after being called N times.
715 | _.after = function(times, func) {
716 | if (times <= 0) return func();
717 | return function() {
718 | if (--times < 1) {
719 | return func.apply(this, arguments);
720 | }
721 | };
722 | };
723 |
724 | // Object Functions
725 | // ----------------
726 |
727 | // Retrieve the names of an object's properties.
728 | // Delegates to **ECMAScript 5**'s native `Object.keys`
729 | _.keys = nativeKeys || function(obj) {
730 | if (obj !== Object(obj)) throw new TypeError('Invalid object');
731 | var keys = [];
732 | for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
733 | return keys;
734 | };
735 |
736 | // Retrieve the values of an object's properties.
737 | _.values = function(obj) {
738 | var values = [];
739 | for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
740 | return values;
741 | };
742 |
743 | // Convert an object into a list of `[key, value]` pairs.
744 | _.pairs = function(obj) {
745 | var pairs = [];
746 | for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
747 | return pairs;
748 | };
749 |
750 | // Invert the keys and values of an object. The values must be serializable.
751 | _.invert = function(obj) {
752 | var result = {};
753 | for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
754 | return result;
755 | };
756 |
757 | // Return a sorted list of the function names available on the object.
758 | // Aliased as `methods`
759 | _.functions = _.methods = function(obj) {
760 | var names = [];
761 | for (var key in obj) {
762 | if (_.isFunction(obj[key])) names.push(key);
763 | }
764 | return names.sort();
765 | };
766 |
767 | // Extend a given object with all the properties in passed-in object(s).
768 | _.extend = function(obj) {
769 | each(slice.call(arguments, 1), function(source) {
770 | if (source) {
771 | for (var prop in source) {
772 | obj[prop] = source[prop];
773 | }
774 | }
775 | });
776 | return obj;
777 | };
778 |
779 | // Return a copy of the object only containing the whitelisted properties.
780 | _.pick = function(obj) {
781 | var copy = {};
782 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
783 | each(keys, function(key) {
784 | if (key in obj) copy[key] = obj[key];
785 | });
786 | return copy;
787 | };
788 |
789 | // Return a copy of the object without the blacklisted properties.
790 | _.omit = function(obj) {
791 | var copy = {};
792 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
793 | for (var key in obj) {
794 | if (!_.contains(keys, key)) copy[key] = obj[key];
795 | }
796 | return copy;
797 | };
798 |
799 | // Fill in a given object with default properties.
800 | _.defaults = function(obj) {
801 | each(slice.call(arguments, 1), function(source) {
802 | if (source) {
803 | for (var prop in source) {
804 | if (obj[prop] == null) obj[prop] = source[prop];
805 | }
806 | }
807 | });
808 | return obj;
809 | };
810 |
811 | // Create a (shallow-cloned) duplicate of an object.
812 | _.clone = function(obj) {
813 | if (!_.isObject(obj)) return obj;
814 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
815 | };
816 |
817 | // Invokes interceptor with the obj, and then returns obj.
818 | // The primary purpose of this method is to "tap into" a method chain, in
819 | // order to perform operations on intermediate results within the chain.
820 | _.tap = function(obj, interceptor) {
821 | interceptor(obj);
822 | return obj;
823 | };
824 |
825 | // Internal recursive comparison function for `isEqual`.
826 | var eq = function(a, b, aStack, bStack) {
827 | // Identical objects are equal. `0 === -0`, but they aren't identical.
828 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
829 | if (a === b) return a !== 0 || 1 / a == 1 / b;
830 | // A strict comparison is necessary because `null == undefined`.
831 | if (a == null || b == null) return a === b;
832 | // Unwrap any wrapped objects.
833 | if (a instanceof _) a = a._wrapped;
834 | if (b instanceof _) b = b._wrapped;
835 | // Compare `[[Class]]` names.
836 | var className = toString.call(a);
837 | if (className != toString.call(b)) return false;
838 | switch (className) {
839 | // Strings, numbers, dates, and booleans are compared by value.
840 | case '[object String]':
841 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
842 | // equivalent to `new String("5")`.
843 | return a == String(b);
844 | case '[object Number]':
845 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
846 | // other numeric values.
847 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
848 | case '[object Date]':
849 | case '[object Boolean]':
850 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their
851 | // millisecond representations. Note that invalid dates with millisecond representations
852 | // of `NaN` are not equivalent.
853 | return +a == +b;
854 | // RegExps are compared by their source patterns and flags.
855 | case '[object RegExp]':
856 | return a.source == b.source &&
857 | a.global == b.global &&
858 | a.multiline == b.multiline &&
859 | a.ignoreCase == b.ignoreCase;
860 | }
861 | if (typeof a != 'object' || typeof b != 'object') return false;
862 | // Assume equality for cyclic structures. The algorithm for detecting cyclic
863 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
864 | var length = aStack.length;
865 | while (length--) {
866 | // Linear search. Performance is inversely proportional to the number of
867 | // unique nested structures.
868 | if (aStack[length] == a) return bStack[length] == b;
869 | }
870 | // Add the first object to the stack of traversed objects.
871 | aStack.push(a);
872 | bStack.push(b);
873 | var size = 0, result = true;
874 | // Recursively compare objects and arrays.
875 | if (className == '[object Array]') {
876 | // Compare array lengths to determine if a deep comparison is necessary.
877 | size = a.length;
878 | result = size == b.length;
879 | if (result) {
880 | // Deep compare the contents, ignoring non-numeric properties.
881 | while (size--) {
882 | if (!(result = eq(a[size], b[size], aStack, bStack))) break;
883 | }
884 | }
885 | } else {
886 | // Objects with different constructors are not equivalent, but `Object`s
887 | // from different frames are.
888 | var aCtor = a.constructor, bCtor = b.constructor;
889 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
890 | _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
891 | return false;
892 | }
893 | // Deep compare objects.
894 | for (var key in a) {
895 | if (_.has(a, key)) {
896 | // Count the expected number of properties.
897 | size++;
898 | // Deep compare each member.
899 | if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
900 | }
901 | }
902 | // Ensure that both objects contain the same number of properties.
903 | if (result) {
904 | for (key in b) {
905 | if (_.has(b, key) && !(size--)) break;
906 | }
907 | result = !size;
908 | }
909 | }
910 | // Remove the first object from the stack of traversed objects.
911 | aStack.pop();
912 | bStack.pop();
913 | return result;
914 | };
915 |
916 | // Perform a deep comparison to check if two objects are equal.
917 | _.isEqual = function(a, b) {
918 | return eq(a, b, [], []);
919 | };
920 |
921 | // Is a given array, string, or object empty?
922 | // An "empty" object has no enumerable own-properties.
923 | _.isEmpty = function(obj) {
924 | if (obj == null) return true;
925 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
926 | for (var key in obj) if (_.has(obj, key)) return false;
927 | return true;
928 | };
929 |
930 | // Is a given value a DOM element?
931 | _.isElement = function(obj) {
932 | return !!(obj && obj.nodeType === 1);
933 | };
934 |
935 | // Is a given value an array?
936 | // Delegates to ECMA5's native Array.isArray
937 | _.isArray = nativeIsArray || function(obj) {
938 | return toString.call(obj) == '[object Array]';
939 | };
940 |
941 | // Is a given variable an object?
942 | _.isObject = function(obj) {
943 | return obj === Object(obj);
944 | };
945 |
946 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
947 | each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
948 | _['is' + name] = function(obj) {
949 | return toString.call(obj) == '[object ' + name + ']';
950 | };
951 | });
952 |
953 | // Define a fallback version of the method in browsers (ahem, IE), where
954 | // there isn't any inspectable "Arguments" type.
955 | if (!_.isArguments(arguments)) {
956 | _.isArguments = function(obj) {
957 | return !!(obj && _.has(obj, 'callee'));
958 | };
959 | }
960 |
961 | // Optimize `isFunction` if appropriate.
962 | if (typeof (/./) !== 'function') {
963 | _.isFunction = function(obj) {
964 | return typeof obj === 'function';
965 | };
966 | }
967 |
968 | // Is a given object a finite number?
969 | _.isFinite = function(obj) {
970 | return isFinite(obj) && !isNaN(parseFloat(obj));
971 | };
972 |
973 | // Is the given value `NaN`? (NaN is the only number which does not equal itself).
974 | _.isNaN = function(obj) {
975 | return _.isNumber(obj) && obj != +obj;
976 | };
977 |
978 | // Is a given value a boolean?
979 | _.isBoolean = function(obj) {
980 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
981 | };
982 |
983 | // Is a given value equal to null?
984 | _.isNull = function(obj) {
985 | return obj === null;
986 | };
987 |
988 | // Is a given variable undefined?
989 | _.isUndefined = function(obj) {
990 | return obj === void 0;
991 | };
992 |
993 | // Shortcut function for checking if an object has a given property directly
994 | // on itself (in other words, not on a prototype).
995 | _.has = function(obj, key) {
996 | return hasOwnProperty.call(obj, key);
997 | };
998 |
999 | // Utility Functions
1000 | // -----------------
1001 |
1002 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
1003 | // previous owner. Returns a reference to the Underscore object.
1004 | _.noConflict = function() {
1005 | root._ = previousUnderscore;
1006 | return this;
1007 | };
1008 |
1009 | // Keep the identity function around for default iterators.
1010 | _.identity = function(value) {
1011 | return value;
1012 | };
1013 |
1014 | // Run a function **n** times.
1015 | _.times = function(n, iterator, context) {
1016 | var accum = Array(n);
1017 | for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
1018 | return accum;
1019 | };
1020 |
1021 | // Return a random integer between min and max (inclusive).
1022 | _.random = function(min, max) {
1023 | if (max == null) {
1024 | max = min;
1025 | min = 0;
1026 | }
1027 | return min + Math.floor(Math.random() * (max - min + 1));
1028 | };
1029 |
1030 | // List of HTML entities for escaping.
1031 | var entityMap = {
1032 | escape: {
1033 | '&': '&',
1034 | '<': '<',
1035 | '>': '>',
1036 | '"': '"',
1037 | "'": ''',
1038 | '/': '/'
1039 | }
1040 | };
1041 | entityMap.unescape = _.invert(entityMap.escape);
1042 |
1043 | // Regexes containing the keys and values listed immediately above.
1044 | var entityRegexes = {
1045 | escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
1046 | unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
1047 | };
1048 |
1049 | // Functions for escaping and unescaping strings to/from HTML interpolation.
1050 | _.each(['escape', 'unescape'], function(method) {
1051 | _[method] = function(string) {
1052 | if (string == null) return '';
1053 | return ('' + string).replace(entityRegexes[method], function(match) {
1054 | return entityMap[method][match];
1055 | });
1056 | };
1057 | });
1058 |
1059 | // If the value of the named property is a function then invoke it;
1060 | // otherwise, return it.
1061 | _.result = function(object, property) {
1062 | if (object == null) return null;
1063 | var value = object[property];
1064 | return _.isFunction(value) ? value.call(object) : value;
1065 | };
1066 |
1067 | // Add your own custom functions to the Underscore object.
1068 | _.mixin = function(obj) {
1069 | each(_.functions(obj), function(name){
1070 | var func = _[name] = obj[name];
1071 | _.prototype[name] = function() {
1072 | var args = [this._wrapped];
1073 | push.apply(args, arguments);
1074 | return result.call(this, func.apply(_, args));
1075 | };
1076 | });
1077 | };
1078 |
1079 | // Generate a unique integer id (unique within the entire client session).
1080 | // Useful for temporary DOM ids.
1081 | var idCounter = 0;
1082 | _.uniqueId = function(prefix) {
1083 | var id = ++idCounter + '';
1084 | return prefix ? prefix + id : id;
1085 | };
1086 |
1087 | // By default, Underscore uses ERB-style template delimiters, change the
1088 | // following template settings to use alternative delimiters.
1089 | _.templateSettings = {
1090 | evaluate : /<%([\s\S]+?)%>/g,
1091 | interpolate : /<%=([\s\S]+?)%>/g,
1092 | escape : /<%-([\s\S]+?)%>/g
1093 | };
1094 |
1095 | // When customizing `templateSettings`, if you don't want to define an
1096 | // interpolation, evaluation or escaping regex, we need one that is
1097 | // guaranteed not to match.
1098 | var noMatch = /(.)^/;
1099 |
1100 | // Certain characters need to be escaped so that they can be put into a
1101 | // string literal.
1102 | var escapes = {
1103 | "'": "'",
1104 | '\\': '\\',
1105 | '\r': 'r',
1106 | '\n': 'n',
1107 | '\t': 't',
1108 | '\u2028': 'u2028',
1109 | '\u2029': 'u2029'
1110 | };
1111 |
1112 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
1113 |
1114 | // JavaScript micro-templating, similar to John Resig's implementation.
1115 | // Underscore templating handles arbitrary delimiters, preserves whitespace,
1116 | // and correctly escapes quotes within interpolated code.
1117 | _.template = function(text, data, settings) {
1118 | var render;
1119 | settings = _.defaults({}, settings, _.templateSettings);
1120 |
1121 | // Combine delimiters into one regular expression via alternation.
1122 | var matcher = new RegExp([
1123 | (settings.escape || noMatch).source,
1124 | (settings.interpolate || noMatch).source,
1125 | (settings.evaluate || noMatch).source
1126 | ].join('|') + '|$', 'g');
1127 |
1128 | // Compile the template source, escaping string literals appropriately.
1129 | var index = 0;
1130 | var source = "__p+='";
1131 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
1132 | source += text.slice(index, offset)
1133 | .replace(escaper, function(match) { return '\\' + escapes[match]; });
1134 |
1135 | if (escape) {
1136 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
1137 | }
1138 | if (interpolate) {
1139 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
1140 | }
1141 | if (evaluate) {
1142 | source += "';\n" + evaluate + "\n__p+='";
1143 | }
1144 | index = offset + match.length;
1145 | return match;
1146 | });
1147 | source += "';\n";
1148 |
1149 | // If a variable is not specified, place data values in local scope.
1150 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
1151 |
1152 | source = "var __t,__p='',__j=Array.prototype.join," +
1153 | "print=function(){__p+=__j.call(arguments,'');};\n" +
1154 | source + "return __p;\n";
1155 |
1156 | try {
1157 | render = new Function(settings.variable || 'obj', '_', source);
1158 | } catch (e) {
1159 | e.source = source;
1160 | throw e;
1161 | }
1162 |
1163 | if (data) return render(data, _);
1164 | var template = function(data) {
1165 | return render.call(this, data, _);
1166 | };
1167 |
1168 | // Provide the compiled function source as a convenience for precompilation.
1169 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
1170 |
1171 | return template;
1172 | };
1173 |
1174 | // Add a "chain" function, which will delegate to the wrapper.
1175 | _.chain = function(obj) {
1176 | return _(obj).chain();
1177 | };
1178 |
1179 | // OOP
1180 | // ---------------
1181 | // If Underscore is called as a function, it returns a wrapped object that
1182 | // can be used OO-style. This wrapper holds altered versions of all the
1183 | // underscore functions. Wrapped objects may be chained.
1184 |
1185 | // Helper function to continue chaining intermediate results.
1186 | var result = function(obj) {
1187 | return this._chain ? _(obj).chain() : obj;
1188 | };
1189 |
1190 | // Add all of the Underscore functions to the wrapper object.
1191 | _.mixin(_);
1192 |
1193 | // Add all mutator Array functions to the wrapper.
1194 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1195 | var method = ArrayProto[name];
1196 | _.prototype[name] = function() {
1197 | var obj = this._wrapped;
1198 | method.apply(obj, arguments);
1199 | if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
1200 | return result.call(this, obj);
1201 | };
1202 | });
1203 |
1204 | // Add all accessor Array functions to the wrapper.
1205 | each(['concat', 'join', 'slice'], function(name) {
1206 | var method = ArrayProto[name];
1207 | _.prototype[name] = function() {
1208 | return result.call(this, method.apply(this._wrapped, arguments));
1209 | };
1210 | });
1211 |
1212 | _.extend(_.prototype, {
1213 |
1214 | // Start chaining a wrapped Underscore object.
1215 | chain: function() {
1216 | this._chain = true;
1217 | return this;
1218 | },
1219 |
1220 | // Extracts the result from a wrapped and chained object.
1221 | value: function() {
1222 | return this._wrapped;
1223 | }
1224 |
1225 | });
1226 |
1227 | }).call(this);
1228 |
--------------------------------------------------------------------------------