Files

292 lines
8.2 KiB
Ruby

# frozen_string_literal: true
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
require_relative "edit_field"
class ProgressEditField < EditField
MODAL_SELECTOR = "#work_package_progress_modal"
FIELD_NAME_MAP = {
"estimatedTime" => :estimated_hours,
"remainingTime" => :remaining_hours,
"percentageDone" => :done_ratio,
"statusWithinProgressModal" => :status_id
}.freeze
HUMAN_FIELD_NAME_MAP = {
"estimatedTime" => "work",
"remainingTime" => "remaining work",
"percentageDone" => "% complete",
"statusWithinProgressModal" => "status"
}.freeze
def initialize(context,
property_name,
selector: nil,
create_form: false)
super
@field_name = "work_package_#{FIELD_NAME_MAP.fetch(@property_name)}"
@human_field_name = HUMAN_FIELD_NAME_MAP.fetch(@property_name)
@trigger_selector = "input[id$=inline-edit--field-#{@property_name}]"
end
def visible_on_create_form?
false
end
def update(value, save: true, expect_failure: false)
super
end
def reactivate!(expect_open: true)
retry_block(args: { tries: 2 }) do
SeleniumHubWaiter.wait unless using_cuprite?
scroll_to_and_click(display_element)
SeleniumHubWaiter.wait unless using_cuprite?
if expect_open && !active?
raise "Expected field for attribute '#{property_name}' to be active."
end
self
end
end
def active?
page.has_selector?(MODAL_SELECTOR, wait: 1)
end
def clear
move_caret_to_end_of_input
super(with_backspace: true)
end
def set_value(value)
if status_field?
select_status(value)
elsif value == ""
clear
else
page.fill_in field_name, with: value
end
wait_for_preview_to_complete
end
def select_status(value)
value = value.name if value.is_a?(Status)
page.select(value, from: "% Complete")
end
def status_field?
field_name == "work_package_status_id"
end
def focus
return if focused?
page.evaluate_script("arguments[0].focus()", input_element)
wait_for_preview_to_complete
end
# Wait for the popover preview to be refreshed.
# Preview occurs on field blur or change.
def wait_for_preview_to_complete
# The preview on popover has a debounce that must be kept in sync here.
# See frontend/src/stimulus/controllers/dynamic/work-packages/dialog/preview.controller.ts
sleep 0.210
if using_cuprite?
wait_for_network_idle # Wait for preview to finish
end
end
def input_element
modal_element.find_field(field_name)
end
def input_caption_element
input_aria_related_element(describedby: "caption")
end
def input_validation_element
input_aria_related_element(describedby: "validation")
end
def trigger_element
within @context do
page.find(@trigger_selector)
end
end
def save!
submit_by_enter
end
def submit_by_enter
input_element.native.send_keys :return
end
def submit_by_clicking_save
within modal_element do
click_on("Save")
end
end
def close!
page.find("[data-test-selector='op-progress-modal--close-icon']").click
end
def expect_active!
expect(page).to have_css(MODAL_SELECTOR)
end
def expect_inactive!
expect(page).to have_no_css(MODAL_SELECTOR)
end
# The selector for create contexts varies from that
# of update contexts where the work package already
# exists.
def display_selector
if create_form?
".inline-edit--active-field"
else
super
end
end
# Checks if the modal field is in focus.
# It compares the active element in the page (the element in focus) with the input element of the modal.
# If they are the same, it means the modal field is in focus.
# @return [Boolean] true if the modal field is in focus, false otherwise.
def expect_modal_field_in_focus
# Use capybara `synchronize` helper to wait until the modal field is in focus
page.document.synchronize do
unless focused?
raise Capybara::ExpectationNotMet, "Input #{input_element} does not have focus"
end
end
end
def focused?
input_element == page.evaluate_script("document.activeElement")
end
# Checks if the cursor is at the end of the input in the modal field.
# It compares the cursor position (selectionStart) with the length of the value in the input field.
# If they are the same, it means the cursor is at the end of the input.
# @return [Boolean] true if the cursor is at the end of the input, false otherwise.
def expect_cursor_at_end_of_input
expect(cursor_at_end_of_input?).to be(true)
end
def cursor_at_end_of_input?
input_element.evaluate_script("this.selectionStart == this.value.length;")
end
def move_caret_to_end_of_input
page.evaluate_script("arguments[0].setSelectionRange(arguments[0].value.length, arguments[0].value.length)", input_element)
end
def expect_trigger_field_disabled
expect(trigger_element).to be_disabled
end
def expect_modal_field_disabled
expect(page).to have_field(@field_name, disabled: true)
end
def expect_modal_field_read_only
expect(input_element).to be_readonly
end
def expect_modal_field_value(value, disabled: :all)
within modal_element do
if @property_name == "percentageDone" && value.to_s == "-"
expect(page).to have_field(field_name, placeholder: value.to_s)
elsif @property_name == "statusWithinProgressModal"
if value == :empty_without_any_options
expect(page).to have_select(field_name, disabled:, options: [])
else
expect(page).to have_select(field_name, disabled:, with_selected: value.to_s)
end
else
expect(page).to have_field(field_name, disabled:, with: value.to_s)
end
end
end
def expect_caption(expected_caption)
if expected_caption.nil?
expect(input_caption_element).to be_nil, "Expected no caption for #{@human_field_name} field, " \
"got \"#{input_caption_element&.text}\""
else
expect(input_caption_element).to have_text(expected_caption)
end
end
def expect_error(expected_error)
if expected_error.nil?
expect(input_validation_element).to be_nil, "Expected no error message for #{@human_field_name} field, " \
"got \"#{input_validation_element&.text}\""
else
expect(input_validation_element).to have_text(expected_error)
end
end
def expect_select_field_with_options(*expected_options)
within modal_element do
expect(page).to have_select(field_name, with_options: expected_options)
end
end
def expect_select_field_with_no_options(*unexpected_options)
within modal_element do
expect(page).to have_no_select(field_name,
with_options: unexpected_options,
wait: 0)
end
end
private
attr_reader :field_name
def modal_element
page.find(MODAL_SELECTOR)
end
def input_aria_related_element(describedby:)
input_element["aria-describedby"]
.split
.find { it.start_with?("#{describedby}-") }
&.then { |id| find(id:) }
end
end