[Pkg-javascript-commits] [libjs-bootbox] 01/02: Import Upstream version 4.4.0+dfsg1

Johannes Schauer josch at moszumanska.debian.org
Wed Jul 5 18:03:22 UTC 2017


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

josch pushed a commit to branch master
in repository libjs-bootbox.

commit ae544f9c63aeb974b26b09dc313416458582c6f1
Author: Johannes Schauer <josch at debian.org>
Date:   Wed Jul 5 20:02:46 2017 +0200

    Import Upstream version 4.4.0+dfsg1
---
 .gitignore                  |    9 +
 .jshintrc                   |   24 +
 .npmignore                  |    8 +
 .travis.yml                 |    3 +
 CHANGELOG.md                |  186 ++++++
 CONTRIBUTING.md             |   17 +
 Gruntfile.js                |   33 ++
 LICENSE.md                  |   23 +
 README.md                   |   91 +++
 bootbox.js                  |  985 ++++++++++++++++++++++++++++++++
 bower.json                  |   17 +
 build/calculate-size        |   14 +
 header.txt                  |    5 +
 karma-base.conf.js          |   51 ++
 karma-jquery-latest.conf.js |    8 +
 karma.conf.js               |    8 +
 package.json                |   35 ++
 tests/alert.test.js         |  355 ++++++++++++
 tests/bootbox.test.js       |  269 +++++++++
 tests/confirm.test.coffee   |  260 +++++++++
 tests/defaults.test.js      |  186 ++++++
 tests/dialog.test.coffee    |  349 ++++++++++++
 tests/locales.test.coffee   |  378 +++++++++++++
 tests/prompt.test.coffee    | 1306 +++++++++++++++++++++++++++++++++++++++++++
 24 files changed, 4620 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1132214
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.swo
+*.swp
+node_modules
+npm-debug.log
+bootbox.min.js
+.idea
+tests/reports
+tests/coverage
+build/size.csv
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..1a88199
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,24 @@
+{
+    "undef": true,
+    "unused": true,
+    "eqeqeq": true,
+    "curly": true,
+    "newcap": true,
+    "strict": true,
+    "trailing": true,
+    "quotmark": "double",
+    "browser": true,
+
+    "globals": {
+      "beforeEach": true,
+      "afterEach": true,
+      "describe": true,
+      "it": true,
+      "define": true,
+      "module": true,
+      "require": true,
+      "expect": true,
+      "bootbox": true,
+      "sinon": true
+    }
+}
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..ec7f091
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,8 @@
+*.swo
+*.swp
+node_modules
+npm-debug.log
+.idea
+tests/reports
+tests/coverage
+build/size.csv
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6e5919d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+  - "0.10"
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..76f62fa
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,186 @@
+### Latest Release: 4.4.0
+
+* Allow `backdrop` options of `true` and `false` to dismiss modals
+* Pass dialog as `this` value in callbacks
+* Bootstrap 3.3.2 compatibility
+* jQuery 1.11.2 compatibility
+* Add support for `maxlength` prompt input attribute
+* Gracefully detect lack of Bootstrap library rather than crashing
+* Expose `addLocale` and `removeLocale` for custom locale settings
+* Expose `setLocale` helper to select a locale rather than using `setDefaults("locale", ...)`
+* Add Hungarian locale
+* Add Croatian locale
+* Add Bulgarian locale
+* Add Thai locale
+* Add Persian locale
+* Add Albanian locale
+
+## 4.3.0
+
+* Add `size` option (large, small)
+* Stop propagation on form submit
+* Return bootbox object from `hideAll`
+* Add Portuguese locale
+* Add Czech locale
+* Add Greek locale
+* Add Estonian locale
+* Add Indonesian locale
+* Add Japanese locale
+
+### 4.2.0
+
+* Add Swedish locale
+* Add Latvian locale
+* Add Turkish locale
+* Add Hebrew locale
+* Add password input type
+* Add textarea input type
+* Add date input type
+* Add time input type
+* Add number input type
+* Support DOM selectors for container argument
+* UMD support
+* Better support on mobile devices
+
+### 4.1.0
+
+* Add support for placeholder attribute in prompts
+* Add select, email and checkbox types for prompts (thanks [@tarlepp](https://github.com/tarlepp))
+* Add Norwegian locale
+* Allow setDefaults to take two key/val arguments
+* Add unique classes for main dialog methods
+* Create bower package
+
+### 4.0.0
+
+* Bootstrap 3.0.0 compatibility
+* Complete rewrite (and new public API)
+* Use strict mode
+* Add close buttons to wrapper methods (GH-92)
+* Allow dialog titles to be specified (GH-51, GH-112)
+* Allow optional extra class on dialog wrapper (GH-116)
+* Fix ```backdrop: true``` not firing close handler (GH-77)
+* Replace various configuration methods with one ```setDefaults```
+
+### 3.3.0
+
+* Add Polish translation (GH-93)
+* Add Danish translation (GH-96)
+* Pass event object to custom callbacks (GH-103)
+* Add Chinese (Taiwan / China) translations (GH-106)
+* Make prompt input block-level (GH-111)
+* Add link: true option to prevent btn class from being applied (GH-114)
+* Prevent child elements triggering hidden callback (GH-115)
+* Replace Phing with Grunt
+* Replace Closure compiler with UglifyJS
+
+### 3.2.0
+
+* ensure ```onEscape``` handlers return callback values properly (GH-91)
+* ensure clicking close button invokes onEscape handler if present
+
+### 3.1.0
+
+* ensure ```confirm``` and ```prompt``` methods return callback values properly (GH-90)
+* address various jshint warnings (GH-79)
+* add ```setBtnClasses``` method for custom standard button classes (GH-87)
+
+### 3.0.0
+
+* bump Bootstrap dependency to 2.2.2
+* bump jQuery dependency to 1.8.3
+* ensure callbacks are always invoked even if dialogs are dismissed with escape key (GH-49)
+* fix button positions with Bootstrap 2.2.2 (GH-58)
+* stop multiple dialogs crashing browsers (GH-60, GH-64)
+* ensure ```shown``` event is fired properly even when animation is disabled (GH-69)
+* use ```.on``` instead of ```.bind```
+* commentify code a bit more
+
+### 2.5.1
+
+**This was the last version of the library to support Bootstrap 2.0.x**
+
+* ensure bootbox object is explicitly added to window object for minfier visibility
+
+### 2.5.0
+
+* add option to specify proper href attributes for buttons instead of callbacks (@StevePotter)
+* add option to override per-modal classes (@ciaranj)
+
+### 2.4.2
+
+* revert ```backdrop``` default value to 'static' instead of ```true``` to prevent background clicks dismissing dialogs (GH-55)
+
+### 2.4.1
+
+* fix ```backdrop``` when supplied as an argument to ```bootbox.dialog```
+* fix incorrect README version
+
+### 2.4.0
+
+* add ```bootbox.backdrop(bool)``` method (@gucki)
+* add default parameter option to ```bootbox.prompt``` (@pzgz)
+
+### 2.3.3
+
+* add inline ```overflow: hidden``` CSS property (GH-46)
+* move license info to separate hosted file to reduce file size
+
+### 2.3.2
+
+* Change button href attributes to ```javascript:;``` (@joshnesbitt)
+* Explicitly ```window.jQuery``` through to ```Bootbox``` object (@nuegon)
+
+
+### 2.3.1
+
+* Ensure bootbox.prompt() gives focus to input, disable input autocomplete
+
+### 2.3.0
+
+* Added bootbox.prompt() to mimic native prompt() method
+* Added Russian locale (#27)
+
+### 2.2.0
+
+* Allowed button callbacks to explicitly return false to prevent dialog from closing (thanks @benoit-ponsero)
+* Added version number to header comments (#26)
+
+### 2.1.2
+
+* Added close button to re-scoped click handler (thanks @SeanMcGee and @kentbrew)
+
+### 2.1.1
+
+* Fixed incorrect button click handler selector (thanks FGRibreau)
+
+### 2.1.0
+
+* Added support for Bootstrap's Glyphicons via the ```icon``` option
+* Added inline license information into bootbox.js and bootbox.min.js
+* Tidied up source a little
+
+### 2.0.1
+
+* Removed dummy Google Closure Compiler method from minified library (thanks j0k3r!)
+
+### 2.0.0
+
+* Updated Bootstrap dependency from 1.4 to 2.0
+* Class definitions now require ```btn-``` prefix as per Bootstrap 2.0
+* Added Brazilian locale
+* Added ```animate``` dialog option
+* Added ```bootbox.animate(bool)``` option to set default animation preference
+* Animated dialogs now rely on ```bootstrap-transitions.js``` as required by Bootstrap 2.0
+
+### 1.1.2
+
+* Added licensing information to README
+
+#### 1.1.1
+* Updated german locale
+
+#### 1.1.0
+* Secondary option of two-button dialog no longer has 'danger' class
+* New bootbox.modal() method for generic non-dialog popups
+* Allow jQuery objects to be passed as main dialog argument
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..0ba843c
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,17 @@
+## Submitting Pull Requests
+
+**Please follow these basic steps to simplify pull request reviews - if you don't you'll probably just be asked to anyway.**
+
+* Please rebase your branch against the current master
+* Run ```npm install``` to make sure your development dependencies are up-to-date
+* [grunt-cli](https://github.com/gruntjs/grunt-cli) >= 0.4.0 is required to sanity check your contribution
+* Please ensure that the test suite passes **and** that bootbox.js is lint free before submitting a PR by running:
+ * ```grunt```
+* If you've added new functionality, **please** include tests which validate its behaviour
+ * **this includes pull requests which _only_ add new locales!**
+
+## Submitting bug reports
+
+* Where at all possible, please try and provide a link to a jsfiddle.net example or similar
+* Please detail the affected browser(s) and operating system(s)
+* Please be sure to state which version of Bootbox, jQuery **and** Bootstrap you're using
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..cc059ed
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,33 @@
+module.exports = function(grunt) {
+  grunt.initConfig({
+    uglify: {
+      options: {
+        banner: grunt.file.read("header.txt")
+      },
+      build: {
+        files: {
+          "bootbox.min.js": ["bootbox.js"]
+        }
+      }
+    },
+
+    jshint: {
+      options: {
+        jshintrc: ".jshintrc"
+      },
+      all: ["bootbox.js"]
+    },
+
+    karma: {
+      unit: {
+        configFile: "karma.conf.js"
+      }
+    }
+  });
+
+  grunt.loadNpmTasks("grunt-contrib-uglify");
+  grunt.loadNpmTasks("grunt-contrib-jshint");
+  grunt.loadNpmTasks("grunt-karma");
+
+  grunt.registerTask("default", ["jshint", "karma"]);
+};
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..8219f59
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,23 @@
+# License
+
+(The MIT License)
+
+Copyright (C) 2011-2014 by Nick Payne <nick at kurai.co.uk>
+
+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/README.md b/README.md
new file mode 100644
index 0000000..55bf55f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,91 @@
+# Bootbox - Bootstrap powered alert, confirm and flexible dialog boxes
+
+Please see http://bootboxjs.com for full usage instructions, or head over to http://paynedigital.com/bootbox for
+the original writeup about the project.
+
+## Contact
+
+The easiest thing is to [find me on twitter @makeusabrew](http://twitter.com/makeusabrew).
+
+## Contributing
+
+Please see the [CONTRIBUTING](https://github.com/makeusabrew/bootbox/blob/master/CONTRIBUTING.md) file for guidelines.
+
+## Running Tests [![Build Status](https://api.travis-ci.org/makeusabrew/bootbox.svg)](http://travis-ci.org/makeusabrew/bootbox)
+
+Tests are run using [Karma](http://karma-runner.github.io/0.8/index.html) using the Mocha test adapter.
+To run the tests yourself, simply run ```npm install``` within the project followed by ```npm test```.
+Please note that this will require [PhantomJS](http://phantomjs.org/) being installed and in your path - if
+it is not, you may run the tests and capture browsers manually by running ```karma start``` from the root
+of the project.
+
+The project is also hosted on [Travis CI](https://travis-ci.org/makeusabrew/bootbox) - when submitting
+pull requests **please** ensure your tests pass as failing requests will be rejected. See the
+[CONTRIBUTING](https://github.com/makeusabrew/bootbox/blob/master/CONTRIBUTING.md) file for more information.
+
+## Building a minified release
+
+The repository no longer contains a minified bootbox.min.js file - this is now only generated
+[for releases](https://github.com/makeusabrew/bootbox/releases). To build your own minified copy
+for use in development simply run ```npm install``` if you haven't already, followed by ```grunt uglify```.
+This will generate a bootbox.min.js file in your working directory.
+
+## A note on Bootstrap dependencies
+
+Bootbox **4.0.0** is the first release to support Bootstrap 3.0.0.
+
+Bootbox **3.3.0** is the *last* release to support Bootstrap 2.2.x.
+
+Much more dependency information can be found [on the Bootbox website](http://bootboxjs.com/#dependencies).
+
+### Roadmap
+
+The latest major release of Bootbox - 4.0.0 - involved a total rewrite of the
+internal code and introduced an entirely new public API. It has not re-implemented
+some functionality from the 3.x series as of yet; this will be addressed in the
+the form of new minor releases. Please feel free to add feedback and requests.
+
+There is no new major (e.g. 5.x) release on the roadmap at present.
+
+### Latest Release: 4.4.0
+
+* Allow `backdrop` options of `true` and `false` to dismiss modals
+* Pass dialog as `this` value in callbacks
+* Bootstrap 3.3.2 compatibility
+* jQuery 1.11.2 compatibility
+* Add support for `maxlength` prompt input attribute
+* Gracefully detect lack of Bootstrap library rather than crashing
+* Expose `addLocale` and `removeLocale` for custom locale settings
+* Expose `setLocale` helper to select a locale rather than using `setDefaults("locale", ...)`
+* Add Hungarian locale
+* Add Croatian locale
+* Add Bulgarian locale
+* Add Thai locale
+* Add Persian locale
+* Add Albanian locale
+
+For a full list of releases and changes please see [the changelog](https://github.com/makeusabrew/bootbox/blob/master/CHANGELOG.md).
+
+## License
+
+(The MIT License)
+
+Copyright (C) 2011-2015 by Nick Payne <nick at kurai.co.uk>
+
+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/bootbox.js b/bootbox.js
new file mode 100644
index 0000000..3e8312a
--- /dev/null
+++ b/bootbox.js
@@ -0,0 +1,985 @@
+/**
+ * bootbox.js [v4.4.0]
+ *
+ * http://bootboxjs.com/license.txt
+ */
+
+// @see https://github.com/makeusabrew/bootbox/issues/180
+// @see https://github.com/makeusabrew/bootbox/issues/186
+(function (root, factory) {
+
+  "use strict";
+  if (typeof define === "function" && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(["jquery"], factory);
+  } else if (typeof exports === "object") {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"));
+  } else {
+    // Browser globals (root is window)
+    root.bootbox = factory(root.jQuery);
+  }
+
+}(this, function init($, undefined) {
+
+  "use strict";
+
+  // the base DOM structure needed to create a modal
+  var templates = {
+    dialog:
+      "<div class='bootbox modal' tabindex='-1' role='dialog'>" +
+        "<div class='modal-dialog'>" +
+          "<div class='modal-content'>" +
+            "<div class='modal-body'><div class='bootbox-body'></div></div>" +
+          "</div>" +
+        "</div>" +
+      "</div>",
+    header:
+      "<div class='modal-header'>" +
+        "<h4 class='modal-title'></h4>" +
+      "</div>",
+    footer:
+      "<div class='modal-footer'></div>",
+    closeButton:
+      "<button type='button' class='bootbox-close-button close' data-dismiss='modal' aria-hidden='true'>×</button>",
+    form:
+      "<form class='bootbox-form'></form>",
+    inputs: {
+      text:
+        "<input class='bootbox-input bootbox-input-text form-control' autocomplete=off type=text />",
+      textarea:
+        "<textarea class='bootbox-input bootbox-input-textarea form-control'></textarea>",
+      email:
+        "<input class='bootbox-input bootbox-input-email form-control' autocomplete='off' type='email' />",
+      select:
+        "<select class='bootbox-input bootbox-input-select form-control'></select>",
+      checkbox:
+        "<div class='checkbox'><label><input class='bootbox-input bootbox-input-checkbox' type='checkbox' /></label></div>",
+      date:
+        "<input class='bootbox-input bootbox-input-date form-control' autocomplete=off type='date' />",
+      time:
+        "<input class='bootbox-input bootbox-input-time form-control' autocomplete=off type='time' />",
+      number:
+        "<input class='bootbox-input bootbox-input-number form-control' autocomplete=off type='number' />",
+      password:
+        "<input class='bootbox-input bootbox-input-password form-control' autocomplete='off' type='password' />"
+    }
+  };
+
+  var defaults = {
+    // default language
+    locale: "en",
+    // show backdrop or not. Default to static so user has to interact with dialog
+    backdrop: "static",
+    // animate the modal in/out
+    animate: true,
+    // additional class string applied to the top level dialog
+    className: null,
+    // whether or not to include a close button
+    closeButton: true,
+    // show the dialog immediately by default
+    show: true,
+    // dialog container
+    container: "body"
+  };
+
+  // our public object; augmented after our private API
+  var exports = {};
+
+  /**
+   * @private
+   */
+  function _t(key) {
+    var locale = locales[defaults.locale];
+    return locale ? locale[key] : locales.en[key];
+  }
+
+  function processCallback(e, dialog, callback) {
+    e.stopPropagation();
+    e.preventDefault();
+
+    // by default we assume a callback will get rid of the dialog,
+    // although it is given the opportunity to override this
+
+    // so, if the callback can be invoked and it *explicitly returns false*
+    // then we'll set a flag to keep the dialog active...
+    var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false;
+
+    // ... otherwise we'll bin it
+    if (!preserveDialog) {
+      dialog.modal("hide");
+    }
+  }
+
+  function getKeyLength(obj) {
+    // @TODO defer to Object.keys(x).length if available?
+    var k, t = 0;
+    for (k in obj) {
+      t ++;
+    }
+    return t;
+  }
+
+  function each(collection, iterator) {
+    var index = 0;
+    $.each(collection, function(key, value) {
+      iterator(key, value, index++);
+    });
+  }
+
+  function sanitize(options) {
+    var buttons;
+    var total;
+
+    if (typeof options !== "object") {
+      throw new Error("Please supply an object of options");
+    }
+
+    if (!options.message) {
+      throw new Error("Please specify a message");
+    }
+
+    // make sure any supplied options take precedence over defaults
+    options = $.extend({}, defaults, options);
+
+    if (!options.buttons) {
+      options.buttons = {};
+    }
+
+    buttons = options.buttons;
+
+    total = getKeyLength(buttons);
+
+    each(buttons, function(key, button, index) {
+
+      if ($.isFunction(button)) {
+        // short form, assume value is our callback. Since button
+        // isn't an object it isn't a reference either so re-assign it
+        button = buttons[key] = {
+          callback: button
+        };
+      }
+
+      // before any further checks make sure by now button is the correct type
+      if ($.type(button) !== "object") {
+        throw new Error("button with key " + key + " must be an object");
+      }
+
+      if (!button.label) {
+        // the lack of an explicit label means we'll assume the key is good enough
+        button.label = key;
+      }
+
+      if (!button.className) {
+        if (total <= 2 && index === total-1) {
+          // always add a primary to the main option in a two-button dialog
+          button.className = "btn-primary";
+        } else {
+          button.className = "btn-default";
+        }
+      }
+    });
+
+    return options;
+  }
+
+  /**
+   * map a flexible set of arguments into a single returned object
+   * if args.length is already one just return it, otherwise
+   * use the properties argument to map the unnamed args to
+   * object properties
+   * so in the latter case:
+   * mapArguments(["foo", $.noop], ["message", "callback"])
+   * -> { message: "foo", callback: $.noop }
+   */
+  function mapArguments(args, properties) {
+    var argn = args.length;
+    var options = {};
+
+    if (argn < 1 || argn > 2) {
+      throw new Error("Invalid argument length");
+    }
+
+    if (argn === 2 || typeof args[0] === "string") {
+      options[properties[0]] = args[0];
+      options[properties[1]] = args[1];
+    } else {
+      options = args[0];
+    }
+
+    return options;
+  }
+
+  /**
+   * merge a set of default dialog options with user supplied arguments
+   */
+  function mergeArguments(defaults, args, properties) {
+    return $.extend(
+      // deep merge
+      true,
+      // ensure the target is an empty, unreferenced object
+      {},
+      // the base options object for this type of dialog (often just buttons)
+      defaults,
+      // args could be an object or array; if it's an array properties will
+      // map it to a proper options object
+      mapArguments(
+        args,
+        properties
+      )
+    );
+  }
+
+  /**
+   * this entry-level method makes heavy use of composition to take a simple
+   * range of inputs and return valid options suitable for passing to bootbox.dialog
+   */
+  function mergeDialogOptions(className, labels, properties, args) {
+    //  build up a base set of dialog properties
+    var baseOptions = {
+      className: "bootbox-" + className,
+      buttons: createLabels.apply(null, labels)
+    };
+
+    // ensure the buttons properties generated, *after* merging
+    // with user args are still valid against the supplied labels
+    return validateButtons(
+      // merge the generated base properties with user supplied arguments
+      mergeArguments(
+        baseOptions,
+        args,
+        // if args.length > 1, properties specify how each arg maps to an object key
+        properties
+      ),
+      labels
+    );
+  }
+
+  /**
+   * from a given list of arguments return a suitable object of button labels
+   * all this does is normalise the given labels and translate them where possible
+   * e.g. "ok", "confirm" -> { ok: "OK, cancel: "Annuleren" }
+   */
+  function createLabels() {
+    var buttons = {};
+
+    for (var i = 0, j = arguments.length; i < j; i++) {
+      var argument = arguments[i];
+      var key = argument.toLowerCase();
+      var value = argument.toUpperCase();
+
+      buttons[key] = {
+        label: _t(value)
+      };
+    }
+
+    return buttons;
+  }
+
+  function validateButtons(options, buttons) {
+    var allowedButtons = {};
+    each(buttons, function(key, value) {
+      allowedButtons[value] = true;
+    });
+
+    each(options.buttons, function(key) {
+      if (allowedButtons[key] === undefined) {
+        throw new Error("button key " + key + " is not allowed (options are " + buttons.join("\n") + ")");
+      }
+    });
+
+    return options;
+  }
+
+  exports.alert = function() {
+    var options;
+
+    options = mergeDialogOptions("alert", ["ok"], ["message", "callback"], arguments);
+
+    if (options.callback && !$.isFunction(options.callback)) {
+      throw new Error("alert requires callback property to be a function when provided");
+    }
+
+    /**
+     * overrides
+     */
+    options.buttons.ok.callback = options.onEscape = function() {
+      if ($.isFunction(options.callback)) {
+        return options.callback.call(this);
+      }
+      return true;
+    };
+
+    return exports.dialog(options);
+  };
+
+  exports.confirm = function() {
+    var options;
+
+    options = mergeDialogOptions("confirm", ["cancel", "confirm"], ["message", "callback"], arguments);
+
+    /**
+     * overrides; undo anything the user tried to set they shouldn't have
+     */
+    options.buttons.cancel.callback = options.onEscape = function() {
+      return options.callback.call(this, false);
+    };
+
+    options.buttons.confirm.callback = function() {
+      return options.callback.call(this, true);
+    };
+
+    // confirm specific validation
+    if (!$.isFunction(options.callback)) {
+      throw new Error("confirm requires a callback");
+    }
+
+    return exports.dialog(options);
+  };
+
+  exports.prompt = function() {
+    var options;
+    var defaults;
+    var dialog;
+    var form;
+    var input;
+    var shouldShow;
+    var inputOptions;
+
+    // we have to create our form first otherwise
+    // its value is undefined when gearing up our options
+    // @TODO this could be solved by allowing message to
+    // be a function instead...
+    form = $(templates.form);
+
+    // prompt defaults are more complex than others in that
+    // users can override more defaults
+    // @TODO I don't like that prompt has to do a lot of heavy
+    // lifting which mergeDialogOptions can *almost* support already
+    // just because of 'value' and 'inputType' - can we refactor?
+    defaults = {
+      className: "bootbox-prompt",
+      buttons: createLabels("cancel", "confirm"),
+      value: "",
+      inputType: "text"
+    };
+
+    options = validateButtons(
+      mergeArguments(defaults, arguments, ["title", "callback"]),
+      ["cancel", "confirm"]
+    );
+
+    // capture the user's show value; we always set this to false before
+    // spawning the dialog to give us a chance to attach some handlers to
+    // it, but we need to make sure we respect a preference not to show it
+    shouldShow = (options.show === undefined) ? true : options.show;
+
+    /**
+     * overrides; undo anything the user tried to set they shouldn't have
+     */
+    options.message = form;
+
+    options.buttons.cancel.callback = options.onEscape = function() {
+      return options.callback.call(this, null);
+    };
+
+    options.buttons.confirm.callback = function() {
+      var value;
+
+      switch (options.inputType) {
+        case "text":
+        case "textarea":
+        case "email":
+        case "select":
+        case "date":
+        case "time":
+        case "number":
+        case "password":
+          value = input.val();
+          break;
+
+        case "checkbox":
+          var checkedItems = input.find("input:checked");
+
+          // we assume that checkboxes are always multiple,
+          // hence we default to an empty array
+          value = [];
+
+          each(checkedItems, function(_, item) {
+            value.push($(item).val());
+          });
+          break;
+      }
+
+      return options.callback.call(this, value);
+    };
+
+    options.show = false;
+
+    // prompt specific validation
+    if (!options.title) {
+      throw new Error("prompt requires a title");
+    }
+
+    if (!$.isFunction(options.callback)) {
+      throw new Error("prompt requires a callback");
+    }
+
+    if (!templates.inputs[options.inputType]) {
+      throw new Error("invalid prompt type");
+    }
+
+    // create the input based on the supplied type
+    input = $(templates.inputs[options.inputType]);
+
+    switch (options.inputType) {
+      case "text":
+      case "textarea":
+      case "email":
+      case "date":
+      case "time":
+      case "number":
+      case "password":
+        input.val(options.value);
+        break;
+
+      case "select":
+        var groups = {};
+        inputOptions = options.inputOptions || [];
+
+        if (!$.isArray(inputOptions)) {
+          throw new Error("Please pass an array of input options");
+        }
+
+        if (!inputOptions.length) {
+          throw new Error("prompt with select requires options");
+        }
+
+        each(inputOptions, function(_, option) {
+
+          // assume the element to attach to is the input...
+          var elem = input;
+
+          if (option.value === undefined || option.text === undefined) {
+            throw new Error("given options in wrong format");
+          }
+
+          // ... but override that element if this option sits in a group
+
+          if (option.group) {
+            // initialise group if necessary
+            if (!groups[option.group]) {
+              groups[option.group] = $("<optgroup/>").attr("label", option.group);
+            }
+
+            elem = groups[option.group];
+          }
+
+          elem.append("<option value='" + option.value + "'>" + option.text + "</option>");
+        });
+
+        each(groups, function(_, group) {
+          input.append(group);
+        });
+
+        // safe to set a select's value as per a normal input
+        input.val(options.value);
+        break;
+
+      case "checkbox":
+        var values   = $.isArray(options.value) ? options.value : [options.value];
+        inputOptions = options.inputOptions || [];
+
+        if (!inputOptions.length) {
+          throw new Error("prompt with checkbox requires options");
+        }
+
+        if (!inputOptions[0].value || !inputOptions[0].text) {
+          throw new Error("given options in wrong format");
+        }
+
+        // checkboxes have to nest within a containing element, so
+        // they break the rules a bit and we end up re-assigning
+        // our 'input' element to this container instead
+        input = $("<div/>");
+
+        each(inputOptions, function(_, option) {
+          var checkbox = $(templates.inputs[options.inputType]);
+
+          checkbox.find("input").attr("value", option.value);
+          checkbox.find("label").append(option.text);
+
+          // we've ensured values is an array so we can always iterate over it
+          each(values, function(_, value) {
+            if (value === option.value) {
+              checkbox.find("input").prop("checked", true);
+            }
+          });
+
+          input.append(checkbox);
+        });
+        break;
+    }
+
+    // @TODO provide an attributes option instead
+    // and simply map that as keys: vals
+    if (options.placeholder) {
+      input.attr("placeholder", options.placeholder);
+    }
+
+    if (options.pattern) {
+      input.attr("pattern", options.pattern);
+    }
+
+    if (options.maxlength) {
+      input.attr("maxlength", options.maxlength);
+    }
+
+    // now place it in our form
+    form.append(input);
+
+    form.on("submit", function(e) {
+      e.preventDefault();
+      // Fix for SammyJS (or similar JS routing library) hijacking the form post.
+      e.stopPropagation();
+      // @TODO can we actually click *the* button object instead?
+      // e.g. buttons.confirm.click() or similar
+      dialog.find(".btn-primary").click();
+    });
+
+    dialog = exports.dialog(options);
+
+    // clear the existing handler focusing the submit button...
+    dialog.off("shown.bs.modal");
+
+    // ...and replace it with one focusing our input, if possible
+    dialog.on("shown.bs.modal", function() {
+      // need the closure here since input isn't
+      // an object otherwise
+      input.focus();
+    });
+
+    if (shouldShow === true) {
+      dialog.modal("show");
+    }
+
+    return dialog;
+  };
+
+  exports.dialog = function(options) {
+    options = sanitize(options);
+
+    var dialog = $(templates.dialog);
+    var innerDialog = dialog.find(".modal-dialog");
+    var body = dialog.find(".modal-body");
+    var buttons = options.buttons;
+    var buttonStr = "";
+    var callbacks = {
+      onEscape: options.onEscape
+    };
+
+    if ($.fn.modal === undefined) {
+      throw new Error(
+        "$.fn.modal is not defined; please double check you have included " +
+        "the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ " +
+        "for more details."
+      );
+    }
+
+    each(buttons, function(key, button) {
+
+      // @TODO I don't like this string appending to itself; bit dirty. Needs reworking
+      // can we just build up button elements instead? slower but neater. Then button
+      // can just become a template too
+      buttonStr += "<button data-bb-handler='" + key + "' type='button' class='btn " + button.className + "'>" + button.label + "</button>";
+      callbacks[key] = button.callback;
+    });
+
+    body.find(".bootbox-body").html(options.message);
+
+    if (options.animate === true) {
+      dialog.addClass("fade");
+    }
+
+    if (options.className) {
+      dialog.addClass(options.className);
+    }
+
+    if (options.size === "large") {
+      innerDialog.addClass("modal-lg");
+    } else if (options.size === "small") {
+      innerDialog.addClass("modal-sm");
+    }
+
+    if (options.title) {
+      body.before(templates.header);
+    }
+
+    if (options.closeButton) {
+      var closeButton = $(templates.closeButton);
+
+      if (options.title) {
+        dialog.find(".modal-header").prepend(closeButton);
+      } else {
+        closeButton.css("margin-top", "-10px").prependTo(body);
+      }
+    }
+
+    if (options.title) {
+      dialog.find(".modal-title").html(options.title);
+    }
+
+    if (buttonStr.length) {
+      body.after(templates.footer);
+      dialog.find(".modal-footer").html(buttonStr);
+    }
+
+
+    /**
+     * Bootstrap event listeners; used handle extra
+     * setup & teardown required after the underlying
+     * modal has performed certain actions
+     */
+
+    dialog.on("hidden.bs.modal", function(e) {
+      // ensure we don't accidentally intercept hidden events triggered
+      // by children of the current dialog. We shouldn't anymore now BS
+      // namespaces its events; but still worth doing
+      if (e.target === this) {
+        dialog.remove();
+      }
+    });
+
+    /*
+    dialog.on("show.bs.modal", function() {
+      // sadly this doesn't work; show is called *just* before
+      // the backdrop is added so we'd need a setTimeout hack or
+      // otherwise... leaving in as would be nice
+      if (options.backdrop) {
+        dialog.next(".modal-backdrop").addClass("bootbox-backdrop");
+      }
+    });
+    */
+
+    dialog.on("shown.bs.modal", function() {
+      dialog.find(".btn-primary:first").focus();
+    });
+
+    /**
+     * Bootbox event listeners; experimental and may not last
+     * just an attempt to decouple some behaviours from their
+     * respective triggers
+     */
+
+    if (options.backdrop !== "static") {
+      // A boolean true/false according to the Bootstrap docs
+      // should show a dialog the user can dismiss by clicking on
+      // the background.
+      // We always only ever pass static/false to the actual
+      // $.modal function because with `true` we can't trap
+      // this event (the .modal-backdrop swallows it)
+      // However, we still want to sort of respect true
+      // and invoke the escape mechanism instead
+      dialog.on("click.dismiss.bs.modal", function(e) {
+        // @NOTE: the target varies in >= 3.3.x releases since the modal backdrop
+        // moved *inside* the outer dialog rather than *alongside* it
+        if (dialog.children(".modal-backdrop").length) {
+          e.currentTarget = dialog.children(".modal-backdrop").get(0);
+        }
+
+        if (e.target !== e.currentTarget) {
+          return;
+        }
+
+        dialog.trigger("escape.close.bb");
+      });
+    }
+
+    dialog.on("escape.close.bb", function(e) {
+      if (callbacks.onEscape) {
+        processCallback(e, dialog, callbacks.onEscape);
+      }
+    });
+
+    /**
+     * Standard jQuery event listeners; used to handle user
+     * interaction with our dialog
+     */
+
+    dialog.on("click", ".modal-footer button", function(e) {
+      var callbackKey = $(this).data("bb-handler");
+
+      processCallback(e, dialog, callbacks[callbackKey]);
+    });
+
+    dialog.on("click", ".bootbox-close-button", function(e) {
+      // onEscape might be falsy but that's fine; the fact is
+      // if the user has managed to click the close button we
+      // have to close the dialog, callback or not
+      processCallback(e, dialog, callbacks.onEscape);
+    });
+
+    dialog.on("keyup", function(e) {
+      if (e.which === 27) {
+        dialog.trigger("escape.close.bb");
+      }
+    });
+
+    // the remainder of this method simply deals with adding our
+    // dialogent to the DOM, augmenting it with Bootstrap's modal
+    // functionality and then giving the resulting object back
+    // to our caller
+
+    $(options.container).append(dialog);
+
+    dialog.modal({
+      backdrop: options.backdrop ? "static": false,
+      keyboard: false,
+      show: false
+    });
+
+    if (options.show) {
+      dialog.modal("show");
+    }
+
+    // @TODO should we return the raw element here or should
+    // we wrap it in an object on which we can expose some neater
+    // methods, e.g. var d = bootbox.alert(); d.hide(); instead
+    // of d.modal("hide");
+
+   /*
+    function BBDialog(elem) {
+      this.elem = elem;
+    }
+
+    BBDialog.prototype = {
+      hide: function() {
+        return this.elem.modal("hide");
+      },
+      show: function() {
+        return this.elem.modal("show");
+      }
+    };
+    */
+
+    return dialog;
+
+  };
+
+  exports.setDefaults = function() {
+    var values = {};
+
+    if (arguments.length === 2) {
+      // allow passing of single key/value...
+      values[arguments[0]] = arguments[1];
+    } else {
+      // ... and as an object too
+      values = arguments[0];
+    }
+
+    $.extend(defaults, values);
+  };
+
+  exports.hideAll = function() {
+    $(".bootbox").modal("hide");
+
+    return exports;
+  };
+
+
+  /**
+   * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
+   * unlikely to be required. If this gets too large it can be split out into separate JS files.
+   */
+  var locales = {
+    bg_BG : {
+      OK      : "Ок",
+      CANCEL  : "Отказ",
+      CONFIRM : "Потвърждавам"
+    },
+    br : {
+      OK      : "OK",
+      CANCEL  : "Cancelar",
+      CONFIRM : "Sim"
+    },
+    cs : {
+      OK      : "OK",
+      CANCEL  : "Zrušit",
+      CONFIRM : "Potvrdit"
+    },
+    da : {
+      OK      : "OK",
+      CANCEL  : "Annuller",
+      CONFIRM : "Accepter"
+    },
+    de : {
+      OK      : "OK",
+      CANCEL  : "Abbrechen",
+      CONFIRM : "Akzeptieren"
+    },
+    el : {
+      OK      : "Εντάξει",
+      CANCEL  : "Ακύρωση",
+      CONFIRM : "Επιβεβαίωση"
+    },
+    en : {
+      OK      : "OK",
+      CANCEL  : "Cancel",
+      CONFIRM : "OK"
+    },
+    es : {
+      OK      : "OK",
+      CANCEL  : "Cancelar",
+      CONFIRM : "Aceptar"
+    },
+    et : {
+      OK      : "OK",
+      CANCEL  : "Katkesta",
+      CONFIRM : "OK"
+    },
+    fa : {
+      OK      : "قبول",
+      CANCEL  : "لغو",
+      CONFIRM : "تایید"
+    },
+    fi : {
+      OK      : "OK",
+      CANCEL  : "Peruuta",
+      CONFIRM : "OK"
+    },
+    fr : {
+      OK      : "OK",
+      CANCEL  : "Annuler",
+      CONFIRM : "D'accord"
+    },
+    he : {
+      OK      : "אישור",
+      CANCEL  : "ביטול",
+      CONFIRM : "אישור"
+    },
+    hu : {
+      OK      : "OK",
+      CANCEL  : "Mégsem",
+      CONFIRM : "Megerősít"
+    },
+    hr : {
+      OK      : "OK",
+      CANCEL  : "Odustani",
+      CONFIRM : "Potvrdi"
+    },
+    id : {
+      OK      : "OK",
+      CANCEL  : "Batal",
+      CONFIRM : "OK"
+    },
+    it : {
+      OK      : "OK",
+      CANCEL  : "Annulla",
+      CONFIRM : "Conferma"
+    },
+    ja : {
+      OK      : "OK",
+      CANCEL  : "キャンセル",
+      CONFIRM : "確認"
+    },
+    lt : {
+      OK      : "Gerai",
+      CANCEL  : "Atšaukti",
+      CONFIRM : "Patvirtinti"
+    },
+    lv : {
+      OK      : "Labi",
+      CANCEL  : "Atcelt",
+      CONFIRM : "Apstiprināt"
+    },
+    nl : {
+      OK      : "OK",
+      CANCEL  : "Annuleren",
+      CONFIRM : "Accepteren"
+    },
+    no : {
+      OK      : "OK",
+      CANCEL  : "Avbryt",
+      CONFIRM : "OK"
+    },
+    pl : {
+      OK      : "OK",
+      CANCEL  : "Anuluj",
+      CONFIRM : "Potwierdź"
+    },
+    pt : {
+      OK      : "OK",
+      CANCEL  : "Cancelar",
+      CONFIRM : "Confirmar"
+    },
+    ru : {
+      OK      : "OK",
+      CANCEL  : "Отмена",
+      CONFIRM : "Применить"
+    },
+    sq : {
+      OK : "OK",
+      CANCEL : "Anulo",
+      CONFIRM : "Prano"
+    },
+    sv : {
+      OK      : "OK",
+      CANCEL  : "Avbryt",
+      CONFIRM : "OK"
+    },
+    th : {
+      OK      : "ตกลง",
+      CANCEL  : "ยกเลิก",
+      CONFIRM : "ยืนยัน"
+    },
+    tr : {
+      OK      : "Tamam",
+      CANCEL  : "İptal",
+      CONFIRM : "Onayla"
+    },
+    zh_CN : {
+      OK      : "OK",
+      CANCEL  : "取消",
+      CONFIRM : "确认"
+    },
+    zh_TW : {
+      OK      : "OK",
+      CANCEL  : "取消",
+      CONFIRM : "確認"
+    }
+  };
+
+  exports.addLocale = function(name, values) {
+    $.each(["OK", "CANCEL", "CONFIRM"], function(_, v) {
+      if (!values[v]) {
+        throw new Error("Please supply a translation for '" + v + "'");
+      }
+    });
+
+    locales[name] = {
+      OK: values.OK,
+      CANCEL: values.CANCEL,
+      CONFIRM: values.CONFIRM
+    };
+
+    return exports;
+  };
+
+  exports.removeLocale = function(name) {
+    delete locales[name];
+
+    return exports;
+  };
+
+  exports.setLocale = function(name) {
+    return exports.setDefaults("locale", name);
+  };
+
+  exports.init = function(_$) {
+    return init(_$ || $);
+  };
+
+  return exports;
+}));
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..87e1ef5
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,17 @@
+{
+  "name": "bootbox.js",
+  "version": "4.4.0",
+  "main": "./bootbox.js",
+  "ignore": [
+    "CHANGELOG.md",
+    "CONTRIBUTING",
+    "Gruntfile.js",
+    "header.txt",
+    "karma*.js",
+    "build",
+    "tests"
+  ],
+  "dependencies": {
+    "bootstrap": ">= 3.0.0"
+  }
+}
diff --git a/build/calculate-size b/build/calculate-size
new file mode 100755
index 0000000..fbc0555
--- /dev/null
+++ b/build/calculate-size
@@ -0,0 +1,14 @@
+#!/usr/bin/env zsh
+
+SRC="bootbox.js"
+MIN="/tmp/bootbox.min.js"
+
+uglifyjs $SRC -c -m > $MIN
+
+RAW="$(wc -c $SRC | awk '{print $1}')"
+MRAW="$(wc -c $MIN | awk '{print $1}')"
+GZIP="$(cat $SRC | gzip | wc -c | awk '{print $1}')"
+MGZIP="$(cat $MIN | gzip | wc -c | awk '{print $1}')"
+
+echo "raw,min,raw+gzip,min+gzip\n$RAW,$MRAW,$GZIP,$MGZIP" > build/size.csv
+rm $MIN
diff --git a/header.txt b/header.txt
new file mode 100644
index 0000000..2778b0d
--- /dev/null
+++ b/header.txt
@@ -0,0 +1,5 @@
+/**
+ * bootbox.js v4.4.0
+ *
+ * http://bootboxjs.com/license.txt
+ */
diff --git a/karma-base.conf.js b/karma-base.conf.js
new file mode 100644
index 0000000..6dc52a3
--- /dev/null
+++ b/karma-base.conf.js
@@ -0,0 +1,51 @@
+module.exports = function(params) {
+
+  "use strict";
+
+  console.log("Vendor files: " + params.vendor.join(", "));
+
+  return function(config) {
+
+    return config.set({
+      basePath: "",
+      frameworks: ["mocha", "chai"],
+      files: Array.prototype.concat([
+        "node_modules/sinon/lib/sinon.js",
+        "node_modules/sinon/lib/sinon/spy.js",
+        "node_modules/sinon/lib/sinon/stub.js",
+        "node_modules/sinon-chai/lib/sinon-chai.js"],
+
+        params.vendor,
+
+        params.src || "bootbox.js",
+
+        ["tests/**/*.test.coffee",
+        "tests/**/*.test.js"]
+      ),
+      exclude: [],
+      preprocessors: {
+        "**/*.coffee": ["coffee"],
+        "bootbox.js": ["coverage"]
+      },
+      reporters: ["dots", "coverage", "junit"],
+      port: 9876,
+      colors: true,
+      logLevel: config.LOG_INFO,
+      autoWatch: true,
+      browsers: ["PhantomJS"],
+      captureTimeout: 60000,
+      singleRun: true,
+
+      coverageReporter: {
+        type: "html",
+        dir: "tests/coverage"
+      },
+
+      junitReporter: {
+        outputFile: "tests/reports/results.xml"
+      }
+    });
+
+  };
+
+};
diff --git a/karma-jquery-latest.conf.js b/karma-jquery-latest.conf.js
new file mode 100644
index 0000000..094d3e5
--- /dev/null
+++ b/karma-jquery-latest.conf.js
@@ -0,0 +1,8 @@
+var baseConfig = require("./karma-base.conf");
+
+module.exports = baseConfig({
+  vendor: [
+    "tests/vendor/jquery-1.10.2.min.js",
+    "tests/vendor/bootstrap-3.0.0.min.js"
+  ]
+});
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 0000000..5d1f9f3
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,8 @@
+var baseConfig = require("./karma-base.conf");
+
+module.exports = baseConfig({
+  vendor: [
+    "tests/vendor/jquery-1.11.2.min.js",
+    "tests/vendor/bootstrap-3.3.2.min.js"
+  ]
+});
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3b71016
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+  "name": "bootbox",
+  "version": "4.4.0",
+  "description": "Wrappers for JavaScript alert(), confirm() and other flexible dialogs using the Bootstrap framework",
+  "directories": {
+    "test": "tests"
+  },
+  "scripts": {
+    "test": "./node_modules/.bin/karma start"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/makeusabrew/bootbox.git"
+  },
+  "author": "Nick Payne <nick at kurai.co.uk>",
+  "license": "MIT",
+  "readmeFilename": "README.md",
+  "main": "bootbox.js",
+  "devDependencies": {
+    "grunt-contrib-uglify": "~0.2.2",
+    "grunt": "~0.4.1",
+    "karma": "~0.10.1",
+    "grunt-contrib-jshint": "~0.6.3",
+    "karma-mocha": "0.1.3",
+    "karma-phantomjs-launcher": "~0.1.0",
+    "mocha": "~1.12.0",
+    "karma-coffee-preprocessor": "~0.1.0",
+    "karma-coverage": "~0.1.0",
+    "karma-junit-reporter": "~0.1.0",
+    "sinon": "~1.7.3",
+    "sinon-chai": "~2.4.0",
+    "karma-chai": "0.0.1",
+    "grunt-karma": "~0.6.2"
+  }
+}
diff --git a/tests/alert.test.js b/tests/alert.test.js
new file mode 100644
index 0000000..bc441a5
--- /dev/null
+++ b/tests/alert.test.js
@@ -0,0 +1,355 @@
+describe("bootbox.alert", function() {
+
+  "use strict";
+
+  var self;
+
+  beforeEach(function() {
+
+    self = this;
+
+    this.text = function(selector) {
+      return this.find(selector).text();
+    };
+
+    this.find = function(selector) {
+      return this.dialog.find(selector);
+    };
+  });
+
+  describe("basic usage tests", function() {
+
+    describe("with no arguments", function() {
+      beforeEach(function() {
+        this.create = function() {
+          bootbox.alert();
+        };
+      });
+
+      it("throws an error regarding argument length", function() {
+        expect(this.create).to.throw(/argument length/);
+      });
+    });
+
+    describe("with one argument", function() {
+
+      describe("where the argument is a string", function() {
+        beforeEach(function() {
+          this.dialog = bootbox.alert("Hello world!");
+        });
+
+        it("applies the bootbox-alert class to the dialog", function() {
+          expect(this.dialog.hasClass("bootbox-alert")).to.be.true;
+        });
+
+        it("shows the expected body copy", function() {
+          expect(this.text(".bootbox-body")).to.equal("Hello world!");
+        });
+
+        it("shows an OK button", function() {
+          expect(this.text(".modal-footer button:first")).to.equal("OK");
+        });
+
+        it("applies the primary class to the button", function() {
+          expect(this.find(".modal-footer button:first").hasClass("btn-primary")).to.be.true;
+        });
+
+        it("shows a close button inside the body", function() {
+          expect(this.text(".modal-body button")).to.equal("×");
+        });
+
+        it("applies the close class to the close button", function() {
+          expect(this.find(".modal-body button").hasClass("close")).to.be.true;
+        });
+
+        it("applies the correct data-dismiss attribute to the close button", function() {
+          expect(this.find("button.close").attr("data-dismiss")).to.equal("modal");
+        });
+
+        it("applies the correct aria-hidden attribute to the close button", function() {
+          expect(this.find("button.close").attr("aria-hidden")).to.equal("true");
+        });
+
+        it("applies the correct class to the body", function() {
+          expect($("body").hasClass("modal-open")).to.be.true;
+        });
+      });
+    });
+
+    describe("with two arguments", function() {
+      describe("where the second argument is not a function", function() {
+        beforeEach(function() {
+          this.create = function() {
+            bootbox.alert("Hello world!", "not a callback");
+          };
+        });
+
+        it("throws an error requiring a callback", function() {
+          expect(this.create).to.throw(/alert requires callback property to be a function when provided/);
+        });
+      });
+
+      describe("where the second argument is a function", function() {
+        beforeEach(function() {
+          this.create = function() {
+            self.dialog = bootbox.alert("Hello world!", function() {});
+          };
+        });
+
+        it("does not throw an error", function() {
+          expect(this.create).not.to.throw(Error);
+        });
+      });
+    });
+
+    describe("with three arguments", function() {
+      beforeEach(function() {
+        this.create = function() {
+          bootbox.alert(1, 2, 3);
+        };
+      });
+
+      it("throws an error regarding argument length", function() {
+        expect(this.create).to.throw(/argument length/);
+      });
+    });
+
+  });
+
+  describe("configuration options tests", function() {
+    beforeEach(function() {
+      this.options = {
+        message: "Hello world",
+        callback: function() {}
+      };
+
+      this.create = function() {
+        self.dialog = bootbox.alert(self.options);
+      };
+    });
+
+    describe("with a custom ok button", function() {
+      beforeEach(function() {
+        this.options.buttons = {
+          ok: {
+            label: "Custom OK",
+            className: "btn-danger"
+          }
+        };
+
+        this.create();
+
+        this.button = this.dialog.find(".btn:first");
+      });
+
+      it("adds the correct ok button", function() {
+        expect(this.button.text()).to.equal("Custom OK");
+        expect(this.button.hasClass("btn-danger")).to.be.true;
+      });
+    });
+
+    describe("with an unrecognised button key", function() {
+      beforeEach(function() {
+        this.options.buttons = {
+          "Another key": {
+            label: "Custom OK",
+            className: "btn-danger"
+          }
+        };
+      });
+
+      it("throws an error", function() {
+        expect(this.create).to.throw("button key Another key is not allowed (options are ok)");
+      });
+    });
+
+    describe("with a custom title", function() {
+      beforeEach(function() {
+        this.options.title = "Hello?";
+        this.create();
+      });
+
+      it("shows the correct title", function() {
+        expect(this.text("h4")).to.equal("Hello?");
+      });
+    });
+  });
+
+  describe("callback tests", function() {
+
+    describe("with no callback", function() {
+      beforeEach(function() {
+        this.dialog = bootbox.alert({
+          message:"Hello!"
+        });
+
+        this.hidden = sinon.spy(this.dialog, "modal");
+      });
+
+      describe("when dismissing the dialog by clicking OK", function() {
+        beforeEach(function() {
+          this.dialog.find(".btn-primary").trigger("click");
+        });
+
+        it("should hide the modal", function() {
+          expect(this.hidden).to.have.been.calledWithExactly("hide");
+        });
+      });
+
+      describe("when clicking the close button", function() {
+        beforeEach(function() {
+          this.dialog.find(".close").trigger("click");
+        });
+
+        it("should hide the modal", function() {
+          expect(this.hidden).to.have.been.calledWithExactly("hide");
+        });
+      });
+
+      describe("when triggering the escape event", function() {
+        beforeEach(function() {
+          this.dialog.trigger("escape.close.bb");
+        });
+
+        it("should hide the modal", function() {
+          expect(this.hidden).to.have.been.calledWithExactly("hide");
+        });
+      });
+    });
+
+    describe("with a simple callback", function() {
+      beforeEach(function() {
+        this.callback = sinon.spy();
+
+        this.dialog = bootbox.alert({
+          message:"Hello!",
+          callback: this.callback
+        });
+
+        this.hidden = sinon.spy(this.dialog, "modal");
+      });
+
+      describe("when dismissing the dialog by clicking OK", function() {
+        beforeEach(function() {
+          this.dialog.find(".btn-primary").trigger("click");
+        });
+
+        it("should invoke the callback", function() {
+          expect(this.callback).to.have.been.called;
+        });
+
+        it("should pass the dialog as `this`", function() {
+          expect(this.callback.thisValues[0]).to.equal(this.dialog);
+        });
+
+        it("should hide the modal", function() {
+          expect(this.hidden).to.have.been.calledWithExactly("hide");
+        });
+      });
+
+      describe("when clicking the close button", function() {
+        beforeEach(function() {
+          this.dialog.find(".close").trigger("click");
+        });
+
+        it("should invoke the callback", function() {
+          expect(this.callback).to.have.been.called;
+        });
+
+        it("should pass the dialog as `this`", function() {
+          expect(this.callback.thisValues[0]).to.equal(this.dialog);
+        });
+
+        it("should hide the modal", function() {
+          expect(this.hidden).to.have.been.calledWithExactly("hide");
+        });
+      });
+
+      describe("when triggering the escape event", function() {
+        beforeEach(function() {
+          this.dialog.trigger("escape.close.bb");
+        });
+
+        it("should invoke the callback", function() {
+          expect(this.callback).to.have.been.called;
+        });
+
+        it("should pass the dialog as `this`", function() {
+          expect(this.callback.thisValues[0]).to.equal(this.dialog);
+        });
+
+        it("should hide the modal", function() {
+          expect(this.hidden).to.have.been.calledWithExactly("hide");
+        });
+      });
+    });
+
+    describe("with a callback which returns false", function() {
+      beforeEach(function() {
+        this.callback = sinon.stub();
+        this.callback.returns(false);
+
+        this.dialog = bootbox.alert({
+          message:"Hello!",
+          callback: this.callback
+        });
+
+        this.hidden = sinon.spy(this.dialog, "modal");
+      });
+
+      describe("when dismissing the dialog by clicking OK", function() {
+        beforeEach(function() {
+          this.dialog.find(".btn-primary").trigger("click");
+        });
+
+        it("should invoke the callback", function() {
+          expect(this.callback).to.have.been.called;
+        });
+
+        it("should pass the dialog as `this`", function() {
+          expect(this.callback.thisValues[0]).to.equal(this.dialog);
+        });
+
+        it("should not hide the modal", function() {
+          expect(this.hidden).not.to.have.been.called;
+        });
+      });
+
+      describe("when clicking the close button", function() {
+        beforeEach(function() {
+          this.dialog.find(".close").trigger("click");
+        });
+
+        it("should invoke the callback", function() {
+          expect(this.callback).to.have.been.called;
+        });
+
+        it("should pass the dialog as `this`", function() {
+          expect(this.callback.thisValues[0]).to.equal(this.dialog);
+        });
+
+        it("should not hide the modal", function() {
+          expect(this.hidden).not.to.have.been.called;
+        });
+      });
+
+      describe("when triggering the escape event", function() {
+        beforeEach(function() {
+          this.dialog.trigger("escape.close.bb");
+        });
+
+        it("should invoke the callback", function() {
+          expect(this.callback).to.have.been.called;
+        });
+
+        it("should pass the dialog as `this`", function() {
+          expect(this.callback.thisValues[0]).to.equal(this.dialog);
+        });
+
+        it("should not hide the modal", function() {
+          expect(this.hidden).not.to.have.been.called;
+        });
+      });
+    });
+  });
+});
diff --git a/tests/bootbox.test.js b/tests/bootbox.test.js
new file mode 100644
index 0000000..2700224
--- /dev/null
+++ b/tests/bootbox.test.js
@@ -0,0 +1,269 @@
+describe("Bootbox", function() {
+
+  "use strict";
+
+  it("is attached to the window object", function() {
+    expect(window.bootbox).to.be.an("object");
+  });
+
+  it("exposes the correct public API", function() {
+    expect(bootbox.alert).to.be.a("function");
+    expect(bootbox.confirm).to.be.a("function");
+    expect(bootbox.dialog).to.be.a("function");
+    expect(bootbox.setDefaults).to.be.a("function");
+    expect(bootbox.hideAll).to.be.a("function");
+  });
+
+  describe("hideAll", function() {
+    beforeEach(function() {
+      this.hidden = sinon.spy($.fn, "modal");
+      bootbox.hideAll();
+    });
+
+    it("should hide all .bootbox modals", function() {
+      expect(this.hidden).to.have.been.calledWithExactly("hide");
+    });
+  });
+
+  describe("event listeners", function() {
+    describe("hidden.bs.modal", function() {
+      beforeEach(function() {
+        this.dialog = bootbox.alert("hi");
+
+        this.removed = sinon.stub(this.dialog, "remove");
+
+        this.e = function(target) {
+
+          $(this.dialog).trigger($.Event("hidden.bs.modal", {
+            target: target
+          }));
+        };
+      });
+
+      afterEach(function() {
+        this.removed.restore();
+      });
+
+      describe("when triggered with the wrong target", function() {
+        beforeEach(function() {
+          this.e({an: "object"});
+        });
+
+        it("does not remove the dialog", function() {
+          expect(this.removed).not.to.have.been.called;
+        });
+      });
+
+      describe("when triggered with the correct target", function() {
+        beforeEach(function() {
+          this.e(this.dialog.get(0));
+        });
+
+        it("removes the dialog", function() {
+          expect(this.removed).to.have.been.called;
+        });
+      });
+    });
+  });
+
+  describe("If $.fn.modal is undefined", function() {
+    beforeEach(function() {
+      this.oldModal = window.jQuery.fn.modal;
+      window.jQuery.fn.modal = undefined;
+    });
+
+    afterEach(function() {
+      window.jQuery.fn.modal = this.oldModal;
+    });
+
+    describe("When invoking a dialog", function() {
+      beforeEach(function() {
+        try {
+          bootbox.alert("Hi", function() {});
+        } catch (e) {
+          this.e = e;
+        }
+      });
+
+      it("throws the correct error", function() {
+        expect(this.e.message).to.contain("$.fn.modal is not defined");
+      });
+    });
+  });
+
+  describe("adding and removing locales", function() {
+
+    describe("bootbox.addLocale", function() {
+      describe("with invalid values", function() {
+        beforeEach(function() {
+          try {
+            bootbox.addLocale("xy", {
+              OK: "BTN1"
+            });
+          } catch (e) {
+            this.e = e;
+          }
+        });
+
+        it("throws the expected error", function() {
+          expect(this.e.message).to.equal("Please supply a translation for 'CANCEL'");
+        });
+      });
+
+      describe("with invalid values", function() {
+        beforeEach(function() {
+          bootbox
+          .addLocale("xy", {
+            OK: "BTN1",
+            CANCEL: "BTN2",
+            CONFIRM: "BTN3"
+          })
+          .setLocale("xy");
+
+          var d1 = bootbox.alert("foo");
+          var d2 = bootbox.confirm("foo", function() { return true; });
+          this.labels = {
+            ok: d1.find(".btn:first").text(),
+            cancel: d2.find(".btn:first").text(),
+            confirm: d2.find(".btn:last").text()
+          };
+        });
+
+        it("shows the expected OK translation", function() {
+          expect(this.labels.ok).to.equal("BTN1");
+        });
+        it("shows the expected CANCEL translation", function() {
+          expect(this.labels.cancel).to.equal("BTN2");
+        });
+        it("shows the expected PROMPT translation", function() {
+          expect(this.labels.confirm).to.equal("BTN3");
+        });
+      });
+    });
+
+    describe("bootbox.removeLocale", function () {
+      beforeEach(function () {
+        bootbox.removeLocale("xy");
+
+        var d1 = bootbox.alert("foo");
+        var d2 = bootbox.confirm("foo", function () { return true; });
+        this.labels = {
+          ok: d1.find(".btn:first").text(),
+          cancel: d2.find(".btn:first").text(),
+          confirm: d2.find(".btn:last").text()
+        };
+      });
+
+      it("falls back to the default OK translation", function () {
+        expect(this.labels.ok).to.equal("OK");
+      });
+      it("falls back to the default CANCEL translation", function () {
+        expect(this.labels.cancel).to.equal("Cancel");
+      });
+      it("falls back to the default PROMPT translation", function () {
+        expect(this.labels.confirm).to.equal("OK");
+      });
+    });
+  });
+
+  describe("backdrop variations", function() {
+    beforeEach(function() {
+      this.e = function(target) {
+        $(this.dialog).trigger($.Event("click.dismiss.bs.modal", {
+          target: target
+        }));
+      };
+    });
+
+    describe("with the default value", function() {
+      beforeEach(function() {
+        this.callback = sinon.spy();
+        this.dialog = bootbox.alert("hi", this.callback);
+      });
+
+      describe("When triggering the backdrop click dismiss event", function() {
+        beforeEach(function() {
+          this.e({an: "object"});
+        });
+
+        it("does not invoke the callback", function() {
+          expect(this.callback).not.to.have.been.called;
+        });
+      });
+    });
+
+    describe("when set to false", function() {
+      beforeEach(function() {
+        this.callback = sinon.spy();
+        this.dialog = bootbox.alert({
+          message: "hi",
+          callback: this.callback,
+          backdrop: false
+        });
+      });
+
+      describe("When triggering the backdrop click dismiss event", function() {
+        describe("With the wrong target", function() {
+          beforeEach(function() {
+            this.e({an: "object"});
+          });
+
+          it("does not invoke the callback", function() {
+            expect(this.callback).not.to.have.been.called;
+          });
+        });
+
+        describe("With the correct target", function() {
+          beforeEach(function() {
+            this.e(this.dialog.get(0));
+          });
+
+          it("invokes the callback", function() {
+            expect(this.callback).to.have.been.called;
+          });
+
+          it("should pass the dialog as `this`", function() {
+            expect(this.callback.thisValues[0]).to.equal(this.dialog);
+          });
+        });
+      });
+    });
+
+    describe("when set to true", function() {
+      beforeEach(function() {
+        this.callback = sinon.spy();
+        this.dialog = bootbox.alert({
+          message: "hi",
+          callback: this.callback,
+          backdrop: true
+        });
+      });
+
+      describe("When triggering the backdrop click dismiss event", function() {
+        describe("With the wrong target", function() {
+          beforeEach(function() {
+            this.e({an: "object"});
+          });
+
+          it("does not invoke the callback", function() {
+            expect(this.callback).not.to.have.been.called;
+          });
+        });
+
+        describe("With the correct target", function() {
+          beforeEach(function() {
+            this.e(this.dialog.children(".modal-backdrop").get(0));
+          });
+
+          it("invokes the callback", function() {
+            expect(this.callback).to.have.been.called;
+          });
+
+          it("should pass the dialog as `this`", function() {
+            expect(this.callback.thisValues[0]).to.equal(this.dialog);
+          });
+        });
+      });
+    });
+  });
+});
diff --git a/tests/confirm.test.coffee b/tests/confirm.test.coffee
new file mode 100644
index 0000000..fd7d06a
--- /dev/null
+++ b/tests/confirm.test.coffee
@@ -0,0 +1,260 @@
+describe "bootbox.confirm", ->
+
+  describe "basic usage tests", ->
+
+    describe "with one argument", ->
+
+      describe "where the argument is not an object", ->
+        beforeEach ->
+          @create = -> bootbox.confirm "Are you sure?"
+
+        it "throws an error", ->
+          expect(@create).to.throw /confirm requires a callback/
+
+      describe "where the argument is an object", ->
+        beforeEach ->
+          @options = {}
+          @create = => @dialog = bootbox.confirm @options
+
+        describe "with a message property", ->
+          beforeEach ->
+            @options.message = "Are you sure?"
+
+          it "throws an error requiring a callback", ->
+            expect(@create).to.throw /confirm requires a callback/
+
+        describe "with a callback property", ->
+          describe "where the callback is not a function", ->
+            beforeEach ->
+              @options.callback = "Are you sure?"
+
+            it "throws an error requiring a callback", ->
+              expect(@create).to.throw /confirm requires a callback/
+
+          describe "where the callback is a function", ->
+            beforeEach ->
+              @options.callback = -> true
+
+            it "throws an error requiring a message", ->
+              expect(@create).to.throw /Please specify a message/
+
+        describe "with a message and a callback", ->
+          beforeEach ->
+            @options =
+              callback: -> true
+              message: "Are you sure?"
+
+          it "does not throw an error", ->
+            expect(@create).not.to.throw Error
+
+          it "creates a dialog object", ->
+            expect(@dialog).to.be.an "object"
+
+          it "adds the correct button labels", ->
+            expect(@dialog.find(".btn:first").text()).to.equal "Cancel"
+            expect(@dialog.find(".btn:last").text()).to.equal "OK"
+
+          it "adds the correct button classes", ->
+            expect(@dialog.find(".btn:first").hasClass("btn-default")).to.be.true
+            expect(@dialog.find(".btn:last").hasClass("btn-primary")).to.be.true
+
+    describe "with two arguments", ->
+      describe "where the second argument is not a function", ->
+        beforeEach ->
+          @create = =>
+            @dialog = bootbox.confirm "Are you sure?", "callback here"
+
+        it "throws an error requiring a callback", ->
+          expect(@create).to.throw /confirm requires a callback/
+
+      describe "where the second argument is a function", ->
+        beforeEach ->
+          @create = =>
+            @dialog = bootbox.confirm "Are you sure?", -> true
+
+        it "does not throw an error", ->
+          expect(@create).not.to.throw Error
+
+        it "creates a dialog object", ->
+          expect(@dialog).to.be.an "object"
+
+        it "applies the bootbox-confirm class to the dialog", ->
+          expect(@dialog.hasClass("bootbox-confirm")).to.be.true
+
+        it "adds the correct button labels", ->
+          expect(@dialog.find(".btn:first").text()).to.equal "Cancel"
+          expect(@dialog.find(".btn:last").text()).to.equal "OK"
+
+        it "adds the correct button classes", ->
+          expect(@dialog.find(".btn:first").hasClass("btn-default")).to.be.true
+          expect(@dialog.find(".btn:last").hasClass("btn-primary")).to.be.true
+
+        it "shows the dialog", ->
+          expect(@dialog.is(":visible")).to.be.true
+
+  describe "configuration options tests", ->
+    beforeEach ->
+      @options =
+        message: "Are you sure?"
+        callback: -> true
+
+      @create = =>
+        @dialog = bootbox.confirm @options
+
+    describe "with a custom cancel button", ->
+      beforeEach ->
+        @options.buttons =
+          cancel:
+            label: "Custom cancel"
+            className: "btn-danger"
+
+        @create()
+
+        @button = @dialog.find(".btn:first")
+
+      it "adds the correct cancel button", ->
+        expect(@button.text()).to.equal "Custom cancel"
+        expect(@button.hasClass("btn-danger")).to.be.true
+
+    describe "with a custom confirm button", ->
+      beforeEach ->
+        @options.buttons =
+          confirm:
+            label: "Custom confirm"
+            className: "btn-warning"
+
+        @create()
+
+        @button = @dialog.find(".btn:last")
+
+      it "adds the correct confirm button", ->
+        expect(@button.text()).to.equal "Custom confirm"
+        expect(@button.hasClass("btn-warning")).to.be.true
+
+    describe "with an unrecognised button key", ->
+      beforeEach ->
+        @options.buttons =
+          "Bad key":
+            label: "Custom confirm"
+            className: "btn-warning"
+
+      it "throws an error", ->
+        expect(@create).to.throw /key is not allowed/
+
+  describe "callback tests", ->
+    describe "with a simple callback", ->
+      beforeEach ->
+        @callback = sinon.spy()
+
+        @dialog = bootbox.confirm
+          message: "Are you sure?"
+          callback: @callback
+
+        @hidden = sinon.spy @dialog, "modal"
+
+      describe "when dismissing the dialog by clicking OK", ->
+        beforeEach ->
+          @dialog.find(".btn-primary").trigger "click"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly true
+
+        it "should hide the modal", ->
+          expect(@hidden).to.have.been.calledWithExactly "hide"
+
+      describe "when dismissing the dialog by clicking Cancel", ->
+        beforeEach ->
+          @dialog.find(".btn-default").trigger "click"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly false
+
+        it "should hide the modal", ->
+          expect(@hidden).to.have.been.calledWithExactly "hide"
+
+      describe "when triggering the escape event", ->
+        beforeEach ->
+          @dialog.trigger "escape.close.bb"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly false
+
+        it "should hide the modal", ->
+          expect(@hidden).to.have.been.calledWithExactly "hide"
+
+    describe "with a callback which returns false", ->
+      beforeEach ->
+        @callback = sinon.stub()
+        @callback.returns false
+
+        @dialog = bootbox.confirm
+          message: "Are you sure?"
+          callback: @callback
+
+        @hidden = sinon.spy @dialog, "modal"
+
+      describe "when dismissing the dialog by clicking OK", ->
+        beforeEach ->
+          @dialog.find(".btn-primary").trigger "click"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly true
+
+        it "should not hide the modal", ->
+          expect(@hidden).not.to.have.been.called
+
+      describe "when dismissing the dialog by clicking Cancel", ->
+        beforeEach ->
+          @dialog.find(".btn-default").trigger "click"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly false
+
+        it "should not hide the modal", ->
+          expect(@hidden).not.to.have.been.called
+
+      describe "when triggering the escape event", ->
+        beforeEach ->
+          @dialog.trigger "escape.close.bb"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly false
+
+        it "should not hide the modal", ->
+          expect(@hidden).not.to.have.been.called
diff --git a/tests/defaults.test.js b/tests/defaults.test.js
new file mode 100644
index 0000000..89fe717
--- /dev/null
+++ b/tests/defaults.test.js
@@ -0,0 +1,186 @@
+describe("bootbox.setDefaults", function() {
+
+  beforeEach(function() {
+    this.find = function(selector) {
+      return this.dialog.find(selector);
+    };
+  });
+
+  describe("animate", function() {
+    describe("when set to false", function() {
+      beforeEach(function() {
+        bootbox.setDefaults({
+          animate: false
+        });
+        this.dialog = bootbox.dialog({
+          message: "test"
+        });
+      });
+
+      it("does not add the fade class to the dialog", function() {
+        expect(this.dialog.hasClass("fade")).to.be.false;
+      });
+
+      it("applies the correct class to the body", function() {
+        expect($("body").hasClass("modal-open")).to.be.true;
+      });
+
+      describe("when clicking the close button", function() {
+        beforeEach(function() {
+          this.dialog.find(".close").trigger("click");
+        });
+
+        it("removes the modal-open class from the body", function() {
+          expect($("body").hasClass("modal-open")).to.be.false;
+        });
+      });
+    });
+
+    describe("when set to true", function() {
+      beforeEach(function() {
+        bootbox.setDefaults({
+          animate: true
+        });
+        this.dialog = bootbox.dialog({
+          message: "test"
+        });
+      });
+
+      it("adds the fade class to the dialog", function() {
+        expect(this.dialog.hasClass("fade")).to.be.true;
+      });
+    });
+  });
+
+  describe("className", function() {
+    describe("when passed as a string", function() {
+      beforeEach(function() {
+        bootbox.setDefaults({
+          className: "my-class"
+        });
+
+        this.dialog = bootbox.dialog({
+          message: "test"
+        });
+      });
+
+      it("adds the extra class to the outer dialog", function() {
+        expect(this.dialog.hasClass("bootbox")).to.be.true;
+        expect(this.dialog.hasClass("my-class")).to.be.true;
+      });
+    });
+  });
+
+  describe("size", function() {
+    describe("when set to large", function() {
+      beforeEach(function() {
+        bootbox.setDefaults({
+          size: "large"
+        });
+
+        this.dialog = bootbox.dialog({
+          message: "test"
+        });
+      });
+
+      it("adds the large class to the innerDialog", function() {
+        expect(this.dialog.children(".modal-dialog").hasClass("modal-lg")).to.be.true;
+      });
+    });
+    describe("when set to small", function() {
+      beforeEach(function() {
+        bootbox.setDefaults({
+          size: "small"
+        });
+
+        this.dialog = bootbox.dialog({
+          message: "test"
+        });
+      });
+
+      it("adds the small class to the innerDialog", function() {
+        expect(this.dialog.children(".modal-dialog").hasClass("modal-sm")).to.be.true;
+      });
+    });
+  });
+
+  describe("backdrop", function() {
+    describe("when set to false", function() {
+      beforeEach(function() {
+        bootbox.setDefaults({
+          backdrop: false
+        });
+
+        this.dialog = bootbox.dialog({
+          message: "test"
+        });
+      });
+
+      it("does not show a backdrop", function() {
+        expect(this.dialog.next(".modal-backdrop").length).to.equal(0);
+      });
+    });
+  });
+
+  describe("when passed two arguments", function() {
+    beforeEach(function() {
+      bootbox.setDefaults("className", "my-class");
+      this.dialog = bootbox.dialog({
+        message: "test"
+      });
+    });
+
+    it("applies the arguments as a key/value pair", function() {
+      expect(this.dialog.hasClass("bootbox")).to.be.true;
+      expect(this.dialog.hasClass("my-class")).to.be.true;
+    });
+  });
+
+  describe("container", function () {
+    describe("when not explicitly set", function() {
+      beforeEach(function() {
+        this.dialog = bootbox.dialog({
+          message: "test"
+        });
+      });
+
+      it("defaults to the body element", function() {
+        expect(this.dialog.parent().is("body")).to.be.true;
+      });
+    });
+
+    describe("when explicitly set to body", function() {
+      beforeEach(function() {
+        bootbox.setDefaults({
+          container: "body"
+        });
+
+        this.dialog = bootbox.dialog({
+          message: "test"
+        });
+      });
+
+      it("sets the correct parent element", function() {
+        expect(this.dialog.parent().is("body")).to.be.true;
+      });
+    });
+
+    describe("when set to another dom element", function() {
+
+      beforeEach(function() {
+        this.container = $("<div></div>");
+        bootbox.setDefaults({
+          container: this.container
+        });
+
+        this.dialog = bootbox.dialog({
+          message: "test"
+        });
+      });
+
+      it("sets the correct parent element", function() {
+        expect(this.dialog.parent().is(this.container)).to.be.true;
+      });
+    });
+  });
+});
diff --git a/tests/dialog.test.coffee b/tests/dialog.test.coffee
new file mode 100644
index 0000000..5c7000d
--- /dev/null
+++ b/tests/dialog.test.coffee
@@ -0,0 +1,349 @@
+describe "bootbox.dialog", ->
+  beforeEach ->
+
+    # need to take care with these helpers; don't want too much
+    # cleverness in the tests which runs the risk of making them
+    # harder to read. Could we look at custom expectations instead?
+    @find   = (s)    -> @dialog.find s
+    @exists = (s)    -> @find(s).length isnt 0
+    @class  = (s, c) -> @find(s).hasClass(c)
+    @invoke = (s, m) -> @find(s)[m]()
+    @text   = (s)    -> @invoke s, "text"
+    @html   = (s)    -> @invoke s, "html"
+
+  describe "invalid usage tests", ->
+
+    describe "with no arguments", ->
+
+      beforeEach ->
+        @create = -> bootbox.dialog()
+
+      it "throws an error", ->
+        expect(@create).to.throw /supply an object/
+
+    describe "with one argument", ->
+
+      describe "where the argument is not an object", ->
+        beforeEach ->
+          @create = -> bootbox.dialog "test"
+
+        it "throws an error", ->
+          expect(@create).to.throw /supply an object/
+
+      describe "where the argument has no message property", ->
+        beforeEach ->
+          @create = ->
+            bootbox.dialog
+              invalid: "options"
+
+        it "throws an error", ->
+          expect(@create).to.throw /specify a message/
+
+      describe "where the argument has a button with an invalid value", ->
+        beforeEach ->
+          @create = ->
+            bootbox.dialog
+              message: "test"
+              buttons:
+                ok: "foo"
+
+        it "throws an error", ->
+          expect(@create).to.throw /button with key ok must be an object/
+
+  describe "when creating a minimal dialog", ->
+    beforeEach ->
+      @dialog = bootbox.dialog
+        message: "test"
+
+    it "adds the bootbox class to the dialog", ->
+      expect(@dialog.hasClass("bootbox")).to.be.true
+
+    it "adds the bootstrap modal class to the dialog", ->
+      expect(@dialog.hasClass("modal")).to.be.true
+
+    it "adds the fade class to the dialog", ->
+      expect(@dialog.hasClass("fade")).to.be.true
+
+    it "shows the expected message", ->
+      expect(@text(".bootbox-body")).to.equal "test"
+
+    it "does not have a header", ->
+      expect(@exists(".modal-header")).not.to.be.ok
+
+    it "has a close button inside the body", ->
+      expect(@exists(".modal-body .close")).to.be.ok
+
+    it "does not have a footer", ->
+      expect(@exists(".modal-footer")).not.to.be.ok
+
+    it "has a backdrop", ->
+      # bootstrap < 3.3.x
+      # expect(@dialog.next(".modal-backdrop").length).to.equal 1
+      # bootstrap >= 3.3.x
+      expect(@dialog.children(".modal-backdrop").length).to.equal 1
+
+  describe "when creating a dialog with a button", ->
+    beforeEach ->
+      @create = (button = {}) =>
+        @dialog = bootbox.dialog
+          message: "test"
+          buttons:
+            one: button
+
+    describe "when the button has no callback", ->
+      beforeEach ->
+        @create
+          label: "My Label"
+
+        @hidden = sinon.spy @dialog, "modal"
+
+      it "shows a footer", ->
+        expect(@exists(".modal-footer")).to.be.ok
+
+      it "shows one button", ->
+        expect(@find(".btn").length).to.equal 1
+
+      it "shows the correct button text", ->
+        expect(@text(".btn")).to.equal "My Label"
+
+      it "applies the correct button class", ->
+        expect(@class(".btn", "btn-primary")).to.be.true
+
+      describe "when triggering the escape event", ->
+        beforeEach ->
+          @dialog.trigger "escape.close.bb"
+
+        it "should not hide the modal", ->
+          expect(@hidden).not.to.have.been.called
+
+      describe "when clicking the close button", ->
+        beforeEach ->
+          @dialog.find(".close").trigger "click"
+
+        it "should hide the modal", ->
+          expect(@hidden).to.have.been.calledWithExactly "hide"
+
+    describe "when the button has a label and callback", ->
+      beforeEach ->
+        @callback = sinon.spy()
+
+        @create
+          label: "Another Label"
+          callback: @callback
+
+        @hidden = sinon.spy @dialog, "modal"
+
+      it "shows a footer", ->
+        expect(@exists(".modal-footer")).to.be.ok
+
+      it "shows the correct button text", ->
+        expect(@text(".btn")).to.equal "Another Label"
+
+      describe "when dismissing the dialog by clicking OK", ->
+        beforeEach ->
+          @dialog.find(".btn-primary").trigger "click"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "should hide the modal", ->
+          expect(@hidden).to.have.been.calledWithExactly "hide"
+
+      describe "when triggering the escape event", ->
+        beforeEach ->
+          @dialog.trigger "escape.close.bb"
+
+        it "should not invoke the callback", ->
+          expect(@callback).not.to.have.been.called
+
+        it "should not hide the modal", ->
+          expect(@hidden).not.to.have.been.called
+
+      describe "when clicking the close button", ->
+        beforeEach ->
+          @dialog.find(".close").trigger "click"
+
+        it "should not invoke the callback", ->
+          expect(@callback).not.to.have.been.called
+
+        it "should hide the modal", ->
+          expect(@hidden).to.have.been.called
+
+    describe "when the button has a custom class", ->
+      beforeEach ->
+        @create
+          label: "Test Label"
+          className: "btn-custom"
+
+      it "shows the correct button text", ->
+        expect(@text(".btn")).to.equal "Test Label"
+
+      it "adds the custom class to the button", ->
+        expect(@class(".btn", "btn-custom")).to.be.true
+
+    describe "when the button has no explicit label", ->
+      beforeEach ->
+        @create = (buttons) ->
+          @dialog = bootbox.dialog
+            message: "test"
+            buttons: buttons
+
+      describe "when its value is an object", ->
+        beforeEach ->
+          @create
+            "Short form":
+              className: "btn-custom"
+              callback: -> true
+
+        it "uses the key name as the button text", ->
+          expect(@text(".btn")).to.equal "Short form"
+
+        it "adds the custom class to the button", ->
+          expect(@class(".btn", "btn-custom")).to.be.true
+
+      describe "when its value is a function", ->
+        beforeEach ->
+          @callback = sinon.spy()
+          @create
+            my_label: @callback
+
+        it "uses the key name as the button text", ->
+          expect(@text(".btn")).to.equal "my_label"
+
+        describe "when dismissing the dialog by clicking the button", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+      describe "when its value is not an object or function", ->
+        beforeEach ->
+          @badCreate = =>
+            @create
+              "Short form": "hello world"
+
+        it "throws an error", ->
+          expect(@badCreate).to.throw /button with key Short form must be an object/
+
+  describe "when creating a dialog with a title", ->
+    beforeEach ->
+      @dialog = bootbox.dialog
+        title: "My Title"
+        message: "test"
+
+    it "has a header", ->
+      expect(@exists(".modal-header")).to.be.ok
+
+    it "shows the correct title text", ->
+      expect(@text(".modal-title")).to.equal "My Title"
+
+    it "has a close button inside the header", ->
+      expect(@exists(".modal-header .close")).to.be.ok
+
+  describe "when creating a dialog with no backdrop", ->
+    beforeEach ->
+      @dialog = bootbox.dialog
+        message: "No backdrop in sight"
+        backdrop: false
+
+    it "does not have a backdrop", ->
+      expect(@dialog.next(".modal-backdrop").length).to.equal 0
+
+  describe "when creating a dialog with no close button", ->
+    beforeEach ->
+      @dialog = bootbox.dialog
+        message: "No backdrop in sight"
+        closeButton: false
+
+    it "does not have a close button inside the body", ->
+      expect(@exists(".modal-body .close")).not.to.be.ok
+
+  describe "when creating a dialog with an onEscape handler", ->
+    beforeEach ->
+      @e = (keyCode) ->
+        $(@dialog).trigger($.Event "keyup", which: keyCode)
+
+    describe "with a simple callback", ->
+      beforeEach ->
+        @callback = sinon.spy()
+
+        @dialog = bootbox.dialog
+          message: "Are you sure?"
+          onEscape: @callback
+
+        @hidden = sinon.spy @dialog, "modal"
+        @trigger = sinon.spy(@dialog, "trigger").withArgs "escape.close.bb"
+
+      describe "when triggering the keyup event", ->
+
+        describe "when the key is not the escape key", ->
+          beforeEach -> @e 15
+
+          it "does not trigger the escape event", ->
+            expect(@trigger).not.to.have.been.called
+
+          it "should not hide the modal", ->
+            expect(@hidden).not.to.have.been.called
+
+        describe "when the key is the escape key", ->
+          beforeEach -> @e 27
+
+          it "triggers the escape event", ->
+            expect(@trigger).to.have.been.calledWithExactly "escape.close.bb"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "should hide the modal", ->
+            expect(@hidden).to.have.been.calledWithExactly "hide"
+
+    describe "with a callback which returns false", ->
+      beforeEach ->
+        @callback = sinon.stub().returns false
+
+        @dialog = bootbox.dialog
+          message: "Are you sure?"
+          onEscape: @callback
+
+        @hidden = sinon.spy @dialog, "modal"
+
+      describe "when triggering the escape keyup event", ->
+        beforeEach -> @e 27
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "should not hide the modal", ->
+          expect(@hidden).not.to.have.been.called
+
+  describe "with size option", ->
+    describe "when the size option is set to large", ->
+      beforeEach ->
+        @dialog = bootbox.dialog
+          message: "test"
+          size: "large"
+
+      it "adds the large class to the innerDialog", ->
+        expect(@dialog.children(".modal-dialog").hasClass("modal-lg")).to.be.true
+
+    describe "when the size option is set to small", ->
+      beforeEach ->
+        @dialog = bootbox.dialog
+          message: "test"
+          size: "small"
+
+      it "adds the large class to the innerDialog", ->
+        expect(@dialog.children(".modal-dialog").hasClass("modal-sm")).to.be.true
diff --git a/tests/locales.test.coffee b/tests/locales.test.coffee
new file mode 100644
index 0000000..680653e
--- /dev/null
+++ b/tests/locales.test.coffee
@@ -0,0 +1,378 @@
+describe "bootbox locales", ->
+  beforeEach ->
+
+    @setLocale = (locale) ->
+      bootbox.setLocale locale
+
+      d1 = bootbox.alert "foo"
+      d2 = bootbox.confirm "foo", -> true
+
+      @labels =
+        ok: d1.find(".btn:first").text()
+        cancel: d2.find(".btn:first").text()
+        confirm: d2.find(".btn:last").text()
+
+  describe "Invalid locale", ->
+    beforeEach ->
+      @setLocale "xx"
+
+    it "shows the default OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the default CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Cancel"
+
+    it "shows the default CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "OK"
+
+  describe "English", ->
+    beforeEach ->
+      @setLocale "en"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Cancel"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "OK"
+
+  describe "French", ->
+    beforeEach ->
+      @setLocale "fr"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Annuler"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "D'accord"
+
+  describe "German", ->
+    beforeEach ->
+      @setLocale "de"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Abbrechen"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Akzeptieren"
+
+  describe "Spanish", ->
+    beforeEach ->
+      @setLocale "es"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Cancelar"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Aceptar"
+
+  describe "Portuguese", ->
+    beforeEach ->
+      @setLocale "br"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Cancelar"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Sim"
+
+  describe "Dutch", ->
+    beforeEach ->
+      @setLocale "nl"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Annuleren"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Accepteren"
+
+  describe "Russian", ->
+    beforeEach ->
+      @setLocale "ru"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Отмена"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Применить"
+
+  describe "Indonesian", ->
+    beforeEach ->
+      @setLocale "id"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Batal"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "OK"
+
+  describe "Italian", ->
+    beforeEach ->
+      @setLocale "it"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Annulla"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Conferma"
+
+  describe "Polish", ->
+    beforeEach ->
+      @setLocale "pl"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Anuluj"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Potwierdź"
+
+  describe "Danish", ->
+    beforeEach ->
+      @setLocale "da"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Annuller"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Accepter"
+
+  describe "Chinese", ->
+    describe "Taiwan", ->
+      beforeEach ->
+        @setLocale "zh_TW"
+
+      it "shows the correct OK translation", ->
+        expect(@labels.ok).to.equal "OK"
+
+      it "shows the correct CANCEL translation", ->
+        expect(@labels.cancel).to.equal "取消"
+
+      it "shows the correct CONFIRM translation", ->
+        expect(@labels.confirm).to.equal "確認"
+
+    describe "China", ->
+      beforeEach ->
+        @setLocale "zh_CN"
+
+      it "shows the correct OK translation", ->
+        expect(@labels.ok).to.equal "OK"
+
+      it "shows the correct CANCEL translation", ->
+        expect(@labels.cancel).to.equal "取消"
+
+      it "shows the correct CONFIRM translation", ->
+        expect(@labels.confirm).to.equal "确认"
+
+  describe "Norwegian", ->
+    beforeEach ->
+      @setLocale "no"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Avbryt"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "OK"
+
+  describe "Swedish", ->
+    beforeEach ->
+      @setLocale "sv"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Avbryt"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "OK"
+
+  describe "Latvian", ->
+    beforeEach ->
+      @setLocale "lv"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "Labi"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Atcelt"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Apstiprināt"
+
+  describe "Lithuanian", ->
+    beforeEach ->
+      @setLocale "lt"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "Gerai"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Atšaukti"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Patvirtinti"
+
+  describe "Turkish", ->
+    beforeEach ->
+      @setLocale "tr"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "Tamam"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "İptal"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Onayla"
+
+  describe "Hebrew", ->
+    beforeEach ->
+      @setLocale "he"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "אישור"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "ביטול"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "אישור"
+
+  describe "Greek", ->
+    beforeEach ->
+      @setLocale "el"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "Εντάξει"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Ακύρωση"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Επιβεβαίωση"
+
+  describe "Japanese", ->
+    beforeEach ->
+      @setLocale "ja"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "キャンセル"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "確認"
+
+  describe "Hungarian", ->
+    beforeEach ->
+      @setLocale "hu"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Mégsem"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Megerősít"
+
+  describe "Croatian", ->
+    beforeEach ->
+      @setLocale "hr"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Odustani"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Potvrdi"
+
+  describe "Bulgarian", ->
+    beforeEach ->
+      @setLocale "bg_BG"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "Ок"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Отказ"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Потвърждавам"
+
+  describe "Thai", ->
+    beforeEach ->
+      @setLocale "th"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "ตกลง"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "ยกเลิก"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "ยืนยัน"
+
+  describe "Persian", ->
+    beforeEach ->
+      @setLocale "fa"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "قبول"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "لغو"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "تایید"
+
+  describe "Albanian", ->
+    beforeEach ->
+      @setLocale "sq"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Anulo"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "Prano"
diff --git a/tests/prompt.test.coffee b/tests/prompt.test.coffee
new file mode 100644
index 0000000..ce25d77
--- /dev/null
+++ b/tests/prompt.test.coffee
@@ -0,0 +1,1306 @@
+describe "bootbox.prompt", ->
+  beforeEach ->
+    window.bootbox = bootbox.init()
+
+    @find   = (selector) -> @dialog.find selector
+    @text   = (selector) -> @find(selector).text()
+    @exists = (selector) -> @find(selector).length isnt 0
+
+  describe "basic usage tests", ->
+
+    describe "with one argument", ->
+
+      describe "where the argument is not an object", ->
+        beforeEach ->
+          @create = -> bootbox.prompt "What is your name?"
+
+        it "throws an error", ->
+          expect(@create).to.throw /prompt requires a callback/
+
+      describe "where the argument is an object", ->
+        beforeEach ->
+          @options = {}
+          @create = => @dialog = bootbox.prompt @options
+
+        describe "with a title property", ->
+          beforeEach ->
+            @options.title = "What is your name?"
+
+          it "throws an error requiring a callback", ->
+            expect(@create).to.throw /prompt requires a callback/
+
+          describe "and a callback property", ->
+            describe "where the callback is not a function", ->
+              beforeEach ->
+                @options.callback = "Not a function"
+
+              it "throws an error requiring a callback", ->
+                expect(@create).to.throw /prompt requires a callback/
+
+        describe "with a callback function", ->
+          beforeEach ->
+            @options.callback = -> true
+
+          it "throws an error requiring a title", ->
+            expect(@create).to.throw /prompt requires a title/
+
+        describe "with a title and a callback", ->
+          beforeEach ->
+            @options =
+              callback: -> true
+              title: "What is your name?"
+
+          it "does not throw an error", ->
+            expect(@create).not.to.throw Error
+
+          it "creates a dialog object", ->
+            expect(@dialog).to.be.an "object"
+
+          it "applies the bootbox-prompt class to the dialog", ->
+            expect(@dialog.hasClass("bootbox-prompt")).to.be.true
+
+          it "adds the correct button labels", ->
+            expect(@dialog.find(".btn:first").text()).to.equal "Cancel"
+            expect(@dialog.find(".btn:last").text()).to.equal "OK"
+
+          it "adds the correct button classes", ->
+            expect(@dialog.find(".btn:first").hasClass("btn-default")).to.be.true
+            expect(@dialog.find(".btn:last").hasClass("btn-primary")).to.be.true
+
+    describe "with two arguments", ->
+      describe "where the second argument is not a function", ->
+        beforeEach ->
+          @create = =>
+            @dialog = bootbox.prompt "What is your name?", "callback here"
+
+        it "throws an error requiring a callback", ->
+          expect(@create).to.throw /prompt requires a callback/
+
+      describe "where the second argument is a function", ->
+        beforeEach ->
+          @create = =>
+            @dialog = bootbox.prompt "What is your name?", -> true
+
+        it "does not throw an error", ->
+          expect(@create).not.to.throw Error
+
+        it "creates a dialog object", ->
+          expect(@dialog).to.be.an "object"
+
+        it "adds the correct button labels", ->
+          expect(@text(".btn:first")).to.equal "Cancel"
+          expect(@text(".btn:last")).to.equal "OK"
+
+        it "adds the correct button classes", ->
+          expect(@dialog.find(".btn:first").hasClass("btn-default")).to.be.true
+          expect(@dialog.find(".btn:last").hasClass("btn-primary")).to.be.true
+
+        it "adds the expected dialog title", ->
+          expect(@text("h4")).to.equal "What is your name?"
+
+        it "adds a close button", ->
+          expect(@dialog.find(".modal-header .close")).to.be.ok
+
+        it "creates a form with a text input", ->
+          expect(@dialog.find("form input[type=text]")).to.be.ok
+
+        it "with no default value", ->
+          expect(@dialog.find("form input[type=text]").val()).to.equal ""
+
+        it "shows the dialog", ->
+          expect(@dialog.is(":visible")).to.be.true
+
+  describe "configuration options tests", ->
+    beforeEach ->
+      @options =
+        title: "What is your name?"
+        callback: -> true
+
+      @create = =>
+        @dialog = bootbox.prompt @options
+
+    describe "with a custom cancel button", ->
+      beforeEach ->
+        @options.buttons =
+          cancel:
+            label: "Custom cancel"
+            className: "btn-danger"
+
+        @create()
+
+        @button = @dialog.find(".btn:first")
+
+      it "adds the correct cancel button", ->
+        expect(@button.text()).to.equal "Custom cancel"
+        expect(@button.hasClass("btn-danger")).to.be.true
+
+    describe "with a custom confirm button", ->
+      beforeEach ->
+        @options.buttons =
+          confirm:
+            label: "Custom confirm"
+            className: "btn-warning"
+
+        @create()
+
+        @button = @dialog.find(".btn:last")
+
+      it "adds the correct confirm button", ->
+        expect(@button.text()).to.equal "Custom confirm"
+        expect(@button.hasClass("btn-warning")).to.be.true
+
+    describe "with an unrecognised button key", ->
+      beforeEach ->
+        @options.buttons =
+          prompt:
+            label: "Custom confirm"
+            className: "btn-warning"
+
+      it "throws an error", ->
+        expect(@create).to.throw /key prompt is not allowed/
+
+    describe "setting show to false", ->
+      beforeEach ->
+        @options.show = false
+
+        @shown = sinon.spy()
+
+        sinon.stub bootbox, "dialog", =>
+          on: ->
+          off: ->
+          modal: @shown
+
+        @create()
+
+      it "does not show the dialog", ->
+        expect(@shown).not.to.have.been.called
+
+    describe "invalid prompt type", ->
+      beforeEach ->
+        @options.inputType = 'foobar'
+
+      it "throws an error", ->
+        expect(@create).to.throw /invalid prompt type/
+
+    describe "setting inputType text", ->
+      beforeEach ->
+        @options.inputType = "text"
+
+      describe "without default value", ->
+        beforeEach ->
+          @create()
+
+        it "shows text input ", ->
+          expect(@exists("input[type='text']")).to.be.ok
+
+        it "has proper class", ->
+          expect(@find("input[type='text']").hasClass("bootbox-input")).to.be.ok
+          expect(@find("input[type='text']").hasClass("bootbox-input-text")).to.be.ok
+
+      describe "with default value", ->
+        beforeEach ->
+          @options.value = "John Smith"
+          @create()
+
+        it "has correct default value", ->
+          expect(@find("input[type='text']").val()).to.equal "John Smith"
+
+      describe "with placeholder", ->
+        beforeEach ->
+          @options.placeholder = "enter your name"
+          @create()
+
+        it "has correct placeholder value", ->
+          expect(@find("input[type='text']").prop("placeholder")).to.equal "enter your name"
+
+      describe "with pattern", ->
+        beforeEach ->
+          @options.pattern = "\d{1,2}/\d{1,2}/\d{4}"
+          @create()
+
+        it "has correct pattern value", ->
+          expect(@find("input[type='text']").prop("pattern")).to.equal "\d{1,2}/\d{1,2}/\d{4}"
+
+      describe "with maxlength", ->
+        beforeEach ->
+          @options.maxlength = 5
+          @create()
+
+        it "has correct maxlength value", ->
+          expect(@find("input[type='text']").prop("maxlength")).to.equal 5
+
+    describe "setting inputType textarea", ->
+      beforeEach ->
+        @options.inputType = "textarea"
+
+      describe "without default value", ->
+        beforeEach ->
+          @create()
+
+        it "shows text input", ->
+          expect(@exists("textarea")).to.be.ok
+
+        it "has proper class", ->
+          expect(@find("textarea").hasClass("bootbox-input")).to.be.ok
+          expect(@find("textarea").hasClass("bootbox-input-textarea")).to.be.ok
+
+      describe "with default value", ->
+        beforeEach ->
+          @options.value = "Once upon a time..."
+          @create()
+
+        it "has correct default value", ->
+          expect(@find("textarea").val()).to.equal "Once upon a time..."
+
+      describe "with placeholder", ->
+        beforeEach ->
+          @options.placeholder = "enter your favorite fairy tale"
+          @create()
+
+        it "has correct placeholder value", ->
+          expect(@find("textarea").prop("placeholder")).to.equal "enter your favorite fairy tale"
+
+    describe "setting inputType email", ->
+      beforeEach ->
+        @options.inputType = "email"
+
+      describe "without default value", ->
+        beforeEach ->
+          @create()
+
+        it "shows email input", ->
+          expect(@exists("input[type='email']")).to.be.ok
+
+        it "has proper class", ->
+          expect(@find("input[type='email']").hasClass("bootbox-input")).to.be.ok
+          expect(@find("input[type='email']").hasClass("bootbox-input-email")).to.be.ok
+
+      describe "with default value", ->
+        beforeEach ->
+          @options.value = "john at smith.com"
+          @create()
+
+        it "has correct default value", ->
+          expect(@find("input[type='email']").val()).to.equal "john at smith.com"
+
+      describe "with placeholder", ->
+        beforeEach ->
+          @options.placeholder = "enter your email"
+          @create()
+
+        it "has correct placeholder value", ->
+          expect(@find("input[type='email']").prop("placeholder")).to.equal "enter your email"
+
+      describe "with pattern", ->
+        beforeEach ->
+          @options.pattern = "\d{1,2}/\d{1,2}/\d{4}"
+          @create()
+
+        it "has correct pattern value", ->
+          expect(@find("input[type='email']").prop("pattern")).to.equal "\d{1,2}/\d{1,2}/\d{4}"
+
+    describe "setting inputType password", ->
+      beforeEach ->
+        @options.inputType = "password"
+
+      describe "without default value", ->
+        beforeEach ->
+          @create()
+
+        it "shows password input", ->
+          expect(@exists("input[type='password']")).to.be.ok
+
+        it "has proper class", ->
+          expect(@find("input[type='password']").hasClass("bootbox-input")).to.be.ok
+          expect(@find("input[type='password']").hasClass("bootbox-input-password")).to.be.ok
+
+      describe "with default value", ->
+        beforeEach ->
+          @options.value = "qwerty"
+          @create()
+
+        it "has correct default value", ->
+          expect(@find("input[type='password']").val()).to.equal "qwerty"
+
+      describe "with placeholder", ->
+        beforeEach ->
+          @options.placeholder = "enter your password"
+          @create()
+
+        it "has correct placeholder value", ->
+          expect(@find("input[type='password']").prop("placeholder")).to.equal "enter your password"
+
+    describe "setting inputType select", ->
+      describe "without options", ->
+        beforeEach ->
+          @options.inputType = 'select'
+
+        it "throws an error", ->
+          expect(@create).to.throw /prompt with select requires options/
+
+      describe "with invalid options", ->
+        beforeEach ->
+          @options.inputType = 'select'
+          @options.inputOptions = 'foo'
+
+        it "throws an error", ->
+          expect(@create).to.throw "Please pass an array of input options"
+
+      describe "with empty options", ->
+        beforeEach ->
+          @options.inputType = 'select'
+          @options.inputOptions = []
+
+        it "throws an error", ->
+          expect(@create).to.throw /prompt with select requires options/
+
+      describe "with options in the wrong format", ->
+        beforeEach ->
+          @options.inputType = "select"
+          @options.inputOptions = [{foo: "bar"}]
+
+        it "throws an error", ->
+          expect(@create).to.throw /given options in wrong format/
+
+      describe "with a value but no text", ->
+        beforeEach ->
+          @options.inputType = 'select'
+          @options.inputOptions = [{value: 'bar'}]
+
+        it "throws an error", ->
+          expect(@create).to.throw /given options in wrong format/
+
+      describe "with an invalid second options", ->
+        beforeEach ->
+          @options.inputType = 'select'
+          @options.inputOptions = [
+            {value: "bar", text: "bar"}
+            {text: "foo"}
+          ]
+
+        it "throws an error", ->
+          expect(@create).to.throw /given options in wrong format/
+
+
+      describe "with valid options", ->
+        beforeEach ->
+          @options.inputType = "select"
+          @options.inputOptions = [{value: 1, text: 'foo'},{value: 2, text: 'bar'},{value: 3, text: 'foobar'}]
+
+          @create()
+
+        it "shows select input", ->
+          expect(@exists("select")).to.be.ok
+
+        it "has proper class", ->
+          expect(@find("select").hasClass("bootbox-input")).to.be.ok
+          expect(@find("select").hasClass("bootbox-input-select")).to.be.ok
+
+        it "with three options", ->
+          expect(@find("option").length).to.equal 3
+
+      describe "with zero as the first option", ->
+        beforeEach ->
+          @options.inputType = "select"
+          @options.inputOptions = [{value: 0, text: "foo"}]
+
+          @create()
+
+        it "shows the select input", ->
+          expect(@exists("select")).to.be.ok
+
+      describe "with false as the first option", ->
+        beforeEach ->
+          @options.inputType = "select"
+          @options.inputOptions = [{value: false, text: "foo"}]
+
+          @create()
+
+        it "shows the select input", ->
+          expect(@exists("select")).to.be.ok
+
+      describe "with option groups", ->
+        beforeEach ->
+          @options.inputType = 'select'
+          @options.inputOptions = [
+            {value: 1, group: 'foo', text: 'foo'}
+            {value: 2, group: 'bar', text: 'bar'}
+            {value: 3, group: 'foo', text: 'foobar'}
+            {value: 4, group: 'bar', text: 'barfoo'}
+          ]
+
+          @create()
+
+        it "shows select input", ->
+          expect(@exists("select")).to.be.ok
+
+        it "has proper class", ->
+          expect(@find("select").hasClass("bootbox-input")).to.be.ok
+          expect(@find("select").hasClass("bootbox-input-select")).to.be.ok
+
+        it "with two option group", ->
+          expect(@find("optgroup").length).to.equal 2
+
+        it "with four options", ->
+          expect(@find("option").length).to.equal 4
+
+    describe "setting inputType checkbox", ->
+      describe "without options", ->
+        beforeEach ->
+          @options.inputType = 'checkbox'
+
+        it "throws an error", ->
+            expect(@create).to.throw /prompt with checkbox requires options/
+
+      describe "with options in the wrong format", ->
+        beforeEach ->
+          @options.inputType = "checkbox"
+          @options.inputOptions = [{foo: "bar"}]
+
+        it "throws an error", ->
+          expect(@create).to.throw /given options in wrong format/
+
+      describe "with options", ->
+        beforeEach ->
+          @options.inputType = 'checkbox'
+          @options.inputOptions = [
+            {value: 1, text: 'foo'}
+            {value: 2, text: 'bar'}
+            {value: 3, text: 'foobar'}
+          ]
+
+          @create()
+
+        it "shows checkbox input", ->
+          expect(@exists("input[type='checkbox']")).to.be.ok
+
+        it "has proper class", ->
+          expect(@find("input[type='checkbox']").hasClass("bootbox-input")).to.be.ok
+          expect(@find("input[type='checkbox']").hasClass("bootbox-input-checkbox")).to.be.ok
+
+        it "with three checkboxes", ->
+          expect(@find("input[type='checkbox']").length).to.equal 3
+
+    describe "setting inputType date", ->
+      beforeEach ->
+        @options.inputType = "date"
+
+      describe "without default value", ->
+        beforeEach ->
+          @create()
+
+        it "shows date input ", ->
+          expect(@exists("input[type='date']")).to.be.ok
+
+        it "has proper class", ->
+          expect(@find("input[type='date']").hasClass("bootbox-input")).to.be.ok
+          expect(@find("input[type='date']").hasClass("bootbox-input-date")).to.be.ok
+
+      describe "with default value", ->
+        beforeEach ->
+          @options.value = "17/08/2005"
+          @create()
+
+        it "has correct default value", ->
+          expect(@find("input[type='date']").val()).to.equal "17/08/2005"
+
+      describe "with placeholder", ->
+        beforeEach ->
+          @options.placeholder = "enter the date"
+          @create()
+
+        it "has correct placeholder value", ->
+          expect(@find("input[type='date']").prop("placeholder")).to.equal "enter the date"
+
+      describe "with pattern", ->
+        beforeEach ->
+          @options.pattern = "\d{1,2}/\d{1,2}/\d{4}"
+          @create()
+
+        it "has correct pattern value", ->
+          expect(@find("input[type='date']").prop("pattern")).to.equal "\d{1,2}/\d{1,2}/\d{4}"
+
+    describe "setting inputType time", ->
+      beforeEach ->
+        @options.inputType = "time"
+
+      describe "without default value", ->
+        beforeEach ->
+          @create()
+
+        it "shows time input", ->
+          expect(@exists("input[type='time']")).to.be.ok
+
+        it "has proper class", ->
+          expect(@find("input[type='time']").hasClass("bootbox-input")).to.be.ok
+          expect(@find("input[type='time']").hasClass("bootbox-input-time")).to.be.ok
+
+      describe "with default value", ->
+        beforeEach ->
+          @options.value = "19:02"
+          @create()
+
+        it "has correct default value", ->
+          expect(@find("input[type='time']").val()).to.equal "19:02"
+
+      describe "with placeholder", ->
+        beforeEach ->
+          @options.placeholder = "enter the time"
+          @create()
+
+        it "has correct placeholder value", ->
+          expect(@find("input[type='time']").prop("placeholder")).to.equal "enter the time"
+
+      describe "with pattern", ->
+        beforeEach ->
+          @options.pattern = "\d{1,2}/\d{1,2}/\d{4}"
+          @create()
+
+        it "has correct pattern value", ->
+          expect(@find("input[type='time']").prop("pattern")).to.equal "\d{1,2}/\d{1,2}/\d{4}"
+
+    describe "setting inputType number", ->
+      beforeEach ->
+        @options.inputType = "number"
+
+      describe "without default value", ->
+        beforeEach ->
+          @create()
+
+        it "shows number input ", ->
+          expect(@exists("input[type='number']")).to.be.ok
+
+        it "has proper class", ->
+          expect(@find("input[type='number']").hasClass("bootbox-input")).to.be.ok
+          expect(@find("input[type='number']").hasClass("bootbox-input-number")).to.be.ok
+
+      describe "with default value", ->
+        beforeEach ->
+          @options.value = "300"
+          @create()
+
+        it "has correct default value", ->
+          expect(@find("input[type='number']").val()).to.equal "300"
+
+      describe "with placeholder", ->
+        beforeEach ->
+          @options.placeholder = "enter the number"
+          @create()
+
+        it "has correct placeholder value", ->
+          expect(@find("input[type='number']").prop("placeholder")).to.equal "enter the number"
+
+  describe "callback tests", ->
+    describe "with a simple callback", ->
+      beforeEach ->
+        @callback = sinon.spy()
+
+        @dialog = bootbox.prompt
+          title: "What is your name?"
+          callback: @callback
+
+        @hidden = sinon.spy @dialog, "modal"
+
+      describe "when entering no value in the text input", ->
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly ""
+
+          it "should hide the modal", ->
+            expect(@hidden).to.have.been.calledWithExactly "hide"
+
+        describe "when submitting the form", ->
+          beforeEach ->
+            @dialog.find(".bootbox-form").trigger "submit"
+
+          it "invokes the callback with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly ""
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "should hide the modal", ->
+            expect(@hidden).to.have.been.calledWithExactly "hide"
+
+      describe "when entering a value in the text input", ->
+        beforeEach ->
+          @dialog.find(".bootbox-input").val "Test input"
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly "Test input"
+
+          it "should hide the modal", ->
+            expect(@hidden).to.have.been.calledWithExactly "hide"
+
+        describe "when submitting the form", ->
+          beforeEach ->
+            @dialog.find(".bootbox-form").trigger "submit"
+
+          it "invokes the callback with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly "Test input"
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "should hide the modal", ->
+            expect(@hidden).to.have.been.calledWithExactly "hide"
+
+      describe "when dismissing the dialog by clicking Cancel", ->
+        beforeEach ->
+          @dialog.find(".btn-default").trigger "click"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly null
+
+        it "should hide the modal", ->
+          expect(@hidden).to.have.been.calledWithExactly "hide"
+
+      describe "when triggering the escape event", ->
+        beforeEach ->
+          @dialog.trigger "escape.close.bb"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly null
+
+        it "should hide the modal", ->
+          expect(@hidden).to.have.been.calledWithExactly "hide"
+
+      describe "when dismissing the dialog by clicking the close button", ->
+        beforeEach ->
+          @dialog.find(".close").trigger "click"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly null
+
+        it "should hide the modal", ->
+          expect(@hidden).to.have.been.calledWithExactly "hide"
+
+    describe "with a callback which returns false", ->
+      beforeEach ->
+        @callback = sinon.stub()
+        @callback.returns false
+
+        @dialog = bootbox.prompt
+          title: "What is your name?"
+          callback: @callback
+
+        @hidden = sinon.spy @dialog, "modal"
+
+      describe "when entering no value in the text input", ->
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly ""
+
+          it "should not hide the modal", ->
+            expect(@hidden).not.to.have.been.called
+
+      describe "when entering a value in the text input", ->
+        beforeEach ->
+          @dialog.find(".bootbox-input").val "Test input"
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly "Test input"
+
+          it "should not hide the modal", ->
+            expect(@hidden).not.to.have.been.called
+
+      describe "when dismissing the dialog by clicking Cancel", ->
+        beforeEach ->
+          @dialog.find(".btn-default").trigger "click"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly null
+
+        it "should not hide the modal", ->
+          expect(@hidden).not.to.have.been.called
+
+      describe "when triggering the escape event", ->
+        beforeEach ->
+          @dialog.trigger "escape.close.bb"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly null
+
+        it "should not hide the modal", ->
+          expect(@hidden).not.to.have.been.called
+
+      describe "when dismissing the dialog by clicking the close button", ->
+        beforeEach ->
+          @dialog.find(".close").trigger "click"
+
+        it "should invoke the callback", ->
+          expect(@callback).to.have.been.called
+
+        it "should pass the dialog as `this`", ->
+          expect(@callback.thisValues[0]).to.equal @dialog
+
+        it "with the correct value", ->
+          expect(@callback).to.have.been.calledWithExactly null
+
+        it "should not hide the modal", ->
+          expect(@hidden).not.to.have.been.called
+
+    describe "with a default value", ->
+      beforeEach ->
+        @callback = sinon.spy()
+
+        @dialog = bootbox.prompt
+          title: "What is your name?"
+          value: "Bob"
+          callback: @callback
+
+        @hidden = sinon.spy @dialog, "modal"
+
+      it "populates the input with the default value", ->
+        expect(@dialog.find(".bootbox-input").val()).to.equal "Bob"
+
+      describe "when entering no value in the text input", ->
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly "Bob"
+
+        describe "when dismissing the dialog by clicking Cancel", ->
+          beforeEach ->
+            @dialog.find(".btn-default").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly null
+
+      describe "when entering a value in the text input", ->
+        beforeEach ->
+          @dialog.find(".bootbox-input").val "Alice"
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly "Alice"
+
+        describe "when dismissing the dialog by clicking Cancel", ->
+          beforeEach ->
+            @dialog.find(".btn-default").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly null
+
+    describe "with a placeholder", ->
+      beforeEach ->
+        @callback = sinon.spy()
+
+        @dialog = bootbox.prompt
+          title: "What is your name?"
+          placeholder: "e.g. Bob Smith"
+          callback: -> true
+
+      it "populates the input with the placeholder attribute", ->
+        expect(@dialog.find(".bootbox-input").attr("placeholder")).to.equal "e.g. Bob Smith"
+
+    describe "with inputType select", ->
+      describe "without a default value", ->
+        beforeEach ->
+          @callback = sinon.spy()
+
+          @dialog = bootbox.prompt
+            title: "What is your IDE?"
+            callback: @callback
+            inputType: "select"
+            inputOptions: [
+              {value: '#', text: 'Choose one'},
+              {value: 1, text: 'Vim'},
+              {value: 2, text: 'Sublime Text'},
+              {value: 3, text: 'WebStorm/PhpStorm'},
+              {value: 4, text: 'Komodo IDE'},
+            ]
+
+          @hidden = sinon.spy @dialog, "modal"
+
+        it "has correct number values in list", ->
+          expect(@find(".bootbox-input-select option").length).to.equal 5
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly "#"
+
+        describe "when dismissing the dialog by clicking Cancel", ->
+          beforeEach ->
+            @dialog.find(".btn-default").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly null
+
+      describe "with a default value", ->
+        beforeEach ->
+          @callback = sinon.spy()
+
+          @dialog = bootbox.prompt
+            title: "What is your IDE?"
+            callback: @callback
+            value: 1
+            inputType: "select"
+            inputOptions: [
+              {value: '#', text: 'Choose one'},
+              {value: 1, text: 'Vim'},
+              {value: 2, text: 'Sublime Text'},
+              {value: 3, text: 'WebStorm/PhpStorm'},
+              {value: 4, text: 'Komodo IDE'},
+            ]
+
+          @hidden = sinon.spy @dialog, "modal"
+
+        it "specified option is selected", ->
+          expect(@dialog.find(".bootbox-input-select").val()).to.equal "1"
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly "1"
+
+        describe "when dismissing the dialog by clicking Cancel", ->
+          beforeEach ->
+            @dialog.find(".btn-default").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly null
+
+        describe "when changing the selected option and dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".bootbox-input-select").val(3)
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly "3"
+
+    describe "with inputType email", ->
+      describe "without a default value", ->
+        beforeEach ->
+          @callback = sinon.spy()
+
+          @dialog = bootbox.prompt
+            title: "What is your email?"
+            inputType: "email"
+            callback: @callback
+
+          @hidden = sinon.spy @dialog, "modal"
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "should pass the dialog as `this`", ->
+            expect(@callback.thisValues[0]).to.equal @dialog
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly ""
+
+          it "should hide the modal", ->
+            expect(@hidden).to.have.been.calledWithExactly "hide"
+
+        describe "when submitting the form", ->
+          beforeEach ->
+            @dialog.find(".bootbox-form").trigger "submit"
+
+          it "invokes the callback with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly ""
+
+          it "should hide the modal", ->
+            expect(@hidden).to.have.been.calledWithExactly "hide"
+
+        describe "when entering a value in the email input", ->
+          beforeEach ->
+            @dialog.find(".bootbox-input-email").val "john at smith.com"
+
+          describe "when dismissing the dialog by clicking OK", ->
+            beforeEach ->
+              @dialog.find(".btn-primary").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "should pass the dialog as `this`", ->
+              expect(@callback.thisValues[0]).to.equal @dialog
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly "john at smith.com"
+
+          describe "when dismissing the dialog by clicking Cancel", ->
+            beforeEach ->
+              @dialog.find(".btn-default").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly null
+
+      describe "with a default value", ->
+        beforeEach ->
+          @callback = sinon.spy()
+
+          @dialog = bootbox.prompt
+            title: "What is your email?"
+            inputType: "email"
+            value: "john at smith.com"
+            callback: @callback
+
+          @hidden = sinon.spy @dialog, "modal"
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly "john at smith.com"
+
+          it "should hide the modal", ->
+            expect(@hidden).to.have.been.calledWithExactly "hide"
+
+        describe "when submitting the form", ->
+          beforeEach ->
+            @dialog.find(".bootbox-form").trigger "submit"
+
+          it "invokes the callback with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly "john at smith.com"
+
+          it "should hide the modal", ->
+            expect(@hidden).to.have.been.calledWithExactly "hide"
+
+        describe "when changing a value in the email input", ->
+          beforeEach ->
+            @dialog.find(".bootbox-input-email").val "smith at john.com"
+
+          describe "when dismissing the dialog by clicking OK", ->
+            beforeEach ->
+              @dialog.find(".btn-primary").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly "smith at john.com"
+
+          describe "when dismissing the dialog by clicking Cancel", ->
+            beforeEach ->
+              @dialog.find(".btn-default").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly null
+
+    describe "with input type checkbox", ->
+      describe "without a default value", ->
+        beforeEach ->
+          @callback = sinon.spy()
+
+          @dialog = bootbox.prompt
+            title: "What is your IDE?"
+            inputType: 'checkbox'
+            inputOptions: [
+              {value: 1, text: 'Vim'},
+              {value: 2, text: 'Sublime Text'},
+              {value: 3, text: 'WebStorm/PhpStorm'},
+              {value: 4, text: 'Komodo IDE'},
+            ]
+            callback: @callback
+
+          @hidden = sinon.spy @dialog, "modal"
+
+        describe "when dismissing the dialog by clicking OK", ->
+          beforeEach ->
+            @dialog.find(".btn-primary").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "with an undefined value", ->
+            expect(@callback).to.have.been.calledWithExactly []
+
+          it "should hide the modal", ->
+            expect(@hidden).to.have.been.calledWithExactly "hide"
+
+        describe "when dismissing the dialog by clicking Cancel", ->
+          beforeEach ->
+            @dialog.find(".btn-default").trigger "click"
+
+          it "should invoke the callback", ->
+            expect(@callback).to.have.been.called
+
+          it "with the correct value", ->
+            expect(@callback).to.have.been.calledWithExactly null
+
+      describe "with default value", ->
+        describe "one value checked", ->
+          beforeEach ->
+            @callback = sinon.spy()
+
+            @dialog = bootbox.prompt
+              title: "What is your IDE?"
+              callback: @callback
+              value: 2
+              inputType: "checkbox"
+              inputOptions: [
+                {value: 1, text: 'Vim'},
+                {value: 2, text: 'Sublime Text'},
+                {value: 3, text: 'WebStorm/PhpStorm'},
+                {value: 4, text: 'Komodo IDE'},
+              ]
+
+            @hidden = sinon.spy @dialog, "modal"
+
+          it "specified checkbox is checked", ->
+            expect(@dialog.find("input:checkbox:checked").val()).to.equal "2"
+
+          describe "when dismissing the dialog by clicking OK", ->
+            beforeEach ->
+              @dialog.find(".btn-primary").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly ["2"]
+
+          describe "when dismissing the dialog by clicking Cancel", ->
+            beforeEach ->
+              @dialog.find(".btn-default").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly null
+
+          describe "when changing the checked option and dismissing the dialog by clicking Cancel", ->
+            beforeEach ->
+              @dialog.find("input:checkbox:checked").prop('checked', false)
+              @dialog.find("input:checkbox[value=3]").prop('checked', true)
+              @dialog.find(".btn-default").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly null
+
+          describe "when changing the selected option and dismissing the dialog by clicking OK", ->
+            beforeEach ->
+              @dialog.find("input:checkbox:checked").prop('checked', false)
+              @dialog.find("input:checkbox[value=3]").prop('checked', true)
+              @dialog.find(".btn-primary").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly ["3"]
+
+        describe "multiple value checked", ->
+          beforeEach ->
+            @callback = sinon.spy()
+
+            @dialog = bootbox.prompt
+              title: "What is your IDE?"
+              callback: @callback
+              value: [2, 3]
+              inputType: "checkbox"
+              inputOptions: [
+                {value: 1, text: 'Vim'}
+                {value: 2, text: 'Sublime Text'}
+                {value: 3, text: 'WebStorm/PhpStorm'}
+                {value: 4, text: 'Komodo IDE'}
+              ]
+
+            @hidden = sinon.spy @dialog, "modal"
+
+          it "specified checkboxes are checked", ->
+            checked = []
+
+            @dialog.find("input:checkbox:checked").each (foo, bar) =>
+              checked.push $(bar).val()
+
+            expect(checked).to.deep.equal ["2", "3"]
+
+          describe "when dismissing the dialog by clicking OK", ->
+            beforeEach ->
+              @dialog.find(".btn-primary").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly ["2", "3"]
+
+          describe "when dismissing the dialog by clicking Cancel", ->
+            beforeEach ->
+              @dialog.find(".btn-default").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly null
+
+          describe "when changing the checked options and dismissing the dialog by clicking Cancel", ->
+            beforeEach ->
+              @dialog.find("input:checkbox:checked").prop('checked', false)
+              @dialog.find("input:checkbox[value=1]").prop('checked', true)
+              @dialog.find("input:checkbox[value=4]").prop('checked', true)
+              @dialog.find(".btn-default").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly null
+
+          describe "when changing the checked options and dismissing the dialog by clicking OK", ->
+            beforeEach ->
+              @dialog.find("input:checkbox:checked").prop('checked', false)
+              @dialog.find("input:checkbox[value=1]").prop('checked', true)
+              @dialog.find("input:checkbox[value=4]").prop('checked', true)
+              @dialog.find(".btn-primary").trigger "click"
+
+            it "should invoke the callback", ->
+              expect(@callback).to.have.been.called
+
+            it "with the correct value", ->
+              expect(@callback).to.have.been.calledWithExactly ["1", "4"]
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/libjs-bootbox.git



More information about the Pkg-javascript-commits mailing list