2026-02-22 12:50:55 +00:00
|
|
|
defmodule BerrypodWeb.Admin.AnalyticsTest do
|
|
|
|
|
use BerrypodWeb.ConnCase, async: false
|
|
|
|
|
|
|
|
|
|
import Phoenix.LiveViewTest
|
|
|
|
|
import Berrypod.AccountsFixtures
|
|
|
|
|
|
|
|
|
|
alias Berrypod.Analytics.{Buffer, Event}
|
|
|
|
|
alias Berrypod.Repo
|
|
|
|
|
|
|
|
|
|
setup do
|
|
|
|
|
send(Buffer, :flush)
|
|
|
|
|
:timer.sleep(50)
|
2026-02-24 22:56:19 +00:00
|
|
|
Repo.delete_all(Event)
|
2026-02-22 12:50:55 +00:00
|
|
|
|
|
|
|
|
user = user_fixture()
|
|
|
|
|
%{user: user}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "unauthenticated" do
|
|
|
|
|
test "redirects to login", %{conn: conn} do
|
|
|
|
|
{:error, redirect} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
assert {:redirect, %{to: path}} = redirect
|
|
|
|
|
assert path == ~p"/users/log-in"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "analytics dashboard" do
|
|
|
|
|
setup %{conn: conn, user: user} do
|
|
|
|
|
%{conn: log_in_user(conn, user)}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "renders the analytics page", %{conn: conn} do
|
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
assert html =~ "Analytics"
|
|
|
|
|
assert html =~ "Unique visitors"
|
|
|
|
|
assert html =~ "Total pageviews"
|
|
|
|
|
assert html =~ "Bounce rate"
|
|
|
|
|
assert html =~ "Visit duration"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "shows zero state with no data", %{conn: conn} do
|
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
assert html =~ "No data for this period"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "shows data when events exist", %{conn: conn} do
|
|
|
|
|
now = DateTime.utc_now() |> DateTime.truncate(:second)
|
|
|
|
|
|
|
|
|
|
Repo.insert_all(Event, [
|
|
|
|
|
[
|
|
|
|
|
id: Ecto.UUID.generate(),
|
|
|
|
|
name: "pageview",
|
|
|
|
|
pathname: "/",
|
|
|
|
|
visitor_hash: :crypto.strong_rand_bytes(8),
|
|
|
|
|
session_hash: :crypto.strong_rand_bytes(8),
|
|
|
|
|
browser: "Chrome",
|
|
|
|
|
os: "macOS",
|
|
|
|
|
inserted_at: now
|
|
|
|
|
]
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
2026-02-23 01:01:25 +00:00
|
|
|
assert has_element?(view, "[data-bars]")
|
2026-02-22 12:50:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "changes period", %{conn: conn} do
|
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
html = render_click(view, "change_period", %{"period" => "7d"})
|
|
|
|
|
assert html =~ "Analytics"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "changes tab to sources", %{conn: conn} do
|
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
html = render_click(view, "change_tab", %{"tab" => "sources"})
|
|
|
|
|
assert html =~ "Top sources"
|
|
|
|
|
assert html =~ "Top referrers"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "changes tab to countries", %{conn: conn} do
|
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
html = render_click(view, "change_tab", %{"tab" => "countries"})
|
|
|
|
|
assert html =~ "Countries"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "changes tab to devices", %{conn: conn} do
|
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
html = render_click(view, "change_tab", %{"tab" => "devices"})
|
|
|
|
|
assert html =~ "Browsers"
|
|
|
|
|
assert html =~ "Operating systems"
|
|
|
|
|
assert html =~ "Screen sizes"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "changes tab to funnel", %{conn: conn} do
|
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
html = render_click(view, "change_tab", %{"tab" => "funnel"})
|
|
|
|
|
assert html =~ "Conversion funnel"
|
|
|
|
|
end
|
2026-02-23 13:46:34 +00:00
|
|
|
|
|
|
|
|
test "clicking a source adds a filter chip", %{conn: conn} do
|
|
|
|
|
now = DateTime.utc_now() |> DateTime.truncate(:second)
|
|
|
|
|
|
|
|
|
|
Repo.insert_all(Event, [
|
|
|
|
|
[
|
|
|
|
|
id: Ecto.UUID.generate(),
|
|
|
|
|
name: "pageview",
|
|
|
|
|
pathname: "/",
|
|
|
|
|
visitor_hash: :crypto.strong_rand_bytes(8),
|
|
|
|
|
session_hash: :crypto.strong_rand_bytes(8),
|
|
|
|
|
referrer_source: "Google",
|
|
|
|
|
inserted_at: now
|
|
|
|
|
]
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
# Switch to sources tab and click the source
|
|
|
|
|
render_click(view, "change_tab", %{"tab" => "sources"})
|
|
|
|
|
|
|
|
|
|
html =
|
|
|
|
|
render_click(view, "add_filter", %{"dimension" => "referrer_source", "value" => "Google"})
|
|
|
|
|
|
|
|
|
|
assert html =~ "Source: Google"
|
|
|
|
|
assert has_element?(view, "#analytics-filters")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "removing a filter chip clears it", %{conn: conn} do
|
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
render_click(view, "add_filter", %{"dimension" => "country_code", "value" => "GB"})
|
|
|
|
|
assert render(view) =~ "Country: United Kingdom"
|
|
|
|
|
|
|
|
|
|
html = render_click(view, "remove_filter", %{"dimension" => "country_code"})
|
|
|
|
|
refute html =~ "Country: United Kingdom"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "clear all removes all filters", %{conn: conn} do
|
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
render_click(view, "add_filter", %{"dimension" => "country_code", "value" => "GB"})
|
|
|
|
|
render_click(view, "add_filter", %{"dimension" => "browser", "value" => "Chrome"})
|
|
|
|
|
assert render(view) =~ "Clear all"
|
|
|
|
|
|
|
|
|
|
html = render_click(view, "clear_filters", %{})
|
|
|
|
|
refute html =~ "Country: United Kingdom"
|
|
|
|
|
refute html =~ "Browser: Chrome"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "changing period preserves filters", %{conn: conn} do
|
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
|
|
|
|
|
|
|
|
|
render_click(view, "add_filter", %{"dimension" => "country_code", "value" => "GB"})
|
|
|
|
|
html = render_click(view, "change_period", %{"period" => "7d"})
|
|
|
|
|
|
|
|
|
|
assert html =~ "Country: United Kingdom"
|
|
|
|
|
end
|
2026-02-22 12:50:55 +00:00
|
|
|
end
|
|
|
|
|
end
|