# coding: UTF-8

# Most of cucumber-rails' original web steps plus a few of our own.
#
# Note that cucumber-rails deprecated all its steps quite a while ago with the following
# deprecation notice. Decide for yourself whether you want to use them:
#
# > This file was generated by Cucumber-Rails and is only here to get you a head start
# > These step definitions are thin wrappers around the Capybara/Webrat API that lets you
# > visit pages, interact with widgets and make assertions about page content.
#
# > If you use these step definitions as basis for your features you will quickly end up
# > with features that are:
#
# > * Hard to maintain
# > * Verbose to read
#
# > A much better approach is to write your own higher level step definitions, following
# > the advice in the following blog posts:
#
# > * http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html
# > * http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/
# > * http://elabs.se/blog/15-you-re-cuking-it-wrong
#
# FILE_COMMENT_END

require 'spreewald_support/tolerance_for_selenium_sync_issues'
require 'spreewald_support/path_selector_fallbacks'
require 'spreewald_support/step_fallback'
require 'spreewald_support/custom_matchers'
require 'spreewald_support/web_steps_helpers'
require 'spreewald_support/driver_info'
require 'spreewald_support/comparison'
require 'spreewald_support/field_errors'
require 'uri'
require 'cgi'


# You can append `within [selector]` to any other web step, even multiple times.
# Be aware that within will only look at the first element that matches.
# If this is a problem for you following links, you might want to have a look
# at the 'When I follow "..." inside any "..."'-step.
#
# Example:
#
#     Then I should see "some text" within ".page_body"
When /^(.*) within (.*[^:])$/ do |nested_step, parent|
  selector = _selector_for(parent)
  if selector.is_a?(String) || selector.is_a?(Array) # could also be a Capybara::Node::Element
    patiently do
      args, kwargs = deconstruct_selector(selector)
      expect(page).to have_selector(*args, **kwargs)
    end
  end
  patiently do
    with_scope(parent) { step(nested_step) }
  end
end.overridable(:priority => -1)

# nodoc
When /^(.*) within (.*[^:]):$/ do |nested_step, parent, table_or_string|
  patiently do
    with_scope(parent) { step("#{nested_step}:", table_or_string) }
  end
end.overridable(:priority => -1)

Given /^(?:|I )am on (.+)$/ do |page_name|
  visit _path_to(page_name)
end.overridable

When /^(?:|I )go to (.+)$/ do |page_name|
  visit _path_to(page_name)
end.overridable

Then /^(?:|I )should be on (.+)$/ do |page_name|
  patiently do
    fragment = URI.parse(current_url).fragment
    fragment.sub!(/[#?].*/, '') if fragment # most js frameworks will usually use ? and # for params, we dont care about those
    current_path = URI.parse(current_url).path
    current_path << "##{fragment}" if fragment.present?
    expected_path = _path_to(page_name)

    # Consider two pages equal if they only differ by a trailing slash.
    current_path = expected_path if current_path.chomp("/") == expected_path.chomp("/")
    current_path = expected_path if current_path.gsub("/#", "#") == expected_path.gsub("/#", "#")

    expect(current_path).to eq(expected_path)
  end
end.overridable

When /^(?:|I )press "([^"]*)"$/ do |button|
  patiently do
    click_button(button)
  end
end.overridable

When /^(?:|I )follow "([^"]*)"$/ do |link|
  patiently do
    click_link(link)
  end
end.overridable

# Fill in text field
When /^(?:|I )fill in "([^"]*)" (?:with|for) "([^"]*)"$/ do |field, value|
  patiently do
    fill_in(field, :with => value)
  end
end.overridable

# Fill in text field with multi-line block
# You can use a doc string to supply multi-line text
#
# Example:
#
#     When I fill in "some field" with:
#     """
#     Apple
#     Banana
#     Pear
#     """
When /^(?:|I )fill in "([^"]*)" (?:with|for):$/ do |field, value|
  patiently do
    fill_in(field, :with => value)
  end
end.overridable

# Fill in text field
When /^(?:|I )fill in "([^"]*)" (?:with|for) '(.*)'$/ do |field, value|
  patiently do
    fill_in(field, :with => value)
  end
