Last active
December 20, 2015 00:09
-
-
Save kaspth/6039324 to your computer and use it in GitHub Desktop.
An attempt to absolve css_select and assert_select from their argument parsing. To make the code more declarative and stuff... The order of arguments:
0: html element (optional)
1: selector
2: comparator
3: message
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def css_select(*args) | |
# See assert_select to understand what's going on here. | |
arg = args.shift | |
if arg.is_a?(HTML::Node) | |
root = arg | |
arg = args.shift | |
elsif arg == nil | |
raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?" | |
elsif defined?(@selected) && @selected | |
matches = [] | |
@selected.each do |selected| | |
subset = css_select(selected, HTML::Selector.new(arg.dup, args.dup)) | |
subset.each do |match| | |
matches << match unless matches.any? { |m| m.equal?(match) } | |
end | |
end | |
return matches | |
else | |
root = response_from_page | |
end | |
case arg | |
when String | |
selector = HTML::Selector.new(arg, args) | |
when Array | |
selector = HTML::Selector.new(*arg) | |
when HTML::Selector | |
selector = arg | |
else raise ArgumentError, "Expecting a selector as the first argument" | |
end | |
selector.select(root) | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def css_select(*args) | |
# See assert_select to understand what's going on here. | |
parser = ArgumentsParser.new(self, args, Proc.new do |selector| | |
# will only be called if we're a nested select, i.e. @selected is set | |
matches = [] | |
@selected.each do |selected| | |
subset = css_select(selected, selector) | |
subset.each do |match| | |
matches << match unless matches.include?(match) | |
end | |
end | |
return matches | |
end) | |
parser.root.css(parser.selector) | |
end | |
def assert_select(*args, &block) | |
@selected ||= nil | |
parser = ArgumentsParser.new(self, args, Proc.new do |_| | |
root = Loofah.fragment('') | |
root.add_child @selected | |
root | |
end) | |
# Start with optional element followed by mandatory selector. | |
root = parser.root | |
# First or second argument is the selector | |
selector = parser.selector | |
# Next argument is used for equality tests. | |
equals = parser.equals | |
# Last argument is the message we use if the assertion fails. | |
message = parser.message | |
#- message = "No match made with selector #{selector.inspect}" unless message | |
matches = root.css(selector) | |
# more code which I've skipped for now... | |
end | |
protected | |
# +assert_select+ and +css_select+ call this to obtain the content in the HTML page. | |
def response_from_page | |
html_document.root | |
end | |
def html_document | |
@html_document ||= if @response.content_type =~ /xml$/ | |
Loofah.xml_fragment(@response.body) | |
else | |
Loofah.fragment(@response.body) | |
end | |
end | |
def nested_call? | |
defined(@selected) && @selected | |
end | |
class ArgumentsParser #:nodoc: | |
attr_accessor :root, :selector | |
def initialize(initiator, *args, &root_for_nested_call_proc) | |
raise ArgumentError, "ArgumentsParser expects a block for parsing a nested call's arguments" unless block_given? | |
@initiator = initiator | |
@args = args | |
# see +determine_root_from+ | |
@selector_is_second_argument = false | |
@root = determine_root_from(@args.shift) | |
arg = @selector_is_second_argument ? @args.shift : @args.first | |
@selector = selector_from(arg) | |
end | |
def determine_root_from(root_or_selector) | |
if root_or_selector.is_a?(Nokogiri::XML::Node) | |
# First argument is a node (tag or text, but also HTML root), | |
# so we know what we're selecting from, | |
# we also know that the second argument is the selector | |
@selector_is_second_argument = true | |
root_or_selector | |
elsif root_or_selector == nil | |
raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?" | |
elsif @initiator.nested_call? | |
# root_or_selector is a selector since the first call failed | |
root_for_nested_select_proc.call(root_or_selector) | |
else | |
@initiator.response_from_page | |
end | |
end | |
def selector_from(arg) | |
unless arg.is_a? String | |
raise ArgumentError, "Expecting a selector as the first argument" | |
end | |
arg | |
end | |
end | |
class HTMLArgumentsParser < ArgumentsParser | |
attr_accessor :equals, :message | |
def initialize(*) | |
super | |
@equals = assign_equals_from(@args.shift) | |
@message = @args.shift | |
if @args.shift | |
raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type" | |
end | |
end | |
def assign_equals_from(comparator) | |
equals = {} | |
case comparator | |
when Hash | |
equals = comparator | |
when String, Regexp | |
equals[:text] = comparator | |
when Integer | |
equals[:count] = comparator | |
when Range | |
equals[:minimum] = comparator.begin | |
equals[:maximum] = comparator.end | |
when FalseClass | |
equals[:count] = 0 | |
when NilClass, TrueClass | |
equals[:minimum] = 1 | |
else raise ArgumentError, "I don't understand what you're trying to match" | |
end | |
# By default we're looking for at least one match. | |
if equals[:count] | |
equals[:minimum] = equals[:maximum] = equals[:count] | |
else | |
equals[:minimum] ||= 1 | |
end | |
equals | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi @vipulnsward, could I get some feedback on this? 😄 (I know that some of it is horrible.)
I was trying to abstract the knowledge of the order of arguments away from css_select and assert_select.
Compare the old css_select and my version. If it wasn't for that proc, it would be:
Since most of it was dealing with arguments.