├── .gitmodules ├── .midje.clj ├── .gitignore ├── .travis.yml ├── test ├── cljs │ ├── purnam │ │ ├── test_angular_filters.cljs │ │ └── test_angular.cljs │ └── midje_doc │ │ ├── gyr_guide.clj │ │ ├── gyr_quickstart.cljs │ │ └── api │ │ ├── test_gyr.cljs │ │ └── gyr.cljs └── clj │ ├── purnam │ └── checks.clj │ └── gyr │ ├── test_core.clj │ └── test_test.clj ├── project.clj ├── src └── gyr │ ├── directives.cljs │ ├── core.clj │ ├── test.clj │ └── filters.cljs ├── karma.conf.js ├── README.md └── harness └── js └── angular-mocks.js /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "doc"] 2 | path = doc 3 | url = https://github.com/purnam/gyr.git 4 | branch = gh-pages 5 | -------------------------------------------------------------------------------- /.midje.clj: -------------------------------------------------------------------------------- 1 | #_(change-defaults :emitter 'midje.emission.plugins.progress 2 | :print-level :print-facts) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /docs 4 | /doc 5 | /classes 6 | /checkouts 7 | pom.xml 8 | pom.xml.asc 9 | *.jar 10 | *.class 11 | .lein-deps-sum 12 | .lein-failures 13 | .lein-plugins 14 | .lein-repl-history 15 | .DS_Store 16 | .#* 17 | .nrepl-port 18 | *.iml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | before_install: 4 | - npm install karma 5 | script: 6 | - lein2 midje 7 | - lein2 cljsbuild once 8 | - karma start --single-run --browsers PhantomJS 9 | notifications: 10 | email: 11 | recipients: 12 | - z@caudate.me -------------------------------------------------------------------------------- /test/cljs/purnam/test_angular_filters.cljs: -------------------------------------------------------------------------------- 1 | (ns purnam.test-angular-filters 2 | (:require [goog.object :as o] 3 | [gyr.filters] 4 | [purnam.test]) 5 | (:use-macros [purnam.core :only [obj arr ! def.n]] 6 | [purnam.test :only [describe it is is-not]] 7 | [gyr.core :only [def.module]] 8 | [gyr.test :only [describe.controller describe.ng 9 | it-uses it-compiles it-uses-filter]])) 10 | 11 | (def.module test [gyr.filters]) 12 | 13 | (describe.ng 14 | {:doc "Testing Filters" 15 | :module gyr.filters 16 | :inject [$filter]} 17 | 18 | (it "can test for range filters" 19 | (let [r (($filter "call") 5 #(+ %1 %2 %3 %4 %5) 1 2 3 4)] 20 | (is r 15)))) -------------------------------------------------------------------------------- /test/clj/purnam/checks.clj: -------------------------------------------------------------------------------- 1 | (ns purnam.checks) 2 | 3 | (defn match-sym? [v] 4 | (and (symbol? v) 5 | (re-find #"^%" (str v)))) 6 | 7 | (defn match 8 | ([f1 f2] (match f1 f2 (atom {}))) 9 | ([v1 v2 dict] 10 | (cond (or (and (list? v1) (list? v2)) 11 | (and (vector? v1) (vector? v2))) 12 | (and (= (count v1) (count v2)) 13 | (->> (map #(match %1 %2 dict) v1 v2) 14 | (every? true?))) 15 | 16 | (and (associative? v1) (associative? v2)) 17 | (and (= (keys v1) (keys v2)) 18 | (->> (map #(match %1 %2 dict) (vals v1) (vals v2)) 19 | (every? true))) 20 | 21 | (match-sym? v2) 22 | (if-let [vd (@dict v2)] 23 | (match v1 vd dict) 24 | (do (swap! dict assoc v2 v1) 25 | true)) 26 | :else (= v1 v2)))) 27 | 28 | (defn matches [template] 29 | (fn [value] 30 | (match value template))) 31 | 32 | (defn expands-into [result] 33 | (fn [form] 34 | (match (macroexpand-1 form) result))) -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject im.chit/gyr "0.3.1" 2 | :description "Write angular.js like its angular.cljs" 3 | :url "http://www.github.com/purnam/gyr" 4 | :license {:name "The MIT License" 5 | :url "http://opensource.org/licencses/MIT"} 6 | :dependencies [[org.clojure/clojure "1.5.1"] 7 | [im.chit/purnam "0.4.3"]] 8 | :profiles {:dev {:dependencies [[org.clojure/clojurescript "0.0-2080"] 9 | [midje "1.6.0"]] 10 | :plugins [[lein-midje "3.1.3"] 11 | [lein-cljsbuild "1.0.0"]]}} 12 | :test-paths ["test/clj"] 13 | :documentation {:files {"doc/index" 14 | {:input "test/cljs/midje_doc/gyr_guide.clj" 15 | :title "gyr" 16 | :sub-title "Angularjs extensions for clojurescript" 17 | :author "Chris Zheng" 18 | :email "z@caudate.me" 19 | :tracking "UA-31320512-2"}}} 20 | :cljsbuild 21 | {:builds 22 | [{:source-paths ["src" "test/cljs"], 23 | :id "js-test", 24 | :compiler {:pretty-print true, 25 | :output-to "target/gyr-test.js", 26 | :optimizations :whitespace}}]}) -------------------------------------------------------------------------------- /src/gyr/directives.cljs: -------------------------------------------------------------------------------- 1 | (ns gyr.directives 2 | (:require-macros [purnam.core :as j]) 3 | (:use-macros [purnam.core :only [obj ! def.n]] 4 | [gyr.core :only [def.module def.directive]])) 5 | 6 | (def.module gyr []) 7 | 8 | (def.directive gyr.ngBlur [$parse] 9 | (fn [scope elem attrs] 10 | (let [f ($parse attrs.ngBlur)] 11 | (elem.bind 12 | "blur" 13 | (fn [e] 14 | (scope.$apply (fn [] (f scope (obj :$event e))))))))) 15 | 16 | (def.directive gyr.ngFocus [$parse] 17 | (fn [scope elem attrs] 18 | (let [f ($parse attrs.ngFocus)] 19 | (elem.bind 20 | "focus" 21 | (fn [e] 22 | (scope.$apply (fn [] (f scope (obj :$event e))))))))) 23 | 24 | (def.directive gyr.ngLet [] 25 | (obj 26 | :scope false 27 | :link 28 | (fn [$scope $element $attr] 29 | (let [regex #"^\s*(.*)\s+as\s+(.*)\s*"] 30 | (doseq [line ($attr.ngLet.split ";")] 31 | (if-let [match (line.match regex)] 32 | (let [varName match.1 33 | varExp match.2 34 | assign 35 | (fn [value] 36 | (! $scope.|varName| value))] 37 | (assign ($scope.$eval varExp)) 38 | ($scope.$watch varExp assign)))))))) 39 | -------------------------------------------------------------------------------- /test/cljs/midje_doc/gyr_guide.clj: -------------------------------------------------------------------------------- 1 | (ns midje-doc.gyr-guide) 2 | 3 | [[:chapter {:title "Introduction"}]] 4 | 5 | "[gyr](https://github.com/purnam/gyr) has been spun out from the [purnam](https://github.com/purnam/purnam) core libraries into its own seperate project. By using a mixture of javascript/clojurescript syntax, `angular.js` became smaller, more readable and easier to handle. The library offers: 6 | 7 | - [gyr.core](#gyr-core) - a simple dsl for eliminating boilerplate *angular.js* 8 | - [gyr.test](#gyr-test) - testing macros for eliminating more boilerplate test code for services, controllers, directives and filters 9 | - [gyr.filters](#gyr-filters) - currently undocumented 10 | 11 | More complete examples can be seen [here](https://www.github.com/purnam/example.gyr) 12 | " 13 | 14 | [[:chapter {:title "Installation"}]] 15 | 16 | "To install `gyr`, add to `project.clj` dependencies: 17 | 18 | `[im.chit/gyr` \"`{{PROJECT.version}}`\"`]` 19 | 20 | [purnam](https://github.com/purnam/purnam) come included with `gyr` so that all the language extension macros are avaliable as well. 21 | " 22 | 23 | [[:chapter {:title "Usage"}]] 24 | 25 | [[:file {:src "test/cljs/midje_doc/gyr_quickstart.cljs"}]] 26 | 27 | [[:file {:src "test/cljs/midje_doc/api/gyr.cljs"}]] 28 | 29 | [[:file {:src "test/cljs/midje_doc/api/test_gyr.cljs"}]] -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue Oct 29 2013 22:49:41 GMT+1100 (EST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path, that will be used to resolve files and exclude 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | frameworks: ['jasmine'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'harness/js/angular.js', 18 | 'harness/js/angular-mocks.js', 19 | 'target/gyr-test.js' 20 | ], 21 | 22 | 23 | // list of files to exclude 24 | exclude: [ 25 | 26 | ], 27 | 28 | 29 | // test results reporter to use 30 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 31 | reporters: ['progress'], 32 | 33 | 34 | // web server port 35 | port: 9876, 36 | 37 | 38 | // enable / disable colors in the output (reporters and logs) 39 | colors: true, 40 | 41 | 42 | // level of logging 43 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 44 | logLevel: config.LOG_INFO, 45 | 46 | 47 | // enable / disable watching file and executing tests whenever any file changes 48 | autoWatch: true, 49 | 50 | 51 | // Start these browsers, currently available: 52 | // - Chrome 53 | // - ChromeCanary 54 | // - Firefox 55 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 56 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 57 | // - PhantomJS 58 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 59 | browsers: ['Chrome'], 60 | 61 | 62 | // If browser does not capture in given timeout [ms], kill it 63 | captureTimeout: 60000, 64 | 65 | 66 | // Continuous Integration mode 67 | // if true, it capture browsers, run tests and exit 68 | singleRun: false 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /src/gyr/core.clj: -------------------------------------------------------------------------------- 1 | (ns gyr.core 2 | (:use [purnam.common.expand :only [expand]]) 3 | (:require [clojure.string :as s])) 4 | 5 | (defn inj-array [params] 6 | (cons 'array (map str params))) 7 | 8 | (defn inj-fn [params body] 9 | (concat (cons 'array 10 | (if (empty? params) [] 11 | `(~@(map str params)))) 12 | [(concat ['fn params] 13 | (if (empty? body) nil 14 | `(~@body)))])) 15 | 16 | (defn module-syms [sym] 17 | (let [symbols (s/split (str sym) #"\.") 18 | _ (assert (<= 2 (count symbols)) 19 | "The controller must be defined in 20 | the form [.].") 21 | ctrl (last symbols) 22 | module (s/join "." (butlast symbols) )] 23 | [module ctrl])) 24 | 25 | (defn angular-module 26 | ([ss] (list '.module 'js/angular ss)) 27 | ([ss params] (list '.module 'js/angular ss (inj-array params)))) 28 | 29 | (defmacro def.module [sym params] 30 | (let [ss (str sym) 31 | symbols (s/split ss #"\.")] 32 | (list 'def (symbol (s/join "_" symbols)) 33 | (angular-module ss params)))) 34 | 35 | (defmacro def.config [mod params & body] 36 | (list '.config (angular-module (str mod)) (inj-fn params (expand body)))) 37 | 38 | (defmacro def.run [mod params & body] 39 | (list '.run (angular-module (str mod)) (inj-fn params (expand body)))) 40 | 41 | (defn value-fn [sym f body] 42 | (let [[module ctrl] (module-syms sym) 43 | dsym (symbol (s/join "_" (s/split (str sym) #"\.")))] 44 | (list 'do 45 | (list 'def dsym body) 46 | (list f (angular-module module) 47 | ctrl dsym)))) 48 | 49 | (defn function-fn [sym f params body] 50 | (let [fn-body (inj-fn params (expand body))] 51 | (value-fn sym f fn-body))) 52 | 53 | (defn angular-function [f] 54 | (list 'defmacro (symbol (str "def." f)) '[sym params & body] 55 | (list 'function-fn 'sym (list 'quote (symbol (str "." f))) 'params 'body))) 56 | 57 | (defn angular-value [f] 58 | (list 'defmacro (symbol (str "def." f)) '[sym body] 59 | (list 'value-fn 'sym (list 'quote (symbol (str "." f))) 'body))) 60 | 61 | (defmacro angular.functions [v] 62 | (apply list 'do (map #(angular-function %) v))) 63 | 64 | (defmacro angular.values [v] 65 | (apply list 'do (map #(angular-value %) v))) 66 | 67 | (angular.functions [controller service factory provider filter directive]) 68 | (angular.values [constant value]) 69 | -------------------------------------------------------------------------------- /test/cljs/midje_doc/gyr_quickstart.cljs: -------------------------------------------------------------------------------- 1 | (ns midje-doc.gyr-quickstart 2 | (:require [purnam.test]) 3 | (:use-macros [purnam.core :only [! f.n def.n obj arr]] 4 | [gyr.core :only [def.module def.controller 5 | def.value def.constant 6 | def.filter def.factory 7 | def.provider def.service 8 | def.directive def.config]] 9 | [purnam.test :only [describe is it]] 10 | [gyr.test :only [describe.ng describe.controller it-uses]])) 11 | 12 | [[:section {:title "DSL for Angular"}]] 13 | 14 | "[Angular.js](http://angularjs.org) is the premier javascript framework for building large-scale, single-page applications. [gyr.core](#gyr-core) allows clean, readable definitions of angular.js modules and tests. Below is the definition of an angular.js module, a storage service, and a controller that uses the service." 15 | 16 | [[{:title "module definition"}]] 17 | 18 | (def.module myApp []) 19 | 20 | (def.service myApp.storage [] 21 | (let [store (atom {})] 22 | (obj :put (fn [k v] (swap! store #(assoc % k v))) 23 | :get (fn [k] (@store k)) 24 | :clear (fn [] (reset! store {})) 25 | :print (fn [] (js/console.log (clj->js @store)))))) 26 | 27 | (def.controller myApp.AppCtrl [$scope storage] 28 | (! $scope.key "hello") 29 | (! $scope.val "world") 30 | (! $scope.printStore storage.print) 31 | (! $scope.clearStore storage.clear) 32 | (! $scope.putStore storage.put) 33 | (! $scope.getStore storage.get)) 34 | 35 | [[:section {:title "Testing for Angular"}]] 36 | 37 | "Testing angular.js apps are quite brain intensive when using pure javascript. The [gyr.test](#gyr-test) namespace takes care of all the injections for us" 38 | 39 | [[{:title "testing services"}]] 40 | (describe.ng 41 | {:doc "Storage" 42 | :module myApp 43 | :inject [storage]} 44 | (it "allows saving and retriving" 45 | (storage.put "hello" "world") 46 | (is (storage.get "hello") "world") 47 | 48 | (storage.clear) 49 | (is (storage.get "hello") nil))) 50 | 51 | [[{:title "testing controllers"}]] 52 | (describe.controller 53 | {:doc "AppCtrl" 54 | :module myApp 55 | :controller AppCtrl} 56 | 57 | (it "has key and val within the scope" 58 | (is $scope.key "hello") 59 | (is $scope.val "world")) 60 | 61 | (it "has put and get functionality" 62 | ($scope.putStore $scope.key $scope.val) 63 | (is ($scope.getStore "hello") "world")) 64 | 65 | (it "additional tests" 66 | (! $scope.key "bye") 67 | ($scope.putStore $scope.key $scope.val) 68 | (is ($scope.getStore "hello") nil) 69 | (is ($scope.getStore "bye") "world"))) 70 | 71 | 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gyr 2 | 3 | [![Build Status](https://travis-ci.org/purnam/gyr.png?branch=master)](https://travis-ci.org/purnam/gyr) 4 | 5 | A nicer angularjs interop for clojurescript 6 | 7 | ## Usage 8 | 9 | Stable Version: 10 | 11 | ```clojure 12 | [im.chit/gyr "0.3.1"] 13 | ``` 14 | 15 | Documentation: 16 | 17 | - [API](http://purnam.github.io/gyr/) 18 | - [Sample Application](https://github.com/purnam/example.gyr) 19 | 20 | ## Angular Simplified 21 | 22 | As much as I became amazed at the possibilities of angularjs, I was also experiencing alot of pain when developing with javascript. Javascript is not the best language to work with and unless one is serious javascript expert (which I am far from), it was very easy to complect modules within large *angular.js* applications. Using coffeescript did not solve the problem and in fact, made things worse with its white-space indentation style. 23 | 24 | I also found that *properly architected* angular.js applications had WAY too many files for my liking - as seen in the [Year of Moo](www.yearofmoo.com/‎) projects. I wanted to use clojure syntax so that my code was smaller, more readable and easier to handle. Essentially, I believe that great angular apps should be able to be written in one file. 25 | 26 | Gyr currently offers: 27 | 28 | - [gyr.core](http://purnam.github.io/gyr/#gyr-core) - a simple dsl for eliminating boilerplate *angular.js* 29 | - [gyr.test](http://purnam.github.io/gyr/#gyr-test) - testing macros for eliminating more boilerplate test code for services, controllers, directives and filters 30 | - `gyr.filters` - undocumented library of clojure-style filters for angular.js 31 | 32 | ##### Angular JS 33 | 34 | ```clojure 35 | ;; purnam.angular 36 | 37 | (def.module my.app []) 38 | 39 | (def.config my.app [$routeProvider] 40 | (-> $routeProvider 41 | (.when "/" (obj :templateUrl "views/main.html")) 42 | (.otherwise (obj :redirectTo "/")))) 43 | 44 | (def.controller my.app.MainCtrl [$scope $http] 45 | (! $scope.msg "") 46 | (! $scope.setMessage (fn [msg] (! $scope.msg msg))) 47 | (! $scope.loginQuery 48 | (fn [user pass] 49 | (let [q (obj :user user 50 | :pass pass)] 51 | (-> $http 52 | (.post "/login" q) 53 | (.success (fn [res] 54 | (if (= res "true") 55 | (! $scope.loginSuccess true) 56 | (! $scope.loginSuccess false)))) 57 | (.error (fn [] (js/console.log "error!!"))))))) 58 | ``` 59 | 60 | ##### AngularJS Testing 61 | ```clojure 62 | ;; purnam.test.angular 63 | 64 | (describe.controller 65 | {:doc "A sample controller for testing purposes" 66 | :module my.app 67 | :controller MainCtrl} 68 | 69 | (it "should be able to change the message within the $scope" 70 | (is $scope.msg "Hello") 71 | (do ($scope.setMessage "World!") 72 | (is $scope.msg "World!")) 73 | 74 | (do ($scope.setMessage "Angular Rocks!") 75 | (is $scope.msg "Angular Rocks!")))) 76 | ``` 77 | 78 | 79 | ## License 80 | 81 | Copyright © 2014 Chris Zheng 82 | 83 | Distributed under the The MIT License. 84 | -------------------------------------------------------------------------------- /test/clj/gyr/test_core.clj: -------------------------------------------------------------------------------- 1 | (ns gyr.test-core 2 | (:use [midje.sweet :exclude [contains]] 3 | purnam.checks 4 | gyr.core)) 5 | 6 | (fact "inj-array" 7 | (inj-array '[ ]) 8 | => '(array "" "")) 9 | 10 | (fact "inj-fn" 11 | (inj-fn '[ ] '( )) 12 | => '(array "" "" 13 | (fn [ ] ))) 14 | 15 | (fact "module-syms" 16 | (module-syms ') 17 | => (throws AssertionError) 18 | 19 | (module-syms '.) 20 | => ["" ""] 21 | 22 | (module-syms '..) 23 | => ["." ""]) 24 | 25 | (fact "def.module" 26 | (macroexpand-1 27 | '(def.module . [])) 28 | => '(def _ 29 | (.module js/angular "." (array))) 30 | 31 | (macroexpand-1 32 | '(def.module . [ .])) 33 | => '(def _ 34 | (.module js/angular "." 35 | (array "" "."))) 36 | 37 | (macroexpand-1 38 | '(def.module my.app [ui ui.bootstrap])) 39 | => '(def my_app (.module js/angular "my.app" 40 | (array "ui" "ui.bootstrap")))) 41 | 42 | (fact "def.config" 43 | (macroexpand-1 44 | '(def.config . [ ] )) 45 | => '(.config (.module js/angular ".") 46 | (array "" "" 47 | (fn [ ] ))) 48 | 49 | (macroexpand-1 50 | '(def.config my.app [$routeParams]))) 51 | 52 | (fact "value-fn" 53 | (value-fn '.. '. ') 54 | => '(do (def __ ) 55 | (. (.module js/angular ".") 56 | "" __))) 57 | 58 | (fact "function-fn" 59 | (function-fn '.. '. '[ ] 60 | '( )) 61 | '(do (def __ 62 | (array "" "" 63 | (fn [ ] ))) 64 | (. (.module js/angular ".") 65 | "" 66 | __))) 67 | 68 | (fact "angular-function" 69 | (angular-function 'controller) 70 | => '(defmacro def.controller [sym params & body] 71 | (function-fn sym (quote .controller) params body)) 72 | (angular-function 'service) 73 | => '(defmacro def.service [sym params & body] 74 | (function-fn sym (quote .service) params body))) 75 | 76 | (fact "angular-value" 77 | (angular-value 'value) 78 | => '(defmacro def.value [sym body] 79 | (value-fn sym (quote .value) body))) 80 | 81 | (fact "def.service" 82 | (macroexpand-1 83 | '(def.service . [ ] 84 | )) 85 | => '(do (def _ 86 | (array "" "" 87 | (fn [ ] ))) 88 | (.service (.module js/angular "") 89 | "" _))) 90 | 91 | (fact "def.value" 92 | (macroexpand-1 93 | '(def.value . )) 94 | => '(do (def _ ) 95 | (.value (.module js/angular "") 96 | "" _))) 97 | -------------------------------------------------------------------------------- /src/gyr/test.clj: -------------------------------------------------------------------------------- 1 | (ns gyr.test 2 | (:require [clojure.string :as s] 3 | [purnam.common.scope :refer [change-roots-map]] 4 | [purnam.test.jasmine :refer [describe-fn it-preprocess it-fn 5 | describe-default-options 6 | describe-roots-map]])) 7 | 8 | (def l list) 9 | 10 | (defn describe-parse-inject [form] 11 | (cond (symbol? form) 12 | {:var form :inj [form] :body form} 13 | 14 | (vector? form) 15 | (let [[v res] form] 16 | (cond (symbol? res) 17 | {:var v :inj [res] :body res} 18 | 19 | (list? res) 20 | {:var v :inj (first res) :body (second res)})))) 21 | 22 | (defn describe-parse-injects [spec injects] 23 | (let [data (map describe-parse-inject injects) 24 | injs (set (mapcat :inj data)) 25 | bfn (fn [v b] (l 'aset spec (str v) b)) 26 | vdata (map :var data) 27 | bindings (map bfn vdata (map :body data)) 28 | ivars (mapcat (fn [x] [x x]) vdata)] 29 | {:injs injs :bindings bindings :ivars ivars})) 30 | 31 | (defn describe-ng-fn [options body] 32 | (let [options (merge describe-default-options options) 33 | {:keys [module spec inject]} options 34 | {:keys [injs ivars bindings]} (describe-parse-injects spec inject) 35 | rm (describe-roots-map spec ivars)] 36 | (describe-fn 37 | (dissoc options :inject :module) 38 | (apply list 39 | (l 'js/beforeEach 40 | (l 'js/module (str module))) 41 | 42 | (l 'js/beforeEach 43 | (l 'js/inject 44 | (concat (l 'array) 45 | (map str injs) 46 | (l (apply 47 | l 'fn (apply vector injs) 48 | bindings))))) 49 | 50 | (change-roots-map 51 | body rm))))) 52 | 53 | (defmacro describe.ng [options & body] 54 | (describe-ng-fn options body)) 55 | 56 | (defn it-uses-fn [names desc body] 57 | (it-fn desc 58 | (l (l 'js/inject 59 | (concat (l 'array) 60 | (map str names) 61 | (l (concat (l 'fn names) 62 | body))))))) 63 | 64 | (defmacro it-uses [names desc & body] 65 | (let [[desc body] (it-preprocess desc body)] 66 | (it-uses-fn names desc body))) 67 | 68 | (defn it-uses-filter-fn [filt desc body] 69 | (it-uses-fn '[$filter] 70 | desc 71 | (l (apply l 'let [filt (l '$filter (str filt))] 72 | body)))) 73 | 74 | (defmacro it-uses-filter [[filt] desc & body] 75 | (let [[desc body] (it-preprocess desc body)] 76 | (it-uses-filter-fn filt desc body))) 77 | 78 | (defn it-compiles-fn [ele html desc body] 79 | (it-uses-fn '[$compile $rootScope] 80 | desc 81 | (l (apply l 'let [ele (l (l '$compile html) '$rootScope)] 82 | body)))) 83 | 84 | (defmacro it-compiles [[ele html] desc & body] 85 | (let [[desc body] (it-preprocess desc body)] 86 | (it-compiles-fn ele html desc body))) 87 | 88 | (defn describe-controller-fn [options body] 89 | (let [{:keys [controller inject]} options 90 | ninject (conj inject ['$scope (l ['$rootScope '$controller] 91 | (l 'let ['scp# '($rootScope.$new)] 92 | (l '$controller (str controller) (l 'obj :$scope 'scp#)) 93 | 'scp#))])] 94 | (describe-ng-fn (-> options (assoc :inject ninject) (dissoc :controller)) body))) 95 | 96 | (defmacro describe.controller [options & body] 97 | (describe-controller-fn options body)) 98 | -------------------------------------------------------------------------------- /src/gyr/filters.cljs: -------------------------------------------------------------------------------- 1 | (ns gyr.filters 2 | (:require [goog.object :as o] 3 | [goog.array :as a] 4 | [purnam.native.functions :as j]) 5 | (:use-macros [purnam.core :only [obj arr ! def.n]] 6 | [gyr.core :only [def.module def.filter]])) 7 | 8 | (defn augment-fn-string [func] 9 | (if (string? func) 10 | (fn [x] 11 | (j/aget-in x (st/split func #"\."))) 12 | func)) 13 | 14 | (defn check-fn [func chk] 15 | (fn [x] 16 | (let [res (func x)] 17 | (if (fn? chk) 18 | (chk res) 19 | (= res chk))))) 20 | 21 | (def.module gyr.filters []) 22 | 23 | (def.filter gyr.filters.subArray [] 24 | (fn [input start end] 25 | (let [out (if input (a/clone input) (arr))] 26 | (a/slice out start end)))) 27 | 28 | (def.filter gyr.filters.pr [] 29 | (fn [input title] 30 | (js/console.log title input) 31 | input)) 32 | 33 | 34 | (def.filter gyr.filters.unique [] 35 | (fn [input] 36 | (a/removeDuplicates input (arr)))) 37 | 38 | (def.filter gyr.filters.toArray [] 39 | (fn [input] 40 | (a/toArray input))) 41 | 42 | (def.filter gyr.filters.toObject [] 43 | (fn [input kfunc] 44 | (a/toObject input kfunc))) 45 | 46 | (def.filter gyr.filters.call [] 47 | (fn [input func & args] 48 | (apply func input args))) 49 | 50 | (def.filter gyr.filters.apply [] 51 | (fn [input func args] 52 | (.apply func args))) 53 | 54 | (def.filter gyr.filters.map [] 55 | (fn [input func] 56 | (.map input (augment-fn-string func)))) 57 | 58 | (def.filter gyr.filters.filter [] 59 | (fn 60 | ([input func] 61 | (a/filter input (augment-fn-string func))) 62 | ([input func chk] 63 | (a/filter input 64 | (check-fn (augment-fn-string func) chk))))) 65 | 66 | (def.filter gyr.filters.take [] 67 | (fn [input num] 68 | (a/slice input 0 num))) 69 | 70 | (def.filter gyr.filters.drop [] 71 | (fn [input num] 72 | (a/slice input num))) 73 | 74 | (def.filter gyr.filters.flatten [] 75 | (fn [input] 76 | (a/flatten input))) 77 | 78 | (def.filter gyr.filters.count [] 79 | (fn [input] 80 | (.-length input))) 81 | 82 | (def.filter gyr.filters.sortBy [] 83 | (fn 84 | ([input func] 85 | (let [f (augment-fn-string func) 86 | out (a/clone input)] 87 | (.sort out 88 | (fn [a b] 89 | (> (f a) (f b)))) 90 | out)) 91 | 92 | ([input func rev] 93 | (let [f (augment-fn-string func) 94 | out (a/clone input)] 95 | (a/sort out 96 | (fn [a b] 97 | (< (f a) (f b)))) 98 | out)))) 99 | 100 | (def.filter gyr.filters.change [] 101 | (fn [input] 102 | (let [out (arr)] 103 | (input.forEach 104 | (fn [v] (out.push v))) 105 | out))) 106 | 107 | (def.filter gyr.filters.partition [] 108 | (fn [input n] 109 | (loop [i 0 110 | j -1 111 | out (arr)] 112 | (cond (>= i input.length) out 113 | 114 | (= 0 (mod i n)) 115 | (let [oarr (arr input.|i|)] 116 | (! input.|i|.$$hashKey (str "A" i)) 117 | (! oarr.$$hashKey (str "A" i)) 118 | (out.push oarr) 119 | (recur (inc i) (inc j) out)) 120 | 121 | :else 122 | (do (! input.|i|.$$hashKey (str "A" i)) 123 | (out.|j|.push input.|i|) 124 | (recur (inc i) j out)))))) 125 | ;; TODO 126 | (def.filter gyr.filters.groupBy [] 127 | (fn 128 | ([input func] 129 | (a/bucket input (augment-fn-string func))) 130 | ([input func chk] 131 | (a/bucket input 132 | (check-fn (augment-fn-string func) chk))))) 133 | 134 | -------------------------------------------------------------------------------- /test/cljs/midje_doc/api/test_gyr.cljs: -------------------------------------------------------------------------------- 1 | (ns midje-doc.api.test-gyr 2 | (:require [purnam.test] 3 | [purnam.native] 4 | [midje-doc.api.gyr :as test-app]) 5 | (:use-macros [purnam.core :only [f.n def.n obj arr]] 6 | [purnam.test :only [describe is it]] 7 | [gyr.core :only [def.module def.controller def.service]] 8 | [gyr.test :only [describe.ng describe.controller it-uses it-uses-filter it-compiles]])) 9 | 10 | [[:chapter {:title "gyr.test" :tag "gyr-test"}]] 11 | 12 | [[:section {:title "init" :tag "gyr-init"}]] 13 | 14 | "All tests require the following declaration in the namespace. Note that `purnam.test` is required" 15 | 16 | (comment 17 | (:require [purnam.test]) 18 | (:use-macros [gyr.test :only 19 | [describe.ng it-uses it-compiles it-uses-filter]])) 20 | 21 | [[:section {:title "services" :tag "services"}]] 22 | "Angular constants, values, services, factories and providers are all tested the same way: with `describe.ng` and `it-uses`. There are a couple ways of testing out functionality. The easiest is with `:inject`. The definition are from previously defined examples - [constant](#def-constant), [value](#def-value)" 23 | 24 | (describe.ng 25 | {:doc "The Meaning of Life" 26 | :module my.app 27 | :inject [MeaningOfLife AnotherMeaningOfLife]} 28 | 29 | (it "should be contentious" 30 | (is MeaningOfLife 42) 31 | (is AnotherMeaningOfLife "A Mystery"))) 32 | 33 | "`:inject` can also be used to bind other values within the modules" 34 | 35 | (describe.ng 36 | {:doc "The Meaning of Life" 37 | :module my.app 38 | :inject [[MeaningOfLife ([] "Don't Know")]]} 39 | 40 | (it "should be unclear" 41 | (is MeaningOfLife "Don't Know"))) 42 | 43 | "or to reference other defined services:" 44 | 45 | (describe.ng 46 | {:doc "The Meaning of Life" 47 | :module my.app 48 | :inject [[AnotherMeaningOfLife 49 | ([MeaningOfLife] 50 | (+ 13 MeaningOfLife))]]} 51 | 52 | (it "should be another random number" 53 | (is AnotherMeaningOfLife 55))) 54 | 55 | "[services](#def-service) are tested the same way. However this time, we will use `it-uses` to inject them into the test suite. It uses just puts the default" 56 | 57 | (describe.ng 58 | {:doc "Login Service" 59 | :module my.app} 60 | 61 | (it-uses [LoginService] 62 | (is LoginService.user.login "login") 63 | (is LoginService.user.password "secret"))) 64 | 65 | [[:section {:title "filters" :tag "filters"}]] 66 | 67 | "Filters are dependent on the `$filters` service and so are tested in the following way." 68 | 69 | (describe.ng 70 | {:doc "Testing Filters" 71 | :module my.app} 72 | 73 | (it-uses [$filter] 74 | (let [r (($filter "range") (arr) 5)] 75 | (is r.length 5) 76 | (is r (arr 0 1 2 3 4))))) 77 | 78 | "A convience macro `it-uses-filter` can also be used to achieve the same effect." 79 | 80 | (describe.ng 81 | {:doc "Testing Filters" 82 | :module my.app} 83 | 84 | (it-uses-filter [range] 85 | (let [r (range (arr) 5)] 86 | (is r.length 5) 87 | (is r (arr 0 1 2 3 4))))) 88 | 89 | [[:section {:title "directives" :tag "directives"}]] 90 | 91 | "Directives require the `$compile` and `$rootScope` services." 92 | 93 | (describe.ng 94 | {:doc "Directives" 95 | :module my.app 96 | :inject [$compile $rootScope]} 97 | 98 | (it "should change the html output" 99 | (let [ele (($compile "
User
") 100 | $rootScope)] 101 | ;;(js/console.log ele (ele.html)) 102 | (is (ele.html) (type "Welome User"))))) 103 | 104 | "For convenience, there is the `it-compiles` macro which hides the implementation details." 105 | 106 | (describe.ng 107 | {:doc "Directives" 108 | :module my.app} 109 | 110 | (it-compiles [ele "
User
"] 111 | (is (ele.html) (type "Welome User")))) 112 | 113 | [[:section {:title "controllers" :tag "controllers"}]] 114 | 115 | "Controllers require a bit more boilerplate to set up testing. The following is a test for [SimpleCtrl](#def-controller) using describe.ng" 116 | 117 | (describe.ng 118 | {:doc "A sample controller for testing purposes" 119 | :module my.app 120 | :inject [[$scope ([$rootScope $controller] 121 | ($controller "SimpleCtrl" (obj :$scope ($rootScope.$new))))]]} 122 | 123 | (it "should set a message within the $scope" 124 | (is $scope.msg "Hello")) 125 | 126 | (it "should be able to change the message within the $scope" 127 | (do ($scope.setMessage "World!") 128 | (is $scope.msg "World!")) 129 | 130 | (do ($scope.setMessage "Angular Rocks!") 131 | (is $scope.msg "Angular Rocks!")))) 132 | 133 | "`describe.controller` hides all implementation details to make the tests much more clearer to read." 134 | 135 | (describe.controller 136 | {:doc "A sample controller for testing purposes" 137 | :module my.app 138 | :controller SimpleCtrl} 139 | 140 | (it "should set a message within the $scope" 141 | (is $scope.msg "Hello")) 142 | 143 | (it "should be able to change the message within the $scope" 144 | (do ($scope.setMessage "World!") 145 | (is $scope.msg "World!")) 146 | 147 | (do ($scope.setMessage "Angular Rocks!") 148 | (is $scope.msg "Angular Rocks!")))) 149 | 150 | 151 | -------------------------------------------------------------------------------- /test/cljs/purnam/test_angular.cljs: -------------------------------------------------------------------------------- 1 | (ns purnam.test-angular 2 | (:require [goog.object :as o] 3 | [purnam.test]) 4 | (:use-macros [purnam.core :only [obj arr ! def.n]] 5 | [purnam.test :only [describe it is is-not]] 6 | [gyr.core :only [def.module def.config def.factory 7 | def.filter def.controller 8 | def.service def.directive]] 9 | [gyr.test :only [describe.controller describe.ng 10 | it-uses it-compiles it-uses-filter]])) 11 | 12 | 13 | (def.module sample.filters []) 14 | 15 | (def.filter sample.filters.range [] 16 | (fn [input total] 17 | (when input 18 | (doseq [i (range (js/parseInt total))] 19 | (input.push i)) 20 | input))) 21 | 22 | (let [spec (js-obj)] 23 | (js/describe "Testing Filters" 24 | (clojure.core/fn [] (js/beforeEach 25 | (js/module "sample.filters")) 26 | (js/beforeEach 27 | (js/inject 28 | (array "$filter" 29 | (fn [$filter] 30 | (aset spec "$filter" $filter))))) 31 | 32 | (it (let [r ((let [obj# (purnam.native.functions/aget-in spec []) 33 | fn# (aget obj# "$filter")] 34 | (.call fn# obj# "range")) (arr) 5)] 35 | (is (purnam.native.functions/aget-in r ["length"]) 5) 36 | (is (purnam.native.functions/aget-in r ["0"]) 0))) nil))) 37 | 38 | 39 | (describe.ng 40 | {:doc "Testing Filters" 41 | :module sample.filters 42 | :inject [$filter]} 43 | 44 | (it "can test for range filters" 45 | (let [r (($filter "range") (arr) 5)] 46 | (is r.length 5) 47 | (is r.0 0) 48 | (is r.1 1)))) 49 | 50 | (describe.ng 51 | {:doc "Testing Filters" 52 | :module sample.filters} 53 | 54 | (it-uses [$filter] 55 | (let [r (($filter "range") (arr) 5)] 56 | (is r.length 5) 57 | (is r.0 0))) 58 | 59 | (it-uses-filter 60 | [range] 61 | (is-not range nil) 62 | (let [r (range (arr) 5)] 63 | (is r.length 5) 64 | (is r.0 0)))) 65 | 66 | (def.module sample [sample.filters]) 67 | 68 | (def.directive sample.spWelcome [] 69 | (fn [$scope element attrs] 70 | (let [html (element.html)] 71 | (element.html (str "Welome " html ""))))) 72 | 73 | (describe.ng 74 | {:doc "Testing Directives" 75 | :module sample 76 | :inject [$compile $rootScope]} 77 | 78 | (it "Testing the Compilation" 79 | (let [ele (($compile "
User
") 80 | $rootScope)] 81 | (is (ele.html) "Welome User")))) 82 | 83 | (describe.ng 84 | {:doc "Testing Directives" 85 | :module sample} 86 | 87 | (it-compiles [ele "
User
"] 88 | (is (ele.html) "Welome User"))) 89 | 90 | 91 | (def.service sample.SimpleService [] 92 | (obj :user {:login "login"} 93 | :changeLogin (fn [login] 94 | (! this.user.login login)))) 95 | 96 | ;; 97 | ;; Angular Module Testing for Simple Service 98 | ;; 99 | 100 | (describe.ng 101 | {:doc "Simple Services Test" 102 | :module sample 103 | :inject [SimpleService] 104 | :globals [compare (obj :login "login" 105 | :password "secret" 106 | :greeting "hello world")]} 107 | 108 | (it "SimpleService Basics" 109 | (is-not SimpleService.user compare) 110 | ;;(is-equal SimpleService.user compare) 111 | ) 112 | 113 | (it "SimpleService Change Login" 114 | (is SimpleService.user.login "login") 115 | 116 | (do (SimpleService.changeLogin "newLogin") 117 | (is SimpleService.user.login "newLogin"))) 118 | 119 | (it "SimpleService Change Login" 120 | (is SimpleService.user.login "login"))) 121 | 122 | ;; 123 | ;; Angular Test Controller Example 124 | ;; 125 | 126 | 127 | (def.controller sample.SimpleCtrl [$scope] 128 | (! $scope.msg "Hello") 129 | (! $scope.setMessage (fn [msg] (! $scope.msg msg)))) 130 | 131 | ;; 132 | ;; Controller Testing 133 | ;; 134 | 135 | 136 | (describe.ng 137 | {:doc "A sample controller for testing purposes" 138 | :module sample 139 | :inject [[$scope ([$rootScope $controller] 140 | ($controller "SimpleCtrl" (obj :$scope ($rootScope.$new))))]]} 141 | 142 | (it "should have an object called `spec`" 143 | (is-not spec js/undefined)) 144 | 145 | (it "should set a message within the $scope" 146 | (is spec.$scope.msg "Hello") ;; The $scope is automatically registered for us 147 | (is $scope.msg "Hello") ;; We can also use spec.$scope 148 | ) 149 | 150 | (it "should be able to change the message within the $scope" 151 | (do ($scope.setMessage "World!") 152 | (is $scope.msg "World!")) 153 | 154 | (do ($scope.setMessage "Angular Rocks!") 155 | (is $scope.msg "Angular Rocks!")))) 156 | 157 | (describe.controller 158 | {:doc "A sample controller for testing purposes" 159 | :module sample 160 | :controller SimpleCtrl} 161 | 162 | (it "should have an object called `spec`" 163 | (is-not spec js/undefined)) 164 | 165 | (it "should set a message within the $scope" 166 | (is spec.$scope.msg "Hello") ;; The $scope is automatically registered for us 167 | (is $scope.msg "Hello") ;; We can also use spec.$scope 168 | ) 169 | 170 | (it "should be able to change the message within the $scope" 171 | (do ($scope.setMessage "World!") 172 | (is $scope.msg "World!")) 173 | 174 | (do ($scope.setMessage "Angular Rocks!") 175 | (is $scope.msg "Angular Rocks!")))) 176 | 177 | (def.module hello []) 178 | (def.factory hello.Data [] 179 | (obj :a 1)) 180 | 181 | (describe.ng 182 | {:doc "Simple Services Test" 183 | :module hello 184 | :inject [Data]} 185 | (it "SimpleService Change Login" 186 | (is Data.a 1) 187 | (is 1 1) 188 | )) 189 | -------------------------------------------------------------------------------- /test/cljs/midje_doc/api/gyr.cljs: -------------------------------------------------------------------------------- 1 | (ns midje-doc.api.gyr 2 | (:require [purnam.test]) 3 | (:use-macros [purnam.core :only [! f.n def.n obj arr]] 4 | [gyr.core :only [def.module def.controller 5 | def.value def.constant 6 | def.filter def.factory 7 | def.provider def.service 8 | def.directive def.config]] 9 | [purnam.test :only [describe is it]] 10 | [gyr.test :only [describe.ng describe.controller it-uses]])) 11 | 12 | [[:chapter {:title "gyr.core" :tag "gyr-core"}]] 13 | 14 | "Libraries to work with angular.js" 15 | 16 | [[:section {:title "init" :tag "init-angular"}]] 17 | 18 | "`gyr.core` macros are imported through the :use-macro call:" 19 | 20 | (comment 21 | (:use-macros [gyr.core :only [def.module def.controller 22 | def.value def.constant 23 | def.filter def.factory 24 | def.provider def.service 25 | def.directive def.config]])) 26 | 27 | 28 | [[:section {:title "def.module" :tag "def-module"}]] 29 | 30 | "`def.module` provides an easy way to define angular modules. The following clojurescript code generates the equivalent javascript code below it:" 31 | 32 | [[{:hide true}]] 33 | (def.module my.app []) 34 | 35 | (comment 36 | (def.module my.app [ui ui.bootstrap])) 37 | 38 | [[{:lang "js"}]] 39 | [[:code "angular.module('my.app', ['ui', 'ui.bootstrap'])"]] 40 | 41 | "Typically, the `def.module` is at the very top of the file, one module is defined for one clojure namespace." 42 | 43 | [[:section {:title "def.config" :tag "def-config"}]] 44 | 45 | "`def.config` is used to setup module providers. " 46 | 47 | (comment 48 | (def.config [... ...] 49 | ... 50 | 51 | ... )) 52 | 53 | "It is most commonly used to setup the routing for an application." 54 | 55 | (comment 56 | (def.config my.app [$locationProvider $routeProvider] 57 | (doto $locationProvider (.hashPrefix "!")) 58 | (doto $routeProvider 59 | (.when "" (obj :redirectTo "/home"))))) 60 | 61 | "The equivalent javascript code can be seen below." 62 | 63 | [[{:lang "js"}]] 64 | [[:code "angular.module('my.app') 65 | .config(['$locationProvider', '$routeProvider', 66 | function($locationProvider, $routeProvider){ 67 | $locationProvider.hashPrefix('!'); 68 | $routeProvider.when('', {redirectTo: '/home'}); 69 | }]);"]] 70 | 71 | [[:section {:title "def.controller" :tag "def-controller"}]] 72 | 73 | "`def.controller` defines a controller. The typical usage is like this:" 74 | 75 | (comment 76 | (def.controller . [... ...] 77 | ... 78 | 79 | ... )) 80 | 81 | "A sample controller" 82 | 83 | (def.controller my.app.SimpleCtrl [$scope] 84 | (! $scope.msg "Hello") 85 | (! $scope.setMessage (fn [msg] (! $scope.msg msg)))) 86 | 87 | "Produces the equivalent javascript code:" 88 | 89 | [[{:lang "js"}]] 90 | [[:code "angular.module('my.app') 91 | .controller('SimpleCtrl', ['$scope', function($scope){ 92 | $scope.msg = 'Hello' 93 | $scope.setMessage = function (msg){ 94 | $scope.msg = msg; 95 | }}])"]] 96 | 97 | [[:section {:title "def.directive" :tag "def-directive"}]] 98 | 99 | "`def.directive` defines a directive. The typical usage is like this:" 100 | 101 | (comment 102 | (def.directive . [... ...] 103 | ;; Initialisation code to return a function: 104 | (fn [$scope element attrs] 105 | .... .... )) 106 | ) 107 | 108 | "A sample directive" 109 | 110 | (def.directive my.app.appWelcome [] 111 | (fn [$scope element attrs] 112 | (let [html (element.html)] 113 | (element.html (str "Welcome " html ""))))) 114 | 115 | "Produces the equivalent javascript code:" 116 | 117 | [[{:lang "js"}]] 118 | [[:code "angular.module('my.app') 119 | .directive('appWelcome', [function() { 120 | return function($scope, element, attrs) { 121 | var html = element.html(); 122 | element.html('Welcome: ' + html + ''); 123 | };}]);"]] 124 | 125 | [[:section {:title "def.filter" :tag "def-filter"}]] 126 | 127 | "`def.filter` defines a filter. The typical usage is like this:" 128 | 129 | (comment 130 | (def.filter . [... ...] 131 | 132 | ;; Initialisation code to return a function: 133 | 134 | (fn [input & args] 135 | .... .... ))) 136 | 137 | "The sample filter" 138 | 139 | (def.filter my.app.range [] 140 | (fn [input total] 141 | (when input 142 | (doseq [i (range (js/parseInt total))] 143 | (input.push i)) 144 | input))) 145 | 146 | "Produces the equivalent javascript code:" 147 | 148 | [[{:lang "js"}]] 149 | [[:code "angular.module('my.app') 150 | .filter('range', [function() { 151 | return function(input, total) { 152 | if(!input) return null; 153 | total = parseInt(total); 154 | for (var i=0; i . 166 | )) 167 | 168 | "The sample constant" 169 | 170 | (def.constant my.app.MeaningOfLife 42) 171 | 172 | "Produces the equivalent javascript code:" 173 | 174 | [[{:lang "js"}]] 175 | [[:code 176 | "angular.module('my.app') 177 | .constant('MeaningOfLife', 42);"]] 178 | 179 | [[:section {:title "def.value" :tag "def-value"}]] 180 | 181 | "`def.value` defines a value. The typical usage is like this:" 182 | 183 | (comment 184 | (def.value . 185 | )) 186 | 187 | "The sample value" 188 | 189 | (def.value my.app.AnotherMeaningOfLife "A Mystery") 190 | 191 | "Produces the equivalent javascript code:" 192 | 193 | [[{:lang "js"}]] 194 | [[:code 195 | "angular.module('my.app') 196 | .value('AnotherMeaningOfLife', 'A Mystery');"]] 197 | 198 | [[:section {:title "def.service" :tag "def-service"}]] 199 | 200 | "`def.service` defines a service. The typical usage is like this:" 201 | 202 | (comment 203 | (def.service . [... ...] 204 | )) 205 | 206 | "The sample service" 207 | 208 | (def.service my.app.LoginService [] 209 | (obj :user {:login "login" 210 | :password "secret" 211 | :greeting "hello world"} 212 | :changeLogin (fn [login] 213 | (! this.user.login login)))) 214 | 215 | "Produces the equivalent javascript code:" 216 | 217 | [[{:lang "js"}]] 218 | [[:code 219 | "angular.module('my.app') 220 | .service('LoginService', [function(){ 221 | return {user: {:login 'login', 222 | :password 'secret', 223 | :greeting 'hello world'}, 224 | changeLogin: function (login){ 225 | this.user.login = login;}}}]);"]] 226 | 227 | [[:section {:title "def.factory" :tag "def-factory"}]] 228 | 229 | "`def.factory` defines a factory. The typical usage is like this:" 230 | 231 | (comment 232 | (def.factory . [... ...] 233 | )) 234 | 235 | "The sample factory" 236 | 237 | (comment 238 | (def.factory my.app.LoginService [] 239 | (obj :user {:login "login" 240 | :password "secret" 241 | :greeting "hello world"} 242 | :changeLogin (fn [login] 243 | (! this.user.login login))))) 244 | 245 | "Produces the equivalent javascript code:" 246 | 247 | [[{:lang "js"}]] 248 | [[:code 249 | "angular.module('my.app') 250 | .factory('LoginService', [function(){ 251 | return {user: {:login 'login', 252 | :password 'secret', 253 | :greeting 'hello world'}, 254 | changeLogin: function (login){ 255 | this.user.login = login;}}}]);"]] 256 | 257 | 258 | [[:section {:title "def.provider" :tag "def-provider"}]] 259 | 260 | "`def.provider` defines a provider. The typical usage is like this:" 261 | 262 | (comment 263 | (def.provider . [... ...] 264 | )) 265 | 266 | "The following is a definition, configuration, and usage of a provider" 267 | 268 | (def.provider my.app.HelloWorld [] 269 | (obj :name "Default" 270 | :$get (fn [] 271 | (let [n self.name] 272 | (obj :sayHello 273 | (fn [] (str "Hello " n "!"))))) 274 | :setName (fn [name] 275 | (! self.name name)))) 276 | 277 | (def.config my.app [HelloWorldProvider] 278 | (HelloWorldProvider.setName "World")) 279 | 280 | (def.controller my.app.sfpMainCtrl [$scope HelloWorld] 281 | (! $scope.hello (str (HelloWorld.sayHello) " From Provider"))) 282 | -------------------------------------------------------------------------------- /test/clj/gyr/test_test.clj: -------------------------------------------------------------------------------- 1 | (ns gyr.test-test 2 | (:use [midje.sweet :exclude [contains]] 3 | purnam.checks 4 | gyr.test)) 5 | 6 | 7 | (fact "describe.ng" 8 | (macroexpand-1 9 | '(describe.ng 10 | {:doc "" 11 | :module 12 | :inject [[ ] 13 | 14 | ] 15 | :vars [ ]} 16 | )) 17 | => '(let [spec (js-obj)] 18 | (aset spec "" ) 19 | (js/describe 20 | "" 21 | (clojure.core/fn [] 22 | (js/beforeEach (js/module "")) 23 | (js/beforeEach 24 | (js/inject (array "" "" "" 25 | (fn [ ] 26 | (aset spec "" ) 27 | (aset spec "" ) 28 | (aset spec "" ))))) 29 | 30 | nil)))) 31 | 32 | (fact "describe.ng" 33 | (macroexpand-1 34 | '(describe.ng 35 | {:doc "Testing" 36 | :module my.app 37 | :inject [$http 38 | [$compile $compile] 39 | [$filter $mockFilter] 40 | [$scope 41 | ([$rootScope] 42 | ($rootScope.$new))] 43 | [$httpBackend 44 | ([$httpBackend] 45 | (do (-> $httpBackend 46 | (.when "GET" "/hello") 47 | (.respond 200 "hello world")) 48 | $httpBackend))]] 49 | :vars [o (range 20)]})) 50 | => '(let [spec (js-obj)] 51 | (aset spec "o" (range 20)) 52 | (js/describe 53 | "Testing" 54 | (clojure.core/fn [] 55 | (js/beforeEach (js/module "my.app")) 56 | (js/beforeEach 57 | (js/inject 58 | (array "$httpBackend" "$http" "$mockFilter" "$compile" "$rootScope" 59 | (fn [$httpBackend $http $mockFilter $compile $rootScope] 60 | (aset spec "$http" $http) 61 | (aset spec "$compile" $compile) 62 | (aset spec "$filter" $mockFilter) 63 | (aset spec "$scope" 64 | (let [obj# (purnam.common/aget-in $rootScope []) 65 | fn# (aget obj# "$new")] 66 | (.call fn# obj#))) 67 | (aset spec "$httpBackend" 68 | (do (-> $httpBackend 69 | (.when "GET" "/hello") 70 | (.respond 200 "hello world")) 71 | $httpBackend)))))) 72 | nil)))) 73 | 74 | 75 | (fact "is-uses" 76 | (macroexpand-1 77 | '(it-uses 78 | [$compile] 79 | ($compile "oeuoeuoe"))) 80 | => '(js/it "" (clojure.core/fn [] 81 | (js/inject (array "$compile" 82 | (fn [$compile] 83 | ($compile "oeuoeuoe"))))))) 84 | 85 | (fact "is-uses-filter" 86 | (macroexpand-1 87 | '(it-uses-filter 88 | [range] 89 | (is-not range nil) 90 | (let [r (range (arr) 5)] 91 | (is r.length 5) 92 | (is r.0 0)))) 93 | => '(js/it "" (clojure.core/fn [] 94 | (js/inject (array "$filter" 95 | (fn [$filter] 96 | (let [range ($filter "range")] 97 | (is-not range nil) 98 | (let [r (range (arr) 5)] 99 | (is r.length 5) 100 | (is r.0 0))))))))) 101 | 102 | (fact "it-compiles" 103 | (macroexpand-1 104 | '(it-compiles 105 | [ele "
User
"] 106 | "Testing the Compilation" 107 | (is (ele.html) "Welome User"))) 108 | => '(js/it "Testing the Compilation" 109 | (clojure.core/fn [] 110 | (js/inject (array "$compile" "$rootScope" 111 | (fn [$compile $rootScope] 112 | (let [ele (($compile "
User
") 113 | $rootScope)] 114 | (is (ele.html) "Welome User")))))))) 115 | 116 | 117 | (fact "describe.ng" 118 | (macroexpand-1 119 | '(describe.ng 120 | {:doc "Testing Filters" 121 | :module sample.filters 122 | :inject [$filter]} 123 | 124 | (it (let [r (($filter "range") (arr) 5)] 125 | (is r.length 5) 126 | (is r.0 0))))) 127 | 128 | => '(let [spec (js-obj)] 129 | (js/describe 130 | "Testing Filters" 131 | (clojure.core/fn [] 132 | (js/beforeEach (js/module "sample.filters")) 133 | (js/beforeEach 134 | (js/inject 135 | (array "$filter" 136 | (fn [$filter] (aset spec "$filter" $filter))))) 137 | (it (let [r ((let [obj# (purnam.common/aget-in spec []) 138 | fn# (aget obj# "$filter")] 139 | (.call fn# obj# "range")) (arr) 5)] 140 | (purnam.test/is (purnam.common/aget-in r ["length"]) 5 "'r.length'" "'5'") 141 | (purnam.test/is (purnam.common/aget-in r ["0"]) 0 "'r.0'" "'0'"))) nil)))) 142 | 143 | (fact "describe.controller" 144 | (macroexpand-1 145 | '(describe.controller 146 | {:doc "" 147 | :module 148 | :controller 149 | :inject {: 150 | : }} 151 | ( $scope.) 152 | ( .) 153 | ( .))) 154 | => 155 | '(let [spec (js-obj)] 156 | (js/describe 157 | "" 158 | (clojure.core/fn [] 159 | (js/beforeEach (js/module "")) 160 | (js/beforeEach 161 | (js/inject 162 | (array "$controller" "" "$rootScope" "" 163 | (fn [$controller 164 | $rootScope ] 165 | (aset spec "$scope" 166 | (let [scp# 167 | (let [obj# (purnam.common/aget-in $rootScope []) 168 | fn# (aget obj# "$new")] 169 | (.call fn# obj#))] 170 | ($controller "" 171 | (obj :$scope scp#)) scp#)) 172 | (aset spec ":" ) 173 | (aset spec ":" ))))) 174 | ( (purnam.common/aget-in spec ["$scope" ""])) 175 | ( (purnam.common/aget-in [""])) 176 | ( (purnam.common/aget-in [""])) nil)))) 177 | 178 | 179 | (comment 180 | (fact "ng" 181 | (macroexpand-1 182 | '(ng [] 183 | "" 184 | )) 185 | => '(js/it "" (js/inject (array "" (fn [] )))) 186 | 187 | (macroexpand-1 188 | '(ng [] 189 | )) 190 | => '(js/it "" (js/inject (array "" (fn [] )))) 191 | 192 | (macroexpand-1 193 | '(ng [] 194 | "" 195 | ))) 196 | 197 | (fact "ng-filter" 198 | (macroexpand-1 199 | '(ng-filter [] 200 | "" 201 | )) 202 | => '(js/it "" 203 | (js/inject 204 | (array "$filter" 205 | (fn [$filter] 206 | (let [ ($filter "")] 207 | )))))) 208 | 209 | (fact "ng-compile" 210 | (macroexpand-1 211 | '(ng-compile [ ] 212 | "" 213 | )) 214 | => '(js/it "" 215 | (js/inject 216 | (array "$compile" "$rootScope" 217 | (fn [$compile $rootScope] 218 | (let [ (($compile ) $rootScope)] 219 | )))))) 220 | 221 | (fact "describe.controller" 222 | (macroexpand-1 223 | '(describe.controller 224 | {:doc "" 225 | :module 226 | :controller } 227 | )) 228 | => 229 | '(let [spec (js-obj)] 230 | (describe {:doc "" 231 | :module 232 | :controller } 233 | (js/beforeEach (js/module "")) 234 | (js/beforeEach 235 | (js/inject 236 | (array "$rootScope" "$controller" 237 | (fn [$rootScope $controller] 238 | (! spec.$scope ($rootScope.$new)) 239 | ($controller "" spec))))) 240 | ))) 241 | 242 | 243 | (fact "describe.controller" 244 | (macroexpand-1 245 | '(describe.controller 246 | {:doc "" 247 | :module 248 | :controller } 249 | ( $scope.) 250 | ( $ctrl.))) 251 | => 252 | '(let [spec (js-obj)] 253 | (describe 254 | {:doc "" 255 | :module 256 | :controller } 257 | (js/beforeEach (js/module "")) 258 | (js/beforeEach 259 | (js/inject 260 | (array "$rootScope" "$controller" 261 | (fn [$rootScope $controller] 262 | (! spec.$scope ($rootScope.$new)) 263 | ($controller "" spec))))) 264 | ( spec.$scope.) 265 | ( $ctrl.)))) 266 | 267 | 268 | 269 | (fact "describe.controller will generate this type of template:" 270 | (macroexpand-1 271 | '(describe.controller 272 | {:doc "" 273 | :module 274 | :controller 275 | :inject {: 276 | : }} 277 | ( $scope.) 278 | ( .) 279 | ( .))) 280 | => 281 | '(let [spec (js-obj)] 282 | (describe 283 | {:doc "" 284 | :module 285 | :controller 286 | :inject {: 287 | : }} 288 | (js/beforeEach (js/module "")) 289 | (js/beforeEach 290 | (js/inject 291 | (array "$rootScope" "$controller" "" "" 292 | (fn [$rootScope $controller ] 293 | (! spec.$scope ($rootScope.$new)) 294 | (! spec. ) 295 | (! spec. ) 296 | ($controller "" spec))))) 297 | ( spec.$scope.) 298 | ( spec..) 299 | ( spec..)))) 300 | ) 301 | -------------------------------------------------------------------------------- /harness/js/angular-mocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.1.4 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | * 6 | * TODO(vojta): wrap whole file into closure during build 7 | */ 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name angular.mock 12 | * @description 13 | * 14 | * Namespace from 'angular-mocks.js' which contains testing related code. 15 | */ 16 | angular.mock = {}; 17 | 18 | /** 19 | * ! This is a private undocumented service ! 20 | * 21 | * @name ngMock.$browser 22 | * 23 | * @description 24 | * This service is a mock implementation of {@link ng.$browser}. It provides fake 25 | * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, 26 | * cookies, etc... 27 | * 28 | * The api of this service is the same as that of the real {@link ng.$browser $browser}, except 29 | * that there are several helper methods available which can be used in tests. 30 | */ 31 | angular.mock.$BrowserProvider = function() { 32 | this.$get = function(){ 33 | return new angular.mock.$Browser(); 34 | }; 35 | }; 36 | 37 | angular.mock.$Browser = function() { 38 | var self = this; 39 | 40 | this.isMock = true; 41 | self.$$url = "http://server/"; 42 | self.$$lastUrl = self.$$url; // used by url polling fn 43 | self.pollFns = []; 44 | 45 | // TODO(vojta): remove this temporary api 46 | self.$$completeOutstandingRequest = angular.noop; 47 | self.$$incOutstandingRequestCount = angular.noop; 48 | 49 | 50 | // register url polling fn 51 | 52 | self.onUrlChange = function(listener) { 53 | self.pollFns.push( 54 | function() { 55 | if (self.$$lastUrl != self.$$url) { 56 | self.$$lastUrl = self.$$url; 57 | listener(self.$$url); 58 | } 59 | } 60 | ); 61 | 62 | return listener; 63 | }; 64 | 65 | self.cookieHash = {}; 66 | self.lastCookieHash = {}; 67 | self.deferredFns = []; 68 | self.deferredNextId = 0; 69 | 70 | self.defer = function(fn, delay) { 71 | delay = delay || 0; 72 | self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); 73 | self.deferredFns.sort(function(a,b){ return a.time - b.time;}); 74 | return self.deferredNextId++; 75 | }; 76 | 77 | 78 | self.defer.now = 0; 79 | 80 | 81 | self.defer.cancel = function(deferId) { 82 | var fnIndex; 83 | 84 | angular.forEach(self.deferredFns, function(fn, index) { 85 | if (fn.id === deferId) fnIndex = index; 86 | }); 87 | 88 | if (fnIndex !== undefined) { 89 | self.deferredFns.splice(fnIndex, 1); 90 | return true; 91 | } 92 | 93 | return false; 94 | }; 95 | 96 | 97 | /** 98 | * @name ngMock.$browser#defer.flush 99 | * @methodOf ngMock.$browser 100 | * 101 | * @description 102 | * Flushes all pending requests and executes the defer callbacks. 103 | * 104 | * @param {number=} number of milliseconds to flush. See {@link #defer.now} 105 | */ 106 | self.defer.flush = function(delay) { 107 | if (angular.isDefined(delay)) { 108 | self.defer.now += delay; 109 | } else { 110 | if (self.deferredFns.length) { 111 | self.defer.now = self.deferredFns[self.deferredFns.length-1].time; 112 | } else { 113 | throw Error('No deferred tasks to be flushed'); 114 | } 115 | } 116 | 117 | while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { 118 | self.deferredFns.shift().fn(); 119 | } 120 | }; 121 | /** 122 | * @name ngMock.$browser#defer.now 123 | * @propertyOf ngMock.$browser 124 | * 125 | * @description 126 | * Current milliseconds mock time. 127 | */ 128 | 129 | self.$$baseHref = ''; 130 | self.baseHref = function() { 131 | return this.$$baseHref; 132 | }; 133 | }; 134 | angular.mock.$Browser.prototype = { 135 | 136 | /** 137 | * @name ngMock.$browser#poll 138 | * @methodOf ngMock.$browser 139 | * 140 | * @description 141 | * run all fns in pollFns 142 | */ 143 | poll: function poll() { 144 | angular.forEach(this.pollFns, function(pollFn){ 145 | pollFn(); 146 | }); 147 | }, 148 | 149 | addPollFn: function(pollFn) { 150 | this.pollFns.push(pollFn); 151 | return pollFn; 152 | }, 153 | 154 | url: function(url, replace) { 155 | if (url) { 156 | this.$$url = url; 157 | return this; 158 | } 159 | 160 | return this.$$url; 161 | }, 162 | 163 | cookies: function(name, value) { 164 | if (name) { 165 | if (value == undefined) { 166 | delete this.cookieHash[name]; 167 | } else { 168 | if (angular.isString(value) && //strings only 169 | value.length <= 4096) { //strict cookie storage limits 170 | this.cookieHash[name] = value; 171 | } 172 | } 173 | } else { 174 | if (!angular.equals(this.cookieHash, this.lastCookieHash)) { 175 | this.lastCookieHash = angular.copy(this.cookieHash); 176 | this.cookieHash = angular.copy(this.cookieHash); 177 | } 178 | return this.cookieHash; 179 | } 180 | }, 181 | 182 | notifyWhenNoOutstandingRequests: function(fn) { 183 | fn(); 184 | } 185 | }; 186 | 187 | 188 | /** 189 | * @ngdoc object 190 | * @name ngMock.$exceptionHandlerProvider 191 | * 192 | * @description 193 | * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed 194 | * into the `$exceptionHandler`. 195 | */ 196 | 197 | /** 198 | * @ngdoc object 199 | * @name ngMock.$exceptionHandler 200 | * 201 | * @description 202 | * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed 203 | * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration 204 | * information. 205 | * 206 | * 207 | *
 208 |  *   describe('$exceptionHandlerProvider', function() {
 209 |  *
 210 |  *     it('should capture log messages and exceptions', function() {
 211 |  *
 212 |  *       module(function($exceptionHandlerProvider) {
 213 |  *         $exceptionHandlerProvider.mode('log');
 214 |  *       });
 215 |  *
 216 |  *       inject(function($log, $exceptionHandler, $timeout) {
 217 |  *         $timeout(function() { $log.log(1); });
 218 |  *         $timeout(function() { $log.log(2); throw 'banana peel'; });
 219 |  *         $timeout(function() { $log.log(3); });
 220 |  *         expect($exceptionHandler.errors).toEqual([]);
 221 |  *         expect($log.assertEmpty());
 222 |  *         $timeout.flush();
 223 |  *         expect($exceptionHandler.errors).toEqual(['banana peel']);
 224 |  *         expect($log.log.logs).toEqual([[1], [2], [3]]);
 225 |  *       });
 226 |  *     });
 227 |  *   });
 228 |  * 
229 | */ 230 | 231 | angular.mock.$ExceptionHandlerProvider = function() { 232 | var handler; 233 | 234 | /** 235 | * @ngdoc method 236 | * @name ngMock.$exceptionHandlerProvider#mode 237 | * @methodOf ngMock.$exceptionHandlerProvider 238 | * 239 | * @description 240 | * Sets the logging mode. 241 | * 242 | * @param {string} mode Mode of operation, defaults to `rethrow`. 243 | * 244 | * - `rethrow`: If any errors are are passed into the handler in tests, it typically 245 | * means that there is a bug in the application or test, so this mock will 246 | * make these tests fail. 247 | * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an 248 | * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. 249 | * See {@link ngMock.$log#assertEmpty assertEmpty()} and 250 | * {@link ngMock.$log#reset reset()} 251 | */ 252 | this.mode = function(mode) { 253 | switch(mode) { 254 | case 'rethrow': 255 | handler = function(e) { 256 | throw e; 257 | }; 258 | break; 259 | case 'log': 260 | var errors = []; 261 | 262 | handler = function(e) { 263 | if (arguments.length == 1) { 264 | errors.push(e); 265 | } else { 266 | errors.push([].slice.call(arguments, 0)); 267 | } 268 | }; 269 | 270 | handler.errors = errors; 271 | break; 272 | default: 273 | throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); 274 | } 275 | }; 276 | 277 | this.$get = function() { 278 | return handler; 279 | }; 280 | 281 | this.mode('rethrow'); 282 | }; 283 | 284 | 285 | /** 286 | * @ngdoc service 287 | * @name ngMock.$log 288 | * 289 | * @description 290 | * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays 291 | * (one array per logging level). These arrays are exposed as `logs` property of each of the 292 | * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. 293 | * 294 | */ 295 | angular.mock.$LogProvider = function() { 296 | 297 | function concat(array1, array2, index) { 298 | return array1.concat(Array.prototype.slice.call(array2, index)); 299 | } 300 | 301 | 302 | this.$get = function () { 303 | var $log = { 304 | log: function() { $log.log.logs.push(concat([], arguments, 0)); }, 305 | warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, 306 | info: function() { $log.info.logs.push(concat([], arguments, 0)); }, 307 | error: function() { $log.error.logs.push(concat([], arguments, 0)); } 308 | }; 309 | 310 | /** 311 | * @ngdoc method 312 | * @name ngMock.$log#reset 313 | * @methodOf ngMock.$log 314 | * 315 | * @description 316 | * Reset all of the logging arrays to empty. 317 | */ 318 | $log.reset = function () { 319 | /** 320 | * @ngdoc property 321 | * @name ngMock.$log#log.logs 322 | * @propertyOf ngMock.$log 323 | * 324 | * @description 325 | * Array of logged messages. 326 | */ 327 | $log.log.logs = []; 328 | /** 329 | * @ngdoc property 330 | * @name ngMock.$log#warn.logs 331 | * @propertyOf ngMock.$log 332 | * 333 | * @description 334 | * Array of logged messages. 335 | */ 336 | $log.warn.logs = []; 337 | /** 338 | * @ngdoc property 339 | * @name ngMock.$log#info.logs 340 | * @propertyOf ngMock.$log 341 | * 342 | * @description 343 | * Array of logged messages. 344 | */ 345 | $log.info.logs = []; 346 | /** 347 | * @ngdoc property 348 | * @name ngMock.$log#error.logs 349 | * @propertyOf ngMock.$log 350 | * 351 | * @description 352 | * Array of logged messages. 353 | */ 354 | $log.error.logs = []; 355 | }; 356 | 357 | /** 358 | * @ngdoc method 359 | * @name ngMock.$log#assertEmpty 360 | * @methodOf ngMock.$log 361 | * 362 | * @description 363 | * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. 364 | */ 365 | $log.assertEmpty = function() { 366 | var errors = []; 367 | angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { 368 | angular.forEach($log[logLevel].logs, function(log) { 369 | angular.forEach(log, function (logItem) { 370 | errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); 371 | }); 372 | }); 373 | }); 374 | if (errors.length) { 375 | errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + 376 | "log message was not checked and removed:"); 377 | errors.push(''); 378 | throw new Error(errors.join('\n---------\n')); 379 | } 380 | }; 381 | 382 | $log.reset(); 383 | return $log; 384 | }; 385 | }; 386 | 387 | 388 | (function() { 389 | var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; 390 | 391 | function jsonStringToDate(string){ 392 | var match; 393 | if (match = string.match(R_ISO8061_STR)) { 394 | var date = new Date(0), 395 | tzHour = 0, 396 | tzMin = 0; 397 | if (match[9]) { 398 | tzHour = int(match[9] + match[10]); 399 | tzMin = int(match[9] + match[11]); 400 | } 401 | date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); 402 | date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); 403 | return date; 404 | } 405 | return string; 406 | } 407 | 408 | function int(str) { 409 | return parseInt(str, 10); 410 | } 411 | 412 | function padNumber(num, digits, trim) { 413 | var neg = ''; 414 | if (num < 0) { 415 | neg = '-'; 416 | num = -num; 417 | } 418 | num = '' + num; 419 | while(num.length < digits) num = '0' + num; 420 | if (trim) 421 | num = num.substr(num.length - digits); 422 | return neg + num; 423 | } 424 | 425 | 426 | /** 427 | * @ngdoc object 428 | * @name angular.mock.TzDate 429 | * @description 430 | * 431 | * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. 432 | * 433 | * Mock of the Date type which has its timezone specified via constructor arg. 434 | * 435 | * The main purpose is to create Date-like instances with timezone fixed to the specified timezone 436 | * offset, so that we can test code that depends on local timezone settings without dependency on 437 | * the time zone settings of the machine where the code is running. 438 | * 439 | * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) 440 | * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* 441 | * 442 | * @example 443 | * !!!! WARNING !!!!! 444 | * This is not a complete Date object so only methods that were implemented can be called safely. 445 | * To make matters worse, TzDate instances inherit stuff from Date via a prototype. 446 | * 447 | * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is 448 | * incomplete we might be missing some non-standard methods. This can result in errors like: 449 | * "Date.prototype.foo called on incompatible Object". 450 | * 451 | *
 452 |    * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
 453 |    * newYearInBratislava.getTimezoneOffset() => -60;
 454 |    * newYearInBratislava.getFullYear() => 2010;
 455 |    * newYearInBratislava.getMonth() => 0;
 456 |    * newYearInBratislava.getDate() => 1;
 457 |    * newYearInBratislava.getHours() => 0;
 458 |    * newYearInBratislava.getMinutes() => 0;
 459 |    * newYearInBratislava.getSeconds() => 0;
 460 |    * 
461 | * 462 | */ 463 | angular.mock.TzDate = function (offset, timestamp) { 464 | var self = new Date(0); 465 | if (angular.isString(timestamp)) { 466 | var tsStr = timestamp; 467 | 468 | self.origDate = jsonStringToDate(timestamp); 469 | 470 | timestamp = self.origDate.getTime(); 471 | if (isNaN(timestamp)) 472 | throw { 473 | name: "Illegal Argument", 474 | message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" 475 | }; 476 | } else { 477 | self.origDate = new Date(timestamp); 478 | } 479 | 480 | var localOffset = new Date(timestamp).getTimezoneOffset(); 481 | self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; 482 | self.date = new Date(timestamp + self.offsetDiff); 483 | 484 | self.getTime = function() { 485 | return self.date.getTime() - self.offsetDiff; 486 | }; 487 | 488 | self.toLocaleDateString = function() { 489 | return self.date.toLocaleDateString(); 490 | }; 491 | 492 | self.getFullYear = function() { 493 | return self.date.getFullYear(); 494 | }; 495 | 496 | self.getMonth = function() { 497 | return self.date.getMonth(); 498 | }; 499 | 500 | self.getDate = function() { 501 | return self.date.getDate(); 502 | }; 503 | 504 | self.getHours = function() { 505 | return self.date.getHours(); 506 | }; 507 | 508 | self.getMinutes = function() { 509 | return self.date.getMinutes(); 510 | }; 511 | 512 | self.getSeconds = function() { 513 | return self.date.getSeconds(); 514 | }; 515 | 516 | self.getMilliseconds = function() { 517 | return self.date.getMilliseconds(); 518 | }; 519 | 520 | self.getTimezoneOffset = function() { 521 | return offset * 60; 522 | }; 523 | 524 | self.getUTCFullYear = function() { 525 | return self.origDate.getUTCFullYear(); 526 | }; 527 | 528 | self.getUTCMonth = function() { 529 | return self.origDate.getUTCMonth(); 530 | }; 531 | 532 | self.getUTCDate = function() { 533 | return self.origDate.getUTCDate(); 534 | }; 535 | 536 | self.getUTCHours = function() { 537 | return self.origDate.getUTCHours(); 538 | }; 539 | 540 | self.getUTCMinutes = function() { 541 | return self.origDate.getUTCMinutes(); 542 | }; 543 | 544 | self.getUTCSeconds = function() { 545 | return self.origDate.getUTCSeconds(); 546 | }; 547 | 548 | self.getUTCMilliseconds = function() { 549 | return self.origDate.getUTCMilliseconds(); 550 | }; 551 | 552 | self.getDay = function() { 553 | return self.date.getDay(); 554 | }; 555 | 556 | // provide this method only on browsers that already have it 557 | if (self.toISOString) { 558 | self.toISOString = function() { 559 | return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + 560 | padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + 561 | padNumber(self.origDate.getUTCDate(), 2) + 'T' + 562 | padNumber(self.origDate.getUTCHours(), 2) + ':' + 563 | padNumber(self.origDate.getUTCMinutes(), 2) + ':' + 564 | padNumber(self.origDate.getUTCSeconds(), 2) + '.' + 565 | padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' 566 | } 567 | } 568 | 569 | //hide all methods not implemented in this mock that the Date prototype exposes 570 | var unimplementedMethods = ['getUTCDay', 571 | 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 572 | 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', 573 | 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', 574 | 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', 575 | 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; 576 | 577 | angular.forEach(unimplementedMethods, function(methodName) { 578 | self[methodName] = function() { 579 | throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); 580 | }; 581 | }); 582 | 583 | return self; 584 | }; 585 | 586 | //make "tzDateInstance instanceof Date" return true 587 | angular.mock.TzDate.prototype = Date.prototype; 588 | })(); 589 | 590 | /** 591 | * @ngdoc function 592 | * @name angular.mock.createMockWindow 593 | * @description 594 | * 595 | * This function creates a mock window object useful for controlling access ot setTimeout, but mocking out 596 | * sufficient window's properties to allow Angular to execute. 597 | * 598 | * @example 599 | * 600 | *
 601 |     beforeEach(module(function($provide) {
 602 |       $provide.value('$window', window = angular.mock.createMockWindow());
 603 |     }));
 604 | 
 605 |     it('should do something', inject(function($window) {
 606 |       var val = null;
 607 |       $window.setTimeout(function() { val = 123; }, 10);
 608 |       expect(val).toEqual(null);
 609 |       window.setTimeout.expect(10).process();
 610 |       expect(val).toEqual(123);
 611 |     });
 612 |  * 
613 | * 614 | */ 615 | angular.mock.createMockWindow = function() { 616 | var mockWindow = {}; 617 | var setTimeoutQueue = []; 618 | 619 | mockWindow.document = window.document; 620 | mockWindow.getComputedStyle = angular.bind(window, window.getComputedStyle); 621 | mockWindow.scrollTo = angular.bind(window, window.scrollTo); 622 | mockWindow.navigator = window.navigator; 623 | mockWindow.setTimeout = function(fn, delay) { 624 | setTimeoutQueue.push({fn: fn, delay: delay}); 625 | }; 626 | mockWindow.setTimeout.queue = setTimeoutQueue; 627 | mockWindow.setTimeout.expect = function(delay) { 628 | if (setTimeoutQueue.length > 0) { 629 | return { 630 | process: function() { 631 | setTimeoutQueue.shift().fn(); 632 | } 633 | }; 634 | } else { 635 | expect('SetTimoutQueue empty. Expecting delay of ').toEqual(delay); 636 | } 637 | }; 638 | 639 | return mockWindow; 640 | }; 641 | 642 | /** 643 | * @ngdoc function 644 | * @name angular.mock.dump 645 | * @description 646 | * 647 | * *NOTE*: this is not an injectable instance, just a globally available function. 648 | * 649 | * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging. 650 | * 651 | * This method is also available on window, where it can be used to display objects on debug console. 652 | * 653 | * @param {*} object - any object to turn into string. 654 | * @return {string} a serialized string of the argument 655 | */ 656 | angular.mock.dump = function(object) { 657 | return serialize(object); 658 | 659 | function serialize(object) { 660 | var out; 661 | 662 | if (angular.isElement(object)) { 663 | object = angular.element(object); 664 | out = angular.element('
'); 665 | angular.forEach(object, function(element) { 666 | out.append(angular.element(element).clone()); 667 | }); 668 | out = out.html(); 669 | } else if (angular.isArray(object)) { 670 | out = []; 671 | angular.forEach(object, function(o) { 672 | out.push(serialize(o)); 673 | }); 674 | out = '[ ' + out.join(', ') + ' ]'; 675 | } else if (angular.isObject(object)) { 676 | if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { 677 | out = serializeScope(object); 678 | } else if (object instanceof Error) { 679 | out = object.stack || ('' + object.name + ': ' + object.message); 680 | } else { 681 | out = angular.toJson(object, true); 682 | } 683 | } else { 684 | out = String(object); 685 | } 686 | 687 | return out; 688 | } 689 | 690 | function serializeScope(scope, offset) { 691 | offset = offset || ' '; 692 | var log = [offset + 'Scope(' + scope.$id + '): {']; 693 | for ( var key in scope ) { 694 | if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { 695 | log.push(' ' + key + ': ' + angular.toJson(scope[key])); 696 | } 697 | } 698 | var child = scope.$$childHead; 699 | while(child) { 700 | log.push(serializeScope(child, offset + ' ')); 701 | child = child.$$nextSibling; 702 | } 703 | log.push('}'); 704 | return log.join('\n' + offset); 705 | } 706 | }; 707 | 708 | /** 709 | * @ngdoc object 710 | * @name ngMock.$httpBackend 711 | * @description 712 | * Fake HTTP backend implementation suitable for unit testing application that use the 713 | * {@link ng.$http $http service}. 714 | * 715 | * *Note*: For fake http backend implementation suitable for end-to-end testing or backend-less 716 | * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. 717 | * 718 | * During unit testing, we want our unit tests to run quickly and have no external dependencies so 719 | * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or 720 | * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is 721 | * to verify whether a certain request has been sent or not, or alternatively just let the 722 | * application make requests, respond with pre-trained responses and assert that the end result is 723 | * what we expect it to be. 724 | * 725 | * This mock implementation can be used to respond with static or dynamic responses via the 726 | * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). 727 | * 728 | * When an Angular application needs some data from a server, it calls the $http service, which 729 | * sends the request to a real server using $httpBackend service. With dependency injection, it is 730 | * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify 731 | * the requests and respond with some testing data without sending a request to real server. 732 | * 733 | * There are two ways to specify what test data should be returned as http responses by the mock 734 | * backend when the code under test makes http requests: 735 | * 736 | * - `$httpBackend.expect` - specifies a request expectation 737 | * - `$httpBackend.when` - specifies a backend definition 738 | * 739 | * 740 | * # Request Expectations vs Backend Definitions 741 | * 742 | * Request expectations provide a way to make assertions about requests made by the application and 743 | * to define responses for those requests. The test will fail if the expected requests are not made 744 | * or they are made in the wrong order. 745 | * 746 | * Backend definitions allow you to define a fake backend for your application which doesn't assert 747 | * if a particular request was made or not, it just returns a trained response if a request is made. 748 | * The test will pass whether or not the request gets made during testing. 749 | * 750 | * 751 | * 752 | * 753 | * 754 | * 755 | * 756 | * 757 | * 758 | * 759 | * 760 | * 761 | * 762 | * 763 | * 764 | * 765 | * 766 | * 767 | * 768 | * 769 | * 770 | * 771 | * 772 | * 773 | * 774 | * 775 | * 776 | * 777 | * 778 | * 779 | * 780 | * 781 | * 782 | * 783 | *
Request expectationsBackend definitions
Syntax.expect(...).respond(...).when(...).respond(...)
Typical usagestrict unit testsloose (black-box) unit testing
Fulfills multiple requestsNOYES
Order of requests mattersYESNO
Request requiredYESNO
Response requiredoptional (see below)YES
784 | * 785 | * In cases where both backend definitions and request expectations are specified during unit 786 | * testing, the request expectations are evaluated first. 787 | * 788 | * If a request expectation has no response specified, the algorithm will search your backend 789 | * definitions for an appropriate response. 790 | * 791 | * If a request didn't match any expectation or if the expectation doesn't have the response 792 | * defined, the backend definitions are evaluated in sequential order to see if any of them match 793 | * the request. The response from the first matched definition is returned. 794 | * 795 | * 796 | * # Flushing HTTP requests 797 | * 798 | * The $httpBackend used in production, always responds to requests with responses asynchronously. 799 | * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are 800 | * hard to write, follow and maintain. At the same time the testing mock, can't respond 801 | * synchronously because that would change the execution of the code under test. For this reason the 802 | * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending 803 | * requests and thus preserving the async api of the backend, while allowing the test to execute 804 | * synchronously. 805 | * 806 | * 807 | * # Unit testing with mock $httpBackend 808 | * 809 | *
 810 |    // controller
 811 |    function MyController($scope, $http) {
 812 |      $http.get('/auth.py').success(function(data) {
 813 |        $scope.user = data;
 814 |      });
 815 | 
 816 |      this.saveMessage = function(message) {
 817 |        $scope.status = 'Saving...';
 818 |        $http.post('/add-msg.py', message).success(function(response) {
 819 |          $scope.status = '';
 820 |        }).error(function() {
 821 |          $scope.status = 'ERROR!';
 822 |        });
 823 |      };
 824 |    }
 825 | 
 826 |    // testing controller
 827 |    var $httpBackend;
 828 | 
 829 |    beforeEach(inject(function($injector) {
 830 |      $httpBackend = $injector.get('$httpBackend');
 831 | 
 832 |      // backend definition common for all tests
 833 |      $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
 834 |    }));
 835 | 
 836 | 
 837 |    afterEach(function() {
 838 |      $httpBackend.verifyNoOutstandingExpectation();
 839 |      $httpBackend.verifyNoOutstandingRequest();
 840 |    });
 841 | 
 842 | 
 843 |    it('should fetch authentication token', function() {
 844 |      $httpBackend.expectGET('/auth.py');
 845 |      var controller = scope.$new(MyController);
 846 |      $httpBackend.flush();
 847 |    });
 848 | 
 849 | 
 850 |    it('should send msg to server', function() {
 851 |      // now you don’t care about the authentication, but
 852 |      // the controller will still send the request and
 853 |      // $httpBackend will respond without you having to
 854 |      // specify the expectation and response for this request
 855 |      $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
 856 | 
 857 |      var controller = scope.$new(MyController);
 858 |      $httpBackend.flush();
 859 |      controller.saveMessage('message content');
 860 |      expect(controller.status).toBe('Saving...');
 861 |      $httpBackend.flush();
 862 |      expect(controller.status).toBe('');
 863 |    });
 864 | 
 865 | 
 866 |    it('should send auth header', function() {
 867 |      $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
 868 |        // check if the header was send, if it wasn't the expectation won't
 869 |        // match the request and the test will fail
 870 |        return headers['Authorization'] == 'xxx';
 871 |      }).respond(201, '');
 872 | 
 873 |      var controller = scope.$new(MyController);
 874 |      controller.saveMessage('whatever');
 875 |      $httpBackend.flush();
 876 |    });
 877 |    
878 | */ 879 | angular.mock.$HttpBackendProvider = function() { 880 | this.$get = ['$rootScope', createHttpBackendMock]; 881 | }; 882 | 883 | /** 884 | * General factory function for $httpBackend mock. 885 | * Returns instance for unit testing (when no arguments specified): 886 | * - passing through is disabled 887 | * - auto flushing is disabled 888 | * 889 | * Returns instance for e2e testing (when `$delegate` and `$browser` specified): 890 | * - passing through (delegating request to real backend) is enabled 891 | * - auto flushing is enabled 892 | * 893 | * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) 894 | * @param {Object=} $browser Auto-flushing enabled if specified 895 | * @return {Object} Instance of $httpBackend mock 896 | */ 897 | function createHttpBackendMock($rootScope, $delegate, $browser) { 898 | var definitions = [], 899 | expectations = [], 900 | responses = [], 901 | responsesPush = angular.bind(responses, responses.push); 902 | 903 | function createResponse(status, data, headers) { 904 | if (angular.isFunction(status)) return status; 905 | 906 | return function() { 907 | return angular.isNumber(status) 908 | ? [status, data, headers] 909 | : [200, status, data]; 910 | }; 911 | } 912 | 913 | // TODO(vojta): change params to: method, url, data, headers, callback 914 | function $httpBackend(method, url, data, callback, headers) { 915 | var xhr = new MockXhr(), 916 | expectation = expectations[0], 917 | wasExpected = false; 918 | 919 | function prettyPrint(data) { 920 | return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) 921 | ? data 922 | : angular.toJson(data); 923 | } 924 | 925 | if (expectation && expectation.match(method, url)) { 926 | if (!expectation.matchData(data)) 927 | throw Error('Expected ' + expectation + ' with different data\n' + 928 | 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); 929 | 930 | if (!expectation.matchHeaders(headers)) 931 | throw Error('Expected ' + expectation + ' with different headers\n' + 932 | 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + 933 | prettyPrint(headers)); 934 | 935 | expectations.shift(); 936 | 937 | if (expectation.response) { 938 | responses.push(function() { 939 | var response = expectation.response(method, url, data, headers); 940 | xhr.$$respHeaders = response[2]; 941 | callback(response[0], response[1], xhr.getAllResponseHeaders()); 942 | }); 943 | return; 944 | } 945 | wasExpected = true; 946 | } 947 | 948 | var i = -1, definition; 949 | while ((definition = definitions[++i])) { 950 | if (definition.match(method, url, data, headers || {})) { 951 | if (definition.response) { 952 | // if $browser specified, we do auto flush all requests 953 | ($browser ? $browser.defer : responsesPush)(function() { 954 | var response = definition.response(method, url, data, headers); 955 | xhr.$$respHeaders = response[2]; 956 | callback(response[0], response[1], xhr.getAllResponseHeaders()); 957 | }); 958 | } else if (definition.passThrough) { 959 | $delegate(method, url, data, callback, headers); 960 | } else throw Error('No response defined !'); 961 | return; 962 | } 963 | } 964 | throw wasExpected ? 965 | Error('No response defined !') : 966 | Error('Unexpected request: ' + method + ' ' + url + '\n' + 967 | (expectation ? 'Expected ' + expectation : 'No more request expected')); 968 | } 969 | 970 | /** 971 | * @ngdoc method 972 | * @name ngMock.$httpBackend#when 973 | * @methodOf ngMock.$httpBackend 974 | * @description 975 | * Creates a new backend definition. 976 | * 977 | * @param {string} method HTTP method. 978 | * @param {string|RegExp} url HTTP url. 979 | * @param {(string|RegExp)=} data HTTP request body. 980 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 981 | * object and returns true if the headers match the current definition. 982 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 983 | * request is handled. 984 | * 985 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 986 | * – The respond method takes a set of static data to be returned or a function that can return 987 | * an array containing response status (number), response data (string) and response headers 988 | * (Object). 989 | */ 990 | $httpBackend.when = function(method, url, data, headers) { 991 | var definition = new MockHttpExpectation(method, url, data, headers), 992 | chain = { 993 | respond: function(status, data, headers) { 994 | definition.response = createResponse(status, data, headers); 995 | } 996 | }; 997 | 998 | if ($browser) { 999 | chain.passThrough = function() { 1000 | definition.passThrough = true; 1001 | }; 1002 | } 1003 | 1004 | definitions.push(definition); 1005 | return chain; 1006 | }; 1007 | 1008 | /** 1009 | * @ngdoc method 1010 | * @name ngMock.$httpBackend#whenGET 1011 | * @methodOf ngMock.$httpBackend 1012 | * @description 1013 | * Creates a new backend definition for GET requests. For more info see `when()`. 1014 | * 1015 | * @param {string|RegExp} url HTTP url. 1016 | * @param {(Object|function(Object))=} headers HTTP headers. 1017 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1018 | * request is handled. 1019 | */ 1020 | 1021 | /** 1022 | * @ngdoc method 1023 | * @name ngMock.$httpBackend#whenHEAD 1024 | * @methodOf ngMock.$httpBackend 1025 | * @description 1026 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1027 | * 1028 | * @param {string|RegExp} url HTTP url. 1029 | * @param {(Object|function(Object))=} headers HTTP headers. 1030 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1031 | * request is handled. 1032 | */ 1033 | 1034 | /** 1035 | * @ngdoc method 1036 | * @name ngMock.$httpBackend#whenDELETE 1037 | * @methodOf ngMock.$httpBackend 1038 | * @description 1039 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1040 | * 1041 | * @param {string|RegExp} url HTTP url. 1042 | * @param {(Object|function(Object))=} headers HTTP headers. 1043 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1044 | * request is handled. 1045 | */ 1046 | 1047 | /** 1048 | * @ngdoc method 1049 | * @name ngMock.$httpBackend#whenPOST 1050 | * @methodOf ngMock.$httpBackend 1051 | * @description 1052 | * Creates a new backend definition for POST requests. For more info see `when()`. 1053 | * 1054 | * @param {string|RegExp} url HTTP url. 1055 | * @param {(string|RegExp)=} data HTTP request body. 1056 | * @param {(Object|function(Object))=} headers HTTP headers. 1057 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1058 | * request is handled. 1059 | */ 1060 | 1061 | /** 1062 | * @ngdoc method 1063 | * @name ngMock.$httpBackend#whenPUT 1064 | * @methodOf ngMock.$httpBackend 1065 | * @description 1066 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1067 | * 1068 | * @param {string|RegExp} url HTTP url. 1069 | * @param {(string|RegExp)=} data HTTP request body. 1070 | * @param {(Object|function(Object))=} headers HTTP headers. 1071 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1072 | * request is handled. 1073 | */ 1074 | 1075 | /** 1076 | * @ngdoc method 1077 | * @name ngMock.$httpBackend#whenJSONP 1078 | * @methodOf ngMock.$httpBackend 1079 | * @description 1080 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1081 | * 1082 | * @param {string|RegExp} url HTTP url. 1083 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1084 | * request is handled. 1085 | */ 1086 | createShortMethods('when'); 1087 | 1088 | 1089 | /** 1090 | * @ngdoc method 1091 | * @name ngMock.$httpBackend#expect 1092 | * @methodOf ngMock.$httpBackend 1093 | * @description 1094 | * Creates a new request expectation. 1095 | * 1096 | * @param {string} method HTTP method. 1097 | * @param {string|RegExp} url HTTP url. 1098 | * @param {(string|RegExp)=} data HTTP request body. 1099 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1100 | * object and returns true if the headers match the current expectation. 1101 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1102 | * request is handled. 1103 | * 1104 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1105 | * – The respond method takes a set of static data to be returned or a function that can return 1106 | * an array containing response status (number), response data (string) and response headers 1107 | * (Object). 1108 | */ 1109 | $httpBackend.expect = function(method, url, data, headers) { 1110 | var expectation = new MockHttpExpectation(method, url, data, headers); 1111 | expectations.push(expectation); 1112 | return { 1113 | respond: function(status, data, headers) { 1114 | expectation.response = createResponse(status, data, headers); 1115 | } 1116 | }; 1117 | }; 1118 | 1119 | 1120 | /** 1121 | * @ngdoc method 1122 | * @name ngMock.$httpBackend#expectGET 1123 | * @methodOf ngMock.$httpBackend 1124 | * @description 1125 | * Creates a new request expectation for GET requests. For more info see `expect()`. 1126 | * 1127 | * @param {string|RegExp} url HTTP url. 1128 | * @param {Object=} headers HTTP headers. 1129 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1130 | * request is handled. See #expect for more info. 1131 | */ 1132 | 1133 | /** 1134 | * @ngdoc method 1135 | * @name ngMock.$httpBackend#expectHEAD 1136 | * @methodOf ngMock.$httpBackend 1137 | * @description 1138 | * Creates a new request expectation for HEAD requests. For more info see `expect()`. 1139 | * 1140 | * @param {string|RegExp} url HTTP url. 1141 | * @param {Object=} headers HTTP headers. 1142 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1143 | * request is handled. 1144 | */ 1145 | 1146 | /** 1147 | * @ngdoc method 1148 | * @name ngMock.$httpBackend#expectDELETE 1149 | * @methodOf ngMock.$httpBackend 1150 | * @description 1151 | * Creates a new request expectation for DELETE requests. For more info see `expect()`. 1152 | * 1153 | * @param {string|RegExp} url HTTP url. 1154 | * @param {Object=} headers HTTP headers. 1155 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1156 | * request is handled. 1157 | */ 1158 | 1159 | /** 1160 | * @ngdoc method 1161 | * @name ngMock.$httpBackend#expectPOST 1162 | * @methodOf ngMock.$httpBackend 1163 | * @description 1164 | * Creates a new request expectation for POST requests. For more info see `expect()`. 1165 | * 1166 | * @param {string|RegExp} url HTTP url. 1167 | * @param {(string|RegExp)=} data HTTP request body. 1168 | * @param {Object=} headers HTTP headers. 1169 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1170 | * request is handled. 1171 | */ 1172 | 1173 | /** 1174 | * @ngdoc method 1175 | * @name ngMock.$httpBackend#expectPUT 1176 | * @methodOf ngMock.$httpBackend 1177 | * @description 1178 | * Creates a new request expectation for PUT requests. For more info see `expect()`. 1179 | * 1180 | * @param {string|RegExp} url HTTP url. 1181 | * @param {(string|RegExp)=} data HTTP request body. 1182 | * @param {Object=} headers HTTP headers. 1183 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1184 | * request is handled. 1185 | */ 1186 | 1187 | /** 1188 | * @ngdoc method 1189 | * @name ngMock.$httpBackend#expectPATCH 1190 | * @methodOf ngMock.$httpBackend 1191 | * @description 1192 | * Creates a new request expectation for PATCH requests. For more info see `expect()`. 1193 | * 1194 | * @param {string|RegExp} url HTTP url. 1195 | * @param {(string|RegExp)=} data HTTP request body. 1196 | * @param {Object=} headers HTTP headers. 1197 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1198 | * request is handled. 1199 | */ 1200 | 1201 | /** 1202 | * @ngdoc method 1203 | * @name ngMock.$httpBackend#expectJSONP 1204 | * @methodOf ngMock.$httpBackend 1205 | * @description 1206 | * Creates a new request expectation for JSONP requests. For more info see `expect()`. 1207 | * 1208 | * @param {string|RegExp} url HTTP url. 1209 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1210 | * request is handled. 1211 | */ 1212 | createShortMethods('expect'); 1213 | 1214 | 1215 | /** 1216 | * @ngdoc method 1217 | * @name ngMock.$httpBackend#flush 1218 | * @methodOf ngMock.$httpBackend 1219 | * @description 1220 | * Flushes all pending requests using the trained responses. 1221 | * 1222 | * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, 1223 | * all pending requests will be flushed. If there are no pending requests when the flush method 1224 | * is called an exception is thrown (as this typically a sign of programming error). 1225 | */ 1226 | $httpBackend.flush = function(count) { 1227 | $rootScope.$digest(); 1228 | if (!responses.length) throw Error('No pending request to flush !'); 1229 | 1230 | if (angular.isDefined(count)) { 1231 | while (count--) { 1232 | if (!responses.length) throw Error('No more pending request to flush !'); 1233 | responses.shift()(); 1234 | } 1235 | } else { 1236 | while (responses.length) { 1237 | responses.shift()(); 1238 | } 1239 | } 1240 | $httpBackend.verifyNoOutstandingExpectation(); 1241 | }; 1242 | 1243 | 1244 | /** 1245 | * @ngdoc method 1246 | * @name ngMock.$httpBackend#verifyNoOutstandingExpectation 1247 | * @methodOf ngMock.$httpBackend 1248 | * @description 1249 | * Verifies that all of the requests defined via the `expect` api were made. If any of the 1250 | * requests were not made, verifyNoOutstandingExpectation throws an exception. 1251 | * 1252 | * Typically, you would call this method following each test case that asserts requests using an 1253 | * "afterEach" clause. 1254 | * 1255 | *
1256 |    *   afterEach($httpBackend.verifyExpectations);
1257 |    * 
1258 | */ 1259 | $httpBackend.verifyNoOutstandingExpectation = function() { 1260 | $rootScope.$digest(); 1261 | if (expectations.length) { 1262 | throw Error('Unsatisfied requests: ' + expectations.join(', ')); 1263 | } 1264 | }; 1265 | 1266 | 1267 | /** 1268 | * @ngdoc method 1269 | * @name ngMock.$httpBackend#verifyNoOutstandingRequest 1270 | * @methodOf ngMock.$httpBackend 1271 | * @description 1272 | * Verifies that there are no outstanding requests that need to be flushed. 1273 | * 1274 | * Typically, you would call this method following each test case that asserts requests using an 1275 | * "afterEach" clause. 1276 | * 1277 | *
1278 |    *   afterEach($httpBackend.verifyNoOutstandingRequest);
1279 |    * 
1280 | */ 1281 | $httpBackend.verifyNoOutstandingRequest = function() { 1282 | if (responses.length) { 1283 | throw Error('Unflushed requests: ' + responses.length); 1284 | } 1285 | }; 1286 | 1287 | 1288 | /** 1289 | * @ngdoc method 1290 | * @name ngMock.$httpBackend#resetExpectations 1291 | * @methodOf ngMock.$httpBackend 1292 | * @description 1293 | * Resets all request expectations, but preserves all backend definitions. Typically, you would 1294 | * call resetExpectations during a multiple-phase test when you want to reuse the same instance of 1295 | * $httpBackend mock. 1296 | */ 1297 | $httpBackend.resetExpectations = function() { 1298 | expectations.length = 0; 1299 | responses.length = 0; 1300 | }; 1301 | 1302 | return $httpBackend; 1303 | 1304 | 1305 | function createShortMethods(prefix) { 1306 | angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { 1307 | $httpBackend[prefix + method] = function(url, headers) { 1308 | return $httpBackend[prefix](method, url, undefined, headers) 1309 | } 1310 | }); 1311 | 1312 | angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { 1313 | $httpBackend[prefix + method] = function(url, data, headers) { 1314 | return $httpBackend[prefix](method, url, data, headers) 1315 | } 1316 | }); 1317 | } 1318 | } 1319 | 1320 | function MockHttpExpectation(method, url, data, headers) { 1321 | 1322 | this.data = data; 1323 | this.headers = headers; 1324 | 1325 | this.match = function(m, u, d, h) { 1326 | if (method != m) return false; 1327 | if (!this.matchUrl(u)) return false; 1328 | if (angular.isDefined(d) && !this.matchData(d)) return false; 1329 | if (angular.isDefined(h) && !this.matchHeaders(h)) return false; 1330 | return true; 1331 | }; 1332 | 1333 | this.matchUrl = function(u) { 1334 | if (!url) return true; 1335 | if (angular.isFunction(url.test)) return url.test(u); 1336 | return url == u; 1337 | }; 1338 | 1339 | this.matchHeaders = function(h) { 1340 | if (angular.isUndefined(headers)) return true; 1341 | if (angular.isFunction(headers)) return headers(h); 1342 | return angular.equals(headers, h); 1343 | }; 1344 | 1345 | this.matchData = function(d) { 1346 | if (angular.isUndefined(data)) return true; 1347 | if (data && angular.isFunction(data.test)) return data.test(d); 1348 | if (data && !angular.isString(data)) return angular.toJson(data) == d; 1349 | return data == d; 1350 | }; 1351 | 1352 | this.toString = function() { 1353 | return method + ' ' + url; 1354 | }; 1355 | } 1356 | 1357 | function MockXhr() { 1358 | 1359 | // hack for testing $http, $httpBackend 1360 | MockXhr.$$lastInstance = this; 1361 | 1362 | this.open = function(method, url, async) { 1363 | this.$$method = method; 1364 | this.$$url = url; 1365 | this.$$async = async; 1366 | this.$$reqHeaders = {}; 1367 | this.$$respHeaders = {}; 1368 | }; 1369 | 1370 | this.send = function(data) { 1371 | this.$$data = data; 1372 | }; 1373 | 1374 | this.setRequestHeader = function(key, value) { 1375 | this.$$reqHeaders[key] = value; 1376 | }; 1377 | 1378 | this.getResponseHeader = function(name) { 1379 | // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last 1380 | var header = this.$$respHeaders[name]; 1381 | if (header) return header; 1382 | 1383 | name = angular.lowercase(name); 1384 | header = this.$$respHeaders[name]; 1385 | if (header) return header; 1386 | 1387 | header = undefined; 1388 | angular.forEach(this.$$respHeaders, function(headerVal, headerName) { 1389 | if (!header && angular.lowercase(headerName) == name) header = headerVal; 1390 | }); 1391 | return header; 1392 | }; 1393 | 1394 | this.getAllResponseHeaders = function() { 1395 | var lines = []; 1396 | 1397 | angular.forEach(this.$$respHeaders, function(value, key) { 1398 | lines.push(key + ': ' + value); 1399 | }); 1400 | return lines.join('\n'); 1401 | }; 1402 | 1403 | this.abort = angular.noop; 1404 | } 1405 | 1406 | 1407 | /** 1408 | * @ngdoc function 1409 | * @name ngMock.$timeout 1410 | * @description 1411 | * 1412 | * This service is just a simple decorator for {@link ng.$timeout $timeout} service 1413 | * that adds a "flush" and "verifyNoPendingTasks" methods. 1414 | */ 1415 | 1416 | angular.mock.$TimeoutDecorator = function($delegate, $browser) { 1417 | 1418 | /** 1419 | * @ngdoc method 1420 | * @name ngMock.$timeout#flush 1421 | * @methodOf ngMock.$timeout 1422 | * @description 1423 | * 1424 | * Flushes the queue of pending tasks. 1425 | */ 1426 | $delegate.flush = function() { 1427 | $browser.defer.flush(); 1428 | }; 1429 | 1430 | /** 1431 | * @ngdoc method 1432 | * @name ngMock.$timeout#verifyNoPendingTasks 1433 | * @methodOf ngMock.$timeout 1434 | * @description 1435 | * 1436 | * Verifies that there are no pending tasks that need to be flushed. 1437 | */ 1438 | $delegate.verifyNoPendingTasks = function() { 1439 | if ($browser.deferredFns.length) { 1440 | throw Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + 1441 | formatPendingTasksAsString($browser.deferredFns)); 1442 | } 1443 | }; 1444 | 1445 | function formatPendingTasksAsString(tasks) { 1446 | var result = []; 1447 | angular.forEach(tasks, function(task) { 1448 | result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); 1449 | }); 1450 | 1451 | return result.join(', '); 1452 | } 1453 | 1454 | return $delegate; 1455 | }; 1456 | 1457 | /** 1458 | * 1459 | */ 1460 | angular.mock.$RootElementProvider = function() { 1461 | this.$get = function() { 1462 | return angular.element('
'); 1463 | } 1464 | }; 1465 | 1466 | /** 1467 | * @ngdoc overview 1468 | * @name ngMock 1469 | * @description 1470 | * 1471 | * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful 1472 | * mocks to the {@link AUTO.$injector $injector}. 1473 | */ 1474 | angular.module('ngMock', ['ng']).provider({ 1475 | $browser: angular.mock.$BrowserProvider, 1476 | $exceptionHandler: angular.mock.$ExceptionHandlerProvider, 1477 | $log: angular.mock.$LogProvider, 1478 | $httpBackend: angular.mock.$HttpBackendProvider, 1479 | $rootElement: angular.mock.$RootElementProvider 1480 | }).config(function($provide) { 1481 | $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); 1482 | }); 1483 | 1484 | /** 1485 | * @ngdoc overview 1486 | * @name ngMockE2E 1487 | * @description 1488 | * 1489 | * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. 1490 | * Currently there is only one mock present in this module - 1491 | * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. 1492 | */ 1493 | angular.module('ngMockE2E', ['ng']).config(function($provide) { 1494 | $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); 1495 | }); 1496 | 1497 | /** 1498 | * @ngdoc object 1499 | * @name ngMockE2E.$httpBackend 1500 | * @description 1501 | * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of 1502 | * applications that use the {@link ng.$http $http service}. 1503 | * 1504 | * *Note*: For fake http backend implementation suitable for unit testing please see 1505 | * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. 1506 | * 1507 | * This implementation can be used to respond with static or dynamic responses via the `when` api 1508 | * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the 1509 | * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch 1510 | * templates from a webserver). 1511 | * 1512 | * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application 1513 | * is being developed with the real backend api replaced with a mock, it is often desirable for 1514 | * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch 1515 | * templates or static files from the webserver). To configure the backend with this behavior 1516 | * use the `passThrough` request handler of `when` instead of `respond`. 1517 | * 1518 | * Additionally, we don't want to manually have to flush mocked out requests like we do during unit 1519 | * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests 1520 | * automatically, closely simulating the behavior of the XMLHttpRequest object. 1521 | * 1522 | * To setup the application to run with this http backend, you have to create a module that depends 1523 | * on the `ngMockE2E` and your application modules and defines the fake backend: 1524 | * 1525 | *
1526 |  *   myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
1527 |  *   myAppDev.run(function($httpBackend) {
1528 |  *     phones = [{name: 'phone1'}, {name: 'phone2'}];
1529 |  *
1530 |  *     // returns the current list of phones
1531 |  *     $httpBackend.whenGET('/phones').respond(phones);
1532 |  *
1533 |  *     // adds a new phone to the phones array
1534 |  *     $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
1535 |  *       phones.push(angular.fromJSON(data));
1536 |  *     });
1537 |  *     $httpBackend.whenGET(/^\/templates\//).passThrough();
1538 |  *     //...
1539 |  *   });
1540 |  * 
1541 | * 1542 | * Afterwards, bootstrap your app with this new module. 1543 | */ 1544 | 1545 | /** 1546 | * @ngdoc method 1547 | * @name ngMockE2E.$httpBackend#when 1548 | * @methodOf ngMockE2E.$httpBackend 1549 | * @description 1550 | * Creates a new backend definition. 1551 | * 1552 | * @param {string} method HTTP method. 1553 | * @param {string|RegExp} url HTTP url. 1554 | * @param {(string|RegExp)=} data HTTP request body. 1555 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1556 | * object and returns true if the headers match the current definition. 1557 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1558 | * control how a matched request is handled. 1559 | * 1560 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1561 | * – The respond method takes a set of static data to be returned or a function that can return 1562 | * an array containing response status (number), response data (string) and response headers 1563 | * (Object). 1564 | * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` 1565 | * handler, will be pass through to the real backend (an XHR request will be made to the 1566 | * server. 1567 | */ 1568 | 1569 | /** 1570 | * @ngdoc method 1571 | * @name ngMockE2E.$httpBackend#whenGET 1572 | * @methodOf ngMockE2E.$httpBackend 1573 | * @description 1574 | * Creates a new backend definition for GET requests. For more info see `when()`. 1575 | * 1576 | * @param {string|RegExp} url HTTP url. 1577 | * @param {(Object|function(Object))=} headers HTTP headers. 1578 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1579 | * control how a matched request is handled. 1580 | */ 1581 | 1582 | /** 1583 | * @ngdoc method 1584 | * @name ngMockE2E.$httpBackend#whenHEAD 1585 | * @methodOf ngMockE2E.$httpBackend 1586 | * @description 1587 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1588 | * 1589 | * @param {string|RegExp} url HTTP url. 1590 | * @param {(Object|function(Object))=} headers HTTP headers. 1591 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1592 | * control how a matched request is handled. 1593 | */ 1594 | 1595 | /** 1596 | * @ngdoc method 1597 | * @name ngMockE2E.$httpBackend#whenDELETE 1598 | * @methodOf ngMockE2E.$httpBackend 1599 | * @description 1600 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1601 | * 1602 | * @param {string|RegExp} url HTTP url. 1603 | * @param {(Object|function(Object))=} headers HTTP headers. 1604 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1605 | * control how a matched request is handled. 1606 | */ 1607 | 1608 | /** 1609 | * @ngdoc method 1610 | * @name ngMockE2E.$httpBackend#whenPOST 1611 | * @methodOf ngMockE2E.$httpBackend 1612 | * @description 1613 | * Creates a new backend definition for POST requests. For more info see `when()`. 1614 | * 1615 | * @param {string|RegExp} url HTTP url. 1616 | * @param {(string|RegExp)=} data HTTP request body. 1617 | * @param {(Object|function(Object))=} headers HTTP headers. 1618 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1619 | * control how a matched request is handled. 1620 | */ 1621 | 1622 | /** 1623 | * @ngdoc method 1624 | * @name ngMockE2E.$httpBackend#whenPUT 1625 | * @methodOf ngMockE2E.$httpBackend 1626 | * @description 1627 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1628 | * 1629 | * @param {string|RegExp} url HTTP url. 1630 | * @param {(string|RegExp)=} data HTTP request body. 1631 | * @param {(Object|function(Object))=} headers HTTP headers. 1632 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1633 | * control how a matched request is handled. 1634 | */ 1635 | 1636 | /** 1637 | * @ngdoc method 1638 | * @name ngMockE2E.$httpBackend#whenPATCH 1639 | * @methodOf ngMockE2E.$httpBackend 1640 | * @description 1641 | * Creates a new backend definition for PATCH requests. For more info see `when()`. 1642 | * 1643 | * @param {string|RegExp} url HTTP url. 1644 | * @param {(string|RegExp)=} data HTTP request body. 1645 | * @param {(Object|function(Object))=} headers HTTP headers. 1646 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1647 | * control how a matched request is handled. 1648 | */ 1649 | 1650 | /** 1651 | * @ngdoc method 1652 | * @name ngMockE2E.$httpBackend#whenJSONP 1653 | * @methodOf ngMockE2E.$httpBackend 1654 | * @description 1655 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1656 | * 1657 | * @param {string|RegExp} url HTTP url. 1658 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1659 | * control how a matched request is handled. 1660 | */ 1661 | angular.mock.e2e = {}; 1662 | angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock]; 1663 | 1664 | 1665 | angular.mock.clearDataCache = function() { 1666 | var key, 1667 | cache = angular.element.cache; 1668 | 1669 | for(key in cache) { 1670 | if (cache.hasOwnProperty(key)) { 1671 | var handle = cache[key].handle; 1672 | 1673 | handle && angular.element(handle.elem).unbind(); 1674 | delete cache[key]; 1675 | } 1676 | } 1677 | }; 1678 | 1679 | 1680 | window.jstestdriver && (function(window) { 1681 | /** 1682 | * Global method to output any number of objects into JSTD console. Useful for debugging. 1683 | */ 1684 | window.dump = function() { 1685 | var args = []; 1686 | angular.forEach(arguments, function(arg) { 1687 | args.push(angular.mock.dump(arg)); 1688 | }); 1689 | jstestdriver.console.log.apply(jstestdriver.console, args); 1690 | if (window.console) { 1691 | window.console.log.apply(window.console, args); 1692 | } 1693 | }; 1694 | })(window); 1695 | 1696 | 1697 | (window.jasmine || window.mocha) && (function(window) { 1698 | 1699 | var currentSpec = null; 1700 | 1701 | beforeEach(function() { 1702 | currentSpec = this; 1703 | }); 1704 | 1705 | afterEach(function() { 1706 | var injector = currentSpec.$injector; 1707 | 1708 | currentSpec.$injector = null; 1709 | currentSpec.$modules = null; 1710 | currentSpec = null; 1711 | 1712 | if (injector) { 1713 | injector.get('$rootElement').unbind(); 1714 | injector.get('$browser').pollFns.length = 0; 1715 | } 1716 | 1717 | angular.mock.clearDataCache(); 1718 | 1719 | // clean up jquery's fragment cache 1720 | angular.forEach(angular.element.fragments, function(val, key) { 1721 | delete angular.element.fragments[key]; 1722 | }); 1723 | 1724 | MockXhr.$$lastInstance = null; 1725 | 1726 | angular.forEach(angular.callbacks, function(val, key) { 1727 | delete angular.callbacks[key]; 1728 | }); 1729 | angular.callbacks.counter = 0; 1730 | }); 1731 | 1732 | function isSpecRunning() { 1733 | return currentSpec && (window.mocha || currentSpec.queue.running); 1734 | } 1735 | 1736 | /** 1737 | * @ngdoc function 1738 | * @name angular.mock.module 1739 | * @description 1740 | * 1741 | * *NOTE*: This function is also published on window for easy access.
1742 | * 1743 | * This function registers a module configuration code. It collects the configuration information 1744 | * which will be used when the injector is created by {@link angular.mock.inject inject}. 1745 | * 1746 | * See {@link angular.mock.inject inject} for usage example 1747 | * 1748 | * @param {...(string|Function)} fns any number of modules which are represented as string 1749 | * aliases or as anonymous module initialization functions. The modules are used to 1750 | * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. 1751 | */ 1752 | window.module = angular.mock.module = function() { 1753 | var moduleFns = Array.prototype.slice.call(arguments, 0); 1754 | return isSpecRunning() ? workFn() : workFn; 1755 | ///////////////////// 1756 | function workFn() { 1757 | if (currentSpec.$injector) { 1758 | throw Error('Injector already created, can not register a module!'); 1759 | } else { 1760 | var modules = currentSpec.$modules || (currentSpec.$modules = []); 1761 | angular.forEach(moduleFns, function(module) { 1762 | modules.push(module); 1763 | }); 1764 | } 1765 | } 1766 | }; 1767 | 1768 | /** 1769 | * @ngdoc function 1770 | * @name angular.mock.inject 1771 | * @description 1772 | * 1773 | * *NOTE*: This function is also published on window for easy access.
1774 | * 1775 | * The inject function wraps a function into an injectable function. The inject() creates new 1776 | * instance of {@link AUTO.$injector $injector} per test, which is then used for 1777 | * resolving references. 1778 | * 1779 | * See also {@link angular.mock.module module} 1780 | * 1781 | * Example of what a typical jasmine tests looks like with the inject method. 1782 | *
1783 |    *
1784 |    *   angular.module('myApplicationModule', [])
1785 |    *       .value('mode', 'app')
1786 |    *       .value('version', 'v1.0.1');
1787 |    *
1788 |    *
1789 |    *   describe('MyApp', function() {
1790 |    *
1791 |    *     // You need to load modules that you want to test,
1792 |    *     // it loads only the "ng" module by default.
1793 |    *     beforeEach(module('myApplicationModule'));
1794 |    *
1795 |    *
1796 |    *     // inject() is used to inject arguments of all given functions
1797 |    *     it('should provide a version', inject(function(mode, version) {
1798 |    *       expect(version).toEqual('v1.0.1');
1799 |    *       expect(mode).toEqual('app');
1800 |    *     }));
1801 |    *
1802 |    *
1803 |    *     // The inject and module method can also be used inside of the it or beforeEach
1804 |    *     it('should override a version and test the new version is injected', function() {
1805 |    *       // module() takes functions or strings (module aliases)
1806 |    *       module(function($provide) {
1807 |    *         $provide.value('version', 'overridden'); // override version here
1808 |    *       });
1809 |    *
1810 |    *       inject(function(version) {
1811 |    *         expect(version).toEqual('overridden');
1812 |    *       });
1813 |    *     ));
1814 |    *   });
1815 |    *
1816 |    * 
1817 | * 1818 | * @param {...Function} fns any number of functions which will be injected using the injector. 1819 | */ 1820 | window.inject = angular.mock.inject = function() { 1821 | var blockFns = Array.prototype.slice.call(arguments, 0); 1822 | var errorForStack = new Error('Declaration Location'); 1823 | return isSpecRunning() ? workFn() : workFn; 1824 | ///////////////////// 1825 | function workFn() { 1826 | var modules = currentSpec.$modules || []; 1827 | 1828 | modules.unshift('ngMock'); 1829 | modules.unshift('ng'); 1830 | var injector = currentSpec.$injector; 1831 | if (!injector) { 1832 | injector = currentSpec.$injector = angular.injector(modules); 1833 | } 1834 | for(var i = 0, ii = blockFns.length; i < ii; i++) { 1835 | try { 1836 | injector.invoke(blockFns[i] || angular.noop, this); 1837 | } catch (e) { 1838 | if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; 1839 | throw e; 1840 | } finally { 1841 | errorForStack = null; 1842 | } 1843 | } 1844 | } 1845 | }; 1846 | })(window); 1847 | --------------------------------------------------------------------------------