diff --git a/templates/index.html b/templates/index.html index a3e495c..f78e451 100644 --- a/templates/index.html +++ b/templates/index.html @@ -122,7 +122,7 @@
-
+
@@ -258,12 +258,7 @@ retPhase = true; } else { /* selecting return date */ - if (sameDay(d, outDate)) { - /* tapped same day → reset */ - outDate = null; - retDate = null; - retPhase = false; - } else if (d < outDate) { + if (d < outDate) { /* earlier than outbound → new outbound, keep retPhase */ outDate = d; } else { @@ -309,7 +304,7 @@ function buildMonth(year, month) { /* effective range (includes hover preview) */ var rangeA = null, rangeB = null; - if (outDate && retDate) { + if (outDate && retDate && !sameDay(outDate, retDate)) { rangeA = outDate < retDate ? outDate : retDate; rangeB = outDate < retDate ? retDate : outDate; } else if (outDate && retPhase && hoverDate && hoverDate > outDate) { @@ -419,6 +414,9 @@ /* ── hover range: update cell styles in-place (no DOM rebuild) ───── */ function applyHoverStyles(rangeA, rangeB) { + if (!rangeA || !rangeB || rangeB <= rangeA) { + rangeA = rangeB = null; + } document.querySelectorAll('.cal-cell[data-date]').forEach(function (cell) { var d = new Date(cell.getAttribute('data-date') + 'T00:00:00'); var col = parseInt(cell.getAttribute('data-col'), 10); @@ -451,7 +449,10 @@ el.innerHTML = 'Select outbound date'; } else if (retPhase) { el.innerHTML = 'Outbound: ' + dispDate(outDate) + - '  ·  Now select return date'; + '  ·  Now select return date' + + ' — select the same date for a day trip.'; + } else if (sameDay(outDate, retDate)) { + el.innerHTML = 'Day trip: ' + dispDate(outDate) + ' (out and back)'; } else { el.innerHTML = 'Outbound: ' + dispDate(outDate) + '  ·  Return: ' + dispDate(retDate) + ''; diff --git a/tests/test_playwright_return_fares.py b/tests/test_playwright_return_fares.py index 6eb6ad2..19bc4ca 100644 --- a/tests/test_playwright_return_fares.py +++ b/tests/test_playwright_return_fares.py @@ -1,4 +1,5 @@ import threading +from datetime import date, timedelta from typing import Any, Generator import pytest @@ -647,29 +648,91 @@ def test_return_advance_first_standard_premier_totals(local_server: str) -> None browser.close() -def test_return_calendar_selects_outbound_before_return(local_server: str) -> None: +@pytest.mark.parametrize("return_days", [0, 7], ids=["day-trip", "longer-trip"]) +@pytest.mark.parametrize("mobile", [False, True], ids=["desktop", "mobile"]) +def test_return_calendar_selects_outbound_before_return( + local_server: str, return_days: int, mobile: bool +) -> None: with sync_playwright() as p: browser = _launch_browser(p) - page = browser.new_page() + page = browser.new_page( + viewport={"width": 390 if mobile else 1280, "height": 844}, + has_touch=mobile, + ) page.goto(f"{local_server}/", wait_until="domcontentloaded") + today = date.fromisoformat(page.locator("#travel_date").input_value()) + outbound = (today + timedelta(days=1)).isoformat() + returning = (today + timedelta(days=1 + return_days)).isoformat() - page.locator("#journey-return").check(force=True) + def select_day(value: str) -> None: + button = page.locator(f'.cal-cell[data-date="{value}"] button') + if mobile: + button.tap() + else: + button.click() + + page.locator("#journey-return").check() assert page.locator("#cal-hint").inner_text() == "Select outbound date" assert page.locator("#travel_date").input_value() == "" assert page.locator("#return_date").input_value() == "" - page.get_by_role("button", name="10 June 2026").click() - assert page.locator("#travel_date").input_value() == "2026-06-10" + select_day(outbound) + assert page.locator("#travel_date").input_value() == outbound assert page.locator("#return_date").input_value() == "" assert "Now select return date" in page.locator("#cal-hint").inner_text() + assert "same date for a day trip" in page.locator("#cal-hint").inner_text() - page.get_by_role("button", name="17 June 2026").click() - assert page.locator("#travel_date").input_value() == "2026-06-10" - assert page.locator("#return_date").input_value() == "2026-06-17" - assert "Return: Wed 17 Jun" in page.locator("#cal-hint").inner_text() + select_day(returning) + assert page.locator("#travel_date").input_value() == outbound + assert page.locator("#return_date").input_value() == returning + assert page.locator("#return_date").get_attribute("name") == "return_date" + hint = page.locator("#cal-hint").inner_text() + assert ("Day trip:" if return_days == 0 else "Return:") in hint + if return_days == 0: + assert page.locator(".cal-selected").count() == 1 + # A day trip must not show a range extending into the next day, + # including after the pointer leaves the calendar. + page.locator("h2").hover() + assert page.locator(".cal-in-range").count() == 0 + assert page.locator(f'.cal-cell[data-date="{outbound}"]').evaluate( + "el => el.style.background" + ) == "" + assert page.evaluate( + "document.documentElement.scrollWidth <= window.innerWidth" + ) page.locator('button[type="submit"]').click() - page.wait_for_url("**/results/BRI/paris/return/2026-06-10/2026-06-17", timeout=10000) + page.wait_for_url( + f"**/results/BRI/paris/return/{outbound}/{returning}", timeout=10000 + ) + browser.close() + + +def test_return_calendar_can_restart_and_move_outbound_earlier(local_server: str) -> None: + with sync_playwright() as p: + browser = _launch_browser(p) + page = browser.new_page() + page.goto(f"{local_server}/", wait_until="domcontentloaded") + today = date.fromisoformat(page.locator("#travel_date").input_value()) + tomorrow = (today + timedelta(days=1)).isoformat() + page.locator("#journey-return").check() + day = page.locator(f'.cal-cell[data-date="{tomorrow}"] button') + day.click() + day.click() + assert page.locator("#return_date").input_value() == tomorrow + + # The next click starts a fresh selection, even on the selected date. + day.click() + assert page.locator("#return_date").input_value() == "" + page.locator(f'.cal-cell[data-date="{today}"] button').click() + assert page.locator("#travel_date").input_value() == today.isoformat() + assert page.locator("#return_date").input_value() == "" + day.click() + assert page.locator("#return_date").input_value() == tomorrow + + page.locator("#journey-outbound").check() + assert page.locator("#travel_date").input_value() == today.isoformat() + assert page.locator("#return_date").get_attribute("name") == "" browser.close()