end.overridable

# Select from select box
When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field|
  patiently do
    select(value, :from => field)
  end
end.overridable

# Check a checkbox
When /^(?:|I )check "([^"]*)"$/ do |field|
  patiently do
    check(field)
  end
end.overridable

# Uncheck a checkbox
When /^(?:|I )uncheck "([^"]*)"$/ do |field|
  patiently do
    uncheck(field)
  end
end.overridable

# Select a radio button
When /^(?:|I )choose "([^"]*)"$/ do |field|
  patiently do
    choose(field)
  end
end.overridable

# Attach a file to a file upload form field
When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field|
  options = {}
  options[:make_visible] = true if javascript_capable?

  patiently do
    attach_file(field, File.expand_path(path), **options)
  end
end.overridable

# Attach multiple files to a file upload form field
#
# Example:
#
#     And attach the files ["file_1.jpg", "file_2.png" and "file_3.webp"] to "My Files"
When(/^(?:|I )attach the files \[(.+)\] to "(.+)"$/) do |path_list, field|
  paths = path_list.scan(/"([^"]+)"/).flatten

  options = { multiple: true }
  options[:make_visible] = true if javascript_capable?

  patiently do
    attach_file(field, paths.map { |path| File.expand_path(path) }, **options)
  end
end.overridable

# Checks that some text appears on the page
#
# Note that this does not detect if the text might be hidden via CSS
Then /^(?:|I )should( not)? see "([^"]*)"$/ do |negate, text|
  expectation = negate ? :not_to : :to

  patiently do
    expect(page).send(expectation, have_content(text))
  end
end.overridable

# Checks that a regexp appears on the page
#
# Note that this does not detect if the text might be hidden via CSS
Then /^(?:|I )should( not)? see \/(.*)\/$/ do |negate, regexp|
  expectation = negate ? :not_to : :to
  regexp = Regexp.new(regexp)

  patiently do
    expect(page).send(expectation, have_xpath('.//descendant-or-self::*', text: regexp))
  end
end.overridable

# Checks for the existance of an input field (given its id or label)
Then /^I should( not)? see a field "([^"]*)"$/ do |negate, name|
  if negate
    expect(page).to have_no_field(name)
  else
    patiently do
      expect(page).to have_field(name, disabled: :all)
    end
  end
end.overridable

# Use this step to test for a number or money amount instead of a simple `Then I should see`
#
# Checks for an unexpected minus sign, correct decimal places etc.
#
# See [here](https://makandracards.com/makandra/1225-test-that-a-number-or-money-amount-is-shown-with-cucumber) for details
Then /^I should( not)? see the (?:number|amount) ([\-\d,\.]+)(?: (.*?))?$/ do |negate, amount, unit|
  expect_to_match = negate ? :not_to : :to
  is_negative = amount.start_with?('-')
  absolute_amount = amount.gsub(/^-/, '')
  expect_minus = is_negative ? '-' : '(?:[^\\-]|^)'
  word_boundary = '\b'
  unit_or_boundary = if unit
    nbsp = " "
    "( |#{nbsp})(#{unit})"
  else
    word_boundary
  end

  regexp = Regexp.new(expect_minus + word_boundary + Regexp.quote(absolute_amount) + unit_or_boundary)

  patiently do
    expect(page.text).send(expect_to_match, match(regexp))
  end
end.overridable(priority: -5) # priority lower than within

# Like `Then I should see`, but with single instead of double quotes. In case
# the expected string contains quotes as well.
Then /^(?:|I )should( not)? see '([^']*)'$/ do |negate, text|
  expectation = negate ? :not_to : :to

  patiently do
    expect(page).send(expectation, have_content(text))
  end
end.overridable

# Check that the raw HTML contains a string
Then /^I should( not)? see "([^\"]*)" in the HTML$/ do |negate, text|
  expectation = negate ? :not_to : :to

  patiently do
    expect(page.body).send(expectation, include(text))
  end
end.overridable

# Checks that status code is 400..599
Then /^I should see an error$/ do
  expect((400 .. 599)).to include(page.status_code)
