mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Refactor views for user administration to be correctly in the users controller, add feature specs
This commit is contained in:
@@ -62,7 +62,7 @@ module Users
|
||||
private
|
||||
|
||||
def path_for(year:)
|
||||
url_for(controller: params[:controller], action: params[:action], user_id: params[:user_id], year:)
|
||||
url_for(controller: params[:controller], action: params[:action], user_id: params[:user_id], year:, tab: params[:tab])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,19 +36,12 @@ class Users::NonWorkingTimesController < ApplicationController
|
||||
|
||||
before_action :check_working_times_feature_flag_is_active
|
||||
|
||||
authorization_checked! :index, :new, :create, :edit, :update, :destroy, :working_days_preview
|
||||
authorization_checked! :new, :create, :edit, :update, :destroy, :working_days_preview
|
||||
|
||||
before_action :find_user
|
||||
before_action :authorize_manage_working_times
|
||||
before_action :find_non_working_time, only: %i[edit update destroy]
|
||||
|
||||
def index
|
||||
@year = (params[:year].presence || Date.current.year).to_i
|
||||
@non_working_times = @user.non_working_time_entities_for_year(@year)
|
||||
|
||||
render "users/edit"
|
||||
end
|
||||
|
||||
def new
|
||||
@non_working_time = @user.non_working_times.build(prefilled_params)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class Users::WorkingHoursController < ApplicationController
|
||||
|
||||
before_action :check_working_times_feature_flag_is_active
|
||||
|
||||
authorization_checked! :index, :new, :edit, :create, :update, :destroy
|
||||
authorization_checked! :new, :edit, :create, :update, :destroy
|
||||
|
||||
before_action :find_user
|
||||
before_action :authorize_manage_working_times
|
||||
@@ -45,20 +45,6 @@ class Users::WorkingHoursController < ApplicationController
|
||||
before_action :authorize_working_hours_edit, only: %i[edit update]
|
||||
before_action :authorize_working_hours_delete, only: %i[destroy]
|
||||
|
||||
def index
|
||||
@current_working_hours = @user.working_hours.current
|
||||
|
||||
@future_working_hours = @user.working_hours.upcoming(Date.current + 1)
|
||||
|
||||
@past_working_hours = if @current_working_hours
|
||||
@user.working_hours.history_for(@current_working_hours)
|
||||
else
|
||||
UserWorkingHours.none
|
||||
end
|
||||
|
||||
render "users/edit"
|
||||
end
|
||||
|
||||
def new
|
||||
@user_working_hours = if current_context?
|
||||
duplicate_current_working_hours(@user)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
class UsersController < ApplicationController
|
||||
include OpTurbo::ComponentStream
|
||||
include WorkingTimesAuthorization
|
||||
|
||||
layout "admin"
|
||||
|
||||
@@ -88,6 +89,8 @@ class UsersController < ApplicationController
|
||||
@membership ||= Member.new
|
||||
@individual_principal = @user
|
||||
@contract = Users::UpdateContract.new(@user, current_user)
|
||||
|
||||
prepare_views_for_tab
|
||||
end
|
||||
|
||||
def create # rubocop:disable Metrics/AbcSize
|
||||
@@ -353,4 +356,27 @@ class UsersController < ApplicationController
|
||||
login: params[:user][:login] || params[:user][:mail],
|
||||
status: User.statuses[:invited])
|
||||
end
|
||||
|
||||
def prepare_views_for_tab # rubocop:disable Metrics/AbcSize
|
||||
if params[:tab] == "non_working_times"
|
||||
authorize_manage_working_times
|
||||
check_working_times_feature_flag_is_active
|
||||
|
||||
@year = (params[:year].presence || Date.current.year).to_i
|
||||
@non_working_times = @user.non_working_time_entities_for_year(@year)
|
||||
elsif params[:tab] == "working_hours"
|
||||
authorize_manage_working_times
|
||||
check_working_times_feature_flag_is_active
|
||||
|
||||
@current_working_hours = @user.working_hours.current
|
||||
|
||||
@future_working_hours = @user.working_hours.upcoming(Date.current + 1)
|
||||
|
||||
@past_working_hours = if @current_working_hours
|
||||
@user.working_hours.history_for(@current_working_hours)
|
||||
else
|
||||
UserWorkingHours.none
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -52,7 +52,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
"admin--users-password-auth-selected-value": @user.ldap_auth_source_id.blank?
|
||||
},
|
||||
as: :user do |f| %>
|
||||
<%= render partial: "form", locals: { f: f } %>
|
||||
<%= render partial: "users/form", locals: { f: f } %>
|
||||
|
||||
<%= styled_button_tag t(:button_save), class: "-primary -with-icon icon-checkmark" %>
|
||||
<% end %>
|
||||
|
||||
@@ -27,13 +27,4 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%= render(Users::NonWorkingTimes::SubHeaderComponent.new(year: @year, user: @user)) %>
|
||||
|
||||
<%= render(Primer::OpenProject::FlexLayout.new(align_items: :flex_start, gap: :normal)) do |layout| %>
|
||||
<% layout.with_column(flex: 1) do %>
|
||||
<%= render(Users::NonWorkingTimes::CalendarComponent.new(non_working_times: @non_working_times, year: @year)) %>
|
||||
<% end %>
|
||||
<% layout.with_column do %>
|
||||
<%= render(Users::NonWorkingTimes::SidebarComponent.new(non_working_times: @non_working_times, year: @year)) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render(Users::NonWorkingTimes::YearOverviewComponent.new(year: @year, non_working_times: @non_working_times, user: @user)) %>
|
||||
|
||||
+2
-2
@@ -920,8 +920,8 @@ Rails.application.routes.draw do
|
||||
|
||||
resources :users, constraints: { id: /(\d+|me)/ }, except: :edit do
|
||||
resources :memberships, controller: "users/memberships", only: %i[update create destroy]
|
||||
resources :working_hours, controller: "users/working_hours"
|
||||
resources :non_working_times, controller: "users/non_working_times", only: %i[index new create edit update destroy] do
|
||||
resources :working_hours, controller: "users/working_hours", except: [:index]
|
||||
resources :non_working_times, controller: "users/non_working_times", except: [:index] do
|
||||
collection do
|
||||
get :working_days_preview
|
||||
end
|
||||
|
||||
@@ -69,14 +69,14 @@ module OpenProject
|
||||
{
|
||||
name: "working_hours",
|
||||
partial: "users/working_hours/list",
|
||||
path: ->(params) { user_working_hours_path(params[:user]) },
|
||||
path: ->(params) { edit_user_path(params[:user], tab: :working_hours) },
|
||||
label: :label_working_hours,
|
||||
only_if: ->(*) { OpenProject::FeatureDecisions.user_working_times_active? && User.current.allowed_globally?(:manage_working_times) }
|
||||
},
|
||||
{
|
||||
name: "non_working_times",
|
||||
partial: "users/non_working_times/list",
|
||||
path: ->(params) { user_non_working_times_path(params[:user]) },
|
||||
path: ->(params) { edit_user_path(params[:user], tab: :non_working_times) },
|
||||
label: :label_non_working_days,
|
||||
only_if: ->(*) { OpenProject::FeatureDecisions.user_working_times_active? && User.current.allowed_globally?(:manage_working_times) }
|
||||
},
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
# 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 "My working times pages", :js, with_flag: { user_working_times: true } do
|
||||
describe "/my/non_working_times" do
|
||||
let(:nwt_page) { Pages::Users::NonWorkingTimes.new(year: 2026) }
|
||||
|
||||
context "with manage_own_working_times permission" do
|
||||
current_user { create(:user, global_permissions: [:manage_own_working_times]) }
|
||||
|
||||
it "renders the calendar for the current user" do
|
||||
nwt_page.visit!
|
||||
|
||||
nwt_page.expect_calendar_rendered
|
||||
end
|
||||
|
||||
it "makes the calendar selectable (new URL data attribute is present)" do
|
||||
nwt_page.visit!
|
||||
|
||||
nwt_page.expect_selectable_calendar
|
||||
end
|
||||
|
||||
it "shows the add button" do
|
||||
nwt_page.visit!
|
||||
|
||||
nwt_page.expect_add_button
|
||||
end
|
||||
|
||||
context "when clicking a calendar day" do
|
||||
it "opens the create dialog pre-filled with that date" do
|
||||
nwt_page.visit!
|
||||
|
||||
nwt_page.click_calendar_day("2026-04-14")
|
||||
|
||||
nwt_page.expect_dialog_open
|
||||
nwt_page.expect_dialog_dates(start_date: "2026-04-14", end_date: "2026-04-14")
|
||||
end
|
||||
end
|
||||
|
||||
context "when creating a non-working time" do
|
||||
it "can create an entry for themselves" do
|
||||
nwt_page.visit!
|
||||
|
||||
nwt_page.open_create_dialog
|
||||
|
||||
nwt_page.set_start_date(Date.new(2026, 7, 6))
|
||||
nwt_page.set_end_date(Date.new(2026, 7, 10))
|
||||
|
||||
nwt_page.confirm_dialog
|
||||
|
||||
expect(current_user.non_working_times.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "when editing an existing entry" do
|
||||
let!(:nwt) do
|
||||
create(:user_non_working_time, user: current_user,
|
||||
start_date: Date.new(2026, 8, 3),
|
||||
end_date: Date.new(2026, 8, 7))
|
||||
end
|
||||
|
||||
it "can edit via the sidebar link" do
|
||||
nwt_page.visit!
|
||||
|
||||
nwt_page.open_edit_dialog_from_sidebar
|
||||
nwt_page.expect_dialog_start_date("2026-08-03")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with manage_working_times permission" do
|
||||
current_user { create(:user, global_permissions: [:manage_working_times]) }
|
||||
|
||||
it "renders the calendar with the add button and selectable calendar" do
|
||||
nwt_page.visit!
|
||||
|
||||
nwt_page.expect_add_button
|
||||
nwt_page.expect_selectable_calendar
|
||||
end
|
||||
end
|
||||
|
||||
context "with no working times permissions" do
|
||||
current_user { create(:user) }
|
||||
|
||||
it "renders the page but without the add button or selectable calendar" do
|
||||
nwt_page.visit!
|
||||
|
||||
nwt_page.expect_calendar_rendered
|
||||
nwt_page.expect_no_add_button
|
||||
nwt_page.expect_non_selectable_calendar
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "/my/working_hours" do
|
||||
let(:wh_page) { Pages::Users::WorkingHours.new }
|
||||
|
||||
context "with manage_own_working_times permission" do
|
||||
current_user { create(:user, global_permissions: [:manage_own_working_times]) }
|
||||
|
||||
it "renders the current schedule section" do
|
||||
wh_page.visit!
|
||||
|
||||
wh_page.expect_current_schedule_section
|
||||
wh_page.expect_future_section
|
||||
wh_page.expect_history_section
|
||||
end
|
||||
|
||||
it "shows the pencil button to manage the current schedule" do
|
||||
wh_page.visit!
|
||||
|
||||
wh_page.expect_editable_current_schedule
|
||||
end
|
||||
|
||||
it "shows the add button for future schedules" do
|
||||
wh_page.visit!
|
||||
|
||||
wh_page.expect_add_future_button
|
||||
end
|
||||
|
||||
context "when creating a current schedule" do
|
||||
it "opens the dialog without a valid_from field and creates the record" do
|
||||
wh_page.visit!
|
||||
|
||||
wh_page.open_current_schedule_dialog
|
||||
wh_page.expect_dialog_title_current
|
||||
wh_page.expect_no_valid_from_field
|
||||
|
||||
wh_page.submit_dialog
|
||||
|
||||
expect(current_user.working_hours.current).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context "with an existing current schedule" do
|
||||
let!(:working_hours) do
|
||||
create(:user_working_hours,
|
||||
user: current_user,
|
||||
valid_from: Date.current,
|
||||
monday: 480, tuesday: 480, wednesday: 480, thursday: 480, friday: 480,
|
||||
saturday: 0, sunday: 0,
|
||||
availability_factor: 100)
|
||||
end
|
||||
|
||||
it "shows the correct stats on the current schedule card" do
|
||||
wh_page.visit!
|
||||
|
||||
wh_page.expect_stats(work_days: 5, weekly_hours: "40h", availability: "100%")
|
||||
end
|
||||
|
||||
it "opens the edit dialog for the current schedule" do
|
||||
wh_page.visit!
|
||||
|
||||
wh_page.open_current_schedule_dialog
|
||||
wh_page.expect_dialog_title_current
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with manage_working_times permission" do
|
||||
current_user { create(:user, global_permissions: [:manage_working_times]) }
|
||||
|
||||
it "renders the working hours page" do
|
||||
wh_page.visit!
|
||||
|
||||
wh_page.expect_current_schedule_section
|
||||
end
|
||||
end
|
||||
|
||||
context "with no working times permissions" do
|
||||
current_user { create(:user) }
|
||||
|
||||
it "renders the page but without the edit pencil enabled" do
|
||||
wh_page.visit!
|
||||
|
||||
wh_page.expect_current_schedule_section
|
||||
wh_page.expect_not_editable_current_schedule
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -31,80 +31,50 @@
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe "User non-working times", :js, with_flag: { user_working_times: true } do
|
||||
shared_let(:admin) { create(:admin) }
|
||||
shared_let(:user) { create(:user, global_permissions: %i[manage_user view_all_principals manage_working_times]) }
|
||||
shared_let(:managed_user) { create(:user) }
|
||||
|
||||
let(:dialog_selector) { "##{Users::NonWorkingTimes::DialogComponent::DIALOG_ID}" }
|
||||
let(:nwt_page) { Pages::Users::NonWorkingTimes.new(user: managed_user, year: 2026) }
|
||||
|
||||
def visit_non_working_times(for_user: managed_user, year: 2026)
|
||||
visit user_non_working_times_path(for_user, year:)
|
||||
end
|
||||
|
||||
def open_create_dialog
|
||||
click_on I18n.t(:button_add_non_working_time)
|
||||
expect(page).to have_css(dialog_selector)
|
||||
end
|
||||
|
||||
def set_date_in_dialog(field_name, date)
|
||||
datepicker = Components::BasicDatepicker.new(dialog_selector)
|
||||
datepicker.open("input[name='non_working_time[#{field_name}]']")
|
||||
datepicker.set_date(date)
|
||||
end
|
||||
|
||||
def submit_dialog
|
||||
within(dialog_selector) { click_on I18n.t(:button_confirm) }
|
||||
expect(page).to have_no_css(dialog_selector)
|
||||
end
|
||||
|
||||
def expect_sidebar_entry(text)
|
||||
expect(page).to have_css("a[data-controller='async-dialog']", text:)
|
||||
end
|
||||
|
||||
def expect_no_sidebar_entry(text)
|
||||
expect(page).to have_no_css("a[data-controller='async-dialog']", text:)
|
||||
end
|
||||
|
||||
current_user { admin }
|
||||
current_user { user }
|
||||
|
||||
describe "creating a non-working time" do
|
||||
before { visit_non_working_times }
|
||||
before { nwt_page.visit! }
|
||||
|
||||
it "creates a single-day entry" do
|
||||
open_create_dialog
|
||||
nwt_page.open_create_dialog
|
||||
|
||||
set_date_in_dialog(:start_date, Date.new(2026, 3, 10))
|
||||
set_date_in_dialog(:end_date, Date.new(2026, 3, 10))
|
||||
nwt_page.set_start_date(Date.new(2026, 3, 10))
|
||||
nwt_page.set_end_date(Date.new(2026, 3, 10))
|
||||
|
||||
submit_dialog
|
||||
nwt_page.confirm_dialog
|
||||
|
||||
expect_sidebar_entry("Mar 10")
|
||||
nwt_page.expect_sidebar_entry("Mar 10")
|
||||
expect(managed_user.non_working_times.count).to eq(1)
|
||||
end
|
||||
|
||||
it "creates a multi-day range and shows correct working day count" do
|
||||
open_create_dialog
|
||||
nwt_page.open_create_dialog
|
||||
|
||||
# Monday to Friday = 5 working days
|
||||
set_date_in_dialog(:start_date, Date.new(2026, 3, 9))
|
||||
set_date_in_dialog(:end_date, Date.new(2026, 3, 13))
|
||||
nwt_page.set_start_date(Date.new(2026, 3, 9))
|
||||
nwt_page.set_end_date(Date.new(2026, 3, 13))
|
||||
|
||||
submit_dialog
|
||||
nwt_page.confirm_dialog
|
||||
|
||||
expect_sidebar_entry("5 working days")
|
||||
nwt_page.expect_sidebar_entry("5 working days")
|
||||
end
|
||||
|
||||
it "shows a validation error when end date is before start date" do
|
||||
open_create_dialog
|
||||
nwt_page.open_create_dialog
|
||||
|
||||
set_date_in_dialog(:start_date, Date.new(2026, 3, 13))
|
||||
set_date_in_dialog(:end_date, Date.new(2026, 3, 9))
|
||||
nwt_page.set_start_date(Date.new(2026, 3, 13))
|
||||
nwt_page.set_end_date(Date.new(2026, 3, 9))
|
||||
|
||||
within(dialog_selector) { click_on I18n.t(:button_confirm) }
|
||||
within(nwt_page.dialog_selector) { click_on I18n.t(:button_confirm) }
|
||||
|
||||
expect(page).to have_css(dialog_selector)
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_text(I18n.t("activerecord.errors.models.user_non_working_time.attributes.end_date.not_before_start_date"))
|
||||
end
|
||||
nwt_page.expect_dialog_open
|
||||
nwt_page.expect_validation_error(I18n.t("activerecord.errors.messages.not_before_start_date"))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -115,69 +85,144 @@ RSpec.describe "User non-working times", :js, with_flag: { user_working_times: t
|
||||
end_date: Date.new(2026, 3, 11))
|
||||
end
|
||||
|
||||
before { visit_non_working_times }
|
||||
before { nwt_page.visit! }
|
||||
|
||||
it "opens the edit dialog when clicking a sidebar entry" do
|
||||
find("a[data-controller='async-dialog']").click
|
||||
expect(page).to have_css(dialog_selector)
|
||||
nwt_page.open_edit_dialog_from_sidebar
|
||||
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_field("non_working_time[start_date]", with: "2026-03-09")
|
||||
expect(page).to have_field("non_working_time[end_date]", with: "2026-03-11")
|
||||
end
|
||||
nwt_page.expect_dialog_dates(start_date: "2026-03-09", end_date: "2026-03-11")
|
||||
end
|
||||
|
||||
it "saves updated dates" do
|
||||
find("a[data-controller='async-dialog']").click
|
||||
expect(page).to have_css(dialog_selector)
|
||||
nwt_page.open_edit_dialog_from_sidebar
|
||||
|
||||
set_date_in_dialog(:end_date, Date.new(2026, 3, 13))
|
||||
submit_dialog
|
||||
nwt_page.set_end_date(Date.new(2026, 3, 13))
|
||||
nwt_page.confirm_dialog
|
||||
|
||||
expect(non_working_time.reload.end_date).to eq(Date.new(2026, 3, 13))
|
||||
end
|
||||
end
|
||||
|
||||
describe "deleting a non-working time" do
|
||||
shared_let(:non_working_time) do
|
||||
let!(:non_working_time) do
|
||||
create(:user_non_working_time, user: managed_user,
|
||||
start_date: Date.new(2026, 4, 1),
|
||||
end_date: Date.new(2026, 4, 3))
|
||||
end
|
||||
|
||||
before { visit_non_working_times }
|
||||
before { nwt_page.visit! }
|
||||
|
||||
it "deletes the entry via the delete button in the edit dialog" do
|
||||
find("a[data-controller='async-dialog']").click
|
||||
expect(page).to have_css(dialog_selector)
|
||||
nwt_page.open_edit_dialog_from_sidebar
|
||||
nwt_page.delete_in_dialog
|
||||
|
||||
accept_confirm do
|
||||
within(dialog_selector) { click_on I18n.t(:button_delete) }
|
||||
end
|
||||
|
||||
expect(page).to have_no_css(dialog_selector)
|
||||
expect(UserNonWorkingTime.exists?(non_working_time.id)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "calendar interaction" do
|
||||
before { nwt_page.visit! }
|
||||
|
||||
it "pre-fills start and end date when clicking a single calendar day" do
|
||||
nwt_page.click_calendar_day("2026-03-10")
|
||||
|
||||
nwt_page.expect_dialog_open
|
||||
nwt_page.expect_dialog_dates(start_date: "2026-03-10", end_date: "2026-03-10")
|
||||
end
|
||||
|
||||
it "passes the new URL to the calendar so day selection is enabled" do
|
||||
nwt_page.expect_selectable_calendar
|
||||
end
|
||||
end
|
||||
|
||||
describe "calendar interaction - editing from the calendar event" do
|
||||
shared_let(:non_working_time) do
|
||||
create(:user_non_working_time, user: managed_user,
|
||||
start_date: Date.new(2026, 3, 9),
|
||||
end_date: Date.new(2026, 3, 11))
|
||||
end
|
||||
|
||||
before { nwt_page.visit! }
|
||||
|
||||
it "opens the edit dialog when clicking a calendar event" do
|
||||
nwt_page.open_edit_dialog_from_calendar
|
||||
|
||||
nwt_page.expect_dialog_dates(start_date: "2026-03-09", end_date: "2026-03-11")
|
||||
end
|
||||
end
|
||||
|
||||
describe "working days count preview" do
|
||||
before { nwt_page.visit! }
|
||||
|
||||
it "updates the working days count in real time as dates change" do
|
||||
nwt_page.open_create_dialog
|
||||
|
||||
# Monday to Friday = 5 working days
|
||||
nwt_page.set_start_date(Date.new(2026, 3, 9))
|
||||
nwt_page.set_end_date(Date.new(2026, 3, 13))
|
||||
|
||||
nwt_page.expect_working_days_count(5)
|
||||
end
|
||||
end
|
||||
|
||||
describe "overlap validation" do
|
||||
shared_let(:existing_nwt) do
|
||||
create(:user_non_working_time, user: managed_user,
|
||||
start_date: Date.new(2026, 3, 9),
|
||||
end_date: Date.new(2026, 3, 15))
|
||||
end
|
||||
|
||||
before { nwt_page.visit! }
|
||||
|
||||
it "shows a validation error when the new range overlaps an existing entry" do
|
||||
nwt_page.open_create_dialog
|
||||
|
||||
nwt_page.set_start_date(Date.new(2026, 3, 12))
|
||||
nwt_page.set_end_date(Date.new(2026, 3, 20))
|
||||
|
||||
within(nwt_page.dialog_selector) { click_on I18n.t(:button_confirm) }
|
||||
|
||||
nwt_page.expect_dialog_open
|
||||
nwt_page.expect_validation_error(I18n.t("activerecord.errors.messages.overlapping_range"))
|
||||
end
|
||||
end
|
||||
|
||||
describe "global non-working days exclusion" do
|
||||
shared_let(:holiday) do
|
||||
create(:non_working_day, date: Date.new(2026, 3, 11)) # Wednesday
|
||||
end
|
||||
|
||||
before { nwt_page.visit! }
|
||||
|
||||
it "excludes system non-working days from the working day count preview" do
|
||||
nwt_page.open_create_dialog
|
||||
|
||||
# Mon Mar 9 to Fri Mar 13 would be 5 days, but Wed Mar 11 is a system holiday
|
||||
nwt_page.set_start_date(Date.new(2026, 3, 9))
|
||||
nwt_page.set_end_date(Date.new(2026, 3, 13))
|
||||
|
||||
nwt_page.expect_working_days_count(4)
|
||||
end
|
||||
end
|
||||
|
||||
describe "access control" do
|
||||
context "with manage_own_working_times permission" do
|
||||
current_user { create(:user, global_permissions: [:manage_own_working_times]) }
|
||||
let(:nwt_page) { Pages::Users::NonWorkingTimes.new(user: current_user, year: 2026) }
|
||||
|
||||
it "can view and manage their own non-working times" do
|
||||
visit user_non_working_times_path(current_user, year: 2026)
|
||||
|
||||
expect(page).to have_button(I18n.t(:button_add_non_working_time))
|
||||
it "is denied access to their own non-working times on the users page" do
|
||||
nwt_page.visit!
|
||||
nwt_page.expect_not_authorized
|
||||
end
|
||||
|
||||
it "is denied access to another user's non-working times" do
|
||||
visit_non_working_times
|
||||
expect(page).to have_text(I18n.t(:notice_not_authorized))
|
||||
Pages::Users::NonWorkingTimes.new(user: managed_user, year: 2026).visit!
|
||||
nwt_page.expect_not_authorized
|
||||
end
|
||||
end
|
||||
|
||||
context "with manage_working_times permission" do
|
||||
current_user { create(:user, global_permissions: [:manage_working_times]) }
|
||||
context "with manage_user, view_all_principals, and manage_working_times permissions" do
|
||||
current_user { create(:user, global_permissions: %i[manage_user view_all_principals manage_working_times]) }
|
||||
|
||||
shared_let(:other_user_nwt) do
|
||||
create(:user_non_working_time, user: managed_user,
|
||||
@@ -185,29 +230,26 @@ RSpec.describe "User non-working times", :js, with_flag: { user_working_times: t
|
||||
end_date: Date.new(2026, 5, 8))
|
||||
end
|
||||
|
||||
before { visit_non_working_times }
|
||||
before { nwt_page.visit! }
|
||||
|
||||
it "can view another user's non-working times page with the add button" do
|
||||
expect(page).to have_button(I18n.t(:button_add_non_working_time))
|
||||
nwt_page.expect_add_button
|
||||
end
|
||||
|
||||
it "can open the edit dialog for another user's entry via the sidebar" do
|
||||
find("a[data-controller='async-dialog']").click
|
||||
expect(page).to have_css(dialog_selector)
|
||||
nwt_page.open_edit_dialog_from_sidebar
|
||||
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_field("non_working_time[start_date]", with: "2026-05-04")
|
||||
expect(page).to have_button(I18n.t(:button_delete))
|
||||
end
|
||||
nwt_page.expect_dialog_start_date("2026-05-04")
|
||||
nwt_page.expect_dialog_has_delete_button
|
||||
end
|
||||
|
||||
it "can create a new entry for another user" do
|
||||
open_create_dialog
|
||||
nwt_page.open_create_dialog
|
||||
|
||||
set_date_in_dialog(:start_date, Date.new(2026, 6, 1))
|
||||
set_date_in_dialog(:end_date, Date.new(2026, 6, 5))
|
||||
nwt_page.set_start_date(Date.new(2026, 6, 1))
|
||||
nwt_page.set_end_date(Date.new(2026, 6, 5))
|
||||
|
||||
submit_dialog
|
||||
nwt_page.confirm_dialog
|
||||
|
||||
expect(managed_user.non_working_times.count).to eq(2)
|
||||
end
|
||||
@@ -217,8 +259,8 @@ RSpec.describe "User non-working times", :js, with_flag: { user_working_times: t
|
||||
current_user { create(:user) }
|
||||
|
||||
it "is denied access" do
|
||||
visit_non_working_times
|
||||
expect(page).to have_text(I18n.t(:notice_not_authorized))
|
||||
nwt_page.visit!
|
||||
nwt_page.expect_not_authorized
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# 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 "User working hours", :js, with_flag: { user_working_times: true } do
|
||||
shared_let(:admin) { create(:admin) }
|
||||
shared_let(:managed_user) { create(:user) }
|
||||
|
||||
let(:wh_page) { Pages::Users::WorkingHours.new(user: managed_user) }
|
||||
|
||||
current_user { admin }
|
||||
|
||||
describe "current schedule card" do
|
||||
context "when no working hours exist" do
|
||||
before { wh_page.visit! }
|
||||
|
||||
it "shows the not-set placeholder text in the stats" do
|
||||
wh_page.expect_current_schedule_section
|
||||
wh_page.expect_not_set
|
||||
end
|
||||
|
||||
it "shows the edit pencil linked to the create dialog" do
|
||||
wh_page.expect_editable_current_schedule
|
||||
end
|
||||
end
|
||||
|
||||
context "when working hours are set for today" do
|
||||
shared_let(:working_hours) do
|
||||
create(:user_working_hours,
|
||||
user: managed_user,
|
||||
valid_from: Date.current,
|
||||
monday: 480, tuesday: 480, wednesday: 480, thursday: 480, friday: 480,
|
||||
saturday: 0, sunday: 0,
|
||||
availability_factor: 80)
|
||||
end
|
||||
|
||||
before { wh_page.visit! }
|
||||
|
||||
it "displays the correct work days, hours, availability, and effective hours" do
|
||||
wh_page.expect_stats(
|
||||
work_days: 5,
|
||||
weekly_hours: "40h",
|
||||
availability: "80%",
|
||||
effective_hours: "32h"
|
||||
)
|
||||
end
|
||||
|
||||
it "shows the pencil linked to the edit dialog" do
|
||||
wh_page.open_current_schedule_dialog
|
||||
|
||||
wh_page.expect_dialog_title_current
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating a current schedule" do
|
||||
before { wh_page.visit! }
|
||||
|
||||
it "creates working hours via the current schedule dialog" do
|
||||
wh_page.open_current_schedule_dialog
|
||||
|
||||
wh_page.expect_dialog_title_current
|
||||
wh_page.expect_no_valid_from_field
|
||||
|
||||
wh_page.submit_dialog
|
||||
|
||||
expect(managed_user.working_hours.count).to eq(1)
|
||||
expect(managed_user.working_hours.current.valid_from).to eq(Date.current)
|
||||
end
|
||||
end
|
||||
|
||||
describe "editing the current schedule" do
|
||||
shared_let(:working_hours) do
|
||||
create(:user_working_hours,
|
||||
user: managed_user,
|
||||
valid_from: Date.current,
|
||||
monday: 480, tuesday: 480, wednesday: 480, thursday: 480, friday: 480,
|
||||
saturday: 0, sunday: 0,
|
||||
availability_factor: 100)
|
||||
end
|
||||
|
||||
before { wh_page.visit! }
|
||||
|
||||
it "opens the edit dialog with the current schedule's title" do
|
||||
wh_page.open_current_schedule_dialog
|
||||
|
||||
wh_page.expect_dialog_title_current
|
||||
wh_page.expect_no_valid_from_field
|
||||
end
|
||||
|
||||
it "saves changes to the current schedule" do
|
||||
wh_page.open_current_schedule_dialog
|
||||
wh_page.set_availability_factor(75)
|
||||
wh_page.save_dialog
|
||||
|
||||
expect(working_hours.reload.availability_factor).to eq(75)
|
||||
end
|
||||
end
|
||||
|
||||
describe "future schedules" do
|
||||
describe "adding a future schedule" do
|
||||
before { wh_page.visit! }
|
||||
|
||||
it "shows the future schedule section with an add button" do
|
||||
wh_page.expect_future_section
|
||||
wh_page.expect_add_future_button
|
||||
end
|
||||
|
||||
it "shows the blank slate when no future schedules exist" do
|
||||
wh_page.expect_future_blank_slate
|
||||
end
|
||||
|
||||
it "creates a future schedule via the dialog" do
|
||||
wh_page.open_add_future_schedule_dialog
|
||||
|
||||
wh_page.expect_dialog_title_future
|
||||
wh_page.expect_valid_from_field
|
||||
|
||||
wh_page.set_valid_from(Date.new(2027, 1, 1))
|
||||
|
||||
wh_page.submit_dialog
|
||||
|
||||
expect(managed_user.working_hours.upcoming(Date.new(2027, 1, 1)).count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "editing a future schedule" do
|
||||
shared_let(:future_wh) do
|
||||
create(:user_working_hours,
|
||||
user: managed_user,
|
||||
valid_from: Date.new(2027, 6, 1),
|
||||
monday: 240, tuesday: 240, wednesday: 240, thursday: 240, friday: 240,
|
||||
saturday: 0, sunday: 0,
|
||||
availability_factor: 100)
|
||||
end
|
||||
|
||||
before { wh_page.visit! }
|
||||
|
||||
it "opens the edit dialog from the action menu" do
|
||||
wh_page.open_row_action_menu
|
||||
click_on I18n.t(:button_edit)
|
||||
|
||||
expect(page).to have_css(wh_page.dialog_selector)
|
||||
wh_page.expect_dialog_title_future
|
||||
end
|
||||
|
||||
it "saves updated values" do
|
||||
wh_page.open_row_action_menu
|
||||
click_on I18n.t(:button_edit)
|
||||
|
||||
wh_page.set_availability_factor(50)
|
||||
wh_page.save_dialog
|
||||
|
||||
expect(future_wh.reload.availability_factor).to eq(50)
|
||||
end
|
||||
end
|
||||
|
||||
describe "deleting a future schedule" do
|
||||
it "deletes the schedule via the action menu" do
|
||||
future_wh = create(:user_working_hours,
|
||||
user: managed_user,
|
||||
valid_from: Date.new(2027, 6, 1),
|
||||
monday: 480, tuesday: 480, wednesday: 480, thursday: 480, friday: 480,
|
||||
saturday: 0, sunday: 0,
|
||||
availability_factor: 100)
|
||||
|
||||
wh_page.visit!
|
||||
|
||||
wh_page.open_row_action_menu
|
||||
wh_page.delete_schedule
|
||||
|
||||
expect(UserWorkingHours.exists?(future_wh.id)).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "schedule history" do
|
||||
shared_let(:past_wh) do
|
||||
create(:user_working_hours,
|
||||
user: managed_user,
|
||||
valid_from: Date.new(2025, 1, 1),
|
||||
monday: 360, tuesday: 360, wednesday: 360, thursday: 360, friday: 360,
|
||||
saturday: 0, sunday: 0,
|
||||
availability_factor: 100)
|
||||
end
|
||||
|
||||
shared_let(:current_wh) do
|
||||
create(:user_working_hours,
|
||||
user: managed_user,
|
||||
valid_from: Date.new(2026, 1, 1),
|
||||
monday: 480, tuesday: 480, wednesday: 480, thursday: 480, friday: 480,
|
||||
saturday: 0, sunday: 0,
|
||||
availability_factor: 100)
|
||||
end
|
||||
|
||||
before { wh_page.visit! }
|
||||
|
||||
it "shows past schedules in the history section" do
|
||||
wh_page.expect_history_section
|
||||
# The 2025 entry has 6h/day × 5 days = 30h/week
|
||||
expect(page).to have_text("30h")
|
||||
end
|
||||
end
|
||||
|
||||
describe "access control" do
|
||||
context "with manage_own_working_times permission" do
|
||||
current_user { create(:user, global_permissions: [:manage_own_working_times]) }
|
||||
let(:wh_page) { Pages::Users::WorkingHours.new(user: current_user) }
|
||||
|
||||
it "is denied access to their own working hours on the users page" do
|
||||
wh_page.visit!
|
||||
wh_page.expect_not_authorized
|
||||
end
|
||||
|
||||
it "is denied access to another user's working hours" do
|
||||
Pages::Users::WorkingHours.new(user: managed_user).visit!
|
||||
wh_page.expect_not_authorized
|
||||
end
|
||||
end
|
||||
|
||||
context "with manage_user, view_all_principals, and manage_working_times permissions" do
|
||||
current_user { create(:user, global_permissions: %i[manage_user view_all_principals manage_working_times]) }
|
||||
|
||||
it "can view another user's working hours page" do
|
||||
wh_page.visit!
|
||||
|
||||
wh_page.expect_current_schedule_section
|
||||
wh_page.expect_editable_current_schedule
|
||||
end
|
||||
end
|
||||
|
||||
context "with no working times permissions" do
|
||||
current_user { create(:user) }
|
||||
|
||||
it "is denied access" do
|
||||
wh_page.visit!
|
||||
wh_page.expect_not_authorized
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,183 @@
|
||||
# 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 "support/pages/page"
|
||||
|
||||
module Pages
|
||||
module Users
|
||||
class NonWorkingTimes < ::Pages::Page
|
||||
attr_reader :user, :year
|
||||
|
||||
# Pass user: nil for the /my/non_working_times context
|
||||
def initialize(user: nil, year: Date.current.year)
|
||||
super()
|
||||
@user = user
|
||||
@year = year
|
||||
end
|
||||
|
||||
def path
|
||||
if user
|
||||
edit_user_path(user, tab: :non_working_times, year:)
|
||||
else
|
||||
my_non_working_times_path(year:)
|
||||
end
|
||||
end
|
||||
|
||||
def dialog_selector
|
||||
"##{::Users::NonWorkingTimes::DialogComponent::DIALOG_ID}"
|
||||
end
|
||||
|
||||
# -- Actions --
|
||||
|
||||
def open_create_dialog
|
||||
click_on I18n.t(:button_add_non_working_time)
|
||||
expect(page).to have_css(dialog_selector)
|
||||
end
|
||||
|
||||
def open_edit_dialog_from_sidebar
|
||||
find("a[data-controller='async-dialog'][href*='/edit']").click
|
||||
expect(page).to have_css(dialog_selector)
|
||||
end
|
||||
|
||||
def open_edit_dialog_from_calendar
|
||||
find(".non-working-day--user").click
|
||||
expect(page).to have_css(dialog_selector)
|
||||
end
|
||||
|
||||
def click_calendar_day(date)
|
||||
find("[data-date='#{date}']").click
|
||||
end
|
||||
|
||||
def set_start_date(date)
|
||||
set_date_field(:start_date, date)
|
||||
end
|
||||
|
||||
def set_end_date(date)
|
||||
set_date_field(:end_date, date)
|
||||
end
|
||||
|
||||
def confirm_dialog
|
||||
within(dialog_selector) { click_on I18n.t(:button_confirm) }
|
||||
expect(page).to have_no_css(dialog_selector)
|
||||
end
|
||||
|
||||
def delete_in_dialog
|
||||
accept_confirm do
|
||||
within(dialog_selector) { click_on I18n.t(:button_delete) }
|
||||
end
|
||||
expect(page).to have_no_css(dialog_selector)
|
||||
end
|
||||
|
||||
# -- Expectations --
|
||||
|
||||
def expect_dialog_open
|
||||
expect(page).to have_css(dialog_selector)
|
||||
end
|
||||
|
||||
def expect_dialog_closed
|
||||
expect(page).to have_no_css(dialog_selector)
|
||||
end
|
||||
|
||||
def expect_dialog_start_date(value)
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_field("user_non_working_time[start_date]", with: value)
|
||||
end
|
||||
end
|
||||
|
||||
def expect_dialog_end_date(value)
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_field("user_non_working_time[end_date]", with: value)
|
||||
end
|
||||
end
|
||||
|
||||
def expect_dialog_dates(start_date:, end_date:)
|
||||
expect_dialog_start_date(start_date)
|
||||
expect_dialog_end_date(end_date)
|
||||
end
|
||||
|
||||
def expect_dialog_has_delete_button
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_link(I18n.t(:button_delete))
|
||||
end
|
||||
end
|
||||
|
||||
def expect_validation_error(message)
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_text(message)
|
||||
end
|
||||
end
|
||||
|
||||
def expect_working_days_count(count)
|
||||
expect(page).to have_field(I18n.t(:label_working_days), disabled: true, with: count.to_s)
|
||||
end
|
||||
|
||||
def expect_sidebar_entry(text)
|
||||
expect(page).to have_css("a[data-controller='async-dialog']", text:)
|
||||
end
|
||||
|
||||
def expect_no_sidebar_entry(text)
|
||||
expect(page).to have_no_css("a[data-controller='async-dialog']", text:)
|
||||
end
|
||||
|
||||
def expect_add_button
|
||||
expect(page).to have_link(I18n.t(:button_add_non_working_time))
|
||||
end
|
||||
|
||||
def expect_no_add_button
|
||||
expect(page).to have_no_link(I18n.t(:button_add_non_working_time))
|
||||
end
|
||||
|
||||
def expect_selectable_calendar
|
||||
expect(page).to have_css("[data-users--non-working-times-new-url-value]")
|
||||
end
|
||||
|
||||
def expect_non_selectable_calendar
|
||||
expect(page).to have_no_css("[data-users--non-working-times-new-url-value]")
|
||||
end
|
||||
|
||||
def expect_calendar_rendered
|
||||
expect(page).to have_css(".op-fc-wrapper")
|
||||
expect(page).to have_css(".users-non-working-times-calendar-view")
|
||||
end
|
||||
|
||||
def expect_not_authorized
|
||||
expect(page).to have_text(I18n.t(:notice_not_authorized))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_date_field(field_name, date)
|
||||
datepicker = Components::BasicDatepicker.new(dialog_selector)
|
||||
datepicker.open("input[name='user_non_working_time[#{field_name}]']")
|
||||
datepicker.set_date(date)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,171 @@
|
||||
# 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 "support/pages/page"
|
||||
|
||||
module Pages
|
||||
module Users
|
||||
class WorkingHours < ::Pages::Page
|
||||
attr_reader :user
|
||||
|
||||
# Pass user: nil for the /my/working_hours context
|
||||
def initialize(user: nil)
|
||||
super()
|
||||
@user = user
|
||||
end
|
||||
|
||||
def path
|
||||
if user
|
||||
edit_user_path(user, tab: :working_hours)
|
||||
else
|
||||
my_working_hours_path
|
||||
end
|
||||
end
|
||||
|
||||
def dialog_selector
|
||||
"##{::Users::WorkingHours::DialogComponent::DIALOG_ID}"
|
||||
end
|
||||
|
||||
# -- Actions --
|
||||
|
||||
def open_current_schedule_dialog
|
||||
find("a[data-controller='async-dialog'][href*='current']").click
|
||||
expect(page).to have_css(dialog_selector)
|
||||
end
|
||||
|
||||
def open_add_future_schedule_dialog
|
||||
first("a[data-controller='async-dialog'][href$='working_hours/new']").click
|
||||
expect(page).to have_css(dialog_selector)
|
||||
end
|
||||
|
||||
def open_row_action_menu
|
||||
find(:link_or_button) { it.has_selector?("svg.octicon-kebab-horizontal") }.click
|
||||
end
|
||||
|
||||
def set_valid_from(date)
|
||||
datepicker = Components::BasicDatepicker.new(dialog_selector)
|
||||
datepicker.open("input[name='user_working_hours[valid_from]']")
|
||||
datepicker.set_date(date)
|
||||
end
|
||||
|
||||
def set_availability_factor(value)
|
||||
within(dialog_selector) do
|
||||
fill_in "user_working_hours[availability_factor]", with: value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def submit_dialog
|
||||
within(dialog_selector) { click_on I18n.t(:button_create) }
|
||||
expect(page).to have_no_css(dialog_selector)
|
||||
end
|
||||
|
||||
def save_dialog
|
||||
within(dialog_selector) { click_on I18n.t(:button_save) }
|
||||
expect(page).to have_no_css(dialog_selector)
|
||||
end
|
||||
|
||||
def delete_schedule
|
||||
accept_confirm do
|
||||
click_on I18n.t(:button_delete)
|
||||
end
|
||||
expect(page).to have_no_css(dialog_selector)
|
||||
end
|
||||
|
||||
# -- Expectations --
|
||||
|
||||
def expect_current_schedule_section
|
||||
expect(page).to have_text(I18n.t("users.working_hours.current_schedule.title"))
|
||||
end
|
||||
|
||||
def expect_future_section
|
||||
expect(page).to have_text(I18n.t("users.working_hours.future.title"))
|
||||
end
|
||||
|
||||
def expect_history_section
|
||||
expect(page).to have_text(I18n.t("users.working_hours.history.title"))
|
||||
end
|
||||
|
||||
def expect_not_set
|
||||
expect(page).to have_text(I18n.t("users.working_hours.current_schedule.not_set"), minimum: 1)
|
||||
end
|
||||
|
||||
def expect_stats(work_days: nil, weekly_hours: nil, availability: nil, effective_hours: nil)
|
||||
expect(page).to have_text(work_days.to_s) if work_days
|
||||
expect(page).to have_text(weekly_hours) if weekly_hours
|
||||
expect(page).to have_text(availability) if availability
|
||||
expect(page).to have_text(effective_hours) if effective_hours
|
||||
end
|
||||
|
||||
def expect_future_blank_slate
|
||||
expect(page).to have_text(I18n.t("users.working_hours.future.blank_title"))
|
||||
end
|
||||
|
||||
def expect_editable_current_schedule
|
||||
expect(page).to have_css("a[data-controller='async-dialog'][href*='current']")
|
||||
end
|
||||
|
||||
def expect_not_editable_current_schedule
|
||||
expect(page).to have_no_css("a[data-controller='async-dialog'][href*='current']")
|
||||
end
|
||||
|
||||
def expect_add_future_button
|
||||
expect(page).to have_css("a[data-controller='async-dialog'][href$='working_hours/new']")
|
||||
end
|
||||
|
||||
def expect_dialog_title_current
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_text(I18n.t("users.working_hours.form.title_current"))
|
||||
end
|
||||
end
|
||||
|
||||
def expect_dialog_title_future
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_text(I18n.t("users.working_hours.form.title"))
|
||||
end
|
||||
end
|
||||
|
||||
def expect_no_valid_from_field
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_no_field(I18n.t("users.working_hours.form.start_date"))
|
||||
end
|
||||
end
|
||||
|
||||
def expect_valid_from_field
|
||||
within(dialog_selector) do
|
||||
expect(page).to have_field(I18n.t("users.working_hours.form.start_date"))
|
||||
end
|
||||
end
|
||||
|
||||
def expect_not_authorized
|
||||
expect(page).to have_text(I18n.t(:notice_not_authorized))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user