# frozen_string_literal: true
module RSpecHTML
# Provides element/attribute/text searching for HTML entities
# rubocop:disable Metrics/ClassLength
class Search
attr_reader :siblings
def initialize(element, siblings)
@element = element
@siblings = siblings
end
def to_s
@element&.to_s
end
def include?(val)
text.include?(val)
end
def css(*args)
self.class.new(@element&.css(*args), :css)
end
def xpath(*args)
self.class.new(@element&.xpath(*args), :xpath)
end
def present?
!@element.nil?
end
alias exist? present?
# rubocop:disable Naming/PredicateName
def has_css?(*args)
!@element&.css(*args)&.empty?
end
def has_xpath?(*args)
!@element&.xpath(*args)&.empty?
end
# rubocop:enable Naming/PredicateName
def [](val)
return index(val) if val.is_a?(Integer)
return range(val) if val.is_a?(Range)
@element&.attr(val.to_s)
end
def text
@element&.text&.gsub(/\s+/, ' ')&.strip || ''
end
def truncated_text
max = RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length
return text if text.size <= max
"#{text[0..max]}...#{text[-max..-1]}"
end
def attributes
@element&.attributes&.to_h { |key, val| [key.to_sym, val.to_s] } || {}
end
def size
return @element.size if @element.respond_to?(:size)
@siblings&.size || 0
end
alias length size
def new_from_find(tag, options)
Element.new(
find(tag),
tag,
options: options,
siblings: find(tag, all: true)
)
end
def new_from_where(tag, options)
Element.new(
where(tag, options),
tag,
options: options,
siblings: where(tag, options, all: true)
)
end
private
def index(val)
zero_index_error if val.zero?
self.class.new(@siblings[val - 1], @element.name)
end
def range(val)
zero_index_error if val.first.zero?
self.class.new(@siblings[(val.first - 1)..(val.last - 1)], :range)
end
def zero_index_error
raise ArgumentError, 'Index for matched sets starts at 1, not 0.'
end
def where(tag, query, all: false)
matched = matched_from_query(tag, query)
return nil unless matched || all
return matched&.first unless all
matched
end
def matched_from_query(tag, query)
if query.is_a?(String)
@element&.css("#{tag}#{query}")
elsif query[:class]
where_class(tag, query[:class]) & where_xpath(tag, query.merge(class: nil))
else
where_xpath(tag, query)
end
end
def where_xpath(tag, query)
conditions = "[#{where_conditions(query)}]" unless query.compact.empty?
@element&.xpath("//#{tag}#{conditions}")
end
def where_conditions(query)
query.compact.map do |key, value|
next if value.nil?
next %(@#{key}="#{value}") unless key == :text
%[contains(text(),"#{value}")]
end.join ' and '
end
def where_class(tag, class_or_classes)
classes = class_or_classes.is_a?(Array) ? class_or_classes : class_or_classes.to_s.split
selector = classes.map(&:to_s).join('.')
@element&.css("#{tag}.#{selector}")
end
def find(tag, all: false)
return @element&.css(tag.to_s)&.first unless all
@element&.css(tag.to_s)
end
end
# rubocop:enable Metrics/ClassLength
end