Commit 05605d3f authored by Stan Hu's avatar Stan Hu

Merge branch '237932-search-ui-implement-issue-scope-results-filter-by-state' into 'master'

Search UI add issue scope results filtering by state

See merge request gitlab-org/gitlab!39881
parents a1b5d10c e9aa261b
import Search from './search';
import initStateFilter from '~/search/state_filter';
document.addEventListener('DOMContentLoaded', () => new Search());
document.addEventListener('DOMContentLoaded', () => {
initStateFilter();
return new Search();
});
<script>
import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
import { FILTER_STATES, FILTER_HEADER, FILTER_TEXT } from '../constants';
import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
const FILTERS_ARRAY = Object.values(FILTER_STATES);
export default {
name: 'StateFilter',
components: {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
},
props: {
scope: {
type: String,
required: true,
},
state: {
type: String,
required: false,
default: FILTER_STATES.ANY.value,
validator: v => FILTERS_ARRAY.some(({ value }) => value === v),
},
},
computed: {
selectedFilterText() {
let filterText = FILTER_TEXT;
if (this.selectedFilter === FILTER_STATES.CLOSED.value) {
filterText = FILTER_STATES.CLOSED.label;
} else if (this.selectedFilter === FILTER_STATES.OPEN.value) {
filterText = FILTER_STATES.OPEN.label;
}
return filterText;
},
selectedFilter: {
get() {
if (FILTERS_ARRAY.some(({ value }) => value === this.state)) {
return this.state;
}
return FILTER_STATES.ANY.value;
},
set(state) {
visitUrl(setUrlParams({ state }));
},
},
},
methods: {
dropDownItemClass(filter) {
return {
'gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2! gl-mb-2':
filter === FILTER_STATES.ANY,
};
},
isFilterSelected(filter) {
return filter === this.selectedFilter;
},
handleFilterChange(state) {
this.selectedFilter = state;
},
},
filterStates: FILTER_STATES,
filterHeader: FILTER_HEADER,
filtersArray: FILTERS_ARRAY,
};
</script>
<template>
<gl-dropdown
v-if="scope === 'issues'"
:text="selectedFilterText"
class="col-sm-3 gl-pt-4 gl-pl-0"
>
<header class="gl-text-center gl-font-weight-bold gl-font-lg">
{{ $options.filterHeader }}
</header>
<gl-dropdown-divider />
<gl-dropdown-item
v-for="filter in $options.filtersArray"
:key="filter.value"
:is-check-item="true"
:is-checked="isFilterSelected(filter.value)"
:class="dropDownItemClass(filter)"
@click="handleFilterChange(filter.value)"
>
{{ filter.label }}
</gl-dropdown-item>
</gl-dropdown>
</template>
import { __ } from '~/locale';
export const FILTER_HEADER = __('Status');
export const FILTER_TEXT = __('Any Status');
export const FILTER_STATES = {
ANY: {
label: __('Any'),
value: 'all',
},
OPEN: {
label: __('Open'),
value: 'opened',
},
CLOSED: {
label: __('Closed'),
value: 'closed',
},
};
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import StateFilter from './components/state_filter.vue';
Vue.use(Translate);
export default () => {
const el = document.getElementById('js-search-filter-by-state');
if (!el) return false;
return new Vue({
el,
components: {
StateFilter,
},
data() {
const { dataset } = this.$options.el;
return {
scope: dataset.scope,
state: dataset.state,
};
},
render(createElement) {
return createElement('state-filter', {
props: {
scope: this.scope,
state: this.state,
},
});
},
});
};
......@@ -8,7 +8,7 @@
# current_user - which user use
# params:
# scope: 'created_by_me' or 'assigned_to_me' or 'all'
# state: 'open' or 'closed' or 'all'
# state: 'opened' or 'closed' or 'all'
# group_id: integer
# project_id: integer
# milestone_title: string
......
# frozen_string_literal: true
module SearchHelper
SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets].freeze
SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets, :state].freeze
def search_autocomplete_opts(term)
return unless current_user
......
......@@ -13,7 +13,8 @@ module Search
def execute
Gitlab::SearchResults.new(current_user,
params[:search],
projects)
projects,
filters: { state: params[:state] })
end
def projects
......
......@@ -15,7 +15,8 @@ module Search
current_user,
params[:search],
projects,
group: group
group: group,
filters: { state: params[:state] }
)
end
......
......@@ -12,7 +12,8 @@ module Search
Gitlab::ProjectSearchResults.new(current_user,
params[:search],
project: project,
repository_ref: params[:repository_ref])
repository_ref: params[:repository_ref],
filters: { state: params[:state] })
end
def scope
......
......@@ -22,6 +22,8 @@
= _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
= render_if_exists 'shared/promotions/promote_advanced_search'
#js-search-filter-by-state{ 'v-cloak': true, data: { scope: @scope, state: params[:state] } }
.results.gl-mt-3
- if @scope == 'commits'
%ul.content-list.commit-list
......
---
title: Search UI Allow issue scope results filtering by state
merge_request: 39881
author:
type: changed
......@@ -15,7 +15,8 @@ module EE
current_user,
params[:search],
projects,
public_and_internal_projects: elastic_global
public_and_internal_projects: elastic_global,
filters: { state: params[:state] }
)
end
......
......@@ -24,7 +24,8 @@ module EE
params[:search],
projects,
group: group,
public_and_internal_projects: elastic_global
public_and_internal_projects: elastic_global,
filters: { state: params[:state] }
)
end
end
......
......@@ -14,7 +14,8 @@ module EE
current_user,
params[:search],
project: project,
repository_ref: repository_ref
repository_ref: repository_ref,
filters: { state: params[:state] }
)
end
......
......@@ -21,12 +21,25 @@ module Elastic
options[:features] = 'issues'
query_hash = project_ids_filter(query_hash, options)
query_hash = confidentiality_filter(query_hash, options)
query_hash = state_filter(query_hash, options)
search(query_hash, options)
end
private
def state_filter(query_hash, options)
state = options[:state]
return query_hash if state.blank? || state == 'all'
return query_hash unless %w(all opened closed).include?(state)
filter = { match: { state: state } }
query_hash[:query][:bool][:filter] << filter
query_hash
end
def confidentiality_filter(query_hash, options)
current_user = options[:current_user]
project_ids = options[:project_ids]
......
......@@ -6,7 +6,7 @@ module Elastic
def as_indexed_json(options = {})
data = {}
# We don't use as_json(only: ...) because it calls all virtual and serialized attributtes
# We don't use as_json(only: ...) because it calls all virtual and serialized attributes
# https://gitlab.com/gitlab-org/gitlab/issues/349
[:id, :iid, :title, :description, :created_at, :updated_at, :state, :project_id, :author_id, :confidential].each do |attr|
data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr)
......
......@@ -9,13 +9,14 @@ module Gitlab
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
attr_reader :group, :default_project_filter
attr_reader :group, :default_project_filter, :filters
def initialize(current_user, query, limit_projects = nil, group:, public_and_internal_projects: false, default_project_filter: false)
def initialize(current_user, query, limit_projects = nil, group:, public_and_internal_projects: false, default_project_filter: false, filters: {})
@group = group
@default_project_filter = default_project_filter
@filters = filters
super(current_user, query, limit_projects, public_and_internal_projects: public_and_internal_projects)
super(current_user, query, limit_projects, public_and_internal_projects: public_and_internal_projects, filters: filters)
end
def generic_search_results
......@@ -23,7 +24,8 @@ module Gitlab
current_user,
query,
limit_projects,
group: group
group: group,
filters: filters
)
end
end
......
......@@ -6,16 +6,16 @@ module Gitlab
# superclass inside a module, because autoloading can occur in a
# different order between execution environments.
class ProjectSearchResults < Gitlab::Elastic::SearchResults
attr_reader :project, :repository_ref
attr_reader :project, :repository_ref, :filters
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
def initialize(current_user, query, project:, repository_ref: nil)
def initialize(current_user, query, project:, repository_ref: nil, filters: {})
@project = project
@repository_ref = repository_ref.presence || project.default_branch
super(current_user, query, [project], public_and_internal_projects: false)
super(current_user, query, [project], public_and_internal_projects: false, filters: filters)
end
def generic_search_results
......@@ -23,7 +23,8 @@ module Gitlab
current_user,
query,
project: project,
repository_ref: repository_ref
repository_ref: repository_ref,
filters: filters
)
end
......
......@@ -7,7 +7,7 @@ module Gitlab
DEFAULT_PER_PAGE = Gitlab::SearchResults::DEFAULT_PER_PAGE
attr_reader :current_user, :query, :public_and_internal_projects
attr_reader :current_user, :query, :public_and_internal_projects, :filters
# Limit search results by passed projects
# It allows us to search only for projects user has access to
......@@ -16,11 +16,12 @@ module Gitlab
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
def initialize(current_user, query, limit_projects = nil, public_and_internal_projects: true)
def initialize(current_user, query, limit_projects = nil, public_and_internal_projects: true, filters: {})
@current_user = current_user
@query = query
@limit_projects = limit_projects
@public_and_internal_projects = public_and_internal_projects
@filters = filters
end
def objects(scope, page: 1, per_page: DEFAULT_PER_PAGE, preload_method: nil)
......@@ -221,7 +222,10 @@ module Gitlab
def issues
strong_memoize(:issues) do
Issue.elastic_search(query, options: base_options)
options = base_options
options[:state] = filters[:state] if filters.key?(:state)
Issue.elastic_search(query, options: options)
end
end
......
......@@ -2,17 +2,32 @@
require 'spec_helper'
RSpec.describe Gitlab::Elastic::GroupSearchResults do
RSpec.describe Gitlab::Elastic::GroupSearchResults, :elastic do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:guest) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::GUEST) } }
let(:filters) { {} }
let(:query) { '*' }
subject(:results) { described_class.new(user, query, group: group) }
subject(:results) { described_class.new(user, query, Project.all, group: group, filters: filters) }
before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
end
context 'issues search', :sidekiq_inline do
let!(:project) { create(:project, :public, group: group) }
let!(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let!(:opened_issue) { create(:issue, :opened, project: project, title: 'foo opened') }
let(:query) { 'foo' }
include_examples 'search issues scope filters by state' do
before do
ensure_elasticsearch_index!
end
end
end
context 'user search' do
let(:query) { guest.username }
......@@ -40,8 +55,6 @@ RSpec.describe Gitlab::Elastic::GroupSearchResults do
end
context 'query performance' do
let(:query) { '*' }
include_examples 'does not hit Elasticsearch twice for objects and counts', %w|projects notes blobs wiki_blobs commits issues merge_requests milestones|
end
end
......@@ -3,12 +3,13 @@
require 'spec_helper'
RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, :repository) }
let(:query) { 'hello world' }
let(:repository_ref) { nil }
let(:filters) { {} }
subject(:results) { described_class.new(user, query, project: project, repository_ref: repository_ref) }
subject(:results) { described_class.new(user, query, project: project, repository_ref: repository_ref, filters: filters) }
before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
......@@ -30,9 +31,9 @@ RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
it { expect(results.query).to eq('hello world') }
end
describe "search", :sidekiq_might_not_need_inline do
let(:project) { create(:project, :public, :repository, :wiki_repo) }
let(:private_project) { create(:project, :repository, :wiki_repo) }
describe "search", :sidekiq_inline do
let_it_be(:project) { create(:project, :public, :repository, :wiki_repo) }
let_it_be(:private_project) { create(:project, :repository, :wiki_repo) }
before do
[project, private_project].each do |project|
......@@ -56,7 +57,7 @@ RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
end
context 'visibility checks' do
let(:project) { create(:project, :public, :wiki_repo) }
let_it_be(:project) { create(:project, :public, :wiki_repo) }
let(:query) { 'term' }
before do
......@@ -67,6 +68,19 @@ RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
expect(results.wiki_blobs_count).to eq(1)
end
end
context 'filtering' do
include_examples 'search issues scope filters by state' do
let!(:project) { create(:project, :public) }
let!(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let!(:opened_issue) { create(:issue, :opened, project: project, title: 'foo opened') }
let(:query) { 'foo' }
before do
ensure_elasticsearch_index!
end
end
end
end
describe "search for commits in non-default branch" do
......@@ -147,13 +161,14 @@ RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
it { expect(results.limited_users_count).to eq(1) }
describe 'pagination' do
let(:query) {}
let(:query) { }
let!(:user2) { create(:user).tap { |u| project.add_user(u, Gitlab::Access::REPORTER) } }
let_it_be(:user2) { create(:user).tap { |u| project.add_user(u, Gitlab::Access::REPORTER) } }
it 'returns the correct page of results' do
expect(results.objects('users', page: 1, per_page: 1)).to eq([project.owner])
expect(results.objects('users', page: 2, per_page: 1)).to eq([user2])
# UsersFinder defaults to order_id_desc, the newer result will be first
expect(results.objects('users', page: 1, per_page: 1)).to eq([user2])
expect(results.objects('users', page: 2, per_page: 1)).to eq([project.owner])
end
it 'returns the correct number of results for one page' do
......
......@@ -171,6 +171,20 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
expect(results.objects('issues')).to be_empty
expect(results.issues_count).to eq 0
end
context 'filtering' do
let!(:project) { create(:project, :public) }
let!(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let!(:opened_issue) { create(:issue, :opened, project: project, title: 'foo opened') }
let(:results) { described_class.new(user, 'foo', [project], filters: filters) }
include_examples 'search issues scope filters by state' do
before do
ensure_elasticsearch_index!
end
end
end
end
describe 'notes' do
......
......@@ -4,10 +4,10 @@ module Gitlab
class GroupSearchResults < SearchResults
attr_reader :group
def initialize(current_user, query, limit_projects = nil, group:, default_project_filter: false)
def initialize(current_user, query, limit_projects = nil, group:, default_project_filter: false, filters: {})
@group = group
super(current_user, query, limit_projects, default_project_filter: default_project_filter)
super(current_user, query, limit_projects, default_project_filter: default_project_filter, filters: filters)
end
# rubocop:disable CodeReuse/ActiveRecord
......
......@@ -4,11 +4,11 @@ module Gitlab
class ProjectSearchResults < SearchResults
attr_reader :project, :repository_ref
def initialize(current_user, query, project:, repository_ref: nil)
def initialize(current_user, query, project:, repository_ref: nil, filters: {})
@project = project
@repository_ref = repository_ref.presence
super(current_user, query, [project])
super(current_user, query, [project], filters: filters)
end
def objects(scope, page: nil, per_page: DEFAULT_PER_PAGE, preload_method: nil)
......
......@@ -7,7 +7,7 @@ module Gitlab
DEFAULT_PAGE = 1
DEFAULT_PER_PAGE = 20
attr_reader :current_user, :query
attr_reader :current_user, :query, :filters
# Limit search results by passed projects
# It allows us to search only for projects user has access to
......@@ -19,11 +19,12 @@ module Gitlab
# query
attr_reader :default_project_filter
def initialize(current_user, query, limit_projects = nil, default_project_filter: false)
def initialize(current_user, query, limit_projects = nil, default_project_filter: false, filters: {})
@current_user = current_user
@query = query
@limit_projects = limit_projects || Project.all
@default_project_filter = default_project_filter
@filters = filters
end
def objects(scope, page: nil, per_page: DEFAULT_PER_PAGE, without_count: true, preload_method: nil)
......@@ -190,6 +191,8 @@ module Gitlab
else
params[:search] = query
end
params[:state] = filters[:state] if filters.key?(:state)
end
end
......
......@@ -2957,6 +2957,9 @@ msgstr ""
msgid "Any Author"
msgstr ""
msgid "Any Status"
msgstr ""
msgid "Any branch"
msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import StateFilter from '~/search/state_filter/components/state_filter.vue';
import { FILTER_STATES } from '~/search/state_filter/constants';
import * as urlUtils from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
setUrlParams: jest.fn(),
}));
function createComponent(props = { scope: 'issues' }) {
return shallowMount(StateFilter, {
propsData: {
...props,
},
});
}
describe('StateFilter', () => {
let wrapper;
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const findGlDropdown = () => wrapper.find(GlDropdown);
const findGlDropdownItems = () => findGlDropdown().findAll(GlDropdownItem);
const findDropdownItemsText = () => findGlDropdownItems().wrappers.map(w => w.text());
const firstDropDownItem = () => findGlDropdownItems().at(0);
describe('template', () => {
describe.each`
scope | showStateDropdown
${'issues'} | ${true}
${'projects'} | ${false}
${'milestones'} | ${false}
${'users'} | ${false}
${'merge_requests'} | ${false}
${'notes'} | ${false}
${'wiki_blobs'} | ${false}
${'blobs'} | ${false}
`(`state dropdown`, ({ scope, showStateDropdown }) => {
beforeEach(() => {
wrapper = createComponent({ scope });
});
it(`does${showStateDropdown ? '' : ' not'} render when scope is ${scope}`, () => {
expect(findGlDropdown().exists()).toBe(showStateDropdown);
});
});
describe('Filter options', () => {
it('renders a dropdown item for each filterOption', () => {
expect(findDropdownItemsText()).toStrictEqual(
Object.keys(FILTER_STATES).map(key => {
return FILTER_STATES[key].label;
}),
);
});
it('clicking a dropdown item calls setUrlParams', () => {
const state = FILTER_STATES[Object.keys(FILTER_STATES)[0]].value;
firstDropDownItem().vm.$emit('click');
expect(urlUtils.setUrlParams).toHaveBeenCalledWith({ state });
});
it('clicking a dropdown item calls visitUrl', () => {
firstDropDownItem().vm.$emit('click');
expect(urlUtils.visitUrl).toHaveBeenCalled();
});
});
});
});
......@@ -6,9 +6,22 @@ RSpec.describe Gitlab::GroupSearchResults do
# group creation calls GroupFinder, so need to create the group
# before so expect(GroupsFinder) check works
let_it_be(:group) { create(:group) }
let(:user) { create(:user) }
let_it_be(:user) { create(:user) }
let(:filters) { {} }
let(:limit_projects) { Project.all }
let(:query) { 'gob' }
subject(:results) { described_class.new(user, 'gob', anything, group: group) }
subject(:results) { described_class.new(user, query, limit_projects, group: group, filters: filters) }
describe 'issues search' do
let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:opened_issue) { create(:issue, :opened, project: project, title: 'foo opened') }
let_it_be(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let(:query) { 'foo' }
let(:filters) { { state: 'opened' } }
include_examples 'search issues scope filters by state'
end
describe 'user search' do
subject(:objects) { results.objects('users') }
......
......@@ -5,12 +5,13 @@ require 'spec_helper'
RSpec.describe Gitlab::ProjectSearchResults do
include SearchHelpers
let(:user) { create(:user) }
let(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:query) { 'hello world' }
let(:repository_ref) { nil }
let(:filters) { {} }
subject(:results) { described_class.new(user, query, project: project, repository_ref: repository_ref) }
subject(:results) { described_class.new(user, query, project: project, repository_ref: repository_ref, filters: filters) }
context 'with a repository_ref' do
context 'when empty' do
......@@ -258,6 +259,24 @@ RSpec.describe Gitlab::ProjectSearchResults do
describe "confidential issues" do
include_examples "access restricted confidential issues"
end
context 'filtering' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let_it_be(:opened_issue) { create(:issue, :opened, project: project, title: 'foo opened') }
let(:query) { 'foo' }
include_examples 'search issues scope filters by state'
end
it 'filters issues when state is provided', :aggregate_failures do
closed_issue = create(:issue, :closed, project: project, title: "Revert: #{issue.title}")
results = described_class.new(project.creator, query, project: project, filters: { state: 'opened' })
expect(results.objects('issues')).not_to include closed_issue
expect(results.objects('issues')).to include issue
end
end
describe 'notes search' do
......
......@@ -6,13 +6,14 @@ RSpec.describe Gitlab::SearchResults do
include ProjectForksHelper
include SearchHelpers
let(:user) { create(:user) }
let!(:project) { create(:project, name: 'foo') }
let!(:issue) { create(:issue, project: project, title: 'foo') }
let!(:merge_request) { create(:merge_request, source_project: project, title: 'foo') }
let!(:milestone) { create(:milestone, project: project, title: 'foo') }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, name: 'foo') }
let_it_be(:issue) { create(:issue, project: project, title: 'foo') }
let_it_be(:milestone) { create(:milestone, project: project, title: 'foo') }
let(:merge_request) { create(:merge_request, source_project: project, title: 'foo') }
let(:filters) { {} }
subject(:results) { described_class.new(user, 'foo', Project.all) }
subject(:results) { described_class.new(user, 'foo', Project.all, filters: filters) }
context 'as a user with access' do
before do
......@@ -105,10 +106,10 @@ RSpec.describe Gitlab::SearchResults do
describe '#limited_issues_count' do
it 'runs single SQL query to get the limited amount of issues' do
create(:milestone, project: project, title: 'foo2')
create(:issue, project: project, title: 'foo2')
expect(results).to receive(:issues).with(public_only: true).and_call_original
expect(results).not_to receive(:issues).with(no_args).and_call_original
expect(results).not_to receive(:issues).with(no_args)
expect(results.limited_issues_count).to eq(1)
end
......@@ -165,6 +166,13 @@ RSpec.describe Gitlab::SearchResults do
results.objects('issues')
end
context 'filtering' do
let_it_be(:closed_issue) { create(:issue, :closed, project: project, title: 'foo closed') }
let_it_be(:opened_issue) { create(:issue, :opened, project: project, title: 'foo open') }
include_examples 'search issues scope filters by state'
end
end
describe '#users' do
......
# frozen_string_literal: true
RSpec.shared_examples 'search issues scope filters by state' do
context 'state not provided' do
let(:filters) { {} }
it 'returns opened and closed issues', :aggregate_failures do
expect(results.objects('issues')).to include opened_issue
expect(results.objects('issues')).to include closed_issue
end
end
context 'all state' do
let(:filters) { { state: 'all' } }
it 'returns opened and closed issues', :aggregate_failures do
expect(results.objects('issues')).to include opened_issue
expect(results.objects('issues')).to include closed_issue
end
end
context 'closed state' do
let(:filters) { { state: 'closed' } }
it 'returns only closed issues', :aggregate_failures do
expect(results.objects('issues')).not_to include opened_issue
expect(results.objects('issues')).to include closed_issue
end
end
context 'opened state' do
let(:filters) { { state: 'opened' } }
it 'returns only opened issues', :aggregate_failures do
expect(results.objects('issues')).to include opened_issue
expect(results.objects('issues')).not_to include closed_issue
end
end
context 'unsupported state' do
let(:filters) { { state: 'hello' } }
it 'returns only opened issues', :aggregate_failures do
expect(results.objects('issues')).to include opened_issue
expect(results.objects('issues')).to include closed_issue
end
end
end
......@@ -54,6 +54,12 @@ RSpec.describe 'search/_results' do
expect(rendered).to have_selector('[data-track-event=click_text]')
expect(rendered).to have_selector('[data-track-property=search_result]')
end
it 'renders the state filter drop down' do
render
expect(rendered).to have_selector('#js-search-filter-by-state')
end
end
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment