fix(ui): stop the pages being cached, and the labels duplicating (#19)
Build and Push Image / Build and push image (push) Successful in 3m29s
Build and Push Image / Build and push image (push) Successful in 3m29s
fix(ui): stop the pages being cached, and the labels duplicating (#19)
This commit was merged in pull request #19.
This commit is contained in:
@@ -87,6 +87,18 @@ def create_app(config: dict | None = None) -> Flask:
|
|||||||
if config:
|
if config:
|
||||||
app.config.update(config)
|
app.config.update(config)
|
||||||
|
|
||||||
|
# The pages must not be cached by the browser.
|
||||||
|
|
||||||
|
# A phone held on to a cached copy of the brewing page and showed
|
||||||
|
# the old inline advice instead of the button and the pop-out, so
|
||||||
|
# the change was not visible until the cache was cleared by hand.
|
||||||
|
@app.after_request
|
||||||
|
def _no_store(response):
|
||||||
|
if response.mimetype == "text/html":
|
||||||
|
response.headers["Cache-Control"] = "no-store, must-revalidate"
|
||||||
|
response.headers["Pragma"] = "no-cache"
|
||||||
|
return response
|
||||||
|
|
||||||
csrf = CSRFProtect(app)
|
csrf = CSRFProtect(app)
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
|
|||||||
@@ -484,9 +484,14 @@ table.data .num { font-family: var(--mono); text-align: right; white-space: nowr
|
|||||||
table.data .comment { color: var(--text-muted); font-size: .84rem; }
|
table.data .comment { color: var(--text-muted); font-size: .84rem; }
|
||||||
|
|
||||||
/* A heading that is too long for a phone column. The long form shows
|
/* A heading that is too long for a phone column. The long form shows
|
||||||
on a desktop and the short form on a phone - see the media query. */
|
on a desktop and the short form on a phone - see the media query.
|
||||||
table.data .short { display: none; }
|
|
||||||
table.data .advice-trigger .short { display: none; }
|
The rule is on `.short` alone, not on `table.data .short`, so it
|
||||||
|
applies everywhere the pattern is used - in a table heading, in the
|
||||||
|
advice button, and on the Active Beers page. When only the table
|
||||||
|
was covered, the advice button could show both forms at once and
|
||||||
|
read "Read the adviceAdvice". */
|
||||||
|
.short { display: none; }
|
||||||
|
|
||||||
/* On a narrow screen the header wraps onto two rows: the brand and the
|
/* On a narrow screen the header wraps onto two rows: the brand and the
|
||||||
sign-out on the first, the nav tabs on the second where they get the
|
sign-out on the first, the nav tabs on the second where they get the
|
||||||
@@ -570,19 +575,17 @@ table.data .advice-trigger .short { display: none; }
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The heading gets a short form, because a heading that is cut off
|
/* The long form gives way to the short form on a phone, because a
|
||||||
is worse than a heading that is brief. */
|
heading that is cut off is worse than a heading that is brief.
|
||||||
table.data .col-abv .long,
|
|
||||||
table.data .col-advice .long,
|
The rule is on the span itself rather than on the column, so it
|
||||||
table.data .col-finished .long,
|
applies everywhere the pattern is used - in a table heading, in
|
||||||
table.data .col-comment .long {
|
the advice button, and on the Active Beers page. */
|
||||||
|
.long {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.data .col-abv .short,
|
.short {
|
||||||
table.data .col-advice .short,
|
|
||||||
table.data .col-finished .short,
|
|
||||||
table.data .col-comment .short {
|
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,14 +597,6 @@ table.data .advice-trigger .short { display: none; }
|
|||||||
gap: .2rem;
|
gap: .2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.data .advice-trigger .long {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data .advice-trigger .short {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The action column holds a button. */
|
/* The action column holds a button. */
|
||||||
table.data .col-action {
|
table.data .col-action {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -714,3 +714,15 @@ def test_the_api_exposes_the_attenuation_and_curve(logged_in, repo):
|
|||||||
assert len(entry["curve"]) == 2
|
assert len(entry["curve"]) == 2
|
||||||
assert entry["curve"][0]["progress"] == 0.0
|
assert entry["curve"][0]["progress"] == 0.0
|
||||||
assert entry["curve"][1]["progress"] == 1.0
|
assert entry["curve"][1]["progress"] == 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_pages_are_not_cached(client):
|
||||||
|
"""The HTML pages must not be cached by the browser.
|
||||||
|
|
||||||
|
A phone held on to a cached copy of the brewing page and kept
|
||||||
|
showing the old inline advice after the button and the pop-out had
|
||||||
|
been deployed, so the change was invisible until the cache was
|
||||||
|
cleared by hand.
|
||||||
|
"""
|
||||||
|
response = client.get("/login")
|
||||||
|
assert "no-store" in response.headers.get("Cache-Control", "")
|
||||||
|
|||||||
@@ -1049,6 +1049,36 @@ def test_the_tables_keep_their_columns_on_a_phone(client, repo):
|
|||||||
assert "Cider raspberry elderberry" in body
|
assert "Cider raspberry elderberry" in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_long_and_short_labels_are_both_in_the_page(client, repo):
|
||||||
|
"""The long and the short label are both in the markup.
|
||||||
|
|
||||||
|
CSS picks which one shows - the long form on a desktop, the short
|
||||||
|
form on a phone. If only one is in the markup the other cannot be
|
||||||
|
shown at all, and if the CSS rule misses one of them the page shows
|
||||||
|
"Read the adviceAdvice".
|
||||||
|
|
||||||
|
This is a markup assertion, not a CSS assertion: it makes sure
|
||||||
|
both forms exist for the stylesheet to choose between.
|
||||||
|
"""
|
||||||
|
repo.users.insert_one({"username": "beer", "password": "beer_pass"})
|
||||||
|
client.post("/login", data={"username": "beer", "password": "beer_pass"},
|
||||||
|
follow_redirects=True)
|
||||||
|
|
||||||
|
seed_readings(repo, [("1", "Ale", "1042", "2026-01-01", False, "")])
|
||||||
|
doc = repo.readings.find_one({"beer_run_id": "1"})
|
||||||
|
repo.set_reading_advice(str(doc["_id"]), "Warm it to 21C and wait.", ok=True)
|
||||||
|
|
||||||
|
body = client.get("/brews").get_data(as_text=True)
|
||||||
|
|
||||||
|
# The heading has both forms.
|
||||||
|
assert '<span class="long">Brewing advice</span>' in body
|
||||||
|
assert '<span class="short">Advice</span>' in body
|
||||||
|
|
||||||
|
# The button has both forms.
|
||||||
|
assert '<span class="long">Read the advice</span>' in body
|
||||||
|
assert '<span class="short">Advice</span>' in body
|
||||||
|
|
||||||
|
|
||||||
def test_a_failed_advice_is_marked_as_such(client, repo):
|
def test_a_failed_advice_is_marked_as_such(client, repo):
|
||||||
"""A generation failure reads differently from real advice."""
|
"""A generation failure reads differently from real advice."""
|
||||||
repo.users.insert_one({"username": "beer", "password": "beer_pass"})
|
repo.users.insert_one({"username": "beer", "password": "beer_pass"})
|
||||||
|
|||||||
Reference in New Issue
Block a user