From 6f8e7e29fe4cd7711246d7d9eb15523f9570a41e Mon Sep 17 00:00:00 2001 From: Ryan Brewster Date: Fri, 24 Oct 2014 20:15:42 -0700 Subject: [PATCH] 2.0 * framework updates * core refactor * bower new console docs demo and plugins --- .bowerrc | 3 + .gitignore | 3 + .new | 9 + .ruby-version | 1 - CHANGELOG.md | 6 + Gemfile | 28 +- Gemfile.lock | 51 +- Guardfile | 2 - LICENSE-MIT | 2 +- README.md | 348 +- bower.json | 13 + demo/atrackt_demo.coffee | 9 + demo/atrackt_demo.css | 21 + demo/atrackt_demo.css.map | 7 + demo/atrackt_demo.js | 15 + demo/atrackt_demo.sass | 21 + demo/index.html | 99 +- lib/atrackt.console.css | 25 + lib/atrackt.console.css.map | 7 + lib/atrackt.console.js | 119 + lib/atrackt.debug.js | 169 - lib/atrackt.js | 523 +- lib/plugins/atrackt.localytics.js | 31 +- lib/plugins/atrackt.omniture.js | 57 +- package.json | 5 + spec/atrackt.console_spec.sass | 4 + spec/atrackt_spec.coffee | 248 + spec/atrackt_spec.js.coffee | 480 -- spec/index.html | 68 +- spec/plugins/atrackt.localytics_spec.coffee | 84 + .../plugins/atrackt.localytics_spec.js.coffee | 95 - ...js.coffee => atrackt.omniture_spec.coffee} | 39 +- ...ec_helper.js.coffee => spec_helper.coffee} | 0 spec/vendor/chai.js | 3765 --------------- spec/vendor/sinon-chai.js | 106 - spec/vendor/sinon.js | 4246 ----------------- src/atrackt.coffee | 270 ++ src/atrackt.console.coffee | 113 + src/atrackt.console.sass | 30 + src/atrackt.debug.js.coffee | 180 - src/atrackt.js.coffee | 307 -- src/plugins/atrackt.localytics.coffee | 40 + src/plugins/atrackt.localytics.js.coffee | 39 - ...ture.js.coffee => atrackt.omniture.coffee} | 36 +- testem.yml | 26 +- yuyi_menu | 9 + 46 files changed, 1627 insertions(+), 10132 deletions(-) create mode 100644 .bowerrc create mode 100644 .new delete mode 100644 .ruby-version create mode 100644 CHANGELOG.md delete mode 100644 Guardfile create mode 100644 bower.json create mode 100644 demo/atrackt_demo.coffee create mode 100644 demo/atrackt_demo.css create mode 100644 demo/atrackt_demo.css.map create mode 100644 demo/atrackt_demo.js create mode 100644 demo/atrackt_demo.sass create mode 100644 lib/atrackt.console.css create mode 100644 lib/atrackt.console.css.map create mode 100644 lib/atrackt.console.js delete mode 100644 lib/atrackt.debug.js create mode 100644 package.json create mode 100644 spec/atrackt.console_spec.sass create mode 100644 spec/atrackt_spec.coffee delete mode 100644 spec/atrackt_spec.js.coffee create mode 100644 spec/plugins/atrackt.localytics_spec.coffee delete mode 100644 spec/plugins/atrackt.localytics_spec.js.coffee rename spec/plugins/{atrackt.omniture_spec.js.coffee => atrackt.omniture_spec.coffee} (62%) rename spec/{spec_helper.js.coffee => spec_helper.coffee} (100%) delete mode 100644 spec/vendor/chai.js delete mode 100644 spec/vendor/sinon-chai.js delete mode 100644 spec/vendor/sinon.js create mode 100644 src/atrackt.coffee create mode 100644 src/atrackt.console.coffee create mode 100644 src/atrackt.console.sass delete mode 100644 src/atrackt.debug.js.coffee delete mode 100644 src/atrackt.js.coffee create mode 100644 src/plugins/atrackt.localytics.coffee delete mode 100644 src/plugins/atrackt.localytics.js.coffee rename src/plugins/{atrackt.omniture.js.coffee => atrackt.omniture.coffee} (59%) create mode 100644 yuyi_menu diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..6866ac2 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "vendor" +} diff --git a/.gitignore b/.gitignore index d36977d..921155f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ +.DS_Store +.sass-cache .tmp +vendor diff --git a/.new b/.new new file mode 100644 index 0000000..8b732d6 --- /dev/null +++ b/.new @@ -0,0 +1,9 @@ +--- +license: MIT +version: 0.0.0 +developer: + name: Ryan Brester + email: brewster1134@gmail.com +project_name: atrackt +project_filename: atrackt +type: js diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index c82eec7..0000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -1.9.3-p448 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..64db5ba --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +#### CHANGE LOG +###### 0.x +* beta libraries + +###### 1.0.0 +* stable libraries diff --git a/Gemfile b/Gemfile index a12dccd..7788961 100644 --- a/Gemfile +++ b/Gemfile @@ -1,28 +1,2 @@ source 'https://rubygems.org' - -group :development do - gem 'guard' - gem 'guard-coffeescript' - gem 'json' -end - -# Platform specific gems (set `require: false`) -group :development do - gem 'rb-fsevent', require: false - gem 'growl', require: false - gem 'terminal-notifier-guard', require: false -end - -# OS X -if RUBY_PLATFORM.downcase =~ /darwin/ - require 'rb-fsevent' - - # 10.8 Mountain Lion - if RUBY_PLATFORM.downcase =~ /darwin12/ - require 'terminal-notifier-guard' - - # 10.7 Lion and below - else - require 'growl' - end -end +gem 'sass', '~> 3.4.6' diff --git a/Gemfile.lock b/Gemfile.lock index 20587c4..07fd731 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,57 +1,10 @@ GEM remote: https://rubygems.org/ specs: - celluloid (0.15.2) - timers (~> 1.1.0) - celluloid-io (0.15.0) - celluloid (>= 0.15.0) - nio4r (>= 0.5.0) - coderay (1.1.0) - coffee-script (2.2.0) - coffee-script-source - execjs - coffee-script-source (1.7.0) - execjs (2.0.2) - ffi (1.9.3) - formatador (0.2.4) - growl (1.0.3) - guard (2.5.1) - formatador (>= 0.2.4) - listen (~> 2.6) - lumberjack (~> 1.0) - pry (>= 0.9.12) - thor (>= 0.18.1) - guard-coffeescript (1.4.0) - coffee-script (>= 2.2.0) - guard (>= 1.1.0) - json (1.8.1) - listen (2.7.1) - celluloid (>= 0.15.2) - celluloid-io (>= 0.15.0) - rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9) - lumberjack (1.0.4) - method_source (0.8.2) - nio4r (1.0.0) - pry (0.9.12.6) - coderay (~> 1.0) - method_source (~> 0.8) - slop (~> 3.4) - rb-fsevent (0.9.4) - rb-inotify (0.9.3) - ffi (>= 0.5.0) - slop (3.5.0) - terminal-notifier-guard (1.5.3) - thor (0.18.1) - timers (1.1.0) + sass (3.4.6) PLATFORMS ruby DEPENDENCIES - growl - guard - guard-coffeescript - json - rb-fsevent - terminal-notifier-guard + sass (~> 3.4.6) diff --git a/Guardfile b/Guardfile deleted file mode 100644 index 091134e..0000000 --- a/Guardfile +++ /dev/null @@ -1,2 +0,0 @@ -guard :coffeescript, input: 'src', output: 'lib', hide_success: true -guard :coffeescript, input: 'spec', output: '.tmp', hide_success: true diff --git a/LICENSE-MIT b/LICENSE-MIT index bd9717f..582182d 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2013 Ryan Brewster +Copyright (c) 2014 Ryan Brewster | atrackt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md index d668c83..8b0655d 100644 --- a/README.md +++ b/README.md @@ -1,272 +1,214 @@ # Atrackt ---- - A library for making complex tracking & analytics easier. -## Dependencies - +### Dependencies * [jQuery](http://jquery.com) -* [Underscore.js](http://underscorejs.org) - -## Tracking An Element - -When an element is tracked, there are several basic values that are included. -* *location*: This represents the page that tracking events come from. It will track the first value it finds from the following: - * `$('body').data('track-location')` - The custom value attached to the body element's `data-track-location` attribute. - * `$(document).attr('title')` - The page title - * `document.URL` - The page URL +### Quick Usage +* Load the atrackt core library & any plugin(s) + * `` + * `` +* Bind something to track -* *categories*: This represents the elements location on the page. It traverses the dom from the element and collects data along the way. Specifically any parent element with the `data-track-cat` value set (including the element itself). - * In the exmaple below, if the `a` element is tracked, the value for categories would be an array of `[ 'one', 'two', 'three' ]` - -```html -
-
- -
-
+```coffee +Atrackt.setEvent + click: 'a, button' ``` +##### That's it. When any any button or anchor is clicked on, it will be tracked with omniture. -* *value*: This reperesents the value of the tracked element. It will track the first value it finds from the following: - * `data-track-value` A custom value to explicitly set - * `title` The value of the title attribute - * `name` The value of the name attribute - * `text` The text value of the element. This contains only text and will not include any HTML. - * `val` The value (if a form element) - * `id` The value of the id attribute - * `class` The value of the id attribute - -* *event*: This represents the type of event that fired the tracking call. - -* *plugin*: The name of the plugin responsible for tracking the element - -#### `Custom Tracking` - -Set `data-track-function` to add a custom function to a specific element. This function will be run before the send method is called. You can then modify the data or do any number of things before the data is tracked. - -It accepts 3 arguments - -* `[Object]` - * The generated data to track -* `[jQuery Object]` - * The element being tracked -* `[String]` - * The event that triggered the tracking +## Methods -For example, you could track things conditionally... +###### A note on all methods... methods called on `Atrackt` are consider global, and will include all registered plugins. To target a plugin directly, you can access it through the plugins object. ```coffee -$('a#foo').data 'track-function', (data, el) -> - if data.value == 'foo' || el.data('foo') == true - data.foo = true - else - data.foo = false +Atrackt.plugins.omniture.setEvent + click: 'a.omniture' ``` -## Usage - -* Download the [script](https://raw.github.com/brewster1134/atrackt/master/js/atrackt.js) _(right-click & save as)_ -* Add the script to your page - * `` -* Add a plugin to your page _([or write your own!](#registering-plugins))_ _AFTER_ `atrackt.js` - * `` - -That's it! The settings from your plugin will bind events to elements and you can start tracking! - -### Advanced Usage - -#### `registerPlugin` - -Call `registerPlugin` to quickly register a custom plugin. - -The minimum a plugin needs is a `send` method. This is a function that accepts the tracking object, and any additional options as an argument. You can do additional processing on the object and pass it wherever you need to track it. +--- +### `setEvent` +The `setEvent` method accepts a custom event object which uses the event name as the key, and a css selector, jquery object, or html node as the value. ```coffee -Atrackt.registerPlugin 'testPlugin', - send: (obj, options) -> - # do stuff to the object and send it somewhere +Atrack.setEvent + click: '.css-selector' + mouseenter: $('.jquery-object') + customevent: document.querySelector('.html-node') ``` -#### `bind` & `unbind` - -Typically just creating a send method to manually track objects is not enough. Normally you want to bind a whole bunch of elements to an event _(or events)_ to track. - -Call 'bind' and 'unbind' to register jquery selectors or jquery objects to automatically fire tracking events. These methods accept a special events object. +You can also pass in an array of multiple object types -The format is an event type as the key, and an array of jquery selectors, or a jquery object as the value. Any matching selectors or objects will be automatically bound and tracked with the given event. +```coffee +Atrack.setEvent + click: [ '.css-selector', $('.jquery-object'), document.querySelector('.html-node') ] +``` -__NOTE__ To attach events to elements, you will likely need to call bind after the elements have loaded ($ ->), or in the success callback for an ajax request. +--- +### `setData` +You can add data globally that will always be included with every tracking call using `setData`. ```coffee -# bind on ALL registered plugins -Atrackt.bind - click: ['a'] - hover: ['a', 'button' ] - -# bind on a specific plugin -Atrackt.plugins['testPlugin'].bind - click: ['a'] - hover: ['a', 'button' ] +Atrackt.setData + foo: 'bar' ``` -The same options are available to `unbind` elements. +--- +### `setOptions` +You can add options globally that will always be included with every tracking call using `setOptions`. ```coffee -Atrackt.unbind - click: ['a'] - -Atrackt.plugins['testPlugin'].unbind - click: ['a'] +Atrackt.setOptions + foo: 'bar' ``` -You can also bind/unbind a specific element instead of a selector. This is helpful if you generate new elements dynamically and need to track them as they are created. +--- +### `setCallback` +Callbacks can be run before or after a tracking call is made. You must specify `before` or `after`, along with a function that will be run. Callbacks accepts 2 arguments, 1 for data, and 1 for options. In `before` callbacks, you can alter those objects and they will be tracked. ```coffee -Atrackt.bind - click: $('div#foo') - -Atrackt.plugins['testPlugin'].bind - click: $('div#foo') +Atrackt.setCallback 'before', (data, options) -> + data.foo = 'bar' + options.foo = 'bar' ``` -#### `track` -Call 'track' to manually track any JS object. It will add the additional Atrackt data and pass it to each registered plugin to be tracked. +--- +### `track` +Instead of binding elements to events, you can track data directly. This is helpful for tracking different states of your app. -It accepts 3 arguments. +You can track standard javascript objects, jquery objects, or html nodes. -* `[Object]` _required_ - * The data you want to track -* `[Object]` _optional_ - * Any options you want to send the plugin to customize tracking - must be namespaced to the plugin!) -* `[String]` _optional_ - * An event. If an event is passed, it will be check that the event namespace matches each plugin. +The `track` method accepts 2 methods, data & options ```coffee Atrackt.track - data: 'foo' + fooData: 'bar' , - testPlugin: - option: 'bar' + fooOption: 'bar' ``` -#### `refresh` - -Call `refresh` if you need to re-scan the dom and re-bind elements based on the `bind` and `unbind` data. - -```coffee -Atrackt.refresh() -``` - -#### `setOptions` +--- +## Element Tracking +When an element is tracked, there are several basic values that are included. See more information about each value below. -Call `setOptions` on a specific plugin if you need to pass custom options to your plugin. This will will set attributes on the `options` object in your plugin. If your plugin already has default options set, the custom options well simply extend over them. +* `_categories` represent an elements virtual position on the page +* `_location` represents the page that tracking events come from +* `_value` represents an elements unique value +* `_event` type of event (if triggered by an event) -_setOptions is not available to set options on all plugins at once. options should be specific to a plugin_ +--- +###### `_categories` -```coffee -Atrackt.registerPlugin 'testPlugin', - send: -> - options: - foo: 'foo' +It traverses the dom from the element and collects data along the way, in reverse order. Specifically any parent element with the `data-atrackt-category` value set (including the element itself). + * In the example below, if the `a` element is tracked, the value for categories would be an array of `[ 'one', 'two', 'three' ]` -Atrackt.plugins['testPlugin'].setOptions - foo: 'bar' +```html +
+
+ +
+
``` +--- +###### `_location` +It will track the first value it finds from the following: + * `$('body').data('atrackt-location')` Data attribute on the body + * `$(document).attr('title')` The standard page title + * `document.URL` The page URL -#### `setGlobalData` +--- +###### `_value` + * `data-atrackt-value` A custom value to explicitly set + * `title` The value of the title attribute + * `name` The value of the name attribute + * `text` The text value of the element. This contains only text and will not include any HTML. + * `val` The value (if a form element) + * `id` The value of the id attribute + * `class` The value of the class attribute -Call `setGlobalData` to add attributes that will be tracked with every tracking call. Global data will _NOT_ overwrite the main values provided by Atrackt (location, categories, value, event). +--- +###### `_event` +If triggered by an event, this value will be the name of the event _(eg click, mouseenter, etc)_ -```coffee -# set globalData for all plugins -Atrackt.setGlobalData - foo: 'bar' +--- +##### Custom Function +On a per-element basis, you can add a custom function to the the `data-atrackt-function` data attribute that will be called each time that element is tracked. The function accepts 2 arguments, 1 for data, and 1 for options. You can alter those objects before they are tracked. -# set globalData for a specific plugin -Atrackt.plugins['testPlugin'].setGlobalData - foo: 'bar' +```coffee +$('a#foo').data 'atrackt-function', (data, options) -> + if options.hasColor + data.color = $(@).css('color') + else + data.color = 'none' ``` -#### `setCallback` +## Creating Plugins +Creating new plugins for Atrackt is very simple. At the bare minimum, you need a name, and a `send` method. The `send` method is where all of the plugin logic should live to interact with whatever tracking strategy you are using. Look at the source of the included plugins for better examples. -Call `setCallback` to assign functions to be run before and after the plugin's send function. Currently the only callbacks supported are 'before' and 'after' +Call `setPlugin` to quickly register a custom plugin. -```coffee -# set before callback for all plugins -Atrackt.setCallback 'before', (data, options ,event) -> +The minimum a plugin needs is a `send` method. This is a function that accepts the tracking object, and any additional options as an argument. You can do additional processing on the object and pass it wherever you need to track it. -# set after callback for a specific plugin -Atrackt.plugins['testPlugin'].setCallback 'after', (data, options ,event) -> +--- +#### `setPlugin` +```coffee +Atrackt.setPlugin 'testPlugin', + send: (data, options) -> + # do stuff ``` -## Debugging Console - -To better visualize what elements you are tracking, you can load the debugging console. +## Console +To better visualize what dom elements you are tracking, you can load the atrackt console. When the console is loaded, no actual tracking calls will be made. Instead the data that would normally be passed to the plugins will be logged to the console to help with debugging. -* Download the [script](https://raw.github.com/brewster1134/atrackt/master/js/atrackt.debug.js) _(right-click & save as)_ -* Add the script to your page _AFTER_ `atrackt.js` - * `` +* Load the atrackt.console library after the atrackt core library + * `` + * `` +* Visit the page with `?atracktConsole` at the end of thr URL -Simply add the url paramater `debugTracking=true` to the end of any URL to show the debugging console. For example `http://foo.com?debugTracking=true` +You should see a console show up at the top of your page that shows all the elements currently bound to events to be tracked. When new elements are bound, the console will update. -It is a bit crude, but it gives you a visual overview of your elements. +## Mutations +Sometimes elements you want to track get loaded asynchronously after page load. Modern browsers support _Mutation Observers_ which you can tap into to make sure new elements you want to track, are automatically bound when they are added. This code is not included in atrackt, but below is a simple example. -* The console lists all the elements currently being tracked along with their various values. -* If you hover over an element in the console, it will scroll to that element on your page and highlight it. -* If you hover over a tracked element on your page, it will scroll to that entry in your console and highlight it. -* Clicking on a row in the console will refresh any stale data on the element. This can happen if an element is tracked before it gets added to the dom. Since it doesn't have any parent elements yet, the categories column will be empty. -* The debugger will also show you errors if you have any. - * If you have multiple elements tracking the same data, they will be highlighted and show the error in the error column. **NOTE** Since duplicate items will have the same ID, the debugging console UI will not be able to scroll to BOTH duplicate elements. Check your javascript console to see the offending elements. +```coffee +# Get the cross-browser MutationObserver object +MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver + +# Create a mutation observer +observer = new MutationObserver (mutations) -> + for mutation in mutations + # loop through mutations and look for added nodes + for addedNode in mutation.addedNodes + # if added node is an anchor or a button, bind it to a click event + for el in addedNode.parentNode?.querySelectorAll?('a, button') || [] + Atrackt.setEvent + click: el + +# start observing on the body element after the dom is finished loading +document.addEventListener 'DOMContentLoaded', -> + observer.observe document.body, + childList: true + subtree: true +``` ## Demo - -Download the project and open `demo/index.html` in your browser. - -Click the link or visit `demo/index.html?debugTracking=true` to view the debugging console. +Download the project and open `demo/index.html` in your browser for a simple demo. Make sure to include `?atracktConsole` in the url to see the console! ## Development -### Dependencies - -* Ruby 1.9.3 - * [rbenv](https://github.com/sstephenson/rbenv) and [ruby-build](https://github.com/sstephenson/ruby-build) - * `rbenv install 1.9.3` -* Bundler Gem - * `gem install bundler` -* Bundled Gems - * `bundle install` -* Node.js & NPM - * OS X - * [HomeBrew](http://mxcl.github.io/homebrew/) _recommended_ - * 'brew install node' -* [Testem](https://github.com/airportyh/testem) - * `npm install testem -g` - -### Optional +Development dependencies are handled through [yuyi](https://github.com/brewster1134/yuyi) -* [PhantomJS](http://phantomjs.org) - * OS X - * [HomeBrew](http://mxcl.github.io/homebrew/) _recommended_ - * `brew install phantomjs` -* [Growl](http://growl.info/downloads) - * OS X 10.8 - -### Compiling - -Do **NOT** modify any `.js` files! Modify the coffee files in the `src` directory. Guard will watch for changes and compile them to the `lib` directory. - -`bundle exec guard` - -## Testing +```shell +gem install yuyi +yuyi -m https://raw.githubusercontent.com/brewster1134/atrackt/master/yuyi_menu +bundle install +bower install +npm install +``` -Simply run `testem`. Run `testem -g` for Growl support. +Do **NOT** modify any `.js` files! Modify the coffee files in the `src` directory. Testem will watch for changes and compile them to the `lib` directory. -### To-Do +#### Compiling & Testing +Testem will handle compiling coffeescript & sass files, and running the tests. -* pass an element to refresh() to scope to -* suport binding an element only if it matches a plugins selector rules (this requires some serious thought for the API) -* IE testing -* Support multiple callbacks +Simply run `testem`. -or- Run `testem -g` for Growl support. diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..6c8ae64 --- /dev/null +++ b/bower.json @@ -0,0 +1,13 @@ +{ + "name": "atrackt", + "dependencies": { + "jquery": "~2.1.1" + }, + "devDependencies": { + "chai": "~1.9.1", + "chai-as-promised": "~4.1.1", + "mocha": "~1.21.4", + "sinon": "~1.10.3", + "sinon-chai": "~2.5.0" + } +} diff --git a/demo/atrackt_demo.coffee b/demo/atrackt_demo.coffee new file mode 100644 index 0000000..00f1453 --- /dev/null +++ b/demo/atrackt_demo.coffee @@ -0,0 +1,9 @@ +$ -> + Atrackt.setPlugin 'Demo Plugin', + send: -> + + Atrackt.setEvent + click: '.track' + + $('a.custom').data 'atrackt-function', -> + console.log 'Custom Function Called!' diff --git a/demo/atrackt_demo.css b/demo/atrackt_demo.css new file mode 100644 index 0000000..716502a --- /dev/null +++ b/demo/atrackt_demo.css @@ -0,0 +1,21 @@ +a { + cursor: pointer; + display: block; + padding: 20px; + text-align: center; + border: 1px solid black; + margin: 0 auto 20px; + text-decoration: none; } + a.console-link { + background: bisque; } + a.track { + background: burlywood; } + +.set { + background: ivory; + text-align: center; + padding: 30px; + border: 1px solid lightgrey; + margin-bottom: 30px; } + +/*# sourceMappingURL=atrackt_demo.css.map */ diff --git a/demo/atrackt_demo.css.map b/demo/atrackt_demo.css.map new file mode 100644 index 0000000..7362a3a --- /dev/null +++ b/demo/atrackt_demo.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "AAAA,CAAC;EACC,MAAM,EAAE,OAAO;EACf,OAAO,EAAE,KAAK;EACd,OAAO,EAAE,IAAI;EACb,UAAU,EAAE,MAAM;EAClB,MAAM,EAAE,eAAe;EACvB,MAAM,EAAE,WAAW;EACnB,eAAe,EAAE,IAAI;EAErB,cAAc;IACZ,UAAU,EAAE,MAAM;EAEpB,OAAO;IACL,UAAU,EAAE,SAAS;;AAEzB,IAAI;EACF,UAAU,EAAE,KAAK;EACjB,UAAU,EAAE,MAAM;EAClB,OAAO,EAAE,IAAI;EACb,MAAM,EAAE,mBAAmB;EAC3B,aAAa,EAAE,IAAI", +"sources": ["atrackt_demo.sass"], +"names": [], +"file": "atrackt_demo.css" +} \ No newline at end of file diff --git a/demo/atrackt_demo.js b/demo/atrackt_demo.js new file mode 100644 index 0000000..4736844 --- /dev/null +++ b/demo/atrackt_demo.js @@ -0,0 +1,15 @@ +// Generated by CoffeeScript 1.8.0 +(function() { + $(function() { + Atrackt.setPlugin('Demo Plugin', { + send: function() {} + }); + Atrackt.setEvent({ + click: '.track' + }); + return $('a.custom').data('atrackt-function', function() { + return console.log('Custom Function Called!'); + }); + }); + +}).call(this); diff --git a/demo/atrackt_demo.sass b/demo/atrackt_demo.sass new file mode 100644 index 0000000..8cf0ff7 --- /dev/null +++ b/demo/atrackt_demo.sass @@ -0,0 +1,21 @@ +a + cursor: pointer + display: block + padding: 20px + text-align: center + border: 1px solid black + margin: 0 auto 20px + text-decoration: none + + &.console-link + background: bisque + + &.track + background: burlywood + +.set + background: ivory + text-align: center + padding: 30px + border: 1px solid lightgrey + margin-bottom: 30px diff --git a/demo/index.html b/demo/index.html index e168d57..1658a75 100644 --- a/demo/index.html +++ b/demo/index.html @@ -1,79 +1,40 @@ - - - - - - - - -

