Allow same-day return journeys in the date selector
Select the outbound date again to complete a day trip, with a clear hint and summary and no misleading range highlight. Cover desktop and mobile selection, submission, and reselection with Playwright. Closes #9
This commit is contained in:
parent
c9aad7ad4c
commit
8be2e29d76
2 changed files with 83 additions and 19 deletions
|
|
@ -122,7 +122,7 @@
|
||||||
<button type="button" id="cal-next" class="cal-nav-btn" aria-label="Next month">›</button>
|
<button type="button" id="cal-next" class="cal-nav-btn" aria-label="Next month">›</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="cal-months"></div>
|
<div id="cal-months"></div>
|
||||||
<div id="cal-hint" class="cal-hint"></div>
|
<div id="cal-hint" class="cal-hint" aria-live="polite"></div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Hidden inputs submitted with the form -->
|
<!-- Hidden inputs submitted with the form -->
|
||||||
<input type="hidden" id="travel_date" name="travel_date">
|
<input type="hidden" id="travel_date" name="travel_date">
|
||||||
|
|
@ -258,12 +258,7 @@
|
||||||
retPhase = true;
|
retPhase = true;
|
||||||
} else {
|
} else {
|
||||||
/* selecting return date */
|
/* selecting return date */
|
||||||
if (sameDay(d, outDate)) {
|
if (d < outDate) {
|
||||||
/* tapped same day → reset */
|
|
||||||
outDate = null;
|
|
||||||
retDate = null;
|
|
||||||
retPhase = false;
|
|
||||||
} else if (d < outDate) {
|
|
||||||
/* earlier than outbound → new outbound, keep retPhase */
|
/* earlier than outbound → new outbound, keep retPhase */
|
||||||
outDate = d;
|
outDate = d;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -309,7 +304,7 @@
|
||||||
function buildMonth(year, month) {
|
function buildMonth(year, month) {
|
||||||
/* effective range (includes hover preview) */
|
/* effective range (includes hover preview) */
|
||||||
var rangeA = null, rangeB = null;
|
var rangeA = null, rangeB = null;
|
||||||
if (outDate && retDate) {
|
if (outDate && retDate && !sameDay(outDate, retDate)) {
|
||||||
rangeA = outDate < retDate ? outDate : retDate;
|
rangeA = outDate < retDate ? outDate : retDate;
|
||||||
rangeB = outDate < retDate ? retDate : outDate;
|
rangeB = outDate < retDate ? retDate : outDate;
|
||||||
} else if (outDate && retPhase && hoverDate && hoverDate > outDate) {
|
} else if (outDate && retPhase && hoverDate && hoverDate > outDate) {
|
||||||
|
|
@ -419,6 +414,9 @@
|
||||||
/* ── hover range: update cell styles in-place (no DOM rebuild) ───── */
|
/* ── hover range: update cell styles in-place (no DOM rebuild) ───── */
|
||||||
|
|
||||||
function applyHoverStyles(rangeA, rangeB) {
|
function applyHoverStyles(rangeA, rangeB) {
|
||||||
|
if (!rangeA || !rangeB || rangeB <= rangeA) {
|
||||||
|
rangeA = rangeB = null;
|
||||||
|
}
|
||||||
document.querySelectorAll('.cal-cell[data-date]').forEach(function (cell) {
|
document.querySelectorAll('.cal-cell[data-date]').forEach(function (cell) {
|
||||||
var d = new Date(cell.getAttribute('data-date') + 'T00:00:00');
|
var d = new Date(cell.getAttribute('data-date') + 'T00:00:00');
|
||||||
var col = parseInt(cell.getAttribute('data-col'), 10);
|
var col = parseInt(cell.getAttribute('data-col'), 10);
|
||||||
|
|
@ -451,7 +449,10 @@
|
||||||
el.innerHTML = '<span class="cal-cta">Select outbound date</span>';
|
el.innerHTML = '<span class="cal-cta">Select outbound date</span>';
|
||||||
} else if (retPhase) {
|
} else if (retPhase) {
|
||||||
el.innerHTML = 'Outbound: <strong>' + dispDate(outDate) +
|
el.innerHTML = 'Outbound: <strong>' + dispDate(outDate) +
|
||||||
'</strong> · <span class="cal-cta">Now select return date</span>';
|
'</strong> · <span class="cal-cta">Now select return date</span>' +
|
||||||
|
' — select the same date for a day trip.';
|
||||||
|
} else if (sameDay(outDate, retDate)) {
|
||||||
|
el.innerHTML = 'Day trip: <strong>' + dispDate(outDate) + '</strong> (out and back)';
|
||||||
} else {
|
} else {
|
||||||
el.innerHTML = 'Outbound: <strong>' + dispDate(outDate) +
|
el.innerHTML = 'Outbound: <strong>' + dispDate(outDate) +
|
||||||
'</strong> · Return: <strong>' + dispDate(retDate) + '</strong>';
|
'</strong> · Return: <strong>' + dispDate(retDate) + '</strong>';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import threading
|
import threading
|
||||||
|
from datetime import date, timedelta
|
||||||
from typing import Any, Generator
|
from typing import Any, Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -647,29 +648,91 @@ def test_return_advance_first_standard_premier_totals(local_server: str) -> None
|
||||||
browser.close()
|
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:
|
with sync_playwright() as p:
|
||||||
browser = _launch_browser(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")
|
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("#cal-hint").inner_text() == "Select outbound date"
|
||||||
assert page.locator("#travel_date").input_value() == ""
|
assert page.locator("#travel_date").input_value() == ""
|
||||||
assert page.locator("#return_date").input_value() == ""
|
assert page.locator("#return_date").input_value() == ""
|
||||||
|
|
||||||
page.get_by_role("button", name="10 June 2026").click()
|
select_day(outbound)
|
||||||
assert page.locator("#travel_date").input_value() == "2026-06-10"
|
assert page.locator("#travel_date").input_value() == outbound
|
||||||
assert page.locator("#return_date").input_value() == ""
|
assert page.locator("#return_date").input_value() == ""
|
||||||
assert "Now select return date" in page.locator("#cal-hint").inner_text()
|
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()
|
select_day(returning)
|
||||||
assert page.locator("#travel_date").input_value() == "2026-06-10"
|
assert page.locator("#travel_date").input_value() == outbound
|
||||||
assert page.locator("#return_date").input_value() == "2026-06-17"
|
assert page.locator("#return_date").input_value() == returning
|
||||||
assert "Return: Wed 17 Jun" in page.locator("#cal-hint").inner_text()
|
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.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()
|
browser.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue