├── app ├── templates │ └── setup.html ├── images │ ├── agroups-logo.png │ └── github-circled-alt.svg ├── hello_world │ ├── hello_universe.js │ ├── hello_world.js │ ├── hello_universe.spec.js │ └── hello_world.spec.js ├── spec.js ├── app.js ├── controllers │ ├── project.js │ ├── dashboard.js │ ├── setupLoader.js │ └── setup.js ├── spec.html ├── lib │ ├── electron_boilerplate │ │ ├── env_config.js │ │ ├── dev_helper.js │ │ ├── context_menu.js │ │ ├── window_state.js │ │ └── external_links.js │ └── jasmine │ │ ├── boot.js │ │ ├── jasmine-html.js │ │ ├── jasmine.css │ │ └── jasmine.js ├── package.json ├── stylesheets │ └── main.less ├── background.js └── app.html ├── .bowerrc ├── .travis.yml ├── gulpfile.js ├── resources ├── icon.png ├── icon.psd ├── osx │ ├── icon.icns │ ├── dmg-icon.icns │ ├── dmg-background.png │ ├── dmg-background.psd │ ├── dmg-background@2x.png │ ├── dmg-background@2x.psd │ ├── appdmg.json │ ├── helper_apps │ │ ├── Info.plist │ │ ├── Info EH.plist │ │ └── Info NP.plist │ └── Info.plist ├── windows │ ├── icon.ico │ ├── setup-icon.ico │ ├── setup-banner.bmp │ ├── setup-banner.psd │ └── installer.nsi ├── agroups-io-printscreen.png └── linux │ ├── DEBIAN │ └── control │ └── app.desktop ├── .gitignore ├── config ├── env_test.json ├── env_development.json └── env_production.json ├── tasks ├── release.js ├── generate_specs_import.js ├── utils.js ├── start.js ├── app_npm_install.js ├── release_linux.js ├── release_windows.js ├── build.js └── release_osx.js ├── bower.json ├── package.json ├── LICENSE ├── README.md └── .jshintrc /app/templates/setup.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/vendor" 3 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./tasks/build'); 4 | require('./tasks/release'); 5 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/icon.png -------------------------------------------------------------------------------- /resources/icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/icon.psd -------------------------------------------------------------------------------- /resources/osx/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/osx/icon.icns -------------------------------------------------------------------------------- /app/images/agroups-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/app/images/agroups-logo.png -------------------------------------------------------------------------------- /resources/osx/dmg-icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/osx/dmg-icon.icns -------------------------------------------------------------------------------- /resources/windows/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/windows/icon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | Thumbs.db 5 | /build/ 6 | /releases/ 7 | /tmp/ 8 | /.idea/ 9 | /app/vendor -------------------------------------------------------------------------------- /config/env_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "description": "Add here any environment specific stuff you like." 4 | } 5 | -------------------------------------------------------------------------------- /resources/osx/dmg-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/osx/dmg-background.png -------------------------------------------------------------------------------- /resources/osx/dmg-background.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/osx/dmg-background.psd -------------------------------------------------------------------------------- /resources/windows/setup-icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/windows/setup-icon.ico -------------------------------------------------------------------------------- /resources/osx/dmg-background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/osx/dmg-background@2x.png -------------------------------------------------------------------------------- /resources/osx/dmg-background@2x.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/osx/dmg-background@2x.psd -------------------------------------------------------------------------------- /resources/windows/setup-banner.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/windows/setup-banner.bmp -------------------------------------------------------------------------------- /resources/windows/setup-banner.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/windows/setup-banner.psd -------------------------------------------------------------------------------- /app/hello_world/hello_universe.js: -------------------------------------------------------------------------------- 1 | var greet = function () { 2 | return 'Hello Universe!'; 3 | }; 4 | 5 | export default greet; 6 | -------------------------------------------------------------------------------- /resources/agroups-io-printscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgroupsIO/AgroupsIO-Desktop/HEAD/resources/agroups-io-printscreen.png -------------------------------------------------------------------------------- /config/env_development.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "development", 3 | "description": "Add here any environment specific stuff you like." 4 | } 5 | -------------------------------------------------------------------------------- /config/env_production.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "production", 3 | "description": "Add here any environment specific stuff you like." 4 | } 5 | -------------------------------------------------------------------------------- /app/hello_world/hello_world.js: -------------------------------------------------------------------------------- 1 | export var greet = function () { 2 | return 'Agroups.IO'; 3 | }; 4 | 5 | export var bye = function () { 6 | return 'See ya!'; 7 | }; 8 | -------------------------------------------------------------------------------- /resources/linux/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: {{name}} 2 | Version: {{version}} 3 | Maintainer: {{author}} 4 | Priority: optional 5 | Architecture: amd64 6 | Installed-Size: {{size}} 7 | Description: {{description}} 8 | -------------------------------------------------------------------------------- /app/spec.js: -------------------------------------------------------------------------------- 1 | // This file is generated automatically. 2 | // All your modifications to it will be lost (so don't do it). 3 | import "./hello_world/hello_universe.spec.js"; 4 | import "./hello_world/hello_world.spec.js"; -------------------------------------------------------------------------------- /resources/linux/app.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Encoding=UTF-8 5 | Name={{productName}} 6 | Comment={{description}} 7 | Exec=/opt/{{name}}/{{name}} 8 | Icon=/opt/{{name}}/icon.png 9 | Terminal=false 10 | Categories=Application; 11 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import { setup } from './controllers/setup'; 2 | 3 | window.jQuery = window.$ = require('jquery'); 4 | window.Hammer = require('hammerjs'); 5 | 6 | $(document).ready(() => { 7 | 8 | setup.render(); 9 | !navigator.onLine ? setup.offlineChecker() : null; 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /app/controllers/project.js: -------------------------------------------------------------------------------- 1 | export var project = { 2 | projectDir: null, 3 | npm: { 4 | file: null, 5 | dependenciesShow: [], 6 | devDependenciesShow: [], 7 | optionalDependenciesShow: [] 8 | }, 9 | bower: null, 10 | composer: null 11 | }; 12 | -------------------------------------------------------------------------------- /app/hello_world/hello_universe.spec.js: -------------------------------------------------------------------------------- 1 | // Default imports test 2 | import greet from './hello_universe'; 3 | 4 | describe("hello universe", function () { 5 | 6 | it("greets better than hello world", function () { 7 | expect(greet()).toBe('Hello Universe!'); 8 | }); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /resources/osx/appdmg.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "{{productName}}", 3 | "icon": "{{dmgIcon}}", 4 | "background": "{{dmgBackground}}", 5 | "icon-size": 128, 6 | "contents": [ 7 | { "x": 410, "y": 220, "type": "link", "path": "/Applications" }, 8 | { "x": 130, "y": 220, "type": "file", "path": "{{appPath}}" } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /app/hello_world/hello_world.spec.js: -------------------------------------------------------------------------------- 1 | // Named imports test 2 | import { greet, bye } from './hello_world'; 3 | 4 | describe("hello world", function () { 5 | 6 | it("greets", function () { 7 | expect(greet()).toBe('Hello World!'); 8 | }); 9 | 10 | it("says goodbye", function () { 11 | expect(bye()).toBe('See ya!'); 12 | }); 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /tasks/release.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var utils = require('./utils'); 5 | 6 | var releaseForOs = { 7 | osx: require('./release_osx'), 8 | linux: require('./release_linux'), 9 | windows: require('./release_windows'), 10 | }; 11 | 12 | gulp.task('release', ['build'], function () { 13 | return releaseForOs[utils.os()](); 14 | }); 15 | -------------------------------------------------------------------------------- /app/spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/lib/electron_boilerplate/env_config.js: -------------------------------------------------------------------------------- 1 | // Loads config/env_XXX.json file and puts it 2 | // in proper place for given Electron context. 3 | 4 | 'use strict'; 5 | 6 | (function () { 7 | var jetpack = require('fs-jetpack'); 8 | if (typeof window === 'object') { 9 | // Web browser context, __dirname points to folder where app.html file is. 10 | window.env = jetpack.read(__dirname + '/env_config.json', 'json'); 11 | } else { 12 | // Node context 13 | module.exports = jetpack.read(__dirname + '/../../env_config.json', 'json'); 14 | } 15 | }()); 16 | -------------------------------------------------------------------------------- /resources/osx/helper_apps/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | {{identifier}}.helper 7 | CFBundleName 8 | {{productName}} Helper 9 | CFBundlePackageType 10 | APPL 11 | DTSDKName 12 | macosx 13 | LSUIElement 14 | 15 | NSSupportsAutomaticGraphicsSwitching 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agroups-io", 3 | "productName": "Agroups.IO", 4 | "identifier": "com.example.agroups-io", 5 | "description": "A open-source desktop GUI app to manage all package managers like a bower, npm and composer.", 6 | "version": "0.1.0", 7 | "author": "Jonatas Freitas ", 8 | "main": "background.js", 9 | "config": { 10 | "target": "development" 11 | }, 12 | "dependencies": { 13 | "fs-jetpack": "^0.7.0", 14 | "hammerjs": "^2.0.4", 15 | "jquery": "^2.1.4", 16 | "mustache": "^2.1.3", 17 | "npm": "^3.3.5", 18 | "q": "^1.4.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agroups-io", 3 | "version": "0.1.0", 4 | "homepage": "https://github.com/AgroupsIO/AgroupsIO-Desktop", 5 | "authors": [ 6 | "Jonatas Freitas " 7 | ], 8 | "moduleType": [ 9 | "amd", 10 | "es6", 11 | "node" 12 | ], 13 | "license": "MIT", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "app/vendor", 19 | "test", 20 | "tests" 21 | ], 22 | "dependencies": { 23 | "hammerjs": "~2.0.4", 24 | "Materialize": "materialize#~0.97.1", 25 | "mustache.js": "mustache#~2.1.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /resources/osx/helper_apps/Info EH.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | {{productName}} Helper EH 7 | CFBundleExecutable 8 | {{productName}} Helper EH 9 | CFBundleIdentifier 10 | {{identifier}}.helper.EH 11 | CFBundleName 12 | {{productName}} Helper EH 13 | CFBundlePackageType 14 | APPL 15 | DTSDKName 16 | macosx 17 | LSUIElement 18 | 19 | NSSupportsAutomaticGraphicsSwitching 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/osx/helper_apps/Info NP.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | {{productName}} Helper NP 7 | CFBundleExecutable 8 | {{productName}} Helper NP 9 | CFBundleIdentifier 10 | {{identifier}}.helper.NP 11 | CFBundleName 12 | {{productName}} Helper NP 13 | CFBundlePackageType 14 | APPL 15 | DTSDKName 16 | macosx 17 | LSUIElement 18 | 19 | NSSupportsAutomaticGraphicsSwitching 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "asar": "^0.7.2", 4 | "electron-prebuilt": "^0.32.2", 5 | "fs-jetpack": "^0.7.0", 6 | "gulp": "^3.9.0", 7 | "gulp-less": "^3.0.3", 8 | "gulp-mustache": "^1.1.2", 9 | "gulp-util": "^3.0.6", 10 | "q": "^1.4.1", 11 | "rollup": "^0.16.1", 12 | "tree-kill": "^0.1.1", 13 | "yargs": "^3.15.0" 14 | }, 15 | "optionalDependencies": { 16 | "appdmg": "^0.3.2", 17 | "rcedit": "^0.3.0" 18 | }, 19 | "scripts": { 20 | "postinstall": "node ./tasks/app_npm_install && npm install bower -g && bower install", 21 | "app-install": "node ./tasks/app_npm_install && npm install bower -g && bower install", 22 | "build": "./node_modules/.bin/gulp build", 23 | "release": "./node_modules/.bin/gulp release --env=production", 24 | "start": "node ./tasks/start", 25 | "test": "node ./tasks/start --env=test" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tasks/generate_specs_import.js: -------------------------------------------------------------------------------- 1 | // Spec files are scattered through the whole project. Here we're searching 2 | // for them and generate one entry file which will run all the tests. 3 | 4 | 'use strict'; 5 | 6 | var jetpack = require('fs-jetpack'); 7 | var srcDir = jetpack.cwd('app'); 8 | 9 | var fileName = 'spec.js'; 10 | var fileBanner = "// This file is generated automatically.\n" 11 | + "// All your modifications to it will be lost (so don't do it).\n"; 12 | var whatToInclude = [ 13 | '*.spec.js', 14 | '!node_modules/**', 15 | ]; 16 | 17 | module.exports = function () { 18 | return srcDir.findAsync('.', { matching: whatToInclude }, 'relativePath') 19 | .then(function (specPaths) { 20 | var fileContent = specPaths.map(function (path) { 21 | return 'import "' + path + '";'; 22 | }).join('\n'); 23 | return srcDir.writeAsync(fileName, fileBanner + fileContent); 24 | }) 25 | .then(function () { 26 | return srcDir.path(fileName); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /app/lib/electron_boilerplate/dev_helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = require('app'); 4 | var Menu = require('menu'); 5 | var BrowserWindow = require('browser-window'); 6 | 7 | module.exports.setDevMenu = function () { 8 | var devMenu = Menu.buildFromTemplate([{ 9 | label: 'Development', 10 | submenu: [{ 11 | label: 'Reload', 12 | accelerator: 'CmdOrCtrl+R', 13 | click: function () { 14 | BrowserWindow.getFocusedWindow().reloadIgnoringCache(); 15 | } 16 | },{ 17 | label: 'Toggle DevTools', 18 | accelerator: 'Alt+CmdOrCtrl+I', 19 | click: function () { 20 | BrowserWindow.getFocusedWindow().toggleDevTools(); 21 | } 22 | },{ 23 | label: 'Quit', 24 | accelerator: 'CmdOrCtrl+Q', 25 | click: function () { 26 | app.quit(); 27 | } 28 | }] 29 | }]); 30 | Menu.setApplicationMenu(devMenu); 31 | }; 32 | -------------------------------------------------------------------------------- /tasks/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var argv = require('yargs').argv; 4 | var os = require('os'); 5 | var jetpack = require('fs-jetpack'); 6 | 7 | module.exports.os = function () { 8 | switch (os.platform()) { 9 | case 'darwin': 10 | return 'osx'; 11 | case 'linux': 12 | return 'linux'; 13 | case 'win32': 14 | return 'windows'; 15 | } 16 | return 'unsupported'; 17 | }; 18 | 19 | module.exports.replace = function (str, patterns) { 20 | Object.keys(patterns).forEach(function (pattern) { 21 | var matcher = new RegExp('{{' + pattern + '}}', 'g'); 22 | str = str.replace(matcher, patterns[pattern]); 23 | }); 24 | return str; 25 | }; 26 | 27 | module.exports.getEnvName = function () { 28 | return argv.env || 'development'; 29 | }; 30 | 31 | module.exports.getElectronVersion = function () { 32 | var manifest = jetpack.read(__dirname + '/../package.json', 'json'); 33 | return manifest.devDependencies['electron-prebuilt'].substring(1); 34 | }; 35 | -------------------------------------------------------------------------------- /resources/osx/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | {{productName}} 7 | CFBundleExecutable 8 | {{productName}} 9 | CFBundleIconFile 10 | icon.icns 11 | CFBundleIdentifier 12 | {{identifier}} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | {{productName}} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleVersion 20 | {{version}} 21 | CFBundleGetInfoString 22 | {{version}} 23 | LSMinimumSystemVersion 24 | 10.8.0 25 | NSMainNibFile 26 | MainMenu 27 | NSPrincipalClass 28 | AtomApplication 29 | NSSupportsAutomaticGraphicsSwitching 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 AgroupsIO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /app/lib/electron_boilerplate/context_menu.js: -------------------------------------------------------------------------------- 1 | // This gives you default context menu (cut, copy, paste) 2 | // in all input fields and textareas across your app. 3 | 4 | (function () { 5 | 'use strict'; 6 | 7 | var remote = require('remote'); 8 | var Menu = remote.require('menu'); 9 | var MenuItem = remote.require('menu-item'); 10 | 11 | var cut = new MenuItem({ 12 | label: "Cut", 13 | click: function () { 14 | document.execCommand("cut"); 15 | } 16 | }); 17 | 18 | var copy = new MenuItem({ 19 | label: "Copy", 20 | click: function () { 21 | document.execCommand("copy"); 22 | } 23 | }); 24 | 25 | var paste = new MenuItem({ 26 | label: "Paste", 27 | click: function () { 28 | document.execCommand("paste"); 29 | } 30 | }); 31 | 32 | var textMenu = new Menu(); 33 | textMenu.append(cut); 34 | textMenu.append(copy); 35 | textMenu.append(paste); 36 | 37 | document.addEventListener('contextmenu', function(e) { 38 | 39 | switch (e.target.nodeName) { 40 | case 'TEXTAREA': 41 | case 'INPUT': 42 | e.preventDefault(); 43 | textMenu.popup(remote.getCurrentWindow()); 44 | break; 45 | } 46 | 47 | }, false); 48 | 49 | }()); 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### In progress, not have any release. 2 | 3 | # Agroups.io 4 | [![Build with http://gulpjs.com/](http://img.shields.io/badge/built%20with-gulp-red.svg)]() 5 | 6 | 7 | ![Agroups.IO](https://github.com/AgroupsIO/AgroupsIO-Desktop/blob/master/resources/agroups-io-printscreen.png) 8 | 9 | The open-source project Agroups.io is a desktop application for OSX, Linux and Windows developed entirely with javascript based on *[Electron](http://electron.atom.io/)*. 10 | 11 | With it you can in a graphical interface manage packages managers like npm (node.js), composer (php) and Bower (js). 12 | 13 | In the future we will also implement the functionality to manage the Grunt and Karma configuration files. 14 | 15 | ####Contribute to the Agroups.io, it's yours too. 16 | 17 | ## To run and build project: 18 | ##### Prerequisites: 19 | - *[Node.js](https://nodejs.org/download)* 20 | 21 | ##### After install prerequisites, run: 22 | - ```npm install``` to install all Node and Bower dependencies. 23 | 24 | ##### To start development environment, run: 25 | - ```npm start``` 26 | 27 | ##### To start test environment, run: 28 | - ```npm test``` 29 | 30 | ##### To build release for OSX, Win and Linux, run: 31 | - ```npm run release``` 32 | 33 | ## Documentation 34 | - *[Roadmap](https://trello.com/b/IgWdeH1j/agroups-io-roadmap)* 35 | - *[Wireframe](https://moqups.com/jonatasfreitasv@gmail.com/dOHIovDo)* 36 | 37 | ## Licence 38 | *[MIT](http://opensource.org/licenses/MIT)* 39 | -------------------------------------------------------------------------------- /app/lib/electron_boilerplate/window_state.js: -------------------------------------------------------------------------------- 1 | // Simple module to help you remember the size and position of windows. 2 | // Can be used for more than one window, just construct many 3 | // instances of it and give each different name. 4 | 5 | 'use strict'; 6 | 7 | var app = require('app'); 8 | var jetpack = require('fs-jetpack'); 9 | 10 | module.exports = function (name, defaults) { 11 | 12 | var userDataDir = jetpack.cwd(app.getPath('userData')); 13 | var stateStoreFile = 'window-state-' + name +'.json'; 14 | 15 | var state = userDataDir.read(stateStoreFile, 'json') || { 16 | width: defaults.width, 17 | height: defaults.height 18 | }; 19 | 20 | var saveState = function (win) { 21 | if (!win.isMaximized() && !win.isMinimized()) { 22 | var position = win.getPosition(); 23 | var size = win.getSize(); 24 | state.x = position[0]; 25 | state.y = position[1]; 26 | state.width = size[0]; 27 | state.height = size[1]; 28 | } 29 | state.isMaximized = win.isMaximized(); 30 | userDataDir.write(stateStoreFile, state, { atomic: true }); 31 | }; 32 | 33 | return { 34 | get x() { return state.x; }, 35 | get y() { return state.y; }, 36 | get width() { return state.width; }, 37 | get height() { return state.height; }, 38 | get isMaximized() { return state.isMaximized; }, 39 | saveState: saveState 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /app/stylesheets/main.less: -------------------------------------------------------------------------------- 1 | @import (css) "../vendor/Materialize/dist/css/materialize.min.css"; 2 | 3 | html, body { 4 | width: 100%; 5 | height: 100%; 6 | margin: 0; 7 | padding: 0; 8 | -webkit-touch-callout: none; 9 | -webkit-user-select: none; 10 | -khtml-user-select: none; 11 | -moz-user-select: none; 12 | -ms-user-select: none; 13 | user-select: none; 14 | } 15 | 16 | h1, h2, h3, h4, h5 17 | { 18 | color: #9E9E9E; 19 | } 20 | 21 | body { 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | font-family: 'Roboto', sans-serif; 26 | background-color: #263238; 27 | } 28 | 29 | a { 30 | text-decoration: none; 31 | } 32 | 33 | #target{ 34 | height: 100%; 35 | width: 100%; 36 | } 37 | 38 | #setupContainer{ 39 | margin-top: 90px; 40 | } 41 | 42 | 43 | #setupLoaderContainer { 44 | margin-top: 226px; 45 | } 46 | 47 | #logoHeader{ 48 | float: left; 49 | margin-top: 15px; 50 | height: 37px; 51 | } 52 | 53 | #titleHeader{ 54 | float: left; 55 | font-size: 26px; 56 | margin-left: 13px; 57 | cursor: pointer; 58 | } 59 | 60 | .top-nav { 61 | padding-left: 18px; 62 | } 63 | 64 | .preLoaderContainer{ 65 | position: fixed; 66 | top: 250px; 67 | left: 470px; 68 | } 69 | 70 | .top-nav{ 71 | background-color: #455a64; 72 | margin-bottom: 13px; 73 | } 74 | 75 | .dep-collection { 76 | padding-bottom: 10px; 77 | } 78 | 79 | .dep-collection li { 80 | float: left; 81 | width: 312px; 82 | margin-left: 10px !important; 83 | margin-top: 10px !important; 84 | } 85 | -------------------------------------------------------------------------------- /app/lib/electron_boilerplate/external_links.js: -------------------------------------------------------------------------------- 1 | // Convenient way for opening links in external browser, not in the app. 2 | // Useful especially if you have a lot of links to deal with. 3 | // 4 | // Usage: 5 | // 6 | // Every link with class ".js-external-link" will be opened in external browser. 7 | // google 8 | // 9 | // The same behaviour for many links can be achieved by adding 10 | // this class to any parent tag of an anchor tag. 11 | // 15 | 16 | (function () { 17 | 'use strict'; 18 | 19 | var shell = require('shell'); 20 | 21 | var supportExternalLinks = function (e) { 22 | var href; 23 | var isExternal = false; 24 | 25 | var checkDomElement = function (element) { 26 | if (element.nodeName === 'A') { 27 | href = element.getAttribute('href'); 28 | } 29 | if (element.classList.contains('js-external-link')) { 30 | isExternal = true; 31 | } 32 | if (href && isExternal) { 33 | shell.openExternal(href); 34 | e.preventDefault(); 35 | } else if (element.parentElement) { 36 | checkDomElement(element.parentElement); 37 | } 38 | } 39 | 40 | checkDomElement(e.target); 41 | } 42 | 43 | document.addEventListener('click', supportExternalLinks, false); 44 | }()); 45 | -------------------------------------------------------------------------------- /app/background.js: -------------------------------------------------------------------------------- 1 | // This is main process of Electron, started as first thing when the Electron 2 | // app starts, and running through entire life of your application. 3 | // It doesn't have any windows which you can see on screen, but we can open 4 | // window from here. 5 | 6 | var app = require('app'); 7 | var BrowserWindow = require('browser-window'); 8 | var env = require('./lib/electron_boilerplate/env_config'); 9 | var devHelper = require('./lib/electron_boilerplate/dev_helper'); 10 | var windowStateKeeper = require('./lib/electron_boilerplate/window_state'); 11 | var dialog = require('dialog'); 12 | 13 | var mainWindow; 14 | 15 | // Preserver of the window size and position between app launches. 16 | var mainWindowState = windowStateKeeper('main', { 17 | width: 1000, 18 | height: 600 19 | }); 20 | 21 | app.on('ready', function () { 22 | 23 | mainWindow = new BrowserWindow({ 24 | width: mainWindowState.width, 25 | height: mainWindowState.height, 26 | center: true, 27 | resizable: false, 28 | fullscreen: false 29 | }); 30 | 31 | /* 32 | if (mainWindowState.isMaximized) { 33 | mainWindow.maximize(); 34 | } 35 | */ 36 | 37 | if (env.name === 'test') { 38 | mainWindow.loadUrl('file://' + __dirname + '/spec.html'); 39 | } else { 40 | mainWindow.loadUrl('file://' + __dirname + '/app.html'); 41 | } 42 | 43 | if (env.name !== 'production') { 44 | devHelper.setDevMenu(); 45 | //mainWindow.openDevTools(); 46 | } 47 | 48 | mainWindow.on('close', function () { 49 | mainWindowState.saveState(mainWindow); 50 | }); 51 | }); 52 | 53 | app.on('window-all-closed', function () { 54 | app.quit(); 55 | }); 56 | -------------------------------------------------------------------------------- /tasks/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Q = require('q'); 4 | var electron = require('electron-prebuilt'); 5 | var pathUtil = require('path'); 6 | var childProcess = require('child_process'); 7 | var kill = require('tree-kill'); 8 | var utils = require('./utils'); 9 | var watch; 10 | 11 | var gulpPath = pathUtil.resolve('./node_modules/.bin/gulp'); 12 | if (process.platform === 'win32') { 13 | gulpPath += '.cmd'; 14 | } 15 | 16 | var runBuild = function () { 17 | var deferred = Q.defer(); 18 | 19 | var build = childProcess.spawn(gulpPath, [ 20 | 'build', 21 | '--env=' + utils.getEnvName(), 22 | '--color' 23 | ], { 24 | stdio: 'inherit' 25 | }); 26 | 27 | build.on('close', function (code) { 28 | deferred.resolve(); 29 | }); 30 | 31 | return deferred.promise; 32 | }; 33 | 34 | var runGulpWatch = function () { 35 | watch = childProcess.spawn(gulpPath, [ 36 | 'watch', 37 | '--env=' + utils.getEnvName(), 38 | '--color' 39 | ], { 40 | stdio: 'inherit' 41 | }); 42 | 43 | watch.on('close', function (code) { 44 | // Gulp watch exits when error occured during build. 45 | // Just respawn it then. 46 | runGulpWatch(); 47 | }); 48 | }; 49 | 50 | var runApp = function () { 51 | var app = childProcess.spawn(electron, ['./build'], { 52 | stdio: 'inherit' 53 | }); 54 | 55 | app.on('close', function (code) { 56 | // User closed the app. Kill the host process. 57 | kill(watch.pid, 'SIGKILL', function () { 58 | process.exit(); 59 | }); 60 | }); 61 | }; 62 | 63 | runBuild() 64 | .then(function () { 65 | runGulpWatch(); 66 | runApp(); 67 | }); -------------------------------------------------------------------------------- /app/images/github-circled-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tasks/app_npm_install.js: -------------------------------------------------------------------------------- 1 | // This script allows you to install native modules (those which have 2 | // to be compiled) for your Electron app. 3 | // The problem is that 'npm install' compiles them against node.js you have 4 | // installed on your computer, NOT against node.js used in Electron 5 | // runtime we've downloaded to this project. 6 | 7 | 'use strict'; 8 | 9 | var childProcess = require('child_process'); 10 | var jetpack = require('fs-jetpack'); 11 | var argv = require('yargs').argv; 12 | 13 | var utils = require('./utils'); 14 | 15 | var electronVersion = utils.getElectronVersion(); 16 | 17 | var nodeModulesDir = jetpack.cwd(__dirname + '/../app/node_modules') 18 | var dependenciesCompiledAgainst = nodeModulesDir.read('electron_version'); 19 | 20 | // When you raised version of Electron used in your project, the safest 21 | // thing to do is remove all installed dependencies and install them 22 | // once again (so they compile against new version if you use any 23 | // native package). 24 | if (electronVersion !== dependenciesCompiledAgainst) { 25 | nodeModulesDir.dir('.', { empty: true }); 26 | nodeModulesDir.write('electron_version', electronVersion); 27 | } 28 | 29 | // Tell the 'npm install' which is about to start that we want for it 30 | // to compile for Electron. 31 | process.env.npm_config_disturl = "https://atom.io/download/atom-shell"; 32 | process.env.npm_config_target = electronVersion; 33 | 34 | var params = ['install']; 35 | // Maybe there was name of package user wants to install passed as a parameter. 36 | if (argv._.length > 0) { 37 | params.push(argv._[0]); 38 | params.push('--save'); 39 | } 40 | 41 | 42 | var installCommand = null; 43 | 44 | if (process.platform === 'win32') { 45 | installCommand = 'npm.cmd' 46 | } else { 47 | installCommand = 'npm' 48 | } 49 | 50 | var install = childProcess.spawn(installCommand, params, { 51 | cwd: __dirname + '/../app', 52 | env: process.env, 53 | stdio: 'inherit' 54 | }); 55 | -------------------------------------------------------------------------------- /app/controllers/dashboard.js: -------------------------------------------------------------------------------- 1 | var Mustache = require('mustache'); 2 | var npm = require('npm'); 3 | var Q = require('q'); 4 | var shell = require('shell'); 5 | 6 | window.jQuery = window.$ = require('jquery'); 7 | window.Hammer = require('hammerjs'); 8 | 9 | import { project } from './project'; 10 | 11 | export const dashboard = { 12 | 13 | template: $('#dashboard').html(), 14 | 15 | render: function() { 16 | $('#target').html(Mustache.render(this.template, this.view())); 17 | $('#dashboardContainer').fadeIn(1000); 18 | $('.collapsible').collapsible(); 19 | this.bindEvents(); 20 | }, 21 | 22 | view: function(){ 23 | 24 | let view = { 25 | dependenciesShow:[], 26 | dependenciesShowLength: 0, 27 | devDependenciesShow:[], 28 | devDependenciesShowLength: 0 29 | }; 30 | 31 | // TODO: Change array to JSON 32 | $.each(project.npm.dependenciesShow, (key, value) => { 33 | $.each(value, (k, v) => { 34 | view.dependenciesShow.push(v); 35 | }); 36 | }); 37 | view.dependenciesShowLength = view.dependenciesShow.length; 38 | 39 | $.each(project.npm.devDependenciesShow, (key, value) => { 40 | $.each(value, (k, v) => { 41 | view.devDependenciesShow.push(v); 42 | }); 43 | }); 44 | view.devDependenciesShowLength = view.devDependenciesShow.length; 45 | 46 | return view; 47 | 48 | }, 49 | 50 | bindEvents: function(){ 51 | 52 | $('#titleHeader').on('click', () => location.reload()); 53 | 54 | $('.external-link').on('click', (e) => { 55 | 56 | e.preventDefault(); 57 | 58 | const url = e.currentTarget.href.replace( 59 | 'git+', '').replace( 60 | 'git:', 'https:').replace( 61 | 'ssh:', 'https:'); 62 | 63 | shell.openExternal(url); 64 | console.log(url); 65 | }); 66 | 67 | } 68 | 69 | }; -------------------------------------------------------------------------------- /tasks/release_linux.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Q = require('q'); 4 | var gulpUtil = require('gulp-util'); 5 | var childProcess = require('child_process'); 6 | var jetpack = require('fs-jetpack'); 7 | var asar = require('asar'); 8 | var utils = require('./utils'); 9 | 10 | var projectDir; 11 | var releasesDir; 12 | var packName; 13 | var packDir; 14 | var tmpDir; 15 | var readyAppDir; 16 | var manifest; 17 | 18 | var init = function () { 19 | projectDir = jetpack; 20 | tmpDir = projectDir.dir('./tmp', { empty: true }); 21 | releasesDir = projectDir.dir('./releases'); 22 | manifest = projectDir.read('app/package.json', 'json'); 23 | packName = manifest.name + '_' + manifest.version; 24 | packDir = tmpDir.dir(packName); 25 | readyAppDir = packDir.cwd('opt', manifest.name); 26 | 27 | return Q(); 28 | }; 29 | 30 | var copyRuntime = function () { 31 | return projectDir.copyAsync('node_modules/electron-prebuilt/dist', readyAppDir.path(), { overwrite: true }); 32 | }; 33 | 34 | var packageBuiltApp = function () { 35 | var deferred = Q.defer(); 36 | 37 | asar.createPackage(projectDir.path('build'), readyAppDir.path('resources/app.asar'), function() { 38 | deferred.resolve(); 39 | }); 40 | 41 | return deferred.promise; 42 | }; 43 | 44 | var finalize = function () { 45 | // Create .desktop file from the template 46 | var desktop = projectDir.read('resources/linux/app.desktop'); 47 | desktop = utils.replace(desktop, { 48 | name: manifest.name, 49 | productName: manifest.productName, 50 | description: manifest.description, 51 | version: manifest.version, 52 | author: manifest.author 53 | }); 54 | packDir.write('usr/share/applications/' + manifest.name + '.desktop', desktop); 55 | 56 | // Copy icon 57 | projectDir.copy('resources/icon.png', readyAppDir.path('icon.png')); 58 | 59 | return Q(); 60 | }; 61 | 62 | var renameApp = function() { 63 | return readyAppDir.renameAsync("electron", manifest.name); 64 | }; 65 | 66 | var packToDebFile = function () { 67 | var deferred = Q.defer(); 68 | 69 | var debFileName = packName + '_amd64.deb'; 70 | var debPath = releasesDir.path(debFileName); 71 | 72 | gulpUtil.log('Creating DEB package...'); 73 | 74 | // Counting size of the app in KiB 75 | var appSize = Math.round(readyAppDir.inspectTree('.').size / 1024); 76 | 77 | // Preparing debian control file 78 | var control = projectDir.read('resources/linux/DEBIAN/control'); 79 | control = utils.replace(control, { 80 | name: manifest.name, 81 | description: manifest.description, 82 | version: manifest.version, 83 | author: manifest.author, 84 | size: appSize 85 | }); 86 | packDir.write('DEBIAN/control', control); 87 | 88 | // Build the package... 89 | childProcess.exec('fakeroot dpkg-deb -Zxz --build ' + packDir.path() + ' ' + debPath, 90 | function (error, stdout, stderr) { 91 | if (error || stderr) { 92 | console.log("ERROR while building DEB package:"); 93 | console.log(error); 94 | console.log(stderr); 95 | } else { 96 | gulpUtil.log('DEB package ready!', debPath); 97 | } 98 | deferred.resolve(); 99 | }); 100 | 101 | return deferred.promise; 102 | }; 103 | 104 | var cleanClutter = function () { 105 | return tmpDir.removeAsync('.'); 106 | }; 107 | 108 | module.exports = function () { 109 | return init() 110 | .then(copyRuntime) 111 | .then(packageBuiltApp) 112 | .then(finalize) 113 | .then(renameApp) 114 | .then(packToDebFile) 115 | .then(cleanClutter); 116 | }; 117 | -------------------------------------------------------------------------------- /tasks/release_windows.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Q = require('q'); 4 | var gulpUtil = require('gulp-util'); 5 | var childProcess = require('child_process'); 6 | var jetpack = require('fs-jetpack'); 7 | var asar = require('asar'); 8 | var utils = require('./utils'); 9 | 10 | var projectDir; 11 | var tmpDir; 12 | var releasesDir; 13 | var readyAppDir; 14 | var manifest; 15 | 16 | var init = function () { 17 | projectDir = jetpack; 18 | tmpDir = projectDir.dir('./tmp', { empty: true }); 19 | releasesDir = projectDir.dir('./releases'); 20 | manifest = projectDir.read('app/package.json', 'json'); 21 | readyAppDir = tmpDir.cwd(manifest.name); 22 | 23 | return Q(); 24 | }; 25 | 26 | var copyRuntime = function () { 27 | return projectDir.copyAsync('node_modules/electron-prebuilt/dist', readyAppDir.path(), { overwrite: true }); 28 | }; 29 | 30 | var cleanupRuntime = function () { 31 | return readyAppDir.removeAsync('resources/default_app'); 32 | }; 33 | 34 | var packageBuiltApp = function () { 35 | var deferred = Q.defer(); 36 | 37 | asar.createPackage(projectDir.path('build'), readyAppDir.path('resources/app.asar'), function() { 38 | deferred.resolve(); 39 | }); 40 | 41 | return deferred.promise; 42 | }; 43 | 44 | var finalize = function () { 45 | var deferred = Q.defer(); 46 | 47 | projectDir.copy('resources/windows/icon.ico', readyAppDir.path('icon.ico')); 48 | 49 | // Replace Electron icon for your own. 50 | var rcedit = require('rcedit'); 51 | rcedit(readyAppDir.path('electron.exe'), { 52 | 'icon': projectDir.path('resources/windows/icon.ico'), 53 | 'version-string': { 54 | 'ProductName': manifest.productName, 55 | 'FileDescription': manifest.description, 56 | } 57 | }, function (err) { 58 | if (!err) { 59 | deferred.resolve(); 60 | } 61 | }); 62 | 63 | return deferred.promise; 64 | }; 65 | 66 | var renameApp = function () { 67 | return readyAppDir.renameAsync('electron.exe', manifest.productName + '.exe'); 68 | }; 69 | 70 | var createInstaller = function () { 71 | var deferred = Q.defer(); 72 | 73 | var finalPackageName = manifest.name + '_' + manifest.version + '.exe'; 74 | var installScript = projectDir.read('resources/windows/installer.nsi'); 75 | installScript = utils.replace(installScript, { 76 | name: manifest.name, 77 | productName: manifest.productName, 78 | version: manifest.version, 79 | src: readyAppDir.path(), 80 | dest: releasesDir.path(finalPackageName), 81 | icon: readyAppDir.path('icon.ico'), 82 | setupIcon: projectDir.path('resources/windows/setup-icon.ico'), 83 | banner: projectDir.path('resources/windows/setup-banner.bmp'), 84 | }); 85 | tmpDir.write('installer.nsi', installScript); 86 | 87 | gulpUtil.log('Building installer with NSIS...'); 88 | 89 | // Remove destination file if already exists. 90 | releasesDir.remove(finalPackageName); 91 | 92 | // Note: NSIS have to be added to PATH (environment variables). 93 | var nsis = childProcess.spawn('makensis', [ 94 | tmpDir.path('installer.nsi') 95 | ], { 96 | stdio: 'inherit' 97 | }); 98 | nsis.on('error', function (err) { 99 | if (err.message === 'spawn makensis ENOENT') { 100 | throw "Can't find NSIS. Are you sure you've installed it and" 101 | + " added to PATH environment variable?"; 102 | } else { 103 | throw err; 104 | } 105 | }); 106 | nsis.on('close', function () { 107 | gulpUtil.log('Installer ready!', releasesDir.path(finalPackageName)); 108 | deferred.resolve(); 109 | }); 110 | 111 | return deferred.promise; 112 | }; 113 | 114 | var cleanClutter = function () { 115 | return tmpDir.removeAsync('.'); 116 | }; 117 | 118 | module.exports = function () { 119 | return init() 120 | .then(copyRuntime) 121 | .then(cleanupRuntime) 122 | .then(packageBuiltApp) 123 | .then(finalize) 124 | .then(renameApp) 125 | .then(createInstaller) 126 | .then(cleanClutter); 127 | }; 128 | -------------------------------------------------------------------------------- /app/controllers/setupLoader.js: -------------------------------------------------------------------------------- 1 | var Mustache = require('mustache'); 2 | var npm = require('npm'); 3 | var Q = require('q'); 4 | 5 | window.jQuery = window.$ = require('jquery'); 6 | window.Hammer = require('hammerjs'); 7 | 8 | import { project } from './project'; 9 | import { dashboard } from './dashboard'; 10 | 11 | export const setupLoader = { 12 | 13 | template: $('#setupLoader').html(), 14 | 15 | render: function() { 16 | $('#target').html(Mustache.render(this.template)); 17 | $('#setupLoaderContainer').fadeIn(1000); 18 | this.load(); 19 | }, 20 | 21 | load: function() { 22 | 23 | this.loadDependencies().then(() => { 24 | this.loadDevDependencies().then(() => { 25 | console.log(project); 26 | dashboard.render(); 27 | }); 28 | }); 29 | 30 | }, 31 | 32 | increment: function(number){ 33 | $('#setupLoaderProgress').width(number + '%'); 34 | }, 35 | 36 | text: function(text){ 37 | $('#loaderText').html(text); 38 | }, 39 | 40 | // TODO: refactor this with loadDevDependencies 41 | loadDependencies: function(){ 42 | 43 | var deferred = Q.defer(); 44 | 45 | if(!project.npm.file.dependencies) { 46 | deferred.resolve(); 47 | return deferred.promise; 48 | } 49 | 50 | let totalOfDependencies = Object.keys( 51 | project.npm.file.dependencies).length; 52 | 53 | npm.load((err) => { 54 | 55 | err ? deferred.reject(new Error(err)) : null; 56 | 57 | $.each(project.npm.file.dependencies, (key, value) => { 58 | npm.commands.show([`${key}@${value.replace('^', '')}`], (err, data) => { 59 | 60 | // TODO: Change array to JSON 61 | !err ? project.npm.dependenciesShow.push(data) : 62 | project.npm.dependenciesShow.push(err); 63 | 64 | }); 65 | }); 66 | 67 | }); 68 | 69 | var resolve = setInterval(() => { 70 | this.increment((project.npm.dependenciesShow.length/totalOfDependencies) * 100); 71 | this.text(`Load NPM dependencies information - ${project.npm.dependenciesShow.length} / ${totalOfDependencies}`); 72 | 73 | if(project.npm.dependenciesShow.length === totalOfDependencies){ 74 | clearInterval(resolve); 75 | deferred.resolve(); 76 | } 77 | }, 50); 78 | 79 | return deferred.promise; 80 | 81 | }, 82 | 83 | // TODO: refactor this with loadDependencies 84 | loadDevDependencies: function(){ 85 | 86 | var deferred = Q.defer(); 87 | 88 | if(!project.npm.file.devDependencies) { 89 | deferred.resolve(); 90 | return deferred.promise; 91 | } 92 | 93 | let totalOfDependencies = Object.keys( 94 | project.npm.file.devDependencies).length; 95 | 96 | totalOfDependencies < 0 ? deferred.resolve() : null; 97 | 98 | npm.load((err) => { 99 | 100 | err ? deferred.reject(new Error(err)) : null; 101 | 102 | $.each(project.npm.file.devDependencies, (key, value) => { 103 | npm.commands.show([`${key}@${value.replace('^', '')}`], 104 | (err, data) => { 105 | 106 | // TODO: Change array to JSON 107 | !err ? project.npm.devDependenciesShow.push(data) : 108 | project.npm.devDependenciesShow.push(err); 109 | 110 | }); 111 | }); 112 | 113 | }); 114 | 115 | var resolve = setInterval(() => { 116 | this.increment((project.npm.devDependenciesShow.length/totalOfDependencies) * 100); 117 | this.text(`Load NPM dev dependencies information - ${project.npm.devDependenciesShow.length} / ${totalOfDependencies}`); 118 | 119 | if(project.npm.devDependenciesShow.length === totalOfDependencies){ 120 | clearInterval(resolve); 121 | deferred.resolve(); 122 | } 123 | }, 50); 124 | 125 | return deferred.promise; 126 | 127 | } 128 | 129 | }; 130 | -------------------------------------------------------------------------------- /tasks/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pathUtil = require('path'); 4 | var Q = require('q'); 5 | var gulp = require('gulp'); 6 | var rollup = require('rollup'); 7 | var less = require('gulp-less'); 8 | var jetpack = require('fs-jetpack'); 9 | 10 | var utils = require('./utils'); 11 | var generateSpecsImportFile = require('./generate_specs_import'); 12 | 13 | var projectDir = jetpack; 14 | var srcDir = projectDir.cwd('./app'); 15 | var destDir = projectDir.cwd('./build'); 16 | 17 | var paths = { 18 | copyFromAppDir: [ 19 | './node_modules/**', 20 | './vendor/**', 21 | './lib/**', 22 | './**/*.html', 23 | './images/*' 24 | ] 25 | }; 26 | 27 | // ------------------------------------- 28 | // Tasks 29 | // ------------------------------------- 30 | 31 | gulp.task('clean', function(callback) { 32 | return destDir.dirAsync('.', { empty: true }); 33 | }); 34 | 35 | 36 | var copyTask = function () { 37 | return projectDir.copyAsync('app', destDir.path(), { 38 | overwrite: true, 39 | matching: paths.copyFromAppDir 40 | }); 41 | }; 42 | gulp.task('copy', ['clean'], copyTask); 43 | gulp.task('copy-watch', copyTask); 44 | 45 | 46 | var bundle = function (src, dest) { 47 | var deferred = Q.defer(); 48 | 49 | rollup.rollup({ 50 | entry: src 51 | }).then(function (bundle) { 52 | var jsFile = pathUtil.basename(dest); 53 | var result = bundle.generate({ 54 | format: 'iife', 55 | sourceMap: true, 56 | sourceMapFile: jsFile 57 | }); 58 | return Q.all([ 59 | destDir.writeAsync(dest, result.code + '\n//# sourceMappingURL=' + jsFile + '.map'), 60 | destDir.writeAsync(dest + '.map', result.map.toString()) 61 | ]); 62 | }).then(function () { 63 | deferred.resolve(); 64 | }).catch(function (err) { 65 | console.error(err); 66 | }); 67 | 68 | return deferred.promise; 69 | }; 70 | 71 | var bundleApplication = function () { 72 | return Q.all([ 73 | bundle(srcDir.path('background.js'), destDir.path('background.js')), 74 | bundle(srcDir.path('app.js'), destDir.path('app.js')) 75 | ]); 76 | }; 77 | 78 | var bundleSpecs = function () { 79 | generateSpecsImportFile().then(function (specEntryPointPath) { 80 | return Q.all([ 81 | bundle(srcDir.path('background.js'), destDir.path('background.js')), 82 | bundle(specEntryPointPath, destDir.path('spec.js')) 83 | ]); 84 | }); 85 | }; 86 | 87 | var bundleTask = function () { 88 | if (utils.getEnvName() === 'test') { 89 | return bundleSpecs(); 90 | } 91 | return bundleApplication(); 92 | }; 93 | gulp.task('bundle', ['clean'], bundleTask); 94 | gulp.task('bundle-watch', bundleTask); 95 | 96 | 97 | var lessTask = function () { 98 | return gulp.src('app/stylesheets/main.less') 99 | .pipe(less()) 100 | .pipe(gulp.dest(destDir.path('stylesheets'))); 101 | }; 102 | gulp.task('less', ['clean'], lessTask); 103 | gulp.task('less-watch', lessTask); 104 | 105 | 106 | gulp.task('finalize', ['clean'], function () { 107 | var manifest = srcDir.read('package.json', 'json'); 108 | // Add "dev" or "test" suffix to name, so Electron will write all data 109 | // like cookies and localStorage in separate places for each environment. 110 | switch (utils.getEnvName()) { 111 | case 'development': 112 | manifest.name += '-dev'; 113 | manifest.productName += ' Dev'; 114 | break; 115 | case 'test': 116 | manifest.name += '-test'; 117 | manifest.productName += ' Test'; 118 | break; 119 | } 120 | destDir.write('package.json', manifest); 121 | 122 | var configFilePath = projectDir.path('config/env_' + utils.getEnvName() + '.json'); 123 | destDir.copy(configFilePath, 'env_config.json'); 124 | }); 125 | 126 | 127 | gulp.task('watch', function () { 128 | gulp.watch('app/**/*.js', ['bundle-watch']); 129 | gulp.watch(paths.copyFromAppDir, { cwd: 'app' }, ['copy-watch']); 130 | gulp.watch('app/**/*.less', ['less-watch']); 131 | }); 132 | 133 | 134 | gulp.task('build', ['bundle', 'less', 'copy', 'finalize']); 135 | -------------------------------------------------------------------------------- /resources/windows/installer.nsi: -------------------------------------------------------------------------------- 1 | ; NSIS packaging/install script 2 | ; Docs: http://nsis.sourceforge.net/Docs/Contents.html 3 | 4 | !include LogicLib.nsh 5 | !include nsDialogs.nsh 6 | 7 | ; -------------------------------- 8 | ; Variables 9 | ; -------------------------------- 10 | 11 | !define dest "{{dest}}" 12 | !define src "{{src}}" 13 | !define name "{{name}}" 14 | !define productName "{{productName}}" 15 | !define version "{{version}}" 16 | !define icon "{{icon}}" 17 | !define setupIcon "{{setupIcon}}" 18 | !define banner "{{banner}}" 19 | 20 | !define exec "{{productName}}.exe" 21 | 22 | !define regkey "Software\${productName}" 23 | !define uninstkey "Software\Microsoft\Windows\CurrentVersion\Uninstall\${productName}" 24 | 25 | !define uninstaller "uninstall.exe" 26 | 27 | ; -------------------------------- 28 | ; Installation 29 | ; -------------------------------- 30 | 31 | SetCompressor lzma 32 | 33 | Name "${productName}" 34 | Icon "${setupIcon}" 35 | OutFile "${dest}" 36 | InstallDir "$PROGRAMFILES\${productName}" 37 | InstallDirRegKey HKLM "${regkey}" "" 38 | 39 | CRCCheck on 40 | SilentInstall normal 41 | 42 | XPStyle on 43 | ShowInstDetails nevershow 44 | AutoCloseWindow false 45 | WindowIcon off 46 | 47 | Caption "${productName} Setup" 48 | ; Don't add sub-captions to title bar 49 | SubCaption 3 " " 50 | SubCaption 4 " " 51 | 52 | Page custom welcome 53 | Page instfiles 54 | 55 | Var Image 56 | Var ImageHandle 57 | 58 | Function .onInit 59 | 60 | ; Extract banner image for welcome page 61 | InitPluginsDir 62 | ReserveFile "${banner}" 63 | File /oname=$PLUGINSDIR\banner.bmp "${banner}" 64 | 65 | FunctionEnd 66 | 67 | ; Custom welcome page 68 | Function welcome 69 | 70 | nsDialogs::Create 1018 71 | 72 | ${NSD_CreateLabel} 185 1u 210 100% "Welcome to ${productName} version ${version} installer.$\r$\n$\r$\nClick install to begin." 73 | 74 | ${NSD_CreateBitmap} 0 0 170 210 "" 75 | Pop $Image 76 | ${NSD_SetImage} $Image $PLUGINSDIR\banner.bmp $ImageHandle 77 | 78 | nsDialogs::Show 79 | 80 | ${NSD_FreeImage} $ImageHandle 81 | 82 | FunctionEnd 83 | 84 | ; Installation declarations 85 | Section "Install" 86 | 87 | WriteRegStr HKLM "${regkey}" "Install_Dir" "$INSTDIR" 88 | WriteRegStr HKLM "${uninstkey}" "DisplayName" "${productName}" 89 | WriteRegStr HKLM "${uninstkey}" "DisplayIcon" '"$INSTDIR\icon.ico"' 90 | WriteRegStr HKLM "${uninstkey}" "UninstallString" '"$INSTDIR\${uninstaller}"' 91 | 92 | ; Remove all application files copied by previous installation 93 | RMDir /r "$INSTDIR" 94 | 95 | SetOutPath $INSTDIR 96 | 97 | ; Include all files from /build directory 98 | File /r "${src}\*" 99 | 100 | ; Create start menu shortcut 101 | CreateShortCut "$SMPROGRAMS\${productName}.lnk" "$INSTDIR\${exec}" "" "$INSTDIR\icon.ico" 102 | 103 | WriteUninstaller "${uninstaller}" 104 | 105 | SectionEnd 106 | 107 | ; -------------------------------- 108 | ; Uninstaller 109 | ; -------------------------------- 110 | 111 | ShowUninstDetails nevershow 112 | 113 | UninstallCaption "Uninstall ${productName}" 114 | UninstallText "Don't like ${productName} anymore? Hit uninstall button." 115 | UninstallIcon "${icon}" 116 | 117 | UninstPage custom un.confirm un.confirmOnLeave 118 | UninstPage instfiles 119 | 120 | Var RemoveAppDataCheckbox 121 | Var RemoveAppDataCheckbox_State 122 | 123 | ; Custom uninstall confirm page 124 | Function un.confirm 125 | 126 | nsDialogs::Create 1018 127 | 128 | ${NSD_CreateLabel} 1u 1u 100% 24u "If you really want to remove ${productName} from your computer press uninstall button." 129 | 130 | ${NSD_CreateCheckbox} 1u 35u 100% 10u "Remove also my ${productName} personal data" 131 | Pop $RemoveAppDataCheckbox 132 | 133 | nsDialogs::Show 134 | 135 | FunctionEnd 136 | 137 | Function un.confirmOnLeave 138 | 139 | ; Save checkbox state on page leave 140 | ${NSD_GetState} $RemoveAppDataCheckbox $RemoveAppDataCheckbox_State 141 | 142 | FunctionEnd 143 | 144 | ; Uninstall declarations 145 | Section "Uninstall" 146 | 147 | DeleteRegKey HKLM "${uninstkey}" 148 | DeleteRegKey HKLM "${regkey}" 149 | 150 | Delete "$SMPROGRAMS\${productName}.lnk" 151 | 152 | ; Remove whole directory from Program Files 153 | RMDir /r "$INSTDIR" 154 | 155 | ; Remove also appData directory generated by your app if user checked this option 156 | ${If} $RemoveAppDataCheckbox_State == ${BST_CHECKED} 157 | RMDir /r "$LOCALAPPDATA\${name}" 158 | ${EndIf} 159 | 160 | SectionEnd 161 | -------------------------------------------------------------------------------- /tasks/release_osx.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Q = require('q'); 4 | var gulpUtil = require('gulp-util'); 5 | var jetpack = require('fs-jetpack'); 6 | var asar = require('asar'); 7 | var utils = require('./utils'); 8 | 9 | var projectDir; 10 | var releasesDir; 11 | var tmpDir; 12 | var finalAppDir; 13 | var manifest; 14 | 15 | var init = function () { 16 | projectDir = jetpack; 17 | tmpDir = projectDir.dir('./tmp', { empty: true }); 18 | releasesDir = projectDir.dir('./releases'); 19 | manifest = projectDir.read('app/package.json', 'json'); 20 | finalAppDir = tmpDir.cwd(manifest.productName + '.app'); 21 | 22 | return Q(); 23 | }; 24 | 25 | var copyRuntime = function () { 26 | return projectDir.copyAsync('node_modules/electron-prebuilt/dist/Electron.app', finalAppDir.path()); 27 | }; 28 | 29 | var cleanupRuntime = function() { 30 | finalAppDir.remove('Contents/Resources/default_app'); 31 | finalAppDir.remove('Contents/Resources/atom.icns'); 32 | return Q(); 33 | } 34 | 35 | var packageBuiltApp = function () { 36 | var deferred = Q.defer(); 37 | 38 | asar.createPackage(projectDir.path('build'), finalAppDir.path('Contents/Resources/app.asar'), function() { 39 | deferred.resolve(); 40 | }); 41 | 42 | return deferred.promise; 43 | }; 44 | 45 | var finalize = function () { 46 | // Prepare main Info.plist 47 | var info = projectDir.read('resources/osx/Info.plist'); 48 | info = utils.replace(info, { 49 | productName: manifest.productName, 50 | identifier: manifest.identifier, 51 | version: manifest.version 52 | }); 53 | finalAppDir.write('Contents/Info.plist', info); 54 | 55 | // Prepare Info.plist of Helper apps 56 | [' EH', ' NP', ''].forEach(function (helper_suffix) { 57 | info = projectDir.read('resources/osx/helper_apps/Info' + helper_suffix + '.plist'); 58 | info = utils.replace(info, { 59 | productName: manifest.productName, 60 | identifier: manifest.identifier 61 | }); 62 | finalAppDir.write('Contents/Frameworks/Electron Helper' + helper_suffix + '.app/Contents/Info.plist', info); 63 | }); 64 | 65 | // Copy icon 66 | projectDir.copy('resources/osx/icon.icns', finalAppDir.path('Contents/Resources/icon.icns')); 67 | 68 | return Q(); 69 | }; 70 | 71 | var renameApp = function() { 72 | // Rename helpers 73 | [' Helper EH', ' Helper NP', ' Helper'].forEach(function (helper_suffix) { 74 | finalAppDir.rename('Contents/Frameworks/Electron' + helper_suffix + '.app/Contents/MacOS/Electron' + helper_suffix, manifest.productName + helper_suffix ); 75 | finalAppDir.rename('Contents/Frameworks/Electron' + helper_suffix + '.app', manifest.productName + helper_suffix + '.app'); 76 | }); 77 | // Rename application 78 | finalAppDir.rename('Contents/MacOS/Electron', manifest.productName); 79 | return Q(); 80 | } 81 | 82 | var packToDmgFile = function () { 83 | var deferred = Q.defer(); 84 | 85 | var appdmg = require('appdmg'); 86 | var dmgName = manifest.name + '_' + manifest.version + '.dmg'; 87 | 88 | // Prepare appdmg config 89 | var dmgManifest = projectDir.read('resources/osx/appdmg.json'); 90 | dmgManifest = utils.replace(dmgManifest, { 91 | productName: manifest.productName, 92 | appPath: finalAppDir.path(), 93 | dmgIcon: projectDir.path("resources/osx/dmg-icon.icns"), 94 | dmgBackground: projectDir.path("resources/osx/dmg-background.png") 95 | }); 96 | tmpDir.write('appdmg.json', dmgManifest); 97 | 98 | // Delete DMG file with this name if already exists 99 | releasesDir.remove(dmgName); 100 | 101 | gulpUtil.log('Packaging to DMG file...'); 102 | 103 | var readyDmgPath = releasesDir.path(dmgName); 104 | appdmg({ 105 | source: tmpDir.path('appdmg.json'), 106 | target: readyDmgPath 107 | }) 108 | .on('error', function (err) { 109 | console.error(err); 110 | }) 111 | .on('finish', function () { 112 | gulpUtil.log('DMG file ready!', readyDmgPath); 113 | deferred.resolve(); 114 | }); 115 | 116 | return deferred.promise; 117 | }; 118 | 119 | var cleanClutter = function () { 120 | return tmpDir.removeAsync('.'); 121 | }; 122 | 123 | module.exports = function () { 124 | return init() 125 | .then(copyRuntime) 126 | .then(cleanupRuntime) 127 | .then(packageBuiltApp) 128 | .then(finalize) 129 | .then(renameApp) 130 | .then(packToDmgFile) 131 | .then(cleanClutter); 132 | }; 133 | -------------------------------------------------------------------------------- /app/controllers/setup.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | var remote = require('remote'); 3 | var dialog = remote.require('dialog'); 4 | var app = remote.require('app'); 5 | var jetpack = require('fs-jetpack'); 6 | var Mustache = require('mustache'); 7 | window.jQuery = window.$ = require('jquery'); 8 | window.Hammer = require('hammerjs'); 9 | 10 | import { project } from './project'; 11 | import { setupLoader } from './setupLoader'; 12 | 13 | export const setup = { 14 | 15 | template: $('#setup').html(), 16 | 17 | name: localStorage.getItem('name'), 18 | 19 | render: function() { 20 | 21 | $('#target').html(Mustache.render(this.template)); 22 | 23 | !navigator.onLine ? this.offlineChecker() : 24 | this.name ? this.sayHello() : this.termsModal(); 25 | 26 | $('#setupContainer').delay(500).fadeIn(1000); 27 | this.bindEvents(); 28 | 29 | }, 30 | 31 | bindEvents: function() { 32 | 33 | $('#btnDisagree').on('click', () => app.quit()); 34 | 35 | $('#btnAgree').on('click', 36 | () => { 37 | $('#agreeModal').closeModal(); 38 | this.nameModal(); 39 | }); 40 | 41 | $('#btnContinue').on('click', 42 | () => { 43 | if($('#firstName').val() !== '') { 44 | $('#nameModal').closeModal(); 45 | localStorage.setItem('name', $('#firstName').val()); 46 | this.sayHello(); 47 | } 48 | }); 49 | 50 | $('#selectFolder').on('click', () => { 51 | setup.setProjectFolder(); 52 | this.setManagerFiles(); 53 | this.start(); 54 | }); 55 | 56 | $('#firstName').on('keypress', (e) => { 57 | if(e.which === 13) { 58 | e.preventDefault(); 59 | if($('#firstName').val() !== '') { 60 | $('#nameModal').closeModal(); 61 | localStorage.setItem('name', $('#firstName').val()); 62 | this.sayHello(); 63 | } 64 | } 65 | }); 66 | 67 | }, 68 | 69 | termsModal: function () { 70 | $('#agreeModal').openModal({ 71 | dismissible: false 72 | }); 73 | }, 74 | 75 | nameModal: function() { 76 | $('#nameModal').openModal({ 77 | dismissible: false 78 | }); 79 | }, 80 | 81 | offlineChecker: function(){ 82 | 83 | $('#offline').openModal({ 84 | dismissible: false 85 | }); 86 | 87 | const check = setInterval(() => { 88 | console.log('Checking'); 89 | if(navigator.onLine){ 90 | $('#offline').closeModal(); 91 | this.render(); 92 | clearInterval(check); 93 | } 94 | }, 1000); 95 | 96 | }, 97 | 98 | noManagerFilesModal: function(){ 99 | $('#noManagerFiles').openModal(); 100 | }, 101 | 102 | sayHello: function() { 103 | 104 | setTimeout(() => { 105 | 106 | const now = new Date(); 107 | const hour = now.getHours(); 108 | const greeting = hour < 12 ? 'morning' : 109 | hour >= 12 && hour <= 17 ? 'afternoon' : 110 | hour >= 17 ? 'evening' : 'job'; 111 | 112 | const msg = name => `Hello ${name}, good ${greeting}! `; 113 | 114 | Materialize.toast(msg(localStorage.getItem('name')), 1500, 'black-text white'); 115 | 116 | }, 1000); 117 | 118 | }, 119 | 120 | setProjectFolder: function(){ 121 | project.projectDir = dialog.showOpenDialog( 122 | { properties: ['openDirectory'] } 123 | )[0]; 124 | }, 125 | 126 | setManagerFiles: function(){ 127 | project.npm.file = jetpack.read(project.projectDir + '/package.json', 'json'); 128 | project.bower = jetpack.read(project.projectDir + '/bower.json', 'json'); 129 | project.composer = jetpack.read(project.projectDir + '/composer.json', 'json'); 130 | }, 131 | 132 | checkManagerFiles: function(){ 133 | return !!(project.npm || project.bower || project.composer); 134 | }, 135 | 136 | start: function(){ 137 | 138 | this.checkManagerFiles() ? this.goToSetupLoader() : 139 | this.noManagerFilesModal(); 140 | 141 | }, 142 | 143 | goToSetupLoader: function(){ 144 | 145 | $('#setupContainer').fadeOut(500, () => { 146 | setupLoader.render(); 147 | }); 148 | 149 | } 150 | 151 | }; 152 | -------------------------------------------------------------------------------- /app/lib/jasmine/boot.js: -------------------------------------------------------------------------------- 1 | /** 2 | Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. 3 | 4 | If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. 5 | 6 | The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. 7 | 8 | [jasmine-gem]: http://github.com/pivotal/jasmine-gem 9 | */ 10 | 11 | (function() { 12 | 13 | /** 14 | * ## Require & Instantiate 15 | * 16 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. 17 | */ 18 | window.jasmine = jasmineRequire.core(jasmineRequire); 19 | 20 | /** 21 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. 22 | */ 23 | jasmineRequire.html(jasmine); 24 | 25 | /** 26 | * Create the Jasmine environment. This is used to run all specs in a project. 27 | */ 28 | var env = jasmine.getEnv(); 29 | 30 | /** 31 | * ## The Global Interface 32 | * 33 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. 34 | */ 35 | var jasmineInterface = jasmineRequire.interface(jasmine, env); 36 | 37 | /** 38 | * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. 39 | */ 40 | if (typeof window == "undefined" && typeof exports == "object") { 41 | extend(exports, jasmineInterface); 42 | } else { 43 | extend(window, jasmineInterface); 44 | } 45 | 46 | /** 47 | * ## Runner Parameters 48 | * 49 | * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. 50 | */ 51 | 52 | var queryString = new jasmine.QueryString({ 53 | getWindowLocation: function() { return window.location; } 54 | }); 55 | 56 | var catchingExceptions = queryString.getParam("catch"); 57 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); 58 | 59 | /** 60 | * ## Reporters 61 | * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). 62 | */ 63 | var htmlReporter = new jasmine.HtmlReporter({ 64 | env: env, 65 | onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, 66 | addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, 67 | getContainer: function() { return document.body; }, 68 | createElement: function() { return document.createElement.apply(document, arguments); }, 69 | createTextNode: function() { return document.createTextNode.apply(document, arguments); }, 70 | timer: new jasmine.Timer() 71 | }); 72 | 73 | /** 74 | * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. 75 | */ 76 | env.addReporter(jasmineInterface.jsApiReporter); 77 | env.addReporter(htmlReporter); 78 | 79 | /** 80 | * Filter which specs will be run by matching the start of the full name against the `spec` query param. 81 | */ 82 | var specFilter = new jasmine.HtmlSpecFilter({ 83 | filterString: function() { return queryString.getParam("spec"); } 84 | }); 85 | 86 | env.specFilter = function(spec) { 87 | return specFilter.matches(spec.getFullName()); 88 | }; 89 | 90 | /** 91 | * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. 92 | */ 93 | window.setTimeout = window.setTimeout; 94 | window.setInterval = window.setInterval; 95 | window.clearTimeout = window.clearTimeout; 96 | window.clearInterval = window.clearInterval; 97 | 98 | /** 99 | * ## Execution 100 | * 101 | * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. 102 | */ 103 | var currentWindowOnload = window.onload; 104 | 105 | window.onload = function() { 106 | if (currentWindowOnload) { 107 | currentWindowOnload(); 108 | } 109 | htmlReporter.initialize(); 110 | env.execute(); 111 | }; 112 | 113 | /** 114 | * Helper function for readability above. 115 | */ 116 | function extend(destination, source) { 117 | for (var property in source) destination[property] = source[property]; 118 | return destination; 119 | } 120 | 121 | }()); 122 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // -------------------------------------------------------------------- 3 | // JSHint Configuration, Strict Edition 4 | // -------------------------------------------------------------------- 5 | // 6 | // This is a options template for [JSHint][1], using [JSHint example][2] 7 | // and [Ory Band's example][3] as basis and setting config values to 8 | // be most strict: 9 | // 10 | // * set all enforcing options to true 11 | // * set all relaxing options to false 12 | // * set all environment options to false, except the browser value 13 | // * set all JSLint legacy options to false 14 | // 15 | // [1]: http://www.jshint.com/ 16 | // [2]: https://github.com/jshint/node-jshint/blob/master/example/config.json 17 | // [3]: https://github.com/oryband/dotfiles/blob/master/jshintrc 18 | // 19 | // @author http://michael.haschke.biz/ 20 | // @license http://unlicense.org/ 21 | 22 | // == Enforcing Options =============================================== 23 | // 24 | // These options tell JSHint to be more strict towards your code. Use 25 | // them if you want to allow only a safe subset of JavaScript, very 26 | // useful when your codebase is shared with a big number of developers 27 | // with different skill levels. 28 | "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.). 29 | "curly" : true, // Require {} for every new block or scope. 30 | "eqeqeq" : true, // Require triple equals i.e. `===`. 31 | "forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`. 32 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 33 | "latedef" : true, // Prohibit variable use before definition. 34 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 35 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 36 | "noempty" : true, // Prohibit use of empty blocks. 37 | "nonew" : false, // Prohibit use of constructors for side-effects. 38 | "plusplus" : true, // Prohibit use of `++` & `--`. 39 | "regexp" : true, // Prohibit `.` and `[^...]` in regular expressions. 40 | "undef" : true, // Require all non-global variables be declared before they are used. 41 | "strict" : false, // Require `use strict` pragma in every file. 42 | "trailing" : true, // Prohibit trailing whitespaces. 43 | 44 | // == Relaxing Options ================================================ 45 | // 46 | // These options allow you to suppress certain types of warnings. Use 47 | // them only if you are absolutely positive that you know what you are 48 | // doing. 49 | 50 | "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). 51 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 52 | "debug" : false, // Allow debugger statements e.g. browser breakpoints. 53 | "eqnull" : false, // Tolerate use of `== null`. 54 | "es6" : true, // Allow EcmaScript 5 syntax. 55 | "esnext" : true, // Allow ES.next specific features such as `const` and `let`. 56 | "evil" : false, // Tolerate use of `eval`. 57 | "expr" : false, // Tolerate `ExpressionStatement` as Programs. 58 | "funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside. 59 | "globalstrict" : false, // Allow global "use strict" (also enables 'strict'). 60 | "iterator" : false, // Allow usage of __iterator__ property. 61 | "lastsemic" : false, // Tolerat missing semicolons when the it is omitted for the last statement in a one-line block. 62 | "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 63 | "laxcomma" : false, // Suppress warnings about comma-first coding style. 64 | "loopfunc" : false, // Allow functions to be defined within loops. 65 | "multistr" : false, // Tolerate multi-line strings. 66 | "onecase" : false, // Tolerate switches with just one case. 67 | "proto" : false, // Tolerate __proto__ property. This property is deprecated. 68 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. 69 | "scripturl" : true, // Tolerate script-targeted URLs. 70 | "smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignmnent only. 71 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 72 | "sub" : true, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 73 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. 74 | "validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function. 75 | 76 | // == Environments ==================================================== 77 | // 78 | // These options pre-define global variables that are exposed by 79 | // popular JavaScript libraries and runtime environments—such as 80 | // browser or node.js. 81 | 82 | "browser" : true, // Standard browser globals e.g. `window`, `document`. 83 | "couch" : false, // Enable globals exposed by CouchDB. 84 | "devel" : false, // Allow development statements e.g. `console.log();`. 85 | "dojo" : false, // Enable globals exposed by Dojo Toolkit. 86 | "jquery" : true, // Enable globals exposed by jQuery JavaScript library. 87 | "mootools" : false, // Enable globals exposed by MooTools JavaScript framework. 88 | "node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment. 89 | "nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape. 90 | "prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework. 91 | "rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment. 92 | "wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host. 93 | 94 | // == Formatting and Complexity ======================================= 95 | // 96 | // Indentation and max length work to naturally keep files small, 97 | // editable, and with limited complexity. The other options are a 98 | // strict way to enforce these. 99 | 100 | "maxlen" : 80, // Maximum line length, helps reduce cyclomatic complexity 101 | "indent" : 2, // Specify indentation spacing 102 | "maxparams" : 10, // Maximum number of formal parameters allowed per function 103 | "maxdepth" : 4, // Maximum nested statement depth for a function 104 | "maxstatements" : 33, // Maximum number of statements allowed per function 105 | "maxcomplexity" : 15, // Maximum cyclomatic complexity of a function 106 | // http://en.wikipedia.org/wiki/Cyclomatic_complexity 107 | 108 | // == JSLint Legacy =================================================== 109 | // 110 | // These options are legacy from JSLint. Aside from bug fixes they will 111 | // not be improved in any way and might be removed at any point. 112 | 113 | "nomen" : false, // Prohibit use of initial or trailing underbars in names. 114 | "onevar" : true, // Allow only one `var` statement per function. 115 | "passfail" : false, // Stop on first error. 116 | "white" : true, // Check against strict whitespace and indentation rules. 117 | 118 | // == Undocumented Options ============================================ 119 | // 120 | // While I've found these options in [example1][2] and [example2][3] 121 | // they are not described in the [JSHint Options documentation][4]. 122 | // 123 | // [4]: http://www.jshint.com/options/ 124 | 125 | "maxerr" : 100, // Maximum errors before stopping. 126 | "predef" : [ // Extra globals. 127 | "require", 128 | "define", 129 | "escape" 130 | ], 131 | "globals":[ 132 | // Jasmine Globals 133 | "describe", 134 | "it", 135 | "expect", 136 | // Node Globals 137 | "require", 138 | "process", 139 | "module", 140 | "exports", 141 | "app.quit()", 142 | "Materialize" 143 | ] 144 | } -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Agroups.io 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 102 | 103 | 126 | 127 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /app/lib/jasmine/jasmine-html.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2015 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | jasmineRequire.html = function(j$) { 24 | j$.ResultsNode = jasmineRequire.ResultsNode(); 25 | j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); 26 | j$.QueryString = jasmineRequire.QueryString(); 27 | j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); 28 | }; 29 | 30 | jasmineRequire.HtmlReporter = function(j$) { 31 | 32 | var noopTimer = { 33 | start: function() {}, 34 | elapsed: function() { return 0; } 35 | }; 36 | 37 | function HtmlReporter(options) { 38 | var env = options.env || {}, 39 | getContainer = options.getContainer, 40 | createElement = options.createElement, 41 | createTextNode = options.createTextNode, 42 | onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, 43 | addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, 44 | timer = options.timer || noopTimer, 45 | results = [], 46 | specsExecuted = 0, 47 | failureCount = 0, 48 | pendingSpecCount = 0, 49 | htmlReporterMain, 50 | symbols, 51 | failedSuites = []; 52 | 53 | this.initialize = function() { 54 | clearPrior(); 55 | htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'}, 56 | createDom('div', {className: 'banner'}, 57 | createDom('a', {className: 'title', href: 'http://jasmine.github.io/', target: '_blank'}), 58 | createDom('span', {className: 'version'}, j$.version) 59 | ), 60 | createDom('ul', {className: 'symbol-summary'}), 61 | createDom('div', {className: 'alert'}), 62 | createDom('div', {className: 'results'}, 63 | createDom('div', {className: 'failures'}) 64 | ) 65 | ); 66 | getContainer().appendChild(htmlReporterMain); 67 | 68 | symbols = find('.symbol-summary'); 69 | }; 70 | 71 | var totalSpecsDefined; 72 | this.jasmineStarted = function(options) { 73 | totalSpecsDefined = options.totalSpecsDefined || 0; 74 | timer.start(); 75 | }; 76 | 77 | var summary = createDom('div', {className: 'summary'}); 78 | 79 | var topResults = new j$.ResultsNode({}, '', null), 80 | currentParent = topResults; 81 | 82 | this.suiteStarted = function(result) { 83 | currentParent.addChild(result, 'suite'); 84 | currentParent = currentParent.last(); 85 | }; 86 | 87 | this.suiteDone = function(result) { 88 | if (result.status == 'failed') { 89 | failedSuites.push(result); 90 | } 91 | 92 | if (currentParent == topResults) { 93 | return; 94 | } 95 | 96 | currentParent = currentParent.parent; 97 | }; 98 | 99 | this.specStarted = function(result) { 100 | currentParent.addChild(result, 'spec'); 101 | }; 102 | 103 | var failures = []; 104 | this.specDone = function(result) { 105 | if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') { 106 | console.error('Spec \'' + result.fullName + '\' has no expectations.'); 107 | } 108 | 109 | if (result.status != 'disabled') { 110 | specsExecuted++; 111 | } 112 | 113 | symbols.appendChild(createDom('li', { 114 | className: noExpectations(result) ? 'empty' : result.status, 115 | id: 'spec_' + result.id, 116 | title: result.fullName 117 | } 118 | )); 119 | 120 | if (result.status == 'failed') { 121 | failureCount++; 122 | 123 | var failure = 124 | createDom('div', {className: 'spec-detail failed'}, 125 | createDom('div', {className: 'description'}, 126 | createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName) 127 | ), 128 | createDom('div', {className: 'messages'}) 129 | ); 130 | var messages = failure.childNodes[1]; 131 | 132 | for (var i = 0; i < result.failedExpectations.length; i++) { 133 | var expectation = result.failedExpectations[i]; 134 | messages.appendChild(createDom('div', {className: 'result-message'}, expectation.message)); 135 | messages.appendChild(createDom('div', {className: 'stack-trace'}, expectation.stack)); 136 | } 137 | 138 | failures.push(failure); 139 | } 140 | 141 | if (result.status == 'pending') { 142 | pendingSpecCount++; 143 | } 144 | }; 145 | 146 | this.jasmineDone = function() { 147 | var banner = find('.banner'); 148 | banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's')); 149 | 150 | var alert = find('.alert'); 151 | 152 | alert.appendChild(createDom('span', { className: 'exceptions' }, 153 | createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'), 154 | createDom('input', { 155 | className: 'raise', 156 | id: 'raise-exceptions', 157 | type: 'checkbox' 158 | }) 159 | )); 160 | var checkbox = find('#raise-exceptions'); 161 | 162 | checkbox.checked = !env.catchingExceptions(); 163 | checkbox.onclick = onRaiseExceptionsClick; 164 | 165 | if (specsExecuted < totalSpecsDefined) { 166 | var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; 167 | alert.appendChild( 168 | createDom('span', {className: 'bar skipped'}, 169 | createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage) 170 | ) 171 | ); 172 | } 173 | var statusBarMessage = ''; 174 | var statusBarClassName = 'bar '; 175 | 176 | if (totalSpecsDefined > 0) { 177 | statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); 178 | if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } 179 | statusBarClassName += (failureCount > 0) ? 'failed' : 'passed'; 180 | } else { 181 | statusBarClassName += 'skipped'; 182 | statusBarMessage += 'No specs found'; 183 | } 184 | 185 | alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); 186 | 187 | for(i = 0; i < failedSuites.length; i++) { 188 | var failedSuite = failedSuites[i]; 189 | for(var j = 0; j < failedSuite.failedExpectations.length; j++) { 190 | var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message; 191 | var errorBarClassName = 'bar errored'; 192 | alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); 193 | } 194 | } 195 | 196 | var results = find('.results'); 197 | results.appendChild(summary); 198 | 199 | summaryList(topResults, summary); 200 | 201 | function summaryList(resultsTree, domParent) { 202 | var specListNode; 203 | for (var i = 0; i < resultsTree.children.length; i++) { 204 | var resultNode = resultsTree.children[i]; 205 | if (resultNode.type == 'suite') { 206 | var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id}, 207 | createDom('li', {className: 'suite-detail'}, 208 | createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) 209 | ) 210 | ); 211 | 212 | summaryList(resultNode, suiteListNode); 213 | domParent.appendChild(suiteListNode); 214 | } 215 | if (resultNode.type == 'spec') { 216 | if (domParent.getAttribute('class') != 'specs') { 217 | specListNode = createDom('ul', {className: 'specs'}); 218 | domParent.appendChild(specListNode); 219 | } 220 | var specDescription = resultNode.result.description; 221 | if(noExpectations(resultNode.result)) { 222 | specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; 223 | } 224 | if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') { 225 | specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason; 226 | } 227 | specListNode.appendChild( 228 | createDom('li', { 229 | className: resultNode.result.status, 230 | id: 'spec-' + resultNode.result.id 231 | }, 232 | createDom('a', {href: specHref(resultNode.result)}, specDescription) 233 | ) 234 | ); 235 | } 236 | } 237 | } 238 | 239 | if (failures.length) { 240 | alert.appendChild( 241 | createDom('span', {className: 'menu bar spec-list'}, 242 | createDom('span', {}, 'Spec List | '), 243 | createDom('a', {className: 'failures-menu', href: '#'}, 'Failures'))); 244 | alert.appendChild( 245 | createDom('span', {className: 'menu bar failure-list'}, 246 | createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'), 247 | createDom('span', {}, ' | Failures '))); 248 | 249 | find('.failures-menu').onclick = function() { 250 | setMenuModeTo('failure-list'); 251 | }; 252 | find('.spec-list-menu').onclick = function() { 253 | setMenuModeTo('spec-list'); 254 | }; 255 | 256 | setMenuModeTo('failure-list'); 257 | 258 | var failureNode = find('.failures'); 259 | for (var i = 0; i < failures.length; i++) { 260 | failureNode.appendChild(failures[i]); 261 | } 262 | } 263 | }; 264 | 265 | return this; 266 | 267 | function find(selector) { 268 | return getContainer().querySelector('.jasmine_html-reporter ' + selector); 269 | } 270 | 271 | function clearPrior() { 272 | // return the reporter 273 | var oldReporter = find(''); 274 | 275 | if(oldReporter) { 276 | getContainer().removeChild(oldReporter); 277 | } 278 | } 279 | 280 | function createDom(type, attrs, childrenVarArgs) { 281 | var el = createElement(type); 282 | 283 | for (var i = 2; i < arguments.length; i++) { 284 | var child = arguments[i]; 285 | 286 | if (typeof child === 'string') { 287 | el.appendChild(createTextNode(child)); 288 | } else { 289 | if (child) { 290 | el.appendChild(child); 291 | } 292 | } 293 | } 294 | 295 | for (var attr in attrs) { 296 | if (attr == 'className') { 297 | el[attr] = attrs[attr]; 298 | } else { 299 | el.setAttribute(attr, attrs[attr]); 300 | } 301 | } 302 | 303 | return el; 304 | } 305 | 306 | function pluralize(singular, count) { 307 | var word = (count == 1 ? singular : singular + 's'); 308 | 309 | return '' + count + ' ' + word; 310 | } 311 | 312 | function specHref(result) { 313 | return addToExistingQueryString('spec', result.fullName); 314 | } 315 | 316 | function defaultQueryString(key, value) { 317 | return '?' + key + '=' + value; 318 | } 319 | 320 | function setMenuModeTo(mode) { 321 | htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode); 322 | } 323 | 324 | function noExpectations(result) { 325 | return (result.failedExpectations.length + result.passedExpectations.length) === 0 && 326 | result.status === 'passed'; 327 | } 328 | } 329 | 330 | return HtmlReporter; 331 | }; 332 | 333 | jasmineRequire.HtmlSpecFilter = function() { 334 | function HtmlSpecFilter(options) { 335 | var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); 336 | var filterPattern = new RegExp(filterString); 337 | 338 | this.matches = function(specName) { 339 | return filterPattern.test(specName); 340 | }; 341 | } 342 | 343 | return HtmlSpecFilter; 344 | }; 345 | 346 | jasmineRequire.ResultsNode = function() { 347 | function ResultsNode(result, type, parent) { 348 | this.result = result; 349 | this.type = type; 350 | this.parent = parent; 351 | 352 | this.children = []; 353 | 354 | this.addChild = function(result, type) { 355 | this.children.push(new ResultsNode(result, type, this)); 356 | }; 357 | 358 | this.last = function() { 359 | return this.children[this.children.length - 1]; 360 | }; 361 | } 362 | 363 | return ResultsNode; 364 | }; 365 | 366 | jasmineRequire.QueryString = function() { 367 | function QueryString(options) { 368 | 369 | this.navigateWithNewParam = function(key, value) { 370 | options.getWindowLocation().search = this.fullStringWithNewParam(key, value); 371 | }; 372 | 373 | this.fullStringWithNewParam = function(key, value) { 374 | var paramMap = queryStringToParamMap(); 375 | paramMap[key] = value; 376 | return toQueryString(paramMap); 377 | }; 378 | 379 | this.getParam = function(key) { 380 | return queryStringToParamMap()[key]; 381 | }; 382 | 383 | return this; 384 | 385 | function toQueryString(paramMap) { 386 | var qStrPairs = []; 387 | for (var prop in paramMap) { 388 | qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])); 389 | } 390 | return '?' + qStrPairs.join('&'); 391 | } 392 | 393 | function queryStringToParamMap() { 394 | var paramStr = options.getWindowLocation().search.substring(1), 395 | params = [], 396 | paramMap = {}; 397 | 398 | if (paramStr.length > 0) { 399 | params = paramStr.split('&'); 400 | for (var i = 0; i < params.length; i++) { 401 | var p = params[i].split('='); 402 | var value = decodeURIComponent(p[1]); 403 | if (value === 'true' || value === 'false') { 404 | value = JSON.parse(value); 405 | } 406 | paramMap[decodeURIComponent(p[0])] = value; 407 | } 408 | } 409 | 410 | return paramMap; 411 | } 412 | 413 | } 414 | 415 | return QueryString; 416 | }; 417 | -------------------------------------------------------------------------------- /app/lib/jasmine/jasmine.css: -------------------------------------------------------------------------------- 1 | body { overflow-y: scroll; } 2 | 3 | .jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333; } 4 | .jasmine_html-reporter a { text-decoration: none; } 5 | .jasmine_html-reporter a:hover { text-decoration: underline; } 6 | .jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; } 7 | .jasmine_html-reporter .banner, .jasmine_html-reporter .symbol-summary, .jasmine_html-reporter .summary, .jasmine_html-reporter .result-message, .jasmine_html-reporter .spec .description, .jasmine_html-reporter .spec-detail .description, .jasmine_html-reporter .alert .bar, .jasmine_html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; } 8 | .jasmine_html-reporter .banner { position: relative; } 9 | .jasmine_html-reporter .banner .title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -moz-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; } 10 | .jasmine_html-reporter .banner .version { margin-left: 14px; position: relative; top: 6px; } 11 | .jasmine_html-reporter .banner .duration { position: absolute; right: 14px; top: 6px; } 12 | .jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; } 13 | .jasmine_html-reporter .version { color: #aaa; } 14 | .jasmine_html-reporter .banner { margin-top: 14px; } 15 | .jasmine_html-reporter .duration { color: #aaa; float: right; } 16 | .jasmine_html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; } 17 | .jasmine_html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; } 18 | .jasmine_html-reporter .symbol-summary li.passed { font-size: 14px; } 19 | .jasmine_html-reporter .symbol-summary li.passed:before { color: #007069; content: "\02022"; } 20 | .jasmine_html-reporter .symbol-summary li.failed { line-height: 9px; } 21 | .jasmine_html-reporter .symbol-summary li.failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; } 22 | .jasmine_html-reporter .symbol-summary li.disabled { font-size: 14px; } 23 | .jasmine_html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; } 24 | .jasmine_html-reporter .symbol-summary li.pending { line-height: 17px; } 25 | .jasmine_html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; } 26 | .jasmine_html-reporter .symbol-summary li.empty { font-size: 14px; } 27 | .jasmine_html-reporter .symbol-summary li.empty:before { color: #ba9d37; content: "\02022"; } 28 | .jasmine_html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 29 | .jasmine_html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 30 | .jasmine_html-reporter .bar.failed { background-color: #ca3a11; } 31 | .jasmine_html-reporter .bar.passed { background-color: #007069; } 32 | .jasmine_html-reporter .bar.skipped { background-color: #bababa; } 33 | .jasmine_html-reporter .bar.errored { background-color: #ca3a11; } 34 | .jasmine_html-reporter .bar.menu { background-color: #fff; color: #aaa; } 35 | .jasmine_html-reporter .bar.menu a { color: #333; } 36 | .jasmine_html-reporter .bar a { color: white; } 37 | .jasmine_html-reporter.spec-list .bar.menu.failure-list, .jasmine_html-reporter.spec-list .results .failures { display: none; } 38 | .jasmine_html-reporter.failure-list .bar.menu.spec-list, .jasmine_html-reporter.failure-list .summary { display: none; } 39 | .jasmine_html-reporter .running-alert { background-color: #666; } 40 | .jasmine_html-reporter .results { margin-top: 14px; } 41 | .jasmine_html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 42 | .jasmine_html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 43 | .jasmine_html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 44 | .jasmine_html-reporter.showDetails .summary { display: none; } 45 | .jasmine_html-reporter.showDetails #details { display: block; } 46 | .jasmine_html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 47 | .jasmine_html-reporter .summary { margin-top: 14px; } 48 | .jasmine_html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; } 49 | .jasmine_html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; } 50 | .jasmine_html-reporter .summary li.passed a { color: #007069; } 51 | .jasmine_html-reporter .summary li.failed a { color: #ca3a11; } 52 | .jasmine_html-reporter .summary li.empty a { color: #ba9d37; } 53 | .jasmine_html-reporter .summary li.pending a { color: #ba9d37; } 54 | .jasmine_html-reporter .description + .suite { margin-top: 0; } 55 | .jasmine_html-reporter .suite { margin-top: 14px; } 56 | .jasmine_html-reporter .suite a { color: #333; } 57 | .jasmine_html-reporter .failures .spec-detail { margin-bottom: 28px; } 58 | .jasmine_html-reporter .failures .spec-detail .description { background-color: #ca3a11; } 59 | .jasmine_html-reporter .failures .spec-detail .description a { color: white; } 60 | .jasmine_html-reporter .result-message { padding-top: 14px; color: #333; white-space: pre; } 61 | .jasmine_html-reporter .result-message span.result { display: block; } 62 | .jasmine_html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666; border: 1px solid #ddd; background: white; white-space: pre; } 63 | -------------------------------------------------------------------------------- /app/lib/jasmine/jasmine.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2015 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | var getJasmineRequireObj = (function (jasmineGlobal) { 24 | var jasmineRequire; 25 | 26 | if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') { 27 | jasmineGlobal = window; 28 | } 29 | jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {}; 30 | 31 | function getJasmineRequire() { 32 | return jasmineRequire; 33 | } 34 | 35 | getJasmineRequire().core = function(jRequire) { 36 | var j$ = {}; 37 | 38 | jRequire.base(j$, jasmineGlobal); 39 | j$.util = jRequire.util(); 40 | j$.Any = jRequire.Any(); 41 | j$.Anything = jRequire.Anything(j$); 42 | j$.CallTracker = jRequire.CallTracker(); 43 | j$.MockDate = jRequire.MockDate(); 44 | j$.Clock = jRequire.Clock(); 45 | j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); 46 | j$.Env = jRequire.Env(j$); 47 | j$.ExceptionFormatter = jRequire.ExceptionFormatter(); 48 | j$.Expectation = jRequire.Expectation(); 49 | j$.buildExpectationResult = jRequire.buildExpectationResult(); 50 | j$.JsApiReporter = jRequire.JsApiReporter(); 51 | j$.matchersUtil = jRequire.matchersUtil(j$); 52 | j$.ObjectContaining = jRequire.ObjectContaining(j$); 53 | j$.ArrayContaining = jRequire.ArrayContaining(j$); 54 | j$.pp = jRequire.pp(j$); 55 | j$.QueueRunner = jRequire.QueueRunner(j$); 56 | j$.ReportDispatcher = jRequire.ReportDispatcher(); 57 | j$.Spec = jRequire.Spec(j$); 58 | j$.SpyRegistry = jRequire.SpyRegistry(j$); 59 | j$.SpyStrategy = jRequire.SpyStrategy(); 60 | j$.StringMatching = jRequire.StringMatching(j$); 61 | j$.Suite = jRequire.Suite(); 62 | j$.Timer = jRequire.Timer(); 63 | j$.version = jRequire.version(); 64 | 65 | j$.matchers = jRequire.requireMatchers(jRequire, j$); 66 | 67 | return j$; 68 | }; 69 | 70 | return getJasmineRequire; 71 | })(this); 72 | 73 | getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 74 | var availableMatchers = [ 75 | 'toBe', 76 | 'toBeCloseTo', 77 | 'toBeDefined', 78 | 'toBeFalsy', 79 | 'toBeGreaterThan', 80 | 'toBeLessThan', 81 | 'toBeNaN', 82 | 'toBeNull', 83 | 'toBeTruthy', 84 | 'toBeUndefined', 85 | 'toContain', 86 | 'toEqual', 87 | 'toHaveBeenCalled', 88 | 'toHaveBeenCalledWith', 89 | 'toMatch', 90 | 'toThrow', 91 | 'toThrowError' 92 | ], 93 | matchers = {}; 94 | 95 | for (var i = 0; i < availableMatchers.length; i++) { 96 | var name = availableMatchers[i]; 97 | matchers[name] = jRequire[name](j$); 98 | } 99 | 100 | return matchers; 101 | }; 102 | 103 | getJasmineRequireObj().base = function(j$, jasmineGlobal) { 104 | j$.unimplementedMethod_ = function() { 105 | throw new Error('unimplemented method'); 106 | }; 107 | 108 | j$.MAX_PRETTY_PRINT_DEPTH = 40; 109 | j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; 110 | j$.DEFAULT_TIMEOUT_INTERVAL = 5000; 111 | 112 | j$.getGlobal = function() { 113 | return jasmineGlobal; 114 | }; 115 | 116 | j$.getEnv = function(options) { 117 | var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); 118 | //jasmine. singletons in here (setTimeout blah blah). 119 | return env; 120 | }; 121 | 122 | j$.isArray_ = function(value) { 123 | return j$.isA_('Array', value); 124 | }; 125 | 126 | j$.isString_ = function(value) { 127 | return j$.isA_('String', value); 128 | }; 129 | 130 | j$.isNumber_ = function(value) { 131 | return j$.isA_('Number', value); 132 | }; 133 | 134 | j$.isA_ = function(typeName, value) { 135 | return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; 136 | }; 137 | 138 | j$.isDomNode = function(obj) { 139 | return obj.nodeType > 0; 140 | }; 141 | 142 | j$.fnNameFor = function(func) { 143 | return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; 144 | }; 145 | 146 | j$.any = function(clazz) { 147 | return new j$.Any(clazz); 148 | }; 149 | 150 | j$.anything = function() { 151 | return new j$.Anything(); 152 | }; 153 | 154 | j$.objectContaining = function(sample) { 155 | return new j$.ObjectContaining(sample); 156 | }; 157 | 158 | j$.stringMatching = function(expected) { 159 | return new j$.StringMatching(expected); 160 | }; 161 | 162 | j$.arrayContaining = function(sample) { 163 | return new j$.ArrayContaining(sample); 164 | }; 165 | 166 | j$.createSpy = function(name, originalFn) { 167 | 168 | var spyStrategy = new j$.SpyStrategy({ 169 | name: name, 170 | fn: originalFn, 171 | getSpy: function() { return spy; } 172 | }), 173 | callTracker = new j$.CallTracker(), 174 | spy = function() { 175 | var callData = { 176 | object: this, 177 | args: Array.prototype.slice.apply(arguments) 178 | }; 179 | 180 | callTracker.track(callData); 181 | var returnValue = spyStrategy.exec.apply(this, arguments); 182 | callData.returnValue = returnValue; 183 | 184 | return returnValue; 185 | }; 186 | 187 | for (var prop in originalFn) { 188 | if (prop === 'and' || prop === 'calls') { 189 | throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); 190 | } 191 | 192 | spy[prop] = originalFn[prop]; 193 | } 194 | 195 | spy.and = spyStrategy; 196 | spy.calls = callTracker; 197 | 198 | return spy; 199 | }; 200 | 201 | j$.isSpy = function(putativeSpy) { 202 | if (!putativeSpy) { 203 | return false; 204 | } 205 | return putativeSpy.and instanceof j$.SpyStrategy && 206 | putativeSpy.calls instanceof j$.CallTracker; 207 | }; 208 | 209 | j$.createSpyObj = function(baseName, methodNames) { 210 | if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) { 211 | methodNames = baseName; 212 | baseName = 'unknown'; 213 | } 214 | 215 | if (!j$.isArray_(methodNames) || methodNames.length === 0) { 216 | throw 'createSpyObj requires a non-empty array of method names to create spies for'; 217 | } 218 | var obj = {}; 219 | for (var i = 0; i < methodNames.length; i++) { 220 | obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); 221 | } 222 | return obj; 223 | }; 224 | }; 225 | 226 | getJasmineRequireObj().util = function() { 227 | 228 | var util = {}; 229 | 230 | util.inherit = function(childClass, parentClass) { 231 | var Subclass = function() { 232 | }; 233 | Subclass.prototype = parentClass.prototype; 234 | childClass.prototype = new Subclass(); 235 | }; 236 | 237 | util.htmlEscape = function(str) { 238 | if (!str) { 239 | return str; 240 | } 241 | return str.replace(/&/g, '&') 242 | .replace(//g, '>'); 244 | }; 245 | 246 | util.argsToArray = function(args) { 247 | var arrayOfArgs = []; 248 | for (var i = 0; i < args.length; i++) { 249 | arrayOfArgs.push(args[i]); 250 | } 251 | return arrayOfArgs; 252 | }; 253 | 254 | util.isUndefined = function(obj) { 255 | return obj === void 0; 256 | }; 257 | 258 | util.arrayContains = function(array, search) { 259 | var i = array.length; 260 | while (i--) { 261 | if (array[i] === search) { 262 | return true; 263 | } 264 | } 265 | return false; 266 | }; 267 | 268 | util.clone = function(obj) { 269 | if (Object.prototype.toString.apply(obj) === '[object Array]') { 270 | return obj.slice(); 271 | } 272 | 273 | var cloned = {}; 274 | for (var prop in obj) { 275 | if (obj.hasOwnProperty(prop)) { 276 | cloned[prop] = obj[prop]; 277 | } 278 | } 279 | 280 | return cloned; 281 | }; 282 | 283 | return util; 284 | }; 285 | 286 | getJasmineRequireObj().Spec = function(j$) { 287 | function Spec(attrs) { 288 | this.expectationFactory = attrs.expectationFactory; 289 | this.resultCallback = attrs.resultCallback || function() {}; 290 | this.id = attrs.id; 291 | this.description = attrs.description || ''; 292 | this.queueableFn = attrs.queueableFn; 293 | this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; 294 | this.userContext = attrs.userContext || function() { return {}; }; 295 | this.onStart = attrs.onStart || function() {}; 296 | this.getSpecName = attrs.getSpecName || function() { return ''; }; 297 | this.expectationResultFactory = attrs.expectationResultFactory || function() { }; 298 | this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; 299 | this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; 300 | 301 | if (!this.queueableFn.fn) { 302 | this.pend(); 303 | } 304 | 305 | this.result = { 306 | id: this.id, 307 | description: this.description, 308 | fullName: this.getFullName(), 309 | failedExpectations: [], 310 | passedExpectations: [], 311 | pendingReason: '' 312 | }; 313 | } 314 | 315 | Spec.prototype.addExpectationResult = function(passed, data) { 316 | var expectationResult = this.expectationResultFactory(data); 317 | if (passed) { 318 | this.result.passedExpectations.push(expectationResult); 319 | } else { 320 | this.result.failedExpectations.push(expectationResult); 321 | } 322 | }; 323 | 324 | Spec.prototype.expect = function(actual) { 325 | return this.expectationFactory(actual, this); 326 | }; 327 | 328 | Spec.prototype.execute = function(onComplete) { 329 | var self = this; 330 | 331 | this.onStart(this); 332 | 333 | if (this.markedPending || this.disabled) { 334 | complete(); 335 | return; 336 | } 337 | 338 | var fns = this.beforeAndAfterFns(); 339 | var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); 340 | 341 | this.queueRunnerFactory({ 342 | queueableFns: allFns, 343 | onException: function() { self.onException.apply(self, arguments); }, 344 | onComplete: complete, 345 | userContext: this.userContext() 346 | }); 347 | 348 | function complete() { 349 | self.result.status = self.status(); 350 | self.resultCallback(self.result); 351 | 352 | if (onComplete) { 353 | onComplete(); 354 | } 355 | } 356 | }; 357 | 358 | Spec.prototype.onException = function onException(e) { 359 | if (Spec.isPendingSpecException(e)) { 360 | this.pend(extractCustomPendingMessage(e)); 361 | return; 362 | } 363 | 364 | this.addExpectationResult(false, { 365 | matcherName: '', 366 | passed: false, 367 | expected: '', 368 | actual: '', 369 | error: e 370 | }); 371 | }; 372 | 373 | Spec.prototype.disable = function() { 374 | this.disabled = true; 375 | }; 376 | 377 | Spec.prototype.pend = function(message) { 378 | this.markedPending = true; 379 | if (message) { 380 | this.result.pendingReason = message; 381 | } 382 | }; 383 | 384 | Spec.prototype.status = function() { 385 | if (this.disabled) { 386 | return 'disabled'; 387 | } 388 | 389 | if (this.markedPending) { 390 | return 'pending'; 391 | } 392 | 393 | if (this.result.failedExpectations.length > 0) { 394 | return 'failed'; 395 | } else { 396 | return 'passed'; 397 | } 398 | }; 399 | 400 | Spec.prototype.isExecutable = function() { 401 | return !this.disabled && !this.markedPending; 402 | }; 403 | 404 | Spec.prototype.getFullName = function() { 405 | return this.getSpecName(this); 406 | }; 407 | 408 | var extractCustomPendingMessage = function(e) { 409 | var fullMessage = e.toString(), 410 | boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), 411 | boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; 412 | 413 | return fullMessage.substr(boilerplateEnd); 414 | }; 415 | 416 | Spec.pendingSpecExceptionMessage = '=> marked Pending'; 417 | 418 | Spec.isPendingSpecException = function(e) { 419 | return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); 420 | }; 421 | 422 | return Spec; 423 | }; 424 | 425 | if (typeof window == void 0 && typeof exports == 'object') { 426 | exports.Spec = jasmineRequire.Spec; 427 | } 428 | 429 | getJasmineRequireObj().Env = function(j$) { 430 | function Env(options) { 431 | options = options || {}; 432 | 433 | var self = this; 434 | var global = options.global || j$.getGlobal(); 435 | 436 | var totalSpecsDefined = 0; 437 | 438 | var catchExceptions = true; 439 | 440 | var realSetTimeout = j$.getGlobal().setTimeout; 441 | var realClearTimeout = j$.getGlobal().clearTimeout; 442 | this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global)); 443 | 444 | var runnableLookupTable = {}; 445 | var runnableResources = {}; 446 | 447 | var currentSpec = null; 448 | var currentlyExecutingSuites = []; 449 | var currentDeclarationSuite = null; 450 | 451 | var currentSuite = function() { 452 | return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; 453 | }; 454 | 455 | var currentRunnable = function() { 456 | return currentSpec || currentSuite(); 457 | }; 458 | 459 | var reporter = new j$.ReportDispatcher([ 460 | 'jasmineStarted', 461 | 'jasmineDone', 462 | 'suiteStarted', 463 | 'suiteDone', 464 | 'specStarted', 465 | 'specDone' 466 | ]); 467 | 468 | this.specFilter = function() { 469 | return true; 470 | }; 471 | 472 | this.addCustomEqualityTester = function(tester) { 473 | if(!currentRunnable()) { 474 | throw new Error('Custom Equalities must be added in a before function or a spec'); 475 | } 476 | runnableResources[currentRunnable().id].customEqualityTesters.push(tester); 477 | }; 478 | 479 | this.addMatchers = function(matchersToAdd) { 480 | if(!currentRunnable()) { 481 | throw new Error('Matchers must be added in a before function or a spec'); 482 | } 483 | var customMatchers = runnableResources[currentRunnable().id].customMatchers; 484 | for (var matcherName in matchersToAdd) { 485 | customMatchers[matcherName] = matchersToAdd[matcherName]; 486 | } 487 | }; 488 | 489 | j$.Expectation.addCoreMatchers(j$.matchers); 490 | 491 | var nextSpecId = 0; 492 | var getNextSpecId = function() { 493 | return 'spec' + nextSpecId++; 494 | }; 495 | 496 | var nextSuiteId = 0; 497 | var getNextSuiteId = function() { 498 | return 'suite' + nextSuiteId++; 499 | }; 500 | 501 | var expectationFactory = function(actual, spec) { 502 | return j$.Expectation.Factory({ 503 | util: j$.matchersUtil, 504 | customEqualityTesters: runnableResources[spec.id].customEqualityTesters, 505 | customMatchers: runnableResources[spec.id].customMatchers, 506 | actual: actual, 507 | addExpectationResult: addExpectationResult 508 | }); 509 | 510 | function addExpectationResult(passed, result) { 511 | return spec.addExpectationResult(passed, result); 512 | } 513 | }; 514 | 515 | var defaultResourcesForRunnable = function(id, parentRunnableId) { 516 | var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; 517 | 518 | if(runnableResources[parentRunnableId]){ 519 | resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); 520 | resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); 521 | } 522 | 523 | runnableResources[id] = resources; 524 | }; 525 | 526 | var clearResourcesForRunnable = function(id) { 527 | spyRegistry.clearSpies(); 528 | delete runnableResources[id]; 529 | }; 530 | 531 | var beforeAndAfterFns = function(suite, runnablesExplictlySet) { 532 | return function() { 533 | var befores = [], 534 | afters = [], 535 | beforeAlls = [], 536 | afterAlls = []; 537 | 538 | while(suite) { 539 | befores = befores.concat(suite.beforeFns); 540 | afters = afters.concat(suite.afterFns); 541 | 542 | if (runnablesExplictlySet()) { 543 | beforeAlls = beforeAlls.concat(suite.beforeAllFns); 544 | afterAlls = afterAlls.concat(suite.afterAllFns); 545 | } 546 | 547 | suite = suite.parentSuite; 548 | } 549 | return { 550 | befores: beforeAlls.reverse().concat(befores.reverse()), 551 | afters: afters.concat(afterAlls) 552 | }; 553 | }; 554 | }; 555 | 556 | var getSpecName = function(spec, suite) { 557 | return suite.getFullName() + ' ' + spec.description; 558 | }; 559 | 560 | // TODO: we may just be able to pass in the fn instead of wrapping here 561 | var buildExpectationResult = j$.buildExpectationResult, 562 | exceptionFormatter = new j$.ExceptionFormatter(), 563 | expectationResultFactory = function(attrs) { 564 | attrs.messageFormatter = exceptionFormatter.message; 565 | attrs.stackFormatter = exceptionFormatter.stack; 566 | 567 | return buildExpectationResult(attrs); 568 | }; 569 | 570 | // TODO: fix this naming, and here's where the value comes in 571 | this.catchExceptions = function(value) { 572 | catchExceptions = !!value; 573 | return catchExceptions; 574 | }; 575 | 576 | this.catchingExceptions = function() { 577 | return catchExceptions; 578 | }; 579 | 580 | var maximumSpecCallbackDepth = 20; 581 | var currentSpecCallbackDepth = 0; 582 | 583 | function clearStack(fn) { 584 | currentSpecCallbackDepth++; 585 | if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { 586 | currentSpecCallbackDepth = 0; 587 | realSetTimeout(fn, 0); 588 | } else { 589 | fn(); 590 | } 591 | } 592 | 593 | var catchException = function(e) { 594 | return j$.Spec.isPendingSpecException(e) || catchExceptions; 595 | }; 596 | 597 | var queueRunnerFactory = function(options) { 598 | options.catchException = catchException; 599 | options.clearStack = options.clearStack || clearStack; 600 | options.timer = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; 601 | options.fail = self.fail; 602 | 603 | new j$.QueueRunner(options).execute(); 604 | }; 605 | 606 | var topSuite = new j$.Suite({ 607 | env: this, 608 | id: getNextSuiteId(), 609 | description: 'Jasmine__TopLevel__Suite', 610 | queueRunner: queueRunnerFactory 611 | }); 612 | runnableLookupTable[topSuite.id] = topSuite; 613 | defaultResourcesForRunnable(topSuite.id); 614 | currentDeclarationSuite = topSuite; 615 | 616 | this.topSuite = function() { 617 | return topSuite; 618 | }; 619 | 620 | this.execute = function(runnablesToRun) { 621 | if(runnablesToRun) { 622 | runnablesExplictlySet = true; 623 | } else if (focusedRunnables.length) { 624 | runnablesExplictlySet = true; 625 | runnablesToRun = focusedRunnables; 626 | } else { 627 | runnablesToRun = [topSuite.id]; 628 | } 629 | 630 | var allFns = []; 631 | for(var i = 0; i < runnablesToRun.length; i++) { 632 | var runnable = runnableLookupTable[runnablesToRun[i]]; 633 | allFns.push((function(runnable) { return { fn: function(done) { runnable.execute(done); } }; })(runnable)); 634 | } 635 | 636 | reporter.jasmineStarted({ 637 | totalSpecsDefined: totalSpecsDefined 638 | }); 639 | 640 | queueRunnerFactory({queueableFns: allFns, onComplete: reporter.jasmineDone}); 641 | }; 642 | 643 | this.addReporter = function(reporterToAdd) { 644 | reporter.addReporter(reporterToAdd); 645 | }; 646 | 647 | var spyRegistry = new j$.SpyRegistry({currentSpies: function() { 648 | if(!currentRunnable()) { 649 | throw new Error('Spies must be created in a before function or a spec'); 650 | } 651 | return runnableResources[currentRunnable().id].spies; 652 | }}); 653 | 654 | this.spyOn = function() { 655 | return spyRegistry.spyOn.apply(spyRegistry, arguments); 656 | }; 657 | 658 | var suiteFactory = function(description) { 659 | var suite = new j$.Suite({ 660 | env: self, 661 | id: getNextSuiteId(), 662 | description: description, 663 | parentSuite: currentDeclarationSuite, 664 | queueRunner: queueRunnerFactory, 665 | onStart: suiteStarted, 666 | expectationFactory: expectationFactory, 667 | expectationResultFactory: expectationResultFactory, 668 | runnablesExplictlySetGetter: runnablesExplictlySetGetter, 669 | resultCallback: function(attrs) { 670 | if (!suite.disabled) { 671 | clearResourcesForRunnable(suite.id); 672 | } 673 | currentlyExecutingSuites.pop(); 674 | reporter.suiteDone(attrs); 675 | } 676 | }); 677 | 678 | runnableLookupTable[suite.id] = suite; 679 | return suite; 680 | 681 | function suiteStarted(suite) { 682 | currentlyExecutingSuites.push(suite); 683 | defaultResourcesForRunnable(suite.id, suite.parentSuite.id); 684 | reporter.suiteStarted(suite.result); 685 | } 686 | }; 687 | 688 | this.describe = function(description, specDefinitions) { 689 | var suite = suiteFactory(description); 690 | addSpecsToSuite(suite, specDefinitions); 691 | return suite; 692 | }; 693 | 694 | this.xdescribe = function(description, specDefinitions) { 695 | var suite = this.describe(description, specDefinitions); 696 | suite.disable(); 697 | return suite; 698 | }; 699 | 700 | var focusedRunnables = []; 701 | 702 | this.fdescribe = function(description, specDefinitions) { 703 | var suite = suiteFactory(description); 704 | suite.isFocused = true; 705 | 706 | focusedRunnables.push(suite.id); 707 | unfocusAncestor(); 708 | addSpecsToSuite(suite, specDefinitions); 709 | 710 | return suite; 711 | }; 712 | 713 | function addSpecsToSuite(suite, specDefinitions) { 714 | var parentSuite = currentDeclarationSuite; 715 | parentSuite.addChild(suite); 716 | currentDeclarationSuite = suite; 717 | 718 | var declarationError = null; 719 | try { 720 | specDefinitions.call(suite); 721 | } catch (e) { 722 | declarationError = e; 723 | } 724 | 725 | if (declarationError) { 726 | self.it('encountered a declaration exception', function() { 727 | throw declarationError; 728 | }); 729 | } 730 | 731 | currentDeclarationSuite = parentSuite; 732 | } 733 | 734 | function findFocusedAncestor(suite) { 735 | while (suite) { 736 | if (suite.isFocused) { 737 | return suite.id; 738 | } 739 | suite = suite.parentSuite; 740 | } 741 | 742 | return null; 743 | } 744 | 745 | function unfocusAncestor() { 746 | var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); 747 | if (focusedAncestor) { 748 | for (var i = 0; i < focusedRunnables.length; i++) { 749 | if (focusedRunnables[i] === focusedAncestor) { 750 | focusedRunnables.splice(i, 1); 751 | break; 752 | } 753 | } 754 | } 755 | } 756 | 757 | var runnablesExplictlySet = false; 758 | 759 | var runnablesExplictlySetGetter = function(){ 760 | return runnablesExplictlySet; 761 | }; 762 | 763 | var specFactory = function(description, fn, suite, timeout) { 764 | totalSpecsDefined++; 765 | var spec = new j$.Spec({ 766 | id: getNextSpecId(), 767 | beforeAndAfterFns: beforeAndAfterFns(suite, runnablesExplictlySetGetter), 768 | expectationFactory: expectationFactory, 769 | resultCallback: specResultCallback, 770 | getSpecName: function(spec) { 771 | return getSpecName(spec, suite); 772 | }, 773 | onStart: specStarted, 774 | description: description, 775 | expectationResultFactory: expectationResultFactory, 776 | queueRunnerFactory: queueRunnerFactory, 777 | userContext: function() { return suite.clonedSharedUserContext(); }, 778 | queueableFn: { 779 | fn: fn, 780 | timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } 781 | } 782 | }); 783 | 784 | runnableLookupTable[spec.id] = spec; 785 | 786 | if (!self.specFilter(spec)) { 787 | spec.disable(); 788 | } 789 | 790 | return spec; 791 | 792 | function specResultCallback(result) { 793 | clearResourcesForRunnable(spec.id); 794 | currentSpec = null; 795 | reporter.specDone(result); 796 | } 797 | 798 | function specStarted(spec) { 799 | currentSpec = spec; 800 | defaultResourcesForRunnable(spec.id, suite.id); 801 | reporter.specStarted(spec.result); 802 | } 803 | }; 804 | 805 | this.it = function(description, fn, timeout) { 806 | var spec = specFactory(description, fn, currentDeclarationSuite, timeout); 807 | currentDeclarationSuite.addChild(spec); 808 | return spec; 809 | }; 810 | 811 | this.xit = function() { 812 | var spec = this.it.apply(this, arguments); 813 | spec.pend(); 814 | return spec; 815 | }; 816 | 817 | this.fit = function(){ 818 | var spec = this.it.apply(this, arguments); 819 | 820 | focusedRunnables.push(spec.id); 821 | unfocusAncestor(); 822 | return spec; 823 | }; 824 | 825 | this.expect = function(actual) { 826 | if (!currentRunnable()) { 827 | throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); 828 | } 829 | 830 | return currentRunnable().expect(actual); 831 | }; 832 | 833 | this.beforeEach = function(beforeEachFunction, timeout) { 834 | currentDeclarationSuite.beforeEach({ 835 | fn: beforeEachFunction, 836 | timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } 837 | }); 838 | }; 839 | 840 | this.beforeAll = function(beforeAllFunction, timeout) { 841 | currentDeclarationSuite.beforeAll({ 842 | fn: beforeAllFunction, 843 | timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } 844 | }); 845 | }; 846 | 847 | this.afterEach = function(afterEachFunction, timeout) { 848 | currentDeclarationSuite.afterEach({ 849 | fn: afterEachFunction, 850 | timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } 851 | }); 852 | }; 853 | 854 | this.afterAll = function(afterAllFunction, timeout) { 855 | currentDeclarationSuite.afterAll({ 856 | fn: afterAllFunction, 857 | timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } 858 | }); 859 | }; 860 | 861 | this.pending = function(message) { 862 | var fullMessage = j$.Spec.pendingSpecExceptionMessage; 863 | if(message) { 864 | fullMessage += message; 865 | } 866 | throw fullMessage; 867 | }; 868 | 869 | this.fail = function(error) { 870 | var message = 'Failed'; 871 | if (error) { 872 | message += ': '; 873 | message += error.message || error; 874 | } 875 | 876 | currentRunnable().addExpectationResult(false, { 877 | matcherName: '', 878 | passed: false, 879 | expected: '', 880 | actual: '', 881 | message: message, 882 | error: error && error.message ? error : null 883 | }); 884 | }; 885 | } 886 | 887 | return Env; 888 | }; 889 | 890 | getJasmineRequireObj().JsApiReporter = function() { 891 | 892 | var noopTimer = { 893 | start: function(){}, 894 | elapsed: function(){ return 0; } 895 | }; 896 | 897 | function JsApiReporter(options) { 898 | var timer = options.timer || noopTimer, 899 | status = 'loaded'; 900 | 901 | this.started = false; 902 | this.finished = false; 903 | 904 | this.jasmineStarted = function() { 905 | this.started = true; 906 | status = 'started'; 907 | timer.start(); 908 | }; 909 | 910 | var executionTime; 911 | 912 | this.jasmineDone = function() { 913 | this.finished = true; 914 | executionTime = timer.elapsed(); 915 | status = 'done'; 916 | }; 917 | 918 | this.status = function() { 919 | return status; 920 | }; 921 | 922 | var suites = [], 923 | suites_hash = {}; 924 | 925 | this.suiteStarted = function(result) { 926 | suites_hash[result.id] = result; 927 | }; 928 | 929 | this.suiteDone = function(result) { 930 | storeSuite(result); 931 | }; 932 | 933 | this.suiteResults = function(index, length) { 934 | return suites.slice(index, index + length); 935 | }; 936 | 937 | function storeSuite(result) { 938 | suites.push(result); 939 | suites_hash[result.id] = result; 940 | } 941 | 942 | this.suites = function() { 943 | return suites_hash; 944 | }; 945 | 946 | var specs = []; 947 | 948 | this.specDone = function(result) { 949 | specs.push(result); 950 | }; 951 | 952 | this.specResults = function(index, length) { 953 | return specs.slice(index, index + length); 954 | }; 955 | 956 | this.specs = function() { 957 | return specs; 958 | }; 959 | 960 | this.executionTime = function() { 961 | return executionTime; 962 | }; 963 | 964 | } 965 | 966 | return JsApiReporter; 967 | }; 968 | 969 | getJasmineRequireObj().CallTracker = function() { 970 | 971 | function CallTracker() { 972 | var calls = []; 973 | 974 | this.track = function(context) { 975 | calls.push(context); 976 | }; 977 | 978 | this.any = function() { 979 | return !!calls.length; 980 | }; 981 | 982 | this.count = function() { 983 | return calls.length; 984 | }; 985 | 986 | this.argsFor = function(index) { 987 | var call = calls[index]; 988 | return call ? call.args : []; 989 | }; 990 | 991 | this.all = function() { 992 | return calls; 993 | }; 994 | 995 | this.allArgs = function() { 996 | var callArgs = []; 997 | for(var i = 0; i < calls.length; i++){ 998 | callArgs.push(calls[i].args); 999 | } 1000 | 1001 | return callArgs; 1002 | }; 1003 | 1004 | this.first = function() { 1005 | return calls[0]; 1006 | }; 1007 | 1008 | this.mostRecent = function() { 1009 | return calls[calls.length - 1]; 1010 | }; 1011 | 1012 | this.reset = function() { 1013 | calls = []; 1014 | }; 1015 | } 1016 | 1017 | return CallTracker; 1018 | }; 1019 | 1020 | getJasmineRequireObj().Clock = function() { 1021 | function Clock(global, delayedFunctionScheduler, mockDate) { 1022 | var self = this, 1023 | realTimingFunctions = { 1024 | setTimeout: global.setTimeout, 1025 | clearTimeout: global.clearTimeout, 1026 | setInterval: global.setInterval, 1027 | clearInterval: global.clearInterval 1028 | }, 1029 | fakeTimingFunctions = { 1030 | setTimeout: setTimeout, 1031 | clearTimeout: clearTimeout, 1032 | setInterval: setInterval, 1033 | clearInterval: clearInterval 1034 | }, 1035 | installed = false, 1036 | timer; 1037 | 1038 | 1039 | self.install = function() { 1040 | replace(global, fakeTimingFunctions); 1041 | timer = fakeTimingFunctions; 1042 | installed = true; 1043 | 1044 | return self; 1045 | }; 1046 | 1047 | self.uninstall = function() { 1048 | delayedFunctionScheduler.reset(); 1049 | mockDate.uninstall(); 1050 | replace(global, realTimingFunctions); 1051 | 1052 | timer = realTimingFunctions; 1053 | installed = false; 1054 | }; 1055 | 1056 | self.mockDate = function(initialDate) { 1057 | mockDate.install(initialDate); 1058 | }; 1059 | 1060 | self.setTimeout = function(fn, delay, params) { 1061 | if (legacyIE()) { 1062 | if (arguments.length > 2) { 1063 | throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); 1064 | } 1065 | return timer.setTimeout(fn, delay); 1066 | } 1067 | return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); 1068 | }; 1069 | 1070 | self.setInterval = function(fn, delay, params) { 1071 | if (legacyIE()) { 1072 | if (arguments.length > 2) { 1073 | throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); 1074 | } 1075 | return timer.setInterval(fn, delay); 1076 | } 1077 | return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); 1078 | }; 1079 | 1080 | self.clearTimeout = function(id) { 1081 | return Function.prototype.call.apply(timer.clearTimeout, [global, id]); 1082 | }; 1083 | 1084 | self.clearInterval = function(id) { 1085 | return Function.prototype.call.apply(timer.clearInterval, [global, id]); 1086 | }; 1087 | 1088 | self.tick = function(millis) { 1089 | if (installed) { 1090 | mockDate.tick(millis); 1091 | delayedFunctionScheduler.tick(millis); 1092 | } else { 1093 | throw new Error('Mock clock is not installed, use jasmine.clock().install()'); 1094 | } 1095 | }; 1096 | 1097 | return self; 1098 | 1099 | function legacyIE() { 1100 | //if these methods are polyfilled, apply will be present 1101 | return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; 1102 | } 1103 | 1104 | function replace(dest, source) { 1105 | for (var prop in source) { 1106 | dest[prop] = source[prop]; 1107 | } 1108 | } 1109 | 1110 | function setTimeout(fn, delay) { 1111 | return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); 1112 | } 1113 | 1114 | function clearTimeout(id) { 1115 | return delayedFunctionScheduler.removeFunctionWithId(id); 1116 | } 1117 | 1118 | function setInterval(fn, interval) { 1119 | return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); 1120 | } 1121 | 1122 | function clearInterval(id) { 1123 | return delayedFunctionScheduler.removeFunctionWithId(id); 1124 | } 1125 | 1126 | function argSlice(argsObj, n) { 1127 | return Array.prototype.slice.call(argsObj, n); 1128 | } 1129 | } 1130 | 1131 | return Clock; 1132 | }; 1133 | 1134 | getJasmineRequireObj().DelayedFunctionScheduler = function() { 1135 | function DelayedFunctionScheduler() { 1136 | var self = this; 1137 | var scheduledLookup = []; 1138 | var scheduledFunctions = {}; 1139 | var currentTime = 0; 1140 | var delayedFnCount = 0; 1141 | 1142 | self.tick = function(millis) { 1143 | millis = millis || 0; 1144 | var endTime = currentTime + millis; 1145 | 1146 | runScheduledFunctions(endTime); 1147 | currentTime = endTime; 1148 | }; 1149 | 1150 | self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { 1151 | var f; 1152 | if (typeof(funcToCall) === 'string') { 1153 | /* jshint evil: true */ 1154 | f = function() { return eval(funcToCall); }; 1155 | /* jshint evil: false */ 1156 | } else { 1157 | f = funcToCall; 1158 | } 1159 | 1160 | millis = millis || 0; 1161 | timeoutKey = timeoutKey || ++delayedFnCount; 1162 | runAtMillis = runAtMillis || (currentTime + millis); 1163 | 1164 | var funcToSchedule = { 1165 | runAtMillis: runAtMillis, 1166 | funcToCall: f, 1167 | recurring: recurring, 1168 | params: params, 1169 | timeoutKey: timeoutKey, 1170 | millis: millis 1171 | }; 1172 | 1173 | if (runAtMillis in scheduledFunctions) { 1174 | scheduledFunctions[runAtMillis].push(funcToSchedule); 1175 | } else { 1176 | scheduledFunctions[runAtMillis] = [funcToSchedule]; 1177 | scheduledLookup.push(runAtMillis); 1178 | scheduledLookup.sort(function (a, b) { 1179 | return a - b; 1180 | }); 1181 | } 1182 | 1183 | return timeoutKey; 1184 | }; 1185 | 1186 | self.removeFunctionWithId = function(timeoutKey) { 1187 | for (var runAtMillis in scheduledFunctions) { 1188 | var funcs = scheduledFunctions[runAtMillis]; 1189 | var i = indexOfFirstToPass(funcs, function (func) { 1190 | return func.timeoutKey === timeoutKey; 1191 | }); 1192 | 1193 | if (i > -1) { 1194 | if (funcs.length === 1) { 1195 | delete scheduledFunctions[runAtMillis]; 1196 | deleteFromLookup(runAtMillis); 1197 | } else { 1198 | funcs.splice(i, 1); 1199 | } 1200 | 1201 | // intervals get rescheduled when executed, so there's never more 1202 | // than a single scheduled function with a given timeoutKey 1203 | break; 1204 | } 1205 | } 1206 | }; 1207 | 1208 | self.reset = function() { 1209 | currentTime = 0; 1210 | scheduledLookup = []; 1211 | scheduledFunctions = {}; 1212 | delayedFnCount = 0; 1213 | }; 1214 | 1215 | return self; 1216 | 1217 | function indexOfFirstToPass(array, testFn) { 1218 | var index = -1; 1219 | 1220 | for (var i = 0; i < array.length; ++i) { 1221 | if (testFn(array[i])) { 1222 | index = i; 1223 | break; 1224 | } 1225 | } 1226 | 1227 | return index; 1228 | } 1229 | 1230 | function deleteFromLookup(key) { 1231 | var value = Number(key); 1232 | var i = indexOfFirstToPass(scheduledLookup, function (millis) { 1233 | return millis === value; 1234 | }); 1235 | 1236 | if (i > -1) { 1237 | scheduledLookup.splice(i, 1); 1238 | } 1239 | } 1240 | 1241 | function reschedule(scheduledFn) { 1242 | self.scheduleFunction(scheduledFn.funcToCall, 1243 | scheduledFn.millis, 1244 | scheduledFn.params, 1245 | true, 1246 | scheduledFn.timeoutKey, 1247 | scheduledFn.runAtMillis + scheduledFn.millis); 1248 | } 1249 | 1250 | function forEachFunction(funcsToRun, callback) { 1251 | for (var i = 0; i < funcsToRun.length; ++i) { 1252 | callback(funcsToRun[i]); 1253 | } 1254 | } 1255 | 1256 | function runScheduledFunctions(endTime) { 1257 | if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { 1258 | return; 1259 | } 1260 | 1261 | do { 1262 | currentTime = scheduledLookup.shift(); 1263 | 1264 | var funcsToRun = scheduledFunctions[currentTime]; 1265 | delete scheduledFunctions[currentTime]; 1266 | 1267 | forEachFunction(funcsToRun, function(funcToRun) { 1268 | if (funcToRun.recurring) { 1269 | reschedule(funcToRun); 1270 | } 1271 | }); 1272 | 1273 | forEachFunction(funcsToRun, function(funcToRun) { 1274 | funcToRun.funcToCall.apply(null, funcToRun.params || []); 1275 | }); 1276 | } while (scheduledLookup.length > 0 && 1277 | // checking first if we're out of time prevents setTimeout(0) 1278 | // scheduled in a funcToRun from forcing an extra iteration 1279 | currentTime !== endTime && 1280 | scheduledLookup[0] <= endTime); 1281 | } 1282 | } 1283 | 1284 | return DelayedFunctionScheduler; 1285 | }; 1286 | 1287 | getJasmineRequireObj().ExceptionFormatter = function() { 1288 | function ExceptionFormatter() { 1289 | this.message = function(error) { 1290 | var message = ''; 1291 | 1292 | if (error.name && error.message) { 1293 | message += error.name + ': ' + error.message; 1294 | } else { 1295 | message += error.toString() + ' thrown'; 1296 | } 1297 | 1298 | if (error.fileName || error.sourceURL) { 1299 | message += ' in ' + (error.fileName || error.sourceURL); 1300 | } 1301 | 1302 | if (error.line || error.lineNumber) { 1303 | message += ' (line ' + (error.line || error.lineNumber) + ')'; 1304 | } 1305 | 1306 | return message; 1307 | }; 1308 | 1309 | this.stack = function(error) { 1310 | return error ? error.stack : null; 1311 | }; 1312 | } 1313 | 1314 | return ExceptionFormatter; 1315 | }; 1316 | 1317 | getJasmineRequireObj().Expectation = function() { 1318 | 1319 | function Expectation(options) { 1320 | this.util = options.util || { buildFailureMessage: function() {} }; 1321 | this.customEqualityTesters = options.customEqualityTesters || []; 1322 | this.actual = options.actual; 1323 | this.addExpectationResult = options.addExpectationResult || function(){}; 1324 | this.isNot = options.isNot; 1325 | 1326 | var customMatchers = options.customMatchers || {}; 1327 | for (var matcherName in customMatchers) { 1328 | this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); 1329 | } 1330 | } 1331 | 1332 | Expectation.prototype.wrapCompare = function(name, matcherFactory) { 1333 | return function() { 1334 | var args = Array.prototype.slice.call(arguments, 0), 1335 | expected = args.slice(0), 1336 | message = ''; 1337 | 1338 | args.unshift(this.actual); 1339 | 1340 | var matcher = matcherFactory(this.util, this.customEqualityTesters), 1341 | matcherCompare = matcher.compare; 1342 | 1343 | function defaultNegativeCompare() { 1344 | var result = matcher.compare.apply(null, args); 1345 | result.pass = !result.pass; 1346 | return result; 1347 | } 1348 | 1349 | if (this.isNot) { 1350 | matcherCompare = matcher.negativeCompare || defaultNegativeCompare; 1351 | } 1352 | 1353 | var result = matcherCompare.apply(null, args); 1354 | 1355 | if (!result.pass) { 1356 | if (!result.message) { 1357 | args.unshift(this.isNot); 1358 | args.unshift(name); 1359 | message = this.util.buildFailureMessage.apply(null, args); 1360 | } else { 1361 | if (Object.prototype.toString.apply(result.message) === '[object Function]') { 1362 | message = result.message(); 1363 | } else { 1364 | message = result.message; 1365 | } 1366 | } 1367 | } 1368 | 1369 | if (expected.length == 1) { 1370 | expected = expected[0]; 1371 | } 1372 | 1373 | // TODO: how many of these params are needed? 1374 | this.addExpectationResult( 1375 | result.pass, 1376 | { 1377 | matcherName: name, 1378 | passed: result.pass, 1379 | message: message, 1380 | actual: this.actual, 1381 | expected: expected // TODO: this may need to be arrayified/sliced 1382 | } 1383 | ); 1384 | }; 1385 | }; 1386 | 1387 | Expectation.addCoreMatchers = function(matchers) { 1388 | var prototype = Expectation.prototype; 1389 | for (var matcherName in matchers) { 1390 | var matcher = matchers[matcherName]; 1391 | prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); 1392 | } 1393 | }; 1394 | 1395 | Expectation.Factory = function(options) { 1396 | options = options || {}; 1397 | 1398 | var expect = new Expectation(options); 1399 | 1400 | // TODO: this would be nice as its own Object - NegativeExpectation 1401 | // TODO: copy instead of mutate options 1402 | options.isNot = true; 1403 | expect.not = new Expectation(options); 1404 | 1405 | return expect; 1406 | }; 1407 | 1408 | return Expectation; 1409 | }; 1410 | 1411 | //TODO: expectation result may make more sense as a presentation of an expectation. 1412 | getJasmineRequireObj().buildExpectationResult = function() { 1413 | function buildExpectationResult(options) { 1414 | var messageFormatter = options.messageFormatter || function() {}, 1415 | stackFormatter = options.stackFormatter || function() {}; 1416 | 1417 | var result = { 1418 | matcherName: options.matcherName, 1419 | message: message(), 1420 | stack: stack(), 1421 | passed: options.passed 1422 | }; 1423 | 1424 | if(!result.passed) { 1425 | result.expected = options.expected; 1426 | result.actual = options.actual; 1427 | } 1428 | 1429 | return result; 1430 | 1431 | function message() { 1432 | if (options.passed) { 1433 | return 'Passed.'; 1434 | } else if (options.message) { 1435 | return options.message; 1436 | } else if (options.error) { 1437 | return messageFormatter(options.error); 1438 | } 1439 | return ''; 1440 | } 1441 | 1442 | function stack() { 1443 | if (options.passed) { 1444 | return ''; 1445 | } 1446 | 1447 | var error = options.error; 1448 | if (!error) { 1449 | try { 1450 | throw new Error(message()); 1451 | } catch (e) { 1452 | error = e; 1453 | } 1454 | } 1455 | return stackFormatter(error); 1456 | } 1457 | } 1458 | 1459 | return buildExpectationResult; 1460 | }; 1461 | 1462 | getJasmineRequireObj().MockDate = function() { 1463 | function MockDate(global) { 1464 | var self = this; 1465 | var currentTime = 0; 1466 | 1467 | if (!global || !global.Date) { 1468 | self.install = function() {}; 1469 | self.tick = function() {}; 1470 | self.uninstall = function() {}; 1471 | return self; 1472 | } 1473 | 1474 | var GlobalDate = global.Date; 1475 | 1476 | self.install = function(mockDate) { 1477 | if (mockDate instanceof GlobalDate) { 1478 | currentTime = mockDate.getTime(); 1479 | } else { 1480 | currentTime = new GlobalDate().getTime(); 1481 | } 1482 | 1483 | global.Date = FakeDate; 1484 | }; 1485 | 1486 | self.tick = function(millis) { 1487 | millis = millis || 0; 1488 | currentTime = currentTime + millis; 1489 | }; 1490 | 1491 | self.uninstall = function() { 1492 | currentTime = 0; 1493 | global.Date = GlobalDate; 1494 | }; 1495 | 1496 | createDateProperties(); 1497 | 1498 | return self; 1499 | 1500 | function FakeDate() { 1501 | switch(arguments.length) { 1502 | case 0: 1503 | return new GlobalDate(currentTime); 1504 | case 1: 1505 | return new GlobalDate(arguments[0]); 1506 | case 2: 1507 | return new GlobalDate(arguments[0], arguments[1]); 1508 | case 3: 1509 | return new GlobalDate(arguments[0], arguments[1], arguments[2]); 1510 | case 4: 1511 | return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]); 1512 | case 5: 1513 | return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], 1514 | arguments[4]); 1515 | case 6: 1516 | return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], 1517 | arguments[4], arguments[5]); 1518 | default: 1519 | return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], 1520 | arguments[4], arguments[5], arguments[6]); 1521 | } 1522 | } 1523 | 1524 | function createDateProperties() { 1525 | FakeDate.prototype = GlobalDate.prototype; 1526 | 1527 | FakeDate.now = function() { 1528 | if (GlobalDate.now) { 1529 | return currentTime; 1530 | } else { 1531 | throw new Error('Browser does not support Date.now()'); 1532 | } 1533 | }; 1534 | 1535 | FakeDate.toSource = GlobalDate.toSource; 1536 | FakeDate.toString = GlobalDate.toString; 1537 | FakeDate.parse = GlobalDate.parse; 1538 | FakeDate.UTC = GlobalDate.UTC; 1539 | } 1540 | } 1541 | 1542 | return MockDate; 1543 | }; 1544 | 1545 | getJasmineRequireObj().pp = function(j$) { 1546 | 1547 | function PrettyPrinter() { 1548 | this.ppNestLevel_ = 0; 1549 | this.seen = []; 1550 | } 1551 | 1552 | PrettyPrinter.prototype.format = function(value) { 1553 | this.ppNestLevel_++; 1554 | try { 1555 | if (j$.util.isUndefined(value)) { 1556 | this.emitScalar('undefined'); 1557 | } else if (value === null) { 1558 | this.emitScalar('null'); 1559 | } else if (value === 0 && 1/value === -Infinity) { 1560 | this.emitScalar('-0'); 1561 | } else if (value === j$.getGlobal()) { 1562 | this.emitScalar(''); 1563 | } else if (value.jasmineToString) { 1564 | this.emitScalar(value.jasmineToString()); 1565 | } else if (typeof value === 'string') { 1566 | this.emitString(value); 1567 | } else if (j$.isSpy(value)) { 1568 | this.emitScalar('spy on ' + value.and.identity()); 1569 | } else if (value instanceof RegExp) { 1570 | this.emitScalar(value.toString()); 1571 | } else if (typeof value === 'function') { 1572 | this.emitScalar('Function'); 1573 | } else if (typeof value.nodeType === 'number') { 1574 | this.emitScalar('HTMLNode'); 1575 | } else if (value instanceof Date) { 1576 | this.emitScalar('Date(' + value + ')'); 1577 | } else if (j$.util.arrayContains(this.seen, value)) { 1578 | this.emitScalar(''); 1579 | } else if (j$.isArray_(value) || j$.isA_('Object', value)) { 1580 | this.seen.push(value); 1581 | if (j$.isArray_(value)) { 1582 | this.emitArray(value); 1583 | } else { 1584 | this.emitObject(value); 1585 | } 1586 | this.seen.pop(); 1587 | } else { 1588 | this.emitScalar(value.toString()); 1589 | } 1590 | } finally { 1591 | this.ppNestLevel_--; 1592 | } 1593 | }; 1594 | 1595 | PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1596 | for (var property in obj) { 1597 | if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } 1598 | fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && 1599 | obj.__lookupGetter__(property) !== null) : false); 1600 | } 1601 | }; 1602 | 1603 | PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; 1604 | PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; 1605 | PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; 1606 | PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; 1607 | 1608 | function StringPrettyPrinter() { 1609 | PrettyPrinter.call(this); 1610 | 1611 | this.string = ''; 1612 | } 1613 | 1614 | j$.util.inherit(StringPrettyPrinter, PrettyPrinter); 1615 | 1616 | StringPrettyPrinter.prototype.emitScalar = function(value) { 1617 | this.append(value); 1618 | }; 1619 | 1620 | StringPrettyPrinter.prototype.emitString = function(value) { 1621 | this.append('\'' + value + '\''); 1622 | }; 1623 | 1624 | StringPrettyPrinter.prototype.emitArray = function(array) { 1625 | if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { 1626 | this.append('Array'); 1627 | return; 1628 | } 1629 | var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); 1630 | this.append('[ '); 1631 | for (var i = 0; i < length; i++) { 1632 | if (i > 0) { 1633 | this.append(', '); 1634 | } 1635 | this.format(array[i]); 1636 | } 1637 | if(array.length > length){ 1638 | this.append(', ...'); 1639 | } 1640 | this.append(' ]'); 1641 | }; 1642 | 1643 | StringPrettyPrinter.prototype.emitObject = function(obj) { 1644 | var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null'; 1645 | this.append(constructorName); 1646 | 1647 | if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { 1648 | return; 1649 | } 1650 | 1651 | var self = this; 1652 | this.append('({ '); 1653 | var first = true; 1654 | 1655 | this.iterateObject(obj, function(property, isGetter) { 1656 | if (first) { 1657 | first = false; 1658 | } else { 1659 | self.append(', '); 1660 | } 1661 | 1662 | self.append(property); 1663 | self.append(': '); 1664 | if (isGetter) { 1665 | self.append(''); 1666 | } else { 1667 | self.format(obj[property]); 1668 | } 1669 | }); 1670 | 1671 | this.append(' })'); 1672 | }; 1673 | 1674 | StringPrettyPrinter.prototype.append = function(value) { 1675 | this.string += value; 1676 | }; 1677 | 1678 | return function(value) { 1679 | var stringPrettyPrinter = new StringPrettyPrinter(); 1680 | stringPrettyPrinter.format(value); 1681 | return stringPrettyPrinter.string; 1682 | }; 1683 | }; 1684 | 1685 | getJasmineRequireObj().QueueRunner = function(j$) { 1686 | 1687 | function once(fn) { 1688 | var called = false; 1689 | return function() { 1690 | if (!called) { 1691 | called = true; 1692 | fn(); 1693 | } 1694 | }; 1695 | } 1696 | 1697 | function QueueRunner(attrs) { 1698 | this.queueableFns = attrs.queueableFns || []; 1699 | this.onComplete = attrs.onComplete || function() {}; 1700 | this.clearStack = attrs.clearStack || function(fn) {fn();}; 1701 | this.onException = attrs.onException || function() {}; 1702 | this.catchException = attrs.catchException || function() { return true; }; 1703 | this.userContext = attrs.userContext || {}; 1704 | this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; 1705 | this.fail = attrs.fail || function() {}; 1706 | } 1707 | 1708 | QueueRunner.prototype.execute = function() { 1709 | this.run(this.queueableFns, 0); 1710 | }; 1711 | 1712 | QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { 1713 | var length = queueableFns.length, 1714 | self = this, 1715 | iterativeIndex; 1716 | 1717 | 1718 | for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { 1719 | var queueableFn = queueableFns[iterativeIndex]; 1720 | if (queueableFn.fn.length > 0) { 1721 | attemptAsync(queueableFn); 1722 | return; 1723 | } else { 1724 | attemptSync(queueableFn); 1725 | } 1726 | } 1727 | 1728 | var runnerDone = iterativeIndex >= length; 1729 | 1730 | if (runnerDone) { 1731 | this.clearStack(this.onComplete); 1732 | } 1733 | 1734 | function attemptSync(queueableFn) { 1735 | try { 1736 | queueableFn.fn.call(self.userContext); 1737 | } catch (e) { 1738 | handleException(e, queueableFn); 1739 | } 1740 | } 1741 | 1742 | function attemptAsync(queueableFn) { 1743 | var clearTimeout = function () { 1744 | Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]); 1745 | }, 1746 | next = once(function () { 1747 | clearTimeout(timeoutId); 1748 | self.run(queueableFns, iterativeIndex + 1); 1749 | }), 1750 | timeoutId; 1751 | 1752 | next.fail = function() { 1753 | self.fail.apply(null, arguments); 1754 | next(); 1755 | }; 1756 | 1757 | if (queueableFn.timeout) { 1758 | timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { 1759 | var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); 1760 | onException(error, queueableFn); 1761 | next(); 1762 | }, queueableFn.timeout()]]); 1763 | } 1764 | 1765 | try { 1766 | queueableFn.fn.call(self.userContext, next); 1767 | } catch (e) { 1768 | handleException(e, queueableFn); 1769 | next(); 1770 | } 1771 | } 1772 | 1773 | function onException(e, queueableFn) { 1774 | self.onException(e); 1775 | } 1776 | 1777 | function handleException(e, queueableFn) { 1778 | onException(e, queueableFn); 1779 | if (!self.catchException(e)) { 1780 | //TODO: set a var when we catch an exception and 1781 | //use a finally block to close the loop in a nice way.. 1782 | throw e; 1783 | } 1784 | } 1785 | }; 1786 | 1787 | return QueueRunner; 1788 | }; 1789 | 1790 | getJasmineRequireObj().ReportDispatcher = function() { 1791 | function ReportDispatcher(methods) { 1792 | 1793 | var dispatchedMethods = methods || []; 1794 | 1795 | for (var i = 0; i < dispatchedMethods.length; i++) { 1796 | var method = dispatchedMethods[i]; 1797 | this[method] = (function(m) { 1798 | return function() { 1799 | dispatch(m, arguments); 1800 | }; 1801 | }(method)); 1802 | } 1803 | 1804 | var reporters = []; 1805 | 1806 | this.addReporter = function(reporter) { 1807 | reporters.push(reporter); 1808 | }; 1809 | 1810 | return this; 1811 | 1812 | function dispatch(method, args) { 1813 | for (var i = 0; i < reporters.length; i++) { 1814 | var reporter = reporters[i]; 1815 | if (reporter[method]) { 1816 | reporter[method].apply(reporter, args); 1817 | } 1818 | } 1819 | } 1820 | } 1821 | 1822 | return ReportDispatcher; 1823 | }; 1824 | 1825 | 1826 | getJasmineRequireObj().SpyRegistry = function(j$) { 1827 | 1828 | function SpyRegistry(options) { 1829 | options = options || {}; 1830 | var currentSpies = options.currentSpies || function() { return []; }; 1831 | 1832 | this.spyOn = function(obj, methodName) { 1833 | if (j$.util.isUndefined(obj)) { 1834 | throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); 1835 | } 1836 | 1837 | if (j$.util.isUndefined(methodName)) { 1838 | throw new Error('No method name supplied'); 1839 | } 1840 | 1841 | if (j$.util.isUndefined(obj[methodName])) { 1842 | throw new Error(methodName + '() method does not exist'); 1843 | } 1844 | 1845 | if (obj[methodName] && j$.isSpy(obj[methodName])) { 1846 | //TODO?: should this return the current spy? Downside: may cause user confusion about spy state 1847 | throw new Error(methodName + ' has already been spied upon'); 1848 | } 1849 | 1850 | var spy = j$.createSpy(methodName, obj[methodName]); 1851 | 1852 | currentSpies().push({ 1853 | spy: spy, 1854 | baseObj: obj, 1855 | methodName: methodName, 1856 | originalValue: obj[methodName] 1857 | }); 1858 | 1859 | obj[methodName] = spy; 1860 | 1861 | return spy; 1862 | }; 1863 | 1864 | this.clearSpies = function() { 1865 | var spies = currentSpies(); 1866 | for (var i = 0; i < spies.length; i++) { 1867 | var spyEntry = spies[i]; 1868 | spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; 1869 | } 1870 | }; 1871 | } 1872 | 1873 | return SpyRegistry; 1874 | }; 1875 | 1876 | getJasmineRequireObj().SpyStrategy = function() { 1877 | 1878 | function SpyStrategy(options) { 1879 | options = options || {}; 1880 | 1881 | var identity = options.name || 'unknown', 1882 | originalFn = options.fn || function() {}, 1883 | getSpy = options.getSpy || function() {}, 1884 | plan = function() {}; 1885 | 1886 | this.identity = function() { 1887 | return identity; 1888 | }; 1889 | 1890 | this.exec = function() { 1891 | return plan.apply(this, arguments); 1892 | }; 1893 | 1894 | this.callThrough = function() { 1895 | plan = originalFn; 1896 | return getSpy(); 1897 | }; 1898 | 1899 | this.returnValue = function(value) { 1900 | plan = function() { 1901 | return value; 1902 | }; 1903 | return getSpy(); 1904 | }; 1905 | 1906 | this.returnValues = function() { 1907 | var values = Array.prototype.slice.call(arguments); 1908 | plan = function () { 1909 | return values.shift(); 1910 | }; 1911 | return getSpy(); 1912 | }; 1913 | 1914 | this.throwError = function(something) { 1915 | var error = (something instanceof Error) ? something : new Error(something); 1916 | plan = function() { 1917 | throw error; 1918 | }; 1919 | return getSpy(); 1920 | }; 1921 | 1922 | this.callFake = function(fn) { 1923 | plan = fn; 1924 | return getSpy(); 1925 | }; 1926 | 1927 | this.stub = function(fn) { 1928 | plan = function() {}; 1929 | return getSpy(); 1930 | }; 1931 | } 1932 | 1933 | return SpyStrategy; 1934 | }; 1935 | 1936 | getJasmineRequireObj().Suite = function() { 1937 | function Suite(attrs) { 1938 | this.env = attrs.env; 1939 | this.id = attrs.id; 1940 | this.parentSuite = attrs.parentSuite; 1941 | this.description = attrs.description; 1942 | this.onStart = attrs.onStart || function() {}; 1943 | this.resultCallback = attrs.resultCallback || function() {}; 1944 | this.clearStack = attrs.clearStack || function(fn) {fn();}; 1945 | this.expectationFactory = attrs.expectationFactory; 1946 | this.expectationResultFactory = attrs.expectationResultFactory; 1947 | this.runnablesExplictlySetGetter = attrs.runnablesExplictlySetGetter || function() {}; 1948 | 1949 | this.beforeFns = []; 1950 | this.afterFns = []; 1951 | this.beforeAllFns = []; 1952 | this.afterAllFns = []; 1953 | this.queueRunner = attrs.queueRunner || function() {}; 1954 | this.disabled = false; 1955 | 1956 | this.children = []; 1957 | 1958 | this.result = { 1959 | id: this.id, 1960 | description: this.description, 1961 | fullName: this.getFullName(), 1962 | failedExpectations: [] 1963 | }; 1964 | } 1965 | 1966 | Suite.prototype.expect = function(actual) { 1967 | return this.expectationFactory(actual, this); 1968 | }; 1969 | 1970 | Suite.prototype.getFullName = function() { 1971 | var fullName = this.description; 1972 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 1973 | if (parentSuite.parentSuite) { 1974 | fullName = parentSuite.description + ' ' + fullName; 1975 | } 1976 | } 1977 | return fullName; 1978 | }; 1979 | 1980 | Suite.prototype.disable = function() { 1981 | this.disabled = true; 1982 | }; 1983 | 1984 | Suite.prototype.beforeEach = function(fn) { 1985 | this.beforeFns.unshift(fn); 1986 | }; 1987 | 1988 | Suite.prototype.beforeAll = function(fn) { 1989 | this.beforeAllFns.push(fn); 1990 | }; 1991 | 1992 | Suite.prototype.afterEach = function(fn) { 1993 | this.afterFns.unshift(fn); 1994 | }; 1995 | 1996 | Suite.prototype.afterAll = function(fn) { 1997 | this.afterAllFns.push(fn); 1998 | }; 1999 | 2000 | Suite.prototype.addChild = function(child) { 2001 | this.children.push(child); 2002 | }; 2003 | 2004 | Suite.prototype.status = function() { 2005 | if (this.disabled) { 2006 | return 'disabled'; 2007 | } 2008 | 2009 | if (this.result.failedExpectations.length > 0) { 2010 | return 'failed'; 2011 | } else { 2012 | return 'finished'; 2013 | } 2014 | }; 2015 | 2016 | Suite.prototype.execute = function(onComplete) { 2017 | var self = this; 2018 | 2019 | this.onStart(this); 2020 | 2021 | if (this.disabled) { 2022 | complete(); 2023 | return; 2024 | } 2025 | 2026 | var allFns = []; 2027 | 2028 | for (var i = 0; i < this.children.length; i++) { 2029 | allFns.push(wrapChildAsAsync(this.children[i])); 2030 | } 2031 | 2032 | if (this.isExecutable()) { 2033 | allFns = this.beforeAllFns.concat(allFns); 2034 | allFns = allFns.concat(this.afterAllFns); 2035 | } 2036 | 2037 | this.queueRunner({ 2038 | queueableFns: allFns, 2039 | onComplete: complete, 2040 | userContext: this.sharedUserContext(), 2041 | onException: function() { self.onException.apply(self, arguments); } 2042 | }); 2043 | 2044 | function complete() { 2045 | self.result.status = self.status(); 2046 | self.resultCallback(self.result); 2047 | 2048 | if (onComplete) { 2049 | onComplete(); 2050 | } 2051 | } 2052 | 2053 | function wrapChildAsAsync(child) { 2054 | return { fn: function(done) { child.execute(done); } }; 2055 | } 2056 | }; 2057 | 2058 | Suite.prototype.isExecutable = function() { 2059 | var runnablesExplicitlySet = this.runnablesExplictlySetGetter(); 2060 | return !runnablesExplicitlySet && hasExecutableChild(this.children); 2061 | }; 2062 | 2063 | Suite.prototype.sharedUserContext = function() { 2064 | if (!this.sharedContext) { 2065 | this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; 2066 | } 2067 | 2068 | return this.sharedContext; 2069 | }; 2070 | 2071 | Suite.prototype.clonedSharedUserContext = function() { 2072 | return clone(this.sharedUserContext()); 2073 | }; 2074 | 2075 | Suite.prototype.onException = function() { 2076 | if(isAfterAll(this.children)) { 2077 | var data = { 2078 | matcherName: '', 2079 | passed: false, 2080 | expected: '', 2081 | actual: '', 2082 | error: arguments[0] 2083 | }; 2084 | this.result.failedExpectations.push(this.expectationResultFactory(data)); 2085 | } else { 2086 | for (var i = 0; i < this.children.length; i++) { 2087 | var child = this.children[i]; 2088 | child.onException.apply(child, arguments); 2089 | } 2090 | } 2091 | }; 2092 | 2093 | Suite.prototype.addExpectationResult = function () { 2094 | if(isAfterAll(this.children) && isFailure(arguments)){ 2095 | var data = arguments[1]; 2096 | this.result.failedExpectations.push(this.expectationResultFactory(data)); 2097 | } else { 2098 | for (var i = 0; i < this.children.length; i++) { 2099 | var child = this.children[i]; 2100 | child.addExpectationResult.apply(child, arguments); 2101 | } 2102 | } 2103 | }; 2104 | 2105 | function isAfterAll(children) { 2106 | return children && children[0].result.status; 2107 | } 2108 | 2109 | function isFailure(args) { 2110 | return !args[0]; 2111 | } 2112 | 2113 | function hasExecutableChild(children) { 2114 | var foundActive = false; 2115 | for (var i = 0; i < children.length; i++) { 2116 | if (children[i].isExecutable()) { 2117 | foundActive = true; 2118 | break; 2119 | } 2120 | } 2121 | return foundActive; 2122 | } 2123 | 2124 | function clone(obj) { 2125 | var clonedObj = {}; 2126 | for (var prop in obj) { 2127 | if (obj.hasOwnProperty(prop)) { 2128 | clonedObj[prop] = obj[prop]; 2129 | } 2130 | } 2131 | 2132 | return clonedObj; 2133 | } 2134 | 2135 | return Suite; 2136 | }; 2137 | 2138 | if (typeof window == void 0 && typeof exports == 'object') { 2139 | exports.Suite = jasmineRequire.Suite; 2140 | } 2141 | 2142 | getJasmineRequireObj().Timer = function() { 2143 | var defaultNow = (function(Date) { 2144 | return function() { return new Date().getTime(); }; 2145 | })(Date); 2146 | 2147 | function Timer(options) { 2148 | options = options || {}; 2149 | 2150 | var now = options.now || defaultNow, 2151 | startTime; 2152 | 2153 | this.start = function() { 2154 | startTime = now(); 2155 | }; 2156 | 2157 | this.elapsed = function() { 2158 | return now() - startTime; 2159 | }; 2160 | } 2161 | 2162 | return Timer; 2163 | }; 2164 | 2165 | getJasmineRequireObj().Any = function() { 2166 | 2167 | function Any(expectedObject) { 2168 | this.expectedObject = expectedObject; 2169 | } 2170 | 2171 | Any.prototype.asymmetricMatch = function(other) { 2172 | if (this.expectedObject == String) { 2173 | return typeof other == 'string' || other instanceof String; 2174 | } 2175 | 2176 | if (this.expectedObject == Number) { 2177 | return typeof other == 'number' || other instanceof Number; 2178 | } 2179 | 2180 | if (this.expectedObject == Function) { 2181 | return typeof other == 'function' || other instanceof Function; 2182 | } 2183 | 2184 | if (this.expectedObject == Object) { 2185 | return typeof other == 'object'; 2186 | } 2187 | 2188 | if (this.expectedObject == Boolean) { 2189 | return typeof other == 'boolean'; 2190 | } 2191 | 2192 | return other instanceof this.expectedObject; 2193 | }; 2194 | 2195 | Any.prototype.jasmineToString = function() { 2196 | return ''; 2197 | }; 2198 | 2199 | return Any; 2200 | }; 2201 | 2202 | getJasmineRequireObj().Anything = function(j$) { 2203 | 2204 | function Anything() {} 2205 | 2206 | Anything.prototype.asymmetricMatch = function(other) { 2207 | return !j$.util.isUndefined(other) && other !== null; 2208 | }; 2209 | 2210 | Anything.prototype.jasmineToString = function() { 2211 | return ''; 2212 | }; 2213 | 2214 | return Anything; 2215 | }; 2216 | 2217 | getJasmineRequireObj().ArrayContaining = function(j$) { 2218 | function ArrayContaining(sample) { 2219 | this.sample = sample; 2220 | } 2221 | 2222 | ArrayContaining.prototype.asymmetricMatch = function(other) { 2223 | var className = Object.prototype.toString.call(this.sample); 2224 | if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); } 2225 | 2226 | for (var i = 0; i < this.sample.length; i++) { 2227 | var item = this.sample[i]; 2228 | if (!j$.matchersUtil.contains(other, item)) { 2229 | return false; 2230 | } 2231 | } 2232 | 2233 | return true; 2234 | }; 2235 | 2236 | ArrayContaining.prototype.jasmineToString = function () { 2237 | return ''; 2238 | }; 2239 | 2240 | return ArrayContaining; 2241 | }; 2242 | 2243 | getJasmineRequireObj().ObjectContaining = function(j$) { 2244 | 2245 | function ObjectContaining(sample) { 2246 | this.sample = sample; 2247 | } 2248 | 2249 | ObjectContaining.prototype.asymmetricMatch = function(other) { 2250 | if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } 2251 | 2252 | for (var property in this.sample) { 2253 | if (!Object.prototype.hasOwnProperty.call(other, property) || 2254 | !j$.matchersUtil.equals(this.sample[property], other[property])) { 2255 | return false; 2256 | } 2257 | } 2258 | 2259 | return true; 2260 | }; 2261 | 2262 | ObjectContaining.prototype.jasmineToString = function() { 2263 | return ''; 2264 | }; 2265 | 2266 | return ObjectContaining; 2267 | }; 2268 | 2269 | getJasmineRequireObj().StringMatching = function(j$) { 2270 | 2271 | function StringMatching(expected) { 2272 | if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { 2273 | throw new Error('Expected is not a String or a RegExp'); 2274 | } 2275 | 2276 | this.regexp = new RegExp(expected); 2277 | } 2278 | 2279 | StringMatching.prototype.asymmetricMatch = function(other) { 2280 | return this.regexp.test(other); 2281 | }; 2282 | 2283 | StringMatching.prototype.jasmineToString = function() { 2284 | return ''; 2285 | }; 2286 | 2287 | return StringMatching; 2288 | }; 2289 | 2290 | getJasmineRequireObj().matchersUtil = function(j$) { 2291 | // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? 2292 | 2293 | return { 2294 | equals: function(a, b, customTesters) { 2295 | customTesters = customTesters || []; 2296 | 2297 | return eq(a, b, [], [], customTesters); 2298 | }, 2299 | 2300 | contains: function(haystack, needle, customTesters) { 2301 | customTesters = customTesters || []; 2302 | 2303 | if ((Object.prototype.toString.apply(haystack) === '[object Array]') || 2304 | (!!haystack && !haystack.indexOf)) 2305 | { 2306 | for (var i = 0; i < haystack.length; i++) { 2307 | if (eq(haystack[i], needle, [], [], customTesters)) { 2308 | return true; 2309 | } 2310 | } 2311 | return false; 2312 | } 2313 | 2314 | return !!haystack && haystack.indexOf(needle) >= 0; 2315 | }, 2316 | 2317 | buildFailureMessage: function() { 2318 | var args = Array.prototype.slice.call(arguments, 0), 2319 | matcherName = args[0], 2320 | isNot = args[1], 2321 | actual = args[2], 2322 | expected = args.slice(3), 2323 | englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); 2324 | 2325 | var message = 'Expected ' + 2326 | j$.pp(actual) + 2327 | (isNot ? ' not ' : ' ') + 2328 | englishyPredicate; 2329 | 2330 | if (expected.length > 0) { 2331 | for (var i = 0; i < expected.length; i++) { 2332 | if (i > 0) { 2333 | message += ','; 2334 | } 2335 | message += ' ' + j$.pp(expected[i]); 2336 | } 2337 | } 2338 | 2339 | return message + '.'; 2340 | } 2341 | }; 2342 | 2343 | function isAsymmetric(obj) { 2344 | return obj && j$.isA_('Function', obj.asymmetricMatch); 2345 | } 2346 | 2347 | function asymmetricMatch(a, b) { 2348 | var asymmetricA = isAsymmetric(a), 2349 | asymmetricB = isAsymmetric(b); 2350 | 2351 | if (asymmetricA && asymmetricB) { 2352 | return undefined; 2353 | } 2354 | 2355 | if (asymmetricA) { 2356 | return a.asymmetricMatch(b); 2357 | } 2358 | 2359 | if (asymmetricB) { 2360 | return b.asymmetricMatch(a); 2361 | } 2362 | } 2363 | 2364 | // Equality function lovingly adapted from isEqual in 2365 | // [Underscore](http://underscorejs.org) 2366 | function eq(a, b, aStack, bStack, customTesters) { 2367 | var result = true; 2368 | 2369 | var asymmetricResult = asymmetricMatch(a, b); 2370 | if (!j$.util.isUndefined(asymmetricResult)) { 2371 | return asymmetricResult; 2372 | } 2373 | 2374 | for (var i = 0; i < customTesters.length; i++) { 2375 | var customTesterResult = customTesters[i](a, b); 2376 | if (!j$.util.isUndefined(customTesterResult)) { 2377 | return customTesterResult; 2378 | } 2379 | } 2380 | 2381 | if (a instanceof Error && b instanceof Error) { 2382 | return a.message == b.message; 2383 | } 2384 | 2385 | // Identical objects are equal. `0 === -0`, but they aren't identical. 2386 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). 2387 | if (a === b) { return a !== 0 || 1 / a == 1 / b; } 2388 | // A strict comparison is necessary because `null == undefined`. 2389 | if (a === null || b === null) { return a === b; } 2390 | var className = Object.prototype.toString.call(a); 2391 | if (className != Object.prototype.toString.call(b)) { return false; } 2392 | switch (className) { 2393 | // Strings, numbers, dates, and booleans are compared by value. 2394 | case '[object String]': 2395 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 2396 | // equivalent to `new String("5")`. 2397 | return a == String(b); 2398 | case '[object Number]': 2399 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 2400 | // other numeric values. 2401 | return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); 2402 | case '[object Date]': 2403 | case '[object Boolean]': 2404 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 2405 | // millisecond representations. Note that invalid dates with millisecond representations 2406 | // of `NaN` are not equivalent. 2407 | return +a == +b; 2408 | // RegExps are compared by their source patterns and flags. 2409 | case '[object RegExp]': 2410 | return a.source == b.source && 2411 | a.global == b.global && 2412 | a.multiline == b.multiline && 2413 | a.ignoreCase == b.ignoreCase; 2414 | } 2415 | if (typeof a != 'object' || typeof b != 'object') { return false; } 2416 | 2417 | var aIsDomNode = j$.isDomNode(a); 2418 | var bIsDomNode = j$.isDomNode(b); 2419 | if (aIsDomNode && bIsDomNode) { 2420 | // At first try to use DOM3 method isEqualNode 2421 | if (a.isEqualNode) { 2422 | return a.isEqualNode(b); 2423 | } 2424 | // IE8 doesn't support isEqualNode, try to use outerHTML && innerText 2425 | var aIsElement = a instanceof Element; 2426 | var bIsElement = b instanceof Element; 2427 | if (aIsElement && bIsElement) { 2428 | return a.outerHTML == b.outerHTML; 2429 | } 2430 | if (aIsElement || bIsElement) { 2431 | return false; 2432 | } 2433 | return a.innerText == b.innerText && a.textContent == b.textContent; 2434 | } 2435 | if (aIsDomNode || bIsDomNode) { 2436 | return false; 2437 | } 2438 | 2439 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 2440 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 2441 | var length = aStack.length; 2442 | while (length--) { 2443 | // Linear search. Performance is inversely proportional to the number of 2444 | // unique nested structures. 2445 | if (aStack[length] == a) { return bStack[length] == b; } 2446 | } 2447 | // Add the first object to the stack of traversed objects. 2448 | aStack.push(a); 2449 | bStack.push(b); 2450 | var size = 0; 2451 | // Recursively compare objects and arrays. 2452 | // Compare array lengths to determine if a deep comparison is necessary. 2453 | if (className == '[object Array]' && a.length !== b.length) { 2454 | result = false; 2455 | } 2456 | 2457 | if (result) { 2458 | // Objects with different constructors are not equivalent, but `Object`s 2459 | // from different frames are. 2460 | var aCtor = a.constructor, bCtor = b.constructor; 2461 | if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) && 2462 | isFunction(bCtor) && (bCtor instanceof bCtor))) { 2463 | return false; 2464 | } 2465 | // Deep compare objects. 2466 | for (var key in a) { 2467 | if (has(a, key)) { 2468 | // Count the expected number of properties. 2469 | size++; 2470 | // Deep compare each member. 2471 | if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } 2472 | } 2473 | } 2474 | // Ensure that both objects contain the same number of properties. 2475 | if (result) { 2476 | for (key in b) { 2477 | if (has(b, key) && !(size--)) { break; } 2478 | } 2479 | result = !size; 2480 | } 2481 | } 2482 | // Remove the first object from the stack of traversed objects. 2483 | aStack.pop(); 2484 | bStack.pop(); 2485 | 2486 | return result; 2487 | 2488 | function has(obj, key) { 2489 | return Object.prototype.hasOwnProperty.call(obj, key); 2490 | } 2491 | 2492 | function isFunction(obj) { 2493 | return typeof obj === 'function'; 2494 | } 2495 | } 2496 | }; 2497 | 2498 | getJasmineRequireObj().toBe = function() { 2499 | function toBe() { 2500 | return { 2501 | compare: function(actual, expected) { 2502 | return { 2503 | pass: actual === expected 2504 | }; 2505 | } 2506 | }; 2507 | } 2508 | 2509 | return toBe; 2510 | }; 2511 | 2512 | getJasmineRequireObj().toBeCloseTo = function() { 2513 | 2514 | function toBeCloseTo() { 2515 | return { 2516 | compare: function(actual, expected, precision) { 2517 | if (precision !== 0) { 2518 | precision = precision || 2; 2519 | } 2520 | 2521 | return { 2522 | pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) 2523 | }; 2524 | } 2525 | }; 2526 | } 2527 | 2528 | return toBeCloseTo; 2529 | }; 2530 | 2531 | getJasmineRequireObj().toBeDefined = function() { 2532 | function toBeDefined() { 2533 | return { 2534 | compare: function(actual) { 2535 | return { 2536 | pass: (void 0 !== actual) 2537 | }; 2538 | } 2539 | }; 2540 | } 2541 | 2542 | return toBeDefined; 2543 | }; 2544 | 2545 | getJasmineRequireObj().toBeFalsy = function() { 2546 | function toBeFalsy() { 2547 | return { 2548 | compare: function(actual) { 2549 | return { 2550 | pass: !!!actual 2551 | }; 2552 | } 2553 | }; 2554 | } 2555 | 2556 | return toBeFalsy; 2557 | }; 2558 | 2559 | getJasmineRequireObj().toBeGreaterThan = function() { 2560 | 2561 | function toBeGreaterThan() { 2562 | return { 2563 | compare: function(actual, expected) { 2564 | return { 2565 | pass: actual > expected 2566 | }; 2567 | } 2568 | }; 2569 | } 2570 | 2571 | return toBeGreaterThan; 2572 | }; 2573 | 2574 | 2575 | getJasmineRequireObj().toBeLessThan = function() { 2576 | function toBeLessThan() { 2577 | return { 2578 | 2579 | compare: function(actual, expected) { 2580 | return { 2581 | pass: actual < expected 2582 | }; 2583 | } 2584 | }; 2585 | } 2586 | 2587 | return toBeLessThan; 2588 | }; 2589 | getJasmineRequireObj().toBeNaN = function(j$) { 2590 | 2591 | function toBeNaN() { 2592 | return { 2593 | compare: function(actual) { 2594 | var result = { 2595 | pass: (actual !== actual) 2596 | }; 2597 | 2598 | if (result.pass) { 2599 | result.message = 'Expected actual not to be NaN.'; 2600 | } else { 2601 | result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; 2602 | } 2603 | 2604 | return result; 2605 | } 2606 | }; 2607 | } 2608 | 2609 | return toBeNaN; 2610 | }; 2611 | 2612 | getJasmineRequireObj().toBeNull = function() { 2613 | 2614 | function toBeNull() { 2615 | return { 2616 | compare: function(actual) { 2617 | return { 2618 | pass: actual === null 2619 | }; 2620 | } 2621 | }; 2622 | } 2623 | 2624 | return toBeNull; 2625 | }; 2626 | 2627 | getJasmineRequireObj().toBeTruthy = function() { 2628 | 2629 | function toBeTruthy() { 2630 | return { 2631 | compare: function(actual) { 2632 | return { 2633 | pass: !!actual 2634 | }; 2635 | } 2636 | }; 2637 | } 2638 | 2639 | return toBeTruthy; 2640 | }; 2641 | 2642 | getJasmineRequireObj().toBeUndefined = function() { 2643 | 2644 | function toBeUndefined() { 2645 | return { 2646 | compare: function(actual) { 2647 | return { 2648 | pass: void 0 === actual 2649 | }; 2650 | } 2651 | }; 2652 | } 2653 | 2654 | return toBeUndefined; 2655 | }; 2656 | 2657 | getJasmineRequireObj().toContain = function() { 2658 | function toContain(util, customEqualityTesters) { 2659 | customEqualityTesters = customEqualityTesters || []; 2660 | 2661 | return { 2662 | compare: function(actual, expected) { 2663 | 2664 | return { 2665 | pass: util.contains(actual, expected, customEqualityTesters) 2666 | }; 2667 | } 2668 | }; 2669 | } 2670 | 2671 | return toContain; 2672 | }; 2673 | 2674 | getJasmineRequireObj().toEqual = function() { 2675 | 2676 | function toEqual(util, customEqualityTesters) { 2677 | customEqualityTesters = customEqualityTesters || []; 2678 | 2679 | return { 2680 | compare: function(actual, expected) { 2681 | var result = { 2682 | pass: false 2683 | }; 2684 | 2685 | result.pass = util.equals(actual, expected, customEqualityTesters); 2686 | 2687 | return result; 2688 | } 2689 | }; 2690 | } 2691 | 2692 | return toEqual; 2693 | }; 2694 | 2695 | getJasmineRequireObj().toHaveBeenCalled = function(j$) { 2696 | 2697 | function toHaveBeenCalled() { 2698 | return { 2699 | compare: function(actual) { 2700 | var result = {}; 2701 | 2702 | if (!j$.isSpy(actual)) { 2703 | throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); 2704 | } 2705 | 2706 | if (arguments.length > 1) { 2707 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); 2708 | } 2709 | 2710 | result.pass = actual.calls.any(); 2711 | 2712 | result.message = result.pass ? 2713 | 'Expected spy ' + actual.and.identity() + ' not to have been called.' : 2714 | 'Expected spy ' + actual.and.identity() + ' to have been called.'; 2715 | 2716 | return result; 2717 | } 2718 | }; 2719 | } 2720 | 2721 | return toHaveBeenCalled; 2722 | }; 2723 | 2724 | getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { 2725 | 2726 | function toHaveBeenCalledWith(util, customEqualityTesters) { 2727 | return { 2728 | compare: function() { 2729 | var args = Array.prototype.slice.call(arguments, 0), 2730 | actual = args[0], 2731 | expectedArgs = args.slice(1), 2732 | result = { pass: false }; 2733 | 2734 | if (!j$.isSpy(actual)) { 2735 | throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); 2736 | } 2737 | 2738 | if (!actual.calls.any()) { 2739 | result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; 2740 | return result; 2741 | } 2742 | 2743 | if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { 2744 | result.pass = true; 2745 | result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; 2746 | } else { 2747 | result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; 2748 | } 2749 | 2750 | return result; 2751 | } 2752 | }; 2753 | } 2754 | 2755 | return toHaveBeenCalledWith; 2756 | }; 2757 | 2758 | getJasmineRequireObj().toMatch = function(j$) { 2759 | 2760 | function toMatch() { 2761 | return { 2762 | compare: function(actual, expected) { 2763 | if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { 2764 | throw new Error('Expected is not a String or a RegExp'); 2765 | } 2766 | 2767 | var regexp = new RegExp(expected); 2768 | 2769 | return { 2770 | pass: regexp.test(actual) 2771 | }; 2772 | } 2773 | }; 2774 | } 2775 | 2776 | return toMatch; 2777 | }; 2778 | 2779 | getJasmineRequireObj().toThrow = function(j$) { 2780 | 2781 | function toThrow(util) { 2782 | return { 2783 | compare: function(actual, expected) { 2784 | var result = { pass: false }, 2785 | threw = false, 2786 | thrown; 2787 | 2788 | if (typeof actual != 'function') { 2789 | throw new Error('Actual is not a Function'); 2790 | } 2791 | 2792 | try { 2793 | actual(); 2794 | } catch (e) { 2795 | threw = true; 2796 | thrown = e; 2797 | } 2798 | 2799 | if (!threw) { 2800 | result.message = 'Expected function to throw an exception.'; 2801 | return result; 2802 | } 2803 | 2804 | if (arguments.length == 1) { 2805 | result.pass = true; 2806 | result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; 2807 | 2808 | return result; 2809 | } 2810 | 2811 | if (util.equals(thrown, expected)) { 2812 | result.pass = true; 2813 | result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; 2814 | } else { 2815 | result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; 2816 | } 2817 | 2818 | return result; 2819 | } 2820 | }; 2821 | } 2822 | 2823 | return toThrow; 2824 | }; 2825 | 2826 | getJasmineRequireObj().toThrowError = function(j$) { 2827 | function toThrowError (util) { 2828 | return { 2829 | compare: function(actual) { 2830 | var threw = false, 2831 | pass = {pass: true}, 2832 | fail = {pass: false}, 2833 | thrown; 2834 | 2835 | if (typeof actual != 'function') { 2836 | throw new Error('Actual is not a Function'); 2837 | } 2838 | 2839 | var errorMatcher = getMatcher.apply(null, arguments); 2840 | 2841 | try { 2842 | actual(); 2843 | } catch (e) { 2844 | threw = true; 2845 | thrown = e; 2846 | } 2847 | 2848 | if (!threw) { 2849 | fail.message = 'Expected function to throw an Error.'; 2850 | return fail; 2851 | } 2852 | 2853 | if (!(thrown instanceof Error)) { 2854 | fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; 2855 | return fail; 2856 | } 2857 | 2858 | if (errorMatcher.hasNoSpecifics()) { 2859 | pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.'; 2860 | return pass; 2861 | } 2862 | 2863 | if (errorMatcher.matches(thrown)) { 2864 | pass.message = function() { 2865 | return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; 2866 | }; 2867 | return pass; 2868 | } else { 2869 | fail.message = function() { 2870 | return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + 2871 | ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; 2872 | }; 2873 | return fail; 2874 | } 2875 | } 2876 | }; 2877 | 2878 | function getMatcher() { 2879 | var expected = null, 2880 | errorType = null; 2881 | 2882 | if (arguments.length == 2) { 2883 | expected = arguments[1]; 2884 | if (isAnErrorType(expected)) { 2885 | errorType = expected; 2886 | expected = null; 2887 | } 2888 | } else if (arguments.length > 2) { 2889 | errorType = arguments[1]; 2890 | expected = arguments[2]; 2891 | if (!isAnErrorType(errorType)) { 2892 | throw new Error('Expected error type is not an Error.'); 2893 | } 2894 | } 2895 | 2896 | if (expected && !isStringOrRegExp(expected)) { 2897 | if (errorType) { 2898 | throw new Error('Expected error message is not a string or RegExp.'); 2899 | } else { 2900 | throw new Error('Expected is not an Error, string, or RegExp.'); 2901 | } 2902 | } 2903 | 2904 | function messageMatch(message) { 2905 | if (typeof expected == 'string') { 2906 | return expected == message; 2907 | } else { 2908 | return expected.test(message); 2909 | } 2910 | } 2911 | 2912 | return { 2913 | errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception', 2914 | thrownDescription: function(thrown) { 2915 | var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', 2916 | thrownMessage = ''; 2917 | 2918 | if (expected) { 2919 | thrownMessage = ' with message ' + j$.pp(thrown.message); 2920 | } 2921 | 2922 | return thrownName + thrownMessage; 2923 | }, 2924 | messageDescription: function() { 2925 | if (expected === null) { 2926 | return ''; 2927 | } else if (expected instanceof RegExp) { 2928 | return ' with a message matching ' + j$.pp(expected); 2929 | } else { 2930 | return ' with message ' + j$.pp(expected); 2931 | } 2932 | }, 2933 | hasNoSpecifics: function() { 2934 | return expected === null && errorType === null; 2935 | }, 2936 | matches: function(error) { 2937 | return (errorType === null || error.constructor === errorType) && 2938 | (expected === null || messageMatch(error.message)); 2939 | } 2940 | }; 2941 | } 2942 | 2943 | function isStringOrRegExp(potential) { 2944 | return potential instanceof RegExp || (typeof potential == 'string'); 2945 | } 2946 | 2947 | function isAnErrorType(type) { 2948 | if (typeof type !== 'function') { 2949 | return false; 2950 | } 2951 | 2952 | var Surrogate = function() {}; 2953 | Surrogate.prototype = type.prototype; 2954 | return (new Surrogate()) instanceof Error; 2955 | } 2956 | } 2957 | 2958 | return toThrowError; 2959 | }; 2960 | 2961 | getJasmineRequireObj().interface = function(jasmine, env) { 2962 | var jasmineInterface = { 2963 | describe: function(description, specDefinitions) { 2964 | return env.describe(description, specDefinitions); 2965 | }, 2966 | 2967 | xdescribe: function(description, specDefinitions) { 2968 | return env.xdescribe(description, specDefinitions); 2969 | }, 2970 | 2971 | fdescribe: function(description, specDefinitions) { 2972 | return env.fdescribe(description, specDefinitions); 2973 | }, 2974 | 2975 | it: function() { 2976 | return env.it.apply(env, arguments); 2977 | }, 2978 | 2979 | xit: function() { 2980 | return env.xit.apply(env, arguments); 2981 | }, 2982 | 2983 | fit: function() { 2984 | return env.fit.apply(env, arguments); 2985 | }, 2986 | 2987 | beforeEach: function() { 2988 | return env.beforeEach.apply(env, arguments); 2989 | }, 2990 | 2991 | afterEach: function() { 2992 | return env.afterEach.apply(env, arguments); 2993 | }, 2994 | 2995 | beforeAll: function() { 2996 | return env.beforeAll.apply(env, arguments); 2997 | }, 2998 | 2999 | afterAll: function() { 3000 | return env.afterAll.apply(env, arguments); 3001 | }, 3002 | 3003 | expect: function(actual) { 3004 | return env.expect(actual); 3005 | }, 3006 | 3007 | pending: function() { 3008 | return env.pending.apply(env, arguments); 3009 | }, 3010 | 3011 | fail: function() { 3012 | return env.fail.apply(env, arguments); 3013 | }, 3014 | 3015 | spyOn: function(obj, methodName) { 3016 | return env.spyOn(obj, methodName); 3017 | }, 3018 | 3019 | jsApiReporter: new jasmine.JsApiReporter({ 3020 | timer: new jasmine.Timer() 3021 | }), 3022 | 3023 | jasmine: jasmine 3024 | }; 3025 | 3026 | jasmine.addCustomEqualityTester = function(tester) { 3027 | env.addCustomEqualityTester(tester); 3028 | }; 3029 | 3030 | jasmine.addMatchers = function(matchers) { 3031 | return env.addMatchers(matchers); 3032 | }; 3033 | 3034 | jasmine.clock = function() { 3035 | return env.clock; 3036 | }; 3037 | 3038 | return jasmineInterface; 3039 | }; 3040 | 3041 | getJasmineRequireObj().version = function() { 3042 | return '2.2.0'; 3043 | }; 3044 | --------------------------------------------------------------------------------