Check the javascript console for more details!

- Show Debugging Console -
- Custom Function -
- Foo1 - Foo2 - Foo3 - Foo3 - Foo4 - Foo5 +
+
+

1st Set

+ Track w/ Custom Value
-
- Bar1 - Bar2 - Bar3 - Bar4 - Bar5 + + - + + + + + + + + + diff --git a/lib/atrackt.console.css b/lib/atrackt.console.css new file mode 100644 index 0000000..bf540cf --- /dev/null +++ b/lib/atrackt.console.css @@ -0,0 +1,25 @@ +body.atrackt-console { + padding-top: 200px; } + body.atrackt-console .atrackt-console-active { + border: 2px solid orange; } + body.atrackt-console #atrackt-console { + font-family: monospace; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 200px; + overflow: scroll; + background-color: white; + border-bottom: 1px solid black; + z-index: 999; } + body.atrackt-console #atrackt-console table { + text-align: left; + width: 100%; } + body.atrackt-console #atrackt-console table tr.atrackt-console-active { + background-color: orange; } + body.atrackt-console #atrackt-console table tr.error { + color: white; + background-color: red; } + +/*# sourceMappingURL=atrackt.console.css.map */ diff --git a/lib/atrackt.console.css.map b/lib/atrackt.console.css.map new file mode 100644 index 0000000..70f026c --- /dev/null +++ b/lib/atrackt.console.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "AAEA,oBAAoB;EAClB,WAAW,EAHI,KAAK;EAKpB,4CAAuB;IACrB,MAAM,EAAE,gBAAgB;EAE1B,qCAAgB;IACd,WAAW,EAAE,SAAS;IACtB,QAAQ,EAAE,KAAK;IACf,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,IAAI;IACX,MAAM,EAdO,KAAK;IAelB,QAAQ,EAAE,MAAM;IAChB,gBAAgB,EAAE,KAAK;IACvB,aAAa,EAAE,eAAe;IAC9B,OAAO,EAAE,GAAG;IAEZ,2CAAK;MACH,UAAU,EAAE,IAAI;MAChB,KAAK,EAAE,IAAI;MAGT,qEAAwB;QACtB,gBAAgB,EAAE,MAAM;MAC1B,oDAAO;QACL,KAAK,EAAE,KAAK;QACZ,gBAAgB,EAAE,GAAG", +"sources": ["../src/atrackt.console.sass"], +"names": [], +"file": "atrackt.console.css" +} \ No newline at end of file diff --git a/lib/atrackt.console.js b/lib/atrackt.console.js new file mode 100644 index 0000000..90d63f7 --- /dev/null +++ b/lib/atrackt.console.js @@ -0,0 +1,119 @@ +// Generated by CoffeeScript 1.8.0 + +/* +Atrackt Tracking Library +https://github.com/brewster1134/atrackt +@version 1.0.0 +@author Ryan Brewster + */ + +(function() { + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + (function(root, factory) { + if (location.href.indexOf('atracktConsole') > -1) { + if (typeof define === 'function' && define.amd) { + return define(['jquery', 'atrackt'], function($, Atrackt) { + return window.Atrackt = new (factory($, Atrackt)); + }); + } else { + return window.Atrackt = new (factory($, Atrackt.constructor)); + } + } + })(this, function($, Atrackt) { + var AtracktConsole; + return AtracktConsole = (function(_super) { + __extends(AtracktConsole, _super); + + function AtracktConsole() { + var consoleHtml; + consoleHtml = "
\n

Location:

