Skip to content

Commit

Permalink
Fire focus events when filling fields, selecting options, pressing bu…
Browse files Browse the repository at this point in the history
…ttons, etc.
  • Loading branch information
assaf committed May 25, 2012
1 parent 3f60fcb commit 3a69400
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 26 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ zombie.js-changelog(7) -- Changelog

Tweak to in-line script processing to fix a problem no one reported.

Fire `focus` and `blur` events when switching windows.
Fire `focus` and `blur` events when filling fields, selecting fields, pressing
button and switching windows.


## Version 1.1.5 2012-05-24
Expand Down
5 changes: 5 additions & 0 deletions doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,11 @@ Unselects the option (an `OPTION` element).
Returns this so you can chain multiple methods.


### browser.focused : element

Returns the element in focus.


## State Management

The browser maintains state as you navigate from one page to another. Zombie.js
Expand Down
53 changes: 32 additions & 21 deletions lib/zombie/browser.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ class Browser extends EventEmitter
throw new Error("This INPUT field is disabled")
if field.getAttribute("readonly")
throw new Error("This INPUT field is readonly")
@window._focused = field
field.value = value
@fire "change", field, callback
return this
Expand Down Expand Up @@ -589,27 +590,6 @@ class Browser extends EventEmitter
return option
throw new Error("No OPTION '#{value}'")

# ### browser.attach(selector, filename, callback) => this
#
# Attaches a file to the specified input field. The second argument is the file name.
#
# Without callback, returns this.
attach: (selector, filename, callback)->
field = @field(selector)
unless field && field.tagName == "INPUT" && field.type == "file"
throw new Error("No file INPUT matching '#{selector}'")
if filename
stat = FS.statSync(filename)
file = new (@window.File)()
file.name = Path.basename(filename)
file.type = Mime.lookup(filename)
file.size = stat.size
field.files ||= []
field.files.push file
field.value = filename
@fire "change", field, callback
return this

# ### browser.select(selector, value, callback) => this
#
# Selects an option.
Expand All @@ -633,6 +613,7 @@ class Browser extends EventEmitter
if option && !option.getAttribute("selected")
select = @xpath("./ancestor::select", option).value[0]
option.setAttribute("selected", "selected")
@window._focused = select
@fire "change", select, callback
else if callback
process.nextTick ->
Expand Down Expand Up @@ -664,12 +645,35 @@ class Browser extends EventEmitter
unless select.multiple
throw new Error("Cannot unselect in single select")
option.removeAttribute("selected")
@window._focused = select
@fire "change", select, callback
else if callback
process.nextTick ->
callback null, false
return this

# ### browser.attach(selector, filename, callback) => this
#
# Attaches a file to the specified input field. The second argument is the file name.
#
# Without callback, returns this.
attach: (selector, filename, callback)->
field = @field(selector)
unless field && field.tagName == "INPUT" && field.type == "file"
throw new Error("No file INPUT matching '#{selector}'")
if filename
stat = FS.statSync(filename)
file = new (@window.File)()
file.name = Path.basename(filename)
file.type = Mime.lookup(filename)
file.size = stat.size
field.files ||= []
field.files.push file
field.value = filename
@window._focused = field
@fire "change", field, callback
return this

# ### browser.button(selector) : Element
#
# Finds a button using CSS selector, button name or button text (`BUTTON` or `INPUT` element).
Expand Down Expand Up @@ -699,8 +703,15 @@ class Browser extends EventEmitter
throw new Error("No BUTTON '#{selector}'")
if button.getAttribute("disabled")
throw new Error("This button is disabled")
@window._focused = button
return @fire("click", button, callback)

# ### browser.focused => element
#
# Returns the element in focus.
focused: ->
return @window._focused


# Cookies and storage
# -------------------
Expand Down
7 changes: 5 additions & 2 deletions lib/zombie/forms.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ HTML.HTMLInputElement.prototype.click = ->
click = =>
event = @ownerDocument.createEvent("HTMLEvents")
event.initEvent "click", true, true
return !@ownerDocument.parentWindow.browser.dispatchEvent(this, event)
cancelled = @ownerDocument.parentWindow.browser.dispatchEvent(this, event)
unless cancelled
@ownerDocument.parentWindow._focused = this
return !cancelled

# If that works out, we follow with a change event
change = =>
Expand Down Expand Up @@ -158,7 +161,7 @@ HTML.HTMLInputElement.prototype.click = ->
change()
else
for radio in radios
radio.checked = radio == checked
radio.checked = (radio == checked)
else
click()
else
Expand Down
32 changes: 32 additions & 0 deletions lib/zombie/windows.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,38 @@ class Windows
process.nextTick ->
eventloop.dispatch window, event

# -- Focusing --

