File: form.rb

package info (click to toggle)
ruby-capybara 3.40.0%2Bds-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,368 kB
  • sloc: ruby: 23,988; javascript: 752; makefile: 11
file content (149 lines) | stat: -rw-r--r-- 4,927 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# frozen_string_literal: true

class Capybara::RackTest::Form < Capybara::RackTest::Node
  # This only needs to inherit from Rack::Test::UploadedFile because Rack::Test checks for
  # the class specifically when determining whether to construct the request as multipart.
  # That check should be based solely on the form element's 'enctype' attribute value,
  # which should probably be provided to Rack::Test in its non-GET request methods.
  class NilUploadedFile < Rack::Test::UploadedFile
    def initialize # rubocop:disable Lint/MissingSuper
      @empty_file = Tempfile.new('nil_uploaded_file')
      @empty_file.close
    end

    def original_filename; ''; end
    def content_type; 'application/octet-stream'; end
    def path; @empty_file.path; end
    def size; 0; end
    def read; ''; end
    def append_to(_); end
    def set_encoding(_); end # rubocop:disable Naming/AccessorMethodName
  end

  def params(button)
    form_element_types = %i[input select textarea button]
    form_elements_xpath = XPath.generate do |xp|
      xpath = xp.descendant(*form_element_types).where(!xp.attr(:form))
      xpath += xp.anywhere(*form_element_types).where(xp.attr(:form) == native[:id]) if native[:id]
      xpath.where(!xp.attr(:disabled))
    end.to_s

    form_elements = native.xpath(form_elements_xpath).reject { |el| submitter?(el) && (el != button.native) }

    form_params = form_elements.each_with_object({}.compare_by_identity) do |field, params|
      case field.name
      when 'input', 'button' then add_input_param(field, params)
      when 'select' then add_select_param(field, params)
      when 'textarea' then add_textarea_param(field, params)
      end
    end

    form_params.each_with_object(make_params) do |(name, value), params|
      merge_param!(params, name, value)
    end.to_params_hash

    # form_elements.each_with_object(make_params) do |field, params|
    #   case field.name
    #   when 'input', 'button' then add_input_param(field, params)
    #   when 'select' then add_select_param(field, params)
    #   when 'textarea' then add_textarea_param(field, params)
    #   end
    # end.to_params_hash
  end

  def submit(button)
    action = button&.[]('formaction') || native['action']
    method = button&.[]('formmethod') || request_method
    driver.submit(method, action.to_s, params(button), content_type: native['enctype'])
  end

  def multipart?
    self[:enctype] == 'multipart/form-data'
  end

private

  class ParamsHash < Hash
    def to_params_hash
      self
    end
  end

  def request_method
    /post/i.match?(self[:method] || '') ? :post : :get
  end

  def merge_param!(params, key, value)
    key = key.to_s
    if Rack::Utils.respond_to?(:default_query_parser)
      Rack::Utils.default_query_parser.normalize_params(params, key, value, Rack::Utils.param_depth_limit)
    else
      Rack::Utils.normalize_params(params, key, value)
    end
  end

  def make_params
    if Rack::Utils.respond_to?(:default_query_parser)
      Rack::Utils.default_query_parser.make_params
    else
      ParamsHash.new
    end
  end

  def add_input_param(field, params)
    name, value = field['name'].to_s, field['value'].to_s
    return if name.empty?

    value = case field['type']
    when 'radio', 'checkbox'
      return unless field['checked']

      Capybara::RackTest::Node.new(driver, field).value.to_s
    when 'file'
      return if value.empty? && params.keys.include?(name) && Rack::Test::VERSION.to_f >= 2.0 # rubocop:disable Performance/InefficientHashSearch

      if multipart?
        file_to_upload(value)
      else
        File.basename(value)
      end
    else
      value
    end
    # merge_param!(params, name, value)
    params[name] = value
  end

  def file_to_upload(filename)
    if filename.empty?
      NilUploadedFile.new
    else
      mime_info = MiniMime.lookup_by_filename(filename)
      Rack::Test::UploadedFile.new(filename, mime_info&.content_type&.to_s)
    end
  end

  def add_select_param(field, params)
    name = field['name']
    if field.has_attribute?('multiple')
      value = field.xpath('.//option[@selected]').map do |option|
        # merge_param!(params, field['name'], (option['value'] || option.text).to_s)
        (option['value'] || option.text).to_s
      end
      params[name] = value unless value.empty?
    else
      option = field.xpath('.//option[@selected]').first || field.xpath('.//option').first
      # merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
      params[name] = (option['value'] || option.text).to_s if option
    end
  end

  def add_textarea_param(field, params)
    # merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n"))
    params[field['name']] = field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n")
  end

  def submitter?(el)
    (%w[submit image].include? el['type']) || (el.name == 'button')
  end
end