mirror of
https://github.com/plausible/analytics.git
synced 2024-12-25 10:33:01 +03:00
43be271836
* Add hint to creatable ComboBoxes without suggestions available * Load external resources once in funnel settings * Load external resources once in goal settings * Make Custom Props Settings UI match Goal Settings * Remove unnecessary goals query This should be done only once in the live view * Remove funnels feature flag * fixup * Make the modal scrollable * By default, focus first suggestion for creatables * Update StaticSearch So it's capable of casting custom data structures into weighted items. Missing tests added. * Add Search + modal to funnel settings * Add sample props to seeds * Load all suggestions asynchronously, unless `Mix.env == :test` * ComboBox: Fix inconsistent suggestions We require "Create ..." element to be only focused when there are no suggestions available. This causes some issues, depending on the state, the least focusable index might be either 0 ("Create...") or 1. This patch addresses all the quirks with focus. * Fix ComboBox max results message So that AlpineJS doesn't think it's a focusable option. * Keep the state up to date when changing props * Add hint to creatable ComboBoxes without suggestions available * Load external resources once in funnel settings * Load external resources once in goal settings * Make Custom Props Settings UI match Goal Settings * Remove unnecessary goals query This should be done only once in the live view * Remove funnels feature flag * fixup * Make the modal scrollable * By default, focus first suggestion for creatables * Add sample props to seeds * Load all suggestions asynchronously, unless `Mix.env == :test` * ComboBox: Fix inconsistent suggestions We require "Create ..." element to be only focused when there are no suggestions available. This causes some issues, depending on the state, the least focusable index might be either 0 ("Create...") or 1. This patch addresses all the quirks with focus. * Fix ComboBox max results message So that AlpineJS doesn't think it's a focusable option. * Keep the state up to date when changing props * Fixup site_id * Fix typo * fixup
332 lines
11 KiB
Elixir
332 lines
11 KiB
Elixir
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]
|
|
|
|
test "lists funnels for the site and renders help link", %{conn: conn, site: site} do
|
|
{:ok, _} = setup_funnels(site)
|
|
conn = get(conn, "/#{site.domain}/settings/funnels")
|
|
|
|
resp = html_response(conn, 200)
|
|
assert resp =~ "Compose Goals into Funnels"
|
|
assert resp =~ "From blog to signup"
|
|
assert resp =~ "From signup to blog"
|
|
assert element_exists?(resp, "a[href=\"https://plausible.io/docs/funnel-analysis\"]")
|
|
end
|
|
|
|
test "search funnels input is rendered", %{conn: conn, site: site} do
|
|
setup_goals(site)
|
|
conn = get(conn, "/#{site.domain}/settings/funnels")
|
|
resp = html_response(conn, 200)
|
|
assert element_exists?(resp, ~s/input[type="text"]#filter-text/)
|
|
assert element_exists?(resp, ~s/form[phx-change="filter"]#filter-form/)
|
|
end
|
|
|
|
test "lists funnels with delete actions", %{conn: conn, site: site} do
|
|
{:ok, [f1_id, f2_id]} = setup_funnels(site)
|
|
conn = get(conn, "/#{site.domain}/settings/funnels")
|
|
|
|
resp = html_response(conn, 200)
|
|
|
|
assert element_exists?(
|
|
resp,
|
|
~s/button[phx-click="delete-funnel"][phx-value-funnel-id=#{f1_id}]#delete-funnel-#{f1_id}/
|
|
)
|
|
|
|
assert element_exists?(
|
|
resp,
|
|
~s/button[phx-click="delete-funnel"][phx-value-funnel-id=#{f2_id}]#delete-funnel-#{f2_id}/
|
|
)
|
|
end
|
|
|
|
test "if goals are present, Add Funnel button is rendered", %{conn: conn, site: site} do
|
|
{:ok, _} = setup_funnels(site)
|
|
conn = get(conn, "/#{site.domain}/settings/funnels")
|
|
resp = conn |> html_response(200)
|
|
assert element_exists?(resp, ~S/button[phx-click="add-funnel"]/)
|
|
end
|
|
|
|
test "if not enough goals are present, renders a hint to create goals + no search", %{
|
|
conn: conn,
|
|
site: site
|
|
} do
|
|
{:ok, _} = Plausible.Goals.create(site, %{"page_path" => "/go/to/blog/**"})
|
|
conn = get(conn, "/#{site.domain}/settings/funnels")
|
|
|
|
doc = conn |> html_response(200)
|
|
assert Floki.text(doc) =~ "You need to define at least two goals to create a funnel."
|
|
|
|
add_goals_path = Routes.site_path(conn, :settings_goals, site.domain)
|
|
assert element_exists?(doc, ~s/a[href="#{add_goals_path}"]/)
|
|
|
|
refute element_exists?(doc, ~s/input[type="text"]#filter-text/)
|
|
refute element_exists?(doc, ~s/form[phx-change="filter"]#filter-form/)
|
|
end
|
|
end
|
|
|
|
describe "FunnelSettings live view" do
|
|
setup [:create_user, :log_in, :create_site]
|
|
|
|
test "allows list filtering / search", %{conn: conn, site: site} do
|
|
{:ok, _} = setup_funnels(site, ["Funnel One", "Search Me"])
|
|
{lv, html} = get_liveview(conn, site, with_html?: true)
|
|
|
|
assert html =~ "Funnel One"
|
|
assert html =~ "Search Me"
|
|
|
|
html = type_into_search(lv, "search")
|
|
|
|
refute html =~ "Funnel One"
|
|
assert html =~ "Search Me"
|
|
end
|
|
|
|
test "allows resetting filter text via backspace icon", %{conn: conn, site: site} do
|
|
{:ok, _} = setup_funnels(site, ["Funnel One", "Another"])
|
|
{lv, html} = get_liveview(conn, site, with_html?: true)
|
|
|
|
refute element_exists?(html, ~s/svg[phx-click="reset-filter-text"]#reset-filter/)
|
|
|
|
html = type_into_search(lv, "one")
|
|
refute html =~ "Another"
|
|
|
|
assert element_exists?(html, ~s/svg[phx-click="reset-filter-text"]#reset-filter/)
|
|
|
|
html = lv |> element(~s/svg#reset-filter/) |> render_click()
|
|
|
|
assert html =~ "Funnel One"
|
|
assert html =~ "Another"
|
|
end
|
|
|
|
test "allows resetting filter text via no match link", %{conn: conn, site: site} do
|
|
{:ok, _} = setup_funnels(site)
|
|
lv = get_liveview(conn, site)
|
|
html = type_into_search(lv, "Definitely this is not going to render any matches")
|
|
|
|
assert html =~ "No funnels found for this site. Please refine or"
|
|
assert html =~ "reset your search"
|
|
|
|
assert element_exists?(html, ~s/a[phx-click="reset-filter-text"]#reset-filter-hint/)
|
|
html = lv |> element(~s/a#reset-filter-hint/) |> render_click()
|
|
|
|
refute html =~ "No funnels found for this site. Please refine or"
|
|
end
|
|
|
|
test "allows to delete funnels", %{conn: conn, site: site} do
|
|
{:ok, [f1_id, _f2_id]} = setup_funnels(site)
|
|
|
|
{lv, html} = get_liveview(conn, site, with_html?: true)
|
|
|
|
assert html =~ "From blog to signup"
|
|
assert html =~ "From signup to blog"
|
|
|
|
html = lv |> element(~s/button#delete-funnel-#{f1_id}/) |> render_click()
|
|
|
|
refute html =~ "From blog to signup"
|
|
assert html =~ "From signup to blog"
|
|
|
|
html = get(conn, "/#{site.domain}/settings/funnels") |> html_response(200)
|
|
|
|
refute html =~ "From blog to signup"
|
|
assert html =~ "From signup to blog"
|
|
end
|
|
|
|
test "renders the funnel form on clicking 'Add Funnel' button", %{conn: conn, site: site} do
|
|
setup_goals(site)
|
|
lv = get_liveview(conn, site)
|
|
doc = render_click(lv, "add-funnel")
|
|
|
|
assert element_exists?(
|
|
doc,
|
|
~s/form[phx-change="validate"][phx-submit="save"][phx-click-away="cancel-add-funnel"]/
|
|
)
|
|
|
|
assert element_exists?(doc, ~s/form input[type="text"][name="funnel[name]"]/)
|
|
|
|
assert element_exists?(
|
|
doc,
|
|
~s/input[type="hidden"][name="funnel[steps][][goal_id]"]#submit-step-1/
|
|
)
|
|
|
|
step_setup_controls = [
|
|
~s/input[type="hidden"][name="funnel[steps][][goal_id]"]#submit-step-1/,
|
|
~s/input[type="hidden"][name="funnel[steps][][goal_id]"]#submit-step-2/,
|
|
~s/input[type="text"][name="display-step-1"]#step-1/,
|
|
~s/input[type="text"][name="display-step-2"]#step-2/,
|
|
~s/a[phx-click="add-step"]/
|
|
]
|
|
|
|
Enum.each(step_setup_controls, &assert(element_exists?(doc, &1)))
|
|
end
|
|
|
|
test "clicking 'Add another step' adds a pair of inputs and renders remove step buttons", %{
|
|
conn: conn,
|
|
site: site
|
|
} do
|
|
setup_goals(site)
|
|
lv = get_liveview(conn, site)
|
|
|
|
lv |> element(~s/button[phx-click="add-funnel"]/) |> render_click()
|
|
|
|
assert lv = find_live_child(lv, "funnels-form")
|
|
lv |> element("form") |> render_change(%{funnel: %{name: "My test funnel"}})
|
|
doc = lv |> element(~s/a[phx-click="add-step"]/) |> render_click()
|
|
|
|
assert element_exists?(
|
|
doc,
|
|
~s/input[type="hidden"][name="funnel[steps][][goal_id]"]#submit-step-3/
|
|
)
|
|
|
|
assert element_exists?(doc, ~s/input[type="text"][name="display-step-1"]#step-1/)
|
|
|
|
assert element_exists?(
|
|
doc,
|
|
~s/svg#remove-step-1[phx-click="remove-step"][phx-value-step-idx="1"]/
|
|
)
|
|
|
|
assert element_exists?(
|
|
doc,
|
|
~s/svg#remove-step-2[phx-click="remove-step"][phx-value-step-idx="2"]/
|
|
)
|
|
|
|
assert element_exists?(
|
|
doc,
|
|
~s/svg#remove-step-3[phx-click="remove-step"][phx-value-step-idx="3"]/
|
|
)
|
|
end
|
|
|
|
test "clicking the 'remove step' button removes a step", %{site: site, conn: conn} do
|
|
setup_goals(site)
|
|
lv = get_liveview(conn, site)
|
|
|
|
lv |> element(~s/button[phx-click="add-funnel"]/) |> render_click()
|
|
assert lv = find_live_child(lv, "funnels-form")
|
|
lv |> element("form") |> render_change(%{funnel: %{name: "My test funnel"}})
|
|
lv |> element(~s/a[phx-click="add-step"]/) |> render_click()
|
|
|
|
doc = lv |> element(~s/#remove-step-2/) |> render_click()
|
|
|
|
assert element_exists?(doc, ~s/input#step-1/)
|
|
assert element_exists?(doc, ~s/input#step-3/)
|
|
refute element_exists?(doc, ~s/input#step-2/)
|
|
end
|
|
|
|
test "save button becomes active once at least two steps are selected", %{
|
|
conn: conn,
|
|
site: site
|
|
} do
|
|
setup_goals(site)
|
|
lv = get_liveview(conn, site)
|
|
lv |> element(~s/button[phx-click="add-funnel"]/) |> render_click()
|
|
|
|
assert lv = find_live_child(lv, "funnels-form")
|
|
|
|
lv
|
|
|> element("li#dropdown-step-1-option-1 a")
|
|
|> render_click()
|
|
|
|
doc =
|
|
lv
|
|
|> element("li#dropdown-step-2-option-1 a")
|
|
|> render_click()
|
|
|
|
save_inactive = ~s/form button#save.cursor-not-allowed/
|
|
save_active = ~s/form button#save[type="submit"]/
|
|
|
|
refute element_exists?(doc, save_active)
|
|
assert element_exists?(doc, save_inactive)
|
|
|
|
doc =
|
|
lv
|
|
|> element("form")
|
|
|> render_change(%{
|
|
funnel: %{
|
|
name: "My test funnel",
|
|
steps: [
|
|
%{goal_id: 1},
|
|
%{goal_id: 2}
|
|
]
|
|
}
|
|
})
|
|
|
|
assert element_exists?(doc, save_active)
|
|
refute element_exists?(doc, save_inactive)
|
|
end
|
|
|
|
test "funnel gets evaluated on every select, assuming a second has passed between selections",
|
|
%{
|
|
conn: conn,
|
|
site: site
|
|
} do
|
|
setup_goals(site)
|
|
lv = get_liveview(conn, site)
|
|
lv |> element(~s/button[phx-click="add-funnel"]/) |> render_click()
|
|
|
|
assert lv = find_live_child(lv, "funnels-form")
|
|
|
|
lv |> element("li#dropdown-step-1-option-1 a") |> render_click()
|
|
|
|
:timer.sleep(1001)
|
|
|
|
lv |> element("li#dropdown-step-2-option-1 a") |> render_click()
|
|
|
|
doc = lv |> element("#step-eval-0") |> render()
|
|
assert text_of_element(doc, ~s/#step-eval-0/) =~ "Entering Visitors: 0"
|
|
|
|
doc = lv |> element("#step-eval-1") |> render()
|
|
assert text_of_element(doc, ~s/#step-eval-1/) =~ "Dropoff: 0%"
|
|
|
|
doc = lv |> element("#funnel-eval") |> render()
|
|
assert text_of_element(doc, ~s/#funnel-eval/) =~ "Last month conversion rate: 0%"
|
|
end
|
|
end
|
|
|
|
defp setup_funnels(site, names \\ []) do
|
|
{:ok, [g1, g2]} = setup_goals(site)
|
|
|
|
{:ok, f1} =
|
|
Plausible.Funnels.create(
|
|
site,
|
|
Enum.at(names, 0) || "From blog to signup",
|
|
[%{"goal_id" => g1.id}, %{"goal_id" => g2.id}]
|
|
)
|
|
|
|
{:ok, f2} =
|
|
Plausible.Funnels.create(
|
|
site,
|
|
Enum.at(names, 1) || "From signup to blog",
|
|
[%{"goal_id" => g2.id}, %{"goal_id" => g1.id}]
|
|
)
|
|
|
|
{:ok, [f1.id, f2.id]}
|
|
end
|
|
|
|
defp setup_goals(site) do
|
|
{:ok, g1} = Plausible.Goals.create(site, %{"page_path" => "/go/to/blog/**"})
|
|
{:ok, g2} = Plausible.Goals.create(site, %{"event_name" => "Signup"})
|
|
{:ok, [g1, g2]}
|
|
end
|
|
|
|
defp get_liveview(conn, site, opts \\ []) do
|
|
conn = assign(conn, :live_module, PlausibleWeb.Live.FunnelSettings)
|
|
{:ok, lv, html} = live(conn, "/#{site.domain}/settings/funnels")
|
|
|
|
if Keyword.get(opts, :with_html?) do
|
|
{lv, html}
|
|
else
|
|
lv
|
|
end
|
|
end
|
|
|
|
defp type_into_search(lv, text) do
|
|
lv
|
|
|> element("form#filter-form")
|
|
|> render_change(%{
|
|
"_target" => ["filter-text"],
|
|
"filter-text" => "#{text}"
|
|
})
|
|
end
|
|
end
|