Dragging a widget into place does nothing, and the test that covers it cannot tell #174

Closed
opened 2026-09-12 07:27:10 +00:00 by tiagoagueda · 1 comment
Owner

Arranging the dashboard by dragging (#125) does not work. The arrows do.

The tests pass, and they would pass either way

tests/e2e/test_widget_drag.py drives the gesture like this:

fire(row, 'dragstart');
fire(onto, 'dragover');
fire(onto, 'drop');
fire(row, 'dragend');

Four synthetic DragEvents dispatched from page.evaluate. That exercises the four
listeners in app.js and proves the placement arithmetic, the form post and the redirect
are right — which they are. It proves nothing about whether a real pointer drag ever
reaches those listeners, because no part of the browser's own drag machinery runs. A green
suite here is consistent with the feature being completely dead in a real browser, which is
what is being reported.

Leading suspect: no dragenter handler

app.js cancels dragover and nothing else:

document.addEventListener("dragover", function (event) {
  var row = event.target.closest && event.target.closest("[data-widget-row]");
  if (draggedWidget && row) {
    event.preventDefault();
    ...

For an element to become a valid drop target, the specification has both dragenter and
dragover cancelled. Chromium is forgiving about the missing dragenter; Firefox is not,
and refuses the drop — the row never accepts, and the drag springs back with nothing
posted. The board drag (test_board_drag.py) fires synthetic events the same way, so if it
shares the pattern it may share the fault.

Worth knowing before fixing: the browser suite runs --browser chromium only. If this
is the cause, no amount of running the existing suite would ever have shown it.

Also worth checking while in here

  • Only a row is a drop target. drop returns early unless
    event.target.closest("[data-widget-row]") matches, so the gap between rows, and the
    space below the last one, swallow the gesture. Dropping past the end is the natural way
    to say last, and widgets.place() already clamps for exactly that — the client never
    gives it the chance.
  • A drop lands on one side going up and the other going down. The index posted is
    widgetRows(list).indexOf(row), counted while the dragged row is still in the list;
    widgets.place() then removes the widget before inserting. Dragging upward puts it
    before the row it was dropped on, dragging downward puts it after. Both are defensible
    and the pair is not; the existing test asserts the downward case and so pins half of it.
  • readyWidgetDragging() runs on DOMContentLoaded and never again. readyColumnWidths
    immediately above it also listens for htmx:afterSwap. A drop reloads the page today so
    nothing is lost, but the asymmetry is a trap for whoever makes this list swap.

What closing this needs

A test that fails now. Synthetic DragEvents cannot produce one, so it wants either a real
mouse.down/mouse.move/mouse.up drag, or the suite running against Firefox as well —
and the second is worth having regardless, given the extension ships for Firefox too.

Arranging the dashboard by dragging (#125) does not work. The arrows do. ### The tests pass, and they would pass either way `tests/e2e/test_widget_drag.py` drives the gesture like this: fire(row, 'dragstart'); fire(onto, 'dragover'); fire(onto, 'drop'); fire(row, 'dragend'); Four synthetic `DragEvent`s dispatched from `page.evaluate`. That exercises the four listeners in `app.js` and proves the placement arithmetic, the form post and the redirect are right — which they are. It proves nothing about whether a **real pointer drag** ever reaches those listeners, because no part of the browser's own drag machinery runs. A green suite here is consistent with the feature being completely dead in a real browser, which is what is being reported. ### Leading suspect: no `dragenter` handler `app.js` cancels `dragover` and nothing else: document.addEventListener("dragover", function (event) { var row = event.target.closest && event.target.closest("[data-widget-row]"); if (draggedWidget && row) { event.preventDefault(); ... For an element to become a valid drop target, the specification has both `dragenter` **and** `dragover` cancelled. Chromium is forgiving about the missing `dragenter`; Firefox is not, and refuses the drop — the row never accepts, and the drag springs back with nothing posted. The board drag (`test_board_drag.py`) fires synthetic events the same way, so if it shares the pattern it may share the fault. Worth knowing before fixing: **the browser suite runs `--browser chromium` only**. If this is the cause, no amount of running the existing suite would ever have shown it. ### Also worth checking while in here - **Only a row is a drop target.** `drop` returns early unless `event.target.closest("[data-widget-row]")` matches, so the gap between rows, and the space below the last one, swallow the gesture. Dropping past the end is the natural way to say *last*, and `widgets.place()` already clamps for exactly that — the client never gives it the chance. - **A drop lands on one side going up and the other going down.** The index posted is `widgetRows(list).indexOf(row)`, counted while the dragged row is still in the list; `widgets.place()` then removes the widget before inserting. Dragging upward puts it *before* the row it was dropped on, dragging downward puts it *after*. Both are defensible and the pair is not; the existing test asserts the downward case and so pins half of it. - **`readyWidgetDragging()` runs on `DOMContentLoaded` and never again.** `readyColumnWidths` immediately above it also listens for `htmx:afterSwap`. A drop reloads the page today so nothing is lost, but the asymmetry is a trap for whoever makes this list swap. ### What closing this needs A test that fails now. Synthetic `DragEvent`s cannot produce one, so it wants either a real `mouse.down`/`mouse.move`/`mouse.up` drag, or the suite running against Firefox as well — and the second is worth having regardless, given the extension ships for Firefox too.
Author
Owner

Landed on 0.3.0 as 2d6097c4c.

The board drag had the same fault, so both are fixed here — neither cancelled dragenter, which Chromium forgives and Firefox does not. Both test helpers now send dragenter as a browser does, and each file gained a test that asks the handlers the question a browser asks. Those fail without the fix in Chromium, so no second browser is needed in CI to keep watch.

Landed on `0.3.0` as `2d6097c4c`. **The board drag had the same fault**, so both are fixed here — neither cancelled `dragenter`, which Chromium forgives and Firefox does not. Both test helpers now send `dragenter` as a browser does, and each file gained a test that asks the handlers the question a browser asks. Those fail without the fix *in Chromium*, so no second browser is needed in CI to keep watch.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#174
No description provided.