bud panels duplicate-add guard: server-side already_present flag + client-side error Brief w. FYI flash highlight on the existing entry — for each of the three #id_bud_btn panels (My Buds / post-share / gatekeeper-invite), the JSON response from add_bud / share_post / invite_gamer now carries {already_present, recipient_display, recipient_user_id}; bud-btn.js branches on already_present → calls new Brief.showDuplicateBanner({display_name, target_selector}) instead of the normal onSuccess append; banner title reads @<username> is already present, NVM dismisses, FYI dismisses AND eases in the .bud-duplicate-flash class (color: var(--terUser); text-shadow: 0 0 .5em var(--ninUser); transition: 600ms) onto the existing element (.bud-entry .bud-name / .post-recipient[data-user-id=…] / .gate-slot.filled[data-user-id=…]); gatekeeper "already present" = recipient is either GateSlot.FILLED + gamer OR has TableSeat OR has a pending RoomInvite (highlight target only set when seated — pending invites have no visible slot); .post-recipient chips + .gate-slot.filled cells gain data-user-id so the FYI selector can find them; my_buds.html now loads note.js via the {% block scripts %} pattern (Brief module is required by the duplicate banner path); bonus: latent test_jasmine.py bug fixed — "0 failures" in result.text matched "10 failures" / "20 failures" / etc, silently passing up to 99 failed specs; replaced w. re.search(r"(?<!\d)0 failures\b", …) (caught my new red specs, would've caught any prior Jasmine regression); 18 new ITs + 10 new Jasmine specs + 3 new FTs (one per panel) — TDD
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed

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:
Disco DeDisco
2026-05-12 16:40:15 -04:00
parent 264ed5968e
commit be919c7aff
19 changed files with 738 additions and 38 deletions

View File

@@ -239,3 +239,84 @@ class SharePostImplicitAutoAddTest(TestCase):
"""Privacy: unregistered email doesn't touch the buds graph."""
self._share("ghost@test.io")
self.assertEqual(self.sharer.buds.count(), 0)
class AddBudAlreadyPresentTest(TestCase):
"""Duplicate-add guard: add_bud's JSON response distinguishes "newly
added" from "already a bud" so the bud-btn JS can render an error
Brief titled `@<username> is already present` instead of the normal
bud-entry append. `recipient_user_id` is the highlight target id that
the Brief FYI button toggles `.bud-duplicate-flash` onto."""
def setUp(self):
self.user = User.objects.create(email="me@test.io", username="me")
self.client.force_login(self.user)
self.alice = User.objects.create(email="alice@test.io", username="alice")
self.user.buds.add(self.alice)
def _add(self, recipient):
return self.client.post(
reverse("billboard:add_bud"), {"recipient": recipient},
)
def test_already_present_true_when_candidate_is_already_a_bud(self):
self.assertTrue(self._add("alice@test.io").json()["already_present"])
def test_already_present_false_for_new_bud(self):
User.objects.create(email="bob@test.io", username="bob")
self.assertFalse(self._add("bob@test.io").json()["already_present"])
def test_already_present_false_for_unregistered_email(self):
self.assertFalse(self._add("ghost@test.io").json()["already_present"])
def test_recipient_display_carries_username_on_duplicate(self):
self.assertEqual(self._add("alice@test.io").json()["recipient_display"], "alice")
def test_recipient_user_id_carries_alice_id_on_duplicate(self):
self.assertEqual(
self._add("alice@test.io").json()["recipient_user_id"],
str(self.alice.id),
)
def test_username_typed_recipient_also_detects_duplicate(self):
"""A user typing 'alice' (no @) resolves the same way as the email."""
self.assertTrue(self._add("alice").json()["already_present"])
class SharePostAlreadyPresentTest(TestCase):
"""Duplicate-share guard mirrors AddBudAlreadyPresentTest — when the
recipient is already in post.shared_with, response carries
already_present + recipient_user_id for the .post-recipient highlight."""
def setUp(self):
from apps.billboard.models import Post
self.sharer = User.objects.create(email="sharer@test.io", username="sharer")
self.client.force_login(self.sharer)
self.alice = User.objects.create(email="alice@test.io", username="alice")
self.post = Post.objects.create(owner=self.sharer)
self.post.shared_with.add(self.alice)
def _share(self, recipient):
return self.client.post(
reverse("billboard:share_post", args=[self.post.id]),
data={"recipient": recipient},
HTTP_ACCEPT="application/json",
)
def test_already_present_true_when_recipient_in_shared_with(self):
self.assertTrue(self._share("alice@test.io").json()["already_present"])
def test_already_present_false_for_new_recipient(self):
User.objects.create(email="bob@test.io", username="bob")
self.assertFalse(self._share("bob@test.io").json()["already_present"])
def test_recipient_user_id_present_on_duplicate(self):
self.assertEqual(
self._share("alice@test.io").json()["recipient_user_id"],
str(self.alice.id),
)
def test_recipient_display_already_present_on_duplicate(self):
"""`recipient_display` already exists on the success path; on
duplicate the same field must carry the matched user's handle."""
self.assertEqual(self._share("alice@test.io").json()["recipient_display"], "alice")