Primerize the administration user form. wp/72005

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
David F
2026-06-11 15:14:06 +02:00
committed by David.
parent 55474a1cc1
commit 9731068dea
38 changed files with 973 additions and 456 deletions
@@ -1,5 +0,0 @@
<%= @form.fields_for_custom_fields :custom_field_values, @form.object, field_options do |cf_form| %>
<%= content_tag :div, class: css_classes do
cf_form.cf_form_field(container_class:)
end %>
<% end %>
@@ -1,11 +0,0 @@
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend"><%= title %></legend>
<% @section.attribute_order.each do |key| %>
<% if built_in?(key) %>
<%= render "users/form/built_in_fields/#{key}", f: @form, contract: @contract, user: @user %>
<% elsif (cf = visible_custom_field(key)) %>
<%= render Users::Form::CustomFieldFieldComponent.new(custom_field: cf, form: @form) %>
<% end %>
<% end %>
</fieldset>
@@ -0,0 +1,33 @@
<% if editing? %>
<%= render(Primer::Box.new(mb: 3)) do %>
<%= render(Primer::Beta::Text.new(tag: :div, font_weight: :bold)) { I18n.t("attributes.status") } %>
<%= render(Primer::Beta::Text.new(tag: :div)) { helpers.full_user_status(@user, true) } %>
<% end %>
<% end %>
<%= render(form_list) %>
<% if show_no_login_message? %>
<%= render(Primer::Beta::Text.new(tag: :p, color: :muted)) { I18n.t("user.no_login") } %>
<% end %>
<% if show_external_auth? %>
<%= render("users/form/authentication/external", user: @user) %>
<% end %>
<%= helpers.render_user_form_hooks(user: @user, form: @builder) %>
<% if show_consent? %>
<%= render("users/consent", user: @user) %>
<% end %>
<% if show_preferences? %>
<%= fields_for(:pref, @user.pref, builder: @builder.class) do |pref_f| %>
<%= render(Users::Form::PreferencesForm.new(pref_f)) %>
<% end %>
<% end %>
<%= render(Primer::Beta::Button.new(type: :submit, scheme: :primary, name: "submit")) { creating? ? I18n.t(:button_create) : I18n.t(:button_save) } %>
<% if creating? %>
<%= render(Primer::Beta::Button.new(type: :submit, name: "continue")) { I18n.t(:button_create_and_continue) } %>
<% end %>
+95
View File
@@ -0,0 +1,95 @@
# 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.
#++
module Users
# Coordinates the administration user form: receives the surrounding
# `settings_primer_form_with` builder, composes the inner forms into a
# Primer::Forms::FormList and renders the read-only / plain bits (status,
# consent, external auth, hooks, submit) and the pref-scoped preferences form
# around it. Create vs edit is derived from `user.new_record?`.
class FormComponent < ApplicationComponent
def initialize(builder:, user:, contract:)
super()
@builder = builder
@user = user
@contract = contract
end
private
def creating? = @user.new_record?
def editing? = !creating?
def form_list
Primer::Forms::FormList.new(*input_forms)
end
def input_forms
forms = [Users::Form::AttributesForm.new(@builder, user: @user, contract: @contract)]
forms << Users::Form::AuthenticationSourceForm.new(@builder, user: @user) if show_auth_source?
if show_password?
forms << Users::Form::PasswordForm.new(@builder, user: @user,
assign_random_password_checked: assign_random_password_checked?)
end
forms
end
def show_auth_source?
return false if editing? && @user.uses_external_authentication?
creating? ? can_users_have_auth_source? : (User.current.admin? || can_users_have_auth_source?)
end
def show_password?
editing? && User.current.admin? && !@user.uses_external_authentication? && !disable_password_login?
end
def show_preferences? = editing? && User.current.admin?
def show_external_auth? = editing? && @user.uses_external_authentication?
def show_no_login_message?
editing? && User.current.admin? && !@user.uses_external_authentication? && disable_password_login?
end
def show_consent? = editing? && Setting.consent_required?
def can_users_have_auth_source?
LdapAuthSource.any? && !disable_password_login?
end
def disable_password_login?
OpenProject::Configuration.disable_password_login?
end
def assign_random_password_checked?
helpers.params.dig(:user, :assign_random_password).present?
end
end
end
@@ -52,13 +52,17 @@ module CustomFields::CustomFieldRendering
def render_custom_fields(form:)
custom_fields.each do |custom_field|
form.fields_for(:custom_field_values) do |builder|
custom_field_input(builder, custom_field)
end
if custom_field.has_comment?
form.fields_for(:custom_comments) do |builder|
custom_comment_input(builder, custom_field)
end
render_custom_field(form:, custom_field:)
end
end
def render_custom_field(form:, custom_field:)
form.fields_for(:custom_field_values) do |builder|
custom_field_input(builder, custom_field)
end
if custom_field.has_comment?
form.fields_for(:custom_comments) do |builder|
custom_comment_input(builder, custom_field)
end
end
end
+112
View File
@@ -0,0 +1,112 @@
# 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.
#++
module Users
module Form
# Admin flag + the custom-field sections (built-in fields interleaved with
# custom fields per UserCustomFieldSection, honoring attribute_order).
class AttributesForm < ApplicationForm
include CustomFields::CustomFieldRendering
form do |f|
admin_flag(f) if User.current.admin?
user_sections(f)
end
def initialize(user:, contract:)
super()
@user = user
@contract = contract
end
private
def custom_fields
@custom_fields ||= @user.available_custom_fields
end
def admin_flag(form)
form.check_box(name: :admin,
label: User.human_attribute_name(:admin),
disabled: @user == User.current)
end
def user_sections(form)
UserCustomFieldSection.includes(:custom_fields).each do |section| # rubocop:disable Rails/FindEach -- honor default scope ordering
render_section(form, section)
end
end
def render_section(form, section)
visible_cfs_by_key = section.custom_fields.visible(User.current).index_by(&:column_name)
form.fieldset_group(title: section_title(section)) do |group|
section.attribute_order.each do |key|
if UserCustomFieldSection::BUILT_IN_ATTRIBUTES.include?(key)
render_built_in(group, key)
elsif (custom_field = visible_cfs_by_key[key])
render_custom_field(form: group, custom_field:)
end
end
end
end
def section_title(section)
section.name.presence || I18n.t("settings.user_custom_fields.label_untitled_section")
end
def render_built_in(group, key) # rubocop:disable Metrics/AbcSize
case key
when "firstname", "lastname", "mail"
group.text_field(name: key.to_sym,
label: User.human_attribute_name(key),
required: true,
disabled: !@contract.writable?(key.to_sym),
input_width: :medium)
when "login"
return if @user.new_record?
group.text_field(name: :login,
label: User.human_attribute_name(:login),
required: true,
disabled: !@contract.writable?(:login),
input_width: :medium)
when "language"
group.select_list(name: :language,
label: User.human_attribute_name(:language),
include_blank: "--- #{I18n.t(:actionview_instancetag_blank_option)} ---",
input_width: :medium) do |list|
helpers.lang_options_for_select.each { |label, value| list.option(label:, value:) }
end
end
end
end
end
end
@@ -0,0 +1,80 @@
# 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.
#++
module Users
module Form
# LDAP authentication source select. For a new record it also renders the
# hidden login field the admin--users controller reveals when an LDAP source
# is selected; for a persisted record the login is already a built-in field,
# so the select is wrapped in a titled "Authentication" fieldset only.
#
# The coordinator decides whether to include this form at all.
class AuthenticationSourceForm < ApplicationForm
form do |f|
if @user.new_record?
ldap_auth_source_select(f)
hidden_login(f)
else
f.fieldset_group(title: I18n.t(:label_authentication)) do |group|
ldap_auth_source_select(group)
end
end
end
def initialize(user:)
super()
@user = user
end
private
def ldap_auth_source_select(target)
target.select_list(
name: :ldap_auth_source_id,
label: User.human_attribute_name(:auth_source),
include_blank: I18n.t(:label_internal),
input_width: :medium,
data: { action: "admin--users#toggleAuthenticationFields" }
) do |list|
LdapAuthSource.order(:name).each { |source| list.option(label: source.name, value: source.id) }
end
end
def hidden_login(form)
form.group(hidden: true, data: { "admin--users-target": "authSourceFields" }) do |group|
group.text_field(name: :login,
label: User.human_attribute_name(:login),
required: true,
input_width: :medium)
end
end
end
end
end
+124
View File
@@ -0,0 +1,124 @@
# 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.
#++
module Users
module Form
# The internal password / activation block as a single Primer group. All three
# Stimulus controllers sit on the wrapper: the password-requirements controller
# finds both its passwordInput and requirement targets within it.
# assign_random_password and send_information are not model
# attributes, so they render with an explicit checked: and no hidden companion
# (the controller relies on their absence when unchecked); send_information is
# unscoped (top-level param). Included by the coordinator only for editing an
# internal user as admin while password login is enabled.
class PasswordForm < ApplicationForm
form do |f|
f.group(
hidden: !@user.change_password_allowed?,
data: {
controller: "disable-when-checked password-force-change password-requirements",
"admin--users-target": "passwordFields"
}
) do |group|
assign_random_password(group)
password_fields(group) unless disable_password_choice?
send_information(group)
force_password_change(group)
end
end
def initialize(user:, assign_random_password_checked:)
super()
@user = user
@assign_random_password_checked = assign_random_password_checked
end
private
def disable_password_choice?
OpenProject::Configuration.disable_password_choice?
end
def assign_random_password(group)
group.check_box(name: :assign_random_password,
id: "user_assign_random_password",
checked: @assign_random_password_checked,
include_hidden: false,
label: I18n.t("user.assign_random_password"),
data: {
"disable-when-checked-target": "cause",
"password-force-change-target": "assignRandomPassword"
})
end
def password_fields(group)
group.text_field(name: :password,
id: "user_password",
type: :password,
required: @user.new_record?,
label: User.human_attribute_name(:password),
caption: helpers.password_complexity_requirements,
input_width: :medium,
data: {
"disable-when-checked-target": "effect",
"password-requirements-target": "passwordInput"
})
group.text_field(name: :password_confirmation,
id: "user_password_confirmation",
type: :password,
required: @user.new_record?,
label: User.human_attribute_name(:password_confirmation),
input_width: :medium,
data: { "disable-when-checked-target": "effect" })
end
def send_information(group)
group.check_box(name: :send_information,
id: "send_information",
scope_name_to_model: false,
scope_id_to_model: false,
checked: false,
include_hidden: false,
label: I18n.t(:label_send_information),
caption: I18n.t("users.send_information_hint"),
data: { "password-force-change-target": "sendInformationCheckbox" })
end
def force_password_change(group)
group.check_box(name: :force_password_change,
id: "user_force_password_change",
checked: @user.force_password_change,
label: User.human_attribute_name(:force_password_change),
caption: I18n.t("users.force_password_change_hint"),
data: { "password-force-change-target": "forceChangeCheckbox" })
end
end
end
end
+87
View File
@@ -0,0 +1,87 @@
# 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.
#++
module Users
module Form
# Preferences section of the administration user form: time zone, color mode
# and keyboard shortcuts. Bound to the user's preference (scope: :pref).
#
# My::LookAndFeelForm could not be reused directly because it renders its own
# submit button and extra fields (comments sorting, contrast); the time zone
# option building mirrors My::TimeZoneForm.
class PreferencesForm < ApplicationForm
form do |f|
f.fieldset_group(title: I18n.t(:label_preferences)) do |group|
group.select_list(
name: :time_zone,
label: UserPreference.human_attribute_name(:time_zone),
include_blank: false,
input_width: :medium
) do |list|
time_zone_options.each { |label, value| list.option(label:, value:) }
end
group.select_list(
name: :theme,
label: UserPreference.human_attribute_name(:theme),
caption: UserPreference.human_attribute_name(:mode_guideline),
include_blank: false,
input_width: :medium
) do |list|
helpers.theme_options_for_select.each { |label, value| list.option(label:, value:) }
end
group.check_box(
name: :disable_keyboard_shortcuts,
label: UserPreference.human_attribute_name(:disable_keyboard_shortcuts),
caption: helpers.link_translate(:"user_preferences.disable_keyboard_shortcuts_caption",
links: { docs_url: %i[shortcuts] })
)
end
end
private
def time_zone_options
UserPreferences::UpdateContract
.assignable_time_zones
.group_by { |zone| zone.tzinfo.canonical_zone }
.map { |canonical_zone, zones| time_zone_entry(canonical_zone, zones) }
end
def time_zone_entry(canonical_zone, zones)
zone_names = zones.map(&:name).join(", ")
offset = ActiveSupport::TimeZone.seconds_to_utc_offset(canonical_zone.base_utc_offset)
["(UTC#{offset}) #{zone_names}", canonical_zone.identifier]
end
end
end
end
+30
View File
@@ -153,4 +153,34 @@ module UsersHelper
def can_users_have_auth_source?
LdapAuthSource.any? && !OpenProject::Configuration.disable_password_login?
end
# Renders the user form extension hooks inside the (Primer) user form.
#
# - +view_users_primer_form+ receives the Primer form builder and is the API
# plugins should use going forward.
# - +view_users_form+ is deprecated but kept working: it receives a legacy
# TabularFormBuilder rendered inside the same form, so existing plugins keep
# submitting their fields. It is only rendered (and the deprecation logged)
# when a listener is actually registered.
def render_user_form_hooks(user:, form:)
safe_join([
call_hook(:view_users_primer_form, user:, form:),
legacy_user_form_hook(user:)
].compact)
end
private
def legacy_user_form_hook(user:)
return unless OpenProject::Hook.hook_listeners(:view_users_form).any?
OpenProject::Deprecation.warn(
"The `view_users_form` hook is deprecated; migrate to `view_users_primer_form` " \
"which receives a Primer form builder."
)
fields_for(:user, user, builder: TabularFormBuilder) do |legacy_form|
call_hook(:view_users_form, user:, form: legacy_form)
end
end
end
+3 -2
View File
@@ -1,11 +1,12 @@
<%# locals: (user:) -%>
<section class="form--section">
<h3 class="form--section-title"><%= t("consent.title") %></h3>
<% consent_link = admin_settings_users_path(anchor: "consent_settings") %>
<%= render ::Components::OnOffStatusComponent.new(
{
is_on: @user.consented_at.present?,
on_text: format_time(@user.consented_at),
is_on: user.consented_at.present?,
on_text: format_time(user.consented_at),
on_description: link_translate("consent.user_has_consented", links: { consent_settings: consent_link }),
off_text: t(:label_never),
off_description: link_translate("consent.not_yet_consented", links: { consent_settings: consent_link })
-51
View File
@@ -1,51 +0,0 @@
<%#-- 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.
++#%>
<%= error_messages_for @user %>
<section class="form--section">
<% if current_user.admin? %>
<%= render partial: "users/form/admin_flag", locals: { f: f } %>
<% end %>
<% UserCustomFieldSection.includes(:custom_fields).each do |section| # rubocop:disable Rails/FindEach -- to honor default scope ordering %>
<%= render Users::Form::CustomFieldSectionComponent.new(section:, form: f, contract: @contract, user: @user) %>
<% end %>
<%= call_hook(:view_users_form, user: @user, form: f) %>
</section>
<% if Setting.consent_required? %>
<%= render partial: "users/consent" %>
<% end %>
<%= render partial: "users/form/authentication/form", locals: { f: f } %>
<% if current_user.admin? %>
<%= render partial: "users/form/preferences", locals: { f: f } %>
<% end %>
+16 -28
View File
@@ -27,32 +27,20 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<section class="form--section">
<div class="form--field">
<%= styled_label_tag "FIXME", I18n.t("attributes.status") %>
<div class="form--field-container">
<span>
<span class=""><%= full_user_status(@user, true) %></span>
</span>
</div>
</div>
</section>
<%= error_messages_for @user %>
<%= labelled_tabular_form_for @user,
url: { controller: "/users",
action: "update",
tab: nil },
html: {
method: :put,
autocomplete: "off"
},
data: {
controller: "admin--users",
turbo: false,
"admin--users-password-auth-selected-value": @user.ldap_auth_source_id.blank?
},
as: :user do |f| %>
<%= render partial: "users/form", locals: { f: f } %>
<%= styled_button_tag t(:button_save), class: "-primary -with-icon icon-checkmark" %>
<% end %>
<%=
settings_primer_form_with(
model: @user,
url: { controller: "/users", action: "update", tab: nil },
method: :put,
html: { autocomplete: "off" },
data: {
controller: "admin--users",
turbo: false,
"admin--users-password-auth-selected-value": @user.ldap_auth_source_id.blank?
}
) do |f|
render(Users::FormComponent.new(builder: f, user: @user, contract: @contract))
end
%>
-51
View File
@@ -1,51 +0,0 @@
<%#-- 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.
++#%>
<%= fields_for :pref, @user.pref, builder: TabularFormBuilder, lang: current_language do |pref_fields| %>
<%= render Settings::TimeZoneSettingComponent.new(
"time_zone",
form: pref_fields,
include_blank: false,
container_class: defined?(input_size) ? "-#{input_size}" : "-wide"
) %>
<div class="form--field">
<%= pref_fields.select :theme, theme_options_for_select, container_class: "-middle" %> <!-- TODO -->
<div class="form--field-instructions">
<%= I18n.t("activerecord.attributes.user_preference.mode_guideline") %>
</div>
</div>
<div class="form--field">
<%= pref_fields.check_box :disable_keyboard_shortcuts,
label: I18n.t("activerecord.attributes.user_preference.disable_keyboard_shortcuts") %>
<span class="form--field-instructions">
<%= link_translate(:"user_preferences.disable_keyboard_shortcuts_caption",
links: { docs_url: %i[shortcuts] }) %>
</span>
</div>
<% end %>
-40
View File
@@ -1,40 +0,0 @@
<%#-- 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.
++#%>
<section class="form--section">
<% if current_user.admin? %>
<%= render partial: "users/form/authentication/auth_source", locals: { f: f } %>
<%= render partial: "users/form/admin_flag", locals: { f: f } %>
<% end %>
<% UserCustomFieldSection.includes(:custom_fields).each do |section| # rubocop:disable Rails/FindEach -- to honor default scope ordering %>
<%= render Users::Form::CustomFieldSectionComponent.new(section:, form: f, contract: @contract, user: @user) %>
<% end %>
<%= call_hook(:view_users_form, user: @user, form: f) %>
</section>
@@ -1,4 +0,0 @@
<div class="form--field">
<%= f.check_box :admin,
disabled: (@user == User.current) %>
</div>
@@ -1,4 +0,0 @@
<section class="form--section">
<h3 class="form--section-title"><%= t(:label_preferences) %></h3>
<%= render partial: "users/preferences", locals: { input_size: :middle } %>
</section>
@@ -1,22 +0,0 @@
<% if can_users_have_auth_source? %>
<div class="form--field">
<%= f.collection_select :ldap_auth_source_id,
LdapAuthSource.all,
:id,
:name,
{
label: :"activerecord.attributes.user.auth_source",
container_class: "-middle",
include_blank: t(:label_internal)
},
data: {
action: "admin--users#toggleAuthenticationFields"
} %>
</div>
<div class="form--field"
hidden
data-admin--users-target="authSourceFields">
<%= f.text_field :login, required: true, size: 25 %>
</div>
<% end %>
@@ -1,19 +1,16 @@
<div class="form--field -reduced-margin">
<%= styled_label_tag nil, I18n.t("user.authentication_provider") %>
<div class="form--field-container">
<%= @user.human_authentication_provider %>
</div>
</div>
<div class="form--field-instructions">
<%= I18n.t("user.authentication_settings_disabled_due_to_external_authentication") %>
</div>
<%# locals: (user:) -%>
<%= render(Primer::Box.new(mb: 3)) do %>
<%= render(Primer::Beta::Text.new(tag: :div, font_weight: :bold)) { I18n.t("user.authentication_provider") } %>
<%= render(Primer::Beta::Text.new(tag: :div)) { user.human_authentication_provider } %>
<%= render(Primer::Beta::Text.new(tag: :div, color: :muted, font_size: :small)) do %>
<%= I18n.t("user.authentication_settings_disabled_due_to_external_authentication") %>
<% end %>
<% end %>
<div class="form--field -top-margin -reduced-margin">
<%= styled_label_tag nil, User.human_attribute_name(:identity_url) %>
<div class="form--field-container -empty">
<%= @user.identity_url.split(":", 2).last %>
</div>
</div>
<div class="form--field-instructions">
<%= I18n.t("user.identity_url_text") %>
</div>
<%= render(Primer::Box.new) do %>
<%= render(Primer::Beta::Text.new(tag: :div, font_weight: :bold)) { User.human_attribute_name(:identity_url) } %>
<%= render(Primer::Beta::Text.new(tag: :div)) { user.identity_url.split(":", 2).last } %>
<%= render(Primer::Beta::Text.new(tag: :div, color: :muted, font_size: :small)) do %>
<%= I18n.t("user.identity_url_text") %>
<% end %>
<% end %>
@@ -1,15 +0,0 @@
<section class="form--section">
<% if current_user.admin? || can_users_have_auth_source? || @user.uses_external_authentication? %>
<h3 class="form--section-title"><%= t(:label_authentication) %></h3>
<% end %>
<% if @user.uses_external_authentication? %>
<%= render partial: "users/form/authentication/external", locals: { f: f } %>
<% else %>
<%= render partial: "users/form/authentication/auth_source", locals: { f: f } %>
<% if current_user.admin? %>
<%= render partial: "users/form/authentication/internal", locals: { f: f } %>
<% end %>
<% end %>
</section>
@@ -1,12 +0,0 @@
<% if OpenProject::Configuration.disable_password_login? %>
<div id="no_password_info">
<div class="form--field">
<%= styled_label_tag nil, I18n.t(:warning) %>
<div class="form--field-container">
<%= I18n.t "user.no_login" %>
</div>
</div>
</div>
<% else %>
<%= render partial: "users/form/authentication/internal_password", locals: { f: f } %>
<% end %>
@@ -1,71 +0,0 @@
<%= content_tag :div,
data: {
controller: "disable-when-checked password-force-change",
"admin--users-target": "passwordFields"
},
hidden: !@user.change_password_allowed? do %>
<% assign_random_password_enabled = params[:user] &&
params[:user][:assign_random_password] %>
<div class="form--field">
<%= styled_label_tag "user_assign_random_password",
I18n.t("user.assign_random_password") %>
<div class="form--field-container">
<%= styled_check_box_tag "user[assign_random_password]",
"1",
assign_random_password_enabled,
data: {
"disable-when-checked-target": "cause",
"password-force-change-target": "assignRandomPassword"
} %>
</div>
</div>
<% unless OpenProject::Configuration.disable_password_choice? %>
<div class="form--field" data-controller="password-requirements">
<%= f.password_field :password,
required: @user.new_record?,
disabled: assign_random_password_enabled,
data: {
"disable-when-checked-target": "effect",
"password-requirements-target": "passwordInput"
},
container_class: "-middle" %>
<div class="form--field-instructions">
<%= password_complexity_requirements %>
</div>
</div>
<div class="form--field">
<%= f.password_field :password_confirmation,
required: @user.new_record?,
disabled: assign_random_password_enabled,
data: {
"disable-when-checked-target": "effect"
},
container_class: "-middle" %>
</div>
<% end %>
<div class="form--field send-information">
<%= styled_label_tag "send_information", t(:label_send_information) %>
<div class="form--field-container">
<%= styled_check_box_tag(
"send_information",
"1",
false,
data: { "password-force-change-target": "sendInformationCheckbox" }
) %>
</div>
<div class="form--field-instructions">
<%= t("users.send_information_hint") %>
</div>
</div>
<div class="form--field">
<%= f.check_box :force_password_change,
disabled: assign_random_password_enabled,
data: { "password-force-change-target": "forceChangeCheckbox" } %>
<div class="form--field-instructions">
<%= t("users.force_password_change_hint") %>
</div>
</div>
<% end %>
@@ -1,7 +0,0 @@
<%# locals: (f:, contract:, user:) -%>
<div class="form--field -required">
<%= f.text_field :firstname,
required: true,
disabled: !contract.writable?(:firstname),
container_class: "-middle" %>
</div>
@@ -1,7 +0,0 @@
<%# locals: (f:, contract:, user:) -%>
<div class="form--field">
<%= f.select :language,
lang_options_for_select,
prompt: "--- #{t(:actionview_instancetag_blank_option)} ---",
container_class: "-middle" %>
</div>
@@ -1,7 +0,0 @@
<%# locals: (f:, contract:, user:) -%>
<div class="form--field -required">
<%= f.text_field :lastname,
required: true,
disabled: !contract.writable?(:lastname),
container_class: "-middle" %>
</div>
@@ -1,10 +0,0 @@
<%# locals: (f:, contract:, user:) -%>
<% unless user.new_record? %>
<div class="form--field -required">
<%= f.text_field :login,
required: true,
disabled: !contract.writable?(:login),
size: 25,
container_class: "-middle" %>
</div>
<% end %>
@@ -1,7 +0,0 @@
<%# locals: (f:, contract:, user:) -%>
<div class="form--field -required">
<%= f.text_field :mail,
required: true,
disabled: !contract.writable?(:mail),
container_class: "-middle" %>
</div>
+14 -16
View File
@@ -43,19 +43,17 @@ See COPYRIGHT and LICENSE files for more details.
<%= error_messages_for @user %>
<%= labelled_tabular_form_for @user,
url: { action: "create" },
html: { class: nil, autocomplete: "off" },
data: {
controller: "admin--users",
turbo: false,
"admin--users-password-auth-selected-value": @user.ldap_auth_source_id.blank?
},
as: :user do |f| %>
<%= render partial: "simple_form", locals: { f: f, auth_sources: @ldap_auth_sources, user: @user } %>
<p>
<%= styled_button_tag t(:button_create), class: "-primary -with-icon icon-checkmark" %>
<%= styled_button_tag t(:button_create_and_continue), name: "continue", class: "-primary -with-icon icon-checkmark" %>
</p>
<% end %>
<%=
settings_primer_form_with(
model: @user,
url: { action: "create" },
html: { autocomplete: "off" },
data: {
controller: "admin--users",
turbo: false,
"admin--users-password-auth-selected-value": @user.ldap_auth_source_id.blank?
}
) do |f|
render(Users::FormComponent.new(builder: f, user: @user, contract: @contract))
end
%>
@@ -0,0 +1,93 @@
# 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 Users::FormComponent, type: :component do
let(:current_user) { build_stubbed(:admin) }
let(:contract) { instance_double(Users::UpdateContract, writable?: true) }
before do
User.current = current_user
create(:user_custom_field_section, attribute_order: UserCustomFieldSection::BUILT_IN_ATTRIBUTES)
end
def render_component(user:)
render_in_view_context(user, contract) do |form_user, form_contract|
primer_form_with(model: form_user, url: "/users") do |form|
render(Users::FormComponent.new(builder: form, user: form_user, contract: form_contract))
end
end
end
context "for a new user" do
it "renders the attributes and the create buttons, but no password block" do
render_component(user: User.new)
expect(page).to have_field("user[firstname]")
expect(page).to have_button(I18n.t(:button_create))
expect(page).to have_button(I18n.t(:button_create_and_continue))
expect(page).to have_no_css("[data-admin--users-target='passwordFields']", visible: :all)
end
end
context "for a persisted internal user edited by an admin" do
let(:user) { create(:user) }
it "renders status, the password block, preferences and a Save button" do
render_component(user:)
expect(page).to have_css("[data-admin--users-target='passwordFields']", visible: :all)
expect(page).to have_select("pref[time_zone]")
expect(page).to have_button(I18n.t(:button_save))
end
it "renders the consent status when consent is required" do
allow(Setting).to receive(:consent_required?).and_return(true)
render_component(user:)
expect(page).to have_text(I18n.t("consent.title"))
end
end
context "for a persisted external-auth user" do
let(:user) { create(:user, identity_url: "saml:user-id") }
before { allow(user).to receive(:uses_external_authentication?).and_return(true) }
it "renders the read-only authentication provider instead of the password block" do
render_component(user:)
expect(page).to have_text(I18n.t("user.authentication_provider"))
expect(page).to have_no_css("[data-admin--users-target='passwordFields']", visible: :all)
end
end
end
+1 -1
View File
@@ -59,7 +59,7 @@ RSpec.describe "edit users", :js do
before { visit edit_user_path(user) }
it "saves a custom field value" do
within "fieldset", text: "Professional info".upcase do
within "fieldset", text: "Professional info" do
fill_in "Job title", with: "Software Engineer"
fill_in "Internal code", with: "SE"
end
+3 -3
View File
@@ -143,19 +143,19 @@ RSpec.describe "random password generation", :js do
# And I try to set my new password to "adminADMIN"
fill_in "user_password", with: "adminADMIN"
fill_in "user_password_confirmation", with: "adminADMIN"
scroll_to_and_click(find(".button", text: "Save"))
scroll_to_and_click(find_button("Save"))
expect_flash(type: :error, message: "Password Must include characters of the following types")
# Has numeric but still missing special
fill_in "user_password", with: "adminADMIN123"
fill_in "user_password_confirmation", with: "adminADMIN123"
scroll_to_and_click(find(".button", text: "Save"))
scroll_to_and_click(find_button("Save"))
expect_flash(type: :error, message: "Password Must include characters of the following types")
# All classes
fill_in "user_password", with: "adminADMIN1!"
fill_in "user_password_confirmation", with: "adminADMIN1!"
scroll_to_and_click(find(".button", text: "Save"))
scroll_to_and_click(find_button("Save"))
expect_flash(message: I18n.t(:notice_successful_update))
end
end
@@ -204,4 +204,26 @@ RSpec.describe CustomFields::CustomFieldRendering do
end
end
end
describe "#render_custom_field" do
let(:values_builder) { instance_double(ActionView::Helpers::FormBuilder) }
let(:custom_field) { build(:custom_field, :string) }
before do
allow(builder).to receive(:fields_for).with(:custom_field_values).and_yield(values_builder)
allow(form_instance).to receive(:additional_custom_field_input_arguments).and_return({})
end
it "renders a single custom field input" do
allow(CustomFields::Inputs::String).to receive(:new)
form_instance.render_custom_field(form: builder, custom_field:)
expect(CustomFields::Inputs::String).to have_received(:new).with(
values_builder,
custom_field:,
object: model
)
end
end
end
@@ -28,41 +28,38 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Users
module Form
class CustomFieldFieldComponent < ApplicationComponent
include ApplicationHelper
require "spec_helper"
def initialize(custom_field:, form:)
super()
@custom_field = custom_field
@form = form
end
RSpec.describe Users::Form::AttributesForm, type: :forms do
before do
User.current = current_user
create(:user_custom_field_section, attribute_order: UserCustomFieldSection::BUILT_IN_ATTRIBUTES)
end
def field_options
{
custom_value: @form.object.custom_value_for(@custom_field),
custom_field: @custom_field
}
end
include_context "with rendered form"
def css_classes
["form--field", @custom_field.attribute_name, required_class].compact
end
let(:current_user) { build_stubbed(:admin) }
let(:contract) { instance_double(Users::UpdateContract, writable?: true) }
let(:params) { { user: model, contract: } }
def container_class
case @custom_field.field_format
when "text" then "-xxwide"
when "date" then "-xslim"
else "-middle"
end
end
context "for a persisted user" do
let(:model) { build_stubbed(:user) }
private
it "renders the built-in fields, login and the admin flag" do
expect(page).to have_field("user[firstname]")
expect(page).to have_field("user[lastname]")
expect(page).to have_field("user[mail]")
expect(page).to have_select("user[language]")
expect(page).to have_field("user[login]")
expect(page).to have_field("user[admin]")
end
end
def required_class
"-required" if @custom_field.is_required? && !@custom_field.boolean?
end
context "for a new user" do
let(:model) { User.new }
it "omits the login field" do
expect(page).to have_no_field("user[login]")
end
end
end
@@ -0,0 +1,63 @@
# 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 Users::Form::AuthenticationSourceForm, type: :forms do
before do
User.current = build_stubbed(:admin)
create(:ldap_auth_source)
end
include_context "with rendered form"
let(:params) { { user: model } }
context "for a new user" do
let(:model) { User.new }
it "renders the auth source select with the toggle action and a hidden login group" do
expect(page).to have_select("user[ldap_auth_source_id]")
expect(page).to have_css("[data-action~='admin--users#toggleAuthenticationFields']")
expect(page).to have_css("[data-admin--users-target='authSourceFields'][hidden]", visible: :all)
expect(page).to have_field("user[login]", visible: :all)
end
end
context "for a persisted user" do
let(:model) { build_stubbed(:user) }
it "renders the select inside a titled Authentication fieldset and no hidden login" do
expect(page).to have_select("user[ldap_auth_source_id]")
expect(page).to have_css("fieldset", text: /#{I18n.t(:label_authentication)}/i)
expect(page).to have_no_css("[data-admin--users-target='authSourceFields']", visible: :all)
end
end
end
@@ -0,0 +1,61 @@
# 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 Users::Form::PasswordForm, type: :forms do
include_context "with rendered form"
let(:model) { build_stubbed(:user) }
let(:params) { { user: model, assign_random_password_checked: false } }
before { allow(model).to receive(:change_password_allowed?).and_return(true) }
it "renders one wrapper carrying the three controllers and the passwordFields target" do
expect(page).to have_css(
"[data-controller~='disable-when-checked'][data-controller~='password-force-change']" \
"[data-controller~='password-requirements'][data-admin--users-target='passwordFields']",
visible: :all
)
end
it "renders the password fields with their ids and password type" do
expect(page).to have_field("user[password]", type: "password", visible: :all)
expect(page).to have_field("user[password_confirmation]", type: "password", visible: :all)
expect(page).to have_css("#user_password", visible: :all)
end
it "renders the unscoped send_information checkbox and the scoped flags" do
expect(page).to have_field("send_information", visible: :all)
expect(page).to have_css("#send_information", visible: :all)
expect(page).to have_field("user[assign_random_password]", visible: :all)
expect(page).to have_field("user[force_password_change]", visible: :all)
end
end
@@ -28,35 +28,16 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Users
module Form
class CustomFieldSectionComponent < ApplicationComponent
def initialize(section:, form:, contract:, user:)
super()
@section = section
@form = form
@contract = contract
@user = user
@visible_cfs_by_key = visible_cfs_by_key(section)
end
require "spec_helper"
def title
@section.name.presence || I18n.t("settings.user_custom_fields.label_untitled_section")
end
RSpec.describe Users::Form::PreferencesForm, type: :forms do
include_context "with rendered form"
def built_in?(key)
UserCustomFieldSection::BUILT_IN_ATTRIBUTES.include?(key)
end
let(:model) { build_stubbed(:user_preference) }
def visible_custom_field(key)
@visible_cfs_by_key[key]
end
private
def visible_cfs_by_key(section)
section.custom_fields.visible(User.current).index_by(&:column_name)
end
end
it "renders the time zone, color mode and keyboard shortcuts fields" do
expect(page).to have_select("Time zone")
expect(page).to have_select("Color mode")
expect(page).to have_field("Disable keyboard shortcuts")
end
end
@@ -0,0 +1,74 @@
# 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 UsersHelper do
describe "#render_user_form_hooks" do
let(:form) { instance_double(Primer::Forms::Builder) }
let(:user) { build_stubbed(:user) }
before do
allow(helper).to receive(:call_hook).and_return("".html_safe)
allow(OpenProject::Deprecation).to receive(:warn)
allow(helper).to receive(:fields_for).and_yield(instance_double(TabularFormBuilder))
end
it "always calls the new Primer hook" do
allow(OpenProject::Hook).to receive(:hook_listeners).with(:view_users_form).and_return([])
helper.render_user_form_hooks(user:, form:)
expect(helper).to have_received(:call_hook).with(:view_users_primer_form, hash_including(form:))
end
context "when no legacy listener is registered" do
before { allow(OpenProject::Hook).to receive(:hook_listeners).with(:view_users_form).and_return([]) }
it "does not warn and does not render the legacy hook" do
helper.render_user_form_hooks(user:, form:)
expect(OpenProject::Deprecation).not_to have_received(:warn)
expect(helper).not_to have_received(:fields_for)
end
end
context "when a legacy listener is registered" do
before { allow(OpenProject::Hook).to receive(:hook_listeners).with(:view_users_form).and_return([instance_double(Object)]) }
it "logs a deprecation and renders the legacy hook" do
helper.render_user_form_hooks(user:, form:)
expect(OpenProject::Deprecation).to have_received(:warn)
expect(helper).to have_received(:call_hook).with(:view_users_form, hash_including(:form))
end
end
end
end
+2 -1
View File
@@ -43,6 +43,7 @@ RSpec.describe "users/edit" do
assign(:auth_sources, [])
assign(:contract, Users::UpdateContract.new(user, current_user))
User.current = current_user
without_partial_double_verification do
allow(view).to receive(:current_user).and_return(current_user)
end
@@ -105,7 +106,7 @@ RSpec.describe "users/edit" do
end
context "with password-based login" do
let(:user) { build(:user, id: 42) }
let(:user) { build_stubbed(:user) }
context "with password login disabled" do
before do