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

787 lines
31 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 "spec_helper"
RSpec.describe "Progress modal", :js do
shared_let(:user) { create(:admin) }
shared_let(:role) { create(:project_role) }
shared_let(:type_task) { create(:type_task) }
shared_let(:project) { create(:project, types: [type_task]) }
shared_let(:priority) { create(:default_priority, name: "Normal") }
shared_let(:open_status_with_0p_done_ratio) do
create(:status, name: "open", default_done_ratio: 0)
end
shared_let(:in_progress_status_with_50p_done_ratio) do
create(:status, name: "in progress", default_done_ratio: 50)
end
shared_let(:complete_status_with_100p_done_ratio) do
create(:status, name: "complete", default_done_ratio: 100)
end
shared_let(:estimated_hours) { 10.0 }
shared_let(:remaining_hours) { 5.0 }
shared_let(:work_package) do
create(:work_package,
project:,
type: type_task,
status: open_status_with_0p_done_ratio) do |wp|
update_work_package_with(wp, estimated_hours:, remaining_hours:)
end
end
def update_work_package_with(work_package, attributes)
WorkPackages::UpdateService.new(model: work_package,
user:,
contract_class: WorkPackages::CreateContract)
.call(**attributes)
end
let(:progress_query) do
create(:query,
project:,
user:,
display_sums: false,
column_names: %i[id subject type status
estimated_hours remaining_hours done_ratio]) do |query|
create(:view_work_packages_table, query:)
end
end
let(:work_package_table) { Pages::WorkPackagesTable.new(project) }
let(:work_package_row) { work_package_table.work_package_container(work_package) }
let(:progress_popover) { work_package_table.progress_popover(work_package) }
let(:work_package_create_page) { Pages::FullWorkPackageCreate.new(project:) }
current_user { user }
def visit_progress_query_displaying_work_package
work_package_table.visit_query(progress_query)
work_package_table.expect_work_package_listed(work_package)
end
describe "clicking on a field on the work package table" do
it "sets the cursor after the last character on the selected input field" do
visit_progress_query_displaying_work_package
progress_popover.open_by_clicking_on_field(:work)
progress_popover.expect_cursor_at_end_of_input(:work)
end
end
describe "work based mode" do
it "formats the values on input field blur" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.set_values(work: "5")
progress_popover.focus(:remaining_work)
progress_popover.expect_values(work: "5h")
progress_popover.set_values(remaining_work: "2")
progress_popover.focus(:percent_complete)
progress_popover.expect_values(remaining_work: "2h")
progress_popover.set_values(percent_complete: "30")
progress_popover.focus(:work)
progress_popover.expect_values(percent_complete: "30%")
end
shared_examples_for "opens the modal with the clicked field in focus" do
it "when clicking on a field opens the modal and focuses on the related modal input field", :aggregate_failures do
visit_progress_query_displaying_work_package
%i[work remaining_work percent_complete].each do |field_name|
progress_popover.open_by_clicking_on_field(field_name)
progress_popover.expect_focused(field_name)
progress_popover.close
end
end
end
context "with no fields set" do
before do
update_work_package_with(work_package, estimated_hours: nil, remaining_hours: nil, done_ratio: nil)
end
include_examples "opens the modal with the clicked field in focus"
end
context "with all fields set" do
before do
update_work_package_with(work_package, estimated_hours: 25.0, remaining_hours: 15.0)
end
include_examples "opens the modal with the clicked field in focus"
end
end
describe "status based mode", with_settings: { work_package_done_ratio: "status" } do
it "formats the values on input field blur" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.set_values(work: "5")
progress_popover.focus(:status)
progress_popover.expect_values(work: "5h")
end
describe "clicking on the work field in the work package table " \
"with no fields set" do
before { update_work_package_with(work_package, estimated_hours: nil, remaining_hours: nil) }
it "opens the modal with work in focus" do
visit_progress_query_displaying_work_package
progress_popover.open_by_clicking_on_field(:work)
progress_popover.expect_focused(:work)
end
end
describe "clicking on the work field in the work package table " \
"with all fields set" do
before { update_work_package_with(work_package, estimated_hours: 20.0, remaining_hours: 15.0) }
it "opens the modal with work in focus" do
visit_progress_query_displaying_work_package
progress_popover.open_by_clicking_on_field(:work)
progress_popover.expect_focused(:work)
end
end
describe "Remaining work field" do
it "is readonly" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.expect_read_only(:remaining_work)
end
end
describe "Status field" do
before { open_status_with_0p_done_ratio.update!(is_default: true) }
it "renders only assignable statuses as options" do
# Create a single valid transition from "open" to "in progress"
create(:workflow,
type_id: type_task.id,
old_status: open_status_with_0p_done_ratio,
new_status: in_progress_status_with_50p_done_ratio,
role:)
visit_progress_query_displaying_work_package
progress_popover.open
# The only defined workflow is "open" to "in progress" so "complete" must
# not be listed as an available option
progress_popover.expect_select_with_options(:status, "open (0%)", "in progress (50%)")
progress_popover.expect_select_without_options(:status, "complete (100%)")
# Create another valid transition from "open" to "complete"
create(:workflow,
type_id: type_task.id,
old_status: open_status_with_0p_done_ratio,
new_status: complete_status_with_100p_done_ratio,
role:)
visit_progress_query_displaying_work_package
wait_for_reload
progress_popover.open
progress_popover.expect_select_with_options(:status, "open (0%)", "in progress (50%)", "complete (100%)")
end
end
context "when on a new work package form" do
let(:progress_popover) { Components::WorkPackages::ProgressPopover.new(create_form: true) }
specify "modal renders when no default status is set for new work packages" do
work_package_create_page.visit!
progress_popover.open
progress_popover.expect_value(:status, :empty_without_any_options, disabled: true)
end
context "with a default status set for new work packages" do
before_all do
open_status_with_0p_done_ratio.update!(is_default: true)
create(:workflow,
type_id: type_task.id,
old_status: open_status_with_0p_done_ratio,
new_status: in_progress_status_with_50p_done_ratio,
role:)
create(:workflow,
type_id: type_task.id,
old_status: open_status_with_0p_done_ratio,
new_status: complete_status_with_100p_done_ratio,
role:)
end
it "can create work package after setting work" do
work_package_create_page.visit!
work_package_create_page.expect_fully_loaded
progress_popover.open
progress_popover.set_values(work: "invalid")
progress_popover.expect_values(remaining_work: "")
progress_popover.expect_errors(work: "Is not a valid duration.")
# The modal does not go away when clicking Save until all fields are valid
3.times do
progress_popover.save
sleep 0.2
progress_popover.expect_errors(work: "Is not a valid duration.")
end
progress_popover.set_values(work: "10h")
progress_popover.save
work_package_create_page.set_attributes({ subject: "hello" })
work_package_create_page.save!
work_package_table.expect_and_dismiss_toaster(message: "Successful creation.", wait: 5)
expect(WorkPackage.order(id: :asc).last).to have_attributes(
estimated_hours: 10.0,
remaining_hours: 10.0,
done_ratio: 0
)
end
it "renders the status selection field inside the modal as disabled " \
"and allows setting the status solely by the top-left field" do
work_package_create_page.visit!
work_package_create_page.expect_fully_loaded
progress_popover.open
progress_popover.expect_disabled(:status)
progress_popover.expect_value(:status, "open (0%)", disabled: true)
progress_popover.close
work_package_create_page.set_status("in progress")
progress_popover.open
progress_popover.expect_value(:status, "in progress (50%)", disabled: true)
end
it "can open the modal, then save without modifying anything" do
work_package_create_page.visit!
work_package_create_page.set_attributes({ subject: "hello" })
progress_popover.open
progress_popover.save
work_package_create_page.expect_no_toaster(type: "error")
work_package_create_page.save!
work_package_table.expect_and_dismiss_toaster(message: "Successful creation.")
end
it "updates remaining work when status is changed" do
work_package_create_page.visit!
work_package_create_page.set_attributes({ subject: "hello" })
progress_popover.open
progress_popover.expect_hints(work: nil, remaining_work: nil)
progress_popover.set_values(work: "14h")
progress_popover.expect_values(remaining_work: "14h")
progress_popover.expect_hints(remaining_work: :derived)
progress_popover.save
work_package_create_page.set_status("in progress")
progress_popover.open
progress_popover.expect_values(work: "14h", remaining_work: "7h")
progress_popover.expect_hints(remaining_work: :derived)
end
end
end
end
describe "opening the progress modal" do
before_all do
create(:workflow,
type_id: type_task.id,
old_status: open_status_with_0p_done_ratio,
new_status: in_progress_status_with_50p_done_ratio,
role:)
create(:workflow,
type_id: type_task.id,
old_status: open_status_with_0p_done_ratio,
new_status: complete_status_with_100p_done_ratio,
role:)
end
describe "field value format" do
context "with all values set" do
before { update_work_package_with(work_package, estimated_hours: 10.0, remaining_hours: 2.12345) }
it "populates fields with values correctly formatted" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.expect_values(
work: "10h",
remaining_work: "2.12h", # 2h 7m
percent_complete: "79%"
)
end
end
context "on create page" do
it "can create work package after setting progress values multiple times" do
work_package_create_page.visit!
work_package_create_page.set_attributes({ subject: "hello" })
work_edit_field = ProgressEditField.new("#content", :estimatedTime)
remaining_work_edit_field = ProgressEditField.new("#content", :remainingTime)
percent_complete_edit_field = ProgressEditField.new("#content", :percentageDone)
expect(work_edit_field.trigger_element.value).to eq("-")
expect(remaining_work_edit_field.trigger_element.value).to eq("-")
expect(percent_complete_edit_field.trigger_element.value).to eq("-")
work_package_create_page.set_progress_attributes({ estimatedTime: "0h" })
expect(work_edit_field.trigger_element.value).to eq("0h")
expect(remaining_work_edit_field.trigger_element.value).to eq("0h")
expect(percent_complete_edit_field.trigger_element.value).to eq("-")
work_package_create_page.set_progress_attributes({ estimatedTime: "5h" })
expect(work_edit_field.trigger_element.value).to eq("5h")
expect(remaining_work_edit_field.trigger_element.value).to eq("5h")
expect(percent_complete_edit_field.trigger_element.value).to eq("0%")
work_package_create_page.set_progress_attributes({ percentageDone: "40%" })
expect(work_edit_field.trigger_element.value).to eq("5h")
expect(remaining_work_edit_field.trigger_element.value).to eq("3h")
expect(percent_complete_edit_field.trigger_element.value).to eq("40%")
work_package_create_page.save!
work_package_table.expect_and_dismiss_toaster(message: "Successful creation.", wait: 5)
expect(WorkPackage.order(id: :asc).last).to have_attributes(
estimated_hours: 5.0,
remaining_hours: 3.0,
done_ratio: 40
)
end
end
context "with % complete set and setting long decimal values in modal" do
before do
work_package.attributes = {
estimated_hours: nil, remaining_hours: nil, done_ratio: 89
}
work_package.save(validate: false)
end
it "does not lose precision due to conversion from ISO duration to hours (rounded to closest minute)" do
visit_progress_query_displaying_work_package
# set work to 2.5567
progress_popover.open
progress_popover.set_values(work: "2.5567")
progress_popover.save
work_package_table.expect_and_dismiss_toaster(message: "Successful update.")
# work should have been set to 2.56 and remaining work to 0.28
work_package.reload
expect(work_package.estimated_hours).to eq(2.56)
expect(work_package.remaining_hours).to eq(0.28)
# work should be displayed as "2h 34m" ("2h 33m 36s" rounded to minutes),
# and remaining work as "17m" ("16m 48s" rounded to minutes)
progress_popover.open
progress_popover.expect_values(
work: "2.56h", # 2h 34m
remaining_work: "0.28h" # 17m
)
end
end
context "with empty values" do
before do
update_work_package_with(work_package, estimated_hours: nil, remaining_hours: nil, done_ratio: nil)
end
it "populates all fields with blank values" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.expect_values(
work: "",
remaining_work: "",
percent_complete: ""
)
end
end
context "with invalid values" do
before do
update_work_package_with(work_package, estimated_hours: nil, remaining_hours: nil, done_ratio: nil)
end
it "does not close the modal when clicking save multiple times (Bug #57423)" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.set_values(work: "invalid")
progress_popover.expect_values(remaining_work: "")
progress_popover.expect_errors(work: "Is not a valid duration.")
# The modal does not go away when clicking save
3.times do
progress_popover.save
sleep 0.2
progress_popover.expect_errors(work: "Is not a valid duration.")
end
end
end
describe "status field", with_settings: { work_package_done_ratio: "status" } do
it "renders the status options as the << status_name (percent_complete_value %) >>" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.expect_select_with_options(:status,
"open (0%)",
"in progress (50%)",
"complete (100%)")
end
end
end
it "disables the field that triggered the modal" do
visit_progress_query_displaying_work_package
work_edit_field = ProgressEditField.new(work_package_row, :estimatedTime)
work_edit_field.activate!
work_edit_field.expect_trigger_field_disabled
end
it "allows clicking on a field other than the one that triggered the modal " \
"and opens the modal with said field selected" do
visit_progress_query_displaying_work_package
work_edit_field = ProgressEditField.new(work_package_row, :estimatedTime)
remaining_work_edit_field = ProgressEditField.new(work_package_row, :remainingTime)
remaining_work_edit_field.activate!
remaining_work_edit_field.expect_modal_field_in_focus
remaining_work_edit_field.expect_trigger_field_disabled
work_edit_field.reactivate!
work_edit_field.expect_modal_field_in_focus
work_edit_field.expect_trigger_field_disabled
end
end
describe "Live-update edge cases" do
context "given work = 10h, remaining work = 4h, % complete = 60%" do
before { update_work_package_with(work_package, estimated_hours: 10.0, remaining_hours: 4.0) }
specify "Case 1: When I clear work it clears remaining work" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.set_values(work: "")
progress_popover.expect_values(remaining_work: "")
progress_popover.expect_hints(remaining_work: :cleared_because_work_is_empty,
percent_complete: nil)
end
specify "Case 2: when work is set to 12h, " \
"remaining work is automatically set to 6h " \
"and subsequently work is set to 14h, " \
"remaining work updates to 8h" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.set_values(work: "12")
progress_popover.expect_values(remaining_work: "6h")
progress_popover.expect_hints(remaining_work: { increased_by_delta_like_work: { delta: 2 } },
percent_complete: :derived)
progress_popover.set_values(work: "14")
progress_popover.expect_values(remaining_work: "8h")
progress_popover.expect_hints(remaining_work: { increased_by_delta_like_work: { delta: 4 } },
percent_complete: :derived)
end
specify "Case 3: when work is set to 2h, " \
"remaining work is automatically set to 0h, " \
"and work is subsequently set to 12h, " \
"remaining work is updated to 6h" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.set_values(work: "2")
progress_popover.expect_values(remaining_work: "0h")
progress_popover.expect_hints(remaining_work: { decreased_by_delta_like_work: { delta: -8 } },
percent_complete: :derived)
progress_popover.set_values(work: "12")
progress_popover.expect_values(remaining_work: "6h")
progress_popover.expect_hints(remaining_work: { increased_by_delta_like_work: { delta: 2 } },
percent_complete: :derived)
end
specify "Case 23-7: when remaining work or % complete are set, work never " \
"changes, instead remaining work and % complete are derived" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.set_values(remaining_work: "2h")
progress_popover.expect_values(work: "10h", percent_complete: "80%")
progress_popover.expect_hints(work: nil,
remaining_work: nil,
percent_complete: :derived)
progress_popover.set_values(percent_complete: "50%")
progress_popover.expect_values(work: "10h", remaining_work: "5h")
progress_popover.expect_hints(work: nil,
remaining_work: :derived,
percent_complete: nil)
progress_popover.set_values(remaining_work: "9h")
progress_popover.expect_values(work: "10h", percent_complete: "10%")
progress_popover.expect_hints(work: nil,
remaining_work: nil,
percent_complete: :derived)
end
# scenario from https://community.openproject.org/wp/57370
specify "Case 23-11: when work is cleared, and remaining work is set, " \
"then work is derived again" do
visit_progress_query_displaying_work_package
progress_popover.open
# clear work
progress_popover.set_values(work: "")
progress_popover.expect_values(work: "", remaining_work: "", percent_complete: "60%")
progress_popover.expect_hints(work: nil,
remaining_work: :cleared_because_work_is_empty,
percent_complete: nil)
# set remaining work
progress_popover.set_values(remaining_work: "8h")
# work is derived
progress_popover.expect_values(work: "20h", remaining_work: "8h", percent_complete: "60%")
progress_popover.expect_hints(work: :derived,
remaining_work: nil,
percent_complete: nil)
end
# scenario from https://community.openproject.org/wp/57370
specify "Case 23-14: when remaining work is cleared, and work is set, " \
"then remaining work is derived again" do
visit_progress_query_displaying_work_package
progress_popover.open
# clear work
progress_popover.set_values(remaining_work: "")
progress_popover.expect_values(work: "", remaining_work: "", percent_complete: "60%")
progress_popover.expect_hints(work: :cleared_because_remaining_work_is_empty,
remaining_work: nil,
percent_complete: nil)
# set work
progress_popover.set_values(work: "20h")
# => remaining work is derived
progress_popover.expect_values(work: "20h", remaining_work: "8h", percent_complete: "60%")
progress_popover.expect_hints(work: nil,
remaining_work: :derived,
percent_complete: nil)
end
# scenario from https://community.openproject.org/wp/57370
specify "Case 33-1: when work and % complete are cleared, and then work " \
"is set again then % complete is derived again" do
visit_progress_query_displaying_work_package
progress_popover.open
# clear work and % complete
progress_popover.set_values(work: "", percent_complete: "")
progress_popover.expect_values(work: "", remaining_work: "4h", percent_complete: "")
progress_popover.expect_hints(work: nil,
remaining_work: nil,
percent_complete: nil)
# set work
progress_popover.set_values(work: "20h")
# => % complete is derived
progress_popover.expect_values(work: "20h", remaining_work: "4h", percent_complete: "80%")
progress_popover.expect_hints(work: nil,
remaining_work: nil,
percent_complete: :derived)
end
# scenario from https://community.openproject.org/wp/57370
specify "Case 33-2: when remaining work and % complete are cleared, " \
"changing or clearing work does not modify % complete at all" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.set_values(remaining_work: "")
progress_popover.expect_values(work: "", remaining_work: "", percent_complete: "60%")
progress_popover.expect_hints(work: :cleared_because_remaining_work_is_empty)
progress_popover.set_values(percent_complete: "")
progress_popover.expect_values(work: "10h", remaining_work: "", percent_complete: "")
progress_popover.expect_hints(work: nil, remaining_work: nil, percent_complete: nil)
# partially deleting work value like when pressing backspace
progress_popover.set_values(work: "1")
progress_popover.expect_values(work: "1", remaining_work: "", percent_complete: "")
progress_popover.expect_hints(work: nil, remaining_work: nil, percent_complete: nil)
# completly clearing work value
progress_popover.set_values(work: "")
progress_popover.expect_values(work: "", remaining_work: "", percent_complete: "")
progress_popover.expect_hints(work: nil, remaining_work: nil, percent_complete: nil)
end
end
context "given work, remaining work, and % complete are all empty" do
before do
update_work_package_with(work_package, estimated_hours: nil, remaining_hours: nil, done_ratio: nil)
end
# scenario from https://community.openproject.org/wp/57370
specify "Case 20-4: when remaining work and % complete are both set, work " \
"is derived because it's initially empty" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.set_values(remaining_work: "2h", percent_complete: "50%")
progress_popover.expect_values(work: "4h")
progress_popover.expect_hints(work: :derived,
remaining_work: nil,
percent_complete: nil)
progress_popover.set_values(remaining_work: "10h")
progress_popover.expect_values(work: "20h")
progress_popover.expect_hints(work: :derived,
remaining_work: nil,
percent_complete: nil)
end
# scenario from https://community.openproject.org/wp/57370
specify "Case 30-1: when % complete is set, remaining work is set, and " \
"% complete is changed, then work is always derived" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.set_values(percent_complete: "40%")
progress_popover.expect_values(work: "", remaining_work: "", percent_complete: "40%")
progress_popover.expect_hints(work: nil,
remaining_work: nil,
percent_complete: nil)
progress_popover.set_values(remaining_work: "60h")
progress_popover.expect_values(work: "100h", remaining_work: "60h", percent_complete: "40%")
progress_popover.expect_hints(work: :derived,
remaining_work: nil,
percent_complete: nil)
progress_popover.set_values(percent_complete: "80%")
progress_popover.expect_values(work: "300h", remaining_work: "60h", percent_complete: "80%")
progress_popover.expect_hints(work: :derived,
remaining_work: nil,
percent_complete: nil)
end
end
context "in status-based mode",
with_settings: { work_package_done_ratio: "status" } do
before_all do
open_status_with_0p_done_ratio.update!(is_default: true)
create(:workflow,
type_id: type_task.id,
old_status: open_status_with_0p_done_ratio,
new_status: in_progress_status_with_50p_done_ratio,
role:)
create(:workflow,
type_id: type_task.id,
old_status: open_status_with_0p_done_ratio,
new_status: complete_status_with_100p_done_ratio,
role:)
end
context "given status has % complete to 50% and work is unset" do
before do
update_work_package_with(work_package, status: in_progress_status_with_50p_done_ratio,
estimated_hours: nil)
end
specify "when setting work, it updates remaining work and % complete" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.expect_values(work: "", remaining_work: "")
progress_popover.expect_hints(work: nil, remaining_work: nil)
progress_popover.set_values(work: "10h")
progress_popover.expect_values(work: "10h", remaining_work: "5h")
progress_popover.expect_hints(work: nil, remaining_work: :derived)
end
end
context "given work = 10h" do
before do
update_work_package_with(work_package, estimated_hours: 10)
end
specify "when changing the status or work, it updates remaining work" do
visit_progress_query_displaying_work_package
progress_popover.open
progress_popover.expect_hints(work: nil, remaining_work: nil)
progress_popover.set_values(status: in_progress_status_with_50p_done_ratio)
progress_popover.expect_values(work: "10h", remaining_work: "5h")
progress_popover.expect_hints(work: nil, remaining_work: :derived)
progress_popover.set_values(work: "")
progress_popover.expect_values(work: "", remaining_work: "")
progress_popover.expect_hints(work: nil, remaining_work: :cleared_because_work_is_empty)
end
end
end
end
end