Files
upstream-zulip/web/tests/box_resize.test.cjs
upstream-mirror 64898b2c79
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
snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
2026-09-15 15:05:30 +05:00

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;
}
});