Bootstrap InputPicker tests

This commit is contained in:
Adam Rutkowski 2023-05-29 15:18:14 +02:00
parent b95b957772
commit edf11ee958
6 changed files with 216 additions and 58 deletions

View File

@ -16,7 +16,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.InputPicker do
end
attr(:placeholder, :string, default: "Select option or search by typing")
attr(:id, :any, default: nil)
attr(:id, :any, required: true)
attr(:options, :list, required: true)
attr(:submit_name, :string, required: true)
attr(:display_value, :string, default: "")
@ -177,35 +177,39 @@ defmodule PlausibleWeb.Live.FunnelSettings.InputPicker do
def handle_event("search", %{"_target" => [target]} = params, socket) do
input = params[target]
input_len = String.length(input)
input_len = input |> String.trim() |> String.length()
if input_len > 0 do
suggestions =
socket.assigns.options
|> Enum.reject(fn {_, value} ->
input_len > String.length(value)
end)
|> Enum.sort_by(
fn {_, value} ->
if value == input do
3
else
input = String.downcase(input)
value = String.downcase(value)
weight = if String.contains?(value, input), do: 1, else: 0
weight + String.jaro_distance(value, input)
end
end,
:desc
)
|> Enum.take(@max_options_displayed)
suggestions = suggest(input, socket.assigns.options)
{:noreply, assign(socket, %{suggestions: suggestions})}
else
{:noreply, socket}
end
end
def suggest(input, options) do
input_len = String.length(input)
options
|> Enum.reject(fn {_, value} ->
input_len > String.length(value)
end)
|> Enum.sort_by(
fn {_, value} ->
if value == input do
3
else
input = String.downcase(input)
value = String.downcase(value)
weight = if String.contains?(value, input), do: 1, else: 0
weight + String.jaro_distance(value, input)
end
end,
:desc
)
|> Enum.take(@max_options_displayed)
end
defp do_select(socket, submit_value, display_value) do
id = socket.assigns.id

View File

@ -76,7 +76,7 @@ defmodule Plausible.MixProject do
{:excoveralls, "~> 0.10", only: :test},
{:exvcr, "~> 0.11", only: :test},
{:finch, "~> 0.14.0", override: true},
{:floki, "~> 0.32.0", only: :test},
{:floki, "~> 0.32.0", only: [:dev, :test]},
{:fun_with_flags, "~> 1.9.0"},
{:fun_with_flags_ui, "~> 0.8"},
{:locus, "~> 2.3"},

View File

@ -3,6 +3,7 @@ defmodule PlausibleWeb.ErrorReportControllerTest do
use Bamboo.Test
import Phoenix.View
import Plausible.Test.Support.HTML
alias PlausibleWeb.Endpoint
alias PlausibleWeb.ErrorView
@ -144,25 +145,4 @@ defmodule PlausibleWeb.ErrorReportControllerTest do
end
end
end
defp form_exists?(html, action_path) do
element_exists?(html, "form[action=\"" <> action_path <> "\"]")
end
defp element_exists?(html, selector) do
html
|> find(selector)
|> Enum.empty?()
|> Kernel.not()
end
defp submit_button(html, form) do
find(html, "#{form} button[type=\"submit\"]")
end
defp find(html, value) do
html
|> Floki.parse_document!()
|> Floki.find(value)
end
end

View File

