Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
236 lines
8.8 KiB
JavaScript
236 lines
8.8 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("node:assert/strict");
|
|
|
|
const {JSDOM} = require("jsdom");
|
|
|
|
const {set_global, zrequire} = require("./lib/namespace.cjs");
|
|
const {run_test} = require("./lib/test.cjs");
|
|
|
|
const {window} = new JSDOM("");
|
|
// Only assign globals our library actually reads (document and
|
|
// getComputedStyle). `global.HTMLElement` is intentionally NOT
|
|
// overwritten: index.cjs already points it at its own JSDOM's
|
|
// HTMLElement, and later tests such as `compose_paste.test.cjs`
|
|
// rely on `instanceof HTMLElement` matching elements parsed via
|
|
// their DOMParser. Routing the assignments through `set_global`
|
|
// ensures the globals are restored after this test, since later
|
|
// tests in the same process can be sensitive to seeing a real
|
|
// `document.createElement` (e.g., it makes simplebar's `canUseDOM`
|
|
// check true, causing it to call `window.addEventListener` at
|
|
// module load time, which the test environment doesn't provide).
|
|
set_global("document", window.document);
|
|
set_global("getComputedStyle", window.getComputedStyle.bind(window));
|
|
|
|
// `setPointerCapture` / `releasePointerCapture` are not implemented by
|
|
// jsdom; stub them on this test's own HTMLElement prototype so our
|
|
// pointer event handlers can call them without throwing. We avoid
|
|
// stubbing on `global.HTMLElement` since that prototype is shared
|
|
// with other tests.
|
|
window.HTMLElement.prototype.setPointerCapture = function () {};
|
|
window.HTMLElement.prototype.releasePointerCapture = function () {};
|
|
|
|
const box_resize = zrequire("box_resize");
|
|
|
|
function make_box() {
|
|
const box = document.createElement("div");
|
|
// Provide a predictable starting rect so tests don't depend on
|
|
// jsdom's layout (which is a no-op).
|
|
let current_rect = {width: 100, height: 80};
|
|
box.getBoundingClientRect = () => ({
|
|
x: 0,
|
|
y: 0,
|
|
left: 0,
|
|
top: 0,
|
|
right: current_rect.width,
|
|
bottom: current_rect.height,
|
|
width: current_rect.width,
|
|
height: current_rect.height,
|
|
});
|
|
box.set_rect = (width, height) => {
|
|
current_rect = {width, height};
|
|
};
|
|
return box;
|
|
}
|
|
|
|
function fire(handle, type, {clientX = 0, clientY = 0, pointerId = 1} = {}) {
|
|
const event = new window.Event(type, {bubbles: true, cancelable: true});
|
|
Object.assign(event, {clientX, clientY, pointerId});
|
|
handle.dispatchEvent(event);
|
|
return event;
|
|
}
|
|
|
|
run_test("make_resizable creates one handle per direction with expected class", () => {
|
|
const box = make_box();
|
|
box_resize.make_resizable(box, ["top", "right", "bottom_left"]);
|
|
|
|
assert.equal(box.children.length, 3);
|
|
const classes = [...box.children].map((h) => h.className);
|
|
assert.ok(classes[0].includes("resizable-box-handle-top"));
|
|
assert.ok(classes[1].includes("resizable-box-handle-right"));
|
|
assert.ok(classes[2].includes("resizable-box-handle-bottom-left"));
|
|
// Underscores in the direction are converted to hyphens for the class.
|
|
assert.ok(!classes[2].includes("bottom_left"));
|
|
});
|
|
|
|
run_test("cleanup removes the handles it added", () => {
|
|
const box = make_box();
|
|
const existing_child = document.createElement("span");
|
|
box.append(existing_child);
|
|
|
|
const cleanup = box_resize.make_resizable(box, ["right", "bottom"]);
|
|
assert.equal(box.children.length, 3);
|
|
|
|
cleanup();
|
|
assert.equal(box.children.length, 1);
|
|
assert.equal(box.firstElementChild, existing_child);
|
|
});
|
|
|
|
run_test("right handle grows width by 2 * dx and does not touch height", () => {
|
|
const box = make_box();
|
|
box.set_rect(100, 80);
|
|
box_resize.make_resizable(box, ["right"]);
|
|
const handle = box.firstElementChild;
|
|
|
|
fire(handle, "pointerdown", {clientX: 50, clientY: 40});
|
|
fire(handle, "pointermove", {clientX: 60, clientY: 45});
|
|
|
|
// Dragging right by 10: box grows by 20 (10 on each side, centered).
|
|
assert.equal(box.style.width, "120px");
|
|
// `height` is left untouched by an edge-only handle.
|
|
assert.equal(box.style.height, "");
|
|
});
|
|
|
|
run_test("left handle grows width when pointer moves leftward", () => {
|
|
const box = make_box();
|
|
box.set_rect(100, 80);
|
|
box_resize.make_resizable(box, ["left"]);
|
|
const handle = box.firstElementChild;
|
|
|
|
fire(handle, "pointerdown", {clientX: 50, clientY: 40});
|
|
// Dragging the left edge 15px to the left: the box grows symmetrically
|
|
// by 30px total.
|
|
fire(handle, "pointermove", {clientX: 35, clientY: 40});
|
|
|
|
assert.equal(box.style.width, "130px");
|
|
});
|
|
|
|
run_test("top handle grows height when pointer moves upward", () => {
|
|
const box = make_box();
|
|
box.set_rect(100, 80);
|
|
box_resize.make_resizable(box, ["top"]);
|
|
const handle = box.firstElementChild;
|
|
|
|
fire(handle, "pointerdown", {clientX: 50, clientY: 40});
|
|
fire(handle, "pointermove", {clientX: 50, clientY: 30});
|
|
|
|
// Dragging the top edge up by 10 => height grows by 20.
|
|
assert.equal(box.style.height, "100px");
|
|
assert.equal(box.style.width, "");
|
|
});
|
|
|
|
run_test("corner handle grows both dimensions with correct signs", () => {
|
|
const box = make_box();
|
|
box.set_rect(200, 100);
|
|
box_resize.make_resizable(box, ["top_left"]);
|
|
const handle = box.firstElementChild;
|
|
|
|
fire(handle, "pointerdown", {clientX: 50, clientY: 50});
|
|
// Move 5 left, 8 up: both dx_sign and dy_sign are -1 for top_left,
|
|
// so width grows by 2*5 = 10 and height grows by 2*8 = 16.
|
|
fire(handle, "pointermove", {clientX: 45, clientY: 42});
|
|
|
|
assert.equal(box.style.width, "210px");
|
|
assert.equal(box.style.height, "116px");
|
|
});
|
|
|
|
run_test("on_resize callback fires once per pointermove after pointerdown", () => {
|
|
const box = make_box();
|
|
box.set_rect(100, 80);
|
|
let call_count = 0;
|
|
box_resize.make_resizable(box, ["bottom_right"], () => {
|
|
call_count += 1;
|
|
});
|
|
const handle = box.firstElementChild;
|
|
|
|
fire(handle, "pointerdown", {clientX: 0, clientY: 0});
|
|
assert.equal(call_count, 0);
|
|
fire(handle, "pointermove", {clientX: 5, clientY: 5});
|
|
fire(handle, "pointermove", {clientX: 10, clientY: 10});
|
|
assert.equal(call_count, 2);
|
|
|
|
fire(handle, "pointerup", {clientX: 10, clientY: 10});
|
|
fire(handle, "pointermove", {clientX: 20, clientY: 20});
|
|
assert.equal(call_count, 2);
|
|
});
|
|
|
|
run_test("pointerup stops subsequent pointermove events from resizing", () => {
|
|
const box = make_box();
|
|
box.set_rect(100, 80);
|
|
box_resize.make_resizable(box, ["right"]);
|
|
const handle = box.firstElementChild;
|
|
|
|
fire(handle, "pointerdown", {clientX: 50, clientY: 40});
|
|
fire(handle, "pointermove", {clientX: 60, clientY: 40});
|
|
assert.equal(box.style.width, "120px");
|
|
|
|
fire(handle, "pointerup", {clientX: 60, clientY: 40});
|
|
fire(handle, "pointermove", {clientX: 200, clientY: 40});
|
|
// Width should not have changed after pointerup.
|
|
assert.equal(box.style.width, "120px");
|
|
});
|
|
|
|
run_test("pointercancel stops subsequent pointermove events from resizing", () => {
|
|
const box = make_box();
|
|
box.set_rect(100, 80);
|
|
box_resize.make_resizable(box, ["right"]);
|
|
const handle = box.firstElementChild;
|
|
|
|
fire(handle, "pointerdown", {clientX: 50, clientY: 40});
|
|
fire(handle, "pointermove", {clientX: 60, clientY: 40});
|
|
assert.equal(box.style.width, "120px");
|
|
|
|
// The browser can take the gesture away at any time; we must
|
|
// tear the listeners down or subsequent moves would keep resizing.
|
|
fire(handle, "pointercancel", {clientX: 60, clientY: 40});
|
|
fire(handle, "pointermove", {clientX: 200, clientY: 40});
|
|
assert.equal(box.style.width, "120px");
|
|
});
|
|
|
|
run_test("resize clamps to CSS min and max bounds during a single drag", () => {
|
|
const box = make_box();
|
|
box.set_rect(100, 80);
|
|
// Jsdom does not resolve the element's real computed style from a
|
|
// stylesheet, so we stub getComputedStyle to return the min/max
|
|
// bounds we want to test against. The library only calls it on
|
|
// `box`, so we don't need a passthrough.
|
|
const original_get_computed_style = global.getComputedStyle;
|
|
global.getComputedStyle = () => ({
|
|
minWidth: "60px",
|
|
maxWidth: "140px",
|
|
minHeight: "0px",
|
|
maxHeight: "none",
|
|
});
|
|
|
|
try {
|
|
box_resize.make_resizable(box, ["right"]);
|
|
const handle = box.firstElementChild;
|
|
|
|
fire(handle, "pointerdown", {clientX: 50, clientY: 40});
|
|
|
|
// Overshoot the max: raw would be 100 + 2*100 = 300, clamp to 140.
|
|
fire(handle, "pointermove", {clientX: 150, clientY: 40});
|
|
assert.equal(box.style.width, "140px");
|
|
|
|
// Pull back within bounds: raw is 100 + 2*10 = 120, no clamp.
|
|
fire(handle, "pointermove", {clientX: 60, clientY: 40});
|
|
assert.equal(box.style.width, "120px");
|
|
|
|
// Undershoot the min: raw would be 100 - 2*40 = 20, clamp to 60.
|
|
fire(handle, "pointermove", {clientX: 10, clientY: 40});
|
|
assert.equal(box.style.width, "60px");
|
|
} finally {
|
|
global.getComputedStyle = original_get_computed_style;
|
|
}
|
|
});
|