├── spec ├── .rspec ├── test_src │ ├── text.txt │ ├── file2.js │ ├── file3.js │ ├── file1.js │ ├── file4.js │ ├── nested │ │ └── foo.txt │ ├── foo.txt │ └── license.txt ├── spec_helper.rb ├── JFile ├── server_spec.rb └── jbundle_spec.rb ├── bin └── jbundle ├── lib ├── jbundle │ ├── version.rb │ ├── templates │ │ ├── jasmine │ │ │ ├── jasmine_favicon.png │ │ │ ├── spec_helper.js │ │ │ ├── tests.js.tt │ │ │ ├── index.html.tt │ │ │ ├── jasmine.css │ │ │ ├── jasmine-html.js │ │ │ └── jasmine.js │ │ ├── qunit │ │ │ ├── tests.js.tt │ │ │ ├── index.html.tt │ │ │ ├── qunit.css │ │ │ └── qunit.js │ │ ├── lib.tt │ │ ├── jfile.tt │ │ └── license.tt │ ├── bundle_utils.rb │ ├── file.rb │ ├── bundle.rb │ ├── server.rb │ ├── config.rb │ ├── builder.rb │ ├── command_line.rb │ └── writer.rb └── jbundle.rb ├── .gitignore ├── Gemfile ├── Rakefile ├── jbundle.gemspec └── README.md /spec/.rspec: -------------------------------------------------------------------------------- 1 | --color -------------------------------------------------------------------------------- /spec/test_src/text.txt: -------------------------------------------------------------------------------- 1 | Foo -------------------------------------------------------------------------------- /spec/test_src/file2.js: -------------------------------------------------------------------------------- 1 | var a2 = 2; -------------------------------------------------------------------------------- /spec/test_src/file3.js: -------------------------------------------------------------------------------- 1 | var a3 = 3; -------------------------------------------------------------------------------- /bin/jbundle: -------------------------------------------------------------------------------- 1 | require 'jbundle' 2 | 3 | JBundle::CommandLine.start -------------------------------------------------------------------------------- /spec/test_src/file1.js: -------------------------------------------------------------------------------- 1 | var VERSION = ''; 2 | var a1 = 1; -------------------------------------------------------------------------------- /spec/test_src/file4.js: -------------------------------------------------------------------------------- 1 | var a4 = 4; 2 | var src_mode = ''; -------------------------------------------------------------------------------- /spec/test_src/nested/foo.txt: -------------------------------------------------------------------------------- 1 | This file is in a nested subdirectory -------------------------------------------------------------------------------- /lib/jbundle/version.rb: -------------------------------------------------------------------------------- 1 | module JBundle 2 | VERSION = "0.1.4" 3 | end 4 | -------------------------------------------------------------------------------- /spec/test_src/foo.txt: -------------------------------------------------------------------------------- 1 | # This file should just be copied, not processed nor minified -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/* 2 | *.gem 3 | .bundle 4 | spec/dist/* 5 | .rvmrc 6 | Gemfile.lock 7 | 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :gemcutter 2 | 3 | # Specify your gem's dependencies in jbundle.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /spec/test_src/license.txt: -------------------------------------------------------------------------------- 1 | /* Version: . This is the version. 2 | This is a license 3 | -----------------------*/ -------------------------------------------------------------------------------- /lib/jbundle/templates/jasmine/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pl/jbundle/master/lib/jbundle/templates/jasmine/jasmine_favicon.png -------------------------------------------------------------------------------- /lib/jbundle/templates/qunit/tests.js.tt: -------------------------------------------------------------------------------- 1 | module('General'); 2 | 3 | test('it should pass test', function () { 4 | var obj = new <%= @klass_name %>('John Doe'); 5 | equal('Mr. John Doe', obj.title()) 6 | }); -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'spec/rake/spectask' 5 | Spec::Rake::SpecTask.new(:spec) do |t| 6 | t.spec_files = FileList["spec/**/*_spec.rb"] 7 | t.spec_opts = %w{-f s -c -L mtime} 8 | end 9 | 10 | task :default => :spec 11 | -------------------------------------------------------------------------------- /lib/jbundle/bundle_utils.rb: -------------------------------------------------------------------------------- 1 | module JBundle 2 | 3 | module BundleUtils 4 | 5 | protected 6 | 7 | def parse_name(name) 8 | if name.is_a?(Hash) 9 | name.first 10 | else 11 | [name, name] 12 | end 13 | end 14 | 15 | end 16 | 17 | end -------------------------------------------------------------------------------- /lib/jbundle/templates/jasmine/spec_helper.js: -------------------------------------------------------------------------------- 1 | beforeEach(function() { 2 | this.addMatchers({ 3 | toBePlaying: function(expectedSong) { 4 | var player = this.actual; 5 | return player.currentlyPlayingSong === expectedSong && 6 | player.isPlaying; 7 | } 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /lib/jbundle/templates/jasmine/tests.js.tt: -------------------------------------------------------------------------------- 1 | describe("<%= @klass_name %>", function() { 2 | var obj; 3 | 4 | beforeEach(function() { 5 | obj = new <%= @klass_name %>('John Doe'); 6 | }); 7 | 8 | it("should pass test", function() { 9 | expect(obj.title()).toEqual("Mr. John Doe"); 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /lib/jbundle/templates/lib.tt: -------------------------------------------------------------------------------- 1 | var <%= @klass_name %> = (function () { 2 | 3 | function <%= @klass_name %> (name) { 4 | this.name = name; 5 | } 6 | 7 | <%= @klass_name %>.prototype = { 8 | title: function () { 9 | return 'Mr. ' + this.name; 10 | } 11 | } 12 | 13 | return <%= @klass_name %>; 14 | })(); -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | Bundler.setup 4 | 5 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 6 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 7 | 8 | DIST = File.dirname(__FILE__)+'/dist' 9 | 10 | require 'jbundle' 11 | begin 12 | require 'spec' 13 | rescue LoadError 14 | require 'rubygems' 15 | gem 'rspec' 16 | require 'spec' 17 | end 18 | -------------------------------------------------------------------------------- /lib/jbundle/file.rb: -------------------------------------------------------------------------------- 1 | module JBundle 2 | 3 | class File 4 | 5 | include Enumerable 6 | include JBundle::BundleUtils 7 | 8 | attr_reader :name, :original_name, :options 9 | 10 | def initialize(name, options = {}) 11 | @original_name, @name = parse_name(name) 12 | @options = options 13 | end 14 | 15 | def each(&block) 16 | yield original_name 17 | end 18 | 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /lib/jbundle/bundle.rb: -------------------------------------------------------------------------------- 1 | module JBundle 2 | 3 | class Bundle 4 | 5 | include Enumerable 6 | include JBundle::BundleUtils 7 | 8 | attr_reader :name, :original_name, :licenses, :options 9 | 10 | def initialize(name, options) 11 | @original_name, @name = parse_name(name) 12 | @options = options 13 | @files = [] 14 | @licenses = [] 15 | end 16 | 17 | def file(f) 18 | @files << f 19 | end 20 | 21 | def license(license_file) 22 | @licenses << license_file 23 | end 24 | 25 | def each(&block) 26 | @files.each &block 27 | end 28 | 29 | end 30 | 31 | end -------------------------------------------------------------------------------- /lib/jbundle/templates/qunit/index.html.tt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 |

QUnit Test Suite

13 |

14 |
15 |

