From 464e97499c402cbb68af4e7bcdfd4fe848dc98e0 Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 25 Mar 2025 17:33:36 +0100 Subject: [PATCH] rename steps and step definitions --- app/components/projects/table_component.rb | 2 +- .../project_life_cycle_steps/base_contract.rb | 6 +- ..._life_cycle_step_definitions_controller.rb | 6 +- .../settings/life_cycle_steps_controller.rb | 6 +- app/helpers/highlighting_helper.rb | 2 +- .../project_life_cycle_step_journal.rb | 2 +- app/models/permitted_params.rb | 2 +- app/models/project.rb | 10 +-- app/models/project/gate.rb | 45 ----------- app/models/project/life_cycle_step.rb | 62 --------------- app/models/project/{stage.rb => phase.rb} | 28 ++++++- ...step_definition.rb => phase_definition.rb} | 27 ++----- .../scopes/with_project_count.rb | 6 +- .../filters/dynamically_from_life_cycle.rb | 2 +- .../projects/filters/filter_on_life_cycle.rb | 18 ++--- .../projects/orders/life_cycle_step_order.rb | 2 +- .../projects/selects/life_cycle_step.rb | 4 +- app/models/work_package.rb | 2 +- .../life_cycle_step_definition_seeder.rb | 2 +- .../create_service/project_life_cycle_step.rb | 2 +- .../preview_attributes_service.rb | 2 +- .../form.html.erb | 2 +- app/views/highlighting/styles.css.erb | 2 +- config/locales/en.yml | 4 +- .../20250324161229_merge_lifecycle_steps.rb | 4 + .../project_life_cycle_step_active.rb | 2 +- .../project_life_cycle_step_dates.rb | 2 +- .../sections/edit_component.html.erb | 2 +- .../sections/show_component.rb | 2 +- .../base_contract_spec.rb | 22 +++--- ...ject_life_cycle_step_definition_factory.rb | 41 ---------- .../project_life_cycle_step_factory.rb | 49 ------------ .../project_phase_definition_factory.rb | 12 +-- .../factories/project_phase_factory.rb | 15 ++-- .../overview_page/dialog/update_spec.rb | 4 +- .../life_cycle/overview_page/sidebar_spec.rb | 2 +- .../project_life_cycle_step_active_spec.rb | 2 +- .../project_life_cycle_step_dates_spec.rb | 2 +- spec/models/project/gate_definition_spec.rb | 57 -------------- spec/models/project/gate_spec.rb | 57 -------------- spec/models/project/life_cycle_step_spec.rb | 77 ------------------- ...ition_spec.rb => phase_definition_spec.rb} | 37 ++++----- .../scopes/with_project_count_spec.rb | 4 +- .../project/{stage_spec.rb => phase_spec.rb} | 47 +++++++---- spec/models/project/stage_definition_spec.rb | 57 -------------- spec/models/project_spec.rb | 14 ++-- .../filters/life_cycle_gate_filter_spec.rb | 2 +- .../filters/life_cycle_stage_filter_spec.rb | 2 +- .../orders/life_cycle_step_order_spec.rb | 8 +- ...oject_query_results_life_cycle_any_spec.rb | 8 +- ...ject_query_results_life_cycle_gate_spec.rb | 2 +- ...ect_query_results_life_cycle_stage_spec.rb | 2 +- .../life_cycle_step_definition_seeder_spec.rb | 6 +- .../project_life_cycles/edit_dialog.rb | 6 +- .../shared/project_life_cycle_helpers.rb | 72 ----------------- 55 files changed, 180 insertions(+), 685 deletions(-) delete mode 100644 app/models/project/gate.rb delete mode 100644 app/models/project/life_cycle_step.rb rename app/models/project/{stage.rb => phase.rb} (71%) rename app/models/project/{life_cycle_step_definition.rb => phase_definition.rb} (72%) rename app/models/project/{life_cycle_step_definitions => phase_definitions}/scopes/with_project_count.rb (92%) delete mode 100644 spec/factories/project_life_cycle_step_definition_factory.rb delete mode 100644 spec/factories/project_life_cycle_step_factory.rb rename app/models/project/gate_definition.rb => spec/factories/project_phase_definition_factory.rb (80%) rename app/models/project/stage_definition.rb => spec/factories/project_phase_factory.rb (80%) delete mode 100644 spec/models/project/gate_definition_spec.rb delete mode 100644 spec/models/project/gate_spec.rb delete mode 100644 spec/models/project/life_cycle_step_spec.rb rename spec/models/project/{life_cycle_step_definition_spec.rb => phase_definition_spec.rb} (64%) rename spec/models/project/{life_cycle_step_definitions => phase_definitions}/scopes/with_project_count_spec.rb (93%) rename spec/models/project/{stage_spec.rb => phase_spec.rb} (77%) delete mode 100644 spec/models/project/stage_definition_spec.rb delete mode 100644 spec/support/shared/project_life_cycle_helpers.rb diff --git a/app/components/projects/table_component.rb b/app/components/projects/table_component.rb index 93378fcfa7b..0e0a4062084 100644 --- a/app/components/projects/table_component.rb +++ b/app/components/projects/table_component.rb @@ -196,7 +196,7 @@ module Projects end def project_life_cycle_step_by_definition(definition, project) - @project_life_cycle_steps_by_definition ||= Project::LifeCycleStep + @project_life_cycle_steps_by_definition ||= Project::Phase .visible .index_by { |s| [s.definition_id, s.project_id] } diff --git a/app/contracts/project_life_cycle_steps/base_contract.rb b/app/contracts/project_life_cycle_steps/base_contract.rb index 62ea58b5721..86fb03e69ef 100644 --- a/app/contracts/project_life_cycle_steps/base_contract.rb +++ b/app/contracts/project_life_cycle_steps/base_contract.rb @@ -31,7 +31,7 @@ module ProjectLifeCycleSteps validate :select_custom_fields_permission validate :consecutive_steps_have_increasing_dates - def valid?(context = :saving_life_cycle_steps) = super + def valid?(context = :saving_phases) = super def select_custom_fields_permission return if user.allowed_in_project?(:edit_project_stages_and_gates, model) @@ -41,7 +41,7 @@ module ProjectLifeCycleSteps def consecutive_steps_have_increasing_dates # Filter out steps with missing dates before proceeding with comparison - filtered_steps = model.available_life_cycle_steps.select(&:start_date) + filtered_steps = model.available_phases.select(&:start_date) # Only proceed with comparisons if there are at least 2 valid steps return if filtered_steps.size < 2 @@ -53,7 +53,7 @@ module ProjectLifeCycleSteps field = current_step.is_a?(Project::Stage) ? :date_range : :date model.errors.import( current_step.errors.add(field, :non_continuous_dates, step:), - attribute: :"available_life_cycle_steps.#{field}" + attribute: :"available_phases.#{field}" ) end end diff --git a/app/controllers/admin/settings/project_life_cycle_step_definitions_controller.rb b/app/controllers/admin/settings/project_life_cycle_step_definitions_controller.rb index 363e8038a7d..51cb25f5b4c 100644 --- a/app/controllers/admin/settings/project_life_cycle_step_definitions_controller.rb +++ b/app/controllers/admin/settings/project_life_cycle_step_definitions_controller.rb @@ -61,7 +61,7 @@ module Admin::Settings end def create - @definition = Project::LifeCycleStepDefinition.new(definition_params) + @definition = Project::PhaseDefinition.new(definition_params) if @definition.save flash[:notice] = I18n.t(:notice_successful_create) @@ -123,11 +123,11 @@ module Admin::Settings end def find_definitions - @definitions = Project::LifeCycleStepDefinition.with_project_count + @definitions = Project::PhaseDefinition.with_project_count end def find_definition - @definition = Project::LifeCycleStepDefinition.find(params[:id]) + @definition = Project::PhaseDefinition.find(params[:id]) end def definition_params diff --git a/app/controllers/projects/settings/life_cycle_steps_controller.rb b/app/controllers/projects/settings/life_cycle_steps_controller.rb index 89f04991572..7041ac5f6dc 100644 --- a/app/controllers/projects/settings/life_cycle_steps_controller.rb +++ b/app/controllers/projects/settings/life_cycle_steps_controller.rb @@ -38,7 +38,7 @@ class Projects::Settings::LifeCycleStepsController < Projects::SettingsControlle def index; end def toggle - definition = Project::LifeCycleStepDefinition.where(id: params[:id]) + definition = Project::PhaseDefinition.where(id: params[:id]) upsert_steps(definition, active: params["value"]) end @@ -58,7 +58,7 @@ class Projects::Settings::LifeCycleStepsController < Projects::SettingsControlle private def load_life_cycle_definitions - @life_cycle_definitions = Project::LifeCycleStepDefinition.order(position: :asc) + @life_cycle_definitions = Project::PhaseDefinition.order(position: :asc) end def deny_access_on_feature_flag @@ -66,7 +66,7 @@ class Projects::Settings::LifeCycleStepsController < Projects::SettingsControlle end def upsert_steps(definitions, active:) - Project::LifeCycleStep.upsert_all( + Project::Phase.upsert_all( definitions.map do |definition| { project_id: @project.id, diff --git a/app/helpers/highlighting_helper.rb b/app/helpers/highlighting_helper.rb index 8297a7ddcb0..920c13d1b7e 100644 --- a/app/helpers/highlighting_helper.rb +++ b/app/helpers/highlighting_helper.rb @@ -4,6 +4,6 @@ module HighlightingHelper end def highlight_css_updated_at - ApplicationRecord.most_recently_changed Status, IssuePriority, Type, UserPreference, Project::LifeCycleStepDefinition + ApplicationRecord.most_recently_changed Status, IssuePriority, Type, UserPreference, Project::PhaseDefinition end end diff --git a/app/models/journal/project_life_cycle_step_journal.rb b/app/models/journal/project_life_cycle_step_journal.rb index a907dc46886..6409a3bf2ce 100644 --- a/app/models/journal/project_life_cycle_step_journal.rb +++ b/app/models/journal/project_life_cycle_step_journal.rb @@ -31,7 +31,7 @@ class Journal::ProjectLifeCycleStepJournal < Journal::AssociatedJournal self.table_name = "project_life_cycle_step_journals" - belongs_to :life_cycle_step, class_name: "Project::LifeCycleStep" + belongs_to :phase, class_name: "Project::LifeCycleStep" alias_attribute :date, :start_date diff --git a/app/models/permitted_params.rb b/app/models/permitted_params.rb index d145da84d56..7b29410cec8 100644 --- a/app/models/permitted_params.rb +++ b/app/models/permitted_params.rb @@ -292,7 +292,7 @@ class PermittedParams def project_life_cycles params.require(:project).permit( - available_life_cycle_steps_attributes: %i[id date date_range] + available_phases_attributes: %i[id date date_range] ) end diff --git a/app/models/project.rb b/app/models/project.rb index 3f8f81abd2c..4a14db770ce 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -86,17 +86,17 @@ class Project < ApplicationRecord has_many :notification_settings, dependent: :destroy has_many :project_storages, dependent: :destroy, class_name: "Storages::ProjectStorage" has_many :storages, through: :project_storages - has_many :life_cycle_steps, class_name: "Project::LifeCycleStep", dependent: :destroy - has_many :available_life_cycle_steps, + has_many :phases, class_name: "Project::Phase", dependent: :destroy + has_many :available_phases, -> { visible.eager_load(:definition).order(position: :asc) }, - class_name: "Project::LifeCycleStep", + class_name: "Project::Phase", inverse_of: :project, dependent: :destroy has_many :recurring_meetings, dependent: :destroy - accepts_nested_attributes_for :available_life_cycle_steps - validates_associated :available_life_cycle_steps, on: :saving_life_cycle_steps + accepts_nested_attributes_for :available_phases + validates_associated :available_phase, on: :saving_phases store_attribute :settings, :deactivate_work_package_attachments, :boolean diff --git a/app/models/project/gate.rb b/app/models/project/gate.rb deleted file mode 100644 index 8c49a5360c5..00000000000 --- a/app/models/project/gate.rb +++ /dev/null @@ -1,45 +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. -#++ - -class Project::Gate < Project::LifeCycleStep - alias_attribute :date, :start_date - - # This ensures the type cannot be changed after initialising the class. - validates :type, inclusion: { in: %w[Project::Gate], message: :must_be_a_gate } - validate :end_date_not_allowed - - def end_date_not_allowed - if end_date.present? - errors.add(:base, :end_date_not_allowed) - end - end - - def not_set? - date.blank? - end -end diff --git a/app/models/project/life_cycle_step.rb b/app/models/project/life_cycle_step.rb deleted file mode 100644 index e703c0b26b7..00000000000 --- a/app/models/project/life_cycle_step.rb +++ /dev/null @@ -1,62 +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. -#++ - -class Project::LifeCycleStep < ApplicationRecord - belongs_to :project, optional: false, inverse_of: :available_life_cycle_steps - belongs_to :definition, - optional: false, - class_name: "Project::LifeCycleStepDefinition" - has_many :work_packages, inverse_of: :project_life_cycle_step, dependent: :nullify - - delegate :name, :position, to: :definition - - attr_readonly :definition_id, :type - - validates :type, inclusion: { in: %w[Project::Stage Project::Gate], message: :must_be_a_stage_or_gate } - validate :validate_type_and_class_name_are_identical - - scope :active, -> { where(active: true) } - - class << self - def visible(user = User.current) - allowed_projects = Project.allowed_to(user, :view_project_stages_and_gates) - active.where(project: allowed_projects) - end - end - - def validate_type_and_class_name_are_identical - if type != self.class.name - errors.add(:type, :type_and_class_name_mismatch) - end - end - - def column_name - # The id of the associated definition is relevant for displaying the correct column headers - "lcsd_#{definition_id}" - end -end diff --git a/app/models/project/stage.rb b/app/models/project/phase.rb similarity index 71% rename from app/models/project/stage.rb rename to app/models/project/phase.rb index 2ee54f26cd3..c72623b781e 100644 --- a/app/models/project/stage.rb +++ b/app/models/project/phase.rb @@ -26,11 +26,28 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class Project::Stage < Project::LifeCycleStep - # This ensures the type cannot be changed after initialising the class. - validates :type, inclusion: { in: %w[Project::Stage], message: :must_be_a_stage } +class Project::Phase < ApplicationRecord + belongs_to :project, optional: false, inverse_of: :available_phases + belongs_to :definition, + optional: false, + class_name: "Project::PhaseDefinition" + has_many :work_packages, inverse_of: :project_phase, dependent: :nullify + validate :validate_date_range + delegate :name, :position, to: :definition + + attr_readonly :definition_id, :type + + scope :active, -> { where(active: true) } + + class << self + def visible(user = User.current) + allowed_projects = Project.allowed_to(user, :view_project_stages_and_gates) + active.where(project: allowed_projects) + end + end + def working_days_count return nil if not_set? @@ -61,4 +78,9 @@ class Project::Stage < Project::LifeCycleStep errors.add(:date_range, :start_date_must_be_before_end_date) end end + + def column_name + # The id of the associated definition is relevant for displaying the correct column headers + "lcsd_#{definition_id}" + end end diff --git a/app/models/project/life_cycle_step_definition.rb b/app/models/project/phase_definition.rb similarity index 72% rename from app/models/project/life_cycle_step_definition.rb rename to app/models/project/phase_definition.rb index 031e72d4ee5..2ce0a661959 100644 --- a/app/models/project/life_cycle_step_definition.rb +++ b/app/models/project/phase_definition.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -26,22 +28,18 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class Project::LifeCycleStepDefinition < ApplicationRecord +class Project::PhaseDefinition < ApplicationRecord include ::Scopes::Scoped - has_many :life_cycle_steps, - class_name: "Project::LifeCycleStep", + has_many :phases, + class_name: "Project::Phase", foreign_key: :definition_id, inverse_of: :definition, dependent: :destroy - has_many :projects, through: :life_cycle_steps + has_many :projects, through: :phases belongs_to :color, optional: false validates :name, presence: true, uniqueness: true - validates :type, inclusion: { in: %w[Project::StageDefinition Project::GateDefinition], message: :must_be_a_stage_or_gate } - validate :validate_type_and_class_name_are_identical - - attr_readonly :type acts_as_list @@ -49,19 +47,8 @@ class Project::LifeCycleStepDefinition < ApplicationRecord scopes :with_project_count - def step_class - raise NotImplementedError - end - def column_name + # TODO: rename "lcsd_#{id}" end - - private - - def validate_type_and_class_name_are_identical - if type != self.class.name - errors.add(:type, :type_and_class_name_mismatch) - end - end end diff --git a/app/models/project/life_cycle_step_definitions/scopes/with_project_count.rb b/app/models/project/phase_definitions/scopes/with_project_count.rb similarity index 92% rename from app/models/project/life_cycle_step_definitions/scopes/with_project_count.rb rename to app/models/project/phase_definitions/scopes/with_project_count.rb index e4448a90187..7ed76b63c6a 100644 --- a/app/models/project/life_cycle_step_definitions/scopes/with_project_count.rb +++ b/app/models/project/phase_definitions/scopes/with_project_count.rb @@ -28,18 +28,18 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module Project::LifeCycleStepDefinitions::Scopes +module Project::PhaseDefinitions::Scopes module WithProjectCount extend ActiveSupport::Concern class_methods do def with_project_count - project_counts = Project::LifeCycleStep + project_counts = Project::Phase .where(active: true) .group(:definition_id) .select(:definition_id, "COUNT(project_id) AS count") - Project::LifeCycleStepDefinition + Project::PhaseDefinition .with(project_counts:) .joins("LEFT OUTER JOIN project_counts ON #{quoted_table_name}.id = project_counts.definition_id") .select("#{quoted_table_name}.*") diff --git a/app/models/queries/projects/filters/dynamically_from_life_cycle.rb b/app/models/queries/projects/filters/dynamically_from_life_cycle.rb index 9479ed1d6f3..7c264742aa0 100644 --- a/app/models/queries/projects/filters/dynamically_from_life_cycle.rb +++ b/app/models/queries/projects/filters/dynamically_from_life_cycle.rb @@ -69,7 +69,7 @@ module Queries::Projects::Filters::DynamicallyFromLifeCycle key = %w[Queries::Projects::Filters::LifeCycleStepFilter all_step_definitions] RequestStore - .fetch(key) { Project::LifeCycleStepDefinition.all.to_a } + .fetch(key) { Project::PhaseDefinition.all.to_a } .select { |lcsd| lcsd.is_a?(step_subclass) } end diff --git a/app/models/queries/projects/filters/filter_on_life_cycle.rb b/app/models/queries/projects/filters/filter_on_life_cycle.rb index a001f25f0f0..1ba4da12fa8 100644 --- a/app/models/queries/projects/filters/filter_on_life_cycle.rb +++ b/app/models/queries/projects/filters/filter_on_life_cycle.rb @@ -100,14 +100,14 @@ module Queries::Projects::Filters::FilterOnLifeCycle def stage_where_on(start_date, end_date = start_date) life_cycle_scope(Project::Stage.name) - .where(date_range_clause(Project::LifeCycleStep.table_name, "start_date", nil, start_date)) - .where(date_range_clause(Project::LifeCycleStep.table_name, "end_date", end_date, nil)) + .where(date_range_clause(Project::Phase.table_name, "start_date", nil, start_date)) + .where(date_range_clause(Project::Phase.table_name, "end_date", end_date, nil)) end def stage_where_between(start_date, end_date) life_cycle_scope(Project::Stage.name) - .where(date_range_clause(Project::LifeCycleStep.table_name, "start_date", start_date, nil)) - .where(date_range_clause(Project::LifeCycleStep.table_name, "end_date", nil, end_date)) + .where(date_range_clause(Project::Phase.table_name, "start_date", start_date, nil)) + .where(date_range_clause(Project::Phase.table_name, "end_date", nil, end_date)) end def stage_overlaps_this_week @@ -116,8 +116,8 @@ module Queries::Projects::Filters::FilterOnLifeCycle .where.not(end_date: nil) .where( <<~SQL.squish, beginning_of_week, end_of_week - daterange(#{Project::LifeCycleStep.table_name}.start_date, - #{Project::LifeCycleStep.table_name}.end_date, + daterange(#{Project::Phase.table_name}.start_date, + #{Project::Phase.table_name}.end_date, '[]') && daterange(?, ?, '[]') @@ -140,7 +140,7 @@ module Queries::Projects::Filters::FilterOnLifeCycle def gate_where(start_date, end_date = start_date) # On gates, only the start_date is set. life_cycle_scope(Project::Gate.name) - .where(date_range_clause(Project::LifeCycleStep.table_name, "start_date", start_date, end_date)) + .where(date_range_clause(Project::Phase.table_name, "start_date", start_date, end_date)) end def parsed_start @@ -164,8 +164,8 @@ module Queries::Projects::Filters::FilterOnLifeCycle end def life_cycle_scope(type) - life_cycle_scope = Project::LifeCycleStep - .where("#{Project::LifeCycleStep.table_name}.project_id = #{Project.table_name}.id") + life_cycle_scope = Project::Phase + .where("#{Project::Phase.table_name}.project_id = #{Project.table_name}.id") .where(project_id: Project.allowed_to(User.current, :view_project_stages_and_gates)) .where(type:) .active diff --git a/app/models/queries/projects/orders/life_cycle_step_order.rb b/app/models/queries/projects/orders/life_cycle_step_order.rb index 7234a0fac86..9b930390969 100644 --- a/app/models/queries/projects/orders/life_cycle_step_order.rb +++ b/app/models/queries/projects/orders/life_cycle_step_order.rb @@ -40,7 +40,7 @@ class Queries::Projects::Orders::LifeCycleStepOrder < Queries::Orders::Base def life_cycle_step_definition return @life_cycle_step_definition if defined?(@life_cycle_step_definition) - @life_cycle_step_definition = Project::LifeCycleStepDefinition.find_by(id: attribute[/\Alcsd_(\d+)\z/, 1]) + @life_cycle_step_definition = Project::PhaseDefinition.find_by(id: attribute[/\Alcsd_(\d+)\z/, 1]) end def available? diff --git a/app/models/queries/projects/selects/life_cycle_step.rb b/app/models/queries/projects/selects/life_cycle_step.rb index 16b14d901aa..691862595ca 100644 --- a/app/models/queries/projects/selects/life_cycle_step.rb +++ b/app/models/queries/projects/selects/life_cycle_step.rb @@ -38,7 +38,7 @@ class Queries::Projects::Selects::LifeCycleStep < Queries::Selects::Base def self.all_available return [] unless available? - Project::LifeCycleStepDefinition + Project::PhaseDefinition .pluck(:id) .map { |id| new(:"lcsd_#{id}") } end @@ -50,7 +50,7 @@ class Queries::Projects::Selects::LifeCycleStep < Queries::Selects::Base def life_cycle_step_definition return @life_cycle_step_definition if defined?(@life_cycle_step_definition) - @life_cycle_step_definition = Project::LifeCycleStepDefinition + @life_cycle_step_definition = Project::PhaseDefinition .find_by(id: attribute[KEY, 1]) end diff --git a/app/models/work_package.rb b/app/models/work_package.rb index 29440e3d9f8..62c0cd9b7db 100644 --- a/app/models/work_package.rb +++ b/app/models/work_package.rb @@ -55,7 +55,7 @@ class WorkPackage < ApplicationRecord belongs_to :assigned_to, class_name: "Principal", optional: true belongs_to :responsible, class_name: "Principal", optional: true belongs_to :version, optional: true - belongs_to :project_life_cycle_step, class_name: "Project::LifeCycleStep", optional: true + belongs_to :project_phase, class_name: "Project::Phase", optional: true belongs_to :priority, class_name: "IssuePriority" belongs_to :category, class_name: "Category", optional: true diff --git a/app/seeders/basic_data/life_cycle_step_definition_seeder.rb b/app/seeders/basic_data/life_cycle_step_definition_seeder.rb index 54166f73101..689f6e4ace1 100644 --- a/app/seeders/basic_data/life_cycle_step_definition_seeder.rb +++ b/app/seeders/basic_data/life_cycle_step_definition_seeder.rb @@ -27,7 +27,7 @@ #++ module BasicData class LifeCycleStepDefinitionSeeder < ModelSeeder - self.model_class = Project::LifeCycleStepDefinition + self.model_class = Project::PhaseDefinition self.seed_data_model_key = "life_cycles" self.needs = [ BasicData::LifeCycleColorSeeder diff --git a/app/services/journals/create_service/project_life_cycle_step.rb b/app/services/journals/create_service/project_life_cycle_step.rb index dcc76976976..8a69d1366c6 100644 --- a/app/services/journals/create_service/project_life_cycle_step.rb +++ b/app/services/journals/create_service/project_life_cycle_step.rb @@ -79,7 +79,7 @@ class Journals::CreateService FROM project_phases WHERE project_phases.project_id = :journable_id) phases ON - phases.id = project_phase_journals.life_cycle_step_id + phases.id = project_phase_journals.phase_id WHERE phases.start_date IS DISTINCT FROM project_phase_journals.start_date OR phases.end_date IS DISTINCT FROM project_phase_journals.end_date diff --git a/app/services/project_life_cycle_steps/preview_attributes_service.rb b/app/services/project_life_cycle_steps/preview_attributes_service.rb index 0070cabfe24..1c155ff7537 100644 --- a/app/services/project_life_cycle_steps/preview_attributes_service.rb +++ b/app/services/project_life_cycle_steps/preview_attributes_service.rb @@ -39,7 +39,7 @@ module ProjectLifeCycleSteps def clear_unchanged_fields(service_call) service_call .result - .available_life_cycle_steps + .available_phases .select(&:not_set?) .each { _1.errors.clear } end diff --git a/app/views/admin/settings/project_life_cycle_step_definitions/form.html.erb b/app/views/admin/settings/project_life_cycle_step_definitions/form.html.erb index 1542e789e27..0e49b6a395d 100644 --- a/app/views/admin/settings/project_life_cycle_step_definitions/form.html.erb +++ b/app/views/admin/settings/project_life_cycle_step_definitions/form.html.erb @@ -37,7 +37,7 @@ See COPYRIGHT and LICENSE files for more details. <%= admin_settings_primer_form_with( - model: [:admin, :settings, @definition.becomes(Project::LifeCycleStepDefinition)] + model: [:admin, :settings, @definition.becomes(Project::PhaseDefinition)] ) do |f| render Projects::LifeCycleStepDefinitions::Form.new(f) end diff --git a/app/views/highlighting/styles.css.erb b/app/views/highlighting/styles.css.erb index 8093142c47b..e963618168d 100644 --- a/app/views/highlighting/styles.css.erb +++ b/app/views/highlighting/styles.css.erb @@ -3,7 +3,7 @@ <%= resources_scope_color_css("priority", ::IssuePriority) %> <%= resources_scope_color_css("type", ::Type, inline_foreground: true) %> <%# Color coded icons %> -<%= resources_scope_color_css("life_cycle_step_definition", Project::LifeCycleStepDefinition, inline_foreground: true) %> +<%= resources_scope_color_css("life_cycle_step_definition", Project::PhaseDefinition, inline_foreground: true) %> <% Meetings::Statuses::AVAILABLE.each do |meeting_status| %> <%= resource_color_css("meeting_status", meeting_status) %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 79a9a73312e..f8f3825b1aa 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1034,9 +1034,9 @@ en: project_custom_field: is_required: "Required for all projects" custom_field_section: Section - project/life_cycle_step: + project/phase: definition: "Definition" - project/life_cycle_step_definition: + project/phase_definition: name: "Name" color: "Color" query: diff --git a/db/migrate/20250324161229_merge_lifecycle_steps.rb b/db/migrate/20250324161229_merge_lifecycle_steps.rb index 6d983303fa3..02fd9440d14 100644 --- a/db/migrate/20250324161229_merge_lifecycle_steps.rb +++ b/db/migrate/20250324161229_merge_lifecycle_steps.rb @@ -103,6 +103,10 @@ class MergeLifecycleSteps < ActiveRecord::Migration[8.0] change_table(:project_life_cycle_step_journals) do |t| t.rename :life_cycle_step_id, :phase_id end + + change_table(:work_packages) do |t| + t.rename :project_life_cycle_step_id, :project_phase_id + end end def rename_tables diff --git a/lib/open_project/journal_formatter/project_life_cycle_step_active.rb b/lib/open_project/journal_formatter/project_life_cycle_step_active.rb index 22e937b94e2..0f43d0e433d 100644 --- a/lib/open_project/journal_formatter/project_life_cycle_step_active.rb +++ b/lib/open_project/journal_formatter/project_life_cycle_step_active.rb @@ -32,7 +32,7 @@ class OpenProject::JournalFormatter::ProjectLifeCycleStepActive < JournalFormatt def render(key, values, options = { html: true }) return if !values[0] == !values[1] - step = Project::LifeCycleStep.find(key[/\d+/]) + step = Project::Phase.find(key[/\d+/]) name = step.definition.name label = options[:html] ? content_tag(:strong, name) : name diff --git a/lib/open_project/journal_formatter/project_life_cycle_step_dates.rb b/lib/open_project/journal_formatter/project_life_cycle_step_dates.rb index d58678fe110..97377781471 100644 --- a/lib/open_project/journal_formatter/project_life_cycle_step_dates.rb +++ b/lib/open_project/journal_formatter/project_life_cycle_step_dates.rb @@ -30,7 +30,7 @@ class OpenProject::JournalFormatter::ProjectLifeCycleStepDates < JournalFormatter::Base def render(key, values, options = { html: true }) - step = Project::LifeCycleStep.find(key[/\d+/]) + step = Project::Phase.find(key[/\d+/]) name = step.definition.name label = options[:html] ? content_tag(:strong, name) : name diff --git a/modules/overviews/app/components/project_life_cycles/sections/edit_component.html.erb b/modules/overviews/app/components/project_life_cycles/sections/edit_component.html.erb index 70b01c579ff..3ba10a71d01 100644 --- a/modules/overviews/app/components/project_life_cycles/sections/edit_component.html.erb +++ b/modules/overviews/app/components/project_life_cycles/sections/edit_component.html.erb @@ -16,7 +16,7 @@ url: update_project_life_cycles_path(project_id: model.id) ) do |f| render(Primer::Forms::SpacingWrapper.new) do - f.fields_for(:available_life_cycle_steps) do |life_cycle_form| + f.fields_for(:available_phases) do |life_cycle_form| render(Projects::LifeCycles::Form.new(life_cycle_form)) end end diff --git a/modules/overviews/app/components/project_life_cycles/sections/show_component.rb b/modules/overviews/app/components/project_life_cycles/sections/show_component.rb index f848609e596..08152955659 100644 --- a/modules/overviews/app/components/project_life_cycles/sections/show_component.rb +++ b/modules/overviews/app/components/project_life_cycles/sections/show_component.rb @@ -37,7 +37,7 @@ module ProjectLifeCycles super @project = project - @life_cycle_steps = @project.available_life_cycle_steps + @life_cycle_steps = @project.available_phases end private diff --git a/spec/contracts/project_life_cycle_steps/base_contract_spec.rb b/spec/contracts/project_life_cycle_steps/base_contract_spec.rb index e818921de7a..37bfc950163 100644 --- a/spec/contracts/project_life_cycle_steps/base_contract_spec.rb +++ b/spec/contracts/project_life_cycle_steps/base_contract_spec.rb @@ -39,7 +39,7 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do context "with authorized user" do let(:user) { build_stubbed(:user) } - let(:project) { build_stubbed(:project, available_life_cycle_steps: steps) } + let(:project) { build_stubbed(:project, available_phases: steps) } let(:steps) { [] } before do @@ -81,7 +81,7 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do let(:steps) { [gate3, gate1] } it_behaves_like "contract is invalid", - "available_life_cycle_steps.date": :non_continuous_dates + "available_phases.date": :non_continuous_dates it "adds an error to the decreasing step" do contract.validate @@ -93,7 +93,7 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do let(:steps) { [gate3, stage2] } it_behaves_like "contract is invalid", - "available_life_cycle_steps.date_range": :non_continuous_dates + "available_phases.date_range": :non_continuous_dates it "adds an error to the decreasing step" do contract.validate @@ -107,7 +107,7 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do let(:steps) { [gate1, step4] } it_behaves_like "contract is invalid", - "available_life_cycle_steps.date": :non_continuous_dates + "available_phases.date": :non_continuous_dates end context "when steps have touching start and end dates" do @@ -116,14 +116,14 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do let(:steps) { [stage2, stage4] } it_behaves_like "contract is invalid", - "available_life_cycle_steps.date_range": :non_continuous_dates + "available_phases.date_range": :non_continuous_dates context "when having an empty step in between" do let(:step_missing_dates) { build_stubbed(:project_stage, start_date: nil, end_date: nil) } let(:steps) { [stage2, step_missing_dates, stage4] } it_behaves_like "contract is invalid", - "available_life_cycle_steps.date_range": :non_continuous_dates + "available_phases.date_range": :non_continuous_dates end end @@ -132,14 +132,14 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do let(:steps) { [gate1, gate4] } it_behaves_like "contract is invalid", - "available_life_cycle_steps.date": :non_continuous_dates + "available_phases.date": :non_continuous_dates context "when having an empty step in between" do let(:step_missing_dates) { build_stubbed(:project_stage, start_date: nil, end_date: nil) } let(:steps) { [gate1, step_missing_dates, gate4] } it_behaves_like "contract is invalid", - "available_life_cycle_steps.date": :non_continuous_dates + "available_phases.date": :non_continuous_dates end end @@ -200,7 +200,7 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do let(:steps) { [stage2, step_missing_dates, gate1] } it_behaves_like "contract is invalid", - "available_life_cycle_steps.date": :non_continuous_dates + "available_phases.date": :non_continuous_dates it "adds an error to the decreasing step" do contract.validate @@ -211,11 +211,11 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do end describe "triggering validations on the model" do - it "sets the :saving_life_cycle_steps validation context" do + it "sets the :saving_phases validation context" do allow(project).to receive(:valid?) contract.validate - expect(project).to have_received(:valid?).with(:saving_life_cycle_steps) + expect(project).to have_received(:valid?).with(:saving_phases) end end end diff --git a/spec/factories/project_life_cycle_step_definition_factory.rb b/spec/factories/project_life_cycle_step_definition_factory.rb deleted file mode 100644 index f629dcb4018..00000000000 --- a/spec/factories/project_life_cycle_step_definition_factory.rb +++ /dev/null @@ -1,41 +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. -#++ - -FactoryBot.define do - factory :project_life_cycle_step_definition, class: "Project::LifeCycleStepDefinition" do - color - - factory :project_gate_definition, class: "Project::GateDefinition" do - sequence(:name) { |n| "Gate Definition No. #{n}" } - end - - factory :project_stage_definition, class: "Project::StageDefinition" do - sequence(:name) { |n| "Stage Definition No. #{n}" } - end - end -end diff --git a/spec/factories/project_life_cycle_step_factory.rb b/spec/factories/project_life_cycle_step_factory.rb deleted file mode 100644 index 465dd702bfc..00000000000 --- a/spec/factories/project_life_cycle_step_factory.rb +++ /dev/null @@ -1,49 +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. -#++ - -FactoryBot.define do - factory :project_life_cycle_step, class: "Project::LifeCycleStep" do - project - active { true } - - trait :skip_validate do - to_create { |instance| instance.save(validate: false) } - end - - factory :project_stage, class: "Project::Stage" do - definition factory: :project_stage_definition - start_date { Date.current - 2.days } - end_date { Date.current + 2.days } - end - - factory :project_gate, class: "Project::Gate" do - definition factory: :project_gate_definition - date { Date.current + 2.days } - end - end -end diff --git a/app/models/project/gate_definition.rb b/spec/factories/project_phase_definition_factory.rb similarity index 80% rename from app/models/project/gate_definition.rb rename to spec/factories/project_phase_definition_factory.rb index a0cfef69352..e9b5657169e 100644 --- a/app/models/project/gate_definition.rb +++ b/spec/factories/project_phase_definition_factory.rb @@ -26,14 +26,10 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class Project::GateDefinition < Project::LifeCycleStepDefinition - has_many :gates, # Alias for life_cycle_steps - class_name: "Project::Gate", - foreign_key: :definition_id, - inverse_of: :definition, - dependent: :destroy +FactoryBot.define do + factory :project_phase_definition, class: "Project::PhaseDefinition" do + sequence(:name) { |n| "Phase definition #{n}" } - def step_class - Project::Gate + color end end diff --git a/app/models/project/stage_definition.rb b/spec/factories/project_phase_factory.rb similarity index 80% rename from app/models/project/stage_definition.rb rename to spec/factories/project_phase_factory.rb index f373d13cd64..de8269b9676 100644 --- a/app/models/project/stage_definition.rb +++ b/spec/factories/project_phase_factory.rb @@ -26,14 +26,13 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class Project::StageDefinition < Project::LifeCycleStepDefinition - has_many :stages, # Alias for life_cycle_steps - class_name: "Project::Stage", - foreign_key: :definition_id, - inverse_of: :definition, - dependent: :destroy +FactoryBot.define do + factory :project_phase, class: "Project::Phase" do + project + definition factory: :project_phase_definition + active { true } - def step_class - Project::Stage + start_date { Date.current - 2.days } + end_date { Date.current + 2.days } end end diff --git a/spec/features/projects/life_cycle/overview_page/dialog/update_spec.rb b/spec/features/projects/life_cycle/overview_page/dialog/update_spec.rb index 242bfd7267c..3b03cdd8ae3 100644 --- a/spec/features/projects/life_cycle/overview_page/dialog/update_spec.rb +++ b/spec/features/projects/life_cycle/overview_page/dialog/update_spec.rb @@ -47,7 +47,7 @@ RSpec.describe "Edit project stages and gates on project overview page", :js, wi describe "with the dialog open" do context "when all LifeCycleSteps are blank" do before do - Project::LifeCycleStep.update_all(start_date: nil, end_date: nil) + Project::Phase.update_all(start_date: nil, end_date: nil) end it "shows all the Project::LifeCycleSteps without a value" do @@ -80,7 +80,7 @@ RSpec.describe "Edit project stages and gates on project overview page", :js, wi expect_angular_frontend_initialized - project.available_life_cycle_steps.each do |step| + project.available_phases.each do |step| dialog.expect_input_for(step) end diff --git a/spec/features/projects/life_cycle/overview_page/sidebar_spec.rb b/spec/features/projects/life_cycle/overview_page/sidebar_spec.rb index 65f0335494c..3f5017902d5 100644 --- a/spec/features/projects/life_cycle/overview_page/sidebar_spec.rb +++ b/spec/features/projects/life_cycle/overview_page/sidebar_spec.rb @@ -132,7 +132,7 @@ RSpec.describe "Show project life cycles on project overview page", :js, with_fl describe "with no values" do before do - Project::LifeCycleStep.update_all(start_date: nil, end_date: nil) + Project::Phase.update_all(start_date: nil, end_date: nil) end it "shows the correct value for the project custom field if given" do diff --git a/spec/lib/open_project/journal_formatter/project_life_cycle_step_active_spec.rb b/spec/lib/open_project/journal_formatter/project_life_cycle_step_active_spec.rb index a308c7fd7de..cf6e5fef469 100644 --- a/spec/lib/open_project/journal_formatter/project_life_cycle_step_active_spec.rb +++ b/spec/lib/open_project/journal_formatter/project_life_cycle_step_active_spec.rb @@ -37,7 +37,7 @@ RSpec.describe OpenProject::JournalFormatter::ProjectLifeCycleStepActive do subject(:result) { described_class.new(nil).render(key, values, html:) } before do - allow(Project::LifeCycleStep).to receive(:find).with(step.id.to_s).and_return(step) + allow(Project::Phase).to receive(:find).with(step.id.to_s).and_return(step) end def date(day) = Date.new(2025, 1, day) diff --git a/spec/lib/open_project/journal_formatter/project_life_cycle_step_dates_spec.rb b/spec/lib/open_project/journal_formatter/project_life_cycle_step_dates_spec.rb index 44f1eccaa8f..92b7429beeb 100644 --- a/spec/lib/open_project/journal_formatter/project_life_cycle_step_dates_spec.rb +++ b/spec/lib/open_project/journal_formatter/project_life_cycle_step_dates_spec.rb @@ -37,7 +37,7 @@ RSpec.describe OpenProject::JournalFormatter::ProjectLifeCycleStepDates do subject(:result) { described_class.new(nil).render(key, values, html:) } before do - allow(Project::LifeCycleStep).to receive(:find).with(step.id.to_s).and_return(step) + allow(Project::Phase).to receive(:find).with(step.id.to_s).and_return(step) end def date(day) = Date.new(2025, 1, day) diff --git a/spec/models/project/gate_definition_spec.rb b/spec/models/project/gate_definition_spec.rb deleted file mode 100644 index 2b1d4c2f9ec..00000000000 --- a/spec/models/project/gate_definition_spec.rb +++ /dev/null @@ -1,57 +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. -#++ - -require "rails_helper" -require "support/shared/project_life_cycle_helpers" - -RSpec.describe Project::GateDefinition do - it_behaves_like "a Project::LifeCycleStepDefinition event" - - describe "associations" do - it "has many gates" do - expect(subject).to have_many(:gates).class_name("Project::Gate") - .with_foreign_key(:definition_id) - .inverse_of(:definition) - .dependent(:destroy) - end - end - - describe "validations" do - it "is invalid if type and class name do not match" do - subject.type = "Project::StageDefinition" - expect(subject).not_to be_valid - expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch) - end - end - - describe "#step_class" do - it "returns Project::Stage" do - expect(subject.step_class).to eq(Project::Gate) - end - end -end diff --git a/spec/models/project/gate_spec.rb b/spec/models/project/gate_spec.rb deleted file mode 100644 index 5fb5bcc7825..00000000000 --- a/spec/models/project/gate_spec.rb +++ /dev/null @@ -1,57 +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. -#++ - -require "rails_helper" -require "support/shared/project_life_cycle_helpers" - -RSpec.describe Project::Gate do - it_behaves_like "a Project::LifeCycleStep event" - - describe "validations" do - it { is_expected.to validate_inclusion_of(:type).in_array(["Project::Gate"]).with_message(:must_be_a_gate) } - - it "is invalid if `end_date` is present" do - subject.end_date = Time.zone.today - - expect(subject).not_to be_valid - expect(subject.errors[:base]) - .to include("Cannot assign end date to a Project::Gate") - end - - it "is valid if `end_date` is not present" do - valid_gate = build(:project_gate, end_date: nil) - expect(valid_gate).to be_valid - end - - it "is invalid if type and class name do not match" do - subject.type = "Project::Stage" - expect(subject).not_to be_valid - expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch) - end - end -end diff --git a/spec/models/project/life_cycle_step_spec.rb b/spec/models/project/life_cycle_step_spec.rb deleted file mode 100644 index 9c8660f41e3..00000000000 --- a/spec/models/project/life_cycle_step_spec.rb +++ /dev/null @@ -1,77 +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. -#++ - -require "rails_helper" - -RSpec.describe Project::LifeCycleStep do - it "can be instantiated" do - expect { described_class.new }.not_to raise_error(NotImplementedError) - end - - describe "with an instantiated Gate" do - subject { build :project_gate } - - it { is_expected.to have_readonly_attribute(:definition_id) } - it { is_expected.to have_readonly_attribute(:type) } - end - - describe "validations" do - it "is invalid if type and class name do not match" do - subject.type = "Project::Gate" - expect(subject).not_to be_valid - expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch) - end - end - - describe ".visible" do - let(:project) { create(:project) } - let(:development_project) { create(:project) } - let(:user) do - create(:user, - member_with_permissions: - { project => %i(view_project view_project_stages_and_gates), - development_project => %i(view_project) }) - end - - let!(:life_cycle_gate) { create(:project_gate, project:) } - let!(:life_cycle_stage) { create(:project_stage, project:) } - let!(:life_cycle_stage_dev) { create(:project_stage, project: development_project) } - let!(:inactive_life_cycle_gate) { create(:project_gate, project:, active: false) } - let!(:inactive_life_cycle_stage) { create(:project_stage, project: development_project, active: false) } - - it "returns active steps where the user has a view_project_stages_and_gates permission" do - expected_steps = [life_cycle_gate, life_cycle_stage] - expect(described_class.visible(user)).to eq(expected_steps) - end - end - - # For more specs see: - # - spec/support/shared/project_life_cycle_helpers.rb - # - spec/models/project/gate_spec.rb - # - spec/models/project/stage_spec.rb -end diff --git a/spec/models/project/life_cycle_step_definition_spec.rb b/spec/models/project/phase_definition_spec.rb similarity index 64% rename from spec/models/project/life_cycle_step_definition_spec.rb rename to spec/models/project/phase_definition_spec.rb index 8fee988c59a..ddba4df3883 100644 --- a/spec/models/project/life_cycle_step_definition_spec.rb +++ b/spec/models/project/phase_definition_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -28,36 +30,25 @@ require "rails_helper" -RSpec.describe Project::LifeCycleStepDefinition do +RSpec.describe Project::PhaseDefinition do it "can be instantiated" do expect { described_class.new }.not_to raise_error end - context "with a Project::StageDefinition" do - subject { create :project_stage_definition } - - it { is_expected.to have_readonly_attribute(:type) } - end - describe "validations" do it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name) } - - it { - expect(subject).to validate_inclusion_of(:type) - .in_array(%w[Project::StageDefinition Project::GateDefinition]) - .with_message(:must_be_a_stage_or_gate) - } - - it "is invalid if type and class name do not match" do - subject.type = "Project::GateDefinition" - expect(subject).not_to be_valid - expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch) - end end - # For more specs see: - # - spec/support/shared/project_life_cycle_helpers.rb - # - spec/models/project/gate_definition_spec.rb - # - spec/models/project/stage_definition_spec.rb + describe "associations" do + it "has many phases" do + expect(subject).to have_many(:phases).class_name("Project::Phase") + .with_foreign_key(:definition_id) + .inverse_of(:definition) + .dependent(:destroy) + end + + it { is_expected.to have_many(:projects).through(:phases) } + it { is_expected.to belong_to(:color).required } + end end diff --git a/spec/models/project/life_cycle_step_definitions/scopes/with_project_count_spec.rb b/spec/models/project/phase_definitions/scopes/with_project_count_spec.rb similarity index 93% rename from spec/models/project/life_cycle_step_definitions/scopes/with_project_count_spec.rb rename to spec/models/project/phase_definitions/scopes/with_project_count_spec.rb index a43ab0ee3c3..777be1831d3 100644 --- a/spec/models/project/life_cycle_step_definitions/scopes/with_project_count_spec.rb +++ b/spec/models/project/phase_definitions/scopes/with_project_count_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe Project::LifeCycleStepDefinitions::Scopes::WithProjectCount do +RSpec.describe Project::PhaseDefinitions::Scopes::WithProjectCount do let!(:definition_a) { create(:project_stage_definition, name: "foo") } let!(:definition_b) { create(:project_gate_definition, name: "bar") } let!(:definition_c) { create(:project_stage_definition, name: "baz") } @@ -49,7 +49,7 @@ RSpec.describe Project::LifeCycleStepDefinitions::Scopes::WithProjectCount do describe ".with_project_count" do it "queries project counts alongside definitions" do - expect(Project::LifeCycleStepDefinition.with_project_count).to contain_exactly( + expect(Project::PhaseDefinition.with_project_count).to contain_exactly( having_attributes(id: definition_a.id, name: "foo", project_count: 2), having_attributes(id: definition_b.id, name: "bar", project_count: 1), having_attributes(id: definition_c.id, name: "baz", project_count: 0) diff --git a/spec/models/project/stage_spec.rb b/spec/models/project/phase_spec.rb similarity index 77% rename from spec/models/project/stage_spec.rb rename to spec/models/project/phase_spec.rb index 99388f780f8..5eca5547448 100644 --- a/spec/models/project/stage_spec.rb +++ b/spec/models/project/phase_spec.rb @@ -27,17 +27,36 @@ #++ require "rails_helper" -require "support/shared/project_life_cycle_helpers" -RSpec.describe Project::Stage do - it_behaves_like "a Project::LifeCycleStep event" +RSpec.describe Project::Phase do + it "can be instantiated" do + expect { described_class.new }.not_to raise_error(NotImplementedError) + end - describe "validations" do - it { is_expected.to validate_inclusion_of(:type).in_array(["Project::Stage"]).with_message(:must_be_a_stage) } + it { is_expected.to have_readonly_attribute(:definition_id) } - it "is valid when `start_date` and `end_date` are present" do - valid_stage = build(:project_stage) - expect(valid_stage).to be_valid + describe "associations" do + it { is_expected.to belong_to(:project).required } + it { is_expected.to belong_to(:definition).required } + it { is_expected.to have_many(:work_packages) } + end + + describe ".visible" do + let(:project) { create(:project) } + let(:development_project) { create(:project) } + let(:user) do + create(:user, + member_with_permissions: + { project => %i(view_project view_project_stages_and_gates), + development_project => %i(view_project) }) + end + + let!(:phase) { create(:project_phase, project:) } + let!(:phase_dev) { create(:project_phase, project: development_project) } + let!(:inactive_phase) { create(:project_phase, project: development_project, active: false) } + + it "returns active phases where the user has a view_project_stages_and_gates permission" do + expect(described_class.visible(user)).to contain_exactly(phase) end end @@ -69,7 +88,7 @@ RSpec.describe Project::Stage do describe "#validate_date_range" do it "is valid when both dates are blank" do - stage = build(:project_stage, start_date: nil, end_date: nil) + stage = build(:project_phase, start_date: nil, end_date: nil) expect(stage).to be_valid end @@ -98,12 +117,6 @@ RSpec.describe Project::Stage do expect(subject).not_to be_valid expect(subject.errors[:date_range]).to be_empty end - - it "is invalid if type and class name do not match" do - subject.type = "Project::Gate" - expect(subject).not_to be_valid - expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch) - end end describe "#working_days_count" do @@ -135,8 +148,8 @@ RSpec.describe Project::Stage do allow(Day).to receive(:working).and_return(Day) allow(Day).to receive(:from_range) - .with(from: subject.start_date, to: subject.end_date) - .and_return([]) + .with(from: subject.start_date, to: subject.end_date) + .and_return([]) expect(subject.working_days_count).to eq(0) diff --git a/spec/models/project/stage_definition_spec.rb b/spec/models/project/stage_definition_spec.rb deleted file mode 100644 index 0f236e6b46a..00000000000 --- a/spec/models/project/stage_definition_spec.rb +++ /dev/null @@ -1,57 +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. -#++ - -require "rails_helper" -require "support/shared/project_life_cycle_helpers" - -RSpec.describe Project::StageDefinition do - it_behaves_like "a Project::LifeCycleStepDefinition event" - - describe "associations" do - it "has many stages" do - expect(subject).to have_many(:stages).class_name("Project::Stage") - .with_foreign_key(:definition_id) - .inverse_of(:definition) - .dependent(:destroy) - end - end - - describe "validations" do - it "is invalid if type and class name do not match" do - subject.type = "Project::GateDefinition" - expect(subject).not_to be_valid - expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch) - end - end - - describe "#step_class" do - it "returns Project::Stage" do - expect(subject.step_class).to eq(Project::Stage) - end - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 03da5e4c8f6..5da9dc98d3b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -383,8 +383,8 @@ RSpec.describe Project do describe "life_cycles" do it { is_expected.to have_many(:life_cycle_steps).class_name("Project::LifeCycleStep").dependent(:destroy) } - it "has many available_life_cycle_steps" do - expect(subject).to have_many(:available_life_cycle_steps) + it "has many available_phases" do + expect(subject).to have_many(:available_phases) .class_name("Project::LifeCycleStep") .inverse_of(:project) .dependent(:destroy) @@ -392,18 +392,18 @@ RSpec.describe Project do end it "checks for active flag" do - expect(subject.available_life_cycle_steps.to_sql) + expect(subject.available_phases.to_sql) .to include("\"project_life_cycle_steps\".\"active\" = TRUE") end it "checks for :view_project_stages_and_gates permission" do project_condition = described_class.allowed_to(User.current, :view_project_stages_and_gates).select(:id) - expect(subject.available_life_cycle_steps.to_sql).to include(project_condition.to_sql) + expect(subject.available_phases.to_sql).to include(project_condition.to_sql) end it "eager loads :definition" do - expect(subject.available_life_cycle_steps.to_sql) + expect(subject.available_phases.to_sql) .to include("LEFT OUTER JOIN \"project_life_cycle_step_definitions\" ON") end @@ -419,8 +419,8 @@ RSpec.describe Project do expect(project).to be_valid end - it "is invalid with the :saving_life_cycle_steps validation context" do - expect(project).not_to be_valid(:saving_life_cycle_steps) + it "is invalid with the :saving_phases validation context" do + expect(project).not_to be_valid(:saving_phases) end end end diff --git a/spec/models/queries/projects/filters/life_cycle_gate_filter_spec.rb b/spec/models/queries/projects/filters/life_cycle_gate_filter_spec.rb index 56fd9999e8f..75772b29430 100644 --- a/spec/models/queries/projects/filters/life_cycle_gate_filter_spec.rb +++ b/spec/models/queries/projects/filters/life_cycle_gate_filter_spec.rb @@ -41,7 +41,7 @@ RSpec.describe Queries::Projects::Filters::LifeCycleGateFilter do end before do - allow(Project::LifeCycleStepDefinition) + allow(Project::PhaseDefinition) .to receive(:all) .and_return([stage, gate]) end diff --git a/spec/models/queries/projects/filters/life_cycle_stage_filter_spec.rb b/spec/models/queries/projects/filters/life_cycle_stage_filter_spec.rb index 141261db623..41d84fb76f9 100644 --- a/spec/models/queries/projects/filters/life_cycle_stage_filter_spec.rb +++ b/spec/models/queries/projects/filters/life_cycle_stage_filter_spec.rb @@ -41,7 +41,7 @@ RSpec.describe Queries::Projects::Filters::LifeCycleStageFilter do end before do - allow(Project::LifeCycleStepDefinition) + allow(Project::PhaseDefinition) .to receive(:all) .and_return([stage, gate]) end diff --git a/spec/models/queries/projects/orders/life_cycle_step_order_spec.rb b/spec/models/queries/projects/orders/life_cycle_step_order_spec.rb index 4f2055ee8c2..6a6c2e5ac7d 100644 --- a/spec/models/queries/projects/orders/life_cycle_step_order_spec.rb +++ b/spec/models/queries/projects/orders/life_cycle_step_order_spec.rb @@ -104,11 +104,11 @@ RSpec.describe Queries::Projects::Orders::LifeCycleStepOrder do let(:id) { 42 } before do - allow(Project::LifeCycleStepDefinition).to receive(:find_by).with(id: id.to_s).and_return(step_definition) + allow(Project::PhaseDefinition).to receive(:find_by).with(id: id.to_s).and_return(step_definition) end context "when life cycle definition exists" do - let(:step_definition) { instance_double(Project::LifeCycleStepDefinition) } + let(:step_definition) { instance_double(Project::PhaseDefinition) } it "returns the life cycle definition" do expect(instance.life_cycle_step_definition).to eq(step_definition) @@ -117,7 +117,7 @@ RSpec.describe Queries::Projects::Orders::LifeCycleStepOrder do it "memoizes the life cycle definition" do 2.times { instance.life_cycle_step_definition } - expect(Project::LifeCycleStepDefinition).to have_received(:find_by).once + expect(Project::PhaseDefinition).to have_received(:find_by).once end end @@ -131,7 +131,7 @@ RSpec.describe Queries::Projects::Orders::LifeCycleStepOrder do it "memoizes the life cycle" do 2.times { instance.life_cycle_step_definition } - expect(Project::LifeCycleStepDefinition).to have_received(:find_by).once + expect(Project::PhaseDefinition).to have_received(:find_by).once end end end diff --git a/spec/models/queries/projects/project_query_results_life_cycle_any_spec.rb b/spec/models/queries/projects/project_query_results_life_cycle_any_spec.rb index ff45a3d92c4..c9bc21bc2b7 100644 --- a/spec/models/queries/projects/project_query_results_life_cycle_any_spec.rb +++ b/spec/models/queries/projects/project_query_results_life_cycle_any_spec.rb @@ -61,25 +61,25 @@ RSpec.describe ProjectQuery, "results of 'Any stage or gate' filter" do # rubocop:disable RSpec/ScatteredSetup def self.remove_gate before do - Project::LifeCycleStep.where(type: Project::Gate.name).destroy_all + Project::Phase.where(type: Project::Gate.name).destroy_all end end def self.remove_stage before do - Project::LifeCycleStep.where(type: Project::Stage.name).destroy_all + Project::Phase.where(type: Project::Stage.name).destroy_all end end def self.disable_stage before do - Project::LifeCycleStep.where(type: Project::Stage.name).update_all(active: false) + Project::Phase.where(type: Project::Stage.name).update_all(active: false) end end def self.disable_gate before do - Project::LifeCycleStep.where(type: Project::Gate.name).update_all(active: false) + Project::Phase.where(type: Project::Gate.name).update_all(active: false) end end diff --git a/spec/models/queries/projects/project_query_results_life_cycle_gate_spec.rb b/spec/models/queries/projects/project_query_results_life_cycle_gate_spec.rb index 777b76cfac7..46fd9ab13fa 100644 --- a/spec/models/queries/projects/project_query_results_life_cycle_gate_spec.rb +++ b/spec/models/queries/projects/project_query_results_life_cycle_gate_spec.rb @@ -67,7 +67,7 @@ RSpec.describe ProjectQuery, "results of a life cycle gate filter" do # rubocop:disable RSpec/ScatteredSetup def self.disable_gate before do - Project::LifeCycleStep.where(type: Project::Gate.name).update_all(active: false) + Project::Phase.where(type: Project::Gate.name).update_all(active: false) end end diff --git a/spec/models/queries/projects/project_query_results_life_cycle_stage_spec.rb b/spec/models/queries/projects/project_query_results_life_cycle_stage_spec.rb index 1f7c09b8aaf..c15add3bc4c 100644 --- a/spec/models/queries/projects/project_query_results_life_cycle_stage_spec.rb +++ b/spec/models/queries/projects/project_query_results_life_cycle_stage_spec.rb @@ -69,7 +69,7 @@ RSpec.describe ProjectQuery, "results of a life cycle stage filter" do # rubocop:disable RSpec/ScatteredSetup def self.disable_stage before do - Project::LifeCycleStep.where(type: Project::Stage.name).update_all(active: false) + Project::Phase.where(type: Project::Stage.name).update_all(active: false) end end diff --git a/spec/seeders/basic_data/life_cycle_step_definition_seeder_spec.rb b/spec/seeders/basic_data/life_cycle_step_definition_seeder_spec.rb index 367f1fdcc5a..16a440e90be 100644 --- a/spec/seeders/basic_data/life_cycle_step_definition_seeder_spec.rb +++ b/spec/seeders/basic_data/life_cycle_step_definition_seeder_spec.rb @@ -68,7 +68,7 @@ RSpec.describe BasicData::LifeCycleStepDefinitionSeeder do end it "creates the corresponding life cycles with the given attributes" do - expect(Project::LifeCycleStepDefinition.count).to eq(5) + expect(Project::PhaseDefinition.count).to eq(5) expect(Project::StageDefinition.find_by(name: "Initiating")).to have_attributes( color: have_attributes(name: "PM2 Orange") ) @@ -87,7 +87,7 @@ RSpec.describe BasicData::LifeCycleStepDefinitionSeeder do end it "references the life cycles in the seed data" do - Project::LifeCycleStepDefinition.find_each do |expected_stage| + Project::PhaseDefinition.find_each do |expected_stage| reference = :"default_life_cycle_#{expected_stage.name.downcase.gsub(/\s+/, '_')}" expect(seed_data.find_reference(reference)).to eq(expected_stage) end @@ -126,7 +126,7 @@ RSpec.describe BasicData::LifeCycleStepDefinitionSeeder do end it "creates no life cycles" do - expect(Project::LifeCycleStepDefinition.count).to eq(0) + expect(Project::PhaseDefinition.count).to eq(0) end end end diff --git a/spec/support/components/projects/project_life_cycles/edit_dialog.rb b/spec/support/components/projects/project_life_cycles/edit_dialog.rb index fc6e218d02f..73f12bac816 100644 --- a/spec/support/components/projects/project_life_cycles/edit_dialog.rb +++ b/spec/support/components/projects/project_life_cycles/edit_dialog.rb @@ -59,7 +59,7 @@ module Components end datepicker.open( - "input[id^='project_available_life_cycle_steps_attributes_#{step.position - 1}']" + "input[id^='project_available_phases_attributes_#{step.position - 1}']" ) Array(value).each do |date| @@ -104,7 +104,7 @@ module Components expect(page).to have_field( label, with: value, - name: "project[available_life_cycle_steps_attributes][#{position - 1}][#{field}]" + name: "project[available_phases_attributes][#{position - 1}][#{field}]" ) end end @@ -144,7 +144,7 @@ module Components def expect_selector_for(step, selector:, text: nil, present: true) within_async_content do field = step.is_a?(Project::Stage) ? :date_range : :date - input_id = "#project_available_life_cycle_steps_attributes_#{step.position - 1}_#{field}" + input_id = "#project_available_phases_attributes_#{step.position - 1}_#{field}" parent = find(input_id).ancestor("primer-datepicker-field") if present diff --git a/spec/support/shared/project_life_cycle_helpers.rb b/spec/support/shared/project_life_cycle_helpers.rb deleted file mode 100644 index 44e8ecca80c..00000000000 --- a/spec/support/shared/project_life_cycle_helpers.rb +++ /dev/null @@ -1,72 +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. -#++ - -RSpec.shared_examples_for "a Project::LifeCycleStepDefinition event" do - it "inherits from LifeCycleStepDefinition" do - expect(described_class).to be < Project::LifeCycleStepDefinition - end - - describe "associations" do - it { is_expected.to have_many(:life_cycle_steps).inverse_of(:definition).dependent(:destroy) } - it { is_expected.to have_many(:projects).through(:life_cycle_steps) } - it { is_expected.to belong_to(:color).required } - end - - describe "validations" do - it { is_expected.to validate_presence_of(:name) } - - it "is invalid if type is not Project::StageDefinition or Project::GateDefinition" do - life_cycle = described_class.new - life_cycle.type = "InvalidType" - expect(life_cycle).not_to be_valid - expect(life_cycle.errors[:type]) - .to include("must be either Project::StageDefinition or Project::GateDefinition") - end - end -end - -RSpec.shared_examples_for "a Project::LifeCycleStep event" do - it "inherits from Project::LifeCycleStep" do - expect(described_class).to be < Project::LifeCycleStep - end - - describe "associations" do - it { is_expected.to belong_to(:project).required } - it { is_expected.to belong_to(:definition).required } - it { is_expected.to have_many(:work_packages) } - end - - describe "validations" do - it "is invalid if type is not Project::Stage or Project::Gate" do - life_cycle = described_class.new - life_cycle.type = "InvalidType" - expect(life_cycle).not_to be_valid - expect(life_cycle.errors[:type]).to include("must be either Project::Stage or Project::Gate") - end - end -end