offloaded Significator FTs into FTs.test_room_sig_select; new sig-select.js imported into room.html; new apps.epic.consumers & .views, ITs to confirm functionality
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Disco DeDisco
2026-03-25 11:03:53 -04:00
parent 0494710ce0
commit f0f419ff7e
7 changed files with 433 additions and 316 deletions

View File

@@ -25,3 +25,6 @@ class RoomConsumer(AsyncJsonWebsocketConsumer):
async def roles_revealed(self, event):
await self.send_json(event)
async def sig_selected(self, event):
await self.send_json(event)

View File

@@ -0,0 +1,96 @@
var SigSelect = (function () {
var SIG_ORDER = ['PC', 'NC', 'EC', 'SC', 'AC', 'BC'];
var sigDeck, selectUrl, userRole;
function getActiveRole() {
for (var i = 0; i < SIG_ORDER.length; i++) {
var seat = document.querySelector('.table-seat[data-role="' + SIG_ORDER[i] + '"]');
if (seat && !seat.dataset.sigDone) return SIG_ORDER[i];
}
return null;
}
function isEligible() {
return !!(userRole && userRole === getActiveRole());
}
function getCsrf() {
var m = document.cookie.match(/csrftoken=([^;]+)/);
return m ? m[1] : '';
}
function applySelection(cardId, role, deckType) {
// Remove only the specific pile copy (levity or gravity) of this card
var selector = '.sig-card.' + deckType + '-deck[data-card-id="' + cardId + '"]';
sigDeck.querySelectorAll(selector).forEach(function (c) { c.remove(); });
// Mark this seat done, remove active
var seat = document.querySelector('.table-seat[data-role="' + role + '"]');
if (seat) {
seat.classList.remove('active');
seat.dataset.sigDone = '1';
}
// Advance active to next seat
var nextRole = getActiveRole();
if (nextRole) {
var nextSeat = document.querySelector('.table-seat[data-role="' + nextRole + '"]');
if (nextSeat) nextSeat.classList.add('active');
}
// Place a card placeholder in inventory
var invSlot = document.getElementById('id_inv_sig_card');
if (invSlot) {
var card = document.createElement('div');
card.className = 'card';
invSlot.appendChild(card);
}
}
function init() {
sigDeck = document.getElementById('id_sig_deck');
if (!sigDeck) return;
selectUrl = sigDeck.dataset.selectSigUrl;
userRole = sigDeck.dataset.userRole;
sigDeck.addEventListener('click', function (e) {
var card = e.target.closest('.sig-card');
if (!card) return;
if (!isEligible()) return;
var activeRole = getActiveRole();
var cardId = card.dataset.cardId;
var deckType = card.dataset.deck;
window.showGuard(card, 'Select this significator?', function () {
fetch(selectUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': getCsrf(),
},
body: 'card_id=' + encodeURIComponent(cardId) + '&deck_type=' + encodeURIComponent(deckType),
}).then(function (response) {
if (response.ok) {
applySelection(cardId, activeRole, deckType);
}
});
});
});
}
window.addEventListener('room:sig_selected', function (e) {
if (!sigDeck) return;
var cardId = String(e.detail.card_id);
var role = e.detail.role;
var deckType = e.detail.deck_type;
// Idempotent — skip if this copy already removed (local selector already did it)
if (!sigDeck.querySelector('.sig-card.' + deckType + '-deck[data-card-id="' + cardId + '"]')) return;
applySelection(cardId, role, deckType);
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
}());

View File

@@ -902,7 +902,7 @@ class SelectSigCardViewTest(TestCase):
def test_select_sig_notifies_ws(self):
with patch("apps.epic.views._notify_sig_selected") as mock_notify:
self._post()
mock_notify.assert_called_once_with(self.room.id)
mock_notify.assert_called_once()
def test_select_sig_requires_login(self):
self.client.logout()

View File

@@ -64,10 +64,10 @@ def _notify_role_select_start(room_id):
)
def _notify_sig_selected(room_id):
def _notify_sig_selected(room_id, card_id, role, deck_type='levity'):
async_to_sync(get_channel_layer().group_send)(
f'room_{room_id}',
{'type': 'sig_selected'},
{'type': 'sig_selected', 'card_id': str(card_id), 'role': role, 'deck_type': deck_type},
)
@@ -194,7 +194,9 @@ def _role_select_context(room, user):
ctx["user_seat"] = user_seat
ctx["partner_seat"] = partner_seat
ctx["revealed_seats"] = room.table_seats.filter(role_revealed=True).order_by("slot_number")
ctx["sig_cards"] = sig_deck_cards(room)
raw_sig_cards = sig_deck_cards(room)
half = len(raw_sig_cards) // 2
ctx["sig_cards"] = [(c, 'levity') for c in raw_sig_cards[:half]] + [(c, 'gravity') for c in raw_sig_cards[half:]]
ctx["sig_seats"] = sig_seat_order(room)
ctx["sig_active_seat"] = active_sig_seat(room)
return ctx
@@ -500,7 +502,8 @@ def select_sig(request, room_id):
return HttpResponse(status=409)
active_seat.significator = card
active_seat.save()
_notify_sig_selected(room_id)
deck_type = request.POST.get('deck_type', 'levity')
_notify_sig_selected(room_id, card.pk, active_seat.role, deck_type)
return HttpResponse(status=200)