Files
Christophe Bliard 447e07b38d Fix flaky spec in progress_modal_spec
failing spec: spec/features/work_packages/progress_modal_spec.rb:195
failing run: https://github.com/opf/openproject/actions/runs/21204407008/job/60997306384

In the spec, another page is visited during the test, meaning the
internal ids of the capybara elements may not be the same as the ones
before the page is reloaded. This is a problem because the
`progress_popover` caches the container element, and it's not valid
anymore (it's stale).

One fix could be to create a new `progress_popover` instance after the other page is visited.

I found it better to be able to pass a lambda for the container element
to the `progress_popover` constructor, so that it always gets it afresh
and will never be stale.
2026-01-22 15:30:43 +01:00

203 lines
6.1 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 "../../toasts/expectations"
module Components
module WorkPackages
class ProgressPopover
include Capybara::DSL
include Capybara::RSpecMatchers
include RSpec::Matchers
# include Toasts::Expectations
JS_FIELD_NAME_MAP = {
estimated_time: :estimatedTime,
percent_complete: :percentageDone,
percentage_done: :percentageDone,
remaining_work: :remainingTime,
remaining_time: :remainingTime,
status: :statusWithinProgressModal,
status_within_progress_modal: :statusWithinProgressModal,
work: :estimatedTime
}.freeze
WORK_PACKAGE_FIELD_NAME_MAP = {
estimated_time: :estimated_hours,
percent_complete: :done_ratio,
percentage_done: :done_ratio,
remaining_work: :remaining_hours,
remaining_time: :remaining_hours,
work: :estimated_hours
}.freeze
attr_reader :create_form
def initialize(container: page, create_form: false)
@container_or_lambda_to_get_container = container
@create_form = create_form
end
def container
if @container_or_lambda_to_get_container.respond_to?(:call)
@container_or_lambda_to_get_container.call
else
@container_or_lambda_to_get_container
end
end
def open
open_by_clicking_on_field(:work)
end
def open_by_clicking_on_field(field_name)
field(field_name).activate!
wait_for_network_idle # Wait for initial loading to be ready
end
def close
field(:work).close!
end
def save
field(:work).submit_by_clicking_save
end
def focus(field_name)
field(field_name).focus
end
def set_value(field_name, value)
focus(field_name)
field(field_name).set_value(value)
end
def set_values(**field_value_pairs)
field_value_pairs.each do |field_name, value|
set_value(field_name, value)
end
wait_for_network_idle # Wait for live-update to finish
end
def expect_cursor_at_end_of_input(field_name)
field(field_name).expect_cursor_at_end_of_input
end
def expect_disabled(field_name)
field(field_name).expect_modal_field_disabled
end
def expect_focused(field_name)
field(field_name).expect_modal_field_in_focus
end
def expect_read_only(field_name)
field(field_name).expect_modal_field_read_only
end
def expect_select_with_options(field_name, *options)
field(field_name).expect_select_field_with_options(*options)
end
def expect_select_without_options(field_name, *options)
field(field_name).expect_select_field_with_no_options(*options)
end
def expect_value(field_name, value, **properties)
field(field_name).expect_modal_field_value(value, **properties)
end
def expect_values(**field_value_pairs)
aggregate_failures("progress popover values expectations") do
field_value_pairs.each do |field_name, value|
expect_value(field_name, value)
end
end
end
def expect_hint(field_name, hint)
progress_hint =
case hint
when nil
nil
when Symbol
WorkPackage::ProgressHint.new("#{wp_field_name(field_name)}.#{hint}")
when Hash
hint, params = hint.first
WorkPackage::ProgressHint.new("#{wp_field_name(field_name)}.#{hint}", params)
end
field(field_name).expect_caption(progress_hint&.message)
end
def expect_hints(**field_hint_pairs)
aggregate_failures("progress popover hints expectations") do
field_hint_pairs.each do |field_name, hint|
expect_hint(field_name, hint)
end
end
end
def expect_error(field_name, error_message)
field(field_name).expect_error(error_message)
end
def expect_errors(**field_error_pairs)
aggregate_failures("progress popover errors expectations") do
field_error_pairs.each do |field_name, error_message|
expect_error(field_name, error_message)
end
end
end
private
def field(field_name)
field_name = js_field_name(field_name)
ProgressEditField.new(container, field_name, create_form:)
end
def js_field_name(field_name)
field_name = field_name.to_s.underscore.to_sym
JS_FIELD_NAME_MAP.fetch(field_name) do
raise ArgumentError, "cannot map '#{field_name.inspect}' to its javascript field name"
end
end
def wp_field_name(field_name)
field_name = field_name.to_s.underscore.to_sym
WORK_PACKAGE_FIELD_NAME_MAP.fetch(field_name) do
raise ArgumentError, "cannot map '#{field_name.inspect}' to its work package field name"
end
end
end
end
end