diff --git a/custom_components/localthings/config_flow.py b/custom_components/localthings/config_flow.py index 5044b61..7afd95a 100644 --- a/custom_components/localthings/config_flow.py +++ b/custom_components/localthings/config_flow.py @@ -1011,12 +1011,29 @@ class LocalThingsOptionsFlow(config_entries.OptionsFlow): return None def _cloud_progress_placeholders(self, coord) -> dict[str, str]: + """Counts plus the names assigned so far. + + Listing them is what makes a nine-program walk followable -- it is + the only orientation available, since the programs still to do are + unnamed by definition. Shown while naming too, where it doubles as + duplicate avoidance: the form rejects a repeated name, so seeing the + others first beats being bounced. + + In the appliance's own advertised order, which is at least a stable + order, without numbering them -- whether that order matches the dial + is plausible but unverified, and implying it would be worse than + saying nothing. + """ rep = coord.cloud_course_rep() courses = cycle_options(coord.canonical_resources(MAIN)) record = coord.cloud_courses.snapshot() slots = cloudcourse.cloud_slots(rep, courses) - named = sum(1 for s in slots if (record["slots"].get(s) or {}).get("name")) - return {"named": str(named), "total": str(len(slots))} + names = [n for s in slots if (n := (record["slots"].get(s) or {}).get("name"))] + return { + "named": str(len(names)), + "total": str(len(slots)), + "named_list": ", ".join(names) if names else "none yet", + } async def async_step_cloud_name( self, user_input: dict[str, Any] | None = None diff --git a/custom_components/localthings/translations/cs.json b/custom_components/localthings/translations/cs.json index 6f2060b..ba42b63 100644 --- a/custom_components/localthings/translations/cs.json +++ b/custom_components/localthings/translations/cs.json @@ -1466,14 +1466,14 @@ }, "cloud_name": { "title": "Name this cycle", - "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\n{named} of {total} named — you can close this dialog whenever you like, names are saved as you go.", + "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.", "data": { "name": "Name" } }, "cloud_timeout": { "title": "No cycle selected", - "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\n{named} of {total} named — everything named so far is already saved.", + "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.", "menu_options": { "cloud_guided": "Wait again", "cloud_finish": "Finish" @@ -1490,7 +1490,7 @@ "not_loaded": "Toto zařízení ještě není připojeno. Zkuste to znovu, až se načte." }, "progress": { - "cloud_wait": "Select a downloaded cycle on the appliance now.\n\n{named} of {total} named — you can close this dialog at any time, names are saved as you go." + "cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go." } }, "issues": { diff --git a/custom_components/localthings/translations/de.json b/custom_components/localthings/translations/de.json index e81aff2..9f1b2f7 100644 --- a/custom_components/localthings/translations/de.json +++ b/custom_components/localthings/translations/de.json @@ -1466,14 +1466,14 @@ }, "cloud_name": { "title": "Name this cycle", - "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\n{named} of {total} named — you can close this dialog whenever you like, names are saved as you go.", + "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.", "data": { "name": "Name" } }, "cloud_timeout": { "title": "No cycle selected", - "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\n{named} of {total} named — everything named so far is already saved.", + "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.", "menu_options": { "cloud_guided": "Wait again", "cloud_finish": "Finish" @@ -1490,7 +1490,7 @@ "not_loaded": "Dieses Gerät ist noch nicht verbunden. Versuchen Sie es erneut, sobald es geladen wurde." }, "progress": { - "cloud_wait": "Select a downloaded cycle on the appliance now.\n\n{named} of {total} named — you can close this dialog at any time, names are saved as you go." + "cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go." } }, "issues": { diff --git a/custom_components/localthings/translations/en.json b/custom_components/localthings/translations/en.json index c593801..19c4a91 100644 --- a/custom_components/localthings/translations/en.json +++ b/custom_components/localthings/translations/en.json @@ -1466,14 +1466,14 @@ }, "cloud_name": { "title": "Name this cycle", - "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\n{named} of {total} named — you can close this dialog whenever you like, names are saved as you go.", + "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.", "data": { "name": "Name" } }, "cloud_timeout": { "title": "No cycle selected", - "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\n{named} of {total} named — everything named so far is already saved.", + "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.", "menu_options": { "cloud_guided": "Wait again", "cloud_finish": "Finish" @@ -1490,7 +1490,7 @@ "not_loaded": "This device isn't connected yet. Try again once it has loaded." }, "progress": { - "cloud_wait": "Select a downloaded cycle on the appliance now.\n\n{named} of {total} named — you can close this dialog at any time, names are saved as you go." + "cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go." } }, "issues": { diff --git a/custom_components/localthings/translations/es.json b/custom_components/localthings/translations/es.json index 86567e9..f3ec02d 100644 --- a/custom_components/localthings/translations/es.json +++ b/custom_components/localthings/translations/es.json @@ -113,14 +113,14 @@ }, "cloud_name": { "title": "Name this cycle", - "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\n{named} of {total} named — you can close this dialog whenever you like, names are saved as you go.", + "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.", "data": { "name": "Name" } }, "cloud_timeout": { "title": "No cycle selected", - "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\n{named} of {total} named — everything named so far is already saved.", + "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.", "menu_options": { "cloud_guided": "Wait again", "cloud_finish": "Finish" @@ -137,7 +137,7 @@ "not_loaded": "Este dispositivo aún no está conectado. Inténtalo de nuevo cuando se haya cargado." }, "progress": { - "cloud_wait": "Select a downloaded cycle on the appliance now.\n\n{named} of {total} named — you can close this dialog at any time, names are saved as you go." + "cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go." } }, "issues": { diff --git a/custom_components/localthings/translations/it.json b/custom_components/localthings/translations/it.json index 0884c2e..b34ec07 100644 --- a/custom_components/localthings/translations/it.json +++ b/custom_components/localthings/translations/it.json @@ -1466,14 +1466,14 @@ }, "cloud_name": { "title": "Name this cycle", - "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\n{named} of {total} named — you can close this dialog whenever you like, names are saved as you go.", + "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.", "data": { "name": "Name" } }, "cloud_timeout": { "title": "No cycle selected", - "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\n{named} of {total} named — everything named so far is already saved.", + "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.", "menu_options": { "cloud_guided": "Wait again", "cloud_finish": "Finish" @@ -1490,7 +1490,7 @@ "not_loaded": "Questo dispositivo non è ancora connesso. Riprova dopo il caricamento." }, "progress": { - "cloud_wait": "Select a downloaded cycle on the appliance now.\n\n{named} of {total} named — you can close this dialog at any time, names are saved as you go." + "cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go." } }, "issues": { diff --git a/custom_components/localthings/translations/ko.json b/custom_components/localthings/translations/ko.json index 91bea6b..30f234d 100644 --- a/custom_components/localthings/translations/ko.json +++ b/custom_components/localthings/translations/ko.json @@ -1466,14 +1466,14 @@ }, "cloud_name": { "title": "Name this cycle", - "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\n{named} of {total} named — you can close this dialog whenever you like, names are saved as you go.", + "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.", "data": { "name": "Name" } }, "cloud_timeout": { "title": "No cycle selected", - "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\n{named} of {total} named — everything named so far is already saved.", + "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.", "menu_options": { "cloud_guided": "Wait again", "cloud_finish": "Finish" @@ -1490,7 +1490,7 @@ "not_loaded": "이 기기는 아직 연결되지 않았습니다. 기기를 불러온 후 다시 시도하세요." }, "progress": { - "cloud_wait": "Select a downloaded cycle on the appliance now.\n\n{named} of {total} named — you can close this dialog at any time, names are saved as you go." + "cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go." } }, "issues": { diff --git a/custom_components/localthings/translations/nl.json b/custom_components/localthings/translations/nl.json index 214bb03..7071363 100644 --- a/custom_components/localthings/translations/nl.json +++ b/custom_components/localthings/translations/nl.json @@ -1466,14 +1466,14 @@ }, "cloud_name": { "title": "Name this cycle", - "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\n{named} of {total} named — you can close this dialog whenever you like, names are saved as you go.", + "description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.", "data": { "name": "Name" } }, "cloud_timeout": { "title": "No cycle selected", - "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\n{named} of {total} named — everything named so far is already saved.", + "description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.", "menu_options": { "cloud_guided": "Wait again", "cloud_finish": "Finish" @@ -1490,7 +1490,7 @@ "not_loaded": "Dit apparaat is nog niet verbonden. Probeer het opnieuw zodra het is geladen." }, "progress": { - "cloud_wait": "Select a downloaded cycle on the appliance now.\n\n{named} of {total} named — you can close this dialog at any time, names are saved as you go." + "cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go." } }, "issues": { diff --git a/tests/test_cloud_courses_flow.py b/tests/test_cloud_courses_flow.py index 52bad55..4ee1629 100644 --- a/tests/test_cloud_courses_flow.py +++ b/tests/test_cloud_courses_flow.py @@ -589,3 +589,36 @@ async def test_closing_the_dialog_stops_probing_the_appliance(hass: HomeAssistan settled = probes await asyncio.sleep(0.05) assert probes == settled + + +async def test_guided_lists_the_names_assigned_so_far(hass: HomeAssistant): + """The only orientation available on a long walk -- the programs still to + do are unnamed by definition, so 'done so far' is all there is to show. + It doubles as duplicate avoidance, since the form rejects a repeat.""" + coordinator = await _coordinator(hass) + handler = await _options_handler(hass, coordinator) + + assert handler._cloud_progress_placeholders(coordinator)["named_list"] == "none yet" + + coordinator.apply_cloud_courses({"55": "Sports", "6B": "Jeans"}, "87") + await _flush(hass) + placeholders = handler._cloud_progress_placeholders(coordinator) + assert placeholders["named"] == "2" + assert placeholders["total"] == "9" + # The appliance's own advertised order, not naming order: 6B precedes 55 + # in CloudExtraCourse_0A5C286B2D0C55301A. + assert placeholders["named_list"] == "Jeans, Sports" + + +async def test_the_name_form_shows_the_other_names_while_typing(hass: HomeAssistant): + coordinator = await _coordinator(hass) + coordinator.apply_cloud_courses({"6B": "Jeans"}, "87") + await _flush(hass) + + handler = await _options_handler(hass, coordinator) + await handler.async_step_cloud_guided() + _stub_probe(coordinator, ["55"]) + await _run_wait(handler) + + form = await handler.async_step_cloud_name() + assert form["description_placeholders"]["named_list"] == "Jeans"