mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
659 lines
22 KiB
Ruby
659 lines
22 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 "Projects", "creation", :js do
|
|
shared_let(:project_custom_field_section) { create(:project_custom_field_section, name: "Section A") }
|
|
|
|
current_user { create(:admin) }
|
|
|
|
shared_let(:project) { create(:project, name: "Foo project", identifier: "foo-project") }
|
|
|
|
let(:projects_page) { Pages::Projects::Index.new }
|
|
|
|
before do
|
|
projects_page.visit!
|
|
end
|
|
|
|
context "with the button on the toolbar items" do
|
|
it "can navigate to the create project page" do
|
|
projects_page.create_new_workspace :project, open_menu: true
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
expect(page).to have_current_path(new_project_path)
|
|
end
|
|
end
|
|
|
|
it "can create a project" do
|
|
projects_page.create_new_workspace :project, open_menu: true
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
# Step 1: Select workspace type (blank project)
|
|
click_on "Continue"
|
|
|
|
# Step 2: Fill in project details
|
|
expect(page).to have_text("2 of 2")
|
|
fill_in "Name", with: "Foo bar"
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
|
|
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
|
expect(page).to have_content "Foo bar"
|
|
end
|
|
|
|
it "redirects to the parent project page when users cancels while creating subproject" do
|
|
# Go to parent project page
|
|
visit project_overview_path(project.id)
|
|
|
|
# Start creating a subproject from parent context
|
|
page.find_test_selector("quick-add-menu-button").click
|
|
page.find_test_selector("quick-add-menu-item", text: "Project", wait: 5).click
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
click_on "Cancel"
|
|
|
|
expect(page).to have_current_path project_overview_path(project.id)
|
|
end
|
|
|
|
it "redirects to projects#index when users cancels" do
|
|
visit new_project_path
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
click_on "Cancel"
|
|
expect(page).to have_current_path projects_path
|
|
end
|
|
|
|
it "redirects to the parent project page when users press the close icon while creating subproject" do
|
|
# Go to parent project page
|
|
visit project_overview_path(project.id)
|
|
|
|
# Start creating a subproject from parent context
|
|
page.find_test_selector("quick-add-menu-button").click
|
|
page.find_test_selector("quick-add-menu-item", text: "Project", wait: 5).click
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
# Click the close (X) icon in the header
|
|
find_test_selector("new_project_form_close_icon").click
|
|
|
|
expect(page).to have_current_path project_overview_path(project.id)
|
|
end
|
|
|
|
it "redirects to projects#index when users click on close icon" do
|
|
visit new_project_path
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
find_test_selector("new_project_form_close_icon").click
|
|
expect(page).to have_current_path projects_path
|
|
end
|
|
|
|
it "does not create a project with an already existing identifier" do
|
|
projects_page.create_new_workspace :project, open_menu: true
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
# Step 1: Select workspace type (blank project)
|
|
click_on "Continue"
|
|
|
|
# Step 2: Fill in project details
|
|
fill_in "Name", with: "Foo project"
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
|
|
expect(page).to have_current_path /\/projects\/foo-project-1\/?/
|
|
|
|
project = Project.last
|
|
expect(project.identifier).to eq "foo-project-1"
|
|
end
|
|
|
|
it "does not create a project when the name is not present" do
|
|
projects_page.create_new_workspace :project, open_menu: true
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
# Step 1: Select workspace type (blank project)
|
|
click_on "Continue"
|
|
|
|
# Step 2: Try to complete without name
|
|
expect(page).to have_text("2 of 2")
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :error, message: /^Creation failed/
|
|
|
|
expect(page).to have_text("2 of 2")
|
|
expect(page).to have_field "Name", validation_error: "can't be blank."
|
|
end
|
|
|
|
context "with a multi-select list custom field" do
|
|
shared_let(:list_custom_field) do
|
|
create(:list_project_custom_field,
|
|
name: "List CF",
|
|
is_required: true,
|
|
is_for_all: true,
|
|
multi_value: true,
|
|
project_custom_field_section:)
|
|
end
|
|
let(:list_field) do
|
|
FormFields::SelectFormField.new(
|
|
list_custom_field,
|
|
selector: "[data-test-selector='#{list_custom_field.attribute_name(:kebab_case)}'"
|
|
)
|
|
end
|
|
|
|
it "can create a project" do
|
|
projects_page.create_new_workspace :project, open_menu: true
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
# Step 1: Select workspace type (blank project)
|
|
click_on "Continue"
|
|
|
|
# Step 2: Fill in project details
|
|
expect(page).to have_text("2 of 3")
|
|
fill_in "Name", with: "Foo bar"
|
|
click_on "Continue"
|
|
|
|
# Step 3: Fill in custom fields
|
|
expect(page).to have_text("3 of 3")
|
|
expect(page).to have_combo_box "List CF *"
|
|
list_field.select_option "A", "B"
|
|
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
|
|
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
|
expect(page).to have_content "Foo bar"
|
|
|
|
project = Project.last
|
|
expect(project.name).to eq "Foo bar"
|
|
cvs = project.custom_value_for(list_custom_field)
|
|
expect(cvs.count).to eq 2
|
|
expect(cvs.map(&:typed_value)).to contain_exactly "A", "B"
|
|
end
|
|
end
|
|
|
|
context "with a multi-select version custom field" do
|
|
include_context "ng-select-autocomplete helpers"
|
|
|
|
shared_let(:public_project) do
|
|
create(:project, name: "Public Pr", identifier: "public-pr", public: true)
|
|
end
|
|
|
|
shared_let(:versions) do
|
|
[
|
|
create(:version, project:, name: "Ringbo 1.0", sharing: "system"),
|
|
create(:version, project: public_project, name: "Ringbo 2.0", sharing: "system")
|
|
]
|
|
end
|
|
|
|
shared_let(:version_custom_field) do
|
|
create(:version_project_custom_field,
|
|
name: "Version CF",
|
|
is_required: true,
|
|
is_for_all: true,
|
|
multi_value: true,
|
|
project_custom_field_section:)
|
|
end
|
|
|
|
let(:version_field) do
|
|
FormFields::SelectFormField.new(
|
|
version_custom_field,
|
|
selector: "[data-test-selector='#{version_custom_field.attribute_name(:kebab_case)}'"
|
|
)
|
|
end
|
|
|
|
it "can create a project" do
|
|
projects_page.create_new_workspace :project, open_menu: true
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
# Step 1: Select workspace type (blank project)
|
|
click_on "Continue"
|
|
|
|
# Step 2: Fill in project details
|
|
expect(page).to have_text("2 of 3")
|
|
fill_in "Name", with: "Foo bar"
|
|
click_on "Continue"
|
|
|
|
# Step 3: Fill in custom fields
|
|
expect(page).to have_text("3 of 3")
|
|
expect(page).to have_combo_box "Version CF *"
|
|
|
|
# expect the versions are grouped by the project name
|
|
version_field.expect_option(versions.first.name, grouping: project.name)
|
|
version_field.expect_option(versions.last.name, grouping: public_project.name)
|
|
|
|
version_field.select_option(versions.first.name, versions.last.name)
|
|
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
|
|
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
|
expect(page).to have_content "Foo bar"
|
|
|
|
project = Project.last
|
|
expect(project.name).to eq "Foo bar"
|
|
|
|
typed_values = project.custom_value_for(version_custom_field).map(&:typed_value)
|
|
expect(typed_values).to eq versions
|
|
end
|
|
end
|
|
|
|
it "hides the active field and the identifier" do
|
|
visit new_project_path
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
expect(page).to have_no_content "Active"
|
|
expect(page).to have_no_content "Identifier"
|
|
end
|
|
|
|
context "with optional and required custom fields" do
|
|
shared_let(:optional_custom_field) do
|
|
create(:project_custom_field, name: "Optional Foo",
|
|
field_format: "string",
|
|
is_for_all: true,
|
|
project_custom_field_section:)
|
|
end
|
|
shared_let(:required_custom_field) do
|
|
create(:project_custom_field, name: "Required Foo",
|
|
field_format: "string",
|
|
is_for_all: true,
|
|
is_required: true,
|
|
project_custom_field_section:)
|
|
end
|
|
|
|
context "with required custom fields" do
|
|
shared_let(:required_user_custom_field) do
|
|
create(:user_project_custom_field, name: "Required User",
|
|
is_for_all: true,
|
|
is_required: true,
|
|
project_custom_field_section:)
|
|
end
|
|
|
|
shared_let(:required_but_inactive_custom_field) do
|
|
create(:text_project_custom_field,
|
|
name: "Required inactive",
|
|
is_required: true,
|
|
project_custom_field_section:)
|
|
end
|
|
|
|
shared_let(:required_inactive_custom_field_with_default_value) do
|
|
create(:text_project_custom_field,
|
|
name: "Required inactive with default value",
|
|
is_required: true,
|
|
default_value: "foo",
|
|
project_custom_field_section:)
|
|
end
|
|
|
|
it "renders activated required custom fields for new" do
|
|
visit new_project_path
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
# Step 1: Select workspace type (blank project)
|
|
click_on "Continue"
|
|
|
|
# Step 2: Project details - skip to step 3
|
|
expect(page).to have_text("2 of 3")
|
|
fill_in "Name", with: "Test Project"
|
|
click_on "Continue"
|
|
|
|
# Step 3: Custom fields
|
|
expect(page).to have_text("3 of 3")
|
|
expect(page).to have_field "Required Foo *"
|
|
expect(page).to have_field "Required User *"
|
|
|
|
# Optional fields should not be shown
|
|
expect(page).to have_no_field "Optional Foo"
|
|
|
|
# Inactive fields, even if required, should not be shown
|
|
expect(page).to have_no_field "Required Inactive *"
|
|
expect(page).to have_no_field "Required Inactive with default value *"
|
|
end
|
|
end
|
|
|
|
context "with correct validations" do
|
|
before do
|
|
visit new_project_path
|
|
end
|
|
|
|
it "requires the required custom field" do
|
|
expect(page).to have_heading "New project"
|
|
|
|
# Step 1: Select workspace type (blank project)
|
|
click_on "Continue"
|
|
|
|
# Step 2: Fill in name
|
|
expect(page).to have_text("2 of 3")
|
|
fill_in "Name", with: "Test Project"
|
|
click_on "Continue"
|
|
|
|
# Step 3: Try to complete without required custom field
|
|
expect(page).to have_text("3 of 3")
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :error, message: /^Creation failed/
|
|
|
|
expect(page).to have_text("3 of 3")
|
|
expect(page).to have_field "Required Foo *", validation_error: "can't be blank."
|
|
end
|
|
end
|
|
|
|
context "with correct custom field activation" do
|
|
shared_let(:unused_custom_field) do
|
|
create(:project_custom_field, name: "Unused Foo",
|
|
field_format: "string",
|
|
project_custom_field_section:)
|
|
end
|
|
|
|
before do
|
|
visit new_project_path
|
|
|
|
expect(page).to have_heading "New project" # rubocop:disable RSpec/ExpectInHook
|
|
|
|
# Step 1: Select workspace type
|
|
click_on "Continue"
|
|
|
|
# Step 2: Fill in project details
|
|
fill_in "Name", with: "Foo bar"
|
|
click_on "Continue"
|
|
|
|
# Step 3: Fill in required custom field
|
|
fill_in "Required Foo", with: "Required value"
|
|
end
|
|
|
|
it "enables custom fields with provided values and for_all fields for this project" do
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
|
|
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
|
|
|
project = Project.last
|
|
|
|
# unused custom field should not be activated
|
|
expect(project.project_custom_field_ids).to contain_exactly(
|
|
required_custom_field.id, optional_custom_field.id
|
|
)
|
|
end
|
|
|
|
context "with correct handling of default values" do
|
|
shared_let(:custom_field_with_default_value) do
|
|
create(:project_custom_field, name: "Foo with default value",
|
|
field_format: "string",
|
|
is_required: true,
|
|
is_for_all: true,
|
|
default_value: "Default value",
|
|
project_custom_field_section:)
|
|
end
|
|
|
|
it "enables custom fields with default values if not set to blank explicitly" do
|
|
# don't touch the default value
|
|
wait_for_turbo { click_on "Complete" }
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
|
|
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
|
|
|
project = Project.last
|
|
|
|
# custom_field_with_default_value should be activated and contain the default value
|
|
expect(project.project_custom_field_ids).to contain_exactly(
|
|
custom_field_with_default_value.id, required_custom_field.id, optional_custom_field.id
|
|
)
|
|
|
|
expect(project.custom_value_for(custom_field_with_default_value).value).to eq("Default value")
|
|
end
|
|
|
|
it "does enable custom fields with default values if overwritten with a new value" do
|
|
fill_in "Foo with default value", with: "foo"
|
|
|
|
click_on "Complete"
|
|
|
|
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
|
|
project = Project.last
|
|
|
|
# custom_field_with_default_value should be activated and contain the overwritten value
|
|
expect(project.project_custom_field_ids).to contain_exactly(
|
|
custom_field_with_default_value.id, required_custom_field.id, optional_custom_field.id
|
|
)
|
|
|
|
expect(project.custom_value_for(custom_field_with_default_value).value).to eq("foo")
|
|
end
|
|
end
|
|
|
|
context "with correct handling of invisible values" do
|
|
shared_let(:invisible_field) do
|
|
create(:string_project_custom_field, name: "Text for Admins only",
|
|
is_required: true,
|
|
is_for_all: true,
|
|
admin_only: true,
|
|
project_custom_field_section:)
|
|
end
|
|
|
|
context "with an admin user" do
|
|
it "shows invisible fields in the form and allows their activation" do
|
|
expect(page).to have_content "Text for Admins only"
|
|
|
|
fill_in "Text for Admins only", with: "foo"
|
|
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
|
|
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
|
|
|
project = Project.last
|
|
|
|
expect(project.project_custom_field_ids).to contain_exactly(
|
|
required_custom_field.id, optional_custom_field.id, invisible_field.id
|
|
)
|
|
|
|
expect(project.custom_value_for(invisible_field).typed_value).to eq("foo")
|
|
end
|
|
end
|
|
|
|
context "with a non-admin user" do
|
|
current_user { create(:user, global_permissions: %i[add_project]) }
|
|
|
|
it "does not show invisible fields in the form and thus not activates the invisible field" do
|
|
pending "Admin-only project attributes currently prevent users from creating projects (OP#64479)"
|
|
|
|
expect(page).to have_no_content "Text for Admins only"
|
|
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
|
|
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
|
|
|
project = Project.last
|
|
|
|
expect(project.project_custom_field_ids).to contain_exactly(
|
|
required_custom_field.id, optional_custom_field.id
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a required custom field that is not for all projects" do
|
|
shared_let(:required_not_for_all_custom_field) do
|
|
create(:project_custom_field, name: "Required not for all",
|
|
field_format: "string",
|
|
is_required: true,
|
|
is_for_all: false,
|
|
project_custom_field_section:)
|
|
end
|
|
|
|
it "does not show the project attributes step" do
|
|
projects_page.create_new_workspace :project, open_menu: true
|
|
|
|
expect(page).to have_heading "New project"
|
|
|
|
# Step 1: Select workspace type (blank project)
|
|
click_on "Continue"
|
|
|
|
# Step 2: Fill in project details
|
|
# Should show "2 of 2" because the required field is not for all projects
|
|
# The bug causes this to show "2 of 3" incorrectly
|
|
expect(page).to have_text("2 of 2")
|
|
fill_in "Name", with: "Project without step 3"
|
|
|
|
# Should have Complete button (not Continue) since this is the last step
|
|
expect(page).to have_button("Complete")
|
|
expect(page).to have_no_button("Continue")
|
|
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
|
|
expect(page).to have_current_path %r{/projects/project-without-step-3/?}
|
|
expect(page).to have_content "Project without step 3"
|
|
|
|
# The required_not_for_all field should NOT be activated for the new project
|
|
new_project = Project.find_by(name: "Project without step 3")
|
|
expect(new_project.project_custom_field_ids).not_to include(required_not_for_all_custom_field.id)
|
|
end
|
|
end
|
|
|
|
context "with semantic identifiers", with_settings: { work_packages_identifier: "semantic" } do
|
|
it "auto-suggests an identifier when the name field is blurred" do
|
|
projects_page.create_new_workspace :project, open_menu: true
|
|
click_on "Continue"
|
|
|
|
fill_in "Name", with: "Flight Planning Algorithm"
|
|
find("body").click # blur the name field
|
|
|
|
expect(page).to have_field "Identifier", with: "FPA"
|
|
end
|
|
|
|
it "allows overriding the auto-suggested identifier" do
|
|
projects_page.create_new_workspace :project, open_menu: true
|
|
click_on "Continue"
|
|
|
|
fill_in "Name", with: "Flight Planning Algorithm"
|
|
find("body").click
|
|
expect(page).to have_field "Identifier", with: "FPA"
|
|
|
|
fill_in "Identifier", with: "MYIDENT"
|
|
click_on "Complete"
|
|
|
|
expect_and_dismiss_flash type: :success, message: "Successful creation."
|
|
expect(page).to have_current_path %r{/projects/MYIDENT/?}
|
|
end
|
|
|
|
it "shows a validation error for identifiers not starting with a letter" do
|
|
projects_page.create_new_workspace :project, open_menu: true
|
|
click_on "Continue"
|
|
|
|
fill_in "Name", with: "Flight Planning Algorithm"
|
|
find("body").click
|
|
expect(page).to have_field "Identifier", with: "FPA"
|
|
|
|
expect(page).to have_field "Identifier", with: "FPA"
|
|
fill_in "Identifier", with: "3INVALID"
|
|
click_on "Complete"
|
|
|
|
expect(page).to have_text "Identifier must start with a letter"
|
|
end
|
|
end
|
|
|
|
context "with workspace type badges in parent field" do
|
|
include_context "ng-select-autocomplete helpers"
|
|
|
|
shared_let(:portfolio) { create(:portfolio, name: "Parent Portfolio") }
|
|
shared_let(:program) { create(:program, name: "Parent Program") }
|
|
|
|
it "displays workspace type badges for portfolios and programs in the parent field" do
|
|
visit new_project_path
|
|
|
|
# Step 1: Select workspace type (blank project)
|
|
click_on "Continue"
|
|
|
|
# Step 2: Fill in project details
|
|
fill_in "Name", with: "Test Subproject"
|
|
|
|
# Open parent field autocompleter
|
|
expect(page).to have_combo_box "Subproject of"
|
|
parent_autocompleter = page.find("opce-project-autocompleter")
|
|
|
|
# Search for portfolio
|
|
dropdown = search_autocomplete(parent_autocompleter,
|
|
query: "Portfolio",
|
|
results_selector: ".ng-dropdown-panel-items")
|
|
|
|
within(dropdown) do
|
|
expect(page).to have_text("Portfolio")
|
|
expect(page).to have_css("svg.octicon")
|
|
end
|
|
|
|
# Clear and search for program
|
|
parent_autocompleter.find("input").set("")
|
|
|
|
dropdown = search_autocomplete(parent_autocompleter,
|
|
query: "Program",
|
|
results_selector: ".ng-dropdown-panel-items")
|
|
|
|
within(dropdown) do
|
|
expect(page).to have_text("Program")
|
|
expect(page).to have_css("svg.octicon")
|
|
end
|
|
|
|
# Clear and search for regular project - should not have workspace type badge
|
|
parent_autocompleter.find("input").set("")
|
|
|
|
dropdown = search_autocomplete(parent_autocompleter,
|
|
query: "Foo project",
|
|
results_selector: ".ng-dropdown-panel-items")
|
|
|
|
within(dropdown) do
|
|
expect(page).to have_text("Foo project")
|
|
expect(page).to have_no_css("svg.octicon")
|
|
end
|
|
end
|
|
end
|
|
end
|