├── .gitignore
├── KoansRunner.html
├── LICENSE
├── README.markdown
├── jasmine
└── runner_specs
│ ├── TestJSKoansRunner.html
│ └── suites
│ └── KoansRunnerSpec.js
├── koans
├── AboutApplyingWhatWeHaveLearnt.js
├── AboutArrays.js
├── AboutExpects.js
├── AboutFunctions.js
├── AboutHigherOrderFunctions.js
├── AboutInheritance.js
├── AboutMutability.js
└── AboutObjects.js
└── lib
├── FILL_ME_IN.js
├── jasmine
├── jasmine-html.js
├── jasmine.css
├── jasmine.js
├── jskoans-jasmine-html.js
└── jskoans-jasmine.css
├── jsTestDriver
├── JsTestDriver.jar
├── capture_browser.sh
├── jsTestDriver.conf
└── run_all_koans.sh
└── underscore-min.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/KoansRunner.html:
--------------------------------------------------------------------------------
1 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2 | "http://www.w3.org/TR/html4/loose.dtd">
3 | <html>
4 | <head>
5 | <title>Javascript Koans</title>
6 | <link rel="stylesheet" type="text/css" href="lib/jasmine/jskoans-jasmine.css">
7 | <script type="text/javascript" src="lib/jasmine/jasmine.js"></script>
8 | <script type="text/javascript" src="lib/jasmine/jasmine-html.js"></script>
9 | <script type="text/javascript" src="lib/jasmine/jskoans-jasmine-html.js"></script>
10 |
11 | <script type="text/javascript" src="lib/underscore-min.js"></script>
12 |
13 | <script type="text/javascript" src="lib/FILL_ME_IN.js"></script>
14 |
15 | <script type="text/javascript" src="koans/AboutExpects.js"></script>
16 | <script type="text/javascript" src="koans/AboutArrays.js"></script>
17 | <script type="text/javascript" src="koans/AboutFunctions.js"></script>
18 | <script type="text/javascript" src="koans/AboutObjects.js"></script>
19 | <script type="text/javascript" src="koans/AboutMutability.js"></script>
20 | <script type="text/javascript" src="koans/AboutHigherOrderFunctions.js"></script>
21 | <script type="text/javascript" src="koans/AboutInheritance.js"></script>
22 | <script type="text/javascript" src="koans/AboutApplyingWhatWeHaveLearnt.js"></script>
23 |
24 | </head>
25 | <body>
26 |
27 | <script type="text/javascript">
28 | jasmine.getEnv().addReporter(new JsKoansReporter());
29 | jasmine.getEnv().execute();
30 | </script>
31 |
32 | </body>
33 | </html>
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2010-2014 David Laing and Greg Malcolm
4 |
5 | Permission is hereby granted, free of charge, to any person
6 | obtaining a copy of this software and associated documentation
7 | files (the "Software"), to deal in the Software without
8 | restriction, including without limitation the rights to use,
9 | copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following
12 | conditions:
13 |
14 | The above copyright notice and this permission notice shall be
15 | included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 | OTHER DEALINGS IN THE SOFTWARE.
25 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # javascript-koans
2 | Based on Edgecase's fantastic
3 | [Ruby koans](http://github.com/edgecase/ruby_koans), the goal of the
4 | Javascript koans is to teach you Javascript programming through
5 | testing.
6 |
7 | When you first run the koans, you'll be presented with a runtime error and a
8 | stack trace indicating where the error occurred. Your goal is to make the
9 | error go away. As you fix each error, you should learn something about the
10 | Javascript language and functional programming in general.
11 |
12 | Your journey towards Javascript enlightenment starts in the koans/AboutExpects.js file. These
13 | koans will be very simple, so don't overthink them! As you progress through
14 | more koans, more and more Javascript syntax will be introduced which will allow
15 | you to solve more complicated problems and use more advanced techniques.
16 |
17 | ## Running the Koans
18 | Simply navigate to the Javascript Koans folder using a file browser, and
19 | double click on KoansRunnner.html.
20 |
21 | Any browser will do, but for the best results Firefox or Chrome is
22 | recommended. More stack trace information shows up for javascript on these
23 | browsers.
24 |
25 | The first error will be in koans/AboutExpects.js. Fix the first test and
26 | refresh the browser. Rinse and repeat until all tests turn green.
27 |
28 | The test runner used is [Jasmine](http://jasmine.github.io/) with a customized report viewer.
29 |
30 | ### Changelog
31 | * v3 - Nov 2010 - Moved out of branch of functional-koans project, into own top level project
32 | * v2 - Sept 2010 - Second version based on jasmine (Thanks Greg Malcolm!)
33 | * v1 - July 2010 - First version based on jsTestDriver
34 |
35 | ### Acknowledgements
36 | * Dick Wall (the Java posse) - for bringing the idea of koans to my attention
37 | * Edgecase - for the great Ruby Koans
38 | * Douglas Crockford - for Javascript; the good bits
39 |
40 | ### [MIT Licensed](LICENSE)
41 |
--------------------------------------------------------------------------------
/jasmine/runner_specs/TestJSKoansRunner.html:
--------------------------------------------------------------------------------
1 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2 | "http://www.w3.org/TR/html4/loose.dtd">
3 | <html>
4 | <head>
5 | <title>Jasmine Test Runner</title>
6 | <link rel="stylesheet" type="text/css" href="../../jasmine/jasmine.css">
7 | <script type="text/javascript" src="../jasmine.js"></script>
8 | <script type="text/javascript" src="../jasmine-html.js"></script>
9 |
10 | <!-- include source files here... -->
11 | <script type="text/javascript" src="../jskoans-jasmine-html.js"></script>
12 |
13 | <!-- include spec files here... -->
14 | <script type="text/javascript" src="suites/KoansRunnerSpec.js"></script>
15 |
16 | </head>
17 | <body>
18 | <script type="text/javascript">
19 | jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
20 | jasmine.getEnv().execute();
21 | </script>
22 |
23 | </body>
24 | </html>
25 |
--------------------------------------------------------------------------------
/jasmine/runner_specs/suites/KoansRunnerSpec.js:
--------------------------------------------------------------------------------
1 | describe("KoansRunner", function() {
2 | var env;
3 | var reporter;
4 | var body;
5 | var fakeDocument;
6 |
7 | beforeEach(function() {
8 | env = new jasmine.Env();
9 | env.updateInterval = 0;
10 |
11 | body = document.createElement("body");
12 | fakeDocument = { body: body, location: { search: "" } };
13 | reporter = new JsKoansReporter(fakeDocument);
14 | });
15 |
16 | function fakeSpec(name) {
17 | return {
18 | getFullName: function() {
19 | return name;
20 | }
21 | };
22 | }
23 |
24 | function fakeSuite(desc) {
25 | return {
26 | parentSuite: null,
27 | description: desc
28 | };
29 | }
30 |
31 | function findElements(divs, withClass) {
32 | var els = [];
33 | for (var i = 0; i < divs.length; i++) {
34 | if (divs[i].className == withClass) els.push(divs[i]);
35 | }
36 | return els;
37 | }
38 |
39 | function findElement(divs, withClass) {
40 | var els = findElements(divs, withClass);
41 | if (els.length > 0) return els[0];
42 | throw new Error("couldn't find div with class " + withClass);
43 | }
44 |
45 | it("should run only specs beginning with spec parameter", function() {
46 | fakeDocument.location.search = "?spec=run%20this";
47 | expect(reporter.specFilter(fakeSpec("run this"))).toBeTruthy();
48 | expect(reporter.specFilter(fakeSpec("not the right spec"))).toBeFalsy();
49 | expect(reporter.specFilter(fakeSpec("not run this"))).toBeFalsy();
50 | });
51 |
52 | it("should display empty divs for every suite when the runner is starting", function() {
53 | reporter.reportRunnerStarting({
54 | env: env,
55 | suites: function() {
56 | return [ new jasmine.Suite({}, "suite 1", null, null) ];
57 | }
58 | });
59 |
60 | var divs = findElements(body.getElementsByTagName("div"), "suite");
61 | expect(divs.length).toEqual(1);
62 | expect(divs[0].innerHTML).toContain("suite 1");
63 | });
64 |
65 | describe('Matcher reporting', function () {
66 | var getErrorMessageDiv = function (body) {
67 | var divs = body.getElementsByTagName("div");
68 | for (var i = 0; i < divs.length; i++) {
69 | if (divs[i].className.match(/errorMessage/)) {
70 | return divs[i];
71 | }
72 | }
73 | };
74 |
75 | var runner, spec, fakeTimer;
76 | beforeEach(function () {
77 | fakeTimer = new jasmine.FakeTimer();
78 | env.setTimeout = fakeTimer.setTimeout;
79 | env.clearTimeout = fakeTimer.clearTimeout;
80 | env.setInterval = fakeTimer.setInterval;
81 | env.clearInterval = fakeTimer.clearInterval;
82 | runner = env.currentRunner();
83 | var suite = new jasmine.Suite(env, 'some suite');
84 | runner.add(suite);
85 | spec = new jasmine.Spec(env, suite, 'some spec');
86 | suite.add(spec);
87 | fakeDocument.location.search = "?";
88 | env.addReporter(reporter);
89 | });
90 |
91 | describe('toContain', function () {
92 | it('should show actual and expected', function () {
93 | spec.runs(function () {
94 | this.expect('foo').toContain('bar');
95 | });
96 | runner.execute();
97 | fakeTimer.tick(0);
98 |
99 | var resultEl = getErrorMessageDiv(body);
100 | expect(resultEl.innerHTML).toMatch(/foo/);
101 | expect(resultEl.innerHTML).toMatch(/bar/);
102 | });
103 | });
104 | });
105 |
106 |
107 | describe("failure messages (integration)", function () {
108 | var spec, results, expectationResult;
109 |
110 | beforeEach(function() {
111 | results = {
112 | passed: function() {
113 | return false;
114 | },
115 | getItems: function() {
116 | }};
117 |
118 | var suite1 = new jasmine.Suite(env, "suite 1", null, null);
119 |
120 | spec = {
121 | suite: suite1,
122 | getFullName: function() {
123 | return "foo";
124 | },
125 | results: function() {
126 | return results;
127 | }
128 | };
129 |
130 | reporter.reportRunnerStarting({
131 | env: env,
132 | suites: function() {
133 | return [ suite1 ];
134 | }
135 | });
136 | });
137 |
138 | it("should add the failure message to the DOM (non-toEquals matchers)", function() {
139 | expectationResult = new jasmine.ExpectationResult({
140 | matcherName: "toBeNull", passed: false, message: "Expected 'a' to be null, but it was not"
141 | });
142 |
143 | spyOn(results, 'getItems').andReturn([expectationResult]);
144 |
145 | reporter.reportSpecResults(spec);
146 |
147 | var divs = body.getElementsByTagName("div");
148 | var errorDiv = findElement(divs, 'errorMessage');
149 | expect(errorDiv.innerHTML).toEqual("Expected 'a' to be null, but it was not");
150 | });
151 |
152 | it("should add the failure message to the DOM (non-toEquals matchers) html escaping", function() {
153 | expectationResult = new jasmine.ExpectationResult({
154 | matcherName: "toBeNull", passed: false, message: "Expected '1 < 2' to <b>e null, & it was not"
155 | });
156 |
157 | spyOn(results, 'getItems').andReturn([expectationResult]);
158 |
159 | reporter.reportSpecResults(spec);
160 |
161 | var divs = body.getElementsByTagName("div");
162 | var errorDiv = findElement(divs, 'errorMessage');
163 | expect(errorDiv.innerHTML).toEqual("Expected '1 < 2' to <b>e null, & it was not");
164 | });
165 | });
166 |
167 | describe("duplicate example names", function() {
168 | it("should report failures correctly", function() {
169 | var suite1 = env.describe("suite", function() {
170 | env.it("will have log messages", function() {
171 | this.log("this one passes!");
172 | this.expect(true).toBeTruthy();
173 | });
174 | });
175 |
176 | var suite2 = env.describe("suite", function() {
177 | env.it("will have log messages", function() {
178 | this.log("this one fails!");
179 | this.expect(true).toBeFalsy();
180 | });
181 | });
182 |
183 | env.addReporter(reporter);
184 | env.execute();
185 |
186 | var divs = body.getElementsByTagName("div");
187 | var failedSpecDiv = findElement(divs, 'suite failed');
188 | expect(failedSpecDiv.className).toEqual('suite failed');
189 | expect(failedSpecDiv.innerHTML).toContain("damaging your karma");
190 | expect(failedSpecDiv.innerHTML).not.toContain("has expanded your awareness");
191 |
192 | var passedSpecDiv = findElement(divs, 'suite passed');
193 | expect(passedSpecDiv.className).toEqual('suite passed');
194 | expect(passedSpecDiv.innerHTML).toContain("has expanded your awareness");
195 | expect(passedSpecDiv.innerHTML).not.toContain("damaging your karma");
196 | });
197 | });
198 |
199 | describe('#reportSpecStarting', function() {
200 | var spec1;
201 | beforeEach(function () {
202 | env.describe("suite 1", function() {
203 | spec1 = env.it("spec 1", function() {
204 | });
205 | });
206 | });
207 |
208 | it('should not log running specs by default', function() {
209 | spyOn(reporter, 'log');
210 |
211 | reporter.reportSpecStarting(spec1);
212 |
213 | expect(reporter.log).not.toHaveBeenCalled();
214 | });
215 | });
216 |
217 | describe('showing progress', function() {
218 | beforeEach(function() {
219 | env.describe("suite 1", function() {
220 | env.it("spec A");
221 | env.it("spec B");
222 | });
223 | env.describe("suite 2", function() {
224 | env.it("spec C");
225 | });
226 | });
227 |
228 | describe('subjects', function() {
229 | describe("with no failures", function() {
230 | beforeEach(function() {
231 | env.addReporter(reporter);
232 | env.execute();
233 | });
234 |
235 | it("should have 2 subjects", function() {
236 | expect(reporter.noOfSubjects).toBe(2);
237 | });
238 |
239 | it("should not have any failed subjects", function() {
240 | expect(reporter.failedSubjects).toBe(0);
241 | });
242 | });
243 |
244 | describe("with 1 failure", function() {
245 | beforeEach(function() {
246 | env.describe("suite with error", function() {
247 | env.it("spec X", function() {
248 | expect(true).toBeFalsey();
249 | });
250 | env.it("spec Y", function() {
251 | expect(true).toBeFalsey();
252 | });
253 | });
254 |
255 | env.addReporter(reporter);
256 | env.execute();
257 | });
258 |
259 | it("should have 3 subjects", function() {
260 | expect(reporter.noOfSubjects).toBe(3);
261 | });
262 |
263 | it("should have a failure", function() {
264 | expect(reporter.failedSubjects).toBe(1);
265 | });
266 | });
267 |
268 | describe("with 2 failures", function() {
269 | beforeEach(function() {
270 | env.describe("suite with error", function() {
271 | env.it("spec X", function() {
272 | expect(true).toBeFalsey();
273 | });
274 | });
275 | env.describe("suite with error too", function() {
276 | env.it("spec Y", function() {
277 | expect(true).toBeFalsey();
278 | });
279 | });
280 |
281 | env.addReporter(reporter);
282 | env.execute();
283 | });
284 |
285 | it("should have 4 subjects", function() {
286 | expect(reporter.noOfSubjects).toBe(4);
287 | });
288 |
289 | it("should have 2 failures", function() {
290 | expect(reporter.failedSubjects).toBe(2);
291 | });
292 | });
293 |
294 | describe("with embedded suites only outer suites count as subjects", function() {
295 | beforeEach(function() {
296 | env.describe("outer suite", function() {
297 | env.it("spec for outer suite", function() {
298 | expect(true).toBeFalsey();
299 | });
300 |
301 | env.describe("inner suite", function() {
302 | env.it("spec for inner suite", function() {
303 | expect(true).toBeFalsey();
304 | });
305 | });
306 | });
307 |
308 | env.addReporter(reporter);
309 | env.execute();
310 | });
311 |
312 | it("should have 3 suites", function() {
313 | expect(reporter.noOfSubjects).toBe(3);
314 | });
315 |
316 | it("should have 1 failure", function() {
317 | expect(reporter.failedSubjects).toBe(1);
318 | });
319 |
320 | });
321 | });
322 | });
323 |
324 | describe("presentation", function() {
325 | describe("showing the suite description", function() {
326 | it("should prefix outer suite descriptions with 'Thinking'", function() {
327 | suite = fakeSuite("About Pies");
328 | description = reporter.getSuiteDescription(suite);
329 |
330 | expect(description).toEqual("Thinking About Pies");
331 | });
332 |
333 | it("should prefix inner suite descriptions with 'Thinking'", function() {
334 | suite = fakeSuite("cherries");
335 | suite.parentSuite = "Something";
336 | description = reporter.getSuiteDescription(suite);
337 |
338 | expect(description).toEqual("Considering cherries");
339 | });
340 | });
341 | });
342 | });
343 |
--------------------------------------------------------------------------------
/koans/AboutApplyingWhatWeHaveLearnt.js:
--------------------------------------------------------------------------------
1 | var _; //globals
2 |
3 | describe("About Applying What We Have Learnt", function() {
4 |
5 | var products;
6 |
7 | beforeEach(function () {
8 | products = [
9 | { name: "Sonoma", ingredients: ["artichoke", "sundried tomatoes", "mushrooms"], containsNuts: false },
10 | { name: "Pizza Primavera", ingredients: ["roma", "sundried tomatoes", "goats cheese", "rosemary"], containsNuts: false },
11 | { name: "South Of The Border", ingredients: ["black beans", "jalapenos", "mushrooms"], containsNuts: false },
12 | { name: "Blue Moon", ingredients: ["blue cheese", "garlic", "walnuts"], containsNuts: true },
13 | { name: "Taste Of Athens", ingredients: ["spinach", "kalamata olives", "sesame seeds"], containsNuts: true }
14 | ];
15 | });
16 |
17 | /*********************************************************************************/
18 |
19 | it("given I'm allergic to nuts and hate mushrooms, it should find a pizza I can eat (imperative)", function () {
20 |
21 | var i,j,hasMushrooms, productsICanEat = [];
22 |
23 | for (i = 0; i < products.length; i+=1) {
24 | if (products[i].containsNuts === false) {
25 | hasMushrooms = false;
26 | for (j = 0; j < products[i].ingredients.length; j+=1) {
27 | if (products[i].ingredients[j] === "mushrooms") {
28 | hasMushrooms = true;
29 | }
30 | }
31 | if (!hasMushrooms) productsICanEat.push(products[i]);
32 | }
33 | }
34 |
35 | expect(productsICanEat.length).toBe(FILL_ME_IN);
36 | });
37 |
38 | it("given I'm allergic to nuts and hate mushrooms, it should find a pizza I can eat (functional)", function () {
39 |
40 | var productsICanEat = [];
41 |
42 | /* solve using filter() & all() / any() */
43 |
44 | expect(productsICanEat.length).toBe(FILL_ME_IN);
45 | });
46 |
47 | /*********************************************************************************/
48 |
49 | it("should add all the natural numbers below 1000 that are multiples of 3 or 5 (imperative)", function () {
50 |
51 | var sum = 0;
52 | for(var i=1; i<1000; i+=1) {
53 | if (i % 3 === 0 || i % 5 === 0) {
54 | sum += i;
55 | }
56 | }
57 |
58 | expect(sum).toBe(FILL_ME_IN);
59 | });
60 |
61 | it("should add all the natural numbers below 1000 that are multiples of 3 or 5 (functional)", function () {
62 |
63 | var sum = FILL_ME_IN; /* try chaining range() and reduce() */
64 |
65 | expect(233168).toBe(FILL_ME_IN);
66 | });
67 |
68 | /*********************************************************************************/
69 | it("should count the ingredient occurrence (imperative)", function () {
70 | var ingredientCount = { "{ingredient name}": 0 };
71 |
72 | for (i = 0; i < products.length; i+=1) {
73 | for (j = 0; j < products[i].ingredients.length; j+=1) {
74 | ingredientCount[products[i].ingredients[j]] = (ingredientCount[products[i].ingredients[j]] || 0) + 1;
75 | }
76 | }
77 |
78 | expect(ingredientCount['mushrooms']).toBe(FILL_ME_IN);
79 | });
80 |
81 | it("should count the ingredient occurrence (functional)", function () {
82 | var ingredientCount = { "{ingredient name}": 0 };
83 |
84 | /* chain() together map(), flatten() and reduce() */
85 |
86 | expect(ingredientCount['mushrooms']).toBe(FILL_ME_IN);
87 | });
88 |
89 | /*********************************************************************************/
90 | /* UNCOMMENT FOR EXTRA CREDIT */
91 | /*
92 | it("should find the largest prime factor of a composite number", function () {
93 |
94 | });
95 |
96 | it("should find the largest palindrome made from the product of two 3 digit numbers", function () {
97 |
98 | });
99 |
100 | it("should find the smallest number divisible by each of the numbers 1 to 20", function () {
101 |
102 |
103 | });
104 |
105 | it("should find the difference between the sum of the squares and the square of the sums", function () {
106 |
107 | });
108 |
109 | it("should find the 10001st prime", function () {
110 |
111 | });
112 | */
113 | });
114 |
--------------------------------------------------------------------------------
/koans/AboutArrays.js:
--------------------------------------------------------------------------------
1 | describe("About Arrays", function() {
2 |
3 | //We shall contemplate truth by testing reality, via spec expectations.
4 | it("should create arrays", function() {
5 | var emptyArray = [];
6 | expect(typeof(emptyArray)).toBe(FILL_ME_IN); //A mistake? - http://javascript.crockford.com/remedial.html
7 | expect(emptyArray.length).toBe(FILL_ME_IN);
8 |
9 | var multiTypeArray = [0, 1, "two", function () { return 3; }, {value1: 4, value2: 5}, [6, 7]];
10 | expect(multiTypeArray[0]).toBe(FILL_ME_IN);
11 | expect(multiTypeArray[2]).toBe(FILL_ME_IN);
12 | expect(multiTypeArray[3]()).toBe(FILL_ME_IN);
13 | expect(multiTypeArray[4].value1).toBe(FILL_ME_IN);
14 | expect(multiTypeArray[4]["value2"]).toBe(FILL_ME_IN);
15 | expect(multiTypeArray[5][0]).toBe(FILL_ME_IN);
16 | });
17 |
18 | it("should understand array literals", function () {
19 | var array = [];
20 | expect(array).toEqual([]);
21 |
22 | array[0] = 1;
23 | expect(array).toEqual([1]);
24 |
25 | array[1] = 2;
26 | expect(array).toEqual([1, FILL_ME_IN]);
27 |
28 | array.push(3);
29 | expect(array).toEqual(FILL_ME_IN);
30 | });
31 |
32 | it("should understand array length", function () {
33 | var fourNumberArray = [1, 2, 3, 4];
34 |
35 | expect(fourNumberArray.length).toBe(FILL_ME_IN);
36 | fourNumberArray.push(5, 6);
37 | expect(fourNumberArray.length).toBe(FILL_ME_IN);
38 |
39 | var tenEmptyElementArray = new Array(10);
40 | expect(tenEmptyElementArray.length).toBe(FILL_ME_IN);
41 |
42 | tenEmptyElementArray.length = 5;
43 | expect(tenEmptyElementArray.length).toBe(FILL_ME_IN);
44 | });
45 |
46 | it("should slice arrays", function () {
47 | var array = ["peanut", "butter", "and", "jelly"];
48 |
49 | expect(array.slice(0, 1)).toEqual(FILL_ME_IN);
50 | expect(array.slice(0, 2)).toEqual(FILL_ME_IN);
51 | expect(array.slice(2, 2)).toEqual(FILL_ME_IN);
52 | expect(array.slice(2, 20)).toEqual(FILL_ME_IN);
53 | expect(array.slice(3, 0)).toEqual(FILL_ME_IN);
54 | expect(array.slice(3, 100)).toEqual(FILL_ME_IN);
55 | expect(array.slice(5, 1)).toEqual(FILL_ME_IN);
56 | });
57 |
58 | it("should know array references", function () {
59 | var array = [ "zero", "one", "two", "three", "four", "five" ];
60 |
61 | function passedByReference(refArray) {
62 | refArray[1] = "changed in function";
63 | }
64 | passedByReference(array);
65 | expect(array[1]).toBe(FILL_ME_IN);
66 |
67 | var assignedArray = array;
68 | assignedArray[5] = "changed in assignedArray";
69 | expect(array[5]).toBe(FILL_ME_IN);
70 |
71 | var copyOfArray = array.slice();
72 | copyOfArray[3] = "changed in copyOfArray";
73 | expect(array[3]).toBe(FILL_ME_IN);
74 | });
75 |
76 | it("should push and pop", function () {
77 | var array = [1, 2];
78 | array.push(3);
79 |
80 | expect(array).toEqual(FILL_ME_IN);
81 |
82 | var poppedValue = array.pop();
83 | expect(poppedValue).toBe(FILL_ME_IN);
84 | expect(array).toEqual(FILL_ME_IN);
85 | });
86 |
87 | it("should know about shifting arrays", function () {
88 | var array = [1, 2];
89 |
90 | array.unshift(3);
91 | expect(array).toEqual(FILL_ME_IN);
92 |
93 | var shiftedValue = array.shift();
94 | expect(shiftedValue).toEqual(FILL_ME_IN);
95 | expect(array).toEqual(FILL_ME_IN);
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/koans/AboutExpects.js:
--------------------------------------------------------------------------------
1 | describe('About Expects', function() {
2 |
3 | // We shall contemplate truth by testing reality, via spec expectations.
4 | it('should expect true', function() {
5 |
6 | // Your journey begins here: Replace the word false with true
7 | expect(false).toBeTruthy();
8 | });
9 |
10 | // To understand reality, we must compare our expectations against reality.
11 | it('should expect equality', function() {
12 | var expectedValue = FILL_ME_IN;
13 | var actualValue = 1 + 1;
14 |
15 | expect(actualValue === expectedValue).toBeTruthy();
16 | });
17 |
18 | // Some ways of asserting equality are better than others.
19 | it('should assert equality a better way', function() {
20 | var expectedValue = FILL_ME_IN;
21 | var actualValue = 1 + 1;
22 |
23 | // toEqual() compares using common sense equality.
24 | expect(actualValue).toEqual(expectedValue);
25 | });
26 |
27 | // Sometimes you need to be precise about what you "type."
28 | it('should assert equality with ===', function() {
29 | var expectedValue = FILL_ME_IN;
30 | var actualValue = (1 + 1).toString();
31 |
32 | // toBe() will always use === to compare.
33 | expect(actualValue).toBe(expectedValue);
34 | });
35 |
36 | // Sometimes we will ask you to fill in the values.
37 | it('should have filled in values', function() {
38 | expect(1 + 1).toEqual(FILL_ME_IN);
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/koans/AboutFunctions.js:
--------------------------------------------------------------------------------
1 | describe("About Functions", function() {
2 |
3 | it("should declare functions", function() {
4 |
5 | function add(a, b) {
6 | return a + b;
7 | }
8 |
9 | expect(add(1, 2)).toBe(FILL_ME_IN);
10 | });
11 |
12 | it("should know internal variables override outer variables", function () {
13 | var message = "Outer";
14 |
15 | function getMessage() {
16 | return message;
17 | }
18 |
19 | function overrideMessage() {
20 | var message = "Inner";
21 | return message;
22 | }
23 |
24 | expect(getMessage()).toBe(FILL_ME_IN);
25 | expect(overrideMessage()).toBe(FILL_ME_IN);
26 | expect(message).toBe(FILL_ME_IN);
27 | });
28 |
29 | it("should have lexical scoping", function () {
30 | var variable = "top-level";
31 | function parentfunction() {
32 | var variable = "local";
33 | function childfunction() {
34 | return variable;
35 | }
36 | return childfunction();
37 | }
38 | expect(parentfunction()).toBe(FILL_ME_IN);
39 | });
40 |
41 | it("should use lexical scoping to synthesise functions", function () {
42 |
43 | function makeMysteryFunction(makerValue)
44 | {
45 | var newFunction = function doMysteriousThing(param)
46 | {
47 | return makerValue + param;
48 | };
49 | return newFunction;
50 | }
51 |
52 | var mysteryFunction3 = makeMysteryFunction(3);
53 | var mysteryFunction5 = makeMysteryFunction(5);
54 |
55 | expect(mysteryFunction3(10) + mysteryFunction5(5)).toBe(FILL_ME_IN);
56 | });
57 |
58 | it("should allow extra function arguments", function () {
59 |
60 | function returnFirstArg(firstArg) {
61 | return firstArg;
62 | }
63 |
64 | expect(returnFirstArg("first", "second", "third")).toBe(FILL_ME_IN);
65 |
66 | function returnSecondArg(firstArg, secondArg) {
67 | return secondArg;
68 | }
69 |
70 | expect(returnSecondArg("only give first arg")).toBe(FILL_ME_IN);
71 |
72 | function returnAllArgs() {
73 | var argsArray = [];
74 | for (var i = 0; i < arguments.length; i += 1) {
75 | argsArray.push(arguments[i]);
76 | }
77 | return argsArray.join(",");
78 | }
79 |
80 | expect(returnAllArgs("first", "second", "third")).toBe(FILL_ME_IN);
81 | });
82 |
83 | it("should pass functions as values", function () {
84 |
85 | var appendRules = function (name) {
86 | return name + " rules!";
87 | };
88 |
89 | var appendDoubleRules = function (name) {
90 | return name + " totally rules!";
91 | };
92 |
93 | var praiseSinger = { givePraise: appendRules };
94 | expect(praiseSinger.givePraise("John")).toBe(FILL_ME_IN);
95 |
96 | praiseSinger.givePraise = appendDoubleRules;
97 | expect(praiseSinger.givePraise("Mary")).toBe(FILL_ME_IN);
98 |
99 | });
100 | });
101 |
--------------------------------------------------------------------------------
/koans/AboutHigherOrderFunctions.js:
--------------------------------------------------------------------------------
1 | var _; //globals
2 |
3 | /* This section uses a functional extension known as Underscore.js - http://documentcloud.github.com/underscore/
4 | "Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support
5 | that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects.
6 | It's the tie to go along with jQuery's tux."
7 | */
8 | describe("About Higher Order Functions", function () {
9 |
10 | it("should use filter to return array items that meet a criteria", function () {
11 | var numbers = [1,2,3];
12 | var odd = _(numbers).filter(function (x) { return x % 2 !== 0 });
13 |
14 | expect(odd).toEqual(FILL_ME_IN);
15 | expect(odd.length).toBe(FILL_ME_IN);
16 | expect(numbers.length).toBe(FILL_ME_IN);
17 | });
18 |
19 | it("should use 'map' to transform each element", function () {
20 | var numbers = [1, 2, 3];
21 | var numbersPlus1 = _(numbers).map(function(x) { return x + 1 });
22 |
23 | expect(numbersPlus1).toEqual(FILL_ME_IN);
24 | expect(numbers).toEqual(FILL_ME_IN);
25 | });
26 |
27 | it("should use 'reduce' to update the same result on each iteration", function () {
28 | var numbers = [1, 2, 3];
29 | var reduction = _(numbers).reduce(
30 | function(/* result from last call */ memo, /* current */ x) { return memo + x }, /* initial */ 0);
31 |
32 | expect(reduction).toBe(FILL_ME_IN);
33 | expect(numbers).toEqual(FILL_ME_IN);
34 | });
35 |
36 | it("should use 'forEach' for simple iteration", function () {
37 | var numbers = [1,2,3];
38 | var msg = "";
39 | var isEven = function (item) {
40 | msg += (item % 2) === 0;
41 | };
42 |
43 | _(numbers).forEach(isEven);
44 |
45 | expect(msg).toEqual(FILL_ME_IN);
46 | expect(numbers).toEqual(FILL_ME_IN);
47 | });
48 |
49 | it("should use 'all' to test whether all items pass condition", function () {
50 | var onlyEven = [2,4,6];
51 | var mixedBag = [2,4,5,6];
52 |
53 | var isEven = function(x) { return x % 2 === 0 };
54 |
55 | expect(_(onlyEven).all(isEven)).toBe(FILL_ME_IN);
56 | expect(_(mixedBag).all(isEven)).toBe(FILL_ME_IN);
57 | });
58 |
59 | it("should use 'any' to test if any items passes condition" , function () {
60 | var onlyEven = [2,4,6];
61 | var mixedBag = [2,4,5,6];
62 |
63 | var isEven = function(x) { return x % 2 === 0 };
64 |
65 | expect(_(onlyEven).any(isEven)).toBe(FILL_ME_IN);
66 | expect(_(mixedBag).any(isEven)).toBe(FILL_ME_IN);
67 | });
68 |
69 | it("should use range to generate an array", function() {
70 | expect(_.range(3)).toEqual(FILL_ME_IN);
71 | expect(_.range(1, 4)).toEqual(FILL_ME_IN);
72 | expect(_.range(0, -4, -1)).toEqual(FILL_ME_IN);
73 | });
74 |
75 | it("should use flatten to make nested arrays easy to work with", function() {
76 | expect(_([ [1, 2], [3, 4] ]).flatten()).toEqual(FILL_ME_IN);
77 | });
78 |
79 | it("should use chain() ... .value() to use multiple higher order functions", function() {
80 | var result = _([ [0, 1], 2 ]).chain()
81 | .flatten()
82 | .map(function(x) { return x+1 } )
83 | .reduce(function (sum, x) { return sum + x })
84 | .value();
85 |
86 | expect(result).toEqual(FILL_ME_IN);
87 | });
88 |
89 | });
90 |
91 |
--------------------------------------------------------------------------------
/koans/AboutInheritance.js:
--------------------------------------------------------------------------------
1 | function Muppet(age, hobby) {
2 | this.age = age;
3 | this.hobby = hobby;
4 |
5 | this.answerNanny = function(){
6 | return "Everything's cool!";
7 | }
8 | }
9 |
10 | function SwedishChef(age, hobby, mood) {
11 | Muppet.call(this, age, hobby);
12 | this.mood = mood;
13 |
14 | this.cook = function() {
15 | return "Mmmm soup!";
16 | }
17 | }
18 |
19 | SwedishChef.prototype = new Muppet();
20 |
21 | describe("About inheritance", function() {
22 | beforeEach(function(){
23 | this.muppet = new Muppet(2, "coding");
24 | this.swedishChef = new SwedishChef(2, "cooking", "chillin");
25 | });
26 |
27 | it("should be able to call a method on the derived object", function() {
28 | expect(this.swedishChef.cook()).toEqual(FILL_ME_IN);
29 | });
30 |
31 | it("should be able to call a method on the base object", function() {
32 | expect(this.swedishChef.answerNanny()).toEqual(FILL_ME_IN);
33 | });
34 |
35 | it("should set constructor parameters on the base object", function() {
36 | expect(this.swedishChef.age).toEqual(FILL_ME_IN);
37 | expect(this.swedishChef.hobby).toEqual(FILL_ME_IN);
38 | });
39 |
40 | it("should set constructor parameters on the derived object", function() {
41 | expect(this.swedishChef.mood).toEqual(FILL_ME_IN);
42 | });
43 | });
44 |
45 | // http://javascript.crockford.com/prototypal.html
46 | Object.prototype.beget = function () {
47 | function F() {}
48 | F.prototype = this;
49 | return new F();
50 | }
51 |
52 | function Gonzo(age, hobby, trick) {
53 | Muppet.call(this, age, hobby);
54 | this.trick = trick;
55 |
56 | this.doTrick = function() {
57 | return this.trick;
58 | }
59 | }
60 |
61 | //no longer need to call the Muppet (base type) constructor
62 | Gonzo.prototype = Muppet.prototype.beget();
63 |
64 | describe("About Crockford's inheritance improvement", function() {
65 | beforeEach(function(){
66 | this.gonzo = new Gonzo(3, "daredevil performer", "eat a tire");
67 | });
68 |
69 | it("should be able to call a method on the derived object", function() {
70 | expect(this.gonzo.doTrick()).toEqual(FILL_ME_IN);
71 | });
72 |
73 | it("should be able to call a method on the base object", function() {
74 | expect(this.gonzo.answerNanny()).toEqual(FILL_ME_IN);
75 | });
76 |
77 | it("should set constructor parameters on the base object", function() {
78 | expect(this.gonzo.age).toEqual(FILL_ME_IN);
79 | expect(this.gonzo.hobby).toEqual(FILL_ME_IN);
80 | });
81 |
82 | it("should set constructor parameters on the derived object", function() {
83 | expect(this.gonzo.trick).toEqual(FILL_ME_IN);
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/koans/AboutMutability.js:
--------------------------------------------------------------------------------
1 | describe("About Mutability", function() {
2 |
3 | it("should expect object properties to be public and mutable", function () {
4 | var aPerson = {firstname: "John", lastname: "Smith" };
5 | aPerson.firstname = "Alan";
6 |
7 | expect(aPerson.firstname).toBe(FILL_ME_IN);
8 | });
9 |
10 | it("should understand that constructed properties are public and mutable", function () {
11 | function Person(firstname, lastname)
12 | {
13 | this.firstname = firstname;
14 | this.lastname = lastname;
15 | }
16 | var aPerson = new Person ("John", "Smith");
17 | aPerson.firstname = "Alan";
18 |
19 | expect(aPerson.firstname).toBe(FILL_ME_IN);
20 | });
21 |
22 | it("should expect prototype properties to be public and mutable", function () {
23 | function Person(firstname, lastname)
24 | {
25 | this.firstname = firstname;
26 | this.lastname = lastname;
27 | }
28 | Person.prototype.getFullName = function () {
29 | return this.firstname + " " + this.lastname;
30 | };
31 |
32 | var aPerson = new Person ("John", "Smith");
33 | expect(aPerson.getFullName()).toBe(FILL_ME_IN);
34 |
35 | aPerson.getFullName = function () {
36 | return this.lastname + ", " + this.firstname;
37 | };
38 |
39 | expect(aPerson.getFullName()).toBe(FILL_ME_IN);
40 | });
41 |
42 | it("should know that variables inside a constructor and constructor args are private", function () {
43 | function Person(firstname, lastname)
44 | {
45 | var fullName = firstname + " " + lastname;
46 |
47 | this.getFirstName = function () { return firstname; };
48 | this.getLastName = function () { return lastname; };
49 | this.getFullName = function () { return fullName; };
50 | }
51 | var aPerson = new Person ("John", "Smith");
52 |
53 | aPerson.firstname = "Penny";
54 | aPerson.lastname = "Andrews";
55 | aPerson.fullName = "Penny Andrews";
56 |
57 | expect(aPerson.getFirstName()).toBe(FILL_ME_IN);
58 | expect(aPerson.getLastName()).toBe(FILL_ME_IN);
59 | expect(aPerson.getFullName()).toBe(FILL_ME_IN);
60 |
61 | aPerson.getFullName = function () {
62 | return aPerson.lastname + ", " + aPerson.firstname;
63 | };
64 |
65 | expect(aPerson.getFullName()).toBe(FILL_ME_IN);
66 | });
67 |
68 | });
69 |
--------------------------------------------------------------------------------
/koans/AboutObjects.js:
--------------------------------------------------------------------------------
1 | describe("About Objects", function () {
2 |
3 | describe("Properties", function () {
4 | var megalomaniac;
5 |
6 | beforeEach(function () {
7 | megalomaniac = { mastermind: "Joker", henchwoman: "Harley" };
8 | });
9 |
10 | it("should confirm objects are collections of properties", function () {
11 | expect(megalomaniac.mastermind).toBe(FILL_ME_IN);
12 | });
13 |
14 | it("should confirm that properties are case sensitive", function () {
15 | expect(megalomaniac.henchwoman).toBe(FILL_ME_IN);
16 | expect(megalomaniac.henchWoman).toBe(FILL_ME_IN);
17 | });
18 | });
19 |
20 |
21 | it("should know properties that are functions act like methods", function () {
22 | var megalomaniac = {
23 | mastermind : "Brain",
24 | henchman: "Pinky",
25 | battleCry: function (noOfBrains) {
26 | return "They are " + this.henchman + " and the" +
27 | Array(noOfBrains + 1).join(" " + this.mastermind);
28 | }
29 | };
30 |
31 | var battleCry = megalomaniac.battleCry(4);
32 | expect(FILL_ME_IN).toMatch(battleCry);
33 | });
34 |
35 | it("should confirm that when a function is attached to an object, 'this' refers to the object", function () {
36 | var currentDate = new Date();
37 | var currentYear = (currentDate.getFullYear());
38 | var megalomaniac = {
39 | mastermind: "James Wood",
40 | henchman: "Adam West",
41 | birthYear: 1970,
42 | calculateAge: function () {
43 | return currentYear - this.birthYear;
44 | }
45 | };
46 |
47 | expect(currentYear).toBe(FILL_ME_IN);
48 | expect(megalomaniac.calculateAge()).toBe(FILL_ME_IN);
49 | });
50 |
51 | describe("'in' keyword", function () {
52 | var megalomaniac;
53 | beforeEach(function () {
54 | megalomaniac = {
55 | mastermind: "The Monarch",
56 | henchwoman: "Dr Girlfriend",
57 | theBomb: true
58 | };
59 | });
60 |
61 | it("should have the bomb", function () {
62 |
63 | var hasBomb = "theBomb" in megalomaniac;
64 |
65 | expect(hasBomb).toBe(FILL_ME_IN);
66 | });
67 |
68 | it("should not have the detonator however", function () {
69 |
70 | var hasDetonator = "theDetonator" in megalomaniac;
71 |
72 | expect(hasDetonator).toBe(FILL_ME_IN);
73 | });
74 | });
75 |
76 | it("should know that properties can be added and deleted", function () {
77 | var megalomaniac = { mastermind : "Agent Smith", henchman: "Agent Smith" };
78 |
79 | expect("secretary" in megalomaniac).toBe(FILL_ME_IN);
80 |
81 | megalomaniac.secretary = "Agent Smith";
82 | expect("secretary" in megalomaniac).toBe(FILL_ME_IN);
83 |
84 | delete megalomaniac.henchman;
85 | expect("henchman" in megalomaniac).toBe(FILL_ME_IN);
86 | });
87 |
88 |
89 | it("should use prototype to add to all objects", function () {
90 | function Circle(radius)
91 | {
92 | this.radius = radius;
93 | }
94 |
95 | var simpleCircle = new Circle(10);
96 | var colouredCircle = new Circle(5);
97 | colouredCircle.colour = "red";
98 |
99 | expect(simpleCircle.colour).toBe(FILL_ME_IN);
100 | expect(colouredCircle.colour).toBe(FILL_ME_IN);
101 |
102 | Circle.prototype.describe = function () {
103 | return "This circle has a radius of: " + this.radius;
104 | };
105 |
106 | expect(simpleCircle.describe()).toBe(FILL_ME_IN);
107 | expect(colouredCircle.describe()).toBe(FILL_ME_IN);
108 | });
109 | });
110 |
--------------------------------------------------------------------------------
/lib/FILL_ME_IN.js:
--------------------------------------------------------------------------------
1 | var FILL_ME_IN = "Fill this value in";
2 |
--------------------------------------------------------------------------------
/lib/jasmine/jasmine-html.js:
--------------------------------------------------------------------------------
1 | jasmine.TrivialReporter = function(doc) {
2 | this.document = doc || document;
3 | this.suiteDivs = {};
4 | this.logRunningSpecs = false;
5 | };
6 |
7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
8 | var el = document.createElement(type);
9 |
10 | for (var i = 2; i < arguments.length; i++) {
11 | var child = arguments[i];
12 |
13 | if (typeof child === 'string') {
14 | el.appendChild(document.createTextNode(child));
15 | } else {
16 | if (child) { el.appendChild(child); }
17 | }
18 | }
19 |
20 | for (var attr in attrs) {
21 | if (attr == "className") {
22 | el[attr] = attrs[attr];
23 | } else {
24 | el.setAttribute(attr, attrs[attr]);
25 | }
26 | }
27 |
28 | return el;
29 | };
30 |
31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
32 | var showPassed, showSkipped;
33 |
34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
35 | this.createDom('div', { className: 'banner' },
36 | this.createDom('div', { className: 'logo' },
37 | "Jasmine",
38 | this.createDom('span', { className: 'version' }, runner.env.versionString())),
39 | this.createDom('div', { className: 'options' },
40 | "Show ",
41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
45 | )
46 | ),
47 |
48 | this.runnerDiv = this.createDom('div', { className: 'runner running' },
49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
52 | );
53 |
54 | this.document.body.appendChild(this.outerDiv);
55 |
56 | var suites = runner.suites();
57 | for (var i = 0; i < suites.length; i++) {
58 | var suite = suites[i];
59 | var suiteDiv = this.createDom('div', { className: 'suite' },
60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
62 | this.suiteDivs[suite.id] = suiteDiv;
63 | var parentDiv = this.outerDiv;
64 | if (suite.parentSuite) {
65 | parentDiv = this.suiteDivs[suite.parentSuite.id];
66 | }
67 | parentDiv.appendChild(suiteDiv);
68 | }
69 |
70 | this.startedAt = new Date();
71 |
72 | var self = this;
73 | showPassed.onchange = function(evt) {
74 | if (evt.target.checked) {
75 | self.outerDiv.className += ' show-passed';
76 | } else {
77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
78 | }
79 | };
80 |
81 | showSkipped.onchange = function(evt) {
82 | if (evt.target.checked) {
83 | self.outerDiv.className += ' show-skipped';
84 | } else {
85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
86 | }
87 | };
88 | };
89 |
90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
91 | var results = runner.results();
92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
93 | this.runnerDiv.setAttribute("class", className);
94 | //do it twice for IE
95 | this.runnerDiv.setAttribute("className", className);
96 | var specs = runner.specs();
97 | var specCount = 0;
98 | for (var i = 0; i < specs.length; i++) {
99 | if (this.specFilter(specs[i])) {
100 | specCount++;
101 | }
102 | }
103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
106 |
107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
108 | };
109 |
110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
111 | var results = suite.results();
112 | var status = results.passed() ? 'passed' : 'failed';
113 | if (results.totalCount == 0) { // todo: change this to check results.skipped
114 | status = 'skipped';
115 | }
116 | this.suiteDivs[suite.id].className += " " + status;
117 | };
118 |
119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
120 | if (this.logRunningSpecs) {
121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
122 | }
123 | };
124 |
125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
126 | var results = spec.results();
127 | var status = results.passed() ? 'passed' : 'failed';
128 | if (results.skipped) {
129 | status = 'skipped';
130 | }
131 | var specDiv = this.createDom('div', { className: 'spec ' + status },
132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
133 | this.createDom('a', {
134 | className: 'description',
135 | href: '?spec=' + encodeURIComponent(spec.getFullName()),
136 | title: spec.getFullName()
137 | }, spec.description));
138 |
139 |
140 | var resultItems = results.getItems();
141 | var messagesDiv = this.createDom('div', { className: 'messages' });
142 | for (var i = 0; i < resultItems.length; i++) {
143 | var result = resultItems[i];
144 |
145 | if (result.type == 'log') {
146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
147 | } else if (result.type == 'expect' && result.passed && !result.passed()) {
148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
149 |
150 | if (result.trace.stack) {
151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
152 | }
153 | }
154 | }
155 |
156 | if (messagesDiv.childNodes.length > 0) {
157 | specDiv.appendChild(messagesDiv);
158 | }
159 |
160 | this.suiteDivs[spec.suite.id].appendChild(specDiv);
161 | };
162 |
163 | jasmine.TrivialReporter.prototype.log = function() {
164 | var console = jasmine.getGlobal().console;
165 | if (console && console.log) console.log.apply(console, arguments);
166 | };
167 |
168 | jasmine.TrivialReporter.prototype.getLocation = function() {
169 | return this.document.location;
170 | };
171 |
172 | jasmine.TrivialReporter.prototype.specFilter = function(spec) {
173 | var paramMap = {};
174 | var params = this.getLocation().search.substring(1).split('&');
175 | for (var i = 0; i < params.length; i++) {
176 | var p = params[i].split('=');
177 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
178 | }
179 |
180 | if (!paramMap["spec"]) return true;
181 | return spec.getFullName().indexOf(paramMap["spec"]) == 0;
182 | };
183 |
--------------------------------------------------------------------------------
/lib/jasmine/jasmine.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
3 | }
4 |
5 |
6 | .jasmine_reporter a:visited, .jasmine_reporter a {
7 | color: #303;
8 | }
9 |
10 | .jasmine_reporter a:hover, .jasmine_reporter a:active {
11 | color: blue;
12 | }
13 |
14 | .run_spec {
15 | float:right;
16 | padding-right: 5px;
17 | font-size: .8em;
18 | text-decoration: none;
19 | }
20 |
21 | .jasmine_reporter {
22 | margin: 0 5px;
23 | }
24 |
25 | .banner {
26 | color: #303;
27 | background-color: #fef;
28 | padding: 5px;
29 | }
30 |
31 | .logo {
32 | float: left;
33 | font-size: 1.1em;
34 | padding-left: 5px;
35 | }
36 |
37 | .logo .version {
38 | font-size: .6em;
39 | padding-left: 1em;
40 | }
41 |
42 | .runner.running {
43 | background-color: yellow;
44 | }
45 |
46 |
47 | .options {
48 | text-align: right;
49 | font-size: .8em;
50 | }
51 |
52 |
53 |
54 |
55 | .suite {
56 | border: 1px outset gray;
57 | margin: 5px 0;
58 | padding-left: 1em;
59 | }
60 |
61 | .suite .suite {
62 | margin: 5px;
63 | }
64 |
65 | .suite.passed {
66 | background-color: #dfd;
67 | }
68 |
69 | .suite.failed {
70 | background-color: #fdd;
71 | }
72 |
73 | .spec {
74 | margin: 5px;
75 | padding-left: 1em;
76 | clear: both;
77 | }
78 |
79 | .spec.failed, .spec.passed, .spec.skipped {
80 | padding-bottom: 5px;
81 | border: 1px solid gray;
82 | }
83 |
84 | .spec.failed {
85 | background-color: #fbb;
86 | border-color: red;
87 | }
88 |
89 | .spec.passed {
90 | background-color: #bfb;
91 | border-color: green;
92 | }
93 |
94 | .spec.skipped {
95 | background-color: #bbb;
96 | }
97 |
98 | .messages {
99 | border-left: 1px dashed gray;
100 | padding-left: 1em;
101 | padding-right: 1em;
102 | }
103 |
104 | .passed {
105 | background-color: #cfc;
106 | display: none;
107 | }
108 |
109 | .failed {
110 | background-color: #fbb;
111 | }
112 |
113 | .skipped {
114 | color: #777;
115 | background-color: #eee;
116 | display: none;
117 | }
118 |
119 |
120 | /*.resultMessage {*/
121 | /*white-space: pre;*/
122 | /*}*/
123 |
124 | .resultMessage span.result {
125 | display: block;
126 | line-height: 2em;
127 | color: black;
128 | }
129 |
130 | .resultMessage .mismatch {
131 | color: black;
132 | }
133 |
134 | .stackTrace {
135 | white-space: pre;
136 | font-size: .8em;
137 | margin-left: 10px;
138 | max-height: 5em;
139 | overflow: auto;
140 | border: 1px inset red;
141 | padding: 1em;
142 | background: #eef;
143 | }
144 |
145 | .finished-at {
146 | padding-left: 1em;
147 | font-size: .6em;
148 | }
149 |
150 | .show-passed .passed,
151 | .show-skipped .skipped {
152 | display: block;
153 | }
154 |
155 |
156 | #jasmine_content {
157 | position:fixed;
158 | right: 100%;
159 | }
160 |
161 | .runner {
162 | border: 1px solid gray;
163 | display: block;
164 | margin: 5px 0;
165 | padding: 2px 0 2px 10px;
166 | }
167 |
--------------------------------------------------------------------------------
/lib/jasmine/jasmine.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
3 | *
4 | * @namespace
5 | */
6 | var jasmine = {};
7 |
8 | /**
9 | * @private
10 | */
11 | jasmine.unimplementedMethod_ = function() {
12 | throw new Error("unimplemented method");
13 | };
14 |
15 | /**
16 | * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
17 | * a plain old variable and may be redefined by somebody else.
18 | *
19 | * @private
20 | */
21 | jasmine.undefined = jasmine.___undefined___;
22 |
23 | /**
24 | * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
25 | *
26 | */
27 | jasmine.DEFAULT_UPDATE_INTERVAL = 250;
28 |
29 | /**
30 | * Default timeout interval in milliseconds for waitsFor() blocks.
31 | */
32 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
33 |
34 | jasmine.getGlobal = function() {
35 | function getGlobal() {
36 | return this;
37 | }
38 |
39 | return getGlobal();
40 | };
41 |
42 | /**
43 | * Allows for bound functions to be compared. Internal use only.
44 | *
45 | * @ignore
46 | * @private
47 | * @param base {Object} bound 'this' for the function
48 | * @param name {Function} function to find
49 | */
50 | jasmine.bindOriginal_ = function(base, name) {
51 | var original = base[name];
52 | if (original.apply) {
53 | return function() {
54 | return original.apply(base, arguments);
55 | };
56 | } else {
57 | // IE support
58 | return jasmine.getGlobal()[name];
59 | }
60 | };
61 |
62 | jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
63 | jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
64 | jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
65 | jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
66 |
67 | jasmine.MessageResult = function(values) {
68 | this.type = 'log';
69 | this.values = values;
70 | this.trace = new Error(); // todo: test better
71 | };
72 |
73 | jasmine.MessageResult.prototype.toString = function() {
74 | var text = "";
75 | for(var i = 0; i < this.values.length; i++) {
76 | if (i > 0) text += " ";
77 | if (jasmine.isString_(this.values[i])) {
78 | text += this.values[i];
79 | } else {
80 | text += jasmine.pp(this.values[i]);
81 | }
82 | }
83 | return text;
84 | };
85 |
86 | jasmine.ExpectationResult = function(params) {
87 | this.type = 'expect';
88 | this.matcherName = params.matcherName;
89 | this.passed_ = params.passed;
90 | this.expected = params.expected;
91 | this.actual = params.actual;
92 |
93 | this.message = this.passed_ ? 'Passed.' : params.message;
94 | this.trace = this.passed_ ? '' : new Error(this.message);
95 | };
96 |
97 | jasmine.ExpectationResult.prototype.toString = function () {
98 | return this.message;
99 | };
100 |
101 | jasmine.ExpectationResult.prototype.passed = function () {
102 | return this.passed_;
103 | };
104 |
105 | /**
106 | * Getter for the Jasmine environment. Ensures one gets created
107 | */
108 | jasmine.getEnv = function() {
109 | return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
110 | };
111 |
112 | /**
113 | * @ignore
114 | * @private
115 | * @param value
116 | * @returns {Boolean}
117 | */
118 | jasmine.isArray_ = function(value) {
119 | return jasmine.isA_("Array", value);
120 | };
121 |
122 | /**
123 | * @ignore
124 | * @private
125 | * @param value
126 | * @returns {Boolean}
127 | */
128 | jasmine.isString_ = function(value) {
129 | return jasmine.isA_("String", value);
130 | };
131 |
132 | /**
133 | * @ignore
134 | * @private
135 | * @param value
136 | * @returns {Boolean}
137 | */
138 | jasmine.isNumber_ = function(value) {
139 | return jasmine.isA_("Number", value);
140 | };
141 |
142 | /**
143 | * @ignore
144 | * @private
145 | * @param {String} typeName
146 | * @param value
147 | * @returns {Boolean}
148 | */
149 | jasmine.isA_ = function(typeName, value) {
150 | return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
151 | };
152 |
153 | /**
154 | * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
155 | *
156 | * @param value {Object} an object to be outputted
157 | * @returns {String}
158 | */
159 | jasmine.pp = function(value) {
160 | var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
161 | stringPrettyPrinter.format(value);
162 | return stringPrettyPrinter.string;
163 | };
164 |
165 | /**
166 | * Returns true if the object is a DOM Node.
167 | *
168 | * @param {Object} obj object to check
169 | * @returns {Boolean}
170 | */
171 | jasmine.isDomNode = function(obj) {
172 | return obj['nodeType'] > 0;
173 | };
174 |
175 | /**
176 | * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
177 | *
178 | * @example
179 | * // don't care about which function is passed in, as long as it's a function
180 | * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
181 | *
182 | * @param {Class} clazz
183 | * @returns matchable object of the type clazz
184 | */
185 | jasmine.any = function(clazz) {
186 | return new jasmine.Matchers.Any(clazz);
187 | };
188 |
189 | /**
190 | * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
191 | *
192 | * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
193 | * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
194 | *
195 | * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
196 | *
197 | * Spies are torn down at the end of every spec.
198 | *
199 | * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
200 | *
201 | * @example
202 | * // a stub
203 | * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
204 | *
205 | * // spy example
206 | * var foo = {
207 | * not: function(bool) { return !bool; }
208 | * }
209 | *
210 | * // actual foo.not will not be called, execution stops
211 | * spyOn(foo, 'not');
212 |
213 | // foo.not spied upon, execution will continue to implementation
214 | * spyOn(foo, 'not').andCallThrough();
215 | *
216 | * // fake example
217 | * var foo = {
218 | * not: function(bool) { return !bool; }
219 | * }
220 | *
221 | * // foo.not(val) will return val
222 | * spyOn(foo, 'not').andCallFake(function(value) {return value;});
223 | *
224 | * // mock example
225 | * foo.not(7 == 7);
226 | * expect(foo.not).toHaveBeenCalled();
227 | * expect(foo.not).toHaveBeenCalledWith(true);
228 | *
229 | * @constructor
230 | * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
231 | * @param {String} name
232 | */
233 | jasmine.Spy = function(name) {
234 | /**
235 | * The name of the spy, if provided.
236 | */
237 | this.identity = name || 'unknown';
238 | /**
239 | * Is this Object a spy?
240 | */
241 | this.isSpy = true;
242 | /**
243 | * The actual function this spy stubs.
244 | */
245 | this.plan = function() {
246 | };
247 | /**
248 | * Tracking of the most recent call to the spy.
249 | * @example
250 | * var mySpy = jasmine.createSpy('foo');
251 | * mySpy(1, 2);
252 | * mySpy.mostRecentCall.args = [1, 2];
253 | */
254 | this.mostRecentCall = {};
255 |
256 | /**
257 | * Holds arguments for each call to the spy, indexed by call count
258 | * @example
259 | * var mySpy = jasmine.createSpy('foo');
260 | * mySpy(1, 2);
261 | * mySpy(7, 8);
262 | * mySpy.mostRecentCall.args = [7, 8];
263 | * mySpy.argsForCall[0] = [1, 2];
264 | * mySpy.argsForCall[1] = [7, 8];
265 | */
266 | this.argsForCall = [];
267 | this.calls = [];
268 | };
269 |
270 | /**
271 | * Tells a spy to call through to the actual implemenatation.
272 | *
273 | * @example
274 | * var foo = {
275 | * bar: function() { // do some stuff }
276 | * }
277 | *
278 | * // defining a spy on an existing property: foo.bar
279 | * spyOn(foo, 'bar').andCallThrough();
280 | */
281 | jasmine.Spy.prototype.andCallThrough = function() {
282 | this.plan = this.originalValue;
283 | return this;
284 | };
285 |
286 | /**
287 | * For setting the return value of a spy.
288 | *
289 | * @example
290 | * // defining a spy from scratch: foo() returns 'baz'
291 | * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
292 | *
293 | * // defining a spy on an existing property: foo.bar() returns 'baz'
294 | * spyOn(foo, 'bar').andReturn('baz');
295 | *
296 | * @param {Object} value
297 | */
298 | jasmine.Spy.prototype.andReturn = function(value) {
299 | this.plan = function() {
300 | return value;
301 | };
302 | return this;
303 | };
304 |
305 | /**
306 | * For throwing an exception when a spy is called.
307 | *
308 | * @example
309 | * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
310 | * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
311 | *
312 | * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
313 | * spyOn(foo, 'bar').andThrow('baz');
314 | *
315 | * @param {String} exceptionMsg
316 | */
317 | jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
318 | this.plan = function() {
319 | throw exceptionMsg;
320 | };
321 | return this;
322 | };
323 |
324 | /**
325 | * Calls an alternate implementation when a spy is called.
326 | *
327 | * @example
328 | * var baz = function() {
329 | * // do some stuff, return something
330 | * }
331 | * // defining a spy from scratch: foo() calls the function baz
332 | * var foo = jasmine.createSpy('spy on foo').andCall(baz);
333 | *
334 | * // defining a spy on an existing property: foo.bar() calls an anonymnous function
335 | * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
336 | *
337 | * @param {Function} fakeFunc
338 | */
339 | jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
340 | this.plan = fakeFunc;
341 | return this;
342 | };
343 |
344 | /**
345 | * Resets all of a spy's the tracking variables so that it can be used again.
346 | *
347 | * @example
348 | * spyOn(foo, 'bar');
349 | *
350 | * foo.bar();
351 | *
352 | * expect(foo.bar.callCount).toEqual(1);
353 | *
354 | * foo.bar.reset();
355 | *
356 | * expect(foo.bar.callCount).toEqual(0);
357 | */
358 | jasmine.Spy.prototype.reset = function() {
359 | this.wasCalled = false;
360 | this.callCount = 0;
361 | this.argsForCall = [];
362 | this.calls = [];
363 | this.mostRecentCall = {};
364 | };
365 |
366 | jasmine.createSpy = function(name) {
367 |
368 | var spyObj = function() {
369 | spyObj.wasCalled = true;
370 | spyObj.callCount++;
371 | var args = jasmine.util.argsToArray(arguments);
372 | spyObj.mostRecentCall.object = this;
373 | spyObj.mostRecentCall.args = args;
374 | spyObj.argsForCall.push(args);
375 | spyObj.calls.push({object: this, args: args});
376 | return spyObj.plan.apply(this, arguments);
377 | };
378 |
379 | var spy = new jasmine.Spy(name);
380 |
381 | for (var prop in spy) {
382 | spyObj[prop] = spy[prop];
383 | }
384 |
385 | spyObj.reset();
386 |
387 | return spyObj;
388 | };
389 |
390 | /**
391 | * Determines whether an object is a spy.
392 | *
393 | * @param {jasmine.Spy|Object} putativeSpy
394 | * @returns {Boolean}
395 | */
396 | jasmine.isSpy = function(putativeSpy) {
397 | return putativeSpy && putativeSpy.isSpy;
398 | };
399 |
400 | /**
401 | * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
402 | * large in one call.
403 | *
404 | * @param {String} baseName name of spy class
405 | * @param {Array} methodNames array of names of methods to make spies
406 | */
407 | jasmine.createSpyObj = function(baseName, methodNames) {
408 | if (!jasmine.isArray_(methodNames) || methodNames.length == 0) {
409 | throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
410 | }
411 | var obj = {};
412 | for (var i = 0; i < methodNames.length; i++) {
413 | obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
414 | }
415 | return obj;
416 | };
417 |
418 | /**
419 | * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
420 | *
421 | * Be careful not to leave calls to <code>jasmine.log</code> in production code.
422 | */
423 | jasmine.log = function() {
424 | var spec = jasmine.getEnv().currentSpec;
425 | spec.log.apply(spec, arguments);
426 | };
427 |
428 | /**
429 | * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
430 | *
431 | * @example
432 | * // spy example
433 | * var foo = {
434 | * not: function(bool) { return !bool; }
435 | * }
436 | * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
437 | *
438 | * @see jasmine.createSpy
439 | * @param obj
440 | * @param methodName
441 | * @returns a Jasmine spy that can be chained with all spy methods
442 | */
443 | var spyOn = function(obj, methodName) {
444 | return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
445 | };
446 |
447 | /**
448 | * Creates a Jasmine spec that will be added to the current suite.
449 | *
450 | * // TODO: pending tests
451 | *
452 | * @example
453 | * it('should be true', function() {
454 | * expect(true).toEqual(true);
455 | * });
456 | *
457 | * @param {String} desc description of this specification
458 | * @param {Function} func defines the preconditions and expectations of the spec
459 | */
460 | var it = function(desc, func) {
461 | return jasmine.getEnv().it(desc, func);
462 | };
463 |
464 | /**
465 | * Creates a <em>disabled</em> Jasmine spec.
466 | *
467 | * A convenience method that allows existing specs to be disabled temporarily during development.
468 | *
469 | * @param {String} desc description of this specification
470 | * @param {Function} func defines the preconditions and expectations of the spec
471 | */
472 | var xit = function(desc, func) {
473 | return jasmine.getEnv().xit(desc, func);
474 | };
475 |
476 | /**
477 | * Starts a chain for a Jasmine expectation.
478 | *
479 | * It is passed an Object that is the actual value and should chain to one of the many
480 | * jasmine.Matchers functions.
481 | *
482 | * @param {Object} actual Actual value to test against and expected value
483 | */
484 | var expect = function(actual) {
485 | return jasmine.getEnv().currentSpec.expect(actual);
486 | };
487 |
488 | /**
489 | * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
490 | *
491 | * @param {Function} func Function that defines part of a jasmine spec.
492 | */
493 | var runs = function(func) {
494 | jasmine.getEnv().currentSpec.runs(func);
495 | };
496 |
497 | /**
498 | * Waits a fixed time period before moving to the next block.
499 | *
500 | * @deprecated Use waitsFor() instead
501 | * @param {Number} timeout milliseconds to wait
502 | */
503 | var waits = function(timeout) {
504 | jasmine.getEnv().currentSpec.waits(timeout);
505 | };
506 |
507 | /**
508 | * Waits for the latchFunction to return true before proceeding to the next block.
509 | *
510 | * @param {Function} latchFunction
511 | * @param {String} optional_timeoutMessage
512 | * @param {Number} optional_timeout
513 | */
514 | var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
515 | jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
516 | };
517 |
518 | /**
519 | * A function that is called before each spec in a suite.
520 | *
521 | * Used for spec setup, including validating assumptions.
522 | *
523 | * @param {Function} beforeEachFunction
524 | */
525 | var beforeEach = function(beforeEachFunction) {
526 | jasmine.getEnv().beforeEach(beforeEachFunction);
527 | };
528 |
529 | /**
530 | * A function that is called after each spec in a suite.
531 | *
532 | * Used for restoring any state that is hijacked during spec execution.
533 | *
534 | * @param {Function} afterEachFunction
535 | */
536 | var afterEach = function(afterEachFunction) {
537 | jasmine.getEnv().afterEach(afterEachFunction);
538 | };
539 |
540 | /**
541 | * Defines a suite of specifications.
542 | *
543 | * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
544 | * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
545 | * of setup in some tests.
546 | *
547 | * @example
548 | * // TODO: a simple suite
549 | *
550 | * // TODO: a simple suite with a nested describe block
551 | *
552 | * @param {String} description A string, usually the class under test.
553 | * @param {Function} specDefinitions function that defines several specs.
554 | */
555 | var describe = function(description, specDefinitions) {
556 | return jasmine.getEnv().describe(description, specDefinitions);
557 | };
558 |
559 | /**
560 | * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
561 | *
562 | * @param {String} description A string, usually the class under test.
563 | * @param {Function} specDefinitions function that defines several specs.
564 | */
565 | var xdescribe = function(description, specDefinitions) {
566 | return jasmine.getEnv().xdescribe(description, specDefinitions);
567 | };
568 |
569 |
570 | // Provide the XMLHttpRequest class for IE 5.x-6.x:
571 | jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
572 | try {
573 | return new ActiveXObject("Msxml2.XMLHTTP.6.0");
574 | } catch(e) {
575 | }
576 | try {
577 | return new ActiveXObject("Msxml2.XMLHTTP.3.0");
578 | } catch(e) {
579 | }
580 | try {
581 | return new ActiveXObject("Msxml2.XMLHTTP");
582 | } catch(e) {
583 | }
584 | try {
585 | return new ActiveXObject("Microsoft.XMLHTTP");
586 | } catch(e) {
587 | }
588 | throw new Error("This browser does not support XMLHttpRequest.");
589 | } : XMLHttpRequest;
590 | /**
591 | * @namespace
592 | */
593 | jasmine.util = {};
594 |
595 | /**
596 | * Declare that a child class inherit it's prototype from the parent class.
597 | *
598 | * @private
599 | * @param {Function} childClass
600 | * @param {Function} parentClass
601 | */
602 | jasmine.util.inherit = function(childClass, parentClass) {
603 | /**
604 | * @private
605 | */
606 | var subclass = function() {
607 | };
608 | subclass.prototype = parentClass.prototype;
609 | childClass.prototype = new subclass;
610 | };
611 |
612 | jasmine.util.formatException = function(e) {
613 | var lineNumber;
614 | if (e.line) {
615 | lineNumber = e.line;
616 | }
617 | else if (e.lineNumber) {
618 | lineNumber = e.lineNumber;
619 | }
620 |
621 | var file;
622 |
623 | if (e.sourceURL) {
624 | file = e.sourceURL;
625 | }
626 | else if (e.fileName) {
627 | file = e.fileName;
628 | }
629 |
630 | var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
631 |
632 | if (file && lineNumber) {
633 | message += ' in ' + file + ' (line ' + lineNumber + ')';
634 | }
635 |
636 | return message;
637 | };
638 |
639 | jasmine.util.htmlEscape = function(str) {
640 | if (!str) return str;
641 | return str.replace(/&/g, '&')
642 | .replace(/</g, '<')
643 | .replace(/>/g, '>');
644 | };
645 |
646 | jasmine.util.argsToArray = function(args) {
647 | var arrayOfArgs = [];
648 | for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
649 | return arrayOfArgs;
650 | };
651 |
652 | jasmine.util.extend = function(destination, source) {
653 | for (var property in source) destination[property] = source[property];
654 | return destination;
655 | };
656 |
657 | /**
658 | * Environment for Jasmine
659 | *
660 | * @constructor
661 | */
662 | jasmine.Env = function() {
663 | this.currentSpec = null;
664 | this.currentSuite = null;
665 | this.currentRunner_ = new jasmine.Runner(this);
666 |
667 | this.reporter = new jasmine.MultiReporter();
668 |
669 | this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
670 | this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
671 | this.lastUpdate = 0;
672 | this.specFilter = function() {
673 | return true;
674 | };
675 |
676 | this.nextSpecId_ = 0;
677 | this.nextSuiteId_ = 0;
678 | this.equalityTesters_ = [];
679 |
680 | // wrap matchers
681 | this.matchersClass = function() {
682 | jasmine.Matchers.apply(this, arguments);
683 | };
684 | jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
685 |
686 | jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
687 | };
688 |
689 |
690 | jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
691 | jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
692 | jasmine.Env.prototype.setInterval = jasmine.setInterval;
693 | jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
694 |
695 | /**
696 | * @returns an object containing jasmine version build info, if set.
697 | */
698 | jasmine.Env.prototype.version = function () {
699 | if (jasmine.version_) {
700 | return jasmine.version_;
701 | } else {
702 | throw new Error('Version not set');
703 | }
704 | };
705 |
706 | /**
707 | * @returns string containing jasmine version build info, if set.
708 | */
709 | jasmine.Env.prototype.versionString = function() {
710 | if (jasmine.version_) {
711 | var version = this.version();
712 | return version.major + "." + version.minor + "." + version.build + " revision " + version.revision;
713 | } else {
714 | return "version unknown";
715 | }
716 | };
717 |
718 | /**
719 | * @returns a sequential integer starting at 0
720 | */
721 | jasmine.Env.prototype.nextSpecId = function () {
722 | return this.nextSpecId_++;
723 | };
724 |
725 | /**
726 | * @returns a sequential integer starting at 0
727 | */
728 | jasmine.Env.prototype.nextSuiteId = function () {
729 | return this.nextSuiteId_++;
730 | };
731 |
732 | /**
733 | * Register a reporter to receive status updates from Jasmine.
734 | * @param {jasmine.Reporter} reporter An object which will receive status updates.
735 | */
736 | jasmine.Env.prototype.addReporter = function(reporter) {
737 | this.reporter.addReporter(reporter);
738 | };
739 |
740 | jasmine.Env.prototype.execute = function() {
741 | this.currentRunner_.execute();
742 | };
743 |
744 | jasmine.Env.prototype.describe = function(description, specDefinitions) {
745 | var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
746 |
747 | var parentSuite = this.currentSuite;
748 | if (parentSuite) {
749 | parentSuite.add(suite);
750 | } else {
751 | this.currentRunner_.add(suite);
752 | }
753 |
754 | this.currentSuite = suite;
755 |
756 | var declarationError = null;
757 | try {
758 | specDefinitions.call(suite);
759 | } catch(e) {
760 | declarationError = e;
761 | }
762 |
763 | this.currentSuite = parentSuite;
764 |
765 | if (declarationError) {
766 | this.it("encountered a declaration exception", function() {
767 | throw declarationError;
768 | });
769 | }
770 |
771 | return suite;
772 | };
773 |
774 | jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
775 | if (this.currentSuite) {
776 | this.currentSuite.beforeEach(beforeEachFunction);
777 | } else {
778 | this.currentRunner_.beforeEach(beforeEachFunction);
779 | }
780 | };
781 |
782 | jasmine.Env.prototype.currentRunner = function () {
783 | return this.currentRunner_;
784 | };
785 |
786 | jasmine.Env.prototype.afterEach = function(afterEachFunction) {
787 | if (this.currentSuite) {
788 | this.currentSuite.afterEach(afterEachFunction);
789 | } else {
790 | this.currentRunner_.afterEach(afterEachFunction);
791 | }
792 |
793 | };
794 |
795 | jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
796 | return {
797 | execute: function() {
798 | }
799 | };
800 | };
801 |
802 | jasmine.Env.prototype.it = function(description, func) {
803 | var spec = new jasmine.Spec(this, this.currentSuite, description);
804 | this.currentSuite.add(spec);
805 | this.currentSpec = spec;
806 |
807 | if (func) {
808 | spec.runs(func);
809 | }
810 |
811 | return spec;
812 | };
813 |
814 | jasmine.Env.prototype.xit = function(desc, func) {
815 | return {
816 | id: this.nextSpecId(),
817 | runs: function() {
818 | }
819 | };
820 | };
821 |
822 | jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
823 | if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
824 | return true;
825 | }
826 |
827 | a.__Jasmine_been_here_before__ = b;
828 | b.__Jasmine_been_here_before__ = a;
829 |
830 | var hasKey = function(obj, keyName) {
831 | return obj != null && obj[keyName] !== jasmine.undefined;
832 | };
833 |
834 | for (var property in b) {
835 | if (!hasKey(a, property) && hasKey(b, property)) {
836 | mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
837 | }
838 | }
839 | for (property in a) {
840 | if (!hasKey(b, property) && hasKey(a, property)) {
841 | mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
842 | }
843 | }
844 | for (property in b) {
845 | if (property == '__Jasmine_been_here_before__') continue;
846 | if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
847 | mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
848 | }
849 | }
850 |
851 | if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
852 | mismatchValues.push("arrays were not the same length");
853 | }
854 |
855 | delete a.__Jasmine_been_here_before__;
856 | delete b.__Jasmine_been_here_before__;
857 | return (mismatchKeys.length == 0 && mismatchValues.length == 0);
858 | };
859 |
860 | jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
861 | mismatchKeys = mismatchKeys || [];
862 | mismatchValues = mismatchValues || [];
863 |
864 | for (var i = 0; i < this.equalityTesters_.length; i++) {
865 | var equalityTester = this.equalityTesters_[i];
866 | var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
867 | if (result !== jasmine.undefined) return result;
868 | }
869 |
870 | if (a === b) return true;
871 |
872 | if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
873 | return (a == jasmine.undefined && b == jasmine.undefined);
874 | }
875 |
876 | if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
877 | return a === b;
878 | }
879 |
880 | if (a instanceof Date && b instanceof Date) {
881 | return a.getTime() == b.getTime();
882 | }
883 |
884 | if (a instanceof jasmine.Matchers.Any) {
885 | return a.matches(b);
886 | }
887 |
888 | if (b instanceof jasmine.Matchers.Any) {
889 | return b.matches(a);
890 | }
891 |
892 | if (jasmine.isString_(a) && jasmine.isString_(b)) {
893 | return (a == b);
894 | }
895 |
896 | if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
897 | return (a == b);
898 | }
899 |
900 | if (typeof a === "object" && typeof b === "object") {
901 | return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
902 | }
903 |
904 | //Straight check
905 | return (a === b);
906 | };
907 |
908 | jasmine.Env.prototype.contains_ = function(haystack, needle) {
909 | if (jasmine.isArray_(haystack)) {
910 | for (var i = 0; i < haystack.length; i++) {
911 | if (this.equals_(haystack[i], needle)) return true;
912 | }
913 | return false;
914 | }
915 | return haystack.indexOf(needle) >= 0;
916 | };
917 |
918 | jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
919 | this.equalityTesters_.push(equalityTester);
920 | };
921 | /** No-op base class for Jasmine reporters.
922 | *
923 | * @constructor
924 | */
925 | jasmine.Reporter = function() {
926 | };
927 |
928 | //noinspection JSUnusedLocalSymbols
929 | jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
930 | };
931 |
932 | //noinspection JSUnusedLocalSymbols
933 | jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
934 | };
935 |
936 | //noinspection JSUnusedLocalSymbols
937 | jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
938 | };
939 |
940 | //noinspection JSUnusedLocalSymbols
941 | jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
942 | };
943 |
944 | //noinspection JSUnusedLocalSymbols
945 | jasmine.Reporter.prototype.reportSpecResults = function(spec) {
946 | };
947 |
948 | //noinspection JSUnusedLocalSymbols
949 | jasmine.Reporter.prototype.log = function(str) {
950 | };
951 |
952 | /**
953 | * Blocks are functions with executable code that make up a spec.
954 | *
955 | * @constructor
956 | * @param {jasmine.Env} env
957 | * @param {Function} func
958 | * @param {jasmine.Spec} spec
959 | */
960 | jasmine.Block = function(env, func, spec) {
961 | this.env = env;
962 | this.func = func;
963 | this.spec = spec;
964 | };
965 |
966 | jasmine.Block.prototype.execute = function(onComplete) {
967 | try {
968 | this.func.apply(this.spec);
969 | } catch (e) {
970 | this.spec.fail(e);
971 | }
972 | onComplete();
973 | };
974 | /** JavaScript API reporter.
975 | *
976 | * @constructor
977 | */
978 | jasmine.JsApiReporter = function() {
979 | this.started = false;
980 | this.finished = false;
981 | this.suites_ = [];
982 | this.results_ = {};
983 | };
984 |
985 | jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
986 | this.started = true;
987 | var suites = runner.topLevelSuites();
988 | for (var i = 0; i < suites.length; i++) {
989 | var suite = suites[i];
990 | this.suites_.push(this.summarize_(suite));
991 | }
992 | };
993 |
994 | jasmine.JsApiReporter.prototype.suites = function() {
995 | return this.suites_;
996 | };
997 |
998 | jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
999 | var isSuite = suiteOrSpec instanceof jasmine.Suite;
1000 | var summary = {
1001 | id: suiteOrSpec.id,
1002 | name: suiteOrSpec.description,
1003 | type: isSuite ? 'suite' : 'spec',
1004 | children: []
1005 | };
1006 |
1007 | if (isSuite) {
1008 | var children = suiteOrSpec.children();
1009 | for (var i = 0; i < children.length; i++) {
1010 | summary.children.push(this.summarize_(children[i]));
1011 | }
1012 | }
1013 | return summary;
1014 | };
1015 |
1016 | jasmine.JsApiReporter.prototype.results = function() {
1017 | return this.results_;
1018 | };
1019 |
1020 | jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
1021 | return this.results_[specId];
1022 | };
1023 |
1024 | //noinspection JSUnusedLocalSymbols
1025 | jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
1026 | this.finished = true;
1027 | };
1028 |
1029 | //noinspection JSUnusedLocalSymbols
1030 | jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
1031 | };
1032 |
1033 | //noinspection JSUnusedLocalSymbols
1034 | jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
1035 | this.results_[spec.id] = {
1036 | messages: spec.results().getItems(),
1037 | result: spec.results().failedCount > 0 ? "failed" : "passed"
1038 | };
1039 | };
1040 |
1041 | //noinspection JSUnusedLocalSymbols
1042 | jasmine.JsApiReporter.prototype.log = function(str) {
1043 | };
1044 |
1045 | jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
1046 | var results = {};
1047 | for (var i = 0; i < specIds.length; i++) {
1048 | var specId = specIds[i];
1049 | results[specId] = this.summarizeResult_(this.results_[specId]);
1050 | }
1051 | return results;
1052 | };
1053 |
1054 | jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
1055 | var summaryMessages = [];
1056 | var messagesLength = result.messages.length;
1057 | for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
1058 | var resultMessage = result.messages[messageIndex];
1059 | summaryMessages.push({
1060 | text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
1061 | passed: resultMessage.passed ? resultMessage.passed() : true,
1062 | type: resultMessage.type,
1063 | message: resultMessage.message,
1064 | trace: {
1065 | stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
1066 | }
1067 | });
1068 | }
1069 |
1070 | return {
1071 | result : result.result,
1072 | messages : summaryMessages
1073 | };
1074 | };
1075 |
1076 | /**
1077 | * @constructor
1078 | * @param {jasmine.Env} env
1079 | * @param actual
1080 | * @param {jasmine.Spec} spec
1081 | */
1082 | jasmine.Matchers = function(env, actual, spec, opt_isNot) {
1083 | this.env = env;
1084 | this.actual = actual;
1085 | this.spec = spec;
1086 | this.isNot = opt_isNot || false;
1087 | this.reportWasCalled_ = false;
1088 | };
1089 |
1090 | // todo: @deprecated as of Jasmine 0.11, remove soon [xw]
1091 | jasmine.Matchers.pp = function(str) {
1092 | throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
1093 | };
1094 |
1095 | // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
1096 | jasmine.Matchers.prototype.report = function(result, failing_message, details) {
1097 | throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
1098 | };
1099 |
1100 | jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
1101 | for (var methodName in prototype) {
1102 | if (methodName == 'report') continue;
1103 | var orig = prototype[methodName];
1104 | matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
1105 | }
1106 | };
1107 |
1108 | jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
1109 | return function() {
1110 | var matcherArgs = jasmine.util.argsToArray(arguments);
1111 | var result = matcherFunction.apply(this, arguments);
1112 |
1113 | if (this.isNot) {
1114 | result = !result;
1115 | }
1116 |
1117 | if (this.reportWasCalled_) return result;
1118 |
1119 | var message;
1120 | if (!result) {
1121 | if (this.message) {
1122 | message = this.message.apply(this, arguments);
1123 | if (jasmine.isArray_(message)) {
1124 | message = message[this.isNot ? 1 : 0];
1125 | }
1126 | } else {
1127 | var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
1128 | message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
1129 | if (matcherArgs.length > 0) {
1130 | for (var i = 0; i < matcherArgs.length; i++) {
1131 | if (i > 0) message += ",";
1132 | message += " " + jasmine.pp(matcherArgs[i]);
1133 | }
1134 | }
1135 | message += ".";
1136 | }
1137 | }
1138 | var expectationResult = new jasmine.ExpectationResult({
1139 | matcherName: matcherName,
1140 | passed: result,
1141 | expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
1142 | actual: this.actual,
1143 | message: message
1144 | });
1145 | this.spec.addMatcherResult(expectationResult);
1146 | return jasmine.undefined;
1147 | };
1148 | };
1149 |
1150 |
1151 |
1152 |
1153 | /**
1154 | * toBe: compares the actual to the expected using ===
1155 | * @param expected
1156 | */
1157 | jasmine.Matchers.prototype.toBe = function(expected) {
1158 | return this.actual === expected;
1159 | };
1160 |
1161 | /**
1162 | * toNotBe: compares the actual to the expected using !==
1163 | * @param expected
1164 | * @deprecated as of 1.0. Use not.toBe() instead.
1165 | */
1166 | jasmine.Matchers.prototype.toNotBe = function(expected) {
1167 | return this.actual !== expected;
1168 | };
1169 |
1170 | /**
1171 | * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
1172 | *
1173 | * @param expected
1174 | */
1175 | jasmine.Matchers.prototype.toEqual = function(expected) {
1176 | return this.env.equals_(this.actual, expected);
1177 | };
1178 |
1179 | /**
1180 | * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
1181 | * @param expected
1182 | * @deprecated as of 1.0. Use not.toNotEqual() instead.
1183 | */
1184 | jasmine.Matchers.prototype.toNotEqual = function(expected) {
1185 | return !this.env.equals_(this.actual, expected);
1186 | };
1187 |
1188 | /**
1189 | * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
1190 | * a pattern or a String.
1191 | *
1192 | * @param expected
1193 | */
1194 | jasmine.Matchers.prototype.toMatch = function(expected) {
1195 | return new RegExp(expected).test(this.actual);
1196 | };
1197 |
1198 | /**
1199 | * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
1200 | * @param expected
1201 | * @deprecated as of 1.0. Use not.toMatch() instead.
1202 | */
1203 | jasmine.Matchers.prototype.toNotMatch = function(expected) {
1204 | return !(new RegExp(expected).test(this.actual));
1205 | };
1206 |
1207 | /**
1208 | * Matcher that compares the actual to jasmine.undefined.
1209 | */
1210 | jasmine.Matchers.prototype.toBeDefined = function() {
1211 | return (this.actual !== jasmine.undefined);
1212 | };
1213 |
1214 | /**
1215 | * Matcher that compares the actual to jasmine.undefined.
1216 | */
1217 | jasmine.Matchers.prototype.toBeUndefined = function() {
1218 | return (this.actual === jasmine.undefined);
1219 | };
1220 |
1221 | /**
1222 | * Matcher that compares the actual to null.
1223 | */
1224 | jasmine.Matchers.prototype.toBeNull = function() {
1225 | return (this.actual === null);
1226 | };
1227 |
1228 | /**
1229 | * Matcher that boolean not-nots the actual.
1230 | */
1231 | jasmine.Matchers.prototype.toBeTruthy = function() {
1232 | return !!this.actual;
1233 | };
1234 |
1235 |
1236 | /**
1237 | * Matcher that boolean nots the actual.
1238 | */
1239 | jasmine.Matchers.prototype.toBeFalsy = function() {
1240 | return !this.actual;
1241 | };
1242 |
1243 |
1244 | /**
1245 | * Matcher that checks to see if the actual, a Jasmine spy, was called.
1246 | */
1247 | jasmine.Matchers.prototype.toHaveBeenCalled = function() {
1248 | if (arguments.length > 0) {
1249 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
1250 | }
1251 |
1252 | if (!jasmine.isSpy(this.actual)) {
1253 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1254 | }
1255 |
1256 | this.message = function() {
1257 | return [
1258 | "Expected spy " + this.actual.identity + " to have been called.",
1259 | "Expected spy " + this.actual.identity + " not to have been called."
1260 | ];
1261 | };
1262 |
1263 | return this.actual.wasCalled;
1264 | };
1265 |
1266 | /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
1267 | jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
1268 |
1269 | /**
1270 | * Matcher that checks to see if the actual, a Jasmine spy, was not called.
1271 | *
1272 | * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
1273 | */
1274 | jasmine.Matchers.prototype.wasNotCalled = function() {
1275 | if (arguments.length > 0) {
1276 | throw new Error('wasNotCalled does not take arguments');
1277 | }
1278 |
1279 | if (!jasmine.isSpy(this.actual)) {
1280 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1281 | }
1282 |
1283 | this.message = function() {
1284 | return [
1285 | "Expected spy " + this.actual.identity + " to not have been called.",
1286 | "Expected spy " + this.actual.identity + " to have been called."
1287 | ];
1288 | };
1289 |
1290 | return !this.actual.wasCalled;
1291 | };
1292 |
1293 | /**
1294 | * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
1295 | *
1296 | * @example
1297 | *
1298 | */
1299 | jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
1300 | var expectedArgs = jasmine.util.argsToArray(arguments);
1301 | if (!jasmine.isSpy(this.actual)) {
1302 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1303 | }
1304 | this.message = function() {
1305 | if (this.actual.callCount == 0) {
1306 | // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
1307 | return [
1308 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
1309 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
1310 | ];
1311 | } else {
1312 | return [
1313 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
1314 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
1315 | ];
1316 | }
1317 | };
1318 |
1319 | return this.env.contains_(this.actual.argsForCall, expectedArgs);
1320 | };
1321 |
1322 | /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
1323 | jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
1324 |
1325 | /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
1326 | jasmine.Matchers.prototype.wasNotCalledWith = function() {
1327 | var expectedArgs = jasmine.util.argsToArray(arguments);
1328 | if (!jasmine.isSpy(this.actual)) {
1329 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1330 | }
1331 |
1332 | this.message = function() {
1333 | return [
1334 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
1335 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
1336 | ]
1337 | };
1338 |
1339 | return !this.env.contains_(this.actual.argsForCall, expectedArgs);
1340 | };
1341 |
1342 | /**
1343 | * Matcher that checks that the expected item is an element in the actual Array.
1344 | *
1345 | * @param {Object} expected
1346 | */
1347 | jasmine.Matchers.prototype.toContain = function(expected) {
1348 | return this.env.contains_(this.actual, expected);
1349 | };
1350 |
1351 | /**
1352 | * Matcher that checks that the expected item is NOT an element in the actual Array.
1353 | *
1354 | * @param {Object} expected
1355 | * @deprecated as of 1.0. Use not.toNotContain() instead.
1356 | */
1357 | jasmine.Matchers.prototype.toNotContain = function(expected) {
1358 | return !this.env.contains_(this.actual, expected);
1359 | };
1360 |
1361 | jasmine.Matchers.prototype.toBeLessThan = function(expected) {
1362 | return this.actual < expected;
1363 | };
1364 |
1365 | jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
1366 | return this.actual > expected;
1367 | };
1368 |
1369 | /**
1370 | * Matcher that checks that the expected exception was thrown by the actual.
1371 | *
1372 | * @param {String} expected
1373 | */
1374 | jasmine.Matchers.prototype.toThrow = function(expected) {
1375 | var result = false;
1376 | var exception;
1377 | if (typeof this.actual != 'function') {
1378 | throw new Error('Actual is not a function');
1379 | }
1380 | try {
1381 | this.actual();
1382 | } catch (e) {
1383 | exception = e;
1384 | }
1385 | if (exception) {
1386 | result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
1387 | }
1388 |
1389 | var not = this.isNot ? "not " : "";
1390 |
1391 | this.message = function() {
1392 | if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
1393 | return ["Expected function " + not + "to throw", expected ? expected.message || expected : " an exception", ", but it threw", exception.message || exception].join(' ');
1394 | } else {
1395 | return "Expected function to throw an exception.";
1396 | }
1397 | };
1398 |
1399 | return result;
1400 | };
1401 |
1402 | jasmine.Matchers.Any = function(expectedClass) {
1403 | this.expectedClass = expectedClass;
1404 | };
1405 |
1406 | jasmine.Matchers.Any.prototype.matches = function(other) {
1407 | if (this.expectedClass == String) {
1408 | return typeof other == 'string' || other instanceof String;
1409 | }
1410 |
1411 | if (this.expectedClass == Number) {
1412 | return typeof other == 'number' || other instanceof Number;
1413 | }
1414 |
1415 | if (this.expectedClass == Function) {
1416 | return typeof other == 'function' || other instanceof Function;
1417 | }
1418 |
1419 | if (this.expectedClass == Object) {
1420 | return typeof other == 'object';
1421 | }
1422 |
1423 | return other instanceof this.expectedClass;
1424 | };
1425 |
1426 | jasmine.Matchers.Any.prototype.toString = function() {
1427 | return '<jasmine.any(' + this.expectedClass + ')>';
1428 | };
1429 |
1430 | /**
1431 | * @constructor
1432 | */
1433 | jasmine.MultiReporter = function() {
1434 | this.subReporters_ = [];
1435 | };
1436 | jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
1437 |
1438 | jasmine.MultiReporter.prototype.addReporter = function(reporter) {
1439 | this.subReporters_.push(reporter);
1440 | };
1441 |
1442 | (function() {
1443 | var functionNames = [
1444 | "reportRunnerStarting",
1445 | "reportRunnerResults",
1446 | "reportSuiteResults",
1447 | "reportSpecStarting",
1448 | "reportSpecResults",
1449 | "log"
1450 | ];
1451 | for (var i = 0; i < functionNames.length; i++) {
1452 | var functionName = functionNames[i];
1453 | jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
1454 | return function() {
1455 | for (var j = 0; j < this.subReporters_.length; j++) {
1456 | var subReporter = this.subReporters_[j];
1457 | if (subReporter[functionName]) {
1458 | subReporter[functionName].apply(subReporter, arguments);
1459 | }
1460 | }
1461 | };
1462 | })(functionName);
1463 | }
1464 | })();
1465 | /**
1466 | * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
1467 | *
1468 | * @constructor
1469 | */
1470 | jasmine.NestedResults = function() {
1471 | /**
1472 | * The total count of results
1473 | */
1474 | this.totalCount = 0;
1475 | /**
1476 | * Number of passed results
1477 | */
1478 | this.passedCount = 0;
1479 | /**
1480 | * Number of failed results
1481 | */
1482 | this.failedCount = 0;
1483 | /**
1484 | * Was this suite/spec skipped?
1485 | */
1486 | this.skipped = false;
1487 | /**
1488 | * @ignore
1489 | */
1490 | this.items_ = [];
1491 | };
1492 |
1493 | /**
1494 | * Roll up the result counts.
1495 | *
1496 | * @param result
1497 | */
1498 | jasmine.NestedResults.prototype.rollupCounts = function(result) {
1499 | this.totalCount += result.totalCount;
1500 | this.passedCount += result.passedCount;
1501 | this.failedCount += result.failedCount;
1502 | };
1503 |
1504 | /**
1505 | * Adds a log message.
1506 | * @param values Array of message parts which will be concatenated later.
1507 | */
1508 | jasmine.NestedResults.prototype.log = function(values) {
1509 | this.items_.push(new jasmine.MessageResult(values));
1510 | };
1511 |
1512 | /**
1513 | * Getter for the results: message & results.
1514 | */
1515 | jasmine.NestedResults.prototype.getItems = function() {
1516 | return this.items_;
1517 | };
1518 |
1519 | /**
1520 | * Adds a result, tracking counts (total, passed, & failed)
1521 | * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
1522 | */
1523 | jasmine.NestedResults.prototype.addResult = function(result) {
1524 | if (result.type != 'log') {
1525 | if (result.items_) {
1526 | this.rollupCounts(result);
1527 | } else {
1528 | this.totalCount++;
1529 | if (result.passed()) {
1530 | this.passedCount++;
1531 | } else {
1532 | this.failedCount++;
1533 | }
1534 | }
1535 | }
1536 | this.items_.push(result);
1537 | };
1538 |
1539 | /**
1540 | * @returns {Boolean} True if <b>everything</b> below passed
1541 | */
1542 | jasmine.NestedResults.prototype.passed = function() {
1543 | return this.passedCount === this.totalCount;
1544 | };
1545 | /**
1546 | * Base class for pretty printing for expectation results.
1547 | */
1548 | jasmine.PrettyPrinter = function() {
1549 | this.ppNestLevel_ = 0;
1550 | };
1551 |
1552 | /**
1553 | * Formats a value in a nice, human-readable string.
1554 | *
1555 | * @param value
1556 | */
1557 | jasmine.PrettyPrinter.prototype.format = function(value) {
1558 | if (this.ppNestLevel_ > 40) {
1559 | throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
1560 | }
1561 |
1562 | this.ppNestLevel_++;
1563 | try {
1564 | if (value === jasmine.undefined) {
1565 | this.emitScalar('undefined');
1566 | } else if (value === null) {
1567 | this.emitScalar('null');
1568 | } else if (value === jasmine.getGlobal()) {
1569 | this.emitScalar('<global>');
1570 | } else if (value instanceof jasmine.Matchers.Any) {
1571 | this.emitScalar(value.toString());
1572 | } else if (typeof value === 'string') {
1573 | this.emitString(value);
1574 | } else if (jasmine.isSpy(value)) {
1575 | this.emitScalar("spy on " + value.identity);
1576 | } else if (value instanceof RegExp) {
1577 | this.emitScalar(value.toString());
1578 | } else if (typeof value === 'function') {
1579 | this.emitScalar('Function');
1580 | } else if (typeof value.nodeType === 'number') {
1581 | this.emitScalar('HTMLNode');
1582 | } else if (value instanceof Date) {
1583 | this.emitScalar('Date(' + value + ')');
1584 | } else if (value.__Jasmine_been_here_before__) {
1585 | this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
1586 | } else if (jasmine.isArray_(value) || typeof value == 'object') {
1587 | value.__Jasmine_been_here_before__ = true;
1588 | if (jasmine.isArray_(value)) {
1589 | this.emitArray(value);
1590 | } else {
1591 | this.emitObject(value);
1592 | }
1593 | delete value.__Jasmine_been_here_before__;
1594 | } else {
1595 | this.emitScalar(value.toString());
1596 | }
1597 | } finally {
1598 | this.ppNestLevel_--;
1599 | }
1600 | };
1601 |
1602 | jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
1603 | for (var property in obj) {
1604 | if (property == '__Jasmine_been_here_before__') continue;
1605 | fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false);
1606 | }
1607 | };
1608 |
1609 | jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
1610 | jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
1611 | jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
1612 | jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
1613 |
1614 | jasmine.StringPrettyPrinter = function() {
1615 | jasmine.PrettyPrinter.call(this);
1616 |
1617 | this.string = '';
1618 | };
1619 | jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
1620 |
1621 | jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
1622 | this.append(value);
1623 | };
1624 |
1625 | jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
1626 | this.append("'" + value + "'");
1627 | };
1628 |
1629 | jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
1630 | this.append('[ ');
1631 | for (var i = 0; i < array.length; i++) {
1632 | if (i > 0) {
1633 | this.append(', ');
1634 | }
1635 | this.format(array[i]);
1636 | }
1637 | this.append(' ]');
1638 | };
1639 |
1640 | jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
1641 | var self = this;
1642 | this.append('{ ');
1643 | var first = true;
1644 |
1645 | this.iterateObject(obj, function(property, isGetter) {
1646 | if (first) {
1647 | first = false;
1648 | } else {
1649 | self.append(', ');
1650 | }
1651 |
1652 | self.append(property);
1653 | self.append(' : ');
1654 | if (isGetter) {
1655 | self.append('<getter>');
1656 | } else {
1657 | self.format(obj[property]);
1658 | }
1659 | });
1660 |
1661 | this.append(' }');
1662 | };
1663 |
1664 | jasmine.StringPrettyPrinter.prototype.append = function(value) {
1665 | this.string += value;
1666 | };
1667 | jasmine.Queue = function(env) {
1668 | this.env = env;
1669 | this.blocks = [];
1670 | this.running = false;
1671 | this.index = 0;
1672 | this.offset = 0;
1673 | this.abort = false;
1674 | };
1675 |
1676 | jasmine.Queue.prototype.addBefore = function(block) {
1677 | this.blocks.unshift(block);
1678 | };
1679 |
1680 | jasmine.Queue.prototype.add = function(block) {
1681 | this.blocks.push(block);
1682 | };
1683 |
1684 | jasmine.Queue.prototype.insertNext = function(block) {
1685 | this.blocks.splice((this.index + this.offset + 1), 0, block);
1686 | this.offset++;
1687 | };
1688 |
1689 | jasmine.Queue.prototype.start = function(onComplete) {
1690 | this.running = true;
1691 | this.onComplete = onComplete;
1692 | this.next_();
1693 | };
1694 |
1695 | jasmine.Queue.prototype.isRunning = function() {
1696 | return this.running;
1697 | };
1698 |
1699 | jasmine.Queue.LOOP_DONT_RECURSE = true;
1700 |
1701 | jasmine.Queue.prototype.next_ = function() {
1702 | var self = this;
1703 | var goAgain = true;
1704 |
1705 | while (goAgain) {
1706 | goAgain = false;
1707 |
1708 | if (self.index < self.blocks.length && !this.abort) {
1709 | var calledSynchronously = true;
1710 | var completedSynchronously = false;
1711 |
1712 | var onComplete = function () {
1713 | if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
1714 | completedSynchronously = true;
1715 | return;
1716 | }
1717 |
1718 | if (self.blocks[self.index].abort) {
1719 | self.abort = true;
1720 | }
1721 |
1722 | self.offset = 0;
1723 | self.index++;
1724 |
1725 | var now = new Date().getTime();
1726 | if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
1727 | self.env.lastUpdate = now;
1728 | self.env.setTimeout(function() {
1729 | self.next_();
1730 | }, 0);
1731 | } else {
1732 | if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
1733 | goAgain = true;
1734 | } else {
1735 | self.next_();
1736 | }
1737 | }
1738 | };
1739 | self.blocks[self.index].execute(onComplete);
1740 |
1741 | calledSynchronously = false;
1742 | if (completedSynchronously) {
1743 | onComplete();
1744 | }
1745 |
1746 | } else {
1747 | self.running = false;
1748 | if (self.onComplete) {
1749 | self.onComplete();
1750 | }
1751 | }
1752 | }
1753 | };
1754 |
1755 | jasmine.Queue.prototype.results = function() {
1756 | var results = new jasmine.NestedResults();
1757 | for (var i = 0; i < this.blocks.length; i++) {
1758 | if (this.blocks[i].results) {
1759 | results.addResult(this.blocks[i].results());
1760 | }
1761 | }
1762 | return results;
1763 | };
1764 |
1765 |
1766 | /**
1767 | * Runner
1768 | *
1769 | * @constructor
1770 | * @param {jasmine.Env} env
1771 | */
1772 | jasmine.Runner = function(env) {
1773 | var self = this;
1774 | self.env = env;
1775 | self.queue = new jasmine.Queue(env);
1776 | self.before_ = [];
1777 | self.after_ = [];
1778 | self.suites_ = [];
1779 | };
1780 |
1781 | jasmine.Runner.prototype.execute = function() {
1782 | var self = this;
1783 | if (self.env.reporter.reportRunnerStarting) {
1784 | self.env.reporter.reportRunnerStarting(this);
1785 | }
1786 | self.queue.start(function () {
1787 | self.finishCallback();
1788 | });
1789 | };
1790 |
1791 | jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
1792 | beforeEachFunction.typeName = 'beforeEach';
1793 | this.before_.splice(0,0,beforeEachFunction);
1794 | };
1795 |
1796 | jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
1797 | afterEachFunction.typeName = 'afterEach';
1798 | this.after_.splice(0,0,afterEachFunction);
1799 | };
1800 |
1801 |
1802 | jasmine.Runner.prototype.finishCallback = function() {
1803 | this.env.reporter.reportRunnerResults(this);
1804 | };
1805 |
1806 | jasmine.Runner.prototype.addSuite = function(suite) {
1807 | this.suites_.push(suite);
1808 | };
1809 |
1810 | jasmine.Runner.prototype.add = function(block) {
1811 | if (block instanceof jasmine.Suite) {
1812 | this.addSuite(block);
1813 | }
1814 | this.queue.add(block);
1815 | };
1816 |
1817 | jasmine.Runner.prototype.specs = function () {
1818 | var suites = this.suites();
1819 | var specs = [];
1820 | for (var i = 0; i < suites.length; i++) {
1821 | specs = specs.concat(suites[i].specs());
1822 | }
1823 | return specs;
1824 | };
1825 |
1826 | jasmine.Runner.prototype.suites = function() {
1827 | return this.suites_;
1828 | };
1829 |
1830 | jasmine.Runner.prototype.topLevelSuites = function() {
1831 | var topLevelSuites = [];
1832 | for (var i = 0; i < this.suites_.length; i++) {
1833 | if (!this.suites_[i].parentSuite) {
1834 | topLevelSuites.push(this.suites_[i]);
1835 | }
1836 | }
1837 | return topLevelSuites;
1838 | };
1839 |
1840 | jasmine.Runner.prototype.results = function() {
1841 | return this.queue.results();
1842 | };
1843 | /**
1844 | * Internal representation of a Jasmine specification, or test.
1845 | *
1846 | * @constructor
1847 | * @param {jasmine.Env} env
1848 | * @param {jasmine.Suite} suite
1849 | * @param {String} description
1850 | */
1851 | jasmine.Spec = function(env, suite, description) {
1852 | if (!env) {
1853 | throw new Error('jasmine.Env() required');
1854 | }
1855 | if (!suite) {
1856 | throw new Error('jasmine.Suite() required');
1857 | }
1858 | var spec = this;
1859 | spec.id = env.nextSpecId ? env.nextSpecId() : null;
1860 | spec.env = env;
1861 | spec.suite = suite;
1862 | spec.description = description;
1863 | spec.queue = new jasmine.Queue(env);
1864 |
1865 | spec.afterCallbacks = [];
1866 | spec.spies_ = [];
1867 |
1868 | spec.results_ = new jasmine.NestedResults();
1869 | spec.results_.description = description;
1870 | spec.matchersClass = null;
1871 | };
1872 |
1873 | jasmine.Spec.prototype.getFullName = function() {
1874 | return this.suite.getFullName() + ' ' + this.description + '.';
1875 | };
1876 |
1877 |
1878 | jasmine.Spec.prototype.results = function() {
1879 | return this.results_;
1880 | };
1881 |
1882 | /**
1883 | * All parameters are pretty-printed and concatenated together, then written to the spec's output.
1884 | *
1885 | * Be careful not to leave calls to <code>jasmine.log</code> in production code.
1886 | */
1887 | jasmine.Spec.prototype.log = function() {
1888 | return this.results_.log(arguments);
1889 | };
1890 |
1891 | jasmine.Spec.prototype.runs = function (func) {
1892 | var block = new jasmine.Block(this.env, func, this);
1893 | this.addToQueue(block);
1894 | return this;
1895 | };
1896 |
1897 | jasmine.Spec.prototype.addToQueue = function (block) {
1898 | if (this.queue.isRunning()) {
1899 | this.queue.insertNext(block);
1900 | } else {
1901 | this.queue.add(block);
1902 | }
1903 | };
1904 |
1905 | /**
1906 | * @param {jasmine.ExpectationResult} result
1907 | */
1908 | jasmine.Spec.prototype.addMatcherResult = function(result) {
1909 | this.results_.addResult(result);
1910 | };
1911 |
1912 | jasmine.Spec.prototype.expect = function(actual) {
1913 | var positive = new (this.getMatchersClass_())(this.env, actual, this);
1914 | positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
1915 | return positive;
1916 | };
1917 |
1918 | /**
1919 | * Waits a fixed time period before moving to the next block.
1920 | *
1921 | * @deprecated Use waitsFor() instead
1922 | * @param {Number} timeout milliseconds to wait
1923 | */
1924 | jasmine.Spec.prototype.waits = function(timeout) {
1925 | var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
1926 | this.addToQueue(waitsFunc);
1927 | return this;
1928 | };
1929 |
1930 | /**
1931 | * Waits for the latchFunction to return true before proceeding to the next block.
1932 | *
1933 | * @param {Function} latchFunction
1934 | * @param {String} optional_timeoutMessage
1935 | * @param {Number} optional_timeout
1936 | */
1937 | jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
1938 | var latchFunction_ = null;
1939 | var optional_timeoutMessage_ = null;
1940 | var optional_timeout_ = null;
1941 |
1942 | for (var i = 0; i < arguments.length; i++) {
1943 | var arg = arguments[i];
1944 | switch (typeof arg) {
1945 | case 'function':
1946 | latchFunction_ = arg;
1947 | break;
1948 | case 'string':
1949 | optional_timeoutMessage_ = arg;
1950 | break;
1951 | case 'number':
1952 | optional_timeout_ = arg;
1953 | break;
1954 | }
1955 | }
1956 |
1957 | var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
1958 | this.addToQueue(waitsForFunc);
1959 | return this;
1960 | };
1961 |
1962 | jasmine.Spec.prototype.fail = function (e) {
1963 | var expectationResult = new jasmine.ExpectationResult({
1964 | passed: false,
1965 | message: e ? jasmine.util.formatException(e) : 'Exception'
1966 | });
1967 | this.results_.addResult(expectationResult);
1968 | };
1969 |
1970 | jasmine.Spec.prototype.getMatchersClass_ = function() {
1971 | return this.matchersClass || this.env.matchersClass;
1972 | };
1973 |
1974 | jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
1975 | var parent = this.getMatchersClass_();
1976 | var newMatchersClass = function() {
1977 | parent.apply(this, arguments);
1978 | };
1979 | jasmine.util.inherit(newMatchersClass, parent);
1980 | jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
1981 | this.matchersClass = newMatchersClass;
1982 | };
1983 |
1984 | jasmine.Spec.prototype.finishCallback = function() {
1985 | this.env.reporter.reportSpecResults(this);
1986 | };
1987 |
1988 | jasmine.Spec.prototype.finish = function(onComplete) {
1989 | this.removeAllSpies();
1990 | this.finishCallback();
1991 | if (onComplete) {
1992 | onComplete();
1993 | }
1994 | };
1995 |
1996 | jasmine.Spec.prototype.after = function(doAfter) {
1997 | if (this.queue.isRunning()) {
1998 | this.queue.add(new jasmine.Block(this.env, doAfter, this));
1999 | } else {
2000 | this.afterCallbacks.unshift(doAfter);
2001 | }
2002 | };
2003 |
2004 | jasmine.Spec.prototype.execute = function(onComplete) {
2005 | var spec = this;
2006 | if (!spec.env.specFilter(spec)) {
2007 | spec.results_.skipped = true;
2008 | spec.finish(onComplete);
2009 | return;
2010 | }
2011 |
2012 | this.env.reporter.reportSpecStarting(this);
2013 |
2014 | spec.env.currentSpec = spec;
2015 |
2016 | spec.addBeforesAndAftersToQueue();
2017 |
2018 | spec.queue.start(function () {
2019 | spec.finish(onComplete);
2020 | });
2021 | };
2022 |
2023 | jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
2024 | var runner = this.env.currentRunner();
2025 | var i;
2026 |
2027 | for (var suite = this.suite; suite; suite = suite.parentSuite) {
2028 | for (i = 0; i < suite.before_.length; i++) {
2029 | this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
2030 | }
2031 | }
2032 | for (i = 0; i < runner.before_.length; i++) {
2033 | this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
2034 | }
2035 | for (i = 0; i < this.afterCallbacks.length; i++) {
2036 | this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
2037 | }
2038 | for (suite = this.suite; suite; suite = suite.parentSuite) {
2039 | for (i = 0; i < suite.after_.length; i++) {
2040 | this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
2041 | }
2042 | }
2043 | for (i = 0; i < runner.after_.length; i++) {
2044 | this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
2045 | }
2046 | };
2047 |
2048 | jasmine.Spec.prototype.explodes = function() {
2049 | throw 'explodes function should not have been called';
2050 | };
2051 |
2052 | jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
2053 | if (obj == jasmine.undefined) {
2054 | throw "spyOn could not find an object to spy upon for " + methodName + "()";
2055 | }
2056 |
2057 | if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
2058 | throw methodName + '() method does not exist';
2059 | }
2060 |
2061 | if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
2062 | throw new Error(methodName + ' has already been spied upon');
2063 | }
2064 |
2065 | var spyObj = jasmine.createSpy(methodName);
2066 |
2067 | this.spies_.push(spyObj);
2068 | spyObj.baseObj = obj;
2069 | spyObj.methodName = methodName;
2070 | spyObj.originalValue = obj[methodName];
2071 |
2072 | obj[methodName] = spyObj;
2073 |
2074 | return spyObj;
2075 | };
2076 |
2077 | jasmine.Spec.prototype.removeAllSpies = function() {
2078 | for (var i = 0; i < this.spies_.length; i++) {
2079 | var spy = this.spies_[i];
2080 | spy.baseObj[spy.methodName] = spy.originalValue;
2081 | }
2082 | this.spies_ = [];
2083 | };
2084 |
2085 | /**
2086 | * Internal representation of a Jasmine suite.
2087 | *
2088 | * @constructor
2089 | * @param {jasmine.Env} env
2090 | * @param {String} description
2091 | * @param {Function} specDefinitions
2092 | * @param {jasmine.Suite} parentSuite
2093 | */
2094 | jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
2095 | var self = this;
2096 | self.id = env.nextSuiteId ? env.nextSuiteId() : null;
2097 | self.description = description;
2098 | self.queue = new jasmine.Queue(env);
2099 | self.parentSuite = parentSuite;
2100 | self.env = env;
2101 | self.before_ = [];
2102 | self.after_ = [];
2103 | self.children_ = [];
2104 | self.suites_ = [];
2105 | self.specs_ = [];
2106 | };
2107 |
2108 | jasmine.Suite.prototype.getFullName = function() {
2109 | var fullName = this.description;
2110 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
2111 | fullName = parentSuite.description + ' ' + fullName;
2112 | }
2113 | return fullName;
2114 | };
2115 |
2116 | jasmine.Suite.prototype.finish = function(onComplete) {
2117 | this.env.reporter.reportSuiteResults(this);
2118 | this.finished = true;
2119 | if (typeof(onComplete) == 'function') {
2120 | onComplete();
2121 | }
2122 | };
2123 |
2124 | jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
2125 | beforeEachFunction.typeName = 'beforeEach';
2126 | this.before_.unshift(beforeEachFunction);
2127 | };
2128 |
2129 | jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
2130 | afterEachFunction.typeName = 'afterEach';
2131 | this.after_.unshift(afterEachFunction);
2132 | };
2133 |
2134 | jasmine.Suite.prototype.results = function() {
2135 | return this.queue.results();
2136 | };
2137 |
2138 | jasmine.Suite.prototype.add = function(suiteOrSpec) {
2139 | this.children_.push(suiteOrSpec);
2140 | if (suiteOrSpec instanceof jasmine.Suite) {
2141 | this.suites_.push(suiteOrSpec);
2142 | this.env.currentRunner().addSuite(suiteOrSpec);
2143 | } else {
2144 | this.specs_.push(suiteOrSpec);
2145 | }
2146 | this.queue.add(suiteOrSpec);
2147 | };
2148 |
2149 | jasmine.Suite.prototype.specs = function() {
2150 | return this.specs_;
2151 | };
2152 |
2153 | jasmine.Suite.prototype.suites = function() {
2154 | return this.suites_;
2155 | };
2156 |
2157 | jasmine.Suite.prototype.children = function() {
2158 | return this.children_;
2159 | };
2160 |
2161 | jasmine.Suite.prototype.execute = function(onComplete) {
2162 | var self = this;
2163 | this.queue.start(function () {
2164 | self.finish(onComplete);
2165 | });
2166 | };
2167 | jasmine.WaitsBlock = function(env, timeout, spec) {
2168 | this.timeout = timeout;
2169 | jasmine.Block.call(this, env, null, spec);
2170 | };
2171 |
2172 | jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
2173 |
2174 | jasmine.WaitsBlock.prototype.execute = function (onComplete) {
2175 | this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
2176 | this.env.setTimeout(function () {
2177 | onComplete();
2178 | }, this.timeout);
2179 | };
2180 | /**
2181 | * A block which waits for some condition to become true, with timeout.
2182 | *
2183 | * @constructor
2184 | * @extends jasmine.Block
2185 | * @param {jasmine.Env} env The Jasmine environment.
2186 | * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
2187 | * @param {Function} latchFunction A function which returns true when the desired condition has been met.
2188 | * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
2189 | * @param {jasmine.Spec} spec The Jasmine spec.
2190 | */
2191 | jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
2192 | this.timeout = timeout || env.defaultTimeoutInterval;
2193 | this.latchFunction = latchFunction;
2194 | this.message = message;
2195 | this.totalTimeSpentWaitingForLatch = 0;
2196 | jasmine.Block.call(this, env, null, spec);
2197 | };
2198 | jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
2199 |
2200 | jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
2201 |
2202 | jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
2203 | this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
2204 | var latchFunctionResult;
2205 | try {
2206 | latchFunctionResult = this.latchFunction.apply(this.spec);
2207 | } catch (e) {
2208 | this.spec.fail(e);
2209 | onComplete();
2210 | return;
2211 | }
2212 |
2213 | if (latchFunctionResult) {
2214 | onComplete();
2215 | } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
2216 | var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
2217 | this.spec.fail({
2218 | name: 'timeout',
2219 | message: message
2220 | });
2221 |
2222 | this.abort = true;
2223 | onComplete();
2224 | } else {
2225 | this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
2226 | var self = this;
2227 | this.env.setTimeout(function() {
2228 | self.execute(onComplete);
2229 | }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
2230 | }
2231 | };
2232 | // Mock setTimeout, clearTimeout
2233 | // Contributed by Pivotal Computer Systems, www.pivotalsf.com
2234 |
2235 | jasmine.FakeTimer = function() {
2236 | this.reset();
2237 |
2238 | var self = this;
2239 | self.setTimeout = function(funcToCall, millis) {
2240 | self.timeoutsMade++;
2241 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
2242 | return self.timeoutsMade;
2243 | };
2244 |
2245 | self.setInterval = function(funcToCall, millis) {
2246 | self.timeoutsMade++;
2247 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
2248 | return self.timeoutsMade;
2249 | };
2250 |
2251 | self.clearTimeout = function(timeoutKey) {
2252 | self.scheduledFunctions[timeoutKey] = jasmine.undefined;
2253 | };
2254 |
2255 | self.clearInterval = function(timeoutKey) {
2256 | self.scheduledFunctions[timeoutKey] = jasmine.undefined;
2257 | };
2258 |
2259 | };
2260 |
2261 | jasmine.FakeTimer.prototype.reset = function() {
2262 | this.timeoutsMade = 0;
2263 | this.scheduledFunctions = {};
2264 | this.nowMillis = 0;
2265 | };
2266 |
2267 | jasmine.FakeTimer.prototype.tick = function(millis) {
2268 | var oldMillis = this.nowMillis;
2269 | var newMillis = oldMillis + millis;
2270 | this.runFunctionsWithinRange(oldMillis, newMillis);
2271 | this.nowMillis = newMillis;
2272 | };
2273 |
2274 | jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
2275 | var scheduledFunc;
2276 | var funcsToRun = [];
2277 | for (var timeoutKey in this.scheduledFunctions) {
2278 | scheduledFunc = this.scheduledFunctions[timeoutKey];
2279 | if (scheduledFunc != jasmine.undefined &&
2280 | scheduledFunc.runAtMillis >= oldMillis &&
2281 | scheduledFunc.runAtMillis <= nowMillis) {
2282 | funcsToRun.push(scheduledFunc);
2283 | this.scheduledFunctions[timeoutKey] = jasmine.undefined;
2284 | }
2285 | }
2286 |
2287 | if (funcsToRun.length > 0) {
2288 | funcsToRun.sort(function(a, b) {
2289 | return a.runAtMillis - b.runAtMillis;
2290 | });
2291 | for (var i = 0; i < funcsToRun.length; ++i) {
2292 | try {
2293 | var funcToRun = funcsToRun[i];
2294 | this.nowMillis = funcToRun.runAtMillis;
2295 | funcToRun.funcToCall();
2296 | if (funcToRun.recurring) {
2297 | this.scheduleFunction(funcToRun.timeoutKey,
2298 | funcToRun.funcToCall,
2299 | funcToRun.millis,
2300 | true);
2301 | }
2302 | } catch(e) {
2303 | }
2304 | }
2305 | this.runFunctionsWithinRange(oldMillis, nowMillis);
2306 | }
2307 | };
2308 |
2309 | jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
2310 | this.scheduledFunctions[timeoutKey] = {
2311 | runAtMillis: this.nowMillis + millis,
2312 | funcToCall: funcToCall,
2313 | recurring: recurring,
2314 | timeoutKey: timeoutKey,
2315 | millis: millis
2316 | };
2317 | };
2318 |
2319 | /**
2320 | * @namespace
2321 | */
2322 | jasmine.Clock = {
2323 | defaultFakeTimer: new jasmine.FakeTimer(),
2324 |
2325 | reset: function() {
2326 | jasmine.Clock.assertInstalled();
2327 | jasmine.Clock.defaultFakeTimer.reset();
2328 | },
2329 |
2330 | tick: function(millis) {
2331 | jasmine.Clock.assertInstalled();
2332 | jasmine.Clock.defaultFakeTimer.tick(millis);
2333 | },
2334 |
2335 | runFunctionsWithinRange: function(oldMillis, nowMillis) {
2336 | jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
2337 | },
2338 |
2339 | scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
2340 | jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
2341 | },
2342 |
2343 | useMock: function() {
2344 | if (!jasmine.Clock.isInstalled()) {
2345 | var spec = jasmine.getEnv().currentSpec;
2346 | spec.after(jasmine.Clock.uninstallMock);
2347 |
2348 | jasmine.Clock.installMock();
2349 | }
2350 | },
2351 |
2352 | installMock: function() {
2353 | jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
2354 | },
2355 |
2356 | uninstallMock: function() {
2357 | jasmine.Clock.assertInstalled();
2358 | jasmine.Clock.installed = jasmine.Clock.real;
2359 | },
2360 |
2361 | real: {
2362 | setTimeout: jasmine.getGlobal().setTimeout,
2363 | clearTimeout: jasmine.getGlobal().clearTimeout,
2364 | setInterval: jasmine.getGlobal().setInterval,
2365 | clearInterval: jasmine.getGlobal().clearInterval
2366 | },
2367 |
2368 | assertInstalled: function() {
2369 | if (!jasmine.Clock.isInstalled()) {
2370 | throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
2371 | }
2372 | },
2373 |
2374 | isInstalled: function() {
2375 | return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
2376 | },
2377 |
2378 | installed: null
2379 | };
2380 | jasmine.Clock.installed = jasmine.Clock.real;
2381 |
2382 | //else for IE support
2383 | jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
2384 | if (jasmine.Clock.installed.setTimeout.apply) {
2385 | return jasmine.Clock.installed.setTimeout.apply(this, arguments);
2386 | } else {
2387 | return jasmine.Clock.installed.setTimeout(funcToCall, millis);
2388 | }
2389 | };
2390 |
2391 | jasmine.getGlobal().setInterval = function(funcToCall, millis) {
2392 | if (jasmine.Clock.installed.setInterval.apply) {
2393 | return jasmine.Clock.installed.setInterval.apply(this, arguments);
2394 | } else {
2395 | return jasmine.Clock.installed.setInterval(funcToCall, millis);
2396 | }
2397 | };
2398 |
2399 | jasmine.getGlobal().clearTimeout = function(timeoutKey) {
2400 | if (jasmine.Clock.installed.clearTimeout.apply) {
2401 | return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
2402 | } else {
2403 | return jasmine.Clock.installed.clearTimeout(timeoutKey);
2404 | }
2405 | };
2406 |
2407 | jasmine.getGlobal().clearInterval = function(timeoutKey) {
2408 | if (jasmine.Clock.installed.clearTimeout.apply) {
2409 | return jasmine.Clock.installed.clearInterval.apply(this, arguments);
2410 | } else {
2411 | return jasmine.Clock.installed.clearInterval(timeoutKey);
2412 | }
2413 | };
2414 |
2415 |
2416 | jasmine.version_= {
2417 | "major": 1,
2418 | "minor": 0,
2419 | "build": "0.rc1",
2420 | "revision": 1282853377
2421 | };
2422 |
--------------------------------------------------------------------------------
/lib/jasmine/jskoans-jasmine-html.js:
--------------------------------------------------------------------------------
1 | JsKoansReporter = function(doc) {
2 | this.document = doc || document;
3 | this.suiteDivs = {};
4 | this.failedSpecs = 0;
5 |
6 | this.noOfSubjects = 0;
7 | this.failedSubjects = 0;
8 | };
9 |
10 | JsKoansReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
11 | var el = document.createElement(type);
12 |
13 | for (var i = 2; i < arguments.length; i++) {
14 | var child = arguments[i];
15 |
16 | if (typeof child === 'string') {
17 | el.appendChild(document.createTextNode(child));
18 | } else {
19 | if (child) { el.appendChild(child); }
20 | }
21 | }
22 |
23 | for (var attr in attrs) {
24 | if (attr == "className") {
25 | el[attr] = attrs[attr];
26 | } else {
27 | el.setAttribute(attr, attrs[attr]);
28 | }
29 | }
30 |
31 | return el;
32 | };
33 |
34 | JsKoansReporter.prototype.reportRunnerStarting = function(runner) {
35 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter show-passed' },
36 | this.createDom('h1', { }, "Javascript Koans"),
37 | this.runnerDiv = this.createDom('div', { className: 'runner running' },
38 | this.runnerMessageSpan = this.createDom('span', { classname: 'running' }, "Contemplating naval..."),
39 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
40 | );
41 |
42 | this.document.body.appendChild(this.outerDiv);
43 |
44 | var suites = runner.suites();
45 | for (var i = 0; i < suites.length; i++) {
46 | var suite = suites[i];
47 | var suiteDiv = this.createDom('div', { className: 'suite' },
48 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) },
49 | this.getSuiteDescription(suite)));
50 | this.suiteDivs[suite.id] = suiteDiv;
51 | var parentDiv = this.outerDiv;
52 | if (suite.parentSuite) {
53 | parentDiv = this.suiteDivs[suite.parentSuite.id];
54 | }
55 | parentDiv.appendChild(suiteDiv);
56 | }
57 |
58 | this.footerDiv = this.createDom('div', { className: 'banner' },
59 | this.createDom('div', { className: 'logo' },
60 | "Test runner: Jasmine",
61 | this.createDom('span', { className: 'version' }, runner.env.versionString()))
62 | );
63 |
64 | this.outerDiv.appendChild(this.footerDiv);
65 |
66 | this.startedAt = new Date();
67 | };
68 |
69 | JsKoansReporter.prototype.reportRunnerResults = function(runner) {
70 | var results = runner.results();
71 | var className = "progress";
72 | this.runnerDiv.setAttribute("class", className);
73 | //do it twice for IE
74 | this.runnerDiv.setAttribute("className", className);
75 | var specs = runner.specs();
76 | var specCount = 0;
77 | for (var i = 0; i < specs.length; i++) {
78 | if (this.specFilter(specs[i])) {
79 | specCount++;
80 | }
81 | }
82 |
83 | var enlightenmentMessage;
84 | if (this.failedSpecs === 0) {
85 | status = 'passed';
86 | enlightenmentMessage = "Enlightenment!";
87 | } else {
88 | status = 'failed';
89 | enlightenmentMessage = "You have not yet reached enlightenment...";
90 | }
91 |
92 | var suitesCount = runner.suites().length;
93 | var specsCount = runner.specs().length;
94 | var showPassed;
95 | var showAllFailed;
96 |
97 | var progress = this.createDom('div', {},
98 | this.createDom('div', { className: 'enlightenment-' + status }, enlightenmentMessage),
99 | this.createDom('div', { className: 'completion' },
100 | this.createDom('div', {},
101 | this.createDom('span', { className: 'key' }, "Subjects covered: "),
102 | this.createDom('span', { className: 'value' }, this.noOfSubjects - this.failedSubjects + "/" + this.noOfSubjects)
103 | ),
104 | this.createDom('div', {},
105 | this.createDom('span', { className: 'key' }, "Koans learned: "),
106 | this.createDom('span', { className: 'value' }, specsCount - this.failedSpecs + "/" + runner.specs().length)
107 | ),
108 | this.createDom('div', { className: 'options' },
109 | this.createDom('label', { "for": "__jsKoans_showPassed__" }, " Show passed koans"),
110 | showPassed = this.createDom('input', { id: "__jsKoans_showPassed__", type: 'checkbox', checked: '' }),
111 | this.createDom('label', { "for": "__jsKoans_showAllFailed__" }, " Look ahead"),
112 | showAllFailed = this.createDom('input', { id: "__jsKoans_showAllFailed__", type: 'checkbox' })
113 | )
114 | )
115 | );
116 | this.runnerMessageSpan.replaceChild(this.createDom('div', { className: 'description', href: '?'}, progress), this.runnerMessageSpan.firstChild);
117 |
118 | var self = this;
119 | showPassed.onchange = function(evt) {
120 | if (evt.target.checked) {
121 | self.outerDiv.className += ' show-passed';
122 | } else {
123 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
124 | }
125 | };
126 | showAllFailed.onchange = function(evt) {
127 | if (evt.target.checked) {
128 | self.outerDiv.className += ' show-skipped';
129 | } else {
130 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
131 | }
132 | };
133 | };
134 |
135 | JsKoansReporter.prototype.reportSuiteResults = function(suite) {
136 | var results = suite.results();
137 | var status = results.passed() ? 'passed' : 'failed';
138 | if (results.totalCount == 0 || this.failedSubjects > 0) {
139 | status += '-skipped';
140 | }
141 |
142 | if (suite.parentSuite == null) {
143 | this.noOfSubjects +=1;
144 | if (this.failedSpecs > 0) {
145 | this.failedSubjects += 1;
146 | }
147 | }
148 |
149 | this.suiteDivs[suite.id].className += " " + status;
150 | };
151 |
152 | JsKoansReporter.prototype.reportSpecStarting = function(spec) {
153 | };
154 |
155 | JsKoansReporter.prototype.reportSpecResults = function(spec) {
156 | var results = spec.results();
157 | var status = results.passed() ? 'passed' : 'failed';
158 | var skipStatus = status;
159 |
160 | if (results.skipped || this.failedSpecs > 0) {
161 | skipStatus += '-skipped';
162 | }
163 |
164 | var description;
165 | if ( !results.passed() ) {
166 | this.failedSpecs += 1;
167 |
168 | description = "It " + spec.description + ". It is damaging your karma."
169 | } else {
170 | description = "Knowing it " + spec.description + " has expanded your awareness."
171 | }
172 |
173 | var specDiv = this.createDom('div', { className: 'spec ' + skipStatus },
174 | this.createDom('a', { className: 'run_spec_' + status, href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "meditate again"),
175 | this.createDom('a', {
176 | className: 'description',
177 | href: '?spec=' + encodeURIComponent(spec.getFullName()),
178 | title: spec.getFullName()
179 | }, description));
180 |
181 | var resultItems = results.getItems();
182 | var messagesDiv = this.createDom('div', { className: 'messages'});
183 |
184 | for (var i = 0; i < resultItems.length; i++) {
185 | var result = resultItems[i];
186 |
187 | if (result.type == 'expect' && result.passed && !result.passed()) {
188 | messagesDiv.appendChild(
189 | this.createDom('div', { className: 'errorMessage' }, result.message)
190 | );
191 | messagesDiv.appendChild(
192 | this.createDom('div', { className: 'description' }, "Please meditate on the following code:")
193 | );
194 |
195 | if (result.trace.stack) {
196 | var lines = result.trace.stack.split('\n');
197 | var stack = lines[0];
198 | for (var i = 1; i < lines.length; i++) {
199 | if (lines[i].search('/koans/') != -1) {
200 | stack += '\n' + lines[i]
201 | }
202 | }
203 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, stack.trim()));
204 | }
205 |
206 | break;
207 | }
208 | }
209 | if (messagesDiv.childNodes.length > 0) {
210 | specDiv.appendChild(messagesDiv);
211 | }
212 |
213 | this.suiteDivs[spec.suite.id].appendChild(specDiv);
214 |
215 | };
216 |
217 | JsKoansReporter.prototype.log = function() {
218 | var console = jasmine.getGlobal().console;
219 | if (console && console.log) console.log.apply(console, arguments);
220 | };
221 |
222 | JsKoansReporter.prototype.getLocation = function() {
223 | return this.document.location;
224 | };
225 |
226 | JsKoansReporter.prototype.specFilter = function(spec) {
227 | var paramMap = {};
228 | var params = this.getLocation().search.substring(1).split('&');
229 | for (var i = 0; i < params.length; i++) {
230 | var p = params[i].split('=');
231 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
232 | }
233 |
234 | if (!paramMap["spec"]) return true;
235 | return spec.getFullName().indexOf(paramMap["spec"]) == 0;
236 | };
237 |
238 | JsKoansReporter.prototype.getSuiteDescription = function(suite) {
239 | if (null === suite.parentSuite) {
240 | return "Thinking " + suite.description;
241 | } else {
242 | return "Considering " + suite.description;
243 | }
244 | };
245 |
246 |
--------------------------------------------------------------------------------
/lib/jasmine/jskoans-jasmine.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
3 | }
4 |
5 | h1 {
6 | text-align: center;
7 | font-weight: bold;
8 | color: #78f;
9 | }
10 |
11 | .jasmine_reporter a:visited, .jasmine_reporter a {
12 | color: #303;
13 | }
14 |
15 | .jasmine_reporter a:hover, .jasmine_reporter a:active {
16 | color: blue;
17 | }
18 |
19 | .run_spec_failed, .run_spec_skipped {
20 | float:right;
21 | padding-right: 5px;
22 | font-size: .8em;
23 | text-decoration: none;
24 | }
25 |
26 | .run_spec_passed {
27 | display: none;
28 | }
29 |
30 | .jasmine_reporter {
31 | margin: 0 5px;
32 | }
33 |
34 | .banner {
35 | color: #303;
36 | background-color: #fef;
37 | padding: 5px;
38 | font-size: 0.6em;
39 | }
40 |
41 | .logo {
42 | }
43 |
44 | .logo .version {
45 | font-size: 0.9em;
46 | padding-left: 1em;
47 | }
48 |
49 | .runner.running {
50 | background-color: yellow;
51 | }
52 |
53 | .options {
54 | padding-top: 0.5px;
55 | }
56 |
57 | .suite {
58 | border: 1px outset gray;
59 | margin: 5px 0;
60 | padding-left: 1em;
61 | }
62 |
63 | .suite .suite {
64 | margin: 5px;
65 | }
66 |
67 | .suite.passed, .suite.passed-skipped {
68 | background-color: #dfd;
69 | }
70 |
71 | .suite.failed, .suite.failed-skipped {
72 | background-color: #fdd;
73 | }
74 |
75 | .spec {
76 | margin: 5px;
77 | padding-left: 1em;
78 | clear: both;
79 | }
80 |
81 | .spec.failed, .spec.passed, .spec.passed-skipped, .spec.failed-skipped {
82 | padding-bottom: 5px;
83 | border: 1px solid gray;
84 | }
85 |
86 | .spec.failed, .spec.failed-skipped {
87 | background-color: #fbb;
88 | border-color: red;
89 | }
90 |
91 | .spec.passed, .spec.passed-skipped {
92 | background-color: #bfb;
93 | border-color: green;
94 | }
95 |
96 | .messages {
97 | border-left: 1px;
98 | }
99 |
100 | .errorMessage {
101 | font-family: "Menlo", "Monaco", "Andale Mono", "Courier New", "Courier", sans-serif;
102 | color: #055;
103 | white-space: pre;
104 | padding: .2em 1em;
105 | margin-left: 10px;
106 | margin-right: 5px;
107 | margin-bottom: 10px;
108 | background: #eef;
109 | }
110 |
111 | .meditate {
112 | padding-top: 1em;
113 | }
114 |
115 | .passed {
116 | background-color: #cfc;
117 | display: none;
118 | }
119 |
120 | .failed {
121 | background-color: #fbb;
122 | display: block;
123 | }
124 |
125 | .passed-skipped, .failed-skipped {
126 | display: none;
127 | }
128 |
129 | .resultMessage span.result {
130 | display: block;
131 | line-height: 2em;
132 | color: black;
133 | }
134 |
135 | .resultMessage .mismatch {
136 | color: black;
137 | }
138 |
139 | .stackTrace {
140 | white-space: pre;
141 | font-size: .8em;
142 | margin-left: 10px;
143 | margin-right: 5px;
144 | max-height: 5em;
145 | overflow: auto;
146 | border: 1px inset red;
147 | padding: 1em;
148 | color: #eef;
149 | background: #000;
150 | }
151 |
152 | .finished-at {
153 | padding-left: 1em;
154 | font-size: .6em;
155 | }
156 |
157 | .show-skipped .passed-skipped {
158 | display: block;
159 | }
160 |
161 | .show-skipped .failed-skipped {
162 | display: block;
163 | }
164 |
165 | .show-passed .passed {
166 | display: block;
167 | }
168 | .show-failed .failed {
169 | display: block;
170 | }
171 |
172 | #jasmine_content {
173 | position:fixed;
174 | right: 100%;
175 | }
176 |
177 | .runner {
178 | border: 1px solid gray;
179 | display: block;
180 | margin: 5px 0;
181 | padding: 2px 0 2px 10px;
182 | }
183 |
184 | .progress {
185 | }
186 |
187 | .completion {
188 | font-size: .9em;
189 | padding-top: 10px;
190 | }
191 |
192 | .key {
193 | color: #077;
194 | }
195 |
196 | .value {
197 | color: #444;
198 | }
199 |
200 | .description {
201 | text-decoration: none;
202 | }
203 |
204 | .enlightenment-passed {
205 | color: #990;
206 | font-weight: bold;
207 | }
208 | .enlightenment-failed {
209 | color: #550;
210 | }
211 |
--------------------------------------------------------------------------------
/lib/jsTestDriver/JsTestDriver.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrdavidlaing/javascript-koans/1593981b62a63734f70ac9a959e850680c93aac8/lib/jsTestDriver/JsTestDriver.jar
--------------------------------------------------------------------------------
/lib/jsTestDriver/capture_browser.sh:
--------------------------------------------------------------------------------
1 | java -jar JsTestDriver.jar --port 42442 --runnerMode DEBUG --browser /Applications/Firefox-3.app/Contents/MacOS/firefox-bin
--------------------------------------------------------------------------------
/lib/jsTestDriver/jsTestDriver.conf:
--------------------------------------------------------------------------------
1 | server: http://localhost:42442
2 |
3 | load:
4 | - src/dojo-release-1.5.0/dojo/dojo.js
5 | - src/dojo-release-1.5.0/dojox/lang/functional/*.js
6 | - src/*.js
7 |
8 | - src-test/PathToEnlightenment.js
9 | - src-test/AboutAsserts.js
10 | - src-test/AboutArrays.js
11 | - src-test/AboutFunctions.js
12 | - src-test/AboutObjects.js
13 | - src-test/AboutMutability.js
14 | - src-test/AboutLambda.js
15 | - src-test/AboutHigherOrderFunctions.js
16 | - src-test/ApplyingWhatWeveLearnt.js
17 |
--------------------------------------------------------------------------------
/lib/jsTestDriver/run_all_koans.sh:
--------------------------------------------------------------------------------
1 | java -jar JsTestDriver.jar --reset --tests all
--------------------------------------------------------------------------------
/lib/underscore-min.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.1.6
2 | // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Underscore is freely distributable under the MIT license.
4 | // Portions of Underscore are inspired or borrowed from Prototype,
5 | // Oliver Steele's Functional, and John Resig's Micro-Templating.
6 | // For all details and documentation:
7 | // http://documentcloud.github.com/underscore
8 | (function(){var p=this,C=p._,m={},i=Array.prototype,n=Object.prototype,f=i.slice,D=i.unshift,E=n.toString,l=n.hasOwnProperty,s=i.forEach,t=i.map,u=i.reduce,v=i.reduceRight,w=i.filter,x=i.every,y=i.some,o=i.indexOf,z=i.lastIndexOf;n=Array.isArray;var F=Object.keys,q=Function.prototype.bind,b=function(a){return new j(a)};typeof module!=="undefined"&&module.exports?(module.exports=b,b._=b):p._=b;b.VERSION="1.1.6";var h=b.each=b.forEach=function(a,c,d){if(a!=null)if(s&&a.forEach===s)a.forEach(c,d);else if(b.isNumber(a.length))for(var e=
9 | 0,k=a.length;e<k;e++){if(c.call(d,a[e],e,a)===m)break}else for(e in a)if(l.call(a,e)&&c.call(d,a[e],e,a)===m)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(t&&a.map===t)return a.map(c,b);h(a,function(a,g,G){e[e.length]=c.call(b,a,g,G)});return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var k=d!==void 0;a==null&&(a=[]);if(u&&a.reduce===u)return e&&(c=b.bind(c,e)),k?a.reduce(c,d):a.reduce(c);h(a,function(a,b,f){!k&&b===0?(d=a,k=!0):d=c.call(e,d,a,b,f)});if(!k)throw new TypeError("Reduce of empty array with no initial value");
10 | return d};b.reduceRight=b.foldr=function(a,c,d,e){a==null&&(a=[]);if(v&&a.reduceRight===v)return e&&(c=b.bind(c,e)),d!==void 0?a.reduceRight(c,d):a.reduceRight(c);a=(b.isArray(a)?a.slice():b.toArray(a)).reverse();return b.reduce(a,c,d,e)};b.find=b.detect=function(a,c,b){var e;A(a,function(a,g,f){if(c.call(b,a,g,f))return e=a,!0});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.filter===w)return a.filter(c,b);h(a,function(a,g,f){c.call(b,a,g,f)&&(e[e.length]=a)});return e};
11 | b.reject=function(a,c,b){var e=[];if(a==null)return e;h(a,function(a,g,f){c.call(b,a,g,f)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=!0;if(a==null)return e;if(x&&a.every===x)return a.every(c,b);h(a,function(a,g,f){if(!(e=e&&c.call(b,a,g,f)))return m});return e};var A=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=!1;if(a==null)return e;if(y&&a.some===y)return a.some(c,d);h(a,function(a,b,f){if(e=c.call(d,a,b,f))return m});return e};b.include=b.contains=function(a,c){var b=
12 | !1;if(a==null)return b;if(o&&a.indexOf===o)return a.indexOf(c)!=-1;A(a,function(a){if(b=a===c)return!0});return b};b.invoke=function(a,c){var d=f.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,
13 | c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;b<e.computed&&(e={value:a,computed:b})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,f){return{value:a,criteria:c.call(d,a,b,f)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=
14 | function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return a;if(b.isArguments(a))return f.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?f.call(a,0,b):a[0]};b.rest=b.tail=function(a,b,d){return f.call(a,b==null||d?1:b)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a){return b.reduce(a,function(a,d){if(b.isArray(d))return a.concat(b.flatten(d));
15 | a[a.length]=d;return a},[])};b.without=function(a){var c=f.call(arguments,1);return b.filter(a,function(a){return!b.include(c,a)})};b.uniq=b.unique=function(a,c){return b.reduce(a,function(a,e,f){if(0==f||(c===!0?b.last(a)!=e:!b.include(a,e)))a[a.length]=e;return a},[])};b.intersect=function(a){var c=f.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.zip=function(){for(var a=f.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),
16 | e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(o&&a.indexOf===o)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(z&&a.lastIndexOf===z)return a.lastIndexOf(b);for(var d=a.length;d--;)if(a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);d=arguments[2]||1;for(var e=Math.max(Math.ceil((b-a)/
17 | d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};b.bind=function(a,b){if(a.bind===q&&q)return q.apply(a,f.call(arguments,1));var d=f.call(arguments,2);return function(){return a.apply(b,d.concat(f.call(arguments)))}};b.bindAll=function(a){var c=f.call(arguments,1);c.length==0&&(c=b.functions(a));h(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return l.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=
18 | function(a,b){var d=f.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(f.call(arguments,1)))};var B=function(a,b,d){var e;return function(){var f=this,g=arguments,h=function(){e=null;a.apply(f,g)};d&&clearTimeout(e);if(d||!e)e=setTimeout(h,b)}};b.throttle=function(a,b){return B(a,b,!1)};b.debounce=function(a,b){return B(a,b,!0)};b.once=function(a){var b=!1,d;return function(){if(b)return d;b=!0;return d=a.apply(this,arguments)}};
19 | b.wrap=function(a,b){return function(){var d=[a].concat(f.call(arguments));return b.apply(this,d)}};b.compose=function(){var a=f.call(arguments);return function(){for(var b=f.call(arguments),d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}};b.keys=F||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)l.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,
20 | b.identity)};b.functions=b.methods=function(a){return b.filter(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a){h(f.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){h(f.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,c){if(a===c)return!0;var d=typeof a;if(d!=
21 | typeof c)return!1;if(a==c)return!0;if(!a&&c||a&&!c)return!1;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return!1;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return!1;if(a.length&&a.length!==c.length)return!1;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return!1;
22 | for(var f in a)if(!(f in c)||!b.isEqual(a[f],c[f]))return!1;return!0};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(l.call(a,c))return!1;return!0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=n||function(a){return E.call(a)==="[object Array]"};b.isArguments=function(a){return!(!a||!l.call(a,"callee"))};b.isFunction=function(a){return!(!a||!a.constructor||!a.call||!a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};
23 | b.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===!0||a===!1};b.isDate=function(a){return!(!a||!a.getTimezoneOffset||!a.setUTCFullYear)};b.isRegExp=function(a){return!(!a||!a.test||!a.exec||!(a.ignoreCase||a.ignoreCase===!1))};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){p._=C;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=
24 | 0;e<a;e++)b.call(d,e)};b.mixin=function(a){h(b.functions(a),function(c){H(c,b[c]=a[c])})};var I=0;b.uniqueId=function(a){var b=I++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||
25 | null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return c?d(c):d};var j=function(a){this._wrapped=a};b.prototype=j.prototype;var r=function(a,c){return c?b(a).chain():a},H=function(a,c){j.prototype[a]=function(){var a=f.call(arguments);D.call(a,this._wrapped);return r(c.apply(b,a),this._chain)}};b.mixin(b);h(["pop","push","reverse","shift","sort",
26 | "splice","unshift"],function(a){var b=i[a];j.prototype[a]=function(){b.apply(this._wrapped,arguments);return r(this._wrapped,this._chain)}});h(["concat","join","slice"],function(a){var b=i[a];j.prototype[a]=function(){return r(b.apply(this._wrapped,arguments),this._chain)}});j.prototype.chain=function(){this._chain=!0;return this};j.prototype.value=function(){return this._wrapped}})();
27 |
--------------------------------------------------------------------------------