Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added support for `alert`, `confirm` and `prompt`.

  • Loading branch information...
commit 17842a8784cde7c7ac32e34e9ac6e63bd310dbe0 1 parent bba4278
@assaf authored
View
6 CHANGELOG.md
@@ -13,8 +13,10 @@ Added `window.title` accessor (Bob Lail).
Fixed `window.navigator.userAgent` to return `userAgent` property (same
as sent to server) (Assaf Arkin).
- 241 Tests
- 3.4 sec to complete
+Added support for `alert`, `confirm` and `prompt` (Assaf Arkin).
+
+ 254 Tests
+ 3.6 sec to complete
View
2  TODO.md
@@ -24,8 +24,6 @@ zombie.js-todo(7) -- Wishlist
* Time and timezone: within window context, new Date() should use browser clock
and timezone; allow changing browser timezone and default to system's.
-* Prompts: handle window.confirm and window.alert.
-
* Accessors for window.status.
* Accessor for HTTP status from last request, also pass back to visit method callback
View
45 doc/API.md
@@ -362,6 +362,51 @@ Returns session Storage based on the document origin (hostname/port).
See `localStorage` above.
+## Interaction
+
+### browser.onalert(fn)
+
+Called by `window.alert` with the message. If you just want to know if
+an alert was shown, you can also use `prompted` (see below).
+
+### browser.onconfirm(question, response)
+### browser.onconfirm(fn)
+
+The first form specifies a canned response to return when
+`window.confirm` is called with that question. The second form will
+call the function with the question and use the respone of the first
+function to return a value (true or false).
+
+The response to the question can be true or false, so all canned
+responses are converted to either value. If no response available,
+returns false.
+
+For example:
+
+ browser.onconfirm "Are you sure?", true
+
+### browser.onprompt(message, response)
+### browser.onprompt(fn)
+
+The first form specifies a canned response to return when
+`window.prompt` is called with that message. The second form will call
+the function with the message and default value and use the response of
+the first function to return a value or false.
+
+The response to a prompt can be any value (converted to a string), false
+to indicate the user cancelled the prompt (returning null), or nothing
+to have the prompt return the default value or an empty string.
+
+For example:
+
+ browser.onprompt (message)-> Math.random()
+
+### browser.prompted(message) => boolean
+
+Returns true if user was prompted with that message by a previous call
+to `window.alert`, `window.confirm` or `window.prompt`.
+
+
## Events
Since events may execute asynchronously (e.g. XHR requests, timers), the
View
64 spec/browser-spec.coffee
@@ -82,6 +82,29 @@ brains.get "/soup", (req, res)-> res.send """
brains.get "/useragent", (req, res)-> res.send "<body>#{req.headers["user-agent"]}</body>"
+brains.get "/alert", (req, res)-> res.send """
+ <script>
+ alert("Hi");
+ alert("Me again");
+ </script>
+ """
+
+brains.get "/confirm", (req, res)-> res.send """
+ <script>
+ window.first = confirm("continue?");
+ window.second = confirm("more?");
+ window.third = confirm("silent?");
+ </script>
+ """
+
+brains.get "/prompt", (req, res)-> res.send """
+ <script>
+ window.first = prompt("age");
+ window.second = prompt("gender");
+ window.third = prompt("location");
+ window.fourth = prompt("weight");
+ </script>
+ """
vows.describe("Browser").addBatch(
@@ -181,5 +204,46 @@ vows.describe("Browser").addBatch(
"should set the document's title": (browser)->
browser.window.title = "Overwritten"
assert.equal browser.window.title, browser.document.title
+
+ "window.alert":
+ topic: ->
+ browser = new zombie.Browser
+ browser.onalert (message)-> browser.window.first = true if message = "Me again"
+ browser.wants "http://localhost:3003/alert", @callback
+ "should record last alert show to user": (browser)-> assert.ok browser.prompted("Me again")
+ "should call onalert function with message": (browser)-> assert.ok browser.window.first
+
+ "window.confirm":
+ topic: ->
+ browser = new zombie.Browser
+ browser.onconfirm "continue?", true
+ browser.onconfirm (prompt)-> true if prompt == "more?"
+ browser.wants "http://localhost:3003/confirm", @callback
+ "should return canned response": (browser)-> assert.ok browser.window.first
+ "should return response from function": (browser)-> assert.ok browser.window.second
+ "should return false if no response/function": (browser)-> assert.equal browser.window.third, false
+ "should report prompted question": (browser)->
+ assert.ok browser.prompted("continue?")
+ assert.ok browser.prompted("silent?")
+ assert.ok !browser.prompted("missing?")
+
+ "window.prompt":
+ topic: ->
+ browser = new zombie.Browser
+ browser.onprompt "age", 31
+ browser.onprompt (message, def)-> "unknown" if message == "gender"
+ browser.onprompt "location", false
+ browser.wants "http://localhost:3003/prompt", @callback
+ "should return canned response": (browser)-> assert.equal browser.window.first, "31"
+ "should return response from function": (browser)-> assert.equal browser.window.second, "unknown"
+ "should return null if cancelled": (browser)-> assert.isNull browser.window.third
+ "should return empty string if no response/function": (browser)-> assert.equal browser.window.fourth, ""
+ "should report prompts": (browser)->
+ assert.ok browser.prompted("age")
+ assert.ok browser.prompted("gender")
+ assert.ok browser.prompted("location")
+ assert.ok !browser.prompted("not asked")
+
+
).export(module)
View
72 src/hellow.cc
@@ -0,0 +1,72 @@
+/* This code is PUBLIC DOMAIN, and is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND. See the accompanying
+ * LICENSE file.
+ */
+
+#include <v8.h>
+#include <node.h>
+
+using namespace node;
+using namespace v8;
+
+class HelloWorld: ObjectWrap
+{
+private:
+ int m_count;
+public:
+
+ static Persistent<FunctionTemplate> s_ct;
+ static void Init(Handle<Object> target)
+ {
+ HandleScope scope;
+
+ Local<FunctionTemplate> t = FunctionTemplate::New(New);
+
+ s_ct = Persistent<FunctionTemplate>::New(t);
+ s_ct->InstanceTemplate()->SetInternalFieldCount(1);
+ s_ct->SetClassName(String::NewSymbol("HelloWorld"));
+
+ NODE_SET_PROTOTYPE_METHOD(s_ct, "hello", Hello);
+
+ target->Set(String::NewSymbol("HelloWorld"),
+ s_ct->GetFunction());
+ }
+
+ HelloWorld() :
+ m_count(0)
+ {
+ }
+
+ ~HelloWorld()
+ {
+ }
+
+ static Handle<Value> New(const Arguments& args)
+ {
+ HandleScope scope;
+ HelloWorld* hw = new HelloWorld();
+ hw->Wrap(args.This());
+ return args.This();
+ }
+
+ static Handle<Value> Hello(const Arguments& args)
+ {
+ HandleScope scope;
+ HelloWorld* hw = ObjectWrap::Unwrap<HelloWorld>(args.This());
+ hw->m_count++;
+ Local<String> result = String::New("Hello World");
+ return scope.Close(result);
+ }
+
+};
+
+Persistent<FunctionTemplate> HelloWorld::s_ct;
+
+extern "C" {
+ static void init (Handle<Object> target)
+ {
+ HelloWorld::Init(target);
+ }
+
+ NODE_MODULE(helloworld, init);
+}
View
44 src/zombie/browser.coffee
@@ -16,6 +16,7 @@ class Browser extends require("events").EventEmitter
storage = require("./storage").use(this)
eventloop = require("./eventloop").use(this)
history = require("./history").use(this)
+ interact = require("./interact").use(this)
xhr = require("./xhr").use(this)
@@ -77,6 +78,7 @@ class Browser extends require("events").EventEmitter
storage.extend window
eventloop.extend window
history.extend window
+ interact.extend window
xhr.extend window
window.JSON = JSON
# Default onerror handler.
@@ -584,6 +586,48 @@ class Browser extends require("events").EventEmitter
window._vars = ([n,v] for n, v of context).filter((v)-> !window[v[0]])
+ # Interaction
+ # -----------
+
+ # ### browser.onalert(fn)
+ #
+ # Called by `window.alert` with the message.
+ this.onalert = (fn)-> interact.onalert fn
+
+ # ### browser.onconfirm(question, response)
+ # ### browser.onconfirm(fn)
+ #
+ # The first form specifies a canned response to return when
+ # `window.confirm` is called with that question. The second form
+ # will call the function with the question and use the respone of
+ # the first function to return a value (true or false).
+ #
+ # The response to the question can be true or false, so all canned
+ # responses are converted to either value. If no response
+ # available, returns false.
+ this.onconfirm = (question, response)-> interact.onconfirm question, response
+
+ # ### browser.onprompt(message, response)
+ # ### browser.onprompt(fn)
+ #
+ # The first form specifies a canned response to return when
+ # `window.prompt` is called with that message. The second form will
+ # call the function with the message and default value and use the
+ # response of the first function to return a value or false.
+ #
+ # The response to a prompt can be any value (converted to a string),
+ # false to indicate the user cancelled the prompt (returning null),
+ # or nothing to have the prompt return the default value or an empty
+ # string.
+ this.onprompt = (message, response)-> interact.onprompt message, response
+
+ # ### browser.prompted(message) => boolean
+ #
+ # Returns true if user was prompted with that message
+ # (`window.alert`, `window.confirm` or `window.prompt`)
+ this.prompted = (message)-> interact.prompted(message)
+
+
# Debugging
# ---------
View
61 src/zombie/interact.coffee
@@ -0,0 +1,61 @@
+class Interaction
+ constructor: (browser)->
+ # Collects all prompts (alert, confirm, prompt).
+ prompts = []
+
+ alertFns = []
+ # When alert displayed to user, call this function.
+ this.onalert = (fn)-> alertFns.push fn
+
+ confirmFns = []
+ confirmCanned = {}
+ # When prompted with a question, return the response. First argument
+ # may be a function.
+ this.onconfirm = (question, response)->
+ if typeof question == "function"
+ confirmFns.push question
+ else
+ confirmCanned[question] = !!response
+
+ promptFns = []
+ promptCanned = {}
+ # When prompted with message, return response or null if response is
+ # false. First argument may be a function.
+ this.onprompt = (message, response)->
+ if typeof message == "function"
+ promptFns.push message
+ else
+ promptCanned[message] = response
+
+ this.prompted = (message)-> prompts.indexOf(message) >= 0
+
+ this.extend = (window)->
+ # Implements window.alert: show message.
+ window.alert = (message)->
+ prompts.push message
+ fn message for fn in alertFns
+ return
+ # Implements window.confirm: show question and return true/false.
+ window.confirm = (question)->
+ prompts.push question
+ response = confirmCanned[question]
+ unless response || response == false
+ for fn in confirmFns
+ response = fn(question)
+ break if response || response == false
+ return !!response
+ # Implements window.prompt: show message and return value of null.
+ window.prompt = (message, def)->
+ prompts.push message
+ response = promptCanned[message]
+ unless response || response == false
+ for fn in promptFns
+ response = fn(message, def)
+ break if response || response == false
+ return response.toString() if response
+ return null if response == false
+ return def || ""
+
+
+exports.use = (browser)->
+ return new Interaction(browser)
Please sign in to comment.
Something went wrong with that request. Please try again.