my_buds: strip bud-autocomplete bindings from _bud_add_panel.html — the autocomplete pool is request.user.buds (per search_buds view), so on the page where you ADD new buds the suggestions are the precise set you can't usefully re-add; post-share + gatekeeper-invite panels keep the binding (re-sharing w. an existing bud is a real flow); test_autocomplete_suggests_buds_by_username_prefix → test_no_autocomplete_suggestions_on_my_buds_page (asserts #id_bud_suggestions absent — deterministic, no debounce-window race); dead id_bud_suggestions click-outside guard + unused {% load static %} dropped — TDD
Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,6 @@ will layer autocomplete (sky-place-style top-3 username suggestions) and
|
|||||||
implicit auto-add on post-share / gate-invite.
|
implicit auto-add on post-share / gate-invite.
|
||||||
"""
|
"""
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.common.keys import Keys
|
|
||||||
|
|
||||||
from apps.lyric.models import User
|
from apps.lyric.models import User
|
||||||
|
|
||||||
@@ -57,30 +56,21 @@ class MyBudsPageTest(FunctionalTest):
|
|||||||
self.alice, list(self.gamer.buds.all())
|
self.alice, list(self.gamer.buds.all())
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_autocomplete_suggests_buds_by_username_prefix(self):
|
def test_no_autocomplete_suggestions_on_my_buds_page(self):
|
||||||
"""Phase 2: typing in #id_recipient pulls top-3 prefix matches from
|
"""The bud-autocomplete pool is request.user.buds — surfacing buds
|
||||||
request.user.buds and renders them as .bud-suggestion-item buttons.
|
you've already added on the page where you ADD new buds is just
|
||||||
Click → input.value fills with the bud's username (or email if the
|
noise. Post-share + gatekeeper-invite panels keep it (re-sharing
|
||||||
user typed an `@` already)."""
|
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.gamer.buds.add(self.alice)
|
||||||
bob = User.objects.create(email="bob@test.io", username="bob")
|
|
||||||
self.gamer.buds.add(bob)
|
|
||||||
|
|
||||||
self.browser.get(self.live_server_url + "/billboard/my-buds/")
|
self.browser.get(self.live_server_url + "/billboard/my-buds/")
|
||||||
btn = self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_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("al")
|
|
||||||
|
|
||||||
suggestions = self.wait_for(lambda: self.browser.find_element(
|
self.assertEqual(
|
||||||
By.CSS_SELECTOR, "#id_bud_suggestions .bud-suggestion-item"
|
self.browser.find_elements(By.ID, "id_bud_suggestions"),
|
||||||
))
|
[],
|
||||||
self.assertEqual(suggestions.text.strip(), "alice")
|
)
|
||||||
|
|
||||||
suggestions.click()
|
|
||||||
self.wait_for(lambda: self.assertEqual(
|
|
||||||
recipient.get_attribute("value"), "alice"
|
|
||||||
))
|
|
||||||
|
|
||||||
def test_add_unregistered_email_is_silent_noop(self):
|
def test_add_unregistered_email_is_silent_noop(self):
|
||||||
self.browser.get(self.live_server_url + "/billboard/my-buds/")
|
self.browser.get(self.live_server_url + "/billboard/my-buds/")
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
{% load static %}
|
|
||||||
{# ─────────────────────────────────────────────────────────────────────── #}
|
{# ─────────────────────────────────────────────────────────────────────── #}
|
||||||
{# _bud_add_panel.html — bottom-left handshake btn + slide-out add- #}
|
{# _bud_add_panel.html — bottom-left handshake btn + slide-out add- #}
|
||||||
{# bud field. Mirrors _bud_panel.html (post-share) but POSTs to #}
|
{# bud field. Mirrors _bud_panel.html (post-share) but POSTs to #}
|
||||||
@@ -19,18 +18,10 @@
|
|||||||
<button id="id_bud_ok" type="button" class="btn btn-confirm">OK</button>
|
<button id="id_bud_ok" type="button" class="btn btn-confirm">OK</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Autocomplete suggestions list — sibling of #id_bud_panel because the #}
|
{# No autocomplete on this panel — the bud-autocomplete pool is the #}
|
||||||
{# panel has overflow:hidden for its scaleX slide animation. #}
|
{# user's existing buds, which is precisely the set you can't usefully #}
|
||||||
<div id="id_bud_suggestions" class="bud-suggestions" hidden></div>
|
{# re-add. Post-share + gatekeeper-invite panels keep the autocomplete #}
|
||||||
|
{# binding (re-sharing with an existing bud is a real flow). #}
|
||||||
<script src="{% static 'apps/billboard/bud-autocomplete.js' %}"></script>
|
|
||||||
<script>
|
|
||||||
bindBudAutocomplete(
|
|
||||||
document.getElementById('id_recipient'),
|
|
||||||
document.getElementById('id_bud_suggestions'),
|
|
||||||
{ searchUrl: '{% url "billboard:search_buds" %}' }
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
@@ -77,10 +68,6 @@
|
|||||||
if (!html.classList.contains('bud-open')) return;
|
if (!html.classList.contains('bud-open')) return;
|
||||||
if (panel.contains(e.target)) return;
|
if (panel.contains(e.target)) return;
|
||||||
if (e.target === btn || btn.contains(e.target)) return;
|
if (e.target === btn || btn.contains(e.target)) return;
|
||||||
// Suggestions live outside the panel (panel has overflow:hidden
|
|
||||||
// for its scaleX slide); a click inside them must NOT close+clear.
|
|
||||||
var sg = document.getElementById('id_bud_suggestions');
|
|
||||||
if (sg && sg.contains(e.target)) return;
|
|
||||||
_close();
|
_close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user