\n \n \n \n \n \n \n \n \n \n \n \n
PluginEventCategoriesValue
\n
"; + this.$console = $(consoleHtml); + $('#atrackt-location', this.$console).text(this._getLocation()); + $('body').addClass('atrackt-console').prepend(this.$console); + } + + AtracktConsole.prototype.setPlugin = function(pluginName, plugin) { + AtracktConsole.__super__.setPlugin.call(this, pluginName, plugin); + if (plugin) { + plugin._send = plugin.send; + return plugin.send = function(data, options) { + return console.log(plugin.name, data, options); + }; + } + }; + + AtracktConsole.prototype._registerElement = function(context, element, event) { + AtracktConsole.__super__._registerElement.call(this, context, element, event); + return this._renderConsoleElements(); + }; + + AtracktConsole.prototype._renderConsoleElements = function() { + var element, elements, eventType, plugin, pluginName, _i, _len, _ref, _ref1, _results; + $('tbody', this.$console).empty(); + _ref = this._elements; + for (eventType in _ref) { + elements = _ref[eventType]; + for (_i = 0, _len = elements.length; _i < _len; _i++) { + element = elements[_i]; + this._renderConsoleElement('ALL', element, eventType); + } + } + _ref1 = this.plugins; + _results = []; + for (pluginName in _ref1) { + plugin = _ref1[pluginName]; + _results.push((function() { + var _ref2, _results1; + _ref2 = plugin._elements; + _results1 = []; + for (eventType in _ref2) { + elements = _ref2[eventType]; + _results1.push((function() { + var _j, _len1, _results2; + _results2 = []; + for (_j = 0, _len1 = elements.length; _j < _len1; _j++) { + element = elements[_j]; + _results2.push(this._renderConsoleElement(pluginName, element, eventType)); + } + return _results2; + }).call(this)); + } + return _results1; + }).call(this)); + } + return _results; + }; + + AtracktConsole.prototype._renderConsoleElement = function(plugin, element, eventType) { + var $rowEl, $trackEl, elementValueId, trackObject; + trackObject = this._getTrackObject(element, eventType); + $rowEl = $("" + plugin + "" + eventType + "" + trackObject._categories + "" + trackObject._value + ""); + $trackEl = $(element); + $('tbody', this.$console).append($rowEl); + elementValueId = trackObject._categories.slice(0); + elementValueId.push(trackObject._value); + elementValueId.push(eventType); + elementValueId = elementValueId.join('-').replace(/[^A-Za-z0-9]+/g, ''); + if ($("#" + elementValueId, this.$console).length) { + $("#" + elementValueId, this.$console).addClass('error'); + $rowEl.addClass('error'); + } + $trackEl.attr('data-atrackt-id', elementValueId); + $rowEl.attr('id', elementValueId); + return $rowEl.add($trackEl).hover(function() { + $rowEl.addClass('atrackt-console-active'); + return $trackEl.addClass('atrackt-console-active'); + }, function() { + $rowEl.removeClass('atrackt-console-active'); + return $trackEl.removeClass('atrackt-console-active'); + }); + }; + + return AtracktConsole; + + })(Atrackt); + }); + +}).call(this); diff --git a/lib/atrackt.debug.js b/lib/atrackt.debug.js deleted file mode 100644 index 02394fc..0000000 --- a/lib/atrackt.debug.js +++ /dev/null @@ -1,169 +0,0 @@ -/* -Atrackt Debugging Console -@author Ryan Brewster -@version 0.0.3 -*/ - - -(function() { - $.extend(window.Atrackt, { - _debug: function() { - return this._urlParams('debugTracking') === 'true'; - }, - _debugConsoleReset: function() { - if (this._debug()) { - return $('#atrackt-elements tbody').empty(); - } - }, - _debugConsole: function() { - var _this = this; - - if (!Atrackt._debug()) { - return false; - } - return $(function() { - $('body').addClass('atrackt-debug'); - $('').appendTo('head'); - return $('
\ -
\ -
Location: ' + _this._getLocation() + '
\ - \ - \ - \ - \ - \ - \ - \ - \ -
Plugin : EventCategoriesValueError
\ -
\ -
').prependTo('body'); - }); - }, - _debugPluginEvent: function(plugin, event) { - return "
" + plugin + " : " + event + "
"; - }, - _debugElRefresh: function(elId) { - var $bodyEl, $consoleEl, $els; - - $els = $('body [data-atrackt-debug-id=' + elId + ']'); - $consoleEl = $els.filter('.atrackt-element'); - $bodyEl = $els.not('.atrackt-element'); - this._getTrackObject($bodyEl); - return $consoleEl.find('.atrackt-categories').text($bodyEl.data('track-object').categories); - }, - _debugEl: function($el, plugin, event) { - var elId, matchingBodyEls, matchingConsoleEls, mathingEls, pluginEventDiv, pluginEventMainDiv; - - if (!this._debug()) { - return false; - } - this._getTrackObject($el); - elId = this._debugElementId($el); - $el.attr('data-atrackt-debug-id', elId); - matchingConsoleEls = $('body .atrackt-element[data-atrackt-debug-id=' + elId + ']'); - pluginEventDiv = this._debugPluginEvent(plugin, event); - if (matchingConsoleEls.length === 0) { - $('\ - ' + pluginEventDiv + '\ - ' + $el.data('track-object').categories + '\ - ' + $el.data('track-object').value + '\ - \ - ').appendTo('#atrackt-elements tbody'); - } else { - pluginEventMainDiv = matchingConsoleEls.find('.atrackt-plugin-event'); - if (!($(pluginEventMainDiv).has("." + plugin + "." + event).length > 0)) { - pluginEventMainDiv.append(pluginEventDiv); - } - } - mathingEls = $('body [data-atrackt-debug-id=' + elId + ']'); - mathingEls.off('.atrackt-debug'); - matchingConsoleEls = mathingEls.filter('.atrackt-element'); - matchingBodyEls = mathingEls.not('.atrackt-element'); - if (matchingBodyEls.length > 1) { - console.log('DUPLICATE ELEMENTS FOUND', matchingBodyEls); - matchingConsoleEls.addClass('error'); - matchingConsoleEls.find('.atrackt-error').append('DUPLICATE'); - } - matchingConsoleEls.on('click.atrackt-debug', function() { - return Atrackt._debugElRefresh($(this).data('atrackt-debug-id')); - }); - matchingConsoleEls.on('mouseenter.atrackt-debug', function() { - $(this).add($el).addClass('highlight'); - return $('html, body').scrollTop($el.offset().top - $('#atrackt-debug').height() - 20); - }); - matchingConsoleEls.on('mouseleave.atrackt-debug', function() { - return $(this).add($el).removeClass('highlight'); - }); - $el.on('click.atrackt-debug', function() { - return Atrackt._debugElRefresh($(this).data('atrackt-debug-id')); - }); - $el.on('mouseenter.atrackt-debug', function() { - var elIndex, scrollTo, totalEls, totalHeight; - - $(this).add(matchingConsoleEls).addClass('highlight'); - totalHeight = $('#atrackt-elements tbody').height(); - totalEls = $('#atrackt-elements .atrackt-element').length; - elIndex = $('#atrackt-elements .atrackt-element').index(matchingConsoleEls); - scrollTo = (elIndex / totalEls) * totalHeight; - return $('#atrackt-debug').scrollTop(scrollTo); - }); - return $el.on('mouseleave.atrackt-debug', function() { - return $(this).add(matchingConsoleEls).removeClass('highlight'); - }); - }, - _debugElementId: function($el) { - var idArray, _categories, _ctaValue; - - if (!$el.data('track-object')) { - return false; - } - _categories = $el.data('track-object').categories; - _ctaValue = $el.data('track-object').value; - idArray = []; - if (_categories) { - idArray.push(_categories); - } - if (_ctaValue) { - idArray.push(_ctaValue); - } - return idArray.join().toLowerCase().replace(/[^\w]/g, ''); - }, - _debugConsoleDestroy: function() { - $('#atrackt-debug').remove(); - $('body').removeClass('atrackt-debug'); - $('body [data-atrackt-debug-id]').removeAttr('data-atrackt-debug-id'); - $('*', 'body').off('.atrackt-debug'); - return true; - } - }); - - Atrackt._debugConsole(); - -}).call(this); diff --git a/lib/atrackt.js b/lib/atrackt.js index 37c7a4e..a323e7e 100644 --- a/lib/atrackt.js +++ b/lib/atrackt.js @@ -1,311 +1,302 @@ +// Generated by CoffeeScript 1.8.0 + /* Atrackt Tracking Library https://github.com/brewster1134/atrackt -@version 0.1.2 +@version 1.0.0 @author Ryan Brewster -*/ - + */ (function() { - if (!String.prototype.trim) { - String.prototype.trim = function() { - return this.replace(/^\s+|\s+$/g, ''); - }; - } - - (function($, _, window, document) { + (function(root, factory) { + if (typeof define === 'function' && define.amd) { + return define(['jquery'], function($) { + return window.Atrackt || (window.Atrackt = new (factory($))); + }); + } else { + return window.Atrackt || (window.Atrackt = new (factory($))); + } + })(this, function($) { + var Atrackt; window.console || (window.console = { - log: function() {} + log: function() {}, + error: function() {} }); - return window.Atrackt = { - plugins: {}, - registerPlugin: function(pluginName, attrs) { - var _this = this; - if (typeof (attrs != null ? attrs.send : void 0) !== 'function') { - return console.log('NO SEND METHOD DEFINED'); + return Atrackt = (function() { + function Atrackt() {} + + Atrackt.prototype.plugins = {}; + + Atrackt.prototype._data = {}; + + Atrackt.prototype._options = {}; + + Atrackt.prototype._elements = {}; + + Atrackt.prototype._callbacks = {}; + + Atrackt.prototype.setPlugin = function(pluginName, plugin) { + if (!pluginName) { + throw 'ATRACKT ERROR: `setPlugin` - No plugin name defined'; } - attrs.elements || (attrs.elements = {}); - attrs.includeSelectors || (attrs.includeSelectors = {}); - attrs.includeElements || (attrs.includeElements = {}); - attrs.excludeSelectors || (attrs.excludeSelectors = {}); - attrs.excludeElements || (attrs.excludeElements = {}); - attrs.bind = function(eventsObject) { - var currentElements, currentSelectors, data, eventType, _results; - if (eventsObject == null) { - return console.log('NOTHING TO BIND. YOU MUST PASS AN EVENT OBJECT CALLING BIND'); - } - _results = []; - for (eventType in eventsObject) { - data = eventsObject[eventType]; - if (data instanceof Array) { - currentSelectors = attrs.includeSelectors[eventType] || []; - attrs.includeSelectors[eventType] = _.union(currentSelectors, data); - } else if (data instanceof jQuery) { - currentElements = attrs.includeElements[eventType] || []; - attrs.includeElements[eventType] = _.union(currentElements, data); - } - _results.push(_this._bind(pluginName, eventType, data)); + if (typeof (plugin != null ? plugin.send : void 0) !== 'function') { + throw "ATRACKT ERROR: `setPlugin` - No send method was defined for `" + pluginName + "`."; + } + pluginName = pluginName.toLowerCase().replace(/[^a-z]/g, '-'); + this.plugins[pluginName] = plugin; + plugin.name = pluginName; + plugin._data || (plugin._data = {}); + plugin._options || (plugin._options = {}); + plugin._elements || (plugin._elements = {}); + plugin._callbacks || (plugin._callbacks = {}); + plugin.setEvent = (function(_this) { + return function(eventsObject) { + return _this.setEvent(eventsObject, plugin); + }; + })(this); + plugin.setData = (function(_this) { + return function(data) { + return _this.setData(data, plugin); + }; + })(this); + plugin.setOptions = (function(_this) { + return function(options) { + return _this.setOptions(options, plugin); + }; + })(this); + plugin.setCallback = (function(_this) { + return function(name, callback) { + return _this.setCallback(name, callback, plugin); + }; + })(this); + return plugin.track = (function(_this) { + return function(data, options, event, plugin) { + return _this.track(data, options, event, plugin); + }; + })(this); + }; + + Atrackt.prototype.setEvent = function(eventsObject, context) { + var eventType, globalEvent, objects, pluginEvent, _results; + if (context == null) { + context = this; + } + if (!eventsObject) { + throw 'ATRACKT ERROR: `setEvent` - You must pass a valid event object.'; + } + _results = []; + for (eventType in eventsObject) { + objects = eventsObject[eventType]; + globalEvent = [eventType, 'atrackt']; + pluginEvent = globalEvent.slice(0); + if (context.name) { + pluginEvent.push(context.name); } - return _results; - }; - attrs.unbind = function(eventsObject) { - var currentElements, currentSelectors, data, eventType, _results; - if (eventsObject != null) { - _results = []; - for (eventType in eventsObject) { - data = eventsObject[eventType]; - if (data instanceof Array) { - currentSelectors = attrs.excludeSelectors[eventType] || []; - attrs.excludeSelectors[eventType] = _.union(currentSelectors, data); - } else if (data instanceof jQuery) { - currentElements = attrs.excludeElements[eventType] || []; - attrs.excludeElements[eventType] = _.union(currentElements, data); + _results.push($(objects).each((function(_this) { + return function(index, element) { + var globalIndex, pluginData, pluginIndex, pluginName, _base, _base1, _ref, _ref1, _ref2, _results1; + (_base = _this._elements)[eventType] || (_base[eventType] = []); + if (context.name) { + globalIndex = _this._elements[eventType].indexOf(element); + if (globalIndex === -1) { + (_base1 = context._elements)[eventType] || (_base1[eventType] = []); + if (context._elements[eventType].indexOf(element) === -1) { + return _this._registerElement(context, element, eventType); + } + } + } else if (_this._elements[eventType].indexOf(element) === -1) { + _this._registerElement(context, element, eventType); + _ref = _this.plugins; + _results1 = []; + for (pluginName in _ref) { + pluginData = _ref[pluginName]; + pluginIndex = (_ref1 = pluginData._elements[eventType]) != null ? _ref1.indexOf(element) : void 0; + if (pluginIndex !== -1) { + _results1.push((_ref2 = pluginData._elements[eventType]) != null ? _ref2.splice(pluginIndex, 1) : void 0); + } else { + _results1.push(void 0); + } + } + return _results1; } - _results.push(_this._unbind(pluginName, eventType)); - } - return _results; - } else { - attrs.elements = {}; - attrs.includeSelectors = {}; - attrs.includeElements = {}; - attrs.excludeSelectors = {}; - attrs.excludeElements = {}; - return _this._unbind(pluginName); - } - }; - attrs.setOptions = function(options) { - var pluginOptions; - pluginOptions = attrs.options || {}; - return attrs.options = $.extend(true, pluginOptions, options); - }; - attrs.setGlobalData = function(object) { - attrs.globalData || (attrs.globalData = {}); - return $.extend(true, attrs.globalData, object); - }; - attrs.setCallback = function(name, callback) { - if (!_.contains(['before', 'after'], name)) { - return false; - } - attrs.callbacks || (attrs.callbacks = {}); - return attrs.callbacks[name] = callback; - }; - return this.plugins[pluginName] = attrs; - }, - setGlobalData: function(object) { - var pluginData, pluginName, _ref, _results; - _ref = this.plugins; - _results = []; - for (pluginName in _ref) { - pluginData = _ref[pluginName]; - _results.push(pluginData.setGlobalData(object)); + }; + })(this))); } return _results; - }, - setCallback: function(name, callback) { - var pluginData, pluginName, _ref, _results; - _ref = this.plugins; - _results = []; - for (pluginName in _ref) { - pluginData = _ref[pluginName]; - _results.push(pluginData.setCallback(name, callback)); + }; + + Atrackt.prototype.setData = function(data, context) { + if (context == null) { + context = this; } - return _results; - }, - bind: function(eventsObject) { - var pluginData, pluginName, _ref, _results; - _ref = this.plugins; - _results = []; - for (pluginName in _ref) { - pluginData = _ref[pluginName]; - _results.push(pluginData.bind(eventsObject)); + return $.extend(true, context._data, data); + }; + + Atrackt.prototype.setOptions = function(options, context) { + if (context == null) { + context = this; } - return _results; - }, - unbind: function(eventsObject) { - var pluginData, pluginName, _ref, _results; - _ref = this.plugins; - _results = []; - for (pluginName in _ref) { - pluginData = _ref[pluginName]; - _results.push(pluginData.unbind(eventsObject)); + return $.extend(true, context._options, options); + }; + + Atrackt.prototype.setCallback = function(name, callback, context) { + var allowedCallbacks, _base; + if (context == null) { + context = this; } - return _results; - }, - refresh: function() { - var eventType, pluginData, pluginName, selectors, _ref, _ref1; - this._debugConsoleReset(); - _ref = this.plugins; - for (pluginName in _ref) { - pluginData = _ref[pluginName]; - _ref1 = pluginData.elements; - for (eventType in _ref1) { - selectors = _ref1[eventType]; - this._bind(pluginName, eventType); - } + allowedCallbacks = ['before', 'after']; + if (allowedCallbacks.indexOf(name) === -1) { + throw "ATRACKT ERROR: `setCallback` - `" + name + "` is not a valid callback. Only callbacks allowed are: " + (allowedCallbacks.join(', ')); } - return true; - }, - track: function(data, options, event) { - var pluginData, pluginName, trackObject, trackingData, _ref, _ref1, _ref2; + (_base = context._callbacks)[name] || (_base[name] = []); + return context._callbacks[name].push(callback); + }; + + Atrackt.prototype.track = function(data, options, event, context) { + var eventNamespace, pluginData, pluginName, _ref, _results; if (options == null) { options = {}; } - if (!(trackObject = this._getTrackObject(data))) { - return false; + if (context != null ? context.name : void 0) { + options['_plugin'] = context.name; } _ref = this.plugins; + _results = []; for (pluginName in _ref) { pluginData = _ref[pluginName]; - trackingData = $.extend(true, {}, pluginData.globalData, trackObject); - options = $.extend({}, options[pluginName], { - plugin: pluginName - }); - if ((_ref1 = pluginData.callbacks) != null) { - if (typeof _ref1['before'] === "function") { - _ref1['before'](trackingData, options); + if (event) { + eventNamespace = event != null ? event.handleObj.namespace : void 0; + if (eventNamespace === 'atrackt' || eventNamespace === ("atrackt." + pluginName)) { + _results.push(this._trackJqueryObject(pluginData, data, options, event)); + } else { + _results.push(void 0); } - } - if (data instanceof jQuery) { - if ((event == null) || event.handleObj.namespace === ("atrackt." + pluginName)) { - pluginData.send($.extend(trackingData, { - event: event != null ? event.type : void 0 - }), $.extend(options, { - el: data - })); + } else { + if (!options['_plugin'] || options['_plugin'] === pluginName) { + if (data instanceof jQuery) { + _results.push(this._trackJqueryObject(pluginData, data, options, event)); + } else { + _results.push(this._track(pluginData, data, options, event)); + } + } else { + _results.push(void 0); } - } else if (data instanceof Object) { - pluginData.send(trackingData, options); } - if ((_ref2 = pluginData.callbacks) != null) { - if (typeof _ref2['after'] === "function") { - _ref2['after'](trackingData, options); - } + } + return _results; + }; + + Atrackt.prototype._registerElement = function(context, element, event) { + var globalEvent, pluginEvent; + context._elements[event].push(element); + globalEvent = [event, 'atrackt']; + if (context.name) { + pluginEvent = globalEvent.slice(0); + if (context.name) { + pluginEvent.push(context.name); } + } else { + pluginEvent = globalEvent; } - return true; - }, - _cleanup: function() {}, - _collectElements: function(pluginName, eventType) { - var allElements, excludeElements, excludeSelectors, includeElements, includeSelectors, plugin, _ref, _ref1; - this._cleanup(pluginName, eventType); - plugin = this.plugins[pluginName]; - includeSelectors = $((_ref = plugin.includeSelectors[eventType]) != null ? _ref.join(',') : void 0); - includeElements = includeSelectors || []; - _.each(plugin.includeElements[eventType] || [], function(el) { - return includeElements = includeElements.add(el); - }); - excludeSelectors = $((_ref1 = plugin.excludeSelectors[eventType]) != null ? _ref1.join(',') : void 0); - excludeElements = excludeSelectors || []; - _.each(plugin.excludeElements[eventType] || [], function(el) { - return excludeElements = excludeElements.add(el); + $(element).off(globalEvent.join('.')); + return $(element).on(pluginEvent.join('.'), function(e) { + return context.track(this, {}, e); }); - allElements = includeElements.not(excludeElements); - this.plugins[pluginName].elements[eventType] = allElements; - return allElements; - }, - _bind: function(pluginName, eventType, data) { - var selectors; - this._collectElements(pluginName, eventType); - this._unbind(pluginName, eventType, data); - selectors = $(this.plugins[pluginName].elements[eventType]); - if (data instanceof Array) { - selectors = selectors.filter(data.join(',')); - } else if (data instanceof jQuery) { - selectors = data; + }; + + Atrackt.prototype._trackJqueryObject = function(plugin, data, options, event) { + return $(data).each((function(_this) { + return function(index, element) { + return _this._track(plugin, element, options, event); + }; + })(this)); + }; + + Atrackt.prototype._track = function(plugin, data, options, event) { + var callback, metaData, optionsCopy, trackingData, trackingOptions, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _results; + metaData = this._getTrackObject(data, event); + if (!metaData) { + throw 'ATRACKT ERROR: `track` - Only valid selectors, jquery objects, or html nodes are supported.'; } - selectors.on("" + eventType + ".atrackt." + pluginName, function(e) { - return Atrackt.track($(this), {}, e); - }); - if (typeof this._debug === "function" ? this._debug() : void 0) { - selectors.each(function() { - return Atrackt._debugEl($(this), pluginName, eventType); - }); + trackingData = $.extend(true, {}, this._data, plugin._data, options['_data'] || {}, metaData); + optionsCopy = $.extend(true, {}, options); + delete optionsCopy['_data']; + trackingOptions = $.extend(true, {}, this._options, plugin._options, optionsCopy); + _ref = this._callbacks['before'] || []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + callback = _ref[_i]; + if (typeof callback === "function") { + callback(trackingData, trackingOptions); + } } - return selectors; - }, - _unbind: function(pluginName, eventType, data) { - var eventName, selectors; - eventName = '.atrackt'; - selectors = $('*', 'body'); - if (eventType != null) { - eventName = eventType.concat(eventName); + _ref1 = plugin._callbacks['before'] || []; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + callback = _ref1[_j]; + if (typeof callback === "function") { + callback(trackingData, trackingOptions); + } } - if (pluginName != null) { - eventName = eventName.concat("." + pluginName); + if (data instanceof jQuery || data.nodeType === 1) { + if (typeof $(data).data('atrackt-function') === 'function') { + $.proxy($(data).data('atrackt-function'), data)(trackingData, trackingOptions); + } } - if ((pluginName != null) && (eventType != null)) { - selectors = $(this.plugins[pluginName].elements[eventType]); + plugin.send(trackingData, trackingOptions); + _ref2 = plugin._callbacks['after'] || []; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + callback = _ref2[_k]; + if (typeof callback === "function") { + callback(trackingData, trackingOptions); + } } - if (data instanceof Array) { - selectors = selectors.filter(data.join(',')); - } else if (data instanceof jQuery) { - selectors = data; + _ref3 = this._callbacks['after'] || []; + _results = []; + for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { + callback = _ref3[_l]; + _results.push(typeof callback === "function" ? callback(trackingData, trackingOptions) : void 0); } - selectors.off(eventName); - return selectors; - }, - _getTrackObject: function(data, event) { - var $el, _base; - if (data.nodeType === 1) { - data = $(data); + return _results; + }; + + Atrackt.prototype._getTrackObject = function(data, event) { + var $el; + if (data instanceof jQuery || data.nodeType === 1) { + $el = $(data); + data = { + _categories: this._getCategories($el), + _value: this._getValue($el) + }; } - if (data instanceof jQuery) { - $el = data; - $el.data('track-object', { - location: this._getLocation(), - categories: this._getCategories($el), - value: this._getValue($el) - }); - if (typeof (_base = $el.data('track-function')) === "function") { - _base($el.data('track-object'), $el, event); - } - return $el.data('track-object'); - } else if (data instanceof Object) { - $.extend(data, { - location: this._getLocation() - }); - return data; - } else { - console.log('DATA IS NOT TRACKABLE', data); - return false; + data['_location'] = this._getLocation(); + if (event != null ? event.type : void 0) { + data['_event'] = event.type; } - }, - _getLocation: function() { - return $('body').data('track-location') || $(document).attr('title') || document.URL; - }, - _getCategories: function($el) { + return data; + }; + + Atrackt.prototype._getLocation = function() { + return $('body').data('atrackt-location') || $(document).attr('title') || document.URL; + }; + + Atrackt.prototype._getCategories = function($el) { var catArray; catArray = []; - if ($el.data('track-cat')) { - catArray.unshift($el.data('track-cat')); + if ($el.data('atrackt-category')) { + catArray.unshift($el.data('atrackt-category')); } - $el.parents('[data-track-cat]').each(function() { - return catArray.unshift($(this).data('track-cat')); + $el.parents('[data-atrackt-category]').each(function() { + return catArray.unshift($(this).data('atrackt-category')); }); return catArray; - }, - _getValue: function($el) { - return $el.data('track-value') || $el.attr('title') || $el.attr('name') || $el.text().trim() || $el.val() || $el.attr('id') || $el.attr('class'); - }, - _urlParams: function(key) { - var paramString, params; - if (key == null) { - key = null; - } - params = {}; - paramString = window.location.search.substring(1); - $.each(paramString.split('&'), function(i, param) { - var paramObject; - paramObject = param.split('='); - return params[paramObject[0]] = paramObject[1]; - }); - if (key) { - return params[key]; - } else { - return params; - } - } - }; - })(jQuery, _, window, document); + }; + + Atrackt.prototype._getValue = function($el) { + return $el.data('atrackt-value') || $el.attr('title') || $el.attr('name') || $el.text().trim() || $el.val() || $el.attr('id') || $el.attr('class'); + }; + + return Atrackt; + + })(); + }); }).call(this); diff --git a/lib/plugins/atrackt.localytics.js b/lib/plugins/atrackt.localytics.js index d32b825..700d9e7 100644 --- a/lib/plugins/atrackt.localytics.js +++ b/lib/plugins/atrackt.localytics.js @@ -1,37 +1,34 @@ +// Generated by CoffeeScript 1.8.0 + /* Atrackt Localytics Plugin https://github.com/brewster1134/atrackt @author Ryan Brewster -@version 0.0.4 -*/ - +@version 1.0.0 + */ (function() { - window.Atrackt.registerPlugin('localytics', { - send: function(obj, options) { + window.Atrackt.setPlugin('localytics', { + send: function(data, options) { var redirectUrl; if (this._isUiWebView()) { - redirectUrl = this._getRedirectUrl(obj, options); + redirectUrl = this._getRedirectUrl(data, options); return this._redirect(redirectUrl); } else { - return this._callTagMethod(obj, options); + return this._callTagMethod(data, options); } }, - _callTagMethod: function(obj, options) { - return typeof localyticsSession !== "undefined" && localyticsSession !== null ? localyticsSession.tagEvent(options.eventName, obj) : void 0; + _callTagMethod: function(data, options) { + return typeof localyticsSession !== "undefined" && localyticsSession !== null ? localyticsSession.tagEvent(options.eventName, data) : void 0; }, _isUiWebView: function() { return /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent); }, - _getRedirectUrl: function(obj, options) { + _getRedirectUrl: function(data, options) { var redirectUrl; - if (options.screenEvent) { - redirectUrl = "localytics://?screenEvent=" + options.eventName; - } else { - redirectUrl = "localytics://?event=" + options.eventName; - } - if (Object.keys(obj).length) { - redirectUrl += "&attributes=" + (JSON.stringify(obj)); + redirectUrl = options.screenEvent ? "localytics://?screenEvent=" + options.eventName : "localytics://?event=" + options.eventName; + if (Object.keys(data).length) { + redirectUrl += "&attributes=" + (JSON.stringify(data)); } return redirectUrl; }, diff --git a/lib/plugins/atrackt.omniture.js b/lib/plugins/atrackt.omniture.js index 18c03ce..bf8fb5d 100644 --- a/lib/plugins/atrackt.omniture.js +++ b/lib/plugins/atrackt.omniture.js @@ -1,31 +1,32 @@ +// Generated by CoffeeScript 1.8.0 + /* Atrackt Omniture Plugin https://github.com/brewster1134/atrackt @author Ryan Brewster -@version 0.0.7 -*/ - +@version 1.0.0 + */ (function() { - window.Atrackt.registerPlugin('omniture', { - send: function(obj, options) { + window.Atrackt.setPlugin('omniture', { + send: function(data, options) { var arg, _ref, _ref1; - - if (!s) { - return console.log('SITE CATALYST SCRIPT NOT LOADED!'); + if (typeof s === 'undefined') { + return console.error('ATRACKT ERROR: PLUGIN `omniture` - Site catalyst library not loaded'); } - obj.categories = (_ref = obj.categories) != null ? _ref.join(this.options.delimiters.category) : void 0; - obj = this.translatePropMap(obj); - this.buildSObject(obj); + data._categories = (_ref = data._categories) != null ? _ref.join(this.options.delimiters.category) : void 0; + data = this.translatePropMap(data); + this.buildSObject(data); if (options.page && (s.t != null)) { s.t(); } else if (s.tl != null) { arg = ((_ref1 = options.el) != null ? _ref1.attr('href') : void 0) ? options.el[0] : true; - s.tl(arg, 'o', this.buildLinkName(obj)); + s.tl(arg, options['trackingType'], this.buildLinkName(data)); } - return obj; + return data; }, options: { + trackingType: 'o', charReplaceRegex: /[^\x20-\x7E]/g, version: 14, delimiters: { @@ -34,15 +35,14 @@ https://github.com/brewster1134/atrackt }, linkTrackVars: ['products', 'events'], propMap: { - location: 'prop1', - categories: 'prop2', - value: 'prop3', - event: 'prop4' + _location: 'prop1', + _categories: 'prop2', + _value: 'prop3', + _event: 'prop4' } }, buildSObject: function(obj) { var key, linkTrackVars, value; - switch (this.options.version) { case 14: linkTrackVars = this.options.linkTrackVars; @@ -60,31 +60,28 @@ https://github.com/brewster1134/atrackt }, buildLinkName: function(obj) { var linkName; - - linkName = [obj[this.options.propMap.location], obj[this.options.propMap.categories], obj[this.options.propMap.value]]; + linkName = [obj[this.options.propMap._location], obj[this.options.propMap._categories], obj[this.options.propMap._value]]; return linkName.join(this.options.delimiters.linkName); }, translatePropMap: function(obj) { - var _globalData, - _this = this; - + var _globalData; if (this.options.version > 14) { return obj; } _globalData = {}; - $.each(obj, function(k, v) { - var _base; - - return _globalData[_this.keyLookup(k)] = v != null ? typeof (_base = v.toString()).replace === "function" ? _base.replace(_this.options.charReplaceRegex, '') : void 0 : void 0; - }); + $.each(obj, (function(_this) { + return function(k, v) { + var _base; + return _globalData[_this.keyLookup(k)] = v != null ? typeof (_base = v.toString()).replace === "function" ? _base.replace(_this.options.charReplaceRegex, '') : void 0 : void 0; + }; + })(this)); return _globalData; }, keyLookup: function(key) { var _newKey; - _newKey = this.options.propMap[key]; if (!_newKey) { - console.log("NO MAPPING FOR: " + key); + console.error("ATRACKT ERROR: PLUGIN `omniture` - No mapping for `" + key + "` in omniture config"); } return _newKey || key; } diff --git a/package.json b/package.json new file mode 100644 index 0000000..88ff5b3 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "scripts": { + "preinstall": "npm install -g bower coffee-script testem" + } +} diff --git a/spec/atrackt.console_spec.sass b/spec/atrackt.console_spec.sass new file mode 100644 index 0000000..de4a277 --- /dev/null +++ b/spec/atrackt.console_spec.sass @@ -0,0 +1,4 @@ +body.atrackt-console + #mocha + #mocha-stats + top: 260px diff --git a/spec/atrackt_spec.coffee b/spec/atrackt_spec.coffee new file mode 100644 index 0000000..2af8a7d --- /dev/null +++ b/spec/atrackt_spec.coffee @@ -0,0 +1,248 @@ +# Setup plugin +# +Atrackt.setPlugin 'Foo Plugin', + send: -> +_plugin = Atrackt.plugins['foo-plugin'] + +describe 'Atrackt', -> + it 'should set the Atrackt object on window', -> + expect(window.Atrackt).to.exist + + # + # PUBLIC METHODS + # + describe '#setPlugin', -> + it 'should add the plugin object to global plugins', -> + expect(_plugin).to.exist + + plugin_methods = [ + 'setEvent' + 'setOptions' + 'setData' + ] + + for method in plugin_methods + it "should create a #{method} function on the plugin", -> + expect(_plugin[method]).to.be.a 'function' + + context 'when registering an invalid plugin', -> + it 'should throw an exception', -> + noName = -> + Atrackt.setPlugin() + noSend = -> + Atrackt.setPlugin 'invalid' + + expect(noName).to.throw + expect(noSend).to.throw + + describe '#setEvent', -> + it 'should not overwrite global events with plugin events', -> + $fooEl = $('
') + + Atrackt.setEvent + click: $fooEl + + _plugin.setEvent + click: $fooEl + + expect($._data($fooEl[0]).events.click).to.have.length 1 + expect($._data($fooEl[0]).events.click[0].namespace).to.equal 'atrackt' + + expect(Atrackt._elements.click.indexOf($fooEl[0])).to.not.equal -1 + expect((_plugin._elements.click || []).indexOf($fooEl[0])).to.equal -1 + + it 'should overwrite plugin events with global events', -> + $fooEl = $('
') + + _plugin.setEvent + click: $fooEl + + Atrackt.setEvent + click: $fooEl + + expect($._data($fooEl[0]).events.click).to.have.length 1 + expect($._data($fooEl[0]).events.click[0].namespace).to.equal 'atrackt' + + expect(Atrackt._elements.click.indexOf($fooEl[0])).to.not.equal -1 + expect(_plugin._elements.click.indexOf($fooEl[0])).to.equal -1 + + it 'should bind on a plugin namespace', -> + $fooEl = $('
') + + _plugin.setEvent + click: $fooEl + + expect($._data($fooEl[0]).events.click[0].namespace).to.equal 'atrackt.foo-plugin' + + it 'should bind selectors', -> + $fooEl = $('
') + $('body').append $fooEl + + Atrackt.setEvent + click: '.bind-selector' + + expect($._data($fooEl[0]).events.click[0].namespace).to.equal 'atrackt' + + it 'should bind html nodes', -> + $fooEl = $('
') + + Atrackt.setEvent + click: $fooEl[0] + + expect($._data($fooEl[0]).events.click[0].namespace).to.equal 'atrackt' + + it 'should bind jquery objects', -> + $fooEl = $('
') + + Atrackt.setEvent + click: $fooEl + + expect($._data($fooEl[0]).events.click[0].namespace).to.equal 'atrackt' + + describe '#setOptions', -> + before -> + Atrackt.setOptions + global_option: true + + _plugin.setOptions + plugin_option: true + + it 'should set options', -> + expect(Atrackt._options.global_option).to.be.true + expect(_plugin._options.plugin_option).to.be.true + + describe '#setData', -> + before -> + Atrackt.setData + global_data: true + + _plugin.setData + plugin_data: true + + it 'should set data', -> + expect(Atrackt._data.global_data).to.be.true + expect(_plugin._data.plugin_data).to.be.true + + describe '#setCallback', -> + before -> + Atrackt.setCallback 'before', -> + Atrackt.setCallback 'before', -> + + _plugin.setCallback 'after', -> + + it 'should add global callbacks', -> + expect(Atrackt._callbacks.before[0]).to.be.a 'function' + expect(Atrackt._callbacks.before[1]).to.be.a 'function' + + it 'should add plugin callbacks', -> + expect(_plugin._callbacks.after[0]).to.be.a 'function' + + describe '#track', -> + $fooEl = null + beforeCallbackSpy = sinon.spy() + afterCallbackSpy = sinon.spy() + pluginSpy = sinon.spy _plugin, 'send' + + before -> + Atrackt._options = { + global_option: true + } + + _plugin._options = { + plugin_option: true + } + + Atrackt._data = { + global_data: true + } + + _plugin._data = { + plugin_data: true + } + + Atrackt._callbacks = + before: [ beforeCallbackSpy ] + + _plugin._callbacks = + after: [ afterCallbackSpy ] + + after -> + beforeCallbackSpy.reset() + afterCallbackSpy.reset() + pluginSpy.reset() + + context 'when tracking an object', -> + before -> + Atrackt.plugins['foo-plugin'].track + track_data: true + , + track_option: true + _data: + option_data: true + + it 'should call the send method on plugins with data and options', -> + expect(pluginSpy).to.be.calledWithExactly + _location: 'Atrackt Test' + global_data: true + plugin_data: true + track_data: true + option_data: true + , + global_option: true + plugin_option: true + track_option: true + + context 'when tracking an element', -> + before -> + $fooEl = $('') + $fooEl.data 'atrackt-function', (data, options) -> + data['function_data'] = true + options['function_option'] = true + + Atrackt.plugins['foo-plugin'].track $fooEl, + track_option: true + _data: + option_data: true + + it 'should call the send method on plugins with data and options', -> + expect(pluginSpy).to.be.calledWithExactly + _location: 'Atrackt Test' + _categories: ['Anchor'] + _value: 'Foo' + global_data: true + plugin_data: true + option_data: true + function_data: true + , + global_option: true + plugin_option: true + track_option: true + function_option: true + + context 'when tracking by event', -> + before -> + $fooEl = $('') + $fooEl.data 'atrackt-function', (data, options) -> + data['function_data'] = true + options['function_option'] = true + + $('body').append $fooEl + + Atrackt.plugins['foo-plugin'].setEvent + click: $fooEl + + $fooEl.trigger 'click' + + it 'should call the send method on plugins with data and options', -> + expect(pluginSpy).to.be.calledWithExactly + _location: 'Atrackt Test' + _categories: ['Anchor'] + _value: 'Foo' + _event: 'click' + global_data: true + plugin_data: true + function_data: true + , + global_option: true + plugin_option: true + function_option: true diff --git a/spec/atrackt_spec.js.coffee b/spec/atrackt_spec.js.coffee deleted file mode 100644 index df6d02c..0000000 --- a/spec/atrackt_spec.js.coffee +++ /dev/null @@ -1,480 +0,0 @@ -describe 'Atrackt', -> - it 'should set the Atrackt object on window', -> - expect(window.Atrackt).to.exist - - describe '#registerPlugin', -> - before -> - Atrackt.registerPlugin 'plugin', - send: -> - - after -> - Atrackt.plugins = {} - - it 'should add the plugin object to global plugins', -> - expect(Atrackt.plugins['plugin']).to.exist - - it 'should create a bind function', -> - expect(Atrackt.plugins['plugin'].bind).to.be.a 'function' - - it 'should create a unbind function', -> - expect(Atrackt.plugins['plugin'].unbind).to.be.a 'function' - - it 'should create a setOptions function', -> - expect(Atrackt.plugins['plugin'].setOptions).to.be.a 'function' - - it 'should create a setGlobalData function', -> - expect(Atrackt.plugins['plugin'].setGlobalData).to.be.a 'function' - - it 'should create a setCallback function', -> - expect(Atrackt.plugins['plugin'].setCallback).to.be.a 'function' - - context 'when registering an invalid plugin', -> - before -> - Atrackt.registerPlugin 'invalidPlugin' - - it 'should not add the plugin to the global plugins', -> - expect(Atrackt.plugins['invalidPlugin']).to.not.exist - - context 'with valid plugins registered', -> - # used for various tests - el = null - - # fooEl & barEl used for plugin related tests - fooEl = null - fooPlugin = null - fooSendSpy = null - fooBindSpy = null - - barEl = null - barPlugin = null - barSendSpy = null - barBindSpy = null - - before -> - Atrackt.plugins = {} - $('*').off 'atrackt' - - Atrackt.registerPlugin 'fooPlugin', - send: -> - Atrackt.registerPlugin 'barPlugin', - send: -> - - fooEl = $('') - barEl = $('') - fooPlugin = Atrackt.plugins['fooPlugin'] - barPlugin = Atrackt.plugins['barPlugin'] - fooSendSpy = sinon.spy fooPlugin, 'send' - barSendSpy = sinon.spy barPlugin, 'send' - fooBindSpy = sinon.spy fooPlugin, 'bind' - barBindSpy = sinon.spy barPlugin, 'bind' - - $('body').append(fooEl, barEl) - - afterEach -> - fooSendSpy.reset() - barSendSpy.reset() - fooBindSpy.reset() - barBindSpy.reset() - - after -> - Atrackt.plugins = {} - fooSendSpy.restore() - barSendSpy.restore() - fooBindSpy.restore() - barBindSpy.restore() - - describe '#bind', -> - context 'when binding a selector', -> - before -> - Atrackt.bind - click: [ 'a.test' ] - - it 'should call bind events on all plugins', -> - expect(fooBindSpy).to.be.called.once - expect(barBindSpy).to.be.called.once - - it 'should set event on include property', -> - expect(fooPlugin.includeSelectors.click).to.exist - expect(barPlugin.includeSelectors.click).to.exist - - it 'should bind events on all plugins', -> - expect($._data(fooEl[0]).events.click).to.have.length 2 - expect($._data(barEl[0]).events.click).to.have.length 2 - - context 'when called on the plugin', -> - before -> - fooPlugin.includeSelectors = {} - barPlugin.includeSelectors = {} - $('a.foo, a.bar').off '.atrackt' - - fooPlugin.bind - click: [ 'a.foo' ] - barPlugin.bind - hover: [ 'a.bar' ] - - it 'should set event on include property', -> - expect(fooPlugin.includeSelectors.click).to.exist - expect(barPlugin.includeSelectors.hover).to.exist - - it 'should bind events', -> - expect($._data(fooEl[0]).events.click).to.have.length 1 - expect($._data(barEl[0]).events.hover).to.have.length 1 - - it 'should track only events from the foo plugin', -> - $('a.test').trigger 'click' - - expect(fooSendSpy).to.have.been.calledOnce - expect(barSendSpy).to.not.have.been.called - - it 'should track only events from the bar plugin', -> - $('a.test').trigger 'hover' - - expect(fooSendSpy).to.not.be.called - expect(barSendSpy).to.be.called.once - - context 'when binding additional elements', -> - before -> - fooPlugin.bind - click: [ 'button' ] - - it 'should retain previous events', -> - expect(fooPlugin.includeSelectors.click).to.include 'a.foo' - expect(fooPlugin.includeSelectors.click).to.include 'button' - - context 'when binding a jquery object', -> - el = null - - before -> - fooPlugin.includeSelectors = {} - barPlugin.includeSelectors = {} - $('*').off '.atrackt' - - el = $('a.foo') - - Atrackt.bind - click: el - - it 'should add the element to the elements object', -> - expect(fooPlugin.includeElements.click).to.have.length 1 - expect(fooPlugin.includeElements.click).to.contain el - - it 'should bind the element', -> - expect($._data(el[0]).events.click).to.have.length 2 - - context 'when calling bind both types', -> - before -> - Atrackt.bind - click: [ 'a.foo' ] - - Atrackt.bind - click: $('') - - it 'should not unbind other elements from the plugin', -> - expect($._data(fooEl[0]).events.click).to.have.length 2 - - describe '#unbind', -> - beforeEach -> - fooPlugin.includeSelectors = {} - barPlugin.includeSelectors = {} - fooPlugin.excludeSelectors = {} - barPlugin.excludeSelectors = {} - $('*').off '.atrackt' - - Atrackt.plugins['fooPlugin'].bind - click: [ 'a.foo' ] - hover: [ 'a.foo' ] - Atrackt.plugins['barPlugin'].bind - click: [ 'a.bar' ] - hover: [ 'a.bar' ] - - context 'for all plugins', -> - beforeEach -> - Atrackt.unbind() - - it 'should clear the include property', -> - expect(fooPlugin.includeSelectors.click).to.not.exist - expect(fooPlugin.includeSelectors.hover).to.not.exist - expect(barPlugin.includeSelectors.click).to.not.exist - expect(barPlugin.includeSelectors.hover).to.not.exist - - it 'should unbind all events', -> - expect($._data(fooEl[0]).events?.click).to.not.exist - expect($._data(fooEl[0]).events?.hover).to.not.exist - expect($._data(barEl[0]).events?.click).to.not.exist - expect($._data(barEl[0]).events?.hover).to.not.exist - - context 'for all plugins with event object', -> - beforeEach -> - Atrackt.unbind - click: [ 'a.test' ] - - it 'should set the exclude property', -> - expect(fooPlugin.excludeSelectors.click).to.exist - expect(barPlugin.excludeSelectors.click).to.exist - - it 'should unbind specific events', -> - expect($._data(fooEl[0]).events.click).to.not.exist - expect($._data(fooEl[0]).events.hover).to.have.length 1 - expect($._data(barEl[0]).events.click).to.not.exist - expect($._data(barEl[0]).events.hover).to.have.length 1 - - context 'on a specific plugin with no event object', -> - beforeEach -> - Atrackt.plugins['fooPlugin'].unbind() - - it 'should clear the include property', -> - expect(fooPlugin.includeSelectors.click).to.not.exist - expect(fooPlugin.includeSelectors.hover).to.not.exist - expect(barPlugin.includeSelectors.click).to.exist - expect(barPlugin.includeSelectors.hover).to.exist - - it 'should unbind all events', -> - expect($._data(fooEl[0]).events?.click).to.not.exist - expect($._data(fooEl[0]).events?.hover).to.not.exist - expect($._data(barEl[0]).events.click).to.have.length 1 - expect($._data(barEl[0]).events.hover).to.have.length 1 - - context 'on a specific plugin with event object', -> - beforeEach -> - Atrackt.plugins['fooPlugin'].unbind - click: [ 'a.test' ] - - it 'should set the exclude property', -> - expect(fooPlugin.excludeSelectors.click).to.exist - expect(fooPlugin.excludeSelectors.hover).to.not.exist - expect(barPlugin.excludeSelectors.click).to.not.exist - expect(barPlugin.excludeSelectors.hover).to.not.exist - - it 'should unbind specific events', -> - expect($._data(fooEl[0]).events.click).to.not.exist - expect($._data(fooEl[0]).events.hover).to.have.length 1 - expect($._data(barEl[0]).events.click).to.have.length 1 - expect($._data(barEl[0]).events.hover).to.have.length 1 - - describe '#setOptions', -> - before -> - fooPlugin.setOptions - foo: 'bar' - - it 'should set custom options on the plugin', -> - expect(Atrackt.plugins['fooPlugin'].options.foo).to.equal 'bar' - - describe '#callbacks', -> - before -> - window._callbacks = [] - - Atrackt.registerPlugin 'callbacks', - send: (data, options) -> - window._callbacks.push - send: - data: data - options: options - - Atrackt.plugins['callbacks'].setCallback 'before', (data, options ,event) -> - window._callbacks.push - before: - data: data - options: options - event: event - - Atrackt.plugins['callbacks'].setCallback 'after', (data, options ,event) -> - window._callbacks.push - after: - data: data - options: options - event: event - - Atrackt.track - data: 'data' - , - option: 'option' - - it 'should call before callback', -> - expect(window._callbacks[0].before).to.exist - expect(window._callbacks[0].before.data).to.exist - expect(window._callbacks[0].before.options).to.exist - - it 'should call send', -> - expect(window._callbacks[1].send).to.exist - expect(window._callbacks[1].send.data).to.exist - expect(window._callbacks[1].send.options).to.exist - - it 'should call after callback', -> - expect(window._callbacks[2].after).to.exist - expect(window._callbacks[2].after.data).to.exist - expect(window._callbacks[2].after.options).to.exist - - describe '#globalData', -> - before -> - Atrackt.setGlobalData - globalFoo: 'foo' - - it 'should track add global data to the plugins', -> - expect(fooPlugin.globalData.globalFoo).to.equal 'foo' - - context 'when adding additional globalData', -> - before -> - Atrackt.setGlobalData - globalBar: 'bar' - - it 'should retain the previous data', -> - expect(fooPlugin.globalData.globalFoo).to.equal 'foo' - expect(fooPlugin.globalData.globalBar).to.equal 'bar' - - context 'when adding existing globalData', -> - before -> - Atrackt.setGlobalData - globalFoo: 'bar' - Atrackt.track $('') - - it 'should extend existing data', -> - expect(fooPlugin.globalData.globalFoo).to.equal 'bar' - expect(fooPlugin.globalData.globalBar).to.equal 'bar' - - describe '#track', -> - trackPluginSendSpy = null - - before -> - Atrackt.plugins = {} - Atrackt.registerPlugin 'track', - send: -> - trackPluginSendSpy = sinon.spy Atrackt.plugins['track'], 'send' - - after -> - trackPluginSendSpy.restore() - - afterEach -> - trackPluginSendSpy.reset() - - context 'with globalData', -> - before -> - Atrackt.setGlobalData - globalFoo: 'foo' - Atrackt.track $('a.foo') - - it 'should include the global data', -> - expect(trackPluginSendSpy.args[0][0].globalFoo).to.equal 'foo' - - context 'with options', -> - beforeEach -> - el = $('') - Atrackt.track el, - track: - foo: 'bar' - - it 'should call send with the track object', -> - expect(trackPluginSendSpy).to.be.called.once - expect(trackPluginSendSpy.args[0][1].foo).to.equal 'bar' - - it 'should add the plugin name to the options', -> - expect(trackPluginSendSpy.args[0][1].plugin).to.exist - - it 'should add the plugin name to the options', -> - expect(trackPluginSendSpy.args[0][1].el).to.equal el - - context 'with an HTML element', -> - beforeEach -> - el = $('')[0] - Atrackt.track el - - it 'should create the data-track-object on the element', -> - expect($(el).data('track-object').value).to.equal 'html' - - context 'with a jquery element', -> - beforeEach -> - el = $('') - Atrackt.track el - - it 'should create the data-track-object on the element', -> - expect($(el).data('track-object').value).to.equal 'jquery' - - it 'should call send with the track object', -> - expect(trackPluginSendSpy).to.be.called.once - expect(trackPluginSendSpy.args[0][0]).to.be.a 'object' - - context 'with a custom function', -> - beforeEach -> - el = $('') - el.data 'track-function', (data) -> - data.custom = true - Atrackt.track el - - it 'should add custom attribute', -> - expect(el.data('track-object').custom).to.be.true - - context 'with an object', -> - beforeEach -> - Atrackt.track - foo: 'bar' - - it 'should call send with the object', -> - expect(trackPluginSendSpy).to.be.called.once - expect(trackPluginSendSpy.args[0][0].foo).to.equal 'bar' - expect(trackPluginSendSpy.args[0][0].location).to.exist - - context 'with an event', -> - beforeEach -> - Atrackt.track - foo: 'bar' - , {}, - target: 'fooTarget' - - it 'should not contain event data in the tracking object', -> - expect(trackPluginSendSpy.args[1][0].target).to.not.exist - - describe '#refresh', -> - before -> - el = $('') - $(document.body).append(el) - Atrackt.bind - click: [ 'a.refresh' ] - Atrackt.refresh() - - it 'should bind default event to default elements on the dom', -> - expect($._data(el[0], 'events').click).to.exist - - describe '#_getLocation', -> - before -> - $('body').data 'track-location', 'foo' - - it 'should return a value', -> - expect(Atrackt._getLocation()).to.equal 'foo' - - describe '#_getCategories', -> - before -> - html = $('
') - el = html.find('a') - - it 'should return an array of categories', -> - expect(Atrackt._getCategories(el)).to.deep.equal [ 'three', 'two', 'one' ] - - describe '#_getValue', -> - before -> - el = $('') - - it 'should return a value', -> - expect(Atrackt._getValue(el)).to.equal 'foo' - -describe 'Debugging Console', -> - debugConsole = null - - before -> - Atrackt._debug = -> true - Atrackt._debugConsole() - debugConsole = $('#atrackt-debug') - - Atrackt.registerPlugin 'console', - send: -> - - Atrackt.bind - click: [ 'a.refresh' ] - - after -> - Atrackt._debugConsoleDestroy() - - it 'should add the console', -> - expect(debugConsole).to.exist - - it 'should add the element to the console', -> - expect(debugConsole.find('.atrackt-plugin-event').html()).to.contain('console : click') - expect(debugConsole.find('.atrackt-value').html()).to.contain('refresh') diff --git a/spec/index.html b/spec/index.html index 183237b..9963f3d 100644 --- a/spec/index.html +++ b/spec/index.html @@ -1,34 +1,44 @@ - - - - - - - - - - - - + + + + + + + + + + - - - - - - - - -
- + + + + + + + + + + + - diff --git a/spec/plugins/atrackt.localytics_spec.coffee b/spec/plugins/atrackt.localytics_spec.coffee new file mode 100644 index 0000000..f981a86 --- /dev/null +++ b/spec/plugins/atrackt.localytics_spec.coffee @@ -0,0 +1,84 @@ +describe 'Plugin: Localytics', -> + _plugin = Atrackt.plugins['localytics'] + + context 'in a UIWebView/HTML5 hybrid', -> + getRedirectUrlStub = null + redirectStub = null + + before -> + sinon.stub(_plugin, '_isUiWebView').returns true + getRedirectUrlStub = sinon.stub _plugin, '_getRedirectUrl' + redirectStub = sinon.stub _plugin, '_redirect' + + after -> + _plugin._isUiWebView.restore() + _plugin._getRedirectUrl.restore() + _plugin._redirect.restore() + + describe '#send', -> + before -> + _plugin._send + data: 'foo' + , + eventName: 'bar' + + it 'should call getRedirectUrl', -> + expect(getRedirectUrlStub).to.have.been.called.once + expect(getRedirectUrlStub).to.have.been.calledWithExactly + data: 'foo' + , + eventName: 'bar' + + it 'should call redirect', -> + expect(redirectStub).to.be.called.once + + context 'in HTML5', -> + callTagMethodStub = null + redirectStub = null + + before -> + sinon.stub(_plugin, '_isUiWebView').returns false + callTagMethodStub = sinon.stub _plugin, '_callTagMethod' + redirectStub = sinon.stub _plugin, '_redirect' + + after -> + _plugin._isUiWebView.restore() + _plugin._callTagMethod.restore() + _plugin._redirect.restore() + + describe '#send', -> + before -> + _plugin._send + data: 'foo' + , + eventName: 'bar' + + it 'should call callTagMethod', -> + expect(callTagMethodStub).to.be.called.once + expect(callTagMethodStub).to.be.calledWithExactly + data: 'foo' + , + eventName: 'bar' + + it 'should NOT call redirect', -> + expect(redirectStub).to.not.be.called + + describe '#_getRedirectUrl', -> + redirectUrl = null + + before -> + redirectUrl = _plugin._getRedirectUrl + foo: 'bar' + , + eventName: 'foo' + + it 'should create the custom url', -> + expect(redirectUrl).to.equal 'localytics://?event=foo&attributes={"foo":"bar"}' + + context 'when no object is tracked', -> + before -> + redirectUrl = _plugin._getRedirectUrl {}, + eventName: 'foo' + + it 'should not have attributes', -> + expect(redirectUrl).to.equal 'localytics://?event=foo' diff --git a/spec/plugins/atrackt.localytics_spec.js.coffee b/spec/plugins/atrackt.localytics_spec.js.coffee deleted file mode 100644 index ca594c7..0000000 --- a/spec/plugins/atrackt.localytics_spec.js.coffee +++ /dev/null @@ -1,95 +0,0 @@ -describe 'Plugin: Localytics', -> - plugin = null - isUiWebViewStub = null - - before -> - Atrackt.plugins = {} - loadJs 'lib/plugins/atrackt.localytics' - plugin = Atrackt.plugins['localytics'] - isUiWebViewStub = sinon.stub plugin, '_isUiWebView' - - after -> - plugin._isUiWebView.restore() - for event, selectorArray of plugin.events - $(selectorArray.join(',')).off event - - context 'in a UIWebView/HTML5 hybrid', -> - before -> - isUiWebViewStub.returns true - - describe '#send', -> - getRedirectUrlStub = null - redirectStub = null - - before -> - redirectStub = sinon.stub plugin, '_redirect' - getRedirectUrlStub = sinon.stub plugin, '_getRedirectUrl' - getRedirectUrlStub.returns 'foo://url' - - plugin.send - data: 'foo' - , - eventName: 'bar' - - after -> - plugin._redirect.restore() - plugin._getRedirectUrl.restore() - - it 'should call getRedirectUrl', -> - expect(getRedirectUrlStub).to.be.called.once - expect(getRedirectUrlStub).to.be.calledWithExactly - data: 'foo' - , - eventName: 'bar' - - it 'should call redirect', -> - expect(redirectStub).to.be.called.once - - describe '#_getRedirectUrl', -> - redirectUrl = null - - before -> - redirectUrl = plugin._getRedirectUrl - foo: 'bar' - , - eventName: 'foo' - - it 'should create the custom url', -> - expect(redirectUrl).to.equal 'localytics://?event=foo&attributes={"foo":"bar"}' - - context 'when no object is tracked', -> - before -> - redirectUrl = plugin._getRedirectUrl {}, - eventName: 'foo' - - it 'should not have attributes', -> - expect(redirectUrl).to.equal 'localytics://?event=foo' - - context 'in HTML5', -> - callTagMethodStub = null - redirectStub = null - - before -> - isUiWebViewStub.returns false - callTagMethodStub = sinon.stub plugin, '_callTagMethod' - redirectStub = sinon.stub plugin, '_redirect' - - plugin.send - data: 'foo' - , - eventName: 'bar' - - after -> - plugin._callTagMethod.restore() - plugin._redirect.restore() - - describe '#send', -> - it 'should call callTagMethod', -> - expect(callTagMethodStub).to.be.called.once - expect(callTagMethodStub).to.be.calledWithExactly - data: 'foo' - , - eventName: 'bar' - - it 'should NOT call redirect', -> - expect(redirectStub).to.not.be.called diff --git a/spec/plugins/atrackt.omniture_spec.js.coffee b/spec/plugins/atrackt.omniture_spec.coffee similarity index 62% rename from spec/plugins/atrackt.omniture_spec.js.coffee rename to spec/plugins/atrackt.omniture_spec.coffee index 9cc50db..9e5c8fd 100644 --- a/spec/plugins/atrackt.omniture_spec.js.coffee +++ b/spec/plugins/atrackt.omniture_spec.coffee @@ -1,21 +1,12 @@ describe 'Plugin: Omniture', -> - plugin = null - - before -> - Atrackt.plugins = {} - loadJs 'lib/plugins/atrackt.omniture' - plugin = Atrackt.plugins['omniture'] - - after -> - for event, selectorArray of plugin.events - $(selectorArray.join(',')).off event + _plugin = Atrackt.plugins['omniture'] describe '#buildSObject', -> before -> window.s = {} - plugin.options.version = 14 - plugin.options.linkTrackVars = ['foo'] - plugin.buildSObject + _plugin.options.version = 14 + _plugin.options.linkTrackVars = ['foo'] + _plugin.buildSObject bar: 'bar' it 'should add to the s object', -> @@ -28,10 +19,10 @@ describe 'Plugin: Omniture', -> obj = null before -> - plugin.options.propMap = + _plugin.options.propMap = foo: 'prop1' - obj = plugin.send + obj = _plugin._send foo: 'foo' bar: 'bar' , {} @@ -45,24 +36,24 @@ describe 'Plugin: Omniture', -> describe '#keyLookup', -> before -> - plugin.options.propMap = + _plugin.options.propMap = foo: 'bar' it 'should lookup from propMap', -> - expect(plugin.keyLookup('foo')).to.equal 'bar' + expect(_plugin.keyLookup('foo')).to.equal 'bar' describe '#buildLinkName', -> linkName = null before -> - plugin.options.delimiters = + _plugin.options.delimiters = linkName: '|' - plugin.options.propMap = - value: 'prop1' - location: 'prop2' - categories: 'prop3' + _plugin.options.propMap = + _value: 'prop1' + _location: 'prop2' + _categories: 'prop3' - linkName = plugin.buildLinkName + linkName = _plugin.buildLinkName prop1: 'baz' prop2: 'foo' prop3: 'bar' @@ -77,7 +68,7 @@ describe 'Plugin: Omniture', -> before -> pre = integer: 10 - post = plugin.translatePropMap pre + post = _plugin.translatePropMap pre it 'should handle any value type', -> expect(post['integer']).to.equal '10' diff --git a/spec/spec_helper.js.coffee b/spec/spec_helper.coffee similarity index 100% rename from spec/spec_helper.js.coffee rename to spec/spec_helper.coffee diff --git a/spec/vendor/chai.js b/spec/vendor/chai.js deleted file mode 100644 index a7f087c..0000000 --- a/spec/vendor/chai.js +++ /dev/null @@ -1,3765 +0,0 @@ -! -function(name, context, definition) { - if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') { - module.exports = definition(); - } else if (typeof define === 'function' && typeof define.amd === 'object') { - define(function() { - return definition(); - }); - } else { - context[name] = definition(); - } -}('chai', this, function() { - - function require(p) { - var path = require.resolve(p), - mod = require.modules[path]; - if (!mod) throw new Error('failed to require "' + p + '"'); - if (!mod.exports) { - mod.exports = {}; - mod.call(mod.exports, mod, mod.exports, require.relative(path)); - } - return mod.exports; - } - - require.modules = {}; - - require.resolve = function(path) { - var orig = path, - reg = path + '.js', - index = path + '/index.js'; - return require.modules[reg] && reg || require.modules[index] && index || orig; - }; - - require.register = function(path, fn) { - require.modules[path] = fn; - }; - - require.relative = function(parent) { - return function(p) { - if ('.' != p.charAt(0)) return require(p); - - var path = parent.split('/'), - segs = p.split('/'); - path.pop(); - - for (var i = 0; i < segs.length; i++) { - var seg = segs[i]; - if ('..' == seg) path.pop(); - else if ('.' != seg) path.push(seg); - } - - return require(path.join('/')); - }; - }; - - require.alias = function(from, to) { - var fn = require.modules[from]; - require.modules[to] = fn; - }; - - - require.register("chai.js", function(module, exports, require) { - /*! - * chai - * Copyright(c) 2011-2013 Jake Luer - * MIT Licensed - */ - - var used = [], - exports = module.exports = {}; - - /*! - * Chai version - */ - - exports.version = '1.5.0'; - - /*! - * Primary `Assertion` prototype - */ - - exports.Assertion = require('./chai/assertion'); - - /*! - * Assertion Error - */ - - exports.AssertionError = require('./chai/error'); - - /*! - * Utils for plugins (not exported) - */ - - var util = require('./chai/utils'); - - /** - * # .use(function) - * - * Provides a way to extend the internals of Chai - * - * @param {Function} - * @returns {this} for chaining - * @api public - */ - - exports.use = function(fn) { - if (!~used.indexOf(fn)) { - fn(this, util); - used.push(fn); - } - - return this; - }; - - /*! - * Core Assertions - */ - - var core = require('./chai/core/assertions'); - exports.use(core); - - /*! - * Expect interface - */ - - var expect = require('./chai/interface/expect'); - exports.use(expect); - - /*! - * Should interface - */ - - var should = require('./chai/interface/should'); - exports.use(should); - - /*! - * Assert interface - */ - - var assert = require('./chai/interface/assert'); - exports.use(assert); - - }); // module: chai.js - require.register("chai/assertion.js", function(module, exports, require) { - /*! - * chai - * http://chaijs.com - * Copyright(c) 2011-2013 Jake Luer - * MIT Licensed - */ - - /*! - * Module dependencies. - */ - - var AssertionError = require('./error'), - util = require('./utils'), - flag = util.flag; - - /*! - * Module export. - */ - - module.exports = Assertion; - - - /*! - * Assertion Constructor - * - * Creates object for chaining. - * - * @api private - */ - - function Assertion(obj, msg, stack) { - flag(this, 'ssfi', stack || arguments.callee); - flag(this, 'object', obj); - flag(this, 'message', msg); - } - - /*! - * ### Assertion.includeStack - * - * User configurable property, influences whether stack trace - * is included in Assertion error message. Default of false - * suppresses stack trace in the error message - * - * Assertion.includeStack = true; // enable stack on error - * - * @api public - */ - - Assertion.includeStack = false; - - /*! - * ### Assertion.showDiff - * - * User configurable property, influences whether or not - * the `showDiff` flag should be included in the thrown - * AssertionErrors. `false` will always be `false`; `true` - * will be true when the assertion has requested a diff - * be shown. - * - * @api public - */ - - Assertion.showDiff = true; - - Assertion.addProperty = function(name, fn) { - util.addProperty(this.prototype, name, fn); - }; - - Assertion.addMethod = function(name, fn) { - util.addMethod(this.prototype, name, fn); - }; - - Assertion.addChainableMethod = function(name, fn, chainingBehavior) { - util.addChainableMethod(this.prototype, name, fn, chainingBehavior); - }; - - Assertion.overwriteProperty = function(name, fn) { - util.overwriteProperty(this.prototype, name, fn); - }; - - Assertion.overwriteMethod = function(name, fn) { - util.overwriteMethod(this.prototype, name, fn); - }; - - /*! - * ### .assert(expression, message, negateMessage, expected, actual) - * - * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass. - * - * @name assert - * @param {Philosophical} expression to be tested - * @param {String} message to display if fails - * @param {String} negatedMessage to display if negated expression fails - * @param {Mixed} expected value (remember to check for negation) - * @param {Mixed} actual (optional) will default to `this.obj` - * @api private - */ - - Assertion.prototype.assert = function(expr, msg, negateMsg, expected, _actual, showDiff) { - var ok = util.test(this, arguments); - if (true !== showDiff) showDiff = false; - if (true !== Assertion.showDiff) showDiff = false; - - if (!ok) { - var msg = util.getMessage(this, arguments), - actual = util.getActual(this, arguments); - throw new AssertionError({ - message: msg, - actual: actual, - expected: expected, - stackStartFunction: (Assertion.includeStack) ? this.assert : flag(this, 'ssfi'), - showDiff: showDiff - }); - } - }; - - /*! - * ### ._obj - * - * Quick reference to stored `actual` value for plugin developers. - * - * @api private - */ - - Object.defineProperty(Assertion.prototype, '_obj', { - get: function() { - return flag(this, 'object'); - }, - set: function(val) { - flag(this, 'object', val); - } - }); - - }); // module: chai/assertion.js - require.register("chai/core/assertions.js", function(module, exports, require) { - /*! - * chai - * http://chaijs.com - * Copyright(c) 2011-2013 Jake Luer - * MIT Licensed - */ - - module.exports = function(chai, _) { - var Assertion = chai.Assertion, - toString = Object.prototype.toString, - flag = _.flag; - - /** - * ### Language Chains - * - * The following are provide as chainable getters to - * improve the readability of your assertions. They - * do not provide an testing capability unless they - * have been overwritten by a plugin. - * - * **Chains** - * - * - to - * - be - * - been - * - is - * - that - * - and - * - have - * - with - * - at - * - of - * - * @name language chains - * @api public - */ - - ['to', 'be', 'been', 'is', 'and', 'have', 'with', 'that', 'at', 'of'].forEach(function(chain) { - Assertion.addProperty(chain, function() { - return this; - }); - }); - - /** - * ### .not - * - * Negates any of assertions following in the chain. - * - * expect(foo).to.not.equal('bar'); - * expect(goodFn).to.not.throw(Error); - * expect({ foo: 'baz' }).to.have.property('foo') - * .and.not.equal('bar'); - * - * @name not - * @api public - */ - - Assertion.addProperty('not', function() { - flag(this, 'negate', true); - }); - - /** - * ### .deep - * - * Sets the `deep` flag, later used by the `equal` and - * `property` assertions. - * - * expect(foo).to.deep.equal({ bar: 'baz' }); - * expect({ foo: { bar: { baz: 'quux' } } }) - * .to.have.deep.property('foo.bar.baz', 'quux'); - * - * @name deep - * @api public - */ - - Assertion.addProperty('deep', function() { - flag(this, 'deep', true); - }); - - /** - * ### .a(type) - * - * The `a` and `an` assertions are aliases that can be - * used either as language chains or to assert a value's - * type. - * - * // typeof - * expect('test').to.be.a('string'); - * expect({ foo: 'bar' }).to.be.an('object'); - * expect(null).to.be.a('null'); - * expect(undefined).to.be.an('undefined'); - * - * // language chain - * expect(foo).to.be.an.instanceof(Foo); - * - * @name a - * @alias an - * @param {String} type - * @param {String} message _optional_ - * @api public - */ - - function an(type, msg) { - if (msg) flag(this, 'message', msg); - type = type.toLowerCase(); - var obj = flag(this, 'object'), - article = ~ ['a', 'e', 'i', 'o', 'u'].indexOf(type.charAt(0)) ? 'an ' : 'a '; - - this.assert( - type === _.type(obj), 'expected #{this} to be ' + article + type, 'expected #{this} not to be ' + article + type); - } - - Assertion.addChainableMethod('an', an); - Assertion.addChainableMethod('a', an); - - /** - * ### .include(value) - * - * The `include` and `contain` assertions can be used as either property - * based language chains or as methods to assert the inclusion of an object - * in an array or a substring in a string. When used as language chains, - * they toggle the `contain` flag for the `keys` assertion. - * - * expect([1,2,3]).to.include(2); - * expect('foobar').to.contain('foo'); - * expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo'); - * - * @name include - * @alias contain - * @param {Object|String|Number} obj - * @param {String} message _optional_ - * @api public - */ - - function includeChainingBehavior() { - flag(this, 'contains', true); - } - - function include(val, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object') - this.assert(~obj.indexOf(val), 'expected #{this} to include ' + _.inspect(val), 'expected #{this} to not include ' + _.inspect(val)); - } - - Assertion.addChainableMethod('include', include, includeChainingBehavior); - Assertion.addChainableMethod('contain', include, includeChainingBehavior); - - /** - * ### .ok - * - * Asserts that the target is truthy. - * - * expect('everthing').to.be.ok; - * expect(1).to.be.ok; - * expect(false).to.not.be.ok; - * expect(undefined).to.not.be.ok; - * expect(null).to.not.be.ok; - * - * @name ok - * @api public - */ - - Assertion.addProperty('ok', function() { - this.assert( - flag(this, 'object'), 'expected #{this} to be truthy', 'expected #{this} to be falsy'); - }); - - /** - * ### .true - * - * Asserts that the target is `true`. - * - * expect(true).to.be.true; - * expect(1).to.not.be.true; - * - * @name true - * @api public - */ - - Assertion.addProperty('true', function() { - this.assert( - true === flag(this, 'object'), 'expected #{this} to be true', 'expected #{this} to be false', this.negate ? false : true); - }); - - /** - * ### .false - * - * Asserts that the target is `false`. - * - * expect(false).to.be.false; - * expect(0).to.not.be.false; - * - * @name false - * @api public - */ - - Assertion.addProperty('false', function() { - this.assert( - false === flag(this, 'object'), 'expected #{this} to be false', 'expected #{this} to be true', this.negate ? true : false); - }); - - /** - * ### .null - * - * Asserts that the target is `null`. - * - * expect(null).to.be.null; - * expect(undefined).not.to.be.null; - * - * @name null - * @api public - */ - - Assertion.addProperty('null', function() { - this.assert( - null === flag(this, 'object'), 'expected #{this} to be null', 'expected #{this} not to be null'); - }); - - /** - * ### .undefined - * - * Asserts that the target is `undefined`. - * - * expect(undefined).to.be.undefined; - * expect(null).to.not.be.undefined; - * - * @name undefined - * @api public - */ - - Assertion.addProperty('undefined', function() { - this.assert( - undefined === flag(this, 'object'), 'expected #{this} to be undefined', 'expected #{this} not to be undefined'); - }); - - /** - * ### .exist - * - * Asserts that the target is neither `null` nor `undefined`. - * - * var foo = 'hi' - * , bar = null - * , baz; - * - * expect(foo).to.exist; - * expect(bar).to.not.exist; - * expect(baz).to.not.exist; - * - * @name exist - * @api public - */ - - Assertion.addProperty('exist', function() { - this.assert( - null != flag(this, 'object'), 'expected #{this} to exist', 'expected #{this} to not exist'); - }); - - - /** - * ### .empty - * - * Asserts that the target's length is `0`. For arrays, it checks - * the `length` property. For objects, it gets the count of - * enumerable keys. - * - * expect([]).to.be.empty; - * expect('').to.be.empty; - * expect({}).to.be.empty; - * - * @name empty - * @api public - */ - - Assertion.addProperty('empty', function() { - var obj = flag(this, 'object'), - expected = obj; - - if (Array.isArray(obj) || 'string' === typeof object) { - expected = obj.length; - } else if (typeof obj === 'object') { - expected = Object.keys(obj).length; - } - - this.assert(!expected, 'expected #{this} to be empty', 'expected #{this} not to be empty'); - }); - - /** - * ### .arguments - * - * Asserts that the target is an arguments object. - * - * function test () { - * expect(arguments).to.be.arguments; - * } - * - * @name arguments - * @alias Arguments - * @api public - */ - - function checkArguments() { - var obj = flag(this, 'object'), - type = Object.prototype.toString.call(obj); - this.assert('[object Arguments]' === type, 'expected #{this} to be arguments but got ' + type, 'expected #{this} to not be arguments'); - } - - Assertion.addProperty('arguments', checkArguments); - Assertion.addProperty('Arguments', checkArguments); - - /** - * ### .equal(value) - * - * Asserts that the target is strictly equal (`===`) to `value`. - * Alternately, if the `deep` flag is set, asserts that - * the target is deeply equal to `value`. - * - * expect('hello').to.equal('hello'); - * expect(42).to.equal(42); - * expect(1).to.not.equal(true); - * expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' }); - * expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' }); - * - * @name equal - * @alias equals - * @alias eq - * @alias deep.equal - * @param {Mixed} value - * @param {String} message _optional_ - * @api public - */ - - function assertEqual(val, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'deep')) { - return this.eql(val); - } else { - this.assert( - val === obj, 'expected #{this} to equal #{exp}', 'expected #{this} to not equal #{exp}', val, this._obj, true); - } - } - - Assertion.addMethod('equal', assertEqual); - Assertion.addMethod('equals', assertEqual); - Assertion.addMethod('eq', assertEqual); - - /** - * ### .eql(value) - * - * Asserts that the target is deeply equal to `value`. - * - * expect({ foo: 'bar' }).to.eql({ foo: 'bar' }); - * expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]); - * - * @name eql - * @alias eqls - * @param {Mixed} value - * @param {String} message _optional_ - * @api public - */ - - function assertEql(obj, msg) { - if (msg) flag(this, 'message', msg); - this.assert( - _.eql(obj, flag(this, 'object')), 'expected #{this} to deeply equal #{exp}', 'expected #{this} to not deeply equal #{exp}', obj, this._obj, true); - } - - Assertion.addMethod('eql', assertEql); - Assertion.addMethod('eqls', assertEql); - - /** - * ### .above(value) - * - * Asserts that the target is greater than `value`. - * - * expect(10).to.be.above(5); - * - * Can also be used in conjunction with `length` to - * assert a minimum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.above(2); - * expect([ 1, 2, 3 ]).to.have.length.above(2); - * - * @name above - * @alias gt - * @alias greaterThan - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertAbove(n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len > n, 'expected #{this} to have a length above #{exp} but got #{act}', 'expected #{this} to not have a length above #{exp}', n, len); - } else { - this.assert( - obj > n, 'expected #{this} to be above ' + n, 'expected #{this} to be at most ' + n); - } - } - - Assertion.addMethod('above', assertAbove); - Assertion.addMethod('gt', assertAbove); - Assertion.addMethod('greaterThan', assertAbove); - - /** - * ### .least(value) - * - * Asserts that the target is greater than or equal to `value`. - * - * expect(10).to.be.at.least(10); - * - * Can also be used in conjunction with `length` to - * assert a minimum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.of.at.least(2); - * expect([ 1, 2, 3 ]).to.have.length.of.at.least(3); - * - * @name least - * @alias gte - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertLeast(n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len >= n, 'expected #{this} to have a length at least #{exp} but got #{act}', 'expected #{this} to have a length below #{exp}', n, len); - } else { - this.assert( - obj >= n, 'expected #{this} to be at least ' + n, 'expected #{this} to be below ' + n); - } - } - - Assertion.addMethod('least', assertLeast); - Assertion.addMethod('gte', assertLeast); - - /** - * ### .below(value) - * - * Asserts that the target is less than `value`. - * - * expect(5).to.be.below(10); - * - * Can also be used in conjunction with `length` to - * assert a maximum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.below(4); - * expect([ 1, 2, 3 ]).to.have.length.below(4); - * - * @name below - * @alias lt - * @alias lessThan - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertBelow(n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len < n, 'expected #{this} to have a length below #{exp} but got #{act}', 'expected #{this} to not have a length below #{exp}', n, len); - } else { - this.assert( - obj < n, 'expected #{this} to be below ' + n, 'expected #{this} to be at least ' + n); - } - } - - Assertion.addMethod('below', assertBelow); - Assertion.addMethod('lt', assertBelow); - Assertion.addMethod('lessThan', assertBelow); - - /** - * ### .most(value) - * - * Asserts that the target is less than or equal to `value`. - * - * expect(5).to.be.at.most(5); - * - * Can also be used in conjunction with `length` to - * assert a maximum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.of.at.most(4); - * expect([ 1, 2, 3 ]).to.have.length.of.at.most(3); - * - * @name most - * @alias lte - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertMost(n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len <= n, 'expected #{this} to have a length at most #{exp} but got #{act}', 'expected #{this} to have a length above #{exp}', n, len); - } else { - this.assert( - obj <= n, 'expected #{this} to be at most ' + n, 'expected #{this} to be above ' + n); - } - } - - Assertion.addMethod('most', assertMost); - Assertion.addMethod('lte', assertMost); - - /** - * ### .within(start, finish) - * - * Asserts that the target is within a range. - * - * expect(7).to.be.within(5,10); - * - * Can also be used in conjunction with `length` to - * assert a length range. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.within(2,4); - * expect([ 1, 2, 3 ]).to.have.length.within(2,4); - * - * @name within - * @param {Number} start lowerbound inclusive - * @param {Number} finish upperbound inclusive - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('within', function(start, finish, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'), - range = start + '..' + finish; - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len >= start && len <= finish, 'expected #{this} to have a length within ' + range, 'expected #{this} to not have a length within ' + range); - } else { - this.assert( - obj >= start && obj <= finish, 'expected #{this} to be within ' + range, 'expected #{this} to not be within ' + range); - } - }); - - /** - * ### .instanceof(constructor) - * - * Asserts that the target is an instance of `constructor`. - * - * var Tea = function (name) { this.name = name; } - * , Chai = new Tea('chai'); - * - * expect(Chai).to.be.an.instanceof(Tea); - * expect([ 1, 2, 3 ]).to.be.instanceof(Array); - * - * @name instanceof - * @param {Constructor} constructor - * @param {String} message _optional_ - * @alias instanceOf - * @api public - */ - - function assertInstanceOf(constructor, msg) { - if (msg) flag(this, 'message', msg); - var name = _.getName(constructor); - this.assert( - flag(this, 'object') instanceof constructor, 'expected #{this} to be an instance of ' + name, 'expected #{this} to not be an instance of ' + name); - }; - - Assertion.addMethod('instanceof', assertInstanceOf); - Assertion.addMethod('instanceOf', assertInstanceOf); - - /** - * ### .property(name, [value]) - * - * Asserts that the target has a property `name`, optionally asserting that - * the value of that property is strictly equal to `value`. - * If the `deep` flag is set, you can use dot- and bracket-notation for deep - * references into objects and arrays. - * - * // simple referencing - * var obj = { foo: 'bar' }; - * expect(obj).to.have.property('foo'); - * expect(obj).to.have.property('foo', 'bar'); - * - * // deep referencing - * var deepObj = { - * green: { tea: 'matcha' } - * , teas: [ 'chai', 'matcha', { tea: 'konacha' } ] - * }; - - * expect(deepObj).to.have.deep.property('green.tea', 'matcha'); - * expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); - * expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); - * - * You can also use an array as the starting point of a `deep.property` - * assertion, or traverse nested arrays. - * - * var arr = [ - * [ 'chai', 'matcha', 'konacha' ] - * , [ { tea: 'chai' } - * , { tea: 'matcha' } - * , { tea: 'konacha' } ] - * ]; - * - * expect(arr).to.have.deep.property('[0][1]', 'matcha'); - * expect(arr).to.have.deep.property('[1][2].tea', 'konacha'); - * - * Furthermore, `property` changes the subject of the assertion - * to be the value of that property from the original object. This - * permits for further chainable assertions on that property. - * - * expect(obj).to.have.property('foo') - * .that.is.a('string'); - * expect(deepObj).to.have.property('green') - * .that.is.an('object') - * .that.deep.equals({ tea: 'matcha' }); - * expect(deepObj).to.have.property('teas') - * .that.is.an('array') - * .with.deep.property('[2]') - * .that.deep.equals({ tea: 'konacha' }); - * - * @name property - * @alias deep.property - * @param {String} name - * @param {Mixed} value (optional) - * @param {String} message _optional_ - * @returns value of property for chaining - * @api public - */ - - Assertion.addMethod('property', function(name, val, msg) { - if (msg) flag(this, 'message', msg); - - var descriptor = flag(this, 'deep') ? 'deep property ' : 'property ', - negate = flag(this, 'negate'), - obj = flag(this, 'object'), - value = flag(this, 'deep') ? _.getPathValue(name, obj) : obj[name]; - - if (negate && undefined !== val) { - if (undefined === value) { - msg = (msg != null) ? msg + ': ' : ''; - throw new Error(msg + _.inspect(obj) + ' has no ' + descriptor + _.inspect(name)); - } - } else { - this.assert( - undefined !== value, 'expected #{this} to have a ' + descriptor + _.inspect(name), 'expected #{this} to not have ' + descriptor + _.inspect(name)); - } - - if (undefined !== val) { - this.assert( - val === value, 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}', 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}', val, value); - } - - flag(this, 'object', value); - }); - - - /** - * ### .ownProperty(name) - * - * Asserts that the target has an own property `name`. - * - * expect('test').to.have.ownProperty('length'); - * - * @name ownProperty - * @alias haveOwnProperty - * @param {String} name - * @param {String} message _optional_ - * @api public - */ - - function assertOwnProperty(name, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - this.assert( - obj.hasOwnProperty(name), 'expected #{this} to have own property ' + _.inspect(name), 'expected #{this} to not have own property ' + _.inspect(name)); - } - - Assertion.addMethod('ownProperty', assertOwnProperty); - Assertion.addMethod('haveOwnProperty', assertOwnProperty); - - /** - * ### .length(value) - * - * Asserts that the target's `length` property has - * the expected value. - * - * expect([ 1, 2, 3]).to.have.length(3); - * expect('foobar').to.have.length(6); - * - * Can also be used as a chain precursor to a value - * comparison for the length property. - * - * expect('foo').to.have.length.above(2); - * expect([ 1, 2, 3 ]).to.have.length.above(2); - * expect('foo').to.have.length.below(4); - * expect([ 1, 2, 3 ]).to.have.length.below(4); - * expect('foo').to.have.length.within(2,4); - * expect([ 1, 2, 3 ]).to.have.length.within(2,4); - * - * @name length - * @alias lengthOf - * @param {Number} length - * @param {String} message _optional_ - * @api public - */ - - function assertLengthChain() { - flag(this, 'doLength', true); - } - - function assertLength(n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - - this.assert( - len == n, 'expected #{this} to have a length of #{exp} but got #{act}', 'expected #{this} to not have a length of #{act}', n, len); - } - - Assertion.addChainableMethod('length', assertLength, assertLengthChain); - Assertion.addMethod('lengthOf', assertLength, assertLengthChain); - - /** - * ### .match(regexp) - * - * Asserts that the target matches a regular expression. - * - * expect('foobar').to.match(/^foo/); - * - * @name match - * @param {RegExp} RegularExpression - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('match', function(re, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - this.assert( - re.exec(obj), 'expected #{this} to match ' + re, 'expected #{this} not to match ' + re); - }); - - /** - * ### .string(string) - * - * Asserts that the string target contains another string. - * - * expect('foobar').to.have.string('bar'); - * - * @name string - * @param {String} string - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('string', function(str, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).is.a('string'); - - this.assert(~obj.indexOf(str), 'expected #{this} to contain ' + _.inspect(str), 'expected #{this} to not contain ' + _.inspect(str)); - }); - - - /** - * ### .keys(key1, [key2], [...]) - * - * Asserts that the target has exactly the given keys, or - * asserts the inclusion of some keys when using the - * `include` or `contain` modifiers. - * - * expect({ foo: 1, bar: 2 }).to.have.keys(['foo', 'bar']); - * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.keys('foo', 'bar'); - * - * @name keys - * @alias key - * @param {String...|Array} keys - * @api public - */ - - function assertKeys(keys) { - var obj = flag(this, 'object'), - str, ok = true; - - keys = keys instanceof Array ? keys : Array.prototype.slice.call(arguments); - - if (!keys.length) throw new Error('keys required'); - - var actual = Object.keys(obj), - len = keys.length; - - // Inclusion - ok = keys.every(function(key) { - return ~actual.indexOf(key); - }); - - // Strict - if (!flag(this, 'negate') && !flag(this, 'contains')) { - ok = ok && keys.length == actual.length; - } - - // Key string - if (len > 1) { - keys = keys.map(function(key) { - return _.inspect(key); - }); - var last = keys.pop(); - str = keys.join(', ') + ', and ' + last; - } else { - str = _.inspect(keys[0]); - } - - // Form - str = (len > 1 ? 'keys ' : 'key ') + str; - - // Have / include - str = (flag(this, 'contains') ? 'contain ' : 'have ') + str; - - // Assertion - this.assert( - ok, 'expected #{this} to ' + str, 'expected #{this} to not ' + str); - } - - Assertion.addMethod('keys', assertKeys); - Assertion.addMethod('key', assertKeys); - - /** - * ### .throw(constructor) - * - * Asserts that the function target will throw a specific error, or specific type of error - * (as determined using `instanceof`), optionally with a RegExp or string inclusion test - * for the error's message. - * - * var err = new ReferenceError('This is a bad function.'); - * var fn = function () { throw err; } - * expect(fn).to.throw(ReferenceError); - * expect(fn).to.throw(Error); - * expect(fn).to.throw(/bad function/); - * expect(fn).to.not.throw('good function'); - * expect(fn).to.throw(ReferenceError, /bad function/); - * expect(fn).to.throw(err); - * expect(fn).to.not.throw(new RangeError('Out of range.')); - * - * Please note that when a throw expectation is negated, it will check each - * parameter independently, starting with error constructor type. The appropriate way - * to check for the existence of a type of error but for a message that does not match - * is to use `and`. - * - * expect(fn).to.throw(ReferenceError) - * .and.not.throw(/good function/); - * - * @name throw - * @alias throws - * @alias Throw - * @param {ErrorConstructor} constructor - * @param {String|RegExp} expected error message - * @param {String} message _optional_ - * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types - * @api public - */ - - function assertThrows(constructor, errMsg, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).is.a('function'); - - var thrown = false, - desiredError = null, - name = null, - thrownError = null; - - if (arguments.length === 0) { - errMsg = null; - constructor = null; - } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) { - errMsg = constructor; - constructor = null; - } else if (constructor && constructor instanceof Error) { - desiredError = constructor; - constructor = null; - errMsg = null; - } else if (typeof constructor === 'function') { - name = (new constructor()).name; - } else { - constructor = null; - } - - try { - obj(); - } catch (err) { - // first, check desired error - if (desiredError) { - this.assert( - err === desiredError, 'expected #{this} to throw #{exp} but #{act} was thrown', 'expected #{this} to not throw #{exp}', desiredError, err); - - return this; - } - // next, check constructor - if (constructor) { - this.assert( - err instanceof constructor, 'expected #{this} to throw #{exp} but #{act} was thrown', 'expected #{this} to not throw #{exp} but #{act} was thrown', name, err); - - if (!errMsg) return this; - } - // next, check message - var message = 'object' === _.type(err) && "message" in err ? err.message : '' + err; - - if ((message != null) && errMsg && errMsg instanceof RegExp) { - this.assert( - errMsg.exec(message), 'expected #{this} to throw error matching #{exp} but got #{act}', 'expected #{this} to throw error not matching #{exp}', errMsg, message); - - return this; - } else if ((message != null) && errMsg && 'string' === typeof errMsg) { - this.assert(~message.indexOf(errMsg), 'expected #{this} to throw error including #{exp} but got #{act}', 'expected #{this} to throw error not including #{act}', errMsg, message); - - return this; - } else { - thrown = true; - thrownError = err; - } - } - - var actuallyGot = '', - expectedThrown = name !== null ? name : desiredError ? '#{exp}' //_.inspect(desiredError) - : - 'an error'; - - if (thrown) { - actuallyGot = ' but #{act} was thrown' - } - - this.assert( - thrown === true, 'expected #{this} to throw ' + expectedThrown + actuallyGot, 'expected #{this} to not throw ' + expectedThrown + actuallyGot, desiredError, thrownError); - }; - - Assertion.addMethod('throw', assertThrows); - Assertion.addMethod('throws', assertThrows); - Assertion.addMethod('Throw', assertThrows); - - /** - * ### .respondTo(method) - * - * Asserts that the object or class target will respond to a method. - * - * Klass.prototype.bar = function(){}; - * expect(Klass).to.respondTo('bar'); - * expect(obj).to.respondTo('bar'); - * - * To check if a constructor will respond to a static function, - * set the `itself` flag. - * - * Klass.baz = function(){}; - * expect(Klass).itself.to.respondTo('baz'); - * - * @name respondTo - * @param {String} method - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('respondTo', function(method, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'), - itself = flag(this, 'itself'), - context = ('function' === _.type(obj) && !itself) ? obj.prototype[method] : obj[method]; - - this.assert('function' === typeof context, 'expected #{this} to respond to ' + _.inspect(method), 'expected #{this} to not respond to ' + _.inspect(method)); - }); - - /** - * ### .itself - * - * Sets the `itself` flag, later used by the `respondTo` assertion. - * - * function Foo() {} - * Foo.bar = function() {} - * Foo.prototype.baz = function() {} - * - * expect(Foo).itself.to.respondTo('bar'); - * expect(Foo).itself.not.to.respondTo('baz'); - * - * @name itself - * @api public - */ - - Assertion.addProperty('itself', function() { - flag(this, 'itself', true); - }); - - /** - * ### .satisfy(method) - * - * Asserts that the target passes a given truth test. - * - * expect(1).to.satisfy(function(num) { return num > 0; }); - * - * @name satisfy - * @param {Function} matcher - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('satisfy', function(matcher, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - this.assert( - matcher(obj), 'expected #{this} to satisfy ' + _.objDisplay(matcher), 'expected #{this} to not satisfy' + _.objDisplay(matcher), this.negate ? false : true, matcher(obj)); - }); - - /** - * ### .closeTo(expected, delta) - * - * Asserts that the target is equal `expected`, to within a +/- `delta` range. - * - * expect(1.5).to.be.closeTo(1, 0.5); - * - * @name closeTo - * @param {Number} expected - * @param {Number} delta - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('closeTo', function(expected, delta, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - this.assert( - Math.abs(obj - expected) <= delta, 'expected #{this} to be close to ' + expected + ' +/- ' + delta, 'expected #{this} not to be close to ' + expected + ' +/- ' + delta); - }); - - }; - - }); // module: chai/core/assertions.js - require.register("chai/error.js", function(module, exports, require) { - /*! - * chai - * Copyright(c) 2011-2013 Jake Luer - * MIT Licensed - */ - - /*! - * Main export - */ - - module.exports = AssertionError; - - /** - * # AssertionError (constructor) - * - * Create a new assertion error based on the Javascript - * `Error` prototype. - * - * **Options** - * - message - * - actual - * - expected - * - operator - * - startStackFunction - * - * @param {Object} options - * @api public - */ - - function AssertionError(options) { - options = options || {}; - this.message = options.message; - this.actual = options.actual; - this.expected = options.expected; - this.operator = options.operator; - this.showDiff = options.showDiff; - - if (options.stackStartFunction && Error.captureStackTrace) { - var stackStartFunction = options.stackStartFunction; - Error.captureStackTrace(this, stackStartFunction); - } - } - - /*! - * Inherit from Error - */ - - AssertionError.prototype = Object.create(Error.prototype); - AssertionError.prototype.name = 'AssertionError'; - AssertionError.prototype.constructor = AssertionError; - - /** - * # toString() - * - * Override default to string method - */ - - AssertionError.prototype.toString = function() { - return this.message; - }; - - }); // module: chai/error.js - require.register("chai/interface/assert.js", function(module, exports, require) { - /*! - * chai - * Copyright(c) 2011-2013 Jake Luer - * MIT Licensed - */ - - - module.exports = function(chai, util) { - - /*! - * Chai dependencies. - */ - - var Assertion = chai.Assertion, - flag = util.flag; - - /*! - * Module export. - */ - - /** - * ### assert(expression, message) - * - * Write your own test expressions. - * - * assert('foo' !== 'bar', 'foo is not bar'); - * assert(Array.isArray([]), 'empty arrays are arrays'); - * - * @param {Mixed} expression to test for truthiness - * @param {String} message to display on error - * @name assert - * @api public - */ - - var assert = chai.assert = function(express, errmsg) { - var test = new Assertion(null); - test.assert( - express, errmsg, '[ negation message unavailable ]'); - }; - - /** - * ### .fail(actual, expected, [message], [operator]) - * - * Throw a failure. Node.js `assert` module-compatible. - * - * @name fail - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @param {String} operator - * @api public - */ - - assert.fail = function(actual, expected, message, operator) { - throw new chai.AssertionError({ - actual: actual, - expected: expected, - message: message, - operator: operator, - stackStartFunction: assert.fail - }); - }; - - /** - * ### .ok(object, [message]) - * - * Asserts that `object` is truthy. - * - * assert.ok('everything', 'everything is ok'); - * assert.ok(false, 'this will fail'); - * - * @name ok - * @param {Mixed} object to test - * @param {String} message - * @api public - */ - - assert.ok = function(val, msg) { - new Assertion(val, msg).is.ok; - }; - - /** - * ### .equal(actual, expected, [message]) - * - * Asserts non-strict equality (`==`) of `actual` and `expected`. - * - * assert.equal(3, '3', '== coerces values to strings'); - * - * @name equal - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.equal = function(act, exp, msg) { - var test = new Assertion(act, msg); - - test.assert( - exp == flag(test, 'object'), 'expected #{this} to equal #{exp}', 'expected #{this} to not equal #{act}', exp, act); - }; - - /** - * ### .notEqual(actual, expected, [message]) - * - * Asserts non-strict inequality (`!=`) of `actual` and `expected`. - * - * assert.notEqual(3, 4, 'these numbers are not equal'); - * - * @name notEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.notEqual = function(act, exp, msg) { - var test = new Assertion(act, msg); - - test.assert( - exp != flag(test, 'object'), 'expected #{this} to not equal #{exp}', 'expected #{this} to equal #{act}', exp, act); - }; - - /** - * ### .strictEqual(actual, expected, [message]) - * - * Asserts strict equality (`===`) of `actual` and `expected`. - * - * assert.strictEqual(true, true, 'these booleans are strictly equal'); - * - * @name strictEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.strictEqual = function(act, exp, msg) { - new Assertion(act, msg).to.equal(exp); - }; - - /** - * ### .notStrictEqual(actual, expected, [message]) - * - * Asserts strict inequality (`!==`) of `actual` and `expected`. - * - * assert.notStrictEqual(3, '3', 'no coercion for strict equality'); - * - * @name notStrictEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.notStrictEqual = function(act, exp, msg) { - new Assertion(act, msg).to.not.equal(exp); - }; - - /** - * ### .deepEqual(actual, expected, [message]) - * - * Asserts that `actual` is deeply equal to `expected`. - * - * assert.deepEqual({ tea: 'green' }, { tea: 'green' }); - * - * @name deepEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.deepEqual = function(act, exp, msg) { - new Assertion(act, msg).to.eql(exp); - }; - - /** - * ### .notDeepEqual(actual, expected, [message]) - * - * Assert that `actual` is not deeply equal to `expected`. - * - * assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' }); - * - * @name notDeepEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.notDeepEqual = function(act, exp, msg) { - new Assertion(act, msg).to.not.eql(exp); - }; - - /** - * ### .isTrue(value, [message]) - * - * Asserts that `value` is true. - * - * var teaServed = true; - * assert.isTrue(teaServed, 'the tea has been served'); - * - * @name isTrue - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isTrue = function(val, msg) { - new Assertion(val, msg).is['true']; - }; - - /** - * ### .isFalse(value, [message]) - * - * Asserts that `value` is false. - * - * var teaServed = false; - * assert.isFalse(teaServed, 'no tea yet? hmm...'); - * - * @name isFalse - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isFalse = function(val, msg) { - new Assertion(val, msg).is['false']; - }; - - /** - * ### .isNull(value, [message]) - * - * Asserts that `value` is null. - * - * assert.isNull(err, 'there was no error'); - * - * @name isNull - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNull = function(val, msg) { - new Assertion(val, msg).to.equal(null); - }; - - /** - * ### .isNotNull(value, [message]) - * - * Asserts that `value` is not null. - * - * var tea = 'tasty chai'; - * assert.isNotNull(tea, 'great, time for tea!'); - * - * @name isNotNull - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotNull = function(val, msg) { - new Assertion(val, msg).to.not.equal(null); - }; - - /** - * ### .isUndefined(value, [message]) - * - * Asserts that `value` is `undefined`. - * - * var tea; - * assert.isUndefined(tea, 'no tea defined'); - * - * @name isUndefined - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isUndefined = function(val, msg) { - new Assertion(val, msg).to.equal(undefined); - }; - - /** - * ### .isDefined(value, [message]) - * - * Asserts that `value` is not `undefined`. - * - * var tea = 'cup of chai'; - * assert.isDefined(tea, 'tea has been defined'); - * - * @name isUndefined - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isDefined = function(val, msg) { - new Assertion(val, msg).to.not.equal(undefined); - }; - - /** - * ### .isFunction(value, [message]) - * - * Asserts that `value` is a function. - * - * function serveTea() { return 'cup of tea'; }; - * assert.isFunction(serveTea, 'great, we can have tea now'); - * - * @name isFunction - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isFunction = function(val, msg) { - new Assertion(val, msg).to.be.a('function'); - }; - - /** - * ### .isNotFunction(value, [message]) - * - * Asserts that `value` is _not_ a function. - * - * var serveTea = [ 'heat', 'pour', 'sip' ]; - * assert.isNotFunction(serveTea, 'great, we have listed the steps'); - * - * @name isNotFunction - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotFunction = function(val, msg) { - new Assertion(val, msg).to.not.be.a('function'); - }; - - /** - * ### .isObject(value, [message]) - * - * Asserts that `value` is an object (as revealed by - * `Object.prototype.toString`). - * - * var selection = { name: 'Chai', serve: 'with spices' }; - * assert.isObject(selection, 'tea selection is an object'); - * - * @name isObject - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isObject = function(val, msg) { - new Assertion(val, msg).to.be.a('object'); - }; - - /** - * ### .isNotObject(value, [message]) - * - * Asserts that `value` is _not_ an object. - * - * var selection = 'chai' - * assert.isObject(selection, 'tea selection is not an object'); - * assert.isObject(null, 'null is not an object'); - * - * @name isNotObject - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotObject = function(val, msg) { - new Assertion(val, msg).to.not.be.a('object'); - }; - - /** - * ### .isArray(value, [message]) - * - * Asserts that `value` is an array. - * - * var menu = [ 'green', 'chai', 'oolong' ]; - * assert.isArray(menu, 'what kind of tea do we want?'); - * - * @name isArray - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isArray = function(val, msg) { - new Assertion(val, msg).to.be.an('array'); - }; - - /** - * ### .isNotArray(value, [message]) - * - * Asserts that `value` is _not_ an array. - * - * var menu = 'green|chai|oolong'; - * assert.isNotArray(menu, 'what kind of tea do we want?'); - * - * @name isNotArray - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotArray = function(val, msg) { - new Assertion(val, msg).to.not.be.an('array'); - }; - - /** - * ### .isString(value, [message]) - * - * Asserts that `value` is a string. - * - * var teaOrder = 'chai'; - * assert.isString(teaOrder, 'order placed'); - * - * @name isString - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isString = function(val, msg) { - new Assertion(val, msg).to.be.a('string'); - }; - - /** - * ### .isNotString(value, [message]) - * - * Asserts that `value` is _not_ a string. - * - * var teaOrder = 4; - * assert.isNotString(teaOrder, 'order placed'); - * - * @name isNotString - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotString = function(val, msg) { - new Assertion(val, msg).to.not.be.a('string'); - }; - - /** - * ### .isNumber(value, [message]) - * - * Asserts that `value` is a number. - * - * var cups = 2; - * assert.isNumber(cups, 'how many cups'); - * - * @name isNumber - * @param {Number} value - * @param {String} message - * @api public - */ - - assert.isNumber = function(val, msg) { - new Assertion(val, msg).to.be.a('number'); - }; - - /** - * ### .isNotNumber(value, [message]) - * - * Asserts that `value` is _not_ a number. - * - * var cups = '2 cups please'; - * assert.isNotNumber(cups, 'how many cups'); - * - * @name isNotNumber - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotNumber = function(val, msg) { - new Assertion(val, msg).to.not.be.a('number'); - }; - - /** - * ### .isBoolean(value, [message]) - * - * Asserts that `value` is a boolean. - * - * var teaReady = true - * , teaServed = false; - * - * assert.isBoolean(teaReady, 'is the tea ready'); - * assert.isBoolean(teaServed, 'has tea been served'); - * - * @name isBoolean - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isBoolean = function(val, msg) { - new Assertion(val, msg).to.be.a('boolean'); - }; - - /** - * ### .isNotBoolean(value, [message]) - * - * Asserts that `value` is _not_ a boolean. - * - * var teaReady = 'yep' - * , teaServed = 'nope'; - * - * assert.isNotBoolean(teaReady, 'is the tea ready'); - * assert.isNotBoolean(teaServed, 'has tea been served'); - * - * @name isNotBoolean - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotBoolean = function(val, msg) { - new Assertion(val, msg).to.not.be.a('boolean'); - }; - - /** - * ### .typeOf(value, name, [message]) - * - * Asserts that `value`'s type is `name`, as determined by - * `Object.prototype.toString`. - * - * assert.typeOf({ tea: 'chai' }, 'object', 'we have an object'); - * assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array'); - * assert.typeOf('tea', 'string', 'we have a string'); - * assert.typeOf(/tea/, 'regexp', 'we have a regular expression'); - * assert.typeOf(null, 'null', 'we have a null'); - * assert.typeOf(undefined, 'undefined', 'we have an undefined'); - * - * @name typeOf - * @param {Mixed} value - * @param {String} name - * @param {String} message - * @api public - */ - - assert.typeOf = function(val, type, msg) { - new Assertion(val, msg).to.be.a(type); - }; - - /** - * ### .notTypeOf(value, name, [message]) - * - * Asserts that `value`'s type is _not_ `name`, as determined by - * `Object.prototype.toString`. - * - * assert.notTypeOf('tea', 'number', 'strings are not numbers'); - * - * @name notTypeOf - * @param {Mixed} value - * @param {String} typeof name - * @param {String} message - * @api public - */ - - assert.notTypeOf = function(val, type, msg) { - new Assertion(val, msg).to.not.be.a(type); - }; - - /** - * ### .instanceOf(object, constructor, [message]) - * - * Asserts that `value` is an instance of `constructor`. - * - * var Tea = function (name) { this.name = name; } - * , chai = new Tea('chai'); - * - * assert.instanceOf(chai, Tea, 'chai is an instance of tea'); - * - * @name instanceOf - * @param {Object} object - * @param {Constructor} constructor - * @param {String} message - * @api public - */ - - assert.instanceOf = function(val, type, msg) { - new Assertion(val, msg).to.be.instanceOf(type); - }; - - /** - * ### .notInstanceOf(object, constructor, [message]) - * - * Asserts `value` is not an instance of `constructor`. - * - * var Tea = function (name) { this.name = name; } - * , chai = new String('chai'); - * - * assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea'); - * - * @name notInstanceOf - * @param {Object} object - * @param {Constructor} constructor - * @param {String} message - * @api public - */ - - assert.notInstanceOf = function(val, type, msg) { - new Assertion(val, msg).to.not.be.instanceOf(type); - }; - - /** - * ### .include(haystack, needle, [message]) - * - * Asserts that `haystack` includes `needle`. Works - * for strings and arrays. - * - * assert.include('foobar', 'bar', 'foobar contains string "bar"'); - * assert.include([ 1, 2, 3 ], 3, 'array contains value'); - * - * @name include - * @param {Array|String} haystack - * @param {Mixed} needle - * @param {String} message - * @api public - */ - - assert.include = function(exp, inc, msg) { - var obj = new Assertion(exp, msg); - - if (Array.isArray(exp)) { - obj.to.include(inc); - } else if ('string' === typeof exp) { - obj.to.contain.string(inc); - } - }; - - /** - * ### .match(value, regexp, [message]) - * - * Asserts that `value` matches the regular expression `regexp`. - * - * assert.match('foobar', /^foo/, 'regexp matches'); - * - * @name match - * @param {Mixed} value - * @param {RegExp} regexp - * @param {String} message - * @api public - */ - - assert.match = function(exp, re, msg) { - new Assertion(exp, msg).to.match(re); - }; - - /** - * ### .notMatch(value, regexp, [message]) - * - * Asserts that `value` does not match the regular expression `regexp`. - * - * assert.notMatch('foobar', /^foo/, 'regexp does not match'); - * - * @name notMatch - * @param {Mixed} value - * @param {RegExp} regexp - * @param {String} message - * @api public - */ - - assert.notMatch = function(exp, re, msg) { - new Assertion(exp, msg).to.not.match(re); - }; - - /** - * ### .property(object, property, [message]) - * - * Asserts that `object` has a property named by `property`. - * - * assert.property({ tea: { green: 'matcha' }}, 'tea'); - * - * @name property - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.property = function(obj, prop, msg) { - new Assertion(obj, msg).to.have.property(prop); - }; - - /** - * ### .notProperty(object, property, [message]) - * - * Asserts that `object` does _not_ have a property named by `property`. - * - * assert.notProperty({ tea: { green: 'matcha' }}, 'coffee'); - * - * @name notProperty - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.notProperty = function(obj, prop, msg) { - new Assertion(obj, msg).to.not.have.property(prop); - }; - - /** - * ### .deepProperty(object, property, [message]) - * - * Asserts that `object` has a property named by `property`, which can be a - * string using dot- and bracket-notation for deep reference. - * - * assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green'); - * - * @name deepProperty - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.deepProperty = function(obj, prop, msg) { - new Assertion(obj, msg).to.have.deep.property(prop); - }; - - /** - * ### .notDeepProperty(object, property, [message]) - * - * Asserts that `object` does _not_ have a property named by `property`, which - * can be a string using dot- and bracket-notation for deep reference. - * - * assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); - * - * @name notDeepProperty - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.notDeepProperty = function(obj, prop, msg) { - new Assertion(obj, msg).to.not.have.deep.property(prop); - }; - - /** - * ### .propertyVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property` with value given - * by `value`. - * - * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good'); - * - * @name propertyVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.propertyVal = function(obj, prop, val, msg) { - new Assertion(obj, msg).to.have.property(prop, val); - }; - - /** - * ### .propertyNotVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property`, but with a value - * different from that given by `value`. - * - * assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad'); - * - * @name propertyNotVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.propertyNotVal = function(obj, prop, val, msg) { - new Assertion(obj, msg).to.not.have.property(prop, val); - }; - - /** - * ### .deepPropertyVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property` with value given - * by `value`. `property` can use dot- and bracket-notation for deep - * reference. - * - * assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); - * - * @name deepPropertyVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.deepPropertyVal = function(obj, prop, val, msg) { - new Assertion(obj, msg).to.have.deep.property(prop, val); - }; - - /** - * ### .deepPropertyNotVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property`, but with a value - * different from that given by `value`. `property` can use dot- and - * bracket-notation for deep reference. - * - * assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); - * - * @name deepPropertyNotVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.deepPropertyNotVal = function(obj, prop, val, msg) { - new Assertion(obj, msg).to.not.have.deep.property(prop, val); - }; - - /** - * ### .lengthOf(object, length, [message]) - * - * Asserts that `object` has a `length` property with the expected value. - * - * assert.lengthOf([1,2,3], 3, 'array has length of 3'); - * assert.lengthOf('foobar', 5, 'string has length of 6'); - * - * @name lengthOf - * @param {Mixed} object - * @param {Number} length - * @param {String} message - * @api public - */ - - assert.lengthOf = function(exp, len, msg) { - new Assertion(exp, msg).to.have.length(len); - }; - - /** - * ### .throws(function, [constructor/string/regexp], [string/regexp], [message]) - * - * Asserts that `function` will throw an error that is an instance of - * `constructor`, or alternately that it will throw an error with message - * matching `regexp`. - * - * assert.throw(fn, 'function throws a reference error'); - * assert.throw(fn, /function throws a reference error/); - * assert.throw(fn, ReferenceError); - * assert.throw(fn, ReferenceError, 'function throws a reference error'); - * assert.throw(fn, ReferenceError, /function throws a reference error/); - * - * @name throws - * @alias throw - * @alias Throw - * @param {Function} function - * @param {ErrorConstructor} constructor - * @param {RegExp} regexp - * @param {String} message - * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types - * @api public - */ - - assert.Throw = function(fn, errt, errs, msg) { - if ('string' === typeof errt || errt instanceof RegExp) { - errs = errt; - errt = null; - } - - new Assertion(fn, msg).to.Throw(errt, errs); - }; - - /** - * ### .doesNotThrow(function, [constructor/regexp], [message]) - * - * Asserts that `function` will _not_ throw an error that is an instance of - * `constructor`, or alternately that it will not throw an error with message - * matching `regexp`. - * - * assert.doesNotThrow(fn, Error, 'function does not throw'); - * - * @name doesNotThrow - * @param {Function} function - * @param {ErrorConstructor} constructor - * @param {RegExp} regexp - * @param {String} message - * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types - * @api public - */ - - assert.doesNotThrow = function(fn, type, msg) { - if ('string' === typeof type) { - msg = type; - type = null; - } - - new Assertion(fn, msg).to.not.Throw(type); - }; - - /** - * ### .operator(val1, operator, val2, [message]) - * - * Compares two values using `operator`. - * - * assert.operator(1, '<', 2, 'everything is ok'); - * assert.operator(1, '>', 2, 'this will fail'); - * - * @name operator - * @param {Mixed} val1 - * @param {String} operator - * @param {Mixed} val2 - * @param {String} message - * @api public - */ - - assert.operator = function(val, operator, val2, msg) { - if (!~ ['==', '===', '>', '>=', '<', '<=', '!=', '!=='].indexOf(operator)) { - throw new Error('Invalid operator "' + operator + '"'); - } - var test = new Assertion(eval(val + operator + val2), msg); - test.assert( - true === flag(test, 'object'), 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2), 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2)); - }; - - /** - * ### .closeTo(actual, expected, delta, [message]) - * - * Asserts that the target is equal `expected`, to within a +/- `delta` range. - * - * assert.closeTo(1.5, 1, 0.5, 'numbers are close'); - * - * @name closeTo - * @param {Number} actual - * @param {Number} expected - * @param {Number} delta - * @param {String} message - * @api public - */ - - assert.closeTo = function(act, exp, delta, msg) { - new Assertion(act, msg).to.be.closeTo(exp, delta); - }; - - /*! - * Undocumented / untested - */ - - assert.ifError = function(val, msg) { - new Assertion(val, msg).to.not.be.ok; - }; - - /*! - * Aliases. - */ - - (function alias(name, as) { - assert[as] = assert[name]; - return alias; - }) - ('Throw', 'throw') - ('Throw', 'throws'); - }; - - }); // module: chai/interface/assert.js - require.register("chai/interface/expect.js", function(module, exports, require) { - /*! - * chai - * Copyright(c) 2011-2013 Jake Luer - * MIT Licensed - */ - - module.exports = function(chai, util) { - chai.expect = function(val, message) { - return new chai.Assertion(val, message); - }; - }; - - - }); // module: chai/interface/expect.js - require.register("chai/interface/should.js", function(module, exports, require) { - /*! - * chai - * Copyright(c) 2011-2013 Jake Luer - * MIT Licensed - */ - - module.exports = function(chai, util) { - var Assertion = chai.Assertion; - - function loadShould() { - // modify Object.prototype to have `should` - Object.defineProperty(Object.prototype, 'should', { - set: function(value) { - // See https://github.com/chaijs/chai/issues/86: this makes - // `whatever.should = someValue` actually set `someValue`, which is - // especially useful for `global.should = require('chai').should()`. - // - // Note that we have to use [[DefineProperty]] instead of [[Put]] - // since otherwise we would trigger this very setter! - Object.defineProperty(this, 'should', { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - }, - get: function() { - if (this instanceof String || this instanceof Number) { - return new Assertion(this.constructor(this)); - } else if (this instanceof Boolean) { - return new Assertion(this == true); - } - return new Assertion(this); - }, - configurable: true - }); - - var should = {}; - - should.equal = function(val1, val2, msg) { - new Assertion(val1, msg).to.equal(val2); - }; - - should.Throw = function(fn, errt, errs, msg) { - new Assertion(fn, msg).to.Throw(errt, errs); - }; - - should.exist = function(val, msg) { - new Assertion(val, msg).to.exist; - } - - // negation - should.not = {} - - should.not.equal = function(val1, val2, msg) { - new Assertion(val1, msg).to.not.equal(val2); - }; - - should.not.Throw = function(fn, errt, errs, msg) { - new Assertion(fn, msg).to.not.Throw(errt, errs); - }; - - should.not.exist = function(val, msg) { - new Assertion(val, msg).to.not.exist; - } - - should['throw'] = should['Throw']; - should.not['throw'] = should.not['Throw']; - - return should; - }; - - chai.should = loadShould; - chai.Should = loadShould; - }; - - }); // module: chai/interface/should.js - require.register("chai/utils/addChainableMethod.js", function(module, exports, require) { - /*! - * Chai - addChainingMethod utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /*! - * Module dependencies - */ - - var transferFlags = require('./transferFlags'); - - /*! - * Module variables - */ - - // Check whether `__proto__` is supported - var hasProtoSupport = '__proto__' in Object; - - // Without `__proto__` support, this module will need to add properties to a function. - // However, some Function.prototype methods cannot be overwritten, - // and there seems no easy cross-platform way to detect them (@see chaijs/chai/issues/69). - var excludeNames = /^(?:length|name|arguments|caller)$/; - - /** - * ### addChainableMethod (ctx, name, method, chainingBehavior) - * - * Adds a method to an object, such that the method can also be chained. - * - * utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) { - * var obj = utils.flag(this, 'object'); - * new chai.Assertion(obj).to.be.equal(str); - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.addChainableMethod('foo', fn, chainingBehavior); - * - * The result can then be used as both a method assertion, executing both `method` and - * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`. - * - * expect(fooStr).to.be.foo('bar'); - * expect(fooStr).to.be.foo.equal('foo'); - * - * @param {Object} ctx object to which the method is added - * @param {String} name of method to add - * @param {Function} method function to be used for `name`, when called - * @param {Function} chainingBehavior function to be called every time the property is accessed - * @name addChainableMethod - * @api public - */ - - module.exports = function(ctx, name, method, chainingBehavior) { - if (typeof chainingBehavior !== 'function') chainingBehavior = function() {}; - - Object.defineProperty(ctx, name, { - get: function() { - chainingBehavior.call(this); - - var assert = function() { - var result = method.apply(this, arguments); - return result === undefined ? this : result; - }; - - // Use `__proto__` if available - if (hasProtoSupport) { - assert.__proto__ = this; - } - // Otherwise, redefine all properties (slow!) - else { - var asserterNames = Object.getOwnPropertyNames(ctx); - asserterNames.forEach(function(asserterName) { - if (!excludeNames.test(asserterName)) { - var pd = Object.getOwnPropertyDescriptor(ctx, asserterName); - Object.defineProperty(assert, asserterName, pd); - } - }); - } - - transferFlags(this, assert); - return assert; - }, - configurable: true - }); - }; - - }); // module: chai/utils/addChainableMethod.js - require.register("chai/utils/addMethod.js", function(module, exports, require) { - /*! - * Chai - addMethod utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /** - * ### .addMethod (ctx, name, method) - * - * Adds a method to the prototype of an object. - * - * utils.addMethod(chai.Assertion.prototype, 'foo', function (str) { - * var obj = utils.flag(this, 'object'); - * new chai.Assertion(obj).to.be.equal(str); - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.addMethod('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(fooStr).to.be.foo('bar'); - * - * @param {Object} ctx object to which the method is added - * @param {String} name of method to add - * @param {Function} method function to be used for name - * @name addMethod - * @api public - */ - - module.exports = function(ctx, name, method) { - ctx[name] = function() { - var result = method.apply(this, arguments); - return result === undefined ? this : result; - }; - }; - - }); // module: chai/utils/addMethod.js - require.register("chai/utils/addProperty.js", function(module, exports, require) { - /*! - * Chai - addProperty utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /** - * ### addProperty (ctx, name, getter) - * - * Adds a property to the prototype of an object. - * - * utils.addProperty(chai.Assertion.prototype, 'foo', function () { - * var obj = utils.flag(this, 'object'); - * new chai.Assertion(obj).to.be.instanceof(Foo); - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.addProperty('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.be.foo; - * - * @param {Object} ctx object to which the property is added - * @param {String} name of property to add - * @param {Function} getter function to be used for name - * @name addProperty - * @api public - */ - - module.exports = function(ctx, name, getter) { - Object.defineProperty(ctx, name, { - get: function() { - var result = getter.call(this); - return result === undefined ? this : result; - }, - configurable: true - }); - }; - - }); // module: chai/utils/addProperty.js - require.register("chai/utils/eql.js", function(module, exports, require) { - // This is (almost) directly from Node.js assert - // https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/assert.js - module.exports = _deepEqual; - - var getEnumerableProperties = require('./getEnumerableProperties'); - - // for the browser - var Buffer; - try { - Buffer = require('buffer').Buffer; - } catch (ex) { - Buffer = { - isBuffer: function() { - return false; - } - }; - } - - function _deepEqual(actual, expected, memos) { - - // 7.1. All identical values are equivalent, as determined by ===. - if (actual === expected) { - return true; - - } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { - if (actual.length != expected.length) return false; - - for (var i = 0; i < actual.length; i++) { - if (actual[i] !== expected[i]) return false; - } - - return true; - - // 7.2. If the expected value is a Date object, the actual value is - // equivalent if it is also a Date object that refers to the same time. - } else if (actual instanceof Date && expected instanceof Date) { - return actual.getTime() === expected.getTime(); - - // 7.3. Other pairs that do not both pass typeof value == 'object', - // equivalence is determined by ==. - } else if (typeof actual != 'object' && typeof expected != 'object') { - return actual === expected; - - // 7.4. For all other Object pairs, including Array objects, equivalence is - // determined by having the same number of owned properties (as verified - // with Object.prototype.hasOwnProperty.call), the same set of keys - // (although not necessarily the same order), equivalent values for every - // corresponding key, and an identical 'prototype' property. Note: this - // accounts for both named and indexed properties on Arrays. - } else { - return objEquiv(actual, expected, memos); - } - } - - function isUndefinedOrNull(value) { - return value === null || value === undefined; - } - - function isArguments(object) { - return Object.prototype.toString.call(object) == '[object Arguments]'; - } - - function objEquiv(a, b, memos) { - if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) return false; - - // an identical 'prototype' property. - if (a.prototype !== b.prototype) return false; - - // check if we have already compared a and b - var i; - if (memos) { - for (i = 0; i < memos.length; i++) { - if ((memos[i][0] === a && memos[i][1] === b) || (memos[i][0] === b && memos[i][1] === a)) return true; - } - } else { - memos = []; - } - - //~~~I've managed to break Object.keys through screwy arguments passing. - // Converting to array solves the problem. - if (isArguments(a)) { - if (!isArguments(b)) { - return false; - } - a = pSlice.call(a); - b = pSlice.call(b); - return _deepEqual(a, b, memos); - } - try { - var ka = getEnumerableProperties(a), - kb = getEnumerableProperties(b), - key; - } catch (e) { //happens when one is a string literal and the other isn't - return false; - } - - // having the same number of owned properties (keys incorporates - // hasOwnProperty) - if (ka.length != kb.length) return false; - - //the same set of keys (although not necessarily the same order), - ka.sort(); - kb.sort(); - //~~~cheap key test - for (i = ka.length - 1; i >= 0; i--) { - if (ka[i] != kb[i]) return false; - } - - // remember objects we have compared to guard against circular references - memos.push([a, b]); - - //equivalent values for every corresponding key, and - //~~~possibly expensive deep test - for (i = ka.length - 1; i >= 0; i--) { - key = ka[i]; - if (!_deepEqual(a[key], b[key], memos)) return false; - } - - return true; - } - - }); // module: chai/utils/eql.js - require.register("chai/utils/flag.js", function(module, exports, require) { - /*! - * Chai - flag utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /** - * ### flag(object ,key, [value]) - * - * Get or set a flag value on an object. If a - * value is provided it will be set, else it will - * return the currently set value or `undefined` if - * the value is not set. - * - * utils.flag(this, 'foo', 'bar'); // setter - * utils.flag(this, 'foo'); // getter, returns `bar` - * - * @param {Object} object (constructed Assertion - * @param {String} key - * @param {Mixed} value (optional) - * @name flag - * @api private - */ - - module.exports = function(obj, key, value) { - var flags = obj.__flags || (obj.__flags = Object.create(null)); - if (arguments.length === 3) { - flags[key] = value; - } else { - return flags[key]; - } - }; - - }); // module: chai/utils/flag.js - require.register("chai/utils/getActual.js", function(module, exports, require) { - /*! - * Chai - getActual utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /** - * # getActual(object, [actual]) - * - * Returns the `actual` value for an Assertion - * - * @param {Object} object (constructed Assertion) - * @param {Arguments} chai.Assertion.prototype.assert arguments - */ - - module.exports = function(obj, args) { - var actual = args[4]; - return 'undefined' !== typeof actual ? actual : obj._obj; - }; - - }); // module: chai/utils/getActual.js - require.register("chai/utils/getEnumerableProperties.js", function(module, exports, require) { - /*! - * Chai - getEnumerableProperties utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /** - * ### .getEnumerableProperties(object) - * - * This allows the retrieval of enumerable property names of an object, - * inherited or not. - * - * @param {Object} object - * @returns {Array} - * @name getEnumerableProperties - * @api public - */ - - module.exports = function getEnumerableProperties(object) { - var result = []; - for (var name in object) { - result.push(name); - } - return result; - }; - - }); // module: chai/utils/getEnumerableProperties.js - require.register("chai/utils/getMessage.js", function(module, exports, require) { - /*! - * Chai - message composition utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /*! - * Module dependancies - */ - - var flag = require('./flag'), - getActual = require('./getActual'), - inspect = require('./inspect'), - objDisplay = require('./objDisplay'); - - /** - * ### .getMessage(object, message, negateMessage) - * - * Construct the error message based on flags - * and template tags. Template tags will return - * a stringified inspection of the object referenced. - * - * Messsage template tags: - * - `#{this}` current asserted object - * - `#{act}` actual value - * - `#{exp}` expected value - * - * @param {Object} object (constructed Assertion) - * @param {Arguments} chai.Assertion.prototype.assert arguments - * @name getMessage - * @api public - */ - - module.exports = function(obj, args) { - var negate = flag(obj, 'negate'), - val = flag(obj, 'object'), - expected = args[3], - actual = getActual(obj, args), - msg = negate ? args[2] : args[1], - flagMsg = flag(obj, 'message'); - - msg = msg || ''; - msg = msg.replace(/#{this}/g, objDisplay(val)).replace(/#{act}/g, objDisplay(actual)).replace(/#{exp}/g, objDisplay(expected)); - - return flagMsg ? flagMsg + ': ' + msg : msg; - }; - - }); // module: chai/utils/getMessage.js - require.register("chai/utils/getName.js", function(module, exports, require) { - /*! - * Chai - getName utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /** - * # getName(func) - * - * Gets the name of a function, in a cross-browser way. - * - * @param {Function} a function (usually a constructor) - */ - - module.exports = function(func) { - if (func.name) return func.name; - - var match = /^\s?function ([^(]*)\(/.exec(func); - return match && match[1] ? match[1] : ""; - }; - - }); // module: chai/utils/getName.js - require.register("chai/utils/getPathValue.js", function(module, exports, require) { - /*! - * Chai - getPathValue utility - * Copyright(c) 2012-2013 Jake Luer - * @see https://github.com/logicalparadox/filtr - * MIT Licensed - */ - - /** - * ### .getPathValue(path, object) - * - * This allows the retrieval of values in an - * object given a string path. - * - * var obj = { - * prop1: { - * arr: ['a', 'b', 'c'] - * , str: 'Hello' - * } - * , prop2: { - * arr: [ { nested: 'Universe' } ] - * , str: 'Hello again!' - * } - * } - * - * The following would be the results. - * - * getPathValue('prop1.str', obj); // Hello - * getPathValue('prop1.att[2]', obj); // b - * getPathValue('prop2.arr[0].nested', obj); // Universe - * - * @param {String} path - * @param {Object} object - * @returns {Object} value or `undefined` - * @name getPathValue - * @api public - */ - - var getPathValue = module.exports = function(path, obj) { - var parsed = parsePath(path); - return _getPathValue(parsed, obj); - }; - - /*! - * ## parsePath(path) - * - * Helper function used to parse string object - * paths. Use in conjunction with `_getPathValue`. - * - * var parsed = parsePath('myobject.property.subprop'); - * - * ### Paths: - * - * * Can be as near infinitely deep and nested - * * Arrays are also valid using the formal `myobject.document[3].property`. - * - * @param {String} path - * @returns {Object} parsed - * @api private - */ - - function parsePath(path) { - var str = path.replace(/\[/g, '.['), - parts = str.match(/(\\\.|[^.]+?)+/g); - return parts.map(function(value) { - var re = /\[(\d+)\]$/, - mArr = re.exec(value) - if (mArr) return { - i: parseFloat(mArr[1]) - }; - else return { - p: value - }; - }); - }; - - /*! - * ## _getPathValue(parsed, obj) - * - * Helper companion function for `.parsePath` that returns - * the value located at the parsed address. - * - * var value = getPathValue(parsed, obj); - * - * @param {Object} parsed definition from `parsePath`. - * @param {Object} object to search against - * @returns {Object|Undefined} value - * @api private - */ - - function _getPathValue(parsed, obj) { - var tmp = obj, - res; - for (var i = 0, l = parsed.length; i < l; i++) { - var part = parsed[i]; - if (tmp) { - if ('undefined' !== typeof part.p) tmp = tmp[part.p]; - else if ('undefined' !== typeof part.i) tmp = tmp[part.i]; - if (i == (l - 1)) res = tmp; - } else { - res = undefined; - } - } - return res; - }; - - }); // module: chai/utils/getPathValue.js - require.register("chai/utils/getProperties.js", function(module, exports, require) { - /*! - * Chai - getProperties utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /** - * ### .getProperties(object) - * - * This allows the retrieval of property names of an object, enumerable or not, - * inherited or not. - * - * @param {Object} object - * @returns {Array} - * @name getProperties - * @api public - */ - - module.exports = function getProperties(object) { - var result = Object.getOwnPropertyNames(subject); - - function addProperty(property) { - if (result.indexOf(property) === -1) { - result.push(property); - } - } - - var proto = Object.getPrototypeOf(subject); - while (proto !== null) { - Object.getOwnPropertyNames(proto).forEach(addProperty); - proto = Object.getPrototypeOf(proto); - } - - return result; - }; - - }); // module: chai/utils/getProperties.js - require.register("chai/utils/index.js", function(module, exports, require) { - /*! - * chai - * Copyright(c) 2011 Jake Luer - * MIT Licensed - */ - - /*! - * Main exports - */ - - var exports = module.exports = {}; - - /*! - * test utility - */ - - exports.test = require('./test'); - - /*! - * type utility - */ - - exports.type = require('./type'); - - /*! - * message utility - */ - - exports.getMessage = require('./getMessage'); - - /*! - * actual utility - */ - - exports.getActual = require('./getActual'); - - /*! - * Inspect util - */ - - exports.inspect = require('./inspect'); - - /*! - * Object Display util - */ - - exports.objDisplay = require('./objDisplay'); - - /*! - * Flag utility - */ - - exports.flag = require('./flag'); - - /*! - * Flag transferring utility - */ - - exports.transferFlags = require('./transferFlags'); - - /*! - * Deep equal utility - */ - - exports.eql = require('./eql'); - - /*! - * Deep path value - */ - - exports.getPathValue = require('./getPathValue'); - - /*! - * Function name - */ - - exports.getName = require('./getName'); - - /*! - * add Property - */ - - exports.addProperty = require('./addProperty'); - - /*! - * add Method - */ - - exports.addMethod = require('./addMethod'); - - /*! - * overwrite Property - */ - - exports.overwriteProperty = require('./overwriteProperty'); - - /*! - * overwrite Method - */ - - exports.overwriteMethod = require('./overwriteMethod'); - - /*! - * Add a chainable method - */ - - exports.addChainableMethod = require('./addChainableMethod'); - - - }); // module: chai/utils/index.js - require.register("chai/utils/inspect.js", function(module, exports, require) { - // This is (almost) directly from Node.js utils - // https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js - var getName = require('./getName'); - var getProperties = require('./getProperties'); - var getEnumerableProperties = require('./getEnumerableProperties'); - - module.exports = inspect; - - /** - * Echos the value of a value. Trys to print the value out - * in the best way possible given the different types. - * - * @param {Object} obj The object to print out. - * @param {Boolean} showHidden Flag that shows hidden (not enumerable) - * properties of objects. - * @param {Number} depth Depth in which to descend in object. Default is 2. - * @param {Boolean} colors Flag to turn on ANSI escape codes to color the - * output. Default is false (no coloring). - */ - function inspect(obj, showHidden, depth, colors) { - var ctx = { - showHidden: showHidden, - seen: [], - stylize: function(str) { - return str; - } - }; - return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); - } - - // https://gist.github.com/1044128/ - var getOuterHTML = function(element) { - if ('outerHTML' in element) return element.outerHTML; - var ns = "http://www.w3.org/1999/xhtml"; - var container = document.createElementNS(ns, '_'); - var elemProto = (window.HTMLElement || window.Element).prototype; - var xmlSerializer = new XMLSerializer(); - var html; - if (document.xmlVersion) { - return xmlSerializer.serializeToString(element); - } else { - container.appendChild(element.cloneNode(false)); - html = container.innerHTML.replace('><', '>' + element.innerHTML + '<'); - container.innerHTML = ''; - return html; - } - }; - - // Returns true if object is a DOM element. - var isDOMElement = function(object) { - if (typeof HTMLElement === 'object') { - return object instanceof HTMLElement; - } else { - return object && typeof object === 'object' && object.nodeType === 1 && typeof object.nodeName === 'string'; - } - }; - - function formatValue(ctx, value, recurseTimes) { - // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it - if (value && typeof value.inspect === 'function' && - // Filter out the util module, it's inspect function is special - value.inspect !== exports.inspect && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - return value.inspect(recurseTimes); - } - - // Primitive types cannot have properties - var primitive = formatPrimitive(ctx, value); - if (primitive) { - return primitive; - } - - // If it's DOM elem, get outer HTML. - if (isDOMElement(value)) { - return getOuterHTML(value); - } - - // Look up the keys of the object. - var visibleKeys = getEnumerableProperties(value); - var keys = ctx.showHidden ? getProperties(value) : visibleKeys; - - // Some type of object without properties can be shortcutted. - // In IE, errors have a single `stack` property, or if they are vanilla `Error`, - // a `stack` plus `description` property; ignore those for consistency. - if (keys.length === 0 || (isError(value) && ( - (keys.length === 1 && keys[0] === 'stack') || (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack')))) { - if (typeof value === 'function') { - var name = getName(value); - var nameSuffix = name ? ': ' + name : ''; - return ctx.stylize('[Function' + nameSuffix + ']', 'special'); - } - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } - if (isDate(value)) { - return ctx.stylize(Date.prototype.toUTCString.call(value), 'date'); - } - if (isError(value)) { - return formatError(value); - } - } - - var base = '', - array = false, - braces = ['{', '}']; - - // Make Array say that they are Array - if (isArray(value)) { - array = true; - braces = ['[', ']']; - } - - // Make functions say that they are functions - if (typeof value === 'function') { - var name = getName(value); - var nameSuffix = name ? ': ' + name : ''; - base = ' [Function' + nameSuffix + ']'; - } - - // Make RegExps say that they are RegExps - if (isRegExp(value)) { - base = ' ' + RegExp.prototype.toString.call(value); - } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + Date.prototype.toUTCString.call(value); - } - - // Make error with message first say the error - if (isError(value)) { - return formatError(value); - } - - if (keys.length === 0 && (!array || value.length == 0)) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } else { - return ctx.stylize('[Object]', 'special'); - } - } - - ctx.seen.push(value); - - var output; - if (array) { - output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); - } else { - output = keys.map(function(key) { - return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); - }); - } - - ctx.seen.pop(); - - return reduceToSingleString(output, base, braces); - } - - - function formatPrimitive(ctx, value) { - switch (typeof value) { - case 'undefined': - return ctx.stylize('undefined', 'undefined'); - - case 'string': - var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '').replace(/'/g, "\\'").replace(/\\"/g, '"') + '\''; - return ctx.stylize(simple, 'string'); - - case 'number': - return ctx.stylize('' + value, 'number'); - - case 'boolean': - return ctx.stylize('' + value, 'boolean'); - } - // For some reason typeof null is "object", so special case here. - if (value === null) { - return ctx.stylize('null', 'null'); - } - } - - - function formatError(value) { - return '[' + Error.prototype.toString.call(value) + ']'; - } - - - function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { - var output = []; - for (var i = 0, l = value.length; i < l; ++i) { - if (Object.prototype.hasOwnProperty.call(value, String(i))) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, String(i), true)); - } else { - output.push(''); - } - } - keys.forEach(function(key) { - if (!key.match(/^\d+$/)) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)); - } - }); - return output; - } - - - function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { - var name, str; - if (value.__lookupGetter__) { - if (value.__lookupGetter__(key)) { - if (value.__lookupSetter__(key)) { - str = ctx.stylize('[Getter/Setter]', 'special'); - } else { - str = ctx.stylize('[Getter]', 'special'); - } - } else { - if (value.__lookupSetter__(key)) { - str = ctx.stylize('[Setter]', 'special'); - } - } - } - if (visibleKeys.indexOf(key) < 0) { - name = '[' + key + ']'; - } - if (!str) { - if (ctx.seen.indexOf(value[key]) < 0) { - if (recurseTimes === null) { - str = formatValue(ctx, value[key], null); - } else { - str = formatValue(ctx, value[key], recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (array) { - str = str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n').substr(2); - } else { - str = '\n' + str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n'); - } - } - } else { - str = ctx.stylize('[Circular]', 'special'); - } - } - if (typeof name === 'undefined') { - if (array && key.match(/^\d+$/)) { - return str; - } - name = JSON.stringify('' + key); - if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { - name = name.substr(1, name.length - 2); - name = ctx.stylize(name, 'name'); - } else { - name = name.replace(/'/g, "\\'").replace(/\\"/g, '"').replace(/(^"|"$)/g, "'"); - name = ctx.stylize(name, 'string'); - } - } - - return name + ': ' + str; - } - - - function reduceToSingleString(output, base, braces) { - var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; - return prev + cur.length + 1; - }, 0); - - if (length > 60) { - return braces[0] + (base === '' ? '' : base + '\n ') + ' ' + output.join(',\n ') + ' ' + braces[1]; - } - - return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; - } - - function isArray(ar) { - return Array.isArray(ar) || (typeof ar === 'object' && objectToString(ar) === '[object Array]'); - } - - function isRegExp(re) { - return typeof re === 'object' && objectToString(re) === '[object RegExp]'; - } - - function isDate(d) { - return typeof d === 'object' && objectToString(d) === '[object Date]'; - } - - function isError(e) { - return typeof e === 'object' && objectToString(e) === '[object Error]'; - } - - function objectToString(o) { - return Object.prototype.toString.call(o); - } - - }); // module: chai/utils/inspect.js - require.register("chai/utils/objDisplay.js", function(module, exports, require) { - /*! - * Chai - flag utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /*! - * Module dependancies - */ - - var inspect = require('./inspect'); - - /** - * ### .objDisplay (object) - * - * Determines if an object or an array matches - * criteria to be inspected in-line for error - * messages or should be truncated. - * - * @param {Mixed} javascript object to inspect - * @name objDisplay - * @api public - */ - - module.exports = function(obj) { - var str = inspect(obj), - type = Object.prototype.toString.call(obj); - - if (str.length >= 40) { - if (type === '[object Function]') { - return !obj.name || obj.name === '' ? '[Function]' : '[Function: ' + obj.name + ']'; - } else if (type === '[object Array]') { - return '[ Array(' + obj.length + ') ]'; - } else if (type === '[object Object]') { - var keys = Object.keys(obj), - kstr = keys.length > 2 ? keys.splice(0, 2).join(', ') + ', ...' : keys.join(', '); - return '{ Object (' + kstr + ') }'; - } else { - return str; - } - } else { - return str; - } - }; - - }); // module: chai/utils/objDisplay.js - require.register("chai/utils/overwriteMethod.js", function(module, exports, require) { - /*! - * Chai - overwriteMethod utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /** - * ### overwriteMethod (ctx, name, fn) - * - * Overwites an already existing method and provides - * access to previous function. Must return function - * to be used for name. - * - * utils.overwriteMethod(chai.Assertion.prototype, 'equal', function (_super) { - * return function (str) { - * var obj = utils.flag(this, 'object'); - * if (obj instanceof Foo) { - * new chai.Assertion(obj.value).to.equal(str); - * } else { - * _super.apply(this, arguments); - * } - * } - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.overwriteMethod('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.equal('bar'); - * - * @param {Object} ctx object whose method is to be overwritten - * @param {String} name of method to overwrite - * @param {Function} method function that returns a function to be used for name - * @name overwriteMethod - * @api public - */ - - module.exports = function(ctx, name, method) { - var _method = ctx[name], - _super = function() { - return this; - }; - - if (_method && 'function' === typeof _method) _super = _method; - - ctx[name] = function() { - var result = method(_super).apply(this, arguments); - return result === undefined ? this : result; - } - }; - - }); // module: chai/utils/overwriteMethod.js - require.register("chai/utils/overwriteProperty.js", function(module, exports, require) { - /*! - * Chai - overwriteProperty utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /** - * ### overwriteProperty (ctx, name, fn) - * - * Overwites an already existing property getter and provides - * access to previous value. Must return function to use as getter. - * - * utils.overwriteProperty(chai.Assertion.prototype, 'ok', function (_super) { - * return function () { - * var obj = utils.flag(this, 'object'); - * if (obj instanceof Foo) { - * new chai.Assertion(obj.name).to.equal('bar'); - * } else { - * _super.call(this); - * } - * } - * }); - * - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.overwriteProperty('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.be.ok; - * - * @param {Object} ctx object whose property is to be overwritten - * @param {String} name of property to overwrite - * @param {Function} getter function that returns a getter function to be used for name - * @name overwriteProperty - * @api public - */ - - module.exports = function(ctx, name, getter) { - var _get = Object.getOwnPropertyDescriptor(ctx, name), - _super = function() {}; - - if (_get && 'function' === typeof _get.get) _super = _get.get - - Object.defineProperty(ctx, name, { - get: function() { - var result = getter(_super).call(this); - return result === undefined ? this : result; - }, - configurable: true - }); - }; - - }); // module: chai/utils/overwriteProperty.js - require.register("chai/utils/test.js", function(module, exports, require) { - /*! - * Chai - test utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /*! - * Module dependancies - */ - - var flag = require('./flag'); - - /** - * # test(object, expression) - * - * Test and object for expression. - * - * @param {Object} object (constructed Assertion) - * @param {Arguments} chai.Assertion.prototype.assert arguments - */ - - module.exports = function(obj, args) { - var negate = flag(obj, 'negate'), - expr = args[0]; - return negate ? !expr : expr; - }; - - }); // module: chai/utils/test.js - require.register("chai/utils/transferFlags.js", function(module, exports, require) { - /*! - * Chai - transferFlags utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /** - * ### transferFlags(assertion, object, includeAll = true) - * - * Transfer all the flags for `assertion` to `object`. If - * `includeAll` is set to `false`, then the base Chai - * assertion flags (namely `object`, `ssfi`, and `message`) - * will not be transferred. - * - * - * var newAssertion = new Assertion(); - * utils.transferFlags(assertion, newAssertion); - * - * var anotherAsseriton = new Assertion(myObj); - * utils.transferFlags(assertion, anotherAssertion, false); - * - * @param {Assertion} assertion the assertion to transfer the flags from - * @param {Object} object the object to transfer the flags too; usually a new assertion - * @param {Boolean} includeAll - * @name getAllFlags - * @api private - */ - - module.exports = function(assertion, object, includeAll) { - var flags = assertion.__flags || (assertion.__flags = Object.create(null)); - - if (!object.__flags) { - object.__flags = Object.create(null); - } - - includeAll = arguments.length === 3 ? includeAll : true; - - for (var flag in flags) { - if (includeAll || (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) { - object.__flags[flag] = flags[flag]; - } - } - }; - - }); // module: chai/utils/transferFlags.js - require.register("chai/utils/type.js", function(module, exports, require) { - /*! - * Chai - type utility - * Copyright(c) 2012-2013 Jake Luer - * MIT Licensed - */ - - /*! - * Detectable javascript natives - */ - - var natives = { - '[object Arguments]': 'arguments', - '[object Array]': 'array', - '[object Date]': 'date', - '[object Function]': 'function', - '[object Number]': 'number', - '[object RegExp]': 'regexp', - '[object String]': 'string' - }; - - /** - * ### type(object) - * - * Better implementation of `typeof` detection that can - * be used cross-browser. Handles the inconsistencies of - * Array, `null`, and `undefined` detection. - * - * utils.type({}) // 'object' - * utils.type(null) // `null' - * utils.type(undefined) // `undefined` - * utils.type([]) // `array` - * - * @param {Mixed} object to detect type of - * @name type - * @api private - */ - - module.exports = function(obj) { - var str = Object.prototype.toString.call(obj); - if (natives[str]) return natives[str]; - if (obj === null) return 'null'; - if (obj === undefined) return 'undefined'; - if (obj === Object(obj)) return 'object'; - return typeof obj; - }; - - }); // module: chai/utils/type.js - require.alias("./chai.js", "chai"); - - return require('chai'); -}); diff --git a/spec/vendor/sinon-chai.js b/spec/vendor/sinon-chai.js deleted file mode 100644 index 8818972..0000000 --- a/spec/vendor/sinon-chai.js +++ /dev/null @@ -1,106 +0,0 @@ -(function(sinonChai) { - "use strict"; - - // Module systems magic dance. - if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { - // NodeJS - module.exports = sinonChai; - } else if (typeof define === "function" && define.amd) { - // AMD - define(function() { - return sinonChai; - }); - } else { - // Other environment (usually