├── .gitignore ├── Gemfile ├── spec ├── jasmine │ ├── jasmine_favicon.png │ ├── jasmine.css │ ├── jasmine-html.js │ └── jasmine.js ├── runner.html ├── uia-shim.js ├── events-spec.js ├── logging-spec.js ├── data-spec.js └── core-spec.js ├── vendor ├── google-compiler │ ├── compiler.jar │ ├── README │ └── COPYING └── evidence.js ├── test └── mechanize-test.js ├── LICENSE ├── src ├── logging.js ├── data.js ├── events.js └── mechanic-core.js ├── Rakefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | dist 3 | pkg -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'rake' 4 | -------------------------------------------------------------------------------- /spec/jasmine/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaykz52/mechanic/HEAD/spec/jasmine/jasmine_favicon.png -------------------------------------------------------------------------------- /vendor/google-compiler/compiler.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaykz52/mechanic/HEAD/vendor/google-compiler/compiler.jar -------------------------------------------------------------------------------- /test/mechanize-test.js: -------------------------------------------------------------------------------- 1 | #import "../src/mechanic.js" 2 | 3 | $.log("Hi!"); 4 | $("window") 5 | .find('#centerButton') 6 | .touch(2) 7 | .capture() 8 | .end() 9 | .children() 10 | .log() 11 | .orientation(UIA_DEVICE_ORIENTATION_LANDSCAPELEFT); 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Jason Kozemczak, http://cozykozy.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /spec/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/logging.js: -------------------------------------------------------------------------------- 1 | // mechanic.js 2 | // Copyright (c) 2012 Jason Kozemczak 3 | // mechanic.js may be freely distributed under the MIT license. 4 | 5 | (function($) { 6 | $.extend($, { 7 | log: function(s, level) { 8 | level = level || 'message'; 9 | if (level === 'error') $.error(s); 10 | else if (level === 'warn') $.warn(s); 11 | else if (typeof $.isVerbose !== "undefined" && $.isVerbose) { 12 | if (level === 'debug') $.debug(s); 13 | else $.message(s); 14 | } 15 | }, 16 | error: function(s) { UIALogger.logError(s); }, 17 | warn: function(s) { UIALogger.logWarning(s); }, 18 | debug: function(s) { UIALogger.logDebug(s); }, 19 | message: function(s) { UIALogger.logMessage(s); }, 20 | capture: function(imageName, rect) { 21 | var target = UIATarget.localTarget(); 22 | imageName = imageName || new Date().toString(); 23 | if (rect) target.captureRectWithName(rect, imageName); 24 | else target.captureScreenWithName(imageName); 25 | } 26 | }); 27 | 28 | $.extend($.fn, { 29 | log: function() { return this.each(function() { this.logElement(); }); }, 30 | logTree: function () { return this.each(function() { this.logElementTree(); }); }, 31 | capture: function(imageName) { 32 | imageName = imageName || new Date().toString(); 33 | return this.each(function() { $.capture(imageName + '-' + this.name(), this.rect()); }); 34 | } 35 | }); 36 | })(mechanic); 37 | -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | // mechanic.js 2 | // Copyright (c) 2012 Jason Kozemczak 3 | // mechanic.js may be freely distributed under the MIT license. 4 | 5 | (function($) { 6 | var app = UIATarget.localTarget().frontMostApp(); 7 | $.extend($.fn, { 8 | name: function() { return (this.length > 0) ? this[0].name() : null; }, 9 | label: function() { return (this.length > 0) ? this[0].label() : null; }, 10 | value: function() { return (this.length > 0) ? this[0].value() : null; }, 11 | isFocused: function() { return (this.length > 0) ? this[0].hasKeyboardFocus() : false; }, 12 | isEnabled: function() { return (this.length > 0) ? this[0].isEnabled() : false; }, 13 | isVisible: function() { return (this.length > 0) ? this[0].isVisible() : false; }, 14 | isValid: function(certain) { 15 | if (this.length != 1) return false; 16 | else if (certain) return this[0].checkIsValid(); 17 | else return this[0].isValid(); 18 | } 19 | }); 20 | 21 | $.extend($, { 22 | version: function() { 23 | return app.version(); 24 | }, 25 | bundleID: function() { 26 | return app.bundleID(); 27 | }, 28 | prefs: function(prefsOrKey) { 29 | // TODO: should we handle no-arg version that returns all prefs??? 30 | if (typeof prefsOrKey == 'string') return app.preferencesValueForKey(prefsOrKey); 31 | else { 32 | $.each(prefsOrKey, function(key, val) { 33 | app.setPreferencesValueForKey(val, key); 34 | }); 35 | } 36 | } 37 | }); 38 | 39 | })(mechanic); 40 | -------------------------------------------------------------------------------- /spec/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 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | // mechanic.js 2 | // Copyright (c) 2012 Jason Kozemczak 3 | // mechanic.js may be freely distributed under the MIT license. 4 | 5 | (function($) { 6 | var target = UIATarget.localTarget(); 7 | $.extend($, { 8 | timeout: function(duration) { target.setTimeout(duration); }, 9 | delay: function(seconds) { target.delay(seconds); }, 10 | cmd: function(path, args, timeout) { target.host().performTaskWithPathArgumentsTimeout(path, args, timeout); }, 11 | orientation: function(orientation) { 12 | if (orientation === undefined || orientation === null) return target.deviceOrientation(); 13 | else target.setDeviceOrientation(orientation); 14 | }, 15 | location: function(coordinates, options) { 16 | options = options || {}; 17 | target.setLocationWithOptions(coordinates, options); 18 | }, 19 | shake: function() { target.shake(); }, 20 | rotate: function(options) { target.rotateWithOptions(options); }, 21 | pinchScreen: function(options) { 22 | if (!options.style) options.style = 'open'; 23 | if (options.style === 'close') target.pinchCloseFromToForDuration(options.from, options.to, options.duration); 24 | else target.pinchOpenFromToForDuration(options.from, options.to, options.duration); 25 | }, 26 | drag: function(options) { target.dragFromToForDuration(options.from, options.to, options.duration); }, 27 | flick: function(options) { target.flickFromTo(options.from, options.to); }, 28 | lock: function(duration) { target.lockForDuration(duration); }, 29 | backgroundApp: function(duration) { target.deactivateAppForDuration(duration); }, 30 | volume: function(direction, duration) { 31 | if (direction === 'up') { 32 | if (duration) target.holdVolumeUp(duration); 33 | else target.clickVolumeUp(); 34 | } else { 35 | if (duration) target.holdVolumeDown(duration); 36 | else target.clickVolumeDown(); 37 | } 38 | }, 39 | input: function(s) { 40 | target.frontMostApp().keyboard().typeString(s); 41 | } 42 | }); 43 | 44 | $.extend($.fn, { 45 | tap: function(options) { 46 | options = options || {}; 47 | return this.each(function() { 48 | // TODO: tapWithOptions supports most of the behavior of doubleTap/twoFingerTap looking at the API, do we need to support these methods?? 49 | if (options.style === 'double') this.doubleTap(); 50 | else if (options.style === 'twoFinger') this.twoFingerTap(); 51 | else this.tapWithOptions(options); 52 | }); 53 | }, 54 | touch: function(duration) { 55 | return this.each(function() { this.touchAndHold(duration); }); 56 | }, 57 | dragInside: function(options) { 58 | return this.each(function() { this.dragInsideWithOptions(options); }); 59 | }, 60 | flick: function(options) { 61 | return this.each(function() { this.flickInsideWithOptions(options); }); 62 | }, 63 | rotate: function(options) { 64 | return this.each(function() { this.rotateWithOptions(options); }); 65 | }, 66 | scrollToVisible: function() { 67 | if (this.length > 0) this[0].scrollToVisible(); 68 | return this; 69 | }, 70 | input: function(s) { 71 | if (this.length > 0) { 72 | this[0].tap(); 73 | $.input(s); 74 | } 75 | } 76 | }); 77 | 78 | 'delay,cmd,orientation,location,shake,pinchScreen,drag,lock,backgroundApp,volume'.split(',').forEach(function(property) { 79 | var fn = $[property]; 80 | $.fn[property] = function() { 81 | fn.apply($, arguments); 82 | return this; 83 | }; 84 | }); 85 | })(mechanic); 86 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/packagetask' 2 | 3 | MECHANIC_VERSION = "0.1.0" 4 | 5 | MECHANIC_ROOT = File.expand_path(File.dirname(__FILE__)) 6 | MECHANIC_SRC_DIR = File.join(MECHANIC_ROOT, 'src') 7 | MECHANIC_DIST_DIR = File.join(MECHANIC_ROOT, 'dist') 8 | MECHANIC_PKG_DIR = File.join(MECHANIC_ROOT, 'pkg') 9 | 10 | MECHANIC_COMPONENTS = [ 11 | 'mechanic-core', 12 | 'logging', 13 | 'data', 14 | 'events' 15 | ] 16 | 17 | task :default => [:clean, :concat, :dist] 18 | 19 | # MECHANIC_TESTS = %w[ 20 | # test/mechanic.html 21 | # test/ajax.html 22 | # test/data.html 23 | # test/detect.html 24 | # test/form.html 25 | # test/fx.html 26 | # test/polyfill.html 27 | # ] 28 | 29 | desc "Clean the distribution directory." 30 | task :clean do 31 | rm_rf MECHANIC_DIST_DIR 32 | mkdir MECHANIC_DIST_DIR 33 | end 34 | 35 | def normalize_whitespace(filename) 36 | contents = File.readlines(filename) 37 | contents.each { |line| line.sub!(/\s+$/, "") } 38 | File.open(filename, "w") do |file| 39 | file.write contents.join("\n").sub(/(\n+)?\Z/m, "\n") 40 | end 41 | end 42 | 43 | desc "Strip trailing whitespace and ensure each file ends with a newline" 44 | task :whitespace do 45 | Dir["*", "src/**/*", "test/**/*", "examples/**/*"].each do |filename| 46 | normalize_whitespace(filename) if File.file?(filename) 47 | end 48 | end 49 | 50 | desc "Concatenate source files to build for release" 51 | task :concat, [:addons] => :whitespace do |task, args| 52 | # colon-separated arguments such as `concat[foo:bar:-baz]` specify 53 | # which components to add or exclude, depending on if it starts with "-" 54 | add, exclude = args[:addons].to_s.split(':').partition {|c| c !~ /^-/ } 55 | exclude.each {|c| c.sub!('-', '') } 56 | components = (MECHANIC_COMPONENTS | add) - exclude 57 | 58 | unless components == MECHANIC_COMPONENTS 59 | puts "Building release by including: #{components.join(', ')}" 60 | end 61 | 62 | File.open(File.join(MECHANIC_DIST_DIR, 'mechanic.js'), 'w') do |f| 63 | f.puts components.map { |component| 64 | File.read File.join(MECHANIC_SRC_DIR, "#{component}.js") 65 | } 66 | end 67 | end 68 | 69 | def google_compiler(src, target) 70 | puts "Minifying #{src} with Google Closure Compiler..." 71 | `java -jar vendor/google-compiler/compiler.jar --js #{src} --summary_detail_level 3 --js_output_file #{target}` 72 | end 73 | 74 | def process_minified(src, target) 75 | cp target, File.join(MECHANIC_DIST_DIR,'temp.js') 76 | msize = File.size(File.join(MECHANIC_DIST_DIR,'temp.js')) 77 | `gzip -9 #{File.join(MECHANIC_DIST_DIR,'temp.js')}` 78 | 79 | osize = File.size(src) 80 | dsize = File.size(File.join(MECHANIC_DIST_DIR,'temp.js.gz')) 81 | rm_rf File.join(MECHANIC_DIST_DIR,'temp.js.gz') 82 | 83 | puts "Original version: %.3fk" % (osize/1024.0) 84 | puts "Minified: %.3fk" % (msize/1024.0) 85 | puts "Minified and gzipped: %.3fk, compression factor %.3f" % [dsize/1024.0, osize/dsize.to_f] 86 | end 87 | 88 | desc "Generates a minified version for distribution using the Google Closure compiler." 89 | task :dist do 90 | src, target = File.join(MECHANIC_DIST_DIR,'mechanic.js'), File.join(MECHANIC_DIST_DIR,'mechanic.min.js') 91 | google_compiler src, target 92 | process_minified src, target 93 | end 94 | 95 | Rake::PackageTask.new('mechanic', MECHANIC_VERSION) do |package| 96 | package.need_tar_gz = true 97 | package.need_zip = true 98 | package.package_dir = MECHANIC_PKG_DIR 99 | package.package_files.include( 100 | 'README.md', 101 | 'LICENSE', 102 | 'dist/**/*', 103 | 'src/**/*', 104 | 'test/**/*', 105 | 'vendor/evidence.js', 106 | 'examples/**/*' 107 | ).exclude(*`git ls-files -o test src examples -z`.split("\0")) 108 | end 109 | 110 | def silence_warnings 111 | require 'stringio' 112 | begin 113 | old_stderr = $stderr 114 | $stderr = StringIO.new 115 | yield 116 | ensure 117 | $stderr = old_stderr 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /spec/uia-shim.js: -------------------------------------------------------------------------------- 1 | function extend(c, p) { 2 | c.prototype = new p(); 3 | c.prototype.constructor = p; 4 | c.prototype.toString = function() { 5 | // TODO: this function is pretty awful, but we'll deal since it's just to set up a semi-accurate picture of UIAutomation as it stands 6 | var parts = c.toString().match(/function\s*(\w+)/); 7 | return "[object " + parts[1] + "]"; 8 | }; 9 | } 10 | 11 | function f(args) { 12 | return new function(args) {}; 13 | } 14 | 15 | function UIAElementArray() {} 16 | extend(UIAElementArray, Array); 17 | UIAElementArray.prototype.toArray = function() { return this; } 18 | 19 | function UIAElement() { } 20 | extend(UIAElement, Object); 21 | UIAElement.prototype.elements = function() { 22 | if (!this.internalElements) this.internalElements = new UIAElementArray(); 23 | return this.internalElements; 24 | }; 25 | UIAElement.prototype.name = function() {}; 26 | UIAElement.prototype.label = function() {}; 27 | UIAElement.prototype.value = function() {}; 28 | UIAElement.prototype.isEnabled = function() {}; 29 | UIAElement.prototype.isVisible = function() {}; 30 | UIAElement.prototype.hasKeyboardFocus = function() {}; 31 | UIAElement.prototype.isValid = function() {}; 32 | UIAElement.prototype.checkIsValid = function() {}; 33 | UIAElement.prototype.tap = function() {}; 34 | UIAElement.prototype.doubleTap = function() {}; 35 | UIAElement.prototype.twoFingerTap = function() {}; 36 | UIAElement.prototype.tapWithOptions = function() {}; 37 | UIAElement.prototype.touchAndHold = function(duration) {}; 38 | UIAElement.prototype.dragInsideWithOptions = function(options) {}; 39 | UIAElement.prototype.flickInsideWithOptions = function(options) {}; 40 | UIAElement.prototype.rotateWithOptions = function(options) {}; 41 | UIAElement.prototype.scrollToVisible = function() {}; 42 | UIAElement.prototype.logElement = function() {}; 43 | UIAElement.prototype.logElementTree = function() {}; 44 | UIAElement.prototype.rect = function(val) {}; 45 | 46 | function UIAWindow() {} 47 | extend(UIAWindow, UIAElement); 48 | 49 | function UIAScrollView() {} 50 | extend(UIAScrollView, UIAElement); 51 | 52 | function UIATabBar() {} 53 | extend(UIATabBar, UIAElement); 54 | 55 | function UIANavigationBar() {} 56 | extend(UIANavigationBar, UIAElement); 57 | 58 | function UIAStaticText() {} 59 | extend(UIAStaticText, UIAElement); 60 | 61 | function UIATextField() {} 62 | extend(UIATextField, UIAElement); 63 | 64 | function UIAImage() {} 65 | extend(UIAImage, UIAElement); 66 | 67 | function UIATableView() {} 68 | extend(UIATableView, UIAElement); 69 | 70 | function UIAButton() {} 71 | extend(UIAButton, UIAElement); 72 | 73 | function UIACollectionView() {} 74 | extend(UIACollectionView, UIAElement); 75 | 76 | function UIALink() {} 77 | extend(UIALink, UIAElement); 78 | 79 | function UIASwitch() {} 80 | extend(UIASwitch, UIAElement); 81 | 82 | function UIAApplication() {} 83 | extend(UIAApplication, UIAElement); 84 | UIAApplication.prototype.mainWindow = function() { 85 | if (!this.internalMainWindow) this.internalMainWindow = new UIAWindow(); 86 | return this.internalMainWindow; 87 | }; 88 | UIAApplication.prototype.version = function() { 89 | return 1; 90 | }; 91 | UIAApplication.prototype.bundleID = function() { 92 | return "bundle1234"; 93 | }; 94 | UIAApplication.prototype.setPreferencesValueForKey = function(val, key) { 95 | // don't do anything... 96 | }; 97 | UIAApplication.prototype.preferencesValueForKey = function(key) { 98 | // don't do anything... 99 | }; 100 | 101 | function UIATarget() {} 102 | UIATarget.prototype.frontMostApp = function() { 103 | if (!this.internalApp) this.internalApp = new UIAApplication(); 104 | return this.internalApp; 105 | 106 | }; 107 | UIATarget.prototype.setTimeout = function(duration) {}; 108 | UIATarget.prototype.captureScreenWithName = function(name) {}; 109 | UIATarget.prototype.captureRectWithName = function(rect, name) {}; 110 | UIATarget.localTarget = function() { 111 | if (!UIATarget.internalTarget) UIATarget.internalTarget = new UIATarget(); 112 | return UIATarget.internalTarget; 113 | }; 114 | 115 | function UIALogger() {} 116 | UIALogger.logError = function (s) { console.log(s); } 117 | UIALogger.logWarning = UIALogger.logError; 118 | UIALogger.logDebug = UIALogger.logError; 119 | UIALogger.logMessage = UIALogger.logError; 120 | -------------------------------------------------------------------------------- /spec/events-spec.js: -------------------------------------------------------------------------------- 1 | describe('Mechanic Event Module', function() { 2 | it('allows chaining from tap event', function() { 3 | var win = new UIAWindow(); 4 | 5 | var sel = $(win); 6 | var returnVal = sel.tap(); 7 | 8 | expect(returnVal).toBe(sel); 9 | }); 10 | 11 | it('calls tapWithOptions with empty object literal for each element in the selector as the default implementation of tap', function() { 12 | var scrollView = new UIAScrollView(); 13 | spyOn(scrollView, 'tapWithOptions'); 14 | var text = new UIAStaticText(); 15 | spyOn(text, 'tapWithOptions'); 16 | 17 | var sel = $([scrollView, text]); 18 | sel.tap(); 19 | 20 | expect(scrollView.tapWithOptions).toHaveBeenCalledWith({}); 21 | expect(text.tapWithOptions).toHaveBeenCalledWith({}); 22 | }); 23 | 24 | it('calls doubleTap when \'style\' of double is passed as an option', function() { 25 | var scrollView = new UIAScrollView(); 26 | spyOn(scrollView, 'doubleTap'); 27 | 28 | var sel = $([scrollView]); 29 | sel.tap({style: 'double'}); 30 | 31 | expect(scrollView.doubleTap).toHaveBeenCalled(); 32 | }); 33 | 34 | it('calls twoFingerTap when \'style\' of twoFinger is passed as an option', function() { 35 | var scrollView = new UIAScrollView(); 36 | spyOn(scrollView, 'twoFingerTap'); 37 | 38 | var sel = $([scrollView]); 39 | sel.tap({style: 'twoFinger'}); 40 | 41 | expect(scrollView.twoFingerTap).toHaveBeenCalled(); 42 | }); 43 | 44 | it('passes options to tapWithOptions', function() { 45 | var scrollView = new UIAScrollView(); 46 | spyOn(scrollView, 'tapWithOptions'); 47 | 48 | var sel = $([scrollView]); 49 | sel.tap({'option1': 'value1', 'option2': 'value2'}); 50 | 51 | expect(scrollView.tapWithOptions).toHaveBeenCalledWith({'option1': 'value1', 'option2': 'value2'}); 52 | }); 53 | 54 | it('allows chaining from touch event', function() { 55 | var win = new UIAWindow(); 56 | 57 | var sel = $(win); 58 | var returnVal = sel.touch(); 59 | 60 | expect(returnVal).toBe(sel); 61 | }); 62 | 63 | 64 | it('calls touchAndHold internally on each element in selector when touch is called', function() { 65 | var mySwitch = new UIASwitch(); 66 | spyOn(mySwitch, 'touchAndHold'); 67 | var image = new UIAImage(); 68 | spyOn(image, 'touchAndHold'); 69 | 70 | var sel = $([mySwitch, image]); 71 | sel.touch(5); 72 | 73 | expect(mySwitch.touchAndHold).toHaveBeenCalledWith(5); 74 | expect(image.touchAndHold).toHaveBeenCalledWith(5); 75 | }); 76 | 77 | it('allows chaining from dragInside event', function() { 78 | var win = new UIAWindow(); 79 | 80 | var sel = $(win); 81 | var returnVal = sel.dragInside(); 82 | 83 | expect(returnVal).toBe(sel); 84 | }); 85 | 86 | it('calls dragInsideWithOptions internally on each element in selector when dragInside is called', function() { 87 | var mySwitch = new UIASwitch(); 88 | spyOn(mySwitch, 'dragInsideWithOptions'); 89 | var image = new UIAImage(); 90 | spyOn(image, 'dragInsideWithOptions'); 91 | var opts = {'key1': 'value1', 'key2': 'value2'}; 92 | 93 | var sel = $([mySwitch, image]); 94 | sel.dragInside(opts); 95 | 96 | expect(mySwitch.dragInsideWithOptions).toHaveBeenCalledWith(opts); 97 | expect(image.dragInsideWithOptions).toHaveBeenCalledWith(opts); 98 | }); 99 | 100 | it('allows chaining from flick event', function() { 101 | var win = new UIAWindow(); 102 | 103 | var sel = $(win); 104 | var returnVal = sel.flick(); 105 | 106 | expect(returnVal).toBe(sel); 107 | }); 108 | 109 | it('calls flickInsideWithOptions internally on each element in selector when flick is called', function() { 110 | var mySwitch = new UIASwitch(); 111 | spyOn(mySwitch, 'flickInsideWithOptions'); 112 | var image = new UIAImage(); 113 | spyOn(image, 'flickInsideWithOptions'); 114 | var opts = {'key1': 'value1', 'key2': 'value2'}; 115 | 116 | var sel = $([mySwitch, image]); 117 | sel.flick(opts); 118 | 119 | expect(mySwitch.flickInsideWithOptions).toHaveBeenCalledWith(opts); 120 | expect(image.flickInsideWithOptions).toHaveBeenCalledWith(opts); 121 | }); 122 | 123 | it('allows chaining from rotate event', function() { 124 | var win = new UIAWindow(); 125 | 126 | var sel = $(win); 127 | var returnVal = sel.rotate(); 128 | 129 | expect(returnVal).toBe(sel); 130 | }); 131 | 132 | it('calls rotateWithOptions internally on each element in selector when rotate is called', function() { 133 | var mySwitch = new UIASwitch(); 134 | spyOn(mySwitch, 'rotateWithOptions'); 135 | var image = new UIAImage(); 136 | spyOn(image, 'rotateWithOptions'); 137 | var opts = {'key1': 'value1', 'key2': 'value2'}; 138 | 139 | var sel = $([mySwitch, image]); 140 | sel.rotate(opts); 141 | 142 | expect(mySwitch.rotateWithOptions).toHaveBeenCalledWith(opts); 143 | expect(image.rotateWithOptions).toHaveBeenCalledWith(opts); 144 | }); 145 | 146 | it('allows chaining from scrollToVisible event', function() { 147 | var win = new UIAWindow(); 148 | 149 | var sel = $(win); 150 | var returnVal = sel.scrollToVisible(); 151 | 152 | expect(returnVal).toBe(sel); 153 | }); 154 | 155 | it('calls scrollToVisible internally on first element in selector when scrollToVisible is called', function() { 156 | var mySwitch = new UIASwitch(); 157 | spyOn(mySwitch, 'scrollToVisible'); 158 | var image = new UIAImage(); 159 | spyOn(image, 'scrollToVisible'); 160 | 161 | var sel = $([mySwitch, image]); 162 | sel.scrollToVisible(); 163 | 164 | expect(mySwitch.scrollToVisible).toHaveBeenCalled(); 165 | expect(image.scrollToVisible).not.toHaveBeenCalled(); 166 | }); 167 | 168 | }); -------------------------------------------------------------------------------- /spec/logging-spec.js: -------------------------------------------------------------------------------- 1 | describe('Mechanic Logging Module', function() { 2 | it('suppresses message and debug logs when isVerbose is false', function() { 3 | spyOn(UIALogger, 'logError'); 4 | spyOn(UIALogger, 'logWarning'); 5 | spyOn(UIALogger, 'logDebug'); 6 | spyOn(UIALogger, 'logMessage'); 7 | 8 | $.isVerbose = false; 9 | $.log('error', 'error'); 10 | $.log('warn', 'warn'); 11 | $.log('debug', 'debug'); 12 | $.log('message', 'message'); 13 | 14 | expect(UIALogger.logError).toHaveBeenCalledWith('error'); 15 | expect(UIALogger.logWarning).toHaveBeenCalledWith('warn'); 16 | expect(UIALogger.logDebug).not.toHaveBeenCalled(); 17 | expect(UIALogger.logMessage).not.toHaveBeenCalled(); 18 | }); 19 | 20 | it('delegates to UIALogger when logging', function() { 21 | spyOn(UIALogger, 'logError'); 22 | spyOn(UIALogger, 'logWarning'); 23 | spyOn(UIALogger, 'logDebug'); 24 | spyOn(UIALogger, 'logMessage'); 25 | 26 | $.isVerbose = true; 27 | $.error('error'); 28 | $.warn('warn'); 29 | $.debug('debug'); 30 | $.message('message'); 31 | 32 | expect(UIALogger.logError).toHaveBeenCalledWith('error'); 33 | expect(UIALogger.logWarning).toHaveBeenCalledWith('warn'); 34 | expect(UIALogger.logDebug).toHaveBeenCalledWith('debug'); 35 | expect(UIALogger.logMessage).toHaveBeenCalledWith('message'); 36 | }); 37 | 38 | it('defaults log level to \'message\' level when none is given to log function', function() { 39 | spyOn(UIALogger, 'logMessage'); 40 | spyOn(UIALogger, 'logError'); 41 | 42 | $.isVerbose = true; 43 | $.log('hello!'); 44 | 45 | expect(UIALogger.logMessage).toHaveBeenCalledWith('hello!'); 46 | expect(UIALogger.logError).not.toHaveBeenCalled(); 47 | }) 48 | 49 | it('defaults log level to \'message\' when an unknown log level is given to log function', function() { 50 | spyOn(UIALogger, 'logMessage'); 51 | spyOn(UIALogger, 'logError'); 52 | 53 | $.isVerbose = true; 54 | $.log('hello!', 'fakelevel'); 55 | 56 | expect(UIALogger.logMessage).toHaveBeenCalledWith('hello!'); 57 | expect(UIALogger.logError).not.toHaveBeenCalled(); 58 | }); 59 | 60 | it('calls appropriate UIALogger method based on log level passed', function() { 61 | spyOn(UIALogger, 'logError'); 62 | spyOn(UIALogger, 'logWarning'); 63 | spyOn(UIALogger, 'logDebug'); 64 | spyOn(UIALogger, 'logMessage'); 65 | 66 | $.isVerbose = true; 67 | $.log('error', 'error'); 68 | $.log('warn', 'warn'); 69 | $.log('debug', 'debug'); 70 | $.log('message', 'message'); 71 | 72 | expect(UIALogger.logError).toHaveBeenCalledWith('error'); 73 | expect(UIALogger.logWarning).toHaveBeenCalledWith('warn'); 74 | expect(UIALogger.logDebug).toHaveBeenCalledWith('debug'); 75 | expect(UIALogger.logMessage).toHaveBeenCalledWith('message'); 76 | }); 77 | 78 | it('calls captureScreenWithName when no rect given to capture', function() { 79 | spyOn(UIATarget.localTarget(), 'captureScreenWithName'); 80 | 81 | $.capture('image'); 82 | 83 | expect(UIATarget.localTarget().captureScreenWithName).toHaveBeenCalledWith('image'); 84 | }); 85 | 86 | it('uses date as image name when no image name is passed to capture.', function() { 87 | spyOn(UIATarget.localTarget(), 'captureScreenWithName'); 88 | 89 | var date = new Date(); 90 | $.capture(); 91 | 92 | expect(UIATarget.localTarget().captureScreenWithName).toHaveBeenCalledWith(date.toString()); 93 | }); 94 | 95 | it('calls captureRectWithName when an image name and rect is passed to capture.', function() { 96 | spyOn(UIATarget.localTarget(), 'captureRectWithName'); 97 | var rect = {x:1, y:2}; 98 | 99 | $.capture("name", rect); 100 | 101 | expect(UIATarget.localTarget().captureRectWithName).toHaveBeenCalledWith(rect, "name"); 102 | }); 103 | 104 | it('allows chaining when log function is called', function() { 105 | var win = new UIAWindow(); 106 | 107 | var sel = $(win); 108 | 109 | expect(sel.log()).toBe(sel); 110 | }); 111 | 112 | it('calls logElement on each element in the selector when log is called', function() { 113 | var text = new UIAStaticText(); 114 | spyOn(text, 'logElement'); 115 | var image = new UIAImage(); 116 | spyOn(image, 'logElement'); 117 | 118 | var sel = $([text, image]); 119 | sel.log(); 120 | 121 | expect(text.logElement).toHaveBeenCalled(); 122 | expect(image.logElement).toHaveBeenCalled(); 123 | }); 124 | 125 | it('allows chaining when logTree function is called', function() { 126 | var win = new UIAWindow(); 127 | 128 | var sel = $(win); 129 | 130 | expect(sel.logTree()).toBe(sel); 131 | }); 132 | 133 | it('calls logElementTree on each element in the selector when logTree is called', function() { 134 | var text = new UIAStaticText(); 135 | spyOn(text, 'logElementTree'); 136 | var image = new UIAImage(); 137 | spyOn(image, 'logElementTree'); 138 | 139 | var sel = $([text, image]); 140 | sel.logTree(); 141 | 142 | expect(text.logElementTree).toHaveBeenCalled(); 143 | expect(image.logElementTree).toHaveBeenCalled(); 144 | }); 145 | 146 | it('allows chaining when capture is called', function() { 147 | var win = new UIAWindow(); 148 | 149 | var sel = $(win); 150 | 151 | expect(sel.capture()).toBe(sel); 152 | }); 153 | 154 | it('calls captureRectWithName for each element in select when capture is called', function() { 155 | rect1 = {x:1, y:1}; 156 | var win = new UIAWindow(); 157 | spyOn(win, 'rect').andReturn(rect1); 158 | 159 | rect2 = {x:2, y:2}; 160 | var text = new UIAStaticText(); 161 | spyOn(text, 'rect').andReturn(rect2); 162 | 163 | spyOn(UIATarget.localTarget(), 'captureRectWithName'); 164 | 165 | 166 | var sel = $([win, text]); 167 | sel.capture(); 168 | 169 | 170 | expect(sel.capture()).toBe(sel); 171 | expect(UIATarget.localTarget().captureRectWithName).toHaveBeenCalledWith(rect1, jasmine.any(String)); 172 | expect(UIATarget.localTarget().captureRectWithName).toHaveBeenCalledWith(rect2, jasmine.any(String)); 173 | }); 174 | }); -------------------------------------------------------------------------------- /spec/data-spec.js: -------------------------------------------------------------------------------- 1 | describe('Mechanic Data Module', function() { 2 | it('has a name selector function that returns the first element\'s name', function() { 3 | var text = new UIAStaticText(); 4 | spyOn(text, 'name').andReturn('name1'); 5 | var win = new UIAWindow(); 6 | 7 | var sel = $([text, win]); 8 | 9 | expect(sel.name()).toEqual('name1'); 10 | expect(text.name).toHaveBeenCalled(); 11 | }); 12 | 13 | it('returns null when name is called on an empty selector', function() { 14 | var sel = $(); 15 | 16 | expect(sel.name()).toEqual(null); 17 | }); 18 | 19 | it('has a label selector function that returns the first element\'s label', function() { 20 | var mySwitch = new UIASwitch(); 21 | spyOn(mySwitch, 'label').andReturn('switchname'); 22 | var table = new UIATableView(); 23 | 24 | var sel = $([mySwitch, table]); 25 | 26 | expect(sel.label()).toEqual('switchname'); 27 | expect(mySwitch.label).toHaveBeenCalled(); 28 | }); 29 | 30 | it('returns null when label is called on an empty selector', function() { 31 | var sel = $(); 32 | 33 | expect(sel.label()).toEqual(null); 34 | }); 35 | 36 | it('has a value selector function that returns the first element\'s value', function() { 37 | var link = new UIALink(); 38 | spyOn(link, 'value').andReturn('linkvalue'); 39 | var table = new UIATableView(); 40 | 41 | var sel = $([link, table]); 42 | 43 | expect(sel.value()).toEqual('linkvalue'); 44 | expect(link.value).toHaveBeenCalled(); 45 | }); 46 | 47 | it('returns null when value is called on an empty selector', function() { 48 | var sel = $(); 49 | 50 | expect(sel.value()).toEqual(null); 51 | }); 52 | 53 | it('has an isFocused selector function that returns whether or not the first element is focused', function() { 54 | var link = new UIALink(); 55 | spyOn(link, 'hasKeyboardFocus').andReturn(true); 56 | var image = new UIAImage(); 57 | 58 | var sel = $([link, image]); 59 | 60 | expect(sel.isFocused()).toEqual(true); 61 | expect(link.hasKeyboardFocus).toHaveBeenCalled(); 62 | }); 63 | 64 | it('returns false when isFocused is called on an empty selector', function() { 65 | var sel = $(); 66 | 67 | expect(sel.isFocused()).toEqual(false); 68 | }); 69 | 70 | it('has an isEnabled selector function that returns whether or not the first element is enabled', function() { 71 | var scroll = new UIAScrollView(); 72 | spyOn(scroll, 'isEnabled').andReturn(true); 73 | var tabbar = new UIATabBar(); 74 | 75 | var sel = $([scroll, tabbar]); 76 | 77 | expect(sel.isEnabled()).toEqual(true); 78 | expect(scroll.isEnabled).toHaveBeenCalled(); 79 | }); 80 | 81 | it('returns false when isEnabled is called on an empty selector', function() { 82 | var sel = $(); 83 | 84 | expect(sel.isEnabled()).toEqual(false); 85 | }); 86 | 87 | 88 | it('has an isVisible selector function that returns whether or not the first element is visible', function() { 89 | var scroll = new UIAScrollView(); 90 | spyOn(scroll, 'isVisible').andReturn(true); 91 | var tabbar = new UIATabBar(); 92 | 93 | var sel = $([scroll, tabbar]); 94 | 95 | expect(sel.isVisible()).toEqual(true); 96 | expect(scroll.isVisible).toHaveBeenCalled(); 97 | }); 98 | 99 | it('returns false when isVisible is called on an empty selector', function() { 100 | var sel = $(); 101 | 102 | expect(sel.isVisible()).toEqual(false); 103 | }); 104 | 105 | it('has an isValid selector function that returns whether or not the element in the selector is valid', function() { 106 | var win = new UIAWindow(); 107 | spyOn(win, 'isValid').andReturn(true); 108 | 109 | var sel = $(win); 110 | 111 | expect(sel.isValid()).toEqual(true); 112 | expect(win.isValid).toHaveBeenCalled(); 113 | }); 114 | 115 | it('will internally use checkIsValid for isValid selector function when true argument is passed in', function() { 116 | var win = new UIAWindow(); 117 | spyOn(win, 'checkIsValid').andReturn(true); 118 | spyOn(win, 'isValid'); 119 | win.checkIsValid(true); 120 | 121 | var sel = $(win); 122 | 123 | expect(sel.isValid(true)).toEqual(true); 124 | expect(win.checkIsValid).toHaveBeenCalled(); 125 | expect(win.isValid).not.toHaveBeenCalled(); 126 | }); 127 | 128 | it('returns false when isValid is called on a selector not containing 1 element', function() { 129 | var win = new UIAWindow(); 130 | spyOn(win, 'isValid'); 131 | var tab = new UIATabBar(); 132 | spyOn(tab, 'isValid'); 133 | 134 | var sel = $([win, tab]); 135 | 136 | expect(sel.isValid()).toEqual(false); 137 | expect(win.isValid).not.toHaveBeenCalled(); 138 | expect(tab.isValid).not.toHaveBeenCalled(); 139 | }); 140 | 141 | describe('mechanic global data functions', function() { 142 | var app = UIATarget.localTarget().frontMostApp(); 143 | 144 | it('uses frontMostApp\'s version() to get app version', function() { 145 | spyOn(app, 'version'); 146 | 147 | $.version(); 148 | 149 | expect(app.version).toHaveBeenCalled(); 150 | }); 151 | 152 | it('uses frontMostApp\'s bundleID to get app\'s bundle ID', function() { 153 | spyOn(app, 'bundleID'); 154 | 155 | $.bundleID(); 156 | 157 | expect(app.bundleID).toHaveBeenCalled(); 158 | }); 159 | 160 | it('sets app preferences for each key/value passed into prefs()', function() { 161 | spyOn(app, 'setPreferencesValueForKey'); 162 | 163 | $.prefs({ 164 | 'pref1': 1234, 165 | 'pref2': 'abcd' 166 | }); 167 | 168 | expect(app.setPreferencesValueForKey).toHaveBeenCalledWith(1234, 'pref1'); 169 | expect(app.setPreferencesValueForKey).toHaveBeenCalledWith('abcd', 'pref2'); 170 | }); 171 | 172 | it('returns the value for the preference specified by the key when prefs is passed a string', function() { 173 | spyOn(app, 'preferencesValueForKey').andReturn('val'); 174 | 175 | var returnedValue = $.prefs('pref1'); 176 | 177 | expect(app.preferencesValueForKey).toHaveBeenCalledWith('pref1'); 178 | expect(returnedValue).toBe('val'); 179 | }); 180 | }); 181 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #[mechanic.js](http://www.cozykozy.com/pages/mechanicjs) (a CSS-style selector engine for UIAutomation) 2 | 3 | mechanic.js lets you take the power of UIAutomation with the simplicity of modern javascript CSS selector engines to make your UIAutomation scripts terse and beautiful. 4 | 5 | Use mechanic.js to trim down your iOS automation scripts or to cut out the cruft in your iOS UATs. Not sure how to get started? Take a look at [the wiki](https://github.com/jaykz52/mechanic/wiki) for ideas! 6 | 7 | You can also just use it to have fun. Want to click every button in the application? That's one line of code with mechanic. Need to get screenshots of every table cell in your favorite UITableView? Still just one line. 8 | 9 | [Download mechanic.js](http://www.cozykozy.com/wp-content/uploads/2013/04/mechanic-0.2.zip) and give it a try! 10 | 11 | ##Features 12 | 13 | Selecting elements with mechanic.js looks a lot like jQuery, Dojo, 14 | Zepto, etc. The following will select all `UIAStaticText` elements in the element named "My Scroll View": 15 | 16 | ``` js 17 | $('[name=My Scroll View] text'); 18 | ``` 19 | 20 | Mechanic provides selector shortcuts to remove some of the verbosity in 21 | selecting elements by type. If you know the name of the element you want 22 | to use you can prepend the name with `#`: 23 | 24 | ``` js 25 | $('#My Scroll View'); 26 | ``` 27 | 28 | More complex CSS-style selectors are also supported: 29 | 30 | ``` js 31 | // all UIAStaticText elements directly descended from a tabbar 32 | $('tabbar > text'); 33 | // all buttons inside the window named 'Main' 34 | $('window[name=Main] button') 35 | // the text field with a value of 'Search' 36 | $('textfield[value=Search]') 37 | // all buttons plus a specific element by name 38 | $('button, #Continue') 39 | ``` 40 | 41 | If you've already got a hold of the instances you care about, they can be passed into mechanic, giving them all the benefits of mechanic: 42 | 43 | ``` js 44 | var mainWindow = UIATarget.localTarget().frontMostApp().mainWindow(); 45 | $(mainWindow).children().log(); // this calls logElement() internally 46 | ``` 47 | 48 | Mechanic comes with a number of utility functions to make your life easier: 49 | 50 | ``` js 51 | $.backgroundApp(2); 52 | $.delay(5); // this calls UIATarget.localTarget().delay() internally 53 | $("window").children().log(); 54 | ``` 55 | 56 | Many of the utility functions are also tagged onto the selector implementation to allow chaining of selector commands and app-level commands, creating a nice cadence to your automation scripts. The following code captures screenshots of each cell in the "trip table" UITableView, waits for 5 seconds, and then logs their parent: 57 | 58 | ``` js 59 | $.delay(5); 60 | $('[name=trip table] cell') 61 | .capture() 62 | .delay(5) 63 | .parent().log(); 64 | ``` 65 | 66 | Many of the UIAutomation user interaction functions have been simplified: 67 | 68 | ``` js 69 | $.volume({direction: "up", duration: 2}); // UIATarget.localTarget().holdVolumeUp(2) 70 | $.volume({direction: "down"}); // UIATarget.localTarget().clickVolumeDown(); 71 | $.prefs({ 72 | "pref1": "value1", // ... frontMostApp().setPreferencesValueForKey("value1", "pref1"); 73 | "pref2": "value2" // ... frontMostApp().setPreferencesValueForKey("value2", "pref2") 74 | }); 75 | ``` 76 | 77 | ###... and more! 78 | 79 | Mechanic has many more functions to make your life in iOS automation easier. [Read the API documentation](https://github.com/jaykz52/mechanic/wiki) to see just what all can be done with Mechanic.js! 80 | 81 | ##Extending Mechanic.js 82 | 83 | If there's a feature you're missing, it's easy to extend mechanic with your own "mixins" 84 | 85 | ``` js 86 | (function() { 87 | $.extend($, { 88 | someAwesomeFeature: function() { 89 | // do something awesome 90 | }, 91 | andAnother: function(someArg, anotherArg) { 92 | // more amazing greatness 93 | } 94 | }); 95 | $.extend($.fn, { 96 | aNewSelectorMethod: function() { 97 | // yadda yadda yadda 98 | return this; // returning 'this' allows you to chain selector-based functions together 99 | } 100 | }); 101 | })(); 102 | 103 | $.someAwesomeFeature(); 104 | $("tableview").aNewSelectorMethod(); 105 | ``` 106 | 107 | ##Including Mechanic.js 108 | 109 | [Grab a copy of mechanic.js](http://www.cozykozy.com/wp-content/uploads/2013/04/mechanic-0.2.zip) and place it alongside your UIAutomation scripts. At the top of your UIAutomation script(s), import mechanic.js: 110 | 111 | ``` js 112 | #import "mechanic.js" 113 | 114 | // you'll now have "mechanic" (with "$" as a shortcut) in the global context. 115 | mechanic("#some element name").ancestry().capture(); 116 | ... 117 | ``` 118 | 119 | ##Building 120 | 121 | You'll need Ruby and Rake to build mechanic. Pull down the repository with git, and run rake: 122 | 123 | ``` sh 124 | $ rake 125 | ``` 126 | 127 | This will concat all of the mechanic modules into 1 file and place it in a "dist" folder. It also outputs a minified version using Google's Closure compiler (which is not entirely important in light of the environment mechanic.js is run under). 128 | 129 | ##Testing 130 | 131 | Progress has been made in writing specs to provide test coverage for mechanic. The specs are jasmine specs, and can only currently be ran in a standalone static html file that serves as the spec runner. I'd like to get to the point where these are ran via jasmine-headless, but in the meantime you can run the tests by opening spec/runner.html in any modern browser. 132 | 133 | To add additional specs, you can either append the specs to an existing spec file (there is a spec for each module), or you can create a separate spec and add a script tag pointing to the new spec in spec/runner.html 134 | 135 | ##Feature Requests/Bugs 136 | 137 | Mechanic is not perfect. Submit bugs and feature requests if there's something that you think is missing! If you encounter bugs, please report it on mechanic's Issues page: http://github.com/jaykz52/mechanic/issues 138 | 139 | If you've got a fix in mind, fork and submit a pull request. We need contributors! 140 | 141 | ##Contributing 142 | 143 | The project is currently in its infancy. We need API documentation, additional features, and test coverage (!!!), so feel free to get in touch via github, or just start submitting patches! Try to keep patches to single issues, this will make our lives easier in the long term. If you or your organization is interested in adopting mechanic.js and have questions, feel free to contact Jason at jason.kozemcak@gmail.com 144 | 145 | ##License 146 | 147 | Copyright (c) 2012 Jason Kozemczak 148 | 149 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 150 | 151 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 152 | 153 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 154 | -------------------------------------------------------------------------------- /spec/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 | -------------------------------------------------------------------------------- /vendor/google-compiler/README: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2009 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // 18 | // Contents 19 | // 20 | 21 | The Closure Compiler performs checking, instrumentation, and 22 | optimizations on JavaScript code. The purpose of this README is to 23 | explain how to build and run the Closure Compiler. 24 | 25 | The Closure Compiler requires Java 6 or higher. 26 | http://www.java.com/ 27 | 28 | 29 | // 30 | // Building The Closure Compiler 31 | // 32 | 33 | There are three ways to get a Closure Compiler executable. 34 | 35 | 1) Use one we built for you. 36 | 37 | Pre-built Closure binaries can be found at 38 | http://code.google.com/p/closure-compiler/downloads/list 39 | 40 | 41 | 2) Check out the source and build it with Apache Ant. 42 | 43 | First, check out the full source tree of the Closure Compiler. There 44 | are instructions on how to do this at the project site. 45 | http://code.google.com/p/closure-compiler/source/checkout 46 | 47 | Apache Ant is a cross-platform build tool. 48 | http://ant.apache.org/ 49 | 50 | At the root of the source tree, there is an Ant file named 51 | build.xml. To use it, navigate to the same directory and type the 52 | command 53 | 54 | ant jar 55 | 56 | This will produce a jar file called "build/compiler.jar". 57 | 58 | 59 | 3) Check out the source and build it with Eclipse. 60 | 61 | Eclipse is a cross-platform IDE. 62 | http://www.eclipse.org/ 63 | 64 | Under Eclipse's File menu, click "New > Project ..." and create a 65 | "Java Project." You will see an options screen. Give the project a 66 | name, select "Create project from existing source," and choose the 67 | root of the checked-out source tree as the existing directory. Verify 68 | that you are using JRE version 6 or higher. 69 | 70 | Eclipse can use the build.xml file to discover rules. When you 71 | navigate to the build.xml file, you will see all the build rules in 72 | the "Outline" pane. Run the "jar" rule to build the compiler in 73 | build/compiler.jar. 74 | 75 | 76 | // 77 | // Running The Closure Compiler 78 | // 79 | 80 | Once you have the jar binary, running the Closure Compiler is straightforward. 81 | 82 | On the command line, type 83 | 84 | java -jar compiler.jar 85 | 86 | This starts the compiler in interactive mode. Type 87 | 88 | var x = 17 + 25; 89 | 90 | then hit "Enter", then hit "Ctrl-Z" (on Windows) or "Ctrl-D" (on Mac or Linux) 91 | and "Enter" again. The Compiler will respond: 92 | 93 | var x=42; 94 | 95 | The Closure Compiler has many options for reading input from a file, 96 | writing output to a file, checking your code, and running 97 | optimizations. To learn more, type 98 | 99 | java -jar compiler.jar --help 100 | 101 | You can read more detailed documentation about the many flags at 102 | http://code.google.com/closure/compiler/docs/gettingstarted_app.html 103 | 104 | 105 | // 106 | // Compiling Multiple Scripts 107 | // 108 | 109 | If you have multiple scripts, you should compile them all together with 110 | one compile command. 111 | 112 | java -jar compiler.jar --js=in1.js --js=in2.js ... --js_output_file=out.js 113 | 114 | The Closure Compiler will concatenate the files in the order they're 115 | passed at the command line. 116 | 117 | If you need to compile many, many scripts together, you may start to 118 | run into problems with managing dependencies between scripts. You 119 | should check out the Closure Library. It contains functions for 120 | enforcing dependencies between scripts, and a tool called calcdeps.py 121 | that knows how to give scripts to the Closure Compiler in the right 122 | order. 123 | 124 | http://code.google.com/p/closure-library/ 125 | 126 | // 127 | // Licensing 128 | // 129 | 130 | Unless otherwise stated, all source files are licensed under 131 | the Apache License, Version 2.0. 132 | 133 | 134 | ----- 135 | Code under: 136 | src/com/google/javascript/rhino 137 | test/com/google/javascript/rhino 138 | 139 | URL: http://www.mozilla.org/rhino 140 | Version: 1.5R3, with heavy modifications 141 | License: Netscape Public License and MPL / GPL dual license 142 | 143 | Description: A partial copy of Mozilla Rhino. Mozilla Rhino is an 144 | implementation of JavaScript for the JVM. The JavaScript parser and 145 | the parse tree data structures were extracted and modified 146 | significantly for use by Google's JavaScript compiler. 147 | 148 | Local Modifications: The packages have been renamespaced. All code not 149 | relavant to parsing has been removed. A JSDoc parser and static typing 150 | system have been added. 151 | 152 | 153 | ----- 154 | Code in: 155 | lib/libtrunk_rhino_parser_jarjared.jar 156 | 157 | Rhino 158 | URL: http://www.mozilla.org/rhino 159 | Version: Trunk 160 | License: Netscape Public License and MPL / GPL dual license 161 | 162 | Description: Mozilla Rhino is an implementation of JavaScript for the JVM. 163 | 164 | Local Modifications: None. We've used JarJar to renamespace the code 165 | post-compilation. See: 166 | http://code.google.com/p/jarjar/ 167 | 168 | 169 | ----- 170 | Code in: 171 | lib/args4j_deploy.jar 172 | 173 | Args4j 174 | URL: https://args4j.dev.java.net/ 175 | Version: 2.0.9 176 | License: MIT 177 | 178 | Description: 179 | args4j is a small Java class library that makes it easy to parse command line 180 | options/arguments in your CUI application. 181 | 182 | Local Modifications: None. 183 | 184 | 185 | ----- 186 | Code in: 187 | lib/guava-r06.jar 188 | 189 | Guava Libraries 190 | URL: http://code.google.com/p/guava-libraries/ 191 | Version: R6 192 | License: Apache License 2.0 193 | 194 | Description: Google's core Java libraries. 195 | 196 | Local Modifications: None. 197 | 198 | 199 | ----- 200 | Code in: 201 | lib/hamcrest-core-1.1.jar 202 | 203 | Hamcrest 204 | URL: http://code.google.com/p/hamcrest 205 | License: BSD 206 | License File: LICENSE 207 | 208 | Description: 209 | Provides a library of matcher objects (also known as constraints or 210 | predicates) allowing 'match' rules to be defined declaratively, to be used in 211 | other frameworks. Typical scenarios include testing frameworks, mocking 212 | libraries and UI validation rules. 213 | 214 | Local modifications: 215 | The original jars contained both source code and compiled classes. 216 | 217 | hamcrest-core-1.1.jar just contains the compiled classes. 218 | 219 | 220 | 221 | ----- 222 | Code in: 223 | lib/jsr305.jar 224 | 225 | Annotations for software defect detection 226 | URL: http://code.google.com/p/jsr-305/ 227 | Version: svn revision 47 228 | License: BSD License 229 | 230 | Description: Annotations for software defect detection. 231 | 232 | Local Modifications: None. 233 | 234 | 235 | ---- 236 | Code in: 237 | lib/junit.jar 238 | 239 | JUnit 240 | URL: http://sourceforge.net/projects/junit/ 241 | Version: 4.5 242 | License: Common Public License 1.0 243 | 244 | Description: A framework for writing and running automated tests in Java. 245 | 246 | Local Modifications: None. 247 | 248 | 249 | --- 250 | Code in: 251 | lib/protobuf-java-2.3.0.jar 252 | 253 | Protocol Buffers 254 | URL: http://code.google.com/p/protobuf/ 255 | Version: 2.3.0 256 | License: New BSD License 257 | 258 | Description: Supporting libraries for protocol buffers, 259 | an encoding of structured data. 260 | 261 | Local Modifications: None 262 | 263 | 264 | --- 265 | Code in: 266 | lib/ant_deploy.jar 267 | 268 | URL: http://ant.apache.org/bindownload.cgi 269 | Version: 1.6.5 270 | License: Apache License 2.0 271 | Description: 272 | Ant is a Java based build tool. In theory it is kind of like "make" 273 | without make's wrinkles and with the full portability of pure java code. 274 | 275 | Local Modifications: 276 | Modified apache-ant-1.6.5/bin/ant to look in the ant.runfiles directory 277 | 278 | 279 | --- 280 | Code in: 281 | lib/json.jar 282 | URL: http://json.org/java/index.html 283 | Version: JSON version 2 284 | License: MIT license 285 | Description: 286 | JSON is a set of java files for use in transmitting data in JSON format. 287 | 288 | Local Modifications: None 289 | 290 | -------------------------------------------------------------------------------- /spec/core-spec.js: -------------------------------------------------------------------------------- 1 | describe('Mechanic Core', function() { 2 | it('returns a wrapped UIAElement when one is passed as an argument', function() { 3 | var window = new UIAWindow(); 4 | 5 | var wrappedWindow = $(window); 6 | 7 | expect(wrappedWindow[0]).toBe(window); 8 | expect(wrappedWindow.length).toEqual(1); 9 | }); 10 | 11 | it('returns a wrapped array of UIAElements when one is passed as an argument', function() { 12 | var window = new UIAWindow(); 13 | var text = new UIAStaticText(); 14 | 15 | var wrappedArray = $([window, text]); 16 | 17 | expect(wrappedArray).toContain(window); 18 | expect(wrappedArray).toContain(text); 19 | expect(wrappedArray.length).toEqual(2); 20 | }); 21 | 22 | it('uses the context passed to it to filter selection', function() { 23 | var window = new UIAWindow(); 24 | var text1 = new UIAStaticText(); 25 | var scrollView = new UIAScrollView(); 26 | var text2 = new UIAStaticText(); 27 | window.elements().push(text1); 28 | window.elements().push(scrollView); 29 | scrollView.elements().push(text2); 30 | 31 | var filteredByContext = $('UIAStaticText', scrollView); 32 | 33 | expect(filteredByContext).toContain(text2); 34 | expect(filteredByContext).toNotContain(text1); 35 | }); 36 | 37 | it('supports selecting by name/label', function() { 38 | var window = new UIAWindow(); 39 | 40 | var text1 = new UIAStaticText(); 41 | spyOn(text1, 'name').andReturn('text1'); 42 | window.elements().push(text1); 43 | 44 | var text2 = new UIAStaticText(); 45 | spyOn(text2, 'name').andReturn('text2'); 46 | window.elements().push(text2); 47 | 48 | 49 | var byNameSelector = $('#text1', window); 50 | 51 | 52 | expect(byNameSelector).toContain(text1); 53 | expect(byNameSelector).toNotContain(text2); 54 | }); 55 | 56 | it('supports selecting by name using longform', function() { 57 | var window = new UIAWindow(); 58 | 59 | var text1 = new UIAStaticText(); 60 | spyOn(text1, 'name').andReturn('text 1'); 61 | window.elements().push(text1); 62 | 63 | var text2 = new UIAStaticText(); 64 | spyOn(text2, 'name').andReturn('text 2'); 65 | window.elements().push(text2); 66 | 67 | 68 | var byNameSelector = $('[name=text 1]', window); 69 | 70 | 71 | expect(byNameSelector).toContain(text1); 72 | expect(byNameSelector).toNotContain(text2); 73 | }) 74 | 75 | it('supports selecting by value', function() { 76 | var window = new UIAWindow(); 77 | 78 | var text1 = new UIATextField(); 79 | spyOn(text1, 'name').andReturn('text 1'); 80 | spyOn(text1, 'value').andReturn('First Name'); 81 | window.elements().push(text1); 82 | 83 | var text2 = new UIAStaticText(); 84 | spyOn(text2, 'name').andReturn('text 2'); 85 | spyOn(text2, 'value').andReturn('Last Name'); 86 | window.elements().push(text2); 87 | 88 | 89 | var byValueSelector = $('[value=First Name]', window); 90 | 91 | expect(byValueSelector).toContain(text1); 92 | expect(byValueSelector).toNotContain(text2); 93 | }) 94 | 95 | it('supports selecting by type', function() { 96 | var window = new UIAWindow(); 97 | var button = new UIAButton(); 98 | window.elements().push(button); 99 | var tableview = new UIATableView(); 100 | window.elements().push(tableview); 101 | 102 | var byTypeSelector = $('UIAButton', window); 103 | 104 | expect(byTypeSelector).toContain(button); 105 | expect(byTypeSelector).toNotContain(tableview); 106 | }); 107 | 108 | it('allows for a number of shortcut type selectors', function() { 109 | var link = new UIALink(); 110 | var text = new UIAStaticText(); 111 | var window = new UIAWindow(); 112 | window.elements().push(link); 113 | window.elements().push(text); 114 | 115 | var byTypeShortcutSel = $('link', window); 116 | }); 117 | 118 | it('allows you to select multiple element groups at once', function() { 119 | var window = new UIAWindow(); 120 | var text1 = new UIAStaticText(); 121 | var text2 = new UIAStaticText(); 122 | var button1 = new UIAButton(); 123 | var button2 = new UIAButton(); 124 | spyOn(text1, 'name').andReturn('text 1'); 125 | spyOn(text2, 'name').andReturn('text 2'); 126 | spyOn(button1, 'name').andReturn('button 1'); 127 | spyOn(button2, 'name').andReturn('button 2'); 128 | window.elements().push(text1); 129 | window.elements().push(text2); 130 | window.elements().push(button1); 131 | window.elements().push(button2); 132 | 133 | var result = $('#button 1, [name=text 2]', window); 134 | expect(result[0]).toBe(button1); 135 | expect(result[1]).toBe(text2); 136 | expect(result.length).toEqual(2); 137 | }); 138 | 139 | it('allows you to select elements that are the children of another selector', function() { 140 | // window 141 | // image#image1 142 | // button#button1 143 | // link#link1 144 | // navigationBar#navbar1 145 | // button#button2 146 | // link#link2 147 | // navigationBar#navbar2 148 | // link#link3 149 | // text#text1 150 | // tabbar#navbar3 151 | // button#button3 152 | // link#link4 153 | var window = new UIAWindow() 154 | var image1 = new UIAImage() 155 | spyOn(image1, 'name').andReturn('image1') 156 | var button1 = new UIAButton() 157 | spyOn(button1, 'name').andReturn('button1') 158 | var link1 = new UIALink() 159 | spyOn(link1, 'name').andReturn('link1') 160 | var navigationBar1 = new UIANavigationBar() 161 | spyOn(navigationBar1, 'name').andReturn('navigationBar1') 162 | var button2 = new UIAButton() 163 | spyOn(button2, 'name').andReturn('button2') 164 | var link2 = new UIALink() 165 | spyOn(link2, 'name').andReturn('link2') 166 | var navigationBar2 = new UIANavigationBar() 167 | spyOn(navigationBar2, 'name').andReturn('navigationBar2') 168 | var link3 = new UIALink() 169 | spyOn(link3, 'name').andReturn('link3') 170 | var text1 = new UIAStaticText() 171 | spyOn(text1, 'name').andReturn('text1') 172 | var tabbar1 = new UIATabBar() 173 | spyOn(tabbar1, 'name').andReturn('tabbar1') 174 | var button3 = new UIAButton() 175 | spyOn(button3, 'name').andReturn('button3') 176 | var link4 = new UIALink() 177 | spyOn(link4, 'name').andReturn('link4') 178 | 179 | window.elements().push(image1) 180 | window.elements().push(button1) 181 | window.elements().push(link1) 182 | window.elements().push(navigationBar1) 183 | window.elements().push(navigationBar2) 184 | navigationBar1.elements().push(button2) 185 | navigationBar1.elements().push(link2) 186 | navigationBar2.elements().push(link3) 187 | navigationBar2.elements().push(text1) 188 | navigationBar2.elements().push(tabbar1) 189 | tabbar1.elements().push(button3) 190 | tabbar1.elements().push(link4) 191 | 192 | var found 193 | 194 | found = $("navigationBar > link", window) 195 | expect(found[0]).toBe(link2) 196 | expect(found[1]).toBe(link3) 197 | expect(found.length).toBe(2) 198 | 199 | found = $("navigationBar > link[name=link3]", window) 200 | expect(found.length).toBe(1) 201 | expect(found[0]).toBe(link3) 202 | 203 | found = $("navigationBar > link", window) 204 | expect(found[0]).toBe(link2) 205 | expect(found[1]).toBe(link3) 206 | expect(found.length).toBe(2) 207 | 208 | found = $("tabbar > *", window) 209 | expect(found[0]).toBe(button3) 210 | expect(found[1]).toBe(link4) 211 | expect(found.length).toBe(2) 212 | 213 | // without the ">", depth is unlimited 214 | found = $("navigationBar link", window) 215 | expect(found[0]).toBe(link2) 216 | expect(found[1]).toBe(link3) 217 | expect(found[2]).toBe(link4) 218 | expect(found.length).toBe(3) 219 | }) 220 | 221 | it('allows you to select elements with names/labels with special characters (closes GI-5)', function() { 222 | var window = new UIAWindow(); 223 | var text = new UIAStaticText(); 224 | spyOn(text, 'name').andReturn('100% Awesome'); 225 | window.elements().push(text); 226 | 227 | var result = $('#100% Awesome', window); 228 | expect(result[0]).toBe(text); 229 | }); 230 | 231 | it('returns the passed selector if a selector is passed as an argument', function() { 232 | var window = new UIAWindow(); 233 | var wrappedWindow = $(window); 234 | 235 | var doubleWrappedWindow = $(wrappedWindow); 236 | 237 | expect(doubleWrappedWindow).toBe(wrappedWindow); 238 | }); 239 | 240 | it('returns an empty selector when no arguments are passed in', function() { 241 | var emptySelector = $(); 242 | 243 | expect(emptySelector.length).toEqual(0); 244 | }); 245 | 246 | it('stores the selector passed to it', function() { 247 | var window = new UIAWindow(); 248 | 249 | var wrappedWindow = $(window); 250 | var otherWrappedWindow = $('window'); 251 | 252 | expect(wrappedWindow.selector).toBe(window); 253 | expect(otherWrappedWindow.selector).toBe('window'); 254 | }); 255 | 256 | describe('pluck', function() { 257 | it('captures the return values of each object when passed the name of a function', function() { 258 | var scrollView = new UIAScrollView(); 259 | spyOn(scrollView, 'name').andReturn('name #1'); 260 | var text = new UIAStaticText(); 261 | spyOn(text, 'name').andReturn('name #2'); 262 | 263 | var sel = $([scrollView, text]); 264 | var names = sel.pluck('name'); 265 | 266 | expect(scrollView.name).toHaveBeenCalled(); 267 | expect(text.name).toHaveBeenCalled(); 268 | 269 | expect(names.length).toBe(2); 270 | expect(names[0]).toBe('name #1'); 271 | expect(names[1]).toBe('name #2'); 272 | }); 273 | }); 274 | }); 275 | -------------------------------------------------------------------------------- /vendor/google-compiler/COPYING: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/mechanic-core.js: -------------------------------------------------------------------------------- 1 | /* 2 | * mechanic.js UIAutomation Library 3 | * http://cozykozy.com/pages/mechanicjs 4 | * 5 | * Copyright (c) 2012 Jason Kozemczak 6 | * mechanic.js may be freely distributed under the MIT license. 7 | * 8 | * Includes parts of Zepto.js 9 | * Copyright 2010-2012, Thomas Fuchs 10 | * Zepto.js may be freely distributed under the MIT license. 11 | */ 12 | 13 | var mechanic = (function() { 14 | // Save a reference to the local target for convenience 15 | var target = UIATarget.localTarget(); 16 | 17 | // Set the default timeout value to 0 to avoid making walking the object tree incredibly slow. 18 | // Developers can adjust this value by calling $.timeout(duration) 19 | target.setTimeout(0); 20 | 21 | // This property is meant to suppress excessive logging which prevents instruments from promptly dumping out screenshots. 22 | this.isVerbose = false 23 | 24 | var app = target.frontMostApp(), 25 | window = app.mainWindow(), 26 | emptyArray = [], 27 | slice = emptyArray.slice 28 | 29 | // Setup a map of UIAElement types to their "shortcut" selectors. 30 | var typeShortcuts = { 31 | 'UIAActionSheet' : ['actionsheet'], 32 | 'UIAActivityIndicator' : ['activityIndicator'], 33 | 'UIAAlert' : ['alert'], 34 | 'UIAButton' : ['button'], 35 | 'UIACollectionCell' : ['collectionCell'], 36 | 'UIACollectionView' : ['collection'], 37 | 'UIAEditingMenu': ['editingMenu'], 38 | 'UIAElement' : ['\\*'], // TODO: sort of a hack 39 | 'UIAImage' : ['image'], 40 | 'UIAKey' : ['key'], 41 | 'UIAKeyboard' : ['keyboard'], 42 | 'UIALink' : ['link'], 43 | 'UIAMapView': ['mapView'], 44 | 'UIAPageIndicator' : ['pageIndicator'], 45 | 'UIAPicker' : ['picker'], 46 | 'UIAPickerWheel' : ['pickerwheel'], 47 | 'UIAPopover' : ['popover'], 48 | 'UIAProgressIndicator' : ['progress'], 49 | 'UIAScrollView' : ['scrollview'], 50 | 'UIASearchBar' : ['searchbar'], 51 | 'UIASecureTextField' : ['secure'], 52 | 'UIASegmentedControl' : ['segmented'], 53 | 'UIASlider' : ['slider'], 54 | 'UIAStaticText' : ['text'], 55 | 'UIAStatusBar' : ['statusbar'], 56 | 'UIASwitch' : ['switch'], 57 | 'UIATabBar' : ['tabbar'], 58 | 'UIATableView' : ['tableview'], 59 | 'UIATableCell' : ['cell', 'tableCell'], 60 | 'UIATableGroup' : ['group'], 61 | 'UIATextField' : ['textfield'], 62 | 'UIATextView' : ['textview'], 63 | 'UIAToolbar' : ['toolbar'], 64 | 'UIAWebView' : ['webview'], 65 | 'UIAWindow' : ['window'], 66 | 'UIANavigationBar': ['navigationBar'] 67 | }; 68 | 69 | // Build a RegExp for picking out type selectors. 70 | var typeSelectorREString = (function() { 71 | var key; 72 | var typeSelectorREString = "\\"; 73 | for (key in typeShortcuts) { 74 | // Instruments' javascript runtime (Xcode 5) automatically adds 75 | // extra keys which are not in the shortcut list above, skip them 76 | if (Array.isArray(typeShortcuts[key])) { 77 | typeSelectorREString += key + "|"; 78 | typeShortcuts[key].forEach(function(shortcut) { typeSelectorREString += shortcut + "|"; }); 79 | } 80 | } 81 | return typeSelectorREString.substr(1, typeSelectorREString.length - 2); 82 | })(); 83 | 84 | var patternName = "[^,\\[\\]]+" 85 | 86 | var selectorPatterns = { 87 | simple: (new RegExp("^#("+patternName+")$")) 88 | ,byType: (new RegExp("^("+typeSelectorREString+")$")) 89 | ,byAttr: (new RegExp("^\\[(\\w+)=("+patternName+")\\]$")) 90 | ,byTypeAndAttr: (new RegExp("^("+typeSelectorREString+")\\[(\\w+)=("+patternName+")\\]$")) 91 | ,children: (new RegExp("^(.*) > (.*)$")) 92 | ,descendents: (new RegExp("^(.+) +(.+)$")) 93 | } 94 | 95 | var searches = { 96 | simple: function(name) { return this.getElementsByName(name) } 97 | ,byType: function(type) { return this.getElementsByType(type) } 98 | ,byAttr: function(attr,value) { return this.getElementsByAttr(attr,value) } 99 | ,byTypeAndAttr: function(type,a,v) { return $(type, this).filter('['+a+'='+v+']') } 100 | ,children: function(parent,child) { return $(parent, this).children().filter(child) } 101 | ,descendents: function(parent,child) { return $(child, $(parent, this)) } 102 | } 103 | 104 | var filters = { 105 | simple: function(name) { return this.name() == name } 106 | ,byType: function(type) { return this.isType(type) } 107 | ,byAttr: function(attr,value){ return this[attr] && this[attr]() == value } 108 | ,byTypeAndAttr: function(type,a,v ) { return this.isType(type) && this[a]() == v } 109 | } 110 | 111 | 112 | function Z(dom, selector){ 113 | dom = dom || emptyArray; 114 | dom.__proto__ = Z.prototype; 115 | dom.selector = selector || ''; 116 | if (dom === emptyArray) { 117 | UIALogger.logWarning("element " + selector + " have not been found"); 118 | } 119 | return dom; 120 | } 121 | 122 | function $(selector, context) { 123 | if (!selector) return Z(); 124 | if (context !== undefined) return $(context).find(selector); 125 | else if (selector instanceof Z) return selector; 126 | else { 127 | var dom; 128 | if (isA(selector)) dom = compact(selector); 129 | else if (selector instanceof UIAElement) dom = [selector]; 130 | else dom = $$(app, selector); 131 | return Z(dom, selector); 132 | } 133 | } 134 | 135 | $.qsa = $$ = function(element, selector) { 136 | var ret = [], 137 | groups = selector.split(/ *, */), 138 | matches 139 | $.each(groups, function() { 140 | for (type in searches) { 141 | if (matches = this.match(selectorPatterns[type])) { 142 | matches.shift() 143 | ret = ret.concat($(searches[type].apply(element, matches))) 144 | break 145 | } 146 | } 147 | }) 148 | return $(ret) 149 | }; 150 | 151 | // Add functions to UIAElement to make object graph searching easier. 152 | UIAElement.prototype.getElementsByName = function(name) { 153 | return this.getElementsByAttr('name', name) 154 | }; 155 | 156 | UIAElement.prototype.getElementsByAttr = function(attr, value) { 157 | return $.map(this.elements(), function(el) { 158 | var matches = el.getElementsByAttr(attr, value), 159 | val = el[attr] 160 | if (typeof val == 'function') val = val.apply(el) 161 | if (typeof val != 'undefined' && val == value) 162 | matches.push(el) 163 | return matches 164 | }) 165 | } 166 | UIAElement.prototype.getElementsByType = function(type) { 167 | return $.map(this.elements(), function(el) { 168 | var matches = el.getElementsByType(type); 169 | if (el.isType(type)) matches.unshift(el); 170 | return matches; 171 | }); 172 | }; 173 | UIAElement.prototype.isType = function(type) { 174 | var thisType = this.toString().split(" ")[1]; 175 | thisType = thisType.substr(0, thisType.length - 1); 176 | if (type === thisType) return true; 177 | else if (typeShortcuts[thisType] !== undefined && typeShortcuts[thisType].indexOf(type) >= 0) return true; 178 | else if (type === '*' || type === 'UIAElement') return true; 179 | else return false; 180 | }; 181 | 182 | function isF(value) { return ({}).toString.call(value) == "[object Function]"; } 183 | function isO(value) { return value instanceof Object; } 184 | function isA(value) { return value instanceof Array; } 185 | function likeArray(obj) { return typeof obj.length == 'number'; } 186 | 187 | function compact(array) { return array.filter(function(item){ return item !== undefined && item !== null; }); } 188 | function flatten(array) { return array.length > 0 ? [].concat.apply([], array) : array; } 189 | 190 | function uniq(array) { return array.filter(function(item,index,array){ return array.indexOf(item) == index; }); } 191 | 192 | function filtered(elements, selector) { 193 | return selector === undefined ? $(elements) : $(elements).filter(selector); 194 | } 195 | 196 | $.extend = function(target){ 197 | var key; 198 | slice.call(arguments, 1).forEach(function(source) { 199 | for (key in source) target[key] = source[key]; 200 | }); 201 | return target; 202 | }; 203 | 204 | $.inArray = function(elem, array, i) { 205 | return emptyArray.indexOf.call(array, elem, i); 206 | }; 207 | 208 | $.map = function(elements, callback) { 209 | var value, values = [], i, key; 210 | if (likeArray(elements)) { 211 | for (i = 0; i < elements.length; i++) { 212 | value = callback(elements[i], i); 213 | if (value != null) values.push(value); 214 | } 215 | } else { 216 | for (key in elements) { 217 | value = callback(elements[key], key); 218 | if (value != null) values.push(value); 219 | } 220 | } 221 | return flatten(values); 222 | }; 223 | 224 | $.each = function(elements, callback) { 225 | var i, key; 226 | if (likeArray(elements)) { 227 | for(i = 0; i < elements.length; i++) { 228 | if(callback.call(elements[i], i, elements[i]) === false) return elements; 229 | } 230 | } else { 231 | for(key in elements) { 232 | if(callback.call(elements[key], key, elements[key]) === false) return elements; 233 | } 234 | } 235 | return elements; 236 | }; 237 | 238 | $.fn = { 239 | forEach: emptyArray.forEach, 240 | reduce: emptyArray.reduce, 241 | push: emptyArray.push, 242 | indexOf: emptyArray.indexOf, 243 | concat: emptyArray.concat, 244 | map: function(fn){ 245 | return $.map(this, function(el, i){ return fn.call(el, i, el); }); 246 | }, 247 | slice: function(){ 248 | return $(slice.apply(this, arguments)); 249 | }, 250 | get: function(idx){ return idx === undefined ? slice.call(this) : this[idx]; }, 251 | size: function(){ return this.length; }, 252 | each: function(callback) { 253 | this.forEach(function(el, idx){ callback.call(el, idx, el); }); 254 | return this; 255 | }, 256 | filter: function(selector) { 257 | var matches 258 | for (type in filters) { 259 | if (matches = selector.match(selectorPatterns[type])) { 260 | matches.shift() // remove the original string, we only want the capture groups 261 | return $.map(this, function(e) { 262 | return filters[type].apply(e, matches) ? e : null 263 | }) 264 | } 265 | } 266 | }, 267 | end: function(){ 268 | return this.prevObject || $(); 269 | }, 270 | andSelf:function(){ 271 | return this.add(this.prevObject || $()); 272 | }, 273 | add:function(selector,context){ 274 | return $(uniq(this.concat($(selector,context)))); 275 | }, 276 | is: function(selector){ 277 | return this.length > 0 && $(this[0]).filter(selector).length > 0; 278 | }, 279 | not: function(selector){ 280 | var nodes=[]; 281 | if (isF(selector) && selector.call !== undefined) 282 | this.each(function(idx){ 283 | if (!selector.call(this,idx)) nodes.push(this); 284 | }); 285 | else { 286 | var excludes = typeof selector == 'string' ? this.filter(selector) : 287 | (likeArray(selector) && isF(selector.item)) ? slice.call(selector) : $(selector); 288 | this.forEach(function(el){ 289 | if (excludes.indexOf(el) < 0) nodes.push(el); 290 | }); 291 | } 292 | return $(nodes); 293 | }, 294 | eq: function(idx){ 295 | return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1); 296 | }, 297 | first: function(){ var el = this[0]; return el && !isO(el) ? el : $(el); }, 298 | last: function(){ var el = this[this.length - 1]; return el && !isO(el) ? el : $(el); }, 299 | find: function(selector) { 300 | var result; 301 | if (this.length == 1) result = $$(this[0], selector); 302 | else result = this.map(function(){ return $$(this, selector); }); 303 | return $(result); 304 | }, 305 | predicate: function(predicate) { 306 | return this.map(function(el, idx) { 307 | if (typeof predicate == 'string') return el.withPredicate(predicate); 308 | else return null; 309 | }); 310 | }, 311 | valueForKey: function(key, value) { 312 | var result = this.map(function(idx, el) { 313 | if (key in el && el[key]() == value) { 314 | return el; 315 | } 316 | return null; 317 | }); 318 | return $(result); 319 | }, 320 | valueInKey: function(key, val) { 321 | var result = this.map(function(idx, el) { 322 | if (key in el) { 323 | var elKey = el[key](); 324 | if (elKey === null) { 325 | return null; 326 | } 327 | // make this a case insensitive search 328 | elKey = elKey.toString().toLowerCase(); 329 | val = val.toString().toLowerCase(); 330 | 331 | if (elKey.indexOf(val) !== -1) { 332 | return el; 333 | } 334 | } 335 | return null; 336 | }); 337 | return $(result); 338 | }, 339 | closest: function(selector, context) { 340 | var el = this[0], candidates = $$(context || app, selector); 341 | if (!candidates.length) el = null; 342 | while (el && candidates.indexOf(el) < 0) 343 | el = el !== context && el !== app && el.parent(); 344 | return $(el); 345 | }, 346 | ancestry: function(selector) { 347 | var ancestors = [], elements = this; 348 | while (elements.length > 0) 349 | elements = $.map(elements, function(node){ 350 | if ((node = node.parent()) && !node.isType('UIAApplication') && ancestors.indexOf(node) < 0) { 351 | ancestors.push(node); 352 | return node; 353 | } 354 | }); 355 | return filtered(ancestors, selector); 356 | }, 357 | parent: function(selector) { 358 | return filtered(uniq(this.map(function() { return this.parent(); })), selector); 359 | }, 360 | children: function(selector) { 361 | return filtered(this.map(function(){ return slice.call(this.elements()); }), selector); 362 | }, 363 | siblings: function(selector) { 364 | return filtered(this.map(function(i, el) { 365 | return slice.call(el.parent().elements()).filter(function(child){ return child!==el; }); 366 | }), selector); 367 | }, 368 | next: function(selector) { 369 | return filtered(this.map(function() { 370 | var els = this.parent().elements().toArray(); 371 | return els[els.indexOf(this) + 1]; 372 | }), selector); 373 | }, 374 | prev: function(selector) { 375 | return filtered(this.map(function() { 376 | var els = this.parent().elements().toArray(); 377 | return els[els.indexOf(this) - 1]; 378 | }), selector); 379 | }, 380 | index: function(element) { 381 | return element ? this.indexOf($(element)[0]) : this.parent().elements().toArray().indexOf(this[0]); 382 | }, 383 | pluck: function(property) { 384 | return this.map(function() { 385 | if (typeof this[property] == 'function') return this[property](); 386 | else return this[property]; 387 | }); 388 | } 389 | }; 390 | 391 | 'filter,add,not,eq,first,last,find,closest,parents,parent,children,siblings'.split(',').forEach(function(property) { 392 | var fn = $.fn[property]; 393 | $.fn[property] = function() { 394 | var ret = fn.apply(this, arguments); 395 | ret.prevObject = this; 396 | return ret; 397 | }; 398 | }); 399 | 400 | Z.prototype = $.fn; 401 | return $; 402 | })(); 403 | 404 | var $ = $ || mechanic; // expose $ shortcut 405 | -------------------------------------------------------------------------------- /vendor/evidence.js: -------------------------------------------------------------------------------- 1 | /* evidence.js, version 0.6 2 | * 3 | * Copyright (c) 2009 Tobie Langel (http://tobielangel.com) 4 | * 5 | * evidence.js is freely distributable under the terms of an MIT-style license. 6 | *--------------------------------------------------------------------------*/ 7 | 8 | (function(global) { 9 | var originalEvidence = global.Evidence, 10 | originalOnload = global.onload; 11 | 12 | function Evidence() { 13 | TestCase.extend.apply(TestCase, arguments); 14 | } 15 | 16 | function noConflict() { 17 | global.Evidence = originalEvidence; 18 | return Evidence; 19 | } 20 | 21 | Evidence.noConflict = noConflict; 22 | Evidence.VERSION = '0.6'; 23 | 24 | var FILE_REGEXP = /.*?\/(\w+\.html)(.*)/; 25 | 26 | function getNameFromFile() { 27 | return (global.location || '').toString().replace(FILE_REGEXP, '$1'); 28 | } 29 | 30 | function chain(subclass, superclass) { 31 | function Subclass() {} 32 | Subclass.prototype = superclass.prototype; 33 | subclass.prototype = new Subclass(); 34 | subclass.prototype.constructor = subclass; 35 | return subclass; 36 | } 37 | 38 | function defer(block, context) { 39 | if ('setTimeout' in global) { 40 | window.setTimeout(function() { 41 | block.call(context); 42 | }, 10); 43 | } else { 44 | block.call(context); 45 | } 46 | } 47 | function AssertionSkippedError(message) { 48 | this.message = message; 49 | } 50 | 51 | AssertionSkippedError.displayName = 'AssertionSkippedError'; 52 | 53 | (function(p) { 54 | p.name = 'AssertionSkippedError'; 55 | })(AssertionSkippedError.prototype); 56 | Evidence.AssertionSkippedError = AssertionSkippedError; 57 | function AssertionFailedError(message, template, args) { 58 | this.message = message; 59 | this.template = template || ''; 60 | this.args = args; 61 | } 62 | 63 | AssertionFailedError.displayName = 'AssertionFailedError'; 64 | 65 | (function(p) { 66 | p.name = 'AssertionFailedError'; 67 | })(AssertionFailedError.prototype); 68 | Evidence.AssertionFailedError = AssertionFailedError; 69 | function AssertionMessage(message, template, args) { 70 | this.message = message.replace(/%/g, '%%'); 71 | this.template = template || ''; 72 | this.args = args; 73 | } 74 | 75 | AssertionMessage.displayName = 'AssertionMessage'; 76 | 77 | (function(p) { 78 | function toString() { 79 | return UI.printf(this.message + this.template, this.args); 80 | } 81 | p.toString = toString; 82 | })(AssertionMessage.prototype); 83 | Evidence.AssertionMessage = AssertionMessage; 84 | 85 | var Assertions = (function() { 86 | function _assertExpression(expression, message, template) { 87 | /*for (var i=0; i < 100000; i++) { 88 | (function(){})() 89 | }*/ 90 | if (expression) { 91 | this.addAssertion(); 92 | } else { 93 | var args = Array.prototype.slice.call(arguments, 3); 94 | throw new AssertionFailedError(message, template, args); 95 | } 96 | } 97 | 98 | function skip(message) { 99 | throw new AssertionSkippedError(message || 'Skipped!'); 100 | } 101 | 102 | function fail(message) { 103 | this._assertExpression(false, message || 'Flunked!'); 104 | } 105 | 106 | function assert(test, message) { 107 | this._assertExpression( 108 | !!test, 109 | message || 'Failed assertion.', 110 | 'Expected %o to evaluate to true.', test 111 | ); 112 | } 113 | 114 | function refute(test, message) { 115 | this._assertExpression( 116 | !test, 117 | message || 'Failed refutation.', 118 | 'Expected %o to evaluate to false.', test 119 | ); 120 | } 121 | 122 | function assertTrue(test, message) { 123 | this._assertExpression( 124 | (test === true), 125 | message || 'Failed assertion.', 126 | 'Expected %o to be true.', test 127 | ); 128 | } 129 | 130 | function refuteTrue(test, message) { 131 | this._assertExpression( 132 | (test !== true), 133 | message || 'Failed refutation.', 134 | 'Expected %o to not be true.', test 135 | ); 136 | } 137 | 138 | function assertNull(test, message) { 139 | this._assertExpression( 140 | (test === null), 141 | message || 'Failed assertion.', 142 | 'Expected %o to be null.', test 143 | ); 144 | } 145 | 146 | function refuteNull(test, message) { 147 | this._assertExpression( 148 | (test !== null), 149 | message || 'Failed refutation.', 150 | 'Expected %o to not be null.', test 151 | ); 152 | } 153 | 154 | function assertUndefined(test, message) { 155 | this._assertExpression( 156 | (typeof test === 'undefined'), 157 | message || 'Failed assertion.', 158 | 'Expected %o to be undefined.', test 159 | ); 160 | } 161 | 162 | function refuteUndefined(test, message) { 163 | this._assertExpression( 164 | (typeof test !== 'undefined'), 165 | message || 'Failed refutation.', 166 | 'Expected %o to not be undefined.', test 167 | ); 168 | } 169 | 170 | function assertFalse(test, message) { 171 | this._assertExpression( 172 | (test === false), 173 | message || 'Failed assertion.', 174 | 'Expected %o to be false.', test 175 | ); 176 | } 177 | 178 | function refuteFalse(test, message) { 179 | this._assertExpression( 180 | (test !== false), 181 | message || 'Failed refutation.', 182 | 'Expected %o to not be false.', test 183 | ); 184 | } 185 | 186 | function assertEqual(expected, actual, message) { 187 | this._assertExpression( 188 | (expected == actual), 189 | message || 'Failed assertion.', 190 | 'Expected %o to be == to %o.', actual, expected 191 | ); 192 | } 193 | 194 | function refuteEqual(expected, actual, message) { 195 | this._assertExpression( 196 | (expected != actual), 197 | message || 'Failed refutation.', 198 | 'Expected %o to be != to %o.', actual, expected 199 | ); 200 | } 201 | 202 | function assertIdentical(expected, actual, message) { 203 | this._assertExpression( 204 | (expected === actual), 205 | message || 'Failed assertion.', 206 | 'Expected %o to be === to %o.', actual, expected 207 | ); 208 | } 209 | 210 | function refuteIdentical(expected, actual, message) { 211 | this._assertExpression( 212 | (expected !== actual), 213 | message || 'Failed refutation.', 214 | 'Expected %o to be !== to %o.', actual, expected 215 | ); 216 | } 217 | 218 | function assertIn(property, object, message) { 219 | this._assertExpression( 220 | (property in object), 221 | message || 'Failed assertion.', 222 | 'Expected "%s" to be a property of %o.', property, object 223 | ); 224 | } 225 | 226 | function refuteIn(property, object, message) { 227 | this._assertExpression( 228 | !(property in object), 229 | message || 'Failed refutation.', 230 | 'Expected "%s" to not be a property of %o.', property, object 231 | ); 232 | } 233 | 234 | return { 235 | _assertExpression: _assertExpression, 236 | skip: skip, 237 | assert: assert, 238 | refute: refute, 239 | assertNot: refute, 240 | assertTrue: assertTrue, 241 | assertNull: assertNull, 242 | assertUndefined: assertUndefined, 243 | assertFalse: assertFalse, 244 | assertIdentical: assertIdentical, 245 | refuteIdentical: refuteIdentical, 246 | assertEqual: assertEqual, 247 | refuteEqual: refuteEqual, 248 | assertIn: assertIn, 249 | refuteIn: refuteIn, 250 | fail: fail, 251 | flunk: fail 252 | }; 253 | })(); 254 | Evidence.Assertions = Assertions; 255 | function TestCase(methodName) { 256 | this._methodName = methodName; 257 | this.name = methodName; 258 | } 259 | 260 | (function() { 261 | function extend(name, methods) { 262 | function TestCaseSubclass(methodName) { 263 | TestCase.call(this, methodName); 264 | } 265 | 266 | if (!methods) { 267 | methods = name; 268 | name = getNameFromFile(); 269 | } 270 | 271 | chain(TestCaseSubclass, this); 272 | TestCaseSubclass.displayName = name; 273 | TestCaseSubclass.extend = extend; 274 | 275 | for(var prop in methods) { 276 | TestCaseSubclass.prototype[prop] = methods[prop]; 277 | } 278 | TestCase.subclasses.push(TestCaseSubclass); 279 | return TestCaseSubclass; 280 | } 281 | 282 | function AssertionsMixin() {} 283 | AssertionsMixin.prototype = Assertions; 284 | TestCase.prototype = new AssertionsMixin(); 285 | TestCase.constructor = TestCase; 286 | 287 | TestCase.displayName = 'TestCase'; 288 | TestCase.extend = extend; 289 | TestCase.subclasses = []; 290 | TestCase.defaultTimeout = 10000; 291 | })(); 292 | 293 | (function(p) { 294 | function run(result) { 295 | if (result) { this._result = result; } 296 | try { 297 | if (this._nextAssertions) { 298 | this._result.restartTest(this); 299 | this._nextAssertions(this); 300 | } else { 301 | /*this._globalProperties = objectKeys(global);*/ 302 | this._result.startTest(this); 303 | this.setUp(this); 304 | this[this._methodName](this); 305 | } 306 | } catch(e) { 307 | this._filterException(e); 308 | } finally { 309 | if (this._paused) { 310 | this._result.pauseTest(this); 311 | } else { 312 | try { 313 | this.tearDown(this); 314 | } catch(e) { 315 | this._filterException(e); 316 | } finally { 317 | this._nextAssertions = null; 318 | this._result.stopTest(this); 319 | defer(function() { 320 | this.parent.next(); 321 | }, this); 322 | } 323 | } 324 | } 325 | } 326 | 327 | function _filterException(e) { 328 | var name = e.name; 329 | switch(name) { 330 | case 'AssertionFailedError': 331 | this._result.addFailure(this, e); 332 | break; 333 | case 'AssertionSkippedError': 334 | this._result.addSkip(this, e); 335 | break; 336 | default: 337 | this._result.addError(this, e); 338 | } 339 | } 340 | 341 | function pause(assertions) { 342 | this._paused = true; 343 | var self = this; 344 | if (assertions) { this._nextAssertions = assertions; } 345 | self._timeoutId = global.setTimeout(function() { 346 | self.resume(function() { 347 | self.fail('Test timed out. Testing was not resumed after being paused.'); 348 | }); 349 | }, TestCase.defaultTimeout); 350 | } 351 | 352 | function resume(assertions) { 353 | if (this._paused) { // avoid race conditions 354 | this._paused = false; 355 | global.clearTimeout(this._timeoutId); 356 | if (assertions) { this._nextAssertions = assertions; } 357 | this.run(); 358 | } 359 | } 360 | 361 | function size() { 362 | return 1; 363 | } 364 | 365 | function toString() { 366 | return this.constructor.displayName + '#' + this.name; 367 | } 368 | 369 | function addAssertion() { 370 | this._result.addAssertion(); 371 | } 372 | 373 | p.run = run; 374 | p.addAssertion = addAssertion; 375 | p._filterException = _filterException; 376 | p.pause = pause; 377 | p.resume = resume; 378 | p.size = size; 379 | p.toString = toString; 380 | p.setUp = function() {}; 381 | p.tearDown = function() {}; 382 | })(TestCase.prototype); 383 | Evidence.TestCase = TestCase; 384 | function TestSuite(name, tests) { 385 | this.name = name; 386 | this._tests = []; 387 | if (tests) { 388 | this.push.apply(this, tests); 389 | } 390 | } 391 | 392 | TestSuite.displayName = 'TestSuite'; 393 | 394 | (function(p) { 395 | function run(result) { 396 | this._index = 0; 397 | this._result = result; 398 | result.startSuite(this); 399 | this.next(); 400 | return result; 401 | } 402 | 403 | function next() { 404 | var next = this._tests[this._index]; 405 | if (next) { 406 | this._index++; 407 | next.run(this._result); 408 | } else { 409 | this._result.stopSuite(this); 410 | if (this.parent) { 411 | this.parent.next(); 412 | } else { 413 | this._result.stop(new Date()); 414 | } 415 | } 416 | } 417 | 418 | function push() { 419 | for (var i = 0, length = arguments.length; i < length; i++) { 420 | var test = arguments[i]; 421 | test.parent = this; 422 | this._tests.push(test); 423 | } 424 | } 425 | 426 | function addTest(test) { 427 | test.parent = this; 428 | this._tests.push(test); 429 | } 430 | 431 | function addTests(tests) { 432 | for (var i = 0, length = tests.length; i < length; i++) { 433 | this.addTest(tests[i]); 434 | } 435 | } 436 | 437 | function size() { 438 | var tests = this._tests, 439 | length = tests.length, 440 | sum = 0; 441 | 442 | for (var i = 0; i < length; i++) { 443 | sum += tests[i].size(); 444 | } 445 | return sum; 446 | } 447 | 448 | function isEmpty() { 449 | return this.size() === 0; 450 | } 451 | 452 | function toString() { 453 | return this.name; 454 | } 455 | p.run = run; 456 | p.next = next; 457 | p.push = push; 458 | p.size = size; 459 | p.isEmpty = isEmpty; 460 | p.toString = toString; 461 | })(TestSuite.prototype); 462 | Evidence.TestSuite = TestSuite; 463 | function TestRunner() { 464 | } 465 | 466 | TestRunner.displayName = 'TestRunner'; 467 | 468 | (function(p) { 469 | function run(suite) { 470 | suite.parent = null; 471 | var result = this._makeResult(); 472 | result.start(new Date()); 473 | suite.run(result); 474 | return result; 475 | } 476 | 477 | function _makeResult() { 478 | return new TestResult(); 479 | } 480 | 481 | p.run = run; 482 | p._makeResult = _makeResult; 483 | })(TestRunner.prototype); 484 | Evidence.TestRunner = TestRunner; 485 | function TestLoader() { 486 | } 487 | 488 | TestLoader.displayName = 'TestLoader'; 489 | 490 | (function(p) { 491 | function loadTestsFromTestCase(testcaseClass) { 492 | var suite = new TestSuite(testcaseClass.displayName), 493 | props = this.getTestCaseNames(testcaseClass); 494 | for (var i=0; i < props.length; i++) { 495 | suite.push(new testcaseClass(props[i])); 496 | } 497 | return suite; 498 | } 499 | 500 | function loadTestsFromTestCases(testcases) { 501 | var suite = new TestSuite(getNameFromFile()); 502 | for (var i = 0; i < testcases.length; i++) { 503 | var testcase = testcases[i]; 504 | var subSuite = defaultLoader.loadTestsFromTestCase(testcase); 505 | if (!subSuite.isEmpty()) { suite.push(subSuite); } 506 | } 507 | return suite; 508 | } 509 | 510 | function getTestCaseNames(testcaseClass) { 511 | var results = [], 512 | proto = testcaseClass.prototype, 513 | prefix = this.testMethodPrefix; 514 | 515 | for (var property in proto) { 516 | if (property.indexOf(prefix) === 0) { 517 | results.push(property); 518 | } 519 | } 520 | return results.sort(); 521 | } 522 | 523 | function loadRegisteredTestCases() { 524 | return loadTestsFromTestCases(TestCase.subclasses); 525 | } 526 | 527 | p.loadTestsFromTestCase = loadTestsFromTestCase; 528 | p.loadRegisteredTestCases = loadRegisteredTestCases; 529 | p.loadTestsFromTestCases = loadTestsFromTestCases; 530 | p.testMethodPrefix = 'test'; 531 | p.getTestCaseNames = getTestCaseNames; 532 | 533 | })(TestLoader.prototype); 534 | Evidence.TestLoader = TestLoader; 535 | function AutoRunner() { 536 | if (global.console && global.console.log) { 537 | this.logger = Logger; 538 | } else if (Object.prototype.toString.call(global.environment) === '[object Environment]' && global.print) { 539 | this.logger = CommandLineLogger; 540 | } else { 541 | this.logger = PopupLogger; 542 | } 543 | this.autoRun = true; 544 | this.verbosity = Logger.INFO; 545 | this.runner = ConsoleTestRunner; 546 | } 547 | 548 | (function() { 549 | function run(options) { 550 | var autoRunner = new this(); 551 | options = options || autoRunner.retrieveOptions(); 552 | autoRunner.processOptions(options); 553 | if (autoRunner.autoRun) { autoRunner.run() }; 554 | } 555 | 556 | AutoRunner.run = run; 557 | AutoRunner.displayName = 'AutoRunner'; 558 | AutoRunner.LOGGERS = { 559 | console: Logger, 560 | popup: PopupLogger, 561 | command_line: CommandLineLogger 562 | }; 563 | 564 | AutoRunner.RUNNERS = { 565 | console: ConsoleTestRunner 566 | }; 567 | })(); 568 | 569 | (function(p) { 570 | function run() { 571 | var logger = new this.logger(this.verbosity), 572 | runner = new this.runner(logger), 573 | suite = defaultLoader.loadRegisteredTestCases(); 574 | if (suite._tests.length <= 1) { 575 | suite = suite._tests[0]; 576 | } 577 | return runner.run(suite); 578 | } 579 | 580 | function processQueryString(str) { 581 | var results = {}; 582 | str = (str + '').match(/^(?:[^?#]*\?)([^#]+?)(?:#.*)?$/); 583 | str = str && str[1]; 584 | 585 | if (!str) { return results; } 586 | 587 | var pairs = str.split('&'), 588 | length = pairs.length; 589 | if (!length) { return results; } 590 | 591 | for (var i = 0; i < length; i++) { 592 | var pair = pairs[i].split('='), 593 | key = decodeURIComponent(pair[0]), 594 | value = pair[1]; 595 | value = value ? decodeURIComponent(value) : true; 596 | results[key] = value; 597 | } 598 | return results; 599 | } 600 | 601 | function processArguments(args) { // RHINO 602 | var results = {}; 603 | 604 | for (var i = 0; i < args.length; i++) { 605 | var arg = args[i]; 606 | if (arg.indexOf('-') === 0) { 607 | var value = args[i + 1]; 608 | if (value && value.indexOf('-') !== 0) { 609 | i++; 610 | } else { 611 | value = true; 612 | } 613 | results[arg.substr(1)] = value; 614 | } 615 | } 616 | return results; 617 | } 618 | 619 | function retrieveOptions() { 620 | if (global.location) { 621 | return this.processQueryString(global.location); 622 | } 623 | if (global.arguments) { 624 | return this.processArguments(global.arguments); 625 | } 626 | return {}; 627 | } 628 | 629 | function processOptions(options) { 630 | for(var key in options) { 631 | var value = options[key]; 632 | switch(key) { 633 | case 'timeout': 634 | TestCase.defaultTimeout = global.parseFloat(value) * 1000; 635 | break; 636 | case 'run': 637 | this.autoRun = value === 'false' ? false : true; 638 | break; 639 | case 'logger': 640 | this.logger = AutoRunner.LOGGERS[value]; 641 | break; 642 | case 'verbosity': 643 | var i = global.parseInt(value); 644 | this.verbosity = global.isNaN(i) ? Logger[value] : i; 645 | break; 646 | case 'runner': 647 | this.runner = AutoRunner.RUNNERS[value]; 648 | break; 649 | } 650 | } 651 | } 652 | 653 | p.run = run; 654 | p.processQueryString = processQueryString; 655 | p.processArguments = processArguments; 656 | p.retrieveOptions = retrieveOptions; 657 | p.processOptions = processOptions; 658 | })(AutoRunner.prototype); 659 | Evidence.AutoRunner = AutoRunner; 660 | function TestResult() { 661 | this.testCount = 0; 662 | this.assertionCount = 0; 663 | this.skipCount = 0; 664 | this.skips = []; 665 | this.failureCount = 0; 666 | this.failures = []; 667 | this.errors = []; 668 | this.errorCount = 0; 669 | this.testCount = 0; 670 | } 671 | 672 | TestResult.displayName = 'TestResult'; 673 | 674 | (function(p) { 675 | function addAssertion() { 676 | this.assertionCount++; 677 | } 678 | 679 | function addSkip(testcase, reason) { 680 | this.skipCount++; 681 | this.skips.push(reason); 682 | } 683 | 684 | function addFailure(testcase, reason) { 685 | this.failureCount++; 686 | this.failures.push(reason); 687 | } 688 | 689 | function addError(testcase, error) { 690 | this.errorCount++; 691 | this.errors.push(error); 692 | } 693 | 694 | function startTest(testcase) { 695 | this.testCount++; 696 | } 697 | 698 | function stopTest(testcase) {} 699 | 700 | function pauseTest(testcase) {} 701 | 702 | function restartTest(testcase) {} 703 | 704 | function startSuite(suite) {} 705 | 706 | function stopSuite(suite) {} 707 | 708 | function start(t0) { 709 | this.t0 = t0; 710 | } 711 | 712 | function stop(t1) { 713 | this.t1 = t1; 714 | } 715 | 716 | function toString() { 717 | return this.testCount + ' tests, ' + 718 | this.assertionCount + ' assertions, ' + 719 | this.failureCount + ' failures, ' + 720 | this.errorCount + ' errors, ' + 721 | this.skipCount + ' skips'; 722 | } 723 | 724 | p.addAssertion = addAssertion; 725 | p.addSkip = addSkip; 726 | p.addFailure = addFailure; 727 | p.addError = addError; 728 | p.startTest = startTest; 729 | p.stopTest = stopTest; 730 | p.pauseTest = pauseTest; 731 | p.restartTest = restartTest; 732 | p.startSuite = startSuite; 733 | p.stopSuite = stopSuite; 734 | p.start = start; 735 | p.stop = stop; 736 | p.toString = toString; 737 | })(TestResult.prototype); 738 | Evidence.TestResult = TestResult; 739 | var Console = {}; 740 | 741 | function Logger(level) { 742 | if (typeof level !== 'undefined') { 743 | this.level = level; 744 | } 745 | } 746 | 747 | Logger.displayName = 'Logger'; 748 | Logger.LEVELS = ['NOTSET', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL']; 749 | Logger.CRITICAL = 5; 750 | Logger.ERROR = 4; 751 | Logger.WARN = 3; 752 | Logger.INFO = 2; 753 | Logger.DEBUG = 1; 754 | Logger.NOTSET = 0; 755 | 756 | (function(p) { 757 | function critical(template, params) { 758 | this.log(Logger.CRITICAL, template, params); 759 | } 760 | 761 | function error(template, params) { 762 | this.log(Logger.ERROR, template, params); 763 | } 764 | 765 | function warn(template, params) { 766 | this.log(Logger.WARN, template, params); 767 | } 768 | 769 | function info(template, params) { 770 | this.log(Logger.INFO, template, params); 771 | } 772 | 773 | function debug(template, params) { 774 | this.log(Logger.DEBUG, template, params); 775 | } 776 | 777 | function log(level, template, params) { 778 | level = level || Logger.NOTSET; 779 | var c = global.console; 780 | 781 | var method = Logger.LEVELS[level].toLowerCase(); 782 | if (method === 'critical') { method = 'error'; } 783 | method = (method in c) ? method : 'log'; 784 | 785 | if (level >= this.level) { 786 | if (params) { 787 | params = params.slice(0); 788 | params.unshift(template); 789 | c[method].apply(c, params); 790 | } else { 791 | c[method](template); 792 | } 793 | } 794 | } 795 | 796 | p.log = log; 797 | p.critical = critical; 798 | p.error = error; 799 | p.warn = warn; 800 | p.info = info; 801 | p.debug = debug; 802 | p.level = 0; 803 | })(Logger.prototype); 804 | Console.Logger = Logger; 805 | function PopupLogger(level) { 806 | Logger.call(this, level); 807 | } 808 | 809 | chain(PopupLogger, Logger); 810 | PopupLogger.displayName = 'PopupLogger'; 811 | 812 | (function(p) { 813 | var BASIC_STYLES = 'color: #333; background-color: #fff; font-family: monospace; border-bottom: 1px solid #ccc;'; 814 | var STYLES = { 815 | WARN: 'color: #000; background-color: #fc6;', 816 | ERROR: 'color: #f00; background-color: #fcc;', 817 | CRITICAL: 'color: #fff; background-color: #000;' 818 | }; 819 | 820 | function _cleanup(html) { 821 | return html.replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&').replace(/[\n\r]+/, '
'); 822 | } 823 | 824 | function _makePopup() { 825 | var popup = global.open('','popup','height=400,width=400'); 826 | var doc = popup.document; 827 | doc.write('\ 828 | \ 829 | \ 830 | \ 831 | Console\ 832 | \ 833 |
\ 834 | '); 835 | doc.close(); 836 | popup.focus(); 837 | return popup; 838 | } 839 | 840 | function _appendLine(level, msg) { 841 | this.popup = this.popup || this._makePopup(); 842 | var levelName = Logger.LEVELS[level]; 843 | 844 | var html = '
'; 848 | if (level > Logger.INFO) { 849 | html += ''; 850 | html += levelName; 851 | html += ': '; 852 | } 853 | html += _cleanup(msg); 854 | html += '
'; 855 | var doc = this.popup.document, 856 | div = doc.createElement('div'); 857 | div.innerHTML = html; 858 | html = div.firstChild; 859 | div = null; 860 | doc.getElementById('evidence_console').appendChild(html); 861 | } 862 | 863 | function log(level, msg, params) { 864 | level = level || Logger.NOTSET; 865 | if (level >= this.level) { 866 | if (params) { 867 | msg = UI.printf(msg, params); 868 | } 869 | this._appendLine(level, msg); 870 | } 871 | } 872 | 873 | p.log = log; 874 | p._makePopup = _makePopup; 875 | p._appendLine = _appendLine; 876 | })(PopupLogger.prototype); 877 | Console.PopupLogger = PopupLogger; 878 | function CommandLineLogger(level) { 879 | Logger.call(this, level); 880 | } 881 | 882 | chain(CommandLineLogger, Logger); 883 | CommandLineLogger.displayName = 'CommandLineLogger'; 884 | 885 | (function(p) { 886 | 887 | function log(level, msg, params) { 888 | level = level || Logger.NOTSET; 889 | if (level >= this.level) { 890 | var prefix = ''; 891 | if (level > Logger.INFO) { 892 | prefix = Logger.LEVELS[level]+ ': '; 893 | } 894 | if (params) { 895 | msg = UI.printf(msg, params); 896 | } 897 | global.print(prefix + msg); 898 | } 899 | } 900 | 901 | p.log = log; 902 | })(CommandLineLogger.prototype); 903 | Console.CommandLineLogger = CommandLineLogger; 904 | function ConsoleTestRunner(logger) { 905 | TestRunner.call(this); 906 | this.logger = logger; 907 | } 908 | 909 | chain(ConsoleTestRunner, TestRunner); 910 | ConsoleTestRunner.displayName = 'ConsoleTestRunner'; 911 | 912 | (function(p) { 913 | function _makeResult() { 914 | return new ConsoleTestResult(this.logger); 915 | } 916 | 917 | p._makeResult = _makeResult; 918 | })(ConsoleTestRunner.prototype); 919 | Console.TestRunner = ConsoleTestRunner; 920 | function ConsoleTestResult(logger) { 921 | TestResult.call(this); 922 | this.logger = logger; 923 | } 924 | 925 | chain(ConsoleTestResult, TestResult); 926 | ConsoleTestResult.displayName = 'ConsoleTestResult'; 927 | 928 | (function(p) { 929 | var _super = TestResult.prototype; 930 | 931 | function addAssertion() { 932 | this.assertionCount++; 933 | } 934 | 935 | function addSkip(testcase, msg) { 936 | _super.addSkip.call(this, testcase, msg); 937 | this.logger.warn('Skipping testcase ' + testcase + ': ' + msg.message); 938 | } 939 | 940 | function addFailure(testcase, msg) { 941 | _super.addFailure.call(this, testcase, msg); 942 | this.logger.error(testcase + ': ' + msg.message + ' ' + msg.template, msg.args); 943 | } 944 | 945 | function addError(testcase, error) { 946 | _super.addError.call(this, testcase, error); 947 | this.logger.error(testcase + ' threw an error. ' + error); 948 | } 949 | 950 | function startTest(testcase) { 951 | _super.startTest.call(this, testcase); 952 | this.logger.debug('Started testcase ' + testcase + '.'); 953 | } 954 | 955 | function stopTest(testcase) { 956 | this.logger.debug('Completed testcase ' + testcase + '.'); 957 | } 958 | 959 | function pauseTest(testcase) { 960 | this.logger.info('Paused testcase ' + testcase + '.'); 961 | } 962 | 963 | function restartTest(testcase) { 964 | this.logger.info('Restarted testcase ' + testcase + '.'); 965 | } 966 | 967 | function startSuite(suite) { 968 | this.logger.info('Started suite ' + suite + '.'); 969 | } 970 | 971 | function stopSuite(suite) { 972 | this.logger.info('Completed suite ' + suite + '.'); 973 | } 974 | 975 | function start(t0) { 976 | _super.start.call(this, t0); 977 | this.logger.info('Started tests.'); 978 | } 979 | 980 | function stop(t1) { 981 | _super.stop.call(this, t1); 982 | this.logger.info('Completed tests in ' + ((t1 - this.t0)/1000) + 's.'); 983 | this.logger.info(this.toString() + '.'); 984 | } 985 | 986 | p.addAssertion = addAssertion; 987 | p.addSkip = addSkip; 988 | p.addFailure = addFailure; 989 | p.addError = addError; 990 | p.startTest = startTest; 991 | p.stopTest = stopTest; 992 | p.pauseTest = pauseTest; 993 | p.restartTest = restartTest; 994 | p.startSuite = startSuite; 995 | p.stopSuite = stopSuite; 996 | p.start = start; 997 | p.stop = stop; 998 | })(ConsoleTestResult.prototype); 999 | 1000 | 1001 | Console.TestResult = ConsoleTestResult; 1002 | var UI = (function() { 1003 | function printf(template, args, inspector) { 1004 | var parts = [], m, 1005 | regexp = /(^%|.%)([a-zA-Z])/, 1006 | args = args.splice(0); // clone args 1007 | 1008 | inspector = inspector || String; 1009 | 1010 | if (template.length <= 0) { 1011 | return ''; 1012 | } 1013 | while (m = regexp.exec(template)) { 1014 | var match = m[0], index = m.index, type, arg; 1015 | 1016 | if (match.indexOf('%%') === 0) { 1017 | parts.push(template.substr(0, index)); 1018 | parts.push(match.substr(1)); 1019 | } else { 1020 | parts.push(template.substr(0, match.indexOf('%' === 0) ? index + 1 : index)); 1021 | type = m[2]; 1022 | arg = args.shift(); 1023 | arg = inspector(arg, type); 1024 | parts.push(arg); 1025 | } 1026 | template = template.substr(index + match.length); 1027 | } 1028 | parts.push(template); 1029 | return parts.join(''); 1030 | } 1031 | 1032 | return { 1033 | printf: printf, 1034 | Console: Console 1035 | }; 1036 | })(); 1037 | Evidence.UI = UI; 1038 | 1039 | var defaultLoader = new TestLoader(); 1040 | Evidence.defaultLoader = defaultLoader; 1041 | 1042 | global.Evidence = Evidence; 1043 | 1044 | if (global.location) { 1045 | global.onload = function() { 1046 | if (typeof originalOnload === 'function') { 1047 | originalOnload.call(global); 1048 | } 1049 | AutoRunner.run(); 1050 | }; 1051 | } else if (global.arguments) { 1052 | var runtime = java.lang.Runtime.getRuntime(); 1053 | var thread = new java.lang.Thread(function() { 1054 | AutoRunner.run(); 1055 | }); 1056 | runtime.addShutdownHook(thread); 1057 | } 1058 | 1059 | })(this); 1060 | -------------------------------------------------------------------------------- /spec/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 | --------------------------------------------------------------------------------