# Handle change to in-focus element
focused = null
Object.defineProperty window, "_focused",
get: ->
return focused
set: (element)->
unless element == focused
if focused
onblur = window.document.createEvent("HTMLEvents")
onblur.initEvent "blur", false, false
previous = focused
previous.dispatchEvent onblur
if element
onfocus = window.document.createEvent("HTMLEvents")
onfocus.initEvent "focus", false, false
element.dispatchEvent onfocus
focused = element

# If window goes in/out of focus, notify focused input field
window.addEventListener "focus", (event)->
if window._focused
onfocus = window.document.createEvent("HTMLEvents")
onfocus.initEvent "focus", false, false
window._focused.dispatchEvent onfocus
window.addEventListener "blur", (event)->
if window._focused
onblur = window.document.createEvent("HTMLEvents")
onblur.initEvent "blur", false, false
window._focused.dispatchEvent onblur

# -- JavaScript evaluation

# Evaulate in context of window. This can be called with a script (String) or a function.
Expand Down
104 changes: 102 additions & 2 deletions test/forms_test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,30 @@ describe "Forms", ->
assert.throws ->
@browser.fill @browser.querySelector("#readonly_input_field"), "yeahh"

describe "should callback", ->
before (done)->
@browser.fill @browser.querySelector("#field-email3"), "headchomper@example.com", done

it "should fire the callback", ->
assert.equal @browser.querySelector("#field-email3").value, "headchomper@example.com"

describe "any field", ->
before ->
@field1 = @browser.querySelector("#field-email2")
@field2 = @browser.querySelector("#field-email3")

it "should fire focus event on selected field", (done)->
@browser.fill @field1, "something"
@field2.addEventListener "focus", ->
done()
done = null
@browser.fill @field2, "else"

it "should fire blur event on previous field", (done)->
@browser.fill @field1, "something"
@field1.addEventListener "blur", ->
done()
done = null
@browser.fill @field2, "else"


describe "check box", ->
before (done)->
Expand Down Expand Up @@ -319,6 +336,27 @@ describe "Forms", ->
it "should turn checkbox on then off", ->
assert.deepEqual @values, [false, true, false]

describe "any checkbox", ->
before ->
@field1 = @browser.querySelector("#field-check")
@field2 = @browser.querySelector("#field-uncheck")

it "should fire focus event on selected field", (done)->
@browser.uncheck @field1
@browser.check @field1
@field2.addEventListener "focus", ->
done()
done = null
@browser.check @field2

it "should fire blur event on previous field", (done)->
@browser.uncheck @field1
@browser.check @field1
@field1.addEventListener "blur", ->
done()
done = null
@browser.check @field2


describe "radio buttons", ->
before (done)->
Expand Down Expand Up @@ -380,6 +418,25 @@ describe "Forms", ->
it "should turn radio on then off", ->
assert.deepEqual @values, [false, true, false]

describe "any radio button", ->
before ->
@field1 = @browser.querySelector("#field-scary")
@field2 = @browser.querySelector("#field-notscary")

it "should fire focus event on selected field", (done)->
@browser.choose @field1
@field2.addEventListener "focus", ->
done()
done = null
@browser.choose @field2

it "should fire blur event on previous field", (done)->
@browser.choose @field1
@field1.addEventListener "blur", ->
done()
done = null
@browser.choose @field2


describe "select option", ->
before (done)->
Expand Down Expand Up @@ -447,6 +504,25 @@ describe "Forms", ->
it "should callback", ->
assert.equal @browser.querySelector("#field-kills").value, "Thousands"

describe "any selection", ->
before ->
@field1 = @browser.querySelector("#field-email2")
@field2 = @browser.querySelector("#field-kills")

it "should fire focus event on selected field", (done)->
@browser.fill @field1, "something"
@field2.addEventListener "focus", ->
done()
done = null
@browser.select @field2, "Five"

it "should fire blur event on previous field", (done)->
@browser.fill @field1, "something"
@field1.addEventListener "blur", ->
done()
done = null
@browser.select @field2, "Five"


describe "multiple select option", ->
before (done)->
Expand Down Expand Up @@ -675,6 +751,30 @@ describe "Forms", ->
it "should not send other button values to server", ->
assert.equal @browser.text("#image_clicked"), "undefined"

describe "pressButton", ->
before (done)->
@browser = new Browser()
@browser.visit("http://localhost:3003/forms/form")
.then =>
@field1 = @browser.querySelector("#field-email2")
return
.then(done, done)

it "should fire focus event on button", (done)->
@browser.fill @field1, "something"
@browser.button("Hit Me").addEventListener "focus", ->
done()
done = null
@browser.pressButton("Hit Me")

it "should fire blur event on previous field", (done)->
@browser.fill @field1, "something"
@field1.addEventListener "blur", ->
done()
done = null
@browser.pressButton("Hit Me")


describe "by clicking image button", ->
before (done)->
@browser = new Browser()
Expand Down

0 comments on commit 3a69400

Please sign in to comment.