UNPKG

@activecollab/components

Version:

ActiveCollab Components

470 lines (465 loc) 12.8 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import { DEFAULT_KEYBOARD_STEP, RESIZE_KEYS, SHIFT_STEP_MULTIPLIER, applyResize, clamp, effectiveBounds, isResizeKey, keyboardResize, makeStart, measurePercent, snap } from "./resizePolicy"; const FREE = { proportional: false }; const start = function (w, h, minW, minH) { if (minW === void 0) { minW = 0; } if (minH === void 0) { minH = 0; } return makeStart({ w, h }, { w: minW, h: minH }); }; describe("clamp / snap", () => { it("clamps to the floor and, when given, the ceiling", () => { expect(clamp(5, 10)).toBe(10); expect(clamp(50, 10, 20)).toBe(20); expect(clamp(15, 10, 20)).toBe(15); }); it("treats a missing ceiling as unbounded", () => { expect(clamp(1e9, 10)).toBe(1e9); }); it("snaps to the nearest multiple, and passes through without a step", () => { expect(snap(217, 8)).toBe(216); expect(snap(220, 8)).toBe(224); expect(snap(217)).toBe(217); expect(snap(217, 0)).toBe(217); }); }); describe("effectiveBounds", () => { it("floors the policy minimum with the content minimum", () => { const bounds = effectiveBounds({ minW: 96, minH: 40, proportional: false }, { w: 160, h: 72 }); // The content wins on both axes here: a card may not be crushed below it. expect(bounds.wMin).toBe(160); expect(bounds.hMin).toBe(72); }); it("keeps the policy minimum when it is the higher of the two", () => { const bounds = effectiveBounds({ minW: 200, proportional: false }, { w: 160, h: 0 }); expect(bounds.wMin).toBe(200); }); it("reports an unset maximum as unbounded", () => { const bounds = effectiveBounds(FREE, { w: 0, h: 0 }); expect(bounds.wMax).toBe(Number.POSITIVE_INFINITY); expect(bounds.hMax).toBe(Number.POSITIVE_INFINITY); }); }); describe("applyResize — bounds", () => { it("stops at minW", () => { const policy = { minW: 180, proportional: false }; expect(applyResize(start(300, 200), { dx: -500, dy: 0 }, policy).w).toBe(180); }); it("stops at maxW", () => { const policy = { maxW: 420, proportional: false }; expect(applyResize(start(300, 200), { dx: +500, dy: 0 }, policy).w).toBe(420); }); it("stops at minH", () => { const policy = { minH: 120, proportional: false }; expect(applyResize(start(300, 200), { dx: 0, dy: -500 }, policy).h).toBe(120); }); it("stops at maxH", () => { const policy = { maxH: 540, proportional: false }; expect(applyResize(start(300, 200), { dx: 0, dy: +500 }, policy).h).toBe(540); }); it("leaves an unset bound unbounded", () => { // No maxW at all: a huge delta is applied in full. expect(applyResize(start(300, 200), { dx: +5000, dy: 0 }, FREE).w).toBe(5300); // No minW either, so the floor is 0 rather than some implied width. expect(applyResize(start(300, 200), { dx: -5000, dy: 0 }, FREE).w).toBe(0); }); it("lets the content minimum override a lower policy minimum", () => { const policy = { minW: 96, proportional: false }; const measured = makeStart({ w: 300, h: 200 }, { w: 160, h: 0 }); // 96 is the policy floor, but the content needs 160 — the higher wins. expect(applyResize(measured, { dx: -500, dy: 0 }, policy).w).toBe(160); }); }); describe("applyResize — proportional", () => { /* The worked example from the spec: a proportional policy meeting a bound must not distort the ratio — the binding axis stops both. */ const policy = { minW: 96, maxW: 620, minH: 72, maxH: 540, proportional: true, step: 8 }; const proportionalStart = makeStart({ w: 300, h: 200 }, { w: 96, h: 72 }); it("holds the ratio when the WIDTH axis binds at the maximum", () => { // scaleMax = min(620/300, 540/200) = 2.066 → width binds expect(applyResize(proportionalStart, { dx: +400, dy: 0 }, policy)).toEqual({ w: 620, h: 413 }); }); it("holds the ratio when the HEIGHT axis binds at the minimum", () => { // scaleMin = max(96/300, 72/200) = 0.36 → height binds expect(applyResize(proportionalStart, { dx: -400, dy: 0 }, policy)).toEqual({ w: 108, h: 72 }); }); it("drives the scale from whichever axis the corner moved further", () => { const free = { proportional: true }; // A purely vertical drag scales the width too, so the corner is not // width-biased: +100 on a 200-tall card is a 1.5× scale. expect(applyResize(start(300, 200), { dx: 0, dy: +100 }, free)).toEqual({ w: 450, h: 300 }); }); it("lets the grid yield rather than break the ratio at a bound", () => { // Snapping 108 to the 8px grid would give 112 — above the binding minimum. // The bound is a hard stop, so the card sits exactly on it instead. const result = applyResize(proportionalStart, { dx: -400, dy: 0 }, policy); expect(result.w % 8).not.toBe(0); expect(result.w).toBe(108); }); it("still snaps to the grid away from any bound", () => { const result = applyResize(proportionalStart, { dx: +37, dy: 0 }, policy); // 337 snaps to 336, and the height follows the same scale. expect(result).toEqual({ w: 336, h: 224 }); }); it("falls back to free-form when a side has no length to form a ratio", () => { // Width-only cards park a placeholder height; the ratio math must not // divide by zero. const result = applyResize(makeStart({ w: 300, h: 0 }, { w: 0, h: 0 }), { dx: +40, dy: +40 }, { proportional: true }); expect(result.w).toBe(340); expect(Number.isNaN(result.h)).toBe(false); }); }); describe("applyResize — shift to constrain", () => { it("locks the ratio for one gesture on a free-form policy", () => { const result = applyResize(start(300, 200), { dx: +150, dy: 0 }, FREE, { constrain: true }); // 1.5× on both axes, from a purely horizontal drag. expect(result).toEqual({ w: 450, h: 300 }); }); it("leaves the axes independent without it", () => { expect(applyResize(start(300, 200), { dx: +150, dy: 0 }, FREE)).toEqual({ w: 450, h: 200 }); }); it("changes nothing on a policy that is already proportional", () => { const policy = { proportional: true }; const delta = { dx: +150, dy: 0 }; expect(applyResize(start(300, 200), delta, policy, { constrain: true })).toEqual(applyResize(start(300, 200), delta, policy)); }); }); describe("applyResize — width-only axis", () => { /* The worked example from the spec: an auto-height card ignores the vertical component entirely. */ it("ignores the vertical component and holds the height", () => { const policy = { minW: 180, maxW: 420, proportional: false, step: 8 }; const result = applyResize(makeStart({ w: 180, h: 240 }, { w: 180, h: 0 }), { dx: +37, dy: +200 }, policy, { axis: "width" }); expect(result).toEqual({ w: 216, h: 240 }); }); it("still lets the height follow the ratio in proportional mode", () => { // `axis: "width"` drops the pointer's vertical INPUT; it does not turn a // proportional card into a free-form one. const result = applyResize(start(300, 200), { dx: +150, dy: +999 }, { proportional: true }, { axis: "width" }); expect(result).toEqual({ w: 450, h: 300 }); }); }); describe("keyboardResize", () => { const policy = { minW: 180, maxW: 420, minH: 120, maxH: 400, proportional: false }; const contentMin = { w: 180, h: 120 }; const size = { w: 300, h: 200 }; it("nudges by the default step when the policy has none", () => { var _keyboardResize, _keyboardResize2, _keyboardResize3, _keyboardResize4; expect((_keyboardResize = keyboardResize("ArrowRight", size, policy, contentMin)) == null ? void 0 : _keyboardResize.w).toBe(300 + DEFAULT_KEYBOARD_STEP); expect((_keyboardResize2 = keyboardResize("ArrowLeft", size, policy, contentMin)) == null ? void 0 : _keyboardResize2.w).toBe(300 - DEFAULT_KEYBOARD_STEP); expect((_keyboardResize3 = keyboardResize("ArrowDown", size, policy, contentMin)) == null ? void 0 : _keyboardResize3.h).toBe(200 + DEFAULT_KEYBOARD_STEP); expect((_keyboardResize4 = keyboardResize("ArrowUp", size, policy, contentMin)) == null ? void 0 : _keyboardResize4.h).toBe(200 - DEFAULT_KEYBOARD_STEP); }); it("nudges by the policy step when there is one", () => { var _keyboardResize5; expect((_keyboardResize5 = keyboardResize("ArrowRight", size, _extends({}, policy, { step: 20 }), contentMin)) == null ? void 0 : _keyboardResize5.w).toBe(320); }); it("makes Shift a coarse nudge", () => { var _keyboardResize6; expect((_keyboardResize6 = keyboardResize("ArrowRight", size, policy, contentMin, { shiftKey: true })) == null ? void 0 : _keyboardResize6.w).toBe(300 + DEFAULT_KEYBOARD_STEP * SHIFT_STEP_MULTIPLIER); }); it("clamps a nudge exactly like the pointer does", () => { var _keyboardResize7; expect((_keyboardResize7 = keyboardResize("ArrowLeft", { w: 184, h: 200 }, policy, contentMin, { shiftKey: true })) == null ? void 0 : _keyboardResize7.w).toBe(180); }); it("jumps to the minimum on Home and the maximum on End", () => { expect(keyboardResize("Home", size, policy, contentMin)).toEqual({ w: 180, h: 120 }); expect(keyboardResize("End", size, policy, contentMin)).toEqual({ w: 420, h: 400 }); }); it("leaves an axis with no maximum where it is on End", () => { const unbounded = { minW: 180, maxW: 420, proportional: false }; expect(keyboardResize("End", size, unbounded, { w: 180, h: 0 })).toEqual({ w: 420, h: 200 }); }); it("leaves an axis with no minimum where it is on Home", () => { // Nobody asked for a floor on either axis, so Home has nothing to jump to — // it must not collapse the card to zero. expect(keyboardResize("Home", size, FREE, { w: 0, h: 0 })).toEqual(size); }); it("ignores the vertical keys on a width-only card", () => { expect(keyboardResize("ArrowUp", size, policy, contentMin, { axis: "width" })).toBeNull(); expect(keyboardResize("ArrowDown", size, policy, contentMin, { axis: "width" })).toBeNull(); }); it("holds the height on a width-only Home and End", () => { expect(keyboardResize("Home", size, policy, contentMin, { axis: "width" })).toEqual({ w: 180, h: 200 }); expect(keyboardResize("End", size, policy, contentMin, { axis: "width" })).toEqual({ w: 420, h: 200 }); }); it("returns null for a key it does not own", () => { expect(keyboardResize("Enter", size, policy, contentMin)).toBeNull(); expect(keyboardResize("a", size, policy, contentMin)).toBeNull(); }); }); describe("isResizeKey", () => { it("owns the arrows plus Home and End, and nothing else", () => { RESIZE_KEYS.forEach(key => expect(isResizeKey(key)).toBe(true)); expect(isResizeKey("Enter")).toBe(false); expect(isResizeKey("Tab")).toBe(false); }); }); describe("measurePercent", () => { const policy = { minW: 200, maxW: 400, proportional: false }; const contentMin = { w: 0, h: 0 }; it("reports the width axis as a percent of its allowed range", () => { expect(measurePercent({ w: 200, h: 0 }, policy, contentMin)).toBe(0); expect(measurePercent({ w: 300, h: 0 }, policy, contentMin)).toBe(50); expect(measurePercent({ w: 400, h: 0 }, policy, contentMin)).toBe(100); }); it("stays inside 0–100 for a size outside the bounds", () => { expect(measurePercent({ w: 40, h: 0 }, policy, contentMin)).toBe(0); expect(measurePercent({ w: 900, h: 0 }, policy, contentMin)).toBe(100); }); it("reports 0 when the range is not finite", () => { expect(measurePercent({ w: 300, h: 0 }, FREE, contentMin)).toBe(0); }); }); //# sourceMappingURL=resizePolicy.test.js.map