add LIKE substring fallback to search and update plan statuses

FTS5 prefix matching misses mid-word substrings (e.g. "ebook" in
"notebook"). When FTS5 returns zero results, fall back to LIKE
query on title and category with proper wildcard escaping. 4 new
tests, 757 total.

Also marks completed plan files (search, admin-redesign,
setup-wizard, products-context) with correct status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-13 09:09:10 +00:00
parent 57c3ba0e28
commit edcbc596e3
7 changed files with 78 additions and 15 deletions

View File

@@ -169,18 +169,24 @@ defmodule SimpleshopTheme.SearchTest do
describe "index_product/1" do
test "indexes a single product", %{ocean: ocean} do
# Clear and rebuild without ocean
# Clear FTS index
SimpleshopTheme.Repo.query!("DELETE FROM products_search_map")
SimpleshopTheme.Repo.query!("DELETE FROM products_search")
assert Search.search("ocean") == []
# Verify FTS index is empty
%{rows: rows} = SimpleshopTheme.Repo.query!("SELECT COUNT(*) FROM products_search_map")
assert rows == [[0]]
# Index just ocean
ocean = SimpleshopTheme.Repo.preload(ocean, [:variants])
Search.index_product(ocean)
# Verify ocean is now in the FTS index
%{rows: [[count]]} = SimpleshopTheme.Repo.query!("SELECT COUNT(*) FROM products_search_map")
assert count == 1
results = Search.search("ocean")
assert length(results) == 1
assert length(results) >= 1
assert hd(results).id == ocean.id
end
@@ -197,13 +203,41 @@ defmodule SimpleshopTheme.SearchTest do
end
end
describe "LIKE fallback" do
test "finds substring matches that FTS5 prefix misses", %{ocean: ocean} do
# "ebook" is in the middle of "Notebook" — FTS5 prefix won't match
results = Search.search("ebook")
assert Enum.any?(results, &(&1.id == ocean.id))
end
test "falls back to category substring match", %{forest: forest} do
# "ppar" is a substring of "Apparel" — FTS5 prefix won't match
results = Search.search("pparel")
assert Enum.any?(results, &(&1.id == forest.id))
end
end
describe "remove_product/1" do
test "removes a product from the index", %{ocean: ocean} do
assert Search.search("ocean") != []
# Verify ocean is in the FTS index
%{rows: [[rowid]]} =
SimpleshopTheme.Repo.query!(
"SELECT rowid FROM products_search_map WHERE product_id = ?1",
[ocean.id]
)
assert rowid
Search.remove_product(ocean.id)
assert Search.search("ocean") == []
# Verify it's gone from the FTS index
%{rows: rows} =
SimpleshopTheme.Repo.query!(
"SELECT rowid FROM products_search_map WHERE product_id = ?1",
[ocean.id]
)
assert rows == []
end
test "is a no-op for unindexed product" do