Compare commits
5 Commits
db443b7533
...
f348a19312
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f348a19312 | ||
|
|
bc4565f161 | ||
|
|
4963237420 | ||
|
|
191dad5365 | ||
|
|
611ca9b5b4 |
@@ -205,8 +205,20 @@ class MySeaDrawSeaLandingTest(FunctionalTest):
|
||||
"""User w. sig → /gameboard/my-sea/ shows the DRY table hex (re-
|
||||
used from my-sign / the room shell) w. a central FREE DRAW btn.
|
||||
Element ID `id_draw_sea_btn` describes intent (the draw entry
|
||||
point) — a future sprint will conditionally swap the label to
|
||||
DRAW SEA once the daily free has been used."""
|
||||
point); the hex-center btn is a 3-way state machine:
|
||||
- FREE DRAW (`#id_draw_sea_btn`) — fresh user (no active row, no
|
||||
deposit). Pinned here.
|
||||
- PAID DRAW (`#id_my_sea_paid_draw_btn`) — deposit reserved.
|
||||
Pinned by `MySeaLandingPaidDrawTest`.
|
||||
- GATE VIEW (`#id_my_sea_gate_view_btn`) — quota spent, no
|
||||
deposit (post-DEL). Pinned by
|
||||
`MySeaDeleteDrawAndGuardPortalTest` (the DEL-confirm flow).
|
||||
|
||||
The three IDs are mutually exclusive — only one renders at a
|
||||
time. This test pins the fresh-user side of that exclusion
|
||||
(FREE DRAW present, the other two absent); the deposit + post-
|
||||
DEL sides have their own dedicated FTs that pin the same
|
||||
invariant from the other directions."""
|
||||
self.create_pre_authenticated_session(self.email)
|
||||
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||
# data-phase=landing on the page wrapper
|
||||
@@ -221,6 +233,17 @@ class MySeaDrawSeaLandingTest(FunctionalTest):
|
||||
self.assertIn("FREE", btn.text.upper())
|
||||
self.assertIn("DRAW", btn.text.upper())
|
||||
self.assertIn("btn-primary", btn.get_attribute("class"))
|
||||
# Mutual exclusion: PAID DRAW + GATE VIEW must NOT also render.
|
||||
self.assertEqual(
|
||||
len(page.find_elements(By.CSS_SELECTOR, "#id_my_sea_paid_draw_btn")),
|
||||
0,
|
||||
"PAID DRAW btn must not render when no deposit is reserved",
|
||||
)
|
||||
self.assertEqual(
|
||||
len(page.find_elements(By.CSS_SELECTOR, "#id_my_sea_gate_view_btn")),
|
||||
0,
|
||||
"GATE VIEW btn must not render when no active draw row exists",
|
||||
)
|
||||
|
||||
# ── Test 2 ───────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1337,9 +1360,13 @@ class MySeaGatekeeperPageTest(FunctionalTest):
|
||||
|
||||
def test_paid_draw_commits_token_and_redirects_to_picker(self):
|
||||
"""PAID DRAW commits the deposited token (FREE token gets
|
||||
consumed → user's token count drops by 1); server resets the
|
||||
MySeaDraw row (hand=[], created_at=now, deposit cleared); user
|
||||
lands back on /gameboard/my-sea/ ready to draw a fresh hand."""
|
||||
consumed → user's token count drops by 1); iter-6c spec then
|
||||
DROPS the active_draw row entirely + redirects to /gameboard/
|
||||
my-sea/?phase=picker so the user lands directly in the picker
|
||||
ready to draw. Previously the row was preserved w. reset
|
||||
created_at — that looped the user back to GATE VIEW
|
||||
(`quota_spent=True` w. row present). See
|
||||
[[sprint-my-sea-iter-6c-may20]] for the rationale."""
|
||||
from apps.gameboard.models import MySeaDraw
|
||||
from apps.lyric.models import Token
|
||||
self._save_empty_hand_draw()
|
||||
@@ -1360,19 +1387,23 @@ class MySeaGatekeeperPageTest(FunctionalTest):
|
||||
)
|
||||
)
|
||||
paid_draw.click()
|
||||
# Redirect lands on /gameboard/my-sea/ (landing or picker).
|
||||
# Redirect lands on /gameboard/my-sea/?phase=picker so the user
|
||||
# drops straight into the picker (no FREE-DRAW landing click).
|
||||
self.wait_for(
|
||||
lambda: self.assertIn("/gameboard/my-sea/", self.browser.current_url)
|
||||
lambda: self.assertIn(
|
||||
"/gameboard/my-sea/?phase=picker", self.browser.current_url
|
||||
)
|
||||
)
|
||||
# FREE token consumed.
|
||||
self.assertEqual(
|
||||
Token.objects.filter(user=self.gamer, token_type=Token.FREE).count(),
|
||||
free_count_before - 1,
|
||||
)
|
||||
# MySeaDraw row: hand reset to empty, deposit cleared, fresh quota.
|
||||
draw = MySeaDraw.objects.get(user=self.gamer)
|
||||
self.assertEqual(draw.hand, [])
|
||||
self.assertIsNone(draw.deposit_token_id)
|
||||
# Row dropped — `active_draw is None` is the signal that lets
|
||||
# the my_sea view honour the `?phase=picker` override.
|
||||
self.assertFalse(
|
||||
MySeaDraw.objects.filter(user=self.gamer).exists()
|
||||
)
|
||||
|
||||
|
||||
class MySeaLandingPaidDrawTest(FunctionalTest):
|
||||
|
||||
@@ -305,6 +305,15 @@ body.page-gameboard {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
// Portrait — stack the cross spread above the form col (mirrors the
|
||||
// gameroom SEA SELECT modal's `@media (max-width: 600px)` stack
|
||||
// pattern in `_card-deck.scss`). Landscape keeps the side-by-side
|
||||
// layout since horizontal real-estate is the abundant axis there.
|
||||
// User-spec 2026-05-20.
|
||||
@media (orientation: portrait) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
// .my-sea-cross renders all 6 surrounding positions (crown/leave/lay/
|
||||
@@ -492,6 +501,45 @@ body.page-gameboard {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
// Portrait — split the form into two columns. LEFT carries the
|
||||
// SPREAD field (label + reversal hint + combobox) above the action
|
||||
// btns (AUTO DRAW / GATE VIEW + DEL); RIGHT carries the DECKS
|
||||
// section (label + GRAVITY/LEVITY stacks) spanning both rows.
|
||||
// Without this rearrange, on a phone-portrait viewport the stacked
|
||||
// form col runs off the bottom of the viewport — DECKS lives above
|
||||
// the action btns + everything below the fold (user-spec
|
||||
// 2026-05-21). `.sea-form-main` uses `display: contents` so its
|
||||
// `.sea-field` + `.sea-stacks` children act as direct grid items of
|
||||
// the form col despite the intermediate wrapper in the DOM.
|
||||
//
|
||||
// Selector chains `.sea-form-col.my-sea-form-col` to win against
|
||||
// `_card-deck.scss`'s base `.sea-form-col { display: flex; flex-
|
||||
// direction: column }` (same 1-class specificity; card-deck loads
|
||||
// AFTER gameboard in `core.scss`, so source order would otherwise
|
||||
// overrule my-sea's grid). Chained 2-class selector pulls
|
||||
// specificity ahead regardless of source order.
|
||||
@media (orientation: portrait) {
|
||||
&.sea-form-col {
|
||||
flex: 0 0 auto;
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-areas:
|
||||
"field stacks"
|
||||
"actions stacks";
|
||||
column-gap: 1rem;
|
||||
row-gap: 0.5rem;
|
||||
align-items: start;
|
||||
|
||||
.sea-form-main { display: contents; }
|
||||
.sea-field { grid-area: field; margin-bottom: 0; }
|
||||
.sea-stacks { grid-area: stacks; margin: 0; justify-content: center; }
|
||||
.sea-form-actions { grid-area: actions; align-self: end; padding-top: 0; }
|
||||
}
|
||||
}
|
||||
|
||||
// Bump the dropdown z-index well above the picker's stacking ints
|
||||
// (cover z:3, cross z:4, modal stage z:9999 only opens on draw
|
||||
// anyway). 1000 sits above any in-page layer the user might be
|
||||
@@ -499,6 +547,22 @@ body.page-gameboard {
|
||||
.sea-select-list {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
// Portrait — open the SPREAD dropdown UPWARD instead of downward.
|
||||
// The portrait form col sits at the bottom of the viewport (below
|
||||
// the cross spread) w. the navbar/footer pinned beneath it, so the
|
||||
// default `top: 100%` dropdown extends BELOW the visible aperture
|
||||
// + the user can't scroll to it. Flipping to `bottom: 100%` makes
|
||||
// the list grow upward into the abundant green aperture above.
|
||||
// Chained `&.sea-form-col` to beat card-deck's later-loaded base
|
||||
// (per [[feedback-scss-import-order-specificity]]).
|
||||
@media (orientation: portrait) {
|
||||
&.sea-form-col .sea-select .sea-select-list {
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
margin: 0 0 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LOCK HAND post-commit visual-lock: dim everything that mutates the
|
||||
|
||||
Reference in New Issue
Block a user