deck contribution sprint 2 + Carte Blanche safeguards — TDD
Sprint 2 UI (game kit applet): - _applet-game-kit.html: in-use deck → two disabled × buttons, .tt-deck-game-name; in-use Carte Blanche → two disabled × buttons, data-current-room-name, .tt-token-room-name; tooltip content mirrors kit bag panel (Default, card count, description, Stock version) - gameboard.js buildMiniContent: 'In-Use' for tokens w. data-current-room-name set - _kit_bag_panel.html: Deck section always renders (placeholder when unequipped) View safeguards: - select_role: look up existing deck from prior seat in same room before equipped_deck (Carte Blanche multi-seat); only unequip when using equipped_deck - drop_token Carte: reject 409 if token.current_room is a different room; unequip from equipped_trinket on drop ITs: SelectRoleMultiSeatTest (2), DropTokenViewTest +3 (carte drop, unequip, lock) Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,10 +64,20 @@ function initGameKitTooltips() {
|
||||
const tokenId = token.dataset.tokenId;
|
||||
const equippedId = gameKit.dataset.equippedId || '';
|
||||
const equippedDeckId = gameKit.dataset.equippedDeckId || '';
|
||||
const inUseDeckIds = new Set((gameKit.dataset.inUseDeckIds || '').split(',').filter(Boolean));
|
||||
if (deckId) {
|
||||
miniPortal.textContent = (equippedDeckId && deckId === equippedDeckId) ? 'Equipped' : 'Not Equipped';
|
||||
if (inUseDeckIds.has(deckId)) {
|
||||
miniPortal.textContent = 'In-Use';
|
||||
} else {
|
||||
miniPortal.textContent = (equippedDeckId && deckId === equippedDeckId) ? 'Equipped' : 'Not Equipped';
|
||||
}
|
||||
} else if (tokenId) {
|
||||
miniPortal.textContent = (equippedId && tokenId === equippedId) ? 'Equipped' : 'Not Equipped';
|
||||
const currentRoomName = token.dataset.currentRoomName || '';
|
||||
if (currentRoomName) {
|
||||
miniPortal.textContent = 'In-Use';
|
||||
} else {
|
||||
miniPortal.textContent = (equippedId && tokenId === equippedId) ? 'Equipped' : 'Not Equipped';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from apps.applets.models import Applet, UserApplet
|
||||
from apps.epic.models import DeckVariant
|
||||
from apps.epic.models import DeckVariant, Room, TableSeat
|
||||
from apps.lyric.models import Token, User
|
||||
|
||||
|
||||
@@ -61,6 +61,45 @@ class GameboardViewTest(TestCase):
|
||||
[_] = self.parsed.cssselect("#id_game_kit #id_kit_dice_set")
|
||||
|
||||
|
||||
class GameboardDeckInUseTest(TestCase):
|
||||
"""Sprint 2: game kit applet renders in-use state for a deck assigned to an active seat."""
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(email="gamer@test.io")
|
||||
self.client.force_login(self.user)
|
||||
Applet.objects.get_or_create(slug="game-kit", defaults={"name": "Game Kit", "context": "gameboard"})
|
||||
self.earthman = DeckVariant.objects.get(slug="earthman")
|
||||
self.room = Room.objects.create(name="Wildfire", owner=self.user)
|
||||
self.seat = TableSeat.objects.create(
|
||||
room=self.room, gamer=self.user, slot_number=1,
|
||||
deck_variant=self.earthman,
|
||||
)
|
||||
response = self.client.get("/gameboard/")
|
||||
self.parsed = lxml.html.fromstring(response.content)
|
||||
|
||||
def test_in_use_deck_don_is_disabled(self):
|
||||
[don] = self.parsed.cssselect("#id_kit_earthman_deck .btn-equip")
|
||||
self.assertIn("btn-disabled", don.get("class", ""))
|
||||
|
||||
def test_in_use_deck_doff_is_absent(self):
|
||||
active_doff = self.parsed.cssselect(
|
||||
"#id_kit_earthman_deck .btn-unequip:not(.btn-disabled)"
|
||||
)
|
||||
self.assertEqual(len(active_doff), 0)
|
||||
|
||||
def test_in_use_deck_tooltip_shows_game_name(self):
|
||||
[label] = self.parsed.cssselect("#id_kit_earthman_deck .tt-deck-game-name")
|
||||
self.assertIn("Wildfire", label.text_content())
|
||||
|
||||
def test_non_in_use_deck_has_normal_don(self):
|
||||
fiorentine = DeckVariant.objects.get(slug="fiorentine-minchiate")
|
||||
self.user.unlocked_decks.add(fiorentine)
|
||||
response = self.client.get("/gameboard/")
|
||||
parsed = lxml.html.fromstring(response.content)
|
||||
[don] = parsed.cssselect("#id_kit_fiorentine_deck .btn-equip")
|
||||
self.assertNotIn("btn-disabled", don.get("class", ""))
|
||||
|
||||
|
||||
class ToggleGameAppletsViewTest(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(email="gamer@test.io")
|
||||
|
||||
@@ -4,7 +4,20 @@ from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.applets.utils import applet_context, apply_applet_toggle
|
||||
from apps.epic.models import DeckVariant, Room
|
||||
|
||||
|
||||
def _annotate_deck_in_use(decks, user):
|
||||
"""Attach .in_use_room_name to each deck — the name of the active room using it, or None."""
|
||||
active = {
|
||||
ts.deck_variant_id: ts.room.name
|
||||
for ts in TableSeat.objects.filter(
|
||||
gamer=user, deck_variant__isnull=False,
|
||||
).select_related("room")
|
||||
}
|
||||
for deck in decks:
|
||||
deck.in_use_room_name = active.get(deck.pk)
|
||||
return decks
|
||||
from apps.epic.models import DeckVariant, Room, TableSeat
|
||||
from apps.epic.utils import rooms_for_user
|
||||
from apps.lyric.models import Token
|
||||
|
||||
@@ -31,7 +44,7 @@ def gameboard(request):
|
||||
"carte": carte,
|
||||
"equipped_trinket_id": request.user.equipped_trinket_id,
|
||||
"equipped_deck_id": request.user.equipped_deck_id,
|
||||
"deck_variants": list(request.user.unlocked_decks.all()),
|
||||
"deck_variants": _annotate_deck_in_use(list(request.user.unlocked_decks.all()), request.user),
|
||||
"free_tokens": free_tokens,
|
||||
"free_count": len(free_tokens),
|
||||
"applets": applet_context(request.user, "gameboard"),
|
||||
@@ -55,7 +68,7 @@ def toggle_game_applets(request):
|
||||
"carte": request.user.tokens.filter(token_type=Token.CARTE).first(),
|
||||
"equipped_trinket_id": request.user.equipped_trinket_id,
|
||||
"equipped_deck_id": request.user.equipped_deck_id,
|
||||
"deck_variants": list(request.user.unlocked_decks.all()),
|
||||
"deck_variants": _annotate_deck_in_use(list(request.user.unlocked_decks.all()), request.user),
|
||||
"free_tokens": free_tokens,
|
||||
"free_count": len(free_tokens),
|
||||
"my_games": rooms_for_user(request.user),
|
||||
|
||||
Reference in New Issue
Block a user