Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUGS-BY-HAND.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ toe (vaak in `tests/e2e/test_every_page.py`) en zet hier een regel bij.
| 2026-11-06 | Nieuwe zin onder de uitgaveregels (KOR uit) bleef Nederlands in het Engels: een "%" in een templatetekst staat in de catalogus als "%%" (M21 deel 2, deze PR) | onvertaalde tekst op een scherm dat `test_every_page` niet ziet | `tests/core/test_translations.py`: elke `{% translate %}`-tekst in elke template wordt in het Engels gevonden zoals Django hem opzoekt |
| 2026-11-07 | Kwartaaltabel van het jaaroverzicht rechts afgeknipt op een telefoon ("Winst", en zonder KOR "Voorbelasting", onzichtbaar); de factuurlijst 30px op 1024px (M21 deel 3, deze PR) | tabel breder dan zijn kaart wordt afgeknipt | `test_every_page`: geen `.table-wrap` met `overflow: hidden` en een bredere tabel; `.table-wrap` scrolt nu opzij |
| 2026-11-07 | De eigenaar zocht de plek voor de API-sleutel van zijn eigen AI-dienst en vond hem niet: die stond onder Instellingen → "Bonnetjes lezen", een naam zonder AI of sleutel, en op AI-assistenten (waar hij keek) maakt Huismus juist zelf een sleutel (deze PR) | instelling vindbaar alleen voor wie weet waarvoor hij dient | de kaart heet "Bonnetjes lezen met AI" en noemt de API-sleutel, AI-assistenten wijst ernaar; `test_the_card_says_it_is_where_your_own_ai_key_goes`, `test_the_page_points_to_where_your_own_ai_key_goes`. Vindbaarheid zelf: geen geautomatiseerde check |
| 2026-11-13 | Desktop-app: "Koppel met Claude" deed niets, en geen enkel formulier meer, tot het venster dichtging. Een htmx-verzoek ging weg van een deel dat zichzelf ververst; een paginawissel verving dat deel voordat het antwoord kwam. htmx meldt het einde alleen aan het element zelf, dus app.js bleef wachten en hield elk POST-formulier tegen (de eigenaar; deze PR) | boekhouding van de pagina hangt aan een htmx-event van een element dat weg kan zijn | `test_browser`: `test_a_request_whose_element_is_gone_does_not_hold_up_the_forms` (geldt voor elk htmx-verzoek: app.js telt nu ook het einde van het verzoek zelf, `loadend`) |
17 changes: 14 additions & 3 deletions huismus/core/static/core/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,22 @@ const formMethod = (event) =>
const sending = new Set();
let waiting = false;
let leaving = false;
document.addEventListener("htmx:beforeSend", (event) => sending.add(event.detail.xhr));
document.addEventListener("htmx:afterRequest", (event) => {
sending.delete(event.detail.xhr);
const ended = (xhr) => {
if (!sending.delete(xhr)) return;
if (sending.size === 0) document.dispatchEvent(new Event("huismus:idle"));
};
// htmx:afterRequest comes first, before htmx sends a request queued behind
// this one. But htmx tells only the element that sent it, and once that
// element is gone (a part that fetches itself, replaced by a page change) the
// document never hears it. So the request's own end (loadend: an answer, an
// error or an abort) counts too; without it the request stayed here, and
// every form waited for it until the window closed (BUGS-BY-HAND.md).
document.addEventListener("htmx:beforeSend", (event) => {
const xhr = event.detail.xhr;
sending.add(xhr);
xhr.addEventListener("loadend", () => ended(xhr));
});
document.addEventListener("htmx:afterRequest", (event) => ended(event.detail.xhr));
document.addEventListener("htmx:beforeRequest", (event) => {
if (leaving) event.preventDefault();
});
Expand Down
27 changes: 27 additions & 0 deletions tests/e2e/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,33 @@ def hold_the_totals(route):
assert Document.objects.get().lines.get().description == "Uren"


def test_a_request_whose_element_is_gone_does_not_hold_up_the_forms(app):
# BUGS-BY-HAND.md, 2026-11-13: in the desktop app "Koppel met Claude"
# did nothing. An htmx request had gone out from an element that was gone
# by the time its answer came (a part that fetches itself every few
# seconds, replaced by a boosted page change). htmx tells only that
# element, so the document never heard the request had ended, and every
# form after it waited for it, for as long as the window stayed open.
held = []
app.route("**/vastgelopen/", lambda route: held.append(route))
app.goto(app.base + "/instellingen/ai-assistenten/")
with app.expect_request("**/vastgelopen/"):
app.evaluate(
"""() => {
const part = document.createElement("div");
part.setAttribute("hx-get", "/vastgelopen/");
part.setAttribute("hx-trigger", "load");
document.body.append(part);
htmx.process(part);
}"""
)
app.evaluate("() => document.querySelector('[hx-get=\"/vastgelopen/\"]').remove()")
held[0].fulfill(status=200, body="")

app.get_by_role("button", name="Koppelen").click()
expect(app.locator("#sleutel")).to_be_attached()


def test_a_form_sent_once_ignores_the_next_click_until_back(app):
# A double click made the browser cut off the first request and send the
# form again; after "Leegmaken" or "Terugzetten" the second one found the
Expand Down
Loading