end.overridable

# Check that an element with the given selector is present on the page.
#
# Example:
#
#     Then I should see an element ".panel"
#     Then I should see the element ".panel"
#     Then I should not see an element ".sidebar"
#     Then I should not see the element ".sidebar"
Then /^I should( not)? see (?:an|the) element "([^"]+)"$/ do |negate, selector|
  expectation = negate ? :not_to : :to
  patiently do
    expect(page).send(expectation, have_css(selector))
  end
end.overridable

# Check that an element with the given [selector alias](https://github.com/makandra/spreewald/blob/master/examples/selectors.rb) is present on the page.
#
# Example:
#
#     Then I should see an element for the panel
#     Then I should see the element for the panel
#     Then I should not see an element for the sidebar
#     Then I should not see the element for the sidebar
Then /^I should( not)? see (?:an|the) element for (.*?)$/ do |negate, locator|
  expectation = negate ? :not_to : :to
  selector = _selector_for(locator)
  patiently do
    args, kwargs = deconstruct_selector(selector)
    expect(page).send(expectation, have_selector(*args, **kwargs))
  end
end.overridable(:priority => -5) # priority must be lower than the "within" step

# Checks that these strings are rendered in the given order in a single line or in multiple lines
#
# Example:
#
#     Then I should see in this order:
#       | Alpha Group |
#       | Augsburg    |
#       | Berlin      |
#       | Beta Group  |
Then /^I should see in this order:?$/ do |text|
  if text.is_a?(String)
    lines = text.split(/\n/)
  else
    lines = text.raw.flatten
  end
  lines = lines.collect { |line| line.gsub(/\s+/, ' ')}.collect(&:strip).reject(&:blank?)
  pattern = lines.collect(&Regexp.method(:quote)).join('.*?')
  pattern = Regexp.compile(pattern)
  patiently do
    expect(page.text.gsub(/\s+/, ' ')).to match(pattern)
  end
end.overridable

# Checks that the page contains a link with a given text or title attribute.
Then /^I should( not)? see a link labeled "([^"]*)"$/ do |negate, label|
  expectation = negate ? :not_to : :to
  link = page.first('a', :text => label, minimum: 0) || page.first(%(a[title="#{label}"]), minimum: 0)
  expect(link).send(expectation, be_present)
end.overridable

# Checks that an input field contains some value (allowing * as wildcard character)
Then /^the "([^"]*)" field should( not)? contain "([^"]*)"$/ do |label, negate, expected_string|
  patiently do
    field = find_with_disabled(:field, label)
    field_value = case field.tag_name
    when 'select'
      options = field.all('option')
      selected_option = options.detect(&:selected?) || options.first
      if selected_option && selected_option.text.present?
        selected_option.text.strip
      else
        ''
      end
    else
      field.value
    end
    expect(field_value).send(negate ? :not_to : :to, contain_with_wildcards(expected_string))
  end
end.overridable

# Checks that a multiline textarea contains some value (allowing * as wildcard character)
Then(/^the "(.*?)" field should( not)? contain:$/) do |label, negate, expected_string|
  patiently do
    field = find_with_disabled(:field, label)
    expect(field.value.chomp).send(negate ? :not_to : :to, contain_with_wildcards(expected_string))
  end
end.overridable

# Checks that a list of label/value pairs are visible as control inputs.
#
# Example:
#
#     Then I should see a form with the following values:
#       | E-mail | foo@bar.com   |
#       | Role   | Administrator |
Then /^I should see a form with the following values:$/ do |table|
  expectations = table.raw
  expectations.each do |label, expected_value|
    step %(the "#{label}" field should contain "#{expected_value}")
  end
end.overridable

# Checks that an input field was wrapped with a validation error
Then /^the "([^"]*)" field should have the error "([^"]*)"$/ do |field, error_message|
  patiently do
    element = find_with_disabled(:field, field)
    field_error_finder = Spreewald::FieldErrorFinder.new(page, element)
    expect(field_error_finder.error_present?).to eq true

    if Spreewald.field_error_class
      unless Spreewald.error_message_xpath_selector
        raise ArgumentError, 'No XPath was specified for the custom error message element.'
      end
    end

    error_message_element = case
      when Spreewald.error_message_xpath_selector
        element.find(:xpath, Spreewald.error_message_xpath_selector)
      when field_error_finder.bootstrap3_error?
        element.find(:xpath, 'parent::*/child::*[@class="help-block"]')
      when field_error_finder.bootstrap45_error?
        element.find(:xpath, 'parent::*/child::*[@class="invalid-feedback"]')
      else
        nil
    end

    if error_message_element
      expect(error_message_element).to have_content(error_message)
    else
      expect(page).to have_content("#{field.titlecase} #{error_message}")
    end
  end
end.overridable

Then /^the "([^\"]*)" field should( not)? have an error$/ do |label, negate|
  patiently do
    expectation = negate ? :not_to : :to
    element = find_with_disabled(:field, label)
    field_error_finder = Spreewald::FieldErrorFinder.new(page, element)

    expect(field_error_finder.error_present?).public_send(expectation, (eq true))
  end
end.overridable

Then /^the "([^"]*)" checkbox should( not)? be checked?$/ do |label, negate|
  expectation = negate ? :not_to : :to

  patiently do
    field = find_with_disabled(:field, label)
    expect(field).send expectation, be_checked
  end
