diff --git a/app/components/blacklight/facet_field_list_range_component.html.erb b/app/components/blacklight/facet_field_list_range_component.html.erb new file mode 100644 index 0000000000..e713728a04 --- /dev/null +++ b/app/components/blacklight/facet_field_list_range_component.html.erb @@ -0,0 +1,17 @@ +<%= render(@layout.new(facet_field: @facet_field)) do |component| %> + <% component.with_label do %> + <%= @facet_field.label %> + <% end %> + + <% component.with_body do %> + <%# Don't display form if the missing facet is selected. Otherwise provide + the form as an easy way for users to updated the range. %> + <% unless @facet_field.missing_selected? %> + <%= render Blacklight::FacetFieldListRangeFormComponent.new(facet_field: @facet_field) %> + <% end %> + + + <% end %> +<% end %> \ No newline at end of file diff --git a/app/components/blacklight/facet_field_list_range_component.rb b/app/components/blacklight/facet_field_list_range_component.rb new file mode 100644 index 0000000000..e7d89ba257 --- /dev/null +++ b/app/components/blacklight/facet_field_list_range_component.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Blacklight + class FacetFieldListRangeComponent < Blacklight::Component + # @param [Blacklight::FacetFieldRangePresenter] facet_field + def initialize(facet_field:, layout: nil) + @facet_field = facet_field + @layout = layout == false ? FacetFieldNoLayoutComponent : Blacklight::FacetFieldComponent + end + + def facet_items(wrapping_element: :li, **item_args) + facet_item_component_class.with_collection(facet_item_presenters, wrapping_element: wrapping_element, **item_args) + end + + def facet_item_presenters + @facet_field.paginator.items.map do |item| + facet_item_presenter(item) + end + end + + def facet_item_presenter(facet_item, deprecated_facet_config = nil, facet_field = nil) + (deprecated_facet_config || facet_config).item_presenter.new(facet_item, deprecated_facet_config || facet_config, helpers, facet_field || @facet_field.key) + end + + def facet_item_component_class(deprecated_facet_config = nil) + (deprecated_facet_config || facet_config).item_component + end + + def facet_config + @facet_field.facet_field + end + end +end diff --git a/app/components/blacklight/facet_field_list_range_form_component.html.erb b/app/components/blacklight/facet_field_list_range_form_component.html.erb new file mode 100644 index 0000000000..358168de23 --- /dev/null +++ b/app/components/blacklight/facet_field_list_range_form_component.html.erb @@ -0,0 +1,12 @@ +<%= form_tag search_action_path, method: :get, class: ['range_limit subsection form-inline', "range_#{@facet_field.key} d-flex justify-content-center"].join(' ') do %> + <%= render hidden_search_state %> + +
+ <%= render_range_input(:start, start_label) %> + <%= render_range_input(:end, end_label) %> +
+ <%= submit_tag t('blacklight.search.facets.range.form.submit'), class: 'submit btn btn-secondary', name: nil %> +
+ <%= submit_tag t('blacklight.search.facets.range.form.submit'), class: "submit btn btn-secondary sr-only", "aria-hidden": "true", name: nil %> +
+<% end %> \ No newline at end of file diff --git a/app/components/blacklight/facet_field_list_range_form_component.rb b/app/components/blacklight/facet_field_list_range_form_component.rb new file mode 100644 index 0000000000..ceb7360384 --- /dev/null +++ b/app/components/blacklight/facet_field_list_range_form_component.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Blacklight + class FacetFieldListRangeFormComponent < Blacklight::Component + delegate :search_action_path, to: :helpers + + def initialize(facet_field:) + @facet_field = facet_field + end + + def start_label + t('blacklight.search.facets.range.form.start_label', field_label: @facet_field.label) + end + + def end_label + t('blacklight.search.facets.range.form.end_label', field_label: @facet_field.label) + end + + def input_options + return {} unless range_config + + range_config.fetch(:input, {}) + .slice(:min, :max, :placeholder, :step) + end + + # type is 'start' or 'end' + def render_range_input(type, input_label = nil) + type = type.to_s + + default = if @facet_field.selected_range.is_a?(Range) + case type + when 'start' then @facet_field.selected_range.first + when 'end' then @facet_field.selected_range.last + end + end + html = number_field_tag("range[#{@facet_field.key}][#{type}]", default, class: "form-control text-center range_#{type}", **input_options) + html += label_tag("range[#{@facet_field.key}][#{type}]", input_label, class: 'sr-only visually-hidden') if input_label.present? + html + end + + private + + ## + # the form needs to serialize any search parameters, including other potential range filters, + # as hidden fields. The parameters for this component's range filter are serialized as number + # inputs, and should not be in the hidden params. + # @return [Blacklight::HiddenSearchStateComponent] + def hidden_search_state + hidden_search_params = @facet_field.search_state.params_for_search.except(:utf8, :page) + hidden_search_params[:range]&.except!(@facet_field.key) + Blacklight::HiddenSearchStateComponent.new(params: hidden_search_params) + end + + def range_config + config = @facet_field.facet_field.range + config == true ? {} : config + end + end +end diff --git a/app/presenters/blacklight/facet_field_range_presenter.rb b/app/presenters/blacklight/facet_field_range_presenter.rb new file mode 100644 index 0000000000..e24447ccaf --- /dev/null +++ b/app/presenters/blacklight/facet_field_range_presenter.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Blacklight + class FacetFieldRangePresenter < Blacklight::FacetFieldPresenter + delegate :response, to: :display_facet + delegate :blacklight_config, to: :search_state + + # Paginator will return the selected item or if no facet is selected, the [Missing] facet. + def paginator + return unless display_facet + + @paginator ||= blacklight_config.facet_paginator_class.new( + Array.wrap(selected_item || display_facet.items.select(&:missing)), + sort: display_facet.sort, + offset: display_facet.offset, + prefix: display_facet.prefix, + limit: facet_limit + ) + end + + def selected_range + values&.first + end + + # Wraps selected range in Blacklight::Solr::Response::Facets::FacetItem object. + # + # @return [Blacklight::Solr::Response::Facets::FacetItem] if range is selected + # @return [NilClass] if no range is selected + def selected_item + return unless selected_range + + Blacklight::Solr::Response::Facets::FacetItem.new(value: selected_range, hits: response.total) + end + + # Returns true if [Missing] facet is selected. + def missing_selected? + selected_range == Blacklight::SearchState::FilterField::MISSING + end + end +end diff --git a/app/presenters/blacklight/facet_item_range_presenter.rb b/app/presenters/blacklight/facet_item_range_presenter.rb new file mode 100644 index 0000000000..5492858d1c --- /dev/null +++ b/app/presenters/blacklight/facet_item_range_presenter.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Blacklight + # Override the default item presenter to provide custom labels for + # range data. + class FacetItemRangePresenter < Blacklight::FacetItemPresenter + # Overriding method to generate a more descriptive label + def label + label_for_range || super + end + + private + + def label_for_range + return unless value.is_a? Range + + view_context.t(range_limit_label_key, start: value.first, end: value.last) + end + + def range_limit_label_key + if value.first == value.last + 'blacklight.search.facets.range.single_value' + else + 'blacklight.search.facets.range.range_value' + end + end + end +end diff --git a/config/locales/blacklight.ar.yml b/config/locales/blacklight.ar.yml index d12cd2bf13..1999da71f2 100644 --- a/config/locales/blacklight.ar.yml +++ b/config/locales/blacklight.ar.yml @@ -113,6 +113,13 @@ ar: count: ترتيب رقمي index: ترتيب أبجدي title: تحديد نطاق البحث + range: + form: + start_label: "%{field_label} بداية المدة" + end_label: "%{field_label} نهاية المدة" + submit: 'تطبيق' + single_value: '%{begin}' + range_value: '%{begin} الى %{end}' filters: label: "%{label}:" remove: diff --git a/config/locales/blacklight.de.yml b/config/locales/blacklight.de.yml index dc2c9ba203..114346d36f 100644 --- a/config/locales/blacklight.de.yml +++ b/config/locales/blacklight.de.yml @@ -104,6 +104,13 @@ de: count: Numerisch ordnen index: A-Z Ordnen title: Suche beschränken + range: + form: + start_label: "%{field_label} Bereichsanfang" + end_label: "%{field_label} Bereichsende" + submit: 'Anwenden' + single_value: '%{begin}' + range_value: '%{begin} bis %{end}' filters: label: "%{label}:" remove: diff --git a/config/locales/blacklight.en.yml b/config/locales/blacklight.en.yml index f091aabace..bd1b8d22d8 100644 --- a/config/locales/blacklight.en.yml +++ b/config/locales/blacklight.en.yml @@ -194,6 +194,13 @@ en: toggle: Toggle facets open: Show facets close: Hide facets + range: + form: + start_label: "%{field_label} range start" + end_label: "%{field_label} range end" + submit: 'Apply' + single_value: "%{start}" + range_value: "%{start} to %{end}" group: more: 'more »' filters: diff --git a/config/locales/blacklight.it.yml b/config/locales/blacklight.it.yml index 672556a8eb..b6d99df7ad 100644 --- a/config/locales/blacklight.it.yml +++ b/config/locales/blacklight.it.yml @@ -104,6 +104,13 @@ it: count: Ordina per numero index: Ordina A-Z title: Affina la ricerca + range: + form: + start_label: "%{field_label} da" + end_label: "%{field_label} a" + submit: 'Invia' + single_value: '%{begin}' + range_value: '%{begin} a %{end}' filters: label: "%{label}:" remove: diff --git a/lib/blacklight/configuration/facet_field.rb b/lib/blacklight/configuration/facet_field.rb index 63a4033311..ee7242878e 100644 --- a/lib/blacklight/configuration/facet_field.rb +++ b/lib/blacklight/configuration/facet_field.rb @@ -70,6 +70,7 @@ def normalize! blacklight_config = nil query.stringify_keys! if query normalize_pivot_config! if pivot + normalize_range_config! if range self.collapse = true if collapse.nil? self.show = true if show.nil? self.if = show if self.if.nil? @@ -98,5 +99,13 @@ def normalize_pivot_config! self.filter_class ||= Blacklight::SearchState::PivotFilterField self.filter_query_builder ||= Blacklight::SearchState::PivotFilterField::QueryBuilder end + + def normalize_range_config! + self.presenter ||= Blacklight::FacetFieldRangePresenter + self.item_presenter ||= Blacklight::FacetItemRangePresenter + self.component ||= Blacklight::FacetFieldListRangeComponent + self.filter_class ||= Blacklight::SearchState::RangeFilterField + self.solr_params = (solr_params || {}).merge({ 'facet.missing' => true }) + end end end diff --git a/lib/blacklight/search_state.rb b/lib/blacklight/search_state.rb index 0cf7f1b674..9621b68935 100644 --- a/lib/blacklight/search_state.rb +++ b/lib/blacklight/search_state.rb @@ -2,6 +2,7 @@ require 'blacklight/search_state/filter_field' require 'blacklight/search_state/pivot_filter_field' +require 'blacklight/search_state/range_filter_field' module Blacklight # This class encapsulates the search state as represented by the query diff --git a/lib/blacklight/search_state/range_filter_field.rb b/lib/blacklight/search_state/range_filter_field.rb new file mode 100644 index 0000000000..597813d452 --- /dev/null +++ b/lib/blacklight/search_state/range_filter_field.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module Blacklight + class SearchState + # Modeling access to filter query parameters + class RangeFilterField < FilterField + # this accessor is unnecessary after Blacklight 7.25.0 + attr_accessor :filters_key + + def initialize(config, search_state) + super + @filters_key = :range + end + + # @param [String,#value] a filter item to add to the url + # @return [Blacklight::SearchState] new state + def add(item) + new_state = search_state.reset_search + params = new_state.params + value = as_url_parameter(item) + + if value.is_a? Range + param_key = filters_key + params[param_key] = (params[param_key] || {}).dup + params[param_key][config.key] = { start: value.first, end: value.last } + new_state.reset(params) + else + super + end + end + + # @param [String,#value] a filter to remove from the url + # @return [Blacklight::SearchState] new state + def remove(item) + new_state = search_state.reset_search + params = new_state.params + value = as_url_parameter(item) + + if value.is_a? Range + param_key = filters_key + params[param_key] = (params[param_key] || {}).dup + params[param_key]&.delete(config.key) + new_state.reset(params) + else + super + end + end + + # @return [Array] an array of applied filters + def values(except: []) + params = search_state.params + param_key = filters_key + + range = if params.dig(param_key, config.key).is_a? Range + params.dig(param_key, config.key) + elsif params.dig(param_key, config.key).is_a? Hash + b_bound = params.dig(param_key, config.key, :start).presence + e_bound = params.dig(param_key, config.key, :end).presence + Range.new(b_bound&.to_i, e_bound&.to_i) if b_bound && e_bound + end + + f = except.include?(:filters) ? [] : [range].compact + f_missing = [Blacklight::SearchState::FilterField::MISSING] if params.dig(filters_key, "-#{key}")&.any? { |v| v == Blacklight::Engine.config.blacklight.facet_missing_param } + f_missing = [] if except.include?(:missing) + + f + (f_missing || []) + end + + # @param [String,#value] a filter to remove from the url + # @return [Boolean] whether the provided filter is currently applied/selected + delegate :include?, to: :values + + # @since Blacklight v7.25.2 + # normal filter fields demangle when they encounter a hash, which they assume to be a number-indexed map + # this filter should allow (expect) hashes if the keys include 'start' or 'end' + def permitted_params + { + filters_key => { config.key => [:start, :end], "-#{config.key}" => [] }, + inclusive_filters_key => { config.key => [:start, :end] } + } + end + end + end +end diff --git a/spec/components/blacklight/facet_field_list_range_component_spec.rb b/spec/components/blacklight/facet_field_list_range_component_spec.rb new file mode 100644 index 0000000000..678a246643 --- /dev/null +++ b/spec/components/blacklight/facet_field_list_range_component_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Blacklight::FacetFieldListRangeComponent, type: :component do + subject(:rendered) do + render_inline_to_capybara_node(described_class.new(facet_field: facet_field)) + end + + let(:facet_field) do + instance_double( + Blacklight::FacetFieldRangePresenter, + paginator: paginator, + facet_field: facet_config, + key: 'field', + label: 'My facet field', + active?: false, + collapsed?: false, + modal_path: nil, + selected_range: nil, + selected_item: nil, + missing_selected?: false, + search_state: Blacklight::SearchState.new({}, nil) + ) + end + + let(:facet_config) do + Blacklight::Configuration::NullField.new( + key: 'field', + item_component: Blacklight::FacetItemComponent, + item_presenter: Blacklight::FacetItemRangePresenter + ) + end + + let(:paginator) { instance_double(Blacklight::FacetPaginator, items: items) } + let(:items) { [] } + + it 'renders into the default facet layout' do + expect(rendered).to have_selector('h3', text: 'My facet field') + expect(rendered).to have_selector '.facet-content.collapse' + end + + it 'renders a form for the range' do + expect(rendered).to have_selector('form[action="http://test.host/catalog"][method="get"]') + expect(rendered).to have_field('range[field][start]') + expect(rendered).to have_field('range[field][end]') + end + + it 'does not render the missing link if there are no matching documents' do + expect(rendered).not_to have_link '[Missing]' + end + + context 'with missing documents' do + let(:items) do + [ + Blacklight::Solr::Response::Facets::FacetItem.new( + value: Blacklight::SearchState::FilterField::MISSING, + hits: 50 + ) + ] + end + + it 'renders a facet value for the documents that are missing the field data' do + expected_facet_query_param = Regexp.new(Regexp.escape({ f: { '-field': ['[* TO *]'] } }.to_param)) + expect(rendered).to have_link '[Missing]', href: expected_facet_query_param + end + end +end diff --git a/spec/components/blacklight/facet_field_list_range_form_component_spec.rb b/spec/components/blacklight/facet_field_list_range_form_component_spec.rb new file mode 100644 index 0000000000..8d236cd671 --- /dev/null +++ b/spec/components/blacklight/facet_field_list_range_form_component_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Blacklight::FacetFieldListRangeFormComponent, type: :component do + subject(:rendered) do + render_inline_to_capybara_node(described_class.new(facet_field: facet_field)) + end + + let(:selected_range) { nil } + let(:search_params) { { another_field: 'another_value' } } + + let(:facet_field) do + instance_double( + Blacklight::FacetFieldRangePresenter, + paginator: paginator, + facet_field: facet_config, + key: 'field', + label: 'My facet field', + active?: false, + collapsed?: false, + modal_path: nil, + selected_range: selected_range, + selected_item: nil, + missing_selected?: false, + search_state: Blacklight::SearchState.new(search_params, nil) + ) + end + + let(:facet_config) do + Blacklight::Configuration::NullField.new( + key: 'field', + item_component: Blacklight::FacetItemComponent, + item_presenter: Blacklight::FacetItemRangePresenter + ) + end + + let(:paginator) { instance_double(Blacklight::FacetPaginator, items: items) } + let(:items) { [] } + + it 'renders a form with no selected range' do + expect(rendered).to have_selector('form[action="http://test.host/catalog"][method="get"]') + expect(rendered).to have_field('range[field][start]', type: 'number') { |e| e['value'].blank? } + expect(rendered).to have_field('range[field][end]', type: 'number') { |e| e['value'].blank? } + expect(rendered).to have_field('another_field', type: 'hidden', with: 'another_value', visible: :hidden) + end + + it 'renders submit controls without a name to suppress from formData' do + anon_submit = rendered.find('input', visible: true) { |ele| ele[:type] == 'submit' && !ele[:'aria-hidden'] && !ele[:name] } + expect(anon_submit).to be_present + expect { rendered.find('input') { |ele| ele[:type] == 'submit' && ele[:name] } }.to raise_error(Capybara::ElementNotFound) + end + + context 'with range data' do + let(:selected_range) { (100..300) } + let(:search_params) do + { + another_field: 'another_value', + range: { + another_range: { start: 128, end: 1024 }, + field: { start: selected_range.first, end: selected_range.last } + } + } + end + + it 'renders a form for the selected range' do + expect(rendered).to have_selector('form[action="http://test.host/catalog"][method="get"]') + expect(rendered).to have_field('range[field][start]', type: 'number', with: selected_range.first) + expect(rendered).to have_field('range[field][end]', type: 'number', with: selected_range.last) + expect(rendered).to have_field('another_field', type: 'hidden', with: 'another_value', visible: :hidden) + expect(rendered).to have_field('range[another_range][start]', type: 'hidden', with: 128, visible: :hidden) + expect(rendered).to have_field('range[another_range][end]', type: 'hidden', with: 1024, visible: :hidden) + end + end + + context 'with configuration options for inputs' do + let(:facet_config) do + Blacklight::Configuration::NullField.new( + key: 'field', + range: { input: { placeholder: 'Year', max: 9999 } }, + item_component: Blacklight::FacetItemComponent, + item_presenter: Blacklight::FacetItemRangePresenter + ) + end + + it 'renders inputs with the options provided' do + expect(rendered).to have_field('range[field][start]', type: :number) do |e| + e['placeholder'] == 'Year' && e['max'] == '9999' + end + expect(rendered).to have_field('range[field][end]', type: 'number') do |e| + e['placeholder'] == 'Year' && e['max'] == '9999' + end + end + end +end diff --git a/spec/lib/blacklight/search_state/range_filter_field_spec.rb b/spec/lib/blacklight/search_state/range_filter_field_spec.rb new file mode 100644 index 0000000000..ece00f2423 --- /dev/null +++ b/spec/lib/blacklight/search_state/range_filter_field_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Blacklight::SearchState::RangeFilterField do + let(:search_state) { Blacklight::SearchState.new(params, blacklight_config, controller) } + + let(:param_values) { {} } + let(:params) { ActionController::Parameters.new(param_values) } + let(:blacklight_config) do + Blacklight::Configuration.new.configure do |config| + config.add_facet_field 'some_field', filter_class: described_class + config.filter_search_state_fields = true + end + end + let(:controller) { double } + let(:filter) { search_state.filter('some_field') } + + describe '#add' do + it 'adds a new range parameter' do + new_state = filter.add(1999..2099) + + expect(new_state.params.dig(:range, 'some_field')).to include start: 1999, end: 2099 + end + end + + context 'with some existing data' do + let(:param_values) { { range: { some_field: { start: '2013', end: '2022' } } } } + + describe '#add' do + it 'replaces the existing range' do + new_state = filter.add(1999..2099) + + expect(new_state.params.dig(:range, 'some_field')).to include start: 1999, end: 2099 + end + end + + describe '#remove' do + it 'removes the existing range' do + new_state = filter.remove(2013..2022) + + expect(new_state.params.dig(:range, 'some_field')).to be_blank + end + end + + describe '#values' do + it 'converts the parameters to a Range' do + expect(filter.values).to eq [2013..2022] + end + end + + describe '#include?' do + it 'compares the provided value to the parameter values' do + expect(filter.include?(2013..2022)).to be true + expect(filter.include?(1234..2345)).to be false + end + end + + describe '#permitted_params' do + let(:rails_params) { ActionController::Parameters.new(param_values) } + let(:blacklight_params) { Blacklight::Parameters.new(rails_params, search_state) } + let(:permitted_params) { blacklight_params.permit_search_params.to_h } + + it 'sanitizes single start/end values as scalars' do + expect(permitted_params.dig(:range, 'some_field')).to include 'start' => '2013', 'end' => '2022' + end + end + end + + context 'with empty data' do + let(:param_values) { { range: { some_field: { start: '', end: '' } } } } + + describe '#values' do + it 'drops the empty range' do + expect(filter.values).to be_empty + end + end + end + + context 'with missing data' do + let(:param_values) { { range: { '-some_field': ['[* TO *]'] } } } + + describe '#values' do + it 'uses the missing special value' do + expect(filter.values).to eq [Blacklight::SearchState::FilterField::MISSING] + end + end + end +end diff --git a/spec/presenters/blacklight/facet_field_range_presenter_spec.rb b/spec/presenters/blacklight/facet_field_range_presenter_spec.rb new file mode 100644 index 0000000000..edffc78c2c --- /dev/null +++ b/spec/presenters/blacklight/facet_field_range_presenter_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Blacklight::FacetFieldRangePresenter, type: :presenter do + subject(:presenter) do + described_class.new(facet_field, display_facet, view_context, search_state) + end + + let(:facet_field) do + Blacklight::Configuration::FacetField.new( + key: 'field_key', + field: 'some_field', + filter_class: Blacklight::SearchState::RangeFilterField + ) + end + + let(:display_facet) do + instance_double(Blacklight::Solr::Response::Facets::FacetField, items: items, sort: :index, offset: 0, prefix: nil, response: response) + end + let(:response) { instance_double(Blacklight::Solr::Response, total: 12) } + + let(:view_context) { controller.view_context } + let(:search_state) { Blacklight::SearchState.new(params, blacklight_config, view_context) } + + let(:blacklight_config) do + Blacklight::Configuration.new.tap { |x| x.facet_fields['field_key'] = facet_field } + end + + let(:params) { {} } + let(:items) { [] } + + describe '#paginator' do + subject(:paginator) { presenter.paginator } + + context 'when no range is selected' do + let(:items) { [Blacklight::Solr::Response::Facets::FacetItem.new(missing: true, value: '[Missing]')] } + + it 'contains [Missing] facet' do + expect(paginator.total_count).to be 1 + expect(paginator.items.first.value).to be '[Missing]' + end + end + + context 'with a user selected range' do + let(:params) { { range: { field_key: { start: 100, end: 250 } } } } + + it 'contains selected facet' do + expect(paginator.total_count).to be 1 + expect(paginator.items.first.value).to eql 100..250 + end + end + end + + describe '#missing_selected?' do + context 'when missing facet is selected' do + let(:params) { { range: { '-field_key' => ['[* TO *]'] } } } + + it 'returns true' do + expect(presenter.missing_selected?).to be true + end + end + + it 'returns false if missing facet not selected' do + expect(presenter.missing_selected?).to be false + end + end + + describe '#selected_range' do + it 'returns nil if no range is selected' do + expect(presenter.selected_range).to be_nil + end + + context 'with a user-selected range' do + let(:params) { { range: { field_key: { start: 100, end: 250 } } } } + + it 'returns the selected range' do + expect(presenter.selected_range).to eq 100..250 + end + end + end +end diff --git a/spec/presenters/blacklight/facet_item_range_presenter_spec.rb b/spec/presenters/blacklight/facet_item_range_presenter_spec.rb new file mode 100644 index 0000000000..9abd7d714a --- /dev/null +++ b/spec/presenters/blacklight/facet_item_range_presenter_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Blacklight::FacetItemRangePresenter, type: :presenter do + subject(:presenter) do + described_class.new(facet_item, facet_config, view_context, facet_field, search_state) + end + + let(:facet_item) { instance_double(Blacklight::Solr::Response::Facets::FacetItem) } + let(:filter_field) { instance_double(Blacklight::SearchState::FilterField, include?: true) } + let(:facet_config) { Blacklight::Configuration::FacetField.new(key: 'key') } + let(:facet_field) { instance_double(Blacklight::Solr::Response::Facets::FacetField) } + let(:view_context) { controller.view_context } + let(:search_state) { instance_double(Blacklight::SearchState, filter: filter_field) } + + describe '#label' do + context 'with a single value' do + let(:facet_item) { 'blah' } + + it 'uses the normal logic for item values' do + expect(presenter.label).to eq 'blah' + end + end + + context 'with a range' do + let(:facet_item) { 1234..2345 } + + it 'translates the range into some nice, human-readable html' do + expect(Capybara.string(presenter.label)).to have_text('1234 to 2345') + end + end + + context 'with a range that has the same start + end values' do + let(:facet_item) { 2021..2021 } + + it 'translates the range into some nice, human-readable html' do + expect(Capybara.string(presenter.label)).to have_text('2021') + end + end + end +end