# frozen_string_literal: true

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

require_relative 'spec_helper'

module Selenium
  module WebDriver
    describe Element, exclusive: {bidi: false, reason: 'Not yet implemented with BiDi'} do
      it 'clicks' do
        driver.navigate.to url_for('formPage.html')
        expect { driver.find_element(id: 'imageButton').click }.not_to raise_error
        reset_driver!(time: 1) if %i[safari safari_preview].include? GlobalTestEnv.browser
      end

      # Safari returns "click intercepted" error instead of "element click intercepted"
      it 'raises if different element receives click', except: {browser: %i[safari safari_preview]} do
        driver.navigate.to url_for('click_tests/overlapping_elements.html')
        expect { driver.find_element(id: 'contents').click }.to raise_error(Error::ElementClickInterceptedError)
      end

      # Safari returns "click intercepted" error instead of "element click intercepted"
      it 'raises if element is partially covered', except: {browser: %i[safari safari_preview]} do
        driver.navigate.to url_for('click_tests/overlapping_elements.html')
        expect { driver.find_element(id: 'other_contents').click }.to raise_error(Error::ElementClickInterceptedError)
      end

      it 'raises if element stale' do
        driver.navigate.to url_for('formPage.html')
        button = driver.find_element(id: 'imageButton')
        driver.navigate.refresh

        expect { button.click }.to raise_exception(Error::StaleElementReferenceError,
                                                   /errors#stale-element-reference-exception/)

        reset_driver!(time: 1) if %i[safari safari_preview].include? GlobalTestEnv.browser
      end

      describe '#submit' do
        it 'valid submit button' do
          driver.navigate.to url_for('formPage.html')
          driver.find_element(id: 'submitButton').submit

          sleep 0.5
          expect(driver.title).to eq('We Arrive Here')
        end

        it 'any input element in form' do
          driver.navigate.to url_for('formPage.html')
          driver.find_element(id: 'checky').submit

          sleep 0.5
          expect(driver.title).to eq('We Arrive Here')
        end

        it 'any element in form' do
          driver.navigate.to url_for('formPage.html')
          driver.find_element(css: 'form > p').submit

          sleep 0.5
          expect(driver.title).to eq('We Arrive Here')
        end

        it 'button with id submit' do
          driver.navigate.to url_for('formPage.html')
          driver.find_element(id: 'submit').submit

          sleep 0.5
          expect(driver.title).to eq('We Arrive Here')
        end

        it 'button with name submit' do
          driver.navigate.to url_for('formPage.html')
          driver.find_element(name: 'submit').submit

          sleep 0.5
          expect(driver.title).to eq('We Arrive Here')
        end

        it 'errors with button outside form' do
          driver.navigate.to url_for('formPage.html')
          expect { driver.find_element(name: 'SearchableText').submit }.to raise_error(Error::UnsupportedOperationError)
        end
      end

      it 'sends empty keys' do
        driver.navigate.to url_for('formPage.html')
        element = wait_for_element(id: 'working')
        element.send_keys
        expect(element.text).to be_empty
      end

      it 'sends string keys' do
        driver.navigate.to url_for('formPage.html')
        wait_for_element(id: 'working')
        expect { driver.find_element(id: 'working').send_keys('foo', 'bar') }.not_to raise_error
      end

      it 'sends key presses' do
        driver.navigate.to url_for('javascriptPage.html')
        key_reporter = driver.find_element(id: 'keyReporter')

        key_reporter.send_keys('Tet', :arrow_left, 's')
        expect(key_reporter.attribute('value')).to eq('Test')
      end

      # https://github.com/mozilla/geckodriver/issues/245
      it 'sends key presses chords', except: {browser: %i[firefox safari safari_preview]} do
        driver.navigate.to url_for('javascriptPage.html')
        key_reporter = driver.find_element(id: 'keyReporter')

        key_reporter.send_keys([:shift, 'h'], 'ello')
        expect(key_reporter.attribute('value')).to eq('Hello')
      end

      it 'handles file uploads' do
        driver.navigate.to url_for('formPage.html')

        element = driver.find_element(id: 'upload')
        expect(element.attribute('value')).to be_empty

        path = WebDriver::Platform.windows? ? WebDriver::Platform.windows_path(__FILE__) : __FILE__

        element.send_keys path

        expect(element.attribute('value')).to include(File.basename(path))
      end

      describe 'properties and attributes' do
        before { driver.navigate.to url_for('formPage.html') }

        context 'when string type' do
          let(:element) { driver.find_element(id: 'checky') }
          let(:prop_or_attr) { 'type' }

          it '#dom_attribute returns attribute value' do
            expect(element.dom_attribute(prop_or_attr)).to eq 'checkbox'
          end

          it '#property returns property value' do
            expect(element.property(prop_or_attr)).to eq 'checkbox'
          end

          it '#attribute returns value' do
            expect(element.attribute(prop_or_attr)).to eq 'checkbox'
          end
        end

        context 'when numeric type' do
          let(:element) { driver.find_element(id: 'withText') }
          let(:prop_or_attr) { 'rows' }

          it '#dom_attribute String' do
            expect(element.dom_attribute(prop_or_attr)).to eq '5'
          end

          it '#property returns Number' do
            expect(element.property(prop_or_attr)).to eq 5
          end

          it '#attribute returns String' do
            expect(element.attribute(prop_or_attr)).to eq '5'
          end
        end

        context 'with boolean type of true' do
          let(:element) { driver.find_element(id: 'checkedchecky') }
          let(:prop_or_attr) { 'checked' }

          it '#dom_attribute returns String', except: {browser: :safari} do
            expect(element.dom_attribute(prop_or_attr)).to eq 'true'
          end

          it '#property returns true' do
            expect(element.property(prop_or_attr)).to be true
          end

          it '#attribute returns String' do
            expect(element.attribute(prop_or_attr)).to eq 'true'
          end

          it '#dom_attribute does not update after click', except: {browser: :safari} do
            element.click
            expect(element.dom_attribute(prop_or_attr)).to eq 'true'
          end

          it '#property updates to false after click' do
            element.click
            expect(element.property(prop_or_attr)).to be false
          end

          it '#attribute updates to nil after click' do
            element.click
            expect(element.attribute(prop_or_attr)).to be_nil
          end
        end

        context 'with boolean type of false' do
          let(:element) { driver.find_element(id: 'checky') }
          let(:prop_or_attr) { 'checked' }

          it '#dom_attribute returns nil' do
            expect(element.dom_attribute(prop_or_attr)).to be_nil
          end

          it '#property returns false' do
            expect(element.property(prop_or_attr)).to be false
          end

          it '#attribute returns nil' do
            expect(element.attribute(prop_or_attr)).to be_nil
          end

          it '#dom_attribute does not update after click' do
            element.click
            expect(element.dom_attribute(prop_or_attr)).to be_nil
          end

          it '#property updates to true after click' do
            element.click
            expect(element.property(prop_or_attr)).to be true
          end

          it '#attribute updates to String after click' do
            element.click
            expect(element.attribute(prop_or_attr)).to eq 'true'
          end
        end

        context 'when property exists but attribute does not' do
          let(:element) { driver.find_element(id: 'withText') }
          let(:prop_or_attr) { 'value' }

          it '#dom_attribute returns nil' do
            expect(element.dom_attribute(prop_or_attr)).to be_nil
          end

          it '#property returns default property' do
            expect(element.property(prop_or_attr)).to eq 'Example text'
          end

          it '#attribute returns default property' do
            expect(element.attribute(prop_or_attr)).to eq 'Example text'
          end

          it '#property returns updated property' do
            element.clear
            expect(element.property(prop_or_attr)).to be_empty
          end

          it '#attribute returns updated property' do
            element.clear
            expect(element.attribute(prop_or_attr)).to be_empty
          end
        end

        context 'when attribute exists but property does not' do
          let(:element) { driver.find_element(id: 'vsearchGadget') }
          let(:prop_or_attr) { 'accesskey' }

          it '#dom_attribute returns attribute' do
            expect(element.dom_attribute(prop_or_attr)).to eq '4'
          end

          it '#property returns nil' do
            expect(element.property(prop_or_attr)).to be_nil
          end

          it '#attribute returns attribute' do
            expect(element.attribute(prop_or_attr)).to eq '4'
          end
        end

        context 'when neither attribute nor property exists' do
          let(:element) { driver.find_element(id: 'checky') }
          let(:prop_or_attr) { 'nonexistent' }

          it '#dom_attribute returns nil' do
            expect(element.dom_attribute(prop_or_attr)).to be_nil
          end

          it '#property returns nil' do
            expect(element.property(prop_or_attr)).to be_nil
          end

          it '#attribute returns nil' do
            expect(element.attribute(prop_or_attr)).to be_nil
          end
        end

        describe 'style' do
          before { driver.navigate.to url_for('clickEventPage.html') }

          let(:element) { driver.find_element(id: 'result') }
          let(:prop_or_attr) { 'style' }

          it '#dom_attribute attribute with no formatting' do
            expect(element.dom_attribute(prop_or_attr)).to eq 'width:300;height:60'
          end

          # TODO: This might not be correct behavior
          it '#property returns object',
             except: [{browser: :firefox,
                       reason: 'https://github.com/mozilla/geckodriver/issues/1846'},
                      {browser: :safari}] do
            expect(element.property(prop_or_attr)).to eq %w[width height]
          end

          it '#attribute returns attribute with formatting' do
            expect(element.attribute(prop_or_attr)).to eq 'width: 300px; height: 60px;'
          end
        end

        describe 'incorrect casing' do
          let(:element) { driver.find_element(id: 'checky') }
          let(:prop_or_attr) { 'nAme' }

          it '#dom_attribute returns correctly cased attribute' do
            expect(element.dom_attribute(prop_or_attr)).to eq 'checky'
          end

          it '#property returns nil' do
            expect(element.property(prop_or_attr)).to be_nil
          end

          it '#attribute returns correctly cased attribute' do
            expect(element.attribute(prop_or_attr)).to eq 'checky'
          end
        end

        describe 'property attribute case difference with attribute casing' do
          let(:element) { driver.find_element(name: 'readonly') }
          let(:prop_or_attr) { 'readonly' }

          it '#dom_attribute returns a String', except: {browser: :safari} do
            expect(element.dom_attribute(prop_or_attr)).to eq 'true'
          end

          it '#property returns nil' do
            expect(element.property(prop_or_attr)).to be_nil
          end

          it '#attribute returns a String' do
            expect(element.attribute(prop_or_attr)).to eq 'true'
          end
        end

        describe 'property attribute case difference with property casing' do
          let(:element) { driver.find_element(name: 'readonly') }
          let(:prop_or_attr) { 'readOnly' }

          it '#dom_attribute returns a String',
             except: [{browser: :firefox,
                       reason: 'https://github.com/mozilla/geckodriver/issues/1850'},
                      {browser: :safari}] do
            expect(element.dom_attribute(prop_or_attr)).to eq 'true'
          end

          it '#property returns property as true' do
            expect(element.property(prop_or_attr)).to be true
          end

          it '#attribute returns property as String' do
            expect(element.attribute(prop_or_attr)).to eq 'true'
          end
        end

        describe 'property attribute name difference with attribute naming' do
          let(:element) { driver.find_element(id: 'wallace') }
          let(:prop_or_attr) { 'class' }

          it '#dom_attribute returns attribute value' do
            expect(element.dom_attribute(prop_or_attr)).to eq 'gromit'
          end

          it '#property returns nil' do
            expect(element.property(prop_or_attr)).to be_nil
          end

          it '#attribute returns attribute value' do
            expect(element.attribute(prop_or_attr)).to eq 'gromit'
          end
        end

        describe 'property attribute name difference with property naming' do
          let(:element) { driver.find_element(id: 'wallace') }
          let(:prop_or_attr) { 'className' }

          it '#dom_attribute returns nil' do
            expect(element.dom_attribute(prop_or_attr)).to be_nil
          end

          it '#property returns property value' do
            expect(element.property(prop_or_attr)).to eq 'gromit'
          end

          it '#attribute returns property value' do
            expect(element.attribute(prop_or_attr)).to eq 'gromit'
          end
        end

        describe 'property attribute value difference' do
          let(:element) { driver.find_element(tag_name: 'form') }
          let(:prop_or_attr) { 'action' }

          it '#dom_attribute returns attribute value' do
            expect(element.dom_attribute(prop_or_attr)).to eq 'resultPage.html'
          end

          it '#property returns property value' do
            expect(element.property(prop_or_attr)).to match(%r{http://(.+)/resultPage\.html})
          end

          it '#attribute returns property value' do
            expect(element.attribute(prop_or_attr)).to match(%r{http://(.+)/resultPage\.html})
          end
        end
      end

      it 'returns ARIA role' do
        driver.navigate.to 'data:text/html,' \
                           "<div role='heading' aria-level='1'>Level 1 Header</div>" \
                           '<h1>Level 1 Header</h1>' \
                           "<h2 role='alert'>Level 2 Header</h2>"
        expect(driver.find_element(tag_name: 'div').aria_role).to eq('heading')
        expect(driver.find_element(tag_name: 'h1').aria_role).to eq('heading')
        expect(driver.find_element(tag_name: 'h2').aria_role).to eq('alert')
      end

      it 'returns accessible name' do
        driver.navigate.to 'data:text/html,<h1>Level 1 Header</h1>'
        expect(driver.find_element(tag_name: 'h1').accessible_name).to eq('Level 1 Header')
      end

      it 'clears' do
        driver.navigate.to url_for('formPage.html')
        expect { driver.find_element(id: 'withText').clear }.not_to raise_error
      end

      it 'gets and set selected' do
        driver.navigate.to url_for('formPage.html')

        cheese = driver.find_element(id: 'cheese')
        peas = driver.find_element(id: 'peas')

        cheese.click

        expect(cheese).to be_selected
        expect(peas).not_to be_selected

        peas.click

        expect(peas).to be_selected
        expect(cheese).not_to be_selected
      end

      it 'gets enabled' do
        driver.navigate.to url_for('formPage.html')
        expect(driver.find_element(id: 'notWorking')).not_to be_enabled
      end

      it 'gets text' do
        driver.navigate.to url_for('xhtmlTest.html')
        expect(driver.find_element(class: 'header').text).to eq('XHTML Might Be The Future')
      end

      it 'gets displayed' do
        driver.navigate.to url_for('xhtmlTest.html')
        expect(driver.find_element(class: 'header')).to be_displayed
      end

      describe 'size and location' do
        it 'gets current location' do
          driver.navigate.to url_for('xhtmlTest.html')
          loc = driver.find_element(class: 'header').location

          expect(loc.x).to be >= 1
          expect(loc.y).to be >= 1
        end

        it 'gets location once scrolled into view' do
          driver.navigate.to url_for('javascriptPage.html')
          loc = driver.find_element(id: 'keyUp').location_once_scrolled_into_view

          expect(loc.x).to be >= 1
          expect(loc.y).to be >= 0 # can be 0 if scrolled to the top
        end

        it 'gets size' do
          driver.navigate.to url_for('xhtmlTest.html')
          size = driver.find_element(class: 'header').size

          expect(size.width).to be_positive
          expect(size.height).to be_positive
        end

        it 'gets rect' do
          driver.navigate.to url_for('xhtmlTest.html')
          rect = driver.find_element(class: 'header').rect

          expect(rect.x).to be_positive
          expect(rect.y).to be_positive
          expect(rect.width).to be_positive
          expect(rect.height).to be_positive
        end
      end

      # IE - https://github.com/SeleniumHQ/selenium/pull/4043
      it 'drags and drop', except: {browser: :ie} do
        driver.navigate.to url_for('dragAndDropTest.html')

        img1 = driver.find_element(id: 'test1')
        img2 = driver.find_element(id: 'test2')

        driver.action.drag_and_drop_by(img1, 100, 100)
              .drag_and_drop(img2, img1)
              .perform

        expect(img1.location).to eq(img2.location)
      end

      it 'gets css property' do
        driver.navigate.to url_for('javascriptPage.html')
        element = driver.find_element(id: 'green-parent')

        style1 = element.css_value('background-color')
        style2 = element.style('background-color') # backwards compatibility

        acceptable = ['rgb(0, 128, 0)', '#008000', 'rgba(0,128,0,1)', 'rgba(0, 128, 0, 1)']
        expect(acceptable).to include(style1, style2)
      end

      it 'knows when two elements are equal' do
        driver.navigate.to url_for('simpleTest.html')

        body = driver.find_element(tag_name: 'body')
        xbody = driver.find_element(xpath: '//body')
        jsbody = driver.execute_script('return document.getElementsByTagName("body")[0]')

        expect(body).to eq(xbody)
        expect(body).to eq(jsbody)
        expect(body).to eql(xbody)
        expect(body).to eql(jsbody)
      end

      it 'knows when element arrays are equal' do
        driver.navigate.to url_for('simpleTest.html')

        tags = driver.find_elements(tag_name: 'div')
        jstags = driver.execute_script('return document.getElementsByTagName("div")')

        expect(tags).to eq(jstags)
      end

      it 'knows when two elements are not equal' do
        driver.navigate.to url_for('simpleTest.html')

        elements = driver.find_elements(tag_name: 'p')
        p1 = elements.fetch(0)
        p2 = elements.fetch(1)

        expect(p1).not_to eq(p2)
        expect(p1).not_to eql(p2)
      end

      it 'returns the same #hash for equal elements when found by Driver#find_element' do
        driver.navigate.to url_for('simpleTest.html')

        body = driver.find_element(tag_name: 'body')
        xbody = driver.find_element(xpath: '//body')

        expect(body.hash).to eq(xbody.hash)
      end

      it 'returns the same #hash for equal elements when found by Driver#find_elements' do
        driver.navigate.to url_for('simpleTest.html')

        body = driver.find_elements(tag_name: 'body').fetch(0)
        xbody = driver.find_elements(xpath: '//body').fetch(0)

        expect(body.hash).to eq(xbody.hash)
      end
    end
  end # WebDriver
end # Selenium