end.overridable

Then /^the radio button "([^"]*)" should( not)? be (?:checked|selected)$/ do |field, negate|
  patiently do
    expect(page.send((negate ? :has_no_checked_field? : :has_checked_field?), field)).to eq(true)
  end
end.overridable

# Example:
#
#     I should have the following query string:
#       | locale        | de  |
#       | currency_code | EUR |
#
# Succeeds when the URL contains the given `locale` and `currency_code` params
Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
  patiently do
    query = URI.parse(current_url).query
    actual_params = query ? CGI.parse(query) : {}
    expected_params = {}
    expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}

    expect(actual_params).to eq(expected_params)
  end
end.overridable

# Open the current Capybara page using the `launchy` or `capybara_screenshot` gem
Then /^show me the page$/ do
  if defined? Capybara::Screenshot
    screenshot_and_save_page
  elsif defined? Launchy
    save_and_open_page
  else
    raise 'Neither launchy nor capybara_screenshot gem is installed. You have to add at least one of them to your Gemfile to use this step'
  end
end.overridable


# Checks `Content-Type` HTTP header
Then /^I should get a response with content-type "([^\"]*)"$/ do |expected_content_type|
  expect(page.response_headers['Content-Type']).to match /\A#{Regexp.quote(expected_content_type)}($|;)/
end.overridable

# Checks `Content-Disposition` HTTP header
#
# Attention: Doesn't work with Selenium, see https://github.com/jnicklas/capybara#gotchas
Then /^I should get a download with filename "([^\"]*)"$/ do |filename|
  content_disposition = page.response_headers['Content-Disposition']
  expect(content_disposition).to be_present

  fields = content_disposition.split(/;\s*/)

  # Rails 6+ encodes filenames as defined in https://tools.ietf.org/html/rfc5987.
  # If available, we prefer the UTF-8 filename.
  download_filename = [/^filename\*=UTF-8''(.+)/, /^filename="(.+?)"/].each do |regexp|
    matched_filename = fields.map { |field| field.scan(regexp).flatten.first }.compact.first
    break CGI.unescape(matched_filename) if matched_filename
  end
  expect(download_filename).to eq(filename)
end.overridable

