mirror of
https://github.com/plausible/analytics.git
synced 2024-12-25 10:33:01 +03:00
9d97dc1912
* Move limit enforcement to accepting site ownerhsip transfer * enforce pageview limit on ownership transfer accept * Refactor plan limit check logic * Extract `ensure_can_take_ownership` to `Invitations` context and refactor * Improve styling of exceeded limits notice in invitation dialog and disable button * styling improvements to notice * make transfer_ownership return transfer to self error * do not allow transferring to user without active subscription WIP * Add missing typespec and improve existing ones * Fix formatting * Explicitly label direct match on function argument for clarity * Slightly refactor `CreateInvitation.bulk_transfer_ownership_direct` * Exclude quota enforcement tests from small build test suite * Remove unused return type from `invite_error()` union type * Do not block plan upgrade when there's pending ownership transfer * Don't block and only warn about missing features on transfer * Remove `x-init` attribute used for debugging * Add tests for `Quota.monthly_pageview_usage/2` * Test and improve site admin ownership transfer actions * Extend tests for `AcceptInvitation.transfer_ownership` * Test transfer ownership controller level accept action error cases * Test choosing plan by user without sites but with a pending ownership transfer * Test invitation x-data in sites LV * Remove sitelocker trigger in invitation acceptance code and simplify logic * Add Quota test for `user.allow_next_upgrade_override` being set * ignore pageview limit only when subscribing to plan * Use sandbox Paddle instance for staging * Use sandbox paddle key for staging and dev --------- Co-authored-by: Robert Joonas <robertjoonas16@gmail.com>
229 lines
6.4 KiB
Elixir
229 lines
6.4 KiB
Elixir
defmodule PlausibleWeb.Live.SitesTest do
|
|
use PlausibleWeb.ConnCase, async: true
|
|
|
|
import Phoenix.LiveViewTest
|
|
import Plausible.Test.Support.HTML
|
|
|
|
alias Plausible.Repo
|
|
|
|
setup [:create_user, :log_in]
|
|
|
|
describe "/sites" do
|
|
test "renders empty sites page", %{conn: conn} do
|
|
{:ok, _lv, html} = live(conn, "/sites")
|
|
|
|
assert text(html) =~ "You don't have any sites yet"
|
|
end
|
|
|
|
@tag :full_build_only
|
|
test "renders ownership transfer invitation for a case with no plan", %{
|
|
conn: conn,
|
|
user: user
|
|
} do
|
|
site = insert(:site)
|
|
|
|
invitation =
|
|
insert(:invitation,
|
|
site_id: site.id,
|
|
inviter: build(:user),
|
|
email: user.email,
|
|
role: :owner
|
|
)
|
|
|
|
{:ok, _lv, html} = live(conn, "/sites")
|
|
|
|
invitation_data = get_invitation_data(html)
|
|
|
|
assert get_in(invitation_data, ["invitations", invitation.invitation_id, "no_plan"])
|
|
end
|
|
|
|
@tag :full_build_only
|
|
test "renders ownership transfer invitation for a case with exceeded limits", %{
|
|
conn: conn,
|
|
user: user
|
|
} do
|
|
site = insert(:site)
|
|
|
|
insert(:growth_subscription, user: user)
|
|
|
|
# fill site quota
|
|
insert_list(10, :site, members: [user])
|
|
|
|
invitation =
|
|
insert(:invitation,
|
|
site_id: site.id,
|
|
inviter: build(:user),
|
|
email: user.email,
|
|
role: :owner
|
|
)
|
|
|
|
{:ok, _lv, html} = live(conn, "/sites")
|
|
|
|
invitation_data = get_invitation_data(html)
|
|
|
|
assert get_in(invitation_data, ["invitations", invitation.invitation_id, "exceeded_limits"]) ==
|
|
"site limit"
|
|
end
|
|
|
|
@tag :full_build_only
|
|
test "renders ownership transfer invitation for a case with missing features", %{
|
|
conn: conn,
|
|
user: user
|
|
} do
|
|
site = insert(:site, allowed_event_props: ["dummy"])
|
|
|
|
insert(:growth_subscription, user: user)
|
|
|
|
invitation =
|
|
insert(:invitation,
|
|
site_id: site.id,
|
|
inviter: build(:user),
|
|
email: user.email,
|
|
role: :owner
|
|
)
|
|
|
|
{:ok, _lv, html} = live(conn, "/sites")
|
|
|
|
invitation_data = get_invitation_data(html)
|
|
|
|
assert get_in(invitation_data, ["invitations", invitation.invitation_id, "missing_features"]) ==
|
|
"Custom Properties"
|
|
end
|
|
|
|
test "renders 24h visitors correctly", %{conn: conn, user: user} do
|
|
site = insert(:site, members: [user])
|
|
|
|
populate_stats(site, [build(:pageview), build(:pageview), build(:pageview)])
|
|
|
|
{:ok, _lv, html} = live(conn, "/sites")
|
|
|
|
site_card = text_of_element(html, "li[data-domain=\"#{site.domain}\"]")
|
|
assert site_card =~ "3 visitors in last 24h"
|
|
assert site_card =~ site.domain
|
|
end
|
|
|
|
test "filters by domain", %{conn: conn, user: user} do
|
|
_site1 = insert(:site, domain: "first.example.com", members: [user])
|
|
_site2 = insert(:site, domain: "second.example.com", members: [user])
|
|
_site3 = insert(:site, domain: "first-another.example.com", members: [user])
|
|
|
|
{:ok, lv, _html} = live(conn, "/sites")
|
|
|
|
type_into_input(lv, "filter_text", "firs")
|
|
html = render(lv)
|
|
|
|
assert html =~ "first.example.com"
|
|
assert html =~ "first-another.example.com"
|
|
refute html =~ "second.example.com"
|
|
end
|
|
|
|
test "filtering plays well with pagination", %{conn: conn, user: user} do
|
|
_site1 = insert(:site, domain: "first.another.example.com", members: [user])
|
|
_site2 = insert(:site, domain: "second.example.com", members: [user])
|
|
_site3 = insert(:site, domain: "third.another.example.com", members: [user])
|
|
|
|
{:ok, lv, html} = live(conn, "/sites?page_size=2")
|
|
|
|
assert html =~ "first.another.example.com"
|
|
assert html =~ "second.example.com"
|
|
refute html =~ "third.another.example.com"
|
|
assert html =~ "page=2"
|
|
refute html =~ "page=1"
|
|
|
|
type_into_input(lv, "filter_text", "anot")
|
|
html = render(lv)
|
|
|
|
assert html =~ "first.another.example.com"
|
|
refute html =~ "second.example.com"
|
|
assert html =~ "third.another.example.com"
|
|
refute html =~ "page=1"
|
|
refute html =~ "page=2"
|
|
end
|
|
end
|
|
|
|
describe "pinning" do
|
|
test "renders pin site option when site not pinned", %{conn: conn, user: user} do
|
|
site = insert(:site, members: [user])
|
|
|
|
{:ok, _lv, html} = live(conn, "/sites")
|
|
|
|
assert text_of_element(
|
|
html,
|
|
~s/li[data-domain="#{site.domain}"] a[phx-value-domain]/
|
|
) == "Pin Site"
|
|
end
|
|
|
|
test "site state changes when pin toggled", %{conn: conn, user: user} do
|
|
site = insert(:site, members: [user])
|
|
|
|
{:ok, lv, _html} = live(conn, "/sites")
|
|
|
|
button_selector = ~s/li[data-domain="#{site.domain}"] a[phx-value-domain]/
|
|
|
|
html =
|
|
lv
|
|
|> element(button_selector)
|
|
|> render_click()
|
|
|
|
assert html =~ "Site pinned"
|
|
|
|
assert text_of_element(html, button_selector) == "Unpin Site"
|
|
|
|
html =
|
|
lv
|
|
|> element(button_selector)
|
|
|> render_click()
|
|
|
|
assert html =~ "Site unpinned"
|
|
|
|
assert text_of_element(html, button_selector) == "Pin Site"
|
|
end
|
|
|
|
test "shows error when pins limit hit", %{conn: conn, user: user} do
|
|
for _ <- 1..9 do
|
|
site = insert(:site, members: [user])
|
|
assert {:ok, _} = Plausible.Sites.toggle_pin(user, site)
|
|
end
|
|
|
|
site = insert(:site, members: [user])
|
|
|
|
{:ok, lv, _html} = live(conn, "/sites")
|
|
|
|
button_selector = ~s/li[data-domain="#{site.domain}"] a[phx-value-domain]/
|
|
|
|
html =
|
|
lv
|
|
|> element(button_selector)
|
|
|> render_click()
|
|
|
|
assert text(html) =~ "Looks like you've hit the pinned sites limit!"
|
|
end
|
|
|
|
test "does not allow pinning site user doesn't have access to", %{conn: conn, user: user} do
|
|
site = insert(:site)
|
|
|
|
{:ok, lv, _html} = live(conn, "/sites")
|
|
|
|
render_click(lv, "pin-toggle", %{"domain" => site.domain})
|
|
|
|
refute Repo.get_by(Plausible.Site.UserPreference, user_id: user.id, site_id: site.id)
|
|
end
|
|
end
|
|
|
|
defp type_into_input(lv, id, text) do
|
|
lv
|
|
|> element("form")
|
|
|> render_change(%{id => text})
|
|
end
|
|
|
|
defp get_invitation_data(html) do
|
|
html
|
|
|> text_of_attr("div[x-data]", "x-data")
|
|
|> String.trim("dropdown")
|
|
|> String.replace("selectedInvitation:", "\"selectedInvitation\":")
|
|
|> String.replace("invitationOpen:", "\"invitationOpen\":")
|
|
|> String.replace("invitations:", "\"invitations\":")
|
|
|> Jason.decode!()
|
|
end
|
|
end
|