Honor UntilChanged in sync; let SearchSelect own its disabled look
Follow-ups on the add-form fixes: - syncSelectInputUntilChanged now actually stops mirroring once the user edits the target (the "UntilChanged" contract). The old focus-based stop was a no-op (wrong removeEventListener reference), so live sync kept clobbering a manually-edited Sort name. Track dirty targets in a Set keyed by syncData index; programmatic writes don't fire "input", so only real user edits mark a target dirty. Drops the dead focus listener. - SearchSelect now greys itself when disabled, via has-[:disabled]: utilities on its container class — the visible "box" is the wrapper <div>, so disabling the transparent inner input alone left it looking active. The component owns its disabled appearance; callers only toggle the inner control's `disabled`. - Document the composite-widget disabling approach in CLAUDE.md and the SearchSelect docstring. Extends the e2e tests: sync drops after a manual Sort name edit; disabled related-game wrapper computes opacity 0.5 (and 1 when re-enabled). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -167,8 +167,39 @@ def test_add_purchase_type_game_disables_related_game_search(
|
||||
(a <div> ignores the disabled property)."""
|
||||
page = authenticated_page
|
||||
page.goto(f"{live_server.url}{reverse('games:add_purchase')}")
|
||||
wrapper = page.locator("#id_related_game")
|
||||
search = page.locator('#id_related_game [data-search-select-search]')
|
||||
|
||||
page.select_option("#id_type", "game")
|
||||
expect(search).to_be_disabled()
|
||||
# The component greys itself via has-[:disabled] when the input is disabled.
|
||||
assert wrapper.evaluate("el => getComputedStyle(el).opacity") == "0.5"
|
||||
|
||||
page.select_option("#id_type", "dlc")
|
||||
expect(search).to_be_enabled()
|
||||
assert wrapper.evaluate("el => getComputedStyle(el).opacity") == "1"
|
||||
|
||||
|
||||
def test_add_game_sync_stops_once_sort_name_edited(
|
||||
authenticated_page: Page, live_server
|
||||
):
|
||||
"""Name → Sort name mirrors live, but stops the moment the user edits Sort
|
||||
name directly (the 'UntilChanged' contract). Editing Name afterwards must
|
||||
not clobber the user's manual Sort name."""
|
||||
page = authenticated_page
|
||||
page.goto(f"{live_server.url}{reverse('games:add_game')}")
|
||||
name = page.locator("#id_name")
|
||||
sort = page.locator("#id_sort_name")
|
||||
|
||||
name.click()
|
||||
name.type("Halo")
|
||||
expect(sort).to_have_value("Halo") # live mirror before any manual edit
|
||||
|
||||
sort.fill("Custom Sort") # user takes over the target → sync drops
|
||||
expect(sort).to_have_value("Custom Sort")
|
||||
|
||||
name.click()
|
||||
name.press("End")
|
||||
name.type(" 2")
|
||||
expect(name).to_have_value("Halo 2")
|
||||
expect(sort).to_have_value("Custom Sort") # not clobbered
|
||||
|
||||
Reference in New Issue
Block a user