16 |
    17 |
    test markup
    18 | 19 | -------------------------------------------------------------------------------- /lib/jbundle/templates/jfile.tt: -------------------------------------------------------------------------------- 1 | # The JFile defines your JavaScript library and its dependencies. 2 | # Read more on https://github.com/ismasan/jbundle/blob/master/README.md 3 | # 4 | # Version. 5 | # jbundle command will put source and minified versions in 6 | # - dist/0.0.1/ 7 | # - dist/0.0/ 8 | # 9 | version '0.0.1' 10 | 11 | # put your development files here 12 | # 13 | src_dir './src' 14 | 15 | # Define one or more JavaScript bundles 16 | # 17 | bundle '<%= @name %>' do 18 | license 'license.txt' 19 | file '<%= @name %>' 20 | end 21 | 22 | # Optional post-bundling filter, for example for string substitution 23 | # 24 | filter do |src, config| 25 | src.gsub //, config.version.to_s 26 | end 27 | 28 | # your packaged, versioned releases go here 29 | # 30 | target_dir 'dist' -------------------------------------------------------------------------------- /spec/JFile: -------------------------------------------------------------------------------- 1 | version '1.6.1' 2 | 3 | src_dir ::File.dirname(__FILE__) + '/test_src' 4 | target_dir DIST 5 | 6 | bundle 'foo.js' do 7 | file 'file1.js' 8 | file 'file2.js' 9 | end 10 | 11 | bundle 'foo2.js' do 12 | license 'license.txt' 13 | file 'file3.js' 14 | file 'file4.js' 15 | end 16 | 17 | file 'file4.js' 18 | 19 | file 'text.txt' 20 | 21 | file 'nested/foo.txt' 22 | 23 | file 'nested/foo.txt' => 'flat_foo.txt' 24 | 25 | # Filter all 26 | filter do |src, config| 27 | src.gsub(//, config.version.full) 28 | end 29 | 30 | # filter full src only 31 | filter :src do |src, config| 32 | src.gsub(//, 'src') 33 | end 34 | 35 | # filter minified src only 36 | filter :min do |src, config| 37 | src.gsub(//, 'min') 38 | end 39 | 40 | after_write do |config| 41 | 42 | config.version.releaseable.each do |v| 43 | from = "#{config.src_dir}/foo.txt" 44 | to = "#{config.target_dir}/#{v}/foo.txt" 45 | puts "copying #{to}" 46 | FileUtils.cp(from, to) 47 | end 48 | 49 | end -------------------------------------------------------------------------------- /lib/jbundle/server.rb: -------------------------------------------------------------------------------- 1 | require 'rack' 2 | module JBundle 3 | 4 | class Server 5 | 6 | 7 | def initialize(jfile = JBundle::JFILE) 8 | @jfile = jfile 9 | end 10 | 11 | # Configure JBundle on every request. 12 | # Expensive but allows for reloading changes to JFile 13 | def call(env) 14 | bundle_name = env['PATH_INFO'].split('/').last 15 | begin 16 | JBundle.config_from_file(@jfile) 17 | compiler = JBundle.build(bundle_name) 18 | body = compiler.buildable? ? compiler.src : compiler.raw_src 19 | [200, {'Content-Type' => ::Rack::Mime.mime_type(compiler.ext)}, [body]] 20 | rescue NoBundleError => boom 21 | p = bundle_name == '' ? '[bundle_name].js' : bundle_name 22 | [404, {'Content-Type' => 'text/plain'}, ["No bundle defined. Try defining /#{p} in your JFile"]] 23 | rescue NoJFileError => boom 24 | [404, {'Content-Type' => 'text/plain'}, [boom.message]] 25 | end 26 | 27 | end 28 | 29 | end 30 | 31 | end -------------------------------------------------------------------------------- /jbundle.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path("../lib/jbundle/version", __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "jbundle" 6 | s.version = JBundle::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.authors = ['Ismael Celis'] 9 | s.email = ['ismaelct@gmail.com'] 10 | s.homepage = "http://github.com/ismasan/jbundle" 11 | s.description = "Writes versioned, bundled and minified javascript files and dependencies" 12 | s.summary = "Good for releasing javascript libraries composed of many files. Writes files apt for deploying to CDNs." 13 | 14 | s.required_rubygems_version = ">= 1.3.6" 15 | s.rubyforge_project = "jbundle" 16 | 17 | s.add_development_dependency "bundler", ">= 1.0.0" 18 | s.add_development_dependency "rspec", '1.3.1' 19 | s.add_dependency 'closure-compiler' 20 | s.add_dependency 'thor' 21 | s.add_dependency 'rack' 22 | 23 | s.files = `git ls-files`.split("\n") 24 | s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact 25 | s.require_path = 'lib' 26 | end 27 | -------------------------------------------------------------------------------- /lib/jbundle/templates/license.tt: -------------------------------------------------------------------------------- 1 | /* <%= @name %>, version 2 | 3 | Copyright (c) <%= Time.now.year %> [your name here] 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | ------------------------------------------------------------------*/ -------------------------------------------------------------------------------- /lib/jbundle/config.rb: -------------------------------------------------------------------------------- 1 | module JBundle 2 | 3 | class Config 4 | 5 | attr_reader :bundles, :files, :filters, :after_write_blocks 6 | 7 | def initialize 8 | @bundles = [] 9 | @files = [] 10 | @filters = {:all => [], :min => [], :src => []} 11 | @after_write_blocks = [] 12 | @version_options = {:directory => true} 13 | end 14 | 15 | def versioned_directories? 16 | @version_options[:directory] 17 | end 18 | 19 | def version(v = nil, opts = {}) 20 | @version_options.merge! opts 21 | @version = JBundle::Version.new(v) if v 22 | @version 23 | end 24 | 25 | def src_dir(dir = nil) 26 | @src_dir = dir if dir 27 | @src_dir || './' 28 | end 29 | 30 | def target_dir(dir = nil) 31 | @target_dir = dir if dir 32 | @target_dir || './' 33 | end 34 | 35 | def bundle(names, options = {}, &block) 36 | name = (names.is_a?(Hash) ? names.keys.first : names).to_sym 37 | if !b = @bundles.detect{|a| a.name == name} 38 | b = Bundle.new(names, options) 39 | @bundles << b 40 | end 41 | b.instance_eval &block if block_given? 42 | b 43 | end 44 | 45 | def file(f, options = {}) 46 | @files << JBundle::File.new(f, options) 47 | end 48 | 49 | def filter(mode = :all, &block) 50 | filters[mode.to_sym] << block 51 | end 52 | 53 | def after_write(&block) 54 | after_write_blocks << block 55 | end 56 | 57 | def bundles_and_files 58 | @bundles + @files 59 | end 60 | 61 | end 62 | end -------------------------------------------------------------------------------- /lib/jbundle/templates/jasmine/index.html.tt: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /spec/server_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | 4 | describe "JBundle::Server" do 5 | 6 | before do 7 | f = File.join(File.expand_path(File.dirname(__FILE__)), 'JFile') 8 | @server = JBundle::Server.new(f) 9 | end 10 | 11 | describe '#call' do 12 | 13 | describe 'with JavaScript bundle' do 14 | before do 15 | @response = @server.call({'PATH_INFO' => '/foo.js'}) 16 | end 17 | 18 | it 'should be 404 if no JFile found' do 19 | s = JBundle::Server.new("doesn-not-exist") 20 | r = s.call({'PATH_INFO' => '/foo.js'}) 21 | r[0].should == 404 22 | end 23 | 24 | it 'should be 200 OK' do 25 | @response[0].should == 200 26 | end 27 | 28 | it 'should be a javascript response' do 29 | @response[1]['Content-Type'].should == 'application/javascript' 30 | end 31 | 32 | it 'should return content for given bundle' do 33 | @response[2].should == [JBundle.build('foo.js').src] 34 | end 35 | 36 | it 'should be 404 when no bundle found' do 37 | r = @server.call({'PATH_INFO' => '/nonexisting.js'}) 38 | r[0].should == 404 39 | end 40 | end 41 | 42 | describe 'with non-JS file' do 43 | 44 | it 'should find and serve by original name' do 45 | response = @server.call({'PATH_INFO' => '/nested/foo.txt'}) 46 | response[0].should == 200 47 | response[1]['Content-Type'].should == 'text/plain' 48 | response[2].should == ["This file is in a nested subdirectory\n"] 49 | end 50 | 51 | it 'should find and serve by distribution file name' do 52 | response = @server.call({'PATH_INFO' => '/flat_foo.txt'}) 53 | response[0].should == 200 54 | response[1]['Content-Type'].should == 'text/plain' 55 | response[2].should == ["This file is in a nested subdirectory\n"] 56 | end 57 | 58 | end 59 | end 60 | 61 | end -------------------------------------------------------------------------------- /lib/jbundle.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'jbundle/config' 3 | require 'jbundle/bundle_utils' 4 | require 'jbundle/file' 5 | require 'jbundle/bundle' 6 | require 'jbundle/builder' 7 | require 'jbundle/writer' 8 | require 'jbundle/server' 9 | require 'jbundle/version' 10 | 11 | module JBundle 12 | 13 | JFILE = 'Jfile' 14 | 15 | class NoJFileError < StandardError;end 16 | class NoBundleError < StandardError;end 17 | 18 | class << self 19 | 20 | attr_accessor :logger 21 | 22 | def log(msg) 23 | logger.call(msg) 24 | end 25 | 26 | def config(&block) 27 | @current_config ||= JBundle::Config.new 28 | @current_config.instance_eval(&block) if block_given? 29 | @current_config 30 | end 31 | 32 | def output 33 | @output ||= Builder.new(config).build! 34 | end 35 | 36 | def reset! 37 | @current_config = nil 38 | @output = nil 39 | self 40 | end 41 | 42 | def write! 43 | _dist = config.target_dir || './dist' 44 | clear_current_dist_dir(_dist) 45 | out = output.map do |compiler| 46 | Writer.new(compiler, config, _dist).write 47 | end.flatten 48 | run_after_write unless config.after_write_blocks.empty? 49 | out 50 | end 51 | 52 | def build(name) 53 | Builder.new(config).build_one find(name) 54 | end 55 | 56 | def find(name) 57 | found = config.bundles_and_files.detect {|f| f.original_name == name || f.name == name} 58 | raise NoBundleError, "No bundle or file found with name #{name}" unless found 59 | found 60 | end 61 | 62 | def config_from_file(file) 63 | raise NoJFileError, "You need to define #{file}" unless ::File.exists?(file) 64 | reset! 65 | config.instance_eval( ::File.read(file), file ) 66 | end 67 | 68 | def run_after_write 69 | config.after_write_blocks.each {|block| block.call(config)} 70 | end 71 | 72 | def clear_current_dist_dir(dist_dir) 73 | config.version.releaseable.each do |version_dir| 74 | FileUtils.rm_rf ::File.join(dist_dir, version_dir) 75 | end 76 | end 77 | 78 | end 79 | 80 | self.logger = lambda {|msg| puts "#{msg}\n"} 81 | 82 | end 83 | 84 | require 'jbundle/command_line' 85 | -------------------------------------------------------------------------------- /lib/jbundle/builder.rb: -------------------------------------------------------------------------------- 1 | require 'closure-compiler' 2 | 3 | module JBundle 4 | 5 | class Compiler 6 | 7 | BUILDABLE_FILES = ['.js'] 8 | 9 | attr_reader :name, :src_dir, :dir 10 | 11 | def initialize(name, file_list, config) 12 | @config, @file_list, @src_dir = config, file_list, config.src_dir 13 | @name, @dir = parse_name(name) 14 | end 15 | 16 | def ext 17 | @ext ||= ::File.extname(name) 18 | end 19 | 20 | def buildable? 21 | BUILDABLE_FILES.include?(ext) 22 | end 23 | 24 | def gzip? 25 | @file_list.options[:gzip] 26 | end 27 | 28 | # This only makes sense for one-file objects 29 | def src_path 30 | ::File.join(@src_dir, @file_list.original_name) 31 | end 32 | 33 | def src 34 | @src ||= filter(filtered_licenses + filtered_src, :src) 35 | end 36 | 37 | def min 38 | @min ||= filter(filtered_licenses, :min) + Closure::Compiler.new.compile(filter(filtered_src, :min)) 39 | end 40 | 41 | def filtered_licenses 42 | @filtered_licenses ||= filter(licenses, :all) 43 | end 44 | 45 | def filtered_src 46 | @filtered_src ||= filter(raw_src, :all) 47 | end 48 | 49 | def min_name 50 | @min_name ||= ( 51 | ext = ::File.extname(name) 52 | name.sub(ext, '') + '.min' + ext 53 | ) 54 | end 55 | 56 | def licenses 57 | @licenses ||= if @file_list.respond_to?(:licenses) 58 | read_files @file_list.licenses 59 | else 60 | '' 61 | end 62 | end 63 | 64 | def raw_src 65 | @raw_src ||= read_files(@file_list) 66 | end 67 | 68 | protected 69 | 70 | def read_files(file_names) 71 | file_names.inject('') do |mem, file_name| 72 | mem << ::File.read(::File.join(src_dir, file_name)) 73 | mem << "\n" 74 | mem 75 | end 76 | end 77 | 78 | def parse_name(name) 79 | n = ::File.basename(name) 80 | d = ::File.dirname(name) if name =~ /\// 81 | [n,d] 82 | end 83 | 84 | def filter(content, mode) 85 | @config.filters[mode].each do |filter| 86 | content = filter.call(content, @config) 87 | end 88 | content 89 | end 90 | 91 | end 92 | 93 | 94 | class Builder 95 | 96 | def initialize(config) 97 | @config = config 98 | @sources = [] 99 | end 100 | 101 | def build! 102 | @sources = @config.bundles_and_files.map do |b| 103 | build_one b 104 | end 105 | end 106 | 107 | def build_one(f) 108 | Compiler.new(f.name, f, @config) 109 | end 110 | 111 | end 112 | 113 | end -------------------------------------------------------------------------------- /lib/jbundle/templates/jasmine/jasmine.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; 3 | } 4 | 5 | 6 | .jasmine_reporter a:visited, .jasmine_reporter a { 7 | color: #303; 8 | } 9 | 10 | .jasmine_reporter a:hover, .jasmine_reporter a:active { 11 | color: blue; 12 | } 13 | 14 | .run_spec { 15 | float:right; 16 | padding-right: 5px; 17 | font-size: .8em; 18 | text-decoration: none; 19 | } 20 | 21 | .jasmine_reporter { 22 | margin: 0 5px; 23 | } 24 | 25 | .banner { 26 | color: #303; 27 | background-color: #fef; 28 | padding: 5px; 29 | } 30 | 31 | .logo { 32 | float: left; 33 | font-size: 1.1em; 34 | padding-left: 5px; 35 | } 36 | 37 | .logo .version { 38 | font-size: .6em; 39 | padding-left: 1em; 40 | } 41 | 42 | .runner.running { 43 | background-color: yellow; 44 | } 45 | 46 | 47 | .options { 48 | text-align: right; 49 | font-size: .8em; 50 | } 51 | 52 | 53 | 54 | 55 | .suite { 56 | border: 1px outset gray; 57 | margin: 5px 0; 58 | padding-left: 1em; 59 | } 60 | 61 | .suite .suite { 62 | margin: 5px; 63 | } 64 | 65 | .suite.passed { 66 | background-color: #dfd; 67 | } 68 | 69 | .suite.failed { 70 | background-color: #fdd; 71 | } 72 | 73 | .spec { 74 | margin: 5px; 75 | padding-left: 1em; 76 | clear: both; 77 | } 78 | 79 | .spec.failed, .spec.passed, .spec.skipped { 80 | padding-bottom: 5px; 81 | border: 1px solid gray; 82 | } 83 | 84 | .spec.failed { 85 | background-color: #fbb; 86 | border-color: red; 87 | } 88 | 89 | .spec.passed { 90 | background-color: #bfb; 91 | border-color: green; 92 | } 93 | 94 | .spec.skipped { 95 | background-color: #bbb; 96 | } 97 | 98 | .messages { 99 | border-left: 1px dashed gray; 100 | padding-left: 1em; 101 | padding-right: 1em; 102 | } 103 | 104 | .passed { 105 | background-color: #cfc; 106 | display: none; 107 | } 108 | 109 | .failed { 110 | background-color: #fbb; 111 | } 112 | 113 | .skipped { 114 | color: #777; 115 | background-color: #eee; 116 | display: none; 117 | } 118 | 119 | 120 | /*.resultMessage {*/ 121 | /*white-space: pre;*/ 122 | /*}*/ 123 | 124 | .resultMessage span.result { 125 | display: block; 126 | line-height: 2em; 127 | color: black; 128 | } 129 | 130 | .resultMessage .mismatch { 131 | color: black; 132 | } 133 | 134 | .stackTrace { 135 | white-space: pre; 136 | font-size: .8em; 137 | margin-left: 10px; 138 | max-height: 5em; 139 | overflow: auto; 140 | border: 1px inset red; 141 | padding: 1em; 142 | background: #eef; 143 | } 144 | 145 | .finished-at { 146 | padding-left: 1em; 147 | font-size: .6em; 148 | } 149 | 150 | .show-passed .passed, 151 | .show-skipped .skipped { 152 | display: block; 153 | } 154 | 155 | 156 | #jasmine_content { 157 | position:fixed; 158 | right: 100%; 159 | } 160 | 161 | .runner { 162 | border: 1px solid gray; 163 | display: block; 164 | margin: 5px 0; 165 | padding: 2px 0 2px 10px; 166 | } 167 | -------------------------------------------------------------------------------- /lib/jbundle/command_line.rb: -------------------------------------------------------------------------------- 1 | require 'thor' 2 | module JBundle 3 | 4 | class CommandLine < Thor 5 | 6 | include Thor::Actions 7 | 8 | default_task :bundle 9 | 10 | map "s" => :server 11 | map "-v" => :version 12 | 13 | desc "bundle", "Minify and bundle bundle declarations into dist directory" 14 | method_option :jfile, :default => JBundle::JFILE, :aliases => "-j" 15 | def bundle 16 | begin 17 | JBundle.config_from_file(options[:jfile]) 18 | JBundle.write! 19 | rescue JBundle::NoJFileError => boom 20 | puts boom.message 21 | end 22 | end 23 | 24 | desc 'server', 'Start test rack server on port 5555' 25 | method_option :port, :default => "5555", :aliases => "-p" 26 | method_option :jfile, :default => JBundle::JFILE, :aliases => "-j" 27 | def server 28 | JBundle.config_from_file(options[:jfile]) 29 | say "Starting test server on http://localhost:#{options[:port]}. Available bundles:", :yellow 30 | JBundle.config.bundles_and_files.each do |f| 31 | say "- /#{f.original_name} ==> /#{f.name}", :green 32 | end 33 | say "Run tests on ./tests/index.html" 34 | 35 | handler = Rack::Handler.default 36 | downward = false 37 | ['INT', 'TERM', 'QUIT'].each do |signal| 38 | trap(signal) do 39 | exit! if downward 40 | downward = true 41 | handler.shutdown if handler.respond_to?(:shutdown) 42 | Process.wait rescue nil 43 | say 'Shutting down test server', :yellow 44 | exit! 45 | end 46 | end 47 | handler.run JBundle::Server.new, {:Port => options[:port]} 48 | end 49 | 50 | desc 'version', 'Print installed JBundle version' 51 | def version 52 | say JBundle::VERSION, :green 53 | end 54 | 55 | def self.source_root 56 | ::File.dirname(__FILE__) 57 | end 58 | 59 | AFTER_INIT_MESSAGE = %( 60 | Done. Try it! 61 | 62 | jbundle s 63 | open test/index.html 64 | 65 | Then package your work 66 | 67 | jbundle 68 | ) 69 | 70 | desc 'init', 'Create example JFile and test stubs. Usage: jbundle init foo.js' 71 | method_option :tests, :default => 'qunit', :aliases => '-t' 72 | def init(name = nil) 73 | name = ask("Name of your library (ie. foo.js, awesome.js, etc.):") unless name 74 | @name = name 75 | @klass_name = name.sub('.js', '').split(/[^a-z0-9]/i).map{|w| w.capitalize}.join 76 | 77 | template('templates/jfile.tt', "JFile") 78 | empty_directory 'src' 79 | template('templates/license.tt', "src/license.txt") 80 | template('templates/lib.tt', "src/#{name}") 81 | case options[:tests] 82 | when 'qunit' then init_qunit 83 | when 'jasmine' then init_jasmine 84 | else puts "Don't know how to initalize tests for #{options[:tests].inspect}" 85 | end 86 | empty_directory 'dist' 87 | say AFTER_INIT_MESSAGE, :yellow 88 | end 89 | 90 | private 91 | 92 | def init_qunit 93 | empty_directory 'test' 94 | template 'templates/qunit/index.html.tt', 'test/index.html' 95 | template 'templates/qunit/tests.js.tt', 'test/tests.js' 96 | copy_file 'templates/qunit/qunit.js', 'test/qunit.js' 97 | copy_file 'templates/qunit/qunit.css', 'test/qunit.css' 98 | end 99 | 100 | def init_jasmine 101 | empty_directory 'test' 102 | template 'templates/jasmine/index.html.tt', 'test/index.html' 103 | template 'templates/jasmine/tests.js.tt', 'test/tests.js' 104 | [ 105 | 'spec_helper.js', 106 | 'jasmine_favicon.png', 107 | 'jasmine.js', 108 | 'jasmine.css', 109 | 'jasmine-html.js' 110 | ].each do |file| 111 | template "templates/jasmine/#{file}", "test/#{file}" 112 | end 113 | end 114 | 115 | end 116 | end -------------------------------------------------------------------------------- /lib/jbundle/writer.rb: -------------------------------------------------------------------------------- 1 | require 'zlib' 2 | 3 | module JBundle 4 | 5 | class Version 6 | 7 | def initialize(string) 8 | @major, @minor, @patch = string.split('.') 9 | raise "require (major.minor.patch) eg: 1.3.1" unless @major && @minor && @patch 10 | end 11 | 12 | def full 13 | [@major, @minor, @patch].join('.') 14 | end 15 | 16 | def major_minor 17 | [@major, @minor].join('.') 18 | end 19 | 20 | def releaseable 21 | prerelease? ? [full] : [full, major_minor] 22 | end 23 | 24 | def to_s 25 | full 26 | end 27 | 28 | def inspect 29 | "[JBundle::Version] #{full}" 30 | end 31 | 32 | protected 33 | 34 | def prerelease? 35 | @patch =~ /-pre/ 36 | end 37 | 38 | end 39 | 40 | class Writer 41 | # Interpolation tokens 42 | TOKENS = { 43 | :version => '[:version]' 44 | } 45 | 46 | def initialize(compiler, config, target_dir) 47 | @compiler, @config, @version, @target_dir = compiler, config, config.version, target_dir 48 | @out = [] 49 | end 50 | 51 | def write 52 | release_targets do |versioned_target, version_string| 53 | if @compiler.buildable? # versionable JS file 54 | @out << write_file( 55 | @compiler.src, 56 | versioned_target, 57 | @compiler.dir, 58 | interpolate(@compiler.name, TOKENS[:version] => version_string) 59 | ) 60 | @out << write_file( 61 | @compiler.min, 62 | versioned_target, 63 | @compiler.dir, 64 | interpolate(@compiler.min_name, TOKENS[:version] => version_string) 65 | ) 66 | if @compiler.gzip? 67 | @out << write_gzip_file( 68 | @compiler.src, 69 | versioned_target, 70 | @compiler.dir, 71 | interpolate(@compiler.name, TOKENS[:version] => version_string) + ".gz" 72 | ) 73 | @out << write_gzip_file( 74 | @compiler.min, 75 | versioned_target, 76 | @compiler.dir, 77 | interpolate(@compiler.min_name, TOKENS[:version] => version_string) + ".gz" 78 | ) 79 | end 80 | else # Other files (HTML, SWF, etc) 81 | @out << copy_file( 82 | @compiler.src_path, 83 | versioned_target, 84 | @compiler.dir, 85 | interpolate(@compiler.name, TOKENS[:version] => version_string) 86 | ) 87 | end 88 | end 89 | @out 90 | end 91 | 92 | protected 93 | 94 | def interpolate(string, tokens) 95 | tokens.each do |key, value| 96 | string = string.gsub(key, value) 97 | end 98 | string 99 | end 100 | 101 | def release_targets(&block) 102 | @version.releaseable.each do |version_string| 103 | versioned_target = @config.versioned_directories? ? ::File.join(@target_dir, version_string) : @target_dir 104 | yield versioned_target, version_string 105 | end 106 | end 107 | 108 | def copy_file(src, target, subdir, name) 109 | sub = ::File.join([target, subdir].compact) 110 | FileUtils.mkdir_p sub 111 | target = ::File.join(sub, name) 112 | JBundle.log("Copying to #{target}") 113 | FileUtils.cp src, target 114 | target 115 | end 116 | 117 | def write_file(content, dir_name, subdir, file_name) 118 | sub = ::File.join([dir_name, subdir].compact) 119 | FileUtils.mkdir_p sub 120 | target = ::File.join(sub, file_name) 121 | JBundle.log("Writing to #{target}") 122 | ::File.open(target, 'w') do |f| 123 | f.write content 124 | end 125 | target 126 | end 127 | 128 | def write_gzip_file(content, dir_name, subdir, file_name) 129 | sub = ::File.join([dir_name, subdir].compact) 130 | FileUtils.mkdir_p sub 131 | target = ::File.join(sub, file_name) 132 | JBundle.log("Writing to #{target}") 133 | ::File.open(target, 'w') do |f| 134 | gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION) 135 | gz.write content 136 | gz.close 137 | end 138 | target 139 | end 140 | 141 | end 142 | 143 | end -------------------------------------------------------------------------------- /lib/jbundle/templates/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 15px 15px 0 0; 42 | -moz-border-radius: 15px 15px 0 0; 43 | -webkit-border-top-right-radius: 15px; 44 | -webkit-border-top-left-radius: 15px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-banner { 58 | height: 5px; 59 | } 60 | 61 | #qunit-testrunner-toolbar { 62 | padding: 0.5em 0 0.5em 2em; 63 | color: #5E740B; 64 | background-color: #eee; 65 | } 66 | 67 | #qunit-userAgent { 68 | padding: 0.5em 0 0.5em 2.5em; 69 | background-color: #2b81af; 70 | color: #fff; 71 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 72 | } 73 | 74 | 75 | /** Tests: Pass/Fail */ 76 | 77 | #qunit-tests { 78 | list-style-position: inside; 79 | } 80 | 81 | #qunit-tests li { 82 | padding: 0.4em 0.5em 0.4em 2.5em; 83 | border-bottom: 1px solid #fff; 84 | list-style-position: inside; 85 | } 86 | 87 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 88 | display: none; 89 | } 90 | 91 | #qunit-tests li strong { 92 | cursor: pointer; 93 | } 94 | 95 | #qunit-tests li a { 96 | padding: 0.5em; 97 | color: #c2ccd1; 98 | text-decoration: none; 99 | } 100 | #qunit-tests li a:hover, 101 | #qunit-tests li a:focus { 102 | color: #000; 103 | } 104 | 105 | #qunit-tests ol { 106 | margin-top: 0.5em; 107 | padding: 0.5em; 108 | 109 | background-color: #fff; 110 | 111 | border-radius: 15px; 112 | -moz-border-radius: 15px; 113 | -webkit-border-radius: 15px; 114 | 115 | box-shadow: inset 0px 2px 13px #999; 116 | -moz-box-shadow: inset 0px 2px 13px #999; 117 | -webkit-box-shadow: inset 0px 2px 13px #999; 118 | } 119 | 120 | #qunit-tests table { 121 | border-collapse: collapse; 122 | margin-top: .2em; 123 | } 124 | 125 | #qunit-tests th { 126 | text-align: right; 127 | vertical-align: top; 128 | padding: 0 .5em 0 0; 129 | } 130 | 131 | #qunit-tests td { 132 | vertical-align: top; 133 | } 134 | 135 | #qunit-tests pre { 136 | margin: 0; 137 | white-space: pre-wrap; 138 | word-wrap: break-word; 139 | } 140 | 141 | #qunit-tests del { 142 | background-color: #e0f2be; 143 | color: #374e0c; 144 | text-decoration: none; 145 | } 146 | 147 | #qunit-tests ins { 148 | background-color: #ffcaca; 149 | color: #500; 150 | text-decoration: none; 151 | } 152 | 153 | /*** Test Counts */ 154 | 155 | #qunit-tests b.counts { color: black; } 156 | #qunit-tests b.passed { color: #5E740B; } 157 | #qunit-tests b.failed { color: #710909; } 158 | 159 | #qunit-tests li li { 160 | margin: 0.5em; 161 | padding: 0.4em 0.5em 0.4em 0.5em; 162 | background-color: #fff; 163 | border-bottom: none; 164 | list-style-position: inside; 165 | } 166 | 167 | /*** Passing Styles */ 168 | 169 | #qunit-tests li li.pass { 170 | color: #5E740B; 171 | background-color: #fff; 172 | border-left: 26px solid #C6E746; 173 | } 174 | 175 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 176 | #qunit-tests .pass .test-name { color: #366097; } 177 | 178 | #qunit-tests .pass .test-actual, 179 | #qunit-tests .pass .test-expected { color: #999999; } 180 | 181 | #qunit-banner.qunit-pass { background-color: #C6E746; } 182 | 183 | /*** Failing Styles */ 184 | 185 | #qunit-tests li li.fail { 186 | color: #710909; 187 | background-color: #fff; 188 | border-left: 26px solid #EE5757; 189 | } 190 | 191 | #qunit-tests > li:last-child { 192 | border-radius: 0 0 15px 15px; 193 | -moz-border-radius: 0 0 15px 15px; 194 | -webkit-border-bottom-right-radius: 15px; 195 | -webkit-border-bottom-left-radius: 15px; 196 | } 197 | 198 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 199 | #qunit-tests .fail .test-name, 200 | #qunit-tests .fail .module-name { color: #000000; } 201 | 202 | #qunit-tests .fail .test-actual { color: #EE5757; } 203 | #qunit-tests .fail .test-expected { color: green; } 204 | 205 | #qunit-banner.qunit-fail { background-color: #EE5757; } 206 | 207 | 208 | /** Result */ 209 | 210 | #qunit-testresult { 211 | padding: 0.5em 0.5em 0.5em 2.5em; 212 | 213 | color: #2b81af; 214 | background-color: #D2E0E6; 215 | 216 | border-bottom: 1px solid white; 217 | } 218 | 219 | /** Fixture */ 220 | 221 | #qunit-fixture { 222 | position: absolute; 223 | top: -10000px; 224 | left: -10000px; 225 | } -------------------------------------------------------------------------------- /spec/jbundle_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | require 'fileutils' 3 | 4 | describe "JBundle" do 5 | 6 | before do 7 | JBundle.config_from_file ::File.join(::File.dirname(__FILE__),'JFile') 8 | end 9 | 10 | it 'should have a version' do 11 | JBundle.config.version.to_s.should == '1.6.1' 12 | end 13 | 14 | it 'should have bundles' do 15 | JBundle.config.bundles[0].name.should == 'foo.js' 16 | JBundle.config.bundles[1].name.should == 'foo2.js' 17 | end 18 | 19 | context 'bundling' do 20 | 21 | it 'should build single bundles' do 22 | JBundle.build('foo.js').src.should == "var VERSION = '1.6.1';\nvar a1 = 1;\nvar a2 = 2;\n" 23 | JBundle.build('file4.js').min.should == "var a4=4,src_mode=\"min\";\n" 24 | end 25 | 26 | it 'should bundle bundles' do 27 | JBundle.output.size.should == 6 28 | JBundle.output[0].name.should == 'foo.js' 29 | JBundle.output[0].src.should == "var VERSION = '1.6.1';\nvar a1 = 1;\nvar a2 = 2;\n" 30 | JBundle.output[0].min.should == "var VERSION=\"1.6.1\",a1=1,a2=2;\n" 31 | end 32 | 33 | it 'should not minify licenses and run min and src filters' do 34 | JBundle.build('foo2.js').src.should == "/* Version: 1.6.1. This is the src version.\nThis is a license\n-----------------------*/\nvar a3 = 3;\nvar a4 = 4;\nvar src_mode = 'src';\n" 35 | JBundle.build('foo2.js').min.should == "/* Version: 1.6.1. This is the min version.\nThis is a license\n-----------------------*/\nvar a3=3,a4=4,src_mode=\"min\";\n" 36 | end 37 | 38 | end 39 | 40 | context 'before writing' do 41 | it 'should clear distribution directories before writing' do 42 | afile = DIST + '/1.6.1/shouldnotbehere.txt' 43 | File.open(afile, 'w+'){|f| f.write('Hi')} 44 | File.exists?(afile).should be_true 45 | JBundle.write! 46 | File.exists?(afile).should be_false 47 | end 48 | end 49 | 50 | context 'writing' do 51 | 52 | before do 53 | #FileUtils.rm_rf DIST 54 | @written_files = JBundle.write! 55 | end 56 | 57 | it 'should write files' do 58 | File.exist?(DIST + '/1.6.1/foo.js').should be_true 59 | File.exist?(DIST + '/1.6.1/foo.min.js').should be_true 60 | File.exist?(DIST + '/1.6.1/foo2.js').should be_true 61 | File.exist?(DIST + '/1.6.1/foo2.min.js').should be_true 62 | File.exist?(DIST + '/1.6.1/file4.js').should be_true 63 | File.exist?(DIST + '/1.6.1/file4.min.js').should be_true 64 | File.exist?(DIST + '/1.6.1/text.txt').should be_true 65 | File.exist?(DIST + '/1.6.1/text.min.txt').should be_false 66 | 67 | File.exist?(DIST + '/1.6/foo.js').should be_true 68 | File.exist?(DIST + '/1.6/foo.min.js').should be_true 69 | File.exist?(DIST + '/1.6/foo2.js').should be_true 70 | File.exist?(DIST + '/1.6/foo2.min.js').should be_true 71 | File.exist?(DIST + '/1.6/file4.js').should be_true 72 | File.exist?(DIST + '/1.6/file4.min.js').should be_true 73 | File.exist?(DIST + '/1.6/text.txt').should be_true 74 | File.exist?(DIST + '/1.6/text.min.txt').should be_false 75 | end 76 | 77 | it 'should have run after_write block' do 78 | File.exist?(DIST + '/1.6.1/foo.txt').should be_true 79 | File.exist?(DIST + '/1.6/foo.txt').should be_true 80 | end 81 | 82 | it 'should copy files in subdirectories' do 83 | File.exist?(DIST + '/1.6.1/nested/foo.txt').should be_true 84 | File.exist?(DIST + '/1.6/nested/foo.txt').should be_true 85 | end 86 | 87 | it 'should copy files to new names if hash given' do 88 | File.exist?(DIST + '/1.6.1/flat_foo.txt').should be_true 89 | File.exist?(DIST + '/1.6/flat_foo.txt').should be_true 90 | end 91 | 92 | it 'should return a list of files written' do 93 | @written_files.should == [ 94 | DIST + '/1.6.1/foo.js', 95 | DIST + '/1.6.1/foo.min.js', 96 | DIST + '/1.6/foo.js', 97 | DIST + '/1.6/foo.min.js', 98 | 99 | DIST + '/1.6.1/foo2.js', 100 | DIST + '/1.6.1/foo2.min.js', 101 | DIST + '/1.6/foo2.js', 102 | DIST + '/1.6/foo2.min.js', 103 | 104 | DIST + '/1.6.1/file4.js', 105 | DIST + '/1.6.1/file4.min.js', 106 | DIST + '/1.6/file4.js', 107 | DIST + '/1.6/file4.min.js', 108 | 109 | DIST + '/1.6.1/text.txt', 110 | DIST + '/1.6/text.txt', 111 | 112 | DIST + '/1.6.1/nested/foo.txt', 113 | DIST + '/1.6/nested/foo.txt', 114 | 115 | DIST + '/1.6.1/flat_foo.txt', 116 | DIST + '/1.6/flat_foo.txt' 117 | ] 118 | end 119 | 120 | end 121 | 122 | end 123 | 124 | describe 'JBundle', 'with no versioned directory and version in bundle name' do 125 | 126 | before do 127 | JBundle.reset!.config do 128 | version '0.0.1', :directory => nil 129 | 130 | src_dir ::File.dirname(__FILE__) + '/test_src' 131 | target_dir DIST 132 | 133 | bundle 'foo.js' => 'foo-[:version].js' do 134 | license 'license.txt' 135 | file 'file3.js' 136 | end 137 | 138 | file 'file3.js' => 'bar-[:version].js' 139 | end 140 | 141 | @written_files = JBundle.write! 142 | end 143 | 144 | it 'should find by original name, not the interpolated version' do 145 | JBundle.build('foo.js').should be_kind_of(JBundle::Compiler) 146 | end 147 | 148 | it 'should create versioned files in dist dir, with no versioned directories' do 149 | @written_files.should == [ 150 | DIST + '/foo-0.0.1.js', 151 | DIST + '/foo-0.0.1.min.js', 152 | DIST + '/foo-0.0.js', 153 | DIST + '/foo-0.0.min.js', 154 | DIST + '/bar-0.0.1.js', 155 | DIST + '/bar-0.0.1.min.js', 156 | DIST + '/bar-0.0.js', 157 | DIST + '/bar-0.0.min.js' 158 | ] 159 | end 160 | 161 | it 'should create versioned files with no version directory' do 162 | File.exist?(DIST + '/foo-0.0.1.js').should be_true 163 | File.exist?(DIST + '/foo-0.0.1.min.js').should be_true 164 | File.exist?(DIST + '/foo-0.0.js').should be_true 165 | File.exist?(DIST + '/foo-0.0.min.js').should be_true 166 | File.exist?(DIST + '/bar-0.0.1.js').should be_true 167 | File.exist?(DIST + '/bar-0.0.1.min.js').should be_true 168 | File.exist?(DIST + '/bar-0.0.js').should be_true 169 | File.exist?(DIST + '/bar-0.0.min.js').should be_true 170 | end 171 | 172 | end 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## JBundle 2 | 3 | Ruby utility to help in developing JavaScript libraries. Lets you declare JavaScript libraries composed of multiple files. Easily bundle and minify your JavaScript bundles when you're done. Includes a Rack server for easy testing. 4 | 5 | ## Installation 6 | 7 | JBundle is a Ruby gem. 8 | 9 | gem install jbundle 10 | 11 | ## Usage 12 | 13 | Define a set of javascript files to bundle and minify 14 | 15 | ```ruby 16 | JBundle.config do 17 | version '1.6.1' 18 | 19 | src_dir File.dirname(__FILE__) + '/src' 20 | 21 | bundle 'foo.js' do 22 | file 'file1.js' 23 | file 'file2.js' 24 | end 25 | 26 | # gzip foo2.js and foo2.min.js 27 | bundle 'foo2.js', gzip: true do 28 | file 'file3.js' 29 | file 'file4.js' 30 | end 31 | 32 | file 'file4.js' 33 | 34 | file 'text.txt' 35 | 36 | # Filters can be use for string substitution 37 | filter do |src, config| 38 | src.gsub(//, config.version) 39 | end 40 | 41 | target_dir 'dist' 42 | 43 | end 44 | ``` 45 | 46 | Then write them to the configured target directory 47 | 48 | JBundle.write! 49 | 50 | JBundle.write! returns an array of paths of all files written. 51 | 52 | This will write the following files: 53 | 54 | 'dist/1.6.1/foo.js' 55 | 'dist/1.6.1/foo.min.js' 56 | 'dist/1.6.1/foo2.js' 57 | 'dist/1.6.1/foo2.min.js' 58 | 'dist/1.6.1/file4.js' 59 | 'dist/1.6.1/file4.min.js' 60 | 'dist/1.6.1/text.txt' 61 | 62 | 'dist/1.6/foo.js' 63 | 'dist/1.6/foo.min.js' 64 | 'dist/1.6/foo2.js' 65 | 'dist/1.6/foo2.min.js' 66 | 'dist/1.6/file4.js' 67 | 'dist/1.6/file4.min.js' 68 | 'dist/1.6/text.txt' 69 | 70 | Or you can build a single bundle/file dynamically (ie. for testing, or for serving and caching on first serve) 71 | 72 | ```ruby 73 | JBundle.config_from_file './JFile' 74 | JBundle.build('foo.js').src 75 | ``` 76 | 77 | Or 78 | 79 | ```ruby 80 | JBundle.config_from_file './JFile' 81 | JBundle.build('foo.js').min 82 | ``` 83 | 84 | You can bundle licenses in bundles. Licenses will not be minified even though they end up being part of minified files 85 | 86 | ```ruby 87 | bundle 'foo2.js' do 88 | license 'license.txt' 89 | file 'file3.js' 90 | file 'file4.js' 91 | end 92 | ``` 93 | 94 | All defined filters will run on the src for all these cases. 95 | 96 | ## Versioned file names, jQuery style 97 | 98 | All of the examples above bundle to versioned directories in the "dist" directory. If you want jQuery-style file names, where there's no version directory and the version number is part of the file name, you can do this: 99 | 100 | ```ruby 101 | version '1.6.1', :directory => false 102 | 103 | bundle 'foo.js' => 'foo2-[:version].js' do 104 | license 'license.txt' 105 | file 'file3.js' 106 | file 'file4.js' 107 | end 108 | ``` 109 | 110 | That will produce: 111 | 112 | 'dist/foo-1.6.1.js' 113 | 'dist/foo-1.6.1.min.js' 114 | 'dist/foo-1.6.js' 115 | 'dist/foo-1.6.min.js' 116 | 117 | That works for single-file libraries too: 118 | 119 | ```ruby 120 | file 'jquery.lightbox.js' => 'jquery.lightbox-[:version].js' 121 | ``` 122 | 123 | ## Filters 124 | 125 | You can filter both minified and un-minified source and license content with the filter method 126 | 127 | ```ruby 128 | # Filters can be use for string substitution 129 | filter do |src, config| 130 | src.gsub(//, config.version) 131 | end 132 | ``` 133 | 134 | You can declare filters that run on un-minified output only 135 | 136 | ```ruby 137 | filter :src do |src, config| 138 | src.gsub(//, 'full source') 139 | end 140 | ``` 141 | 142 | ... And minified output only 143 | 144 | ```ruby 145 | filter :min do |src, config| 146 | src.gsub(//, 'minified source') 147 | end 148 | ``` 149 | 150 | All filters must return a copy of the source, so use src.gsub instead of src.gsub! 151 | 152 | 153 | ## JFile 154 | 155 | You can add configuration in a JFile in the root of your project. 156 | 157 | ```ruby 158 | version '1.0.1' 159 | 160 | src_dir './' 161 | 162 | bundle 'foo.js' do 163 | license 'license.txt' 164 | file 'file1.js' 165 | file 'file2.js' 166 | end 167 | 168 | file 'page.html' 169 | 170 | filter do |src, config| 171 | src.gsub! //, config.version.to_s 172 | end 173 | 174 | target_dir 'dist' 175 | ``` 176 | 177 | Then you can bundle everything up with the command line tool 178 | 179 | $ jbundle 180 | 181 | You can run arbitrary code after writing all versioned files by registering an after_write block in your JFile. The following example copies a .swf file from the src dir to all versioned directories 182 | 183 | ```ruby 184 | after_write do |config| 185 | 186 | config.version.releaseable.each do |version| 187 | from = "#{config.src_dir}/foo.swf" 188 | to = "#{config.target_dir}/#{version}/foo.swf" 189 | puts "copying #{to}" 190 | FileUtils.cp(from, to) 191 | end 192 | 193 | end 194 | ``` 195 | 196 | config.version.releaseble returns an array with with all created versions (ie. ['1.6.1', '1.6'] or just ['1.6.1-pre'] for prereleases). 197 | 198 | Files in subdirectories in the src directory will keep the local directory tree, so 199 | 200 | ```ruby 201 | file 'foo/text.txt' 202 | ``` 203 | 204 | Ends up as ./dist/1.6/foo/text.txt and ./dist/1.6.1/foo/text.txt 205 | 206 | You can also copy to a different file name in the target directory using hash notation 207 | 208 | ```ruby 209 | file 'foo/text.txt' => 'bar.txt' 210 | ``` 211 | 212 | ## Pre-releases 213 | 214 | If you want a prerelease not to overwrite the previous point release, suffix it with "-pre", as in: 215 | 216 | ```ruby 217 | version '1.0.1-pre' 218 | ``` 219 | 220 | ## Test server 221 | 222 | JBundle command-line comes with a built-in Rack server that makes it easy to test you JavaScript bundles as you develop them. 223 | 224 | jbundle server 225 | 226 | Starting test server on http://localhost:5555. Available bundles: 227 | - /foo.js 228 | Run tests on ./tests/index.html 229 | 230 | That serves JavaScript bundles defined in your JFile in port 5555. Pass the -p option for a different port. ./tests/index.html runs your tests (Qunit by default) in the ./tests directory 231 | 232 | You can chose what testing framework to use when initialising the project. Options are qunit and jasmine. 233 | 234 | jbundle init foo.js --tests=jasmine 235 | 236 | Learn more about the JBundle command-line with 237 | 238 | jbundle help # all commands 239 | jbundle help server # server command options 240 | 241 | ## Generator 242 | 243 | The command line has a quick generator that creates stub files for your library code, an example file and tests using Qunit. 244 | 245 | jbundle init my_library.js 246 | 247 | create JFile 248 | create src 249 | create src/license.txt 250 | create src/my_library.js 251 | create test 252 | create test/index.html 253 | create test/tests.js 254 | create test/qunit.js 255 | create test/qunit.css 256 | create dist 257 | Done. Try it! 258 | 259 | jbundle s 260 | open test/index.html 261 | 262 | At the moment only Qunit (default) and Jasmine are supported in the generator but others would be easy to add. 263 | 264 | To generate jasmine test stubs, run the command with `-t jasmine` 265 | 266 | If you don't need the test stubs run the command with `--no-tests` 267 | 268 | ## TODO 269 | 270 | - DRY up stuff, better error handling for missing config 271 | -------------------------------------------------------------------------------- /lib/jbundle/templates/jasmine/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.TrivialReporter = function(doc) { 2 | this.document = doc || document; 3 | this.suiteDivs = {}; 4 | this.logRunningSpecs = false; 5 | }; 6 | 7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 8 | var el = document.createElement(type); 9 | 10 | for (var i = 2; i < arguments.length; i++) { 11 | var child = arguments[i]; 12 | 13 | if (typeof child === 'string') { 14 | el.appendChild(document.createTextNode(child)); 15 | } else { 16 | if (child) { el.appendChild(child); } 17 | } 18 | } 19 | 20 | for (var attr in attrs) { 21 | if (attr == "className") { 22 | el[attr] = attrs[attr]; 23 | } else { 24 | el.setAttribute(attr, attrs[attr]); 25 | } 26 | } 27 | 28 | return el; 29 | }; 30 | 31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 32 | var showPassed, showSkipped; 33 | 34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, 35 | this.createDom('div', { className: 'banner' }, 36 | this.createDom('div', { className: 'logo' }, 37 | this.createDom('span', { className: 'title' }, "Jasmine"), 38 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 39 | this.createDom('div', { className: 'options' }, 40 | "Show ", 41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 45 | ) 46 | ), 47 | 48 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 52 | ); 53 | 54 | this.document.body.appendChild(this.outerDiv); 55 | 56 | var suites = runner.suites(); 57 | for (var i = 0; i < suites.length; i++) { 58 | var suite = suites[i]; 59 | var suiteDiv = this.createDom('div', { className: 'suite' }, 60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 62 | this.suiteDivs[suite.id] = suiteDiv; 63 | var parentDiv = this.outerDiv; 64 | if (suite.parentSuite) { 65 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 66 | } 67 | parentDiv.appendChild(suiteDiv); 68 | } 69 | 70 | this.startedAt = new Date(); 71 | 72 | var self = this; 73 | showPassed.onclick = function(evt) { 74 | if (showPassed.checked) { 75 | self.outerDiv.className += ' show-passed'; 76 | } else { 77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 78 | } 79 | }; 80 | 81 | showSkipped.onclick = function(evt) { 82 | if (showSkipped.checked) { 83 | self.outerDiv.className += ' show-skipped'; 84 | } else { 85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 86 | } 87 | }; 88 | }; 89 | 90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 91 | var results = runner.results(); 92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 93 | this.runnerDiv.setAttribute("class", className); 94 | //do it twice for IE 95 | this.runnerDiv.setAttribute("className", className); 96 | var specs = runner.specs(); 97 | var specCount = 0; 98 | for (var i = 0; i < specs.length; i++) { 99 | if (this.specFilter(specs[i])) { 100 | specCount++; 101 | } 102 | } 103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 106 | 107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 108 | }; 109 | 110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 111 | var results = suite.results(); 112 | var status = results.passed() ? 'passed' : 'failed'; 113 | if (results.totalCount === 0) { // todo: change this to check results.skipped 114 | status = 'skipped'; 115 | } 116 | this.suiteDivs[suite.id].className += " " + status; 117 | }; 118 | 119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 120 | if (this.logRunningSpecs) { 121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 122 | } 123 | }; 124 | 125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 126 | var results = spec.results(); 127 | var status = results.passed() ? 'passed' : 'failed'; 128 | if (results.skipped) { 129 | status = 'skipped'; 130 | } 131 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 133 | this.createDom('a', { 134 | className: 'description', 135 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 136 | title: spec.getFullName() 137 | }, spec.description)); 138 | 139 | 140 | var resultItems = results.getItems(); 141 | var messagesDiv = this.createDom('div', { className: 'messages' }); 142 | for (var i = 0; i < resultItems.length; i++) { 143 | var result = resultItems[i]; 144 | 145 | if (result.type == 'log') { 146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 147 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 149 | 150 | if (result.trace.stack) { 151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 152 | } 153 | } 154 | } 155 | 156 | if (messagesDiv.childNodes.length > 0) { 157 | specDiv.appendChild(messagesDiv); 158 | } 159 | 160 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 161 | }; 162 | 163 | jasmine.TrivialReporter.prototype.log = function() { 164 | var console = jasmine.getGlobal().console; 165 | if (console && console.log) { 166 | if (console.log.apply) { 167 | console.log.apply(console, arguments); 168 | } else { 169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 170 | } 171 | } 172 | }; 173 | 174 | jasmine.TrivialReporter.prototype.getLocation = function() { 175 | return this.document.location; 176 | }; 177 | 178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 179 | var paramMap = {}; 180 | var params = this.getLocation().search.substring(1).split('&'); 181 | for (var i = 0; i < params.length; i++) { 182 | var p = params[i].split('='); 183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 184 | } 185 | 186 | if (!paramMap.spec) { 187 | return true; 188 | } 189 | return spec.getFullName().indexOf(paramMap.spec) === 0; 190 | }; 191 | -------------------------------------------------------------------------------- /lib/jbundle/templates/qunit/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var defined = { 14 | setTimeout: typeof window.setTimeout !== "undefined", 15 | sessionStorage: (function() { 16 | try { 17 | return !!sessionStorage.getItem; 18 | } catch(e){ 19 | return false; 20 | } 21 | })() 22 | }; 23 | 24 | var testId = 0; 25 | 26 | var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { 27 | this.name = name; 28 | this.testName = testName; 29 | this.expected = expected; 30 | this.testEnvironmentArg = testEnvironmentArg; 31 | this.async = async; 32 | this.callback = callback; 33 | this.assertions = []; 34 | }; 35 | Test.prototype = { 36 | init: function() { 37 | var tests = id("qunit-tests"); 38 | if (tests) { 39 | var b = document.createElement("strong"); 40 | b.innerHTML = "Running " + this.name; 41 | var li = document.createElement("li"); 42 | li.appendChild( b ); 43 | li.className = "running"; 44 | li.id = this.id = "test-output" + testId++; 45 | tests.appendChild( li ); 46 | } 47 | }, 48 | setup: function() { 49 | if (this.module != config.previousModule) { 50 | if ( config.previousModule ) { 51 | QUnit.moduleDone( { 52 | name: config.previousModule, 53 | failed: config.moduleStats.bad, 54 | passed: config.moduleStats.all - config.moduleStats.bad, 55 | total: config.moduleStats.all 56 | } ); 57 | } 58 | config.previousModule = this.module; 59 | config.moduleStats = { all: 0, bad: 0 }; 60 | QUnit.moduleStart( { 61 | name: this.module 62 | } ); 63 | } 64 | 65 | config.current = this; 66 | this.testEnvironment = extend({ 67 | setup: function() {}, 68 | teardown: function() {} 69 | }, this.moduleTestEnvironment); 70 | if (this.testEnvironmentArg) { 71 | extend(this.testEnvironment, this.testEnvironmentArg); 72 | } 73 | 74 | QUnit.testStart( { 75 | name: this.testName 76 | } ); 77 | 78 | // allow utility functions to access the current test environment 79 | // TODO why?? 80 | QUnit.current_testEnvironment = this.testEnvironment; 81 | 82 | try { 83 | if ( !config.pollution ) { 84 | saveGlobal(); 85 | } 86 | 87 | this.testEnvironment.setup.call(this.testEnvironment); 88 | } catch(e) { 89 | QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); 90 | } 91 | }, 92 | run: function() { 93 | if ( this.async ) { 94 | QUnit.stop(); 95 | } 96 | 97 | if ( config.notrycatch ) { 98 | this.callback.call(this.testEnvironment); 99 | return; 100 | } 101 | try { 102 | this.callback.call(this.testEnvironment); 103 | } catch(e) { 104 | fail("Test " + this.testName + " died, exception and test follows", e, this.callback); 105 | QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); 106 | // else next test will carry the responsibility 107 | saveGlobal(); 108 | 109 | // Restart the tests if they're blocking 110 | if ( config.blocking ) { 111 | start(); 112 | } 113 | } 114 | }, 115 | teardown: function() { 116 | try { 117 | this.testEnvironment.teardown.call(this.testEnvironment); 118 | checkPollution(); 119 | } catch(e) { 120 | QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); 121 | } 122 | }, 123 | finish: function() { 124 | if ( this.expected && this.expected != this.assertions.length ) { 125 | QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); 126 | } 127 | 128 | var good = 0, bad = 0, 129 | tests = id("qunit-tests"); 130 | 131 | config.stats.all += this.assertions.length; 132 | config.moduleStats.all += this.assertions.length; 133 | 134 | if ( tests ) { 135 | var ol = document.createElement("ol"); 136 | 137 | for ( var i = 0; i < this.assertions.length; i++ ) { 138 | var assertion = this.assertions[i]; 139 | 140 | var li = document.createElement("li"); 141 | li.className = assertion.result ? "pass" : "fail"; 142 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); 143 | ol.appendChild( li ); 144 | 145 | if ( assertion.result ) { 146 | good++; 147 | } else { 148 | bad++; 149 | config.stats.bad++; 150 | config.moduleStats.bad++; 151 | } 152 | } 153 | 154 | // store result when possible 155 | if ( QUnit.config.reorder && defined.sessionStorage ) { 156 | if (bad) { 157 | sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); 158 | } else { 159 | sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); 160 | } 161 | } 162 | 163 | if (bad == 0) { 164 | ol.style.display = "none"; 165 | } 166 | 167 | var b = document.createElement("strong"); 168 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 169 | 170 | var a = document.createElement("a"); 171 | a.innerHTML = "Rerun"; 172 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 173 | 174 | addEvent(b, "click", function() { 175 | var next = b.nextSibling.nextSibling, 176 | display = next.style.display; 177 | next.style.display = display === "none" ? "block" : "none"; 178 | }); 179 | 180 | addEvent(b, "dblclick", function(e) { 181 | var target = e && e.target ? e.target : window.event.srcElement; 182 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 183 | target = target.parentNode; 184 | } 185 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 186 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 187 | } 188 | }); 189 | 190 | var li = id(this.id); 191 | li.className = bad ? "fail" : "pass"; 192 | li.removeChild( li.firstChild ); 193 | li.appendChild( b ); 194 | li.appendChild( a ); 195 | li.appendChild( ol ); 196 | 197 | } else { 198 | for ( var i = 0; i < this.assertions.length; i++ ) { 199 | if ( !this.assertions[i].result ) { 200 | bad++; 201 | config.stats.bad++; 202 | config.moduleStats.bad++; 203 | } 204 | } 205 | } 206 | 207 | try { 208 | QUnit.reset(); 209 | } catch(e) { 210 | fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); 211 | } 212 | 213 | QUnit.testDone( { 214 | name: this.testName, 215 | failed: bad, 216 | passed: this.assertions.length - bad, 217 | total: this.assertions.length 218 | } ); 219 | }, 220 | 221 | queue: function() { 222 | var test = this; 223 | synchronize(function() { 224 | test.init(); 225 | }); 226 | function run() { 227 | // each of these can by async 228 | synchronize(function() { 229 | test.setup(); 230 | }); 231 | synchronize(function() { 232 | test.run(); 233 | }); 234 | synchronize(function() { 235 | test.teardown(); 236 | }); 237 | synchronize(function() { 238 | test.finish(); 239 | }); 240 | } 241 | // defer when previous test run passed, if storage is available 242 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); 243 | if (bad) { 244 | run(); 245 | } else { 246 | synchronize(run); 247 | }; 248 | } 249 | 250 | }; 251 | 252 | var QUnit = { 253 | 254 | // call on start of module test to prepend name to all tests 255 | module: function(name, testEnvironment) { 256 | config.currentModule = name; 257 | config.currentModuleTestEnviroment = testEnvironment; 258 | }, 259 | 260 | asyncTest: function(testName, expected, callback) { 261 | if ( arguments.length === 2 ) { 262 | callback = expected; 263 | expected = 0; 264 | } 265 | 266 | QUnit.test(testName, expected, callback, true); 267 | }, 268 | 269 | test: function(testName, expected, callback, async) { 270 | var name = '' + testName + '', testEnvironmentArg; 271 | 272 | if ( arguments.length === 2 ) { 273 | callback = expected; 274 | expected = null; 275 | } 276 | // is 2nd argument a testEnvironment? 277 | if ( expected && typeof expected === 'object') { 278 | testEnvironmentArg = expected; 279 | expected = null; 280 | } 281 | 282 | if ( config.currentModule ) { 283 | name = '' + config.currentModule + ": " + name; 284 | } 285 | 286 | if ( !validTest(config.currentModule + ": " + testName) ) { 287 | return; 288 | } 289 | 290 | var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); 291 | test.module = config.currentModule; 292 | test.moduleTestEnvironment = config.currentModuleTestEnviroment; 293 | test.queue(); 294 | }, 295 | 296 | /** 297 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 298 | */ 299 | expect: function(asserts) { 300 | config.current.expected = asserts; 301 | }, 302 | 303 | /** 304 | * Asserts true. 305 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 306 | */ 307 | ok: function(a, msg) { 308 | a = !!a; 309 | var details = { 310 | result: a, 311 | message: msg 312 | }; 313 | msg = escapeHtml(msg); 314 | QUnit.log(details); 315 | config.current.assertions.push({ 316 | result: a, 317 | message: msg 318 | }); 319 | }, 320 | 321 | /** 322 | * Checks that the first two arguments are equal, with an optional message. 323 | * Prints out both actual and expected values. 324 | * 325 | * Prefered to ok( actual == expected, message ) 326 | * 327 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 328 | * 329 | * @param Object actual 330 | * @param Object expected 331 | * @param String message (optional) 332 | */ 333 | equal: function(actual, expected, message) { 334 | QUnit.push(expected == actual, actual, expected, message); 335 | }, 336 | 337 | notEqual: function(actual, expected, message) { 338 | QUnit.push(expected != actual, actual, expected, message); 339 | }, 340 | 341 | deepEqual: function(actual, expected, message) { 342 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 343 | }, 344 | 345 | notDeepEqual: function(actual, expected, message) { 346 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); 347 | }, 348 | 349 | strictEqual: function(actual, expected, message) { 350 | QUnit.push(expected === actual, actual, expected, message); 351 | }, 352 | 353 | notStrictEqual: function(actual, expected, message) { 354 | QUnit.push(expected !== actual, actual, expected, message); 355 | }, 356 | 357 | raises: function(block, expected, message) { 358 | var actual, ok = false; 359 | 360 | if (typeof expected === 'string') { 361 | message = expected; 362 | expected = null; 363 | } 364 | 365 | try { 366 | block(); 367 | } catch (e) { 368 | actual = e; 369 | } 370 | 371 | if (actual) { 372 | // we don't want to validate thrown error 373 | if (!expected) { 374 | ok = true; 375 | // expected is a regexp 376 | } else if (QUnit.objectType(expected) === "regexp") { 377 | ok = expected.test(actual); 378 | // expected is a constructor 379 | } else if (actual instanceof expected) { 380 | ok = true; 381 | // expected is a validation function which returns true is validation passed 382 | } else if (expected.call({}, actual) === true) { 383 | ok = true; 384 | } 385 | } 386 | 387 | QUnit.ok(ok, message); 388 | }, 389 | 390 | start: function() { 391 | config.semaphore--; 392 | if (config.semaphore > 0) { 393 | // don't start until equal number of stop-calls 394 | return; 395 | } 396 | if (config.semaphore < 0) { 397 | // ignore if start is called more often then stop 398 | config.semaphore = 0; 399 | } 400 | // A slight delay, to avoid any current callbacks 401 | if ( defined.setTimeout ) { 402 | window.setTimeout(function() { 403 | if ( config.timeout ) { 404 | clearTimeout(config.timeout); 405 | } 406 | 407 | config.blocking = false; 408 | process(); 409 | }, 13); 410 | } else { 411 | config.blocking = false; 412 | process(); 413 | } 414 | }, 415 | 416 | stop: function(timeout) { 417 | config.semaphore++; 418 | config.blocking = true; 419 | 420 | if ( timeout && defined.setTimeout ) { 421 | clearTimeout(config.timeout); 422 | config.timeout = window.setTimeout(function() { 423 | QUnit.ok( false, "Test timed out" ); 424 | QUnit.start(); 425 | }, timeout); 426 | } 427 | } 428 | }; 429 | 430 | // Backwards compatibility, deprecated 431 | QUnit.equals = QUnit.equal; 432 | QUnit.same = QUnit.deepEqual; 433 | 434 | // Maintain internal state 435 | var config = { 436 | // The queue of tests to run 437 | queue: [], 438 | 439 | // block until document ready 440 | blocking: true, 441 | 442 | // by default, run previously failed tests first 443 | // very useful in combination with "Hide passed tests" checked 444 | reorder: true, 445 | 446 | noglobals: false, 447 | notrycatch: false 448 | }; 449 | 450 | // Load paramaters 451 | (function() { 452 | var location = window.location || { search: "", protocol: "file:" }, 453 | params = location.search.slice( 1 ).split( "&" ), 454 | length = params.length, 455 | urlParams = {}, 456 | current; 457 | 458 | if ( params[ 0 ] ) { 459 | for ( var i = 0; i < length; i++ ) { 460 | current = params[ i ].split( "=" ); 461 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 462 | // allow just a key to turn on a flag, e.g., test.html?noglobals 463 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 464 | urlParams[ current[ 0 ] ] = current[ 1 ]; 465 | if ( current[ 0 ] in config ) { 466 | config[ current[ 0 ] ] = current[ 1 ]; 467 | } 468 | } 469 | } 470 | 471 | QUnit.urlParams = urlParams; 472 | config.filter = urlParams.filter; 473 | 474 | // Figure out if we're running the tests from a server or not 475 | QUnit.isLocal = !!(location.protocol === 'file:'); 476 | })(); 477 | 478 | // Expose the API as global variables, unless an 'exports' 479 | // object exists, in that case we assume we're in CommonJS 480 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 481 | extend(window, QUnit); 482 | window.QUnit = QUnit; 483 | } else { 484 | extend(exports, QUnit); 485 | exports.QUnit = QUnit; 486 | } 487 | 488 | // define these after exposing globals to keep them in these QUnit namespace only 489 | extend(QUnit, { 490 | config: config, 491 | 492 | // Initialize the configuration options 493 | init: function() { 494 | extend(config, { 495 | stats: { all: 0, bad: 0 }, 496 | moduleStats: { all: 0, bad: 0 }, 497 | started: +new Date, 498 | updateRate: 1000, 499 | blocking: false, 500 | autostart: true, 501 | autorun: false, 502 | filter: "", 503 | queue: [], 504 | semaphore: 0 505 | }); 506 | 507 | var tests = id( "qunit-tests" ), 508 | banner = id( "qunit-banner" ), 509 | result = id( "qunit-testresult" ); 510 | 511 | if ( tests ) { 512 | tests.innerHTML = ""; 513 | } 514 | 515 | if ( banner ) { 516 | banner.className = ""; 517 | } 518 | 519 | if ( result ) { 520 | result.parentNode.removeChild( result ); 521 | } 522 | 523 | if ( tests ) { 524 | result = document.createElement( "p" ); 525 | result.id = "qunit-testresult"; 526 | result.className = "result"; 527 | tests.parentNode.insertBefore( result, tests ); 528 | result.innerHTML = 'Running...
     '; 529 | } 530 | }, 531 | 532 | /** 533 | * Resets the test setup. Useful for tests that modify the DOM. 534 | * 535 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. 536 | */ 537 | reset: function() { 538 | if ( window.jQuery ) { 539 | jQuery( "#qunit-fixture" ).html( config.fixture ); 540 | } else { 541 | var main = id( 'qunit-fixture' ); 542 | if ( main ) { 543 | main.innerHTML = config.fixture; 544 | } 545 | } 546 | }, 547 | 548 | /** 549 | * Trigger an event on an element. 550 | * 551 | * @example triggerEvent( document.body, "click" ); 552 | * 553 | * @param DOMElement elem 554 | * @param String type 555 | */ 556 | triggerEvent: function( elem, type, event ) { 557 | if ( document.createEvent ) { 558 | event = document.createEvent("MouseEvents"); 559 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 560 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 561 | elem.dispatchEvent( event ); 562 | 563 | } else if ( elem.fireEvent ) { 564 | elem.fireEvent("on"+type); 565 | } 566 | }, 567 | 568 | // Safe object type checking 569 | is: function( type, obj ) { 570 | return QUnit.objectType( obj ) == type; 571 | }, 572 | 573 | objectType: function( obj ) { 574 | if (typeof obj === "undefined") { 575 | return "undefined"; 576 | 577 | // consider: typeof null === object 578 | } 579 | if (obj === null) { 580 | return "null"; 581 | } 582 | 583 | var type = Object.prototype.toString.call( obj ) 584 | .match(/^\[object\s(.*)\]$/)[1] || ''; 585 | 586 | switch (type) { 587 | case 'Number': 588 | if (isNaN(obj)) { 589 | return "nan"; 590 | } else { 591 | return "number"; 592 | } 593 | case 'String': 594 | case 'Boolean': 595 | case 'Array': 596 | case 'Date': 597 | case 'RegExp': 598 | case 'Function': 599 | return type.toLowerCase(); 600 | } 601 | if (typeof obj === "object") { 602 | return "object"; 603 | } 604 | return undefined; 605 | }, 606 | 607 | push: function(result, actual, expected, message) { 608 | var details = { 609 | result: result, 610 | message: message, 611 | actual: actual, 612 | expected: expected 613 | }; 614 | 615 | message = escapeHtml(message) || (result ? "okay" : "failed"); 616 | message = '' + message + ""; 617 | expected = escapeHtml(QUnit.jsDump.parse(expected)); 618 | actual = escapeHtml(QUnit.jsDump.parse(actual)); 619 | var output = message + ''; 620 | if (actual != expected) { 621 | output += ''; 622 | output += ''; 623 | } 624 | if (!result) { 625 | var source = sourceFromStacktrace(); 626 | if (source) { 627 | details.source = source; 628 | output += ''; 629 | } 630 | } 631 | output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    Source:
    ' + source +'
    "; 632 | 633 | QUnit.log(details); 634 | 635 | config.current.assertions.push({ 636 | result: !!result, 637 | message: output 638 | }); 639 | }, 640 | 641 | url: function( params ) { 642 | params = extend( extend( {}, QUnit.urlParams ), params ); 643 | var querystring = "?", 644 | key; 645 | for ( key in params ) { 646 | querystring += encodeURIComponent( key ) + "=" + 647 | encodeURIComponent( params[ key ] ) + "&"; 648 | } 649 | return window.location.pathname + querystring.slice( 0, -1 ); 650 | }, 651 | 652 | // Logging callbacks; all receive a single argument with the listed properties 653 | // run test/logs.html for any related changes 654 | begin: function() {}, 655 | // done: { failed, passed, total, runtime } 656 | done: function() {}, 657 | // log: { result, actual, expected, message } 658 | log: function() {}, 659 | // testStart: { name } 660 | testStart: function() {}, 661 | // testDone: { name, failed, passed, total } 662 | testDone: function() {}, 663 | // moduleStart: { name } 664 | moduleStart: function() {}, 665 | // moduleDone: { name, failed, passed, total } 666 | moduleDone: function() {} 667 | }); 668 | 669 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 670 | config.autorun = true; 671 | } 672 | 673 | addEvent(window, "load", function() { 674 | QUnit.begin({}); 675 | 676 | // Initialize the config, saving the execution queue 677 | var oldconfig = extend({}, config); 678 | QUnit.init(); 679 | extend(config, oldconfig); 680 | 681 | config.blocking = false; 682 | 683 | var userAgent = id("qunit-userAgent"); 684 | if ( userAgent ) { 685 | userAgent.innerHTML = navigator.userAgent; 686 | } 687 | var banner = id("qunit-header"); 688 | if ( banner ) { 689 | banner.innerHTML = ' ' + banner.innerHTML + ' ' + 690 | '' + 691 | ''; 692 | addEvent( banner, "change", function( event ) { 693 | var params = {}; 694 | params[ event.target.name ] = event.target.checked ? true : undefined; 695 | window.location = QUnit.url( params ); 696 | }); 697 | } 698 | 699 | var toolbar = id("qunit-testrunner-toolbar"); 700 | if ( toolbar ) { 701 | var filter = document.createElement("input"); 702 | filter.type = "checkbox"; 703 | filter.id = "qunit-filter-pass"; 704 | addEvent( filter, "click", function() { 705 | var ol = document.getElementById("qunit-tests"); 706 | if ( filter.checked ) { 707 | ol.className = ol.className + " hidepass"; 708 | } else { 709 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 710 | ol.className = tmp.replace(/ hidepass /, " "); 711 | } 712 | if ( defined.sessionStorage ) { 713 | if (filter.checked) { 714 | sessionStorage.setItem("qunit-filter-passed-tests", "true"); 715 | } else { 716 | sessionStorage.removeItem("qunit-filter-passed-tests"); 717 | } 718 | } 719 | }); 720 | if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { 721 | filter.checked = true; 722 | var ol = document.getElementById("qunit-tests"); 723 | ol.className = ol.className + " hidepass"; 724 | } 725 | toolbar.appendChild( filter ); 726 | 727 | var label = document.createElement("label"); 728 | label.setAttribute("for", "qunit-filter-pass"); 729 | label.innerHTML = "Hide passed tests"; 730 | toolbar.appendChild( label ); 731 | } 732 | 733 | var main = id('qunit-fixture'); 734 | if ( main ) { 735 | config.fixture = main.innerHTML; 736 | } 737 | 738 | if (config.autostart) { 739 | QUnit.start(); 740 | } 741 | }); 742 | 743 | function done() { 744 | config.autorun = true; 745 | 746 | // Log the last module results 747 | if ( config.currentModule ) { 748 | QUnit.moduleDone( { 749 | name: config.currentModule, 750 | failed: config.moduleStats.bad, 751 | passed: config.moduleStats.all - config.moduleStats.bad, 752 | total: config.moduleStats.all 753 | } ); 754 | } 755 | 756 | var banner = id("qunit-banner"), 757 | tests = id("qunit-tests"), 758 | runtime = +new Date - config.started, 759 | passed = config.stats.all - config.stats.bad, 760 | html = [ 761 | 'Tests completed in ', 762 | runtime, 763 | ' milliseconds.
    ', 764 | '', 765 | passed, 766 | ' tests of ', 767 | config.stats.all, 768 | ' passed, ', 769 | config.stats.bad, 770 | ' failed.' 771 | ].join(''); 772 | 773 | if ( banner ) { 774 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 775 | } 776 | 777 | if ( tests ) { 778 | id( "qunit-testresult" ).innerHTML = html; 779 | } 780 | 781 | if ( typeof document !== "undefined" && document.title ) { 782 | // show ✖ for good, ✔ for bad suite result in title 783 | // use escape sequences in case file gets loaded with non-utf-8-charset 784 | document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title; 785 | } 786 | 787 | QUnit.done( { 788 | failed: config.stats.bad, 789 | passed: passed, 790 | total: config.stats.all, 791 | runtime: runtime 792 | } ); 793 | } 794 | 795 | function validTest( name ) { 796 | var filter = config.filter, 797 | run = false; 798 | 799 | if ( !filter ) { 800 | return true; 801 | } 802 | 803 | var not = filter.charAt( 0 ) === "!"; 804 | if ( not ) { 805 | filter = filter.slice( 1 ); 806 | } 807 | 808 | if ( name.indexOf( filter ) !== -1 ) { 809 | return !not; 810 | } 811 | 812 | if ( not ) { 813 | run = true; 814 | } 815 | 816 | return run; 817 | } 818 | 819 | // so far supports only Firefox, Chrome and Opera (buggy) 820 | // could be extended in the future to use something like https://github.com/csnover/TraceKit 821 | function sourceFromStacktrace() { 822 | try { 823 | throw new Error(); 824 | } catch ( e ) { 825 | if (e.stacktrace) { 826 | // Opera 827 | return e.stacktrace.split("\n")[6]; 828 | } else if (e.stack) { 829 | // Firefox, Chrome 830 | return e.stack.split("\n")[4]; 831 | } 832 | } 833 | } 834 | 835 | function escapeHtml(s) { 836 | if (!s) { 837 | return ""; 838 | } 839 | s = s + ""; 840 | return s.replace(/[\&"<>\\]/g, function(s) { 841 | switch(s) { 842 | case "&": return "&"; 843 | case "\\": return "\\\\"; 844 | case '"': return '\"'; 845 | case "<": return "<"; 846 | case ">": return ">"; 847 | default: return s; 848 | } 849 | }); 850 | } 851 | 852 | function synchronize( callback ) { 853 | config.queue.push( callback ); 854 | 855 | if ( config.autorun && !config.blocking ) { 856 | process(); 857 | } 858 | } 859 | 860 | function process() { 861 | var start = (new Date()).getTime(); 862 | 863 | while ( config.queue.length && !config.blocking ) { 864 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { 865 | config.queue.shift()(); 866 | } else { 867 | window.setTimeout( process, 13 ); 868 | break; 869 | } 870 | } 871 | if (!config.blocking && !config.queue.length) { 872 | done(); 873 | } 874 | } 875 | 876 | function saveGlobal() { 877 | config.pollution = []; 878 | 879 | if ( config.noglobals ) { 880 | for ( var key in window ) { 881 | config.pollution.push( key ); 882 | } 883 | } 884 | } 885 | 886 | function checkPollution( name ) { 887 | var old = config.pollution; 888 | saveGlobal(); 889 | 890 | var newGlobals = diff( config.pollution, old ); 891 | if ( newGlobals.length > 0 ) { 892 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 893 | } 894 | 895 | var deletedGlobals = diff( old, config.pollution ); 896 | if ( deletedGlobals.length > 0 ) { 897 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 898 | } 899 | } 900 | 901 | // returns a new Array with the elements that are in a but not in b 902 | function diff( a, b ) { 903 | var result = a.slice(); 904 | for ( var i = 0; i < result.length; i++ ) { 905 | for ( var j = 0; j < b.length; j++ ) { 906 | if ( result[i] === b[j] ) { 907 | result.splice(i, 1); 908 | i--; 909 | break; 910 | } 911 | } 912 | } 913 | return result; 914 | } 915 | 916 | function fail(message, exception, callback) { 917 | if ( typeof console !== "undefined" && console.error && console.warn ) { 918 | console.error(message); 919 | console.error(exception); 920 | console.warn(callback.toString()); 921 | 922 | } else if ( window.opera && opera.postError ) { 923 | opera.postError(message, exception, callback.toString); 924 | } 925 | } 926 | 927 | function extend(a, b) { 928 | for ( var prop in b ) { 929 | if ( b[prop] === undefined ) { 930 | delete a[prop]; 931 | } else { 932 | a[prop] = b[prop]; 933 | } 934 | } 935 | 936 | return a; 937 | } 938 | 939 | function addEvent(elem, type, fn) { 940 | if ( elem.addEventListener ) { 941 | elem.addEventListener( type, fn, false ); 942 | } else if ( elem.attachEvent ) { 943 | elem.attachEvent( "on" + type, fn ); 944 | } else { 945 | fn(); 946 | } 947 | } 948 | 949 | function id(name) { 950 | return !!(typeof document !== "undefined" && document && document.getElementById) && 951 | document.getElementById( name ); 952 | } 953 | 954 | // Test for equality any JavaScript type. 955 | // Discussions and reference: http://philrathe.com/articles/equiv 956 | // Test suites: http://philrathe.com/tests/equiv 957 | // Author: Philippe Rathé 958 | QUnit.equiv = function () { 959 | 960 | var innerEquiv; // the real equiv function 961 | var callers = []; // stack to decide between skip/abort functions 962 | var parents = []; // stack to avoiding loops from circular referencing 963 | 964 | // Call the o related callback with the given arguments. 965 | function bindCallbacks(o, callbacks, args) { 966 | var prop = QUnit.objectType(o); 967 | if (prop) { 968 | if (QUnit.objectType(callbacks[prop]) === "function") { 969 | return callbacks[prop].apply(callbacks, args); 970 | } else { 971 | return callbacks[prop]; // or undefined 972 | } 973 | } 974 | } 975 | 976 | var callbacks = function () { 977 | 978 | // for string, boolean, number and null 979 | function useStrictEquality(b, a) { 980 | if (b instanceof a.constructor || a instanceof b.constructor) { 981 | // to catch short annotaion VS 'new' annotation of a declaration 982 | // e.g. var i = 1; 983 | // var j = new Number(1); 984 | return a == b; 985 | } else { 986 | return a === b; 987 | } 988 | } 989 | 990 | return { 991 | "string": useStrictEquality, 992 | "boolean": useStrictEquality, 993 | "number": useStrictEquality, 994 | "null": useStrictEquality, 995 | "undefined": useStrictEquality, 996 | 997 | "nan": function (b) { 998 | return isNaN(b); 999 | }, 1000 | 1001 | "date": function (b, a) { 1002 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); 1003 | }, 1004 | 1005 | "regexp": function (b, a) { 1006 | return QUnit.objectType(b) === "regexp" && 1007 | a.source === b.source && // the regex itself 1008 | a.global === b.global && // and its modifers (gmi) ... 1009 | a.ignoreCase === b.ignoreCase && 1010 | a.multiline === b.multiline; 1011 | }, 1012 | 1013 | // - skip when the property is a method of an instance (OOP) 1014 | // - abort otherwise, 1015 | // initial === would have catch identical references anyway 1016 | "function": function () { 1017 | var caller = callers[callers.length - 1]; 1018 | return caller !== Object && 1019 | typeof caller !== "undefined"; 1020 | }, 1021 | 1022 | "array": function (b, a) { 1023 | var i, j, loop; 1024 | var len; 1025 | 1026 | // b could be an object literal here 1027 | if ( ! (QUnit.objectType(b) === "array")) { 1028 | return false; 1029 | } 1030 | 1031 | len = a.length; 1032 | if (len !== b.length) { // safe and faster 1033 | return false; 1034 | } 1035 | 1036 | //track reference to avoid circular references 1037 | parents.push(a); 1038 | for (i = 0; i < len; i++) { 1039 | loop = false; 1040 | for(j=0;j= 0) { 1185 | type = "array"; 1186 | } else { 1187 | type = typeof obj; 1188 | } 1189 | return type; 1190 | }, 1191 | separator:function() { 1192 | return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; 1193 | }, 1194 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1195 | if ( !this.multiline ) 1196 | return ''; 1197 | var chr = this.indentChar; 1198 | if ( this.HTML ) 1199 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1200 | return Array( this._depth_ + (extra||0) ).join(chr); 1201 | }, 1202 | up:function( a ) { 1203 | this._depth_ += a || 1; 1204 | }, 1205 | down:function( a ) { 1206 | this._depth_ -= a || 1; 1207 | }, 1208 | setParser:function( name, parser ) { 1209 | this.parsers[name] = parser; 1210 | }, 1211 | // The next 3 are exposed so you can use them 1212 | quote:quote, 1213 | literal:literal, 1214 | join:join, 1215 | // 1216 | _depth_: 1, 1217 | // This is the list of parsers, to modify them, use jsDump.setParser 1218 | parsers:{ 1219 | window: '[Window]', 1220 | document: '[Document]', 1221 | error:'[ERROR]', //when no parser is found, shouldn't happen 1222 | unknown: '[Unknown]', 1223 | 'null':'null', 1224 | 'undefined':'undefined', 1225 | 'function':function( fn ) { 1226 | var ret = 'function', 1227 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1228 | if ( name ) 1229 | ret += ' ' + name; 1230 | ret += '('; 1231 | 1232 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); 1233 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); 1234 | }, 1235 | array: array, 1236 | nodelist: array, 1237 | arguments: array, 1238 | object:function( map ) { 1239 | var ret = [ ]; 1240 | QUnit.jsDump.up(); 1241 | for ( var key in map ) 1242 | ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); 1243 | QUnit.jsDump.down(); 1244 | return join( '{', ret, '}' ); 1245 | }, 1246 | node:function( node ) { 1247 | var open = QUnit.jsDump.HTML ? '<' : '<', 1248 | close = QUnit.jsDump.HTML ? '>' : '>'; 1249 | 1250 | var tag = node.nodeName.toLowerCase(), 1251 | ret = open + tag; 1252 | 1253 | for ( var a in QUnit.jsDump.DOMAttrs ) { 1254 | var val = node[QUnit.jsDump.DOMAttrs[a]]; 1255 | if ( val ) 1256 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); 1257 | } 1258 | return ret + close + open + '/' + tag + close; 1259 | }, 1260 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1261 | var l = fn.length; 1262 | if ( !l ) return ''; 1263 | 1264 | var args = Array(l); 1265 | while ( l-- ) 1266 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1267 | return ' ' + args.join(', ') + ' '; 1268 | }, 1269 | key:quote, //object calls it internally, the key part of an item in a map 1270 | functionCode:'[code]', //function calls it internally, it's the content of the function 1271 | attribute:quote, //node calls it internally, it's an html attribute value 1272 | string:quote, 1273 | date:quote, 1274 | regexp:literal, //regex 1275 | number:literal, 1276 | 'boolean':literal 1277 | }, 1278 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1279 | id:'id', 1280 | name:'name', 1281 | 'class':'className' 1282 | }, 1283 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1284 | indentChar:' ',//indentation unit 1285 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1286 | }; 1287 | 1288 | return jsDump; 1289 | })(); 1290 | 1291 | // from Sizzle.js 1292 | function getText( elems ) { 1293 | var ret = "", elem; 1294 | 1295 | for ( var i = 0; elems[i]; i++ ) { 1296 | elem = elems[i]; 1297 | 1298 | // Get the text from text nodes and CDATA nodes 1299 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1300 | ret += elem.nodeValue; 1301 | 1302 | // Traverse everything else, except comment nodes 1303 | } else if ( elem.nodeType !== 8 ) { 1304 | ret += getText( elem.childNodes ); 1305 | } 1306 | } 1307 | 1308 | return ret; 1309 | }; 1310 | 1311 | /* 1312 | * Javascript Diff Algorithm 1313 | * By John Resig (http://ejohn.org/) 1314 | * Modified by Chu Alan "sprite" 1315 | * 1316 | * Released under the MIT license. 1317 | * 1318 | * More Info: 1319 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1320 | * 1321 | * Usage: QUnit.diff(expected, actual) 1322 | * 1323 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1324 | */ 1325 | QUnit.diff = (function() { 1326 | function diff(o, n){ 1327 | var ns = new Object(); 1328 | var os = new Object(); 1329 | 1330 | for (var i = 0; i < n.length; i++) { 1331 | if (ns[n[i]] == null) 1332 | ns[n[i]] = { 1333 | rows: new Array(), 1334 | o: null 1335 | }; 1336 | ns[n[i]].rows.push(i); 1337 | } 1338 | 1339 | for (var i = 0; i < o.length; i++) { 1340 | if (os[o[i]] == null) 1341 | os[o[i]] = { 1342 | rows: new Array(), 1343 | n: null 1344 | }; 1345 | os[o[i]].rows.push(i); 1346 | } 1347 | 1348 | for (var i in ns) { 1349 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1350 | n[ns[i].rows[0]] = { 1351 | text: n[ns[i].rows[0]], 1352 | row: os[i].rows[0] 1353 | }; 1354 | o[os[i].rows[0]] = { 1355 | text: o[os[i].rows[0]], 1356 | row: ns[i].rows[0] 1357 | }; 1358 | } 1359 | } 1360 | 1361 | for (var i = 0; i < n.length - 1; i++) { 1362 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1363 | n[i + 1] == o[n[i].row + 1]) { 1364 | n[i + 1] = { 1365 | text: n[i + 1], 1366 | row: n[i].row + 1 1367 | }; 1368 | o[n[i].row + 1] = { 1369 | text: o[n[i].row + 1], 1370 | row: i + 1 1371 | }; 1372 | } 1373 | } 1374 | 1375 | for (var i = n.length - 1; i > 0; i--) { 1376 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1377 | n[i - 1] == o[n[i].row - 1]) { 1378 | n[i - 1] = { 1379 | text: n[i - 1], 1380 | row: n[i].row - 1 1381 | }; 1382 | o[n[i].row - 1] = { 1383 | text: o[n[i].row - 1], 1384 | row: i - 1 1385 | }; 1386 | } 1387 | } 1388 | 1389 | return { 1390 | o: o, 1391 | n: n 1392 | }; 1393 | } 1394 | 1395 | return function(o, n){ 1396 | o = o.replace(/\s+$/, ''); 1397 | n = n.replace(/\s+$/, ''); 1398 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); 1399 | 1400 | var str = ""; 1401 | 1402 | var oSpace = o.match(/\s+/g); 1403 | if (oSpace == null) { 1404 | oSpace = [" "]; 1405 | } 1406 | else { 1407 | oSpace.push(" "); 1408 | } 1409 | var nSpace = n.match(/\s+/g); 1410 | if (nSpace == null) { 1411 | nSpace = [" "]; 1412 | } 1413 | else { 1414 | nSpace.push(" "); 1415 | } 1416 | 1417 | if (out.n.length == 0) { 1418 | for (var i = 0; i < out.o.length; i++) { 1419 | str += '' + out.o[i] + oSpace[i] + ""; 1420 | } 1421 | } 1422 | else { 1423 | if (out.n[0].text == null) { 1424 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1425 | str += '' + out.o[n] + oSpace[n] + ""; 1426 | } 1427 | } 1428 | 1429 | for (var i = 0; i < out.n.length; i++) { 1430 | if (out.n[i].text == null) { 1431 | str += '' + out.n[i] + nSpace[i] + ""; 1432 | } 1433 | else { 1434 | var pre = ""; 1435 | 1436 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1437 | pre += '' + out.o[n] + oSpace[n] + ""; 1438 | } 1439 | str += " " + out.n[i].text + nSpace[i] + pre; 1440 | } 1441 | } 1442 | } 1443 | 1444 | return str; 1445 | }; 1446 | })(); 1447 | 1448 | })(this); -------------------------------------------------------------------------------- /lib/jbundle/templates/jasmine/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 | jasmine.getGlobal = function() { 43 | function getGlobal() { 44 | return this; 45 | } 46 | 47 | return getGlobal(); 48 | }; 49 | 50 | /** 51 | * Allows for bound functions to be compared. Internal use only. 52 | * 53 | * @ignore 54 | * @private 55 | * @param base {Object} bound 'this' for the function 56 | * @param name {Function} function to find 57 | */ 58 | jasmine.bindOriginal_ = function(base, name) { 59 | var original = base[name]; 60 | if (original.apply) { 61 | return function() { 62 | return original.apply(base, arguments); 63 | }; 64 | } else { 65 | // IE support 66 | return jasmine.getGlobal()[name]; 67 | } 68 | }; 69 | 70 | jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); 71 | jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); 72 | jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); 73 | jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); 74 | 75 | jasmine.MessageResult = function(values) { 76 | this.type = 'log'; 77 | this.values = values; 78 | this.trace = new Error(); // todo: test better 79 | }; 80 | 81 | jasmine.MessageResult.prototype.toString = function() { 82 | var text = ""; 83 | for (var i = 0; i < this.values.length; i++) { 84 | if (i > 0) text += " "; 85 | if (jasmine.isString_(this.values[i])) { 86 | text += this.values[i]; 87 | } else { 88 | text += jasmine.pp(this.values[i]); 89 | } 90 | } 91 | return text; 92 | }; 93 | 94 | jasmine.ExpectationResult = function(params) { 95 | this.type = 'expect'; 96 | this.matcherName = params.matcherName; 97 | this.passed_ = params.passed; 98 | this.expected = params.expected; 99 | this.actual = params.actual; 100 | this.message = this.passed_ ? 'Passed.' : params.message; 101 | 102 | var trace = (params.trace || new Error(this.message)); 103 | this.trace = this.passed_ ? '' : trace; 104 | }; 105 | 106 | jasmine.ExpectationResult.prototype.toString = function () { 107 | return this.message; 108 | }; 109 | 110 | jasmine.ExpectationResult.prototype.passed = function () { 111 | return this.passed_; 112 | }; 113 | 114 | /** 115 | * Getter for the Jasmine environment. Ensures one gets created 116 | */ 117 | jasmine.getEnv = function() { 118 | var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); 119 | return env; 120 | }; 121 | 122 | /** 123 | * @ignore 124 | * @private 125 | * @param value 126 | * @returns {Boolean} 127 | */ 128 | jasmine.isArray_ = function(value) { 129 | return jasmine.isA_("Array", value); 130 | }; 131 | 132 | /** 133 | * @ignore 134 | * @private 135 | * @param value 136 | * @returns {Boolean} 137 | */ 138 | jasmine.isString_ = function(value) { 139 | return jasmine.isA_("String", value); 140 | }; 141 | 142 | /** 143 | * @ignore 144 | * @private 145 | * @param value 146 | * @returns {Boolean} 147 | */ 148 | jasmine.isNumber_ = function(value) { 149 | return jasmine.isA_("Number", value); 150 | }; 151 | 152 | /** 153 | * @ignore 154 | * @private 155 | * @param {String} typeName 156 | * @param value 157 | * @returns {Boolean} 158 | */ 159 | jasmine.isA_ = function(typeName, value) { 160 | return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; 161 | }; 162 | 163 | /** 164 | * Pretty printer for expecations. Takes any object and turns it into a human-readable string. 165 | * 166 | * @param value {Object} an object to be outputted 167 | * @returns {String} 168 | */ 169 | jasmine.pp = function(value) { 170 | var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); 171 | stringPrettyPrinter.format(value); 172 | return stringPrettyPrinter.string; 173 | }; 174 | 175 | /** 176 | * Returns true if the object is a DOM Node. 177 | * 178 | * @param {Object} obj object to check 179 | * @returns {Boolean} 180 | */ 181 | jasmine.isDomNode = function(obj) { 182 | return obj.nodeType > 0; 183 | }; 184 | 185 | /** 186 | * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. 187 | * 188 | * @example 189 | * // don't care about which function is passed in, as long as it's a function 190 | * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); 191 | * 192 | * @param {Class} clazz 193 | * @returns matchable object of the type clazz 194 | */ 195 | jasmine.any = function(clazz) { 196 | return new jasmine.Matchers.Any(clazz); 197 | }; 198 | 199 | /** 200 | * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. 201 | * 202 | * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine 203 | * expectation syntax. Spies can be checked if they were called or not and what the calling params were. 204 | * 205 | * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). 206 | * 207 | * Spies are torn down at the end of every spec. 208 | * 209 | * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. 210 | * 211 | * @example 212 | * // a stub 213 | * var myStub = jasmine.createSpy('myStub'); // can be used anywhere 214 | * 215 | * // spy example 216 | * var foo = { 217 | * not: function(bool) { return !bool; } 218 | * } 219 | * 220 | * // actual foo.not will not be called, execution stops 221 | * spyOn(foo, 'not'); 222 | 223 | // foo.not spied upon, execution will continue to implementation 224 | * spyOn(foo, 'not').andCallThrough(); 225 | * 226 | * // fake example 227 | * var foo = { 228 | * not: function(bool) { return !bool; } 229 | * } 230 | * 231 | * // foo.not(val) will return val 232 | * spyOn(foo, 'not').andCallFake(function(value) {return value;}); 233 | * 234 | * // mock example 235 | * foo.not(7 == 7); 236 | * expect(foo.not).toHaveBeenCalled(); 237 | * expect(foo.not).toHaveBeenCalledWith(true); 238 | * 239 | * @constructor 240 | * @see spyOn, jasmine.createSpy, jasmine.createSpyObj 241 | * @param {String} name 242 | */ 243 | jasmine.Spy = function(name) { 244 | /** 245 | * The name of the spy, if provided. 246 | */ 247 | this.identity = name || 'unknown'; 248 | /** 249 | * Is this Object a spy? 250 | */ 251 | this.isSpy = true; 252 | /** 253 | * The actual function this spy stubs. 254 | */ 255 | this.plan = function() { 256 | }; 257 | /** 258 | * Tracking of the most recent call to the spy. 259 | * @example 260 | * var mySpy = jasmine.createSpy('foo'); 261 | * mySpy(1, 2); 262 | * mySpy.mostRecentCall.args = [1, 2]; 263 | */ 264 | this.mostRecentCall = {}; 265 | 266 | /** 267 | * Holds arguments for each call to the spy, indexed by call count 268 | * @example 269 | * var mySpy = jasmine.createSpy('foo'); 270 | * mySpy(1, 2); 271 | * mySpy(7, 8); 272 | * mySpy.mostRecentCall.args = [7, 8]; 273 | * mySpy.argsForCall[0] = [1, 2]; 274 | * mySpy.argsForCall[1] = [7, 8]; 275 | */ 276 | this.argsForCall = []; 277 | this.calls = []; 278 | }; 279 | 280 | /** 281 | * Tells a spy to call through to the actual implemenatation. 282 | * 283 | * @example 284 | * var foo = { 285 | * bar: function() { // do some stuff } 286 | * } 287 | * 288 | * // defining a spy on an existing property: foo.bar 289 | * spyOn(foo, 'bar').andCallThrough(); 290 | */ 291 | jasmine.Spy.prototype.andCallThrough = function() { 292 | this.plan = this.originalValue; 293 | return this; 294 | }; 295 | 296 | /** 297 | * For setting the return value of a spy. 298 | * 299 | * @example 300 | * // defining a spy from scratch: foo() returns 'baz' 301 | * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); 302 | * 303 | * // defining a spy on an existing property: foo.bar() returns 'baz' 304 | * spyOn(foo, 'bar').andReturn('baz'); 305 | * 306 | * @param {Object} value 307 | */ 308 | jasmine.Spy.prototype.andReturn = function(value) { 309 | this.plan = function() { 310 | return value; 311 | }; 312 | return this; 313 | }; 314 | 315 | /** 316 | * For throwing an exception when a spy is called. 317 | * 318 | * @example 319 | * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' 320 | * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); 321 | * 322 | * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' 323 | * spyOn(foo, 'bar').andThrow('baz'); 324 | * 325 | * @param {String} exceptionMsg 326 | */ 327 | jasmine.Spy.prototype.andThrow = function(exceptionMsg) { 328 | this.plan = function() { 329 | throw exceptionMsg; 330 | }; 331 | return this; 332 | }; 333 | 334 | /** 335 | * Calls an alternate implementation when a spy is called. 336 | * 337 | * @example 338 | * var baz = function() { 339 | * // do some stuff, return something 340 | * } 341 | * // defining a spy from scratch: foo() calls the function baz 342 | * var foo = jasmine.createSpy('spy on foo').andCall(baz); 343 | * 344 | * // defining a spy on an existing property: foo.bar() calls an anonymnous function 345 | * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); 346 | * 347 | * @param {Function} fakeFunc 348 | */ 349 | jasmine.Spy.prototype.andCallFake = function(fakeFunc) { 350 | this.plan = fakeFunc; 351 | return this; 352 | }; 353 | 354 | /** 355 | * Resets all of a spy's the tracking variables so that it can be used again. 356 | * 357 | * @example 358 | * spyOn(foo, 'bar'); 359 | * 360 | * foo.bar(); 361 | * 362 | * expect(foo.bar.callCount).toEqual(1); 363 | * 364 | * foo.bar.reset(); 365 | * 366 | * expect(foo.bar.callCount).toEqual(0); 367 | */ 368 | jasmine.Spy.prototype.reset = function() { 369 | this.wasCalled = false; 370 | this.callCount = 0; 371 | this.argsForCall = []; 372 | this.calls = []; 373 | this.mostRecentCall = {}; 374 | }; 375 | 376 | jasmine.createSpy = function(name) { 377 | 378 | var spyObj = function() { 379 | spyObj.wasCalled = true; 380 | spyObj.callCount++; 381 | var args = jasmine.util.argsToArray(arguments); 382 | spyObj.mostRecentCall.object = this; 383 | spyObj.mostRecentCall.args = args; 384 | spyObj.argsForCall.push(args); 385 | spyObj.calls.push({object: this, args: args}); 386 | return spyObj.plan.apply(this, arguments); 387 | }; 388 | 389 | var spy = new jasmine.Spy(name); 390 | 391 | for (var prop in spy) { 392 | spyObj[prop] = spy[prop]; 393 | } 394 | 395 | spyObj.reset(); 396 | 397 | return spyObj; 398 | }; 399 | 400 | /** 401 | * Determines whether an object is a spy. 402 | * 403 | * @param {jasmine.Spy|Object} putativeSpy 404 | * @returns {Boolean} 405 | */ 406 | jasmine.isSpy = function(putativeSpy) { 407 | return putativeSpy && putativeSpy.isSpy; 408 | }; 409 | 410 | /** 411 | * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something 412 | * large in one call. 413 | * 414 | * @param {String} baseName name of spy class 415 | * @param {Array} methodNames array of names of methods to make spies 416 | */ 417 | jasmine.createSpyObj = function(baseName, methodNames) { 418 | if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { 419 | throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); 420 | } 421 | var obj = {}; 422 | for (var i = 0; i < methodNames.length; i++) { 423 | obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); 424 | } 425 | return obj; 426 | }; 427 | 428 | /** 429 | * All parameters are pretty-printed and concatenated together, then written to the current spec's output. 430 | * 431 | * Be careful not to leave calls to jasmine.log in production code. 432 | */ 433 | jasmine.log = function() { 434 | var spec = jasmine.getEnv().currentSpec; 435 | spec.log.apply(spec, arguments); 436 | }; 437 | 438 | /** 439 | * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. 440 | * 441 | * @example 442 | * // spy example 443 | * var foo = { 444 | * not: function(bool) { return !bool; } 445 | * } 446 | * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops 447 | * 448 | * @see jasmine.createSpy 449 | * @param obj 450 | * @param methodName 451 | * @returns a Jasmine spy that can be chained with all spy methods 452 | */ 453 | var spyOn = function(obj, methodName) { 454 | return jasmine.getEnv().currentSpec.spyOn(obj, methodName); 455 | }; 456 | if (isCommonJS) exports.spyOn = spyOn; 457 | 458 | /** 459 | * Creates a Jasmine spec that will be added to the current suite. 460 | * 461 | * // TODO: pending tests 462 | * 463 | * @example 464 | * it('should be true', function() { 465 | * expect(true).toEqual(true); 466 | * }); 467 | * 468 | * @param {String} desc description of this specification 469 | * @param {Function} func defines the preconditions and expectations of the spec 470 | */ 471 | var it = function(desc, func) { 472 | return jasmine.getEnv().it(desc, func); 473 | }; 474 | if (isCommonJS) exports.it = it; 475 | 476 | /** 477 | * Creates a disabled Jasmine spec. 478 | * 479 | * A convenience method that allows existing specs to be disabled temporarily during development. 480 | * 481 | * @param {String} desc description of this specification 482 | * @param {Function} func defines the preconditions and expectations of the spec 483 | */ 484 | var xit = function(desc, func) { 485 | return jasmine.getEnv().xit(desc, func); 486 | }; 487 | if (isCommonJS) exports.xit = xit; 488 | 489 | /** 490 | * Starts a chain for a Jasmine expectation. 491 | * 492 | * It is passed an Object that is the actual value and should chain to one of the many 493 | * jasmine.Matchers functions. 494 | * 495 | * @param {Object} actual Actual value to test against and expected value 496 | */ 497 | var expect = function(actual) { 498 | return jasmine.getEnv().currentSpec.expect(actual); 499 | }; 500 | if (isCommonJS) exports.expect = expect; 501 | 502 | /** 503 | * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. 504 | * 505 | * @param {Function} func Function that defines part of a jasmine spec. 506 | */ 507 | var runs = function(func) { 508 | jasmine.getEnv().currentSpec.runs(func); 509 | }; 510 | if (isCommonJS) exports.runs = runs; 511 | 512 | /** 513 | * Waits a fixed time period before moving to the next block. 514 | * 515 | * @deprecated Use waitsFor() instead 516 | * @param {Number} timeout milliseconds to wait 517 | */ 518 | var waits = function(timeout) { 519 | jasmine.getEnv().currentSpec.waits(timeout); 520 | }; 521 | if (isCommonJS) exports.waits = waits; 522 | 523 | /** 524 | * Waits for the latchFunction to return true before proceeding to the next block. 525 | * 526 | * @param {Function} latchFunction 527 | * @param {String} optional_timeoutMessage 528 | * @param {Number} optional_timeout 529 | */ 530 | var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 531 | jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); 532 | }; 533 | if (isCommonJS) exports.waitsFor = waitsFor; 534 | 535 | /** 536 | * A function that is called before each spec in a suite. 537 | * 538 | * Used for spec setup, including validating assumptions. 539 | * 540 | * @param {Function} beforeEachFunction 541 | */ 542 | var beforeEach = function(beforeEachFunction) { 543 | jasmine.getEnv().beforeEach(beforeEachFunction); 544 | }; 545 | if (isCommonJS) exports.beforeEach = beforeEach; 546 | 547 | /** 548 | * A function that is called after each spec in a suite. 549 | * 550 | * Used for restoring any state that is hijacked during spec execution. 551 | * 552 | * @param {Function} afterEachFunction 553 | */ 554 | var afterEach = function(afterEachFunction) { 555 | jasmine.getEnv().afterEach(afterEachFunction); 556 | }; 557 | if (isCommonJS) exports.afterEach = afterEach; 558 | 559 | /** 560 | * Defines a suite of specifications. 561 | * 562 | * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared 563 | * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization 564 | * of setup in some tests. 565 | * 566 | * @example 567 | * // TODO: a simple suite 568 | * 569 | * // TODO: a simple suite with a nested describe block 570 | * 571 | * @param {String} description A string, usually the class under test. 572 | * @param {Function} specDefinitions function that defines several specs. 573 | */ 574 | var describe = function(description, specDefinitions) { 575 | return jasmine.getEnv().describe(description, specDefinitions); 576 | }; 577 | if (isCommonJS) exports.describe = describe; 578 | 579 | /** 580 | * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. 581 | * 582 | * @param {String} description A string, usually the class under test. 583 | * @param {Function} specDefinitions function that defines several specs. 584 | */ 585 | var xdescribe = function(description, specDefinitions) { 586 | return jasmine.getEnv().xdescribe(description, specDefinitions); 587 | }; 588 | if (isCommonJS) exports.xdescribe = xdescribe; 589 | 590 | 591 | // Provide the XMLHttpRequest class for IE 5.x-6.x: 592 | jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { 593 | function tryIt(f) { 594 | try { 595 | return f(); 596 | } catch(e) { 597 | } 598 | return null; 599 | } 600 | 601 | var xhr = tryIt(function() { 602 | return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 603 | }) || 604 | tryIt(function() { 605 | return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 606 | }) || 607 | tryIt(function() { 608 | return new ActiveXObject("Msxml2.XMLHTTP"); 609 | }) || 610 | tryIt(function() { 611 | return new ActiveXObject("Microsoft.XMLHTTP"); 612 | }); 613 | 614 | if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); 615 | 616 | return xhr; 617 | } : XMLHttpRequest; 618 | /** 619 | * @namespace 620 | */ 621 | jasmine.util = {}; 622 | 623 | /** 624 | * Declare that a child class inherit it's prototype from the parent class. 625 | * 626 | * @private 627 | * @param {Function} childClass 628 | * @param {Function} parentClass 629 | */ 630 | jasmine.util.inherit = function(childClass, parentClass) { 631 | /** 632 | * @private 633 | */ 634 | var subclass = function() { 635 | }; 636 | subclass.prototype = parentClass.prototype; 637 | childClass.prototype = new subclass(); 638 | }; 639 | 640 | jasmine.util.formatException = function(e) { 641 | var lineNumber; 642 | if (e.line) { 643 | lineNumber = e.line; 644 | } 645 | else if (e.lineNumber) { 646 | lineNumber = e.lineNumber; 647 | } 648 | 649 | var file; 650 | 651 | if (e.sourceURL) { 652 | file = e.sourceURL; 653 | } 654 | else if (e.fileName) { 655 | file = e.fileName; 656 | } 657 | 658 | var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); 659 | 660 | if (file && lineNumber) { 661 | message += ' in ' + file + ' (line ' + lineNumber + ')'; 662 | } 663 | 664 | return message; 665 | }; 666 | 667 | jasmine.util.htmlEscape = function(str) { 668 | if (!str) return str; 669 | return str.replace(/&/g, '&') 670 | .replace(//g, '>'); 672 | }; 673 | 674 | jasmine.util.argsToArray = function(args) { 675 | var arrayOfArgs = []; 676 | for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); 677 | return arrayOfArgs; 678 | }; 679 | 680 | jasmine.util.extend = function(destination, source) { 681 | for (var property in source) destination[property] = source[property]; 682 | return destination; 683 | }; 684 | 685 | /** 686 | * Environment for Jasmine 687 | * 688 | * @constructor 689 | */ 690 | jasmine.Env = function() { 691 | this.currentSpec = null; 692 | this.currentSuite = null; 693 | this.currentRunner_ = new jasmine.Runner(this); 694 | 695 | this.reporter = new jasmine.MultiReporter(); 696 | 697 | this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; 698 | this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; 699 | this.lastUpdate = 0; 700 | this.specFilter = function() { 701 | return true; 702 | }; 703 | 704 | this.nextSpecId_ = 0; 705 | this.nextSuiteId_ = 0; 706 | this.equalityTesters_ = []; 707 | 708 | // wrap matchers 709 | this.matchersClass = function() { 710 | jasmine.Matchers.apply(this, arguments); 711 | }; 712 | jasmine.util.inherit(this.matchersClass, jasmine.Matchers); 713 | 714 | jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); 715 | }; 716 | 717 | 718 | jasmine.Env.prototype.setTimeout = jasmine.setTimeout; 719 | jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; 720 | jasmine.Env.prototype.setInterval = jasmine.setInterval; 721 | jasmine.Env.prototype.clearInterval = jasmine.clearInterval; 722 | 723 | /** 724 | * @returns an object containing jasmine version build info, if set. 725 | */ 726 | jasmine.Env.prototype.version = function () { 727 | if (jasmine.version_) { 728 | return jasmine.version_; 729 | } else { 730 | throw new Error('Version not set'); 731 | } 732 | }; 733 | 734 | /** 735 | * @returns string containing jasmine version build info, if set. 736 | */ 737 | jasmine.Env.prototype.versionString = function() { 738 | if (!jasmine.version_) { 739 | return "version unknown"; 740 | } 741 | 742 | var version = this.version(); 743 | var versionString = version.major + "." + version.minor + "." + version.build; 744 | if (version.release_candidate) { 745 | versionString += ".rc" + version.release_candidate; 746 | } 747 | versionString += " revision " + version.revision; 748 | return versionString; 749 | }; 750 | 751 | /** 752 | * @returns a sequential integer starting at 0 753 | */ 754 | jasmine.Env.prototype.nextSpecId = function () { 755 | return this.nextSpecId_++; 756 | }; 757 | 758 | /** 759 | * @returns a sequential integer starting at 0 760 | */ 761 | jasmine.Env.prototype.nextSuiteId = function () { 762 | return this.nextSuiteId_++; 763 | }; 764 | 765 | /** 766 | * Register a reporter to receive status updates from Jasmine. 767 | * @param {jasmine.Reporter} reporter An object which will receive status updates. 768 | */ 769 | jasmine.Env.prototype.addReporter = function(reporter) { 770 | this.reporter.addReporter(reporter); 771 | }; 772 | 773 | jasmine.Env.prototype.execute = function() { 774 | this.currentRunner_.execute(); 775 | }; 776 | 777 | jasmine.Env.prototype.describe = function(description, specDefinitions) { 778 | var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); 779 | 780 | var parentSuite = this.currentSuite; 781 | if (parentSuite) { 782 | parentSuite.add(suite); 783 | } else { 784 | this.currentRunner_.add(suite); 785 | } 786 | 787 | this.currentSuite = suite; 788 | 789 | var declarationError = null; 790 | try { 791 | specDefinitions.call(suite); 792 | } catch(e) { 793 | declarationError = e; 794 | } 795 | 796 | if (declarationError) { 797 | this.it("encountered a declaration exception", function() { 798 | throw declarationError; 799 | }); 800 | } 801 | 802 | this.currentSuite = parentSuite; 803 | 804 | return suite; 805 | }; 806 | 807 | jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { 808 | if (this.currentSuite) { 809 | this.currentSuite.beforeEach(beforeEachFunction); 810 | } else { 811 | this.currentRunner_.beforeEach(beforeEachFunction); 812 | } 813 | }; 814 | 815 | jasmine.Env.prototype.currentRunner = function () { 816 | return this.currentRunner_; 817 | }; 818 | 819 | jasmine.Env.prototype.afterEach = function(afterEachFunction) { 820 | if (this.currentSuite) { 821 | this.currentSuite.afterEach(afterEachFunction); 822 | } else { 823 | this.currentRunner_.afterEach(afterEachFunction); 824 | } 825 | 826 | }; 827 | 828 | jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { 829 | return { 830 | execute: function() { 831 | } 832 | }; 833 | }; 834 | 835 | jasmine.Env.prototype.it = function(description, func) { 836 | var spec = new jasmine.Spec(this, this.currentSuite, description); 837 | this.currentSuite.add(spec); 838 | this.currentSpec = spec; 839 | 840 | if (func) { 841 | spec.runs(func); 842 | } 843 | 844 | return spec; 845 | }; 846 | 847 | jasmine.Env.prototype.xit = function(desc, func) { 848 | return { 849 | id: this.nextSpecId(), 850 | runs: function() { 851 | } 852 | }; 853 | }; 854 | 855 | jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { 856 | if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { 857 | return true; 858 | } 859 | 860 | a.__Jasmine_been_here_before__ = b; 861 | b.__Jasmine_been_here_before__ = a; 862 | 863 | var hasKey = function(obj, keyName) { 864 | return obj !== null && obj[keyName] !== jasmine.undefined; 865 | }; 866 | 867 | for (var property in b) { 868 | if (!hasKey(a, property) && hasKey(b, property)) { 869 | mismatchKeys.push("expected has key '" + property + "', but missing from actual."); 870 | } 871 | } 872 | for (property in a) { 873 | if (!hasKey(b, property) && hasKey(a, property)) { 874 | mismatchKeys.push("expected missing key '" + property + "', but present in actual."); 875 | } 876 | } 877 | for (property in b) { 878 | if (property == '__Jasmine_been_here_before__') continue; 879 | if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { 880 | 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."); 881 | } 882 | } 883 | 884 | if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { 885 | mismatchValues.push("arrays were not the same length"); 886 | } 887 | 888 | delete a.__Jasmine_been_here_before__; 889 | delete b.__Jasmine_been_here_before__; 890 | return (mismatchKeys.length === 0 && mismatchValues.length === 0); 891 | }; 892 | 893 | jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { 894 | mismatchKeys = mismatchKeys || []; 895 | mismatchValues = mismatchValues || []; 896 | 897 | for (var i = 0; i < this.equalityTesters_.length; i++) { 898 | var equalityTester = this.equalityTesters_[i]; 899 | var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); 900 | if (result !== jasmine.undefined) return result; 901 | } 902 | 903 | if (a === b) return true; 904 | 905 | if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { 906 | return (a == jasmine.undefined && b == jasmine.undefined); 907 | } 908 | 909 | if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { 910 | return a === b; 911 | } 912 | 913 | if (a instanceof Date && b instanceof Date) { 914 | return a.getTime() == b.getTime(); 915 | } 916 | 917 | if (a instanceof jasmine.Matchers.Any) { 918 | return a.matches(b); 919 | } 920 | 921 | if (b instanceof jasmine.Matchers.Any) { 922 | return b.matches(a); 923 | } 924 | 925 | if (jasmine.isString_(a) && jasmine.isString_(b)) { 926 | return (a == b); 927 | } 928 | 929 | if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { 930 | return (a == b); 931 | } 932 | 933 | if (typeof a === "object" && typeof b === "object") { 934 | return this.compareObjects_(a, b, mismatchKeys, mismatchValues); 935 | } 936 | 937 | //Straight check 938 | return (a === b); 939 | }; 940 | 941 | jasmine.Env.prototype.contains_ = function(haystack, needle) { 942 | if (jasmine.isArray_(haystack)) { 943 | for (var i = 0; i < haystack.length; i++) { 944 | if (this.equals_(haystack[i], needle)) return true; 945 | } 946 | return false; 947 | } 948 | return haystack.indexOf(needle) >= 0; 949 | }; 950 | 951 | jasmine.Env.prototype.addEqualityTester = function(equalityTester) { 952 | this.equalityTesters_.push(equalityTester); 953 | }; 954 | /** No-op base class for Jasmine reporters. 955 | * 956 | * @constructor 957 | */ 958 | jasmine.Reporter = function() { 959 | }; 960 | 961 | //noinspection JSUnusedLocalSymbols 962 | jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { 963 | }; 964 | 965 | //noinspection JSUnusedLocalSymbols 966 | jasmine.Reporter.prototype.reportRunnerResults = function(runner) { 967 | }; 968 | 969 | //noinspection JSUnusedLocalSymbols 970 | jasmine.Reporter.prototype.reportSuiteResults = function(suite) { 971 | }; 972 | 973 | //noinspection JSUnusedLocalSymbols 974 | jasmine.Reporter.prototype.reportSpecStarting = function(spec) { 975 | }; 976 | 977 | //noinspection JSUnusedLocalSymbols 978 | jasmine.Reporter.prototype.reportSpecResults = function(spec) { 979 | }; 980 | 981 | //noinspection JSUnusedLocalSymbols 982 | jasmine.Reporter.prototype.log = function(str) { 983 | }; 984 | 985 | /** 986 | * Blocks are functions with executable code that make up a spec. 987 | * 988 | * @constructor 989 | * @param {jasmine.Env} env 990 | * @param {Function} func 991 | * @param {jasmine.Spec} spec 992 | */ 993 | jasmine.Block = function(env, func, spec) { 994 | this.env = env; 995 | this.func = func; 996 | this.spec = spec; 997 | }; 998 | 999 | jasmine.Block.prototype.execute = function(onComplete) { 1000 | try { 1001 | this.func.apply(this.spec); 1002 | } catch (e) { 1003 | this.spec.fail(e); 1004 | } 1005 | onComplete(); 1006 | }; 1007 | /** JavaScript API reporter. 1008 | * 1009 | * @constructor 1010 | */ 1011 | jasmine.JsApiReporter = function() { 1012 | this.started = false; 1013 | this.finished = false; 1014 | this.suites_ = []; 1015 | this.results_ = {}; 1016 | }; 1017 | 1018 | jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { 1019 | this.started = true; 1020 | var suites = runner.topLevelSuites(); 1021 | for (var i = 0; i < suites.length; i++) { 1022 | var suite = suites[i]; 1023 | this.suites_.push(this.summarize_(suite)); 1024 | } 1025 | }; 1026 | 1027 | jasmine.JsApiReporter.prototype.suites = function() { 1028 | return this.suites_; 1029 | }; 1030 | 1031 | jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { 1032 | var isSuite = suiteOrSpec instanceof jasmine.Suite; 1033 | var summary = { 1034 | id: suiteOrSpec.id, 1035 | name: suiteOrSpec.description, 1036 | type: isSuite ? 'suite' : 'spec', 1037 | children: [] 1038 | }; 1039 | 1040 | if (isSuite) { 1041 | var children = suiteOrSpec.children(); 1042 | for (var i = 0; i < children.length; i++) { 1043 | summary.children.push(this.summarize_(children[i])); 1044 | } 1045 | } 1046 | return summary; 1047 | }; 1048 | 1049 | jasmine.JsApiReporter.prototype.results = function() { 1050 | return this.results_; 1051 | }; 1052 | 1053 | jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { 1054 | return this.results_[specId]; 1055 | }; 1056 | 1057 | //noinspection JSUnusedLocalSymbols 1058 | jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { 1059 | this.finished = true; 1060 | }; 1061 | 1062 | //noinspection JSUnusedLocalSymbols 1063 | jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { 1064 | }; 1065 | 1066 | //noinspection JSUnusedLocalSymbols 1067 | jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { 1068 | this.results_[spec.id] = { 1069 | messages: spec.results().getItems(), 1070 | result: spec.results().failedCount > 0 ? "failed" : "passed" 1071 | }; 1072 | }; 1073 | 1074 | //noinspection JSUnusedLocalSymbols 1075 | jasmine.JsApiReporter.prototype.log = function(str) { 1076 | }; 1077 | 1078 | jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ 1079 | var results = {}; 1080 | for (var i = 0; i < specIds.length; i++) { 1081 | var specId = specIds[i]; 1082 | results[specId] = this.summarizeResult_(this.results_[specId]); 1083 | } 1084 | return results; 1085 | }; 1086 | 1087 | jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ 1088 | var summaryMessages = []; 1089 | var messagesLength = result.messages.length; 1090 | for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { 1091 | var resultMessage = result.messages[messageIndex]; 1092 | summaryMessages.push({ 1093 | text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, 1094 | passed: resultMessage.passed ? resultMessage.passed() : true, 1095 | type: resultMessage.type, 1096 | message: resultMessage.message, 1097 | trace: { 1098 | stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined 1099 | } 1100 | }); 1101 | } 1102 | 1103 | return { 1104 | result : result.result, 1105 | messages : summaryMessages 1106 | }; 1107 | }; 1108 | 1109 | /** 1110 | * @constructor 1111 | * @param {jasmine.Env} env 1112 | * @param actual 1113 | * @param {jasmine.Spec} spec 1114 | */ 1115 | jasmine.Matchers = function(env, actual, spec, opt_isNot) { 1116 | this.env = env; 1117 | this.actual = actual; 1118 | this.spec = spec; 1119 | this.isNot = opt_isNot || false; 1120 | this.reportWasCalled_ = false; 1121 | }; 1122 | 1123 | // todo: @deprecated as of Jasmine 0.11, remove soon [xw] 1124 | jasmine.Matchers.pp = function(str) { 1125 | throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); 1126 | }; 1127 | 1128 | // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] 1129 | jasmine.Matchers.prototype.report = function(result, failing_message, details) { 1130 | throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); 1131 | }; 1132 | 1133 | jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { 1134 | for (var methodName in prototype) { 1135 | if (methodName == 'report') continue; 1136 | var orig = prototype[methodName]; 1137 | matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); 1138 | } 1139 | }; 1140 | 1141 | jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { 1142 | return function() { 1143 | var matcherArgs = jasmine.util.argsToArray(arguments); 1144 | var result = matcherFunction.apply(this, arguments); 1145 | 1146 | if (this.isNot) { 1147 | result = !result; 1148 | } 1149 | 1150 | if (this.reportWasCalled_) return result; 1151 | 1152 | var message; 1153 | if (!result) { 1154 | if (this.message) { 1155 | message = this.message.apply(this, arguments); 1156 | if (jasmine.isArray_(message)) { 1157 | message = message[this.isNot ? 1 : 0]; 1158 | } 1159 | } else { 1160 | var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); 1161 | message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; 1162 | if (matcherArgs.length > 0) { 1163 | for (var i = 0; i < matcherArgs.length; i++) { 1164 | if (i > 0) message += ","; 1165 | message += " " + jasmine.pp(matcherArgs[i]); 1166 | } 1167 | } 1168 | message += "."; 1169 | } 1170 | } 1171 | var expectationResult = new jasmine.ExpectationResult({ 1172 | matcherName: matcherName, 1173 | passed: result, 1174 | expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], 1175 | actual: this.actual, 1176 | message: message 1177 | }); 1178 | this.spec.addMatcherResult(expectationResult); 1179 | return jasmine.undefined; 1180 | }; 1181 | }; 1182 | 1183 | 1184 | 1185 | 1186 | /** 1187 | * toBe: compares the actual to the expected using === 1188 | * @param expected 1189 | */ 1190 | jasmine.Matchers.prototype.toBe = function(expected) { 1191 | return this.actual === expected; 1192 | }; 1193 | 1194 | /** 1195 | * toNotBe: compares the actual to the expected using !== 1196 | * @param expected 1197 | * @deprecated as of 1.0. Use not.toBe() instead. 1198 | */ 1199 | jasmine.Matchers.prototype.toNotBe = function(expected) { 1200 | return this.actual !== expected; 1201 | }; 1202 | 1203 | /** 1204 | * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. 1205 | * 1206 | * @param expected 1207 | */ 1208 | jasmine.Matchers.prototype.toEqual = function(expected) { 1209 | return this.env.equals_(this.actual, expected); 1210 | }; 1211 | 1212 | /** 1213 | * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual 1214 | * @param expected 1215 | * @deprecated as of 1.0. Use not.toNotEqual() instead. 1216 | */ 1217 | jasmine.Matchers.prototype.toNotEqual = function(expected) { 1218 | return !this.env.equals_(this.actual, expected); 1219 | }; 1220 | 1221 | /** 1222 | * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes 1223 | * a pattern or a String. 1224 | * 1225 | * @param expected 1226 | */ 1227 | jasmine.Matchers.prototype.toMatch = function(expected) { 1228 | return new RegExp(expected).test(this.actual); 1229 | }; 1230 | 1231 | /** 1232 | * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch 1233 | * @param expected 1234 | * @deprecated as of 1.0. Use not.toMatch() instead. 1235 | */ 1236 | jasmine.Matchers.prototype.toNotMatch = function(expected) { 1237 | return !(new RegExp(expected).test(this.actual)); 1238 | }; 1239 | 1240 | /** 1241 | * Matcher that compares the actual to jasmine.undefined. 1242 | */ 1243 | jasmine.Matchers.prototype.toBeDefined = function() { 1244 | return (this.actual !== jasmine.undefined); 1245 | }; 1246 | 1247 | /** 1248 | * Matcher that compares the actual to jasmine.undefined. 1249 | */ 1250 | jasmine.Matchers.prototype.toBeUndefined = function() { 1251 | return (this.actual === jasmine.undefined); 1252 | }; 1253 | 1254 | /** 1255 | * Matcher that compares the actual to null. 1256 | */ 1257 | jasmine.Matchers.prototype.toBeNull = function() { 1258 | return (this.actual === null); 1259 | }; 1260 | 1261 | /** 1262 | * Matcher that boolean not-nots the actual. 1263 | */ 1264 | jasmine.Matchers.prototype.toBeTruthy = function() { 1265 | return !!this.actual; 1266 | }; 1267 | 1268 | 1269 | /** 1270 | * Matcher that boolean nots the actual. 1271 | */ 1272 | jasmine.Matchers.prototype.toBeFalsy = function() { 1273 | return !this.actual; 1274 | }; 1275 | 1276 | 1277 | /** 1278 | * Matcher that checks to see if the actual, a Jasmine spy, was called. 1279 | */ 1280 | jasmine.Matchers.prototype.toHaveBeenCalled = function() { 1281 | if (arguments.length > 0) { 1282 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); 1283 | } 1284 | 1285 | if (!jasmine.isSpy(this.actual)) { 1286 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1287 | } 1288 | 1289 | this.message = function() { 1290 | return [ 1291 | "Expected spy " + this.actual.identity + " to have been called.", 1292 | "Expected spy " + this.actual.identity + " not to have been called." 1293 | ]; 1294 | }; 1295 | 1296 | return this.actual.wasCalled; 1297 | }; 1298 | 1299 | /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ 1300 | jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; 1301 | 1302 | /** 1303 | * Matcher that checks to see if the actual, a Jasmine spy, was not called. 1304 | * 1305 | * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead 1306 | */ 1307 | jasmine.Matchers.prototype.wasNotCalled = function() { 1308 | if (arguments.length > 0) { 1309 | throw new Error('wasNotCalled does not take arguments'); 1310 | } 1311 | 1312 | if (!jasmine.isSpy(this.actual)) { 1313 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1314 | } 1315 | 1316 | this.message = function() { 1317 | return [ 1318 | "Expected spy " + this.actual.identity + " to not have been called.", 1319 | "Expected spy " + this.actual.identity + " to have been called." 1320 | ]; 1321 | }; 1322 | 1323 | return !this.actual.wasCalled; 1324 | }; 1325 | 1326 | /** 1327 | * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. 1328 | * 1329 | * @example 1330 | * 1331 | */ 1332 | jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { 1333 | var expectedArgs = jasmine.util.argsToArray(arguments); 1334 | if (!jasmine.isSpy(this.actual)) { 1335 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1336 | } 1337 | this.message = function() { 1338 | if (this.actual.callCount === 0) { 1339 | // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] 1340 | return [ 1341 | "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", 1342 | "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." 1343 | ]; 1344 | } else { 1345 | return [ 1346 | "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), 1347 | "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) 1348 | ]; 1349 | } 1350 | }; 1351 | 1352 | return this.env.contains_(this.actual.argsForCall, expectedArgs); 1353 | }; 1354 | 1355 | /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ 1356 | jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; 1357 | 1358 | /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ 1359 | jasmine.Matchers.prototype.wasNotCalledWith = function() { 1360 | var expectedArgs = jasmine.util.argsToArray(arguments); 1361 | if (!jasmine.isSpy(this.actual)) { 1362 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1363 | } 1364 | 1365 | this.message = function() { 1366 | return [ 1367 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", 1368 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" 1369 | ]; 1370 | }; 1371 | 1372 | return !this.env.contains_(this.actual.argsForCall, expectedArgs); 1373 | }; 1374 | 1375 | /** 1376 | * Matcher that checks that the expected item is an element in the actual Array. 1377 | * 1378 | * @param {Object} expected 1379 | */ 1380 | jasmine.Matchers.prototype.toContain = function(expected) { 1381 | return this.env.contains_(this.actual, expected); 1382 | }; 1383 | 1384 | /** 1385 | * Matcher that checks that the expected item is NOT an element in the actual Array. 1386 | * 1387 | * @param {Object} expected 1388 | * @deprecated as of 1.0. Use not.toNotContain() instead. 1389 | */ 1390 | jasmine.Matchers.prototype.toNotContain = function(expected) { 1391 | return !this.env.contains_(this.actual, expected); 1392 | }; 1393 | 1394 | jasmine.Matchers.prototype.toBeLessThan = function(expected) { 1395 | return this.actual < expected; 1396 | }; 1397 | 1398 | jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { 1399 | return this.actual > expected; 1400 | }; 1401 | 1402 | /** 1403 | * Matcher that checks that the expected item is equal to the actual item 1404 | * up to a given level of decimal precision (default 2). 1405 | * 1406 | * @param {Number} expected 1407 | * @param {Number} precision 1408 | */ 1409 | jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { 1410 | if (!(precision === 0)) { 1411 | precision = precision || 2; 1412 | } 1413 | var multiplier = Math.pow(10, precision); 1414 | var actual = Math.round(this.actual * multiplier); 1415 | expected = Math.round(expected * multiplier); 1416 | return expected == actual; 1417 | }; 1418 | 1419 | /** 1420 | * Matcher that checks that the expected exception was thrown by the actual. 1421 | * 1422 | * @param {String} expected 1423 | */ 1424 | jasmine.Matchers.prototype.toThrow = function(expected) { 1425 | var result = false; 1426 | var exception; 1427 | if (typeof this.actual != 'function') { 1428 | throw new Error('Actual is not a function'); 1429 | } 1430 | try { 1431 | this.actual(); 1432 | } catch (e) { 1433 | exception = e; 1434 | } 1435 | if (exception) { 1436 | result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); 1437 | } 1438 | 1439 | var not = this.isNot ? "not " : ""; 1440 | 1441 | this.message = function() { 1442 | if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { 1443 | return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); 1444 | } else { 1445 | return "Expected function to throw an exception."; 1446 | } 1447 | }; 1448 | 1449 | return result; 1450 | }; 1451 | 1452 | jasmine.Matchers.Any = function(expectedClass) { 1453 | this.expectedClass = expectedClass; 1454 | }; 1455 | 1456 | jasmine.Matchers.Any.prototype.matches = function(other) { 1457 | if (this.expectedClass == String) { 1458 | return typeof other == 'string' || other instanceof String; 1459 | } 1460 | 1461 | if (this.expectedClass == Number) { 1462 | return typeof other == 'number' || other instanceof Number; 1463 | } 1464 | 1465 | if (this.expectedClass == Function) { 1466 | return typeof other == 'function' || other instanceof Function; 1467 | } 1468 | 1469 | if (this.expectedClass == Object) { 1470 | return typeof other == 'object'; 1471 | } 1472 | 1473 | return other instanceof this.expectedClass; 1474 | }; 1475 | 1476 | jasmine.Matchers.Any.prototype.toString = function() { 1477 | return ''; 1478 | }; 1479 | 1480 | /** 1481 | * @constructor 1482 | */ 1483 | jasmine.MultiReporter = function() { 1484 | this.subReporters_ = []; 1485 | }; 1486 | jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); 1487 | 1488 | jasmine.MultiReporter.prototype.addReporter = function(reporter) { 1489 | this.subReporters_.push(reporter); 1490 | }; 1491 | 1492 | (function() { 1493 | var functionNames = [ 1494 | "reportRunnerStarting", 1495 | "reportRunnerResults", 1496 | "reportSuiteResults", 1497 | "reportSpecStarting", 1498 | "reportSpecResults", 1499 | "log" 1500 | ]; 1501 | for (var i = 0; i < functionNames.length; i++) { 1502 | var functionName = functionNames[i]; 1503 | jasmine.MultiReporter.prototype[functionName] = (function(functionName) { 1504 | return function() { 1505 | for (var j = 0; j < this.subReporters_.length; j++) { 1506 | var subReporter = this.subReporters_[j]; 1507 | if (subReporter[functionName]) { 1508 | subReporter[functionName].apply(subReporter, arguments); 1509 | } 1510 | } 1511 | }; 1512 | })(functionName); 1513 | } 1514 | })(); 1515 | /** 1516 | * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults 1517 | * 1518 | * @constructor 1519 | */ 1520 | jasmine.NestedResults = function() { 1521 | /** 1522 | * The total count of results 1523 | */ 1524 | this.totalCount = 0; 1525 | /** 1526 | * Number of passed results 1527 | */ 1528 | this.passedCount = 0; 1529 | /** 1530 | * Number of failed results 1531 | */ 1532 | this.failedCount = 0; 1533 | /** 1534 | * Was this suite/spec skipped? 1535 | */ 1536 | this.skipped = false; 1537 | /** 1538 | * @ignore 1539 | */ 1540 | this.items_ = []; 1541 | }; 1542 | 1543 | /** 1544 | * Roll up the result counts. 1545 | * 1546 | * @param result 1547 | */ 1548 | jasmine.NestedResults.prototype.rollupCounts = function(result) { 1549 | this.totalCount += result.totalCount; 1550 | this.passedCount += result.passedCount; 1551 | this.failedCount += result.failedCount; 1552 | }; 1553 | 1554 | /** 1555 | * Adds a log message. 1556 | * @param values Array of message parts which will be concatenated later. 1557 | */ 1558 | jasmine.NestedResults.prototype.log = function(values) { 1559 | this.items_.push(new jasmine.MessageResult(values)); 1560 | }; 1561 | 1562 | /** 1563 | * Getter for the results: message & results. 1564 | */ 1565 | jasmine.NestedResults.prototype.getItems = function() { 1566 | return this.items_; 1567 | }; 1568 | 1569 | /** 1570 | * Adds a result, tracking counts (total, passed, & failed) 1571 | * @param {jasmine.ExpectationResult|jasmine.NestedResults} result 1572 | */ 1573 | jasmine.NestedResults.prototype.addResult = function(result) { 1574 | if (result.type != 'log') { 1575 | if (result.items_) { 1576 | this.rollupCounts(result); 1577 | } else { 1578 | this.totalCount++; 1579 | if (result.passed()) { 1580 | this.passedCount++; 1581 | } else { 1582 | this.failedCount++; 1583 | } 1584 | } 1585 | } 1586 | this.items_.push(result); 1587 | }; 1588 | 1589 | /** 1590 | * @returns {Boolean} True if everything below passed 1591 | */ 1592 | jasmine.NestedResults.prototype.passed = function() { 1593 | return this.passedCount === this.totalCount; 1594 | }; 1595 | /** 1596 | * Base class for pretty printing for expectation results. 1597 | */ 1598 | jasmine.PrettyPrinter = function() { 1599 | this.ppNestLevel_ = 0; 1600 | }; 1601 | 1602 | /** 1603 | * Formats a value in a nice, human-readable string. 1604 | * 1605 | * @param value 1606 | */ 1607 | jasmine.PrettyPrinter.prototype.format = function(value) { 1608 | if (this.ppNestLevel_ > 40) { 1609 | throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); 1610 | } 1611 | 1612 | this.ppNestLevel_++; 1613 | try { 1614 | if (value === jasmine.undefined) { 1615 | this.emitScalar('undefined'); 1616 | } else if (value === null) { 1617 | this.emitScalar('null'); 1618 | } else if (value === jasmine.getGlobal()) { 1619 | this.emitScalar(''); 1620 | } else if (value instanceof jasmine.Matchers.Any) { 1621 | this.emitScalar(value.toString()); 1622 | } else if (typeof value === 'string') { 1623 | this.emitString(value); 1624 | } else if (jasmine.isSpy(value)) { 1625 | this.emitScalar("spy on " + value.identity); 1626 | } else if (value instanceof RegExp) { 1627 | this.emitScalar(value.toString()); 1628 | } else if (typeof value === 'function') { 1629 | this.emitScalar('Function'); 1630 | } else if (typeof value.nodeType === 'number') { 1631 | this.emitScalar('HTMLNode'); 1632 | } else if (value instanceof Date) { 1633 | this.emitScalar('Date(' + value + ')'); 1634 | } else if (value.__Jasmine_been_here_before__) { 1635 | this.emitScalar(''); 1636 | } else if (jasmine.isArray_(value) || typeof value == 'object') { 1637 | value.__Jasmine_been_here_before__ = true; 1638 | if (jasmine.isArray_(value)) { 1639 | this.emitArray(value); 1640 | } else { 1641 | this.emitObject(value); 1642 | } 1643 | delete value.__Jasmine_been_here_before__; 1644 | } else { 1645 | this.emitScalar(value.toString()); 1646 | } 1647 | } finally { 1648 | this.ppNestLevel_--; 1649 | } 1650 | }; 1651 | 1652 | jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1653 | for (var property in obj) { 1654 | if (property == '__Jasmine_been_here_before__') continue; 1655 | fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && 1656 | obj.__lookupGetter__(property) !== null) : false); 1657 | } 1658 | }; 1659 | 1660 | jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; 1661 | jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; 1662 | jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; 1663 | jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; 1664 | 1665 | jasmine.StringPrettyPrinter = function() { 1666 | jasmine.PrettyPrinter.call(this); 1667 | 1668 | this.string = ''; 1669 | }; 1670 | jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); 1671 | 1672 | jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { 1673 | this.append(value); 1674 | }; 1675 | 1676 | jasmine.StringPrettyPrinter.prototype.emitString = function(value) { 1677 | this.append("'" + value + "'"); 1678 | }; 1679 | 1680 | jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { 1681 | this.append('[ '); 1682 | for (var i = 0; i < array.length; i++) { 1683 | if (i > 0) { 1684 | this.append(', '); 1685 | } 1686 | this.format(array[i]); 1687 | } 1688 | this.append(' ]'); 1689 | }; 1690 | 1691 | jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { 1692 | var self = this; 1693 | this.append('{ '); 1694 | var first = true; 1695 | 1696 | this.iterateObject(obj, function(property, isGetter) { 1697 | if (first) { 1698 | first = false; 1699 | } else { 1700 | self.append(', '); 1701 | } 1702 | 1703 | self.append(property); 1704 | self.append(' : '); 1705 | if (isGetter) { 1706 | self.append(''); 1707 | } else { 1708 | self.format(obj[property]); 1709 | } 1710 | }); 1711 | 1712 | this.append(' }'); 1713 | }; 1714 | 1715 | jasmine.StringPrettyPrinter.prototype.append = function(value) { 1716 | this.string += value; 1717 | }; 1718 | jasmine.Queue = function(env) { 1719 | this.env = env; 1720 | this.blocks = []; 1721 | this.running = false; 1722 | this.index = 0; 1723 | this.offset = 0; 1724 | this.abort = false; 1725 | }; 1726 | 1727 | jasmine.Queue.prototype.addBefore = function(block) { 1728 | this.blocks.unshift(block); 1729 | }; 1730 | 1731 | jasmine.Queue.prototype.add = function(block) { 1732 | this.blocks.push(block); 1733 | }; 1734 | 1735 | jasmine.Queue.prototype.insertNext = function(block) { 1736 | this.blocks.splice((this.index + this.offset + 1), 0, block); 1737 | this.offset++; 1738 | }; 1739 | 1740 | jasmine.Queue.prototype.start = function(onComplete) { 1741 | this.running = true; 1742 | this.onComplete = onComplete; 1743 | this.next_(); 1744 | }; 1745 | 1746 | jasmine.Queue.prototype.isRunning = function() { 1747 | return this.running; 1748 | }; 1749 | 1750 | jasmine.Queue.LOOP_DONT_RECURSE = true; 1751 | 1752 | jasmine.Queue.prototype.next_ = function() { 1753 | var self = this; 1754 | var goAgain = true; 1755 | 1756 | while (goAgain) { 1757 | goAgain = false; 1758 | 1759 | if (self.index < self.blocks.length && !this.abort) { 1760 | var calledSynchronously = true; 1761 | var completedSynchronously = false; 1762 | 1763 | var onComplete = function () { 1764 | if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { 1765 | completedSynchronously = true; 1766 | return; 1767 | } 1768 | 1769 | if (self.blocks[self.index].abort) { 1770 | self.abort = true; 1771 | } 1772 | 1773 | self.offset = 0; 1774 | self.index++; 1775 | 1776 | var now = new Date().getTime(); 1777 | if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { 1778 | self.env.lastUpdate = now; 1779 | self.env.setTimeout(function() { 1780 | self.next_(); 1781 | }, 0); 1782 | } else { 1783 | if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { 1784 | goAgain = true; 1785 | } else { 1786 | self.next_(); 1787 | } 1788 | } 1789 | }; 1790 | self.blocks[self.index].execute(onComplete); 1791 | 1792 | calledSynchronously = false; 1793 | if (completedSynchronously) { 1794 | onComplete(); 1795 | } 1796 | 1797 | } else { 1798 | self.running = false; 1799 | if (self.onComplete) { 1800 | self.onComplete(); 1801 | } 1802 | } 1803 | } 1804 | }; 1805 | 1806 | jasmine.Queue.prototype.results = function() { 1807 | var results = new jasmine.NestedResults(); 1808 | for (var i = 0; i < this.blocks.length; i++) { 1809 | if (this.blocks[i].results) { 1810 | results.addResult(this.blocks[i].results()); 1811 | } 1812 | } 1813 | return results; 1814 | }; 1815 | 1816 | 1817 | /** 1818 | * Runner 1819 | * 1820 | * @constructor 1821 | * @param {jasmine.Env} env 1822 | */ 1823 | jasmine.Runner = function(env) { 1824 | var self = this; 1825 | self.env = env; 1826 | self.queue = new jasmine.Queue(env); 1827 | self.before_ = []; 1828 | self.after_ = []; 1829 | self.suites_ = []; 1830 | }; 1831 | 1832 | jasmine.Runner.prototype.execute = function() { 1833 | var self = this; 1834 | if (self.env.reporter.reportRunnerStarting) { 1835 | self.env.reporter.reportRunnerStarting(this); 1836 | } 1837 | self.queue.start(function () { 1838 | self.finishCallback(); 1839 | }); 1840 | }; 1841 | 1842 | jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { 1843 | beforeEachFunction.typeName = 'beforeEach'; 1844 | this.before_.splice(0,0,beforeEachFunction); 1845 | }; 1846 | 1847 | jasmine.Runner.prototype.afterEach = function(afterEachFunction) { 1848 | afterEachFunction.typeName = 'afterEach'; 1849 | this.after_.splice(0,0,afterEachFunction); 1850 | }; 1851 | 1852 | 1853 | jasmine.Runner.prototype.finishCallback = function() { 1854 | this.env.reporter.reportRunnerResults(this); 1855 | }; 1856 | 1857 | jasmine.Runner.prototype.addSuite = function(suite) { 1858 | this.suites_.push(suite); 1859 | }; 1860 | 1861 | jasmine.Runner.prototype.add = function(block) { 1862 | if (block instanceof jasmine.Suite) { 1863 | this.addSuite(block); 1864 | } 1865 | this.queue.add(block); 1866 | }; 1867 | 1868 | jasmine.Runner.prototype.specs = function () { 1869 | var suites = this.suites(); 1870 | var specs = []; 1871 | for (var i = 0; i < suites.length; i++) { 1872 | specs = specs.concat(suites[i].specs()); 1873 | } 1874 | return specs; 1875 | }; 1876 | 1877 | jasmine.Runner.prototype.suites = function() { 1878 | return this.suites_; 1879 | }; 1880 | 1881 | jasmine.Runner.prototype.topLevelSuites = function() { 1882 | var topLevelSuites = []; 1883 | for (var i = 0; i < this.suites_.length; i++) { 1884 | if (!this.suites_[i].parentSuite) { 1885 | topLevelSuites.push(this.suites_[i]); 1886 | } 1887 | } 1888 | return topLevelSuites; 1889 | }; 1890 | 1891 | jasmine.Runner.prototype.results = function() { 1892 | return this.queue.results(); 1893 | }; 1894 | /** 1895 | * Internal representation of a Jasmine specification, or test. 1896 | * 1897 | * @constructor 1898 | * @param {jasmine.Env} env 1899 | * @param {jasmine.Suite} suite 1900 | * @param {String} description 1901 | */ 1902 | jasmine.Spec = function(env, suite, description) { 1903 | if (!env) { 1904 | throw new Error('jasmine.Env() required'); 1905 | } 1906 | if (!suite) { 1907 | throw new Error('jasmine.Suite() required'); 1908 | } 1909 | var spec = this; 1910 | spec.id = env.nextSpecId ? env.nextSpecId() : null; 1911 | spec.env = env; 1912 | spec.suite = suite; 1913 | spec.description = description; 1914 | spec.queue = new jasmine.Queue(env); 1915 | 1916 | spec.afterCallbacks = []; 1917 | spec.spies_ = []; 1918 | 1919 | spec.results_ = new jasmine.NestedResults(); 1920 | spec.results_.description = description; 1921 | spec.matchersClass = null; 1922 | }; 1923 | 1924 | jasmine.Spec.prototype.getFullName = function() { 1925 | return this.suite.getFullName() + ' ' + this.description + '.'; 1926 | }; 1927 | 1928 | 1929 | jasmine.Spec.prototype.results = function() { 1930 | return this.results_; 1931 | }; 1932 | 1933 | /** 1934 | * All parameters are pretty-printed and concatenated together, then written to the spec's output. 1935 | * 1936 | * Be careful not to leave calls to jasmine.log in production code. 1937 | */ 1938 | jasmine.Spec.prototype.log = function() { 1939 | return this.results_.log(arguments); 1940 | }; 1941 | 1942 | jasmine.Spec.prototype.runs = function (func) { 1943 | var block = new jasmine.Block(this.env, func, this); 1944 | this.addToQueue(block); 1945 | return this; 1946 | }; 1947 | 1948 | jasmine.Spec.prototype.addToQueue = function (block) { 1949 | if (this.queue.isRunning()) { 1950 | this.queue.insertNext(block); 1951 | } else { 1952 | this.queue.add(block); 1953 | } 1954 | }; 1955 | 1956 | /** 1957 | * @param {jasmine.ExpectationResult} result 1958 | */ 1959 | jasmine.Spec.prototype.addMatcherResult = function(result) { 1960 | this.results_.addResult(result); 1961 | }; 1962 | 1963 | jasmine.Spec.prototype.expect = function(actual) { 1964 | var positive = new (this.getMatchersClass_())(this.env, actual, this); 1965 | positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); 1966 | return positive; 1967 | }; 1968 | 1969 | /** 1970 | * Waits a fixed time period before moving to the next block. 1971 | * 1972 | * @deprecated Use waitsFor() instead 1973 | * @param {Number} timeout milliseconds to wait 1974 | */ 1975 | jasmine.Spec.prototype.waits = function(timeout) { 1976 | var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); 1977 | this.addToQueue(waitsFunc); 1978 | return this; 1979 | }; 1980 | 1981 | /** 1982 | * Waits for the latchFunction to return true before proceeding to the next block. 1983 | * 1984 | * @param {Function} latchFunction 1985 | * @param {String} optional_timeoutMessage 1986 | * @param {Number} optional_timeout 1987 | */ 1988 | jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 1989 | var latchFunction_ = null; 1990 | var optional_timeoutMessage_ = null; 1991 | var optional_timeout_ = null; 1992 | 1993 | for (var i = 0; i < arguments.length; i++) { 1994 | var arg = arguments[i]; 1995 | switch (typeof arg) { 1996 | case 'function': 1997 | latchFunction_ = arg; 1998 | break; 1999 | case 'string': 2000 | optional_timeoutMessage_ = arg; 2001 | break; 2002 | case 'number': 2003 | optional_timeout_ = arg; 2004 | break; 2005 | } 2006 | } 2007 | 2008 | var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); 2009 | this.addToQueue(waitsForFunc); 2010 | return this; 2011 | }; 2012 | 2013 | jasmine.Spec.prototype.fail = function (e) { 2014 | var expectationResult = new jasmine.ExpectationResult({ 2015 | passed: false, 2016 | message: e ? jasmine.util.formatException(e) : 'Exception', 2017 | trace: { stack: e.stack } 2018 | }); 2019 | this.results_.addResult(expectationResult); 2020 | }; 2021 | 2022 | jasmine.Spec.prototype.getMatchersClass_ = function() { 2023 | return this.matchersClass || this.env.matchersClass; 2024 | }; 2025 | 2026 | jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { 2027 | var parent = this.getMatchersClass_(); 2028 | var newMatchersClass = function() { 2029 | parent.apply(this, arguments); 2030 | }; 2031 | jasmine.util.inherit(newMatchersClass, parent); 2032 | jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); 2033 | this.matchersClass = newMatchersClass; 2034 | }; 2035 | 2036 | jasmine.Spec.prototype.finishCallback = function() { 2037 | this.env.reporter.reportSpecResults(this); 2038 | }; 2039 | 2040 | jasmine.Spec.prototype.finish = function(onComplete) { 2041 | this.removeAllSpies(); 2042 | this.finishCallback(); 2043 | if (onComplete) { 2044 | onComplete(); 2045 | } 2046 | }; 2047 | 2048 | jasmine.Spec.prototype.after = function(doAfter) { 2049 | if (this.queue.isRunning()) { 2050 | this.queue.add(new jasmine.Block(this.env, doAfter, this)); 2051 | } else { 2052 | this.afterCallbacks.unshift(doAfter); 2053 | } 2054 | }; 2055 | 2056 | jasmine.Spec.prototype.execute = function(onComplete) { 2057 | var spec = this; 2058 | if (!spec.env.specFilter(spec)) { 2059 | spec.results_.skipped = true; 2060 | spec.finish(onComplete); 2061 | return; 2062 | } 2063 | 2064 | this.env.reporter.reportSpecStarting(this); 2065 | 2066 | spec.env.currentSpec = spec; 2067 | 2068 | spec.addBeforesAndAftersToQueue(); 2069 | 2070 | spec.queue.start(function () { 2071 | spec.finish(onComplete); 2072 | }); 2073 | }; 2074 | 2075 | jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { 2076 | var runner = this.env.currentRunner(); 2077 | var i; 2078 | 2079 | for (var suite = this.suite; suite; suite = suite.parentSuite) { 2080 | for (i = 0; i < suite.before_.length; i++) { 2081 | this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); 2082 | } 2083 | } 2084 | for (i = 0; i < runner.before_.length; i++) { 2085 | this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); 2086 | } 2087 | for (i = 0; i < this.afterCallbacks.length; i++) { 2088 | this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); 2089 | } 2090 | for (suite = this.suite; suite; suite = suite.parentSuite) { 2091 | for (i = 0; i < suite.after_.length; i++) { 2092 | this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); 2093 | } 2094 | } 2095 | for (i = 0; i < runner.after_.length; i++) { 2096 | this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); 2097 | } 2098 | }; 2099 | 2100 | jasmine.Spec.prototype.explodes = function() { 2101 | throw 'explodes function should not have been called'; 2102 | }; 2103 | 2104 | jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { 2105 | if (obj == jasmine.undefined) { 2106 | throw "spyOn could not find an object to spy upon for " + methodName + "()"; 2107 | } 2108 | 2109 | if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { 2110 | throw methodName + '() method does not exist'; 2111 | } 2112 | 2113 | if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { 2114 | throw new Error(methodName + ' has already been spied upon'); 2115 | } 2116 | 2117 | var spyObj = jasmine.createSpy(methodName); 2118 | 2119 | this.spies_.push(spyObj); 2120 | spyObj.baseObj = obj; 2121 | spyObj.methodName = methodName; 2122 | spyObj.originalValue = obj[methodName]; 2123 | 2124 | obj[methodName] = spyObj; 2125 | 2126 | return spyObj; 2127 | }; 2128 | 2129 | jasmine.Spec.prototype.removeAllSpies = function() { 2130 | for (var i = 0; i < this.spies_.length; i++) { 2131 | var spy = this.spies_[i]; 2132 | spy.baseObj[spy.methodName] = spy.originalValue; 2133 | } 2134 | this.spies_ = []; 2135 | }; 2136 | 2137 | /** 2138 | * Internal representation of a Jasmine suite. 2139 | * 2140 | * @constructor 2141 | * @param {jasmine.Env} env 2142 | * @param {String} description 2143 | * @param {Function} specDefinitions 2144 | * @param {jasmine.Suite} parentSuite 2145 | */ 2146 | jasmine.Suite = function(env, description, specDefinitions, parentSuite) { 2147 | var self = this; 2148 | self.id = env.nextSuiteId ? env.nextSuiteId() : null; 2149 | self.description = description; 2150 | self.queue = new jasmine.Queue(env); 2151 | self.parentSuite = parentSuite; 2152 | self.env = env; 2153 | self.before_ = []; 2154 | self.after_ = []; 2155 | self.children_ = []; 2156 | self.suites_ = []; 2157 | self.specs_ = []; 2158 | }; 2159 | 2160 | jasmine.Suite.prototype.getFullName = function() { 2161 | var fullName = this.description; 2162 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 2163 | fullName = parentSuite.description + ' ' + fullName; 2164 | } 2165 | return fullName; 2166 | }; 2167 | 2168 | jasmine.Suite.prototype.finish = function(onComplete) { 2169 | this.env.reporter.reportSuiteResults(this); 2170 | this.finished = true; 2171 | if (typeof(onComplete) == 'function') { 2172 | onComplete(); 2173 | } 2174 | }; 2175 | 2176 | jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { 2177 | beforeEachFunction.typeName = 'beforeEach'; 2178 | this.before_.unshift(beforeEachFunction); 2179 | }; 2180 | 2181 | jasmine.Suite.prototype.afterEach = function(afterEachFunction) { 2182 | afterEachFunction.typeName = 'afterEach'; 2183 | this.after_.unshift(afterEachFunction); 2184 | }; 2185 | 2186 | jasmine.Suite.prototype.results = function() { 2187 | return this.queue.results(); 2188 | }; 2189 | 2190 | jasmine.Suite.prototype.add = function(suiteOrSpec) { 2191 | this.children_.push(suiteOrSpec); 2192 | if (suiteOrSpec instanceof jasmine.Suite) { 2193 | this.suites_.push(suiteOrSpec); 2194 | this.env.currentRunner().addSuite(suiteOrSpec); 2195 | } else { 2196 | this.specs_.push(suiteOrSpec); 2197 | } 2198 | this.queue.add(suiteOrSpec); 2199 | }; 2200 | 2201 | jasmine.Suite.prototype.specs = function() { 2202 | return this.specs_; 2203 | }; 2204 | 2205 | jasmine.Suite.prototype.suites = function() { 2206 | return this.suites_; 2207 | }; 2208 | 2209 | jasmine.Suite.prototype.children = function() { 2210 | return this.children_; 2211 | }; 2212 | 2213 | jasmine.Suite.prototype.execute = function(onComplete) { 2214 | var self = this; 2215 | this.queue.start(function () { 2216 | self.finish(onComplete); 2217 | }); 2218 | }; 2219 | jasmine.WaitsBlock = function(env, timeout, spec) { 2220 | this.timeout = timeout; 2221 | jasmine.Block.call(this, env, null, spec); 2222 | }; 2223 | 2224 | jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); 2225 | 2226 | jasmine.WaitsBlock.prototype.execute = function (onComplete) { 2227 | if (jasmine.VERBOSE) { 2228 | this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); 2229 | } 2230 | this.env.setTimeout(function () { 2231 | onComplete(); 2232 | }, this.timeout); 2233 | }; 2234 | /** 2235 | * A block which waits for some condition to become true, with timeout. 2236 | * 2237 | * @constructor 2238 | * @extends jasmine.Block 2239 | * @param {jasmine.Env} env The Jasmine environment. 2240 | * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. 2241 | * @param {Function} latchFunction A function which returns true when the desired condition has been met. 2242 | * @param {String} message The message to display if the desired condition hasn't been met within the given time period. 2243 | * @param {jasmine.Spec} spec The Jasmine spec. 2244 | */ 2245 | jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { 2246 | this.timeout = timeout || env.defaultTimeoutInterval; 2247 | this.latchFunction = latchFunction; 2248 | this.message = message; 2249 | this.totalTimeSpentWaitingForLatch = 0; 2250 | jasmine.Block.call(this, env, null, spec); 2251 | }; 2252 | jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); 2253 | 2254 | jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; 2255 | 2256 | jasmine.WaitsForBlock.prototype.execute = function(onComplete) { 2257 | if (jasmine.VERBOSE) { 2258 | this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); 2259 | } 2260 | var latchFunctionResult; 2261 | try { 2262 | latchFunctionResult = this.latchFunction.apply(this.spec); 2263 | } catch (e) { 2264 | this.spec.fail(e); 2265 | onComplete(); 2266 | return; 2267 | } 2268 | 2269 | if (latchFunctionResult) { 2270 | onComplete(); 2271 | } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { 2272 | var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); 2273 | this.spec.fail({ 2274 | name: 'timeout', 2275 | message: message 2276 | }); 2277 | 2278 | this.abort = true; 2279 | onComplete(); 2280 | } else { 2281 | this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; 2282 | var self = this; 2283 | this.env.setTimeout(function() { 2284 | self.execute(onComplete); 2285 | }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); 2286 | } 2287 | }; 2288 | // Mock setTimeout, clearTimeout 2289 | // Contributed by Pivotal Computer Systems, www.pivotalsf.com 2290 | 2291 | jasmine.FakeTimer = function() { 2292 | this.reset(); 2293 | 2294 | var self = this; 2295 | self.setTimeout = function(funcToCall, millis) { 2296 | self.timeoutsMade++; 2297 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); 2298 | return self.timeoutsMade; 2299 | }; 2300 | 2301 | self.setInterval = function(funcToCall, millis) { 2302 | self.timeoutsMade++; 2303 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); 2304 | return self.timeoutsMade; 2305 | }; 2306 | 2307 | self.clearTimeout = function(timeoutKey) { 2308 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 2309 | }; 2310 | 2311 | self.clearInterval = function(timeoutKey) { 2312 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 2313 | }; 2314 | 2315 | }; 2316 | 2317 | jasmine.FakeTimer.prototype.reset = function() { 2318 | this.timeoutsMade = 0; 2319 | this.scheduledFunctions = {}; 2320 | this.nowMillis = 0; 2321 | }; 2322 | 2323 | jasmine.FakeTimer.prototype.tick = function(millis) { 2324 | var oldMillis = this.nowMillis; 2325 | var newMillis = oldMillis + millis; 2326 | this.runFunctionsWithinRange(oldMillis, newMillis); 2327 | this.nowMillis = newMillis; 2328 | }; 2329 | 2330 | jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { 2331 | var scheduledFunc; 2332 | var funcsToRun = []; 2333 | for (var timeoutKey in this.scheduledFunctions) { 2334 | scheduledFunc = this.scheduledFunctions[timeoutKey]; 2335 | if (scheduledFunc != jasmine.undefined && 2336 | scheduledFunc.runAtMillis >= oldMillis && 2337 | scheduledFunc.runAtMillis <= nowMillis) { 2338 | funcsToRun.push(scheduledFunc); 2339 | this.scheduledFunctions[timeoutKey] = jasmine.undefined; 2340 | } 2341 | } 2342 | 2343 | if (funcsToRun.length > 0) { 2344 | funcsToRun.sort(function(a, b) { 2345 | return a.runAtMillis - b.runAtMillis; 2346 | }); 2347 | for (var i = 0; i < funcsToRun.length; ++i) { 2348 | try { 2349 | var funcToRun = funcsToRun[i]; 2350 | this.nowMillis = funcToRun.runAtMillis; 2351 | funcToRun.funcToCall(); 2352 | if (funcToRun.recurring) { 2353 | this.scheduleFunction(funcToRun.timeoutKey, 2354 | funcToRun.funcToCall, 2355 | funcToRun.millis, 2356 | true); 2357 | } 2358 | } catch(e) { 2359 | } 2360 | } 2361 | this.runFunctionsWithinRange(oldMillis, nowMillis); 2362 | } 2363 | }; 2364 | 2365 | jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { 2366 | this.scheduledFunctions[timeoutKey] = { 2367 | runAtMillis: this.nowMillis + millis, 2368 | funcToCall: funcToCall, 2369 | recurring: recurring, 2370 | timeoutKey: timeoutKey, 2371 | millis: millis 2372 | }; 2373 | }; 2374 | 2375 | /** 2376 | * @namespace 2377 | */ 2378 | jasmine.Clock = { 2379 | defaultFakeTimer: new jasmine.FakeTimer(), 2380 | 2381 | reset: function() { 2382 | jasmine.Clock.assertInstalled(); 2383 | jasmine.Clock.defaultFakeTimer.reset(); 2384 | }, 2385 | 2386 | tick: function(millis) { 2387 | jasmine.Clock.assertInstalled(); 2388 | jasmine.Clock.defaultFakeTimer.tick(millis); 2389 | }, 2390 | 2391 | runFunctionsWithinRange: function(oldMillis, nowMillis) { 2392 | jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); 2393 | }, 2394 | 2395 | scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { 2396 | jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); 2397 | }, 2398 | 2399 | useMock: function() { 2400 | if (!jasmine.Clock.isInstalled()) { 2401 | var spec = jasmine.getEnv().currentSpec; 2402 | spec.after(jasmine.Clock.uninstallMock); 2403 | 2404 | jasmine.Clock.installMock(); 2405 | } 2406 | }, 2407 | 2408 | installMock: function() { 2409 | jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; 2410 | }, 2411 | 2412 | uninstallMock: function() { 2413 | jasmine.Clock.assertInstalled(); 2414 | jasmine.Clock.installed = jasmine.Clock.real; 2415 | }, 2416 | 2417 | real: { 2418 | setTimeout: jasmine.getGlobal().setTimeout, 2419 | clearTimeout: jasmine.getGlobal().clearTimeout, 2420 | setInterval: jasmine.getGlobal().setInterval, 2421 | clearInterval: jasmine.getGlobal().clearInterval 2422 | }, 2423 | 2424 | assertInstalled: function() { 2425 | if (!jasmine.Clock.isInstalled()) { 2426 | throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); 2427 | } 2428 | }, 2429 | 2430 | isInstalled: function() { 2431 | return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; 2432 | }, 2433 | 2434 | installed: null 2435 | }; 2436 | jasmine.Clock.installed = jasmine.Clock.real; 2437 | 2438 | //else for IE support 2439 | jasmine.getGlobal().setTimeout = function(funcToCall, millis) { 2440 | if (jasmine.Clock.installed.setTimeout.apply) { 2441 | return jasmine.Clock.installed.setTimeout.apply(this, arguments); 2442 | } else { 2443 | return jasmine.Clock.installed.setTimeout(funcToCall, millis); 2444 | } 2445 | }; 2446 | 2447 | jasmine.getGlobal().setInterval = function(funcToCall, millis) { 2448 | if (jasmine.Clock.installed.setInterval.apply) { 2449 | return jasmine.Clock.installed.setInterval.apply(this, arguments); 2450 | } else { 2451 | return jasmine.Clock.installed.setInterval(funcToCall, millis); 2452 | } 2453 | }; 2454 | 2455 | jasmine.getGlobal().clearTimeout = function(timeoutKey) { 2456 | if (jasmine.Clock.installed.clearTimeout.apply) { 2457 | return jasmine.Clock.installed.clearTimeout.apply(this, arguments); 2458 | } else { 2459 | return jasmine.Clock.installed.clearTimeout(timeoutKey); 2460 | } 2461 | }; 2462 | 2463 | jasmine.getGlobal().clearInterval = function(timeoutKey) { 2464 | if (jasmine.Clock.installed.clearTimeout.apply) { 2465 | return jasmine.Clock.installed.clearInterval.apply(this, arguments); 2466 | } else { 2467 | return jasmine.Clock.installed.clearInterval(timeoutKey); 2468 | } 2469 | }; 2470 | 2471 | jasmine.version_= { 2472 | "major": 1, 2473 | "minor": 1, 2474 | "build": 0, 2475 | "revision": 1315677058 2476 | }; 2477 | --------------------------------------------------------------------------------