demo'd old inventory area in room.html to make way for new content (hex table now centered in view); old test suite now targets Role card in #id_tray cells where appropriate, or skips Sig card select until aforementioned new feature deployed; new scripts & jasmine tests too; removed one irrelevant test case from apps.epic.tests.ITs.test_views.SelectRoleViewTest
This commit is contained in:
@@ -3,6 +3,11 @@ var RoleSelect = (function () {
|
||||
// ahead of the fetch response doesn't get overridden by Tray.open().
|
||||
var _turnChangedBeforeFetch = false;
|
||||
|
||||
// Set to true while placeCard animation is running. handleTurnChanged
|
||||
// defers its work until the animation completes.
|
||||
var _animationPending = false;
|
||||
var _pendingTurnChange = null;
|
||||
|
||||
var ROLES = [
|
||||
{ code: "PC", name: "Player", element: "Fire" },
|
||||
{ code: "BC", name: "Builder", element: "Stone" },
|
||||
@@ -27,18 +32,10 @@ var RoleSelect = (function () {
|
||||
if (backdrop) backdrop.remove();
|
||||
}
|
||||
|
||||
function selectRole(roleCode, cardEl) {
|
||||
function selectRole(roleCode) {
|
||||
_turnChangedBeforeFetch = false; // fresh selection, reset the race flag
|
||||
var invCard = cardEl.cloneNode(true);
|
||||
invCard.classList.add("flipped");
|
||||
// strip old event listeners from the clone by replacing with a clean copy
|
||||
var clean = invCard.cloneNode(true);
|
||||
|
||||
closeFan();
|
||||
|
||||
var invSlot = document.getElementById("id_inv_role_card");
|
||||
if (invSlot) invSlot.appendChild(clean);
|
||||
|
||||
// Immediately lock the stack — do not wait for WS turn_changed
|
||||
var stack = document.querySelector(".card-stack[data-starter-roles]");
|
||||
if (stack) {
|
||||
@@ -60,24 +57,27 @@ var RoleSelect = (function () {
|
||||
}).then(function (response) {
|
||||
if (!response.ok) {
|
||||
// Server rejected (role already taken) — undo optimistic update
|
||||
if (invSlot && invSlot.contains(clean)) invSlot.removeChild(clean);
|
||||
if (stack) {
|
||||
stack.dataset.starterRoles = stack.dataset.starterRoles
|
||||
.split(",").filter(function (r) { return r.trim() !== roleCode; }).join(",");
|
||||
}
|
||||
openFan();
|
||||
} else {
|
||||
// Place role card in tray grid and open the tray
|
||||
var grid = document.getElementById("id_tray_grid");
|
||||
if (grid) {
|
||||
var trayCard = document.createElement("div");
|
||||
trayCard.className = "tray-cell tray-role-card";
|
||||
trayCard.dataset.role = roleCode;
|
||||
grid.insertBefore(trayCard, grid.firstChild);
|
||||
}
|
||||
// Only open if turn_changed hasn't already arrived and closed it.
|
||||
if (typeof Tray !== "undefined" && !_turnChangedBeforeFetch) {
|
||||
Tray.open();
|
||||
// Always animate the role card into the tray, even if turn_changed
|
||||
// already arrived. placeCard opens the tray, arcs the card in,
|
||||
// then force-closes — so the user always sees their role card land.
|
||||
// If turn_changed arrived before the fetch, handleTurnChanged already
|
||||
// ran; _pendingTurnChange will be null and onComplete is a no-op.
|
||||
if (typeof Tray !== "undefined") {
|
||||
_animationPending = true;
|
||||
Tray.placeCard(roleCode, function () {
|
||||
_animationPending = false;
|
||||
if (_pendingTurnChange) {
|
||||
var ev = _pendingTurnChange;
|
||||
_pendingTurnChange = null;
|
||||
handleTurnChanged(ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -135,7 +135,7 @@ var RoleSelect = (function () {
|
||||
"Start round 1 as<br>" + role.name + " (" + role.code + ") …?",
|
||||
function () { // confirm
|
||||
card.classList.remove("guard-active");
|
||||
selectRole(role.code, card);
|
||||
selectRole(role.code);
|
||||
},
|
||||
function () { // dismiss (NVM / outside click)
|
||||
card.classList.remove("guard-active");
|
||||
@@ -165,9 +165,13 @@ var RoleSelect = (function () {
|
||||
}
|
||||
|
||||
function handleTurnChanged(event) {
|
||||
// If a placeCard animation is running, defer until it completes.
|
||||
if (_animationPending) {
|
||||
_pendingTurnChange = event;
|
||||
return;
|
||||
}
|
||||
|
||||
var active = String(event.detail.active_slot);
|
||||
var invSlot = document.getElementById("id_inv_role_card");
|
||||
if (invSlot) invSlot.innerHTML = "";
|
||||
|
||||
// Force-close tray instantly so it never obscures the next player's card-stack.
|
||||
// Also set the race flag so the fetch .then() doesn't re-open if it arrives late.
|
||||
@@ -220,8 +224,13 @@ var RoleSelect = (function () {
|
||||
}
|
||||
|
||||
return {
|
||||
openFan: openFan,
|
||||
closeFan: closeFan,
|
||||
setReload: function (fn) { _reload = fn; },
|
||||
openFan: openFan,
|
||||
closeFan: closeFan,
|
||||
setReload: function (fn) { _reload = fn; },
|
||||
// Testing hook — resets animation-pause state between Jasmine specs
|
||||
_testReset: function () {
|
||||
_animationPending = false;
|
||||
_pendingTurnChange = null;
|
||||
},
|
||||
};
|
||||
}());
|
||||
|
||||
@@ -224,6 +224,37 @@ var Tray = (function () {
|
||||
});
|
||||
}
|
||||
|
||||
// _arcIn — add .arc-in to cardEl, wait for animationend, remove it, call onComplete.
|
||||
function _arcIn(cardEl, onComplete) {
|
||||
cardEl.classList.add('arc-in');
|
||||
cardEl.addEventListener('animationend', function handler() {
|
||||
cardEl.removeEventListener('animationend', handler);
|
||||
cardEl.classList.remove('arc-in');
|
||||
if (onComplete) onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
// placeCard(roleCode, onComplete) — mark the first tray cell with the role,
|
||||
// open the tray, arc-in the cell, then force-close. Calls onComplete after.
|
||||
// The grid always contains exactly 8 .tray-cell elements (from the template);
|
||||
// the first one receives .tray-role-card and data-role instead of a new element
|
||||
// being inserted, so the cell count never changes.
|
||||
function placeCard(roleCode, onComplete) {
|
||||
if (!_grid) { if (onComplete) onComplete(); return; }
|
||||
var firstCell = _grid.querySelector('.tray-cell');
|
||||
if (!firstCell) { if (onComplete) onComplete(); return; }
|
||||
|
||||
firstCell.classList.add('tray-role-card');
|
||||
firstCell.dataset.role = roleCode;
|
||||
firstCell.textContent = roleCode;
|
||||
|
||||
open();
|
||||
_arcIn(firstCell, function () {
|
||||
forceClose();
|
||||
if (onComplete) onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
function _startDrag(clientX, clientY) {
|
||||
_dragHandled = false;
|
||||
if (_wrap) _wrap.classList.add('tray-dragging');
|
||||
@@ -428,6 +459,14 @@ var Tray = (function () {
|
||||
_onBtnClick = null;
|
||||
}
|
||||
_cancelPendingHide();
|
||||
// Clear any role-card state from tray cells (Jasmine afterEach)
|
||||
if (_grid) {
|
||||
_grid.querySelectorAll('.tray-cell').forEach(function (el) {
|
||||
el.classList.remove('tray-role-card', 'arc-in');
|
||||
el.textContent = '';
|
||||
delete el.dataset.role;
|
||||
});
|
||||
}
|
||||
_wrap = null;
|
||||
_btn = null;
|
||||
_tray = null;
|
||||
@@ -446,6 +485,7 @@ var Tray = (function () {
|
||||
close: close,
|
||||
forceClose: forceClose,
|
||||
isOpen: isOpen,
|
||||
placeCard: placeCard,
|
||||
reset: reset,
|
||||
_testSetLandscape: function (v) { _landscapeOverride = v; },
|
||||
};
|
||||
|
||||
@@ -655,43 +655,6 @@ class SelectRoleViewTest(TestCase):
|
||||
)
|
||||
|
||||
|
||||
class RevealPhaseRenderingTest(TestCase):
|
||||
def setUp(self):
|
||||
self.founder = User.objects.create(email="founder@test.io")
|
||||
self.room = Room.objects.create(name="Test Room", owner=self.founder)
|
||||
gamers = [self.founder]
|
||||
for i in range(2, 7):
|
||||
gamers.append(User.objects.create(email=f"g{i}@test.io"))
|
||||
roles = ["PC", "BC", "SC", "AC", "NC", "EC"]
|
||||
for i, (gamer, role) in enumerate(zip(gamers, roles), start=1):
|
||||
TableSeat.objects.create(
|
||||
room=self.room, gamer=gamer, slot_number=i,
|
||||
role=role, role_revealed=True,
|
||||
)
|
||||
self.room.gate_status = Room.OPEN
|
||||
self.room.table_status = Room.SIG_SELECT
|
||||
self.room.save()
|
||||
self.client.force_login(self.founder)
|
||||
|
||||
def test_face_up_role_cards_rendered_when_sig_select(self):
|
||||
response = self.client.get(
|
||||
reverse("epic:gatekeeper", kwargs={"room_id": self.room.id})
|
||||
)
|
||||
self.assertContains(response, "face-up")
|
||||
|
||||
def test_inv_role_card_slot_present(self):
|
||||
response = self.client.get(
|
||||
reverse("epic:gatekeeper", kwargs={"room_id": self.room.id})
|
||||
)
|
||||
self.assertContains(response, "id_inv_role_card")
|
||||
|
||||
def test_partner_indicator_present_when_sig_select(self):
|
||||
response = self.client.get(
|
||||
reverse("epic:gatekeeper", kwargs={"room_id": self.room.id})
|
||||
)
|
||||
self.assertContains(response, "partner-indicator")
|
||||
|
||||
|
||||
class RoomActionsViewTest(TestCase):
|
||||
def setUp(self):
|
||||
self.owner = User.objects.create(email="owner@test.io")
|
||||
|
||||
Reference in New Issue
Block a user