# Checks that a certain option is selected for a text field
Then /^"([^"]*)" should( not)? be selected for "([^"]*)"$/ do |value, invert, field|
  step %(the "#{field}" field should#{invert} contain "#{value}")
end.overridable

Then /^nothing should be selected for "([^"]*)"$/ do |field|
  patiently do
    select = find_with_disabled(:field, field)
    begin
      selected_option = select.find(:xpath, ".//option[@selected = 'selected']") || select.all(:css, 'option').first
      value = selected_option ? selected_option.value : nil
      expect(value).to be_blank
    rescue Capybara::ElementNotFound
    end
  end
end.overridable

# Checks for the presence of an option in a select
Then /^"([^"]*)" should( not)? be an option for "([^"]*)"$/ do |value, negate, field|
  finder_arguments = if Spreewald::Comparison.compare_versions(Capybara::VERSION, :<, "2.12")
    { text: value }
  else
    { exact_text: value }
  end
  patiently do
    if negate
      begin
        expect(find_with_disabled(:field, field)).to have_no_css('option', **finder_arguments)
      rescue Capybara::ElementNotFound
      end
    else
      expect(find_with_disabled(:field, field)).to have_css('option', **finder_arguments)
    end
  end
end.overridable

Then /^the window should be titled "([^"]*)"$/ do |title|
  patiently do
    if Spreewald::Comparison.compare_versions(Capybara::VERSION, :<, "2.1")
      expect(page).to have_selector("title", :content => title)
    else
      expect(page).to have_title(title)
    end
  end
end.overridable

When /^I reload the page$/ do
  if javascript_capable?
    page.execute_script(<<-JAVASCRIPT)
        window.location.reload(true);
    JAVASCRIPT
  else
    visit current_path
  end
end.overridable

# Checks that an element is actually present and visible, also considering styles.
# Within a selenium test, the browser is asked whether the element is really visible
# In a non-selenium test, we only check for `.hidden`, `.invisible` or `style: display:none`
#
# More details [here](https://makandracards.com/makandra/1049-capybara-check-that-a-page-element-is-hidden-via-css)
Then /^(the tag )?"([^\"]+)" should be visible$/ do |tag, selector_or_text|
  options = {}
  tag ? options.store(:selector, selector_or_text) : options.store(:text, selector_or_text)

   assert_visible(options)
end.overridable

# Checks that an element is actually present and hidden, also considering styles.
# Within a selenium test, the browser is asked whether the element is really hidden.
# In a non-selenium test, we only check for `.hidden`, `.invisible` or `style: display:none`
Then /^(the tag )?"([^\"]+)" should be hidden$/ do |tag, selector_or_text|
  options = {}
  tag ? options.store(:selector, selector_or_text) : options.store(:text, selector_or_text)

  assert_hidden(options)
end.overridable

# Click on some text that might not be a link.
#
# Example:
#
#     When I click on "Collapse"
#
When /^I click on "([^\"]+)"$/ do |text|
  patiently do
    contains_text = %{contains(., \"#{text}\")}
    # find the innermost selector that matches
    element = page.find(:xpath, ".//*[#{contains_text} and not (./*[#{contains_text}])]")
    element.click
  end
end.overridable

# Click on an element with the given selector.
#
# Example:
#
#     When I click on the element ".sidebar"
#
When /^I click on the element "([^"]+)"$/ do |selector|
  patiently do
    page.find(selector).click
  end
end.overridable

# Click on the element with the given [selector alias](https://github.com/makandra/spreewald/blob/master/examples/selectors.rb).
#
# Example:
#
#     When I click on the element for the sidebar
When /^I click on the element for (.+?)$/ do |locator|
  patiently do
    selector = _selector_for(locator)
    args, kwargs = deconstruct_selector(selector)
    page.find(*args, **kwargs).click
  end
end.overridable(priority: -5) # priority lower than within

