diff --git a/lib/capybara/poltergeist/client/agent.coffee b/lib/capybara/poltergeist/client/agent.coffee index 391218d..1081546 100644 --- a/lib/capybara/poltergeist/client/agent.coffee +++ b/lib/capybara/poltergeist/client/agent.coffee @@ -235,6 +235,16 @@ class PoltergeistAgent.Node isDisabled: -> @element.disabled || @element.tagName == 'OPTION' && @element.parentNode.disabled + containsSelection: -> + selectedNode = document.getSelection().focusNode + + return false if !selectedNode + + if selectedNode.nodeType == 3 + selectedNode = selectedNode.parentNode + + @element.contains(selectedNode) + frameOffset: -> win = window offset = { top: 0, left: 0 } diff --git a/lib/capybara/poltergeist/client/browser.coffee b/lib/capybara/poltergeist/client/browser.coffee index 984e0c3..7ecb1cc 100644 --- a/lib/capybara/poltergeist/client/browser.coffee +++ b/lib/capybara/poltergeist/client/browser.coffee @@ -262,9 +262,13 @@ class Poltergeist.Browser this.sendResponse(true) send_keys: (page_id, id, keys) -> + target = this.node(page_id, id) + # Programmatically generated focus doesn't work for `sendKeys`. # That's why we need something more realistic like user behavior. - this.node(page_id, id).mouseEvent('click') + if !target.containsSelection() + target.mouseEvent('click') + for sequence in keys key = if sequence.key? then @page.native.event.key[sequence.key] else sequence @page.sendEvent('keypress', key) diff --git a/lib/capybara/poltergeist/client/compiled/agent.js b/lib/capybara/poltergeist/client/compiled/agent.js index 7a21478..e007526 100644 --- a/lib/capybara/poltergeist/client/compiled/agent.js +++ b/lib/capybara/poltergeist/client/compiled/agent.js @@ -365,6 +365,18 @@ PoltergeistAgent.Node = (function() { return this.element.disabled || this.element.tagName === 'OPTION' && this.element.parentNode.disabled; }; + Node.prototype.containsSelection = function() { + var selectedNode; + selectedNode = document.getSelection().focusNode; + if (!selectedNode) { + return false; + } + if (selectedNode.nodeType === 3) { + selectedNode = selectedNode.parentNode; + } + return this.element.contains(selectedNode); + }; + Node.prototype.frameOffset = function() { var offset, rect, style, win; win = window; diff --git a/lib/capybara/poltergeist/client/compiled/browser.js b/lib/capybara/poltergeist/client/compiled/browser.js index 141cd9a..d31ce15 100644 --- a/lib/capybara/poltergeist/client/compiled/browser.js +++ b/lib/capybara/poltergeist/client/compiled/browser.js @@ -351,8 +351,11 @@ Poltergeist.Browser = (function() { }; Browser.prototype.send_keys = function(page_id, id, keys) { - var key, sequence, _i, _len; - this.node(page_id, id).mouseEvent('click'); + var key, sequence, target, _i, _len; + target = this.node(page_id, id); + if (!target.containsSelection()) { + target.mouseEvent('click'); + } for (_i = 0, _len = keys.length; _i < _len; _i++) { sequence = keys[_i]; key = sequence.key != null ? this.page["native"].event.key[sequence.key] : sequence; diff --git a/lib/capybara/poltergeist/client/compiled/node.js b/lib/capybara/poltergeist/client/compiled/node.js index 37611b0..7665dbf 100644 --- a/lib/capybara/poltergeist/client/compiled/node.js +++ b/lib/capybara/poltergeist/client/compiled/node.js @@ -3,7 +3,7 @@ var __slice = [].slice; Poltergeist.Node = (function() { var name, _fn, _i, _len, _ref; - Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes', 'isVisible', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText']; + Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes', 'isVisible', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection']; function Node(page, id) { this.page = page; diff --git a/lib/capybara/poltergeist/client/node.coffee b/lib/capybara/poltergeist/client/node.coffee index c354708..7aa722c 100644 --- a/lib/capybara/poltergeist/client/node.coffee +++ b/lib/capybara/poltergeist/client/node.coffee @@ -4,7 +4,7 @@ class Poltergeist.Node @DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes', 'isVisible', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest', - 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText'] + 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection'] constructor: (@page, @id) -> diff --git a/spec/integration/driver_spec.rb b/spec/integration/driver_spec.rb index 4f60bd5..830bd66 100644 --- a/spec/integration/driver_spec.rb +++ b/spec/integration/driver_spec.rb @@ -779,6 +779,16 @@ module Capybara::Poltergeist expect(input.text).to eq('Input') end + it 'persists focus across calls' do + input = @session.find(:css, '#empty_div') + + input.native.send_keys('helo') + input.native.send_keys(:Left) + input.native.send_keys('l') + + expect(input.text).to eq('hello') + end + it 'sends keys to filled contenteditable div' do input = @session.find(:css, '#filled_div')