diff --git a/app/components/projects/hover_card_component.html.erb b/app/components/projects/hover_card_component.html.erb
new file mode 100644
index 000000000000..e0d6abff7350
--- /dev/null
+++ b/app/components/projects/hover_card_component.html.erb
@@ -0,0 +1,65 @@
+<%#-- 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, see .
+
+See COPYRIGHT and LICENSE files for more details.
+
+++#%>
+
+<%=
+ flex_layout(classes: "op-project-hover-card", data: { test_selector: "project-hover-card-#{@id}" }) do |flex|
+ if breadcrumb_items.present?
+ flex.with_row(mb: 1) do
+ render(Primer::Beta::Breadcrumbs.new(data: { test_selector: "hierarchy-#{@id}" })) do |breadcrumbs|
+ breadcrumb_items.each_with_index do |item, i|
+ breadcrumbs.with_item(href: item[:href], font_size: :small, font_weight: i == breadcrumb_items.size - 1 ? :bold : nil) do
+ render(Primer::Beta::Truncate.new) do |truncate|
+ truncate.with_item(max_width: 135) { item[:text] }
+ end
+ end
+ end
+ end
+ end
+ end
+
+ flex.with_row(mb: 1) do
+ render(Primer::Beta::Text.new(tag: :h3, font_size: 5)) { @name }
+ end
+
+ flex.with_row(flex_layout: true, align_items: :center) do |row|
+ row.with_column(mr: 2) do
+ render(Projects::WorkspaceLabelComponent.new(project: @project, data: { test_selector: "workspace-label-#{@id}" }))
+ end
+
+ if @project.archived?
+ row.with_column(mr: 2) do
+ render(Primer::Beta::Label.new(scheme: :secondary)) { t("project.archive.archived") }
+ end
+ end
+
+ row.with_column do
+ render(Projects::StatusBadgeComponent.new(project: @project, size: :large, with_icon: true, data: { test_selector: "status-badge-#{@id}" }))
+ end
+ end
+ end
+%>
diff --git a/app/components/projects/hover_card_component.rb b/app/components/projects/hover_card_component.rb
new file mode 100644
index 000000000000..5f0fa143a63e
--- /dev/null
+++ b/app/components/projects/hover_card_component.rb
@@ -0,0 +1,54 @@
+# 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, see .
+#
+# See COPYRIGHT and LICENSE files for more details.
+#++
+
+module Projects
+ class HoverCardComponent < ApplicationComponent
+ include OpPrimer::ComponentHelpers
+
+ attr_reader :project
+
+ def initialize(project:, user:)
+ super
+
+ @project = project
+ @id = project.id
+ @name = project.name
+ @user = user
+ end
+
+ def breadcrumb_items
+ ancestors = project.ancestors.visible.active.reorder(:lft)
+ return nil if ancestors.empty?
+
+ (ancestors + [project]).map do |item|
+ { href: project_overview_path(item), text: item.name, selected: item == project }
+ end
+ end
+ end
+end
diff --git a/app/components/projects/info_line_component.html.erb b/app/components/projects/info_line_component.html.erb
index 3996f7534d0a..03ea144c02ff 100644
--- a/app/components/projects/info_line_component.html.erb
+++ b/app/components/projects/info_line_component.html.erb
@@ -28,24 +28,23 @@ See COPYRIGHT and LICENSE files for more details.
<%=
flex_layout(**@system_arguments) do |flex|
- flex.with_column do
- if project.archived?
- render(Primer::Beta::Text.new(font_weight: :bold)) { project.name }
+ flex.with_column(mr: 2) do
+ if @project.archived?
+ render(Primer::Beta::Text.new(font_weight: :bold)) { @project.name }
else
- render(Primer::Beta::Link.new(href: project_path(project), font_weight: :bold)) { project.name }
+ render(Primer::Beta::Link.new(href: project_path(@project), font_weight: :bold)) { @project.name }
end
end
if @show_workspace_type
- flex.with_column(ml: 2, display: :flex, align_items: :center, test_selector: "project-info-line-workspace-type") do
- render(Primer::Beta::Octicon.new(icon: workspace_type_icon, size: :xsmall, color: :muted, mr: 1)) +
- render(Primer::Beta::Text.new(color: :muted, font_size: :small)) { workspace_type_label }
+ flex.with_column(mr: 2) do
+ render(Projects::WorkspaceLabelComponent.new(project: @project))
end
end
- if project.archived?
- flex.with_column(ml: 2) do
- render(Primer::Beta::Label.new) { t("project.archive.archived") }
+ if @project.archived?
+ flex.with_column do
+ render(Primer::Beta::Label.new(scheme: :secondary)) { t("project.archive.archived") }
end
end
end
diff --git a/app/components/projects/info_line_component.rb b/app/components/projects/info_line_component.rb
index 3a27b77726e4..c8049ebeb9ca 100644
--- a/app/components/projects/info_line_component.rb
+++ b/app/components/projects/info_line_component.rb
@@ -40,12 +40,4 @@ def initialize(project:, show_workspace_type: false, **system_arguments)
@system_arguments[:align_items] ||= :center
@system_arguments[:flex_wrap] ||= :wrap
end
-
- private
-
- attr_reader :project
-
- def workspace_type_label = I18n.t(:"label_#{project.workspace_type}")
-
- def workspace_type_icon = helpers.workspace_icon(project.workspace_type)
end
diff --git a/app/components/projects/status_badge_component.html.erb b/app/components/projects/status_badge_component.html.erb
index de00db3a79a7..19159bc0be78 100644
--- a/app/components/projects/status_badge_component.html.erb
+++ b/app/components/projects/status_badge_component.html.erb
@@ -1 +1,14 @@
-<%= render(Primer::Beta::Label.new(size: :medium, inline: true, **@system_arguments)) { name } %>
+<%=
+ render(Primer::Beta::Label.new(size: :medium, **@system_arguments)) do
+ render(Primer::OpenProject::FlexLayout.new(align_items: :center)) do |flex|
+ if @with_icon
+ flex.with_column(mr: 1) do
+ render(Primer::Beta::Octicon.new(icon: @status.icon))
+ end
+ end
+ flex.with_column do
+ render(Primer::Beta::Text.new(font_size: 5, font_weight: :bold)) { name }
+ end
+ end
+ end
+%>
diff --git a/app/components/projects/status_badge_component.rb b/app/components/projects/status_badge_component.rb
index bc28eecb3737..1ff45c4361f9 100644
--- a/app/components/projects/status_badge_component.rb
+++ b/app/components/projects/status_badge_component.rb
@@ -31,10 +31,11 @@ class Projects::StatusBadgeComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include Primer::ClassNameHelper
- def initialize(project:, **system_arguments)
+ def initialize(project:, with_icon: false, **system_arguments)
super
@project = project
+ @with_icon = with_icon
@status = find_status(project.status_code)
@system_arguments = system_arguments
end
diff --git a/app/components/projects/workspace_label_component.html.erb b/app/components/projects/workspace_label_component.html.erb
new file mode 100644
index 000000000000..1580a5c7e55f
--- /dev/null
+++ b/app/components/projects/workspace_label_component.html.erb
@@ -0,0 +1,11 @@
+<%=
+ flex_layout(align_items: :center, **@system_arguments) do |container|
+ container.with_column(mr: 1) do
+ render(Primer::Beta::Octicon.new(icon: icon_name, color: :muted))
+ end
+
+ container.with_column do
+ render(Primer::Beta::Text.new(color: :muted, font_size: :small)) { label }
+ end
+ end
+%>
diff --git a/app/components/projects/workspace_label_component.rb b/app/components/projects/workspace_label_component.rb
new file mode 100644
index 000000000000..f92fb3297025
--- /dev/null
+++ b/app/components/projects/workspace_label_component.rb
@@ -0,0 +1,47 @@
+# 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, see .
+#
+# See COPYRIGHT and LICENSE files for more details.
+#++
+
+class Projects::WorkspaceLabelComponent < ApplicationComponent
+ include OpPrimer::ComponentHelpers
+
+ def initialize(project:, **system_arguments)
+ super
+
+ @project = project
+ @system_arguments = system_arguments
+ end
+
+ private
+
+ attr_reader :project
+
+ def label = I18n.t(:"label_#{project.workspace_type}")
+
+ def icon_name = helpers.workspace_icon(project.workspace_type)
+end
diff --git a/app/views/projects/hover_card/show.html.erb b/app/views/projects/hover_card/show.html.erb
new file mode 100644
index 000000000000..a5a7cc1d1d16
--- /dev/null
+++ b/app/views/projects/hover_card/show.html.erb
@@ -0,0 +1,3 @@
+
+ <%= render Projects::HoverCardComponent.new(id: @id) %>
+
diff --git a/frontend/src/global_styles/primer/_overrides.sass b/frontend/src/global_styles/primer/_overrides.sass
index 3a11cce6c2c4..0b6a8fae8306 100644
--- a/frontend/src/global_styles/primer/_overrides.sass
+++ b/frontend/src/global_styles/primer/_overrides.sass
@@ -52,6 +52,7 @@
page-header,
sub-header,
.op-work-package-details-tab-component,
+.op-project-hover-card,
.UnderlineNav,
.tabnav,
.Box-header,
diff --git a/lookbook/previews/open_project/projects/hover_card_component_preview.rb b/lookbook/previews/open_project/projects/hover_card_component_preview.rb
new file mode 100644
index 000000000000..2297a82e4b46
--- /dev/null
+++ b/lookbook/previews/open_project/projects/hover_card_component_preview.rb
@@ -0,0 +1,59 @@
+# 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, see .
+#
+# See COPYRIGHT and LICENSE files for more details.
+#++
+
+module OpenProject::Projects
+ # @logical_path OpenProject/Projects
+ class HoverCardComponentPreview < ViewComponent::Preview
+ SCOPES = %i[visible active].freeze
+
+ def no_hierarchy
+ user = FactoryBot.build_stubbed(:admin)
+ project = FactoryBot.build_stubbed(:project, :with_status)
+ render(::Projects::HoverCardComponent.new(project:, user:))
+ end
+
+ def with_hierarchy
+ user = FactoryBot.build_stubbed(:admin)
+ root = FactoryBot.build_stubbed(:project, name: "Arcanos Flight Planning")
+ child = FactoryBot.build_stubbed(:project, name: "Mobile app", parent: root)
+ leaf = FactoryBot.build_stubbed(:project, active: false, name: "Data sources", parent: child, status_code: 1)
+
+ chain = [root, child, leaf]
+
+ chain.each_with_index do |project, i|
+ ancestors = chain.first(i)
+ SCOPES.each { |scope| ancestors.define_singleton_method(scope) { |*| self } }
+ ancestors.define_singleton_method(:reorder) { |*| self }
+ project.define_singleton_method(:ancestors) { ancestors }
+ end
+
+ render(::Projects::HoverCardComponent.new(project: leaf, user:))
+ end
+ end
+end
diff --git a/lookbook/previews/open_project/projects/status_badge_component_preview.rb b/lookbook/previews/open_project/projects/status_badge_component_preview.rb
new file mode 100644
index 000000000000..a767f41f070a
--- /dev/null
+++ b/lookbook/previews/open_project/projects/status_badge_component_preview.rb
@@ -0,0 +1,47 @@
+# 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, see .
+#
+# See COPYRIGHT and LICENSE files for more details.
+#++
+
+module OpenProject::Projects
+ # @logical_path OpenProject/Projects
+ class StatusBadgeComponentPreview < ViewComponent::Preview
+ # @label Playground
+ # @param with_icon [boolean]
+ def playground(with_icon: false)
+ project = FactoryBot.build_stubbed(:project, :with_status)
+
+ render(Projects::StatusBadgeComponent.new(project:, with_icon:))
+ end
+
+ def with_icon(with_icon: true)
+ project = FactoryBot.build_stubbed(:project, :with_status)
+
+ render(Projects::StatusBadgeComponent.new(project:, with_icon:))
+ end
+ end
+end
diff --git a/spec/components/projects/hover_card_component_spec.rb b/spec/components/projects/hover_card_component_spec.rb
new file mode 100644
index 000000000000..a0e096f0dbbe
--- /dev/null
+++ b/spec/components/projects/hover_card_component_spec.rb
@@ -0,0 +1,176 @@
+# 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, see .
+#
+# See COPYRIGHT and LICENSE files for more details.
+#++
+
+require "rails_helper"
+
+RSpec.describe Projects::HoverCardComponent, type: :component do
+ include Rails.application.routes.url_helpers
+
+ shared_let(:grandparent) { create(:project, name: "Grandparent") }
+ shared_let(:parent) { create(:project, name: "Parent", parent: grandparent) }
+ shared_let(:project) { create(:project, name: "Leaf project", parent:) }
+ shared_let(:standalone_project) { create(:project, name: "Standalone project") }
+
+ let(:current_user) do
+ create(:user,
+ member_with_permissions: {
+ grandparent => [:view_project],
+ parent => [:view_project],
+ project => [:view_project],
+ standalone_project => [:view_project]
+ })
+ end
+ let(:displayed_project) { project }
+
+ subject { described_class.new(project: displayed_project, user: current_user) }
+
+ before do
+ login_as(current_user)
+ render_inline(subject)
+ end
+
+ it "renders successfully" do
+ expect(page).to have_test_selector("project-hover-card-#{project.id}")
+ expect(page).to have_css(".op-project-hover-card")
+ end
+
+ it "renders the project name as a heading" do
+ expect(page).to have_css("h3", text: "Leaf project")
+ end
+
+ it "renders the workspace label" do
+ expect(page).to have_test_selector("workspace-label-#{project.id}")
+ end
+
+ context "when displaying the status badge" do
+ let(:displayed_project) { create(:project, status_code: "on_track") }
+
+ it "renders the project status badge" do
+ expect(page).to have_test_selector("status-badge-#{displayed_project.id}")
+ end
+ end
+
+ context "when the project is active" do
+ it "does not show the archived label" do
+ expect(page).to have_no_text(I18n.t("project.archive.archived"))
+ end
+ end
+
+ context "when the project is archived" do
+ let(:displayed_project) { create(:project, active: false) }
+
+ it "shows the archived label" do
+ expect(page).to have_text(I18n.t("project.archive.archived"))
+ end
+ end
+
+ context "when displaying the hierarchy" do
+ context "when the project has no ancestors" do
+ let(:displayed_project) { standalone_project }
+
+ it "renders no breadcrumbs" do
+ expect(page).to have_no_test_selector("hierarchy-#{project.id}")
+ end
+ end
+
+ context "when the project has visible ancestors" do
+ it "renders the ancestors followed by the project, in hierarchy order" do
+ hierarchy = find_test_selector("hierarchy-#{project.id}")
+
+ expect(hierarchy).to have_text("Grandparent")
+ expect(hierarchy).to have_text("Parent")
+ expect(hierarchy).to have_text("Leaf project")
+ end
+
+ it "links each item to the project overview" do
+ expect(page).to have_link("Grandparent", href: project_overview_path(grandparent))
+ expect(page).to have_link("Parent", href: project_overview_path(parent))
+ expect(page).to have_link("Leaf project", href: project_overview_path(project))
+ end
+ end
+
+ context "when an ancestor is not visible to the user" do
+ let(:current_user) do
+ create(:user, member_with_permissions: { project => [:view_project], parent => [:view_project] })
+ end
+
+ it "omits the hidden ancestor" do
+ expect(page).to have_no_text("Grandparent")
+ expect(page).to have_text("Parent")
+ end
+ end
+
+ context "when no ancestor is visible to the user" do
+ let(:current_user) do
+ create(:user, member_with_permissions: { project => [:view_project] })
+ end
+
+ it "renders no breadcrumbs and does not leak ancestor names" do
+ expect(page).to have_no_css(".breadcrumb-item")
+ expect(page).to have_no_text("Parent")
+ expect(page).to have_no_text("Grandparent")
+ end
+ end
+
+ context "when an ancestor is archived" do
+ before do
+ parent.update_column(:active, false)
+ render_inline(described_class.new(project:, user: current_user))
+ end
+
+ it "omits the archived ancestor" do
+ expect(page).to have_no_text("Parent")
+ end
+ end
+ end
+
+ describe "#breadcrumb_items" do
+ let(:items) { subject.breadcrumb_items }
+
+ context "without ancestors" do
+ let(:displayed_project) { standalone_project }
+
+ it "returns nil" do
+ expect(items).to be_nil
+ end
+ end
+
+ context "with visible ancestors" do
+ it "returns hashes with href, text and selected" do
+ expect(items).to eq(
+ [
+ { href: project_overview_path(grandparent), text: "Grandparent", selected: false },
+ { href: project_overview_path(parent), text: "Parent", selected: false },
+ { href: project_overview_path(project), text: "Leaf project", selected: true }
+ ]
+ )
+ end
+ end
+ end
+end
diff --git a/spec/components/projects/status_badge_component_spec.rb b/spec/components/projects/status_badge_component_spec.rb
index a64595f1a0eb..2c11432bfa0f 100644
--- a/spec/components/projects/status_badge_component_spec.rb
+++ b/spec/components/projects/status_badge_component_spec.rb
@@ -45,6 +45,16 @@ def render_component(...)
it "renders nothing" do
expect(rendered_component.to_s).to be_empty
end
+
+ context "and with_icon is true" do
+ subject(:rendered_component) do
+ render_component(project:, with_icon: true)
+ end
+
+ it "still renders nothing" do
+ expect(rendered_component.to_s).to be_empty
+ end
+ end
end
context "when status code is set" do
@@ -53,5 +63,23 @@ def render_component(...)
it "renders a label" do
expect(rendered_component).to have_primer_label "Off track"
end
+
+ it "does not render an icon by default" do
+ expect(rendered_component).to have_no_css "svg.octicon"
+ end
+
+ context "with with_icon: true" do
+ subject(:rendered_component) do
+ render_component(project:, with_icon: true)
+ end
+
+ it "renders the label text" do
+ expect(rendered_component).to have_primer_label "Off track"
+ end
+
+ it "renders an icon inside the label" do
+ expect(rendered_component).to have_css ".Label svg.octicon"
+ end
+ end
end
end
diff --git a/spec/components/projects/workspace_label_component_spec.rb b/spec/components/projects/workspace_label_component_spec.rb
new file mode 100644
index 000000000000..54834a3e5942
--- /dev/null
+++ b/spec/components/projects/workspace_label_component_spec.rb
@@ -0,0 +1,81 @@
+# 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, see .
+#
+# See COPYRIGHT and LICENSE files for more details.
+#++
+
+require "rails_helper"
+
+RSpec.describe Projects::WorkspaceLabelComponent, type: :component do
+ def render_component(...)
+ render_inline(described_class.new(...))
+ end
+
+ let(:project) { build_stubbed(:project, workspace_type:) }
+ let(:workspace_type) { :project }
+
+ subject(:rendered_component) do
+ render_component(project:)
+ end
+
+ context "when the workspace is a project" do
+ let(:workspace_type) { :project }
+
+ it "renders the project label" do
+ expect(rendered_component).to have_text "Project"
+ end
+ end
+
+ context "when the workspace is a program" do
+ let(:workspace_type) { :program }
+
+ it "renders the program label" do
+ expect(rendered_component).to have_text "Program"
+ end
+ end
+
+ context "when the workspace is a portfolio" do
+ let(:workspace_type) { :portfolio }
+
+ it "renders the portfolio label" do
+ expect(rendered_component).to have_text "Portfolio"
+ end
+ end
+
+ it "renders an icon" do
+ expect(rendered_component).to have_css "svg.octicon"
+ end
+
+ context "with system arguments" do
+ subject(:rendered_component) do
+ render_component(project:, classes: "my-custom-class")
+ end
+
+ it "forwards them to the wrapper" do
+ expect(rendered_component).to have_css ".my-custom-class"
+ end
+ end
+end