Optimize autocomplete to ignore various types of bogus input that will never match anything. It turns out it's not uncommon for people to do things like paste random URLs into autocomplete, or hold down keys, or enter long strings of gibberish text (sometimes in other languages). Some things, like autocorrect and slash abbreviations, become pathologically slow when fed certain types of bad input. Autocomplete will abort and return nothing in the following situations: * Searching for URLs (tags that start with http:// or https://). * Overly long tags (strings longer than the 170 char tag name limit). * Slash abbreviations longer than 10 chars (e.g. typing `/qwoijqoiqogirqewgoi`). * Slash abbreviations that aren't alphanumeric (e.g. typing `/////////`). * Autocorrect input that contains too much punctuation and not enough actual letters.
211 lines
9.4 KiB
Ruby
211 lines
9.4 KiB
Ruby
require 'test_helper'
|
|
|
|
class AutocompleteServiceTest < ActiveSupport::TestCase
|
|
def autocomplete(query, type, **options)
|
|
results = AutocompleteService.new(query, type, **options).autocomplete_results
|
|
results.map { |r| r[:value] }
|
|
end
|
|
|
|
def assert_autocomplete_includes(expected_value, query, type, **options)
|
|
assert_includes(autocomplete(query, type, **options), expected_value)
|
|
end
|
|
|
|
def assert_autocomplete_equals(expected_value, query, type, **options)
|
|
assert_equal(expected_value, autocomplete(query, type, **options))
|
|
end
|
|
|
|
context "#autocomplete method" do
|
|
should "autocomplete artists" do
|
|
create(:artist, name: "bkub")
|
|
assert_autocomplete_includes("bkub", "bk", :artist)
|
|
end
|
|
|
|
should "autocomplete wiki pages" do
|
|
create(:wiki_page, title: "help:home")
|
|
assert_autocomplete_includes("help:home", "help", :wiki_page)
|
|
end
|
|
|
|
should "autocomplete users" do
|
|
@user = create(:user, name: "fumimi")
|
|
|
|
as(@user) do
|
|
assert_autocomplete_includes("fumimi", "fu", :user)
|
|
assert_autocomplete_includes("@fumimi", "fu", :mention)
|
|
assert_autocomplete_includes("user:fumimi", "user:fu", :tag_query)
|
|
end
|
|
end
|
|
|
|
should "autocomplete pools" do
|
|
as(create(:user)) do
|
|
create(:pool, name: "Disgustingly_Adorable")
|
|
end
|
|
|
|
assert_autocomplete_includes("Disgustingly_Adorable", "disgust", :pool)
|
|
assert_autocomplete_includes("pool:Disgustingly_Adorable", "pool:disgust", :tag_query)
|
|
assert_autocomplete_includes("pool:Disgustingly_Adorable", "-pool:disgust", :tag_query)
|
|
end
|
|
|
|
should "autocomplete favorite groups" do
|
|
user = create(:user)
|
|
create(:favorite_group, name: "Stuff", creator: user)
|
|
|
|
assert_autocomplete_equals(["Stuff"], "stu", :favorite_group, current_user: user)
|
|
assert_autocomplete_equals([], "stu", :favorite_group, current_user: User.anonymous)
|
|
|
|
assert_autocomplete_equals(["favgroup:Stuff"], "favgroup:stu", :tag_query, current_user: user)
|
|
assert_autocomplete_equals([], "favgroup:stu", :tag_query, current_user: User.anonymous)
|
|
end
|
|
|
|
should "autocomplete saved search labels" do
|
|
user = create(:user)
|
|
create(:saved_search, query: "bkub", labels: ["artists"], user: user)
|
|
|
|
assert_autocomplete_equals(["artists"], "art", :saved_search_label, current_user: user)
|
|
assert_autocomplete_equals([], "art", :saved_search_label, current_user: User.anonymous)
|
|
|
|
assert_autocomplete_equals(["search:artists"], "search:art", :tag_query, current_user: user)
|
|
assert_autocomplete_equals([], "search:art", :saved_search_label, current_user: User.anonymous)
|
|
end
|
|
|
|
should "autocomplete single tags" do
|
|
create(:tag, name: "touhou")
|
|
assert_autocomplete_includes("touhou", "tou", :tag)
|
|
end
|
|
|
|
context "for a tag search" do
|
|
should "autocomplete tags" do
|
|
create(:tag, name: "touhou")
|
|
|
|
assert_autocomplete_includes("touhou", "tou", :tag_query)
|
|
assert_autocomplete_includes("touhou", "TOU", :tag_query)
|
|
assert_autocomplete_includes("touhou", "-tou", :tag_query)
|
|
assert_autocomplete_includes("touhou", "~tou", :tag_query)
|
|
end
|
|
|
|
context "for a tag abbreviation" do
|
|
should "autocomplete abbreviations" do
|
|
create(:tag, name: "mole", post_count: 150)
|
|
create(:tag, name: "mole_under_eye", post_count: 100)
|
|
create(:tag, name: "mole_under_mouth", post_count: 50)
|
|
create(:tag, name: "x_x_x_x_x_x_x_x_x_x_x_x", post_count: 1)
|
|
|
|
assert_autocomplete_equals(%w[mole mole_under_eye mole_under_mouth], "/m", :tag_query)
|
|
assert_autocomplete_equals(%w[mole_under_eye mole_under_mouth], "/mu", :tag_query)
|
|
assert_autocomplete_equals(%w[mole_under_mouth], "/mum", :tag_query)
|
|
assert_autocomplete_equals(%w[mole_under_eye], "/mue", :tag_query)
|
|
assert_autocomplete_equals(%w[mole_under_eye], "/*ue", :tag_query)
|
|
assert_autocomplete_equals(%w[mole_under_eye], "/MUE", :tag_query)
|
|
|
|
assert_autocomplete_includes("mole_under_eye", "-/mue", :tag_query)
|
|
assert_autocomplete_includes("mole_under_eye", "~/mue", :tag_query)
|
|
|
|
assert_autocomplete_equals([], "/xxxxxxxxxx", :tag_query)
|
|
assert_autocomplete_equals([], "/_", :tag_query)
|
|
end
|
|
|
|
should "list aliases before abbreviations" do
|
|
create(:tag, name: "hair_ribbon", post_count: 300_000)
|
|
create(:tag, name: "hakurei_reimu", post_count: 50_000)
|
|
create(:tag_alias, antecedent_name: "/hr", consequent_name: "hakurei_reimu")
|
|
|
|
assert_autocomplete_equals(%w[hakurei_reimu hair_ribbon], "/hr", :tag_query)
|
|
end
|
|
end
|
|
|
|
should "autocomplete tags from wiki and artist other names" do
|
|
create(:tag, name: "touhou")
|
|
create(:tag, name: "bkub", category: Tag.categories.artist)
|
|
create(:wiki_page, title: "touhou", other_names: %w[東方 东方 동방])
|
|
create(:artist, name: "bkub", other_names: %w[大川ぶくぶ フミンバイン])
|
|
|
|
assert_autocomplete_equals(["touhou"], "東", :tag_query)
|
|
assert_autocomplete_equals(["touhou"], "东", :tag_query)
|
|
assert_autocomplete_equals(["touhou"], "동", :tag_query)
|
|
|
|
assert_autocomplete_equals([], "*東*", :tag_query)
|
|
assert_autocomplete_equals([], "東*", :tag_query)
|
|
assert_autocomplete_equals([], "*東", :tag_query)
|
|
|
|
assert_autocomplete_equals([], "*方*", :tag_query)
|
|
assert_autocomplete_equals([], "*方", :tag_query)
|
|
assert_autocomplete_equals([], "方", :tag_query)
|
|
|
|
assert_autocomplete_equals([], "*大*", :tag_query)
|
|
assert_autocomplete_equals(["bkub"], "大", :tag_query)
|
|
assert_autocomplete_equals([], "*大", :tag_query)
|
|
|
|
assert_autocomplete_equals([], "*川*", :tag_query)
|
|
assert_autocomplete_equals([], "*川", :tag_query)
|
|
assert_autocomplete_equals([], "川", :tag_query)
|
|
end
|
|
|
|
should "autocomplete wildcard searches" do
|
|
create(:tag, name: "mole", post_count: 150)
|
|
create(:tag, name: "mole_under_eye", post_count: 100)
|
|
create(:tag, name: "mole_under_mouth", post_count: 50)
|
|
|
|
assert_autocomplete_equals(%w[mole mole_under_eye mole_under_mouth], "mole*", :tag_query)
|
|
assert_autocomplete_equals(%w[mole_under_eye mole_under_mouth], "*under*", :tag_query)
|
|
assert_autocomplete_equals(%w[mole_under_eye], "*eye", :tag_query)
|
|
end
|
|
|
|
should "autocorrect misspelled tags" do
|
|
create(:tag, name: "touhou")
|
|
|
|
assert_autocomplete_equals(%w[touhou], "touhuo", :tag_query)
|
|
assert_autocomplete_equals(%w[], ".....", :tag_query)
|
|
assert_autocomplete_equals(%w[], "t___", :tag_query)
|
|
end
|
|
|
|
should "ignore unsupported metatags" do
|
|
assert_autocomplete_equals([], "date:2020", :tag_query)
|
|
assert_autocomplete_equals([], "score:20", :tag_query)
|
|
assert_autocomplete_equals([], "favcount:>20", :tag_query)
|
|
assert_autocomplete_equals([], "age:<1w", :tag_query)
|
|
assert_autocomplete_equals([], "limit:200", :tag_query)
|
|
end
|
|
|
|
should "autocomplete static metatags" do
|
|
assert_autocomplete_equals(["status:active"], "status:act", :tag_query)
|
|
assert_autocomplete_equals(["parent:active"], "parent:act", :tag_query)
|
|
assert_autocomplete_equals(["child:active"], "child:act", :tag_query)
|
|
|
|
assert_autocomplete_equals(["rating:safe"], "rating:s", :tag_query)
|
|
assert_autocomplete_equals(["rating:questionable"], "rating:q", :tag_query)
|
|
assert_autocomplete_equals(["rating:explicit"], "rating:e", :tag_query)
|
|
|
|
assert_autocomplete_equals(["locked:rating"], "locked:r", :tag_query)
|
|
assert_autocomplete_equals(["locked:status"], "locked:s", :tag_query)
|
|
assert_autocomplete_equals(["locked:note"], "locked:n", :tag_query)
|
|
|
|
assert_autocomplete_equals(["embedded:true"], "embedded:t", :tag_query)
|
|
assert_autocomplete_equals(["embedded:false"], "embedded:f", :tag_query)
|
|
|
|
assert_autocomplete_equals(["filetype:jpg"], "filetype:j", :tag_query)
|
|
assert_autocomplete_equals(["filetype:png"], "filetype:p", :tag_query)
|
|
assert_autocomplete_equals(["filetype:gif"], "filetype:g", :tag_query)
|
|
assert_autocomplete_equals(["filetype:swf"], "filetype:s", :tag_query)
|
|
assert_autocomplete_equals(["filetype:zip"], "filetype:z", :tag_query)
|
|
assert_autocomplete_equals(["filetype:webm"], "filetype:w", :tag_query)
|
|
assert_autocomplete_equals(["filetype:mp4"], "filetype:m", :tag_query)
|
|
|
|
assert_autocomplete_equals(["commentary:true"], "commentary:tru", :tag_query)
|
|
assert_autocomplete_equals(["commentary:false"], "commentary:fal", :tag_query)
|
|
assert_autocomplete_equals(["commentary:translated"], "commentary:trans", :tag_query)
|
|
assert_autocomplete_equals(["commentary:untranslated"], "commentary:untrans", :tag_query)
|
|
|
|
assert_autocomplete_equals(["disapproved:breaks_rules"], "disapproved:break", :tag_query)
|
|
assert_autocomplete_equals(["disapproved:poor_quality"], "disapproved:poor", :tag_query)
|
|
assert_autocomplete_equals(["disapproved:disinterest"], "disapproved:dis", :tag_query)
|
|
|
|
assert_autocomplete_equals(["order:score", "order:score_asc"], "order:sco", :tag_query)
|
|
end
|
|
|
|
should "ignore bogus tags" do
|
|
assert_autocomplete_equals([], "x"*200, :tag_query)
|
|
assert_autocomplete_equals([], "http://www.google.com", :tag_query)
|
|
end
|
|
end
|
|
end
|
|
end
|