Files

472 lines
18 KiB
Ruby

# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
RSpec.describe Project, "customizable" do
let!(:section) { create(:project_custom_field_section) }
let!(:bool_custom_field) do
create(:boolean_project_custom_field, project_custom_field_section: section)
end
let!(:text_custom_field) do
create(:text_project_custom_field, project_custom_field_section: section)
end
let!(:list_custom_field) do
create(:list_project_custom_field, project_custom_field_section: section)
end
let(:user) { build_stubbed(:admin) }
before do
allow(User).to receive(:current).and_return user
end
context "when not persisted" do
let(:project) { build(:project) }
describe "#available_custom_fields" do
it "returns all existing project custom fields as available custom fields" do
expect(project.project_custom_field_project_mappings)
.to be_empty
expect(project.project_custom_fields)
.to be_empty
# but:
expect(project.available_custom_fields)
.to contain_exactly(bool_custom_field, text_custom_field, list_custom_field)
end
end
end
context "when persisted" do
let(:project) { create(:project) }
describe "#available_custom_fields" do
it "returns only mapped project custom fields as available custom fields" do
expect(project.project_custom_field_project_mappings)
.to be_empty
expect(project.project_custom_fields)
.to be_empty
# and thus:
expect(project.available_custom_fields)
.to be_empty
project.project_custom_fields << bool_custom_field
expect(project.available_custom_fields)
.to contain_exactly(bool_custom_field)
end
context "with a custom field activated in different projects " \
"and the user has view_project_attributes permission in one of the project " \
"and with a custom field assigned to all projects" do
let(:other_project) { create(:project) }
let!(:project_cf) do
# This custom field is enabled in both project and other_project to test that there is no
# bleeding of enabled custom fields between 2 projects.
create(:project_custom_field_project_mapping, project:).project_custom_field.tap do |pcf|
create(:project_custom_field_project_mapping,
project: other_project,
project_custom_field: pcf)
end
end
let!(:for_all_cf) do
create(:string_project_custom_field, is_for_all: true)
end
let(:user) do
create(:user, member_with_permissions: {
project => [],
other_project => %i(view_project_attributes)
})
end
it "returns available_custom_fields only for the other_project" do
expect(project.available_custom_fields)
.to be_empty
expect(other_project.available_custom_fields)
.to contain_exactly(project_cf, for_all_cf)
end
end
end
describe "#custom_field_values and #custom_value_for" do
it "raises an ArgumentError when a non custom field argument is provided" do
expect { project.custom_value_for("invalid") }
.to raise_error(ArgumentError, /Expected a CustomField, got String/)
end
context "when no custom fields are mapped to this project" do
it "#custom_value_for returns nil" do
expect(project.custom_value_for(text_custom_field))
.to be_nil
expect(project.custom_value_for(bool_custom_field))
.to be_nil
expect(project.custom_value_for(list_custom_field))
.to be_nil
end
it "#custom_field_values returns an empty hash" do
expect(project.custom_field_values)
.to be_empty
end
end
context "when custom fields are mapped to this project" do
before do
project.project_custom_fields << [text_custom_field, bool_custom_field]
end
it "#custom_field_values returns a hash of mapped custom fields with nil values" do
expect(project.custom_value_for(text_custom_field)).to be_present
expect(project.custom_value_for(text_custom_field).value).to be_nil
expect(project.custom_value_for(bool_custom_field)).to be_present
expect(project.custom_value_for(bool_custom_field).value).to be_nil
end
context "when values are set for mapped custom fields" do
before do
project.custom_field_values = {
text_custom_field.id => "foo",
bool_custom_field.id => true
}
end
it "#custom_value_for returns the set custom values" do
expect(project.custom_value_for(text_custom_field).typed_value)
.to eq("foo")
expect(project.custom_value_for(bool_custom_field).typed_value)
.to be_truthy
expect(project.custom_value_for(list_custom_field))
.to be_nil
end
it "#custom_field_values returns a hash of mapped custom fields with their set values" do
expect(project.typed_custom_value_for(text_custom_field))
.to eq("foo")
expect(project.typed_custom_value_for(bool_custom_field))
.to be_truthy
end
end
end
end
end
context "when creating with custom field values" do
let(:project) do
create(:project, custom_field_values: {
text_custom_field.id => "foo",
bool_custom_field.id => true
})
end
it "saves the custom field values properly" do
expect(project.custom_value_for(text_custom_field).typed_value)
.to eq("foo")
expect(project.custom_value_for(bool_custom_field).typed_value)
.to be_truthy
end
it "enables fields with provided values and disables fields with none" do
# list_custom_field is not provided, thus it should not be enabled
expect(project.project_custom_field_project_mappings.pluck(:custom_field_id))
.to contain_exactly(text_custom_field.id, bool_custom_field.id)
expect(project.project_custom_fields)
.to contain_exactly(text_custom_field, bool_custom_field)
end
describe "#valid?", with_ee: %i[calculated_values] do
let(:another_section) { create(:project_custom_field_section) }
let(:project) do
build(:project, custom_field_values: {
text_custom_field.id => "foo",
bool_custom_field.id => true
})
end
context "with required text custom field" do
let!(:required_text_custom_field) do
create(:text_project_custom_field,
is_required: true,
project_custom_field_section: another_section)
end
it "validates all custom values regardless of the custom field values provided" do
expect(project).not_to be_valid(:saving_custom_fields)
expect(project.errors.details)
.to eq({ required_text_custom_field.attribute_getter => [{ error: :blank }] })
expect { project.save!(context: :saving_custom_fields) }.to raise_error(ActiveRecord::RecordInvalid)
end
end
context "with required calculated custom field" do
let!(:calculated_custom_field) do
create(:calculated_value_project_custom_field,
is_required: true,
project_custom_field_section: another_section)
end
it "does not validate calculated custom fields" do
expect(project).to be_valid(:saving_custom_fields)
end
end
end
end
context "when updating with custom field values" do
let!(:project) { create(:project) }
shared_examples "implicitly enabled and saved custom values" do
it "enables fields with provided values" do
# list_custom_field is not provided, thus it should not be enabled
expect(project.project_custom_field_project_mappings.pluck(:custom_field_id))
.to contain_exactly(text_custom_field.id, bool_custom_field.id)
expect(project.project_custom_fields)
.to contain_exactly(text_custom_field, bool_custom_field)
end
it "saves the custom field values properly" do
expect(project.custom_value_for(text_custom_field).typed_value)
.to eq("foo")
expect(project.custom_value_for(bool_custom_field).typed_value)
.to be_truthy
end
end
context "with #update method" do
before do
project.update(custom_field_values: {
text_custom_field.id => "foo",
bool_custom_field.id => true
})
end
it_behaves_like "implicitly enabled and saved custom values"
end
context "with #update! method" do
before do
project.update!(custom_field_values: {
text_custom_field.id => "foo",
bool_custom_field.id => true
})
end
it_behaves_like "implicitly enabled and saved custom values"
end
context "with #custom_field_values= method" do
before do
project.custom_field_values = {
text_custom_field.id => "foo",
bool_custom_field.id => true
}
project.save!(context: :saving_custom_fields)
end
it_behaves_like "implicitly enabled and saved custom values"
end
it "does not re-enable fields without new value which have been disabled in the past (regression)" do
project.update!(custom_field_values: {
text_custom_field.id => "foo",
bool_custom_field.id => true
})
expect(project.reload.project_custom_fields)
.to contain_exactly(text_custom_field, bool_custom_field)
project.project_custom_field_project_mappings.find_by(custom_field_id: text_custom_field.id).destroy
expect(project.reload.project_custom_fields)
.to contain_exactly(bool_custom_field)
project.update!(custom_field_values: {
bool_custom_field.id => true
})
expect(project.reload.project_custom_fields)
.to contain_exactly(bool_custom_field)
end
context "with correct handling of custom fields with default values" do
let!(:text_custom_field_with_default) do
create(:text_project_custom_field,
default_value: "default",
project_custom_field_section: section)
end
it "does not activate custom fields with default values if not explicitly set to a value" do
project.update!(custom_field_values: {
text_custom_field.id => "bar",
bool_custom_field.id => false
})
# text_custom_field_with_default is not provided, thus it should not be enabled (in contrast to creation)
expect(project.project_custom_field_project_mappings.pluck(:custom_field_id))
.to contain_exactly(text_custom_field.id, bool_custom_field.id)
end
it "does activate custom fields with default values if explicitly set to a value" do
project.update!(custom_field_values: {
text_custom_field.id => "bar",
bool_custom_field.id => false,
text_custom_field_with_default.id => "overwritten default"
})
# text_custom_field_with_default is not provided, thus it should not be enabled (in contrast to creation)
expect(project.project_custom_field_project_mappings.pluck(:custom_field_id))
.to contain_exactly(text_custom_field.id, bool_custom_field.id, text_custom_field_with_default.id)
end
end
it "does re-enable fields with new value which have been disabled in the past" do
project.update!(custom_field_values: {
text_custom_field.id => "foo",
bool_custom_field.id => true
})
expect(project.reload.project_custom_fields)
.to contain_exactly(text_custom_field, bool_custom_field)
project.project_custom_field_project_mappings.find_by(custom_field_id: text_custom_field.id).destroy
expect(project.reload.project_custom_fields)
.to contain_exactly(bool_custom_field)
project.update!(custom_field_values: {
text_custom_field.id => "bar"
})
expect(project.reload.project_custom_fields)
.to contain_exactly(text_custom_field, bool_custom_field)
expect(project.custom_value_for(text_custom_field).typed_value)
.to eq("bar")
end
describe "#valid?", with_ee: %i[calculated_values] do
let!(:project) do
create(:project, custom_field_values: {
text_custom_field.id => "foo",
bool_custom_field.id => true
})
end
let(:another_section) { create(:project_custom_field_section) }
let!(:required_text_custom_field) do
create(:text_project_custom_field,
is_for_all: true,
is_required: true,
project_custom_field_section: another_section)
end
let!(:required_calculated_custom_field) do
create(:calculated_value_project_custom_field,
is_for_all: true,
is_required: true,
project_custom_field_section: another_section)
end
it "validates only the user provided custom values" do
# By default no custom fields are validated during the update.
expect(project).to be_valid(:saving_custom_fields)
# Passing the new required custom field to the custom_values_to_validate
# is also required for the validations to kick in
project.custom_values_to_validate = project.custom_field_values
# thus, the project becomes invalid
expect(project.reload).not_to be_valid(:saving_custom_fields)
expect { project.save!(context: :saving_custom_fields) }.to raise_error(ActiveRecord::RecordInvalid)
# only the required_text_custom_field fails validation,
# the calculated_custom_field is not validated at all.
expect(project.errors.details)
.to eq({ required_text_custom_field.attribute_getter => [{ error: :blank }] })
# but we still want to allow updating forms without the invalid required custom field values.
# This is possible to do by omitting the required custom field from the list to be validated.
project.custom_values_to_validate =
project.custom_values.reject { |v| v.custom_field == required_text_custom_field }
expect(project).to be_valid(:saving_custom_fields)
expect { project.save!(context: :saving_custom_fields) }.not_to raise_error
# Resetting the custom_values_to_validate should validate all the custom fields again
project.custom_values_to_validate = project.custom_field_values
expect { project.save!(context: :saving_custom_fields) }.to raise_error(ActiveRecord::RecordInvalid)
end
end
end
context "when updating with custom field setter methods (API approach)" do
let(:project) { create(:project) }
shared_examples "implicitly enabled and saved custom values" do
it "enables fields with provided values" do
# list_custom_field is not provided, thus it should not be enabled
expect(project.project_custom_field_project_mappings.pluck(:custom_field_id))
.to contain_exactly(text_custom_field.id, bool_custom_field.id)
expect(project.project_custom_fields)
.to contain_exactly(text_custom_field, bool_custom_field)
end
it "saves the custom field values properly" do
expect(project.custom_value_for(text_custom_field).typed_value)
.to eq("foo")
expect(project.custom_value_for(bool_custom_field).typed_value)
.to be_truthy
# or via getter methods:
expect(project.send(:"custom_field_#{text_custom_field.id}")).to eq("foo")
expect(project.send(:"custom_field_#{bool_custom_field.id}")).to be_truthy
end
end
context "when setting a value for a disabled custom field" do
before do
project.send(:"custom_field_#{text_custom_field.id}=", "foo")
project.send(:"custom_field_#{bool_custom_field.id}=", true)
project.save!(context: :saving_custom_fields)
end
it_behaves_like "implicitly enabled and saved custom values"
end
end
end