From 42d0ffdcccc137b87ec50782be7bb2c98832c7ee Mon Sep 17 00:00:00 2001 From: Josh Adam <josh.adam@phac-aspc.gc.ca> Date: Thu, 3 Oct 2024 14:16:54 -0500 Subject: [PATCH] Enhancement: Subgroup projects searching and pagination (#792) * chore: Working on search in subgroups * chore: Working on auto-submit * chore: Just need to figure out flattening on search * chore: This wasn't needed * chore: Working search for subgroups and project * test: This does not need to be there anymore * chore: Fixed Shared groups and projects content * test: Update test for pagination * chore: Fixed flaky test * chore: Fixed test * test: Test searching * chore: Cleaned up methods * feat: Updated with ability to set the icon size * chore: Removed the search from the turbo response --- .../namespace_tree_component.html.erb | 3 +- .../namespace_tree_component.rb | 6 +- .../row/row_contents_component.html.erb | 4 +- .../row/row_contents_component.rb | 5 +- .../row/with_children_component.html.erb | 14 +-- .../row/with_children_component.rb | 3 +- .../row/without_children_component.html.erb | 5 +- .../row/without_children_component.rb | 4 +- .../namespace_tree/row_component.rb | 8 +- .../namespace_tree_container_component.rb | 8 +- .../dashboard/groups_controller.rb | 3 +- .../groups/subgroups_controller.rb | 11 +-- app/controllers/groups_controller.rb | 21 ++++ app/models/namespace.rb | 7 ++ .../dashboard/groups/group.turbo_stream.erb | 3 +- app/views/dashboard/groups/index.html.erb | 2 +- app/views/groups/show.html.erb | 96 +++++++++++-------- app/views/groups/subgroups/_index.html.erb | 20 ++++ config/locales/en.yml | 2 + config/locales/fr.yml | 2 + .../groups/subgroups_controller_test.rb | 6 -- test/system/groups_test.rb | 20 ++-- 22 files changed, 161 insertions(+), 92 deletions(-) create mode 100644 app/views/groups/subgroups/_index.html.erb diff --git a/app/components/namespace_tree/namespace_tree_component.html.erb b/app/components/namespace_tree/namespace_tree_component.html.erb index 9b3379c53f..1755f6e554 100644 --- a/app/components/namespace_tree/namespace_tree_component.html.erb +++ b/app/components/namespace_tree/namespace_tree_component.html.erb @@ -1,4 +1,4 @@ -<ul class="groups-list namespace-list-tree flex flex-col"> +<ul class="flex flex-col groups-list namespace-list-tree"> <%= render NamespaceTree::RowComponent.with_collection( namespaces, path: path, @@ -6,5 +6,6 @@ type: @type, collapsed: collapsed, render_flat_list: render_flat_list, + icon_size: icon_size, ) %> </ul> diff --git a/app/components/namespace_tree/namespace_tree_component.rb b/app/components/namespace_tree/namespace_tree_component.rb index d23779dc82..61a7b9ea66 100644 --- a/app/components/namespace_tree/namespace_tree_component.rb +++ b/app/components/namespace_tree/namespace_tree_component.rb @@ -3,10 +3,11 @@ module NamespaceTree # Component to render a namespace tree class NamespaceTreeComponent < Component - attr_reader :parent, :namespaces, :path, :path_args, :collapsed, :render_flat_list + attr_reader :collapsed, :icon_size, :namespaces, :parent, :path, :path_args, :render_flat_list # rubocop: disable Metrics/ParameterLists - def initialize(namespaces:, type:, parent: nil, path: nil, path_args: {}, render_flat_list: false) + def initialize(namespaces:, type:, parent: nil, path: nil, path_args: {}, render_flat_list: false, + icon_size: :small) @parent = parent @namespaces = namespaces @path = path @@ -14,6 +15,7 @@ def initialize(namespaces:, type:, parent: nil, path: nil, path_args: {}, render @type = type @collapsed = true @render_flat_list = render_flat_list + @icon_size = icon_size end # rubocop: enable Metrics/ParameterLists diff --git a/app/components/namespace_tree/row/row_contents_component.html.erb b/app/components/namespace_tree/row/row_contents_component.html.erb index 7990c26ab5..4fb2695c74 100644 --- a/app/components/namespace_tree/row/row_contents_component.html.erb +++ b/app/components/namespace_tree/row/row_contents_component.html.erb @@ -2,7 +2,7 @@ <%= viral_icon(name: :squares_2x2, classes: "h-5 w-5 text-slate-400 mr-2") %> <%= viral_avatar( name: @namespace.name, - size: :medium, + size: @icon_size, colour_string: "#{@namespace.name}-#{@namespace.id}", data: { turbo: false, @@ -13,7 +13,7 @@ <%= viral_icon(name: :rectangle_stack, classes: "h-5 w-5 text-slate-400 mr-2") %> <%= viral_avatar( name: @namespace.name, - size: :small, + size: @icon_size, colour_string: "#{@namespace.name}-#{@namespace.id}", data: { turbo: false, diff --git a/app/components/namespace_tree/row/row_contents_component.rb b/app/components/namespace_tree/row/row_contents_component.rb index ce7d352313..071600e084 100644 --- a/app/components/namespace_tree/row/row_contents_component.rb +++ b/app/components/namespace_tree/row/row_contents_component.rb @@ -4,13 +4,16 @@ module NamespaceTree module Row # Component for the contents of NamespaceTree row class RowContentsComponent < Viral::Component - def initialize(namespace:, path: nil, path_args: {}, collapsed: false, sample_count: 0) + # rubocop: disable Metrics/ParameterLists + def initialize(namespace:, path: nil, path_args: {}, collapsed: false, sample_count: 0, icon_size: :small) @namespace = namespace @path = path @path_args = path_args @collapsed = collapsed @sample_count = sample_count + @icon_size = icon_size end + # rubocop: enable Metrics/ParameterLists end end end diff --git a/app/components/namespace_tree/row/with_children_component.html.erb b/app/components/namespace_tree/row/with_children_component.html.erb index 83d77bb8a6..063d775fc3 100644 --- a/app/components/namespace_tree/row/with_children_component.html.erb +++ b/app/components/namespace_tree/row/with_children_component.html.erb @@ -6,13 +6,7 @@ > <div class=" - flex - items-center - w-full - py-3 - pr-3 - text-left - namespace-entry-contents + flex items-center w-full py-3 pr-3 text-left namespace-entry-contents " data-action="click->groups--row#toggle" aria-label="<%= t(:'dashboard.groups.index.row_aria_label', name: @namespace.name) %>" @@ -32,7 +26,8 @@ namespace: @namespace, path: @path, path_args: @path_args, - collapsed: @collapsed + collapsed: @collapsed, + icon_size: @icon_size, ) %> </div> <% unless @collapsed %> @@ -41,7 +36,8 @@ namespaces: @children, path: @path, path_args: @path_args, - type: @type + type: @type, + icon_size: @icon_size, ) %> <% end %> </li> diff --git a/app/components/namespace_tree/row/with_children_component.rb b/app/components/namespace_tree/row/with_children_component.rb index c0ece97b79..5a17b70554 100644 --- a/app/components/namespace_tree/row/with_children_component.rb +++ b/app/components/namespace_tree/row/with_children_component.rb @@ -5,13 +5,14 @@ module Row # Component for the contents of a namespace row that has children class WithChildrenComponent < Viral::Component # rubocop:disable Metrics/ParameterLists - def initialize(namespace:, children:, type:, path: nil, path_args: {}, collapsed: false) + def initialize(namespace:, children:, type:, path: nil, path_args: {}, collapsed: false, icon_size: :small) @namespace = namespace @children = children @type = type @path = path @path_args = path_args @collapsed = collapsed + @icon_size = icon_size end # rubocop:enable Metrics/ParameterLists diff --git a/app/components/namespace_tree/row/without_children_component.html.erb b/app/components/namespace_tree/row/without_children_component.html.erb index b8dfe1a3e6..bb124f4d62 100644 --- a/app/components/namespace_tree/row/without_children_component.html.erb +++ b/app/components/namespace_tree/row/without_children_component.html.erb @@ -1,8 +1,8 @@ <li class="namespace-entry <%= @namespace.description.present? ? "has-description" : nil %>" > - <div class="namespace-entry-contents py-3 pr-3 flex items-center"> - <span class="folder-toggle-wrap mr-2 flex items-center"> + <div class="flex items-center py-3 pr-3 namespace-entry-contents"> + <span class="flex items-center mr-2 folder-toggle-wrap"> <%= viral_icon(name: :blank, classes: "h-3 w-3") %> </span> <%= render NamespaceTree::Row::RowContentsComponent.new( @@ -10,6 +10,7 @@ path: @path, path_args: @path_args, sample_count: @sample_count, + icon_size: @icon_size, ) %> </div> </li> diff --git a/app/components/namespace_tree/row/without_children_component.rb b/app/components/namespace_tree/row/without_children_component.rb index b879b4d091..3c468031a6 100644 --- a/app/components/namespace_tree/row/without_children_component.rb +++ b/app/components/namespace_tree/row/without_children_component.rb @@ -4,11 +4,11 @@ module NamespaceTree module Row # Component for the contents of a namespace row that has no children class WithoutChildrenComponent < Viral::Component - def initialize(namespace:, path: nil, path_args: {}) + def initialize(namespace:, path: nil, path_args: {}, icon_size: :small) @namespace = namespace @path = path @path_args = path_args - @sample_count = @namespace.type == 'Project' ? @namespace.project.samples.size : 0 + @icon_size = icon_size end end end diff --git a/app/components/namespace_tree/row_component.rb b/app/components/namespace_tree/row_component.rb index 749230597c..3af6c9fa49 100644 --- a/app/components/namespace_tree/row_component.rb +++ b/app/components/namespace_tree/row_component.rb @@ -7,20 +7,22 @@ class RowComponent < ViewComponent::Base erb_template <<~ERB <% if @namespace.children_of_type?(@type) && !@render_flat_list %> - <%= render NamespaceTree::Row::WithChildrenComponent.new(namespace: @namespace, type: @type, children: Group.none, path: @path, path_args: @path_args, collapsed: @collapsed) %> + <%= render NamespaceTree::Row::WithChildrenComponent.new(namespace: @namespace, type: @type, children: Group.none, path: @path, path_args: @path_args, collapsed: @collapsed, icon_size: @icon_size) %> <% else %> - <%= render NamespaceTree::Row::WithoutChildrenComponent.new(namespace: @namespace, path: @path, path_args: @path_args) %> + <%= render NamespaceTree::Row::WithoutChildrenComponent.new(namespace: @namespace, path: @path, path_args: @path_args, icon_size: @icon_size) %> <% end %> ERB # rubocop:disable Metrics/ParameterLists - def initialize(namespace:, type:, path: nil, path_args: {}, collapsed: true, render_flat_list: false) + def initialize(namespace:, type:, path: nil, path_args: {}, collapsed: true, render_flat_list: false, + icon_size: :small) @namespace = namespace @type = type @path = path @path_args = path_args @collapsed = collapsed @render_flat_list = render_flat_list + @icon_size = icon_size end # rubocop:enable Metrics/ParameterLists diff --git a/app/components/namespace_tree_container_component.rb b/app/components/namespace_tree_container_component.rb index 55dd65722a..9b91737693 100644 --- a/app/components/namespace_tree_container_component.rb +++ b/app/components/namespace_tree_container_component.rb @@ -4,15 +4,19 @@ class NamespaceTreeContainerComponent < ViewComponent::Base erb_template <<-ERB <div class="namespace-tree-container"> - <%= render NamespaceTree::NamespaceTreeComponent.new(namespaces: @namespaces, path: @path, path_args: @path_args, type: @type, render_flat_list: @render_flat_list) %> + <%= render NamespaceTree::NamespaceTreeComponent.new(namespaces: @namespaces, path: @path, path_args: @path_args, type: @type, render_flat_list: @render_flat_list, icon_size: @icon_size) %> </div> ERB - def initialize(namespaces:, path: nil, path_args: {}, type: Group.sti_name, render_flat_list: false) + # rubocop: disable Metrics/ParameterLists + def initialize(namespaces:, path: nil, path_args: {}, type: Group.sti_name, render_flat_list: false, + icon_size: :small) @namespaces = namespaces @path = path @path_args = path_args @type = type @render_flat_list = render_flat_list + @icon_size = icon_size end + # rubocop: enable Metrics/ParameterLists end diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb index 49e8dd9a77..e38aca4f44 100644 --- a/app/controllers/dashboard/groups_controller.rb +++ b/app/controllers/dashboard/groups_controller.rb @@ -6,7 +6,6 @@ class GroupsController < ApplicationController before_action :current_page def index - @render_flat_list = flat_list_requested? @q = build_ransack_query set_default_sort @pagy, @groups = pagy(@q.result.include_route) @@ -54,7 +53,7 @@ def toggle_group end def authorized_groups - if @render_flat_list + if flat_list_requested? authorized_scope(Group, type: :relation) else authorized_scope(Group, type: :relation).without_descendants diff --git a/app/controllers/groups/subgroups_controller.rb b/app/controllers/groups/subgroups_controller.rb index 071e56e552..85c55b3f6a 100644 --- a/app/controllers/groups/subgroups_controller.rb +++ b/app/controllers/groups/subgroups_controller.rb @@ -7,16 +7,7 @@ class SubgroupsController < ApplicationController def index authorize! @group, to: :read? - respond_to do |format| - format.html { redirect_to group_path(@group) } - format.turbo_stream do - if params.key? :parent_id - render_subgroup - else - @namespaces = namespace_children - end - end - end + render_subgroup end private diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index a6d2a22e97..05f035ec51 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -15,6 +15,14 @@ def index def show authorize! @group, to: :read? + + @q = if flat_list_requested? + namespace_descendants.ransack(params[:q]) + else + namespace_children.ransack(params[:q]) + end + set_default_sort + @pagy, @namespaces = pagy(@q.result.include_route) end def new @@ -105,6 +113,14 @@ def transfer # rubocop:disable Metrics/AbcSize private + def set_default_sort + @q.sorts = 'created_at desc' if @q.sorts.empty? + end + + def flat_list_requested? + params.dig(:q, :name_or_puid_cont).present? + end + def parent_group @group = Group.find(params[:parent_id]) if params[:parent_id] end @@ -130,6 +146,11 @@ def namespace_children ) end + def namespace_descendants + @group.self_and_descendants_of_type([Namespaces::ProjectNamespace.sti_name, + Group.sti_name]) + end + def resolve_layout case action_name when 'show', 'edit', 'update', 'activity' diff --git a/app/models/namespace.rb b/app/models/namespace.rb index f6b97d6ae9..ea7f9a54c2 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -150,6 +150,13 @@ def self_and_descendants self.class.joins(:route).where(route_path.matches_any([full_path, "#{full_path}/%"])) end + def self_and_descendants_of_type(types) + route_path = Route.arel_table[:path] + + Namespace.joins(:route).where(route_path.matches_any([full_path, "#{full_path}/%"])) + .where(type: types) + end + def self_and_descendant_ids self_and_descendants.as_ids end diff --git a/app/views/dashboard/groups/group.turbo_stream.erb b/app/views/dashboard/groups/group.turbo_stream.erb index d06b262263..544625995b 100644 --- a/app/views/dashboard/groups/group.turbo_stream.erb +++ b/app/views/dashboard/groups/group.turbo_stream.erb @@ -4,6 +4,7 @@ children: @children, collapsed: @collapsed, path: "dashboard_groups_path", - type: [Group.sti_name] + type: [Group.sti_name], + icon_size: :medium, ) %> <% end %> diff --git a/app/views/dashboard/groups/index.html.erb b/app/views/dashboard/groups/index.html.erb index c3dbd7f03c..b88e3b1a67 100644 --- a/app/views/dashboard/groups/index.html.erb +++ b/app/views/dashboard/groups/index.html.erb @@ -15,7 +15,6 @@ > <div class="flex flex-row items-center ml-auto space-x-2 font-normal"> <%= search_form_for @q, url: dashboard_groups_url, html: { "data-controller": "filters", "data-turbo-permanent": "true" } do |f| %> - <%= f.hidden_field :format, value: "turbo_stream" %> <%= f.label :name_or_puid_cont, "SEARCH", class: "sr-only" %> <div class="relative lg:w-72"> <div @@ -56,6 +55,7 @@ namespaces: @groups, path: "dashboard_groups_path", render_flat_list: @render_flat_list, + icon_size: :medium, ) %> <div class="flex flex-row-reverse"> <%= render Viral::Pagy::PaginationComponent.new( diff --git a/app/views/groups/show.html.erb b/app/views/groups/show.html.erb index 0f82030447..1b04a85729 100644 --- a/app/views/groups/show.html.erb +++ b/app/views/groups/show.html.erb @@ -1,19 +1,19 @@ <%= render Viral::PageHeaderComponent.new(title: @group.name, id: @group.puid, subtitle: @group.description) do |component| %> <%= component.icon do %> <%= viral_avatar( - name: @group.name, - colour_string: "#{@group.name}-#{@group.id}", - size: :large - ) %> + name: @group.name, + colour_string: "#{@group.name}-#{@group.id}", + size: :large, + ) %> <% end %> <%= component.with_buttons do %> <% if allowed_to?(:new?, @group) %> <%= link_to t(:"groups.show.create_subgroup_button"), - new_group_path(parent_id: @group.id), - class: "button button--size-default button--state-default" %> + new_group_path(parent_id: @group.id), + class: "button button--size-default button--state-default" %> <%= link_to t(:"groups.show.create_project_button"), - new_project_path(group_id: @group.id), - class: "button button--size-default button--state-primary ml-2" %> + new_project_path(group_id: @group.id), + class: "button button--size-default button--state-primary ml-2" %> <% end %> <% end %> <% end %> @@ -23,44 +23,58 @@ <%= t(:".tabs.subgroups_and_projects") %> <% end %> <%= tabs.with_tab(url: group_path(@group, tab: "shared_namespaces"), controls: "group-projects", selected: @tab == "shared_namespaces") do %> - <%= t(:'.tabs.shared_namespaces') %> + <%= t(:".tabs.shared_namespaces") %> <% end %> <%= tabs.with_tab_content do %> - <%= turbo_frame_tag "group_show_tab_content", "data-turbo-temporary": true, src: ( - if @tab == "shared_namespaces" - group_shared_namespaces_path(@group, format: :turbo_stream) - else - group_subgroups_path(@group, format: :turbo_stream) - end - ) do %> - <table class="min-w-full table-fixed dark:divide-slate-600"> - <tbody - class=" - bg-white - divide-y - divide-slate-200 - dark:bg-slate-800 - dark:divide-slate-700 - " - > - <% 10.times do %> - <tr> + <div class="mt-2"> + <% if @tab == "shared_namespaces" %> + <%= turbo_frame_tag "group_show_tab_content", "data-turbo-temporary": true, src: group_shared_namespaces_path(@group, format: :turbo_stream) do %> + <table class="min-w-full table-fixed dark:divide-slate-600"> + <tbody + class=" + bg-white divide-y divide-slate-200 dark:bg-slate-800 dark:divide-slate-700 + " + > + <% 10.times do %> + <tr> - <td class="p-4 animate-pulse"> - <div class="flex-1 py-1 space-y-6"> - <div class="space-y-3"> - <div class="w-48 h-2 rounded bg-slate-200"></div> - <div class="w-32 h-2 rounded bg-slate-200"></div> - </div> - </div> - </td> + <td class="p-4 animate-pulse"> + <div class="flex-1 py-1 space-y-6"> + <div class="space-y-3"> + <div class="w-48 h-2 rounded bg-slate-200"></div> + <div class="w-32 h-2 rounded bg-slate-200"></div> + </div> + </div> + </td> - </tr> + </tr> + <% end %> + </tbody> + </table> <% end %> - </tbody> - </table> - <% end %> + <% else %> + <div class="flex flex-row-reverse mb-2"> + <%= search_form_for @q, url: group_path(@group), html: { "data-controller": "filters" } do |f| %> + <%= f.label :name_or_puid_cont, t("general.search.name_puid"), class: "sr-only" %> + <div class="relative lg:w-72"> + <div + class=" + absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none + " + > + <%= viral_icon(name: "magnifying_glass", classes: "h-5 w-5") %> + </div> + <%= f.search_field :name_or_puid_cont, + "data-action": "filters#submit", + class: + "block w-full p-2.5 pl-10 text-sm text-slate-900 border border-slate-300 rounded-lg bg-slate-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-slate-700 dark:border-slate-600 dark:placeholder-slate-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500", + placeholder: t("general.search.name_puid") %> + </div> + <% end %> + </div> + <%= render "groups/subgroups/index", locals: { namespaces: @namespaces } %> + <% end %> + </div> <% end %> <% end %> - diff --git a/app/views/groups/subgroups/_index.html.erb b/app/views/groups/subgroups/_index.html.erb new file mode 100644 index 0000000000..1ba13fbc81 --- /dev/null +++ b/app/views/groups/subgroups/_index.html.erb @@ -0,0 +1,20 @@ +<div class="space-y-2"> + + <% if @namespaces.length > 0 %> + <%= render NamespaceTreeContainerComponent.new( + namespaces: @namespaces, + path: "group_subgroups_path", + path_args: { + group_id: @group.full_path, + }, + type: [Group.sti_name, Namespaces::ProjectNamespace.sti_name], + ) %> + <%= render Viral::Pagy::FullComponent.new(@pagy, item: "Subgroups and projects") %> + <% else %> + <%= viral_empty( + title: t(:"groups.show.subgroups.no_subgroups.title"), + description: t(:"groups.show.subgroups.no_subgroups.description"), + icon_name: :squares_2x2, + ) %> + <% end %> +</div> diff --git a/config/locales/en.yml b/config/locales/en.yml index 2159877c69..73369e4564 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -580,6 +580,8 @@ en: project: Create new project screen_reader: close: Close + search: + name_puid: Search by name or ID groups: activity: title: Group activity diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ef9f21b03b..525d6a40b8 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -580,6 +580,8 @@ fr: project: Create new project screen_reader: close: Close + search: + name_puid: Search by name or ID groups: activity: title: Group activity diff --git a/test/controllers/groups/subgroups_controller_test.rb b/test/controllers/groups/subgroups_controller_test.rb index 4883ea56b7..4f4f062b9e 100644 --- a/test/controllers/groups/subgroups_controller_test.rb +++ b/test/controllers/groups/subgroups_controller_test.rb @@ -8,12 +8,6 @@ class SubgroupsControllerTest < ActionDispatch::IntegrationTest sign_in users(:john_doe) @group = groups(:group_one) end - - test 'should redirect to groups index when html format requested' do - get group_subgroups_url(@group) - assert_redirected_to group_url(@group) - end - test 'should get fragment of namesapce tree' do get group_subgroups_url(@group), params: { parent_id: @group.id, format: :turbo_stream } assert_response :ok diff --git a/test/system/groups_test.rb b/test/system/groups_test.rb index 7624e6ff87..2fa507d9f2 100644 --- a/test/system/groups_test.rb +++ b/test/system/groups_test.rb @@ -132,11 +132,9 @@ def setup click_on I18n.t('groups.sidebar.settings') click_link I18n.t('groups.sidebar.general') - within all('form[action="/group-1"]')[0] do - fill_in I18n.t('activerecord.attributes.group.name'), with: group_name - fill_in I18n.t('activerecord.attributes.group.description'), with: group_description - click_on I18n.t('groups.edit.details.submit') - end + fill_in I18n.t('activerecord.attributes.group.name'), with: group_name + fill_in I18n.t('activerecord.attributes.group.description'), with: group_description + click_on I18n.t('groups.edit.details.submit') assert_text I18n.t('groups.update.success', group_name:) @@ -389,7 +387,9 @@ def setup assert_selector 'h1', text: @group.name assert_selector 'a.active', text: I18n.t(:'groups.show.tabs.subgroups_and_projects') - assert_selector 'li.namespace-entry', count: 21 + assert_selector 'li.namespace-entry', count: 20 + click_on I18n.t(:'components.pagination.next') + assert_selector 'li.namespace-entry', count: 1 click_on I18n.t(:'groups.show.tabs.shared_namespaces') assert_selector 'a.active', text: I18n.t(:'groups.show.tabs.shared_namespaces') @@ -403,4 +403,12 @@ def setup assert_selector 'div.namespace-entry-contents', count: 0 assert_text I18n.t('groups.show.shared_namespaces.no_shared.title') end + + test 'search subgroups and projects' do + @group = groups(:group_one) + visit group_url(@group) + assert_text I18n.t(:'components.pagination.next') + fill_in I18n.t('general.search.name_puid'), with: 'project 2' + assert_selector 'li.namespace-entry', count: 5 + end end