post composer: restore validation-error reveal broken by the .composer-row wrap; CI FT cleanup — TDD
The OK-button composer redesign wrapped the line input in .composer-row, so the .form-control.is-invalid input is no longer a general SIBLING of its .invalid-feedback — the `&.is-invalid ~ .invalid-feedback` reveal (\_base.scss) silently stopped matching, so post-line validation errors rendered in the DOM but stayed display:none (invisible to users). Reveal via `.composer-row:has(.form-control.is-invalid) ~ .invalid-feedback`. Greens test_cannot_add_duplicate_lines + test_error_messages_are_cleared_on_input (both were catching this real regression, not flaky).
Harden WalletShopFreeDeckTest: the .tt-micro is briefly detached mid-HTMX-swap, so get_attribute('innerHTML') returns None and a bare assertIn raises TypeError — which wait_for does NOT retry. Coalesce to '' so it polls until the swap settles (explains the local-pass / CI-fail).
Delete test_core_styling.test_layout_and_styling: a Percival-era assertion that the post input is horizontally CENTRED in its section. The responsive .composer-row (input + OK btn) + the orientation-aware right-margin clamp intentionally removed that invariant (the input now lands in different spots per viewport). Zero behavioural coverage lost — the composer is covered by LineValidationTest + PostComposerOkButtonTest.
Skip GameViewsCarouselTest (red planning contract for the unbuilt Game-views carousel — see project-room-game-views-carousel).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,37 +0,0 @@
|
|||||||
from .base import FunctionalTest
|
|
||||||
from .post_page import PostPage
|
|
||||||
|
|
||||||
|
|
||||||
class LayoutAndStylingTest(FunctionalTest):
|
|
||||||
def test_layout_and_styling(self):
|
|
||||||
self.create_pre_authenticated_session("disco@test.io")
|
|
||||||
self.browser.get(self.live_server_url + '/billboard/')
|
|
||||||
post_page = PostPage(self)
|
|
||||||
|
|
||||||
self.browser.set_window_size(1024, 768)
|
|
||||||
|
|
||||||
def section_center(el):
|
|
||||||
return self.browser.execute_script("""
|
|
||||||
var el = arguments[0];
|
|
||||||
var ancestor = el.parentElement;
|
|
||||||
while (ancestor && ancestor.tagName !== 'SECTION'
|
|
||||||
&& !ancestor.classList.contains('container')) {
|
|
||||||
ancestor = ancestor.parentElement;
|
|
||||||
}
|
|
||||||
var a = ancestor || document.body;
|
|
||||||
var s = a.getBoundingClientRect();
|
|
||||||
var cs = window.getComputedStyle(a);
|
|
||||||
var pl = parseFloat(cs.paddingLeft);
|
|
||||||
var pr = parseFloat(cs.paddingRight);
|
|
||||||
var r = el.getBoundingClientRect();
|
|
||||||
return [r.x + r.width / 2, s.x + pl + (s.width - pl - pr) / 2];
|
|
||||||
""", el)
|
|
||||||
|
|
||||||
inputbox = post_page.get_line_input_box()
|
|
||||||
input_c, section_c = section_center(inputbox)
|
|
||||||
self.assertAlmostEqual(input_c, section_c, delta=10)
|
|
||||||
|
|
||||||
post_page.add_post_line("testing")
|
|
||||||
inputbox = post_page.get_line_input_box()
|
|
||||||
input_c, section_c = section_center(inputbox)
|
|
||||||
self.assertAlmostEqual(input_c, section_c, delta=10)
|
|
||||||
@@ -491,11 +491,15 @@ class WalletShopFreeDeckTest(FunctionalTest):
|
|||||||
# + may not be hover-visible under Selenium's strict-target check).
|
# + may not be hover-visible under Selenium's strict-target check).
|
||||||
self.browser.execute_script("arguments[0].click();", free_btn)
|
self.browser.execute_script("arguments[0].click();", free_btn)
|
||||||
# 3. The tile now shows 'Already owned' instead of the FREE ITEM btn.
|
# 3. The tile now shows 'Already owned' instead of the FREE ITEM btn.
|
||||||
|
# `or ""` — the .tt-micro is briefly detached mid-HTMX-swap, when
|
||||||
|
# get_attribute("innerHTML") returns None; a bare assertIn then raises
|
||||||
|
# TypeError (which wait_for does NOT retry). Coalesce to "" so it polls
|
||||||
|
# until the swap settles.
|
||||||
self.wait_for(lambda: self.assertIn(
|
self.wait_for(lambda: self.assertIn(
|
||||||
"Already owned",
|
"Already owned",
|
||||||
self.browser.find_element(
|
self.browser.find_element(
|
||||||
By.CSS_SELECTOR, "#id_shop_tarot-rider-waite-smith .tt-micro"
|
By.CSS_SELECTOR, "#id_shop_tarot-rider-waite-smith .tt-micro"
|
||||||
).get_attribute("innerHTML"),
|
).get_attribute("innerHTML") or "",
|
||||||
))
|
))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.browser.find_elements(
|
self.browser.find_elements(
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ These tests fail until the feature lands — they ARE the contract for the build
|
|||||||
FT bucket: game_room. IT/UT coverage (view context, room↔Post link, aggregate
|
FT bucket: game_room. IT/UT coverage (view context, room↔Post link, aggregate
|
||||||
merge) is written beneath these as the build proceeds.
|
merge) is written beneath these as the build proceeds.
|
||||||
"""
|
"""
|
||||||
|
from unittest import skip
|
||||||
|
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
@@ -35,6 +37,8 @@ from apps.lyric.models import User
|
|||||||
VIEW_ORDER = ["atlas", "scroll", "post", "chat", "pulse"]
|
VIEW_ORDER = ["atlas", "scroll", "post", "chat", "pulse"]
|
||||||
|
|
||||||
|
|
||||||
|
@skip("Game-views carousel — RED contract; feature unbuilt. "
|
||||||
|
"Remove the skip as each view lands. See project-room-game-views-carousel.")
|
||||||
class GameViewsCarouselTest(FunctionalTest):
|
class GameViewsCarouselTest(FunctionalTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|||||||
@@ -184,6 +184,16 @@ body {
|
|||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The post composer wraps its input + OK btn in `.composer-row`, so the
|
||||||
|
// `.form-control.is-invalid` input is no longer a general SIBLING of its
|
||||||
|
// `.invalid-feedback` (they live in different parents) — the
|
||||||
|
// `&.is-invalid ~ .invalid-feedback` reveal above silently stops
|
||||||
|
// matching, so the error renders in the DOM but stays display:none and
|
||||||
|
// the user never sees post-line validation. Reveal via :has() on the row.
|
||||||
|
.composer-row:has(.form-control.is-invalid) ~ .invalid-feedback {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
margin: 0.75rem;
|
margin: 0.75rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user