# Use this step to check external links.
#
# Example:
#
#     Then "Sponsor" should link to "http://makandra.com/"
#
# Don't forget the trailing slash. Otherwise you'll get the error
#   expected: /http:\/\/makandra.com(\?[^\/]*)?$/
#        got: "http://makandra.com/" (using =~)
Then /^"([^"]*)" should link to "([^"]*)"$/ do |link_label, target|
  patiently do
    link = find_link(link_label)
    expect(link[:href]).to match(/#{Regexp.escape target}(\?[^\/]*)?$/) # ignore trailing timestamps
  end
end.overridable

# Click a link within an element matching the given selector. Will try to be clever
# and disregard elements that don't contain a matching link.
#
# Example:
#
#     When I follow "Read more" inside any ".text_snippet"
When /^I follow "([^"]*)" inside any "([^"]*)"$/ do |label, selector|
  node = find("#{selector} a", :text => label)
  node.click
end.overridable

Then /^I should( not)? see "([^"]*)" inside any "([^"]*)"$/ do |negate, text, selector|
  expectation = negate ? :not_to : :to
  expect(page).send(expectation, have_css(selector, :text => text))
end.overridable

When /^I fill in "([^"]*)" with "([^"]*)" inside any "([^"]*)"$/ do |field, value, selector|
  containers = all(:css, selector)
  input = nil
  containers.detect do |container|
    input = container.first(:fillable_field, field)
  rescue Capybara::ElementNotFound
    # Capybara changed the behavior of #first and raises an error when the first container finds no matching field
  end
  if input
    input.set(value)
  else
    raise "Could not find an input field \"#{field}\" inside any \"#{selector}\""
  end
end.overridable

When /^I confirm the browser dialog$/ do
  patiently do
    browser.switch_to.alert.accept
  end
end.overridable

When /^I cancel the browser dialog$/ do
  patiently do
    browser.switch_to.alert.dismiss
  end
end.overridable

When /^I enter "([^"]*)" into the browser dialog$/ do |text|
  patiently do
    alert = browser.switch_to.alert
    alert.send_keys(text)
    alert.accept
  end
end.overridable

# Tests that an input, button, checkbox or radio button with the given label is disabled.
Then /^the "([^\"]*)" (field|button|checkbox|radio button) should( not)? be disabled$/ do |label, kind, negate|
  patiently do
    element = if kind == 'button'
      find_with_disabled(:button, label)
    else
      find_with_disabled(:field, label)
    end

    if Spreewald::Comparison.compare_versions(Capybara::VERSION, :<, "2.1")
      expect(element[:disabled]).send(negate ? :to : :not_to, eq(nil))
    else
      expect(!!element.disabled?).to be !negate
    end
  end
end.overridable

# Tests that a field with the given label is visible.
Then /^the "([^\"]*)" field should( not)? be visible$/ do |label, hidden|
  if Spreewald::Comparison.compare_versions(Capybara::VERSION, :<, "2.1")
    field = find_with_disabled(:field, label)
  else
    # Capybara 2.1+ won't usually interact with hidden elements,
    # but we can override this behavior by passing { visible: false }
    field = find_with_disabled(:field, label, :visible => false)
  end

  selector = "##{field['id']}"

  if hidden
    assert_hidden(selector: selector)
  else
    assert_visible(selector: selector)
  end
end.overridable

# Performs HTTP basic authentication with the given credentials and visits the given path.
#
# More details [here](https://makandracards.com/makandra/971-perform-http-basic-authentication-in-cucumber).
When /^I perform basic authentication as "([^\"]*)\/([^\"]*)" and go to (.*)$/ do |user, password, page_name|
  path = _path_to(page_name)
  if javascript_capable?
    server = Capybara.current_session.server rescue Capybara.current_session.driver.rack_server
    visit("http://#{user}:#{password}@#{server.host}:#{server.port}#{path}")
  else
    authorizers = [
      (browser),
      (self),
      (page.driver)
    ].compact
    authorizer = authorizers.detect { |authorizer| authorizer.respond_to?(:basic_authorize) }
    authorizer.basic_authorize(user, password)
    visit path
  end
end.overridable

# Goes to the previously viewed page.
When /^I go back$/ do
  if javascript_capable?
    page.execute_script('window.history.back()')
  else
    if page.driver.respond_to?(:browser)
      visit page.driver.browser.last_request.env['HTTP_REFERER']
    else
      visit page.driver.last_request.env['HTTP_REFERER']
    end
  end
end.overridable

# Tests whether a select field is sorted. Uses Array#natural_sort, if defined;
# Array#sort else.
Then /^the "(.*?)" select should( not)? be sorted$/ do |label, negate|
  select = find_with_disabled(:field, label)
  options = select.all('option').reject { |o| o.value.blank? }
  option_texts = options.collect(&:text)

  expect(option_texts).send((negate ? :not_to : :to), be_sorted)
end.overridable
