mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
292 lines
8.2 KiB
Ruby
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
|