[Pkg-javascript-commits] [node-es5-shim] 01/05: Imported Upstream version 4.1.14+dfsg

Julien Puydt julien.puydt at laposte.net
Thu Oct 15 06:18:51 UTC 2015


This is an automated email from the git hooks/post-receive script.

jpuydt-guest pushed a commit to branch master
in repository node-es5-shim.

commit de34d43363d8d130a20b6fcb838c604299313798
Author: Julien Puydt <julien.puydt at laposte.net>
Date:   Thu Oct 15 07:50:05 2015 +0200

    Imported Upstream version 4.1.14+dfsg
---
 .eslintrc                     |   37 +
 .gitignore                    |    2 +
 .jscs.json                    |  125 ++
 .travis.yml                   |   58 +
 CHANGES                       |  280 +++++
 CONTRIBUTORS.md               |   27 +
 LICENSE                       |   22 +
 Makefile                      |   63 +
 README.md                     |  167 +++
 bower.json                    |   33 +
 component.json                |   19 +
 es5-sham.js                   |  556 +++++++++
 es5-shim.js                   | 1590 +++++++++++++++++++++++++
 package.json                  |   72 ++
 shims.json                    |    7 +
 tests/helpers/h-matchers.js   |   40 +
 tests/index.html              |   62 +
 tests/index.min.html          |   61 +
 tests/lib/jasmine-html.js     |  190 +++
 tests/lib/jasmine.css         |  166 +++
 tests/lib/jasmine.js          | 2601 +++++++++++++++++++++++++++++++++++++++++
 tests/lib/jasmine_favicon.png |  Bin 0 -> 905 bytes
 tests/lib/json2.js            |  478 ++++++++
 tests/native.html             |   58 +
 tests/spec/helpers-jasmine.js |    2 +
 tests/spec/s-array.js         | 1320 +++++++++++++++++++++
 tests/spec/s-date.js          |  247 ++++
 tests/spec/s-function.js      |  176 +++
 tests/spec/s-global.js        |   55 +
 tests/spec/s-number.js        |   37 +
 tests/spec/s-object.js        |  359 ++++++
 tests/spec/s-string.js        |  232 ++++
 32 files changed, 9142 insertions(+)

diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..8ba8630
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,37 @@
+{
+	"root": true,
+
+	"extends": "@ljharb",
+
+	"rules": {
+		"camelcase": [0],
+		"complexity": [0, 10],
+		"dot-notation": [2, { "allowKeywords": false }],
+		"eqeqeq": [2, "allow-null"],
+		"id-length": [2, { "min": 1, "max": 40 }],
+		"max-nested-callbacks": [2, 5],
+		"max-params": [2, 7],
+		"max-statements": [1, 30],
+		"new-cap": [2, { "capIsNewExceptions": ["ToInteger", "ToObject", "ToPrimitive", "ToUint32"] }],
+		"no-constant-condition": [1],
+		"no-extend-native": [2, {"exceptions": ["Date"]}],
+		"no-extra-parens": [0],
+		"no-extra-semi": [1],
+		"no-func-assign": [1],
+		"no-implicit-coercion": [2, {
+			"boolean": false,
+			"number": false,
+			"string": true
+		 }],
+		"no-invalid-this": [0],
+		"no-native-reassign": [2, {"exceptions": ["Date", "parseInt"]}],
+		"no-new-func": [1],
+		"no-restricted-syntax": [2, "ContinueStatement", "DebuggerStatement", "LabeledStatement", "WithStatement"],
+		"no-shadow": [1],
+		"no-unused-vars": [1, { "vars": "all", "args": "after-used" }],
+		"operator-linebreak": [2, "after"],
+		"quote-props": [1, "as-needed", { "keywords": true }],
+		"spaced-comment": [0],
+		"strict": [0]
+	}
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fd4f2b0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+.DS_Store
diff --git a/.jscs.json b/.jscs.json
new file mode 100644
index 0000000..191a0ed
--- /dev/null
+++ b/.jscs.json
@@ -0,0 +1,125 @@
+{
+	"es3": true,
+
+	"additionalRules": [],
+
+	"requireSemicolons": true,
+
+	"disallowMultipleSpaces": true,
+
+	"disallowIdentifierNames": [],
+
+	"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
+
+	"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch", "function"],
+
+	"disallowSpaceAfterKeywords": [],
+
+	"disallowSpaceBeforeComma": true,
+	"disallowSpaceBeforeSemicolon": true,
+
+	"disallowNodeTypes": [
+		"DebuggerStatement",
+		"LabeledStatement",
+		"SwitchCase",
+		"SwitchStatement",
+		"WithStatement"
+	],
+
+	"requireObjectKeysOnNewLine": false,
+
+	"requireSpacesInAnonymousFunctionExpression": { "beforeOpeningRoundBrace": true, "beforeOpeningCurlyBrace": true },
+	"requireSpacesInNamedFunctionExpression": { "beforeOpeningCurlyBrace": true },
+	"disallowSpacesInNamedFunctionExpression": { "beforeOpeningRoundBrace": true },
+	"requireSpacesInFunctionDeclaration": { "beforeOpeningCurlyBrace": true },
+	"disallowSpacesInFunctionDeclaration": { "beforeOpeningRoundBrace": true },
+
+	"requireSpaceBetweenArguments": true,
+
+	"disallowSpacesInsideParentheses": true,
+
+	"disallowSpacesInsideArrayBrackets": true,
+
+	"disallowQuotedKeysInObjects": "allButReserved",
+
+	"disallowSpaceAfterObjectKeys": true,
+
+	"requireCommaBeforeLineBreak": true,
+
+	"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
+	"requireSpaceAfterPrefixUnaryOperators": [],
+
+	"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
+	"requireSpaceBeforePostfixUnaryOperators": [],
+
+	"disallowSpaceBeforeBinaryOperators": [],
+	"requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
+
+	"requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
+	"disallowSpaceAfterBinaryOperators": [],
+
+	"disallowImplicitTypeConversion": ["binary", "string"],
+
+	"disallowKeywords": ["with", "eval"],
+
+	"requireKeywordsOnNewLine": [],
+	"disallowKeywordsOnNewLine": ["else"],
+
+	"requireLineFeedAtFileEnd": true,
+
+	"disallowTrailingWhitespace": true,
+
+	"disallowTrailingComma": true,
+
+	"excludeFiles": ["node_modules/**", "vendor/**"],
+
+	"disallowMultipleLineStrings": true,
+
+	"requireDotNotation": true,
+
+	"requireParenthesesAroundIIFE": true,
+
+	"validateLineBreaks": "LF",
+
+	"validateQuoteMarks": {
+		"escape": true,
+		"mark": "'"
+	},
+
+	"disallowOperatorBeforeLineBreak": [],
+
+	"requireSpaceBeforeKeywords": [
+		"do",
+		"for",
+		"if",
+		"else",
+		"switch",
+		"case",
+		"try",
+		"catch",
+		"finally",
+		"while",
+		"with",
+		"return"
+	],
+
+	"validateAlignedFunctionParameters": {
+		"lineBreakAfterOpeningBraces": true,
+		"lineBreakBeforeClosingBraces": true
+	},
+
+	"requirePaddingNewLinesBeforeExport": true,
+
+	"validateNewlineAfterArrayElements": {
+		"maximum": 200
+	},
+
+	"requirePaddingNewLinesAfterUseStrict": true,
+
+	"disallowArrowFunctions": true,
+
+	"disallowMultiLineTernary": false,
+
+	"validateOrderInObjectKeys": false
+}
+
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4c29ed5
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,58 @@
+language: node_js
+node_js:
+  - "4.1"
+  - "4.0"
+  - "iojs-v3.3"
+  - "iojs-v3.2"
+  - "iojs-v3.1"
+  - "iojs-v3.0"
+  - "iojs-v2.5"
+  - "iojs-v2.4"
+  - "iojs-v2.3"
+  - "iojs-v2.2"
+  - "iojs-v2.1"
+  - "iojs-v2.0"
+  - "iojs-v1.8"
+  - "iojs-v1.7"
+  - "iojs-v1.6"
+  - "iojs-v1.5"
+  - "iojs-v1.4"
+  - "iojs-v1.3"
+  - "iojs-v1.2"
+  - "iojs-v1.1"
+  - "iojs-v1.0"
+  - "0.12"
+  - "0.11"
+  - "0.10"
+  - "0.9"
+  - "0.8"
+  - "0.6"
+  - "0.4"
+before_install:
+  - '[ "${TRAVIS_NODE_VERSION}" = "0.6" ] || npm install -g npm at 1.4.28 && npm install -g npm'
+sudo: false
+matrix:
+  fast_finish: true
+  allow_failures:
+    - node_js: "4.0"
+    - node_js: "iojs-v3.2"
+    - node_js: "iojs-v3.1"
+    - node_js: "iojs-v3.0"
+    - node_js: "iojs-v2.4"
+    - node_js: "iojs-v2.3"
+    - node_js: "iojs-v2.2"
+    - node_js: "iojs-v2.1"
+    - node_js: "iojs-v2.0"
+    - node_js: "iojs-v1.7"
+    - node_js: "iojs-v1.6"
+    - node_js: "iojs-v1.5"
+    - node_js: "iojs-v1.4"
+    - node_js: "iojs-v1.3"
+    - node_js: "iojs-v1.2"
+    - node_js: "iojs-v1.1"
+    - node_js: "iojs-v1.0"
+    - node_js: "0.11"
+    - node_js: "0.9"
+    - node_js: "0.8"
+    - node_js: "0.6"
+    - node_js: "0.4"
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..a509db2
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,280 @@
+4.1.14
+  - [shim: fix] Wrap more things in a try/catch, because IE sucks and sometimes throws on [[Get]] of window.localStorage (#327)
+  - [Refactor] Use `ES.ToUint32` instead of inline `>>>`
+  - [Tests] up to `node` `v4.1`
+  - [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `semver`, `jscs`
+
+4.1.13
+  - [shim: fix] Fix a bug where `Date(x)` threw instead of equalling `String(Date(x))` (#326)
+
+4.1.12
+  - [fix] Make sure uglify doesn't remove function names
+  - [shim: fix] Use `is-arguments` implementation; don't call down legacy code path in modern engines (#325)
+  - [Tests] up to `io.js` `v3.3`
+  - [Dev Deps] update `eslint`, `@ljharb/eslint-config`
+
+4.1.11
+  - [shim: fix] Object.keys in Safari 9 has some bugs. (Already fixed in Webkit Nightly)
+  - [shim: fix] Omit !Date.parse check in the if statement (#323)
+  - [sham: fix] Fix Object.create sham to not set __proto__ (#301)
+  - [sham: fix] Add a typeof check to Object.getPrototypeOf (#319, #320)
+  - [Tests] up to `io.js` `v3.1`
+  - [Tests] Make sure `Object.getPrototypeOf` tests don't fail when engines implement ES6 semantics
+  - [Docs] Switch from vb.teelaun.ch to versionbadg.es for the npm version badge SVG (#318)
+  - [Dev Deps] Update `eslint`, `uglify-js`, `jscs`; use my personal shared `eslint` config
+
+4.1.10
+  - [Fix] Fix IE 8 issue with `window.frameElement` access in a child iframe (#322)
+  - [Tests] Consolidating `Date.parse` extended year tests
+  - [Tests] Account for a `Date.parse` precision variance in Safari 8
+  - [Tests] DRY up some Date.parse tests
+  - [Tests] Don't create globals in Date tests
+  - [Tests] Better failure output when an invalid date's toJSON throws
+  - [Tests] Also compare lengths of array-likes
+  - [Tests] Extra check on Object.keys(arguments)
+  - [Tests] Skip appropriate tests when objects aren't extensible/freezeable/sealable
+
+4.1.9
+  - [Fix] Remove "extended", add "unicode" in `String#split` shim, to match ES6
+  - [Fix] Object.keys: Preserve the IE 8 dontEnum bugfix, and the automation equality bugfix.
+  - [Fix] Object.keys: Prevent a deprecation message from showing up in Chrome.
+  - [Performance] Speed up blacklisted key check for Object.keys automation equality bug.
+  - [Tests] Test on `io.js` `v2.4`
+  - [Dev Deps] Update `eslint`, `semver`
+
+4.1.8
+  - [Fix] Fix an `Object.keys` IE 8 bug where `localStorage.prototype.constructor === localStorage` would throw (#275)
+  - [Fix] Shimmed `Object.defineProperty` should not throw for an empty descriptor (#315)
+  - [Fix] Fix `Date#toISOString` in Safari 5.1 (#243)
+  - [Fix] Use `Object#propertyIsEnumerable` to default the initial "enumerable" value in `Object.getOwnPropertyDescriptor` sham (#289)
+  - [Fix] Fix `Array#splice` with large sparse arrays in Safari 7/8, and Opera 12.15 (#295)
+  - [Robustness] Safely use and reference many builtins internally (also see #313)
+  - [Tests] Add `Date#{getUTCDate,getUTCMonth}` tests to expose Opera 10.6/11.61/12 `Date` bugs
+  - [Dev Deps] Update `eslint`
+
+4.1.7
+  - Make sure `Date.parse` is not enumerable (#310)
+
+4.1.6
+  - Support IE 8 when `document.domain` is set (#306, #150)
+  - Remove version from `bower.json` (#307)
+
+4.1.5
+  - Add a failing runtime check for Safari 8 `Date.parse`
+  - Update `eslint`, `semver`
+  - Test on `io.js` `v2.2`
+
+4.1.4
+  - Make sure copied `Date` properties remain non-enumerable.
+  - Using a more reliable check for supported property descriptors in non-IE ES3
+  - Fix 'constructor' in Object.defineProperties sham in ES3 (#252, #305)
+  - Use a reference to `Array#concat` rather than relying on the runtime environment's `concat`.
+  - Test on `io.js` `v2.1`
+  - Clean up `Array.prototype` iteration methods
+
+4.1.3
+  - Update `license` in `package.json` per https://docs.npmjs.com/files/package.json#license
+  - Update `uglify-js`, `eslint`
+
+4.1.2
+  - In IE 6-8, `Date` inside the function expression does not reference `DateShim` (#303)
+  - Date: Ensure all code paths have the correct `constructor` property
+  - Date: Don't copy non-own properties from original `Date`
+  - Test up to `io.js` `v2.0.0`
+  - Simplify `isPrimitive` check.
+  - Adding sanity check tests for ES5 `Number` constants.
+  - Update `uglify-js`, `eslint`, `semver`
+
+4.1.1
+  - Fix name of `parseInt` replacement.
+  - Update copyright year
+  - Update `eslint`, `jscs`
+  - Lock `uglify-js` down to v2.4.17, since v2.4.18 and v2.4.19 have a breaking change.
+  - All grade A-supported `node`/`iojs` versions now ship with an `npm` that understands `^`.
+  - Run `travis-ci` tests on latest `node` and `iojs`; speed up builds; allow 0.8 failures.
+  - Ensure some Object tests don't fail in ES6
+  - Make sure `Date` instances don't have an enumerable `constructor` property, when possible.
+
+4.1.0
+  - Update `eslint`
+  - Improve type checks: `Array.isArray`, `isRegex`
+  - Replace `isRegex`/`isString`/`isCallable` checks with inlined versions from npm modules
+  - Note which ES abstract methods are replaceable via `es-abstract`
+  - Run `travis-ci` tests on `iojs`!
+
+4.0.6
+  - Update `jscs`, `uglify-js`, add `eslint`
+  - es5-sham: fix Object.defineProperty to not check for own properties (#211)
+  - Fix Array#splice bug in Safari 5 (#284)
+  - Fix `Object.keys` issue with boxed primitives with extra properties in older browsers. (#242, #285)
+
+4.0.5
+  - Update `jscs` so tests pass
+
+4.0.4
+  - Style/indentation/whitespace cleanups.
+  - README tweaks
+
+4.0.3
+  - Fix keywords (#268)
+  - add some Date tests
+  - Note in README that the es5-sham requires the es5-shim (https://github.com/es-shims/es5-shim/issues/256#issuecomment-52875710)
+
+4.0.2
+  - Start including version numbers in minified files (#267)
+
+4.0.1
+  - Fix legacy arguments object detection in Object.keys (#260)
+
+4.0.0
+  - No longer shim the ES5-spec behavior of splice when `deleteCount` is omitted - since no engines implement it, and ES6 changes it. (#255)
+  - Use Object.defineProperty where available, so that polyfills are non-enumerable when possible (#250)
+  - lots of internal refactoring
+  - Fixed a bug referencing String#indexOf and String#lastIndexOf before polyfilling it (#253, #254)
+
+3.4.0
+  - Removed nonstandard SpiderMonkey extension to Array#splice - when `deleteCount` is omitted, it's now treated as 0. (#192, #239)
+  - Fix Object.keys with Arguments objects in Safari 5.0
+  - Now shimming String#split in Opera 10.6
+  - Avoid using "toString" as a variable name, since that breaks Opera
+  - Internal implementation and test cleanups
+
+3.3.2
+ - Remove an internal "bind" call, which should make the shim a bit faster
+ - Fix a bug with object boxing in Array#reduceRight that was failing a test in IE 6
+
+3.3.1
+ - Fixing an Array#splice bug in IE 6/7
+ - cleaning up Array#splice tests
+
+3.3.0
+ - Fix Array#reduceRight in node 0.6 and older browsers (#238)
+
+3.2.0
+ - Fix es5-sham UMD definition to work properly with AMD (#237)
+ - Ensure that Array methods do not autobox context in strict mode (#233)
+
+3.1.1
+ - Update minified files (#231)
+
+3.1.0
+ - Fix String#replace in Firefox up through 29 (#228)
+
+3.0.2
+ - Fix `Function#bind` in IE 7 and 8 (#224, #225, #226)
+
+3.0.1
+ - Version bump to ensure npm has newest minified assets
+
+3.0.0
+ - es5-sham: fix `Object.getPrototypeOf` and `Object.getOwnPropertyDescriptor` for Opera Mini
+ - Better override noncompliant native ES5 methods: `Array#forEach`, `Array#map`, `Array#filter`, `Array#every`, `Array#some`, `Array#reduce`, `Date.parse`, `String#trim`
+ - Added spec-compliant shim for `parseInt`
+ - Ensure `Object.keys` handles more edge cases with `arguments` objects and boxed primitives
+ - Improve minification of builds
+
+2.3.0
+ - parseInt is now properly shimmed in ES3 browsers to default the radix
+ - update URLs to point to the new organization
+
+2.2.0
+ - Function.prototype.bind shim now reports correct length on a bound function
+ - fix node 0.6.x v8 bug in Array#forEach
+ - test improvements
+
+2.1.0
+ - Object.create fixes
+ - tweaks to the Object.defineProperties shim
+
+2.0.0
+ - Separate reliable shims from dubious shims (shams).
+
+1.2.10
+ - Group-effort Style Cleanup
+ - Took a stab at fixing Object.defineProperty on IE8 without
+   bad side-effects. (@hax)
+ - Object.isExtensible no longer fakes it. (@xavierm)
+ - Date.prototype.toISOString no longer deals with partial
+   ISO dates, per spec (@kitcambridge)
+ - More (mostly from @bryanforbes)
+
+1.2.9
+ - Corrections to toISOString by @kitcambridge
+ - Fixed three bugs in array methods revealed by Jasmine tests.
+ - Cleaned up Function.prototype.bind with more fixes and tests from
+   @bryanforbes.
+
+1.2.8
+ - Actually fixed problems with Function.prototype.bind, and regressions
+   from 1.2.7 (@bryanforbes, @jdalton #36)
+
+1.2.7 - REGRESSED
+ - Fixed problems with Function.prototype.bind when called as a constructor.
+   (@jdalton #36)
+
+1.2.6
+ - Revised Date.parse to match ES 5.1 (kitcambridge)
+
+1.2.5
+ - Fixed a bug for padding it Date..toISOString (tadfisher issue #33)
+
+1.2.4
+ - Fixed a descriptor bug in Object.defineProperty (raynos)
+
+1.2.3
+ - Cleaned up RequireJS and <script> boilerplate
+
+1.2.2
+ - Changed reduce to follow the letter of the spec with regard to having and
+   owning properties.
+ - Fixed a bug where RegExps pass as Functions in some engines in reduce.
+
+1.2.1
+ - Adding few fixes to make jshint happy.
+ - Fix for issue #12, function expressions can cause scoping issues in IE.
+ - NPM will minify on install or when `npm run-script install` is executed.
+ - Adding .gitignore to avoid publishing dev dependencies.
+
+1.2.0
+ - Making script loadable as AMD module.
+ - Adding `indexOf` to the list of safe shims.
+
+1.1.0
+ - Added support for accessor properties where possible (which is all browsers
+   except IE).
+ - Stop exposing bound function's (that are returned by
+   `Function.prototype.bind`) internal properties (`bound, boundTo, boundArgs`)
+   as in some cases (when using facade objects for example) capabilities of the
+   enclosed functions will be leaked.
+ - `Object.create` now explicitly sets `__proto__` property to guarantee
+   correct behavior of `Object.getPrototypeOf`'s on all objects created using
+   `Object.create`.
+ - Switched to `===` from `==` where possible as it's slightly faster on older
+   browsers that are target of this lib.
+ - Added names to all anonymous functions to have a better stack traces.
+
+1.0.0
+ - fixed Date.toISODate, using UTC accessors, as in
+   http://code.google.com/p/v8/source/browse/trunk/src/date.js?r=6120#986
+   (arian)
+
+0.0.4
+ - Revised Object.getPrototypeOf to work in more cases
+   in response to http://ejohn.org/blog/objectgetprototypeof/
+   [issue #2] (fschaefer)
+
+0.0.3
+ - Fixed typos in Object.keys (samsonjs)
+
+0.0.2
+   Per kangax's recommendations:
+ - faster Object.create(null)
+ - fixed a function-scope function declaration statement in Object.create
+
+0.0.1
+ - fixed Object.create(null), in so far as that's possible
+ - reworked Rhino Object.freeze(Function) bug detector and patcher
+
+0.0.0
+ - forked from narwhal-lib
+
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 0000000..43ebf77
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,27 @@
+
+-   kriskowal Kris Kowal Copyright (C) 2009-2011 MIT License
+-   tlrobinson Tom Robinson Copyright (C) 2009-2010 MIT License (Narwhal
+    Project)
+-   dantman Daniel Friesen Copyright (C) 2010 XXX TODO License or CLA
+-   fschaefer Florian Schäfer Copyright (C) 2010 MIT License
+-   Gozala Irakli Gozalishvili Copyright (C) 2010 MIT License
+-   kitcambridge Kit Cambridge Copyright (C) 2011 MIT License
+-   kossnocorp Sasha Koss XXX TODO License or CLA
+-   bryanforbes Bryan Forbes XXX TODO License or CLA
+-   killdream Quildreen Motta Copyright (C) 2011 MIT Licence
+-   michaelficarra Michael Ficarra Copyright (C) 2011 3-clause BSD
+    License
+-   sharkbrainguy Gerard Paapu Copyright (C) 2011 MIT License
+-   bbqsrc Brendan Molloy (C) 2011 Creative Commons Zero (public domain)
+-   iwyg XXX TODO License or CLA
+-   DomenicDenicola Domenic Denicola Copyright (C) 2011 MIT License
+-   xavierm02 Montillet Xavier Copyright (C) 2011 MIT License
+-   Raynos Jake Verbaten Copyright (C) 2011 MIT Licence
+-   samsonjs Sami Samhuri Copyright (C) 2010 MIT License
+-   rwldrn Rick Waldron Copyright (C) 2011 MIT License
+-   lexer Alexey Zakharov XXX TODO License or CLA
+-   280 North Inc. (Now Motorola LLC, a subsidiary of Google Inc.)
+    Copyright (C) 2009 MIT License
+-   Steven Levithan Copyright (C) 2012 MIT License
+-   Jordan Harband (C) 2013 MIT License
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3dbd7de
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (C) 2009-2014 Kristopher Michael Kowal and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5afc70d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,63 @@
+# Since we rely on paths relative to the makefile location, abort if make isn't being run from there.
+$(if $(findstring /,$(MAKEFILE_LIST)),$(error Please only invoke this makefile from the directory it resides in))
+
+	# The files that need updating when incrementing the version number.
+VERSIONED_FILES := *.js *.json *.map README*
+
+
+# Add the local npm packages' bin folder to the PATH, so that `make` can find them, when invoked directly.
+# Note that rather than using `$(npm bin)` the 'node_modules/.bin' path component is hard-coded, so that invocation works even from an environment
+# where npm is (temporarily) unavailable due to having deactivated an nvm instance loaded into the calling shell in order to avoid interference with tests.
+export PATH := $(shell printf '%s' "$$PWD/node_modules/.bin:$$PATH")
+UTILS := semver
+# Make sure that all required utilities can be located.
+UTIL_CHECK := $(or $(shell PATH="$(PATH)" which $(UTILS) >/dev/null && echo 'ok'),$(error Did you forget to run `npm install` after cloning the repo? At least one of the required supporting utilities not found: $(UTILS)))
+
+# Default target (by virtue of being the first non '.'-prefixed in the file).
+.PHONY: _no-target-specified
+_no-target-specified:
+	$(error Please specify the target to make - `make list` shows targets. Alternatively, use `npm test` to run the default tests; `npm run` shows all tests)
+
+# Lists all targets defined in this makefile.
+.PHONY: list
+list:
+	@$(MAKE) -pRrn : -f $(MAKEFILE_LIST) 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | command grep -v -e '^[^[:alnum:]]' -e '^$@$$command ' | sort
+
+# All-tests target: invokes the specified test suites for ALL shells defined in $(SHELLS).
+.PHONY: test
+test:
+	@npm test
+
+.PHONY: _ensure-tag
+_ensure-tag:
+ifndef TAG
+	$(error Please invoke with `make TAG=<new-version> release`, where <new-version> is either an increment specifier (patch, minor, major, prepatch, preminor, premajor, prerelease), or an explicit major.minor.patch version number)
+endif
+
+CHANGELOG_ERROR = $(error No CHANGES specified)
+.PHONY: _ensure-changelog
+_ensure-changelog:
+	@ (git status -sb --porcelain | command grep -E '^( M|[MA] ) CHANGES' > /dev/null) || (echo no CHANGES specified && exit 2)
+
+# Ensures that the git workspace is clean.
+.PHONY: _ensure-clean
+_ensure-clean:
+	@[ -z "$$((git status --porcelain --untracked-files=no || echo err) | command grep -v 'CHANGES')" ] || { echo "Workspace is not clean; please commit changes first." >&2; exit 2; }
+
+# Makes a release; invoke with `make TAG=<versionOrIncrementSpec> release`.
+.PHONY: release
+release: _ensure-tag _ensure-changelog _ensure-clean
+	@old_ver=`git describe --abbrev=0 --tags --match 'v[0-9]*.[0-9]*.[0-9]*'` || { echo "Failed to determine current version." >&2; exit 1; }; old_ver=$${old_ver#v}; \
+	 new_ver=`echo "$(TAG)" | sed 's/^v//'`; new_ver=$${new_ver:-patch}; \
+	 if printf "$$new_ver" | command grep -q '^[0-9]'; then \
+	   semver "$$new_ver" >/dev/null || { echo 'Invalid version number specified: $(TAG) - must be major.minor.patch' >&2; exit 2; }; \
+	   semver -r "> $$old_ver" "$$new_ver" >/dev/null || { echo 'Invalid version number specified: $(TAG) - must be HIGHER than current one.' >&2; exit 2; } \
+	 else \
+	   new_ver=`semver -i "$$new_ver" "$$old_ver"` || { echo 'Invalid version-increment specifier: $(TAG)' >&2; exit 2; } \
+	 fi; \
+	 printf "=== Bumping version **$$old_ver** to **$$new_ver** before committing and tagging:\n=== TYPE 'proceed' TO PROCEED, anything else to abort: " && read response && [ "$$response" = 'proceed' ] || { echo 'Aborted.' >&2; exit 2; };  \
+     npm run minify; \
+     replace "$$old_ver" "$$new_ver" -- $(VERSIONED_FILES) && \
+     replace "blob/master" "blob/v$$new_ver" -- *.min.js && \
+	 git commit -m "v$$new_ver" $(VERSIONED_FILES) CHANGES && \
+	 git tag -a -m "v$$new_ver" "v$$new_ver"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..81d6a87
--- /dev/null
+++ b/README.md
@@ -0,0 +1,167 @@
+#es5-shim <sup>[![Version Badge][npm-version-svg]][npm-url]</sup>
+
+[![npm badge][npm-badge-png]][npm-url]
+
+[![Build Status][travis-svg]][travis-url]
+[![dependency status][deps-svg]][deps-url]
+[![dev dependency status][dev-deps-svg]][dev-deps-url]
+
+`es5-shim.js` and `es5-shim.min.js` monkey-patch a JavaScript context to
+contain all EcmaScript 5 methods that can be faithfully emulated with a
+legacy JavaScript engine.
+
+`es5-sham.js` and `es5-sham.min.js` monkey-patch other ES5 methods as
+closely as possible.  For these methods, as closely as possible to ES5
+is not very close.  Many of these shams are intended only to allow code
+to be written to ES5 without causing run-time errors in older engines.
+In many cases, this means that these shams cause many ES5 methods to
+silently fail.  Decide carefully whether this is what you want.
+**Note:** `es5-sham.js` requires `es5-shim.js` to be able to work properly.
+
+
+## Tests
+
+The tests are written with the Jasmine BDD test framework.
+To run the tests, navigate to <root-folder>/tests/ , or,
+simply `npm install` and `npm test`.
+
+## Shims
+
+### Complete tests ###
+
+* Array.prototype.every
+* Array.prototype.filter
+* Array.prototype.forEach
+* Array.prototype.indexOf
+* Array.prototype.lastIndexOf
+* Array.prototype.map
+* Array.prototype.some
+* Array.prototype.reduce
+* Array.prototype.reduceRight
+* Array.isArray
+* Date.now
+* Date.prototype.toJSON
+* Function.prototype.bind
+    * :warning: Caveat: the bound function has a prototype property.
+    * :warning: Caveat: bound functions do not try too hard to keep you
+      from manipulating their ``arguments`` and ``caller`` properties.
+    * :warning: Caveat: bound functions don't have checks in ``call`` and
+      ``apply`` to avoid executing as a constructor.
+* Number.prototype.toFixed
+* Object.keys
+* String.prototype.split
+* String.prototype.trim
+* String.prototype.replace
+    * Firefox (through v29) natively handles capturing groups incorrectly.
+* Date.parse (for ISO parsing)
+* Date.prototype.toISOString
+* parseInt
+
+## Shams
+
+* :warning: Object.create
+
+    For the case of simply "begetting" an object that inherits
+    prototypically from another, this should work fine across legacy
+    engines.
+
+    :warning: The second argument is passed to Object.defineProperties
+    which will probably fail either silently or with extreme prejudice.
+
+* :warning: Object.getPrototypeOf
+
+    This will return "undefined" in some cases.  It uses `__proto__` if
+    it's available.  Failing that, it uses constructor.prototype, which
+    depends on the constructor property of the object's prototype having
+    not been replaced.  If your object was created like this, it won't
+    work:
+
+        function Foo() {
+        }
+        Foo.prototype = {};
+
+    Because the prototype reassignment destroys the constructor
+    property.
+
+    This will work for all objects that were created using
+    `Object.create` implemented with this library.
+
+* :warning: Object.getOwnPropertyNames
+
+    This method uses Object.keys, so it will not be accurate on legacy
+    engines.
+
+* Object.isSealed
+
+    Returns "false" in all legacy engines for all objects, which is
+    conveniently guaranteed to be accurate.
+
+* Object.isFrozen
+
+    Returns "false" in all legacy engines for all objects, which is
+    conveniently guaranteed to be accurate.
+
+* Object.isExtensible
+
+    Works like a charm, by trying very hard to extend the object then
+    redacting the extension.
+
+### May fail
+
+* :warning: Object.getOwnPropertyDescriptor
+
+    The behavior of this shim does not conform to ES5.  It should
+    probably not be used at this time, until its behavior has been
+    reviewed and been confirmed to be useful in legacy engines.
+
+* :warning: Object.defineProperty
+
+    In the worst of circumstances, IE 8 provides a version of this
+    method that only works on DOM objects.  This sham will not be
+    installed.  The given version of `defineProperty` will throw an
+    exception if used on non-DOM objects.
+
+    In slightly better circumstances, this method will silently fail to
+    set "writable", "enumerable", and "configurable" properties.
+
+    Providing a getter or setter with "get" or "set" on a descriptor
+    will silently fail on engines that lack "__defineGetter__" and
+    "__defineSetter__", which include all versions of IE.
+
+    https://github.com/es-shims/es5-shim/issues#issue/5
+
+* :warning: Object.defineProperties
+
+    This uses the Object.defineProperty shim.
+
+* Object.seal
+
+    Silently fails on all legacy engines.  This should be
+    fine unless you are depending on the safety and security
+    provisions of this method, which you cannot possibly
+    obtain in legacy engines.
+
+* Object.freeze
+
+    Silently fails on all legacy engines.  This should be
+    fine unless you are depending on the safety and security
+    provisions of this method, which you cannot possibly
+    obtain in legacy engines.
+
+* Object.preventExtensions
+
+    Silently fails on all legacy engines.  This should be
+    fine unless you are depending on the safety and security
+    provisions of this method, which you cannot possibly
+    obtain in legacy engines.
+
+[npm-url]: https://npmjs.org/package/es5-shim
+[npm-version-svg]: http://versionbadg.es/es-shims/es5-shim.svg
+[travis-svg]: https://travis-ci.org/es-shims/es5-shim.svg
+[travis-url]: https://travis-ci.org/es-shims/es5-shim
+[deps-svg]: https://david-dm.org/es-shims/es5-shim.svg
+[deps-url]: https://david-dm.org/es-shims/es5-shim
+[dev-deps-svg]: https://david-dm.org/es-shims/es5-shim/dev-status.svg
+[dev-deps-url]: https://david-dm.org/es-shims/es5-shim#info=devDependencies
+[npm-badge-png]: https://nodei.co/npm/es5-shim.png?downloads=true&stars=true
+
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..dcc0b29
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,33 @@
+{
+    "name": "es5-shim",
+    "main": "es5-shim.js",
+    "repository": {
+        "type": "git",
+        "url": "git://github.com/es-shims/es5-shim"
+    },
+    "homepage": "https://github.com/es-shims/es5-shim",
+    "authors": [
+        "Kris Kowal <kris at cixar.com> (http://github.com/kriskowal/)",
+        "Sami Samhuri <sami.samhuri at gmail.com> (http://samhuri.net/)",
+        "Florian Schäfer <florian.schaefer at gmail.com> (http://github.com/fschaefer)",
+        "Irakli Gozalishvili <rfobic at gmail.com> (http://jeditoolkit.com)",
+        "Kit Cambridge <kitcambridge at gmail.com> (http://kitcambridge.github.com)",
+        "Jordan Harband <ljharb at gmail.com> (https://github.com/ljharb/)"
+    ],
+    "description": "ECMAScript 5 compatibility shims for legacy JavaScript engines",
+    "keywords": [
+        "shim",
+        "es5",
+        "es5 shim",
+        "javascript",
+        "ecmascript",
+        "polyfill"
+    ],
+    "license": "MIT",
+    "ignore": [
+        "**/.*",
+        "node_modules",
+        "bower_components",
+        "tests"
+    ]
+}
diff --git a/component.json b/component.json
new file mode 100644
index 0000000..477eeca
--- /dev/null
+++ b/component.json
@@ -0,0 +1,19 @@
+{
+    "name": "es5-shim",
+    "repo": "es-shims/es5-shim",
+    "description": "ECMAScript 5 compatibility shims for legacy JavaScript engines",
+    "version": "v4.1.14",
+    "keywords": [
+        "shim",
+        "es5",
+        "es5 shim",
+        "javascript",
+        "ecmascript",
+        "polyfill"
+    ],
+    "license": "MIT",
+    "main": "es5-shim.js",
+    "scripts": [
+        "es5-shim.js"
+    ]
+}
diff --git a/es5-sham.js b/es5-sham.js
new file mode 100644
index 0000000..42e9c98
--- /dev/null
+++ b/es5-sham.js
@@ -0,0 +1,556 @@
+/*!
+ * https://github.com/es-shims/es5-shim
+ * @license es5-shim Copyright 2009-2015 by contributors, MIT License
+ * see https://github.com/es-shims/es5-shim/blob/master/LICENSE
+ */
+
+// vim: ts=4 sts=4 sw=4 expandtab
+
+// Add semicolon to prevent IIFE from being passed as argument to concatenated code.
+;
+
+// UMD (Universal Module Definition)
+// see https://github.com/umdjs/umd/blob/master/returnExports.js
+(function (root, factory) {
+    'use strict';
+
+    /* global define, exports, module */
+    if (typeof define === 'function' && define.amd) {
+        // AMD. Register as an anonymous module.
+        define(factory);
+    } else if (typeof exports === 'object') {
+        // Node. Does not work with strict CommonJS, but
+        // only CommonJS-like enviroments that support module.exports,
+        // like Node.
+        module.exports = factory();
+    } else {
+        // Browser globals (root is window)
+        root.returnExports = factory();
+  }
+}(this, function () {
+
+var call = Function.prototype.call;
+var prototypeOfObject = Object.prototype;
+var owns = call.bind(prototypeOfObject.hasOwnProperty);
+var propertyIsEnumerable = call.bind(prototypeOfObject.propertyIsEnumerable);
+var toStr = call.bind(prototypeOfObject.toString);
+
+// If JS engine supports accessors creating shortcuts.
+var defineGetter;
+var defineSetter;
+var lookupGetter;
+var lookupSetter;
+var supportsAccessors = owns(prototypeOfObject, '__defineGetter__');
+if (supportsAccessors) {
+    /* eslint-disable no-underscore-dangle */
+    defineGetter = call.bind(prototypeOfObject.__defineGetter__);
+    defineSetter = call.bind(prototypeOfObject.__defineSetter__);
+    lookupGetter = call.bind(prototypeOfObject.__lookupGetter__);
+    lookupSetter = call.bind(prototypeOfObject.__lookupSetter__);
+    /* eslint-enable no-underscore-dangle */
+}
+
+// ES5 15.2.3.2
+// http://es5.github.com/#x15.2.3.2
+if (!Object.getPrototypeOf) {
+    // https://github.com/es-shims/es5-shim/issues#issue/2
+    // http://ejohn.org/blog/objectgetprototypeof/
+    // recommended by fschaefer on github
+    //
+    // sure, and webreflection says ^_^
+    // ... this will nerever possibly return null
+    // ... Opera Mini breaks here with infinite loops
+    Object.getPrototypeOf = function getPrototypeOf(object) {
+        /* eslint-disable no-proto */
+        var proto = object.__proto__;
+        /* eslint-enable no-proto */
+        if (proto || proto === null) {
+            return proto;
+        } else if (toStr(object.constructor) === '[object Function]') {
+            return object.constructor.prototype;
+        } else if (!(object instanceof Object)) {
+          // Correctly return null for Objects created with `Object.create(null)`
+          // (shammed or native) or `{ __proto__: null}`.  Also returns null for
+          // cross-realm objects on browsers that lack `__proto__` support (like
+          // IE <11), but that's the best we can do.
+          return null;
+        } else {
+          return prototypeOfObject;
+        }
+    };
+}
+
+// ES5 15.2.3.3
+// http://es5.github.com/#x15.2.3.3
+
+var doesGetOwnPropertyDescriptorWork = function doesGetOwnPropertyDescriptorWork(object) {
+    try {
+        object.sentinel = 0;
+        return Object.getOwnPropertyDescriptor(object, 'sentinel').value === 0;
+    } catch (exception) {
+        return false;
+    }
+};
+
+// check whether getOwnPropertyDescriptor works if it's given. Otherwise, shim partially.
+if (Object.defineProperty) {
+    var getOwnPropertyDescriptorWorksOnObject = doesGetOwnPropertyDescriptorWork({});
+    var getOwnPropertyDescriptorWorksOnDom = typeof document === 'undefined' ||
+    doesGetOwnPropertyDescriptorWork(document.createElement('div'));
+    if (!getOwnPropertyDescriptorWorksOnDom || !getOwnPropertyDescriptorWorksOnObject) {
+        var getOwnPropertyDescriptorFallback = Object.getOwnPropertyDescriptor;
+    }
+}
+
+if (!Object.getOwnPropertyDescriptor || getOwnPropertyDescriptorFallback) {
+    var ERR_NON_OBJECT = 'Object.getOwnPropertyDescriptor called on a non-object: ';
+
+    /* eslint-disable no-proto */
+    Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(object, property) {
+        if ((typeof object !== 'object' && typeof object !== 'function') || object === null) {
+            throw new TypeError(ERR_NON_OBJECT + object);
+        }
+
+        // make a valiant attempt to use the real getOwnPropertyDescriptor
+        // for I8's DOM elements.
+        if (getOwnPropertyDescriptorFallback) {
+            try {
+                return getOwnPropertyDescriptorFallback.call(Object, object, property);
+            } catch (exception) {
+                // try the shim if the real one doesn't work
+            }
+        }
+
+        var descriptor;
+
+        // If object does not owns property return undefined immediately.
+        if (!owns(object, property)) {
+            return descriptor;
+        }
+
+        // If object has a property then it's for sure `configurable`, and
+        // probably `enumerable`. Detect enumerability though.
+        descriptor = {
+            enumerable: propertyIsEnumerable(object, property),
+            configurable: true
+        };
+
+        // If JS engine supports accessor properties then property may be a
+        // getter or setter.
+        if (supportsAccessors) {
+            // Unfortunately `__lookupGetter__` will return a getter even
+            // if object has own non getter property along with a same named
+            // inherited getter. To avoid misbehavior we temporary remove
+            // `__proto__` so that `__lookupGetter__` will return getter only
+            // if it's owned by an object.
+            var prototype = object.__proto__;
+            var notPrototypeOfObject = object !== prototypeOfObject;
+            // avoid recursion problem, breaking in Opera Mini when
+            // Object.getOwnPropertyDescriptor(Object.prototype, 'toString')
+            // or any other Object.prototype accessor
+            if (notPrototypeOfObject) {
+                object.__proto__ = prototypeOfObject;
+            }
+
+            var getter = lookupGetter(object, property);
+            var setter = lookupSetter(object, property);
+
+            if (notPrototypeOfObject) {
+                // Once we have getter and setter we can put values back.
+                object.__proto__ = prototype;
+            }
+
+            if (getter || setter) {
+                if (getter) {
+                    descriptor.get = getter;
+                }
+                if (setter) {
+                    descriptor.set = setter;
+                }
+                // If it was accessor property we're done and return here
+                // in order to avoid adding `value` to the descriptor.
+                return descriptor;
+            }
+        }
+
+        // If we got this far we know that object has an own property that is
+        // not an accessor so we set it as a value and return descriptor.
+        descriptor.value = object[property];
+        descriptor.writable = true;
+        return descriptor;
+    };
+    /* eslint-enable no-proto */
+}
+
+// ES5 15.2.3.4
+// http://es5.github.com/#x15.2.3.4
+if (!Object.getOwnPropertyNames) {
+    Object.getOwnPropertyNames = function getOwnPropertyNames(object) {
+        return Object.keys(object);
+    };
+}
+
+// ES5 15.2.3.5
+// http://es5.github.com/#x15.2.3.5
+if (!Object.create) {
+
+    // Contributed by Brandon Benvie, October, 2012
+    var createEmpty;
+    var supportsProto = !({ __proto__: null } instanceof Object);
+                        // the following produces false positives
+                        // in Opera Mini => not a reliable check
+                        // Object.prototype.__proto__ === null
+
+    // Check for document.domain and active x support
+    // No need to use active x approach when document.domain is not set
+    // see https://github.com/es-shims/es5-shim/issues/150
+    // variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346
+    /* global ActiveXObject */
+    var shouldUseActiveX = function shouldUseActiveX() {
+        // return early if document.domain not set
+        if (!document.domain) {
+            return false;
+        }
+
+        try {
+            return !!new ActiveXObject('htmlfile');
+        } catch (exception) {
+            return false;
+        }
+    };
+
+    // This supports IE8 when document.domain is used
+    // see https://github.com/es-shims/es5-shim/issues/150
+    // variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346
+    var getEmptyViaActiveX = function getEmptyViaActiveX() {
+        var empty;
+        var xDoc;
+
+        xDoc = new ActiveXObject('htmlfile');
+
+        xDoc.write('<script><\/script>');
+        xDoc.close();
+
+        empty = xDoc.parentWindow.Object.prototype;
+        xDoc = null;
+
+        return empty;
+    };
+
+    // The original implementation using an iframe
+    // before the activex approach was added
+    // see https://github.com/es-shims/es5-shim/issues/150
+    var getEmptyViaIFrame = function getEmptyViaIFrame() {
+        var iframe = document.createElement('iframe');
+        var parent = document.body || document.documentElement;
+        var empty;
+
+        iframe.style.display = 'none';
+        parent.appendChild(iframe);
+        /* eslint-disable no-script-url */
+        iframe.src = 'javascript:';
+        /* eslint-enable no-script-url */
+
+        empty = iframe.contentWindow.Object.prototype;
+        parent.removeChild(iframe);
+        iframe = null;
+
+        return empty;
+    };
+
+    /* global document */
+    if (supportsProto || typeof document === 'undefined') {
+        createEmpty = function () {
+            return { __proto__: null };
+        };
+    } else {
+        // In old IE __proto__ can't be used to manually set `null`, nor does
+        // any other method exist to make an object that inherits from nothing,
+        // aside from Object.prototype itself. Instead, create a new global
+        // object and *steal* its Object.prototype and strip it bare. This is
+        // used as the prototype to create nullary objects.
+        createEmpty = function () {
+            // Determine which approach to use
+            // see https://github.com/es-shims/es5-shim/issues/150
+            var empty = shouldUseActiveX() ? getEmptyViaActiveX() : getEmptyViaIFrame();
+
+            delete empty.constructor;
+            delete empty.hasOwnProperty;
+            delete empty.propertyIsEnumerable;
+            delete empty.isPrototypeOf;
+            delete empty.toLocaleString;
+            delete empty.toString;
+            delete empty.valueOf;
+
+            var Empty = function Empty() {};
+            Empty.prototype = empty;
+            // short-circuit future calls
+            createEmpty = function () {
+                return new Empty();
+            };
+            return new Empty();
+        };
+    }
+
+    Object.create = function create(prototype, properties) {
+
+        var object;
+        var Type = function Type() {}; // An empty constructor.
+
+        if (prototype === null) {
+            object = createEmpty();
+        } else {
+            if (typeof prototype !== 'object' && typeof prototype !== 'function') {
+                // In the native implementation `parent` can be `null`
+                // OR *any* `instanceof Object`  (Object|Function|Array|RegExp|etc)
+                // Use `typeof` tho, b/c in old IE, DOM elements are not `instanceof Object`
+                // like they are in modern browsers. Using `Object.create` on DOM elements
+                // is...err...probably inappropriate, but the native version allows for it.
+                throw new TypeError('Object prototype may only be an Object or null'); // same msg as Chrome
+            }
+            Type.prototype = prototype;
+            object = new Type();
+            // IE has no built-in implementation of `Object.getPrototypeOf`
+            // neither `__proto__`, but this manually setting `__proto__` will
+            // guarantee that `Object.getPrototypeOf` will work as expected with
+            // objects created using `Object.create`
+            /* eslint-disable no-proto */
+            object.__proto__ = prototype;
+            /* eslint-enable no-proto */
+        }
+
+        if (properties !== void 0) {
+            Object.defineProperties(object, properties);
+        }
+
+        return object;
+    };
+}
+
+// ES5 15.2.3.6
+// http://es5.github.com/#x15.2.3.6
+
+// Patch for WebKit and IE8 standard mode
+// Designed by hax <hax.github.com>
+// related issue: https://github.com/es-shims/es5-shim/issues#issue/5
+// IE8 Reference:
+//     http://msdn.microsoft.com/en-us/library/dd282900.aspx
+//     http://msdn.microsoft.com/en-us/library/dd229916.aspx
+// WebKit Bugs:
+//     https://bugs.webkit.org/show_bug.cgi?id=36423
+
+var doesDefinePropertyWork = function doesDefinePropertyWork(object) {
+    try {
+        Object.defineProperty(object, 'sentinel', {});
+        return 'sentinel' in object;
+    } catch (exception) {
+        return false;
+    }
+};
+
+// check whether defineProperty works if it's given. Otherwise,
+// shim partially.
+if (Object.defineProperty) {
+    var definePropertyWorksOnObject = doesDefinePropertyWork({});
+    var definePropertyWorksOnDom = typeof document === 'undefined' ||
+        doesDefinePropertyWork(document.createElement('div'));
+    if (!definePropertyWorksOnObject || !definePropertyWorksOnDom) {
+        var definePropertyFallback = Object.defineProperty,
+            definePropertiesFallback = Object.defineProperties;
+    }
+}
+
+if (!Object.defineProperty || definePropertyFallback) {
+    var ERR_NON_OBJECT_DESCRIPTOR = 'Property description must be an object: ';
+    var ERR_NON_OBJECT_TARGET = 'Object.defineProperty called on non-object: ';
+    var ERR_ACCESSORS_NOT_SUPPORTED = 'getters & setters can not be defined on this javascript engine';
+
+    Object.defineProperty = function defineProperty(object, property, descriptor) {
+        if ((typeof object !== 'object' && typeof object !== 'function') || object === null) {
+            throw new TypeError(ERR_NON_OBJECT_TARGET + object);
+        }
+        if ((typeof descriptor !== 'object' && typeof descriptor !== 'function') || descriptor === null) {
+            throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor);
+        }
+        // make a valiant attempt to use the real defineProperty
+        // for I8's DOM elements.
+        if (definePropertyFallback) {
+            try {
+                return definePropertyFallback.call(Object, object, property, descriptor);
+            } catch (exception) {
+                // try the shim if the real one doesn't work
+            }
+        }
+
+        // If it's a data property.
+        if ('value' in descriptor) {
+            // fail silently if 'writable', 'enumerable', or 'configurable'
+            // are requested but not supported
+            /*
+            // alternate approach:
+            if ( // can't implement these features; allow false but not true
+                ('writable' in descriptor && !descriptor.writable) ||
+                ('enumerable' in descriptor && !descriptor.enumerable) ||
+                ('configurable' in descriptor && !descriptor.configurable)
+            ))
+                throw new RangeError(
+                    'This implementation of Object.defineProperty does not support configurable, enumerable, or writable.'
+                );
+            */
+
+            if (supportsAccessors && (lookupGetter(object, property) || lookupSetter(object, property))) {
+                // As accessors are supported only on engines implementing
+                // `__proto__` we can safely override `__proto__` while defining
+                // a property to make sure that we don't hit an inherited
+                // accessor.
+                /* eslint-disable no-proto */
+                var prototype = object.__proto__;
+                object.__proto__ = prototypeOfObject;
+                // Deleting a property anyway since getter / setter may be
+                // defined on object itself.
+                delete object[property];
+                object[property] = descriptor.value;
+                // Setting original `__proto__` back now.
+                object.__proto__ = prototype;
+                /* eslint-enable no-proto */
+            } else {
+                object[property] = descriptor.value;
+            }
+        } else {
+            if (!supportsAccessors && (('get' in descriptor) || ('set' in descriptor))) {
+                throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
+            }
+            // If we got that far then getters and setters can be defined !!
+            if ('get' in descriptor) {
+                defineGetter(object, property, descriptor.get);
+            }
+            if ('set' in descriptor) {
+                defineSetter(object, property, descriptor.set);
+            }
+        }
+        return object;
+    };
+}
+
+// ES5 15.2.3.7
+// http://es5.github.com/#x15.2.3.7
+if (!Object.defineProperties || definePropertiesFallback) {
+    Object.defineProperties = function defineProperties(object, properties) {
+        // make a valiant attempt to use the real defineProperties
+        if (definePropertiesFallback) {
+            try {
+                return definePropertiesFallback.call(Object, object, properties);
+            } catch (exception) {
+                // try the shim if the real one doesn't work
+            }
+        }
+
+        Object.keys(properties).forEach(function (property) {
+            if (property !== '__proto__') {
+                Object.defineProperty(object, property, properties[property]);
+            }
+        });
+        return object;
+    };
+}
+
+// ES5 15.2.3.8
+// http://es5.github.com/#x15.2.3.8
+if (!Object.seal) {
+    Object.seal = function seal(object) {
+        if (Object(object) !== object) {
+            throw new TypeError('Object.seal can only be called on Objects.');
+        }
+        // this is misleading and breaks feature-detection, but
+        // allows "securable" code to "gracefully" degrade to working
+        // but insecure code.
+        return object;
+    };
+}
+
+// ES5 15.2.3.9
+// http://es5.github.com/#x15.2.3.9
+if (!Object.freeze) {
+    Object.freeze = function freeze(object) {
+        if (Object(object) !== object) {
+            throw new TypeError('Object.freeze can only be called on Objects.');
+        }
+        // this is misleading and breaks feature-detection, but
+        // allows "securable" code to "gracefully" degrade to working
+        // but insecure code.
+        return object;
+    };
+}
+
+// detect a Rhino bug and patch it
+try {
+    Object.freeze(function () {});
+} catch (exception) {
+    Object.freeze = (function (freezeObject) {
+        return function freeze(object) {
+            if (typeof object === 'function') {
+                return object;
+            } else {
+                return freezeObject(object);
+            }
+        };
+    }(Object.freeze));
+}
+
+// ES5 15.2.3.10
+// http://es5.github.com/#x15.2.3.10
+if (!Object.preventExtensions) {
+    Object.preventExtensions = function preventExtensions(object) {
+        if (Object(object) !== object) {
+            throw new TypeError('Object.preventExtensions can only be called on Objects.');
+        }
+        // this is misleading and breaks feature-detection, but
+        // allows "securable" code to "gracefully" degrade to working
+        // but insecure code.
+        return object;
+    };
+}
+
+// ES5 15.2.3.11
+// http://es5.github.com/#x15.2.3.11
+if (!Object.isSealed) {
+    Object.isSealed = function isSealed(object) {
+        if (Object(object) !== object) {
+            throw new TypeError('Object.isSealed can only be called on Objects.');
+        }
+        return false;
+    };
+}
+
+// ES5 15.2.3.12
+// http://es5.github.com/#x15.2.3.12
+if (!Object.isFrozen) {
+    Object.isFrozen = function isFrozen(object) {
+        if (Object(object) !== object) {
+            throw new TypeError('Object.isFrozen can only be called on Objects.');
+        }
+        return false;
+    };
+}
+
+// ES5 15.2.3.13
+// http://es5.github.com/#x15.2.3.13
+if (!Object.isExtensible) {
+    Object.isExtensible = function isExtensible(object) {
+        // 1. If Type(O) is not Object throw a TypeError exception.
+        if (Object(object) !== object) {
+            throw new TypeError('Object.isExtensible can only be called on Objects.');
+        }
+        // 2. Return the Boolean value of the [[Extensible]] internal property of O.
+        var name = '';
+        while (owns(object, name)) {
+            name += '?';
+        }
+        object[name] = true;
+        var returnValue = owns(object, name);
+        delete object[name];
+        return returnValue;
+    };
+}
+
+}));
diff --git a/es5-shim.js b/es5-shim.js
new file mode 100644
index 0000000..879597e
--- /dev/null
+++ b/es5-shim.js
@@ -0,0 +1,1590 @@
+/*!
+ * https://github.com/es-shims/es5-shim
+ * @license es5-shim Copyright 2009-2015 by contributors, MIT License
+ * see https://github.com/es-shims/es5-shim/blob/master/LICENSE
+ */
+
+// vim: ts=4 sts=4 sw=4 expandtab
+
+// Add semicolon to prevent IIFE from being passed as argument to concatenated code.
+;
+
+// UMD (Universal Module Definition)
+// see https://github.com/umdjs/umd/blob/master/returnExports.js
+(function (root, factory) {
+    'use strict';
+
+    /* global define, exports, module */
+    if (typeof define === 'function' && define.amd) {
+        // AMD. Register as an anonymous module.
+        define(factory);
+    } else if (typeof exports === 'object') {
+        // Node. Does not work with strict CommonJS, but
+        // only CommonJS-like enviroments that support module.exports,
+        // like Node.
+        module.exports = factory();
+    } else {
+        // Browser globals (root is window)
+        root.returnExports = factory();
+    }
+}(this, function () {
+
+/**
+ * Brings an environment as close to ECMAScript 5 compliance
+ * as is possible with the facilities of erstwhile engines.
+ *
+ * Annotated ES5: http://es5.github.com/ (specific links below)
+ * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
+ * Required reading: http://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/
+ */
+
+// Shortcut to an often accessed properties, in order to avoid multiple
+// dereference that costs universally. This also holds a reference to known-good
+// functions.
+var $Array = Array;
+var ArrayPrototype = $Array.prototype;
+var $Object = Object;
+var ObjectPrototype = $Object.prototype;
+var FunctionPrototype = Function.prototype;
+var $String = String;
+var StringPrototype = $String.prototype;
+var $Number = Number;
+var NumberPrototype = $Number.prototype;
+var array_slice = ArrayPrototype.slice;
+var array_splice = ArrayPrototype.splice;
+var array_push = ArrayPrototype.push;
+var array_unshift = ArrayPrototype.unshift;
+var array_concat = ArrayPrototype.concat;
+var call = FunctionPrototype.call;
+var max = Math.max;
+var min = Math.min;
+
+// Having a toString local variable name breaks in Opera so use to_string.
+var to_string = ObjectPrototype.toString;
+
+var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
+var isCallable; /* inlined from https://npmjs.com/is-callable */ var fnToStr = Function.prototype.toString, tryFunctionObject = function tryFunctionObject(value) { try { fnToStr.call(value); return true; } catch (e) { return false; } }, fnClass = '[object Function]', genClass = '[object GeneratorFunction]'; isCallable = function isCallable(value) { if (typeof value !== 'function') { return false; } if (hasToStringTag) { return tryFunctionObject(value); } var strClass = to_string.call(val [...]
+var isRegex; /* inlined from https://npmjs.com/is-regex */ var regexExec = RegExp.prototype.exec, tryRegexExec = function tryRegexExec(value) { try { regexExec.call(value); return true; } catch (e) { return false; } }, regexClass = '[object RegExp]'; isRegex = function isRegex(value) { if (typeof value !== 'object') { return false; } return hasToStringTag ? tryRegexExec(value) : to_string.call(value) === regexClass; };
+var isString; /* inlined from https://npmjs.com/is-string */ var strValue = String.prototype.valueOf, tryStringObject = function tryStringObject(value) { try { strValue.call(value); return true; } catch (e) { return false; } }, stringClass = '[object String]'; isString = function isString(value) { if (typeof value === 'string') { return true; } if (typeof value !== 'object') { return false; } return hasToStringTag ? tryStringObject(value) : to_string.call(value) === stringClass; };
+
+/* inlined from http://npmjs.com/define-properties */
+var defineProperties = (function (has) {
+  var supportsDescriptors = $Object.defineProperty && (function () {
+      try {
+          var obj = {};
+          $Object.defineProperty(obj, 'x', { enumerable: false, value: obj });
+          for (var _ in obj) { return false; }
+          return obj.x === obj;
+      } catch (e) { /* this is ES3 */
+          return false;
+      }
+  }());
+
+  // Define configurable, writable and non-enumerable props
+  // if they don't exist.
+  var defineProperty;
+  if (supportsDescriptors) {
+      defineProperty = function (object, name, method, forceAssign) {
+          if (!forceAssign && (name in object)) { return; }
+          $Object.defineProperty(object, name, {
+              configurable: true,
+              enumerable: false,
+              writable: true,
+              value: method
+          });
+      };
+  } else {
+      defineProperty = function (object, name, method, forceAssign) {
+          if (!forceAssign && (name in object)) { return; }
+          object[name] = method;
+      };
+  }
+  return function defineProperties(object, map, forceAssign) {
+      for (var name in map) {
+          if (has.call(map, name)) {
+            defineProperty(object, name, map[name], forceAssign);
+          }
+      }
+  };
+}(ObjectPrototype.hasOwnProperty));
+
+//
+// Util
+// ======
+//
+
+/* replaceable with https://npmjs.com/package/es-abstract /helpers/isPrimitive */
+var isPrimitive = function isPrimitive(input) {
+    var type = typeof input;
+    return input === null || (type !== 'object' && type !== 'function');
+};
+
+var ES = {
+    // ES5 9.4
+    // http://es5.github.com/#x9.4
+    // http://jsperf.com/to-integer
+    /* replaceable with https://npmjs.com/package/es-abstract ES5.ToInteger */
+    ToInteger: function ToInteger(num) {
+        var n = +num;
+        if (n !== n) { // isNaN
+            n = 0;
+        } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
+            n = (n > 0 || -1) * Math.floor(Math.abs(n));
+        }
+        return n;
+    },
+
+    /* replaceable with https://npmjs.com/package/es-abstract ES5.ToPrimitive */
+    ToPrimitive: function ToPrimitive(input) {
+        var val, valueOf, toStr;
+        if (isPrimitive(input)) {
+            return input;
+        }
+        valueOf = input.valueOf;
+        if (isCallable(valueOf)) {
+            val = valueOf.call(input);
+            if (isPrimitive(val)) {
+                return val;
+            }
+        }
+        toStr = input.toString;
+        if (isCallable(toStr)) {
+            val = toStr.call(input);
+            if (isPrimitive(val)) {
+                return val;
+            }
+        }
+        throw new TypeError();
+    },
+
+    // ES5 9.9
+    // http://es5.github.com/#x9.9
+    /* replaceable with https://npmjs.com/package/es-abstract ES5.ToObject */
+    ToObject: function (o) {
+        /* jshint eqnull: true */
+        if (o == null) { // this matches both null and undefined
+            throw new TypeError("can't convert " + o + ' to object');
+        }
+        return $Object(o);
+    },
+
+    /* replaceable with https://npmjs.com/package/es-abstract ES5.ToUint32 */
+    ToUint32: function ToUint32(x) {
+        return x >>> 0;
+    }
+};
+
+//
+// Function
+// ========
+//
+
+// ES-5 15.3.4.5
+// http://es5.github.com/#x15.3.4.5
+
+var Empty = function Empty() {};
+
+defineProperties(FunctionPrototype, {
+    bind: function bind(that) { // .length is 1
+        // 1. Let Target be the this value.
+        var target = this;
+        // 2. If IsCallable(Target) is false, throw a TypeError exception.
+        if (!isCallable(target)) {
+            throw new TypeError('Function.prototype.bind called on incompatible ' + target);
+        }
+        // 3. Let A be a new (possibly empty) internal list of all of the
+        //   argument values provided after thisArg (arg1, arg2 etc), in order.
+        // XXX slicedArgs will stand in for "A" if used
+        var args = array_slice.call(arguments, 1); // for normal call
+        // 4. Let F be a new native ECMAScript object.
+        // 11. Set the [[Prototype]] internal property of F to the standard
+        //   built-in Function prototype object as specified in 15.3.3.1.
+        // 12. Set the [[Call]] internal property of F as described in
+        //   15.3.4.5.1.
+        // 13. Set the [[Construct]] internal property of F as described in
+        //   15.3.4.5.2.
+        // 14. Set the [[HasInstance]] internal property of F as described in
+        //   15.3.4.5.3.
+        var bound;
+        var binder = function () {
+
+            if (this instanceof bound) {
+                // 15.3.4.5.2 [[Construct]]
+                // When the [[Construct]] internal method of a function object,
+                // F that was created using the bind function is called with a
+                // list of arguments ExtraArgs, the following steps are taken:
+                // 1. Let target be the value of F's [[TargetFunction]]
+                //   internal property.
+                // 2. If target has no [[Construct]] internal method, a
+                //   TypeError exception is thrown.
+                // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
+                //   property.
+                // 4. Let args be a new list containing the same values as the
+                //   list boundArgs in the same order followed by the same
+                //   values as the list ExtraArgs in the same order.
+                // 5. Return the result of calling the [[Construct]] internal
+                //   method of target providing args as the arguments.
+
+                var result = target.apply(
+                    this,
+                    array_concat.call(args, array_slice.call(arguments))
+                );
+                if ($Object(result) === result) {
+                    return result;
+                }
+                return this;
+
+            } else {
+                // 15.3.4.5.1 [[Call]]
+                // When the [[Call]] internal method of a function object, F,
+                // which was created using the bind function is called with a
+                // this value and a list of arguments ExtraArgs, the following
+                // steps are taken:
+                // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
+                //   property.
+                // 2. Let boundThis be the value of F's [[BoundThis]] internal
+                //   property.
+                // 3. Let target be the value of F's [[TargetFunction]] internal
+                //   property.
+                // 4. Let args be a new list containing the same values as the
+                //   list boundArgs in the same order followed by the same
+                //   values as the list ExtraArgs in the same order.
+                // 5. Return the result of calling the [[Call]] internal method
+                //   of target providing boundThis as the this value and
+                //   providing args as the arguments.
+
+                // equiv: target.call(this, ...boundArgs, ...args)
+                return target.apply(
+                    that,
+                    array_concat.call(args, array_slice.call(arguments))
+                );
+
+            }
+
+        };
+
+        // 15. If the [[Class]] internal property of Target is "Function", then
+        //     a. Let L be the length property of Target minus the length of A.
+        //     b. Set the length own property of F to either 0 or L, whichever is
+        //       larger.
+        // 16. Else set the length own property of F to 0.
+
+        var boundLength = max(0, target.length - args.length);
+
+        // 17. Set the attributes of the length own property of F to the values
+        //   specified in 15.3.5.1.
+        var boundArgs = [];
+        for (var i = 0; i < boundLength; i++) {
+            array_push.call(boundArgs, '$' + i);
+        }
+
+        // XXX Build a dynamic function with desired amount of arguments is the only
+        // way to set the length property of a function.
+        // In environments where Content Security Policies enabled (Chrome extensions,
+        // for ex.) all use of eval or Function costructor throws an exception.
+        // However in all of these environments Function.prototype.bind exists
+        // and so this code will never be executed.
+        bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
+
+        if (target.prototype) {
+            Empty.prototype = target.prototype;
+            bound.prototype = new Empty();
+            // Clean up dangling references.
+            Empty.prototype = null;
+        }
+
+        // TODO
+        // 18. Set the [[Extensible]] internal property of F to true.
+
+        // TODO
+        // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
+        // 20. Call the [[DefineOwnProperty]] internal method of F with
+        //   arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
+        //   thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
+        //   false.
+        // 21. Call the [[DefineOwnProperty]] internal method of F with
+        //   arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
+        //   [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
+        //   and false.
+
+        // TODO
+        // NOTE Function objects created using Function.prototype.bind do not
+        // have a prototype property or the [[Code]], [[FormalParameters]], and
+        // [[Scope]] internal properties.
+        // XXX can't delete prototype in pure-js.
+
+        // 22. Return F.
+        return bound;
+    }
+});
+
+// _Please note: Shortcuts are defined after `Function.prototype.bind` as we
+// us it in defining shortcuts.
+var owns = call.bind(ObjectPrototype.hasOwnProperty);
+var toStr = call.bind(ObjectPrototype.toString);
+var strSlice = call.bind(StringPrototype.slice);
+var strSplit = call.bind(StringPrototype.split);
+
+//
+// Array
+// =====
+//
+
+var isArray = $Array.isArray || function isArray(obj) {
+    return toStr(obj) === '[object Array]';
+};
+
+// ES5 15.4.4.12
+// http://es5.github.com/#x15.4.4.13
+// Return len+argCount.
+// [bugfix, ielt8]
+// IE < 8 bug: [].unshift(0) === undefined but should be "1"
+var hasUnshiftReturnValueBug = [].unshift(0) !== 1;
+defineProperties(ArrayPrototype, {
+    unshift: function () {
+        array_unshift.apply(this, arguments);
+        return this.length;
+    }
+}, hasUnshiftReturnValueBug);
+
+// ES5 15.4.3.2
+// http://es5.github.com/#x15.4.3.2
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
+defineProperties($Array, { isArray: isArray });
+
+// The IsCallable() check in the Array functions
+// has been replaced with a strict check on the
+// internal class of the object to trap cases where
+// the provided function was actually a regular
+// expression literal, which in V8 and
+// JavaScriptCore is a typeof "function".  Only in
+// V8 are regular expression literals permitted as
+// reduce parameters, so it is desirable in the
+// general case for the shim to match the more
+// strict and common behavior of rejecting regular
+// expressions.
+
+// ES5 15.4.4.18
+// http://es5.github.com/#x15.4.4.18
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach
+
+// Check failure of by-index access of string characters (IE < 9)
+// and failure of `0 in boxedString` (Rhino)
+var boxedString = $Object('a');
+var splitString = boxedString[0] !== 'a' || !(0 in boxedString);
+
+var properlyBoxesContext = function properlyBoxed(method) {
+    // Check node 0.6.21 bug where third parameter is not boxed
+    var properlyBoxesNonStrict = true;
+    var properlyBoxesStrict = true;
+    if (method) {
+        method.call('foo', function (_, __, context) {
+            if (typeof context !== 'object') { properlyBoxesNonStrict = false; }
+        });
+
+        method.call([1], function () {
+            'use strict';
+
+            properlyBoxesStrict = typeof this === 'string';
+        }, 'x');
+    }
+    return !!method && properlyBoxesNonStrict && properlyBoxesStrict;
+};
+
+defineProperties(ArrayPrototype, {
+    forEach: function forEach(callbackfn /*, thisArg*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var i = -1;
+        var length = ES.ToUint32(self.length);
+        var T;
+        if (arguments.length > 1) {
+          T = arguments[1];
+        }
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.forEach callback must be a function');
+        }
+
+        while (++i < length) {
+            if (i in self) {
+                // Invoke the callback function with call, passing arguments:
+                // context, property value, property key, thisArg object
+                if (typeof T !== 'undefined') {
+                    callbackfn.call(T, self[i], i, object);
+                } else {
+                    callbackfn(self[i], i, object);
+                }
+            }
+        }
+    }
+}, !properlyBoxesContext(ArrayPrototype.forEach));
+
+// ES5 15.4.4.19
+// http://es5.github.com/#x15.4.4.19
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map
+defineProperties(ArrayPrototype, {
+    map: function map(callbackfn/*, thisArg*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = ES.ToUint32(self.length);
+        var result = $Array(length);
+        var T;
+        if (arguments.length > 1) {
+            T = arguments[1];
+        }
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.map callback must be a function');
+        }
+
+        for (var i = 0; i < length; i++) {
+            if (i in self) {
+                if (typeof T !== 'undefined') {
+                    result[i] = callbackfn.call(T, self[i], i, object);
+                } else {
+                    result[i] = callbackfn(self[i], i, object);
+                }
+            }
+        }
+        return result;
+    }
+}, !properlyBoxesContext(ArrayPrototype.map));
+
+// ES5 15.4.4.20
+// http://es5.github.com/#x15.4.4.20
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter
+defineProperties(ArrayPrototype, {
+    filter: function filter(callbackfn /*, thisArg*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = ES.ToUint32(self.length);
+        var result = [];
+        var value;
+        var T;
+        if (arguments.length > 1) {
+            T = arguments[1];
+        }
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.filter callback must be a function');
+        }
+
+        for (var i = 0; i < length; i++) {
+            if (i in self) {
+                value = self[i];
+                if (typeof T === 'undefined' ? callbackfn(value, i, object) : callbackfn.call(T, value, i, object)) {
+                    array_push.call(result, value);
+                }
+            }
+        }
+        return result;
+    }
+}, !properlyBoxesContext(ArrayPrototype.filter));
+
+// ES5 15.4.4.16
+// http://es5.github.com/#x15.4.4.16
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every
+defineProperties(ArrayPrototype, {
+    every: function every(callbackfn /*, thisArg*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = ES.ToUint32(self.length);
+        var T;
+        if (arguments.length > 1) {
+            T = arguments[1];
+        }
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.every callback must be a function');
+        }
+
+        for (var i = 0; i < length; i++) {
+            if (i in self && !(typeof T === 'undefined' ? callbackfn(self[i], i, object) : callbackfn.call(T, self[i], i, object))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}, !properlyBoxesContext(ArrayPrototype.every));
+
+// ES5 15.4.4.17
+// http://es5.github.com/#x15.4.4.17
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some
+defineProperties(ArrayPrototype, {
+    some: function some(callbackfn/*, thisArg */) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = ES.ToUint32(self.length);
+        var T;
+        if (arguments.length > 1) {
+            T = arguments[1];
+        }
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.some callback must be a function');
+        }
+
+        for (var i = 0; i < length; i++) {
+            if (i in self && (typeof T === 'undefined' ? callbackfn(self[i], i, object) : callbackfn.call(T, self[i], i, object))) {
+                return true;
+            }
+        }
+        return false;
+    }
+}, !properlyBoxesContext(ArrayPrototype.some));
+
+// ES5 15.4.4.21
+// http://es5.github.com/#x15.4.4.21
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce
+var reduceCoercesToObject = false;
+if (ArrayPrototype.reduce) {
+    reduceCoercesToObject = typeof ArrayPrototype.reduce.call('es5', function (_, __, ___, list) { return list; }) === 'object';
+}
+defineProperties(ArrayPrototype, {
+    reduce: function reduce(callbackfn /*, initialValue*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = ES.ToUint32(self.length);
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.reduce callback must be a function');
+        }
+
+        // no value to return if no initial value and an empty array
+        if (length === 0 && arguments.length === 1) {
+            throw new TypeError('reduce of empty array with no initial value');
+        }
+
+        var i = 0;
+        var result;
+        if (arguments.length >= 2) {
+            result = arguments[1];
+        } else {
+            do {
+                if (i in self) {
+                    result = self[i++];
+                    break;
+                }
+
+                // if array contains no values, no initial value to return
+                if (++i >= length) {
+                    throw new TypeError('reduce of empty array with no initial value');
+                }
+            } while (true);
+        }
+
+        for (; i < length; i++) {
+            if (i in self) {
+                result = callbackfn(result, self[i], i, object);
+            }
+        }
+
+        return result;
+    }
+}, !reduceCoercesToObject);
+
+// ES5 15.4.4.22
+// http://es5.github.com/#x15.4.4.22
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight
+var reduceRightCoercesToObject = false;
+if (ArrayPrototype.reduceRight) {
+    reduceRightCoercesToObject = typeof ArrayPrototype.reduceRight.call('es5', function (_, __, ___, list) { return list; }) === 'object';
+}
+defineProperties(ArrayPrototype, {
+    reduceRight: function reduceRight(callbackfn/*, initial*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = ES.ToUint32(self.length);
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.reduceRight callback must be a function');
+        }
+
+        // no value to return if no initial value, empty array
+        if (length === 0 && arguments.length === 1) {
+            throw new TypeError('reduceRight of empty array with no initial value');
+        }
+
+        var result;
+        var i = length - 1;
+        if (arguments.length >= 2) {
+            result = arguments[1];
+        } else {
+            do {
+                if (i in self) {
+                    result = self[i--];
+                    break;
+                }
+
+                // if array contains no values, no initial value to return
+                if (--i < 0) {
+                    throw new TypeError('reduceRight of empty array with no initial value');
+                }
+            } while (true);
+        }
+
+        if (i < 0) {
+            return result;
+        }
+
+        do {
+            if (i in self) {
+                result = callbackfn(result, self[i], i, object);
+            }
+        } while (i--);
+
+        return result;
+    }
+}, !reduceRightCoercesToObject);
+
+// ES5 15.4.4.14
+// http://es5.github.com/#x15.4.4.14
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
+var hasFirefox2IndexOfBug = ArrayPrototype.indexOf && [0, 1].indexOf(1, 2) !== -1;
+defineProperties(ArrayPrototype, {
+    indexOf: function indexOf(searchElement /*, fromIndex */) {
+        var self = splitString && isString(this) ? strSplit(this, '') : ES.ToObject(this);
+        var length = ES.ToUint32(self.length);
+
+        if (length === 0) {
+            return -1;
+        }
+
+        var i = 0;
+        if (arguments.length > 1) {
+            i = ES.ToInteger(arguments[1]);
+        }
+
+        // handle negative indices
+        i = i >= 0 ? i : max(0, length + i);
+        for (; i < length; i++) {
+            if (i in self && self[i] === searchElement) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}, hasFirefox2IndexOfBug);
+
+// ES5 15.4.4.15
+// http://es5.github.com/#x15.4.4.15
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf
+var hasFirefox2LastIndexOfBug = ArrayPrototype.lastIndexOf && [0, 1].lastIndexOf(0, -3) !== -1;
+defineProperties(ArrayPrototype, {
+    lastIndexOf: function lastIndexOf(searchElement /*, fromIndex */) {
+        var self = splitString && isString(this) ? strSplit(this, '') : ES.ToObject(this);
+        var length = ES.ToUint32(self.length);
+
+        if (length === 0) {
+            return -1;
+        }
+        var i = length - 1;
+        if (arguments.length > 1) {
+            i = min(i, ES.ToInteger(arguments[1]));
+        }
+        // handle negative indices
+        i = i >= 0 ? i : length - Math.abs(i);
+        for (; i >= 0; i--) {
+            if (i in self && searchElement === self[i]) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}, hasFirefox2LastIndexOfBug);
+
+// ES5 15.4.4.12
+// http://es5.github.com/#x15.4.4.12
+var spliceNoopReturnsEmptyArray = (function () {
+    var a = [1, 2];
+    var result = a.splice();
+    return a.length === 2 && isArray(result) && result.length === 0;
+}());
+defineProperties(ArrayPrototype, {
+    // Safari 5.0 bug where .splice() returns undefined
+    splice: function splice(start, deleteCount) {
+        if (arguments.length === 0) {
+            return [];
+        } else {
+            return array_splice.apply(this, arguments);
+        }
+    }
+}, !spliceNoopReturnsEmptyArray);
+
+var spliceWorksWithEmptyObject = (function () {
+    var obj = {};
+    ArrayPrototype.splice.call(obj, 0, 0, 1);
+    return obj.length === 1;
+}());
+defineProperties(ArrayPrototype, {
+    splice: function splice(start, deleteCount) {
+        if (arguments.length === 0) { return []; }
+        var args = arguments;
+        this.length = max(ES.ToInteger(this.length), 0);
+        if (arguments.length > 0 && typeof deleteCount !== 'number') {
+            args = array_slice.call(arguments);
+            if (args.length < 2) {
+                array_push.call(args, this.length - start);
+            } else {
+                args[1] = ES.ToInteger(deleteCount);
+            }
+        }
+        return array_splice.apply(this, args);
+    }
+}, !spliceWorksWithEmptyObject);
+var spliceWorksWithLargeSparseArrays = (function () {
+    // Per https://github.com/es-shims/es5-shim/issues/295
+    // Safari 7/8 breaks with sparse arrays of size 1e5 or greater
+    var arr = new $Array(1e5);
+    // note: the index MUST be 8 or larger or the test will false pass
+    arr[8] = 'x';
+    arr.splice(1, 1);
+    // note: this test must be defined *after* the indexOf shim
+    // per https://github.com/es-shims/es5-shim/issues/313
+    return arr.indexOf('x') === 7;
+}());
+var spliceWorksWithSmallSparseArrays = (function () {
+    // Per https://github.com/es-shims/es5-shim/issues/295
+    // Opera 12.15 breaks on this, no idea why.
+    var n = 256;
+    var arr = [];
+    arr[n] = 'a';
+    arr.splice(n + 1, 0, 'b');
+    return arr[n] === 'a';
+}());
+defineProperties(ArrayPrototype, {
+    splice: function splice(start, deleteCount) {
+        var O = ES.ToObject(this);
+        var A = [];
+        var len = ES.ToUint32(O.length);
+        var relativeStart = ES.ToInteger(start);
+        var actualStart = relativeStart < 0 ? max((len + relativeStart), 0) : min(relativeStart, len);
+        var actualDeleteCount = min(max(ES.ToInteger(deleteCount), 0), len - actualStart);
+
+        var k = 0;
+        var from;
+        while (k < actualDeleteCount) {
+            from = $String(actualStart + k);
+            if (owns(O, from)) {
+                A[k] = O[from];
+            }
+            k += 1;
+        }
+
+        var items = array_slice.call(arguments, 2);
+        var itemCount = items.length;
+        var to;
+        if (itemCount < actualDeleteCount) {
+            k = actualStart;
+            while (k < (len - actualDeleteCount)) {
+                from = $String(k + actualDeleteCount);
+                to = $String(k + itemCount);
+                if (owns(O, from)) {
+                    O[to] = O[from];
+                } else {
+                    delete O[to];
+                }
+                k += 1;
+            }
+            k = len;
+            while (k > (len - actualDeleteCount + itemCount)) {
+                delete O[k - 1];
+                k -= 1;
+            }
+        } else if (itemCount > actualDeleteCount) {
+            k = len - actualDeleteCount;
+            while (k > actualStart) {
+                from = $String(k + actualDeleteCount - 1);
+                to = $String(k + itemCount - 1);
+                if (owns(O, from)) {
+                    O[to] = O[from];
+                } else {
+                    delete O[to];
+                }
+                k -= 1;
+            }
+        }
+        k = actualStart;
+        for (var i = 0; i < items.length; ++i) {
+            O[k] = items[i];
+            k += 1;
+        }
+        O.length = len - actualDeleteCount + itemCount;
+
+        return A;
+    }
+}, !spliceWorksWithLargeSparseArrays || !spliceWorksWithSmallSparseArrays);
+
+//
+// Object
+// ======
+//
+
+// ES5 15.2.3.14
+// http://es5.github.com/#x15.2.3.14
+
+// http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
+var hasDontEnumBug = !({ 'toString': null }).propertyIsEnumerable('toString');
+var hasProtoEnumBug = function () {}.propertyIsEnumerable('prototype');
+var hasStringEnumBug = !owns('x', '0');
+var equalsConstructorPrototype = function (o) {
+    var ctor = o.constructor;
+    return ctor && ctor.prototype === o;
+};
+var blacklistedKeys = {
+    $window: true,
+    $console: true,
+    $parent: true,
+    $self: true,
+    $frames: true,
+    $frameElement: true,
+    $webkitIndexedDB: true,
+    $webkitStorageInfo: true
+};
+var hasAutomationEqualityBug = (function () {
+    /* globals window */
+    if (typeof window === 'undefined') { return false; }
+    for (var k in window) {
+        try {
+            if (!blacklistedKeys['$' + k] && owns(window, k) && window[k] !== null && typeof window[k] === 'object') {
+                equalsConstructorPrototype(window[k]);
+            }
+        } catch (e) {
+            return true;
+        }
+    }
+    return false;
+}());
+var equalsConstructorPrototypeIfNotBuggy = function (object) {
+    if (typeof window === 'undefined' || !hasAutomationEqualityBug) { return equalsConstructorPrototype(object); }
+    try {
+        return equalsConstructorPrototype(object);
+    } catch (e) {
+        return false;
+    }
+};
+var dontEnums = [
+    'toString',
+    'toLocaleString',
+    'valueOf',
+    'hasOwnProperty',
+    'isPrototypeOf',
+    'propertyIsEnumerable',
+    'constructor'
+];
+var dontEnumsLength = dontEnums.length;
+
+// taken directly from https://github.com/ljharb/is-arguments/blob/master/index.js
+// can be replaced with require('is-arguments') if we ever use a build process instead
+var isStandardArguments = function isArguments(value) {
+    return toStr(value) === '[object Arguments]';
+};
+var isLegacyArguments = function isArguments(value) {
+    return value !== null &&
+        typeof value === 'object' &&
+        typeof value.length === 'number' &&
+        value.length >= 0 &&
+        !isArray(value) &&
+        isCallable(value.callee);
+};
+var isArguments = isStandardArguments(arguments) ? isStandardArguments : isLegacyArguments;
+
+defineProperties($Object, {
+    keys: function keys(object) {
+        var isFn = isCallable(object);
+        var isArgs = isArguments(object);
+        var isObject = object !== null && typeof object === 'object';
+        var isStr = isObject && isString(object);
+
+        if (!isObject && !isFn && !isArgs) {
+            throw new TypeError('Object.keys called on a non-object');
+        }
+
+        var theKeys = [];
+        var skipProto = hasProtoEnumBug && isFn;
+        if ((isStr && hasStringEnumBug) || isArgs) {
+            for (var i = 0; i < object.length; ++i) {
+                array_push.call(theKeys, $String(i));
+            }
+        }
+
+        if (!isArgs) {
+            for (var name in object) {
+                if (!(skipProto && name === 'prototype') && owns(object, name)) {
+                    array_push.call(theKeys, $String(name));
+                }
+            }
+        }
+
+        if (hasDontEnumBug) {
+            var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object);
+            for (var j = 0; j < dontEnumsLength; j++) {
+                var dontEnum = dontEnums[j];
+                if (!(skipConstructor && dontEnum === 'constructor') && owns(object, dontEnum)) {
+                    array_push.call(theKeys, dontEnum);
+                }
+            }
+        }
+        return theKeys;
+    }
+});
+
+var keysWorksWithArguments = $Object.keys && (function () {
+    // Safari 5.0 bug
+    return $Object.keys(arguments).length === 2;
+}(1, 2));
+var keysHasArgumentsLengthBug = $Object.keys && (function () {
+    var argKeys = $Object.keys(arguments);
+    return arguments.length !== 1 || argKeys.length !== 1 || argKeys[0] !== 1;
+}(1));
+var originalKeys = $Object.keys;
+defineProperties($Object, {
+    keys: function keys(object) {
+        if (isArguments(object)) {
+            return originalKeys(array_slice.call(object));
+        } else {
+            return originalKeys(object);
+        }
+    }
+}, !keysWorksWithArguments || keysHasArgumentsLengthBug);
+
+//
+// Date
+// ====
+//
+
+// ES5 15.9.5.43
+// http://es5.github.com/#x15.9.5.43
+// This function returns a String value represent the instance in time
+// represented by this Date object. The format of the String is the Date Time
+// string format defined in 15.9.1.15. All fields are present in the String.
+// The time zone is always UTC, denoted by the suffix Z. If the time value of
+// this object is not a finite Number a RangeError exception is thrown.
+var negativeDate = -62198755200000;
+var negativeYearString = '-000001';
+var hasNegativeDateBug = Date.prototype.toISOString && new Date(negativeDate).toISOString().indexOf(negativeYearString) === -1;
+var hasSafari51DateBug = Date.prototype.toISOString && new Date(-1).toISOString() !== '1969-12-31T23:59:59.999Z';
+
+defineProperties(Date.prototype, {
+    toISOString: function toISOString() {
+        var result, length, value, year, month;
+        if (!isFinite(this)) {
+            throw new RangeError('Date.prototype.toISOString called on non-finite value.');
+        }
+
+        year = this.getUTCFullYear();
+
+        month = this.getUTCMonth();
+        // see https://github.com/es-shims/es5-shim/issues/111
+        year += Math.floor(month / 12);
+        month = (month % 12 + 12) % 12;
+
+        // the date time string format is specified in 15.9.1.15.
+        result = [month + 1, this.getUTCDate(), this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds()];
+        year = (
+            (year < 0 ? '-' : (year > 9999 ? '+' : '')) +
+            strSlice('00000' + Math.abs(year), (0 <= year && year <= 9999) ? -4 : -6)
+        );
+
+        length = result.length;
+        while (length--) {
+            value = result[length];
+            // pad months, days, hours, minutes, and seconds to have two
+            // digits.
+            if (value < 10) {
+                result[length] = '0' + value;
+            }
+        }
+        // pad milliseconds to have three digits.
+        return (
+            year + '-' + array_slice.call(result, 0, 2).join('-') +
+            'T' + array_slice.call(result, 2).join(':') + '.' +
+            strSlice('000' + this.getUTCMilliseconds(), -3) + 'Z'
+        );
+    }
+}, hasNegativeDateBug || hasSafari51DateBug);
+
+// ES5 15.9.5.44
+// http://es5.github.com/#x15.9.5.44
+// This function provides a String representation of a Date object for use by
+// JSON.stringify (15.12.3).
+var dateToJSONIsSupported = (function () {
+    try {
+        return Date.prototype.toJSON &&
+            new Date(NaN).toJSON() === null &&
+            new Date(negativeDate).toJSON().indexOf(negativeYearString) !== -1 &&
+            Date.prototype.toJSON.call({ // generic
+                toISOString: function () { return true; }
+            });
+    } catch (e) {
+        return false;
+    }
+}());
+if (!dateToJSONIsSupported) {
+    Date.prototype.toJSON = function toJSON(key) {
+        // When the toJSON method is called with argument key, the following
+        // steps are taken:
+
+        // 1.  Let O be the result of calling ToObject, giving it the this
+        // value as its argument.
+        // 2. Let tv be ES.ToPrimitive(O, hint Number).
+        var O = $Object(this);
+        var tv = ES.ToPrimitive(O);
+        // 3. If tv is a Number and is not finite, return null.
+        if (typeof tv === 'number' && !isFinite(tv)) {
+            return null;
+        }
+        // 4. Let toISO be the result of calling the [[Get]] internal method of
+        // O with argument "toISOString".
+        var toISO = O.toISOString;
+        // 5. If IsCallable(toISO) is false, throw a TypeError exception.
+        if (!isCallable(toISO)) {
+            throw new TypeError('toISOString property is not callable');
+        }
+        // 6. Return the result of calling the [[Call]] internal method of
+        //  toISO with O as the this value and an empty argument list.
+        return toISO.call(O);
+
+        // NOTE 1 The argument is ignored.
+
+        // NOTE 2 The toJSON function is intentionally generic; it does not
+        // require that its this value be a Date object. Therefore, it can be
+        // transferred to other kinds of objects for use as a method. However,
+        // it does require that any such object have a toISOString method. An
+        // object is free to use the argument key to filter its
+        // stringification.
+    };
+}
+
+// ES5 15.9.4.2
+// http://es5.github.com/#x15.9.4.2
+// based on work shared by Daniel Friesen (dantman)
+// http://gist.github.com/303249
+var supportsExtendedYears = Date.parse('+033658-09-27T01:46:40.000Z') === 1e15;
+var acceptsInvalidDates = !isNaN(Date.parse('2012-04-04T24:00:00.500Z')) || !isNaN(Date.parse('2012-11-31T23:59:59.000Z')) || !isNaN(Date.parse('2012-12-31T23:59:60.000Z'));
+var doesNotParseY2KNewYear = isNaN(Date.parse('2000-01-01T00:00:00.000Z'));
+if (doesNotParseY2KNewYear || acceptsInvalidDates || !supportsExtendedYears) {
+    // XXX global assignment won't work in embeddings that use
+    // an alternate object for the context.
+    /* global Date: true */
+    /* eslint-disable no-undef */
+    Date = (function (NativeDate) {
+    /* eslint-enable no-undef */
+        // Date.length === 7
+        var DateShim = function Date(Y, M, D, h, m, s, ms) {
+            var length = arguments.length;
+            var date;
+            if (this instanceof NativeDate) {
+                date = length === 1 && $String(Y) === Y ? // isString(Y)
+                    // We explicitly pass it through parse:
+                    new NativeDate(DateShim.parse(Y)) :
+                    // We have to manually make calls depending on argument
+                    // length here
+                    length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) :
+                    length >= 6 ? new NativeDate(Y, M, D, h, m, s) :
+                    length >= 5 ? new NativeDate(Y, M, D, h, m) :
+                    length >= 4 ? new NativeDate(Y, M, D, h) :
+                    length >= 3 ? new NativeDate(Y, M, D) :
+                    length >= 2 ? new NativeDate(Y, M) :
+                    length >= 1 ? new NativeDate(Y) :
+                                  new NativeDate();
+            } else {
+                date = NativeDate.apply(this, arguments);
+            }
+            if (!isPrimitive(date)) {
+              // Prevent mixups with unfixed Date object
+              defineProperties(date, { constructor: DateShim }, true);
+            }
+            return date;
+        };
+
+        // 15.9.1.15 Date Time String Format.
+        var isoDateExpression = new RegExp('^' +
+            '(\\d{4}|[+-]\\d{6})' + // four-digit year capture or sign +
+                                      // 6-digit extended year
+            '(?:-(\\d{2})' + // optional month capture
+            '(?:-(\\d{2})' + // optional day capture
+            '(?:' + // capture hours:minutes:seconds.milliseconds
+                'T(\\d{2})' + // hours capture
+                ':(\\d{2})' + // minutes capture
+                '(?:' + // optional :seconds.milliseconds
+                    ':(\\d{2})' + // seconds capture
+                    '(?:(\\.\\d{1,}))?' + // milliseconds capture
+                ')?' +
+            '(' + // capture UTC offset component
+                'Z|' + // UTC capture
+                '(?:' + // offset specifier +/-hours:minutes
+                    '([-+])' + // sign capture
+                    '(\\d{2})' + // hours offset capture
+                    ':(\\d{2})' + // minutes offset capture
+                ')' +
+            ')?)?)?)?' +
+        '$');
+
+        var months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
+
+        var dayFromMonth = function dayFromMonth(year, month) {
+            var t = month > 1 ? 1 : 0;
+            return (
+                months[month] +
+                Math.floor((year - 1969 + t) / 4) -
+                Math.floor((year - 1901 + t) / 100) +
+                Math.floor((year - 1601 + t) / 400) +
+                365 * (year - 1970)
+            );
+        };
+
+        var toUTC = function toUTC(t) {
+            return $Number(new NativeDate(1970, 0, 1, 0, 0, 0, t));
+        };
+
+        // Copy any custom methods a 3rd party library may have added
+        for (var key in NativeDate) {
+            if (owns(NativeDate, key)) {
+                DateShim[key] = NativeDate[key];
+            }
+        }
+
+        // Copy "native" methods explicitly; they may be non-enumerable
+        defineProperties(DateShim, {
+            now: NativeDate.now,
+            UTC: NativeDate.UTC
+        }, true);
+        DateShim.prototype = NativeDate.prototype;
+        defineProperties(DateShim.prototype, {
+            constructor: DateShim
+        }, true);
+
+        // Upgrade Date.parse to handle simplified ISO 8601 strings
+        var parseShim = function parse(string) {
+            var match = isoDateExpression.exec(string);
+            if (match) {
+                // parse months, days, hours, minutes, seconds, and milliseconds
+                // provide default values if necessary
+                // parse the UTC offset component
+                var year = $Number(match[1]),
+                    month = $Number(match[2] || 1) - 1,
+                    day = $Number(match[3] || 1) - 1,
+                    hour = $Number(match[4] || 0),
+                    minute = $Number(match[5] || 0),
+                    second = $Number(match[6] || 0),
+                    millisecond = Math.floor($Number(match[7] || 0) * 1000),
+                    // When time zone is missed, local offset should be used
+                    // (ES 5.1 bug)
+                    // see https://bugs.ecmascript.org/show_bug.cgi?id=112
+                    isLocalTime = Boolean(match[4] && !match[8]),
+                    signOffset = match[9] === '-' ? 1 : -1,
+                    hourOffset = $Number(match[10] || 0),
+                    minuteOffset = $Number(match[11] || 0),
+                    result;
+                var hasMinutesOrSecondsOrMilliseconds = minute > 0 || second > 0 || millisecond > 0;
+                if (
+                    hour < (hasMinutesOrSecondsOrMilliseconds ? 24 : 25) &&
+                    minute < 60 && second < 60 && millisecond < 1000 &&
+                    month > -1 && month < 12 && hourOffset < 24 &&
+                    minuteOffset < 60 && // detect invalid offsets
+                    day > -1 &&
+                    day < (dayFromMonth(year, month + 1) - dayFromMonth(year, month))
+                ) {
+                    result = (
+                        (dayFromMonth(year, month) + day) * 24 +
+                        hour +
+                        hourOffset * signOffset
+                    ) * 60;
+                    result = (
+                        (result + minute + minuteOffset * signOffset) * 60 +
+                        second
+                    ) * 1000 + millisecond;
+                    if (isLocalTime) {
+                        result = toUTC(result);
+                    }
+                    if (-8.64e15 <= result && result <= 8.64e15) {
+                        return result;
+                    }
+                }
+                return NaN;
+            }
+            return NativeDate.parse.apply(this, arguments);
+        };
+        defineProperties(DateShim, { parse: parseShim });
+
+        return DateShim;
+    }(Date));
+    /* global Date: false */
+}
+
+// ES5 15.9.4.4
+// http://es5.github.com/#x15.9.4.4
+if (!Date.now) {
+    Date.now = function now() {
+        return new Date().getTime();
+    };
+}
+
+//
+// Number
+// ======
+//
+
+// ES5.1 15.7.4.5
+// http://es5.github.com/#x15.7.4.5
+var hasToFixedBugs = NumberPrototype.toFixed && (
+  (0.00008).toFixed(3) !== '0.000' ||
+  (0.9).toFixed(0) !== '1' ||
+  (1.255).toFixed(2) !== '1.25' ||
+  (1000000000000000128).toFixed(0) !== '1000000000000000128'
+);
+
+var toFixedHelpers = {
+  base: 1e7,
+  size: 6,
+  data: [0, 0, 0, 0, 0, 0],
+  multiply: function multiply(n, c) {
+      var i = -1;
+      var c2 = c;
+      while (++i < toFixedHelpers.size) {
+          c2 += n * toFixedHelpers.data[i];
+          toFixedHelpers.data[i] = c2 % toFixedHelpers.base;
+          c2 = Math.floor(c2 / toFixedHelpers.base);
+      }
+  },
+  divide: function divide(n) {
+      var i = toFixedHelpers.size, c = 0;
+      while (--i >= 0) {
+          c += toFixedHelpers.data[i];
+          toFixedHelpers.data[i] = Math.floor(c / n);
+          c = (c % n) * toFixedHelpers.base;
+      }
+  },
+  numToString: function numToString() {
+      var i = toFixedHelpers.size;
+      var s = '';
+      while (--i >= 0) {
+          if (s !== '' || i === 0 || toFixedHelpers.data[i] !== 0) {
+              var t = $String(toFixedHelpers.data[i]);
+              if (s === '') {
+                  s = t;
+              } else {
+                  s += strSlice('0000000', 0, 7 - t.length) + t;
+              }
+          }
+      }
+      return s;
+  },
+  pow: function pow(x, n, acc) {
+      return (n === 0 ? acc : (n % 2 === 1 ? pow(x, n - 1, acc * x) : pow(x * x, n / 2, acc)));
+  },
+  log: function log(x) {
+      var n = 0;
+      var x2 = x;
+      while (x2 >= 4096) {
+          n += 12;
+          x2 /= 4096;
+      }
+      while (x2 >= 2) {
+          n += 1;
+          x2 /= 2;
+      }
+      return n;
+  }
+};
+
+defineProperties(NumberPrototype, {
+    toFixed: function toFixed(fractionDigits) {
+        var f, x, s, m, e, z, j, k;
+
+        // Test for NaN and round fractionDigits down
+        f = $Number(fractionDigits);
+        f = f !== f ? 0 : Math.floor(f);
+
+        if (f < 0 || f > 20) {
+            throw new RangeError('Number.toFixed called with invalid number of decimals');
+        }
+
+        x = $Number(this);
+
+        // Test for NaN
+        if (x !== x) {
+            return 'NaN';
+        }
+
+        // If it is too big or small, return the string value of the number
+        if (x <= -1e21 || x >= 1e21) {
+            return $String(x);
+        }
+
+        s = '';
+
+        if (x < 0) {
+            s = '-';
+            x = -x;
+        }
+
+        m = '0';
+
+        if (x > 1e-21) {
+            // 1e-21 < x < 1e21
+            // -70 < log2(x) < 70
+            e = toFixedHelpers.log(x * toFixedHelpers.pow(2, 69, 1)) - 69;
+            z = (e < 0 ? x * toFixedHelpers.pow(2, -e, 1) : x / toFixedHelpers.pow(2, e, 1));
+            z *= 0x10000000000000; // Math.pow(2, 52);
+            e = 52 - e;
+
+            // -18 < e < 122
+            // x = z / 2 ^ e
+            if (e > 0) {
+                toFixedHelpers.multiply(0, z);
+                j = f;
+
+                while (j >= 7) {
+                    toFixedHelpers.multiply(1e7, 0);
+                    j -= 7;
+                }
+
+                toFixedHelpers.multiply(toFixedHelpers.pow(10, j, 1), 0);
+                j = e - 1;
+
+                while (j >= 23) {
+                    toFixedHelpers.divide(1 << 23);
+                    j -= 23;
+                }
+
+                toFixedHelpers.divide(1 << j);
+                toFixedHelpers.multiply(1, 1);
+                toFixedHelpers.divide(2);
+                m = toFixedHelpers.numToString();
+            } else {
+                toFixedHelpers.multiply(0, z);
+                toFixedHelpers.multiply(1 << (-e), 0);
+                m = toFixedHelpers.numToString() + strSlice('0.00000000000000000000', 2, 2 + f);
+            }
+        }
+
+        if (f > 0) {
+            k = m.length;
+
+            if (k <= f) {
+                m = s + strSlice('0.0000000000000000000', 0, f - k + 2) + m;
+            } else {
+                m = s + strSlice(m, 0, k - f) + '.' + strSlice(m, k - f);
+            }
+        } else {
+            m = s + m;
+        }
+
+        return m;
+    }
+}, hasToFixedBugs);
+
+//
+// String
+// ======
+//
+
+// ES5 15.5.4.14
+// http://es5.github.com/#x15.5.4.14
+
+// [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers]
+// Many browsers do not split properly with regular expressions or they
+// do not perform the split correctly under obscure conditions.
+// See http://blog.stevenlevithan.com/archives/cross-browser-split
+// I've tested in many browsers and this seems to cover the deviant ones:
+//    'ab'.split(/(?:ab)*/) should be ["", ""], not [""]
+//    '.'.split(/(.?)(.?)/) should be ["", ".", "", ""], not ["", ""]
+//    'tesst'.split(/(s)*/) should be ["t", undefined, "e", "s", "t"], not
+//       [undefined, "t", undefined, "e", ...]
+//    ''.split(/.?/) should be [], not [""]
+//    '.'.split(/()()/) should be ["."], not ["", "", "."]
+
+if (
+    'ab'.split(/(?:ab)*/).length !== 2 ||
+    '.'.split(/(.?)(.?)/).length !== 4 ||
+    'tesst'.split(/(s)*/)[1] === 't' ||
+    'test'.split(/(?:)/, -1).length !== 4 ||
+    ''.split(/.?/).length ||
+    '.'.split(/()()/).length > 1
+) {
+    (function () {
+        var compliantExecNpcg = typeof (/()??/).exec('')[1] === 'undefined'; // NPCG: nonparticipating capturing group
+        var maxSafe32BitInt = Math.pow(2, 32) - 1;
+
+        StringPrototype.split = function (separator, limit) {
+            var string = this;
+            if (typeof separator === 'undefined' && limit === 0) {
+                return [];
+            }
+
+            // If `separator` is not a regex, use native split
+            if (!isRegex(separator)) {
+                return strSplit(this, separator, limit);
+            }
+
+            var output = [];
+            var flags = (separator.ignoreCase ? 'i' : '') +
+                        (separator.multiline ? 'm' : '') +
+                        (separator.unicode ? 'u' : '') + // in ES6
+                        (separator.sticky ? 'y' : ''), // Firefox 3+ and ES6
+                lastLastIndex = 0,
+                // Make `global` and avoid `lastIndex` issues by working with a copy
+                separator2, match, lastIndex, lastLength;
+            var separatorCopy = new RegExp(separator.source, flags + 'g');
+            string += ''; // Type-convert
+            if (!compliantExecNpcg) {
+                // Doesn't need flags gy, but they don't hurt
+                separator2 = new RegExp('^' + separatorCopy.source + '$(?!\\s)', flags);
+            }
+            /* Values for `limit`, per the spec:
+             * If undefined: 4294967295 // maxSafe32BitInt
+             * If 0, Infinity, or NaN: 0
+             * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
+             * If negative number: 4294967296 - Math.floor(Math.abs(limit))
+             * If other: Type-convert, then use the above rules
+             */
+            var splitLimit = typeof limit === 'undefined' ? maxSafe32BitInt : ES.ToUint32(limit);
+            match = separatorCopy.exec(string);
+            while (match) {
+                // `separatorCopy.lastIndex` is not reliable cross-browser
+                lastIndex = match.index + match[0].length;
+                if (lastIndex > lastLastIndex) {
+                    array_push.call(output, strSlice(string, lastLastIndex, match.index));
+                    // Fix browsers whose `exec` methods don't consistently return `undefined` for
+                    // nonparticipating capturing groups
+                    if (!compliantExecNpcg && match.length > 1) {
+                        /* eslint-disable no-loop-func */
+                        match[0].replace(separator2, function () {
+                            for (var i = 1; i < arguments.length - 2; i++) {
+                                if (typeof arguments[i] === 'undefined') {
+                                    match[i] = void 0;
+                                }
+                            }
+                        });
+                        /* eslint-enable no-loop-func */
+                    }
+                    if (match.length > 1 && match.index < string.length) {
+                        array_push.apply(output, array_slice.call(match, 1));
+                    }
+                    lastLength = match[0].length;
+                    lastLastIndex = lastIndex;
+                    if (output.length >= splitLimit) {
+                        break;
+                    }
+                }
+                if (separatorCopy.lastIndex === match.index) {
+                    separatorCopy.lastIndex++; // Avoid an infinite loop
+                }
+                match = separatorCopy.exec(string);
+            }
+            if (lastLastIndex === string.length) {
+                if (lastLength || !separatorCopy.test('')) {
+                    array_push.call(output, '');
+                }
+            } else {
+                array_push.call(output, strSlice(string, lastLastIndex));
+            }
+            return output.length > splitLimit ? strSlice(output, 0, splitLimit) : output;
+        };
+    }());
+
+// [bugfix, chrome]
+// If separator is undefined, then the result array contains just one String,
+// which is the this value (converted to a String). If limit is not undefined,
+// then the output array is truncated so that it contains no more than limit
+// elements.
+// "0".split(undefined, 0) -> []
+} else if ('0'.split(void 0, 0).length) {
+    StringPrototype.split = function split(separator, limit) {
+        if (typeof separator === 'undefined' && limit === 0) { return []; }
+        return strSplit(this, separator, limit);
+    };
+}
+
+var str_replace = StringPrototype.replace;
+var replaceReportsGroupsCorrectly = (function () {
+    var groups = [];
+    'x'.replace(/x(.)?/g, function (match, group) {
+        array_push.call(groups, group);
+    });
+    return groups.length === 1 && typeof groups[0] === 'undefined';
+}());
+
+if (!replaceReportsGroupsCorrectly) {
+    StringPrototype.replace = function replace(searchValue, replaceValue) {
+        var isFn = isCallable(replaceValue);
+        var hasCapturingGroups = isRegex(searchValue) && (/\)[*?]/).test(searchValue.source);
+        if (!isFn || !hasCapturingGroups) {
+            return str_replace.call(this, searchValue, replaceValue);
+        } else {
+            var wrappedReplaceValue = function (match) {
+                var length = arguments.length;
+                var originalLastIndex = searchValue.lastIndex;
+                searchValue.lastIndex = 0;
+                var args = searchValue.exec(match) || [];
+                searchValue.lastIndex = originalLastIndex;
+                array_push.call(args, arguments[length - 2], arguments[length - 1]);
+                return replaceValue.apply(this, args);
+            };
+            return str_replace.call(this, searchValue, wrappedReplaceValue);
+        }
+    };
+}
+
+// ECMA-262, 3rd B.2.3
+// Not an ECMAScript standard, although ECMAScript 3rd Edition has a
+// non-normative section suggesting uniform semantics and it should be
+// normalized across all browsers
+// [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE
+var string_substr = StringPrototype.substr;
+var hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b';
+defineProperties(StringPrototype, {
+    substr: function substr(start, length) {
+        var normalizedStart = start;
+        if (start < 0) {
+            normalizedStart = max(this.length + start, 0);
+        }
+        return string_substr.call(this, normalizedStart, length);
+    }
+}, hasNegativeSubstrBug);
+
+// ES5 15.5.4.20
+// whitespace from: http://es5.github.io/#x15.5.4.20
+var ws = '\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003' +
+    '\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028' +
+    '\u2029\uFEFF';
+var zeroWidth = '\u200b';
+var wsRegexChars = '[' + ws + ']';
+var trimBeginRegexp = new RegExp('^' + wsRegexChars + wsRegexChars + '*');
+var trimEndRegexp = new RegExp(wsRegexChars + wsRegexChars + '*$');
+var hasTrimWhitespaceBug = StringPrototype.trim && (ws.trim() || !zeroWidth.trim());
+defineProperties(StringPrototype, {
+    // http://blog.stevenlevithan.com/archives/faster-trim-javascript
+    // http://perfectionkills.com/whitespace-deviations/
+    trim: function trim() {
+        if (typeof this === 'undefined' || this === null) {
+            throw new TypeError("can't convert " + this + ' to object');
+        }
+        return $String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, '');
+    }
+}, hasTrimWhitespaceBug);
+
+// ES-5 15.1.2.2
+if (parseInt(ws + '08') !== 8 || parseInt(ws + '0x16') !== 22) {
+    /* global parseInt: true */
+    parseInt = (function (origParseInt) {
+        var hexRegex = /^0[xX]/;
+        return function parseInt(str, radix) {
+            var string = $String(str).trim();
+            var defaultedRadix = $Number(radix) || (hexRegex.test(string) ? 16 : 10);
+            return origParseInt(string, defaultedRadix);
+        };
+    }(parseInt));
+}
+
+}));
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b0775fd
--- /dev/null
+++ b/package.json
@@ -0,0 +1,72 @@
+{
+  "name": "es5-shim",
+  "version": "4.1.14",
+  "description": "ECMAScript 5 compatibility shims for legacy JavaScript engines",
+  "homepage": "http://github.com/es-shims/es5-shim/",
+  "contributors": [
+    "Kris Kowal <kris at cixar.com> (http://github.com/kriskowal/)",
+    "Sami Samhuri <sami.samhuri at gmail.com> (http://samhuri.net/)",
+    "Florian Schäfer <florian.schaefer at gmail.com> (http://github.com/fschaefer)",
+    "Irakli Gozalishvili <rfobic at gmail.com> (http://jeditoolkit.com)",
+    "Kit Cambridge <kitcambridge at gmail.com> (http://kitcambridge.github.com)",
+    "Jordan Harband <ljharb at gmail.com> (https://github.com/ljharb/)"
+  ],
+  "bugs": {
+    "mail": "ljharb at gmail.com",
+    "url": "http://github.com/es-shims/es5-shim/issues"
+  },
+  "license": "MIT",
+  "main": "es5-shim.js",
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/es-shims/es5-shim.git"
+  },
+  "scripts": {
+    "minify": "npm run minify-shim && npm run minify-sham",
+    "minify-shim": "uglifyjs es5-shim.js --keep-fnames --comments --source-map=es5-shim.map -m -b ascii_only=true,beautify=false > es5-shim.min.js",
+    "minify-sham": "uglifyjs es5-sham.js --keep-fnames --comments --source-map=es5-sham.map -m -b ascii_only=true,beautify=false > es5-sham.min.js",
+    "test": "npm run lint && jasmine-node --matchall ./ tests/spec/",
+    "test-native": "jasmine-node --matchall tests/spec/",
+    "lint": "npm run jscs && npm run eslint",
+    "eslint": "eslint tests/helpers/*.js tests/spec/*.js es5-shim.js es5-sham.js",
+    "jscs": "jscs tests/helpers/*.js tests/spec/*.js es5-shim.js es5-sham.js"
+  },
+  "devDependencies": {
+    "eslint": "^1.5.1",
+    "@ljharb/eslint-config": "^1.2.0",
+    "jasmine-node": "^1.14.5",
+    "jscs": "^2.2.1",
+    "uglify-js": "^2.4.24",
+    "replace": "^0.3.0",
+    "semver": "^5.0.3"
+  },
+  "engines": {
+    "node": ">=0.4.0"
+  },
+  "testling": {
+    "browsers": [
+      "iexplore/6.0..latest",
+      "firefox/3.0..6.0",
+      "firefox/18.0..latest",
+      "firefox/nightly",
+      "chrome/4.0..10.0",
+      "chrome/25.0..latest",
+      "chrome/canary",
+      "opera/10.0..latest",
+      "opera/next",
+      "safari/4.0..latest",
+      "ipad/6.0..latest",
+      "iphone/6.0..latest",
+      "android-browser/4.2"
+    ]
+  },
+  "keywords": [
+    "shim",
+    "es5",
+    "es5 shim",
+    "javascript",
+    "ecmascript",
+    "polyfill"
+  ]
+}
+
diff --git a/shims.json b/shims.json
new file mode 100644
index 0000000..0c5430a
--- /dev/null
+++ b/shims.json
@@ -0,0 +1,7 @@
+{
+    "Object": {
+        "prototype": {},
+        "keys": "object-keys"
+    }
+}
+
diff --git a/tests/helpers/h-matchers.js b/tests/helpers/h-matchers.js
new file mode 100644
index 0000000..9ce9869
--- /dev/null
+++ b/tests/helpers/h-matchers.js
@@ -0,0 +1,40 @@
+/* global beforeEach, expect */
+
+var has = Object.prototype.hasOwnProperty;
+var getKeys = function (o) {
+    'use strict';
+
+    var key, a = [];
+    for (key in o) {
+        if (has.call(o, key)) {
+            a.push(key);
+        }
+    }
+    return a;
+};
+
+beforeEach(function () {
+    'use strict';
+
+    this.addMatchers({
+        toExactlyMatch: function (expected) {
+            var a1, a2, l, i, key;
+            var actual = this.actual;
+
+            a1 = getKeys(actual);
+            a2 = getKeys(expected);
+
+            l = a1.length;
+            if (l !== a2.length) {
+                return false;
+            }
+            for (i = 0; i < l; i++) {
+                key = a1[i];
+                expect(key).toEqual(a2[i]);
+                expect(actual[key]).toEqual(expected[key]);
+            }
+
+            return true;
+        }
+    });
+});
diff --git a/tests/index.html b/tests/index.html
new file mode 100644
index 0000000..a797edd
--- /dev/null
+++ b/tests/index.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+	<title>Jasmine Spec Runner</title>
+
+	<link rel="shortcut icon" type="image/png" href="lib/jasmine_favicon.png">
+
+	<link rel="stylesheet" type="text/css" href="lib/jasmine.css">
+	<script type="text/javascript" src="lib/jasmine.js"></script>
+	<script type="text/javascript" src="lib/jasmine-html.js"></script>
+	<script type="text/javascript" src="lib/json2.js"></script>
+
+	<!-- include helper files here... -->
+	<script src="helpers/h-matchers.js"></script>
+
+	<!-- include source files here... -->
+	<script src="../es5-shim.js"></script>
+	<script src="../es5-sham.js"></script>
+
+	<!-- include spec files here... -->
+	<script src="spec/s-array.js"></script>
+	<script src="spec/s-function.js"></script>
+	<script src="spec/s-string.js"></script>
+	<script src="spec/s-object.js"></script>
+	<script src="spec/s-number.js"></script>
+	<script src="spec/s-date.js"></script>
+
+
+	<script type="text/javascript">
+		(function() {
+			var jasmineEnv = jasmine.getEnv();
+			jasmineEnv.updateInterval = 1000;
+
+			var trivialReporter = new jasmine.TrivialReporter();
+
+			jasmineEnv.addReporter(trivialReporter);
+
+			jasmineEnv.specFilter = function(spec) {
+				return trivialReporter.specFilter(spec);
+			};
+
+			var currentWindowOnload = window.onload;
+
+			window.onload = function() {
+				if (currentWindowOnload) {
+					currentWindowOnload();
+				}
+				execJasmine();
+			};
+
+			function execJasmine() {
+				jasmineEnv.execute();
+			}
+
+		})();
+	</script>
+
+</head>
+
+<body>
+</body>
+</html>
diff --git a/tests/index.min.html b/tests/index.min.html
new file mode 100644
index 0000000..b8ac467
--- /dev/null
+++ b/tests/index.min.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+	<title>Jasmine Spec Runner</title>
+
+	<link rel="shortcut icon" type="image/png" href="lib/jasmine_favicon.png">
+
+	<link rel="stylesheet" type="text/css" href="lib/jasmine.css">
+	<script type="text/javascript" src="lib/jasmine.js"></script>
+	<script type="text/javascript" src="lib/jasmine-html.js"></script>
+	<script type="text/javascript" src="lib/json2.js"></script>
+
+	<!-- include helper files here... -->
+	<script src="helpers/h-matchers.js"></script>
+
+	<!-- include source files here... -->
+	<script src="../es5-shim.min.js"></script>
+
+	<!-- include spec files here... -->
+	<script src="spec/s-array.js"></script>
+	<script src="spec/s-function.js"></script>
+	<script src="spec/s-string.js"></script>
+	<script src="spec/s-object.js"></script>
+	<script src="spec/s-number.js"></script>
+	<script src="spec/s-date.js"></script>
+
+
+	<script type="text/javascript">
+		(function() {
+			var jasmineEnv = jasmine.getEnv();
+			jasmineEnv.updateInterval = 1000;
+
+			var trivialReporter = new jasmine.TrivialReporter();
+
+			jasmineEnv.addReporter(trivialReporter);
+
+			jasmineEnv.specFilter = function(spec) {
+				return trivialReporter.specFilter(spec);
+			};
+
+			var currentWindowOnload = window.onload;
+
+			window.onload = function() {
+				if (currentWindowOnload) {
+					currentWindowOnload();
+				}
+				execJasmine();
+			};
+
+			function execJasmine() {
+				jasmineEnv.execute();
+			}
+
+		})();
+	</script>
+
+</head>
+
+<body>
+</body>
+</html>
diff --git a/tests/lib/jasmine-html.js b/tests/lib/jasmine-html.js
new file mode 100644
index 0000000..7383401
--- /dev/null
+++ b/tests/lib/jasmine-html.js
@@ -0,0 +1,190 @@
+jasmine.TrivialReporter = function(doc) {
+  this.document = doc || document;
+  this.suiteDivs = {};
+  this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+  var el = document.createElement(type);
+
+  for (var i = 2; i < arguments.length; i++) {
+    var child = arguments[i];
+
+    if (typeof child === 'string') {
+      el.appendChild(document.createTextNode(child));
+    } else {
+      if (child) { el.appendChild(child); }
+    }
+  }
+
+  for (var attr in attrs) {
+    if (attr == "className") {
+      el[attr] = attrs[attr];
+    } else {
+      el.setAttribute(attr, attrs[attr]);
+    }
+  }
+
+  return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+  var showPassed, showSkipped;
+
+  this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
+      this.createDom('div', { className: 'banner' },
+        this.createDom('div', { className: 'logo' },
+            this.createDom('span', { className: 'title' }, "Jasmine"),
+            this.createDom('span', { className: 'version' }, runner.env.versionString())),
+        this.createDom('div', { className: 'options' },
+            "Show ",
+            showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+            this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+            showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+            this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+            )
+          ),
+
+      this.runnerDiv = this.createDom('div', { className: 'runner running' },
+          this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+          this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+          this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+      );
+
+  this.document.body.appendChild(this.outerDiv);
+
+  var suites = runner.suites();
+  for (var i = 0; i < suites.length; i++) {
+    var suite = suites[i];
+    var suiteDiv = this.createDom('div', { className: 'suite' },
+        this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+        this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+    this.suiteDivs[suite.id] = suiteDiv;
+    var parentDiv = this.outerDiv;
+    if (suite.parentSuite) {
+      parentDiv = this.suiteDivs[suite.parentSuite.id];
+    }
+    parentDiv.appendChild(suiteDiv);
+  }
+
+  this.startedAt = new Date();
+
+  var self = this;
+  showPassed.onclick = function(evt) {
+    if (showPassed.checked) {
+      self.outerDiv.className += ' show-passed';
+    } else {
+      self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+    }
+  };
+
+  showSkipped.onclick = function(evt) {
+    if (showSkipped.checked) {
+      self.outerDiv.className += ' show-skipped';
+    } else {
+      self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+    }
+  };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+  var results = runner.results();
+  var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+  this.runnerDiv.setAttribute("class", className);
+  //do it twice for IE
+  this.runnerDiv.setAttribute("className", className);
+  var specs = runner.specs();
+  var specCount = 0;
+  for (var i = 0; i < specs.length; i++) {
+    if (this.specFilter(specs[i])) {
+      specCount++;
+    }
+  }
+  var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+  message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+  this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+  this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+  var results = suite.results();
+  var status = results.passed() ? 'passed' : 'failed';
+  if (results.totalCount === 0) { // todo: change this to check results.skipped
+    status = 'skipped';
+  }
+  this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+  if (this.logRunningSpecs) {
+    this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+  }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+  var results = spec.results();
+  var status = results.passed() ? 'passed' : 'failed';
+  if (results.skipped) {
+    status = 'skipped';
+  }
+  var specDiv = this.createDom('div', { className: 'spec '  + status },
+      this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+      this.createDom('a', {
+        className: 'description',
+        href: '?spec=' + encodeURIComponent(spec.getFullName()),
+        title: spec.getFullName()
+      }, spec.description));
+
+
+  var resultItems = results.getItems();
+  var messagesDiv = this.createDom('div', { className: 'messages' });
+  for (var i = 0; i < resultItems.length; i++) {
+    var result = resultItems[i];
+
+    if (result.type == 'log') {
+      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+    } else if (result.type == 'expect' && result.passed && !result.passed()) {
+      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+      if (result.trace.stack) {
+        messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+      }
+    }
+  }
+
+  if (messagesDiv.childNodes.length > 0) {
+    specDiv.appendChild(messagesDiv);
+  }
+
+  this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+  var console = jasmine.getGlobal().console;
+  if (console && console.log) {
+    if (console.log.apply) {
+      console.log.apply(console, arguments);
+    } else {
+      console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+    }
+  }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+  return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+  var paramMap = {};
+  var params = this.getLocation().search.substring(1).split('&');
+  for (var i = 0; i < params.length; i++) {
+    var p = params[i].split('=');
+    paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+  }
+
+  if (!paramMap.spec) {
+    return true;
+  }
+  return spec.getFullName().indexOf(paramMap.spec) === 0;
+};
diff --git a/tests/lib/jasmine.css b/tests/lib/jasmine.css
new file mode 100644
index 0000000..6583fe7
--- /dev/null
+++ b/tests/lib/jasmine.css
@@ -0,0 +1,166 @@
+body {
+  font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
+}
+
+
+.jasmine_reporter a:visited, .jasmine_reporter a {
+  color: #303; 
+}
+
+.jasmine_reporter a:hover, .jasmine_reporter a:active {
+  color: blue; 
+}
+
+.run_spec {
+  float:right;
+  padding-right: 5px;
+  font-size: .8em;
+  text-decoration: none;
+}
+
+.jasmine_reporter {
+  margin: 0 5px;
+}
+
+.banner {
+  color: #303;
+  background-color: #fef;
+  padding: 5px;
+}
+
+.logo {
+  float: left;
+  font-size: 1.1em;
+  padding-left: 5px;
+}
+
+.logo .version {
+  font-size: .6em;
+  padding-left: 1em;
+}
+
+.runner.running {
+  background-color: yellow;
+}
+
+
+.options {
+  text-align: right;
+  font-size: .8em;
+}
+
+
+
+
+.suite {
+  border: 1px outset gray;
+  margin: 5px 0;
+  padding-left: 1em;
+}
+
+.suite .suite {
+  margin: 5px; 
+}
+
+.suite.passed {
+  background-color: #dfd;
+}
+
+.suite.failed {
+  background-color: #fdd;
+}
+
+.spec {
+  margin: 5px;
+  padding-left: 1em;
+  clear: both;
+}
+
+.spec.failed, .spec.passed, .spec.skipped {
+  padding-bottom: 5px;
+  border: 1px solid gray;
+}
+
+.spec.failed {
+  background-color: #fbb;
+  border-color: red;
+}
+
+.spec.passed {
+  background-color: #bfb;
+  border-color: green;
+}
+
+.spec.skipped {
+  background-color: #bbb;
+}
+
+.messages {
+  border-left: 1px dashed gray;
+  padding-left: 1em;
+  padding-right: 1em;
+}
+
+.passed {
+  background-color: #cfc;
+  display: none;
+}
+
+.failed {
+  background-color: #fbb;
+}
+
+.skipped {
+  color: #777;
+  background-color: #eee;
+  display: none;
+}
+
+
+/*.resultMessage {*/
+  /*white-space: pre;*/
+/*}*/
+
+.resultMessage span.result {
+  display: block;
+  line-height: 2em;
+  color: black;
+}
+
+.resultMessage .mismatch {
+  color: black;
+}
+
+.stackTrace {
+  white-space: pre;
+  font-size: .8em;
+  margin-left: 10px;
+  max-height: 5em;
+  overflow: auto;
+  border: 1px inset red;
+  padding: 1em;
+  background: #eef;
+}
+
+.finished-at {
+  padding-left: 1em;
+  font-size: .6em;
+}
+
+.show-passed .passed,
+.show-skipped .skipped {
+  display: block;
+}
+
+
+#jasmine_content {
+  position:fixed;
+  right: 100%;
+}
+
+.runner {
+  border: 1px solid gray;
+  display: block;
+  margin: 5px 0;
+  padding: 2px 0 2px 10px;
+}
diff --git a/tests/lib/jasmine.js b/tests/lib/jasmine.js
new file mode 100644
index 0000000..edc003b
--- /dev/null
+++ b/tests/lib/jasmine.js
@@ -0,0 +1,2601 @@
+var isCommonJS = typeof window == "undefined" && typeof exports == "object";
+
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+if (isCommonJS) exports.jasmine = jasmine;
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+  throw new Error("unimplemented method");
+};
+
+/**
+ * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Show diagnostic messages in the console if set to true
+ *
+ */
+jasmine.VERBOSE = false;
+
+/**
+ * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Maximum levels of nesting that will be included when an object is pretty-printed
+ */
+jasmine.MAX_PRETTY_PRINT_DEPTH = 40;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+/**
+ * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite.
+ * Set to false to let the exception bubble up in the browser.
+ *
+ */
+jasmine.CATCH_EXCEPTIONS = true;
+
+jasmine.getGlobal = function() {
+  function getGlobal() {
+    return this;
+  }
+
+  return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared.  Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+  var original = base[name];
+  if (original.apply) {
+    return function() {
+      return original.apply(base, arguments);
+    };
+  } else {
+    // IE support
+    return jasmine.getGlobal()[name];
+  }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+  this.type = 'log';
+  this.values = values;
+  this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+  var text = "";
+  for (var i = 0; i < this.values.length; i++) {
+    if (i > 0) text += " ";
+    if (jasmine.isString_(this.values[i])) {
+      text += this.values[i];
+    } else {
+      text += jasmine.pp(this.values[i]);
+    }
+  }
+  return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+  this.type = 'expect';
+  this.matcherName = params.matcherName;
+  this.passed_ = params.passed;
+  this.expected = params.expected;
+  this.actual = params.actual;
+  this.message = this.passed_ ? 'Passed.' : params.message;
+
+  var trace = (params.trace || new Error(this.message));
+  this.trace = this.passed_ ? '' : trace;
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+  return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+  return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+  var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+  return env;
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+  return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+  return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+  return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+  return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations.  Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+  var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+  stringPrettyPrinter.format(value);
+  return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+  return obj.nodeType > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type.  For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+  return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
+ * attributes on the object.
+ *
+ * @example
+ * // don't care about any other attributes than foo.
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
+ *
+ * @param sample {Object} sample
+ * @returns matchable object for the sample
+ */
+jasmine.objectContaining = function (sample) {
+    return new jasmine.Matchers.ObjectContaining(sample);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations.  They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub');  // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ *   not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ *   not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).toHaveBeenCalled();
+ * expect(foo.not).toHaveBeenCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+  /**
+   * The name of the spy, if provided.
+   */
+  this.identity = name || 'unknown';
+  /**
+   *  Is this Object a spy?
+   */
+  this.isSpy = true;
+  /**
+   * The actual function this spy stubs.
+   */
+  this.plan = function() {
+  };
+  /**
+   * Tracking of the most recent call to the spy.
+   * @example
+   * var mySpy = jasmine.createSpy('foo');
+   * mySpy(1, 2);
+   * mySpy.mostRecentCall.args = [1, 2];
+   */
+  this.mostRecentCall = {};
+
+  /**
+   * Holds arguments for each call to the spy, indexed by call count
+   * @example
+   * var mySpy = jasmine.createSpy('foo');
+   * mySpy(1, 2);
+   * mySpy(7, 8);
+   * mySpy.mostRecentCall.args = [7, 8];
+   * mySpy.argsForCall[0] = [1, 2];
+   * mySpy.argsForCall[1] = [7, 8];
+   */
+  this.argsForCall = [];
+  this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ *   bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+  this.plan = this.originalValue;
+  return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+  this.plan = function() {
+    return value;
+  };
+  return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+  this.plan = function() {
+    throw exceptionMsg;
+  };
+  return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ *   // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+  this.plan = fakeFunc;
+  return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+  this.wasCalled = false;
+  this.callCount = 0;
+  this.argsForCall = [];
+  this.calls = [];
+  this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+  var spyObj = function() {
+    spyObj.wasCalled = true;
+    spyObj.callCount++;
+    var args = jasmine.util.argsToArray(arguments);
+    spyObj.mostRecentCall.object = this;
+    spyObj.mostRecentCall.args = args;
+    spyObj.argsForCall.push(args);
+    spyObj.calls.push({object: this, args: args});
+    return spyObj.plan.apply(this, arguments);
+  };
+
+  var spy = new jasmine.Spy(name);
+
+  for (var prop in spy) {
+    spyObj[prop] = spy[prop];
+  }
+
+  spyObj.reset();
+
+  return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+  return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy.  Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+  if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
+    throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+  }
+  var obj = {};
+  for (var i = 0; i < methodNames.length; i++) {
+    obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+  }
+  return obj;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> in production code.
+ */
+jasmine.log = function() {
+  var spec = jasmine.getEnv().currentSpec;
+  spec.log.apply(spec, arguments);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name.  Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ *   not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+  return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+if (isCommonJS) exports.spyOn = spyOn;
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ *   expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+  return jasmine.getEnv().it(desc, func);
+};
+if (isCommonJS) exports.it = it;
+
+/**
+ * Creates a <em>disabled</em> Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+  return jasmine.getEnv().xit(desc, func);
+};
+if (isCommonJS) exports.xit = xit;
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ * @return {jasmine.Matchers}
+ */
+var expect = function(actual) {
+  return jasmine.getEnv().currentSpec.expect(actual);
+};
+if (isCommonJS) exports.expect = expect;
+
+/**
+ * Defines part of a jasmine spec.  Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+  jasmine.getEnv().currentSpec.runs(func);
+};
+if (isCommonJS) exports.runs = runs;
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+var waits = function(timeout) {
+  jasmine.getEnv().currentSpec.waits(timeout);
+};
+if (isCommonJS) exports.waits = waits;
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+  jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
+};
+if (isCommonJS) exports.waitsFor = waitsFor;
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+  jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+if (isCommonJS) exports.beforeEach = beforeEach;
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+  jasmine.getEnv().afterEach(afterEachFunction);
+};
+if (isCommonJS) exports.afterEach = afterEach;
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+  return jasmine.getEnv().describe(description, specDefinitions);
+};
+if (isCommonJS) exports.describe = describe;
+
+/**
+ * Disables a suite of specifications.  Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+  return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+if (isCommonJS) exports.xdescribe = xdescribe;
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+  function tryIt(f) {
+    try {
+      return f();
+    } catch(e) {
+    }
+    return null;
+  }
+
+  var xhr = tryIt(function() {
+    return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+  }) ||
+    tryIt(function() {
+      return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+    }) ||
+    tryIt(function() {
+      return new ActiveXObject("Msxml2.XMLHTTP");
+    }) ||
+    tryIt(function() {
+      return new ActiveXObject("Microsoft.XMLHTTP");
+    });
+
+  if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
+
+  return xhr;
+} : XMLHttpRequest;
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+  /**
+   * @private
+   */
+  var subclass = function() {
+  };
+  subclass.prototype = parentClass.prototype;
+  childClass.prototype = new subclass();
+};
+
+jasmine.util.formatException = function(e) {
+  var lineNumber;
+  if (e.line) {
+    lineNumber = e.line;
+  }
+  else if (e.lineNumber) {
+    lineNumber = e.lineNumber;
+  }
+
+  var file;
+
+  if (e.sourceURL) {
+    file = e.sourceURL;
+  }
+  else if (e.fileName) {
+    file = e.fileName;
+  }
+
+  var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+  if (file && lineNumber) {
+    message += ' in ' + file + ' (line ' + lineNumber + ')';
+  }
+
+  return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+  if (!str) return str;
+  return str.replace(/&/g, '&')
+    .replace(/</g, '<')
+    .replace(/>/g, '>');
+};
+
+jasmine.util.argsToArray = function(args) {
+  var arrayOfArgs = [];
+  for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+  return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+  for (var property in source) destination[property] = source[property];
+  return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+  this.currentSpec = null;
+  this.currentSuite = null;
+  this.currentRunner_ = new jasmine.Runner(this);
+
+  this.reporter = new jasmine.MultiReporter();
+
+  this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+  this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+  this.lastUpdate = 0;
+  this.specFilter = function() {
+    return true;
+  };
+
+  this.nextSpecId_ = 0;
+  this.nextSuiteId_ = 0;
+  this.equalityTesters_ = [];
+
+  // wrap matchers
+  this.matchersClass = function() {
+    jasmine.Matchers.apply(this, arguments);
+  };
+  jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+  jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+  if (jasmine.version_) {
+    return jasmine.version_;
+  } else {
+    throw new Error('Version not set');
+  }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+  if (!jasmine.version_) {
+    return "version unknown";
+  }
+
+  var version = this.version();
+  var versionString = version.major + "." + version.minor + "." + version.build;
+  if (version.release_candidate) {
+    versionString += ".rc" + version.release_candidate;
+  }
+  versionString += " revision " + version.revision;
+  return versionString;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+  return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+  return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+  this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+  this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+  var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+  var parentSuite = this.currentSuite;
+  if (parentSuite) {
+    parentSuite.add(suite);
+  } else {
+    this.currentRunner_.add(suite);
+  }
+
+  this.currentSuite = suite;
+
+  var declarationError = null;
+  try {
+    specDefinitions.call(suite);
+  } catch(e) {
+    declarationError = e;
+  }
+
+  if (declarationError) {
+    this.it("encountered a declaration exception", function() {
+      throw declarationError;
+    });
+  }
+
+  this.currentSuite = parentSuite;
+
+  return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+  if (this.currentSuite) {
+    this.currentSuite.beforeEach(beforeEachFunction);
+  } else {
+    this.currentRunner_.beforeEach(beforeEachFunction);
+  }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+  return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+  if (this.currentSuite) {
+    this.currentSuite.afterEach(afterEachFunction);
+  } else {
+    this.currentRunner_.afterEach(afterEachFunction);
+  }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+  return {
+    execute: function() {
+    }
+  };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+  var spec = new jasmine.Spec(this, this.currentSuite, description);
+  this.currentSuite.add(spec);
+  this.currentSpec = spec;
+
+  if (func) {
+    spec.runs(func);
+  }
+
+  return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+  return {
+    id: this.nextSpecId(),
+    runs: function() {
+    }
+  };
+};
+
+jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) {
+  if (a.source != b.source)
+    mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/");
+
+  if (a.ignoreCase != b.ignoreCase)
+    mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier");
+
+  if (a.global != b.global)
+    mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier");
+
+  if (a.multiline != b.multiline)
+    mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier");
+
+  if (a.sticky != b.sticky)
+    mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier");
+
+  return (mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+  if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+    return true;
+  }
+
+  a.__Jasmine_been_here_before__ = b;
+  b.__Jasmine_been_here_before__ = a;
+
+  var hasKey = function(obj, keyName) {
+    return obj !== null && obj[keyName] !== jasmine.undefined;
+  };
+
+  for (var property in b) {
+    if (!hasKey(a, property) && hasKey(b, property)) {
+      mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+    }
+  }
+  for (property in a) {
+    if (!hasKey(b, property) && hasKey(a, property)) {
+      mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+    }
+  }
+  for (property in b) {
+    if (property == '__Jasmine_been_here_before__') continue;
+    if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+      mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
+    }
+  }
+
+  if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+    mismatchValues.push("arrays were not the same length");
+  }
+
+  delete a.__Jasmine_been_here_before__;
+  delete b.__Jasmine_been_here_before__;
+  return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+  mismatchKeys = mismatchKeys || [];
+  mismatchValues = mismatchValues || [];
+
+  for (var i = 0; i < this.equalityTesters_.length; i++) {
+    var equalityTester = this.equalityTesters_[i];
+    var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+    if (result !== jasmine.undefined) return result;
+  }
+
+  if (a === b) return true;
+
+  if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+    return (a == jasmine.undefined && b == jasmine.undefined);
+  }
+
+  if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+    return a === b;
+  }
+
+  if (a instanceof Date && b instanceof Date) {
+    return a.getTime() == b.getTime();
+  }
+
+  if (a.jasmineMatches) {
+    return a.jasmineMatches(b);
+  }
+
+  if (b.jasmineMatches) {
+    return b.jasmineMatches(a);
+  }
+
+  if (a instanceof jasmine.Matchers.ObjectContaining) {
+    return a.matches(b);
+  }
+
+  if (b instanceof jasmine.Matchers.ObjectContaining) {
+    return b.matches(a);
+  }
+
+  if (jasmine.isString_(a) && jasmine.isString_(b)) {
+    return (a == b);
+  }
+
+  if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+    return (a == b);
+  }
+
+  if (a instanceof RegExp && b instanceof RegExp) {
+    return this.compareRegExps_(a, b, mismatchKeys, mismatchValues);
+  }
+
+  if (typeof a === "object" && typeof b === "object") {
+    return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+  }
+
+  //Straight check
+  return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+  if (jasmine.isArray_(haystack)) {
+    for (var i = 0; i < haystack.length; i++) {
+      if (this.equals_(haystack[i], needle)) return true;
+    }
+    return false;
+  }
+  return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+  this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+  this.env = env;
+  this.func = func;
+  this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {
+  if (!jasmine.CATCH_EXCEPTIONS) {
+    this.func.apply(this.spec);
+  }
+  else {
+    try {
+      this.func.apply(this.spec);
+    } catch (e) {
+      this.spec.fail(e);
+    }
+  }
+  onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+  this.started = false;
+  this.finished = false;
+  this.suites_ = [];
+  this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+  this.started = true;
+  var suites = runner.topLevelSuites();
+  for (var i = 0; i < suites.length; i++) {
+    var suite = suites[i];
+    this.suites_.push(this.summarize_(suite));
+  }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+  return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+  var isSuite = suiteOrSpec instanceof jasmine.Suite;
+  var summary = {
+    id: suiteOrSpec.id,
+    name: suiteOrSpec.description,
+    type: isSuite ? 'suite' : 'spec',
+    children: []
+  };
+
+  if (isSuite) {
+    var children = suiteOrSpec.children();
+    for (var i = 0; i < children.length; i++) {
+      summary.children.push(this.summarize_(children[i]));
+    }
+  }
+  return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+  return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+  return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+  this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+  this.results_[spec.id] = {
+    messages: spec.results().getItems(),
+    result: spec.results().failedCount > 0 ? "failed" : "passed"
+  };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+  var results = {};
+  for (var i = 0; i < specIds.length; i++) {
+    var specId = specIds[i];
+    results[specId] = this.summarizeResult_(this.results_[specId]);
+  }
+  return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+  var summaryMessages = [];
+  var messagesLength = result.messages.length;
+  for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+    var resultMessage = result.messages[messageIndex];
+    summaryMessages.push({
+      text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
+      passed: resultMessage.passed ? resultMessage.passed() : true,
+      type: resultMessage.type,
+      message: resultMessage.message,
+      trace: {
+        stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+      }
+    });
+  }
+
+  return {
+    result : result.result,
+    messages : summaryMessages
+  };
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+  this.env = env;
+  this.actual = actual;
+  this.spec = spec;
+  this.isNot = opt_isNot || false;
+  this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+  throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+};
+
+// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+  throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+  for (var methodName in prototype) {
+    if (methodName == 'report') continue;
+    var orig = prototype[methodName];
+    matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+  }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+  return function() {
+    var matcherArgs = jasmine.util.argsToArray(arguments);
+    var result = matcherFunction.apply(this, arguments);
+
+    if (this.isNot) {
+      result = !result;
+    }
+
+    if (this.reportWasCalled_) return result;
+
+    var message;
+    if (!result) {
+      if (this.message) {
+        message = this.message.apply(this, arguments);
+        if (jasmine.isArray_(message)) {
+          message = message[this.isNot ? 1 : 0];
+        }
+      } else {
+        var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+        message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+        if (matcherArgs.length > 0) {
+          for (var i = 0; i < matcherArgs.length; i++) {
+            if (i > 0) message += ",";
+            message += " " + jasmine.pp(matcherArgs[i]);
+          }
+        }
+        message += ".";
+      }
+    }
+    var expectationResult = new jasmine.ExpectationResult({
+      matcherName: matcherName,
+      passed: result,
+      expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+      actual: this.actual,
+      message: message
+    });
+    this.spec.addMatcherResult(expectationResult);
+    return jasmine.undefined;
+  };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+  return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ * @deprecated as of 1.0. Use not.toBe() instead.
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+  return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+  return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ * @deprecated as of 1.0. Use not.toEqual() instead.
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+  return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression.  Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+  return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ * @deprecated as of 1.0. Use not.toMatch() instead.
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+  return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+  return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+  return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+  return (this.actual === null);
+};
+
+/**
+ * Matcher that compares the actual to NaN.
+ */
+jasmine.Matchers.prototype.toBeNaN = function() {
+	this.message = function() {
+		return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ];
+	};
+
+	return (this.actual !== this.actual);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+  return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+  return !this.actual;
+};
+
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.toHaveBeenCalled = function() {
+  if (arguments.length > 0) {
+    throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+  }
+
+  if (!jasmine.isSpy(this.actual)) {
+    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+  }
+
+  this.message = function() {
+    return [
+      "Expected spy " + this.actual.identity + " to have been called.",
+      "Expected spy " + this.actual.identity + " not to have been called."
+    ];
+  };
+
+  return this.actual.wasCalled;
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
+jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ *
+ * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+  if (arguments.length > 0) {
+    throw new Error('wasNotCalled does not take arguments');
+  }
+
+  if (!jasmine.isSpy(this.actual)) {
+    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+  }
+
+  this.message = function() {
+    return [
+      "Expected spy " + this.actual.identity + " to not have been called.",
+      "Expected spy " + this.actual.identity + " to have been called."
+    ];
+  };
+
+  return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
+  var expectedArgs = jasmine.util.argsToArray(arguments);
+  if (!jasmine.isSpy(this.actual)) {
+    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+  }
+  this.message = function() {
+    var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was.";
+    var positiveMessage = "";
+    if (this.actual.callCount === 0) {
+      positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.";
+    } else {
+      positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '')
+    }
+    return [positiveMessage, invertedMessage];
+  };
+
+  return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
+
+/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+  var expectedArgs = jasmine.util.argsToArray(arguments);
+  if (!jasmine.isSpy(this.actual)) {
+    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+  }
+
+  this.message = function() {
+    return [
+      "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
+      "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
+    ];
+  };
+
+  return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+  return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ * @deprecated as of 1.0. Use not.toContain() instead.
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+  return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+  return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+  return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected item is equal to the actual item
+ * up to a given level of decimal precision (default 2).
+ *
+ * @param {Number} expected
+ * @param {Number} precision, as number of decimal places
+ */
+jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
+  if (!(precision === 0)) {
+    precision = precision || 2;
+  }
+  return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2);
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} [expected]
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+  var result = false;
+  var exception;
+  if (typeof this.actual != 'function') {
+    throw new Error('Actual is not a function');
+  }
+  try {
+    this.actual();
+  } catch (e) {
+    exception = e;
+  }
+  if (exception) {
+    result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+  }
+
+  var not = this.isNot ? "not " : "";
+
+  this.message = function() {
+    if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+      return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
+    } else {
+      return "Expected function to throw an exception.";
+    }
+  };
+
+  return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+  this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
+  if (this.expectedClass == String) {
+    return typeof other == 'string' || other instanceof String;
+  }
+
+  if (this.expectedClass == Number) {
+    return typeof other == 'number' || other instanceof Number;
+  }
+
+  if (this.expectedClass == Function) {
+    return typeof other == 'function' || other instanceof Function;
+  }
+
+  if (this.expectedClass == Object) {
+    return typeof other == 'object';
+  }
+
+  return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.jasmineToString = function() {
+  return '<jasmine.any(' + this.expectedClass + ')>';
+};
+
+jasmine.Matchers.ObjectContaining = function (sample) {
+  this.sample = sample;
+};
+
+jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
+  mismatchKeys = mismatchKeys || [];
+  mismatchValues = mismatchValues || [];
+
+  var env = jasmine.getEnv();
+
+  var hasKey = function(obj, keyName) {
+    return obj != null && obj[keyName] !== jasmine.undefined;
+  };
+
+  for (var property in this.sample) {
+    if (!hasKey(other, property) && hasKey(this.sample, property)) {
+      mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+    }
+    else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
+      mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
+    }
+  }
+
+  return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
+  return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+  this.reset();
+
+  var self = this;
+  self.setTimeout = function(funcToCall, millis) {
+    self.timeoutsMade++;
+    self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+    return self.timeoutsMade;
+  };
+
+  self.setInterval = function(funcToCall, millis) {
+    self.timeoutsMade++;
+    self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+    return self.timeoutsMade;
+  };
+
+  self.clearTimeout = function(timeoutKey) {
+    self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+  };
+
+  self.clearInterval = function(timeoutKey) {
+    self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+  };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+  this.timeoutsMade = 0;
+  this.scheduledFunctions = {};
+  this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+  var oldMillis = this.nowMillis;
+  var newMillis = oldMillis + millis;
+  this.runFunctionsWithinRange(oldMillis, newMillis);
+  this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+  var scheduledFunc;
+  var funcsToRun = [];
+  for (var timeoutKey in this.scheduledFunctions) {
+    scheduledFunc = this.scheduledFunctions[timeoutKey];
+    if (scheduledFunc != jasmine.undefined &&
+        scheduledFunc.runAtMillis >= oldMillis &&
+        scheduledFunc.runAtMillis <= nowMillis) {
+      funcsToRun.push(scheduledFunc);
+      this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+    }
+  }
+
+  if (funcsToRun.length > 0) {
+    funcsToRun.sort(function(a, b) {
+      return a.runAtMillis - b.runAtMillis;
+    });
+    for (var i = 0; i < funcsToRun.length; ++i) {
+      try {
+        var funcToRun = funcsToRun[i];
+        this.nowMillis = funcToRun.runAtMillis;
+        funcToRun.funcToCall();
+        if (funcToRun.recurring) {
+          this.scheduleFunction(funcToRun.timeoutKey,
+              funcToRun.funcToCall,
+              funcToRun.millis,
+              true);
+        }
+      } catch(e) {
+      }
+    }
+    this.runFunctionsWithinRange(oldMillis, nowMillis);
+  }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+  this.scheduledFunctions[timeoutKey] = {
+    runAtMillis: this.nowMillis + millis,
+    funcToCall: funcToCall,
+    recurring: recurring,
+    timeoutKey: timeoutKey,
+    millis: millis
+  };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+  defaultFakeTimer: new jasmine.FakeTimer(),
+
+  reset: function() {
+    jasmine.Clock.assertInstalled();
+    jasmine.Clock.defaultFakeTimer.reset();
+  },
+
+  tick: function(millis) {
+    jasmine.Clock.assertInstalled();
+    jasmine.Clock.defaultFakeTimer.tick(millis);
+  },
+
+  runFunctionsWithinRange: function(oldMillis, nowMillis) {
+    jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+  },
+
+  scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+    jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+  },
+
+  useMock: function() {
+    if (!jasmine.Clock.isInstalled()) {
+      var spec = jasmine.getEnv().currentSpec;
+      spec.after(jasmine.Clock.uninstallMock);
+
+      jasmine.Clock.installMock();
+    }
+  },
+
+  installMock: function() {
+    jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+  },
+
+  uninstallMock: function() {
+    jasmine.Clock.assertInstalled();
+    jasmine.Clock.installed = jasmine.Clock.real;
+  },
+
+  real: {
+    setTimeout: jasmine.getGlobal().setTimeout,
+    clearTimeout: jasmine.getGlobal().clearTimeout,
+    setInterval: jasmine.getGlobal().setInterval,
+    clearInterval: jasmine.getGlobal().clearInterval
+  },
+
+  assertInstalled: function() {
+    if (!jasmine.Clock.isInstalled()) {
+      throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+    }
+  },
+
+  isInstalled: function() {
+    return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
+  },
+
+  installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
+  if (jasmine.Clock.installed.setTimeout.apply) {
+    return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+  } else {
+    return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+  }
+};
+
+jasmine.getGlobal().setInterval = function(funcToCall, millis) {
+  if (jasmine.Clock.installed.setInterval.apply) {
+    return jasmine.Clock.installed.setInterval.apply(this, arguments);
+  } else {
+    return jasmine.Clock.installed.setInterval(funcToCall, millis);
+  }
+};
+
+jasmine.getGlobal().clearTimeout = function(timeoutKey) {
+  if (jasmine.Clock.installed.clearTimeout.apply) {
+    return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+  } else {
+    return jasmine.Clock.installed.clearTimeout(timeoutKey);
+  }
+};
+
+jasmine.getGlobal().clearInterval = function(timeoutKey) {
+  if (jasmine.Clock.installed.clearTimeout.apply) {
+    return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+  } else {
+    return jasmine.Clock.installed.clearInterval(timeoutKey);
+  }
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+  this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+  this.subReporters_.push(reporter);
+};
+
+(function() {
+  var functionNames = [
+    "reportRunnerStarting",
+    "reportRunnerResults",
+    "reportSuiteResults",
+    "reportSpecStarting",
+    "reportSpecResults",
+    "log"
+  ];
+  for (var i = 0; i < functionNames.length; i++) {
+    var functionName = functionNames[i];
+    jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+      return function() {
+        for (var j = 0; j < this.subReporters_.length; j++) {
+          var subReporter = this.subReporters_[j];
+          if (subReporter[functionName]) {
+            subReporter[functionName].apply(subReporter, arguments);
+          }
+        }
+      };
+    })(functionName);
+  }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+  /**
+   * The total count of results
+   */
+  this.totalCount = 0;
+  /**
+   * Number of passed results
+   */
+  this.passedCount = 0;
+  /**
+   * Number of failed results
+   */
+  this.failedCount = 0;
+  /**
+   * Was this suite/spec skipped?
+   */
+  this.skipped = false;
+  /**
+   * @ignore
+   */
+  this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+  this.totalCount += result.totalCount;
+  this.passedCount += result.passedCount;
+  this.failedCount += result.failedCount;
+};
+
+/**
+ * Adds a log message.
+ * @param values Array of message parts which will be concatenated later.
+ */
+jasmine.NestedResults.prototype.log = function(values) {
+  this.items_.push(new jasmine.MessageResult(values));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+  return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+  if (result.type != 'log') {
+    if (result.items_) {
+      this.rollupCounts(result);
+    } else {
+      this.totalCount++;
+      if (result.passed()) {
+        this.passedCount++;
+      } else {
+        this.failedCount++;
+      }
+    }
+  }
+  this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if <b>everything</b> below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+  return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+  this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+  this.ppNestLevel_++;
+  try {
+    if (value === jasmine.undefined) {
+      this.emitScalar('undefined');
+    } else if (value === null) {
+      this.emitScalar('null');
+    } else if (value === jasmine.getGlobal()) {
+      this.emitScalar('<global>');
+    } else if (value.jasmineToString) {
+      this.emitScalar(value.jasmineToString());
+    } else if (typeof value === 'string') {
+      this.emitString(value);
+    } else if (jasmine.isSpy(value)) {
+      this.emitScalar("spy on " + value.identity);
+    } else if (value instanceof RegExp) {
+      this.emitScalar(value.toString());
+    } else if (typeof value === 'function') {
+      this.emitScalar('Function');
+    } else if (typeof value.nodeType === 'number') {
+      this.emitScalar('HTMLNode');
+    } else if (value instanceof Date) {
+      this.emitScalar('Date(' + value + ')');
+    } else if (value.__Jasmine_been_here_before__) {
+      this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
+    } else if (jasmine.isArray_(value) || typeof value == 'object') {
+      value.__Jasmine_been_here_before__ = true;
+      if (jasmine.isArray_(value)) {
+        this.emitArray(value);
+      } else {
+        this.emitObject(value);
+      }
+      delete value.__Jasmine_been_here_before__;
+    } else {
+      this.emitScalar(value.toString());
+    }
+  } finally {
+    this.ppNestLevel_--;
+  }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+  for (var property in obj) {
+    if (!obj.hasOwnProperty(property)) continue;
+    if (property == '__Jasmine_been_here_before__') continue;
+    fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && 
+                                         obj.__lookupGetter__(property) !== null) : false);
+  }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+  jasmine.PrettyPrinter.call(this);
+
+  this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+  this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+  this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+  if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
+    this.append("Array");
+    return;
+  }
+
+  this.append('[ ');
+  for (var i = 0; i < array.length; i++) {
+    if (i > 0) {
+      this.append(', ');
+    }
+    this.format(array[i]);
+  }
+  this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+  if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
+    this.append("Object");
+    return;
+  }
+
+  var self = this;
+  this.append('{ ');
+  var first = true;
+
+  this.iterateObject(obj, function(property, isGetter) {
+    if (first) {
+      first = false;
+    } else {
+      self.append(', ');
+    }
+
+    self.append(property);
+    self.append(' : ');
+    if (isGetter) {
+      self.append('<getter>');
+    } else {
+      self.format(obj[property]);
+    }
+  });
+
+  this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+  this.string += value;
+};
+jasmine.Queue = function(env) {
+  this.env = env;
+
+  // parallel to blocks. each true value in this array means the block will
+  // get executed even if we abort
+  this.ensured = [];
+  this.blocks = [];
+  this.running = false;
+  this.index = 0;
+  this.offset = 0;
+  this.abort = false;
+};
+
+jasmine.Queue.prototype.addBefore = function(block, ensure) {
+  if (ensure === jasmine.undefined) {
+    ensure = false;
+  }
+
+  this.blocks.unshift(block);
+  this.ensured.unshift(ensure);
+};
+
+jasmine.Queue.prototype.add = function(block, ensure) {
+  if (ensure === jasmine.undefined) {
+    ensure = false;
+  }
+
+  this.blocks.push(block);
+  this.ensured.push(ensure);
+};
+
+jasmine.Queue.prototype.insertNext = function(block, ensure) {
+  if (ensure === jasmine.undefined) {
+    ensure = false;
+  }
+
+  this.ensured.splice((this.index + this.offset + 1), 0, ensure);
+  this.blocks.splice((this.index + this.offset + 1), 0, block);
+  this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+  this.running = true;
+  this.onComplete = onComplete;
+  this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+  return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+  var self = this;
+  var goAgain = true;
+
+  while (goAgain) {
+    goAgain = false;
+
+    if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) {
+      var calledSynchronously = true;
+      var completedSynchronously = false;
+
+      var onComplete = function () {
+        if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+          completedSynchronously = true;
+          return;
+        }
+
+        if (self.blocks[self.index].abort) {
+          self.abort = true;
+        }
+
+        self.offset = 0;
+        self.index++;
+
+        var now = new Date().getTime();
+        if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+          self.env.lastUpdate = now;
+          self.env.setTimeout(function() {
+            self.next_();
+          }, 0);
+        } else {
+          if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+            goAgain = true;
+          } else {
+            self.next_();
+          }
+        }
+      };
+      self.blocks[self.index].execute(onComplete);
+
+      calledSynchronously = false;
+      if (completedSynchronously) {
+        onComplete();
+      }
+
+    } else {
+      self.running = false;
+      if (self.onComplete) {
+        self.onComplete();
+      }
+    }
+  }
+};
+
+jasmine.Queue.prototype.results = function() {
+  var results = new jasmine.NestedResults();
+  for (var i = 0; i < this.blocks.length; i++) {
+    if (this.blocks[i].results) {
+      results.addResult(this.blocks[i].results());
+    }
+  }
+  return results;
+};
+
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+  var self = this;
+  self.env = env;
+  self.queue = new jasmine.Queue(env);
+  self.before_ = [];
+  self.after_ = [];
+  self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+  var self = this;
+  if (self.env.reporter.reportRunnerStarting) {
+    self.env.reporter.reportRunnerStarting(this);
+  }
+  self.queue.start(function () {
+    self.finishCallback();
+  });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+  beforeEachFunction.typeName = 'beforeEach';
+  this.before_.splice(0,0,beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+  afterEachFunction.typeName = 'afterEach';
+  this.after_.splice(0,0,afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+  this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+  this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+  if (block instanceof jasmine.Suite) {
+    this.addSuite(block);
+  }
+  this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+  var suites = this.suites();
+  var specs = [];
+  for (var i = 0; i < suites.length; i++) {
+    specs = specs.concat(suites[i].specs());
+  }
+  return specs;
+};
+
+jasmine.Runner.prototype.suites = function() {
+  return this.suites_;
+};
+
+jasmine.Runner.prototype.topLevelSuites = function() {
+  var topLevelSuites = [];
+  for (var i = 0; i < this.suites_.length; i++) {
+    if (!this.suites_[i].parentSuite) {
+      topLevelSuites.push(this.suites_[i]);
+    }
+  }
+  return topLevelSuites;
+};
+
+jasmine.Runner.prototype.results = function() {
+  return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+  if (!env) {
+    throw new Error('jasmine.Env() required');
+  }
+  if (!suite) {
+    throw new Error('jasmine.Suite() required');
+  }
+  var spec = this;
+  spec.id = env.nextSpecId ? env.nextSpecId() : null;
+  spec.env = env;
+  spec.suite = suite;
+  spec.description = description;
+  spec.queue = new jasmine.Queue(env);
+
+  spec.afterCallbacks = [];
+  spec.spies_ = [];
+
+  spec.results_ = new jasmine.NestedResults();
+  spec.results_.description = description;
+  spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+  return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+  return this.results_;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> in production code.
+ */
+jasmine.Spec.prototype.log = function() {
+  return this.results_.log(arguments);
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+  var block = new jasmine.Block(this.env, func, this);
+  this.addToQueue(block);
+  return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+  if (this.queue.isRunning()) {
+    this.queue.insertNext(block);
+  } else {
+    this.queue.add(block);
+  }
+};
+
+/**
+ * @param {jasmine.ExpectationResult} result
+ */
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+  this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+  var positive = new (this.getMatchersClass_())(this.env, actual, this);
+  positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+  return positive;
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+jasmine.Spec.prototype.waits = function(timeout) {
+  var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+  this.addToQueue(waitsFunc);
+  return this;
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+  var latchFunction_ = null;
+  var optional_timeoutMessage_ = null;
+  var optional_timeout_ = null;
+
+  for (var i = 0; i < arguments.length; i++) {
+    var arg = arguments[i];
+    switch (typeof arg) {
+      case 'function':
+        latchFunction_ = arg;
+        break;
+      case 'string':
+        optional_timeoutMessage_ = arg;
+        break;
+      case 'number':
+        optional_timeout_ = arg;
+        break;
+    }
+  }
+
+  var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
+  this.addToQueue(waitsForFunc);
+  return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+  var expectationResult = new jasmine.ExpectationResult({
+    passed: false,
+    message: e ? jasmine.util.formatException(e) : 'Exception',
+    trace: { stack: e.stack }
+  });
+  this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+  return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+  var parent = this.getMatchersClass_();
+  var newMatchersClass = function() {
+    parent.apply(this, arguments);
+  };
+  jasmine.util.inherit(newMatchersClass, parent);
+  jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+  this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+  this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+  this.removeAllSpies();
+  this.finishCallback();
+  if (onComplete) {
+    onComplete();
+  }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+  if (this.queue.isRunning()) {
+    this.queue.add(new jasmine.Block(this.env, doAfter, this), true);
+  } else {
+    this.afterCallbacks.unshift(doAfter);
+  }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+  var spec = this;
+  if (!spec.env.specFilter(spec)) {
+    spec.results_.skipped = true;
+    spec.finish(onComplete);
+    return;
+  }
+
+  this.env.reporter.reportSpecStarting(this);
+
+  spec.env.currentSpec = spec;
+
+  spec.addBeforesAndAftersToQueue();
+
+  spec.queue.start(function () {
+    spec.finish(onComplete);
+  });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+  var runner = this.env.currentRunner();
+  var i;
+
+  for (var suite = this.suite; suite; suite = suite.parentSuite) {
+    for (i = 0; i < suite.before_.length; i++) {
+      this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+    }
+  }
+  for (i = 0; i < runner.before_.length; i++) {
+    this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+  }
+  for (i = 0; i < this.afterCallbacks.length; i++) {
+    this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true);
+  }
+  for (suite = this.suite; suite; suite = suite.parentSuite) {
+    for (i = 0; i < suite.after_.length; i++) {
+      this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true);
+    }
+  }
+  for (i = 0; i < runner.after_.length; i++) {
+    this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true);
+  }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+  throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+  if (obj == jasmine.undefined) {
+    throw "spyOn could not find an object to spy upon for " + methodName + "()";
+  }
+
+  if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+    throw methodName + '() method does not exist';
+  }
+
+  if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+    throw new Error(methodName + ' has already been spied upon');
+  }
+
+  var spyObj = jasmine.createSpy(methodName);
+
+  this.spies_.push(spyObj);
+  spyObj.baseObj = obj;
+  spyObj.methodName = methodName;
+  spyObj.originalValue = obj[methodName];
+
+  obj[methodName] = spyObj;
+
+  return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+  for (var i = 0; i < this.spies_.length; i++) {
+    var spy = this.spies_[i];
+    spy.baseObj[spy.methodName] = spy.originalValue;
+  }
+  this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+  var self = this;
+  self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+  self.description = description;
+  self.queue = new jasmine.Queue(env);
+  self.parentSuite = parentSuite;
+  self.env = env;
+  self.before_ = [];
+  self.after_ = [];
+  self.children_ = [];
+  self.suites_ = [];
+  self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+  var fullName = this.description;
+  for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+    fullName = parentSuite.description + ' ' + fullName;
+  }
+  return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+  this.env.reporter.reportSuiteResults(this);
+  this.finished = true;
+  if (typeof(onComplete) == 'function') {
+    onComplete();
+  }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+  beforeEachFunction.typeName = 'beforeEach';
+  this.before_.unshift(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+  afterEachFunction.typeName = 'afterEach';
+  this.after_.unshift(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+  return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(suiteOrSpec) {
+  this.children_.push(suiteOrSpec);
+  if (suiteOrSpec instanceof jasmine.Suite) {
+    this.suites_.push(suiteOrSpec);
+    this.env.currentRunner().addSuite(suiteOrSpec);
+  } else {
+    this.specs_.push(suiteOrSpec);
+  }
+  this.queue.add(suiteOrSpec);
+};
+
+jasmine.Suite.prototype.specs = function() {
+  return this.specs_;
+};
+
+jasmine.Suite.prototype.suites = function() {
+  return this.suites_;
+};
+
+jasmine.Suite.prototype.children = function() {
+  return this.children_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+  var self = this;
+  this.queue.start(function () {
+    self.finish(onComplete);
+  });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+  this.timeout = timeout;
+  jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+  if (jasmine.VERBOSE) {
+    this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+  }
+  this.env.setTimeout(function () {
+    onComplete();
+  }, this.timeout);
+};
+/**
+ * A block which waits for some condition to become true, with timeout.
+ *
+ * @constructor
+ * @extends jasmine.Block
+ * @param {jasmine.Env} env The Jasmine environment.
+ * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
+ * @param {Function} latchFunction A function which returns true when the desired condition has been met.
+ * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
+ * @param {jasmine.Spec} spec The Jasmine spec.
+ */
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+  this.timeout = timeout || env.defaultTimeoutInterval;
+  this.latchFunction = latchFunction;
+  this.message = message;
+  this.totalTimeSpentWaitingForLatch = 0;
+  jasmine.Block.call(this, env, null, spec);
+};
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
+
+jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
+  if (jasmine.VERBOSE) {
+    this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
+  }
+  var latchFunctionResult;
+  try {
+    latchFunctionResult = this.latchFunction.apply(this.spec);
+  } catch (e) {
+    this.spec.fail(e);
+    onComplete();
+    return;
+  }
+
+  if (latchFunctionResult) {
+    onComplete();
+  } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
+    var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
+    this.spec.fail({
+      name: 'timeout',
+      message: message
+    });
+
+    this.abort = true;
+    onComplete();
+  } else {
+    this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+    var self = this;
+    this.env.setTimeout(function() {
+      self.execute(onComplete);
+    }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+  }
+};
+
+jasmine.version_= {
+  "major": 1,
+  "minor": 3,
+  "build": 1,
+  "revision": 1354556913
+};
+
diff --git a/tests/lib/jasmine_favicon.png b/tests/lib/jasmine_favicon.png
new file mode 100644
index 0000000..218f3b4
Binary files /dev/null and b/tests/lib/jasmine_favicon.png differ
diff --git a/tests/lib/json2.js b/tests/lib/json2.js
new file mode 100644
index 0000000..ac58079
--- /dev/null
+++ b/tests/lib/json2.js
@@ -0,0 +1,478 @@
+/*
+    http://www.JSON.org/json2.js
+    2009-08-17
+
+    Public Domain.
+
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+    See http://www.JSON.org/js.html
+
+    This file creates a global JSON object containing two methods: stringify
+    and parse.
+
+        JSON.stringify(value, replacer, space)
+            value       any JavaScript value, usually an object or array.
+
+            replacer    an optional parameter that determines how object
+                        values are stringified for objects. It can be a
+                        function or an array of strings.
+
+            space       an optional parameter that specifies the indentation
+                        of nested structures. If it is omitted, the text will
+                        be packed without extra whitespace. If it is a number,
+                        it will specify the number of spaces to indent at each
+                        level. If it is a string (such as '\t' or ' '),
+                        it contains the characters used to indent at each level.
+
+            This method produces a JSON text from a JavaScript value.
+
+            When an object value is found, if the object contains a toJSON
+            method, its toJSON method will be called and the result will be
+            stringified. A toJSON method does not serialize: it returns the
+            value represented by the name/value pair that should be serialized,
+            or undefined if nothing should be serialized. The toJSON method
+            will be passed the key associated with the value, and this will be
+            bound to the value
+
+            For example, this would serialize Dates as ISO strings.
+
+                Date.prototype.toJSON = function (key) {
+                    function f(n) {
+                        // Format integers to have at least two digits.
+                        return n < 10 ? '0' + n : n;
+                    }
+
+                    return this.getUTCFullYear()   + '-' +
+                         f(this.getUTCMonth() + 1) + '-' +
+                         f(this.getUTCDate())      + 'T' +
+                         f(this.getUTCHours())     + ':' +
+                         f(this.getUTCMinutes())   + ':' +
+                         f(this.getUTCSeconds())   + 'Z';
+                };
+
+            You can provide an optional replacer method. It will be passed the
+            key and value of each member, with this bound to the containing
+            object. The value that is returned from your method will be
+            serialized. If your method returns undefined, then the member will
+            be excluded from the serialization.
+
+            If the replacer parameter is an array of strings, then it will be
+            used to select the members to be serialized. It filters the results
+            such that only members with keys listed in the replacer array are
+            stringified.
+
+            Values that do not have JSON representations, such as undefined or
+            functions, will not be serialized. Such values in objects will be
+            dropped; in arrays they will be replaced with null. You can use
+            a replacer function to replace those with JSON values.
+            JSON.stringify(undefined) returns undefined.
+
+            The optional space parameter produces a stringification of the
+            value that is filled with line breaks and indentation to make it
+            easier to read.
+
+            If the space parameter is a non-empty string, then that string will
+            be used for indentation. If the space parameter is a number, then
+            the indentation will be that many spaces.
+
+            Example:
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
+            // text is '["e",{"pluribus":"unum"}]'
+
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+            text = JSON.stringify([new Date()], function (key, value) {
+                return this[key] instanceof Date ?
+                    'Date(' + this[key] + ')' : value;
+            });
+            // text is '["Date(---current time---)"]'
+
+
+        JSON.parse(text, reviver)
+            This method parses a JSON text to produce an object or array.
+            It can throw a SyntaxError exception.
+
+            The optional reviver parameter is a function that can filter and
+            transform the results. It receives each of the keys and values,
+            and its return value is used instead of the original value.
+            If it returns what it received, then the structure is not modified.
+            If it returns undefined then the member is deleted.
+
+            Example:
+
+            // Parse the text. Values that look like ISO date strings will
+            // be converted to Date objects.
+
+            myData = JSON.parse(text, function (key, value) {
+                var a;
+                if (typeof value === 'string') {
+                    a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+                    if (a) {
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+                            +a[5], +a[6]));
+                    }
+                }
+                return value;
+            });
+
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+                var d;
+                if (typeof value === 'string' &&
+                        value.slice(0, 5) === 'Date(' &&
+                        value.slice(-1) === ')') {
+                    d = new Date(value.slice(5, -1));
+                    if (d) {
+                        return d;
+                    }
+                }
+                return value;
+            });
+
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+
+    This code should be minified before deployment.
+    See http://javascript.crockford.com/jsmin.html
+
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+    NOT CONTROL.
+*/
+
+/*jslint evil: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+    lastIndex, length, parse, prototype, push, replace, slice, stringify,
+    test, toJSON, toString, valueOf
+*/
+
+"use strict";
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+    this.JSON = {};
+}
+
+(function () {
+
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function (key) {
+
+            return isFinite(this.valueOf()) ?
+                   this.getUTCFullYear()   + '-' +
+                 f(this.getUTCMonth() + 1) + '-' +
+                 f(this.getUTCDate())      + 'T' +
+                 f(this.getUTCHours())     + ':' +
+                 f(this.getUTCMinutes())   + ':' +
+                 f(this.getUTCSeconds())   + 'Z' : null;
+        };
+
+        String.prototype.toJSON =
+        Number.prototype.toJSON =
+        Boolean.prototype.toJSON = function (key) {
+            return this.valueOf();
+        };
+    }
+
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        gap,
+        indent,
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        },
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        escapable.lastIndex = 0;
+        return escapable.test(string) ?
+            '"' + string.replace(escapable, function (a) {
+                var c = meta[a];
+                return typeof c === 'string' ? c :
+                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+            }) + '"' :
+            '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+                typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+        case 'string':
+            return quote(value);
+
+        case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+            return isFinite(value) ? String(value) : 'null';
+
+        case 'boolean':
+        case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+            return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+        case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+            if (!value) {
+                return 'null';
+            }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+            gap += indent;
+            partial = [];
+
+// Is the value an array?
+
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                length = value.length;
+                for (i = 0; i < length; i += 1) {
+                    partial[i] = str(i, value) || 'null';
+                }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                v = partial.length === 0 ? '[]' :
+                    gap ? '[\n' + gap +
+                            partial.join(',\n' + gap) + '\n' +
+                                mind + ']' :
+                          '[' + partial.join(',') + ']';
+                gap = mind;
+                return v;
+            }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+            if (rep && typeof rep === 'object') {
+                length = rep.length;
+                for (i = 0; i < length; i += 1) {
+                    k = rep[i];
+                    if (typeof k === 'string') {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                for (k in value) {
+                    if (Object.hasOwnProperty.call(value, k)) {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+            v = partial.length === 0 ? '{}' :
+                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+                        mind + '}' : '{' + partial.join(',') + '}';
+            gap = mind;
+            return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof JSON.stringify !== 'function') {
+        JSON.stringify = function (value, replacer, space) {
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                    (typeof replacer !== 'object' ||
+                     typeof replacer.length !== 'number')) {
+                throw new Error('JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof JSON.parse !== 'function') {
+        JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            cx.lastIndex = 0;
+            if (cx.test(text)) {
+                text = text.replace(cx, function (a) {
+                    return '\\u' +
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function' ?
+                    walk({'': j}, '') : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('JSON.parse');
+        };
+    }
+}());
diff --git a/tests/native.html b/tests/native.html
new file mode 100644
index 0000000..82631ff
--- /dev/null
+++ b/tests/native.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+	<title>Jasmine Spec Runner</title>
+
+	<link rel="shortcut icon" type="image/png" href="lib/jasmine_favicon.png">
+
+	<link rel="stylesheet" type="text/css" href="lib/jasmine.css">
+	<script type="text/javascript" src="lib/jasmine.js"></script>
+	<script type="text/javascript" src="lib/jasmine-html.js"></script>
+	<script type="text/javascript" src="lib/json2.js"></script>
+
+	<!-- include helper files here... -->
+	<script src="helpers/h-matchers.js"></script>
+
+	<!-- include spec files here... -->
+	<script src="spec/s-array.js"></script>
+	<script src="spec/s-function.js"></script>
+	<script src="spec/s-string.js"></script>
+	<script src="spec/s-object.js"></script>
+	<script src="spec/s-number.js"></script>
+	<script src="spec/s-date.js"></script>
+
+
+	<script type="text/javascript">
+		(function() {
+			var jasmineEnv = jasmine.getEnv();
+			jasmineEnv.updateInterval = 1000;
+
+			var trivialReporter = new jasmine.TrivialReporter();
+
+			jasmineEnv.addReporter(trivialReporter);
+
+			jasmineEnv.specFilter = function(spec) {
+				return trivialReporter.specFilter(spec);
+			};
+
+			var currentWindowOnload = window.onload;
+
+			window.onload = function() {
+				if (currentWindowOnload) {
+					currentWindowOnload();
+				}
+				execJasmine();
+			};
+
+			function execJasmine() {
+				jasmineEnv.execute();
+			}
+
+		})();
+	</script>
+
+</head>
+
+<body>
+</body>
+</html>
diff --git a/tests/spec/helpers-jasmine.js b/tests/spec/helpers-jasmine.js
new file mode 100644
index 0000000..66ace86
--- /dev/null
+++ b/tests/spec/helpers-jasmine.js
@@ -0,0 +1,2 @@
+/* globals require */
+require('../helpers/h-matchers');
diff --git a/tests/spec/s-array.js b/tests/spec/s-array.js
new file mode 100644
index 0000000..4de5872
--- /dev/null
+++ b/tests/spec/s-array.js
@@ -0,0 +1,1320 @@
+/* global describe, it, expect, beforeEach, jasmine, xit */
+
+var toStr = Object.prototype.toString;
+// var canDistinguishSparseFromUndefined = 0 in [undefined]; // IE 6 - 8 have a bug where this returns false.
+var hasStrictMode = (function () {
+  'use strict';
+
+   return !this;
+}());
+
+describe('Array', function () {
+    var testSubject;
+    beforeEach(function () {
+        testSubject = [2, 3, undefined, true, 'hej', null, false, 0];
+        delete testSubject[1];
+    });
+    var createArrayLikeFromArray = function createArrayLikeFromArray(arr) {
+        var o = {};
+        Array.prototype.forEach.call(arr, function (e, i) {
+            o[i] = e;
+        });
+        o.length = arr.length;
+        return o;
+    };
+
+    describe('forEach', function () {
+        var expected, actual;
+
+        beforeEach(function () {
+            expected = { 0: 2, 2: undefined, 3: true, 4: 'hej', 5: null, 6: false, 7: 0 };
+            actual = {};
+        });
+        it('should pass the right parameters', function () {
+            var callback = jasmine.createSpy('callback'),
+                array = ['1'];
+            array.forEach(callback);
+            expect(callback).toHaveBeenCalledWith('1', 0, array);
+        });
+        it('should not affect elements added to the array after it has begun', function () {
+            var arr = [1, 2, 3],
+                i = 0;
+            arr.forEach(function (a) {
+                i++;
+                arr.push(a + 3);
+            });
+            expect(arr).toEqual([1, 2, 3, 4, 5, 6]);
+            expect(i).toBe(3);
+        });
+
+        it('should set the right context when given none', function () {
+            var context;
+            [1].forEach(function () { context = this; });
+            expect(context).toBe(function () { return this; }.call());
+        });
+        it('should iterate all', function () {
+            testSubject.forEach(function (obj, index) {
+                actual[index] = obj;
+            });
+            expect(actual).toExactlyMatch(expected);
+        });
+        it('should iterate all using a context', function () {
+            var o = { a: actual };
+
+            testSubject.forEach(function (obj, index) {
+                this.a[index] = obj;
+            }, o);
+            expect(actual).toExactlyMatch(expected);
+        });
+
+        it('should iterate all in an array-like object', function () {
+            var ts = createArrayLikeFromArray(testSubject);
+            Array.prototype.forEach.call(ts, function (obj, index) {
+                actual[index] = obj;
+            });
+            expect(actual).toExactlyMatch(expected);
+        });
+        it('should iterate all in an array-like object using a context', function () {
+            var ts = createArrayLikeFromArray(testSubject),
+                o = { a: actual };
+
+            Array.prototype.forEach.call(ts, function (obj, index) {
+                this.a[index] = obj;
+            }, o);
+            expect(actual).toExactlyMatch(expected);
+        });
+
+        describe('strings', function () {
+            var str = 'Hello, World!';
+            it('should iterate all in a string', function () {
+                actual = [];
+                Array.prototype.forEach.call(str, function (item, index) {
+                    actual[index] = item;
+                });
+                expect(actual).toExactlyMatch(str.split(''));
+            });
+            it('should iterate all in a string using a context', function () {
+                actual = [];
+                var o = { a: actual };
+                Array.prototype.forEach.call(str, function (item, index) {
+                    this.a[index] = item;
+                }, o);
+                expect(actual).toExactlyMatch(str.split(''));
+            });
+        });
+        it('should have a boxed object as list argument of callback', function () {
+            var listArg;
+            Array.prototype.forEach.call('foo', function (item, index, list) {
+                listArg = list;
+            });
+            expect(typeof listArg).toBe('object');
+            expect(toStr.call(listArg)).toBe('[object String]');
+        });
+        if (hasStrictMode) {
+            it('does not autobox the content in strict mode', function () {
+                var context;
+                [1].forEach(function () {
+                    'use strict';
+
+                    context = this;
+                }, 'x');
+                expect(typeof context).toBe('string');
+            });
+        }
+    });
+    describe('some', function () {
+        var actual, expected, numberOfRuns;
+
+        beforeEach(function () {
+            expected = { 0: 2, 2: undefined, 3: true };
+            actual = {};
+            numberOfRuns = 0;
+        });
+
+        it('should pass the correct values along to the callback', function () {
+            var callback = jasmine.createSpy('callback');
+            var array = ['1'];
+            array.some(callback);
+            expect(callback).toHaveBeenCalledWith('1', 0, array);
+        });
+        it('should not affect elements added to the array after it has begun', function () {
+            var arr = [1, 2, 3],
+                i = 0;
+            arr.some(function (a) {
+                i++;
+                arr.push(a + 3);
+                return i > 3;
+            });
+            expect(arr).toEqual([1, 2, 3, 4, 5, 6]);
+            expect(i).toBe(3);
+        });
+        it('should set the right context when given none', function () {
+            var context;
+            [1].some(function () { context = this; });
+            expect(context).toBe(function () { return this; }.call());
+        });
+
+        it('should return false if it runs to the end', function () {
+            actual = testSubject.some(function () {});
+            expect(actual).toBeFalsy();
+        });
+        it('should return true if it is stopped somewhere', function () {
+            actual = testSubject.some(function () { return true; });
+            expect(actual).toBeTruthy();
+        });
+        it('should return false if there are no elements', function () {
+            actual = [].some(function () { return true; });
+            expect(actual).toBeFalsy();
+        });
+
+        it('should stop after 3 elements', function () {
+            testSubject.some(function (obj, index) {
+                actual[index] = obj;
+                numberOfRuns += 1;
+                return numberOfRuns === 3;
+            });
+            expect(actual).toExactlyMatch(expected);
+        });
+        it('should stop after 3 elements using a context', function () {
+            var o = { a: actual };
+            testSubject.some(function (obj, index) {
+                this.a[index] = obj;
+                numberOfRuns += 1;
+                return numberOfRuns === 3;
+            }, o);
+            expect(actual).toExactlyMatch(expected);
+        });
+
+        it('should stop after 3 elements in an array-like object', function () {
+            var ts = createArrayLikeFromArray(testSubject);
+            Array.prototype.some.call(ts, function (obj, index) {
+                actual[index] = obj;
+                numberOfRuns += 1;
+                return numberOfRuns === 3;
+            });
+            expect(actual).toExactlyMatch(expected);
+        });
+        it('should stop after 3 elements in an array-like object using a context', function () {
+            var ts = createArrayLikeFromArray(testSubject);
+            var o = { a: actual };
+            Array.prototype.some.call(ts, function (obj, index) {
+                this.a[index] = obj;
+                numberOfRuns += 1;
+                return numberOfRuns === 3;
+            }, o);
+            expect(actual).toExactlyMatch(expected);
+        });
+        it('should have a boxed object as list argument of callback', function () {
+            var listArg;
+            Array.prototype.some.call('foo', function (item, index, list) {
+                listArg = list;
+            });
+            expect(typeof listArg).toBe('object');
+            expect(toStr.call(listArg)).toBe('[object String]');
+        });
+    });
+    describe('every', function () {
+        var actual, expected, numberOfRuns;
+
+        beforeEach(function () {
+            expected = { 0: 2, 2: undefined, 3: true };
+            actual = {};
+            numberOfRuns = 0;
+        });
+
+        it('should pass the correct values along to the callback', function () {
+            var callback = jasmine.createSpy('callback');
+            var array = ['1'];
+            array.every(callback);
+            expect(callback).toHaveBeenCalledWith('1', 0, array);
+        });
+        it('should not affect elements added to the array after it has begun', function () {
+            var arr = [1, 2, 3],
+                i = 0;
+            arr.every(function (a) {
+                i++;
+                arr.push(a + 3);
+                return i <= 3;
+            });
+            expect(arr).toEqual([1, 2, 3, 4, 5, 6]);
+            expect(i).toBe(3);
+        });
+        it('should set the right context when given none', function () {
+            var context;
+            [1].every(function () { context = this; });
+            expect(context).toBe(function () { return this; }.call());
+        });
+
+        it('should return true if the array is empty', function () {
+            actual = [].every(function () { return true; });
+            expect(actual).toBeTruthy();
+
+            actual = [].every(function () { return false; });
+            expect(actual).toBeTruthy();
+        });
+        it('should return true if it runs to the end', function () {
+            actual = [1, 2, 3].every(function () { return true; });
+            expect(actual).toBeTruthy();
+        });
+        it('should return false if it is stopped before the end', function () {
+            actual = [1, 2, 3].every(function () { return false; });
+            expect(actual).toBeFalsy();
+        });
+
+        it('should return after 3 elements', function () {
+            testSubject.every(function (obj, index) {
+                actual[index] = obj;
+                numberOfRuns += 1;
+                return numberOfRuns !== 3;
+            });
+            expect(actual).toExactlyMatch(expected);
+        });
+        it('should stop after 3 elements using a context', function () {
+            var o = { a: actual };
+            testSubject.every(function (obj, index) {
+                this.a[index] = obj;
+                numberOfRuns += 1;
+                return numberOfRuns !== 3;
+            }, o);
+            expect(actual).toExactlyMatch(expected);
+        });
+
+        it('should stop after 3 elements in an array-like object', function () {
+            var ts = createArrayLikeFromArray(testSubject);
+            Array.prototype.every.call(ts, function (obj, index) {
+                actual[index] = obj;
+                numberOfRuns += 1;
+                return numberOfRuns !== 3;
+            });
+            expect(actual).toExactlyMatch(expected);
+        });
+        it('should stop after 3 elements in an array-like object using a context', function () {
+            var ts = createArrayLikeFromArray(testSubject);
+            var o = { a: actual };
+            Array.prototype.every.call(ts, function (obj, index) {
+                this.a[index] = obj;
+                numberOfRuns += 1;
+                return numberOfRuns !== 3;
+            }, o);
+            expect(actual).toExactlyMatch(expected);
+        });
+        it('should have a boxed object as list argument of callback', function () {
+            var listArg;
+            Array.prototype.every.call('foo', function (item, index, list) {
+                listArg = list;
+            });
+            expect(typeof listArg).toBe('object');
+            expect(toStr.call(listArg)).toBe('[object String]');
+        });
+    });
+
+    describe('indexOf', function () {
+        'use strict';
+
+        var actual, expected;
+
+        beforeEach(function () {
+            testSubject = [2, 3, undefined, true, 'hej', null, 2, false, 0];
+            delete testSubject[1];
+
+        });
+
+        it('should find the element', function () {
+            expected = 4;
+            actual = testSubject.indexOf('hej');
+            expect(actual).toBe(expected);
+        });
+        it('should not find the element', function () {
+            expected = -1;
+            actual = testSubject.indexOf('mus');
+            expect(actual).toBe(expected);
+        });
+        it('should find undefined as well', function () {
+            expected = -1;
+            actual = testSubject.indexOf(undefined);
+            expect(actual).not.toBe(expected);
+        });
+        it('should skip unset indexes', function () {
+            expected = 2;
+            actual = testSubject.indexOf(undefined);
+            expect(actual).toBe(expected);
+        });
+        it('should use a strict test', function () {
+            actual = testSubject.indexOf(null);
+            expect(actual).toBe(5);
+
+            actual = testSubject.indexOf('2');
+            expect(actual).toBe(-1);
+        });
+        it('should skip the first if fromIndex is set', function () {
+            expect(testSubject.indexOf(2, 2)).toBe(6);
+            expect(testSubject.indexOf(2, 0)).toBe(0);
+            expect(testSubject.indexOf(2, 6)).toBe(6);
+        });
+        it('should work with negative fromIndex', function () {
+            expect(testSubject.indexOf(2, -3)).toBe(6);
+            expect(testSubject.indexOf(2, -9)).toBe(0);
+        });
+        it('should work with fromIndex being greater than the length', function () {
+            expect(testSubject.indexOf(0, 20)).toBe(-1);
+        });
+        it('should work with fromIndex being negative and greater than the length', function () {
+            expect(testSubject.indexOf('hej', -20)).toBe(4);
+        });
+
+        describe('Array-like', function ArrayLike() {
+            var indexOf = Array.prototype.indexOf,
+                testAL;
+            beforeEach(function beforeEach() {
+                testAL = {};
+                testSubject = [2, 3, undefined, true, 'hej', null, 2, false, 0];
+                testSubject.forEach(function (o, i) {
+                    testAL[i] = o;
+                });
+                testAL.length = testSubject.length;
+            });
+            it('should find the element (array-like)', function () {
+                expected = 4;
+                actual = indexOf.call(testAL, 'hej');
+                expect(actual).toBe(expected);
+            });
+            it('should not find the element (array-like)', function () {
+                expected = -1;
+                actual = indexOf.call(testAL, 'mus');
+                expect(actual).toBe(expected);
+            });
+            it('should find undefined as well (array-like)', function () {
+                expected = -1;
+                actual = indexOf.call(testAL, undefined);
+                expect(actual).not.toBe(expected);
+            });
+            it('should skip unset indexes (array-like)', function () {
+                expected = 2;
+                actual = indexOf.call(testAL, undefined);
+                expect(actual).toBe(expected);
+            });
+            it('should use a strict test (array-like)', function () {
+                actual = Array.prototype.indexOf.call(testAL, null);
+                expect(actual).toBe(5);
+
+                actual = Array.prototype.indexOf.call(testAL, '2');
+                expect(actual).toBe(-1);
+            });
+            it('should skip the first if fromIndex is set (array-like)', function () {
+                expect(indexOf.call(testAL, 2, 2)).toBe(6);
+                expect(indexOf.call(testAL, 2, 0)).toBe(0);
+                expect(indexOf.call(testAL, 2, 6)).toBe(6);
+            });
+            it('should work with negative fromIndex (array-like)', function () {
+                expect(indexOf.call(testAL, 2, -3)).toBe(6);
+                expect(indexOf.call(testAL, 2, -9)).toBe(0);
+            });
+            it('should work with fromIndex being greater than the length (array-like)', function () {
+                expect(indexOf.call(testAL, 0, 20)).toBe(-1);
+            });
+            it('should work with fromIndex being negative and greater than the length (array-like)', function () {
+                expect(indexOf.call(testAL, 'hej', -20)).toBe(4);
+            });
+        });
+    });
+    describe('lastIndexOf', function () {
+        'use strict';
+
+        var actual, expected;
+
+        beforeEach(function () {
+            testSubject = [2, 3, undefined, true, 'hej', null, 2, 3, false, 0];
+            delete testSubject[1];
+            delete testSubject[7];
+        });
+        describe('Array', function () {
+            it('should find the element', function () {
+                expected = 4;
+                actual = testSubject.lastIndexOf('hej');
+                expect(actual).toBe(expected);
+            });
+            it('should not find the element', function () {
+                expected = -1;
+                actual = testSubject.lastIndexOf('mus');
+                expect(actual).toBe(expected);
+            });
+            it('should find undefined as well', function () {
+                expected = -1;
+                actual = testSubject.lastIndexOf(undefined);
+                expect(actual).not.toBe(expected);
+            });
+            it('should skip unset indexes', function () {
+                expected = 2;
+                actual = testSubject.lastIndexOf(undefined);
+                expect(actual).toBe(expected);
+            });
+            it('should use a strict test', function () {
+                actual = testSubject.lastIndexOf(null);
+                expect(actual).toBe(5);
+
+                actual = testSubject.lastIndexOf('2');
+                expect(actual).toBe(-1);
+            });
+            it('should skip the first if fromIndex is set', function () {
+                expect(testSubject.lastIndexOf(2, 2)).toBe(0);
+                expect(testSubject.lastIndexOf(2, 0)).toBe(0);
+                expect(testSubject.lastIndexOf(2, 6)).toBe(6);
+            });
+            it('should work with negative fromIndex', function () {
+                expect(testSubject.lastIndexOf(2, -3)).toBe(6);
+                expect(testSubject.lastIndexOf(2, -9)).toBe(0);
+            });
+            it('should work with fromIndex being greater than the length', function () {
+                expect(testSubject.lastIndexOf(2, 20)).toBe(6);
+            });
+            it('should work with fromIndex being negative and greater than the length', function () {
+                expect(testSubject.lastIndexOf(2, -20)).toBe(-1);
+            });
+        });
+
+        describe('Array like', function () {
+            var lastIndexOf = Array.prototype.lastIndexOf,
+                testAL;
+            beforeEach(function () {
+                testAL = {};
+                testSubject.forEach(function (o, i) {
+                    testAL[i] = o;
+                });
+                testAL.length = testSubject.length;
+            });
+            it('should find the element (array-like)', function () {
+                expected = 4;
+                actual = lastIndexOf.call(testAL, 'hej');
+                expect(actual).toBe(expected);
+            });
+            it('should not find the element (array-like)', function () {
+                expected = -1;
+                actual = lastIndexOf.call(testAL, 'mus');
+                expect(actual).toBe(expected);
+            });
+            it('should find undefined as well (array-like)', function () {
+                expected = -1;
+                actual = lastIndexOf.call(testAL, undefined);
+                expect(actual).not.toBe(expected);
+            });
+            it('should skip unset indexes (array-like)', function () {
+                expected = 2;
+                actual = lastIndexOf.call(testAL, undefined);
+                expect(actual).toBe(expected);
+            });
+            it('should use a strict test (array-like)', function () {
+                actual = lastIndexOf.call(testAL, null);
+                expect(actual).toBe(5);
+
+                actual = lastIndexOf.call(testAL, '2');
+                expect(actual).toBe(-1);
+            });
+            it('should skip the first if fromIndex is set', function () {
+                expect(lastIndexOf.call(testAL, 2, 2)).toBe(0);
+                expect(lastIndexOf.call(testAL, 2, 0)).toBe(0);
+                expect(lastIndexOf.call(testAL, 2, 6)).toBe(6);
+            });
+            it('should work with negative fromIndex', function () {
+                expect(lastIndexOf.call(testAL, 2, -3)).toBe(6);
+                expect(lastIndexOf.call(testAL, 2, -9)).toBe(0);
+            });
+            it('should work with fromIndex being greater than the length', function () {
+                expect(lastIndexOf.call(testAL, 2, 20)).toBe(6);
+            });
+            it('should work with fromIndex being negative and greater than the length', function () {
+                expect(lastIndexOf.call(testAL, 2, -20)).toBe(-1);
+            });
+        });
+    });
+
+    describe('filter', function () {
+        var filteredArray,
+            callback = function callback(o, i) {
+                return i !== 3 && i !== 5;
+            };
+
+        beforeEach(function () {
+            testSubject = [2, 3, undefined, true, 'hej', 3, null, false, 0];
+            delete testSubject[1];
+            filteredArray = [2, undefined, 'hej', null, false, 0];
+        });
+        describe('Array object', function () {
+
+            it('should call the callback with the proper arguments', function () {
+                var predicate = jasmine.createSpy('predicate');
+                var arr = ['1'];
+                arr.filter(predicate);
+                expect(predicate).toHaveBeenCalledWith('1', 0, arr);
+            });
+            it('should not affect elements added to the array after it has begun', function () {
+                var arr = [1, 2, 3];
+                var i = 0;
+                arr.filter(function (a) {
+                    i++;
+                    if (i <= 4) {
+                        arr.push(a + 3);
+                    }
+                    return true;
+                });
+                expect(arr).toEqual([1, 2, 3, 4, 5, 6]);
+                expect(i).toBe(3);
+            });
+            it('should skip non-set values', function () {
+                var passedValues = {};
+                testSubject = [1, 2, 3, 4];
+                delete testSubject[1];
+                testSubject.filter(function (o, i) {
+                    passedValues[i] = o;
+                    return true;
+                });
+                expect(passedValues).toExactlyMatch(testSubject);
+            });
+            it('should pass the right context to the filter', function () {
+                var passedValues = {};
+                testSubject = [1, 2, 3, 4];
+                delete testSubject[1];
+                testSubject.filter(function (o, i) {
+                    this[i] = o;
+                    return true;
+                }, passedValues);
+                expect(passedValues).toExactlyMatch(testSubject);
+            });
+            it('should set the right context when given none', function () {
+                var context;
+                [1].filter(function () { context = this; });
+                expect(context).toBe(function () { return this; }.call());
+            });
+            it('should remove only the values for which the callback returns false', function () {
+                var result = testSubject.filter(callback);
+                expect(result).toExactlyMatch(filteredArray);
+            });
+            it('should leave the original array untouched', function () {
+                var copy = testSubject.slice();
+                testSubject.filter(callback);
+                expect(testSubject).toExactlyMatch(copy);
+            });
+            it('should not be affected by same-index mutation', function () {
+                var results = [1, 2, 3].filter(function (value, index, array) {
+                    array[index] = 'a';
+                    return true;
+                });
+                expect(results).toEqual([1, 2, 3]);
+            });
+        });
+        describe('Array like', function () {
+            beforeEach(function () {
+                testSubject = createArrayLikeFromArray(testSubject);
+            });
+            it('should call the predicate with the proper arguments', function () {
+                var predicate = jasmine.createSpy('predicate');
+                var arr = createArrayLikeFromArray(['1']);
+                Array.prototype.filter.call(arr, predicate);
+                expect(predicate).toHaveBeenCalledWith('1', 0, arr);
+            });
+            it('should not affect elements added to the array after it has begun', function () {
+                var arr = createArrayLikeFromArray([1, 2, 3]),
+                    i = 0;
+                Array.prototype.filter.call(arr, function (a) {
+                    i++;
+                    if (i <= 4) {
+                        arr[i + 2] = a + 3;
+                        arr.length += 1;
+                    }
+                    return true;
+                });
+                expect(arr).toEqual([1, 2, 3, 4, 5, 6]);
+                expect(i).toBe(3);
+            });
+            it('should skip non-set values', function () {
+                var passedValues = createArrayLikeFromArray([]);
+                testSubject = createArrayLikeFromArray([1, 2, 3, 4]);
+                delete testSubject[1];
+                Array.prototype.filter.call(testSubject, function (o, i) {
+                    passedValues[i] = o;
+                    passedValues.length = i + 1;
+                    return true;
+                });
+                expect(passedValues).toEqual(testSubject);
+            });
+            it('should set the right context when given none', function () {
+                var context;
+                Array.prototype.filter.call(createArrayLikeFromArray([1]), function () { context = this; }, undefined);
+                expect(context).toBe(function () { return this; }.call());
+            });
+            it('should pass the right context to the filter', function () {
+                var passedValues = {};
+                testSubject = createArrayLikeFromArray([1, 2, 3, 4]);
+                delete testSubject[1];
+                Array.prototype.filter.call(testSubject, function (o, i) {
+                    this[i] = o;
+                    this.length = i + 1;
+                    return true;
+                }, passedValues);
+                expect(passedValues).toEqual(testSubject);
+            });
+            it('should remove only the values for which the callback returns false', function () {
+                var result = Array.prototype.filter.call(testSubject, callback);
+                expect(result).toExactlyMatch(filteredArray);
+            });
+            it('should leave the original array untouched', function () {
+                var copy = createArrayLikeFromArray(testSubject);
+                Array.prototype.filter.call(testSubject, callback);
+                expect(testSubject).toExactlyMatch(copy);
+            });
+        });
+        it('should have a boxed object as list argument of callback', function () {
+            var actual;
+            Array.prototype.filter.call('foo', function (item, index, list) {
+                actual = list;
+            });
+            expect(typeof actual).toBe('object');
+            expect(toStr.call(actual)).toBe('[object String]');
+        });
+    });
+    describe('map', function () {
+        var callback;
+        beforeEach(function () {
+            var i = 0;
+            callback = function () {
+                return i++;
+            };
+        });
+        describe('Array object', function () {
+            it('should call mapper with the right parameters', function () {
+                var mapper = jasmine.createSpy('mapper');
+                var array = [1];
+                array.map(mapper);
+                expect(mapper).toHaveBeenCalledWith(1, 0, array);
+            });
+            it('should set the context correctly', function () {
+                var context = {};
+                testSubject.map(function (o, i) {
+                    this[i] = o;
+                }, context);
+                expect(context).toExactlyMatch(testSubject);
+            });
+            it('should set the right context when given none', function () {
+                var context;
+                [1].map(function () { context = this; });
+                expect(context).toBe(function () { return this; }.call());
+            });
+            it('should not change the array it is called on', function () {
+                var copy = testSubject.slice();
+                testSubject.map(callback);
+                expect(testSubject).toExactlyMatch(copy);
+            });
+            it('should only run for the number of objects in the array when it started', function () {
+                var arr = [1, 2, 3],
+                    i = 0;
+                arr.map(function (o) {
+                    arr.push(o + 3);
+                    i++;
+                    return o;
+                });
+                expect(arr).toExactlyMatch([1, 2, 3, 4, 5, 6]);
+                expect(i).toBe(3);
+            });
+            it('should properly translate the values as according to the callback', function () {
+                var result = testSubject.map(callback),
+                    expected = [0, 0, 1, 2, 3, 4, 5, 6];
+                delete expected[1];
+                expect(result).toExactlyMatch(expected);
+            });
+            it('should skip non-existing values', function () {
+                var array = [1, 2, 3, 4],
+                    i = 0;
+                delete array[2];
+                array.map(function () {
+                    i++;
+                });
+                expect(i).toBe(3);
+            });
+        });
+        describe('Array-like', function () {
+            beforeEach(function () {
+                testSubject = createArrayLikeFromArray(testSubject);
+            });
+            it('should call mapper with the right parameters', function () {
+                var mapper = jasmine.createSpy('mapper');
+                var array = createArrayLikeFromArray([1]);
+                Array.prototype.map.call(array, mapper);
+                expect(mapper).toHaveBeenCalledWith(1, 0, array);
+            });
+            it('should set the context correctly', function () {
+                var context = {};
+                Array.prototype.map.call(testSubject, function (o, i) {
+                    this[i] = o;
+                    this.length = i + 1;
+                }, context);
+                expect(context).toEqual(testSubject);
+            });
+            it('should set the right context when given none', function () {
+                var context;
+                Array.prototype.map.call(createArrayLikeFromArray([1]), function () { context = this; });
+                expect(context).toBe(function () { return this; }.call());
+            });
+            it('should not change the array it is called on', function () {
+                var copy = createArrayLikeFromArray(testSubject);
+                Array.prototype.map.call(testSubject, callback);
+                expect(testSubject).toExactlyMatch(copy);
+            });
+            it('should only run for the number of objects in the array when it started', function () {
+                var arr = createArrayLikeFromArray([1, 2, 3]),
+                    i = 0;
+                Array.prototype.map.call(arr, function (o) {
+                    Array.prototype.push.call(arr, o + 3);
+                    i++;
+                    return o;
+                });
+                expect(arr).toEqual([1, 2, 3, 4, 5, 6]);
+                expect(i).toBe(3);
+            });
+            it('should properly translate the values as according to the callback', function () {
+                var result = Array.prototype.map.call(testSubject, callback),
+                    expected = [0, 0, 1, 2, 3, 4, 5, 6];
+                delete expected[1];
+                expect(result).toExactlyMatch(expected);
+            });
+            it('should skip non-existing values', function () {
+                var array = createArrayLikeFromArray([1, 2, 3, 4]),
+                    i = 0;
+                delete array[2];
+                Array.prototype.map.call(array, function () {
+                    i++;
+                });
+                expect(i).toBe(3);
+            });
+        });
+        it('should have a boxed object as list argument of callback', function () {
+            var actual;
+            Array.prototype.map.call('foo', function (item, index, list) {
+                actual = list;
+            });
+            expect(typeof actual).toBe('object');
+            expect(toStr.call(actual)).toBe('[object String]');
+        });
+    });
+
+    describe('reduce', function () {
+        beforeEach(function () {
+            testSubject = [1, 2, 3];
+        });
+
+        describe('Array', function () {
+            it('should pass the correct arguments to the callback', function () {
+                var spy = jasmine.createSpy().andReturn(0);
+                testSubject.reduce(spy);
+                expect(spy.calls[0].args).toExactlyMatch([1, 2, 1, testSubject]);
+            });
+            it('should start with the right initialValue', function () {
+                var spy = jasmine.createSpy().andReturn(0);
+                testSubject.reduce(spy, 0);
+                expect(spy.calls[0].args).toExactlyMatch([0, 1, 0, testSubject]);
+            });
+            it('should not affect elements added to the array after it has begun', function () {
+                var arr = [1, 2, 3],
+                    i = 0;
+                arr.reduce(function (a, b) {
+                    i++;
+                    if (i <= 4) {
+                        arr.push(a + 3);
+                    }
+                    return b;
+                });
+                expect(arr).toEqual([1, 2, 3, 4, 5]);
+                expect(i).toBe(2);
+            });
+            it('should work as expected for empty arrays', function () {
+                var spy = jasmine.createSpy();
+                expect(function () {
+                    [].reduce(spy);
+                }).toThrow();
+                expect(spy).not.toHaveBeenCalled();
+            });
+            it('should throw correctly if no callback is given', function () {
+                expect(function () {
+                    testSubject.reduce();
+                }).toThrow();
+            });
+            it('should return the expected result', function () {
+                expect(testSubject.reduce(function (a, b) {
+                    return String(a || '') + String(b || '');
+                })).toEqual(testSubject.join(''));
+            });
+            it('should not directly affect the passed array', function () {
+                var copy = testSubject.slice();
+                testSubject.reduce(function (a, b) {
+                    return a + b;
+                });
+                expect(testSubject).toEqual(copy);
+            });
+            it('should skip non-set values', function () {
+                delete testSubject[1];
+                var visited = {};
+                testSubject.reduce(function (a, b) {
+                    if (a) { visited[a] = true; }
+                    if (b) { visited[b] = true; }
+                    return 0;
+                });
+
+                expect(visited).toEqual({ 1: true, 3: true });
+            });
+            it('should have the right length', function () {
+                expect(testSubject.reduce.length).toBe(1);
+            });
+        });
+        describe('Array-like objects', function () {
+            beforeEach(function () {
+                testSubject = createArrayLikeFromArray(testSubject);
+                testSubject.reduce = Array.prototype.reduce;
+            });
+            it('should pass the correct arguments to the callback', function () {
+                var spy = jasmine.createSpy().andReturn(0);
+                testSubject.reduce(spy);
+                expect(spy.calls[0].args).toExactlyMatch([1, 2, 1, testSubject]);
+            });
+            it('should start with the right initialValue', function () {
+                var spy = jasmine.createSpy().andReturn(0);
+                testSubject.reduce(spy, 0);
+                expect(spy.calls[0].args).toExactlyMatch([0, 1, 0, testSubject]);
+            });
+            it('should not affect elements added to the array after it has begun', function () {
+                var arr = createArrayLikeFromArray([1, 2, 3]),
+                    i = 0;
+                Array.prototype.reduce.call(arr, function (a, b) {
+                    i++;
+                    if (i <= 4) {
+                        arr[i + 2] = a + 3;
+                    }
+                    return b;
+                });
+                expect(arr).toEqual({
+                    0: 1,
+                    1: 2,
+                    2: 3,
+                    3: 4,
+                    4: 5,
+                    length: 3
+                });
+                expect(i).toBe(2);
+            });
+            it('should work as expected for empty arrays', function () {
+                var spy = jasmine.createSpy();
+                expect(function () {
+                    Array.prototype.reduce.call({ length: 0 }, spy);
+                }).toThrow();
+                expect(spy).not.toHaveBeenCalled();
+            });
+            it('should throw correctly if no callback is given', function () {
+                expect(function () {
+                    testSubject.reduce();
+                }).toThrow();
+            });
+            it('should return the expected result', function () {
+                expect(testSubject.reduce(function (a, b) {
+                    return String(a || '') + String(b || '');
+                })).toEqual('123');
+            });
+            it('should not directly affect the passed array', function () {
+                var copy = createArrayLikeFromArray(testSubject);
+                testSubject.reduce(function (a, b) {
+                    return a + b;
+                });
+                delete testSubject.reduce;
+                expect(testSubject).toEqual(copy);
+            });
+            it('should skip non-set values', function () {
+                delete testSubject[1];
+                var visited = {};
+                testSubject.reduce(function (a, b) {
+                    if (a) { visited[a] = true; }
+                    if (b) { visited[b] = true; }
+                    return 0;
+                });
+
+                expect(visited).toEqual({ 1: true, 3: true });
+            });
+            it('should have the right length', function () {
+                expect(testSubject.reduce.length).toBe(1);
+            });
+        });
+        it('should have a boxed object as list argument of callback', function () {
+            var actual;
+            Array.prototype.reduce.call('foo', function (accumulator, item, index, list) {
+                actual = list;
+            });
+            expect(typeof actual).toBe('object');
+            expect(toStr.call(actual)).toBe('[object String]');
+        });
+    });
+    describe('reduceRight', function () {
+        beforeEach(function () {
+            testSubject = [1, 2, 3];
+        });
+
+        describe('Array', function () {
+            it('should pass the correct arguments to the callback', function () {
+                var spy = jasmine.createSpy().andReturn(0);
+                testSubject.reduceRight(spy);
+                expect(spy.calls[0].args).toExactlyMatch([3, 2, 1, testSubject]);
+            });
+            it('should start with the right initialValue', function () {
+                var spy = jasmine.createSpy().andReturn(0);
+                testSubject.reduceRight(spy, 0);
+                expect(spy.calls[0].args).toExactlyMatch([0, 3, 2, testSubject]);
+            });
+            it('should not affect elements added to the array after it has begun', function () {
+                var arr = [1, 2, 3],
+                    i = 0;
+                arr.reduceRight(function (a, b) {
+                    i++;
+                    if (i <= 4) {
+                        arr.push(a + 3);
+                    }
+                    return b;
+                });
+                expect(arr).toEqual([1, 2, 3, 6, 5]);
+                expect(i).toBe(2);
+            });
+            it('should work as expected for empty arrays', function () {
+                var spy = jasmine.createSpy();
+                expect(function () {
+                    [].reduceRight(spy);
+                }).toThrow();
+                expect(spy).not.toHaveBeenCalled();
+            });
+            it('should work as expected for empty arrays with an initial value', function () {
+                var spy = jasmine.createSpy(),
+                    result;
+
+                result = [].reduceRight(spy, '');
+                expect(spy).not.toHaveBeenCalled();
+                expect(result).toBe('');
+            });
+            it('should throw correctly if no callback is given', function () {
+                expect(function () {
+                    testSubject.reduceRight();
+                }).toThrow();
+            });
+            it('should return the expected result', function () {
+                expect(testSubject.reduceRight(function (a, b) {
+                    return String(a || '') + String(b || '');
+                })).toBe('321');
+            });
+            it('should not directly affect the passed array', function () {
+                var copy = testSubject.slice();
+                testSubject.reduceRight(function (a, b) {
+                    return a + b;
+                });
+                expect(testSubject).toEqual(copy);
+            });
+            it('should skip non-set values', function () {
+                delete testSubject[1];
+                var visited = {};
+                testSubject.reduceRight(function (a, b) {
+                    if (a) { visited[a] = true; }
+                    if (b) { visited[b] = true; }
+                    return 0;
+                });
+
+                expect(visited).toEqual({ 1: true, 3: true });
+            });
+            it('should have the right length', function () {
+                expect(testSubject.reduceRight.length).toBe(1);
+            });
+        });
+        describe('Array-like objects', function () {
+            beforeEach(function () {
+                testSubject = createArrayLikeFromArray(testSubject);
+                testSubject.reduceRight = Array.prototype.reduceRight;
+            });
+            it('should pass the correct arguments to the callback', function () {
+                var spy = jasmine.createSpy().andReturn(0);
+                testSubject.reduceRight(spy);
+                expect(spy.calls[0].args).toExactlyMatch([3, 2, 1, testSubject]);
+            });
+            it('should start with the right initialValue', function () {
+                var spy = jasmine.createSpy().andReturn(0);
+                testSubject.reduceRight(spy, 0);
+                expect(spy.calls[0].args).toExactlyMatch([0, 3, 2, testSubject]);
+            });
+            it('should not affect elements added to the array after it has begun', function () {
+                var arr = createArrayLikeFromArray([1, 2, 3]),
+                    i = 0;
+                Array.prototype.reduceRight.call(arr, function (a, b) {
+                    i++;
+                    if (i <= 4) {
+                        arr[i + 2] = a + 3;
+                    }
+                    return b;
+                });
+                expect(arr).toEqual({
+                    0: 1,
+                    1: 2,
+                    2: 3,
+                    3: 6,
+                    4: 5,
+                    length: 3 // does not get updated on property assignment
+                });
+                expect(i).toBe(2);
+            });
+            it('should work as expected for empty arrays', function () {
+                var spy = jasmine.createSpy();
+                expect(function () {
+                    Array.prototype.reduceRight.call({ length: 0 }, spy);
+                }).toThrow();
+                expect(spy).not.toHaveBeenCalled();
+            });
+            it('should throw correctly if no callback is given', function () {
+                expect(function () {
+                    testSubject.reduceRight();
+                }).toThrow();
+            });
+            it('should return the expected result', function () {
+                expect(testSubject.reduceRight(function (a, b) {
+                    return String(a || '') + String(b || '');
+                })).toEqual('321');
+            });
+            it('should not directly affect the passed array', function () {
+                var copy = createArrayLikeFromArray(testSubject);
+                testSubject.reduceRight(function (a, b) {
+                    return a + b;
+                });
+                delete testSubject.reduceRight;
+                expect(testSubject).toEqual(copy);
+            });
+            it('should skip non-set values', function () {
+                delete testSubject[1];
+                var visited = {};
+                testSubject.reduceRight(function (a, b) {
+                    if (a) { visited[a] = true; }
+                    if (b) { visited[b] = true; }
+                    return 0;
+                });
+
+                expect(visited).toEqual({ 1: true, 3: true });
+            });
+            it('should have the right length', function () {
+                expect(testSubject.reduceRight.length).toBe(1);
+            });
+        });
+        it('should have a boxed object as list argument of callback', function () {
+            var actual;
+            Array.prototype.reduceRight.call('foo', function (accumulator, item, index, list) {
+                actual = list;
+            });
+            expect(typeof actual).toBe('object');
+            expect(toStr.call(actual)).toBe('[object String]');
+        });
+    });
+
+    describe('isArray', function () {
+        it('should be true for Array', function () {
+            expect(Array.isArray([])).toBe(true);
+        });
+
+        it('should be false for primitives', function () {
+            var primitives = [
+                'foo',
+                true,
+                false,
+                42,
+                0,
+                -0,
+                NaN,
+                Infinity,
+                -Infinity
+            ];
+            primitives.forEach(function (v) {
+                expect(Array.isArray(v)).toBe(false);
+            });
+        });
+
+        it('should fail for other objects', function () {
+            var objects = [
+                {},
+                /foo/,
+                arguments
+            ];
+            if (Object.create) {
+                objects.push(Object.create(null));
+            }
+
+            objects.forEach(function (v) {
+                expect(Array.isArray(v)).toBe(false);
+            });
+        });
+
+        /* globals document */
+        if (typeof document !== 'undefined') {
+            it('should be false for an HTML element', function () {
+                var el = document.getElementsByTagName('div');
+                expect(Array.isArray(el)).toBe(false);
+            });
+        }
+    });
+
+    describe('unshift', function () {
+        it('should return length', function () {
+            expect([].unshift(0)).toBe(1);
+        });
+    });
+
+    describe('splice', function () {
+        var b = ['b'];
+        var a = [1, 'a', b];
+        var test;
+
+        var makeArray = function (l, givenPrefix) {
+            var prefix = givenPrefix || '';
+            var length = l;
+            var arr = [];
+            while (length--) {
+                arr.unshift(prefix + Array(length + 1).join(' ') + length);
+            }
+            return arr;
+        };
+
+        beforeEach(function () {
+            test = a.slice(0);
+        });
+
+        it('has the right length', function () {
+            expect(Array.prototype.splice.length).toBe(2);
+        });
+
+        /* This test is disabled, because ES6 normalizes actual
+         * browser behavior, contradicting ES5.
+         */
+        xit('treats undefined deleteCount as 0', function () {
+            expect(test.splice(0).length).toBe(0);
+            expect(test.splice(0)).toEqual(test.splice(0, 0));
+        });
+
+        it('basic implementation test 1', function () {
+            expect(test.splice(0, 0)).toEqual([]);
+        });
+
+        it('basic implementation test 2', function () {
+            test.splice(0, 2);
+            expect(test).toEqual([b]);
+        });
+
+        it('should return right result 1', function () {
+            var array = [];
+
+            array.splice(0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
+            array.splice(1, 0, 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26');
+            array.splice(5, 0, 'XXX');
+
+            expect(array).toEqual([1, 'F1', 'F2', 'F3', 'F4', 'XXX', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26', 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]);
+        });
+
+        it('should return right result 2', function () {
+            var array = makeArray(6);
+
+            array.splice(array.length - 1, 1, '');
+            array.splice(0, 1, 1, 2, 3, 4);
+            array.splice(0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45);
+
+            array.splice(4, 0, '99999999999999');
+
+            expect(array).toEqual([1, 2, 3, 4, '99999999999999', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 1, 2, 3, 4, ' 1', '  2', '   3', '    4', '']);
+        });
+
+        it('should return right result 3', function () {
+            var array = [1, 2, 3];
+
+            array.splice(0, array.length);
+            array.splice(0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+            array.splice(1, 1, 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26');
+            array.splice(5, 1, 'YYY', 'XXX');
+            array.splice(0, 1);
+            array.splice(0, 2);
+            array.pop();
+            array.push.apply(array, makeArray(10, '-'));
+            array.splice(array.length - 2, 10);
+            array.splice();
+            array.splice(1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9);
+            array.splice(1, 1, 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26', 1, 23, 4, 5, 6, 7, 8);
+            array.splice(30, 10);
+            array.splice(30, 1);
+            array.splice(30, 0);
+            array.splice(2, 5, 1, 2, 3, 'P', 'LLL', 'CCC', 'YYY', 'XXX');
+            array.push(1, 2, 3, 4, 5, 6);
+            array.splice(1, 6, 1, 2, 3, 4, 5, 6, 7, 8, 9, 4, 5, 6, 7, 8, 9);
+            array.splice(3, 7);
+            array.unshift(7, 8, 9, 10, 11);
+            array.pop();
+            array.splice(5, 2);
+            array.pop();
+            array.unshift.apply(array, makeArray(8, '~'));
+            array.pop();
+            array.splice(3, 1, 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26', 1, 23, 4, 5, 6, 7, 8);
+            array.splice(4, 5, 'P', 'LLL', 'CCC', 'YYY', 'XXX');
+
+            expect(array).toEqual(['~0', '~ 1', '~  2', 'F1', 'P', 'LLL', 'CCC', 'YYY', 'XXX', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F26', 1, 23, 4, 5, 6, 7, 8, '~    4', '~     5', '~      6', '~       7', 7, 8, 9, 10, 11, 2, 4, 5, 6, 7, 8, 9, 'CCC', 'YYY', 'XXX', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'F25', 'F2 [...]
+        });
+
+        it('should do nothing if method called with no arguments', function () {
+            expect(test.splice()).toEqual([]);
+            expect(test).toEqual(a);
+        });
+
+        it('should set first argument to 0 if first argument is set but undefined', function () {
+            var test2 = test.slice(0);
+            expect(test.splice(void 0, 2)).toEqual(test2.splice(0, 2));
+            expect(test).toEqual(test2);
+        });
+
+        it('should work with objects - adding 1', function () {
+            var obj = {};
+            Array.prototype.splice.call(obj, 0, 0, 1, 2, 3);
+            expect(obj.length).toBe(3);
+        });
+
+        it('should work with objects - adding 2', function () {
+            var obj = { 0: 1, length: 1 };
+            Array.prototype.splice.call(obj, 1, 0, 2, 3);
+            expect(obj.length).toBe(3);
+        });
+
+        it('should work with objects - removing', function () {
+            var obj = { 0: 1, 1: 2, 2: 3, length: 3 };
+            Array.prototype.splice.call(obj, 0, 3);
+            expect(obj.length).toBe(0);
+        });
+
+        it('should work with objects - replacing', function () {
+            var obj = { 0: 99, length: 1 };
+            Array.prototype.splice.call(obj, 0, 1, 1, 2, 3);
+            expect(obj.length).toBe(3);
+            expect(obj[0]).toBe(1);
+        });
+
+        it('should not break on sparse arrays in Opera', function () {
+            // test from https://github.com/wikimedia/VisualEditor/blob/d468b00311e69c2095b9da360c5745153342a5c3/src/ve.utils.js#L182-L196
+            var n = 256;
+            var arr = [];
+            arr[n] = 'a';
+            arr.splice(n + 1, 0, 'b');
+            expect(arr[n]).toBe('a');
+        });
+
+        it('should not break on sparse arrays in Safari 7/8', function () {
+            // test from https://github.com/wikimedia/VisualEditor/blob/d468b00311e69c2095b9da360c5745153342a5c3/src/ve.utils.js#L182-L196
+            var justFine = new Array(1e5 - 1);
+            justFine[10] = 'x';
+            var tooBig = new Array(1e5);
+            tooBig[8] = 'x';
+
+            justFine.splice(1, 1);
+			expect(8 in justFine).toBe(false);
+            expect(justFine.indexOf('x')).toBe(9);
+            tooBig.splice(1, 1);
+			expect(6 in tooBig).toBe(false);
+            expect(tooBig.indexOf('x')).toBe(7);
+        });
+    });
+
+});
diff --git a/tests/spec/s-date.js b/tests/spec/s-date.js
new file mode 100644
index 0000000..c2ef3ce
--- /dev/null
+++ b/tests/spec/s-date.js
@@ -0,0 +1,247 @@
+/* global describe, it, xit, expect, beforeEach, jasmine */
+
+describe('Date', function () {
+    'use strict';
+
+    var supportsDescriptors = Object.defineProperty && (function () {
+        try {
+            var obj = {};
+            Object.defineProperty(obj, 'x', { enumerable: false, value: obj });
+            for (var _ in obj) { return false; }
+            return obj.x === obj;
+        } catch (e) { /* this is ES3 */
+            return false;
+        }
+    }());
+    var ifSupportsDescriptorsIt = supportsDescriptors ? it : xit;
+    var has = Object.prototype.hasOwnProperty;
+
+    describe('now', function () {
+        it('should be the current time', function () {
+            var before = (new Date()).getTime();
+            var middle = Date.now();
+            var after = (new Date()).getTime();
+            expect(middle).not.toBeLessThan(before);
+            expect(middle).not.toBeGreaterThan(after);
+        });
+    });
+
+    describe('constructor', function () {
+        it('works with standard formats', function () {
+                                                                               //    Chrome 19     Opera 12      Firefox 11    IE 9          Safari 5.1.1
+            expect(+new Date('2012-12-31T23:59:59.000Z')).toBe(1356998399000); //    1356998399000 1356998399000 1356998399000 1356998399000 1356998399000
+            expect(+new Date('2012-04-04T05:02:02.170Z')).toBe(1333515722170); //    1333515722170 1333515722170 1333515722170 1333515722170 1333515722170
+            expect(+new Date('2012-04-04T05:02:02.170999Z')).toBe(1333515722170); // 1333515722170 1333515722170 1333515722170 1333515722170 1333515722170
+            expect(+new Date('2012-04-04T05:02:02.17Z')).toBe(1333515722170); //     1333515722170 1333515722170 1333515722170 1333515722170 1333515722170
+            expect(+new Date('2012-04-04T05:02:02.1Z')).toBe(1333515722100); //      1333515722170 1333515722170 1333515722170 1333515722170 1333515722170
+            expect(+new Date('2012-04-04T24:00:00.000Z')).toBe(1333584000000); //    NaN           1333584000000 1333584000000 1333584000000 1333584000000
+            expect(+new Date('2012-02-29T12:00:00.000Z')).toBe(1330516800000); //    1330516800000 1330516800000 1330516800000 1330516800000 1330516800000
+            expect(+new Date('2011-03-01T12:00:00.000Z')).toBe(1298980800000); //    1298980800000 1298980800000 1298980800000 1298980800000 1298980800000
+
+            // https://github.com/es-shims/es5-shim/issues/80 Safari bug with leap day
+            expect(new Date('2034-03-01T00:00:00.000Z') -
+                        new Date('2034-02-27T23:59:59.999Z')).toBe(86400001); //          86400001      86400001      86400001      86400001             1
+
+        });
+
+        ifSupportsDescriptorsIt('is not enumerable', function () {
+          expect(Object.keys(new Date())).not.toContain('constructor');
+        });
+
+        it('works as a function', function () {
+          expect(Date(0)).toBe(String(Date(0)));
+          expect(Date(1441705534578)).toBe(String(Date(1441705534578)));
+        });
+    });
+
+    describe('parse', function () {
+        // TODO: Write the rest of the test.
+
+        ifSupportsDescriptorsIt('is not enumerable', function () {
+          expect(Object.getOwnPropertyDescriptor(Date, 'parse').enumerable).toBe(false);
+        });
+
+        it('should be an invalid date', function () {
+                                                                        //            Chrome 19     Opera 12      Firefox 11    IE 9          Safari 5.1.1
+            expect(Date.parse('2012-11-31T23:59:59.000Z')).toBeFalsy(); //            1354406399000 NaN           NaN           1354406399000 NaN
+            expect(Date.parse('2012-12-31T23:59:60.000Z')).toBeFalsy(); //            NaN           NaN           NaN           NaN           1356998400000
+            expect(Date.parse('2012-04-04T24:00:00.500Z')).toBeFalsy(); //            NaN           NaN           1333584000500 1333584000500 NaN
+            expect(Date.parse('2012-12-31T10:08:60.000Z')).toBeFalsy(); //            NaN           NaN           NaN           NaN           1356948540000
+            expect(Date.parse('2012-13-01T12:00:00.000Z')).toBeFalsy(); //            NaN           NaN           NaN           NaN           NaN
+            expect(Date.parse('2012-12-32T12:00:00.000Z')).toBeFalsy(); //            NaN           NaN           NaN           NaN           NaN
+            expect(Date.parse('2012-12-31T25:00:00.000Z')).toBeFalsy(); //            NaN           NaN           NaN           NaN           NaN
+            expect(Date.parse('2012-12-31T24:01:00.000Z')).toBeFalsy(); //            NaN           NaN           NaN           1356998460000 NaN
+            expect(Date.parse('2012-12-31T12:60:00.000Z')).toBeFalsy(); //            NaN           NaN           NaN           NaN           NaN
+            expect(Date.parse('2012-12-31T12:00:60.000Z')).toBeFalsy(); //            NaN           NaN           NaN           NaN           1356955260000
+            expect(Date.parse('2012-00-31T23:59:59.000Z')).toBeFalsy(); //            NaN           NaN           NaN           NaN           NaN
+            expect(Date.parse('2012-12-00T23:59:59.000Z')).toBeFalsy(); //            NaN           NaN           NaN           NaN           NaN
+            expect(Date.parse('2011-02-29T12:00:00.000Z')).toBeFalsy(); //            1298980800000 NaN           NaN           1298980800000 NaN
+        });
+
+        it('should work', function () {
+            var dates = {
+                                                           //    Chrome 19     Opera 12      Firefox 11    IE 9          Safari 5.1.1  Safari 8
+                '2012-12-31T23:59:59.000Z': 1356998399000, //    1356998399000 1356998399000 1356998399000 1356998399000 1356998399000 1356998399000
+                '2012-04-04T05:02:02.170Z': 1333515722170, //    1333515722170 1333515722170 1333515722170 1333515722170 1333515722170 1333515722170
+                '2012-04-04T05:02:02.170999Z': 1333515722170, // 1333515722170 1333515722170 1333515722170 1333515722170 1333515722170 1333515722170.999
+                '2012-04-04T05:02:02.17Z': 1333515722170, //     1333515722170 1333515722170 1333515722170 1333515722170 1333515722170 1333515722170
+                '2012-04-04T05:02:02.1Z': 1333515722100, //      1333515722170 1333515722170 1333515722170 1333515722170 1333515722170 1333515722170
+                '2012-04-04T24:00:00.000Z': 1333584000000, //    NaN           1333584000000 1333584000000 1333584000000 1333584000000 1333584000000
+                '2012-02-29T12:00:00.000Z': 1330516800000, //    1330516800000 1330516800000 1330516800000 1330516800000 1330516800000 1330516800000
+                '2011-03-01T12:00:00.000Z': 1298980800000 //     1298980800000 1298980800000 1298980800000 1298980800000 1298980800000 1298980800000
+            };
+            for (var str in dates) {
+                if (has.call(dates, str)) {
+                    expect(Math.floor(Date.parse(str))).toBe(dates[str]);
+                }
+            }
+
+            // https://github.com/es-shims/es5-shim/issues/80 Safari bug with leap day
+            expect(Date.parse('2034-03-01T00:00:00.000Z') -
+                        Date.parse('2034-02-27T23:59:59.999Z')).toBe(86400001); //         86400001      86400001      86400001      86400001             1
+
+        });
+
+        it('should support extended years', function () {
+                                                                               //    Chrome 19     Opera 12      Firefox 11    IE 9          Safari 5.1.1
+            expect(Date.parse('0000-01-01T00:00:00.000Z')).toBe(-621672192e5); //   -621672192e5  -621672192e5  -621672192e5  -621672192e5  -621672192e5
+            expect(Date.parse('0001-01-01T00:00:00Z')).toBe(-621355968e5); //       -621355968e5  -621355968e5  -621355968e5   8.64e15      -621355968e5
+            expect(Date.parse('+275760-09-13T00:00:00.000Z')).toBe(8.64e15); //      8.64e15       NaN           8.64e15       8.64e15       8.64e15
+            expect(Date.parse('-271821-04-20T00:00:00.000Z')).toBe(-8.64e15); //    -8.64e15       NaN          -8.64e15      -8.64e15      -8.6400000864e15
+            expect(Date.parse('+275760-09-13T00:00:00.001Z')).toBeFalsy(); //        NaN           NaN           NaN           8.64e15 + 1   8.64e15 + 1
+            expect(Date.parse('-271821-04-19T23:59:59.999Z')).toBeFalsy(); //        NaN           NaN           NaN          -8.64e15 - 1  -8.6400000864e15 - 1
+            expect(Date.parse('+033658-09-27T01:46:40.000Z')).toBe(1e15); //         1e15          NaN           1e15          1e15          9999999136e5
+            expect(Date.parse('-000001-01-01T00:00:00Z')).toBe(-621987552e5); //    -621987552e5   NaN          -621987552e5  -621987552e5  -621987552e5
+            expect(Date.parse('+002009-12-15T00:00:00Z')).toBe(12608352e5); //       12608352e5    NaN           12608352e5    12608352e5    12608352e5
+        });
+
+        it('works with timezone offsets', function () {
+                                                                                   //  Chrome 19   Opera 12     Firefox 11   IE 9             Safari 5.1.1
+            expect(Date.parse('2012-01-29T12:00:00.000+01:00')).toBe(132783480e4); //  132783480e4 132783480e4  132783480e4  132783480e4      132783480e4
+            expect(Date.parse('2012-01-29T12:00:00.000-00:00')).toBe(132783840e4); //  132783840e4 132783840e4  132783840e4  132783840e4      132783840e4
+            expect(Date.parse('2012-01-29T12:00:00.000+00:00')).toBe(132783840e4); //  132783840e4 132783840e4  132783840e4  132783840e4      132783840e4
+            expect(Date.parse('2012-01-29T12:00:00.000+23:59')).toBe(132775206e4); //  132775206e4 132775206e4  132775206e4  132775206e4      132775206e4
+            expect(Date.parse('2012-01-29T12:00:00.000-23:59')).toBe(132792474e4); //  132792474e4 132792474e4  132792474e4  132792474e4      132792474e4
+            expect(Date.parse('2012-01-29T12:00:00.000+24:00')).toBeFalsy(); //        NaN         1327752e6    NaN          1327752000000    1327752000000
+            expect(Date.parse('2012-01-29T12:00:00.000+24:01')).toBeFalsy(); //        NaN         NaN          NaN          1327751940000    1327751940000
+            expect(Date.parse('2012-01-29T12:00:00.000+24:59')).toBeFalsy(); //        NaN         NaN          NaN          1327748460000    1327748460000
+            expect(Date.parse('2012-01-29T12:00:00.000+25:00')).toBeFalsy(); //        NaN         NaN          NaN          NaN              NaN
+            expect(Date.parse('2012-01-29T12:00:00.000+00:60')).toBeFalsy(); //        NaN         NaN          NaN          NaN              NaN
+            expect(Date.parse('-271821-04-20T00:00:00.000+00:01')).toBeFalsy(); //     NaN         NaN          NaN         -864000000006e4 -864000008646e4
+            expect(Date.parse('-271821-04-20T00:01:00.000+00:01')).toBe(-8.64e15); // -8.64e15     NaN         -8.64e15     -8.64e15        -864000008640e4
+
+            // When time zone is missed, local offset should be used (ES 5.1 bug)
+            // see https://bugs.ecmascript.org/show_bug.cgi?id=112
+            var tzOffset = Number(new Date(1970, 0));
+            // same as (new Date().getTimezoneOffset() * 60000)
+            expect(Date.parse('1970-01-01T00:00:00')).toBe(tzOffset); //              tzOffset    0            0            0               NaN
+        });
+
+        it('should be able to coerce to a number', function () {
+            var actual = Number(new Date(1970, 0));
+            var expected = parseInt(actual, 10);
+            expect(actual).toBeDefined();
+            expect(actual).toEqual(expected);
+            expect(isNaN(actual)).toBeFalsy();
+        });
+
+    });
+
+    describe('toString', function () {
+        var actual;
+
+        beforeEach(function () {
+            actual = (new Date(1970, 0)).toString();
+        });
+
+        it('should show correct date info for ' + actual, function () {
+            expect(actual).toMatch(/1970/);
+            expect(actual).toMatch(/jan/i);
+            expect(actual).toMatch(/thu/i);
+            expect(actual).toMatch(/00:00:00/);
+        });
+    });
+
+    describe('valueOf', function () {
+        // Note that new Date(1970, 0).valueOf() is 0 in UTC timezone.
+        // Check check that it's a number (and an int), not that it's "truthy".
+        var actual;
+
+        beforeEach(function () {
+            actual = (new Date(1970, 0)).valueOf();
+        });
+        it('should give a numeric value', function () {
+            expect(typeof actual).toBe('number');
+        });
+        it('should not be NaN', function () {
+            expect(isNaN(actual)).toBe(false);
+        });
+        it('should give an int value', function () {
+            expect(actual).toBe(Math.floor(actual));
+        });
+    });
+
+    describe('getUTCDate', function () {
+        it('should return the right value for negative dates', function () {
+            // Opera 10.6/11.61/Opera 12 bug
+            expect(new Date(-3509827334573292).getUTCDate()).toBe(1);
+        });
+    });
+
+    describe('getUTCMonth', function () {
+        it('should return the right value for negative dates', function () {
+            // Opera 10.6/11.61/Opera 12 bug
+            expect(new Date(-3509827334573292).getUTCMonth()).toBe(0);
+        });
+
+        it('should return correct values', function () {
+            expect(new Date(8.64e15).getUTCMonth()).toBe(8);
+            expect(new Date(0).getUTCMonth()).toBe(0);
+        });
+    });
+
+    describe('toISOString', function () {
+        // TODO: write the rest of the test.
+
+        it('should support extended years', function () {
+            expect(new Date(-62198755200000).toISOString().indexOf('-000001-01-01')).toBe(0);
+            expect(new Date(8.64e15).toISOString().indexOf('+275760-09-13')).toBe(0);
+        });
+
+        it('should return correct dates', function () {
+            expect(new Date(-1).toISOString()).toBe('1969-12-31T23:59:59.999Z');// Safari 5.1.5 "1969-12-31T23:59:59.-01Z"
+            expect(new Date(-3509827334573292).toISOString()).toBe('-109252-01-01T10:37:06.708Z'); // Opera 11.61/Opera 12 bug with Date#getUTCMonth
+        });
+
+    });
+
+    describe('toJSON', function () {
+
+        // Opera 11.6x/12 bug
+        it('should call toISOString', function () {
+          var date = new Date(0);
+          date.toISOString = function () {
+            return 1;
+          };
+          expect(date.toJSON()).toBe(1);
+        });
+
+        it('should return null for not finite dates', function () {
+          var date = new Date(NaN),
+              json;
+          try {
+            json = date.toJSON();
+          } catch (e) {
+            /* invalid json */
+            expect(e).not.toEqual(jasmine.any(Error));
+          }
+          expect(json).toBe(null);
+        });
+
+        it('should return the isoString when stringified', function () {
+            var date = new Date();
+            expect(JSON.stringify(date.toISOString())).toBe(JSON.stringify(date));
+        });
+    });
+
+});
diff --git a/tests/spec/s-function.js b/tests/spec/s-function.js
new file mode 100644
index 0000000..de84946
--- /dev/null
+++ b/tests/spec/s-function.js
@@ -0,0 +1,176 @@
+/* global describe, it, expect, beforeEach */
+
+describe('Function', function () {
+    'use strict';
+
+    describe('apply', function () {
+        it('works with arraylike objects', function () {
+            var arrayLike = { length: 4, 0: 1, 2: 4, 3: true };
+            var expectedArray = [1, undefined, 4, true];
+            var actualArray = (function () {
+                return Array.prototype.slice.apply(arguments);
+            }.apply(null, arrayLike));
+            expect(actualArray).toEqual(expectedArray);
+        });
+    });
+
+    describe('bind', function () {
+        var actual;
+
+        var testSubject = {
+            push: function (o) {
+                this.a.push(o);
+            }
+        };
+
+        var func = function func() {
+            Array.prototype.forEach.call(arguments, function (a) {
+                this.push(a);
+            }, this);
+            return this;
+        };
+
+        beforeEach(function () {
+            actual = [];
+            testSubject.a = [];
+        });
+
+        it('binds properly without a context', function () {
+            var context;
+            testSubject.func = function () {
+                context = this;
+            }.bind();
+            testSubject.func();
+            expect(context).toBe(function () { return this; }.call());
+        });
+        it('binds properly without a context, and still supplies bound arguments', function () {
+            var a, context;
+            testSubject.func = function () {
+                a = Array.prototype.slice.call(arguments);
+                context = this;
+            }.bind(undefined, 1, 2, 3);
+            testSubject.func(1, 2, 3);
+            expect(a).toEqual([1, 2, 3, 1, 2, 3]);
+            expect(context).toBe(function () { return this; }.call());
+        });
+        it('binds a context properly', function () {
+            testSubject.func = func.bind(actual);
+            testSubject.func(1, 2, 3);
+            expect(actual).toEqual([1, 2, 3]);
+            expect(testSubject.a).toEqual([]);
+        });
+        it('binds a context and supplies bound arguments', function () {
+            testSubject.func = func.bind(actual, 1, 2, 3);
+            testSubject.func(4, 5, 6);
+            expect(actual).toEqual([1, 2, 3, 4, 5, 6]);
+            expect(testSubject.a).toEqual([]);
+        });
+
+        it('returns properly without binding a context', function () {
+            testSubject.func = function () {
+                return this;
+            }.bind();
+            var context = testSubject.func();
+            expect(context).toBe(function () { return this; }.call());
+        });
+        it('returns properly without binding a context, and still supplies bound arguments', function () {
+            var context;
+            testSubject.func = function () {
+                context = this;
+                return Array.prototype.slice.call(arguments);
+            }.bind(undefined, 1, 2, 3);
+            actual = testSubject.func(1, 2, 3);
+            expect(context).toBe(function () { return this; }.call());
+            expect(actual).toEqual([1, 2, 3, 1, 2, 3]);
+        });
+        it('returns properly while binding a context properly', function () {
+            var ret;
+            testSubject.func = func.bind(actual);
+            ret = testSubject.func(1, 2, 3);
+            expect(ret).toBe(actual);
+            expect(ret).not.toBe(testSubject);
+        });
+        it('returns properly while binding a context and supplies bound arguments', function () {
+            var ret;
+            testSubject.func = func.bind(actual, 1, 2, 3);
+            ret = testSubject.func(4, 5, 6);
+            expect(ret).toBe(actual);
+            expect(ret).not.toBe(testSubject);
+        });
+        it('has the new instance\'s context as a constructor', function () {
+            var actualContext;
+            var expectedContext = { foo: 'bar' };
+            testSubject.Func = function () {
+                actualContext = this;
+            }.bind(expectedContext);
+            var result = new testSubject.Func();
+            expect(result).toBeTruthy();
+            expect(actualContext).not.toBe(expectedContext);
+        });
+        it('passes the correct arguments as a constructor', function () {
+            var expected = { name: 'Correct' };
+            testSubject.Func = function (arg) {
+                expect(this.hasOwnProperty('name')).toBe(false);
+                return arg;
+            }.bind({ name: 'Incorrect' });
+            var ret = new testSubject.Func(expected);
+            expect(ret).toBe(expected);
+        });
+        it('returns the return value of the bound function when called as a constructor', function () {
+            var oracle = [1, 2, 3];
+            var Subject = function () {
+                expect(this).not.toBe(oracle);
+                return oracle;
+            }.bind(null);
+            var result = new Subject();
+            expect(result).toBe(oracle);
+        });
+
+        it('returns the correct value if constructor returns primitive', function () {
+            var Subject = function (oracle) {
+                expect(this).not.toBe(oracle);
+                return oracle;
+            }.bind(null);
+
+            var primitives = ['asdf', null, true, 1];
+            for (var i = 0; i < primitives.length; ++i) {
+                expect(new Subject(primitives[i])).not.toBe(primitives[i]);
+            }
+
+            var objects = [[1, 2, 3], {}, function () {}];
+            for (var j = 0; j < objects.length; ++j) {
+                expect(new Subject(objects[j])).toBe(objects[j]);
+            }
+        });
+        it('returns the value that instance of original "class" when called as a constructor', function () {
+            var ClassA = function (x) {
+                this.name = x || 'A';
+            };
+            var ClassB = ClassA.bind(null, 'B');
+
+            var result = new ClassB();
+            expect(result instanceof ClassA).toBe(true);
+            expect(result instanceof ClassB).toBe(true);
+        });
+        it('sets a correct length without thisArg', function () {
+            var Subject = function (a, b, c) { return a + b + c; }.bind();
+            expect(Subject.length).toBe(3);
+        });
+        it('sets a correct length with thisArg', function () {
+            var Subject = function (a, b, c) { return a + b + c + this.d; }.bind({ d: 1 });
+            expect(Subject.length).toBe(3);
+        });
+        it('sets a correct length with thisArg and first argument', function () {
+            var Subject = function (a, b, c) { return a + b + c + this.d; }.bind({ d: 1 }, 1);
+            expect(Subject.length).toBe(2);
+        });
+        it('sets a correct length without thisArg and first argument', function () {
+            var Subject = function (a, b, c) { return a + b + c; }.bind(undefined, 1);
+            expect(Subject.length).toBe(2);
+        });
+        it('sets a correct length without thisArg and too many argument', function () {
+            var Subject = function (a, b, c) { return a + b + c; }.bind(undefined, 1, 2, 3, 4);
+            expect(Subject.length).toBe(0);
+        });
+    });
+});
diff --git a/tests/spec/s-global.js b/tests/spec/s-global.js
new file mode 100644
index 0000000..81f03c6
--- /dev/null
+++ b/tests/spec/s-global.js
@@ -0,0 +1,55 @@
+/* global describe, it, xit, expect */
+
+describe('global methods', function () {
+    'use strict';
+
+    var foo = function foo() {};
+    var functionsHaveNames = foo.name === 'foo';
+    var ifFunctionsHaveNamesIt = functionsHaveNames ? it : xit;
+
+    describe('parseInt', function () {
+        ifFunctionsHaveNamesIt('has the right name', function () {
+          expect(parseInt.name).toBe('parseInt');
+        });
+
+        it('accepts a radix', function () {
+            for (var i = 2; i <= 36; ++i) {
+                expect(parseInt('10', i)).toBe(i);
+            }
+        });
+
+        it('defaults the radix to 10 when the number does not start with 0x or 0X', function () {
+           [
+               '01',
+               '08',
+               '10',
+               '42'
+           ].forEach(function (str) {
+               expect(parseInt(str)).toBe(parseInt(str, 10));
+           });
+        });
+
+        it('defaults the radix to 16 when the number starts with 0x or 0X', function () {
+            expect(parseInt('0x16')).toBe(parseInt('0x16', 16));
+            expect(parseInt('0X16')).toBe(parseInt('0X16', 16));
+        });
+
+        it('ignores leading whitespace', function () {
+            expect(parseInt('  0x16')).toBe(parseInt('0x16', 16));
+            expect(parseInt('  42')).toBe(parseInt('42', 10));
+            expect(parseInt('  08')).toBe(parseInt('08', 10));
+
+            var ws = '\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003' +
+                '\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028' +
+                '\u2029\uFEFF';
+            expect(parseInt(ws + '08')).toBe(parseInt('08', 10));
+            expect(parseInt(ws + '0x16')).toBe(parseInt('0x16', 16));
+        });
+
+       it('defaults the radix properly when not a true number', function () {
+           var fakeZero = { valueOf: function () { return 0; } };
+           expect(parseInt('08', fakeZero)).toBe(parseInt('08', 10));
+           expect(parseInt('0x16', fakeZero)).toBe(parseInt('0x16', 16));
+       });
+    });
+});
diff --git a/tests/spec/s-number.js b/tests/spec/s-number.js
new file mode 100644
index 0000000..9955486
--- /dev/null
+++ b/tests/spec/s-number.js
@@ -0,0 +1,37 @@
+/* global describe, it, expect */
+
+describe('Number', function () {
+    'use strict';
+
+    describe('toFixed', function () {
+        it('should convert numbers correctly', function () {
+            expect((0.00008).toFixed(3)).toBe('0.000');
+            expect((0.9).toFixed(0)).toBe('1');
+            expect((1.255).toFixed(2)).toBe('1.25');
+            expect((1843654265.0774949).toFixed(5)).toBe('1843654265.07749');
+            expect((1000000000000000128).toFixed(0)).toBe('1000000000000000128');
+        });
+    });
+
+    describe('constants', function () {
+        it('should have MAX_VALUE', function () {
+            expect(Number.MAX_VALUE).toBe(1.7976931348623157e308);
+        });
+
+        it('should have MIN_VALUE', function () {
+            expect(Number.MIN_VALUE).toBe(5e-324);
+        });
+
+        it('should have NaN', function () {
+            expect(Number.NaN).not.toBe(Number.NaN);
+        });
+
+        it('should have POSITIVE_INFINITY', function () {
+            expect(Number.POSITIVE_INFINITY).toBe(Infinity);
+        });
+
+        it('should have NEGATIVE_INFINITY', function () {
+            expect(Number.NEGATIVE_INFINITY).toBe(-Infinity);
+        });
+    });
+});
diff --git a/tests/spec/s-object.js b/tests/spec/s-object.js
new file mode 100644
index 0000000..a638e5d
--- /dev/null
+++ b/tests/spec/s-object.js
@@ -0,0 +1,359 @@
+/* global describe, it, xit, expect, beforeEach, jasmine, window */
+
+var ifWindowIt = typeof window === 'undefined' ? xit : it;
+var extensionsPreventible = typeof Object.preventExtensions === 'function' && (function () {
+    var obj = {};
+    Object.preventExtensions(obj);
+    obj.a = 3;
+    return obj.a !== 3;
+}());
+var ifExtensionsPreventibleIt = extensionsPreventible ? it : xit;
+var canSeal = typeof Object.seal === 'function' && (function () {
+    var obj = { a: 3 };
+    Object.seal(obj);
+    delete obj.a;
+    return obj.a === 3;
+}());
+var ifCanSealIt = canSeal ? it : xit;
+var canFreeze = typeof Object.freeze === 'function' && (function () {
+    var obj = {};
+    Object.freeze(obj);
+    obj.a = 3;
+    return obj.a !== 3;
+}());
+var ifCanFreezeIt = canFreeze ? it : xit;
+
+describe('Object', function () {
+    'use strict';
+
+    describe('.keys()', function () {
+        var obj = {
+            str: 'boz',
+            obj: { },
+            arr: [],
+            bool: true,
+            num: 42,
+            'null': null,
+            undefined: undefined
+        };
+
+        var loopedValues = [];
+        for (var k in obj) {
+            loopedValues.push(k);
+        }
+
+        var keys = Object.keys(obj);
+        it('should have correct length', function () {
+            expect(keys.length).toBe(7);
+        });
+
+        describe('arguments objects', function () {
+            it('works with an arguments object', function () {
+                (function () {
+                    expect(arguments.length).toBe(3);
+                    expect(Object.keys(arguments).length).toBe(arguments.length);
+                    expect(Object.keys(arguments)).toEqual(['0', '1', '2']);
+                }(1, 2, 3));
+            });
+
+            it('works with a legacy arguments object', function () {
+                var FakeArguments = function (args) {
+                    args.forEach(function (arg, i) {
+                        this[i] = arg;
+                    }.bind(this));
+                };
+                FakeArguments.prototype.length = 3;
+                FakeArguments.prototype.callee = function () {};
+
+                var fakeOldArguments = new FakeArguments(['a', 'b', 'c']);
+                expect(Object.keys(fakeOldArguments)).toEqual(['0', '1', '2']);
+            });
+        });
+
+        it('should return an Array', function () {
+            expect(Array.isArray(keys)).toBe(true);
+        });
+
+        it('should return names which are own properties', function () {
+            keys.forEach(function (name) {
+                expect(obj.hasOwnProperty(name)).toBe(true);
+            });
+        });
+
+        it('should return names which are enumerable', function () {
+            keys.forEach(function (name) {
+                expect(loopedValues.indexOf(name)).toNotBe(-1);
+            });
+        });
+
+        // ES6 Object.keys does not require this throw
+        xit('should throw error for non object', function () {
+            var e = {};
+            expect(function () {
+                try {
+                    Object.keys(42);
+                } catch (err) {
+                    throw e;
+                }
+            }).toThrow(e);
+        });
+
+        describe('enumerating over non-enumerable properties', function () {
+             it('has no enumerable keys on a Function', function () {
+                 var Foo = function () {};
+                 expect(Object.keys(Foo.prototype)).toEqual([]);
+             });
+
+             it('has no enumerable keys on a boolean', function () {
+                 expect(Object.keys(Boolean.prototype)).toEqual([]);
+             });
+
+             it('has no enumerable keys on an object', function () {
+                 expect(Object.keys(Object.prototype)).toEqual([]);
+             });
+        });
+
+        it('works with boxed primitives', function () {
+            expect(Object.keys(Object('hello'))).toEqual(['0', '1', '2', '3', '4']);
+        });
+
+        it('works with boxed primitives with extra properties', function () {
+            var x = Object('x');
+            x.y = 1;
+            var actual = Object.keys(x);
+            var expected = ['0', 'y'];
+            actual.sort();
+            expected.sort();
+            expect(actual).toEqual(expected);
+        });
+
+        ifWindowIt('can serialize all objects on the `window`', function () {
+          var has = Object.prototype.hasOwnProperty;
+          var keys, exception;
+          var blacklistedKeys = ['window', 'console', 'parent', 'self', 'frames', 'frameElement'];
+          if (Object.defineProperty) {
+              Object.defineProperty(window, 'thrower', { configurable: true, get: function () { throw new RangeError('thrower!'); } });
+          }
+          for (var k in window) {
+              keys = exception = void 0;
+              if (blacklistedKeys.indexOf(k) === -1 && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') {
+                     try {
+                         keys = Object.keys(window[k]);
+                     } catch (e) {
+                         exception = e;
+                     }
+                     expect(Array.isArray(keys)).toEqual(true);
+                     expect(exception).toBeUndefined();
+              }
+          }
+          delete window.thrower;
+        });
+    });
+
+    describe('Object.isExtensible', function () {
+        var obj = { };
+
+        it('should return true if object is extensible', function () {
+            expect(Object.isExtensible(obj)).toBe(true);
+        });
+
+        ifExtensionsPreventibleIt('should return false if object is not extensible', function () {
+            expect(Object.isExtensible(Object.preventExtensions(obj))).toBe(false);
+        });
+
+        ifCanSealIt('should return false if object is sealed', function () {
+            expect(Object.isExtensible(Object.seal(obj))).toBe(false);
+        });
+
+        ifCanFreezeIt('should return false if object is frozen', function () {
+            expect(Object.isExtensible(Object.freeze(obj))).toBe(false);
+        });
+
+        it('should throw error for non object', function () {
+            try {
+                // note: in ES6, this is expected to return false.
+                expect(Object.isExtensible(42)).toBe(false);
+            } catch (err) {
+                expect(err).toEqual(jasmine.any(TypeError));
+            }
+        });
+    });
+
+    describe('Object.defineProperty', function () {
+        var obj;
+
+        beforeEach(function () {
+           obj = {};
+
+           Object.defineProperty(obj, 'name', {
+               value: 'Testing',
+               configurable: true,
+               enumerable: true,
+               writable: true
+           });
+        });
+
+        it('should return the initial value', function () {
+            expect(obj.hasOwnProperty('name')).toBeTruthy();
+            expect(obj.name).toBe('Testing');
+        });
+
+        it('should be setable', function () {
+            obj.name = 'Other';
+            expect(obj.name).toBe('Other');
+        });
+
+        it('should return the parent initial value', function () {
+            var child = Object.create(obj, {});
+
+            expect(child.name).toBe('Testing');
+            expect(child.hasOwnProperty('name')).toBeFalsy();
+        });
+
+        it('should not override the parent value', function () {
+            var child = Object.create(obj, {});
+
+            Object.defineProperty(child, 'name', {
+                value: 'Other'
+            });
+
+            expect(obj.name).toBe('Testing');
+            expect(child.name).toBe('Other');
+        });
+
+        it('should throw error for non object', function () {
+            expect(function () {
+                Object.defineProperty(42, 'name', {});
+            }).toThrow();
+        });
+
+        it('should not throw error for empty descriptor', function () {
+            expect(function () {
+                Object.defineProperty({}, 'name', {});
+            }).not.toThrow();
+        });
+    });
+
+    describe('Object.getOwnPropertyDescriptor', function () {
+        it('should return undefined because the object does not own the property', function () {
+            var descr = Object.getOwnPropertyDescriptor({}, 'name');
+
+            expect(descr).toBeUndefined();
+        });
+
+        it('should return a data descriptor', function () {
+            var descr = Object.getOwnPropertyDescriptor({ name: 'Testing' }, 'name');
+
+            expect(descr).not.toBeUndefined();
+            expect(descr.value).toBe('Testing');
+            expect(descr.writable).toBe(true);
+            expect(descr.enumerable).toBe(true);
+            expect(descr.configurable).toBe(true);
+        });
+
+        it('should return undefined because the object does not own the property', function () {
+            var descr = Object.getOwnPropertyDescriptor(Object.create({ name: 'Testing' }, {}), 'name');
+
+            expect(descr).toBeUndefined();
+        });
+
+        it('should return a data descriptor', function () {
+            var obj = Object.create({}, {
+                name: {
+                    value: 'Testing',
+                    configurable: true,
+                    enumerable: true,
+                    writable: true
+                }
+            });
+
+            var descr = Object.getOwnPropertyDescriptor(obj, 'name');
+
+            expect(descr).not.toBeUndefined();
+            expect(descr.value).toBe('Testing');
+            expect(descr.writable).toBe(true);
+            expect(descr.enumerable).toBe(true);
+            expect(descr.configurable).toBe(true);
+        });
+
+        it('should throw error for non object', function () {
+            try {
+                // note: in ES6, we expect this to return undefined.
+                expect(Object.getOwnPropertyDescriptor(42, 'name')).toBeUndefined();
+            } catch (err) {
+                expect(err).toEqual(jasmine.any(TypeError));
+            }
+        });
+    });
+
+    describe('Object.getPrototypeOf', function () {
+        it('should return the [[Prototype]] of an object', function () {
+            var Foo = function () {};
+
+            var proto = Object.getPrototypeOf(new Foo());
+
+            expect(proto).toBe(Foo.prototype);
+        });
+
+        it('should shamone to the `Object.prototype` if `object.constructor` is not a function', function () {
+            var Foo = function () {};
+            Foo.prototype.constructor = 1;
+
+            var proto = Object.getPrototypeOf(new Foo()),
+                fnToString = Function.prototype.toString;
+
+            if (fnToString.call(Object.getPrototypeOf).indexOf('[native code]') < 0) {
+                expect(proto).toBe(Object.prototype);
+            } else {
+                expect(proto).toBe(Foo.prototype);
+            }
+        });
+
+        it('should throw error for non object', function () {
+            try {
+                expect(Object.getPrototypeOf(1)).toBe(Number.prototype); // ES6 behavior
+            } catch (err) {
+                expect(err).toEqual(jasmine.any(TypeError));
+            }
+        });
+
+        it('should return null on Object.create(null)', function () {
+            var obj = Object.create(null);
+
+            expect(Object.getPrototypeOf(obj)).toBe(null);
+        });
+    });
+
+    describe('Object.defineProperties', function () {
+        it('should define the constructor property', function () {
+            var target = {};
+            var newProperties = {
+                constructor: {
+                  value: 'new constructor'
+                }
+            };
+            Object.defineProperties(target, newProperties);
+            expect(target.constructor).toEqual('new constructor');
+        });
+    });
+
+    describe('Object.create', function () {
+        it('should create objects with no properties when called as `Object.create(null)`', function () {
+            var obj = Object.create(null);
+
+            expect('hasOwnProperty' in obj).toBe(false);
+            expect('toString' in obj).toBe(false);
+            expect('constructor' in obj).toBe(false);
+
+            var protoIsEnumerable = false;
+            for (var k in obj) {
+                if (k === '__proto__') {
+                    protoIsEnumerable = true;
+                }
+            }
+            expect(protoIsEnumerable).toBe(false);
+
+            expect(obj instanceof Object).toBe(false);
+        });
+    });
+});
diff --git a/tests/spec/s-string.js b/tests/spec/s-string.js
new file mode 100644
index 0000000..de62924
--- /dev/null
+++ b/tests/spec/s-string.js
@@ -0,0 +1,232 @@
+/* global describe, it, expect */
+
+describe('String', function () {
+    'use strict';
+
+    describe('trim', function () {
+        var test = '\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFFHello, World!\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF';
+
+        it('trims all ES5 whitespace', function () {
+            expect(test.trim()).toEqual('Hello, World!');
+            expect(test.trim().length).toBe(13);
+        });
+
+        it('does not trim the zero-width space', function () {
+            expect('\u200b'.trim()).toBe('\u200b');
+            expect('\u200b'.trim().length).toBe(1);
+        });
+    });
+
+    describe('replace', function () {
+        it('returns undefined for non-capturing groups', function () {
+            var groups = [];
+            'x'.replace(/x(.)?/g, function (m, group) {
+                groups.push(group); /* "" in FF, `undefined` in CH/WK/IE */
+            });
+            expect(groups.length).toBe(1);
+            expect(groups[0]).toBeUndefined();
+        });
+
+        it('should not fail in Firefox', function () {
+            expect(function () {
+                return '* alef\n* beth \n* gimel~0\n'.replace(
+                    /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
+                    function (match, m1, m2, m3, m4) { return '<li>' + m4 + '</li>\n'; }
+                );
+            }).not.toThrow();
+        });
+    });
+
+    describe('split', function () {
+        var test = 'ab';
+
+        it('If "separator" is undefined must return Array with one String - "this" string', function () {
+            expect(test.split()).toEqual([test]);
+            expect(test.split(void 0)).toEqual([test]);
+        });
+
+        it('If "separator" is undefined and "limit" set to 0 must return Array[]', function () {
+            expect(test.split(void 0, 0)).toEqual([]);
+        });
+
+        describe('Tests from Steven Levithan', function () {
+            it("''.split() results in ['']", function () {
+                expect(''.split()).toEqual(['']);
+            });
+            it("''.split(/./) results in ['']", function () {
+                expect(''.split(/./)).toEqual(['']);
+            });
+            it("''.split(/.?/) results in []", function () {
+                expect(''.split(/.?/)).toEqual([]);
+            });
+            it("''.split(/.??/) results in []", function () {
+                expect(''.split(/.??/)).toEqual([]);
+            });
+            it("'ab'.split(/a*/) results in ['', 'b']", function () {
+                expect('ab'.split(/a*/)).toEqual(['', 'b']);
+            });
+            it("'ab'.split(/a*?/) results in ['a', 'b']", function () {
+                expect('ab'.split(/a*?/)).toEqual(['a', 'b']);
+            });
+            it("'ab'.split(/(?:ab)/) results in ['', '']", function () {
+                expect('ab'.split(/(?:ab)/)).toEqual(['', '']);
+            });
+            it("'ab'.split(/(?:ab)*/) results in ['', '']", function () {
+                expect('ab'.split(/(?:ab)*/)).toEqual(['', '']);
+            });
+            it("'ab'.split(/(?:ab)*?/) results in ['a', 'b']", function () {
+                expect('ab'.split(/(?:ab)*?/)).toEqual(['a', 'b']);
+            });
+            it("'test'.split('') results in ['t', 'e', 's', 't']", function () {
+                expect('test'.split('')).toEqual(['t', 'e', 's', 't']);
+            });
+            it("'test'.split() results in ['test']", function () {
+                expect('test'.split()).toEqual(['test']);
+            });
+            it("'111'.split(1) results in ['', '', '', '']", function () {
+                expect('111'.split(1)).toEqual(['', '', '', '']);
+            });
+            it("'test'.split(/(?:)/, 2) results in ['t', 'e']", function () {
+                expect('test'.split(/(?:)/, 2)).toEqual(['t', 'e']);
+            });
+            it("'test'.split(/(?:)/, -1) results in ['t', 'e', 's', 't']", function () {
+                expect('test'.split(/(?:)/, -1)).toEqual(['t', 'e', 's', 't']);
+            });
+            it("'test'.split(/(?:)/, undefined) results in ['t', 'e', 's', 't']", function () {
+                expect('test'.split(/(?:)/, undefined)).toEqual(['t', 'e', 's', 't']);
+            });
+            it("'test'.split(/(?:)/, null) results in []", function () {
+                expect('test'.split(/(?:)/, null)).toEqual([]);
+            });
+            it("'test'.split(/(?:)/, NaN) results in []", function () {
+                expect('test'.split(/(?:)/, NaN)).toEqual([]);
+            });
+            it("'test'.split(/(?:)/, true) results in ['t']", function () {
+                expect('test'.split(/(?:)/, true)).toEqual(['t']);
+            });
+            it("'test'.split(/(?:)/, '2') results in ['t', 'e']", function () {
+                expect('test'.split(/(?:)/, '2')).toEqual(['t', 'e']);
+            });
+            it("'test'.split(/(?:)/, 'two') results in []", function () {
+                expect('test'.split(/(?:)/, 'two')).toEqual([]);
+            });
+            it("'a'.split(/-/) results in ['a']", function () {
+                expect('a'.split(/-/)).toEqual(['a']);
+            });
+            it("'a'.split(/-?/) results in ['a']", function () {
+                expect('a'.split(/-?/)).toEqual(['a']);
+            });
+            it("'a'.split(/-??/) results in ['a']", function () {
+                expect('a'.split(/-??/)).toEqual(['a']);
+            });
+            it("'a'.split(/a/) results in ['', '']", function () {
+                expect('a'.split(/a/)).toEqual(['', '']);
+            });
+            it("'a'.split(/a?/) results in ['', '']", function () {
+                expect('a'.split(/a?/)).toEqual(['', '']);
+            });
+            it("'a'.split(/a??/) results in ['a']", function () {
+                expect('a'.split(/a??/)).toEqual(['a']);
+            });
+            it("'ab'.split(/-/) results in ['ab']", function () {
+                expect('ab'.split(/-/)).toEqual(['ab']);
+            });
+            it("'ab'.split(/-?/) results in ['a', 'b']", function () {
+                expect('ab'.split(/-?/)).toEqual(['a', 'b']);
+            });
+            it("'ab'.split(/-??/) results in ['a', 'b']", function () {
+                expect('ab'.split(/-??/)).toEqual(['a', 'b']);
+            });
+            it("'a-b'.split(/-/) results in ['a', 'b']", function () {
+                expect('a-b'.split(/-/)).toEqual(['a', 'b']);
+            });
+            it("'a-b'.split(/-?/) results in ['a', 'b']", function () {
+                expect('a-b'.split(/-?/)).toEqual(['a', 'b']);
+            });
+            it("'a-b'.split(/-??/) results in ['a', '-', 'b']", function () {
+                expect('a-b'.split(/-??/)).toEqual(['a', '-', 'b']);
+            });
+            it("'a--b'.split(/-/) results in ['a', '', 'b']", function () {
+                expect('a--b'.split(/-/)).toEqual(['a', '', 'b']);
+            });
+            it("'a--b'.split(/-?/) results in ['a', '', 'b']", function () {
+                expect('a--b'.split(/-?/)).toEqual(['a', '', 'b']);
+            });
+            it("'a--b'.split(/-??/) results in ['a', '-', '-', 'b']", function () {
+                expect('a--b'.split(/-??/)).toEqual(['a', '-', '-', 'b']);
+            });
+            it("''.split(/()()/) results in []", function () {
+                expect(''.split(/()()/)).toEqual([]);
+            });
+            it("'.'.split(/()()/) results in ['.']", function () {
+                expect('.'.split(/()()/)).toEqual(['.']);
+            });
+            it("'.'.split(/(.?)(.?)/) results in ['', '.', '', '']", function () {
+                expect('.'.split(/(.?)(.?)/)).toEqual(['', '.', '', '']);
+            });
+            it("'.'.split(/(.??)(.??)/) results in ['.']", function () {
+                expect('.'.split(/(.??)(.??)/)).toEqual(['.']);
+            });
+            it("'.'.split(/(.)?(.)?/) results in ['', '.', undefined, '']", function () {
+                expect('.'.split(/(.)?(.)?/)).toEqual(['', '.', undefined, '']);
+            });
+            it("'A<B>bold</B>and<CODE>coded</CODE>'.split(/<(\\/)?([^<>]+)>/) results in ['A', undefined, 'B', 'bold', '/', 'B', 'and', undefined, 'CODE', 'coded', '/', 'CODE', '']", function () {
+                expect('A<B>bold</B>and<CODE>coded</CODE>'.split(/<(\/)?([^<>]+)>/)).toEqual(['A', undefined, 'B', 'bold', '/', 'B', 'and', undefined, 'CODE', 'coded', '/', 'CODE', '']);
+            });
+            it("'tesst'.split(/(s)*/) results in ['t', undefined, 'e', 's', 't']", function () {
+                expect('tesst'.split(/(s)*/)).toEqual(['t', undefined, 'e', 's', 't']);
+            });
+            it("'tesst'.split(/(s)*?/) results in ['t', undefined, 'e', undefined, 's', undefined, 's', undefined, 't']", function () {
+                expect('tesst'.split(/(s)*?/)).toEqual(['t', undefined, 'e', undefined, 's', undefined, 's', undefined, 't']);
+            });
+            it("'tesst'.split(/(s*)/) results in ['t', '', 'e', 'ss', 't']", function () {
+                expect('tesst'.split(/(s*)/)).toEqual(['t', '', 'e', 'ss', 't']);
+            });
+            it("'tesst'.split(/(s*?)/) results in ['t', '', 'e', '', 's', '', 's', '', 't']", function () {
+                expect('tesst'.split(/(s*?)/)).toEqual(['t', '', 'e', '', 's', '', 's', '', 't']);
+            });
+            it("'tesst'.split(/(?:s)*/) results in ['t', 'e', 't']", function () {
+                expect('tesst'.split(/(?:s)*/)).toEqual(['t', 'e', 't']);
+            });
+            it("'tesst'.split(/(?=s+)/) results in ['te', 's', 'st']", function () {
+                expect('tesst'.split(/(?=s+)/)).toEqual(['te', 's', 'st']);
+            });
+            it("'test'.split('t') results in ['', 'es', '']", function () {
+                expect('test'.split('t')).toEqual(['', 'es', '']);
+            });
+            it("'test'.split('es') results in ['t', 't']", function () {
+                expect('test'.split('es')).toEqual(['t', 't']);
+            });
+            it("'test'.split(/t/) results in ['', 'es', '']", function () {
+                expect('test'.split(/t/)).toEqual(['', 'es', '']);
+            });
+            it("'test'.split(/es/) results in ['t', 't']", function () {
+                expect('test'.split(/es/)).toEqual(['t', 't']);
+            });
+            it("'test'.split(/(t)/) results in ['', 't', 'es', 't', '']", function () {
+                expect('test'.split(/(t)/)).toEqual(['', 't', 'es', 't', '']);
+            });
+            it("'test'.split(/(es)/) results in ['t', 'es', 't']", function () {
+                expect('test'.split(/(es)/)).toEqual(['t', 'es', 't']);
+            });
+            it("'test'.split(/(t)(e)(s)(t)/) results in ['', 't', 'e', 's', 't', '']", function () {
+                expect('test'.split(/(t)(e)(s)(t)/)).toEqual(['', 't', 'e', 's', 't', '']);
+            });
+            it("'.'.split(/(((.((.??)))))/) results in ['', '.', '.', '.', '', '', '']", function () {
+                expect('.'.split(/(((.((.??)))))/)).toEqual(['', '.', '.', '.', '', '', '']);
+            });
+            it("'.'.split(/(((((.??)))))/) results in ['.']", function () {
+                expect('.'.split(/(((((.??)))))/)).toEqual(['.']);
+            });
+            it("'a b c d'.split(/ /, -(Math.pow(2, 32) - 1)) results in ['a']", function () {
+                expect('a b c d'.split(/ /, -(Math.pow(2, 32) - 1))).toEqual(['a']);
+            });
+            it("'a b c d'.split(/ /, Math.pow(2, 32) + 1) results in ['a']", function () {
+                expect('a b c d'.split(/ /, Math.pow(2, 32) + 1)).toEqual(['a']);
+            });
+            it("'a b c d'.split(/ /, Infinity) results in []", function () {
+                expect('a b c d'.split(/ /, Infinity)).toEqual([]);
+            });
+        });
+    });
+});

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-es5-shim.git



More information about the Pkg-javascript-commits mailing list