Back to Repositories

Testing Misspelling Tolerance Configuration in Searchkick

This test suite validates misspelling handling functionality in the Searchkick gem, focusing on various configurations and edge cases for fuzzy matching and search behavior. It ensures robust search capabilities even when users make typing errors or use approximate spellings.

Test Coverage Overview

The test suite provides comprehensive coverage of Searchkick’s misspelling handling features:

  • Basic misspelling tolerance controls
  • Distance-based matching configuration
  • Prefix length handling for partial matches
  • Field-specific misspelling settings
  • Below threshold functionality
  • Multiple field search scenarios

Implementation Analysis

The testing approach utilizes Minitest framework with a systematic verification of search behaviors:

  • Mock data storage and retrieval patterns
  • Assertion-based validation of search results
  • Configurable search parameters testing
  • Edge case handling for different server versions

Technical Details

Key technical components include:

  • Minitest as the testing framework
  • Custom assert_search and assert_misspellings helpers
  • Version-specific compatibility checks
  • Integration with Elasticsearch/OpenSearch backends

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases for specific functionality
  • Comprehensive edge case coverage
  • Clear test naming conventions
  • Proper test setup and data preparation
  • Version-aware testing logic

ankane/searchkick

test/misspellings_test.rb

            
require_relative "test_helper"

class MisspellingsTest < Minitest::Test
  def test_false
    store_names ["abc", "abd", "aee"]
    assert_search "abc", ["abc"], misspellings: false
  end

  def test_distance
    store_names ["abbb", "aabb"]
    assert_search "aaaa", ["aabb"], misspellings: {distance: 2}
  end

  def test_prefix_length
    store_names ["ap", "api", "apt", "any", "nap", "ah", "ahi"]
    if prefix_length_misspellings_off?
      assert_search "ap", ["ap"], misspellings: {prefix_length: 2}
    else
      assert_search "ap", ["ap", "api", "apt"], misspellings: {prefix_length: 2}
    end
    assert_search "api", ["ap", "api", "apt"], misspellings: {prefix_length: 2}
  end

  def test_prefix_length_operator
    store_names ["ap", "api", "apt", "any", "nap", "ah", "aha"]
    if prefix_length_misspellings_off?
      assert_search "ap ah", ["ap", "ah"], operator: "or", misspellings: {prefix_length: 2}
    else
      assert_search "ap ah", ["ap", "ah", "api", "apt", "aha"], operator: "or", misspellings: {prefix_length: 2}
    end
    assert_search "api ahi", ["ap", "api", "apt", "ah", "aha"], operator: "or", misspellings: {prefix_length: 2}
  end

  def test_fields_operator
    store [
      {name: "red", color: "red"},
      {name: "blue", color: "blue"},
      {name: "cyan", color: "blue green"},
      {name: "magenta", color: "red blue"},
      {name: "green", color: "green"}
    ]
    assert_search "red blue", ["red", "blue", "cyan", "magenta"], operator: "or", fields: ["color"], misspellings: false
  end

  def test_below_unmet
    store_names ["abc", "abd", "aee"]
    assert_search "abc", ["abc", "abd"], misspellings: {below: 2}
  end

  def test_below_unmet_result
    store_names ["abc", "abd", "aee"]
    assert Product.search("abc", misspellings: {below: 2}).misspellings?
  end

  def test_below_met
    store_names ["abc", "abd", "aee"]
    assert_search "abc", ["abc"], misspellings: {below: 1}
  end

  def test_below_met_result
    store_names ["abc", "abd", "aee"]
    assert !Product.search("abc", misspellings: {below: 1}).misspellings?
  end

  def test_field_correct_spelling_still_works
    store [{name: "Sriracha", color: "blue"}]
    assert_misspellings "Sriracha", ["Sriracha"], {fields: [:name, :color]}
    assert_misspellings "blue", ["Sriracha"], {fields: [:name, :color]}
  end

  def test_field_enabled
    store [{name: "Sriracha", color: "blue"}]
    assert_misspellings "siracha", ["Sriracha"], {fields: [:name]}
    assert_misspellings "clue", ["Sriracha"], {fields: [:color]}
  end

  def test_field_disabled
    store [{name: "Sriracha", color: "blue"}]
    assert_misspellings "siracha", [], {fields: [:color]}
    assert_misspellings "clue", [], {fields: [:name]}
  end

  def test_field_with_transpositions
    store [{name: "Sriracha", color: "blue"}]
    assert_misspellings "lbue", [], {transpositions: false, fields: [:color]}
  end

  def test_field_with_edit_distance
    store [{name: "Sriracha", color: "blue"}]
    assert_misspellings "crue", ["Sriracha"], {edit_distance: 2, fields: [:color]}
  end

  def test_field_multiple
    store [
      {name: "Greek Yogurt", color: "white"},
      {name: "Green Onions", color: "yellow"}
    ]
    assert_misspellings "greed", ["Greek Yogurt", "Green Onions"], {fields: [:name, :color]}
    assert_misspellings "mellow", ["Green Onions"], {fields: [:name, :color]}
  end

  def test_field_requires_explicit_search_fields
    store_names ["Sriracha"]
    assert_raises(ArgumentError) do
      assert_search "siracha", ["Sriracha"], {misspellings: {fields: [:name]}}
    end
  end

  def test_field_word_start
    store_names ["Sriracha"]
    assert_search "siracha", ["Sriracha"], fields: [{name: :word_middle}], misspellings: {fields: [:name]}
  end

  private

  def prefix_length_misspellings_off?
    Searchkick.opensearch? ? Searchkick.server_below?("2.0.0", true) : Searchkick.server_below?("8.0.0")
  end
end