Files
python-tdd/src/functional_tests/test_bill_my_buds.py
Disco DeDisco db10f345e4
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
display_name → at_handle for every user-rendering point around the recent-activity surfaces: scroll.html actor <strong>, Most Recent Scroll applet actor <strong>, My Games row body actor prefix, My Scrolls row body actor prefix, My Buds page bud-name span, navbar identity, _bud_panel.html data-sharer-name (consumed by the dynamic post-line-author append on share success) — at_handle was always the right filter for these slots: it produces @<username> when the user has set one, falling back to the truncated email (which already carries an @) so we don't double-prefix; the _my_buds_applet_item.html row was already on at_handle from the 3-col sprint, so this commit just brings the rest of the surfaces in line; _navbar.html swap also drops the literal @ that prefixed {{ user|display_name }} — that literal predated at_handle + worked for users w. usernames (gave @disco) but produced @<email>@<domain> for users w. no username yet; navbar wait_to_be_logged_in(email) FT helper keeps working since the email still appears as a substring whether rendered as @disco@test.io (old, no username) or disco@test.io (new); _bud_add_panel.html's client-side _appendBudEntry JS gains an inline at_handle mirror — display.indexOf('@') >= 0 ? display : '@' + display — since the server's add_bud response packs username or email under the username key (semantic mismatch w. the key name but stable) so the JS has to detect the email case itself; test_bill_my_buds.py two .bud-name text assertions ("alice""@alice") updated for the new prefix; 931 ITs + targeted FT regression on test_bill_my_buds + test_core_navbar + test_core_login green
2026-05-13 01:09:43 -04:00

140 lines
6.3 KiB
Python

"""FT for the My Buds page — bud btn + slide-out add flow.
Phase 1 of the buds sprint: explicit add via my_buds.html. Phase 2
will layer autocomplete (sky-place-style top-3 username suggestions) and
implicit auto-add on post-share / gate-invite.
"""
from selenium.webdriver.common.by import By
from apps.lyric.models import User
from .base import FunctionalTest
class MyBudsPageTest(FunctionalTest):
def setUp(self):
super().setUp()
self.gamer = User.objects.create(email="me@test.io", username="me")
self.alice = User.objects.create(email="alice@test.io", username="alice")
self.create_pre_authenticated_session("me@test.io")
def test_renders_existing_buds(self):
"""Pre-existing buds show up as entries on first render."""
self.gamer.buds.add(self.alice)
self.browser.get(self.live_server_url + "/billboard/my-buds/")
entry = self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, ".bud-entry .bud-name")
)
# Bud names render via `at_handle` filter — `@<username>` w. an
# `@` prefix on users w. a username; truncated email otherwise.
self.assertEqual(entry.text, "@alice")
def test_empty_state_when_no_buds(self):
self.browser.get(self.live_server_url + "/billboard/my-buds/")
empty = self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, ".applet-list-entry--empty")
)
self.assertIn("No buds yet", empty.text)
def test_add_bud_via_bud_btn_appends_entry(self):
self.browser.get(self.live_server_url + "/billboard/my-buds/")
btn = self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_btn"))
btn.click()
recipient = self.wait_for(lambda: self.browser.find_element(By.ID, "id_recipient"))
recipient.send_keys("alice@test.io")
ok = self.browser.find_element(By.CSS_SELECTOR, "#id_bud_panel .btn.btn-confirm")
ok.click()
# New entry appears w. alice's @-prefixed handle (not the bare email)
self.wait_for(lambda: self.assertEqual(
self.browser.find_element(
By.CSS_SELECTOR, f".bud-entry[data-bud-id='{self.alice.id}'] .bud-name"
).text,
"@alice",
))
# Server-side persisted
self.wait_for(lambda: self.assertIn(
self.alice, list(self.gamer.buds.all())
))
# "No buds yet" empty-state row dismisses async as the first bud lands
# (shell partial renders w. .applet-list-entry--empty, not .bud-entry--empty)
self.wait_for(lambda: self.assertEqual(
self.browser.find_elements(By.CSS_SELECTOR, ".applet-list-entry--empty"),
[],
))
def test_no_autocomplete_suggestions_on_my_buds_page(self):
"""The bud-autocomplete pool is request.user.buds — surfacing buds
you've already added on the page where you ADD new buds is just
noise. Post-share + gatekeeper-invite panels keep it (re-sharing
with an existing bud is a real flow); the My Buds add-panel drops
the autocomplete bindings entirely. Absence of #id_bud_suggestions
is the deterministic check (no debounce-window races)."""
self.gamer.buds.add(self.alice)
self.browser.get(self.live_server_url + "/billboard/my-buds/")
self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_btn"))
self.assertEqual(
self.browser.find_elements(By.ID, "id_bud_suggestions"),
[],
)
def test_add_unregistered_email_is_silent_noop(self):
self.browser.get(self.live_server_url + "/billboard/my-buds/")
btn = self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_btn"))
btn.click()
recipient = self.wait_for(lambda: self.browser.find_element(By.ID, "id_recipient"))
recipient.send_keys("ghost@test.io")
ok = self.browser.find_element(By.CSS_SELECTOR, "#id_bud_panel .btn.btn-confirm")
ok.click()
# Wait for the panel close (a positive signal the request landed)
self.wait_for(lambda: self.assertNotIn(
"active", btn.get_attribute("class")
))
# No bud entries (the empty-state row has its own --empty class)
entries = self.browser.find_elements(By.CSS_SELECTOR, ".bud-entry")
self.assertEqual(len(entries), 0)
def test_re_add_existing_bud_shows_already_present_brief_and_fyi_flashes_bud_name(self):
"""Re-adding an existing bud: server returns already_present=true;
client renders the error Brief titled `@alice is already present`;
clicking FYI dismisses the Brief AND adds .bud-duplicate-flash to
the existing .bud-name cell."""
self.gamer.buds.add(self.alice)
self.browser.get(self.live_server_url + "/billboard/my-buds/")
btn = self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_btn"))
btn.click()
recipient = self.wait_for(lambda: self.browser.find_element(By.ID, "id_recipient"))
recipient.send_keys("alice@test.io")
self.browser.find_element(By.CSS_SELECTOR, "#id_bud_panel .btn.btn-confirm").click()
# Error Brief appears w. the duplicate title
title = self.wait_for(lambda: self.browser.find_element(
By.CSS_SELECTOR, ".note-banner--duplicate .note-banner__title"
))
self.assertEqual(title.text, "@alice is already present")
# Only one .bud-entry — no second alice row appended
self.assertEqual(
len(self.browser.find_elements(By.CSS_SELECTOR, ".bud-entry")),
1,
)
# Pre-flash: the .bud-name carries no flash class yet
bud_name = self.browser.find_element(
By.CSS_SELECTOR, f".bud-entry[data-bud-id='{self.alice.id}'] .bud-name"
)
self.assertNotIn("bud-duplicate-flash", bud_name.get_attribute("class") or "")
# Click FYI → Brief dismisses AND .bud-name gets the flash class
self.browser.find_element(By.CSS_SELECTOR, ".note-banner--duplicate .note-banner__fyi").click()
self.wait_for(lambda: self.assertEqual(
self.browser.find_elements(By.CSS_SELECTOR, ".note-banner--duplicate"),
[],
))
self.wait_for(lambda: self.assertIn(
"bud-duplicate-flash", bud_name.get_attribute("class") or ""
))