├── jasmine-core ├── spec ├── version.rb ├── jasmine.css ├── json2.js ├── jasmine-html.js └── jasmine.js ├── .gitignore ├── specs ├── SpecHelper.js ├── NumberToPercentageSpec.js ├── NumberToPhoneSpec.js ├── NumberWithDelimiterSpec.js ├── NumberToHumanSizeSpec.js ├── NumberWithPrecisionSpec.js ├── NumberToCurrencySpec.js ├── NumberToHumanSpec.js └── NumberToRoundedSpec.js ├── package.json ├── LICENSE.md ├── index.html ├── README.md ├── src └── number_helpers.coffee └── lib └── number_helpers.js /jasmine-core/spec: -------------------------------------------------------------------------------- 1 | ../../spec -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules 3 | -------------------------------------------------------------------------------- /jasmine-core/version.rb: -------------------------------------------------------------------------------- 1 | module Jasmine 2 | module Core 3 | VERSION = "1.2.0" 4 | end 5 | end 6 | 7 | -------------------------------------------------------------------------------- /specs/SpecHelper.js: -------------------------------------------------------------------------------- 1 | // THIS IS FOR jasmine-node 2 | // DO NOT include this in the non node jasmine spec runner 3 | 4 | NumberHelpers = require("../lib/number_helpers") 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "number_helpers", 3 | "version": "0.1.2", 4 | "description": "Rails like number helpers in JS", 5 | "author": { 6 | "name": "Christopher Altman", 7 | "email": "caltman@emcien.com" 8 | }, 9 | "main": "./lib/number_helpers.js", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/emcien/number-helpers-coffeescript.git" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /specs/NumberToPercentageSpec.js: -------------------------------------------------------------------------------- 1 | describe("Number To Percentage", function() { 2 | it('Defaults', function() { 3 | var out = NumberHelpers.number_to_percentage(100); 4 | expect(out).toBe('100.000%'); 5 | }); 6 | 7 | it('Two Digit Default', function() { 8 | var out = NumberHelpers.number_to_percentage(98); 9 | expect(out).toBe('98.000%'); 10 | }); 11 | 12 | it('With Percision', function() { 13 | var out = NumberHelpers.number_to_percentage(100, {precision: 0}); 14 | expect(out).toBe('100%'); 15 | }); 16 | 17 | it('Separator and Delimiter', function() { 18 | var out = NumberHelpers.number_to_percentage(1000, {delimiter: '.', separator: ','}); 19 | expect(out).toBe('1.000,000%'); 20 | }); 21 | 22 | it('Precision', function() { 23 | var out = NumberHelpers.number_to_percentage(302.24398923423, {precision: 5}); 24 | expect(out).toBe('302.24399%'); 25 | }); 26 | 27 | it('Non Digit', function() { 28 | var out = NumberHelpers.number_to_percentage('98a'); 29 | expect(out).toBe('98a%'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Emcien All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /specs/NumberToPhoneSpec.js: -------------------------------------------------------------------------------- 1 | describe("Number To Phone", function() { 2 | it('7 digits', function() { 3 | var out = NumberHelpers.number_to_phone(5551234); 4 | expect(out).toBe('555-1234'); 5 | }); 6 | it('10 digits', function() { 7 | var out = NumberHelpers.number_to_phone(1235551234); 8 | expect(out).toBe('123-555-1234'); 9 | }); 10 | it('Area Code', function() { 11 | var out = NumberHelpers.number_to_phone(1235551234, {area_code: true}); 12 | expect(out).toBe('(123) 555-1234'); 13 | }); 14 | it('Delimiter', function() { 15 | var out = NumberHelpers.number_to_phone(1235551234, {delimiter: ' '}); 16 | expect(out).toBe('123 555 1234'); 17 | }); 18 | it('Extension', function() { 19 | var out = NumberHelpers.number_to_phone(1235551234, {area_code: true, extension: 555}); 20 | expect(out).toBe('(123) 555-1234 x 555'); 21 | }); 22 | it('Country Code', function() { 23 | var out = NumberHelpers.number_to_phone(1235551234, {country_code: 1}); 24 | expect(out).toBe('+1-123-555-1234'); 25 | }); 26 | it('NaN', function() { 27 | var out = NumberHelpers.number_to_phone('123a456'); 28 | expect(out).toBe('123a456'); 29 | }); 30 | it('Complex: +1.123.555.1234 x 1343', function() { 31 | var out = NumberHelpers.number_to_phone(1235551234, {country_code: 1, extension: 1343, delimiter: '.'}); 32 | expect(out).toBe('+1.123.555.1234 x 1343'); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /specs/NumberWithDelimiterSpec.js: -------------------------------------------------------------------------------- 1 | describe('Number With Delimiter', function() { 2 | it('Numeric Type Value', function() { 3 | var out = NumberHelpers.number_with_delimiter(12345678); 4 | expect(out).toBe('12,345,678'); 5 | }); 6 | it('String Type Value', function() { 7 | var out = NumberHelpers.number_with_delimiter('123456'); 8 | expect(out).toBe('123,456'); 9 | }); 10 | it('Decimal Value', function() { 11 | var out = NumberHelpers.number_with_delimiter(12345678.05); 12 | expect(out).toBe('12,345,678.05'); 13 | }); 14 | it('Delimiter As Period', function() { 15 | var out = NumberHelpers.number_with_delimiter(12345678, {delimiter:'.'}); 16 | expect(out).toBe('12.345.678'); 17 | }); 18 | it('Delimiter As Comma', function() { 19 | var out = NumberHelpers.number_with_delimiter(12345678, {delimiter:','}); 20 | expect(out).toBe('12,345,678'); 21 | }); 22 | it('Seperatar as Space', function() { 23 | var out = NumberHelpers.number_with_delimiter(12345678.05, {separator:' '}); 24 | expect(out).toBe('12,345,678 05'); 25 | }); 26 | it('Non-numeric Value', function() { 27 | var out = NumberHelpers.number_with_delimiter('112a'); 28 | expect(out).toBe('112a'); 29 | }); 30 | it('Delimiter and Separator Combination', function() { 31 | var out = NumberHelpers.number_with_delimiter(98765432.98, {delimiter:' ',separator:','}); 32 | expect(out).toBe('98 765 432,98'); 33 | }); 34 | }); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Emcien NumbersHelpers Spec Runner 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /specs/NumberToHumanSizeSpec.js: -------------------------------------------------------------------------------- 1 | describe("Number To Human Size", function() { 2 | it('Bytes', function() { 3 | var out = NumberHelpers.number_to_human_size(123); 4 | expect(out).toBe('123 Bytes'); 5 | }); 6 | it('1.21 KB', function() { 7 | var out = NumberHelpers.number_to_human_size(1234); 8 | expect(out).toBe('1.21 KB'); 9 | }); 10 | it('12.1 KB', function() { 11 | var out = NumberHelpers.number_to_human_size(12345); 12 | expect(out).toBe('12.1 KB'); 13 | }); 14 | it('1.18 MB', function() { 15 | var out = NumberHelpers.number_to_human_size(1234567); 16 | expect(out).toBe('1.18 MB'); 17 | }); 18 | it('1.15 GB', function() { 19 | var out = NumberHelpers.number_to_human_size(1234567890); 20 | expect(out).toBe('1.15 GB'); 21 | }); 22 | it('1.12 TB', function() { 23 | var out = NumberHelpers.number_to_human_size(1234567890123); 24 | expect(out).toBe('1.12 TB'); 25 | }); 26 | it('1.2 MB, Precision 2', function() { 27 | var out = NumberHelpers.number_to_human_size(1234567, {precision: 2}); 28 | expect(out).toBe('1.2 MB'); 29 | }); 30 | it('470 KB, Precision 2', function() { 31 | var out = NumberHelpers.number_to_human_size(483989, {precision: 2}); 32 | expect(out).toBe('470 KB'); 33 | }); 34 | it('1,2 MB, Precision 2, Separator ,', function() { 35 | var out = NumberHelpers.number_to_human_size(1234567, {precision: 2, separator: ','}); 36 | expect(out).toBe('1,2 MB'); 37 | }); 38 | it('Precision 5', function() { 39 | var out = NumberHelpers.number_to_human_size(1234567890123, {precision: 5}); 40 | expect(out).toBe('1.1228 TB'); 41 | }); 42 | it('500 MB, Precision 5', function() { 43 | var out = NumberHelpers.number_to_human_size(524288000, {precision: 5, strip_insignificant_zeros: true}); 44 | expect(out).toBe('500 MB'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /specs/NumberWithPrecisionSpec.js: -------------------------------------------------------------------------------- 1 | describe("Number With Precision", function() { 2 | it('Defaults', function() { 3 | var out = NumberHelpers.number_with_precision(111.2345); 4 | expect(out).toBe('111.235'); 5 | }); 6 | it('Precision: 2', function() { 7 | var out = NumberHelpers.number_with_precision(111.2345, {precision: 2}); 8 | expect(out).toBe('111.23'); 9 | }); 10 | it('Precision: 5', function() { 11 | var out = NumberHelpers.number_with_precision(13, {precision: 5}); 12 | expect(out).toBe('13.00000'); 13 | }); 14 | it('Precision: 0', function() { 15 | var out = NumberHelpers.number_with_precision(389.32314, {precision: 0}); 16 | expect(out).toBe('389'); 17 | }); 18 | it('Significant: 0', function() { 19 | var out = NumberHelpers.number_with_precision(111.2345, {significant: true}); 20 | expect(out).toBe('111'); 21 | }); 22 | it('Significant: 1', function() { 23 | var out = NumberHelpers.number_with_precision(111.2345, {significant: true, precision: 1}); 24 | expect(out).toBe('100'); 25 | }); 26 | it('Significant: 4', function() { 27 | var out = NumberHelpers.number_with_precision(389.32314, {significant: true, precision: 4}); 28 | expect(out).toBe('389.3'); 29 | }); 30 | it('Significant: 5', function() { 31 | var out = NumberHelpers.number_with_precision(13, {significant: true, precision: 5}); 32 | expect(out).toBe('13.00000'); 33 | }); 34 | it('Significant: 6', function() { 35 | var out = NumberHelpers.number_with_precision(13.00000004, {significant: true, precision: 6}); 36 | expect(out).toBe('13.000000'); 37 | }); 38 | it('Precision: 2, Seperator: ,, Delimiter: .', function() { 39 | var out = NumberHelpers.number_with_precision(1111.2345, {separator: ',', precision: 2, delimiter: '.'}); 40 | expect(out).toBe('1.111,23'); 41 | }); 42 | it('strip_insignificant_zeros', function() { 43 | var out = NumberHelpers.number_with_precision(13.0, {significant: true, precision: 5, strip_insignificant_zeros: true}); 44 | expect(out).toBe('13'); 45 | }); 46 | it('leaves_significant_zeros', function() { 47 | var out = NumberHelpers.number_with_precision(130.0, {significant: true, precision: 5, strip_insignificant_zeros: true}); 48 | expect(out).toBe('130'); 49 | }); 50 | it('strips out insignificant zeroes after nonzero digits', function() { 51 | var out = NumberHelpers.number_with_precision(13.01000, {significant: true, precision: 5, strip_insignificant_zeros: true}); 52 | expect(out).toBe('13.01'); 53 | }); 54 | it('can strip insignificant decimal parts if requested', function() { 55 | var a = NumberHelpers.number_with_precision(13.010, { strip_empty_fractional_parts: true }); 56 | expect(a).toBe('13.010'); 57 | 58 | var b = NumberHelpers.number_with_precision(13.000, { strip_empty_fractional_parts: true }); 59 | expect(b).toBe('13'); 60 | 61 | var c = NumberHelpers.number_with_precision(13.000, { strip_empty_fractional_parts: true, precision: 4 }); 62 | expect(c).toBe('13'); 63 | 64 | var d = NumberHelpers.number_with_precision(13.000001, { strip_empty_fractional_parts: true, precision: 4 }); 65 | expect(d).toBe('13'); 66 | 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /specs/NumberToCurrencySpec.js: -------------------------------------------------------------------------------- 1 | // number_to_currency(-1234567890.50, :negative_format => "(%u%n)") # => ($1,234,567,890.50) 2 | // number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u") # => 1234567890,50 £ 3 | 4 | describe("Number To Currency", function() { 5 | it('Defaults', function() { 6 | var out = NumberHelpers.number_to_currency(1234567890.50); 7 | expect(out).toBe('$1,234,567,890.50'); 8 | }); 9 | it('String', function() { 10 | var out = NumberHelpers.number_to_currency('1234567890.50'); 11 | expect(out).toBe('$1,234,567,890.50'); 12 | }); 13 | it('Negative number', function() { 14 | var out = NumberHelpers.number_to_currency(-2.99, {precision: 0}); 15 | expect(out).toBe('-$3'); 16 | }); 17 | it('Negative number with precision rounding', function() { 18 | var out = NumberHelpers.number_to_currency(-2.999, {precision: 2}); 19 | expect(out).toBe('-$3.00'); 20 | }); 21 | it('Negative number with precision', function() { 22 | var out = NumberHelpers.number_to_currency(-2.503, {precision: 2}); 23 | expect(out).toBe('-$2.50'); 24 | }); 25 | it('Rounding Up', function() { 26 | var out = NumberHelpers.number_to_currency(1234567890.506); 27 | expect(out).toBe('$1,234,567,890.51'); 28 | }); 29 | it('Simple rounding Up With 9s Precision 0', function() { 30 | var out = NumberHelpers.number_to_currency(2.99, {precision: 0}); 31 | expect(out).toBe('$3'); 32 | }); 33 | it('Simple Precision 2 with 9s', function() { 34 | var out = NumberHelpers.number_to_currency(2.99, {precision: 2}); 35 | expect(out).toBe('$2.99'); 36 | }); 37 | it('Rounding Up With 9s Precision 0', function() { 38 | var out = NumberHelpers.number_to_currency(1234567890.99, {precision: 0}); 39 | expect(out).toBe('$1,234,567,891'); 40 | }); 41 | it('Rounding Up With 9s Precision 2', function() { 42 | var out = NumberHelpers.number_to_currency(1234567890.99, {precision: 2}); 43 | expect(out).toBe('$1,234,567,890.99'); 44 | }); 45 | it('Precision 0', function() { 46 | var out = NumberHelpers.number_to_currency(1234567890.506, {precision: 0}); 47 | expect(out).toBe('$1,234,567,891'); 48 | }); 49 | it('Precision 1', function() { 50 | var out = NumberHelpers.number_to_currency(1234567890.506, {precision: 1}); 51 | expect(out).toBe('$1,234,567,890.5'); 52 | }); 53 | it('Precision 3', function() { 54 | var out = NumberHelpers.number_to_currency(1234567890.506, {precision: 3}); 55 | expect(out).toBe('$1,234,567,890.506'); 56 | }); 57 | it('Precision 4', function() { 58 | var out = NumberHelpers.number_to_currency(1234567890.506, {precision: 4}); 59 | expect(out).toBe('$1,234,567,890.5060'); 60 | }); 61 | it('String', function() { 62 | var out = NumberHelpers.number_to_currency('123a456'); 63 | expect(out).toBe('$123a456'); 64 | }); 65 | it('Unit, Separator, Delimiter Combo', function() { 66 | var out = NumberHelpers.number_to_currency(1234567890.50, {unit:"£", separator:",", delimiter:""}); 67 | expect(out).toBe('£1234567890,50'); 68 | }); 69 | it('allows "after" unit positioning', function() { 70 | var out = NumberHelpers.number_to_currency(123.507, {unit: " CZK", unit_position: "end", precision: 2}); 71 | expect(out).toBe('123.51 CZK'); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /specs/NumberToHumanSpec.js: -------------------------------------------------------------------------------- 1 | // Unsignificant zeros after the decimal separator are stripped out by default (set :strip_insignificant_zeros to false to change that): 2 | // number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion" 3 | 4 | describe("Number To Human", function() { 5 | it('Hundreds', function() { 6 | var out = NumberHelpers.number_to_human(123); 7 | expect(out).toBe('123'); 8 | }); 9 | it('Thousands', function() { 10 | var out = NumberHelpers.number_to_human(1234); 11 | expect(out).toBe('1.23 Thousand'); 12 | }); 13 | it('Thousands', function() { 14 | var out = NumberHelpers.number_to_human(1234); 15 | expect(out).toBe('1.23 Thousand'); 16 | }); 17 | it('Ten Thousands', function() { 18 | var out = NumberHelpers.number_to_human(12345); 19 | expect(out).toBe('12.3 Thousand'); 20 | }); 21 | it('Million', function() { 22 | var out = NumberHelpers.number_to_human(1234567); 23 | expect(out).toBe('1.23 Million'); 24 | }); 25 | it('Billion', function() { 26 | var out = NumberHelpers.number_to_human(1234567890); 27 | expect(out).toBe('1.23 Billion'); 28 | }); 29 | it('Tillion', function() { 30 | var out = NumberHelpers.number_to_human(1234567890123); 31 | expect(out).toBe('1.23 Trillion'); 32 | }); 33 | it('Quadrillion', function() { 34 | var out = NumberHelpers.number_to_human(1234567890123456); 35 | expect(out).toBe('1.23 Quadrillion'); 36 | }); 37 | it('1230 Quadrillion', function() { 38 | var out = NumberHelpers.number_to_human(1234567890123456789); 39 | expect(out).toBe('1230 Quadrillion'); 40 | }); 41 | it('Precision 2', function() { 42 | var out = NumberHelpers.number_to_human(489939, {precision: 2}); 43 | expect(out).toBe('490 Thousand'); 44 | }); 45 | it('Precision 4', function() { 46 | var out = NumberHelpers.number_to_human(489939, {precision: 4}); 47 | expect(out).toBe('489.9 Thousand'); 48 | }); 49 | it('Precision 4, Significant false', function() { 50 | var out = NumberHelpers.number_to_human(1234567, {precision: 4, significant: false}); 51 | expect(out).toBe('1.2346 Million'); 52 | }); 53 | it('Precision 1, Significant false, Seperator ,', function() { 54 | var out = NumberHelpers.number_to_human(1234567, {precision: 1, significant: false, separator: ','}); 55 | expect(out).toBe('1,2 Million'); 56 | }); 57 | it('500 Million', function() { 58 | var out = NumberHelpers.number_to_human(500000000, {precision: 5, strip_insignificant_zeros: true}); 59 | expect(out).toBe('500 Million'); 60 | }); 61 | it('500 Million', function() { 62 | var out = NumberHelpers.number_to_human(500000000, {precision: 5, strip_insignificant_zeros: true}); 63 | expect(out).toBe('500 Million'); 64 | }); 65 | it('Custom labels: K', function() { 66 | var out = NumberHelpers.number_to_human(1234, {space_label: false, labels:{thousand: 'K'}}); 67 | expect(out).toBe('1.23K') 68 | }); 69 | it('Custom labels: M', function() { 70 | var out = NumberHelpers.number_to_human(1234567, {labels:{million: 'M'}}); 71 | expect(out).toBe('1.23 M') 72 | }); 73 | it('Custom labels: B', function() { 74 | var out = NumberHelpers.number_to_human(1234567890, {space_label: false, labels:{billion: 'B'}}); 75 | expect(out).toBe('1.23B') 76 | }); 77 | it('Custom labels: T', function() { 78 | var out = NumberHelpers.number_to_human(1234567890123, {labels:{trillion: 'T'}}); 79 | expect(out).toBe('1.23 T') 80 | }); 81 | it('Custom labels: P', function() { 82 | var out = NumberHelpers.number_to_human(1234567890123456, {labels:{quadrillion: 'P'}}); 83 | expect(out).toBe('1.23 P') 84 | }); 85 | it('No label space', function() { 86 | var out = NumberHelpers.number_to_human(1234, {space_label: false}); 87 | expect(out).toBe('1.23Thousand'); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /specs/NumberToRoundedSpec.js: -------------------------------------------------------------------------------- 1 | // These tests were converted from Rails tests. Higher precision tests were ommitted as they are currently not supported in JavaScript 2 | 3 | describe("NumberToRounded", function() { 4 | it("should reconstitute exponentials", function() { 5 | var num = 34534534; 6 | var exps = num.toExponential().split('e'); 7 | expect(NumberHelpers.reconstitute_exponential(exps[0],exps[1])).toEqual(num+""); 8 | expect(NumberHelpers.reconstitute_exponential(num.toExponential())).toEqual(num+""); 9 | num = 0.000034544; 10 | expect(NumberHelpers.reconstitute_exponential(num.toExponential())).toEqual(num+""); 11 | num = 988.45 12 | expect(NumberHelpers.reconstitute_exponential(num.toExponential())).toEqual(num+""); 13 | num = 9080943000 14 | expect(NumberHelpers.reconstitute_exponential(num.toExponential())).toEqual(num+""); 15 | }); 16 | it("to_rounded", function() { 17 | expect(NumberHelpers.number_to_rounded(-111.2346 )).toEqual("-111.235"); 18 | expect(NumberHelpers.number_to_rounded(111.2346 )).toEqual("111.235"); 19 | expect(NumberHelpers.number_to_rounded(31.825 , { precision : 2 } )).toEqual("31.83"); 20 | expect(NumberHelpers.number_to_rounded(111.2346 , { precision : 2 } )).toEqual("111.23"); 21 | expect(NumberHelpers.number_to_rounded(111 , { precision : 2 } )).toEqual("111.00"); 22 | expect(NumberHelpers.number_to_rounded("111.2346" )).toEqual("111.235"); 23 | expect(NumberHelpers.number_to_rounded("31.825" , { precision : 2 } )).toEqual("31.83"); 24 | expect(NumberHelpers.number_to_rounded(32.6751 * 100.00, {precision: 0} )).toEqual("3268"); 25 | expect(NumberHelpers.number_to_rounded(111.50 , { precision : 0 } )).toEqual("112"); 26 | expect(NumberHelpers.number_to_rounded(1234567891.50 , { precision : 0 } )).toEqual("1234567892"); 27 | expect(NumberHelpers.number_to_rounded(0 , { precision : 0 } )).toEqual("0"); 28 | expect(NumberHelpers.number_to_rounded(0.001 , { precision : 5 } )).toEqual("0.00100"); 29 | expect(NumberHelpers.number_to_rounded(0.00111 , { precision : 3 } )).toEqual("0.001"); 30 | expect(NumberHelpers.number_to_rounded(9.995 , { precision : 2 } )).toEqual("10.00"); 31 | expect(NumberHelpers.number_to_rounded(10.995 , { precision : 2 } )).toEqual("11.00"); 32 | expect(NumberHelpers.number_to_rounded(-0.001 , { precision : 2 } )).toEqual("0.00"); 33 | 34 | }); 35 | it("to_rounded_with_custom_delimiter_and_separator", function() { 36 | expect(NumberHelpers.number_to_rounded(31.825 , { precision : 2, separator : ',' } )).toEqual( '31,83'); 37 | expect(NumberHelpers.number_to_rounded(1231.825 , { precision : 2, separator : ',', delimiter : '.' } )).toEqual('1.231,83'); 38 | }); 39 | it("to_rounded_with_significant_digits", function() { 40 | expect(NumberHelpers.number_to_rounded(123987 , { precision : 3, significant : true } )).toEqual("124000"); 41 | expect(NumberHelpers.number_to_rounded(123987876 , { precision : 2, significant : true } )).toEqual("120000000"); 42 | expect(NumberHelpers.number_to_rounded("43523" , { precision : 1, significant : true } )).toEqual("40000"); 43 | expect(NumberHelpers.number_to_rounded(9775 , { precision : 4, significant : true } )).toEqual("9775"); 44 | expect(NumberHelpers.number_to_rounded(5.3923 , { precision : 2, significant : true } )).toEqual("5.4"); 45 | expect(NumberHelpers.number_to_rounded(5.3923 , { precision : 1, significant : true } )).toEqual("5"); 46 | expect(NumberHelpers.number_to_rounded(1.232 , { precision : 1, significant : true } )).toEqual("1"); 47 | expect(NumberHelpers.number_to_rounded(7 , { precision : 1, significant : true } )).toEqual("7"); 48 | expect(NumberHelpers.number_to_rounded(1 , { precision : 1, significant : true } )).toEqual("1"); 49 | expect(NumberHelpers.number_to_rounded(52.7923 , { precision : 2, significant : true } )).toEqual("53"); 50 | expect(NumberHelpers.number_to_rounded(9775 , { precision : 6, significant : true } )).toEqual("9775.00"); 51 | expect(NumberHelpers.number_to_rounded(5.3929 , { precision : 7, significant : true } )).toEqual("5.392900"); 52 | expect(NumberHelpers.number_to_rounded(0 , { precision : 2, significant : true } )).toEqual("0.0"); 53 | expect(NumberHelpers.number_to_rounded(0 , { precision : 1, significant : true } )).toEqual("0"); 54 | expect(NumberHelpers.number_to_rounded(0.0001 , { precision : 1, significant : true } )).toEqual("0.0001"); 55 | expect(NumberHelpers.number_to_rounded(0.0001 , { precision : 3, significant : true } )).toEqual("0.000100"); 56 | expect(NumberHelpers.number_to_rounded(0.0001111 , { precision : 1, significant : true } )).toEqual("0.0001"); 57 | expect(NumberHelpers.number_to_rounded(9.995 , { precision : 3, significant : true } )).toEqual("10.0"); 58 | expect(NumberHelpers.number_to_rounded(9.994 , { precision : 3, significant : true } )).toEqual("9.99"); 59 | expect(NumberHelpers.number_to_rounded(10.995 , { precision : 3, significant : true } )).toEqual("11.0"); 60 | 61 | }); 62 | it("to_rounded_with_strip_insignificant_zeros", function() { 63 | expect(NumberHelpers.number_to_rounded(9775.43 , { precision : 4, strip_insignificant_zeros : true } )).toEqual("9775.43"); 64 | expect(NumberHelpers.number_to_rounded(9775.2 , { precision : 6, significant : true, strip_insignificant_zeros : true } )).toEqual("9775.2"); 65 | expect(NumberHelpers.number_to_rounded(0 , { precision : 6, significant : true, strip_insignificant_zeros : true } )).toEqual("0"); 66 | }); 67 | // Zero precision with significant is a mistake (would always return zero), 68 | // so we treat it as if significant was false (increases backwards compatibility for number_to_human_size) 69 | 70 | it("to_rounded_with_significant_true_and_zero_precision", function() { 71 | expect(NumberHelpers.number_to_rounded(123.987 , { precision : 0, significant : true } )).toEqual("124"); 72 | expect(NumberHelpers.number_to_rounded(12 , { precision : 0, significant : true } )).toEqual("12"); 73 | expect(NumberHelpers.number_to_rounded("12.3" , { precision : 0, significant : true } )).toEqual("12"); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /jasmine-core/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 24 | #HTMLReporter .runningAlert { background-color: #666666; } 25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 28 | #HTMLReporter .passingAlert { background-color: #a6b779; } 29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 30 | #HTMLReporter .failingAlert { background-color: #cf867e; } 31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 32 | #HTMLReporter .results { margin-top: 14px; } 33 | #HTMLReporter #details { display: none; } 34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 38 | #HTMLReporter.showDetails .summary { display: none; } 39 | #HTMLReporter.showDetails #details { display: block; } 40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 41 | #HTMLReporter .summary { margin-top: 14px; } 42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 45 | #HTMLReporter .description + .suite { margin-top: 0; } 46 | #HTMLReporter .suite { margin-top: 14px; } 47 | #HTMLReporter .suite a { color: #333333; } 48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 51 | #HTMLReporter .resultMessage span.result { display: block; } 52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 53 | 54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 61 | #TrivialReporter .runner.running { background-color: yellow; } 62 | #TrivialReporter .options { text-align: right; font-size: .8em; } 63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 64 | #TrivialReporter .suite .suite { margin: 5px; } 65 | #TrivialReporter .suite.passed { background-color: #dfd; } 66 | #TrivialReporter .suite.failed { background-color: #fdd; } 67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 71 | #TrivialReporter .spec.skipped { background-color: #bbb; } 72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 73 | #TrivialReporter .passed { background-color: #cfc; display: none; } 74 | #TrivialReporter .failed { background-color: #fbb; } 75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 77 | #TrivialReporter .resultMessage .mismatch { color: black; } 78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | (A better formatted version of this documentation can 2 | be viewed [here](http://emcien.github.com/number-helpers-coffeescript)) 3 | 4 | ## Background 5 | 6 | At Emcien, we found more of our application View Code migrating into a 7 | BackBone (or JS) rendered context. The useful Rails NumberHelpers 8 | we have grown accustomed to are not available. Out of necessity, we created a 9 | CoffeeScript implementation of the Rails NumberHelpers to help display 10 | numeric data in a fomatted fashion. 11 | 12 | ## Details 13 | 14 | This library uses the 15 | [Rails NumberHelpers Documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html) 16 | as the recipe for all method names, parameters, and test cases. For testing, 17 | the Jasmine Test Framework implements the same test specs as the Rails code. 18 | 19 | ## Testing 20 | 21 | * jasmine 22 | - Just open the index.html file in your browser 23 | * jasmine-node 24 | - Install jasmine-node `npm install -g jasmine-node` 25 | - Run: `$ jasmine-node specs` 26 | 27 | ### Methods: 28 | 29 | #### `number_to_currency` 30 | 31 | Formats a number into a currency string (e.g., $13.65). 32 | You can customize the format in the options hash. 33 | 34 | `NumberHelpers.number_to_currency(123, {option: value})` 35 | 36 | - `{ precision: 2 }` - Sets the level of precision (defaults to 2). 37 | - `{ unit: '$' }` - Sets the denomination of the currency (defaults to "$"). 38 | - `{ separator: '.' }` - Sets the separator between the units (defaults to "."). 39 | - `{ delimiter: ',' }` - Sets the thousands delimiter (defaults to ","). 40 | 41 | *Examples:* 42 | - `NumberHelpers.number_to_currency(1234567890.50)`: `$1,234,567,890.50` 43 | - `NumberHelpers.number_to_currency('1234567890.50')`: `$1,234,567,890.50` 44 | - `NumberHelpers.number_to_currency(1234567890.506)`: `$1,234,567,890.51` 45 | - `NumberHelpers.number_to_currency(1234567890.506, {precision: 0})`: `$1,234,567,890` 46 | - `NumberHelpers.number_to_currency(1234567890.506, {precision: 1})`: `$1,234,567,890.5` 47 | - `NumberHelpers.number_to_currency(1234567890.506, {precision: 3})`: `$1,234,567,890.506` 48 | - `NumberHelpers.number_to_currency(1234567890.506, {precision: 4})`: `$1,234,567,890.5060` 49 | - `NumberHelpers.number_to_currency('123a456')`: `$123a456` 50 | - `NumberHelpers.number_to_currency(1234567890.50, {unit:"£", separator:",", delimiter:""})`: `£1234567890,50` 51 | 52 | --------- 53 | 54 | #### `number_to_human` 55 | 56 | Pretty prints (formats and approximates) a number in a way it is more 57 | readable by humans (eg.: 1200000000 becomes “1.2 Billion”). This is useful 58 | for numbers that can get very large (and too hard to read). 59 | 60 | `NumberHelpers.number_to_human(123, {option: value})` 61 | 62 | - `{ precision: 3 }` - Sets the level of precision (defaults to 3). 63 | - `{ separator: '.' }` - Sets the separator between the units (defaults to "."). 64 | - `{ delimiter: ',' }` - Sets the thousands delimiter (defaults to ","). 65 | - `{ space_label: false }` - Omit the space between the value and the label. 66 | - If false, then 1234 would be output as 1.23Thousand 67 | - `{ strip_insignificant_zeros: true}` - If true removes insignificant zeros 68 | after the decimal separator (defaults to true) 69 | - `{ significant: true }` 70 | - If true, precision will be the # of significant_digits. 71 | - If false, the # of fractional digits (defaults to true) 72 | - `{ labels: { thousand: 'K', million: 'M' } }` 73 | - Customize the label used for the output value. 74 | - Possible keys are: 75 | - `thousand` 76 | - `million` 77 | - `billion` 78 | - `trillion` 79 | - `quadrillion` 80 | 81 | *Examples:* 82 | - `NumberHelpers.number_to_human(123)`: `123` 83 | - `NumberHelpers.number_to_human(1234)`: `1.23 Thousand` 84 | - `NumberHelpers.number_to_human(12345)`: `12.3 Thousand` 85 | - `NumberHelpers.number_to_human(1234567)`: `1.23 Million` 86 | - `NumberHelpers.number_to_human(1234567890)`: `1.23 Billion` 87 | - `NumberHelpers.number_to_human(1234567890123)`: `1.23 Trillion` 88 | - `NumberHelpers.number_to_human(1234567890123456)`: `1.23 Quadrillion` 89 | - `NumberHelpers.number_to_human(1234567890123456789)`: `1230 Quadrillion` 90 | - `NumberHelpers.number_to_human(489939, {precision: 2})`: `490 Thousand` 91 | - `NumberHelpers.number_to_human(489939, {precision: 4})`: `489.9 Thousand` 92 | - `NumberHelpers.number_to_human(1234567, {precision: 4, significant: false})`: `1.2346 Million` 93 | - `NumberHelpers.number_to_human(1234567, {precision: 1, significant: false, separator: ','})`: `1,2 Million` 94 | - `NumberHelpers.number_to_human(500000000, {precision: 5, strip_insignificant_zeros: true})`: `500 Million` 95 | 96 | ---------- 97 | 98 | ## `number_to_human_size` 99 | 100 | Formats the bytes in number into a more understandable representation 101 | (e.g., giving it 1500 yields 1.5 KB). This method is useful for reporting 102 | file sizes to users. You can customize the format in the options hash. 103 | 104 | `NumberHelpers.number_to_human_size(123, {option: value})` 105 | 106 | - `{ precision: 3 }` - Sets the level of precision (defaults to 3). 107 | - `{ separator: '.' }` - Sets the separator between the units (defaults to "."). 108 | - `{ delimiter: ',' }` - Sets the thousands delimiter (defaults to ","). 109 | - `{ strip_insignificant_zeros: true}` - If true removes insignificant zeros 110 | after the decimal separator (defaults to true) 111 | - `{ significant: true }` (defaults to true) 112 | - If true, precision will be the # of significant_digits. 113 | - If false, the # of fractional digits 114 | 115 | *Examples:* 116 | - `NumberHelpers.number_to_human_size(123)`: `123 Bytes` 117 | - `NumberHelpers.number_to_human_size(1234)`: `1.21 KB` 118 | - `NumberHelpers.number_to_human_size(12345)`: `12.1 KB` 119 | - `NumberHelpers.number_to_human_size(1234567)`: `1.18 MB` 120 | - `NumberHelpers.number_to_human_size(1234567890)`: `1.15 GB` 121 | - `NumberHelpers.number_to_human_size(1234567890123)`: `1.12 TB` 122 | - `NumberHelpers.number_to_human_size(1234567, {precision: 2})`: `1.2 MB` 123 | - `NumberHelpers.number_to_human_size(483989, {precision: 2})`: `470 KB` 124 | - `NumberHelpers.number_to_human_size(1234567, {precision: 2, separator: ','})`: `1,2 MB` 125 | - `NumberHelpers.number_to_human_size(1234567890123, {precision: 5})`: `1.1228 TB` 126 | - `NumberHelpers.number_to_human_size(524288000, {precision: 5, strip_insignificant_zeros: true})`: `500 MB` 127 | 128 | ------------- 129 | 130 | ## `number_to_percentage` 131 | 132 | Formats a number as a percentage string (e.g., 65%). You can 133 | customize the format in the options hash. 134 | 135 | `NumberHelpers.number_to_percentage(123, {option: value})` 136 | 137 | - `{ precision: 3 }` - Sets the level of precision (defaults to 3). 138 | - `{ separator: '.' }` - Sets the separator between the units (defaults to "."). 139 | - `{ delimiter: '' }` - Sets the thousands delimiter (defaults to ""). 140 | - `{ strip_insignificant_zeros: false}` - If true removes insignificant zeros 141 | after the decimal separator (defaults to false) 142 | - `{ significant: false }` - If true, precision will be the # of significant_digits 143 | If false, the # of fractional digits (defaults to false) 144 | 145 | *Examples:* 146 | - `NumberHelpers.number_to_percentage(100)`: `100.000%` 147 | - `NumberHelpers.number_to_percentage(98)`: `98.000%` 148 | - `NumberHelpers.number_to_percentage(100, {precision: 0})`: `100%` 149 | - `NumberHelpers.number_to_percentage(1000, {delimiter: '.', separator: ','})`: `1.000,000%` 150 | - `NumberHelpers.number_to_percentage(302.24398923423, {precision: 5})`: `302.24399%` 151 | - `NumberHelpers.number_to_percentage('98a')`: `98a%` 152 | 153 | ---------- 154 | 155 | ## `number_to_phone` 156 | 157 | Formats a number into a US phone number (e.g., (555) 123-9876). 158 | You can customize the format in the options hash. 159 | 160 | `NumberHelpers.number_to_phone(5551234, {option: value})` 161 | 162 | - `{ area_code: false }` - Adds parentheses around the area code. 163 | - `{ delimiter: '-' }` - Sets the thousands delimiter (defaults to "-"). 164 | - `{ extension: false }` - Specifies an extension to add to the end of the generated number. 165 | - `{ country_code: false }` - Sets the country code for the phone number. 166 | 167 | *Examples:* 168 | - `NumberHelpers.number_to_phone(5551234)`: `555-1234` 169 | - `NumberHelpers.number_to_phone(1235551234)`: `123-555-1234` 170 | - `NumberHelpers.number_to_phone(1235551234, {area_code: true})`: `(123) 555-1234` 171 | - `NumberHelpers.number_to_phone(1235551234, {delimiter: ' '})`: `123 555 1234` 172 | - `NumberHelpers.number_to_phone(1235551234, {area_code: true, extension: 555})`: `(123) 555-1234 x 555` 173 | - `NumberHelpers.number_to_phone(1235551234, {country_code: 1})`: `+1-123-555-1234` 174 | - `NumberHelpers.number_to_phone(123a456)`: `123a456` 175 | - `NumberHelpers.number_to_phone(1235551234, {country_code: 1, extension: 1343, delimiter: '.'})`: `+1.123.555.1234 x 1343` 176 | 177 | ---------- 178 | 179 | ## `number_with_delimiter` 180 | 181 | Formats a number with grouped thousands using delimiter (e.g., 12,324). 182 | You can customize the format in the options hash. 183 | 184 | `NumberHelpers.number_with_delimiter(12345678, {option: value})` 185 | 186 | - `{ separator: '.' }` - Sets the separator between the units (defaults to "."). 187 | - `{ delimiter: ',' }` - Sets the thousands delimiter (defaults to ","). 188 | 189 | *Examples:* 190 | - `NumberHelpers.number_with_delimiter(12345678)`: `12,345,678` 191 | - `NumberHelpers.number_with_delimiter('123456')`: `123,456` 192 | - `NumberHelpers.number_with_delimiter(12345678.05)`: `12,345,678.05` 193 | - `NumberHelpers.number_with_delimiter(12345678, {delimiter:'.'})`: `12.345.678` 194 | - `NumberHelpers.number_with_delimiter(12345678, {delimiter:','})`: `12,345,678` 195 | - `NumberHelpers.number_with_delimiter(12345678.05, {separator:' '})`: `12,345,678 05` 196 | - `NumberHelpers.number_with_delimiter('112a')`: `112a` 197 | - `NumberHelpers.number_with_delimiter(98765432.98, {delimiter:' ',separator:','})`: `98 765 432,98` 198 | 199 | ------ 200 | 201 | ## `number_with_precision` 202 | 203 | Formats a number with the specified level of :precision (e.g., 112.32 has a 204 | precision of 2 if :significant is false, and 5 if :significant is true). 205 | You can customize the format in the options hash. 206 | 207 | `NumberHelpers.number_with_precision(111.2345, {option: value})` 208 | 209 | - `{ precision: 3 }` - Sets the level of precision (defaults to 3). 210 | - `{ separator: '.' }` - Sets the separator between the units (defaults to "."). 211 | - `{ delimiter: ',' }` - Sets the thousands delimiter (defaults to ","). 212 | - `{ strip_insignificant_zeros: false}` - If true removes insignificant zeros after 213 | the decimal separator (defaults to false) 214 | - `{ significant: false }` 215 | - If true, precision will be the # of significant_digits. 216 | - If false, the # of fractional digits (defaults to false) 217 | - `{ strip_empty_fractional_parts: false }` - If true, the number will be expressed as an integer 218 | if the fractional part is empty (if it rounds to 15.00, only '15' is produced). (defaults to false) 219 | 220 | *Examples:* 221 | - `NumberHelpers.number_with_precision(111.2345)`: `111.235` 222 | - `NumberHelpers.number_with_precision(111.2345, {precision: 2})`: `111.23` 223 | - `NumberHelpers.number_with_precision(13, {precision: 5})`: `13.00000` 224 | - `NumberHelpers.number_with_precision(389.32314, {precision: 0})`: `389` 225 | - `NumberHelpers.number_with_precision(111.2345, {significant: true})`: `111` 226 | - `NumberHelpers.number_with_precision(111.2345, {significant: true, precision: 1})`: `100` 227 | - `NumberHelpers.number_with_precision(389.32314, {significant: true, precision: 4})`: `389.3` 228 | - `NumberHelpers.number_with_precision(13, {significant: true, precision: 5})`: `13.00000` 229 | - `NumberHelpers.number_with_precision(13.00000004, {significant: true, precision: 6})`: `13.000000` 230 | - `NumberHelpers.number_with_precision(1111.2345, {separator: ',', precision: 2, delimiter: '.'})`: `1.111,23` 231 | - `NumberHelpers.number_with_precision(13, {significant: true, precision: 5, strip_insignificant_zeros: true})`: `13` 232 | 233 | 234 | This project is maintained by [Emcien](https://github.com/emcien). 235 | 236 | We are always looking for good engineers - 237 | [send](mailto:devjobs@emcien.com) us your github profile and resume! 238 | 239 | -------------------------------------------------------------------------------- /src/number_helpers.coffee: -------------------------------------------------------------------------------- 1 | class NumberHelpers 2 | @ZEROS = new Array(200).join('0'); 3 | @number_to_currency = (float, opts={}) -> 4 | _precision = opts.precision ? 2 5 | _unit = opts.unit ? '$' 6 | _separator = opts.separator ? '.' 7 | _delimiter = opts.delimiter ? ',' 8 | _unit_pos = opts.unit_position ? 'start' 9 | 10 | sign = '' 11 | 12 | # Non-number values return zero precision 13 | if isNaN(float) 14 | result = float 15 | else 16 | float = parseFloat(float) # Arg might be a string, integer 17 | sign = '-' if float < 0 18 | fixedWidth = Math.abs(float).toFixed(_precision) 19 | delimited = NumberHelpers.number_with_delimiter(fixedWidth, {delimiter: _delimiter}) 20 | result = delimited.split('.').join(_separator) 21 | 22 | if _unit_pos == 'end' 23 | return "#{sign}#{result}#{_unit}" 24 | else 25 | return "#{sign}#{_unit}#{result}" 26 | 27 | @number_with_delimiter = (float, opts={}) -> 28 | _separator = opts.separator ? '.' 29 | _delimiter = opts.delimiter ? ',' 30 | 31 | number = float.toString().split(".") 32 | integer = number[0] 33 | decimal = number[1] ? '' 34 | 35 | # Remove separator if no decimal 36 | _separator = '' unless decimal 37 | 38 | rgx = /(\d+)(\d{3})/ 39 | integer = integer.replace(rgx, "$1" + _delimiter + "$2") while rgx.test(integer) if _delimiter 40 | 41 | return "#{integer}#{_separator}#{decimal}" 42 | 43 | @safe_round = (float, _significant, _precision) -> 44 | # Rounding based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round 45 | #Shift 46 | return float unless float 47 | value = (+float).toExponential().toString().split('e'); 48 | _round = if _significant then (-1 - value[1] + _precision) else _precision 49 | 50 | value = Math.round(+(value[0] + 'e' + (+value[1] + _round))); 51 | # Shift back 52 | value = value.toExponential().toString().split('e'); 53 | value = +(value[0] + 'e' + ( +value[1] - _round) ); 54 | 55 | 56 | @number_with_precision = (float, opts={}) -> 57 | _precision = opts.precision ? 3 58 | _delimiter = opts.delimiter ? ',' 59 | _separator = opts.separator ? '.' 60 | _significant = opts.significant ? false 61 | _strip_insignificant_zeros = opts.strip_insignificant_zeros ? false 62 | _skip_empty_fractionals = opts.strip_empty_fractional_parts 63 | 64 | # Break number into inspectable pieces 65 | number = float.toString().split('.') 66 | integer = number[0] 67 | decimal = number[1] ? '' 68 | 69 | # Significant Digits need rounding to match number of sig digits 70 | if _significant 71 | rnd = _precision - integer.length 72 | else 73 | rnd = _precision 74 | 75 | # Make sure rounding is not to a negative place 76 | rnd = 0 if rnd < 1 77 | 78 | # Round 79 | multiple = Math.pow(10, rnd) 80 | if multiple > 1 81 | rounded = @safe_round(float, _significant, _precision) 82 | else 83 | rounded = float 84 | 85 | # Break number into inspectable pieces 86 | number = rounded.toString().split('.') 87 | integer = number[0] 88 | decimal = number[1] ? '' 89 | 90 | decimal = parseFloat("0.#{decimal}").toFixed(_precision) 91 | decimal = decimal.toString().split('.') 92 | decimal = decimal[1] ? '' 93 | 94 | # Reconstitute the number with correct decimal 95 | number = "#{integer}.#{decimal}" * 1 96 | num_array = number.toString().split('') 97 | num_lngth = num_array.length 98 | 99 | # Count Non-zero Digits 100 | i = 0; sigs = 0 101 | while i < num_lngth 102 | sigs++ unless num_array[i] is '.' or num_array[i] is '0' 103 | i++ 104 | 105 | # Significant Digits 106 | if _significant and sigs >= _precision and _precision > 0 107 | significant = number.toPrecision(_precision) * 1 108 | significant = significant.toString().split('.') 109 | integer = significant[0] 110 | decimal = significant[1] ? '' 111 | 112 | 113 | 114 | 115 | # Delimiter Integer 116 | integer = NumberHelpers.number_with_delimiter(integer, {delimiter: _delimiter}) 117 | 118 | # Strip Insignificant Digits 119 | if _strip_insignificant_zeros 120 | dlen = decimal.length 121 | newlen = dlen 122 | 123 | while newlen > 0 and decimal[newlen - 1] == '0' 124 | newlen = newlen - 1 125 | 126 | if newlen == 0 127 | decimal = '' 128 | else if newlen != dlen 129 | decimal = decimal.slice(0, newlen) 130 | 131 | if _skip_empty_fractionals 132 | i = 0; zcount = 0 133 | num_array = decimal.split('') 134 | dlen = decimal.length 135 | while i < dlen 136 | zcount++ if num_array[i] is '0' 137 | i++ 138 | if zcount == dlen 139 | decimal = '' 140 | 141 | # Remove separator if no decimal 142 | _separator = '' unless decimal 143 | 144 | return "#{integer}#{_separator}#{decimal}" 145 | 146 | @number_to_human = (float, opts={}) -> 147 | _precision = opts.precision ? 3 148 | _separator = opts.separator ? '.' 149 | _significant = opts.significant ? true 150 | _delimiter = opts.delimiter ? ',' 151 | _strip_insignificant_zeros = opts.strip_insignificant_zeros ? false 152 | _space_label = if opts.space_label is false then '' else ' ' 153 | _labels = 154 | thousand: opts.labels?.thousand ? 'Thousand' 155 | million: opts.labels?.million ? 'Million' 156 | billion: opts.labels?.billion ? 'Billion' 157 | trillion: opts.labels?.trillion ? 'Trillion' 158 | quadrillion: opts.labels?.quadrillion ? 'Quadrillion' 159 | 160 | # Remove the sign of the number for easier comparision 161 | abs_float = Math.abs(float) 162 | 163 | # Less than Thousand does not need text or a insignifiant digits 164 | if abs_float < Math.pow(10, 3) 165 | denom = 1 166 | label = false 167 | else if abs_float >= Math.pow(10, 3) and abs_float < Math.pow(10, 6) 168 | denom = Math.pow(10, 3) 169 | label = _labels.thousand 170 | else if abs_float >= Math.pow(10, 6) and abs_float < Math.pow(10, 9) 171 | denom = Math.pow(10, 6) 172 | label = _labels.million 173 | else if abs_float >= Math.pow(10, 9) and abs_float < Math.pow(10, 12) 174 | denom = Math.pow(10, 9) 175 | label = _labels.billion 176 | else if abs_float >= Math.pow(10, 12) and abs_float < Math.pow(10, 15) 177 | denom = Math.pow(10, 12) 178 | label = _labels.trillion 179 | else if abs_float >= Math.pow(10, 15) 180 | denom = Math.pow(10, 15) 181 | label = _labels.quadrillion 182 | 183 | # Process the number into a presentable format 184 | number = float / denom 185 | precise = NumberHelpers.number_with_precision(number, 186 | precision: _precision 187 | significant: _significant 188 | delimiter: if label is 'Quadrillion' then '' else _delimiter 189 | separator: _separator 190 | strip_insignificant_zeros: unless label then true else _strip_insignificant_zeros 191 | ) 192 | 193 | #No label needed for less than thousand 194 | if label 195 | return "#{precise}#{_space_label}#{label}" 196 | else 197 | return precise 198 | 199 | @number_to_human_size = (float, opts={}) -> 200 | _precision = opts.precision ? 3 201 | _separator = opts.separator ? '.' 202 | _significant = opts.significant ? true 203 | _delimiter = opts.delimiter ? ',' 204 | _strip_insignificant_zeros = opts.strip_insignificant_zeros ? false 205 | 206 | # Power of 10 to Bytes Converter 207 | float = float / 1.024 if float > 1000 208 | float = float / 1.024 if float > 1000000 209 | float = float / 1.024 if float > 1000000000 210 | float = float / 1.024 if float > 1000000000000 211 | 212 | # Remove the sign of the number for easier comparision 213 | abs_float = Math.abs(float) 214 | 215 | # Less than Thousand does not need text or a insignifiant digits 216 | if abs_float < Math.pow(10, 3) 217 | denom = 1 218 | label = 'Bytes' 219 | else if abs_float >= Math.pow(10, 3) and abs_float < Math.pow(10, 6) 220 | denom = Math.pow(10, 3) 221 | label = 'KB' 222 | else if abs_float >= Math.pow(10, 6) and abs_float < Math.pow(10, 9) 223 | denom = Math.pow(10, 6) 224 | label = 'MB' 225 | else if abs_float >= Math.pow(10, 9) and abs_float < Math.pow(10, 12) 226 | denom = Math.pow(10, 9) 227 | label = 'GB' 228 | else if abs_float >= Math.pow(10, 12) and abs_float < Math.pow(10, 15) 229 | denom = Math.pow(10, 12) 230 | label = 'TB' 231 | 232 | # Process the number into a presentable format 233 | number = float / denom 234 | 235 | precise = NumberHelpers.number_with_precision(number, 236 | precision: _precision 237 | significant: _significant 238 | delimiter: _delimiter 239 | separator: _separator 240 | strip_insignificant_zeros: if label is 'Bytes' then true else _strip_insignificant_zeros 241 | ) 242 | 243 | return "#{precise} #{label}" 244 | 245 | @number_to_phone = (number, opts={}) -> 246 | _area_code = opts.area_code ? false 247 | _delimiter = opts.delimiter ? '-' 248 | _country_code = opts.country_code ? false 249 | _extension = opts.extension ? false 250 | 251 | # Not a numerical value 252 | return number if isNaN(number) 253 | 254 | str = number.toString() 255 | lng = str.length 256 | 257 | # Last Four Digits 258 | last = str.substr(lng - 4, lng) 259 | 260 | if lng < 8 261 | first = str.substr(0, 3) 262 | else 263 | first = str.substr(0, 3) 264 | second = str.substr(3,3) 265 | 266 | # Area Code 267 | if _area_code 268 | first = "(#{first}) #{second}" 269 | else 270 | first = "#{first}#{_delimiter}#{second}" 271 | 272 | # Extension Code 273 | _extension = if _extension then " x #{opts.extension}" else '' 274 | 275 | # Country Code 276 | _country_code = if _country_code then "+#{_country_code}#{_delimiter}" else '' 277 | 278 | return "#{_country_code}#{first}#{_delimiter}#{last}#{_extension}" 279 | 280 | @number_to_percentage = (float, opts={}) -> 281 | _precision = opts.precision ? 3 282 | _separator = opts.separator ? '.' 283 | _significant = opts.significant ? false 284 | _delimiter = opts.delimiter ? '' 285 | _strip_insignificant_zeros = opts.strip_insignificant_zeros ? false 286 | 287 | unless isNaN(float) 288 | float = NumberHelpers.number_with_precision(float, 289 | precision: _precision 290 | significant: _significant 291 | delimiter: _delimiter 292 | separator: _separator 293 | strip_insignificant_zeros: _strip_insignificant_zeros 294 | ) 295 | 296 | return "#{float}%" 297 | 298 | @reconstitute_exponential = (num, exp) -> 299 | if num.indexOf('e') != -1 300 | vals = num.split('e') 301 | num = vals[0] 302 | exp = vals[1] 303 | exp = +exp 304 | num +='' 305 | return num if exp == 0 306 | # format is n.nnnnn or n; decimal implied after first digit 307 | num = num.replace(/\./,'') 308 | numlength = num.length 309 | if exp > 0 310 | if (exp + 1) < numlength 311 | num = num.substr(0,exp+1)+'.'+num.substr(exp+1) 312 | else 313 | # no decimal, but we need added zeros 314 | num = (num + @ZEROS).substr(0,exp+1) 315 | else 316 | #smaller, just move the decimal over 317 | num = "0."+(@ZEROS + num).substr(@ZEROS.length+1+exp) 318 | return num 319 | 320 | 321 | 322 | # Number to rounded handles singificant digits differently than number_with_precision 323 | @number_to_rounded = (float, opts={}) -> 324 | _precision = opts.precision ? 3 325 | _significant = opts.significant ? false 326 | # Be consistent with Ruby implementation 327 | _delimiter = opts.delimiter ? '' 328 | _separator = opts.separator ? '.' 329 | _strip_insignificant_zeros = opts.strip_insignificant_zeros ? false 330 | _skip_empty_fractionals = opts.strip_empty_fractional_parts 331 | # Zero precision with significant is a mistake (would always return zero), 332 | # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size) 333 | 334 | if _significant && !_precision 335 | _significant = false 336 | 337 | rounded = @safe_round(float, _significant, _precision) 338 | 339 | if _significant && _precision 340 | value = rounded.toExponential().split('e'); 341 | significant_val = (+value[0]).toFixed(_precision-1) 342 | rounded = @reconstitute_exponential(significant_val,value[1]) 343 | 344 | else if _precision 345 | # Break number into inspectable pieces 346 | rounded = rounded.toFixed(_precision) 347 | 348 | rounded +='' 349 | number = rounded.split('.') 350 | integer = number[0] 351 | decimal = number[1] ? '' 352 | 353 | # Delimiter Integer 354 | integer = NumberHelpers.number_with_delimiter(integer, {delimiter: _delimiter}) 355 | 356 | # Strip Insignificant Digits 357 | if _strip_insignificant_zeros 358 | dlen = decimal.length 359 | newlen = dlen 360 | 361 | while newlen > 0 and decimal[newlen - 1] == '0' 362 | newlen = newlen - 1 363 | 364 | if newlen == 0 365 | decimal = '' 366 | else if newlen != dlen 367 | decimal = decimal.slice(0, newlen) 368 | 369 | if _skip_empty_fractionals 370 | i = 0; zcount = 0 371 | num_array = decimal.split('') 372 | dlen = decimal.length 373 | while i < dlen 374 | zcount++ if num_array[i] is '0' 375 | i++ 376 | if zcount == dlen 377 | decimal = '' 378 | 379 | # Remove separator if no decimal 380 | _separator = '' unless decimal 381 | 382 | return "#{integer}#{_separator}#{decimal}" 383 | 384 | 385 | 386 | 387 | if typeof module isnt 'undefined' and typeof module.exports isnt 'undefined' 388 | module.exports = NumberHelpers 389 | else 390 | window.NumberHelpers = NumberHelpers 391 | -------------------------------------------------------------------------------- /lib/number_helpers.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | (function() { 3 | var NumberHelpers; 4 | 5 | NumberHelpers = (function() { 6 | function NumberHelpers() {} 7 | 8 | NumberHelpers.ZEROS = new Array(200).join('0'); 9 | 10 | NumberHelpers.number_to_currency = function(float, opts) { 11 | var _delimiter, _precision, _separator, _unit, _unit_pos, delimited, fixedWidth, ref, ref1, ref2, ref3, ref4, result, sign; 12 | if (opts == null) { 13 | opts = {}; 14 | } 15 | _precision = (ref = opts.precision) != null ? ref : 2; 16 | _unit = (ref1 = opts.unit) != null ? ref1 : '$'; 17 | _separator = (ref2 = opts.separator) != null ? ref2 : '.'; 18 | _delimiter = (ref3 = opts.delimiter) != null ? ref3 : ','; 19 | _unit_pos = (ref4 = opts.unit_position) != null ? ref4 : 'start'; 20 | sign = ''; 21 | if (isNaN(float)) { 22 | result = float; 23 | } else { 24 | float = parseFloat(float); 25 | if (float < 0) { 26 | sign = '-'; 27 | } 28 | fixedWidth = Math.abs(float).toFixed(_precision); 29 | delimited = NumberHelpers.number_with_delimiter(fixedWidth, { 30 | delimiter: _delimiter 31 | }); 32 | result = delimited.split('.').join(_separator); 33 | } 34 | if (_unit_pos === 'end') { 35 | return "" + sign + result + _unit; 36 | } else { 37 | return "" + sign + _unit + result; 38 | } 39 | }; 40 | 41 | NumberHelpers.number_with_delimiter = function(float, opts) { 42 | var _delimiter, _separator, decimal, integer, number, ref, ref1, ref2, rgx; 43 | if (opts == null) { 44 | opts = {}; 45 | } 46 | _separator = (ref = opts.separator) != null ? ref : '.'; 47 | _delimiter = (ref1 = opts.delimiter) != null ? ref1 : ','; 48 | number = float.toString().split("."); 49 | integer = number[0]; 50 | decimal = (ref2 = number[1]) != null ? ref2 : ''; 51 | if (!decimal) { 52 | _separator = ''; 53 | } 54 | rgx = /(\d+)(\d{3})/; 55 | if (_delimiter) { 56 | while (rgx.test(integer)) { 57 | integer = integer.replace(rgx, "$1" + _delimiter + "$2"); 58 | } 59 | } 60 | return "" + integer + _separator + decimal; 61 | }; 62 | 63 | NumberHelpers.safe_round = function(float, _significant, _precision) { 64 | var _round, value; 65 | if (!float) { 66 | return float; 67 | } 68 | value = (+float).toExponential().toString().split('e'); 69 | _round = _significant ? -1 - value[1] + _precision : _precision; 70 | value = Math.round(+(value[0] + 'e' + (+value[1] + _round))); 71 | value = value.toExponential().toString().split('e'); 72 | return value = +(value[0] + 'e' + (+value[1] - _round)); 73 | }; 74 | 75 | NumberHelpers.number_with_precision = function(float, opts) { 76 | var _delimiter, _precision, _separator, _significant, _skip_empty_fractionals, _strip_insignificant_zeros, decimal, dlen, i, integer, multiple, newlen, num_array, num_lngth, number, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, rnd, rounded, significant, sigs, zcount; 77 | if (opts == null) { 78 | opts = {}; 79 | } 80 | _precision = (ref = opts.precision) != null ? ref : 3; 81 | _delimiter = (ref1 = opts.delimiter) != null ? ref1 : ','; 82 | _separator = (ref2 = opts.separator) != null ? ref2 : '.'; 83 | _significant = (ref3 = opts.significant) != null ? ref3 : false; 84 | _strip_insignificant_zeros = (ref4 = opts.strip_insignificant_zeros) != null ? ref4 : false; 85 | _skip_empty_fractionals = opts.strip_empty_fractional_parts; 86 | number = float.toString().split('.'); 87 | integer = number[0]; 88 | decimal = (ref5 = number[1]) != null ? ref5 : ''; 89 | if (_significant) { 90 | rnd = _precision - integer.length; 91 | } else { 92 | rnd = _precision; 93 | } 94 | if (rnd < 1) { 95 | rnd = 0; 96 | } 97 | multiple = Math.pow(10, rnd); 98 | if (multiple > 1) { 99 | rounded = this.safe_round(float, _significant, _precision); 100 | } else { 101 | rounded = float; 102 | } 103 | number = rounded.toString().split('.'); 104 | integer = number[0]; 105 | decimal = (ref6 = number[1]) != null ? ref6 : ''; 106 | decimal = parseFloat("0." + decimal).toFixed(_precision); 107 | decimal = decimal.toString().split('.'); 108 | decimal = (ref7 = decimal[1]) != null ? ref7 : ''; 109 | number = (integer + "." + decimal) * 1; 110 | num_array = number.toString().split(''); 111 | num_lngth = num_array.length; 112 | i = 0; 113 | sigs = 0; 114 | while (i < num_lngth) { 115 | if (!(num_array[i] === '.' || num_array[i] === '0')) { 116 | sigs++; 117 | } 118 | i++; 119 | } 120 | if (_significant && sigs >= _precision && _precision > 0) { 121 | significant = number.toPrecision(_precision) * 1; 122 | significant = significant.toString().split('.'); 123 | integer = significant[0]; 124 | decimal = (ref8 = significant[1]) != null ? ref8 : ''; 125 | } 126 | integer = NumberHelpers.number_with_delimiter(integer, { 127 | delimiter: _delimiter 128 | }); 129 | if (_strip_insignificant_zeros) { 130 | dlen = decimal.length; 131 | newlen = dlen; 132 | while (newlen > 0 && decimal[newlen - 1] === '0') { 133 | newlen = newlen - 1; 134 | } 135 | if (newlen === 0) { 136 | decimal = ''; 137 | } else if (newlen !== dlen) { 138 | decimal = decimal.slice(0, newlen); 139 | } 140 | } 141 | if (_skip_empty_fractionals) { 142 | i = 0; 143 | zcount = 0; 144 | num_array = decimal.split(''); 145 | dlen = decimal.length; 146 | while (i < dlen) { 147 | if (num_array[i] === '0') { 148 | zcount++; 149 | } 150 | i++; 151 | } 152 | if (zcount === dlen) { 153 | decimal = ''; 154 | } 155 | } 156 | if (!decimal) { 157 | _separator = ''; 158 | } 159 | return "" + integer + _separator + decimal; 160 | }; 161 | 162 | NumberHelpers.number_to_human = function(float, opts) { 163 | var _delimiter, _labels, _precision, _separator, _significant, _space_label, _strip_insignificant_zeros, abs_float, denom, label, number, precise, ref, ref1, ref10, ref11, ref12, ref13, ref14, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9; 164 | if (opts == null) { 165 | opts = {}; 166 | } 167 | _precision = (ref = opts.precision) != null ? ref : 3; 168 | _separator = (ref1 = opts.separator) != null ? ref1 : '.'; 169 | _significant = (ref2 = opts.significant) != null ? ref2 : true; 170 | _delimiter = (ref3 = opts.delimiter) != null ? ref3 : ','; 171 | _strip_insignificant_zeros = (ref4 = opts.strip_insignificant_zeros) != null ? ref4 : false; 172 | _space_label = opts.space_label === false ? '' : ' '; 173 | _labels = { 174 | thousand: (ref5 = (ref6 = opts.labels) != null ? ref6.thousand : void 0) != null ? ref5 : 'Thousand', 175 | million: (ref7 = (ref8 = opts.labels) != null ? ref8.million : void 0) != null ? ref7 : 'Million', 176 | billion: (ref9 = (ref10 = opts.labels) != null ? ref10.billion : void 0) != null ? ref9 : 'Billion', 177 | trillion: (ref11 = (ref12 = opts.labels) != null ? ref12.trillion : void 0) != null ? ref11 : 'Trillion', 178 | quadrillion: (ref13 = (ref14 = opts.labels) != null ? ref14.quadrillion : void 0) != null ? ref13 : 'Quadrillion' 179 | }; 180 | abs_float = Math.abs(float); 181 | if (abs_float < Math.pow(10, 3)) { 182 | denom = 1; 183 | label = false; 184 | } else if (abs_float >= Math.pow(10, 3) && abs_float < Math.pow(10, 6)) { 185 | denom = Math.pow(10, 3); 186 | label = _labels.thousand; 187 | } else if (abs_float >= Math.pow(10, 6) && abs_float < Math.pow(10, 9)) { 188 | denom = Math.pow(10, 6); 189 | label = _labels.million; 190 | } else if (abs_float >= Math.pow(10, 9) && abs_float < Math.pow(10, 12)) { 191 | denom = Math.pow(10, 9); 192 | label = _labels.billion; 193 | } else if (abs_float >= Math.pow(10, 12) && abs_float < Math.pow(10, 15)) { 194 | denom = Math.pow(10, 12); 195 | label = _labels.trillion; 196 | } else if (abs_float >= Math.pow(10, 15)) { 197 | denom = Math.pow(10, 15); 198 | label = _labels.quadrillion; 199 | } 200 | number = float / denom; 201 | precise = NumberHelpers.number_with_precision(number, { 202 | precision: _precision, 203 | significant: _significant, 204 | delimiter: label === 'Quadrillion' ? '' : _delimiter, 205 | separator: _separator, 206 | strip_insignificant_zeros: !label ? true : _strip_insignificant_zeros 207 | }); 208 | if (label) { 209 | return "" + precise + _space_label + label; 210 | } else { 211 | return precise; 212 | } 213 | }; 214 | 215 | NumberHelpers.number_to_human_size = function(float, opts) { 216 | var _delimiter, _precision, _separator, _significant, _strip_insignificant_zeros, abs_float, denom, label, number, precise, ref, ref1, ref2, ref3, ref4; 217 | if (opts == null) { 218 | opts = {}; 219 | } 220 | _precision = (ref = opts.precision) != null ? ref : 3; 221 | _separator = (ref1 = opts.separator) != null ? ref1 : '.'; 222 | _significant = (ref2 = opts.significant) != null ? ref2 : true; 223 | _delimiter = (ref3 = opts.delimiter) != null ? ref3 : ','; 224 | _strip_insignificant_zeros = (ref4 = opts.strip_insignificant_zeros) != null ? ref4 : false; 225 | if (float > 1000) { 226 | float = float / 1.024; 227 | } 228 | if (float > 1000000) { 229 | float = float / 1.024; 230 | } 231 | if (float > 1000000000) { 232 | float = float / 1.024; 233 | } 234 | if (float > 1000000000000) { 235 | float = float / 1.024; 236 | } 237 | abs_float = Math.abs(float); 238 | if (abs_float < Math.pow(10, 3)) { 239 | denom = 1; 240 | label = 'Bytes'; 241 | } else if (abs_float >= Math.pow(10, 3) && abs_float < Math.pow(10, 6)) { 242 | denom = Math.pow(10, 3); 243 | label = 'KB'; 244 | } else if (abs_float >= Math.pow(10, 6) && abs_float < Math.pow(10, 9)) { 245 | denom = Math.pow(10, 6); 246 | label = 'MB'; 247 | } else if (abs_float >= Math.pow(10, 9) && abs_float < Math.pow(10, 12)) { 248 | denom = Math.pow(10, 9); 249 | label = 'GB'; 250 | } else if (abs_float >= Math.pow(10, 12) && abs_float < Math.pow(10, 15)) { 251 | denom = Math.pow(10, 12); 252 | label = 'TB'; 253 | } 254 | number = float / denom; 255 | precise = NumberHelpers.number_with_precision(number, { 256 | precision: _precision, 257 | significant: _significant, 258 | delimiter: _delimiter, 259 | separator: _separator, 260 | strip_insignificant_zeros: label === 'Bytes' ? true : _strip_insignificant_zeros 261 | }); 262 | return precise + " " + label; 263 | }; 264 | 265 | NumberHelpers.number_to_phone = function(number, opts) { 266 | var _area_code, _country_code, _delimiter, _extension, first, last, lng, ref, ref1, ref2, ref3, second, str; 267 | if (opts == null) { 268 | opts = {}; 269 | } 270 | _area_code = (ref = opts.area_code) != null ? ref : false; 271 | _delimiter = (ref1 = opts.delimiter) != null ? ref1 : '-'; 272 | _country_code = (ref2 = opts.country_code) != null ? ref2 : false; 273 | _extension = (ref3 = opts.extension) != null ? ref3 : false; 274 | if (isNaN(number)) { 275 | return number; 276 | } 277 | str = number.toString(); 278 | lng = str.length; 279 | last = str.substr(lng - 4, lng); 280 | if (lng < 8) { 281 | first = str.substr(0, 3); 282 | } else { 283 | first = str.substr(0, 3); 284 | second = str.substr(3, 3); 285 | if (_area_code) { 286 | first = "(" + first + ") " + second; 287 | } else { 288 | first = "" + first + _delimiter + second; 289 | } 290 | } 291 | _extension = _extension ? " x " + opts.extension : ''; 292 | _country_code = _country_code ? "+" + _country_code + _delimiter : ''; 293 | return "" + _country_code + first + _delimiter + last + _extension; 294 | }; 295 | 296 | NumberHelpers.number_to_percentage = function(float, opts) { 297 | var _delimiter, _precision, _separator, _significant, _strip_insignificant_zeros, ref, ref1, ref2, ref3, ref4; 298 | if (opts == null) { 299 | opts = {}; 300 | } 301 | _precision = (ref = opts.precision) != null ? ref : 3; 302 | _separator = (ref1 = opts.separator) != null ? ref1 : '.'; 303 | _significant = (ref2 = opts.significant) != null ? ref2 : false; 304 | _delimiter = (ref3 = opts.delimiter) != null ? ref3 : ''; 305 | _strip_insignificant_zeros = (ref4 = opts.strip_insignificant_zeros) != null ? ref4 : false; 306 | if (!isNaN(float)) { 307 | float = NumberHelpers.number_with_precision(float, { 308 | precision: _precision, 309 | significant: _significant, 310 | delimiter: _delimiter, 311 | separator: _separator, 312 | strip_insignificant_zeros: _strip_insignificant_zeros 313 | }); 314 | } 315 | return float + "%"; 316 | }; 317 | 318 | NumberHelpers.reconstitute_exponential = function(num, exp) { 319 | var numlength, vals; 320 | if (num.indexOf('e') !== -1) { 321 | vals = num.split('e'); 322 | num = vals[0]; 323 | exp = vals[1]; 324 | } 325 | exp = +exp; 326 | num += ''; 327 | if (exp === 0) { 328 | return num; 329 | } 330 | num = num.replace(/\./, ''); 331 | numlength = num.length; 332 | if (exp > 0) { 333 | if ((exp + 1) < numlength) { 334 | num = num.substr(0, exp + 1) + '.' + num.substr(exp + 1); 335 | } else { 336 | num = (num + this.ZEROS).substr(0, exp + 1); 337 | } 338 | } else { 339 | num = "0." + (this.ZEROS + num).substr(this.ZEROS.length + 1 + exp); 340 | } 341 | return num; 342 | }; 343 | 344 | NumberHelpers.number_to_rounded = function(float, opts) { 345 | var _delimiter, _precision, _separator, _significant, _skip_empty_fractionals, _strip_insignificant_zeros, decimal, dlen, i, integer, newlen, num_array, number, ref, ref1, ref2, ref3, ref4, ref5, rounded, significant_val, value, zcount; 346 | if (opts == null) { 347 | opts = {}; 348 | } 349 | _precision = (ref = opts.precision) != null ? ref : 3; 350 | _significant = (ref1 = opts.significant) != null ? ref1 : false; 351 | _delimiter = (ref2 = opts.delimiter) != null ? ref2 : ''; 352 | _separator = (ref3 = opts.separator) != null ? ref3 : '.'; 353 | _strip_insignificant_zeros = (ref4 = opts.strip_insignificant_zeros) != null ? ref4 : false; 354 | _skip_empty_fractionals = opts.strip_empty_fractional_parts; 355 | if (_significant && !_precision) { 356 | _significant = false; 357 | } 358 | rounded = this.safe_round(float, _significant, _precision); 359 | if (_significant && _precision) { 360 | value = rounded.toExponential().split('e'); 361 | significant_val = (+value[0]).toFixed(_precision - 1); 362 | rounded = this.reconstitute_exponential(significant_val, value[1]); 363 | } else if (_precision) { 364 | rounded = rounded.toFixed(_precision); 365 | } 366 | rounded += ''; 367 | number = rounded.split('.'); 368 | integer = number[0]; 369 | decimal = (ref5 = number[1]) != null ? ref5 : ''; 370 | integer = NumberHelpers.number_with_delimiter(integer, { 371 | delimiter: _delimiter 372 | }); 373 | if (_strip_insignificant_zeros) { 374 | dlen = decimal.length; 375 | newlen = dlen; 376 | while (newlen > 0 && decimal[newlen - 1] === '0') { 377 | newlen = newlen - 1; 378 | } 379 | if (newlen === 0) { 380 | decimal = ''; 381 | } else if (newlen !== dlen) { 382 | decimal = decimal.slice(0, newlen); 383 | } 384 | } 385 | if (_skip_empty_fractionals) { 386 | i = 0; 387 | zcount = 0; 388 | num_array = decimal.split(''); 389 | dlen = decimal.length; 390 | while (i < dlen) { 391 | if (num_array[i] === '0') { 392 | zcount++; 393 | } 394 | i++; 395 | } 396 | if (zcount === dlen) { 397 | decimal = ''; 398 | } 399 | } 400 | if (!decimal) { 401 | _separator = ''; 402 | } 403 | return "" + integer + _separator + decimal; 404 | }; 405 | 406 | return NumberHelpers; 407 | 408 | })(); 409 | 410 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 411 | module.exports = NumberHelpers; 412 | } else { 413 | window.NumberHelpers = NumberHelpers; 414 | } 415 | 416 | }).call(this); 417 | -------------------------------------------------------------------------------- /jasmine-core/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2009-08-17 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | This file creates a global JSON object containing two methods: stringify 12 | and parse. 13 | 14 | JSON.stringify(value, replacer, space) 15 | value any JavaScript value, usually an object or array. 16 | 17 | replacer an optional parameter that determines how object 18 | values are stringified for objects. It can be a 19 | function or an array of strings. 20 | 21 | space an optional parameter that specifies the indentation 22 | of nested structures. If it is omitted, the text will 23 | be packed without extra whitespace. If it is a number, 24 | it will specify the number of spaces to indent at each 25 | level. If it is a string (such as '\t' or ' '), 26 | it contains the characters used to indent at each level. 27 | 28 | This method produces a JSON text from a JavaScript value. 29 | 30 | When an object value is found, if the object contains a toJSON 31 | method, its toJSON method will be called and the result will be 32 | stringified. A toJSON method does not serialize: it returns the 33 | value represented by the name/value pair that should be serialized, 34 | or undefined if nothing should be serialized. The toJSON method 35 | will be passed the key associated with the value, and this will be 36 | bound to the value 37 | 38 | For example, this would serialize Dates as ISO strings. 39 | 40 | Date.prototype.toJSON = function (key) { 41 | function f(n) { 42 | // Format integers to have at least two digits. 43 | return n < 10 ? '0' + n : n; 44 | } 45 | 46 | return this.getUTCFullYear() + '-' + 47 | f(this.getUTCMonth() + 1) + '-' + 48 | f(this.getUTCDate()) + 'T' + 49 | f(this.getUTCHours()) + ':' + 50 | f(this.getUTCMinutes()) + ':' + 51 | f(this.getUTCSeconds()) + 'Z'; 52 | }; 53 | 54 | You can provide an optional replacer method. It will be passed the 55 | key and value of each member, with this bound to the containing 56 | object. The value that is returned from your method will be 57 | serialized. If your method returns undefined, then the member will 58 | be excluded from the serialization. 59 | 60 | If the replacer parameter is an array of strings, then it will be 61 | used to select the members to be serialized. It filters the results 62 | such that only members with keys listed in the replacer array are 63 | stringified. 64 | 65 | Values that do not have JSON representations, such as undefined or 66 | functions, will not be serialized. Such values in objects will be 67 | dropped; in arrays they will be replaced with null. You can use 68 | a replacer function to replace those with JSON values. 69 | JSON.stringify(undefined) returns undefined. 70 | 71 | The optional space parameter produces a stringification of the 72 | value that is filled with line breaks and indentation to make it 73 | easier to read. 74 | 75 | If the space parameter is a non-empty string, then that string will 76 | be used for indentation. If the space parameter is a number, then 77 | the indentation will be that many spaces. 78 | 79 | Example: 80 | 81 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 82 | // text is '["e",{"pluribus":"unum"}]' 83 | 84 | 85 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 86 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 87 | 88 | text = JSON.stringify([new Date()], function (key, value) { 89 | return this[key] instanceof Date ? 90 | 'Date(' + this[key] + ')' : value; 91 | }); 92 | // text is '["Date(---current time---)"]' 93 | 94 | 95 | JSON.parse(text, reviver) 96 | This method parses a JSON text to produce an object or array. 97 | It can throw a SyntaxError exception. 98 | 99 | The optional reviver parameter is a function that can filter and 100 | transform the results. It receives each of the keys and values, 101 | and its return value is used instead of the original value. 102 | If it returns what it received, then the structure is not modified. 103 | If it returns undefined then the member is deleted. 104 | 105 | Example: 106 | 107 | // Parse the text. Values that look like ISO date strings will 108 | // be converted to Date objects. 109 | 110 | myData = JSON.parse(text, function (key, value) { 111 | var a; 112 | if (typeof value === 'string') { 113 | a = 114 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 115 | if (a) { 116 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 117 | +a[5], +a[6])); 118 | } 119 | } 120 | return value; 121 | }); 122 | 123 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 124 | var d; 125 | if (typeof value === 'string' && 126 | value.slice(0, 5) === 'Date(' && 127 | value.slice(-1) === ')') { 128 | d = new Date(value.slice(5, -1)); 129 | if (d) { 130 | return d; 131 | } 132 | } 133 | return value; 134 | }); 135 | 136 | 137 | This is a reference implementation. You are free to copy, modify, or 138 | redistribute. 139 | 140 | This code should be minified before deployment. 141 | See http://javascript.crockford.com/jsmin.html 142 | 143 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 144 | NOT CONTROL. 145 | */ 146 | 147 | /*jslint evil: true */ 148 | 149 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 150 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 151 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 152 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 153 | test, toJSON, toString, valueOf 154 | */ 155 | 156 | "use strict"; 157 | 158 | // Create a JSON object only if one does not already exist. We create the 159 | // methods in a closure to avoid creating global variables. 160 | 161 | if (!this.JSON) { 162 | this.JSON = {}; 163 | } 164 | 165 | (function () { 166 | 167 | function f(n) { 168 | // Format integers to have at least two digits. 169 | return n < 10 ? '0' + n : n; 170 | } 171 | 172 | if (typeof Date.prototype.toJSON !== 'function') { 173 | 174 | Date.prototype.toJSON = function (key) { 175 | 176 | return isFinite(this.valueOf()) ? 177 | this.getUTCFullYear() + '-' + 178 | f(this.getUTCMonth() + 1) + '-' + 179 | f(this.getUTCDate()) + 'T' + 180 | f(this.getUTCHours()) + ':' + 181 | f(this.getUTCMinutes()) + ':' + 182 | f(this.getUTCSeconds()) + 'Z' : null; 183 | }; 184 | 185 | String.prototype.toJSON = 186 | Number.prototype.toJSON = 187 | Boolean.prototype.toJSON = function (key) { 188 | return this.valueOf(); 189 | }; 190 | } 191 | 192 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 193 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | gap, 195 | indent, 196 | meta = { // table of character substitutions 197 | '\b': '\\b', 198 | '\t': '\\t', 199 | '\n': '\\n', 200 | '\f': '\\f', 201 | '\r': '\\r', 202 | '"' : '\\"', 203 | '\\': '\\\\' 204 | }, 205 | rep; 206 | 207 | 208 | function quote(string) { 209 | 210 | // If the string contains no control characters, no quote characters, and no 211 | // backslash characters, then we can safely slap some quotes around it. 212 | // Otherwise we must also replace the offending characters with safe escape 213 | // sequences. 214 | 215 | escapable.lastIndex = 0; 216 | return escapable.test(string) ? 217 | '"' + string.replace(escapable, function (a) { 218 | var c = meta[a]; 219 | return typeof c === 'string' ? c : 220 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 221 | }) + '"' : 222 | '"' + string + '"'; 223 | } 224 | 225 | 226 | function str(key, holder) { 227 | // Produce a string from holder[key]. 228 | 229 | var i, // The loop counter. 230 | k, // The member key. 231 | v, // The member value. 232 | length, 233 | mind = gap, 234 | partial, 235 | value = holder[key]; 236 | 237 | // If the value has a toJSON method, call it to obtain a replacement value. 238 | 239 | if (value && typeof value === 'object' && 240 | typeof value.toJSON === 'function') { 241 | value = value.toJSON(key); 242 | } 243 | 244 | // If we were called with a replacer function, then call the replacer to 245 | // obtain a replacement value. 246 | 247 | if (typeof rep === 'function') { 248 | value = rep.call(holder, key, value); 249 | } 250 | 251 | // What happens next depends on the value's type. 252 | 253 | switch (typeof value) { 254 | case 'string': 255 | return quote(value); 256 | 257 | case 'number': 258 | 259 | // JSON numbers must be finite. Encode non-finite numbers as null. 260 | 261 | return isFinite(value) ? String(value) : 'null'; 262 | 263 | case 'boolean': 264 | case 'null': 265 | 266 | // If the value is a boolean or null, convert it to a string. Note: 267 | // typeof null does not produce 'null'. The case is included here in 268 | // the remote chance that this gets fixed someday. 269 | 270 | return String(value); 271 | 272 | // If the type is 'object', we might be dealing with an object or an array or 273 | // null. 274 | 275 | case 'object': 276 | 277 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 278 | // so watch out for that case. 279 | 280 | if (!value) { 281 | return 'null'; 282 | } 283 | 284 | // Make an array to hold the partial results of stringifying this object value. 285 | 286 | gap += indent; 287 | partial = []; 288 | 289 | // Is the value an array? 290 | 291 | if (Object.prototype.toString.apply(value) === '[object Array]') { 292 | 293 | // The value is an array. Stringify every element. Use null as a placeholder 294 | // for non-JSON values. 295 | 296 | length = value.length; 297 | for (i = 0; i < length; i += 1) { 298 | partial[i] = str(i, value) || 'null'; 299 | } 300 | 301 | // Join all of the elements together, separated with commas, and wrap them in 302 | // brackets. 303 | 304 | v = partial.length === 0 ? '[]' : 305 | gap ? '[\n' + gap + 306 | partial.join(',\n' + gap) + '\n' + 307 | mind + ']' : 308 | '[' + partial.join(',') + ']'; 309 | gap = mind; 310 | return v; 311 | } 312 | 313 | // If the replacer is an array, use it to select the members to be stringified. 314 | 315 | if (rep && typeof rep === 'object') { 316 | length = rep.length; 317 | for (i = 0; i < length; i += 1) { 318 | k = rep[i]; 319 | if (typeof k === 'string') { 320 | v = str(k, value); 321 | if (v) { 322 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 323 | } 324 | } 325 | } 326 | } else { 327 | 328 | // Otherwise, iterate through all of the keys in the object. 329 | 330 | for (k in value) { 331 | if (Object.hasOwnProperty.call(value, k)) { 332 | v = str(k, value); 333 | if (v) { 334 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 335 | } 336 | } 337 | } 338 | } 339 | 340 | // Join all of the member texts together, separated with commas, 341 | // and wrap them in braces. 342 | 343 | v = partial.length === 0 ? '{}' : 344 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 345 | mind + '}' : '{' + partial.join(',') + '}'; 346 | gap = mind; 347 | return v; 348 | } 349 | } 350 | 351 | // If the JSON object does not yet have a stringify method, give it one. 352 | 353 | if (typeof JSON.stringify !== 'function') { 354 | JSON.stringify = function (value, replacer, space) { 355 | // The stringify method takes a value and an optional replacer, and an optional 356 | // space parameter, and returns a JSON text. The replacer can be a function 357 | // that can replace values, or an array of strings that will select the keys. 358 | // A default replacer method can be provided. Use of the space parameter can 359 | // produce text that is more easily readable. 360 | 361 | var i; 362 | gap = ''; 363 | indent = ''; 364 | 365 | // If the space parameter is a number, make an indent string containing that 366 | // many spaces. 367 | 368 | if (typeof space === 'number') { 369 | for (i = 0; i < space; i += 1) { 370 | indent += ' '; 371 | } 372 | 373 | // If the space parameter is a string, it will be used as the indent string. 374 | 375 | } else if (typeof space === 'string') { 376 | indent = space; 377 | } 378 | 379 | // If there is a replacer, it must be a function or an array. 380 | // Otherwise, throw an error. 381 | 382 | rep = replacer; 383 | if (replacer && typeof replacer !== 'function' && 384 | (typeof replacer !== 'object' || 385 | typeof replacer.length !== 'number')) { 386 | throw new Error('JSON.stringify'); 387 | } 388 | 389 | // Make a fake root object containing our value under the key of ''. 390 | // Return the result of stringifying the value. 391 | 392 | return str('', {'': value}); 393 | }; 394 | } 395 | 396 | 397 | // If the JSON object does not yet have a parse method, give it one. 398 | 399 | if (typeof JSON.parse !== 'function') { 400 | JSON.parse = function (text, reviver) { 401 | 402 | // The parse method takes a text and an optional reviver function, and returns 403 | // a JavaScript value if the text is a valid JSON text. 404 | 405 | var j; 406 | 407 | function walk(holder, key) { 408 | 409 | // The walk method is used to recursively walk the resulting structure so 410 | // that modifications can be made. 411 | 412 | var k, v, value = holder[key]; 413 | if (value && typeof value === 'object') { 414 | for (k in value) { 415 | if (Object.hasOwnProperty.call(value, k)) { 416 | v = walk(value, k); 417 | if (v !== undefined) { 418 | value[k] = v; 419 | } else { 420 | delete value[k]; 421 | } 422 | } 423 | } 424 | } 425 | return reviver.call(holder, key, value); 426 | } 427 | 428 | 429 | // Parsing happens in four stages. In the first stage, we replace certain 430 | // Unicode characters with escape sequences. JavaScript handles many characters 431 | // incorrectly, either silently deleting them, or treating them as line endings. 432 | 433 | cx.lastIndex = 0; 434 | if (cx.test(text)) { 435 | text = text.replace(cx, function (a) { 436 | return '\\u' + 437 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 438 | }); 439 | } 440 | 441 | // In the second stage, we run the text against regular expressions that look 442 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 443 | // because they can cause invocation, and '=' because it can cause mutation. 444 | // But just to be safe, we want to reject all unexpected forms. 445 | 446 | // We split the second stage into 4 regexp operations in order to work around 447 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 448 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 449 | // replace all simple value tokens with ']' characters. Third, we delete all 450 | // open brackets that follow a colon or comma or that begin the text. Finally, 451 | // we look to see that the remaining characters are only whitespace or ']' or 452 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 453 | 454 | if (/^[\],:{}\s]*$/. 455 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 456 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 457 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 458 | 459 | // In the third stage we use the eval function to compile the text into a 460 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 461 | // in JavaScript: it can begin a block or an object literal. We wrap the text 462 | // in parens to eliminate the ambiguity. 463 | 464 | j = eval('(' + text + ')'); 465 | 466 | // In the optional fourth stage, we recursively walk the new structure, passing 467 | // each name/value pair to a reviver function for possible transformation. 468 | 469 | return typeof reviver === 'function' ? 470 | walk({'': j}, '') : j; 471 | } 472 | 473 | // If the text is not JSON parseable, then a SyntaxError is thrown. 474 | 475 | throw new SyntaxError('JSON.parse'); 476 | }; 477 | } 478 | }()); 479 | -------------------------------------------------------------------------------- /jasmine-core/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporterHelpers = {}; 2 | 3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { 4 | var el = document.createElement(type); 5 | 6 | for (var i = 2; i < arguments.length; i++) { 7 | var child = arguments[i]; 8 | 9 | if (typeof child === 'string') { 10 | el.appendChild(document.createTextNode(child)); 11 | } else { 12 | if (child) { 13 | el.appendChild(child); 14 | } 15 | } 16 | } 17 | 18 | for (var attr in attrs) { 19 | if (attr == "className") { 20 | el[attr] = attrs[attr]; 21 | } else { 22 | el.setAttribute(attr, attrs[attr]); 23 | } 24 | } 25 | 26 | return el; 27 | }; 28 | 29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { 30 | var results = child.results(); 31 | var status = results.passed() ? 'passed' : 'failed'; 32 | if (results.skipped) { 33 | status = 'skipped'; 34 | } 35 | 36 | return status; 37 | }; 38 | 39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { 40 | var parentDiv = this.dom.summary; 41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; 42 | var parent = child[parentSuite]; 43 | 44 | if (parent) { 45 | if (typeof this.views.suites[parent.id] == 'undefined') { 46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); 47 | } 48 | parentDiv = this.views.suites[parent.id].element; 49 | } 50 | 51 | parentDiv.appendChild(childElement); 52 | }; 53 | 54 | 55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { 56 | for(var fn in jasmine.HtmlReporterHelpers) { 57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; 58 | } 59 | }; 60 | 61 | jasmine.HtmlReporter = function(_doc) { 62 | var self = this; 63 | var doc = _doc || window.document; 64 | 65 | var reporterView; 66 | 67 | var dom = {}; 68 | 69 | // Jasmine Reporter Public Interface 70 | self.logRunningSpecs = false; 71 | 72 | self.reportRunnerStarting = function(runner) { 73 | var specs = runner.specs() || []; 74 | 75 | if (specs.length == 0) { 76 | return; 77 | } 78 | 79 | createReporterDom(runner.env.versionString()); 80 | doc.body.appendChild(dom.reporter); 81 | setExceptionHandling(); 82 | 83 | reporterView = new jasmine.HtmlReporter.ReporterView(dom); 84 | reporterView.addSpecs(specs, self.specFilter); 85 | }; 86 | 87 | self.reportRunnerResults = function(runner) { 88 | reporterView && reporterView.complete(); 89 | }; 90 | 91 | self.reportSuiteResults = function(suite) { 92 | reporterView.suiteComplete(suite); 93 | }; 94 | 95 | self.reportSpecStarting = function(spec) { 96 | if (self.logRunningSpecs) { 97 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 98 | } 99 | }; 100 | 101 | self.reportSpecResults = function(spec) { 102 | reporterView.specComplete(spec); 103 | }; 104 | 105 | self.log = function() { 106 | var console = jasmine.getGlobal().console; 107 | if (console && console.log) { 108 | if (console.log.apply) { 109 | console.log.apply(console, arguments); 110 | } else { 111 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 112 | } 113 | } 114 | }; 115 | 116 | self.specFilter = function(spec) { 117 | if (!focusedSpecName()) { 118 | return true; 119 | } 120 | 121 | return spec.getFullName().indexOf(focusedSpecName()) === 0; 122 | }; 123 | 124 | return self; 125 | 126 | function focusedSpecName() { 127 | var specName; 128 | 129 | (function memoizeFocusedSpec() { 130 | if (specName) { 131 | return; 132 | } 133 | 134 | var paramMap = []; 135 | var params = jasmine.HtmlReporter.parameters(doc); 136 | 137 | for (var i = 0; i < params.length; i++) { 138 | var p = params[i].split('='); 139 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 140 | } 141 | 142 | specName = paramMap.spec; 143 | })(); 144 | 145 | return specName; 146 | } 147 | 148 | function createReporterDom(version) { 149 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, 150 | dom.banner = self.createDom('div', { className: 'banner' }, 151 | self.createDom('span', { className: 'title' }, "Jasmine "), 152 | self.createDom('span', { className: 'version' }, version)), 153 | 154 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), 155 | dom.alert = self.createDom('div', {className: 'alert'}, 156 | self.createDom('span', { className: 'exceptions' }, 157 | self.createDom('label', { className: 'label', for: 'no_try_catch' }, 'No try/catch'), 158 | self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), 159 | dom.results = self.createDom('div', {className: 'results'}, 160 | dom.summary = self.createDom('div', { className: 'summary' }), 161 | dom.details = self.createDom('div', { id: 'details' })) 162 | ); 163 | } 164 | 165 | function noTryCatch() { 166 | return window.location.search.match(/catch=false/); 167 | } 168 | 169 | function searchWithCatch() { 170 | var params = jasmine.HtmlReporter.parameters(window.document); 171 | var removed = false; 172 | var i = 0; 173 | 174 | while (!removed && i < params.length) { 175 | if (params[i].match(/catch=/)) { 176 | params.splice(i, 1); 177 | removed = true; 178 | } 179 | i++; 180 | } 181 | if (jasmine.CATCH_EXCEPTIONS) { 182 | params.push("catch=false"); 183 | } 184 | 185 | return params.join("&"); 186 | } 187 | 188 | function setExceptionHandling() { 189 | var chxCatch = document.getElementById('no_try_catch'); 190 | 191 | if (noTryCatch()) { 192 | chxCatch.setAttribute('checked', true); 193 | jasmine.CATCH_EXCEPTIONS = false; 194 | } 195 | chxCatch.onclick = function() { 196 | window.location.search = searchWithCatch(); 197 | }; 198 | } 199 | }; 200 | jasmine.HtmlReporter.parameters = function(doc) { 201 | var paramStr = doc.location.search.substring(1); 202 | var params = []; 203 | 204 | if (paramStr.length > 0) { 205 | params = paramStr.split('&'); 206 | } 207 | return params; 208 | } 209 | jasmine.HtmlReporter.sectionLink = function(sectionName) { 210 | var link = '?'; 211 | var params = []; 212 | 213 | if (sectionName) { 214 | params.push('spec=' + encodeURIComponent(sectionName)); 215 | } 216 | if (!jasmine.CATCH_EXCEPTIONS) { 217 | params.push("catch=false"); 218 | } 219 | if (params.length > 0) { 220 | link += params.join("&"); 221 | } 222 | 223 | return link; 224 | }; 225 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); 226 | jasmine.HtmlReporter.ReporterView = function(dom) { 227 | this.startedAt = new Date(); 228 | this.runningSpecCount = 0; 229 | this.completeSpecCount = 0; 230 | this.passedCount = 0; 231 | this.failedCount = 0; 232 | this.skippedCount = 0; 233 | 234 | this.createResultsMenu = function() { 235 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, 236 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), 237 | ' | ', 238 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); 239 | 240 | this.summaryMenuItem.onclick = function() { 241 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); 242 | }; 243 | 244 | this.detailsMenuItem.onclick = function() { 245 | showDetails(); 246 | }; 247 | }; 248 | 249 | this.addSpecs = function(specs, specFilter) { 250 | this.totalSpecCount = specs.length; 251 | 252 | this.views = { 253 | specs: {}, 254 | suites: {} 255 | }; 256 | 257 | for (var i = 0; i < specs.length; i++) { 258 | var spec = specs[i]; 259 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); 260 | if (specFilter(spec)) { 261 | this.runningSpecCount++; 262 | } 263 | } 264 | }; 265 | 266 | this.specComplete = function(spec) { 267 | this.completeSpecCount++; 268 | 269 | if (isUndefined(this.views.specs[spec.id])) { 270 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); 271 | } 272 | 273 | var specView = this.views.specs[spec.id]; 274 | 275 | switch (specView.status()) { 276 | case 'passed': 277 | this.passedCount++; 278 | break; 279 | 280 | case 'failed': 281 | this.failedCount++; 282 | break; 283 | 284 | case 'skipped': 285 | this.skippedCount++; 286 | break; 287 | } 288 | 289 | specView.refresh(); 290 | this.refresh(); 291 | }; 292 | 293 | this.suiteComplete = function(suite) { 294 | var suiteView = this.views.suites[suite.id]; 295 | if (isUndefined(suiteView)) { 296 | return; 297 | } 298 | suiteView.refresh(); 299 | }; 300 | 301 | this.refresh = function() { 302 | 303 | if (isUndefined(this.resultsMenu)) { 304 | this.createResultsMenu(); 305 | } 306 | 307 | // currently running UI 308 | if (isUndefined(this.runningAlert)) { 309 | this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); 310 | dom.alert.appendChild(this.runningAlert); 311 | } 312 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); 313 | 314 | // skipped specs UI 315 | if (isUndefined(this.skippedAlert)) { 316 | this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); 317 | } 318 | 319 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 320 | 321 | if (this.skippedCount === 1 && isDefined(dom.alert)) { 322 | dom.alert.appendChild(this.skippedAlert); 323 | } 324 | 325 | // passing specs UI 326 | if (isUndefined(this.passedAlert)) { 327 | this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); 328 | } 329 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); 330 | 331 | // failing specs UI 332 | if (isUndefined(this.failedAlert)) { 333 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); 334 | } 335 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); 336 | 337 | if (this.failedCount === 1 && isDefined(dom.alert)) { 338 | dom.alert.appendChild(this.failedAlert); 339 | dom.alert.appendChild(this.resultsMenu); 340 | } 341 | 342 | // summary info 343 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); 344 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; 345 | }; 346 | 347 | this.complete = function() { 348 | dom.alert.removeChild(this.runningAlert); 349 | 350 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 351 | 352 | if (this.failedCount === 0) { 353 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); 354 | } else { 355 | showDetails(); 356 | } 357 | 358 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); 359 | }; 360 | 361 | return this; 362 | 363 | function showDetails() { 364 | if (dom.reporter.className.search(/showDetails/) === -1) { 365 | dom.reporter.className += " showDetails"; 366 | } 367 | } 368 | 369 | function isUndefined(obj) { 370 | return typeof obj === 'undefined'; 371 | } 372 | 373 | function isDefined(obj) { 374 | return !isUndefined(obj); 375 | } 376 | 377 | function specPluralizedFor(count) { 378 | var str = count + " spec"; 379 | if (count > 1) { 380 | str += "s" 381 | } 382 | return str; 383 | } 384 | 385 | }; 386 | 387 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); 388 | 389 | 390 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) { 391 | this.spec = spec; 392 | this.dom = dom; 393 | this.views = views; 394 | 395 | this.symbol = this.createDom('li', { className: 'pending' }); 396 | this.dom.symbolSummary.appendChild(this.symbol); 397 | 398 | this.summary = this.createDom('div', { className: 'specSummary' }, 399 | this.createDom('a', { 400 | className: 'description', 401 | href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), 402 | title: this.spec.getFullName() 403 | }, this.spec.description) 404 | ); 405 | 406 | this.detail = this.createDom('div', { className: 'specDetail' }, 407 | this.createDom('a', { 408 | className: 'description', 409 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 410 | title: this.spec.getFullName() 411 | }, this.spec.getFullName()) 412 | ); 413 | }; 414 | 415 | jasmine.HtmlReporter.SpecView.prototype.status = function() { 416 | return this.getSpecStatus(this.spec); 417 | }; 418 | 419 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() { 420 | this.symbol.className = this.status(); 421 | 422 | switch (this.status()) { 423 | case 'skipped': 424 | break; 425 | 426 | case 'passed': 427 | this.appendSummaryToSuiteDiv(); 428 | break; 429 | 430 | case 'failed': 431 | this.appendSummaryToSuiteDiv(); 432 | this.appendFailureDetail(); 433 | break; 434 | } 435 | }; 436 | 437 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { 438 | this.summary.className += ' ' + this.status(); 439 | this.appendToSummary(this.spec, this.summary); 440 | }; 441 | 442 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { 443 | this.detail.className += ' ' + this.status(); 444 | 445 | var resultItems = this.spec.results().getItems(); 446 | var messagesDiv = this.createDom('div', { className: 'messages' }); 447 | 448 | for (var i = 0; i < resultItems.length; i++) { 449 | var result = resultItems[i]; 450 | 451 | if (result.type == 'log') { 452 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 453 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 454 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 455 | 456 | if (result.trace.stack) { 457 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 458 | } 459 | } 460 | } 461 | 462 | if (messagesDiv.childNodes.length > 0) { 463 | this.detail.appendChild(messagesDiv); 464 | this.dom.details.appendChild(this.detail); 465 | } 466 | }; 467 | 468 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { 469 | this.suite = suite; 470 | this.dom = dom; 471 | this.views = views; 472 | 473 | this.element = this.createDom('div', { className: 'suite' }, 474 | this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) 475 | ); 476 | 477 | this.appendToSummary(this.suite, this.element); 478 | }; 479 | 480 | jasmine.HtmlReporter.SuiteView.prototype.status = function() { 481 | return this.getSpecStatus(this.suite); 482 | }; 483 | 484 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { 485 | this.element.className += " " + this.status(); 486 | }; 487 | 488 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); 489 | 490 | /* @deprecated Use jasmine.HtmlReporter instead 491 | */ 492 | jasmine.TrivialReporter = function(doc) { 493 | this.document = doc || document; 494 | this.suiteDivs = {}; 495 | this.logRunningSpecs = false; 496 | }; 497 | 498 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 499 | var el = document.createElement(type); 500 | 501 | for (var i = 2; i < arguments.length; i++) { 502 | var child = arguments[i]; 503 | 504 | if (typeof child === 'string') { 505 | el.appendChild(document.createTextNode(child)); 506 | } else { 507 | if (child) { el.appendChild(child); } 508 | } 509 | } 510 | 511 | for (var attr in attrs) { 512 | if (attr == "className") { 513 | el[attr] = attrs[attr]; 514 | } else { 515 | el.setAttribute(attr, attrs[attr]); 516 | } 517 | } 518 | 519 | return el; 520 | }; 521 | 522 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 523 | var showPassed, showSkipped; 524 | 525 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, 526 | this.createDom('div', { className: 'banner' }, 527 | this.createDom('div', { className: 'logo' }, 528 | this.createDom('span', { className: 'title' }, "Jasmine"), 529 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 530 | this.createDom('div', { className: 'options' }, 531 | "Show ", 532 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 533 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 534 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 535 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 536 | ) 537 | ), 538 | 539 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 540 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 541 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 542 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 543 | ); 544 | 545 | this.document.body.appendChild(this.outerDiv); 546 | 547 | var suites = runner.suites(); 548 | for (var i = 0; i < suites.length; i++) { 549 | var suite = suites[i]; 550 | var suiteDiv = this.createDom('div', { className: 'suite' }, 551 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 552 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 553 | this.suiteDivs[suite.id] = suiteDiv; 554 | var parentDiv = this.outerDiv; 555 | if (suite.parentSuite) { 556 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 557 | } 558 | parentDiv.appendChild(suiteDiv); 559 | } 560 | 561 | this.startedAt = new Date(); 562 | 563 | var self = this; 564 | showPassed.onclick = function(evt) { 565 | if (showPassed.checked) { 566 | self.outerDiv.className += ' show-passed'; 567 | } else { 568 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 569 | } 570 | }; 571 | 572 | showSkipped.onclick = function(evt) { 573 | if (showSkipped.checked) { 574 | self.outerDiv.className += ' show-skipped'; 575 | } else { 576 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 577 | } 578 | }; 579 | }; 580 | 581 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 582 | var results = runner.results(); 583 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 584 | this.runnerDiv.setAttribute("class", className); 585 | //do it twice for IE 586 | this.runnerDiv.setAttribute("className", className); 587 | var specs = runner.specs(); 588 | var specCount = 0; 589 | for (var i = 0; i < specs.length; i++) { 590 | if (this.specFilter(specs[i])) { 591 | specCount++; 592 | } 593 | } 594 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 595 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 596 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 597 | 598 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 599 | }; 600 | 601 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 602 | var results = suite.results(); 603 | var status = results.passed() ? 'passed' : 'failed'; 604 | if (results.totalCount === 0) { // todo: change this to check results.skipped 605 | status = 'skipped'; 606 | } 607 | this.suiteDivs[suite.id].className += " " + status; 608 | }; 609 | 610 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 611 | if (this.logRunningSpecs) { 612 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 613 | } 614 | }; 615 | 616 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 617 | var results = spec.results(); 618 | var status = results.passed() ? 'passed' : 'failed'; 619 | if (results.skipped) { 620 | status = 'skipped'; 621 | } 622 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 623 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 624 | this.createDom('a', { 625 | className: 'description', 626 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 627 | title: spec.getFullName() 628 | }, spec.description)); 629 | 630 | 631 | var resultItems = results.getItems(); 632 | var messagesDiv = this.createDom('div', { className: 'messages' }); 633 | for (var i = 0; i < resultItems.length; i++) { 634 | var result = resultItems[i]; 635 | 636 | if (result.type == 'log') { 637 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 638 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 639 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 640 | 641 | if (result.trace.stack) { 642 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 643 | } 644 | } 645 | } 646 | 647 | if (messagesDiv.childNodes.length > 0) { 648 | specDiv.appendChild(messagesDiv); 649 | } 650 | 651 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 652 | }; 653 | 654 | jasmine.TrivialReporter.prototype.log = function() { 655 | var console = jasmine.getGlobal().console; 656 | if (console && console.log) { 657 | if (console.log.apply) { 658 | console.log.apply(console, arguments); 659 | } else { 660 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 661 | } 662 | } 663 | }; 664 | 665 | jasmine.TrivialReporter.prototype.getLocation = function() { 666 | return this.document.location; 667 | }; 668 | 669 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 670 | var paramMap = {}; 671 | var params = this.getLocation().search.substring(1).split('&'); 672 | for (var i = 0; i < params.length; i++) { 673 | var p = params[i].split('='); 674 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 675 | } 676 | 677 | if (!paramMap.spec) { 678 | return true; 679 | } 680 | return spec.getFullName().indexOf(paramMap.spec) === 0; 681 | }; 682 | -------------------------------------------------------------------------------- /jasmine-core/jasmine.js: -------------------------------------------------------------------------------- 1 | var isCommonJS = typeof window == "undefined"; 2 | 3 | /** 4 | * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. 5 | * 6 | * @namespace 7 | */ 8 | var jasmine = {}; 9 | if (isCommonJS) exports.jasmine = jasmine; 10 | /** 11 | * @private 12 | */ 13 | jasmine.unimplementedMethod_ = function() { 14 | throw new Error("unimplemented method"); 15 | }; 16 | 17 | /** 18 | * Use jasmine.undefined instead of undefined, since undefined is just 19 | * a plain old variable and may be redefined by somebody else. 20 | * 21 | * @private 22 | */ 23 | jasmine.undefined = jasmine.___undefined___; 24 | 25 | /** 26 | * Show diagnostic messages in the console if set to true 27 | * 28 | */ 29 | jasmine.VERBOSE = false; 30 | 31 | /** 32 | * 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. 33 | * 34 | */ 35 | jasmine.DEFAULT_UPDATE_INTERVAL = 250; 36 | 37 | /** 38 | * Default timeout interval in milliseconds for waitsFor() blocks. 39 | */ 40 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; 41 | 42 | /** 43 | * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite. 44 | * Set to false to let the exception bubble up in the browser. 45 | * 46 | */ 47 | jasmine.CATCH_EXCEPTIONS = true; 48 | 49 | jasmine.getGlobal = function() { 50 | function getGlobal() { 51 | return this; 52 | } 53 | 54 | return getGlobal(); 55 | }; 56 | 57 | /** 58 | * Allows for bound functions to be compared. Internal use only. 59 | * 60 | * @ignore 61 | * @private 62 | * @param base {Object} bound 'this' for the function 63 | * @param name {Function} function to find 64 | */ 65 | jasmine.bindOriginal_ = function(base, name) { 66 | var original = base[name]; 67 | if (original.apply) { 68 | return function() { 69 | return original.apply(base, arguments); 70 | }; 71 | } else { 72 | // IE support 73 | return jasmine.getGlobal()[name]; 74 | } 75 | }; 76 | 77 | jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); 78 | jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); 79 | jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); 80 | jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); 81 | 82 | jasmine.MessageResult = function(values) { 83 | this.type = 'log'; 84 | this.values = values; 85 | this.trace = new Error(); // todo: test better 86 | }; 87 | 88 | jasmine.MessageResult.prototype.toString = function() { 89 | var text = ""; 90 | for (var i = 0; i < this.values.length; i++) { 91 | if (i > 0) text += " "; 92 | if (jasmine.isString_(this.values[i])) { 93 | text += this.values[i]; 94 | } else { 95 | text += jasmine.pp(this.values[i]); 96 | } 97 | } 98 | return text; 99 | }; 100 | 101 | jasmine.ExpectationResult = function(params) { 102 | this.type = 'expect'; 103 | this.matcherName = params.matcherName; 104 | this.passed_ = params.passed; 105 | this.expected = params.expected; 106 | this.actual = params.actual; 107 | this.message = this.passed_ ? 'Passed.' : params.message; 108 | 109 | var trace = (params.trace || new Error(this.message)); 110 | this.trace = this.passed_ ? '' : trace; 111 | }; 112 | 113 | jasmine.ExpectationResult.prototype.toString = function () { 114 | return this.message; 115 | }; 116 | 117 | jasmine.ExpectationResult.prototype.passed = function () { 118 | return this.passed_; 119 | }; 120 | 121 | /** 122 | * Getter for the Jasmine environment. Ensures one gets created 123 | */ 124 | jasmine.getEnv = function() { 125 | var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); 126 | return env; 127 | }; 128 | 129 | /** 130 | * @ignore 131 | * @private 132 | * @param value 133 | * @returns {Boolean} 134 | */ 135 | jasmine.isArray_ = function(value) { 136 | return jasmine.isA_("Array", value); 137 | }; 138 | 139 | /** 140 | * @ignore 141 | * @private 142 | * @param value 143 | * @returns {Boolean} 144 | */ 145 | jasmine.isString_ = function(value) { 146 | return jasmine.isA_("String", value); 147 | }; 148 | 149 | /** 150 | * @ignore 151 | * @private 152 | * @param value 153 | * @returns {Boolean} 154 | */ 155 | jasmine.isNumber_ = function(value) { 156 | return jasmine.isA_("Number", value); 157 | }; 158 | 159 | /** 160 | * @ignore 161 | * @private 162 | * @param {String} typeName 163 | * @param value 164 | * @returns {Boolean} 165 | */ 166 | jasmine.isA_ = function(typeName, value) { 167 | return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; 168 | }; 169 | 170 | /** 171 | * Pretty printer for expecations. Takes any object and turns it into a human-readable string. 172 | * 173 | * @param value {Object} an object to be outputted 174 | * @returns {String} 175 | */ 176 | jasmine.pp = function(value) { 177 | var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); 178 | stringPrettyPrinter.format(value); 179 | return stringPrettyPrinter.string; 180 | }; 181 | 182 | /** 183 | * Returns true if the object is a DOM Node. 184 | * 185 | * @param {Object} obj object to check 186 | * @returns {Boolean} 187 | */ 188 | jasmine.isDomNode = function(obj) { 189 | return obj.nodeType > 0; 190 | }; 191 | 192 | /** 193 | * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. 194 | * 195 | * @example 196 | * // don't care about which function is passed in, as long as it's a function 197 | * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); 198 | * 199 | * @param {Class} clazz 200 | * @returns matchable object of the type clazz 201 | */ 202 | jasmine.any = function(clazz) { 203 | return new jasmine.Matchers.Any(clazz); 204 | }; 205 | 206 | /** 207 | * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the 208 | * attributes on the object. 209 | * 210 | * @example 211 | * // don't care about any other attributes than foo. 212 | * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); 213 | * 214 | * @param sample {Object} sample 215 | * @returns matchable object for the sample 216 | */ 217 | jasmine.objectContaining = function (sample) { 218 | return new jasmine.Matchers.ObjectContaining(sample); 219 | }; 220 | 221 | /** 222 | * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. 223 | * 224 | * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine 225 | * expectation syntax. Spies can be checked if they were called or not and what the calling params were. 226 | * 227 | * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). 228 | * 229 | * Spies are torn down at the end of every spec. 230 | * 231 | * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. 232 | * 233 | * @example 234 | * // a stub 235 | * var myStub = jasmine.createSpy('myStub'); // can be used anywhere 236 | * 237 | * // spy example 238 | * var foo = { 239 | * not: function(bool) { return !bool; } 240 | * } 241 | * 242 | * // actual foo.not will not be called, execution stops 243 | * spyOn(foo, 'not'); 244 | 245 | // foo.not spied upon, execution will continue to implementation 246 | * spyOn(foo, 'not').andCallThrough(); 247 | * 248 | * // fake example 249 | * var foo = { 250 | * not: function(bool) { return !bool; } 251 | * } 252 | * 253 | * // foo.not(val) will return val 254 | * spyOn(foo, 'not').andCallFake(function(value) {return value;}); 255 | * 256 | * // mock example 257 | * foo.not(7 == 7); 258 | * expect(foo.not).toHaveBeenCalled(); 259 | * expect(foo.not).toHaveBeenCalledWith(true); 260 | * 261 | * @constructor 262 | * @see spyOn, jasmine.createSpy, jasmine.createSpyObj 263 | * @param {String} name 264 | */ 265 | jasmine.Spy = function(name) { 266 | /** 267 | * The name of the spy, if provided. 268 | */ 269 | this.identity = name || 'unknown'; 270 | /** 271 | * Is this Object a spy? 272 | */ 273 | this.isSpy = true; 274 | /** 275 | * The actual function this spy stubs. 276 | */ 277 | this.plan = function() { 278 | }; 279 | /** 280 | * Tracking of the most recent call to the spy. 281 | * @example 282 | * var mySpy = jasmine.createSpy('foo'); 283 | * mySpy(1, 2); 284 | * mySpy.mostRecentCall.args = [1, 2]; 285 | */ 286 | this.mostRecentCall = {}; 287 | 288 | /** 289 | * Holds arguments for each call to the spy, indexed by call count 290 | * @example 291 | * var mySpy = jasmine.createSpy('foo'); 292 | * mySpy(1, 2); 293 | * mySpy(7, 8); 294 | * mySpy.mostRecentCall.args = [7, 8]; 295 | * mySpy.argsForCall[0] = [1, 2]; 296 | * mySpy.argsForCall[1] = [7, 8]; 297 | */ 298 | this.argsForCall = []; 299 | this.calls = []; 300 | }; 301 | 302 | /** 303 | * Tells a spy to call through to the actual implemenatation. 304 | * 305 | * @example 306 | * var foo = { 307 | * bar: function() { // do some stuff } 308 | * } 309 | * 310 | * // defining a spy on an existing property: foo.bar 311 | * spyOn(foo, 'bar').andCallThrough(); 312 | */ 313 | jasmine.Spy.prototype.andCallThrough = function() { 314 | this.plan = this.originalValue; 315 | return this; 316 | }; 317 | 318 | /** 319 | * For setting the return value of a spy. 320 | * 321 | * @example 322 | * // defining a spy from scratch: foo() returns 'baz' 323 | * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); 324 | * 325 | * // defining a spy on an existing property: foo.bar() returns 'baz' 326 | * spyOn(foo, 'bar').andReturn('baz'); 327 | * 328 | * @param {Object} value 329 | */ 330 | jasmine.Spy.prototype.andReturn = function(value) { 331 | this.plan = function() { 332 | return value; 333 | }; 334 | return this; 335 | }; 336 | 337 | /** 338 | * For throwing an exception when a spy is called. 339 | * 340 | * @example 341 | * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' 342 | * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); 343 | * 344 | * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' 345 | * spyOn(foo, 'bar').andThrow('baz'); 346 | * 347 | * @param {String} exceptionMsg 348 | */ 349 | jasmine.Spy.prototype.andThrow = function(exceptionMsg) { 350 | this.plan = function() { 351 | throw exceptionMsg; 352 | }; 353 | return this; 354 | }; 355 | 356 | /** 357 | * Calls an alternate implementation when a spy is called. 358 | * 359 | * @example 360 | * var baz = function() { 361 | * // do some stuff, return something 362 | * } 363 | * // defining a spy from scratch: foo() calls the function baz 364 | * var foo = jasmine.createSpy('spy on foo').andCall(baz); 365 | * 366 | * // defining a spy on an existing property: foo.bar() calls an anonymnous function 367 | * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); 368 | * 369 | * @param {Function} fakeFunc 370 | */ 371 | jasmine.Spy.prototype.andCallFake = function(fakeFunc) { 372 | this.plan = fakeFunc; 373 | return this; 374 | }; 375 | 376 | /** 377 | * Resets all of a spy's the tracking variables so that it can be used again. 378 | * 379 | * @example 380 | * spyOn(foo, 'bar'); 381 | * 382 | * foo.bar(); 383 | * 384 | * expect(foo.bar.callCount).toEqual(1); 385 | * 386 | * foo.bar.reset(); 387 | * 388 | * expect(foo.bar.callCount).toEqual(0); 389 | */ 390 | jasmine.Spy.prototype.reset = function() { 391 | this.wasCalled = false; 392 | this.callCount = 0; 393 | this.argsForCall = []; 394 | this.calls = []; 395 | this.mostRecentCall = {}; 396 | }; 397 | 398 | jasmine.createSpy = function(name) { 399 | 400 | var spyObj = function() { 401 | spyObj.wasCalled = true; 402 | spyObj.callCount++; 403 | var args = jasmine.util.argsToArray(arguments); 404 | spyObj.mostRecentCall.object = this; 405 | spyObj.mostRecentCall.args = args; 406 | spyObj.argsForCall.push(args); 407 | spyObj.calls.push({object: this, args: args}); 408 | return spyObj.plan.apply(this, arguments); 409 | }; 410 | 411 | var spy = new jasmine.Spy(name); 412 | 413 | for (var prop in spy) { 414 | spyObj[prop] = spy[prop]; 415 | } 416 | 417 | spyObj.reset(); 418 | 419 | return spyObj; 420 | }; 421 | 422 | /** 423 | * Determines whether an object is a spy. 424 | * 425 | * @param {jasmine.Spy|Object} putativeSpy 426 | * @returns {Boolean} 427 | */ 428 | jasmine.isSpy = function(putativeSpy) { 429 | return putativeSpy && putativeSpy.isSpy; 430 | }; 431 | 432 | /** 433 | * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something 434 | * large in one call. 435 | * 436 | * @param {String} baseName name of spy class 437 | * @param {Array} methodNames array of names of methods to make spies 438 | */ 439 | jasmine.createSpyObj = function(baseName, methodNames) { 440 | if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { 441 | throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); 442 | } 443 | var obj = {}; 444 | for (var i = 0; i < methodNames.length; i++) { 445 | obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); 446 | } 447 | return obj; 448 | }; 449 | 450 | /** 451 | * All parameters are pretty-printed and concatenated together, then written to the current spec's output. 452 | * 453 | * Be careful not to leave calls to jasmine.log in production code. 454 | */ 455 | jasmine.log = function() { 456 | var spec = jasmine.getEnv().currentSpec; 457 | spec.log.apply(spec, arguments); 458 | }; 459 | 460 | /** 461 | * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. 462 | * 463 | * @example 464 | * // spy example 465 | * var foo = { 466 | * not: function(bool) { return !bool; } 467 | * } 468 | * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops 469 | * 470 | * @see jasmine.createSpy 471 | * @param obj 472 | * @param methodName 473 | * @returns a Jasmine spy that can be chained with all spy methods 474 | */ 475 | var spyOn = function(obj, methodName) { 476 | return jasmine.getEnv().currentSpec.spyOn(obj, methodName); 477 | }; 478 | if (isCommonJS) exports.spyOn = spyOn; 479 | 480 | /** 481 | * Creates a Jasmine spec that will be added to the current suite. 482 | * 483 | * // TODO: pending tests 484 | * 485 | * @example 486 | * it('should be true', function() { 487 | * expect(true).toEqual(true); 488 | * }); 489 | * 490 | * @param {String} desc description of this specification 491 | * @param {Function} func defines the preconditions and expectations of the spec 492 | */ 493 | var it = function(desc, func) { 494 | return jasmine.getEnv().it(desc, func); 495 | }; 496 | if (isCommonJS) exports.it = it; 497 | 498 | /** 499 | * Creates a disabled Jasmine spec. 500 | * 501 | * A convenience method that allows existing specs to be disabled temporarily during development. 502 | * 503 | * @param {String} desc description of this specification 504 | * @param {Function} func defines the preconditions and expectations of the spec 505 | */ 506 | var xit = function(desc, func) { 507 | return jasmine.getEnv().xit(desc, func); 508 | }; 509 | if (isCommonJS) exports.xit = xit; 510 | 511 | /** 512 | * Starts a chain for a Jasmine expectation. 513 | * 514 | * It is passed an Object that is the actual value and should chain to one of the many 515 | * jasmine.Matchers functions. 516 | * 517 | * @param {Object} actual Actual value to test against and expected value 518 | */ 519 | var expect = function(actual) { 520 | return jasmine.getEnv().currentSpec.expect(actual); 521 | }; 522 | if (isCommonJS) exports.expect = expect; 523 | 524 | /** 525 | * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. 526 | * 527 | * @param {Function} func Function that defines part of a jasmine spec. 528 | */ 529 | var runs = function(func) { 530 | jasmine.getEnv().currentSpec.runs(func); 531 | }; 532 | if (isCommonJS) exports.runs = runs; 533 | 534 | /** 535 | * Waits a fixed time period before moving to the next block. 536 | * 537 | * @deprecated Use waitsFor() instead 538 | * @param {Number} timeout milliseconds to wait 539 | */ 540 | var waits = function(timeout) { 541 | jasmine.getEnv().currentSpec.waits(timeout); 542 | }; 543 | if (isCommonJS) exports.waits = waits; 544 | 545 | /** 546 | * Waits for the latchFunction to return true before proceeding to the next block. 547 | * 548 | * @param {Function} latchFunction 549 | * @param {String} optional_timeoutMessage 550 | * @param {Number} optional_timeout 551 | */ 552 | var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 553 | jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); 554 | }; 555 | if (isCommonJS) exports.waitsFor = waitsFor; 556 | 557 | /** 558 | * A function that is called before each spec in a suite. 559 | * 560 | * Used for spec setup, including validating assumptions. 561 | * 562 | * @param {Function} beforeEachFunction 563 | */ 564 | var beforeEach = function(beforeEachFunction) { 565 | jasmine.getEnv().beforeEach(beforeEachFunction); 566 | }; 567 | if (isCommonJS) exports.beforeEach = beforeEach; 568 | 569 | /** 570 | * A function that is called after each spec in a suite. 571 | * 572 | * Used for restoring any state that is hijacked during spec execution. 573 | * 574 | * @param {Function} afterEachFunction 575 | */ 576 | var afterEach = function(afterEachFunction) { 577 | jasmine.getEnv().afterEach(afterEachFunction); 578 | }; 579 | if (isCommonJS) exports.afterEach = afterEach; 580 | 581 | /** 582 | * Defines a suite of specifications. 583 | * 584 | * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared 585 | * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization 586 | * of setup in some tests. 587 | * 588 | * @example 589 | * // TODO: a simple suite 590 | * 591 | * // TODO: a simple suite with a nested describe block 592 | * 593 | * @param {String} description A string, usually the class under test. 594 | * @param {Function} specDefinitions function that defines several specs. 595 | */ 596 | var describe = function(description, specDefinitions) { 597 | return jasmine.getEnv().describe(description, specDefinitions); 598 | }; 599 | if (isCommonJS) exports.describe = describe; 600 | 601 | /** 602 | * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. 603 | * 604 | * @param {String} description A string, usually the class under test. 605 | * @param {Function} specDefinitions function that defines several specs. 606 | */ 607 | var xdescribe = function(description, specDefinitions) { 608 | return jasmine.getEnv().xdescribe(description, specDefinitions); 609 | }; 610 | if (isCommonJS) exports.xdescribe = xdescribe; 611 | 612 | 613 | // Provide the XMLHttpRequest class for IE 5.x-6.x: 614 | jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { 615 | function tryIt(f) { 616 | try { 617 | return f(); 618 | } catch(e) { 619 | } 620 | return null; 621 | } 622 | 623 | var xhr = tryIt(function() { 624 | return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 625 | }) || 626 | tryIt(function() { 627 | return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 628 | }) || 629 | tryIt(function() { 630 | return new ActiveXObject("Msxml2.XMLHTTP"); 631 | }) || 632 | tryIt(function() { 633 | return new ActiveXObject("Microsoft.XMLHTTP"); 634 | }); 635 | 636 | if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); 637 | 638 | return xhr; 639 | } : XMLHttpRequest; 640 | /** 641 | * @namespace 642 | */ 643 | jasmine.util = {}; 644 | 645 | /** 646 | * Declare that a child class inherit it's prototype from the parent class. 647 | * 648 | * @private 649 | * @param {Function} childClass 650 | * @param {Function} parentClass 651 | */ 652 | jasmine.util.inherit = function(childClass, parentClass) { 653 | /** 654 | * @private 655 | */ 656 | var subclass = function() { 657 | }; 658 | subclass.prototype = parentClass.prototype; 659 | childClass.prototype = new subclass(); 660 | }; 661 | 662 | jasmine.util.formatException = function(e) { 663 | var lineNumber; 664 | if (e.line) { 665 | lineNumber = e.line; 666 | } 667 | else if (e.lineNumber) { 668 | lineNumber = e.lineNumber; 669 | } 670 | 671 | var file; 672 | 673 | if (e.sourceURL) { 674 | file = e.sourceURL; 675 | } 676 | else if (e.fileName) { 677 | file = e.fileName; 678 | } 679 | 680 | var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); 681 | 682 | if (file && lineNumber) { 683 | message += ' in ' + file + ' (line ' + lineNumber + ')'; 684 | } 685 | 686 | return message; 687 | }; 688 | 689 | jasmine.util.htmlEscape = function(str) { 690 | if (!str) return str; 691 | return str.replace(/&/g, '&') 692 | .replace(//g, '>'); 694 | }; 695 | 696 | jasmine.util.argsToArray = function(args) { 697 | var arrayOfArgs = []; 698 | for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); 699 | return arrayOfArgs; 700 | }; 701 | 702 | jasmine.util.extend = function(destination, source) { 703 | for (var property in source) destination[property] = source[property]; 704 | return destination; 705 | }; 706 | 707 | /** 708 | * Environment for Jasmine 709 | * 710 | * @constructor 711 | */ 712 | jasmine.Env = function() { 713 | this.currentSpec = null; 714 | this.currentSuite = null; 715 | this.currentRunner_ = new jasmine.Runner(this); 716 | 717 | this.reporter = new jasmine.MultiReporter(); 718 | 719 | this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; 720 | this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; 721 | this.lastUpdate = 0; 722 | this.specFilter = function() { 723 | return true; 724 | }; 725 | 726 | this.nextSpecId_ = 0; 727 | this.nextSuiteId_ = 0; 728 | this.equalityTesters_ = []; 729 | 730 | // wrap matchers 731 | this.matchersClass = function() { 732 | jasmine.Matchers.apply(this, arguments); 733 | }; 734 | jasmine.util.inherit(this.matchersClass, jasmine.Matchers); 735 | 736 | jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); 737 | }; 738 | 739 | 740 | jasmine.Env.prototype.setTimeout = jasmine.setTimeout; 741 | jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; 742 | jasmine.Env.prototype.setInterval = jasmine.setInterval; 743 | jasmine.Env.prototype.clearInterval = jasmine.clearInterval; 744 | 745 | /** 746 | * @returns an object containing jasmine version build info, if set. 747 | */ 748 | jasmine.Env.prototype.version = function () { 749 | if (jasmine.version_) { 750 | return jasmine.version_; 751 | } else { 752 | throw new Error('Version not set'); 753 | } 754 | }; 755 | 756 | /** 757 | * @returns string containing jasmine version build info, if set. 758 | */ 759 | jasmine.Env.prototype.versionString = function() { 760 | if (!jasmine.version_) { 761 | return "version unknown"; 762 | } 763 | 764 | var version = this.version(); 765 | var versionString = version.major + "." + version.minor + "." + version.build; 766 | if (version.release_candidate) { 767 | versionString += ".rc" + version.release_candidate; 768 | } 769 | versionString += " revision " + version.revision; 770 | return versionString; 771 | }; 772 | 773 | /** 774 | * @returns a sequential integer starting at 0 775 | */ 776 | jasmine.Env.prototype.nextSpecId = function () { 777 | return this.nextSpecId_++; 778 | }; 779 | 780 | /** 781 | * @returns a sequential integer starting at 0 782 | */ 783 | jasmine.Env.prototype.nextSuiteId = function () { 784 | return this.nextSuiteId_++; 785 | }; 786 | 787 | /** 788 | * Register a reporter to receive status updates from Jasmine. 789 | * @param {jasmine.Reporter} reporter An object which will receive status updates. 790 | */ 791 | jasmine.Env.prototype.addReporter = function(reporter) { 792 | this.reporter.addReporter(reporter); 793 | }; 794 | 795 | jasmine.Env.prototype.execute = function() { 796 | this.currentRunner_.execute(); 797 | }; 798 | 799 | jasmine.Env.prototype.describe = function(description, specDefinitions) { 800 | var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); 801 | 802 | var parentSuite = this.currentSuite; 803 | if (parentSuite) { 804 | parentSuite.add(suite); 805 | } else { 806 | this.currentRunner_.add(suite); 807 | } 808 | 809 | this.currentSuite = suite; 810 | 811 | var declarationError = null; 812 | try { 813 | specDefinitions.call(suite); 814 | } catch(e) { 815 | declarationError = e; 816 | } 817 | 818 | if (declarationError) { 819 | this.it("encountered a declaration exception", function() { 820 | throw declarationError; 821 | }); 822 | } 823 | 824 | this.currentSuite = parentSuite; 825 | 826 | return suite; 827 | }; 828 | 829 | jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { 830 | if (this.currentSuite) { 831 | this.currentSuite.beforeEach(beforeEachFunction); 832 | } else { 833 | this.currentRunner_.beforeEach(beforeEachFunction); 834 | } 835 | }; 836 | 837 | jasmine.Env.prototype.currentRunner = function () { 838 | return this.currentRunner_; 839 | }; 840 | 841 | jasmine.Env.prototype.afterEach = function(afterEachFunction) { 842 | if (this.currentSuite) { 843 | this.currentSuite.afterEach(afterEachFunction); 844 | } else { 845 | this.currentRunner_.afterEach(afterEachFunction); 846 | } 847 | 848 | }; 849 | 850 | jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { 851 | return { 852 | execute: function() { 853 | } 854 | }; 855 | }; 856 | 857 | jasmine.Env.prototype.it = function(description, func) { 858 | var spec = new jasmine.Spec(this, this.currentSuite, description); 859 | this.currentSuite.add(spec); 860 | this.currentSpec = spec; 861 | 862 | if (func) { 863 | spec.runs(func); 864 | } 865 | 866 | return spec; 867 | }; 868 | 869 | jasmine.Env.prototype.xit = function(desc, func) { 870 | return { 871 | id: this.nextSpecId(), 872 | runs: function() { 873 | } 874 | }; 875 | }; 876 | 877 | jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { 878 | if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { 879 | return true; 880 | } 881 | 882 | a.__Jasmine_been_here_before__ = b; 883 | b.__Jasmine_been_here_before__ = a; 884 | 885 | var hasKey = function(obj, keyName) { 886 | return obj !== null && obj[keyName] !== jasmine.undefined; 887 | }; 888 | 889 | for (var property in b) { 890 | if (!hasKey(a, property) && hasKey(b, property)) { 891 | mismatchKeys.push("expected has key '" + property + "', but missing from actual."); 892 | } 893 | } 894 | for (property in a) { 895 | if (!hasKey(b, property) && hasKey(a, property)) { 896 | mismatchKeys.push("expected missing key '" + property + "', but present in actual."); 897 | } 898 | } 899 | for (property in b) { 900 | if (property == '__Jasmine_been_here_before__') continue; 901 | if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { 902 | 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."); 903 | } 904 | } 905 | 906 | if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { 907 | mismatchValues.push("arrays were not the same length"); 908 | } 909 | 910 | delete a.__Jasmine_been_here_before__; 911 | delete b.__Jasmine_been_here_before__; 912 | return (mismatchKeys.length === 0 && mismatchValues.length === 0); 913 | }; 914 | 915 | jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { 916 | mismatchKeys = mismatchKeys || []; 917 | mismatchValues = mismatchValues || []; 918 | 919 | for (var i = 0; i < this.equalityTesters_.length; i++) { 920 | var equalityTester = this.equalityTesters_[i]; 921 | var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); 922 | if (result !== jasmine.undefined) return result; 923 | } 924 | 925 | if (a === b) return true; 926 | 927 | if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { 928 | return (a == jasmine.undefined && b == jasmine.undefined); 929 | } 930 | 931 | if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { 932 | return a === b; 933 | } 934 | 935 | if (a instanceof Date && b instanceof Date) { 936 | return a.getTime() == b.getTime(); 937 | } 938 | 939 | if (a.jasmineMatches) { 940 | return a.jasmineMatches(b); 941 | } 942 | 943 | if (b.jasmineMatches) { 944 | return b.jasmineMatches(a); 945 | } 946 | 947 | if (a instanceof jasmine.Matchers.ObjectContaining) { 948 | return a.matches(b); 949 | } 950 | 951 | if (b instanceof jasmine.Matchers.ObjectContaining) { 952 | return b.matches(a); 953 | } 954 | 955 | if (jasmine.isString_(a) && jasmine.isString_(b)) { 956 | return (a == b); 957 | } 958 | 959 | if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { 960 | return (a == b); 961 | } 962 | 963 | if (typeof a === "object" && typeof b === "object") { 964 | return this.compareObjects_(a, b, mismatchKeys, mismatchValues); 965 | } 966 | 967 | //Straight check 968 | return (a === b); 969 | }; 970 | 971 | jasmine.Env.prototype.contains_ = function(haystack, needle) { 972 | if (jasmine.isArray_(haystack)) { 973 | for (var i = 0; i < haystack.length; i++) { 974 | if (this.equals_(haystack[i], needle)) return true; 975 | } 976 | return false; 977 | } 978 | return haystack.indexOf(needle) >= 0; 979 | }; 980 | 981 | jasmine.Env.prototype.addEqualityTester = function(equalityTester) { 982 | this.equalityTesters_.push(equalityTester); 983 | }; 984 | /** No-op base class for Jasmine reporters. 985 | * 986 | * @constructor 987 | */ 988 | jasmine.Reporter = function() { 989 | }; 990 | 991 | //noinspection JSUnusedLocalSymbols 992 | jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { 993 | }; 994 | 995 | //noinspection JSUnusedLocalSymbols 996 | jasmine.Reporter.prototype.reportRunnerResults = function(runner) { 997 | }; 998 | 999 | //noinspection JSUnusedLocalSymbols 1000 | jasmine.Reporter.prototype.reportSuiteResults = function(suite) { 1001 | }; 1002 | 1003 | //noinspection JSUnusedLocalSymbols 1004 | jasmine.Reporter.prototype.reportSpecStarting = function(spec) { 1005 | }; 1006 | 1007 | //noinspection JSUnusedLocalSymbols 1008 | jasmine.Reporter.prototype.reportSpecResults = function(spec) { 1009 | }; 1010 | 1011 | //noinspection JSUnusedLocalSymbols 1012 | jasmine.Reporter.prototype.log = function(str) { 1013 | }; 1014 | 1015 | /** 1016 | * Blocks are functions with executable code that make up a spec. 1017 | * 1018 | * @constructor 1019 | * @param {jasmine.Env} env 1020 | * @param {Function} func 1021 | * @param {jasmine.Spec} spec 1022 | */ 1023 | jasmine.Block = function(env, func, spec) { 1024 | this.env = env; 1025 | this.func = func; 1026 | this.spec = spec; 1027 | }; 1028 | 1029 | jasmine.Block.prototype.execute = function(onComplete) { 1030 | if (!jasmine.CATCH_EXCEPTIONS) { 1031 | this.func.apply(this.spec); 1032 | } 1033 | else { 1034 | try { 1035 | this.func.apply(this.spec); 1036 | } catch (e) { 1037 | this.spec.fail(e); 1038 | } 1039 | } 1040 | onComplete(); 1041 | }; 1042 | /** JavaScript API reporter. 1043 | * 1044 | * @constructor 1045 | */ 1046 | jasmine.JsApiReporter = function() { 1047 | this.started = false; 1048 | this.finished = false; 1049 | this.suites_ = []; 1050 | this.results_ = {}; 1051 | }; 1052 | 1053 | jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { 1054 | this.started = true; 1055 | var suites = runner.topLevelSuites(); 1056 | for (var i = 0; i < suites.length; i++) { 1057 | var suite = suites[i]; 1058 | this.suites_.push(this.summarize_(suite)); 1059 | } 1060 | }; 1061 | 1062 | jasmine.JsApiReporter.prototype.suites = function() { 1063 | return this.suites_; 1064 | }; 1065 | 1066 | jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { 1067 | var isSuite = suiteOrSpec instanceof jasmine.Suite; 1068 | var summary = { 1069 | id: suiteOrSpec.id, 1070 | name: suiteOrSpec.description, 1071 | type: isSuite ? 'suite' : 'spec', 1072 | children: [] 1073 | }; 1074 | 1075 | if (isSuite) { 1076 | var children = suiteOrSpec.children(); 1077 | for (var i = 0; i < children.length; i++) { 1078 | summary.children.push(this.summarize_(children[i])); 1079 | } 1080 | } 1081 | return summary; 1082 | }; 1083 | 1084 | jasmine.JsApiReporter.prototype.results = function() { 1085 | return this.results_; 1086 | }; 1087 | 1088 | jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { 1089 | return this.results_[specId]; 1090 | }; 1091 | 1092 | //noinspection JSUnusedLocalSymbols 1093 | jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { 1094 | this.finished = true; 1095 | }; 1096 | 1097 | //noinspection JSUnusedLocalSymbols 1098 | jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { 1099 | }; 1100 | 1101 | //noinspection JSUnusedLocalSymbols 1102 | jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { 1103 | this.results_[spec.id] = { 1104 | messages: spec.results().getItems(), 1105 | result: spec.results().failedCount > 0 ? "failed" : "passed" 1106 | }; 1107 | }; 1108 | 1109 | //noinspection JSUnusedLocalSymbols 1110 | jasmine.JsApiReporter.prototype.log = function(str) { 1111 | }; 1112 | 1113 | jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ 1114 | var results = {}; 1115 | for (var i = 0; i < specIds.length; i++) { 1116 | var specId = specIds[i]; 1117 | results[specId] = this.summarizeResult_(this.results_[specId]); 1118 | } 1119 | return results; 1120 | }; 1121 | 1122 | jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ 1123 | var summaryMessages = []; 1124 | var messagesLength = result.messages.length; 1125 | for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { 1126 | var resultMessage = result.messages[messageIndex]; 1127 | summaryMessages.push({ 1128 | text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, 1129 | passed: resultMessage.passed ? resultMessage.passed() : true, 1130 | type: resultMessage.type, 1131 | message: resultMessage.message, 1132 | trace: { 1133 | stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined 1134 | } 1135 | }); 1136 | } 1137 | 1138 | return { 1139 | result : result.result, 1140 | messages : summaryMessages 1141 | }; 1142 | }; 1143 | 1144 | /** 1145 | * @constructor 1146 | * @param {jasmine.Env} env 1147 | * @param actual 1148 | * @param {jasmine.Spec} spec 1149 | */ 1150 | jasmine.Matchers = function(env, actual, spec, opt_isNot) { 1151 | this.env = env; 1152 | this.actual = actual; 1153 | this.spec = spec; 1154 | this.isNot = opt_isNot || false; 1155 | this.reportWasCalled_ = false; 1156 | }; 1157 | 1158 | // todo: @deprecated as of Jasmine 0.11, remove soon [xw] 1159 | jasmine.Matchers.pp = function(str) { 1160 | throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); 1161 | }; 1162 | 1163 | // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] 1164 | jasmine.Matchers.prototype.report = function(result, failing_message, details) { 1165 | throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); 1166 | }; 1167 | 1168 | jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { 1169 | for (var methodName in prototype) { 1170 | if (methodName == 'report') continue; 1171 | var orig = prototype[methodName]; 1172 | matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); 1173 | } 1174 | }; 1175 | 1176 | jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { 1177 | return function() { 1178 | var matcherArgs = jasmine.util.argsToArray(arguments); 1179 | var result = matcherFunction.apply(this, arguments); 1180 | 1181 | if (this.isNot) { 1182 | result = !result; 1183 | } 1184 | 1185 | if (this.reportWasCalled_) return result; 1186 | 1187 | var message; 1188 | if (!result) { 1189 | if (this.message) { 1190 | message = this.message.apply(this, arguments); 1191 | if (jasmine.isArray_(message)) { 1192 | message = message[this.isNot ? 1 : 0]; 1193 | } 1194 | } else { 1195 | var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); 1196 | message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; 1197 | if (matcherArgs.length > 0) { 1198 | for (var i = 0; i < matcherArgs.length; i++) { 1199 | if (i > 0) message += ","; 1200 | message += " " + jasmine.pp(matcherArgs[i]); 1201 | } 1202 | } 1203 | message += "."; 1204 | } 1205 | } 1206 | var expectationResult = new jasmine.ExpectationResult({ 1207 | matcherName: matcherName, 1208 | passed: result, 1209 | expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], 1210 | actual: this.actual, 1211 | message: message 1212 | }); 1213 | this.spec.addMatcherResult(expectationResult); 1214 | return jasmine.undefined; 1215 | }; 1216 | }; 1217 | 1218 | 1219 | 1220 | 1221 | /** 1222 | * toBe: compares the actual to the expected using === 1223 | * @param expected 1224 | */ 1225 | jasmine.Matchers.prototype.toBe = function(expected) { 1226 | return this.actual === expected; 1227 | }; 1228 | 1229 | /** 1230 | * toNotBe: compares the actual to the expected using !== 1231 | * @param expected 1232 | * @deprecated as of 1.0. Use not.toBe() instead. 1233 | */ 1234 | jasmine.Matchers.prototype.toNotBe = function(expected) { 1235 | return this.actual !== expected; 1236 | }; 1237 | 1238 | /** 1239 | * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. 1240 | * 1241 | * @param expected 1242 | */ 1243 | jasmine.Matchers.prototype.toEqual = function(expected) { 1244 | return this.env.equals_(this.actual, expected); 1245 | }; 1246 | 1247 | /** 1248 | * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual 1249 | * @param expected 1250 | * @deprecated as of 1.0. Use not.toEqual() instead. 1251 | */ 1252 | jasmine.Matchers.prototype.toNotEqual = function(expected) { 1253 | return !this.env.equals_(this.actual, expected); 1254 | }; 1255 | 1256 | /** 1257 | * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes 1258 | * a pattern or a String. 1259 | * 1260 | * @param expected 1261 | */ 1262 | jasmine.Matchers.prototype.toMatch = function(expected) { 1263 | return new RegExp(expected).test(this.actual); 1264 | }; 1265 | 1266 | /** 1267 | * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch 1268 | * @param expected 1269 | * @deprecated as of 1.0. Use not.toMatch() instead. 1270 | */ 1271 | jasmine.Matchers.prototype.toNotMatch = function(expected) { 1272 | return !(new RegExp(expected).test(this.actual)); 1273 | }; 1274 | 1275 | /** 1276 | * Matcher that compares the actual to jasmine.undefined. 1277 | */ 1278 | jasmine.Matchers.prototype.toBeDefined = function() { 1279 | return (this.actual !== jasmine.undefined); 1280 | }; 1281 | 1282 | /** 1283 | * Matcher that compares the actual to jasmine.undefined. 1284 | */ 1285 | jasmine.Matchers.prototype.toBeUndefined = function() { 1286 | return (this.actual === jasmine.undefined); 1287 | }; 1288 | 1289 | /** 1290 | * Matcher that compares the actual to null. 1291 | */ 1292 | jasmine.Matchers.prototype.toBeNull = function() { 1293 | return (this.actual === null); 1294 | }; 1295 | 1296 | /** 1297 | * Matcher that boolean not-nots the actual. 1298 | */ 1299 | jasmine.Matchers.prototype.toBeTruthy = function() { 1300 | return !!this.actual; 1301 | }; 1302 | 1303 | 1304 | /** 1305 | * Matcher that boolean nots the actual. 1306 | */ 1307 | jasmine.Matchers.prototype.toBeFalsy = function() { 1308 | return !this.actual; 1309 | }; 1310 | 1311 | 1312 | /** 1313 | * Matcher that checks to see if the actual, a Jasmine spy, was called. 1314 | */ 1315 | jasmine.Matchers.prototype.toHaveBeenCalled = function() { 1316 | if (arguments.length > 0) { 1317 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); 1318 | } 1319 | 1320 | if (!jasmine.isSpy(this.actual)) { 1321 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1322 | } 1323 | 1324 | this.message = function() { 1325 | return [ 1326 | "Expected spy " + this.actual.identity + " to have been called.", 1327 | "Expected spy " + this.actual.identity + " not to have been called." 1328 | ]; 1329 | }; 1330 | 1331 | return this.actual.wasCalled; 1332 | }; 1333 | 1334 | /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ 1335 | jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; 1336 | 1337 | /** 1338 | * Matcher that checks to see if the actual, a Jasmine spy, was not called. 1339 | * 1340 | * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead 1341 | */ 1342 | jasmine.Matchers.prototype.wasNotCalled = function() { 1343 | if (arguments.length > 0) { 1344 | throw new Error('wasNotCalled does not take arguments'); 1345 | } 1346 | 1347 | if (!jasmine.isSpy(this.actual)) { 1348 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1349 | } 1350 | 1351 | this.message = function() { 1352 | return [ 1353 | "Expected spy " + this.actual.identity + " to not have been called.", 1354 | "Expected spy " + this.actual.identity + " to have been called." 1355 | ]; 1356 | }; 1357 | 1358 | return !this.actual.wasCalled; 1359 | }; 1360 | 1361 | /** 1362 | * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. 1363 | * 1364 | * @example 1365 | * 1366 | */ 1367 | jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { 1368 | var expectedArgs = jasmine.util.argsToArray(arguments); 1369 | if (!jasmine.isSpy(this.actual)) { 1370 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1371 | } 1372 | this.message = function() { 1373 | if (this.actual.callCount === 0) { 1374 | // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] 1375 | return [ 1376 | "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", 1377 | "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." 1378 | ]; 1379 | } else { 1380 | return [ 1381 | "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), 1382 | "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) 1383 | ]; 1384 | } 1385 | }; 1386 | 1387 | return this.env.contains_(this.actual.argsForCall, expectedArgs); 1388 | }; 1389 | 1390 | /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ 1391 | jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; 1392 | 1393 | /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ 1394 | jasmine.Matchers.prototype.wasNotCalledWith = function() { 1395 | var expectedArgs = jasmine.util.argsToArray(arguments); 1396 | if (!jasmine.isSpy(this.actual)) { 1397 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1398 | } 1399 | 1400 | this.message = function() { 1401 | return [ 1402 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", 1403 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" 1404 | ]; 1405 | }; 1406 | 1407 | return !this.env.contains_(this.actual.argsForCall, expectedArgs); 1408 | }; 1409 | 1410 | /** 1411 | * Matcher that checks that the expected item is an element in the actual Array. 1412 | * 1413 | * @param {Object} expected 1414 | */ 1415 | jasmine.Matchers.prototype.toContain = function(expected) { 1416 | return this.env.contains_(this.actual, expected); 1417 | }; 1418 | 1419 | /** 1420 | * Matcher that checks that the expected item is NOT an element in the actual Array. 1421 | * 1422 | * @param {Object} expected 1423 | * @deprecated as of 1.0. Use not.toContain() instead. 1424 | */ 1425 | jasmine.Matchers.prototype.toNotContain = function(expected) { 1426 | return !this.env.contains_(this.actual, expected); 1427 | }; 1428 | 1429 | jasmine.Matchers.prototype.toBeLessThan = function(expected) { 1430 | return this.actual < expected; 1431 | }; 1432 | 1433 | jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { 1434 | return this.actual > expected; 1435 | }; 1436 | 1437 | /** 1438 | * Matcher that checks that the expected item is equal to the actual item 1439 | * up to a given level of decimal precision (default 2). 1440 | * 1441 | * @param {Number} expected 1442 | * @param {Number} precision 1443 | */ 1444 | jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { 1445 | if (!(precision === 0)) { 1446 | precision = precision || 2; 1447 | } 1448 | var multiplier = Math.pow(10, precision); 1449 | var actual = Math.round(this.actual * multiplier); 1450 | expected = Math.round(expected * multiplier); 1451 | return expected == actual; 1452 | }; 1453 | 1454 | /** 1455 | * Matcher that checks that the expected exception was thrown by the actual. 1456 | * 1457 | * @param {String} expected 1458 | */ 1459 | jasmine.Matchers.prototype.toThrow = function(expected) { 1460 | var result = false; 1461 | var exception; 1462 | if (typeof this.actual != 'function') { 1463 | throw new Error('Actual is not a function'); 1464 | } 1465 | try { 1466 | this.actual(); 1467 | } catch (e) { 1468 | exception = e; 1469 | } 1470 | if (exception) { 1471 | result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); 1472 | } 1473 | 1474 | var not = this.isNot ? "not " : ""; 1475 | 1476 | this.message = function() { 1477 | if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { 1478 | return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); 1479 | } else { 1480 | return "Expected function to throw an exception."; 1481 | } 1482 | }; 1483 | 1484 | return result; 1485 | }; 1486 | 1487 | jasmine.Matchers.Any = function(expectedClass) { 1488 | this.expectedClass = expectedClass; 1489 | }; 1490 | 1491 | jasmine.Matchers.Any.prototype.jasmineMatches = function(other) { 1492 | if (this.expectedClass == String) { 1493 | return typeof other == 'string' || other instanceof String; 1494 | } 1495 | 1496 | if (this.expectedClass == Number) { 1497 | return typeof other == 'number' || other instanceof Number; 1498 | } 1499 | 1500 | if (this.expectedClass == Function) { 1501 | return typeof other == 'function' || other instanceof Function; 1502 | } 1503 | 1504 | if (this.expectedClass == Object) { 1505 | return typeof other == 'object'; 1506 | } 1507 | 1508 | return other instanceof this.expectedClass; 1509 | }; 1510 | 1511 | jasmine.Matchers.Any.prototype.jasmineToString = function() { 1512 | return ''; 1513 | }; 1514 | 1515 | jasmine.Matchers.ObjectContaining = function (sample) { 1516 | this.sample = sample; 1517 | }; 1518 | 1519 | jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { 1520 | mismatchKeys = mismatchKeys || []; 1521 | mismatchValues = mismatchValues || []; 1522 | 1523 | var env = jasmine.getEnv(); 1524 | 1525 | var hasKey = function(obj, keyName) { 1526 | return obj != null && obj[keyName] !== jasmine.undefined; 1527 | }; 1528 | 1529 | for (var property in this.sample) { 1530 | if (!hasKey(other, property) && hasKey(this.sample, property)) { 1531 | mismatchKeys.push("expected has key '" + property + "', but missing from actual."); 1532 | } 1533 | else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) { 1534 | mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual."); 1535 | } 1536 | } 1537 | 1538 | return (mismatchKeys.length === 0 && mismatchValues.length === 0); 1539 | }; 1540 | 1541 | jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { 1542 | return ""; 1543 | }; 1544 | // Mock setTimeout, clearTimeout 1545 | // Contributed by Pivotal Computer Systems, www.pivotalsf.com 1546 | 1547 | jasmine.FakeTimer = function() { 1548 | this.reset(); 1549 | 1550 | var self = this; 1551 | self.setTimeout = function(funcToCall, millis) { 1552 | self.timeoutsMade++; 1553 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); 1554 | return self.timeoutsMade; 1555 | }; 1556 | 1557 | self.setInterval = function(funcToCall, millis) { 1558 | self.timeoutsMade++; 1559 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); 1560 | return self.timeoutsMade; 1561 | }; 1562 | 1563 | self.clearTimeout = function(timeoutKey) { 1564 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 1565 | }; 1566 | 1567 | self.clearInterval = function(timeoutKey) { 1568 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 1569 | }; 1570 | 1571 | }; 1572 | 1573 | jasmine.FakeTimer.prototype.reset = function() { 1574 | this.timeoutsMade = 0; 1575 | this.scheduledFunctions = {}; 1576 | this.nowMillis = 0; 1577 | }; 1578 | 1579 | jasmine.FakeTimer.prototype.tick = function(millis) { 1580 | var oldMillis = this.nowMillis; 1581 | var newMillis = oldMillis + millis; 1582 | this.runFunctionsWithinRange(oldMillis, newMillis); 1583 | this.nowMillis = newMillis; 1584 | }; 1585 | 1586 | jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { 1587 | var scheduledFunc; 1588 | var funcsToRun = []; 1589 | for (var timeoutKey in this.scheduledFunctions) { 1590 | scheduledFunc = this.scheduledFunctions[timeoutKey]; 1591 | if (scheduledFunc != jasmine.undefined && 1592 | scheduledFunc.runAtMillis >= oldMillis && 1593 | scheduledFunc.runAtMillis <= nowMillis) { 1594 | funcsToRun.push(scheduledFunc); 1595 | this.scheduledFunctions[timeoutKey] = jasmine.undefined; 1596 | } 1597 | } 1598 | 1599 | if (funcsToRun.length > 0) { 1600 | funcsToRun.sort(function(a, b) { 1601 | return a.runAtMillis - b.runAtMillis; 1602 | }); 1603 | for (var i = 0; i < funcsToRun.length; ++i) { 1604 | try { 1605 | var funcToRun = funcsToRun[i]; 1606 | this.nowMillis = funcToRun.runAtMillis; 1607 | funcToRun.funcToCall(); 1608 | if (funcToRun.recurring) { 1609 | this.scheduleFunction(funcToRun.timeoutKey, 1610 | funcToRun.funcToCall, 1611 | funcToRun.millis, 1612 | true); 1613 | } 1614 | } catch(e) { 1615 | } 1616 | } 1617 | this.runFunctionsWithinRange(oldMillis, nowMillis); 1618 | } 1619 | }; 1620 | 1621 | jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { 1622 | this.scheduledFunctions[timeoutKey] = { 1623 | runAtMillis: this.nowMillis + millis, 1624 | funcToCall: funcToCall, 1625 | recurring: recurring, 1626 | timeoutKey: timeoutKey, 1627 | millis: millis 1628 | }; 1629 | }; 1630 | 1631 | /** 1632 | * @namespace 1633 | */ 1634 | jasmine.Clock = { 1635 | defaultFakeTimer: new jasmine.FakeTimer(), 1636 | 1637 | reset: function() { 1638 | jasmine.Clock.assertInstalled(); 1639 | jasmine.Clock.defaultFakeTimer.reset(); 1640 | }, 1641 | 1642 | tick: function(millis) { 1643 | jasmine.Clock.assertInstalled(); 1644 | jasmine.Clock.defaultFakeTimer.tick(millis); 1645 | }, 1646 | 1647 | runFunctionsWithinRange: function(oldMillis, nowMillis) { 1648 | jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); 1649 | }, 1650 | 1651 | scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { 1652 | jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); 1653 | }, 1654 | 1655 | useMock: function() { 1656 | if (!jasmine.Clock.isInstalled()) { 1657 | var spec = jasmine.getEnv().currentSpec; 1658 | spec.after(jasmine.Clock.uninstallMock); 1659 | 1660 | jasmine.Clock.installMock(); 1661 | } 1662 | }, 1663 | 1664 | installMock: function() { 1665 | jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; 1666 | }, 1667 | 1668 | uninstallMock: function() { 1669 | jasmine.Clock.assertInstalled(); 1670 | jasmine.Clock.installed = jasmine.Clock.real; 1671 | }, 1672 | 1673 | real: { 1674 | setTimeout: jasmine.getGlobal().setTimeout, 1675 | clearTimeout: jasmine.getGlobal().clearTimeout, 1676 | setInterval: jasmine.getGlobal().setInterval, 1677 | clearInterval: jasmine.getGlobal().clearInterval 1678 | }, 1679 | 1680 | assertInstalled: function() { 1681 | if (!jasmine.Clock.isInstalled()) { 1682 | throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); 1683 | } 1684 | }, 1685 | 1686 | isInstalled: function() { 1687 | return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; 1688 | }, 1689 | 1690 | installed: null 1691 | }; 1692 | jasmine.Clock.installed = jasmine.Clock.real; 1693 | 1694 | //else for IE support 1695 | jasmine.getGlobal().setTimeout = function(funcToCall, millis) { 1696 | if (jasmine.Clock.installed.setTimeout.apply) { 1697 | return jasmine.Clock.installed.setTimeout.apply(this, arguments); 1698 | } else { 1699 | return jasmine.Clock.installed.setTimeout(funcToCall, millis); 1700 | } 1701 | }; 1702 | 1703 | jasmine.getGlobal().setInterval = function(funcToCall, millis) { 1704 | if (jasmine.Clock.installed.setInterval.apply) { 1705 | return jasmine.Clock.installed.setInterval.apply(this, arguments); 1706 | } else { 1707 | return jasmine.Clock.installed.setInterval(funcToCall, millis); 1708 | } 1709 | }; 1710 | 1711 | jasmine.getGlobal().clearTimeout = function(timeoutKey) { 1712 | if (jasmine.Clock.installed.clearTimeout.apply) { 1713 | return jasmine.Clock.installed.clearTimeout.apply(this, arguments); 1714 | } else { 1715 | return jasmine.Clock.installed.clearTimeout(timeoutKey); 1716 | } 1717 | }; 1718 | 1719 | jasmine.getGlobal().clearInterval = function(timeoutKey) { 1720 | if (jasmine.Clock.installed.clearTimeout.apply) { 1721 | return jasmine.Clock.installed.clearInterval.apply(this, arguments); 1722 | } else { 1723 | return jasmine.Clock.installed.clearInterval(timeoutKey); 1724 | } 1725 | }; 1726 | 1727 | /** 1728 | * @constructor 1729 | */ 1730 | jasmine.MultiReporter = function() { 1731 | this.subReporters_ = []; 1732 | }; 1733 | jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); 1734 | 1735 | jasmine.MultiReporter.prototype.addReporter = function(reporter) { 1736 | this.subReporters_.push(reporter); 1737 | }; 1738 | 1739 | (function() { 1740 | var functionNames = [ 1741 | "reportRunnerStarting", 1742 | "reportRunnerResults", 1743 | "reportSuiteResults", 1744 | "reportSpecStarting", 1745 | "reportSpecResults", 1746 | "log" 1747 | ]; 1748 | for (var i = 0; i < functionNames.length; i++) { 1749 | var functionName = functionNames[i]; 1750 | jasmine.MultiReporter.prototype[functionName] = (function(functionName) { 1751 | return function() { 1752 | for (var j = 0; j < this.subReporters_.length; j++) { 1753 | var subReporter = this.subReporters_[j]; 1754 | if (subReporter[functionName]) { 1755 | subReporter[functionName].apply(subReporter, arguments); 1756 | } 1757 | } 1758 | }; 1759 | })(functionName); 1760 | } 1761 | })(); 1762 | /** 1763 | * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults 1764 | * 1765 | * @constructor 1766 | */ 1767 | jasmine.NestedResults = function() { 1768 | /** 1769 | * The total count of results 1770 | */ 1771 | this.totalCount = 0; 1772 | /** 1773 | * Number of passed results 1774 | */ 1775 | this.passedCount = 0; 1776 | /** 1777 | * Number of failed results 1778 | */ 1779 | this.failedCount = 0; 1780 | /** 1781 | * Was this suite/spec skipped? 1782 | */ 1783 | this.skipped = false; 1784 | /** 1785 | * @ignore 1786 | */ 1787 | this.items_ = []; 1788 | }; 1789 | 1790 | /** 1791 | * Roll up the result counts. 1792 | * 1793 | * @param result 1794 | */ 1795 | jasmine.NestedResults.prototype.rollupCounts = function(result) { 1796 | this.totalCount += result.totalCount; 1797 | this.passedCount += result.passedCount; 1798 | this.failedCount += result.failedCount; 1799 | }; 1800 | 1801 | /** 1802 | * Adds a log message. 1803 | * @param values Array of message parts which will be concatenated later. 1804 | */ 1805 | jasmine.NestedResults.prototype.log = function(values) { 1806 | this.items_.push(new jasmine.MessageResult(values)); 1807 | }; 1808 | 1809 | /** 1810 | * Getter for the results: message & results. 1811 | */ 1812 | jasmine.NestedResults.prototype.getItems = function() { 1813 | return this.items_; 1814 | }; 1815 | 1816 | /** 1817 | * Adds a result, tracking counts (total, passed, & failed) 1818 | * @param {jasmine.ExpectationResult|jasmine.NestedResults} result 1819 | */ 1820 | jasmine.NestedResults.prototype.addResult = function(result) { 1821 | if (result.type != 'log') { 1822 | if (result.items_) { 1823 | this.rollupCounts(result); 1824 | } else { 1825 | this.totalCount++; 1826 | if (result.passed()) { 1827 | this.passedCount++; 1828 | } else { 1829 | this.failedCount++; 1830 | } 1831 | } 1832 | } 1833 | this.items_.push(result); 1834 | }; 1835 | 1836 | /** 1837 | * @returns {Boolean} True if everything below passed 1838 | */ 1839 | jasmine.NestedResults.prototype.passed = function() { 1840 | return this.passedCount === this.totalCount; 1841 | }; 1842 | /** 1843 | * Base class for pretty printing for expectation results. 1844 | */ 1845 | jasmine.PrettyPrinter = function() { 1846 | this.ppNestLevel_ = 0; 1847 | }; 1848 | 1849 | /** 1850 | * Formats a value in a nice, human-readable string. 1851 | * 1852 | * @param value 1853 | */ 1854 | jasmine.PrettyPrinter.prototype.format = function(value) { 1855 | if (this.ppNestLevel_ > 40) { 1856 | throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); 1857 | } 1858 | 1859 | this.ppNestLevel_++; 1860 | try { 1861 | if (value === jasmine.undefined) { 1862 | this.emitScalar('undefined'); 1863 | } else if (value === null) { 1864 | this.emitScalar('null'); 1865 | } else if (value === jasmine.getGlobal()) { 1866 | this.emitScalar(''); 1867 | } else if (value.jasmineToString) { 1868 | this.emitScalar(value.jasmineToString()); 1869 | } else if (typeof value === 'string') { 1870 | this.emitString(value); 1871 | } else if (jasmine.isSpy(value)) { 1872 | this.emitScalar("spy on " + value.identity); 1873 | } else if (value instanceof RegExp) { 1874 | this.emitScalar(value.toString()); 1875 | } else if (typeof value === 'function') { 1876 | this.emitScalar('Function'); 1877 | } else if (typeof value.nodeType === 'number') { 1878 | this.emitScalar('HTMLNode'); 1879 | } else if (value instanceof Date) { 1880 | this.emitScalar('Date(' + value + ')'); 1881 | } else if (value.__Jasmine_been_here_before__) { 1882 | this.emitScalar(''); 1883 | } else if (jasmine.isArray_(value) || typeof value == 'object') { 1884 | value.__Jasmine_been_here_before__ = true; 1885 | if (jasmine.isArray_(value)) { 1886 | this.emitArray(value); 1887 | } else { 1888 | this.emitObject(value); 1889 | } 1890 | delete value.__Jasmine_been_here_before__; 1891 | } else { 1892 | this.emitScalar(value.toString()); 1893 | } 1894 | } finally { 1895 | this.ppNestLevel_--; 1896 | } 1897 | }; 1898 | 1899 | jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1900 | for (var property in obj) { 1901 | if (property == '__Jasmine_been_here_before__') continue; 1902 | fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && 1903 | obj.__lookupGetter__(property) !== null) : false); 1904 | } 1905 | }; 1906 | 1907 | jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; 1908 | jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; 1909 | jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; 1910 | jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; 1911 | 1912 | jasmine.StringPrettyPrinter = function() { 1913 | jasmine.PrettyPrinter.call(this); 1914 | 1915 | this.string = ''; 1916 | }; 1917 | jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); 1918 | 1919 | jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { 1920 | this.append(value); 1921 | }; 1922 | 1923 | jasmine.StringPrettyPrinter.prototype.emitString = function(value) { 1924 | this.append("'" + value + "'"); 1925 | }; 1926 | 1927 | jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { 1928 | this.append('[ '); 1929 | for (var i = 0; i < array.length; i++) { 1930 | if (i > 0) { 1931 | this.append(', '); 1932 | } 1933 | this.format(array[i]); 1934 | } 1935 | this.append(' ]'); 1936 | }; 1937 | 1938 | jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { 1939 | var self = this; 1940 | this.append('{ '); 1941 | var first = true; 1942 | 1943 | this.iterateObject(obj, function(property, isGetter) { 1944 | if (first) { 1945 | first = false; 1946 | } else { 1947 | self.append(', '); 1948 | } 1949 | 1950 | self.append(property); 1951 | self.append(' : '); 1952 | if (isGetter) { 1953 | self.append(''); 1954 | } else { 1955 | self.format(obj[property]); 1956 | } 1957 | }); 1958 | 1959 | this.append(' }'); 1960 | }; 1961 | 1962 | jasmine.StringPrettyPrinter.prototype.append = function(value) { 1963 | this.string += value; 1964 | }; 1965 | jasmine.Queue = function(env) { 1966 | this.env = env; 1967 | this.blocks = []; 1968 | this.running = false; 1969 | this.index = 0; 1970 | this.offset = 0; 1971 | this.abort = false; 1972 | }; 1973 | 1974 | jasmine.Queue.prototype.addBefore = function(block) { 1975 | this.blocks.unshift(block); 1976 | }; 1977 | 1978 | jasmine.Queue.prototype.add = function(block) { 1979 | this.blocks.push(block); 1980 | }; 1981 | 1982 | jasmine.Queue.prototype.insertNext = function(block) { 1983 | this.blocks.splice((this.index + this.offset + 1), 0, block); 1984 | this.offset++; 1985 | }; 1986 | 1987 | jasmine.Queue.prototype.start = function(onComplete) { 1988 | this.running = true; 1989 | this.onComplete = onComplete; 1990 | this.next_(); 1991 | }; 1992 | 1993 | jasmine.Queue.prototype.isRunning = function() { 1994 | return this.running; 1995 | }; 1996 | 1997 | jasmine.Queue.LOOP_DONT_RECURSE = true; 1998 | 1999 | jasmine.Queue.prototype.next_ = function() { 2000 | var self = this; 2001 | var goAgain = true; 2002 | 2003 | while (goAgain) { 2004 | goAgain = false; 2005 | 2006 | if (self.index < self.blocks.length && !this.abort) { 2007 | var calledSynchronously = true; 2008 | var completedSynchronously = false; 2009 | 2010 | var onComplete = function () { 2011 | if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { 2012 | completedSynchronously = true; 2013 | return; 2014 | } 2015 | 2016 | if (self.blocks[self.index].abort) { 2017 | self.abort = true; 2018 | } 2019 | 2020 | self.offset = 0; 2021 | self.index++; 2022 | 2023 | var now = new Date().getTime(); 2024 | if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { 2025 | self.env.lastUpdate = now; 2026 | self.env.setTimeout(function() { 2027 | self.next_(); 2028 | }, 0); 2029 | } else { 2030 | if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { 2031 | goAgain = true; 2032 | } else { 2033 | self.next_(); 2034 | } 2035 | } 2036 | }; 2037 | self.blocks[self.index].execute(onComplete); 2038 | 2039 | calledSynchronously = false; 2040 | if (completedSynchronously) { 2041 | onComplete(); 2042 | } 2043 | 2044 | } else { 2045 | self.running = false; 2046 | if (self.onComplete) { 2047 | self.onComplete(); 2048 | } 2049 | } 2050 | } 2051 | }; 2052 | 2053 | jasmine.Queue.prototype.results = function() { 2054 | var results = new jasmine.NestedResults(); 2055 | for (var i = 0; i < this.blocks.length; i++) { 2056 | if (this.blocks[i].results) { 2057 | results.addResult(this.blocks[i].results()); 2058 | } 2059 | } 2060 | return results; 2061 | }; 2062 | 2063 | 2064 | /** 2065 | * Runner 2066 | * 2067 | * @constructor 2068 | * @param {jasmine.Env} env 2069 | */ 2070 | jasmine.Runner = function(env) { 2071 | var self = this; 2072 | self.env = env; 2073 | self.queue = new jasmine.Queue(env); 2074 | self.before_ = []; 2075 | self.after_ = []; 2076 | self.suites_ = []; 2077 | }; 2078 | 2079 | jasmine.Runner.prototype.execute = function() { 2080 | var self = this; 2081 | if (self.env.reporter.reportRunnerStarting) { 2082 | self.env.reporter.reportRunnerStarting(this); 2083 | } 2084 | self.queue.start(function () { 2085 | self.finishCallback(); 2086 | }); 2087 | }; 2088 | 2089 | jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { 2090 | beforeEachFunction.typeName = 'beforeEach'; 2091 | this.before_.splice(0,0,beforeEachFunction); 2092 | }; 2093 | 2094 | jasmine.Runner.prototype.afterEach = function(afterEachFunction) { 2095 | afterEachFunction.typeName = 'afterEach'; 2096 | this.after_.splice(0,0,afterEachFunction); 2097 | }; 2098 | 2099 | 2100 | jasmine.Runner.prototype.finishCallback = function() { 2101 | this.env.reporter.reportRunnerResults(this); 2102 | }; 2103 | 2104 | jasmine.Runner.prototype.addSuite = function(suite) { 2105 | this.suites_.push(suite); 2106 | }; 2107 | 2108 | jasmine.Runner.prototype.add = function(block) { 2109 | if (block instanceof jasmine.Suite) { 2110 | this.addSuite(block); 2111 | } 2112 | this.queue.add(block); 2113 | }; 2114 | 2115 | jasmine.Runner.prototype.specs = function () { 2116 | var suites = this.suites(); 2117 | var specs = []; 2118 | for (var i = 0; i < suites.length; i++) { 2119 | specs = specs.concat(suites[i].specs()); 2120 | } 2121 | return specs; 2122 | }; 2123 | 2124 | jasmine.Runner.prototype.suites = function() { 2125 | return this.suites_; 2126 | }; 2127 | 2128 | jasmine.Runner.prototype.topLevelSuites = function() { 2129 | var topLevelSuites = []; 2130 | for (var i = 0; i < this.suites_.length; i++) { 2131 | if (!this.suites_[i].parentSuite) { 2132 | topLevelSuites.push(this.suites_[i]); 2133 | } 2134 | } 2135 | return topLevelSuites; 2136 | }; 2137 | 2138 | jasmine.Runner.prototype.results = function() { 2139 | return this.queue.results(); 2140 | }; 2141 | /** 2142 | * Internal representation of a Jasmine specification, or test. 2143 | * 2144 | * @constructor 2145 | * @param {jasmine.Env} env 2146 | * @param {jasmine.Suite} suite 2147 | * @param {String} description 2148 | */ 2149 | jasmine.Spec = function(env, suite, description) { 2150 | if (!env) { 2151 | throw new Error('jasmine.Env() required'); 2152 | } 2153 | if (!suite) { 2154 | throw new Error('jasmine.Suite() required'); 2155 | } 2156 | var spec = this; 2157 | spec.id = env.nextSpecId ? env.nextSpecId() : null; 2158 | spec.env = env; 2159 | spec.suite = suite; 2160 | spec.description = description; 2161 | spec.queue = new jasmine.Queue(env); 2162 | 2163 | spec.afterCallbacks = []; 2164 | spec.spies_ = []; 2165 | 2166 | spec.results_ = new jasmine.NestedResults(); 2167 | spec.results_.description = description; 2168 | spec.matchersClass = null; 2169 | }; 2170 | 2171 | jasmine.Spec.prototype.getFullName = function() { 2172 | return this.suite.getFullName() + ' ' + this.description + '.'; 2173 | }; 2174 | 2175 | 2176 | jasmine.Spec.prototype.results = function() { 2177 | return this.results_; 2178 | }; 2179 | 2180 | /** 2181 | * All parameters are pretty-printed and concatenated together, then written to the spec's output. 2182 | * 2183 | * Be careful not to leave calls to jasmine.log in production code. 2184 | */ 2185 | jasmine.Spec.prototype.log = function() { 2186 | return this.results_.log(arguments); 2187 | }; 2188 | 2189 | jasmine.Spec.prototype.runs = function (func) { 2190 | var block = new jasmine.Block(this.env, func, this); 2191 | this.addToQueue(block); 2192 | return this; 2193 | }; 2194 | 2195 | jasmine.Spec.prototype.addToQueue = function (block) { 2196 | if (this.queue.isRunning()) { 2197 | this.queue.insertNext(block); 2198 | } else { 2199 | this.queue.add(block); 2200 | } 2201 | }; 2202 | 2203 | /** 2204 | * @param {jasmine.ExpectationResult} result 2205 | */ 2206 | jasmine.Spec.prototype.addMatcherResult = function(result) { 2207 | this.results_.addResult(result); 2208 | }; 2209 | 2210 | jasmine.Spec.prototype.expect = function(actual) { 2211 | var positive = new (this.getMatchersClass_())(this.env, actual, this); 2212 | positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); 2213 | return positive; 2214 | }; 2215 | 2216 | /** 2217 | * Waits a fixed time period before moving to the next block. 2218 | * 2219 | * @deprecated Use waitsFor() instead 2220 | * @param {Number} timeout milliseconds to wait 2221 | */ 2222 | jasmine.Spec.prototype.waits = function(timeout) { 2223 | var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); 2224 | this.addToQueue(waitsFunc); 2225 | return this; 2226 | }; 2227 | 2228 | /** 2229 | * Waits for the latchFunction to return true before proceeding to the next block. 2230 | * 2231 | * @param {Function} latchFunction 2232 | * @param {String} optional_timeoutMessage 2233 | * @param {Number} optional_timeout 2234 | */ 2235 | jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 2236 | var latchFunction_ = null; 2237 | var optional_timeoutMessage_ = null; 2238 | var optional_timeout_ = null; 2239 | 2240 | for (var i = 0; i < arguments.length; i++) { 2241 | var arg = arguments[i]; 2242 | switch (typeof arg) { 2243 | case 'function': 2244 | latchFunction_ = arg; 2245 | break; 2246 | case 'string': 2247 | optional_timeoutMessage_ = arg; 2248 | break; 2249 | case 'number': 2250 | optional_timeout_ = arg; 2251 | break; 2252 | } 2253 | } 2254 | 2255 | var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); 2256 | this.addToQueue(waitsForFunc); 2257 | return this; 2258 | }; 2259 | 2260 | jasmine.Spec.prototype.fail = function (e) { 2261 | var expectationResult = new jasmine.ExpectationResult({ 2262 | passed: false, 2263 | message: e ? jasmine.util.formatException(e) : 'Exception', 2264 | trace: { stack: e.stack } 2265 | }); 2266 | this.results_.addResult(expectationResult); 2267 | }; 2268 | 2269 | jasmine.Spec.prototype.getMatchersClass_ = function() { 2270 | return this.matchersClass || this.env.matchersClass; 2271 | }; 2272 | 2273 | jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { 2274 | var parent = this.getMatchersClass_(); 2275 | var newMatchersClass = function() { 2276 | parent.apply(this, arguments); 2277 | }; 2278 | jasmine.util.inherit(newMatchersClass, parent); 2279 | jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); 2280 | this.matchersClass = newMatchersClass; 2281 | }; 2282 | 2283 | jasmine.Spec.prototype.finishCallback = function() { 2284 | this.env.reporter.reportSpecResults(this); 2285 | }; 2286 | 2287 | jasmine.Spec.prototype.finish = function(onComplete) { 2288 | this.removeAllSpies(); 2289 | this.finishCallback(); 2290 | if (onComplete) { 2291 | onComplete(); 2292 | } 2293 | }; 2294 | 2295 | jasmine.Spec.prototype.after = function(doAfter) { 2296 | if (this.queue.isRunning()) { 2297 | this.queue.add(new jasmine.Block(this.env, doAfter, this)); 2298 | } else { 2299 | this.afterCallbacks.unshift(doAfter); 2300 | } 2301 | }; 2302 | 2303 | jasmine.Spec.prototype.execute = function(onComplete) { 2304 | var spec = this; 2305 | if (!spec.env.specFilter(spec)) { 2306 | spec.results_.skipped = true; 2307 | spec.finish(onComplete); 2308 | return; 2309 | } 2310 | 2311 | this.env.reporter.reportSpecStarting(this); 2312 | 2313 | spec.env.currentSpec = spec; 2314 | 2315 | spec.addBeforesAndAftersToQueue(); 2316 | 2317 | spec.queue.start(function () { 2318 | spec.finish(onComplete); 2319 | }); 2320 | }; 2321 | 2322 | jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { 2323 | var runner = this.env.currentRunner(); 2324 | var i; 2325 | 2326 | for (var suite = this.suite; suite; suite = suite.parentSuite) { 2327 | for (i = 0; i < suite.before_.length; i++) { 2328 | this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); 2329 | } 2330 | } 2331 | for (i = 0; i < runner.before_.length; i++) { 2332 | this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); 2333 | } 2334 | for (i = 0; i < this.afterCallbacks.length; i++) { 2335 | this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); 2336 | } 2337 | for (suite = this.suite; suite; suite = suite.parentSuite) { 2338 | for (i = 0; i < suite.after_.length; i++) { 2339 | this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); 2340 | } 2341 | } 2342 | for (i = 0; i < runner.after_.length; i++) { 2343 | this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); 2344 | } 2345 | }; 2346 | 2347 | jasmine.Spec.prototype.explodes = function() { 2348 | throw 'explodes function should not have been called'; 2349 | }; 2350 | 2351 | jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { 2352 | if (obj == jasmine.undefined) { 2353 | throw "spyOn could not find an object to spy upon for " + methodName + "()"; 2354 | } 2355 | 2356 | if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { 2357 | throw methodName + '() method does not exist'; 2358 | } 2359 | 2360 | if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { 2361 | throw new Error(methodName + ' has already been spied upon'); 2362 | } 2363 | 2364 | var spyObj = jasmine.createSpy(methodName); 2365 | 2366 | this.spies_.push(spyObj); 2367 | spyObj.baseObj = obj; 2368 | spyObj.methodName = methodName; 2369 | spyObj.originalValue = obj[methodName]; 2370 | 2371 | obj[methodName] = spyObj; 2372 | 2373 | return spyObj; 2374 | }; 2375 | 2376 | jasmine.Spec.prototype.removeAllSpies = function() { 2377 | for (var i = 0; i < this.spies_.length; i++) { 2378 | var spy = this.spies_[i]; 2379 | spy.baseObj[spy.methodName] = spy.originalValue; 2380 | } 2381 | this.spies_ = []; 2382 | }; 2383 | 2384 | /** 2385 | * Internal representation of a Jasmine suite. 2386 | * 2387 | * @constructor 2388 | * @param {jasmine.Env} env 2389 | * @param {String} description 2390 | * @param {Function} specDefinitions 2391 | * @param {jasmine.Suite} parentSuite 2392 | */ 2393 | jasmine.Suite = function(env, description, specDefinitions, parentSuite) { 2394 | var self = this; 2395 | self.id = env.nextSuiteId ? env.nextSuiteId() : null; 2396 | self.description = description; 2397 | self.queue = new jasmine.Queue(env); 2398 | self.parentSuite = parentSuite; 2399 | self.env = env; 2400 | self.before_ = []; 2401 | self.after_ = []; 2402 | self.children_ = []; 2403 | self.suites_ = []; 2404 | self.specs_ = []; 2405 | }; 2406 | 2407 | jasmine.Suite.prototype.getFullName = function() { 2408 | var fullName = this.description; 2409 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 2410 | fullName = parentSuite.description + ' ' + fullName; 2411 | } 2412 | return fullName; 2413 | }; 2414 | 2415 | jasmine.Suite.prototype.finish = function(onComplete) { 2416 | this.env.reporter.reportSuiteResults(this); 2417 | this.finished = true; 2418 | if (typeof(onComplete) == 'function') { 2419 | onComplete(); 2420 | } 2421 | }; 2422 | 2423 | jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { 2424 | beforeEachFunction.typeName = 'beforeEach'; 2425 | this.before_.unshift(beforeEachFunction); 2426 | }; 2427 | 2428 | jasmine.Suite.prototype.afterEach = function(afterEachFunction) { 2429 | afterEachFunction.typeName = 'afterEach'; 2430 | this.after_.unshift(afterEachFunction); 2431 | }; 2432 | 2433 | jasmine.Suite.prototype.results = function() { 2434 | return this.queue.results(); 2435 | }; 2436 | 2437 | jasmine.Suite.prototype.add = function(suiteOrSpec) { 2438 | this.children_.push(suiteOrSpec); 2439 | if (suiteOrSpec instanceof jasmine.Suite) { 2440 | this.suites_.push(suiteOrSpec); 2441 | this.env.currentRunner().addSuite(suiteOrSpec); 2442 | } else { 2443 | this.specs_.push(suiteOrSpec); 2444 | } 2445 | this.queue.add(suiteOrSpec); 2446 | }; 2447 | 2448 | jasmine.Suite.prototype.specs = function() { 2449 | return this.specs_; 2450 | }; 2451 | 2452 | jasmine.Suite.prototype.suites = function() { 2453 | return this.suites_; 2454 | }; 2455 | 2456 | jasmine.Suite.prototype.children = function() { 2457 | return this.children_; 2458 | }; 2459 | 2460 | jasmine.Suite.prototype.execute = function(onComplete) { 2461 | var self = this; 2462 | this.queue.start(function () { 2463 | self.finish(onComplete); 2464 | }); 2465 | }; 2466 | jasmine.WaitsBlock = function(env, timeout, spec) { 2467 | this.timeout = timeout; 2468 | jasmine.Block.call(this, env, null, spec); 2469 | }; 2470 | 2471 | jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); 2472 | 2473 | jasmine.WaitsBlock.prototype.execute = function (onComplete) { 2474 | if (jasmine.VERBOSE) { 2475 | this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); 2476 | } 2477 | this.env.setTimeout(function () { 2478 | onComplete(); 2479 | }, this.timeout); 2480 | }; 2481 | /** 2482 | * A block which waits for some condition to become true, with timeout. 2483 | * 2484 | * @constructor 2485 | * @extends jasmine.Block 2486 | * @param {jasmine.Env} env The Jasmine environment. 2487 | * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. 2488 | * @param {Function} latchFunction A function which returns true when the desired condition has been met. 2489 | * @param {String} message The message to display if the desired condition hasn't been met within the given time period. 2490 | * @param {jasmine.Spec} spec The Jasmine spec. 2491 | */ 2492 | jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { 2493 | this.timeout = timeout || env.defaultTimeoutInterval; 2494 | this.latchFunction = latchFunction; 2495 | this.message = message; 2496 | this.totalTimeSpentWaitingForLatch = 0; 2497 | jasmine.Block.call(this, env, null, spec); 2498 | }; 2499 | jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); 2500 | 2501 | jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; 2502 | 2503 | jasmine.WaitsForBlock.prototype.execute = function(onComplete) { 2504 | if (jasmine.VERBOSE) { 2505 | this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); 2506 | } 2507 | var latchFunctionResult; 2508 | try { 2509 | latchFunctionResult = this.latchFunction.apply(this.spec); 2510 | } catch (e) { 2511 | this.spec.fail(e); 2512 | onComplete(); 2513 | return; 2514 | } 2515 | 2516 | if (latchFunctionResult) { 2517 | onComplete(); 2518 | } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { 2519 | var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); 2520 | this.spec.fail({ 2521 | name: 'timeout', 2522 | message: message 2523 | }); 2524 | 2525 | this.abort = true; 2526 | onComplete(); 2527 | } else { 2528 | this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; 2529 | var self = this; 2530 | this.env.setTimeout(function() { 2531 | self.execute(onComplete); 2532 | }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); 2533 | } 2534 | }; 2535 | 2536 | jasmine.version_= { 2537 | "major": 1, 2538 | "minor": 2, 2539 | "build": 0, 2540 | "revision": 1337006083 2541 | }; 2542 | --------------------------------------------------------------------------------