New Post applet: kill #id_text remnant, add OK btn, bolden composer inputs — TDD
Prep for porting a post composer into room.html. Three changes on the New Game / New Post applets:
1. Renamed the old Percival #id_text → #id_new_post_text. _form.html (the shared post composer) is now parameterized on input_id (default id_new_post_text) + submit_id (default id_new_post_btn); the feedback div + aria-describedby track {{ input_id }}_feedback. _applet-new-post.html includes it with input_id="id_new_post_text". room.html will reuse the same partial with input_id="id_room_post_text". Refs updated: _scripts.html (initialize("#id_new_post_text")), Jasmine Spec.js, FT post_page.py. _form.html has exactly one includer.
2. Added an OK .btn-confirm submit to _form.html (flex .composer-row: line input + OK, validation feedback below), mirroring the New Game applet. ENTER still submits, so the existing add_post_line FTs stay green.
3. Composer-input styling in _applets.scss: #id_new_game_name, #id_new_post_text { font-weight: 700; &:focus { color: --terUser } } — mirrors .sky-form-col input. The duoUser fill / secUser default text / terUser border+glow on focus already come from .form-control; this adds the 700 weight + the focus text-colour shift. room's #id_room_post_text joins this selector list when it lands.
Tests: NewPostTest ITs (conventional id + aria-describedby, OK .btn-confirm). 440 dashboard+billboard ITs green; Jasmine spec green with the renamed id; test_bill_new_post FT green (renamed input + OK btn, ENTER submit).
[[project-room-scroll-of-events]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,32 @@ class NewPostTest(TestCase):
|
|||||||
response = self.post_invalid_input()
|
response = self.post_invalid_input()
|
||||||
self.assertContains(response, html.escape(EMPTY_LINE_ERROR))
|
self.assertContains(response, html.escape(EMPTY_LINE_ERROR))
|
||||||
|
|
||||||
|
def _new_post_form(self, response):
|
||||||
|
parsed = lxml.html.fromstring(response.content)
|
||||||
|
forms = [f for f in parsed.cssselect("form")
|
||||||
|
if f.get("action") == "/billboard/new-post"]
|
||||||
|
self.assertTrue(forms, "new-post form not rendered")
|
||||||
|
return forms[0]
|
||||||
|
|
||||||
|
def test_new_post_input_uses_conventional_id_not_percival_id_text(self):
|
||||||
|
"""The line input's id is the conventional #id_new_post_text, not the
|
||||||
|
old Percival remnant #id_text (the form is about to be reused in
|
||||||
|
room.html, which needs its own #id_room_post_text)."""
|
||||||
|
form = self._new_post_form(self.post_invalid_input())
|
||||||
|
ids = [i.get("id") for i in form.cssselect("input")]
|
||||||
|
self.assertIn("id_new_post_text", ids)
|
||||||
|
self.assertNotIn("id_text", ids)
|
||||||
|
# aria-describedby tracks the renamed feedback id too.
|
||||||
|
line_input = form.cssselect("#id_new_post_text")[0]
|
||||||
|
self.assertEqual(line_input.get("aria-describedby"), "id_new_post_text_feedback")
|
||||||
|
|
||||||
|
def test_new_post_applet_has_ok_confirm_button(self):
|
||||||
|
"""New Post gains an OK .btn-confirm submit button, like New Game."""
|
||||||
|
form = self._new_post_form(self.post_invalid_input())
|
||||||
|
buttons = form.cssselect("button.btn-confirm")
|
||||||
|
self.assertTrue(buttons, "no OK .btn-confirm in New Post applet")
|
||||||
|
self.assertEqual(buttons[0].text_content().strip(), "OK")
|
||||||
|
|
||||||
@override_settings(COMPRESS_ENABLED=False)
|
@override_settings(COMPRESS_ENABLED=False)
|
||||||
class PostViewTest(TestCase):
|
class PostViewTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -27,13 +27,14 @@ class PostPage:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_line_input_box(self):
|
def get_line_input_box(self):
|
||||||
# /billboard/ new-post applet uses #id_text (creates a fresh Post);
|
# /billboard/ new-post applet uses #id_new_post_text (creates a fresh
|
||||||
# post.html aperture uses #id_post_line_text (appends to existing).
|
# Post); post.html aperture uses #id_post_line_text (appends to an
|
||||||
# `add_post_line` is called from both contexts, so probe in order.
|
# existing one). `add_post_line` is called from both contexts, so
|
||||||
|
# probe in order.
|
||||||
boxes = self.test.browser.find_elements(By.ID, "id_post_line_text")
|
boxes = self.test.browser.find_elements(By.ID, "id_post_line_text")
|
||||||
if boxes:
|
if boxes:
|
||||||
return boxes[0]
|
return boxes[0]
|
||||||
return self.test.browser.find_element(By.ID, "id_text")
|
return self.test.browser.find_element(By.ID, "id_new_post_text")
|
||||||
|
|
||||||
def add_post_line(self, line_text):
|
def add_post_line(self, line_text):
|
||||||
new_line_no = len(self.get_table_rows()) + 1
|
new_line_no = len(self.get_table_rows()) + 1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
console.log("Spec.js is loading");
|
console.log("Spec.js is loading");
|
||||||
|
|
||||||
describe("GameArray JavaScript", () => {
|
describe("GameArray JavaScript", () => {
|
||||||
const inputId= "id_text";
|
const inputId= "id_new_post_text";
|
||||||
const errorClass = "invalid-feedback";
|
const errorClass = "invalid-feedback";
|
||||||
const inputSelector = `#${inputId}`;
|
const inputSelector = `#${inputId}`;
|
||||||
const errorSelector = `.${errorClass}`;
|
const errorSelector = `.${errorClass}`;
|
||||||
@@ -20,10 +20,10 @@ describe("GameArray JavaScript", () => {
|
|||||||
class="form-control form-control-lg is-invalid"
|
class="form-control form-control-lg is-invalid"
|
||||||
placeholder="Enter a to-do item"
|
placeholder="Enter a to-do item"
|
||||||
value="Value as submitted"
|
value="Value as submitted"
|
||||||
aria-describedby="id_text_feedback"
|
aria-describedby="id_new_post_text_feedback"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<div id="id_text_feedback" class="${errorClass}">An error message</div>
|
<div id="id_new_post_text_feedback" class="${errorClass}">An error message</div>
|
||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(testDiv);
|
document.body.appendChild(testDiv);
|
||||||
|
|||||||
@@ -98,6 +98,20 @@
|
|||||||
}
|
}
|
||||||
#id_bud_menu { @extend %applet-menu; }
|
#id_bud_menu { @extend %applet-menu; }
|
||||||
|
|
||||||
|
// New Game / New Post composer line-inputs — mirror the My Sky form-col inputs
|
||||||
|
// (`_sky.scss` .sky-form-col input): bolder text + a terUser focus shift. The
|
||||||
|
// duoUser fill, secUser default text, and terUser border/glow on focus already
|
||||||
|
// come from `.form-control`; these only add the 700 weight and the focus
|
||||||
|
// text-colour. room.html's #id_room_post_text joins this list when it lands.
|
||||||
|
#id_new_game_name,
|
||||||
|
#id_new_post_text {
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
color: rgba(var(--terUser), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Page-level gear buttons — fixed to viewport bottom-right
|
// Page-level gear buttons — fixed to viewport bottom-right
|
||||||
.gameboard-page,
|
.gameboard-page,
|
||||||
.dashboard-page,
|
.dashboard-page,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
console.log("Spec.js is loading");
|
console.log("Spec.js is loading");
|
||||||
|
|
||||||
describe("GameArray JavaScript", () => {
|
describe("GameArray JavaScript", () => {
|
||||||
const inputId= "id_text";
|
const inputId= "id_new_post_text";
|
||||||
const errorClass = "invalid-feedback";
|
const errorClass = "invalid-feedback";
|
||||||
const inputSelector = `#${inputId}`;
|
const inputSelector = `#${inputId}`;
|
||||||
const errorSelector = `.${errorClass}`;
|
const errorSelector = `.${errorClass}`;
|
||||||
@@ -20,10 +20,10 @@ describe("GameArray JavaScript", () => {
|
|||||||
class="form-control form-control-lg is-invalid"
|
class="form-control form-control-lg is-invalid"
|
||||||
placeholder="Enter a to-do item"
|
placeholder="Enter a to-do item"
|
||||||
value="Value as submitted"
|
value="Value as submitted"
|
||||||
aria-describedby="id_text_feedback"
|
aria-describedby="id_new_post_text_feedback"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<div id="id_text_feedback" class="${errorClass}">An error message</div>
|
<div id="id_new_post_text_feedback" class="${errorClass}">An error message</div>
|
||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(testDiv);
|
document.body.appendChild(testDiv);
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
>
|
>
|
||||||
<h2>New Post</h2>
|
<h2>New Post</h2>
|
||||||
{% url "billboard:new_post" as form_action %}
|
{% url "billboard:new_post" as form_action %}
|
||||||
{% include "apps/dashboard/_partials/_form.html" with form=form form_action=form_action %}
|
{% include "apps/dashboard/_partials/_form.html" with form=form form_action=form_action input_id="id_new_post_text" submit_id="id_new_post_btn" %}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
|
{% comment %}
|
||||||
|
Shared post-composer form. `input_id` lets each surface give the line input a
|
||||||
|
distinct id (new-post applet → id_new_post_text; room.html → id_room_post_text);
|
||||||
|
`submit_id` likewise for the OK button. Mirrors the New Game applet: a flex row
|
||||||
|
with the line input + an OK .btn-confirm submit. The validation feedback sits
|
||||||
|
below the row (and its id tracks `input_id` for aria-describedby).
|
||||||
|
{% endcomment %}
|
||||||
|
{% with input_id=input_id|default:"id_new_post_text" submit_id=submit_id|default:"id_new_post_btn" %}
|
||||||
<form method="POST" action="{{ form_action }}">
|
<form method="POST" action="{{ form_action }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input
|
<div class="composer-row" style="display:flex; gap:0.5rem; align-items:center;">
|
||||||
id="id_text"
|
<input
|
||||||
name="text"
|
id="{{ input_id }}"
|
||||||
class="form-control form-control-lg{% if form.errors.text %} is-invalid{% endif %}"
|
name="text"
|
||||||
placeholder="Enter a post line"
|
class="form-control form-control-lg{% if form.errors.text %} is-invalid{% endif %}"
|
||||||
value="{{ form.text.value | default:'' }}"
|
placeholder="Enter a post line"
|
||||||
aria-describedby="id_text_feedback"
|
value="{{ form.text.value | default:'' }}"
|
||||||
required
|
aria-describedby="{{ input_id }}_feedback"
|
||||||
/>
|
style="flex:1; min-width:0;"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button type="submit" id="{{ submit_id }}" class="btn btn-confirm">OK</button>
|
||||||
|
</div>
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<div id="id_text_feedback" class="invalid-feedback">
|
<div id="{{ input_id }}_feedback" class="invalid-feedback">
|
||||||
{{ form.errors.text.0 }}
|
{{ form.errors.text.0 }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
{% endwith %}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
<script src="{% static "apps/gameboard/gameboard.js" %}"></script>
|
<script src="{% static "apps/gameboard/gameboard.js" %}"></script>
|
||||||
<script>
|
<script>
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
// #id_text — new-post applet on billboard.html;
|
// #id_new_post_text — new-post applet on billboard.html;
|
||||||
// #id_post_line_text — post.html bottom-anchored aperture.
|
// #id_post_line_text — post.html bottom-anchored aperture.
|
||||||
initialize("#id_text");
|
initialize("#id_new_post_text");
|
||||||
initialize("#id_post_line_text");
|
initialize("#id_post_line_text");
|
||||||
bindPaletteSwatches();
|
bindPaletteSwatches();
|
||||||
bindPaletteWheel();
|
bindPaletteWheel();
|
||||||
|
|||||||
Reference in New Issue
Block a user