Files
dartamp/test/dock_logic_test.dart
T
2026-08-16 17:29:00 +02:00

398 lines
13 KiB
Dart

import "dart:ui";
import "package:dartamp/src/docking/dock_logic.dart";
import "package:flutter_test/flutter_test.dart";
void main() {
const snap = DockLogic.defaultSnapLen;
group("windowAttached", () {
test("returns 1 for a vertical seam with vertical overlap", () {
const a = Rect.fromLTWH(0, 0, 275, 116);
const b = Rect.fromLTWH(275, 10, 275, 116);
expect(DockLogic.windowAttached(a, b), 1);
});
test("returns 2 for a horizontal seam with horizontal overlap", () {
const a = Rect.fromLTWH(0, 0, 275, 116);
const b = Rect.fromLTWH(50, 116, 280, 290);
expect(DockLogic.windowAttached(a, b), 2);
});
test("returns 3 when both seams meet", () {
const a = Rect.fromLTWH(0, 0, 275, 116);
const b = Rect.fromLTWH(275, 116, 275, 116);
expect(DockLogic.windowAttached(a, b), 3);
});
test("returns 0 for disjoint windows", () {
const a = Rect.fromLTWH(0, 0, 275, 116);
const b = Rect.fromLTWH(500, 500, 275, 116);
expect(DockLogic.windowAttached(a, b), 0);
});
});
group("snapWindowBelow", () {
const dest = Rect.fromLTWH(0, 0, 275, 116);
test("docks src just below dest when its top is within snap range", () {
final src = Rect.fromLTWH(50, dest.bottom + snap - 3, 275, 116);
final out = DockLogic.snapWindowBelow(src, dest);
expect(out.top, dest.bottom);
expect(out.left, src.left);
});
test("leaves src unchanged outside snap range", () {
final src = Rect.fromLTWH(50, dest.bottom + 40, 275, 116);
expect(DockLogic.snapWindowBelow(src, dest), src);
});
test("does not dock src below dest without horizontal overlap", () {
final src = Rect.fromLTWH(400, dest.bottom + 2, 275, 116);
expect(DockLogic.snapWindowBelow(src, dest), src);
});
test("does not dock src above dest (docking is downward-only)", () {
final src = Rect.fromLTWH(50, dest.top - 3, 275, 116);
expect(DockLogic.snapWindowBelow(src, dest), src);
});
test("does not dock src beside dest (docking is vertical-only)", () {
final src = Rect.fromLTWH(dest.right - 3, 20, 275, 116);
expect(DockLogic.snapWindowBelow(src, dest), src);
});
});
group("snapToScreen", () {
const screen = Rect.fromLTWH(0, 0, 1440, 900);
test("snaps to the left and top edges", () {
const src = Rect.fromLTWH(-5, -5, 275, 116);
final out = DockLogic.snapToScreen(src, screen);
expect(out.left, 0);
expect(out.top, 0);
});
test("snaps to the right and bottom edges", () {
const src = Rect.fromLTWH(1440 - 275 + 5, 900 - 116 + 5, 275, 116);
final out = DockLogic.snapToScreen(src, screen);
expect(out.right, 1440);
expect(out.bottom, 900);
});
test("leaves rect unchanged away from the edges", () {
const src = Rect.fromLTWH(400, 300, 275, 116);
expect(DockLogic.snapToScreen(src, screen), src);
});
});
group("clampVisible", () {
const screen = Rect.fromLTWH(0, 0, 1440, 900);
const minVisible = DockLogic.defaultMinVisible;
test(
"keeps at least minVisible on-screen when dragged far off the left",
() {
const src = Rect.fromLTWH(-500, 300, 275, 116);
final out = DockLogic.clampVisible(src, screen);
expect(out.left, screen.left + minVisible - src.width);
expect(out.top, src.top);
},
);
test(
"keeps at least minVisible on-screen when dragged far off the top",
() {
const src = Rect.fromLTWH(300, -500, 275, 116);
final out = DockLogic.clampVisible(src, screen);
expect(out.left, src.left);
expect(out.top, screen.top + minVisible - src.height);
},
);
test(
"keeps at least minVisible on-screen when dragged far off the right",
() {
const src = Rect.fromLTWH(2000, 300, 275, 116);
final out = DockLogic.clampVisible(src, screen);
expect(out.left, screen.right - minVisible);
expect(out.top, src.top);
},
);
test(
"keeps at least minVisible on-screen when dragged far off the bottom",
() {
const src = Rect.fromLTWH(300, 2000, 275, 116);
final out = DockLogic.clampVisible(src, screen);
expect(out.left, src.left);
expect(out.top, screen.bottom - minVisible);
},
);
test("leaves a fully on-screen rect unchanged", () {
const src = Rect.fromLTWH(400, 300, 275, 116);
expect(DockLogic.clampVisible(src, screen), src);
});
test("returns the rect unchanged for an empty screen", () {
const src = Rect.fromLTWH(-500, 300, 275, 116);
expect(DockLogic.clampVisible(src, Rect.zero), src);
});
test(
"a slightly off-screen rect stays where the window still has minVisible "
"on-screen",
() {
const src = Rect.fromLTWH(-20, 100, 275, 116);
expect(DockLogic.clampVisible(src, screen), src);
},
);
});
group("moveBy and pointInRect", () {
test("moveBy shifts the top-left", () {
final out = DockLogic.moveBy(const Rect.fromLTWH(10, 20, 5, 6), 3, -2);
expect(out.left, 13);
expect(out.top, 18);
expect(out.width, 5);
expect(out.height, 6);
});
test("pointInRect respects the half-open bounds", () {
const r = Rect.fromLTWH(0, 0, 10, 10);
expect(DockLogic.pointInRect(const Offset(5, 5), r), isTrue);
expect(DockLogic.pointInRect(const Offset(10, 5), r), isFalse);
expect(DockLogic.pointInRect(const Offset(5, 10), r), isFalse);
});
});
group("dragCandidate", () {
test("moves the window by the pointer's window-local delta", () {
const grab = Offset(50, 30);
const applied = Rect.fromLTWH(100, 100, 275, 116);
final out = DockLogic.dragCandidate(
applied,
const Offset(150, 130),
grab,
);
expect(out.topLeft, const Offset(200, 200));
expect(out.width, applied.width);
expect(out.height, applied.height);
});
test("tracks the cursor absolutely regardless of the applied base", () {
const grab = Offset(50, 30);
// Pointer at screen (450, 330): window-local (50, 30) when the window
// really is at (400, 300). The base must cancel so a stale (lagging)
// applied position never accumulates error.
const lagging = Rect.fromLTWH(400, 300, 275, 116);
final kept = DockLogic.dragCandidate(lagging, const Offset(50, 30), grab);
expect(kept.topLeft, const Offset(400, 300));
// Cursor moves to screen (500, 400) -> window-local (100, 100).
final moved = DockLogic.dragCandidate(
lagging,
const Offset(100, 100),
grab,
);
expect(moved.topLeft, const Offset(450, 370));
});
});
group("fixOverlaps", () {
test("pushes r2 right when the horizontal gap is smaller", () {
const r1 = Rect.fromLTWH(0, 0, 50, 200);
const r2 = Rect.fromLTWH(40, 10, 50, 50);
final (_, out) = DockLogic.fixOverlaps(r1, r2);
expect(out.left, r1.right);
expect(out.top, r2.top);
});
test("pushes r2 down when the vertical gap is smaller", () {
const r1 = Rect.fromLTWH(0, 0, 200, 50);
const r2 = Rect.fromLTWH(10, 40, 50, 50);
final (_, out) = DockLogic.fixOverlaps(r1, r2);
expect(out.left, r2.left);
expect(out.top, r1.bottom);
});
test("leaves disjoint rects alone", () {
const r1 = Rect.fromLTWH(0, 0, 50, 50);
const r2 = Rect.fromLTWH(100, 100, 50, 50);
final out = DockLogic.fixOverlaps(r1, r2);
expect(out.$1, r1);
expect(out.$2, r2);
});
});
group("adjustSnap", () {
test("re-pins the left window's right edge to the right window's left", () {
const old1 = Rect.fromLTWH(0, 0, 275, 116);
const old2 = Rect.fromLTWH(275, 0, 275, 116);
const new1 = Rect.fromLTWH(0, 0, 330, 116);
const new2 = Rect.fromLTWH(275, 0, 275, 116);
final out = DockLogic.adjustSnap(old1, old2, new1, new2);
expect(out.$1, const Rect.fromLTWH(-55, 0, 330, 116));
expect(out.$2, new2);
});
});
group("dockedBelow", () {
test("includes only the dragged window when nothing is docked", () {
final rects = {
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
DockWindow.eq: const Rect.fromLTWH(500, 500, 275, 116),
};
final attached = DockLogic.dockedBelow(
rects: rects,
dragged: DockWindow.main,
);
expect(attached, {DockWindow.main});
});
test("walks the transitive closure of a vertical column", () {
final rects = {
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
DockWindow.eq: const Rect.fromLTWH(0, 116, 275, 116),
DockWindow.pl: const Rect.fromLTWH(0, 232, 280, 290),
};
final attached = DockLogic.dockedBelow(
rects: rects,
dragged: DockWindow.main,
);
expect(attached, {DockWindow.main, DockWindow.eq, DockWindow.pl});
});
test("excludes a window docked only beside the dragged window", () {
final rects = {
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
DockWindow.eq: const Rect.fromLTWH(275, 0, 275, 116),
};
final attached = DockLogic.dockedBelow(
rects: rects,
dragged: DockWindow.main,
);
expect(attached, {DockWindow.main});
});
test("excludes a window merely overlapping the dragged window", () {
final rects = {
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
DockWindow.eq: const Rect.fromLTWH(0, 100, 275, 116),
};
final attached = DockLogic.dockedBelow(
rects: rects,
dragged: DockWindow.main,
);
expect(attached, {DockWindow.main});
});
test("does not chain through a horizontal seam", () {
final rects = {
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
DockWindow.eq: const Rect.fromLTWH(275, 0, 275, 116),
DockWindow.pl: const Rect.fromLTWH(275, 116, 280, 290),
};
final attached = DockLogic.dockedBelow(
rects: rects,
dragged: DockWindow.main,
);
expect(attached, {DockWindow.main});
});
});
group("defaultSubRect", () {
const main = Rect.fromLTWH(0, 0, 275, 116);
const eqSize = Size(275, 116);
const plSize = Size(280, 290);
test("opens below main when alone (never beside it)", () {
expect(
DockLogic.defaultSubRect(
DockWindow.eq,
main,
eqSize,
const {},
),
const Rect.fromLTWH(0, 116, 275, 116),
);
expect(
DockLogic.defaultSubRect(
DockWindow.pl,
main,
plSize,
const {},
),
const Rect.fromLTWH(0, 116, 280, 290),
);
});
test("stacks pl below an eq dragged into a vertical column", () {
const eqBelow = Rect.fromLTWH(0, 116, 275, 116);
expect(
DockLogic.defaultSubRect(DockWindow.pl, main, plSize, {
DockWindow.eq: eqBelow,
}),
// below eq (bottom = 232), not on top of it
const Rect.fromLTWH(0, 232, 280, 290),
);
});
test("does not stack pl onto an eq sitting to the right of main", () {
const eqRight = Rect.fromLTWH(275, 0, 275, 116);
expect(
DockLogic.defaultSubRect(DockWindow.pl, main, plSize, {
DockWindow.eq: eqRight,
}),
const Rect.fromLTWH(0, 116, 280, 290),
);
});
});
group("repinResized", () {
test("moves a window docked below when the edge grows", () {
final oldRects = {
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
DockWindow.pl: const Rect.fromLTWH(0, 116, 280, 290),
};
const newRect = Rect.fromLTWH(0, 0, 275, 139.2);
final out = DockLogic.repinResized(
DockWindow.main,
newRect,
oldRects,
);
expect(out[DockWindow.pl]!.top, 139.2);
});
test("recursively re-pins a chain docked below", () {
final oldRects = {
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
DockWindow.pl: const Rect.fromLTWH(0, 116, 280, 290),
DockWindow.eq: const Rect.fromLTWH(0, 406, 275, 116),
};
const newRect = Rect.fromLTWH(0, 0, 275, 139.2);
final out = DockLogic.repinResized(
DockWindow.main,
newRect,
oldRects,
);
expect(out[DockWindow.pl]!.top, 139.2);
expect(out[DockWindow.eq]!.top, 429.2);
});
test("does not move a window beside the changed window", () {
final oldRects = {
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
DockWindow.eq: const Rect.fromLTWH(275, 0, 275, 116),
};
const newRect = Rect.fromLTWH(0, 0, 330, 116);
final out = DockLogic.repinResized(
DockWindow.main,
newRect,
oldRects,
);
expect(out[DockWindow.eq]!.left, 275);
});
});
}