@ -0,0 +1,157 @@
defmodule PlausibleWeb.Live.FunnelSettings.InputPickerTest do
use PlausibleWeb.ConnCase, async: true
import Phoenix.LiveViewTest
import Plausible.Test.Support.HTML
alias PlausibleWeb.Live.FunnelSettings.InputPicker
@ul "ul#dropdown-test-component[x-show=isOpen][x-ref=suggestions]"
defp suggestion_li(idx) do
~s/#{@ul} li#dropdown-test-component-option-#{idx - 1}/
end
describe "static" do
test "renders suggestions" do
assert doc = render_sample_component(new_options(10))
assert element_exists?(
doc,
~s/input#test-component[name="display-test-component"][phx-change="search"]/
)
assert element_exists?(doc, @ul)
for i <- 1..10 do
assert element_exists?(doc, suggestion_li(i))
end
end
test "renders up to 15 suggestions by default" do
assert doc = render_sample_component(new_options(20))
assert element_exists?(doc, suggestion_li(14))
assert element_exists?(doc, suggestion_li(15))
refute element_exists?(doc, suggestion_li(16))
refute element_exists?(doc, suggestion_li(17))
end
test "search suggestions: favours exact match" do
options = fake_options(["yellow", "hello", "cruel hello world"])
assert [{_, "hello"}, {_, "cruel hello world"}, {_, "yellow"}] =
InputPicker.suggest("hello", options)
end
test "search suggestions: skips entries shorter than input" do
options = fake_options(["yellow", "hello", "cruel hello world"])
assert [{_, "cruel hello world"}] = InputPicker.suggest("cruel hello", options)
end
test "search suggesions: favours similiarity" do
options = fake_options(["melon", "hello", "yellow"])
assert [{_, "hello"}, {_, "yellow"}, {_, "melon"}] = InputPicker.suggest("hell", options)
end
test "search suggesions: allows fuzzy matching" do
options = fake_options(["/url/0xC0FFEE", "/url/0xDEADBEEF", "/url/other"])
assert [{_, "/url/0xC0FFEE"}, {_, "/url/0xDEADBEEF"}, {_, "/url/other"}] =
InputPicker.suggest("0x FF", options)
end
end
@step1_input "input#step-1"
describe "integration - live rendering" do
setup [:create_user, :log_in, :create_site]
test "search reacts to the input, the user types in", %{conn: conn, site: site} do
setup_goals(site, ["Hello World", "Plausible", "Another World"])
lv = get_liveview(conn, site)
doc =
lv
|> element(@step1_input)
|> render_change(%{
"_target" => ["display-step-1"],
"display-step-1" => "hello"
})
assert text_of_element(doc, "#dropdown-step-1-option-0") == "Hello World"
doc =
lv
|> element(@step1_input)
|> render_change(%{
"_target" => ["display-step-1"],
"display-step-1" => "plausible"
})
assert text_of_element(doc, "#dropdown-step-1-option-0") == "Plausible"
end
test "selecting an option prefills submit value", %{conn: conn, site: site} do
{:ok, [_, _, g3]} = setup_goals(site, ["Hello World", "Plausible", "Another World"])
lv = get_liveview(conn, site)
doc =
lv
|> element(@step1_input)
|> render_change(%{
"_target" => ["display-step-1"],
"display-step-1" => "another"
})
refute element_exists?(doc, ~s/input[type="hidden"][value="#{g3.id}"]/)
lv
|> element("li#dropdown-step-1-option-0 a")
|> render_click()
rendered =
lv
|> element("#submit-step-1")
|> render()
assert element_exists?(rendered, ~s/input[type="hidden"][value="#{g3.id}"]/)
end
end
defp render_sample_component(options) do
render_component(InputPicker,
options: options,
submit_name: "test-submit-name",
id: "test-component"
)
end
defp new_options(n) do
Enum.map(1..n, &{&1, "TestOption #{&1}"})
end
defp get_liveview(conn, site) do
conn = assign(conn, :live_module, PlausibleWeb.Live.FunnelSettings)
{:ok, lv, _html} = live(conn, "/#{site.domain}/settings/funnels")
lv |> element(~s/button[phx-click="add-funnel"]/) |> render_click()
lv |> element("form") |> render_change(%{funnel: %{name: "My test funnel"}})
lv
end
defp setup_goals(site, goal_names) when is_list(goal_names) do
goals =
Enum.map(goal_names, fn goal_name ->
{:ok, g} = Plausible.Goals.create(site, %{"event_name" => goal_name})
g
end)
{:ok, goals}
end
defp fake_options(option_names) do
option_names
|> Enum.shuffle()
|> Enum.with_index(fn element, index -> {index, element} end)
end
end

View File

@ -1,6 +1,7 @@
defmodule PlausibleWeb.FunnelSettingsTest do
defmodule PlausibleWeb.Live.FunnelSettingsTest do
use PlausibleWeb.ConnCase, async: true
import Phoenix.LiveViewTest
import Plausible.Test.Support.HTML
describe "GET /:website/settings/funnels" do
setup [:create_user, :log_in, :create_site]
@ -223,17 +224,4 @@ defmodule PlausibleWeb.FunnelSettingsTest do
{:ok, lv, _html} = live(conn, "/#{site.domain}/settings/funnels")
lv
end
defp element_exists?(html, selector) do
html
|> find(selector)
|> Enum.empty?()
|> Kernel.not()
end
defp find(html, value) do
html
|> Floki.parse_document!()
|> Floki.find(value)
end
end

29
test/support/html.ex Normal file
View File

@ -0,0 +1,29 @@
defmodule Plausible.Test.Support.HTML do
def element_exists?(html, selector) do
html
|> find(selector)
|> Enum.empty?()
|> Kernel.not()
end
def find(html, value) do
html
|> Floki.parse_document!()
|> Floki.find(value)
end
def submit_button(html, form) do
find(html, "#{form} button[type=\"submit\"]")
end
def form_exists?(html, action_path) do
element_exists?(html, "form[action=\"" <> action_path <> "\"]")
end
def text_of_element(html, element) do
html
|> find(element)
|> Floki.text()
|> String.trim()
end
end