@activecollab/components
Version:
ActiveCollab Components
470 lines (465 loc) • 12.8 kB
JavaScript
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